-
-
Save saagarjha/a70d44951cb72f82efee3317d80ac07f to your computer and use it in GitHub Desktop.
// To compile: clang++ -arch x86_64 -arch arm64 -std=c++20 library_injector.cpp -lbsm -lEndpointSecurity -o library_injector, | |
// then codesign with com.apple.developer.endpoint-security.client and run the | |
// program as root. | |
#include <EndpointSecurity/EndpointSecurity.h> | |
#include <algorithm> | |
#include <array> | |
#include <bsm/libbsm.h> | |
#include <cstddef> | |
#include <cstdint> | |
#include <cstdlib> | |
#include <cstring> | |
#include <dispatch/dispatch.h> | |
#include <functional> | |
#include <iostream> | |
#include <mach-o/dyld.h> | |
#include <mach-o/dyld_images.h> | |
#include <mach-o/loader.h> | |
#include <mach-o/nlist.h> | |
#include <mach/mach.h> | |
#ifdef __arm64__ | |
#include <mach/arm/thread_state.h> | |
#elif __x86_64__ | |
#include <mach/i386/thread_state.h> | |
#else | |
#error "Only arm64 and x86_64 are currently supported" | |
#endif | |
#if __has_feature(ptrauth_calls) | |
#include <ptrauth.h> | |
#endif | |
#include <regex> | |
#include <span> | |
#include <stdexcept> | |
#include <string> | |
#include <sys/ptrace.h> | |
#include <sys/sysctl.h> | |
#include <unistd.h> | |
#include <vector> | |
#define ensure(condition) \ | |
do { \ | |
if (!(condition)) { \ | |
throw std::runtime_error(std::string("") + "Check \"" + #condition "\" failed at " + \ | |
__FILE__ + ":" + std::to_string(__LINE__) + " in function " + __FUNCTION__); \ | |
} \ | |
} while (0) | |
#define CS_OPS_STATUS 0 | |
#define CS_ENFORCEMENT 0x00001000 | |
extern "C" { | |
int csops(pid_t pid, unsigned int ops, void *useraddr, size_t usersize); | |
}; | |
auto is_translated(pid_t pid) { | |
auto name = std::array{CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; | |
kinfo_proc proc; | |
size_t size = sizeof(proc); | |
ensure(!sysctl(name.data(), name.size(), &proc, &size, nullptr, 0) && size == sizeof(proc)); | |
return !!(proc.kp_proc.p_flag & P_TRANSLATED); | |
} | |
auto is_cs_enforced(pid_t pid) { | |
int flags; | |
ensure(!csops(pid, CS_OPS_STATUS, &flags, sizeof(flags))); | |
return !!(flags & CS_ENFORCEMENT); | |
} | |
template <typename T> | |
T scan(task_port_t task, std::uintptr_t &address) { | |
T t; | |
vm_size_t count; | |
ensure(vm_read_overwrite(task, address, sizeof(t), reinterpret_cast<pointer_t>(&t), &count) == KERN_SUCCESS && count == sizeof(t)); | |
address += sizeof(t); | |
return t; | |
} | |
std::vector<std::uintptr_t> read_string_array(task_port_t task, std::uintptr_t &base) { | |
auto strings = std::vector<std::uintptr_t>{}; | |
std::uintptr_t string; | |
do { | |
string = scan<std::uintptr_t>(task, base); | |
strings.push_back(string); | |
} while (string); | |
strings.pop_back(); | |
return strings; | |
} | |
std::string read_string(task_port_t task, std::uintptr_t address) { | |
auto string = std::string{}; | |
char c; | |
do { | |
c = scan<char>(task, address); | |
string.push_back(c); | |
} while (c); | |
string.pop_back(); | |
return string; | |
} | |
std::uintptr_t rearrange_stack(task_port_t task, const std::string &library, std::uintptr_t sp) { | |
auto loadAddress = scan<std::uintptr_t>(task, sp); | |
auto argc = scan<std::uintptr_t>(task, sp); | |
auto argvAddresses = read_string_array(task, sp); | |
auto envpAddresses = read_string_array(task, sp); | |
auto appleAddresses = read_string_array(task, sp); | |
auto stringReader = std::bind(read_string, task, std::placeholders::_1); | |
auto argv = std::vector<std::string>{}; | |
std::transform(argvAddresses.begin(), argvAddresses.end(), std::back_inserter(argv), stringReader); | |
auto envp = std::vector<std::string>{}; | |
std::transform(envpAddresses.begin(), envpAddresses.end(), std::back_inserter(envp), stringReader); | |
auto apple = std::vector<std::string>{}; | |
std::transform(appleAddresses.begin(), appleAddresses.end(), std::back_inserter(apple), stringReader); | |
auto dyld_insert_libraries = std::find_if(envp.begin(), envp.end(), [](const auto &string) { | |
return string.starts_with("DYLD_INSERT_LIBRARIES="); | |
}); | |
if (dyld_insert_libraries != envp.end()) { | |
*dyld_insert_libraries += ":" + library; | |
} else { | |
auto variable = "DYLD_INSERT_LIBRARIES=" + library; | |
envp.push_back(variable); | |
} | |
argvAddresses.clear(); | |
envpAddresses.clear(); | |
appleAddresses.clear(); | |
auto strings = std::vector<char>{}; | |
auto arrayGenerator = [&strings](auto &addresses, const auto &string) { | |
addresses.push_back(strings.size()); | |
std::copy(string.begin(), string.end(), std::back_inserter(strings)); | |
strings.push_back('\0'); | |
}; | |
std::for_each(argv.begin(), argv.end(), std::bind(arrayGenerator, std::ref(argvAddresses), std::placeholders::_1)); | |
std::for_each(envp.begin(), envp.end(), std::bind(arrayGenerator, std::ref(envpAddresses), std::placeholders::_1)); | |
std::for_each(apple.begin(), apple.end(), std::bind(arrayGenerator, std::ref(appleAddresses), std::placeholders::_1)); | |
sp -= strings.size(); | |
sp = sp / sizeof(std::uintptr_t) * sizeof(std::uintptr_t); | |
ensure(vm_write(task, sp, reinterpret_cast<vm_offset_t>(strings.data()), strings.size()) == KERN_SUCCESS); | |
auto rebaser = [sp](auto &&address) { | |
address += sp; | |
}; | |
std::for_each(argvAddresses.begin(), argvAddresses.end(), rebaser); | |
std::for_each(envpAddresses.begin(), envpAddresses.end(), rebaser); | |
std::for_each(appleAddresses.begin(), appleAddresses.end(), rebaser); | |
auto addresses = std::vector<std::uintptr_t>{}; | |
std::copy(argvAddresses.begin(), argvAddresses.end(), std::back_inserter(addresses)); | |
addresses.push_back(0); | |
std::copy(envpAddresses.begin(), envpAddresses.end(), std::back_inserter(addresses)); | |
addresses.push_back(0); | |
std::copy(appleAddresses.begin(), appleAddresses.end(), std::back_inserter(addresses)); | |
addresses.push_back(0); | |
sp -= addresses.size() * sizeof(std::uintptr_t); | |
ensure(vm_write(task, sp, reinterpret_cast<vm_offset_t>(addresses.data()), addresses.size() * sizeof(std::uintptr_t)) == KERN_SUCCESS); | |
sp -= sizeof(std::uintptr_t); | |
ensure(vm_write(task, sp, reinterpret_cast<vm_offset_t>(&argc), sizeof(std::uintptr_t)) == KERN_SUCCESS); | |
sp -= sizeof(std::uintptr_t); | |
ensure(vm_write(task, sp, reinterpret_cast<vm_offset_t>(&loadAddress), sizeof(std::uintptr_t)) == KERN_SUCCESS); | |
return sp; | |
} | |
__asm__( | |
".globl _amfi_flags_patch_start\n" | |
".globl _amfi_flags_patch_end\n" | |
"_amfi_flags_patch_start:\n" | |
#if __arm64__ | |
"\tmov x2, #0x5f\n" | |
"\tstr x2, [x1]\n" | |
"\tmov x0, #0\n" | |
"\tret\n" | |
#elif __x86_64__ | |
".intel_syntax noprefix\n" | |
"\tmov QWORD PTR [rsi], 0x5f\n" | |
"\txor rax, rax\n" | |
"\tret\n" | |
#endif | |
"_amfi_flags_patch_end:\n"); | |
extern char amfi_flags_patch_start; | |
extern char amfi_flags_patch_end; | |
#if __arm64__ | |
// This is a clever but incredibly lazy patch. On arm64, the first five | |
// instructions of _dyld_start are as follows: | |
// | |
// mov x0, sp | |
// and sp, x0, #~15 | |
// mov fp, #0 | |
// mov lr, #0 | |
// b start | |
// | |
// We need to bump sp down a bit due to injecting DYLD_INSERT_LIBRARIES, but | |
// because of thread_set_state_allowed we can't set it directly. So we inject | |
// instructions to do it in here. At process startup fp and lr happen to be set | |
// to 0 by the kernel already, which gives us the space to sneak in two extra | |
// instructions. (If we wanted to be slightly less lazy, we could take advantage | |
// of the kernel's laziness and align sp ourselves when writing the initial | |
// stack. This would let us overwrite the instruction aligning sp.) | |
__asm__( | |
".globl _dyld_start_patch_start\n" | |
".globl _dyld_start_patch_end\n" | |
".globl _dyld_start_check_start\n" | |
".globl _dyld_start_check_end\n" | |
"\n" | |
"_dyld_start_patch_start:\n" | |
"_dyld_start_check_start:\n" | |
/* sub sp, sp, [offset & 0xfff] */ // Added dynamically | |
/* sub sp, sp, [offset & ~0xfff], lsl 12 */ // Added dynamically | |
"mov x0, sp\n" | |
"and sp, x0, #~15\n" | |
"_dyld_start_patch_end:\n" | |
// Used as a sanity check | |
"mov fp, #0\n" | |
"mov lr, #0\n" | |
"_dyld_start_check_end:\n"); | |
#elif __x86_64__ | |
// A similar patch for x86_64. The initial sequence is this: | |
// | |
// mov rdi, rsp | |
// and rsp, -16 | |
// mov rbp, 0 | |
// push 0 | |
// jmp start | |
// | |
// We can golf it down with code that is equivalent (save for xor ebp, ebp, | |
// which sets flags-but in this case it doesn't adjust them from what the | |
// kernel sets already, and there isn't any code that relies on its value | |
// anyway). | |
__asm__( | |
".intel_syntax noprefix\n" | |
".globl _dyld_start_patch_start\n" | |
".globl _dyld_start_patch_end\n" | |
".globl _dyld_start_check_start\n" | |
".globl _dyld_start_check_end\n" | |
"\n" | |
"_dyld_start_patch_start:\n" | |
/* sub rsp, [offset] */ // Added dynamically | |
"push rsp\n" | |
"pop rdi\n" | |
"and rsp, -16\n" | |
"xor ebp, ebp\n" | |
"push rbp\n" | |
"_dyld_start_patch_end:\n" | |
"_dyld_start_check_start:\n" | |
"mov rdi, rsp\n" | |
"and rsp, -16\n" | |
"mov rbp, 0\n" | |
"push 0\n" | |
"_dyld_start_check_end:\n"); | |
#endif | |
extern char dyld_start_patch_start; | |
extern char dyld_start_patch_end; | |
extern char dyld_start_check_start; | |
extern char dyld_start_check_end; | |
void write_patch(task_t task, std::uintptr_t address, void *patch_start, void *patch_end) { | |
ensure(vm_protect(task, address / PAGE_SIZE * PAGE_SIZE, PAGE_SIZE, false, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY) == KERN_SUCCESS); | |
ensure(vm_write(task, address, reinterpret_cast<vm_offset_t>(patch_start), reinterpret_cast<std::uintptr_t>(patch_end) - reinterpret_cast<std::uintptr_t>(patch_start)) == KERN_SUCCESS); | |
ensure(vm_protect(task, address / PAGE_SIZE * PAGE_SIZE, PAGE_SIZE, false, VM_PROT_READ | VM_PROT_EXECUTE) == KERN_SUCCESS); | |
} | |
void patch_restrictions(task_t task, std::uintptr_t pc) { | |
task_dyld_info_data_t dyldInfo; | |
mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; | |
ensure(task_info(mach_task_self(), TASK_DYLD_INFO, reinterpret_cast<task_info_t>(&dyldInfo), &count) == KERN_SUCCESS); | |
auto all_image_infos = reinterpret_cast<dyld_all_image_infos *>(dyldInfo.all_image_info_addr); | |
const auto header = reinterpret_cast<const mach_header_64 *>(all_image_infos->dyldImageLoadAddress); | |
auto location = reinterpret_cast<std::uintptr_t>(header + 1); | |
auto base = reinterpret_cast<std::uintptr_t>(header); | |
for (unsigned i = 0; i < header->ncmds; ++i) { | |
auto command = reinterpret_cast<load_command *>(location); | |
if (command->cmd == LC_SYMTAB) { | |
auto command = reinterpret_cast<symtab_command *>(location); | |
auto symbols = std::span{reinterpret_cast<nlist_64 *>(base + command->symoff), command->nsyms}; | |
auto _dyld_start = std::find_if(symbols.begin(), symbols.end(), [base, command](const auto &symbol) { | |
return !std::strcmp(reinterpret_cast<char *>(base + command->stroff) + symbol.n_un.n_strx, "__dyld_start"); | |
}); | |
auto amfi_check_dyld_policy_self = std::find_if(symbols.begin(), symbols.end(), [base, command](const auto &symbol) { | |
return !std::strcmp(reinterpret_cast<char *>(base + command->stroff) + symbol.n_un.n_strx, "_amfi_check_dyld_policy_self"); | |
}); | |
write_patch(task, pc + amfi_check_dyld_policy_self->n_value - _dyld_start->n_value, &amfi_flags_patch_start, &amfi_flags_patch_end); | |
return; | |
} | |
location += command->cmdsize; | |
} | |
ensure(false); | |
} | |
void inject(pid_t pid, const std::string &library) { | |
task_port_t task; | |
ensure(task_for_pid(mach_task_self(), pid, &task) == KERN_SUCCESS); | |
thread_act_array_t threads; | |
mach_msg_type_number_t count; | |
ensure(task_threads(task, &threads, &count) == KERN_SUCCESS); | |
ensure(count == 1); | |
#if __arm64__ | |
arm_thread_state64_t state; | |
count = ARM_THREAD_STATE64_COUNT; | |
thread_state_flavor_t flavor = ARM_THREAD_STATE64; | |
#elif __x86_64__ | |
x86_thread_state64_t state; | |
count = x86_THREAD_STATE64_COUNT; | |
thread_state_flavor_t flavor = x86_THREAD_STATE64; | |
#endif | |
ensure(thread_get_state(*threads, flavor, reinterpret_cast<thread_state_t>(&state), &count) == KERN_SUCCESS); | |
#if __arm64__ | |
ensure(thread_convert_thread_state(*threads, THREAD_CONVERT_THREAD_STATE_TO_SELF, flavor, reinterpret_cast<thread_state_t>(&state), count, reinterpret_cast<thread_state_t>(&state), &count) == KERN_SUCCESS); | |
auto sp = rearrange_stack(task, library, arm_thread_state64_get_sp(state)); | |
patch_restrictions(task, arm_thread_state64_get_pc(state)); | |
if (__builtin_available(macOS 14.4, *)) { | |
} else { | |
arm_thread_state64_set_sp(state, sp); | |
ensure(thread_convert_thread_state(*threads, THREAD_CONVERT_THREAD_STATE_FROM_SELF, flavor, reinterpret_cast<thread_state_t>(&state), count, reinterpret_cast<thread_state_t>(&state), &count) == KERN_SUCCESS); | |
} | |
#elif __x86_64__ | |
auto sp = rearrange_stack(task, library, static_cast<std::uintptr_t>(state.__rsp)); | |
state.__rsp = sp; | |
patch_restrictions(task, state.__rip); | |
#endif | |
if (__builtin_available(macOS 14.4, *)) { | |
#if __arm64__ | |
auto address = arm_thread_state64_get_pc(state); | |
#elif __x86_64__ | |
auto address = state.__rip; | |
#endif | |
auto expected = std::span{&dyld_start_check_start, &dyld_start_check_end}; | |
auto actual = std::vector(expected.begin(), expected.end()); | |
vm_size_t count; | |
ensure(vm_read_overwrite(task, address, actual.size(), reinterpret_cast<pointer_t>(actual.data()), &count) == KERN_SUCCESS && count == expected.size()); | |
ensure(std::equal(expected.begin(), expected.end(), actual.begin(), actual.end())); | |
#if __arm64__ | |
auto difference = arm_thread_state64_get_sp(state) - sp; | |
auto stack_adjustment = std::array{ | |
// sub sp, sp, difference & 0xfff | |
std::byte{0xff}, | |
static_cast<std::byte>(0x03 | (difference & 0x3f) << 2), | |
static_cast<std::byte>(0x00 | (difference & 0xfc0) >> 6), | |
std::byte{0xd1}, | |
// sub sp, sp, difference & ~0xfff, lsl #12 | |
std::byte{0xff}, | |
static_cast<std::byte>(0x03 | ((difference >> 12) & 0x3f) << 2), | |
static_cast<std::byte>(0x40 | ((difference >> 12) & 0xfc0) >> 6), | |
std::byte{0xd1}, | |
}; | |
#elif __x86_64__ | |
auto difference = state.__rsp - sp; | |
auto stack_adjustment = std::array{ | |
// sub rsp, difference | |
std::byte{0x48}, | |
std::byte{0x81}, | |
std::byte{0xec}, | |
static_cast<std::byte>((difference >> 0) & 0xff), | |
static_cast<std::byte>((difference >> 8) & 0xff), | |
static_cast<std::byte>((difference >> 16) & 0xff), | |
static_cast<std::byte>((difference >> 24) & 0xff), | |
}; | |
#endif | |
write_patch(task, address, stack_adjustment.begin(), stack_adjustment.end()); | |
write_patch(task, address + stack_adjustment.size(), &dyld_start_patch_start, &dyld_start_patch_end); | |
} else { | |
ensure(thread_set_state(*threads, flavor, reinterpret_cast<thread_state_t>(&state), count) == KERN_SUCCESS); | |
} | |
mach_port_deallocate(mach_task_self(), *threads); | |
vm_deallocate(mach_task_self(), (vm_address_t)threads, sizeof(*threads)); | |
} | |
int main(int argc, char **argv, char **envp) { | |
if (!getenv("DYLD_IN_CACHE")) { | |
uint32_t length = 0; | |
std::string path; | |
_NSGetExecutablePath(path.data(), &length); | |
path = std::string('0', length); | |
ensure(!_NSGetExecutablePath(path.data(), &length)); | |
std::vector<const char *> environment; | |
while (*envp) { | |
environment.push_back(*envp++); | |
} | |
// This happens to disable dyld-in-cache. | |
environment.push_back("DYLD_IN_CACHE=0"); | |
environment.push_back(nullptr); | |
execve(path.c_str(), argv, const_cast<char **>(environment.data())); | |
ensure(false); | |
} | |
if (argc < 3) { | |
std::cerr << "Usage: " << *argv << " <library to inject> <process paths...>" << std::endl; | |
std::exit(EXIT_FAILURE); | |
} | |
auto library = *++argv; | |
std::vector<std::regex> processes; | |
for (auto process : std::span(++argv, argc - 2)) { | |
processes.push_back(std::regex(process)); | |
} | |
es_client_t *client = NULL; | |
ensure(es_new_client(&client, ^(es_client_t *client, const es_message_t *message) { | |
switch (message->event_type) { | |
case ES_EVENT_TYPE_AUTH_EXEC: { | |
const char *name = message->event.exec.target->executable->path.data; | |
for (const auto &process : processes) { | |
pid_t pid = audit_token_to_pid(message->process->audit_token); | |
if (std::regex_search(name, process) && is_translated(getpid()) == is_translated(pid)) { | |
if (is_cs_enforced(pid)) { | |
ensure(!ptrace(PT_ATTACHEXC, pid, nullptr, 0)); | |
// Work around FB9786809 | |
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1'000'000'000), dispatch_get_main_queue(), ^{ | |
ensure(!ptrace(PT_DETACH, pid, nullptr, 0)); | |
}); | |
} | |
inject(pid, library); | |
} | |
} | |
es_respond_auth_result(client, message, ES_AUTH_RESULT_ALLOW, false); | |
break; | |
} | |
default: | |
ensure(false && "Unexpected event type!"); | |
} | |
}) == ES_NEW_CLIENT_RESULT_SUCCESS); | |
es_event_type_t events[] = {ES_EVENT_TYPE_AUTH_EXEC}; | |
ensure(es_subscribe(client, events, sizeof(events) / sizeof(*events)) == ES_RETURN_SUCCESS); | |
dispatch_main(); | |
} |
Give it a try now
The updated code works seamlessly. I greatly appreciate your hard work!
By the way, I have two minor questions:
- Is it possible to replace set_thread_state on x86_64 platforms? The injector seems to fail on them as well.
- From what I understand, the method appears to be used only for injecting processes at startup. I sometimes use this tool to inject already running processes.
https://github.com/LIJI32/MIP
Is it possible to do that on macOS 14.4 and later?
Thanks a lot!
Oh, I didn't realize this affected x86_64 too. I guess I could support that too. For MIP I think @LIJI32 might be working on a patch for that
Hi @saagarjha, may I know if you have already found a solution for x86_64? Thanks!
Try the latest revision
Not sure if this is related to new changes because I have never used this on Sonoma before, but application I'm injecting into crashes for me.
Crash Report
------------------------------------- Translated Report (Full Report Below) -------------------------------------Incident Identifier: 38356649-7523-4D5F-8CCC-14A7DC8EB2AD
CrashReporter Key: FE938FEC-DD15-3302-DFEE-D430A6D7E851
Hardware Model: iMac19,1
Process: Preview [32397]
Path: /System/Applications/Preview.app/Contents/MacOS/Preview
Identifier: com.apple.Preview
Version: 11.0 (1056.5.1)
Code Type: X86-64 (Native)
Role: Default
Parent Process: launchd [1]
Coalition: com.apple.Preview [429]
Date/Time: 2024-07-16 12:25:18.2517 +0300
Launch Time: 2024-07-16 12:25:18.2366 +0300
OS Version: macOS 14.5 (23F79)
Release Type: User
Report Version: 104
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: UNKNOWN_0xD at 0x0000000000000000
Exception Codes: 0x000000000000000d, 0x0000000000000000
VM Region Info: 0 is not in any region. Bytes before following region: 4390211584
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
UNUSED SPACE AT START
--->
__TEXT 105ad5000-105cda000 [ 2068K] r-x/r-x SM=COW
Termination Reason: SIGNAL 11 Segmentation fault: 11
Terminating Process: exc handler [32397]
Triggered by Thread: 0
Thread 0 Crashed:
0 0x10cc4c78c dyld4::KernelArgs::findApple() const + 16
1 0x10cc49d5f start + 399
Thread 0 crashed with X86 Thread State (64-bit):
rax: 0xa3ab9b237d6aacf0 rbx: 0x000000010cc44000 rcx: 0x000000010cca934c rdx: 0x0000000000000000
rdi: 0x00007ff7ba42ab58 rsi: 0x0000000000000000 rbp: 0x00007ff7ba42a920 rsp: 0x00007ff7ba42a920
r8: 0x000000010cc44870 r9: 0x000000000004b6f0 r10: 0x000000000000000c r11: 0x0000000000000217
r12: 0x00007ff7ba42aa80 r13: 0x00007ff7ba42ab58 r14: 0x000000010cc44000 r15: 0x000000010ccdceb0
rip: 0x000000010cc4c78c rfl: 0x0000000000010296 cr2: 0x0000000000000000
Logical CPU: 3
Error Code: 0x00000000
Trap Number: 13
Binary Images:
0x10cc44000 - 0x10ccd4fff () ???
0x105ad5000 - 0x105cd9fff () <75f8d2f6-f20d-3463-b9e2-76dd0e9e3467> ???
0x0 - 0xffffffffffffffff ??? (*) <00000000-0000-0000-0000-000000000000> ???
Error Formulating Crash Report:
dyld_process_snapshot_get_shared_cache failed
EOF
Full Report
{"app_name":"Preview","timestamp":"2024-07-16 12:25:18.00 +0300","app_version":"11.0","slice_uuid":"75f8d2f6-f20d-3463-b9e2-76dd0e9e3467","build_version":"1056.5.1","platform":0,"bundleID":"com.apple.Preview","share_with_app_devs":0,"is_first_party":1,"bug_type":"309","os_version":"macOS 14.5 (23F79)","roots_installed":0,"name":"Preview","incident_id":"38356649-7523-4D5F-8CCC-14A7DC8EB2AD"}
{
"uptime" : 45000,
"procRole" : "Default",
"version" : 2,
"userID" : 501,
"deployVersion" : 210,
"modelCode" : "iMac19,1",
"coalitionID" : 429,
"osVersion" : {
"train" : "macOS 14.5",
"build" : "23F79",
"releaseType" : "User"
},
"captureTime" : "2024-07-16 12:25:18.2517 +0300",
"codeSigningMonitor" : 0,
"incident" : "38356649-7523-4D5F-8CCC-14A7DC8EB2AD",
"pid" : 32397,
"cpuType" : "X86-64",
"roots_installed" : 0,
"bug_type" : "309",
"procLaunch" : "2024-07-16 12:25:18.2366 +0300",
"procStartAbsTime" : 45267766286222,
"procExitAbsTime" : 45267778641938,
"procName" : "Preview",
"procPath" : "/System/Applications/Preview.app/Contents/MacOS/Preview",
"bundleInfo" : {"CFBundleShortVersionString":"11.0","CFBundleVersion":"1056.5.1","CFBundleIdentifier":"com.apple.Preview"},
"buildInfo" : {"ProjectName":"Preview","SourceVersion":"1056005001000000","BuildVersion":"392"},
"storeInfo" : {"deviceIdentifierForVendor":"13B9DC22-B469-5B20-A46A-4C1D09222528"},
"parentProc" : "launchd",
"parentPid" : 1,
"coalitionName" : "com.apple.Preview",
"crashReporterKey" : "FE938FEC-DD15-3302-DFEE-D430A6D7E851",
"codeSigningID" : "com.apple.Preview",
"codeSigningTeamID" : "",
"codeSigningFlags" : 570509857,
"codeSigningValidationCategory" : 1,
"codeSigningTrustLevel" : 4294967295,
"wakeTime" : 1408,
"sleepWakeUUID" : "9423F30A-35ED-48CB-96EB-43AC660CEFEC",
"sip" : "disabled",
"vmRegionInfo" : "0 is not in any region. Bytes before following region: 4390211584\n REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL\n UNUSED SPACE AT START\n---> \n __TEXT 105ad5000-105cda000 [ 2068K] r-x/r-x SM=COW ",
"exception" : {"codes":"0x000000000000000d, 0x0000000000000000","rawCodes":[13,0],"type":"EXC_BAD_ACCESS","signal":"SIGSEGV","subtype":"UNKNOWN_0xD at 0x0000000000000000"},
"termination" : {"flags":0,"code":11,"namespace":"SIGNAL","indicator":"Segmentation fault: 11","byProc":"exc handler","byPid":32397},
"vmregioninfo" : "0 is not in any region. Bytes before following region: 4390211584\n REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL\n UNUSED SPACE AT START\n---> \n __TEXT 105ad5000-105cda000 [ 2068K] r-x/r-x SM=COW ",
"extMods" : {"caller":{"thread_create":0,"thread_set_state":0,"task_for_pid":0},"system":{"thread_create":0,"thread_set_state":1980,"task_for_pid":129},"targeted":{"thread_create":0,"thread_set_state":0,"task_for_pid":1},"warnings":0},
"faultingThread" : 0,
"threads" : [{"triggered":true,"id":804385,"instructionState":{"instructionStream":{"bytes":[117,7,72,141,13,11,0,0,0,255,209,88,89,195,102,15,31,68,0,0,72,61,0,16,0,0,72,141,76,36,24,114,23,72,129,233,0,16,0,0,132,9,72,45,0,16,0,0,72,61,0,16,0,0,119,233,72,41,193,132,9,195,85,72,137,229,72,139,71,8,72,141,4,199,72,131,192,24,93,195,85,72,137,229,72,139,71,8,72,141,4,199,72,131,192,24,72,131,56,0,72,141,64,8,117,246,93,195,85,72,137,229,65,87,65,86,65,85,65,84,83,72,131,236,40,72,137,77,192,73,137,214,72,137,251,76,141,103,8,76,137,231,232,197,9,0,0,76,141,171,168,0,0,0,76,137,239,76,137,230,76,137,242,232,128,18,0,0,76,141,187,184,0,0,0,76,137,255,76,137,125,208,76,137,230,76,137,234,76,137,241,232],"offset":96}},"threadState":{"r13":{"value":140701958581080},"rax":{"value":11793690625930079472},"rflags":{"value":66198},"cpu":{"value":3},"r14":{"value":4509155328},"rsi":{"value":0},"r8":{"value":4509157488},"cr2":{"value":0},"rdx":{"value":0},"r10":{"value":12},"r9":{"value":308976},"r15":{"value":4509781680,"symbolLocation":0,"symbol":"_NSConcreteStackBlock"},"rbx":{"value":4509155328},"trap":{"value":13},"err":{"value":0},"r11":{"value":535},"rip":{"value":4509190028,"matchesCrashFrame":1},"rbp":{"value":140701958580512},"rsp":{"value":140701958580512},"r12":{"value":140701958580864},"rcx":{"value":4509569868,"symbolLocation":12,"symbol":"_thread_set_tsd_base"},"flavor":"x86_THREAD_STATE","rdi":{"value":140701958581080}},"frames":[{"imageOffset":34700,"symbol":"dyld4::KernelArgs::findApple() const","symbolLocation":16,"imageIndex":0},{"imageOffset":23903,"symbol":"start","symbolLocation":399,"imageIndex":0}]}],
"usedImages" : [
{
"source" : "P",
"arch" : "x86_64",
"base" : 4509155328,
"size" : 593920,
"uuid" : "baa6f02e-dff3-3562-8c99-ea2820c91ad9",
"name" : ""
},
{
"source" : "P",
"arch" : "x86_64",
"base" : 4390211584,
"size" : 2117632,
"uuid" : "75f8d2f6-f20d-3463-b9e2-76dd0e9e3467",
"name" : ""
},
{
"size" : 0,
"source" : "A",
"base" : 0,
"uuid" : "00000000-0000-0000-0000-000000000000"
}
],
"vmSummary" : "ReadOnly portion of Libraries: Total=3136K resident=0K(0%) swapped_out_or_unallocated=3136K(100%)\nWritable regions: Total=8268K written=0K(0%) resident=0K(0%) swapped_out=0K(0%) unallocated=8268K(100%)\n\n VIRTUAL REGION \nREGION TYPE SIZE COUNT (non-coalesced) \n=========== ======= ======= \nSTACK GUARD 56.0M 1 \nStack 8192K 1 \nVM_ALLOCATE 4K 1 \n__DATA 452K 4 \n__DATA_CONST 124K 2 \n__DATA_DIRTY 8K 2 \n__LINKEDIT 496K 3 \n__TEXT 2648K 6 \nmapped file 3.9G 28 \nshared memory 8K 2 \n=========== ======= ======= \nTOTAL 4.0G 50 \n",
"legacyInfo" : {
"threadTriggered" : {
}
},
"logWritingSignature" : "b12e1eed770bf83b08beed1cdd2fa37ce29d708f",
"trialInfo" : {
"rollouts" : [
{
"rolloutId" : "64c025b28b7f0e739e4fbe58",
"factorPackIds" : {
},
"deploymentId" : 240000019
},
{
"rolloutId" : "64c17a9925d75a7281053d4c",
"factorPackIds" : {
"SIRI_AUDIO_DISABLE_MEDIA_ENTITY_SYNC" : "64d29746ad29a465b3bbeace"
},
"deploymentId" : 240000002
}
],
"experiments" : [
]
},
"reportNotes" : [
"dyld_process_snapshot_get_shared_cache failed"
]
}
Commenting out following line prevents crash if that gives some hint on what could be wrong.
ensure(vm_write(task, sp, reinterpret_cast<vm_offset_t>(strings.data()), strings.size()) == KERN_SUCCESS);
I suspect library_injector is crashing (and thus the process it injects into is also crashing because it isn't done with its work). Do you see any crash logs for that?
The library_injector itself doesn't crash, just the application.
Installed 14.4.1, got the same crash, then replaced if (__builtin_available(macOS 14.4, *))
with if (__builtin_available(macOS 14.5, *))
and then injection works on 14.4.1.
Hmm, I'll take a look. I guess this rolled out in 14.5?
The old path works on 14.4.1 so it seems.
This is definitely a noob question, but when signing this binary I'm assuming that I need an .entitlements
file like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.endpoint-security.client</key>
<true/>
</dict>
</plist>
and then I would run something like this?
codesign --force --sign "Apple Development: Steven Hepting (XXXXXXXXXX)" --entitlements entitlements.plist library_injector
@leochou0729 have you tried the new patch on intel yet?
The XNU source for 14.5 is released. This is what thread_set_state_allowed function looks like:
Does this mean we cannot do code injection any more?