Skip to content

Commit

Permalink
Add Rosetta support, closes #25
Browse files Browse the repository at this point in the history
  • Loading branch information
LIJI32 committed Jan 17, 2024
1 parent 77a19d5 commit fee9433
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 18 deletions.
3 changes: 2 additions & 1 deletion MIP/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ endif
ifeq ($(shell uname -m),arm64)
ARCH1 := arm64e
ARCH2 := x86_64
CFLAGS += -DROSETTA
else
ARCH1 := x86_64
ARCH2 := i386
Expand Down Expand Up @@ -77,7 +78,7 @@ build/injector/payloads/%.bin: build/injector/payloads/%.dylib


install: all
if [ -d /usr/lib/mip ]; then \
if [ -d /usr/lib/mip -a ! -d /Library/Apple/System/Library/Frameworks/mip ]; then \
sudo rm /Users/*/Library/MIP ;\
sudo mkdir -p /Library/Apple/System/Library/Frameworks/ ;\
sudo mv /usr/lib/mip /Library/Apple/System/Library/Frameworks/ ;\
Expand Down
128 changes: 117 additions & 11 deletions MIP/injector/inject/inject.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,115 @@ typedef struct {
} x86_thread_state64_t;
#endif

typedef struct {
uint64_t unknown[8];
uint64_t rax;
uint64_t rcx;
uint64_t rdx;
uint64_t rbx;
uint64_t rsp;
uint64_t rbp;
uint64_t rsi;
uint64_t rdi;
uint64_t r8;
uint64_t r9;
uint64_t r10;
uint64_t r11;
uint64_t r12;
uint64_t r13;
uint64_t r14;
uint64_t r15;
uint64_t flags;
} rosetta_state_t;


kern_return_t thread_get_state_x86_64(mach_port_t thread, x86_thread_state64_t *state)
kern_return_t thread_get_state_x86_64(mach_port_t task, mach_port_t thread, x86_thread_state64_t *state)
{
#ifdef __x86_64__
mach_msg_type_number_t size = x86_THREAD_STATE64_COUNT;
return thread_get_state(thread, x86_THREAD_STATE64, (thread_state_t)state, &size);
#else
// Todo: Rosetta
return -1;
arm_thread_state64_t arm_state;
mach_msg_type_number_t size = ARM_THREAD_STATE64_COUNT;
kern_return_t ret = thread_get_state(thread, ARM_THREAD_STATE64, (thread_state_t) &arm_state, &size);
if (ret) return ret;

/* Verify a "safe" injection state: the highest bit of X18 is set if we're
outside of JIT code, then we know the registers stored in the Rosetta
State buffer are up to date. Also, X28 *should* have the value of RIP
during most instructions, so make sure it points to `retq`. */
if (!(arm_state.__x[18] & (1ULL << 63))) return -1;
uint8_t opcode = 0;
mach_vm_size_t read_size = sizeof(opcode);
ret = mach_vm_read_overwrite(task, arm_state.__x[28], read_size, (mach_vm_address_t) &opcode, &read_size);
if (ret) return ret;
if (opcode != 0xc3) return -2;

rosetta_state_t rosetta_state;
read_size = sizeof(rosetta_state);
ret = mach_vm_read_overwrite(task, (arm_state.__x[18] & ~(1ULL << 63)), read_size, (mach_vm_address_t) &rosetta_state, &read_size);

if (ret) return ret;

state->__rax = rosetta_state.rax;
state->__rcx = rosetta_state.rcx;
state->__rdx = rosetta_state.rdx;
state->__rbx = rosetta_state.rbx;
state->__rsp = rosetta_state.rsp;
state->__rbp = rosetta_state.rbp;
state->__rsi = rosetta_state.rsi;
state->__rdi = rosetta_state.rdi;
state->__r8 = rosetta_state.r8;
state->__r9 = rosetta_state.r9;
state->__r10 = rosetta_state.r10;
state->__r11 = rosetta_state.r11;
state->__r12 = rosetta_state.r12;
state->__r13 = rosetta_state.r13;
state->__r14 = rosetta_state.r14;
state->__r15 = rosetta_state.r15;
state->__rip = arm_state.__x[28];


// Todo: convert flags from ARM to Intel, find the segment registers

return 0;
#endif
}

