-
-
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(); | |
} |
With macOS Ventura (13.0, 22A5373b) on x86_64, library_injector always segfaults when patch_restriction
tries to access symbol
. See crash log.
SIP is off, DisableLibraryValidation
is on, and library_injector
is signed with the com.apple.developer.endpoint-security.client
entitlement. library_injector
is ran as sudo
. The same built product works fine on macOS Monterey (12.6, 21G115).
Do you have any idea what's going wrong? Thanks!
Yeah, I do, I just haven't gotten around to fixing it. On Ventura dyld unmaps itself from the address space and vends itself out of the shared cache. This means all the symbols are no longer available in a straightforward way (I mean they are technically still there, but you have to do some math on it that Apple has never bothered documenting and I haven't gotten around to figuring out). Because I am lazy I have been hardcoding the addresses of _dyld_start
and amfi_check_dyld_policy_self
and it works after you do that, but in the future I will probably read the symbols out of the binary on disk. If you want something a little more stable, but are similarly lazy, I can suggest just parsing the output of nm ;)
Yeah, I do, I just haven't gotten around to fixing it. On Ventura dyld unmaps itself from the address space and vends itself out of the shared cache. This means all the symbols are no longer available in a straightforward way (I mean they are technically still there, but you have to do some math on it that Apple has never bothered documenting and I haven't gotten around to figuring out). Because I am lazy I have been hardcoding the addresses of
_dyld_start
andamfi_check_dyld_policy_self
and it works after you do that, but in the future I will probably read the symbols out of the binary on disk. If you want something a little more stable, but are similarly lazy, I can suggest just parsing the output of nm ;)
Thanks for the pointers. Parsing nm
to get the address of _dyld_start
and amfi_check_dyld_policy_self
worked for me!
@cormiertyshawn895 I've updated the gist to disable dyld-in-cache for the process, which means the symbol table parsing should work now.
Hello @saagarjha, may I ask what does the patch_restrictions function try to do? With this patch, is it possible to inject into processes even when SIP is enabled ? Thanks!
It patches dyld to allow for interposing even when normally disabled. Because this edits code directly it requires SIP to be disabled.
Hello @saagarjha , why not used thread_suspend/thread_resume before/after thread_get_state/thread_set_state ? Tnx
The process should be stopped until we respond to Endpoint Security, so I don't see this as necessary.
Seems that thread_set_state cause this problems. I've tried to use thread_terminate and thread_create_running instead of thread_set_state, but that caused a kernel panic.
I did look into it briefly and it works on one of my machines and not another. I am not entirely sure why. Maybe it's because I have library validation turned off?
I find a new boot arg named tss_should_crash in the XNU source code. When I set "tss_should_crash=0" in the boot args, it doesn't crash any more on macOS 14.4. Unfortunately, this code is broken again on 14.5. I have little knowledge about reverse engineering and have no idea what changes in the new kernel because 14.5 kernel code is unavailable now. :(
These are what I've done:
- Disable SIP completely.
- Disable library validation with this command.
/usr/bin/defaults write /Library/Preferences/com.apple.security.libraryvalidation.plist DisableLibraryValidation -bool true - Set boot args with this command.
sudo nvram boot-args="-arm64e_preview_abi tss_should_crash=0"
With these settings, the injector works without any issue on both Intel and Apple Silicon Macs up utill macOS 14.4.
But on macOS 14.5, the code is no longer working. Maybe a new condition is checked in the set_thread_state_allowed function.
What are you trying to inject into?
I try to inject into TextEdit, Microsoft Word, Preview, Finder, .etc. All of them fails. Seems the new feature does not only affects Apple processes.
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?
You'll need to run the process as the same architecture as the thing you want to inject against. I should probably fix this at some point, but for now just run under Rosetta.