├── Makefile ├── README.md ├── ent.plist ├── main.c └── running.png /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | clang main.c -framework Foundation -o inject_arm64 -arch arm64 -framework Security 3 | clang main.c -framework Foundation -o inject_arm64e -arch arm64e -framework Security 4 | clang main.c -D AMFI -framework Foundation -o inject_arm64ea -arch arm64e -framework Security -lbsm -lEndpointSecurity -Wno-unused-value 5 | codesign -f -s - --entitlements ent.plist ./inject_arm64ea 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # inject_aarch64 2 | 3 | Simply clone the directory and run `make` which will create `inject_arm64` and `inject_arm64e` respectively. 4 | 5 | __NOTE:__ AMFI needs to be turned off for arm64e if you are targeting Apple binaries until the ES method is implemented. 6 | 7 | Below is a simple usage on the Books application. 8 | 9 | First step is creating the library. 10 | 11 | ```bash 12 | $ cat lib.c 13 | #include 14 | 15 | __attribute__((constructor)) 16 | static void ctor(void) 17 | { 18 | printf("hello from ReverseApple\n"); 19 | } 20 | $ gcc lib.c -dynamiclib -o lib.dylib -arch arm64e 21 | $ # create ~/Library/Logs/AirTraffic directory because it can be read from sandbox 22 | $ mkdir ~/Library/Logs/AirTraffic 23 | $ # copy to previous location to respect the sandbox 24 | $ cp lib.dylib ~/Library/Logs/AirTraffic/airtraffic.log 25 | $ sudo ./inject_arm64e 42448 ~/Library/Logs/AirTraffic/airtraffic.log 26 | ``` 27 | 28 | ![Running against Books](running.png) 29 | -------------------------------------------------------------------------------- /ent.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | com.apple.developer.endpoint-security.client 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #include 4 | #if __arm64e__ 5 | #include 6 | #include 7 | #endif 8 | 9 | extern kern_return_t mach_vm_allocate(task_t task, mach_vm_address_t *addr, 10 | mach_vm_size_t size, int flags); 11 | extern kern_return_t mach_vm_read(vm_map_t target_task, 12 | mach_vm_address_t address, 13 | mach_vm_size_t size, vm_offset_t *data, 14 | mach_msg_type_number_t *dataCnt); 15 | extern kern_return_t mach_vm_write(vm_map_t target_task, 16 | mach_vm_address_t address, vm_offset_t data, 17 | mach_msg_type_number_t dataCnt); 18 | extern kern_return_t mach_vm_protect(vm_map_t target_task, 19 | mach_vm_address_t address, 20 | mach_vm_size_t size, boolean_t set_maximum, 21 | vm_prot_t new_protection); 22 | 23 | #define STACK_SIZE 65536 24 | #define CODE_SIZE 400 // the size of the shellcode 25 | 26 | #if __arm64e__ 27 | char code[] = "\xff\x83\x00\xd1" // sub sp, sp, #2 28 | "\xfd\x7b\x01\xa9" // stp x29, x30, [sp, #16] 29 | "\xfd\x43\x00\x91" // add x29, sp, #16 30 | "\xe0\x03\x00\x91" // mov x0, sp 31 | "\xe0\x03\x00\x91" // mov x0, sp 32 | "\xe1\x03\x1f\xaa" // mov x1, xzr 33 | "\xe3\x03\x1f\xaa" // mov x3, xzr 34 | "\x82\x01\x00\x10" // adr x2, #52 35 | "\xe2\x23\xc1\xda" // paciza x2 36 | "\x09\x01\x00\x10" // adr x9, #32 37 | "\x29\x01\x40\xf9" // dereference the pointer 38 | "\x20\x01\x3f\xd6" // call pthread_create_from_mach_thread 39 | "\x09\x00\x00\x10" // adr x9, #0 40 | "\x20\x01\x1f\xd6" // br x9 41 | "\xfd\x7b\x42\xa9" // ldp x29, x30, [sp, #0x20] 42 | "\xFF\xC3\x00\x91" // add sp, sp, #0x30 43 | "\xC0\x03\x5F\xD6" // ret 44 | "PTHRDCRT" 45 | 46 | "\x7f\x23\x03\xd5" // pacibsp 47 | "\xff\xc3\x00\xd1" // sub sp, sp, #0x30 48 | "\xfd\x7b\x02\xa9" // stp x29, x30, [sp, #0x20] 49 | "\xFD\x83\x00\x91" // add x29, sp, #0x20 50 | "\x21\x00\x80\xd2" // mov x1, #1 51 | "\xa0\x01\x00\x10" // adr x0, libliblib 52 | "\x09\x01\x00\x10" // adr x9, dlopen__ 53 | "\x29\x01\x40\xf9" // ldr x9, [x9] 54 | "\x20\x01\x3f\xd6" // blr x9 55 | "\x00\x00\x80\xd2" // movz x0, 0 56 | "\xc9\x00\x00\x10" // mov x9, pthr_exit 57 | "\x29\x01\x40\xf9" // ldr x9, [x9] 58 | "\x20\x01\x3f\xd6" // blr x9 59 | "\xff\x0f\x5f\xd6" // retab 60 | "DLOPEN__" 61 | "PTHREXIT" 62 | 63 | // placeholder for dylib path to load 64 | "LIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIB" 65 | "LIBLIBLIBLIBLIB" 66 | "\x00\x00\x00\x00"; 67 | #else 68 | char code[] = 69 | "\xff\x83\x00\xd1" // sup sp, sp, #2 70 | "\xfd\x7b\x01\xa9" // stp x29, x30, [sp, #16] 71 | "\xfd\x43\x00\x91" // add x29, sp, #16 72 | "\xe0\x03\x00\x91" // mov x0, sp 73 | "\xe1\x03\x1f\xaa" // mov x1, xzr 74 | "\xa2\x00\x00\x10" // adr x2, _dopen 75 | "\xe3\x03\x1f\xaa" // mov x3, xzr 76 | "\x44\x01\x00\x58" // ldr x4, pthrcrt => LOAD ADDRESS FROM PTHRCRT 77 | "\x80\x00\x3f\xd6" // ldr x4 => BRANCH TO 78 | // pthread_create_from_mach_thread 79 | // _jmp: 80 | "\x00\x00\x00\x14" // b _jmp 81 | 82 | // _dopen: 83 | "\xa0\x01\x00\x10" // adr x0, dylib => LOAD ADDRESS OF DYLIB INTO x0 84 | "\x21\x00\x80\xd2" // mov x1, #1 => RTLD_LAZY 85 | "\xe7\x00\x00\x58" // ldr x7, dlopen => LOAD ADDRESS FROM dlopen 86 | "\xe0\x00\x3f\xd6" // ldr x6 => BRANCH TO dlopen 87 | "\xe8\x00\x00\x58" // ldr x8, pthrext => LOAD ADDRESS OF pthread_exit 88 | "\x00\x00\x80\xd2" // movz x0, 0 89 | "\x00\x01\x3f\xd6" // blr x8 => BRANCH TO pthread_exit 90 | 91 | "PTHRDCRT" // placeholder for pthread_create_from_mach_thread address 92 | "DLOPEN__" // placeholder for dlopen address 93 | "PTHREXIT" // placeholder for pthread_exit address 94 | "LIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIB" 95 | "LIBLIBLIBLIBLIB" // placeholder for dylib path to load 96 | "\x00\x00\x00\x00"; 97 | #endif 98 | 99 | // This code is based on the https://gist.github.com/saagarjha/a70d44951cb72f82efee3317d80ac07f 100 | // which uses EndpointSecurity and works with AMFI turned on 101 | // with library validation disabled 102 | #ifdef AMFI 103 | // patch _amfi_dyld_check_policy_self to return 0x5f 104 | // meaning everything is allowed 105 | char patch[] = "\xe2\x0b\x80\xd2" // mov x2, 0x5f 106 | "\x22\x00\x00\xf9" // str x2, [x1] 107 | "\x00\x00\x80\xd2" // mov x0, #0 108 | "\xc0\x03\x5f\xd6"; // ret 109 | 110 | void inc_addr(vm_offset_t *addr) { *addr += (vm_offset_t)sizeof(vm_offset_t); } 111 | 112 | uintptr_t rearrange_stack(task_t task, char *lib, uintptr_t sp) { 113 | kern_return_t kr; 114 | vm_offset_t orig_sp = (vm_offset_t)sp; 115 | vm_offset_t load; 116 | vm_offset_t argc; 117 | vm_offset_t argv; 118 | 119 | mach_msg_type_number_t count; 120 | if ((kr = mach_vm_read(task, (mach_vm_address_t)orig_sp, 121 | (mach_vm_size_t)sizeof(vm_offset_t), &load, &count)) != 122 | KERN_SUCCESS) { 123 | fprintf(stderr, "Error reading load address: %s\n", mach_error_string(kr)); 124 | exit(1); 125 | } 126 | 127 | inc_addr(&orig_sp); 128 | 129 | if ((kr = mach_vm_read(task, (mach_vm_address_t)orig_sp, 130 | (mach_vm_size_t)sizeof(vm_offset_t), &argc, &count)) != 131 | KERN_SUCCESS) { 132 | fprintf(stderr, "Error reading argc address: %s\n", mach_error_string(kr)); 133 | exit(1); 134 | } 135 | 136 | printf("* argc: %lu\n", *(unsigned long *)argc); 137 | 138 | /*inc_addr(&orig_sp); 139 | 140 | if ((kr = mach_vm_read(task, (mach_vm_address_t)orig_sp, 141 | (mach_vm_size_t)sizeof(vm_offset_t), &argv, &count)) != 142 | KERN_SUCCESS) { 143 | fprintf(stderr, "Error reading argc address: %s\n", mach_error_string(kr)); 144 | exit(1); 145 | } 146 | 147 | printf("* s = %s\n", (char *)argv);*/ 148 | 149 | return sp; 150 | } 151 | 152 | void inject(pid_t pid, char *library) { 153 | task_port_t task; 154 | kern_return_t kr; 155 | 156 | if ((kr = task_for_pid(mach_task_self(), pid, &task)) != KERN_SUCCESS) { 157 | fprintf(stderr, "Error obtaining task for pid: %s\n", 158 | mach_error_string(kr)); 159 | exit(1); 160 | } 161 | 162 | thread_act_array_t threads; 163 | mach_msg_type_number_t count; 164 | if ((kr = task_threads(task, &threads, &count)) != KERN_SUCCESS) { 165 | fprintf(stderr, "Error enumerating threads: %s\n", mach_error_string(kr)); 166 | exit(1); 167 | } 168 | 169 | if (count != 1) { 170 | fprintf(stderr, "Count of threads is not 1\n"); 171 | exit(1); 172 | } 173 | 174 | arm_thread_state64_t state; 175 | count = ARM_THREAD_STATE64_COUNT; 176 | thread_state_flavor_t flavor = ARM_THREAD_STATE64; 177 | if ((kr = thread_get_state(*threads, flavor, (thread_state_t)&state, 178 | &count)) != KERN_SUCCESS) { 179 | fprintf(stderr, "Error getting thread state: %s\n", mach_error_string(kr)); 180 | exit(1); 181 | } 182 | 183 | if ((kr = thread_convert_thread_state( 184 | *threads, THREAD_CONVERT_THREAD_STATE_TO_SELF, flavor, 185 | (thread_state_t)&state, count, (thread_state_t)&state, &count)) != 186 | KERN_SUCCESS) { 187 | fprintf(stderr, "Error converting thread: %s\n", mach_error_string(kr)); 188 | exit(1); 189 | } 190 | uintptr_t sp = 191 | rearrange_stack(task, library, arm_thread_state64_get_sp(state)); 192 | arm_thread_state64_set_sp(state, sp); 193 | /*patch_restrictions(task, arm_thread_state64_get_pc(state)); 194 | ensure(thread_convert_thread_state( 195 | *threads, THREAD_CONVERT_THREAD_STATE_FROM_SELF, flavor, 196 | reinterpret_cast(&state), count, 197 | reinterpret_cast(&state), &count) == 198 | KERN_SUCCESS); ensure(thread_set_state(*threads, flavor, 199 | reinterpret_cast(&state), 200 | count) == KERN_SUCCESS);*/ 201 | } 202 | #endif 203 | 204 | int main(int argc, char **argv) { 205 | if (argc != 3) { 206 | #ifdef AMFI 207 | fprintf(stderr, "usage: ./binary /path/to/binary /path/to/dylib\n"); 208 | fprintf(stderr, "example: ./binary " 209 | "/System/Applications/Books.app/Contents/MacOS/Books " 210 | "/tmp/xzy.dylib\n"); 211 | exit(1); 212 | #else 213 | fprintf(stderr, "usage: ./binary PID /path/to/dylib\n"); 214 | fprintf(stderr, "example: ./binary 1234 /tmp/xyz.dylib\n"); 215 | exit(1); 216 | #endif 217 | } 218 | 219 | char *lib = argv[2]; 220 | 221 | #ifdef AMFI 222 | char *process = argv[1]; 223 | 224 | es_client_t *client = NULL; 225 | es_new_client(&client, ^(es_client_t *client, const es_message_t *message) { 226 | switch (message->event_type) { 227 | case ES_EVENT_TYPE_AUTH_EXEC: { 228 | const char *name = message->event.exec.target->executable->path.data; 229 | // check if we have the right process 230 | if (strcmp(name, process) == 0) { 231 | pid_t pid = audit_token_to_pid(message->process->audit_token); 232 | printf("* process %s (%d) spawned\n", name, pid); 233 | printf("* injecting into %s\n", name); 234 | inject(pid, lib); 235 | } 236 | } 237 | es_respond_auth_result(client, message, ES_AUTH_RESULT_ALLOW, false); 238 | break; 239 | default: 240 | false && "Unexpected event type"; 241 | } 242 | }); 243 | es_event_type_t events[] = {ES_EVENT_TYPE_AUTH_EXEC}; 244 | es_subscribe(client, events, sizeof(events) / sizeof(*events)); 245 | dispatch_main(); 246 | #endif 247 | 248 | pid_t pid; 249 | pid = atoi(argv[1]); 250 | 251 | task_t remoteTask; 252 | kern_return_t kr; 253 | 254 | kr = task_for_pid(mach_task_self(), pid, &remoteTask); 255 | if (kr != KERN_SUCCESS) { 256 | fprintf(stderr, "failed to get task port for pid=%d, error=%s\n", pid, 257 | mach_error_string(kr)); 258 | exit(1); 259 | } 260 | 261 | printf("* got task=%d for pid=%d\n", remoteTask, pid); 262 | 263 | mach_vm_address_t remoteStack = (vm_address_t)NULL; 264 | mach_vm_address_t remoteCode = (vm_address_t)NULL; 265 | 266 | kr = 267 | mach_vm_allocate(remoteTask, &remoteStack, STACK_SIZE, VM_FLAGS_ANYWHERE); 268 | 269 | if (kr != KERN_SUCCESS) { 270 | fprintf(stderr, "failed to allocate stack memory=%s\n", 271 | mach_error_string(kr)); 272 | exit(1); 273 | } else { 274 | printf("* allocated stack at 0x%llx\n", remoteStack); 275 | } 276 | 277 | kr = mach_vm_allocate(remoteTask, &remoteCode, CODE_SIZE, VM_FLAGS_ANYWHERE); 278 | 279 | if (kr != KERN_SUCCESS) { 280 | fprintf(stderr, "failed to allocate stack memory=%s\n", 281 | mach_error_string(kr)); 282 | exit(1); 283 | } else { 284 | printf("* allocated code at 0x%llx\n", remoteCode); 285 | } 286 | 287 | uint64_t addr_of_dlopen = (uint64_t)dlopen; 288 | uint64_t addr_of_pthread = 289 | (uint64_t)dlsym(RTLD_DEFAULT, "pthread_create_from_mach_thread"); 290 | uint64_t addr_of_pexit = (uint64_t)dlsym(RTLD_DEFAULT, "pthread_exit"); 291 | 292 | #if __arm64e__ 293 | addr_of_dlopen = (uint64_t)ptrauth_strip((void *)addr_of_dlopen, 294 | ptrauth_key_function_pointer); 295 | addr_of_pthread = (uint64_t)ptrauth_strip((void *)addr_of_pthread, 296 | ptrauth_key_function_pointer); 297 | addr_of_pexit = (uint64_t)ptrauth_strip((void *)addr_of_pexit, 298 | ptrauth_key_function_pointer); 299 | #endif 300 | 301 | char *possible_patch_location = (code); 302 | for (int i = 0; i < 0x100; i++) { 303 | possible_patch_location++; 304 | 305 | if (memcmp(possible_patch_location, "PTHRDCRT", 8) == 0) { 306 | memcpy(possible_patch_location, &addr_of_pthread, sizeof(uint64_t)); 307 | printf("* found pthread_create_from_mach_thread at 0x%llx\n", 308 | addr_of_pthread); 309 | } 310 | 311 | if (memcmp(possible_patch_location, "PTHREXIT", 8) == 0) { 312 | memcpy(possible_patch_location, &addr_of_pexit, sizeof(uint64_t)); 313 | printf("* found pthread_exit at 0x%llx\n", addr_of_pexit); 314 | } 315 | 316 | if (memcmp(possible_patch_location, "DLOPEN__", 6) == 0) { 317 | memcpy(possible_patch_location, &addr_of_dlopen, sizeof(uint64_t)); 318 | printf("* found dlopen at 0x%llx\n", addr_of_dlopen); 319 | } 320 | 321 | if (memcmp(possible_patch_location, "LIBLIBLIB", 9) == 0) { 322 | strcpy(possible_patch_location, lib); 323 | } 324 | } 325 | 326 | printf("* finished patching\n"); 327 | 328 | kr = mach_vm_write(remoteTask, remoteCode, (vm_address_t)code, CODE_SIZE); 329 | 330 | if (kr != KERN_SUCCESS) { 331 | fprintf(stderr, "failed to write code at 0x%llx; error=%s\n", remoteCode, 332 | mach_error_string(kr)); 333 | exit(1); 334 | } else { 335 | printf("* written code at 0x%llx\n", remoteCode); 336 | } 337 | 338 | printf("* spawning thread\n"); 339 | kr = vm_protect(remoteTask, remoteCode, CODE_SIZE, FALSE, 340 | VM_PROT_READ | VM_PROT_EXECUTE); 341 | 342 | remoteStack += (STACK_SIZE / 2); 343 | 344 | task_flavor_t flavor = ARM_THREAD_STATE64; 345 | mach_msg_type_number_t count = ARM_THREAD_STATE64_COUNT; 346 | 347 | arm_thread_state64_t state; 348 | 349 | #if __arm64e__ 350 | state.__opaque_pc = ptrauth_sign_unauthenticated( 351 | (void *)remoteCode, ptrauth_key_process_independent_data, 352 | ptrauth_string_discriminator("pc")); 353 | state.__opaque_sp = ptrauth_sign_unauthenticated( 354 | (void *)remoteStack, ptrauth_key_process_independent_data, 355 | ptrauth_string_discriminator("sp")); 356 | state.__opaque_lr = ptrauth_sign_unauthenticated( 357 | (void *)remoteStack, ptrauth_key_process_independent_data, 358 | ptrauth_string_discriminator("lr")); 359 | 360 | #else 361 | state.__pc = (uintptr_t)remoteCode; 362 | state.__sp = (uintptr_t)remoteStack; 363 | #endif 364 | 365 | thread_act_t thread; 366 | kr = thread_create_running(remoteTask, flavor, (thread_state_t)&state, count, 367 | &thread); 368 | 369 | if (kr != KERN_SUCCESS) { 370 | fprintf(stderr, "error spawning thread; error=%s\n", mach_error_string(kr)); 371 | exit(1); 372 | } else { 373 | printf("* finished injecting into pid=%d with dylib=%s\n", pid, lib); 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReverseApple/inject_aarch64/1e82e1a723d43f74b4c25f8c4cd2fde961438c46/running.png --------------------------------------------------------------------------------