kern_return_t thread_set_state_x86_64(mach_port_t thread, const x86_thread_state64_t *state)
kern_return_t thread_set_state_x86_64(mach_port_t task, mach_port_t thread, const x86_thread_state64_t *state)
{
#ifdef __x86_64__
return thread_set_state(thread, x86_THREAD_STATE64, (thread_state_t)state, x86_THREAD_STATE64_COUNT);
#else
// Todo: Rosetta
return -1;
arm_thread_state64_t arm_state;
mach_msg_type_number_t size = ARM_THREAD_STATE64_COUNT;
kern_return_t ret = thread_get_state(thread, ARM_THREAD_STATE64, (thread_state_t) &arm_state, &size);
if (ret) return ret;
if (!(arm_state.__x[18] & (1ULL << 63))) return -1;
rosetta_state_t rosetta_state;
mach_vm_size_t read_size = sizeof(rosetta_state);
ret = mach_vm_read_overwrite(task, (arm_state.__x[18] & ~(1ULL << 63)), read_size, (mach_vm_address_t) &rosetta_state, &read_size);

if (ret) return ret;

rosetta_state.rax = state->__rax;
rosetta_state.rcx = state->__rcx;
rosetta_state.rdx = state->__rdx;
rosetta_state.rbx = state->__rbx;
rosetta_state.rsp = state->__rsp;
rosetta_state.rbp = state->__rbp;
rosetta_state.rsi = state->__rsi;
rosetta_state.rdi = state->__rdi;
rosetta_state.r8 = state->__r8;
rosetta_state.r9 = state->__r9;
rosetta_state.r10 = state->__r10;
rosetta_state.r11 = state->__r11;
rosetta_state.r12 = state->__r12;
rosetta_state.r13 = state->__r13;
rosetta_state.r14 = state->__r14;
rosetta_state.r15 = state->__r15;

return mach_vm_write(task, (arm_state.__x[18] & ~(1ULL << 63)), (vm_offset_t)&rosetta_state, sizeof(rosetta_state));
#endif
}

