-
-
Save vocaeq/fbac63d5d36bc6e1d6d99df9c92f75dc to your computer and use it in GitHub Desktop.
#include <dlfcn.h> | |
#include <stdio.h> | |
#include <unistd.h> | |
#include <sys/types.h> | |
#include <mach/mach.h> | |
#include <mach/error.h> | |
#include <errno.h> | |
#include <stdlib.h> | |
#include <sys/sysctl.h> | |
#include <sys/mman.h> | |
#include <sys/stat.h> | |
#include <pthread.h> | |
#ifdef __arm64__ | |
//#include "mach/arm/thread_status.h" | |
// Apple says: mach/mach_vm.h:1:2: error: mach_vm.h unsupported | |
// And I say, bullshit. | |
kern_return_t mach_vm_allocate | |
( | |
vm_map_t target, | |
mach_vm_address_t *address, | |
mach_vm_size_t size, | |
int flags | |
); | |
kern_return_t mach_vm_write | |
( | |
vm_map_t target_task, | |
mach_vm_address_t address, | |
vm_offset_t data, | |
mach_msg_type_number_t dataCnt | |
); | |
#else | |
#include <mach/mach_vm.h> | |
#endif | |
#define STACK_SIZE 65536 | |
#define CODE_SIZE 128 | |
// | |
// Based on http://newosxbook.com/src.jl?tree=listings&file=inject.c | |
// Updated to work on Mojave by creating a stub mach thread that then | |
// creates a real pthread. Injected mach thread is terminated to clean | |
// up as well. | |
// | |
// Due to popular request: | |
// | |
// Simple injector example (and basis of coreruption tool). | |
// | |
// If you've looked into research on injection techniques in OS X, you | |
// probably know about mach_inject. This tool, part of Dino Dai Zovi's | |
// excellent "Mac Hacker's Handbook" (a must read - kudos, DDZ) was | |
// created to inject code in PPC and i386. Since I couldn't find anything | |
// for x86_64 or ARM, I ended up writing my own tool. | |
// Since, this tool has exploded in functionality - with many other features, | |
// including scriptable debugging, fault injection, function hooking, code | |
// decryption, and what not - which comes in *really* handy on iOS. | |
// | |
// coreruption is still closed source, due its highly.. uhm.. useful | |
// nature. But I'm making this sample free, and I have fully annotated this. | |
// The rest of the stuff you need is in Chapters 11 and 12 MOXiI 1, with more | |
// to come in the 2nd Ed (..in time for iOS 9 :-) | |
// | |
// Go forth and spread your code :-) | |
// | |
// J ([email protected]) 02/05/2014 | |
// | |
// v2: With ARM64 - 06/02/2015 NOTE - ONLY FOR **ARM64**, NOT ARM32! | |
// Get the full bundle at - http://NewOSXBook.com/files/injarm64.tar | |
// with sample dylib and with script to compile this neatly. | |
// | |
//********************************************************************** | |
// Note ARM code IS messy, and I left the addresses wide apart. That's | |
// intentional. Basic ARM64 assembly will enable you to tidy this up and | |
// make the code more compact. | |
// | |
// This is *not* meant to be neat - I'm just preparing this for TG's | |
// upcoming OS X/iOS RE course (http://technologeeks.com/OSXRE) and thought | |
// this would be interesting to share. See you all in MOXiI 2nd Ed! | |
//********************************************************************** | |
// This sample code calls pthread_set_self to promote the injected thread | |
// to a pthread first - otherwise dlopen and many other calls (which rely | |
// on pthread_self()) will crash. | |
// It then calls dlopen() to load the library specified - which will trigger | |
// the library's constructor (q.e.d as far as code injection is concerned) | |
// and sleep for a long time. You can of course replace the sleep with | |
// another function, such as pthread_exit(), etc. | |
// | |
// (For the constructor, use: | |
// | |
// static void whicheverfunc() __attribute__((constructor)); | |
// | |
// in the library you inject) | |
// | |
// Note that the functions are shown here as "_PTHRDSS", "DLOPEN__" and "SLEEP___". | |
// Reason being, that the above are merely placeholders which will be patched with | |
// the runtime addresses when code is actually injected. | |
char injectedCode[] = | |
#ifdef X86_64 | |
// "\xCC" // int3 | |
"\x55" // push rbp | |
"\x48\x89\xE5" // mov rbp, rsp | |
"\x48\x83\xEC\x10" // sub rsp, 0x10 | |
"\x48\x8D\x7D\xF8" // lea rdi, qword [rbp+var_8] | |
"\x31\xC0" // xor eax, eax | |
"\x89\xC1" // mov ecx, eax | |
"\x48\x8D\x15\x21\x00\x00\x00" // lea rdx, qword ptr [rip + 0x21] | |
"\x48\x89\xCE" // mov rsi, rcx | |
"\x48\xB8" // movabs rax, pthread_create_from_mach_thread | |
"PTHRDCRT" | |
"\xFF\xD0" // call rax | |
"\x89\x45\xF4" // mov dword [rbp+var_C], eax | |
"\x48\x83\xC4\x10" // add rsp, 0x10 | |
"\x5D" // pop rbp | |
"\x48\xc7\xc0\x13\x0d\x00\x00" // mov rax, 0xD13 | |
"\xEB\xFE" // jmp 0x0 | |
"\xC3" // ret | |
"\x55" // push rbp | |
"\x48\x89\xE5" // mov rbp, rsp | |
"\x48\x83\xEC\x10" // sub rsp, 0x10 | |
"\xBE\x01\x00\x00\x00" // mov esi, 0x1 | |
"\x48\x89\x7D\xF8" // mov qword [rbp+var_8], rdi | |
"\x48\x8D\x3D\x1D\x00\x00\x00" // lea rdi, qword ptr [rip + 0x2c] | |
"\x48\xB8" // movabs rax, dlopen | |
"DLOPEN__" | |
"\xFF\xD0" // call rax | |
"\x31\xF6" // xor esi, esi | |
"\x89\xF7" // mov edi, esi | |
"\x48\x89\x45\xF0" // mov qword [rbp+var_10], rax | |
"\x48\x89\xF8" // mov rax, rdi | |
"\x48\x83\xC4\x10" // add rsp, 0x10 | |
"\x5D" // pop rbp | |
"\xC3" // ret | |
"LIBLIBLIBLIB" | |
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" | |
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" | |
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" | |
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; | |
#else | |
//"\x20\x8e\x38\xd4" //brk #0xc471 | |
"\xe0\x03\x00\x91\x00\x40\x00\xd1\xe1\x03\x1f\xaa\xe3\x03\x1f\xaa\xc4\x00\x00\x10\x62\x01\x00\x10\x85\x00\x40\xf9\xa0\x00\x3f\xd6\x07\x00\x00\x10\xe0\x00\x1f\xd6\x50\x54\x48\x52\x44\x43\x52\x54\x44\x4c\x4f\x50\x45\x4e\x5f\x5f\x50\x54\x48\x52\x44\x45\x58\x54\x21\x00\x80\xd2\xc0\x00\x00\x10\x47\xff\xff\x10\xe8\x00\x40\xf9\x00\x01\x3f\xd6\x67\xfe\xff\x10\xe0\x00\x1f\xd6\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42"; | |
/* | |
Compile: as shellcode.asm -o shellcode.o && ld ./shellcode.o -o shellcode -lSystem -syslibroot `xcrun -sdk macosx --show-sdk-path` | |
.global _main | |
.align 4 | |
_main: | |
mov x0, sp | |
sub x0, x0, #16 | |
mov x1, xzr | |
mov x3, xzr | |
adr x4, pthrdcrt | |
adr x2, _thread | |
ldr x5, [x4] | |
blr x5 | |
_loop: | |
adr x7, _loop | |
br x7 | |
pthrdcrt: .ascii "PTHRDCRT" | |
dlllopen: .ascii "DLOPEN__" | |
pthrdext: .ascii "PTHRDEXT" | |
_thread: | |
mov x1, #1 | |
adr x0, lib | |
adr x7, dlllopen | |
ldr x8, [x7] | |
blr x8 | |
adr x7, _loop | |
br x7 | |
lib: .ascii "LIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIB" | |
*/ | |
#endif | |
int inject(pid_t pid, const char *lib) | |
{ | |
task_t remoteTask; | |
struct stat buf; | |
/** | |
* First, check we have the library. Otherwise, we won't be able to inject.. | |
*/ | |
int rc = stat(lib, &buf); | |
if (rc != 0) { | |
fprintf(stderr, "Unable to open library file %s (%s) - Cannot inject\n", lib, strerror(errno)); | |
//return (-9); | |
} | |
mach_error_t kr = 0; | |
/** | |
* Second - the critical part - we need task_for_pid in order to get the task port of the target | |
* pid. This is our do-or-die: If we get the port, we can do *ANYTHING* we want. If we don't, we're | |
* #$%#$%. | |
* | |
* In iOS, this will require the task_for_pid-allow entitlement. In OS X, this will require getting past | |
* taskgated, but root access suffices for that. | |
* | |
*/ | |
kr = task_for_pid(mach_task_self(), pid, &remoteTask); | |
if (kr != KERN_SUCCESS) { | |
fprintf(stderr, "Unable to call task_for_pid on pid %d: %s. Cannot continue!\n", pid, mach_error_string(kr)); | |
return (-1); | |
} | |
/** | |
* From here on, it's pretty much straightforward - | |
* Allocate stack and code. We don't really care *where* they get allocated. Just that they get allocated. | |
* So, first, stack: | |
*/ | |
mach_vm_address_t remoteStack64 = (vm_address_t)NULL; | |
mach_vm_address_t remoteCode64 = (vm_address_t)NULL; | |
kr = mach_vm_allocate(remoteTask, &remoteStack64, STACK_SIZE, VM_FLAGS_ANYWHERE); | |
if (kr != KERN_SUCCESS) { | |
fprintf(stderr, "Unable to allocate memory for remote stack in thread: Error %s\n", mach_error_string(kr)); | |
return (-2); | |
} | |
else { | |
fprintf(stderr, "Allocated remote stack @0x%llx\n", remoteStack64); | |
} | |
/** | |
* Then we allocate the memory for the thread | |
*/ | |
remoteCode64 = (vm_address_t)NULL; | |
kr = mach_vm_allocate(remoteTask, &remoteCode64, CODE_SIZE, VM_FLAGS_ANYWHERE); | |
if (kr != KERN_SUCCESS) { | |
fprintf(stderr, "Unable to allocate memory for remote code in thread: Error %s\n", mach_error_string(kr)); | |
return (-2); | |
} | |
/** | |
* Patch code before injecting: That is, insert correct function addresses (and lib name) into placeholders | |
* | |
* Since we use the same shared library cache as our victim, meaning we can use memory addresses from | |
* OUR address space when we inject.. | |
*/ | |
int i = 0; | |
char *possiblePatchLocation = (injectedCode); | |
for (i = 0; i < 0x100; i++) { | |
// Patching is crude, but works. | |
// | |
extern void *_pthread_set_self; | |
possiblePatchLocation++; | |
uint64_t addrOfPthreadCreate = (uint64_t)dlsym(RTLD_DEFAULT, "pthread_create_from_mach_thread"); | |
uint64_t addrOfPthreadExit = (uint64_t)dlsym(RTLD_DEFAULT, "pthread_exit"); | |
uint64_t addrOfDlopen = (uint64_t)dlopen; | |
if (memcmp(possiblePatchLocation, "PTHRDCRT", 8) == 0) { | |
printf("pthread_create_from_mach_thread @%llx\n", addrOfPthreadCreate); | |
memcpy(possiblePatchLocation, &addrOfPthreadCreate, 8); | |
} | |
if (memcmp(possiblePatchLocation, "PTHRDEXT", 8) == 0) { | |
printf("pthread_exit @%llx\n", addrOfPthreadExit); | |
memcpy(possiblePatchLocation, &addrOfPthreadCreate, 8); | |
} | |
if (memcmp(possiblePatchLocation, "DLOPEN__", 6) == 0) { | |
printf("dlopen @%llx\n", addrOfDlopen); | |
memcpy(possiblePatchLocation, &addrOfDlopen, sizeof(uint64_t)); | |
} | |
if (memcmp(possiblePatchLocation, "LIBLIBLIB", 9) == 0) { | |
strcpy(possiblePatchLocation, lib); | |
} | |
} | |
/** | |
* Write the (now patched) code | |
*/ | |
kr = mach_vm_write(remoteTask, // Task port | |
remoteCode64, // Virtual Address (Destination) | |
(vm_address_t)injectedCode, // Source | |
sizeof(injectedCode)); // Length of the source | |
if (kr != KERN_SUCCESS) { | |
fprintf(stderr, "Unable to write remote thread memory: Error %s\n", mach_error_string(kr)); | |
return (-3); | |
} | |
/* | |
* Mark code as executable - This also requires a workaround on iOS, btw. | |
*/ | |
kr = vm_protect(remoteTask, remoteCode64, sizeof(injectedCode), FALSE, VM_PROT_READ | VM_PROT_EXECUTE); | |
/* | |
* Mark stack as writable - not really necessary | |
*/ | |
kr = vm_protect(remoteTask, remoteStack64, STACK_SIZE, TRUE, VM_PROT_READ | VM_PROT_WRITE); | |
if (kr != KERN_SUCCESS) { | |
fprintf(stderr, "Unable to set memory permissions for remote thread: Error %s\n", mach_error_string(kr)); | |
return (-4); | |
} | |
/* | |
* Create thread - This is obviously hardware specific. | |
*/ | |
#ifdef X86_64 | |
x86_thread_state64_t remoteThreadState64; | |
#else | |
// Using unified thread state for backporting to ARMv7, if anyone's interested.. | |
struct arm_unified_thread_state remoteThreadState64; | |
#endif | |
thread_act_t remoteThread; | |
memset(&remoteThreadState64, '\0', sizeof(remoteThreadState64) ); | |
remoteStack64 += (STACK_SIZE / 2); // this is the real stack | |
//remoteStack64 -= 8; // need alignment of 16 | |
const char* p = (const char*) remoteCode64; | |
#ifdef X86_64 | |
remoteThreadState64.__rip = (u_int64_t) (vm_address_t) remoteCode64; | |
// set remote Stack Pointer | |
remoteThreadState64.__rsp = (u_int64_t) remoteStack64; | |
remoteThreadState64.__rbp = (u_int64_t) remoteStack64; | |
#else | |
// Note the similarity - all we change are a couple of regs. | |
remoteThreadState64.ash.flavor = ARM_THREAD_STATE64; | |
remoteThreadState64.ash.count = ARM_THREAD_STATE64_COUNT; | |
remoteThreadState64.ts_64.__pc = (u_int64_t) remoteCode64; | |
remoteThreadState64.ts_64.__sp = (u_int64_t) remoteStack64; | |
// __uint64_t __x[29]; /* General purpose registers x0-x28 */ | |
#endif | |
printf ("Remote Stack 64 0x%llx, Remote code is %p\n", remoteStack64, p ); | |
/* | |
* create thread and launch it in one go | |
*/ | |
#ifdef X86_64 | |
kr = thread_create_running( remoteTask, x86_THREAD_STATE64, | |
(thread_state_t) &remoteThreadState64, x86_THREAD_STATE64_COUNT, &remoteThread ); | |
#else // __arm64__ | |
kr = thread_create_running( remoteTask, ARM_THREAD_STATE64, // ARM_THREAD_STATE64, | |
(thread_state_t) &remoteThreadState64.ts_64, ARM_THREAD_STATE64_COUNT , &remoteThread ); | |
#endif | |
if (kr != KERN_SUCCESS) { fprintf(stderr,"Unable to create remote thread: error %s", mach_error_string (kr)); | |
return (-3); } | |
return (0); | |
} | |
int main(int argc, const char *argv[]) | |
{ | |
if (argc < 3) { | |
fprintf(stderr, "Usage: %s _pid_ _action_\n", argv[0]); | |
fprintf(stderr, " _action_: path to a dylib on disk\n"); | |
exit(0); | |
} | |
pid_t pid = atoi(argv[1]); | |
const char *action = argv[2]; | |
struct stat buf; | |
int rc = stat(action, &buf); | |
if (rc == 0) { | |
inject(pid, action); | |
} | |
else { | |
fprintf(stderr, "Dylib not found\n"); | |
} | |
} |
Thank you for sharing grate source code! It helps me a lot! I have been trying to inject my dylib to my process from scrach.
I have a question. How to check my dylib was loaded?
I compiled this code successfully then I started my target process which I made and I launched inject
process with sudo
.
There are no issues, no crashes but no logs. I saw some logs from 'inject' process and there were messages that the injeciting process was success.
Here is my dylib source code. I suposed happen showing log in my console with PID but no logs.
#include <stdio.h>
#include <unistd.h>
void print_pid()
{
pid_t pid = getpid();
printf("Current pid in sample libyrar: %d\n", pid);
}
__attribute__((constructor))
static void initialize()
{
printf("Initializing sample library...\n");
print_pid();
}
And here is logs from 'inject' process:
Allocated remote stack @0x102e30000
pthread_create_from_mach_thread @1961177f8
dlopen @19611dc04
pthread_exit @1961135cc
Remote Stack 64 0x102e38000, Remote code is 0x102e40000
Is this success to inject?
I'm so sorry. I passed wrong library path. I launched inject process in other folder then I passed relative path so the target process wasn't able to find my library.
I can inject into the same application without any issues on x86, but things change when switching to M2 ARM. application crashes.
I would really appreciate your help. What am I missing?
#include <iostream>
extern "C" void __attribute__((constructor)) load()
{
printf("Hello World");
}
Disassembly of section __DATA,__data:
0000000100008000 <_injectedCode>:
100008000: e0 03 00 91 mov x0, sp
100008004: 00 40 00 d1 sub x0, x0, #16
100008008: e1 03 1f aa mov x1, xzr
10000800c: e3 03 1f aa mov x3, xzr
100008010: c4 00 00 10 adr x4, #24
100008014: 62 01 00 10 adr x2, #44
100008018: 85 00 40 f9 ldr x5, [x4]
10000801c: a0 00 3f d6 blr x5
100008020: 07 00 00 10 adr x7, #0
100008024: e0 00 1f d6 br x7
100008028: 50 54 48 52 <unknown>
10000802c: 44 43 52 54 b.mi 0x1000ac894 <_injectedCode+0xa4894>
100008030: 44 4c 4f 50 adr x4, #649610
100008034: 45 4e 5f 5f <unknown>
100008038: 50 54 48 52 <unknown>
10000803c: 44 45 58 54 b.mi 0x1000b88e4 <_injectedCode+0xb08e4>
100008040: 21 00 80 d2 mov x1, #1
100008044: c0 00 00 10 adr x0, #24
100008048: 47 ff ff 10 adr x7, #-24
10000804c: e8 00 40 f9 ldr x8, [x7]
100008050: 00 01 3f d6 blr x8
100008054: 67 fe ff 10 adr x7, #-52
100008058: e0 00 1f d6 br x7
10000805c: 4c 49 42 4c <unknown>
100008060: 49 42 4c 49 <unknown>
100008064: 42 4c 49 42 <unknown>
100008068: 4c 49 42 4c <unknown>
10000806c: 49 42 4c 49 <unknown>
100008070: 42 4c 49 42 <unknown>
100008074: 4c 49 42 4c <unknown>
100008078: 49 42 4c 49 <unknown>
10000807c: 42 4c 49 42 <unknown>
100008080: 4c 49 42 4c <unknown>
100008084: 49 42 4c 49 <unknown>
100008088: 42 4c 49 42 <unknown>
10000808c: 4c 49 42 4c <unknown>
100008090: 49 42 4c 49 <unknown>
100008094: 42 4c 49 42 <unknown>
100008098: 4c 49 42 4c <unknown>
10000809c: 49 42 4c 49 <unknown>
1000080a0: 42 4c 49 42 <unknown>
1000080a4: 4c 49 42 4c <unknown>
1000080a8: 49 42 4c 49 <unknown>
1000080ac: 42 4c 49 42 <unknown>
1000080b0: 00 <unknown>
my shellcode:
"\xe0\x03\x00\x91\x00\x40\x00\xd1\xe1\x03\x1f\xaa\xe3\x03\x1f\xaa"
"\xc4\x00\x00\x10\x62\x01\x00\x10\x85\x00\x40\xf9\xa0\x00\x3f\xd6"
"\x07\x00\x00\x10\xe0\x00\x1f\xd6\x50\x54\x48\x52\x44\x43\x52\x54"
"\x44\x4c\x4f\x50\x45\x4e\x5f\x5f\x50\x54\x48\x52\x44\x45\x58\x54"
"\x21\x00\x80\xd2\xc0\x00\x00\x10\x47\xff\xff\x10\xe8\x00\x40\xf9"
"\x00\x01\x3f\xd6\x67\xfe\xff\x10\xe0\x00\x1f\xd6\x4c\x49\x42\x4c"
"\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49"
"\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42"
"\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c"
"\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49"
"\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42";
entitlement.xml
codesign --entitlements ent.xml -s - ./injector_arm
<!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.security.get-task-allow</key>
<true/>
<key>com.apple.security.task_for_pid-allow</key>
<true/>
</dict>
</plist>
x86_THREAD_STATE64_COUNT: 42
RIP: 0x114ce3000
RIP address is readable.
RSP: 0x137af8000
RBP: 0x137af8000
Memory at 0x100000000 -> protection: 5
Unable to set thread state: (os/kern) invalid argument
@StarHack thank you for your reply, yes indeed I checked that the thread is injected, however due to limitations in lldb (a longstanding EXC_BAD_ACCESS looping stop issue) I was only able to verify it in the resource monitor. That said, it's quite clear that the threads are created after running the injector a couple of times.
I am using this very simple source to test my injected dylib:
Of course, I ensured my /tmp has rwx permissions (well, on MacOs /tmp is actually a symlink that points to a 777 folder). Seems like the thread is injected, but the code is not running, and I cannot figure out a simpler way to debug a dylib than printing to a file, given access to lldb is not really available.
Do you maybe have some experience with why such a simple dylib would not work?
Edit: I managed to get lldb to work by recompiling it and disabling the EXC_BAD_ACCESS checks in code. I will fiddle with it and get back here if I find the issue.
Okay, it seems I have found the issue.
You of course need to get rid of C++ name mangling to create valid dylib entry points, so make sure to wrap your constructors and destructors with extern "C" if you're building your dylib in C++. Now my dylib can be injected with lldb, but it still doesn't work with your injector.