Expand All @@ -64,15 +154,15 @@ kern_return_t inject_call_to_thread_x86_64(mach_port_t task, mach_port_t thread,
ret = thread_suspend(thread);
if (ret) goto exit;

ret = thread_get_state_x86_64(thread, &state);
ret = thread_get_state_x86_64(task, thread, &state);
if (ret) goto exit;

if (state.__rsp & 7) {
ret = KERN_INVALID_ADDRESS;
goto exit;
}

/* Todo: is mach_vm_write even correct in Rosetta? */
#ifdef __x86_64__
/* Push PC */
state.__rsp -= sizeof(state.__rip);
mach_vm_write(task, state.__rsp, (vm_offset_t)&state.__rip, sizeof(state.__rip));
Expand All @@ -86,7 +176,17 @@ kern_return_t inject_call_to_thread_x86_64(mach_port_t task, mach_port_t thread,

/* Update PC */
state.__rip = function;
ret = thread_set_state_x86_64(thread, &state);
#else
/* Push a NOP function as a return address for alignment, followed by our call */
state.__rsp -= sizeof(state.__rip) * 2;
uint64_t stack[2] = {
ret_addr,
function,
};
mach_vm_write(task, state.__rsp, (vm_offset_t)&stack, sizeof(stack));
#endif

ret = thread_set_state_x86_64(task, thread, &state);
if (ret) goto exit;

exit:
Expand Down Expand Up @@ -253,7 +353,14 @@ kern_return_t inject_stub_to_task(mach_port_t task, mach_vm_address_t *addr, mac
ret = mach_vm_write(task, *addr, (vm_offset_t) code, (mach_msg_type_number_t) code_size);
if (ret) return ret;

ret = vm_protect(task, *addr, code_size, FALSE, VM_PROT_READ | VM_PROT_EXECUTE);
vm_prot_t prot = VM_PROT_READ | VM_PROT_EXECUTE;
#ifndef __x86_64__
if (!is_arm) {
prot |= VM_PROT_WRITE;
}
#endif

ret = vm_protect(task, *addr, code_size, FALSE, prot);
if (ret) return ret;

*ret_addr = *addr + code_size - 1;
Expand Down Expand Up @@ -285,7 +392,6 @@ kern_return_t inject_to_task(mach_port_t task, const char *argument)
}

#ifndef __x86_64__
kern_return_t ret;
if (arm) {
ret = inject_call_to_thread_arm(task, thread, code_addr, ret_addr);
}
Expand Down
55 changes: 54 additions & 1 deletion MIP/injector/payloads/injected_x86_64.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,65 @@ static const void *get_symbol_from_header(const struct mach_header_64 *header, c
return NULL;
}

#ifdef ROSETTA
void __attribute__((naked)) late_inject(void)
{
__asm__ ("push %rsp\n"
"call _c_late_inject\n"
"ret");
}

/* In Rosetta, __TEXT segments are RWX, so we can put our data in __TEXT,
and to properly generate payloads, everything must be in one segment. */
static __attribute__((section("__TEXT,__data"))) uintptr_t *stack_ret = NULL;
static __attribute__((section("__TEXT,__data"))) uintptr_t ret_address = 0;

void __attribute__((preserve_all)) c_late_inject(void)
{
*stack_ret = ret_address;
typeof(dlopen) *$dlopen = NULL;
$dlopen = get_symbol_from_header(get_header_by_path("/usr/lib/system/libdyld.dylib"), "_dlopen");

if ($dlopen) {
$dlopen(argument, RTLD_NOW);
}
}
#endif

void __attribute__((preserve_all)) entry(void)
{
uint64_t flags = get_flags();

typeof(dlopen) *$dlopen = NULL;
#ifdef ROSETTA
/*
For some reason, calling `dlopen` from the usual `lsdinjector` context,
specifically while using the Rosetta runtime, some Mach port connection
gets screwed up, which ends up crashing the next time it is used. If we
detect this scenario, we inject the dlopen call to after we return to
_LSApplicationCheckIn, which is safe.
*/
uintptr_t _LSApplicationCheckIn = (uintptr_t) get_symbol_from_header(
get_header_by_path("/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/LaunchServices"),
"__LSApplicationCheckIn");

if (_LSApplicationCheckIn) {
uintptr_t *stack = __builtin_frame_address(4);
for (unsigned i = 0; i < 128; i++) {
/* TODO: This will work for lsdinjector, but this can probably be improved.
It might have false positives (and even crashes) on some manual `inject`
scenarios. */
if (stack[i] > _LSApplicationCheckIn && stack[i] < _LSApplicationCheckIn + 4096) {
ret_address = stack[i];
stack_ret = &stack[i];
stack[i] = (uintptr_t)&late_inject;
return;
}
}
}
#endif

typeof(dlopen) *$dlopen = NULL;

/* We can't call dyld`dlopen when dyld3 is being used, so we must find libdyld`dlopen and call that instead */
$dlopen = get_symbol_from_header(get_header_by_path("/usr/lib/system/libdyld.dylib"), "_dlopen");

Expand Down
7 changes: 2 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ MIP has the following advantages when comparing to other injection techniques:
* Does not modify any system file on disk and can be easily uninstalled without rebooting
* Does not use `DYLD_INSERT_LIBRARIES`, which may break the system if a file is deleted.
* Works with both 32- and 64-bit applications, and allows injection to Garbage Collected processes (On El Capitan and older, GC was removed in Sierra)
* Supports every macOS major up to and including Monterey
* Supports every macOS major up to and including Sonoma
* Supports ARM64-based Macs
* Supports injection to translated Rosetta 2 processes

## How To Compile
You will need Xcode's command-line tools, as well as binutils for `gobjcopy` (`brew install binutils`), which should be linked as `gobjcopy`. You will also need a signing identity, which may be self-signed. Not signing MIP binaries properly will make your system unstable! On Intel Macs, you will need the [10.13 SDK](https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX10.13.sdk.tar.xz), to compile the 32-bit portions of MIP.
Expand Down Expand Up @@ -78,7 +79,3 @@ The payload function is a compiled but unlinked C code, so it can't used any ext
## Upgrading Notes

If you were using an old version on MIP that used `/usr/lib/mip` as its data directory on macOS Mojave or older, upon upgrading to macOS Catalina or newer MIP bundles that linked against `/usr/lib/mip/loader.dylib` will cease functioning. They must be recompiled and linked against `/Library/Apple/System/Library/Frameworks/mip/loader.dylib` instead.

## Rosetta Support

MIP is currently unable to inject to Intel processes running through Rosetta. This will be addressed in a future version.

1 comment on commit fee9433

@AngeloD2022
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've always wondered how this could be achieved. Well done!

Please sign in to comment.