├── Makefile ├── README.md ├── ent.xml └── objc_trace.m /Makefile: -------------------------------------------------------------------------------- 1 | GCC_BIN=`xcrun --sdk iphoneos --find clang` 2 | GCC=$(GCC_BASE) -arch arm64 3 | SDK=`xcrun --sdk iphoneos --show-sdk-path` 4 | 5 | CFLAGS = -lobjc 6 | GCC_BASE = $(GCC_BIN) -Os $(CFLAGS) -isysroot $(SDK) -F$(SDK)/System/Library/Frameworks -F$(SDK)/System/Library/PrivateFrameworks 7 | 8 | all: libobjc_trace 9 | 10 | libobjc_trace: objc_trace.m 11 | $(GCC) -shared -o $@.dylib $^ 12 | ldid -Sent.xml $@.dylib 13 | 14 | clean: 15 | rm -f *.o objc_trace.dylib -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tracing Objective-C method calls 2 | 3 | Detailed description can found in this blog posting http://nologic.github.io/blog/2016/02/28/ARM64-method-tracing/ 4 | -------------------------------------------------------------------------------- /ent.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | com.apple.springboard.debugapplications 5 | 6 | get-task-allow 7 | 8 | proc_info-allow 9 | 10 | task_for_pid-allow 11 | 12 | run-unsigned-code 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /objc_trace.m: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | __attribute__((naked)) 21 | id objc_msgSend_trace(id self, SEL op) { 22 | __asm__ __volatile__ ( 23 | "stp fp, lr, [sp, #-16]!;\n" 24 | "mov fp, sp;\n" 25 | 26 | /** 27 | * Store the value of all the parameter registers (x0-x8, q0-q7) so we can 28 | * restore everything to the initial state at the time of the actual function 29 | * call 30 | */ 31 | "sub sp, sp, #(10*8 + 8*16);\n" 32 | "stp q0, q1, [sp, #(0*16)];\n" 33 | "stp q2, q3, [sp, #(2*16)];\n" 34 | "stp q4, q5, [sp, #(4*16)];\n" 35 | "stp q6, q7, [sp, #(6*16)];\n" 36 | "stp x0, x1, [sp, #(8*16+0*8)];\n" 37 | "stp x2, x3, [sp, #(8*16+2*8)];\n" 38 | "stp x4, x5, [sp, #(8*16+4*8)];\n" 39 | "stp x6, x7, [sp, #(8*16+6*8)];\n" 40 | "str x8, [sp, #(8*16+8*8)];\n" 41 | 42 | "BL _hook_callback64_pre;\n" 43 | "mov x9, x0;\n" 44 | 45 | // Restore all the parameter registers to the initial state. 46 | "ldp q0, q1, [sp, #(0*16)];\n" 47 | "ldp q2, q3, [sp, #(2*16)];\n" 48 | "ldp q4, q5, [sp, #(4*16)];\n" 49 | "ldp q6, q7, [sp, #(6*16)];\n" 50 | "ldp x0, x1, [sp, #(8*16+0*8)];\n" 51 | "ldp x2, x3, [sp, #(8*16+2*8)];\n" 52 | "ldp x4, x5, [sp, #(8*16+4*8)];\n" 53 | "ldp x6, x7, [sp, #(8*16+6*8)];\n" 54 | "ldr x8, [sp, #(8*16+8*8)];\n" 55 | // Restore the stack pointer, frame pointer and link register 56 | "mov sp, fp;\n" 57 | "ldp fp, lr, [sp], #16;\n" 58 | 59 | "BR x9;\n" // call the original 60 | ); 61 | } 62 | 63 | __attribute__((naked)) 64 | id mach_msg_trace(id self, SEL op) { 65 | __asm__ __volatile__ ( 66 | "stp fp, lr, [sp, #-16]!;\n" 67 | "mov fp, sp;\n" 68 | 69 | /** 70 | * Store the value of all the parameter registers (x0-x8, q0-q7) so we can 71 | * restore everything to the initial state at the time of the actual function 72 | * call 73 | */ 74 | "sub sp, sp, #(10*8 + 8*16);\n" 75 | "stp q0, q1, [sp, #(0*16)];\n" 76 | "stp q2, q3, [sp, #(2*16)];\n" 77 | "stp q4, q5, [sp, #(4*16)];\n" 78 | "stp q6, q7, [sp, #(6*16)];\n" 79 | "stp x0, x1, [sp, #(8*16+0*8)];\n" 80 | "stp x2, x3, [sp, #(8*16+2*8)];\n" 81 | "stp x4, x5, [sp, #(8*16+4*8)];\n" 82 | "stp x6, x7, [sp, #(8*16+6*8)];\n" 83 | "str x8, [sp, #(8*16+8*8)];\n" 84 | 85 | "BL _hook_mach_msg_pre;\n" 86 | "mov x9, x0;\n" 87 | 88 | // Restore all the parameter registers to the initial state. 89 | "ldp q0, q1, [sp, #(0*16)];\n" 90 | "ldp q2, q3, [sp, #(2*16)];\n" 91 | "ldp q4, q5, [sp, #(4*16)];\n" 92 | "ldp q6, q7, [sp, #(6*16)];\n" 93 | "ldp x0, x1, [sp, #(8*16+0*8)];\n" 94 | "ldp x2, x3, [sp, #(8*16+2*8)];\n" 95 | "ldp x4, x5, [sp, #(8*16+4*8)];\n" 96 | "ldp x6, x7, [sp, #(8*16+6*8)];\n" 97 | "ldr x8, [sp, #(8*16+8*8)];\n" 98 | 99 | "BLR x9;\n" // call the original 100 | "BL _hook_mach_msg_post;\n" 101 | 102 | // Restore the stack pointer, frame pointer and link register 103 | "mov sp, fp;\n" 104 | "ldp fp, lr, [sp], #16;\n" 105 | 106 | "RET;\n" 107 | ); 108 | } 109 | 110 | void* original_msgSend = NULL; 111 | void* original_mach_msg = NULL; 112 | FILE* output = NULL; 113 | 114 | void* getParam(int num, void* a1, void* a2, void* a3, void* a4, void* a5) { 115 | switch(num) { 116 | case 1: return a1; 117 | case 2: return a2; 118 | case 3: return a3; 119 | case 4: return a4; 120 | case 5: return a5; 121 | } 122 | 123 | return NULL; 124 | } 125 | 126 | typedef struct { 127 | int in_use; 128 | mach_port_t machTID; 129 | mach_msg_header_t* msg; 130 | mach_msg_size_t receive_limit; 131 | } thread_state; 132 | 133 | #define NUM_STATES 1024 134 | thread_state msg_states[NUM_STATES]; 135 | pthread_mutex_t states_lock; 136 | 137 | thread_state* allocate_state(mach_port_t machTID) { 138 | for(int i = 0; i < NUM_STATES; i++) { 139 | if(msg_states[i].in_use == 0) { 140 | msg_states[i].in_use = 1; 141 | msg_states[i].machTID = machTID; 142 | 143 | return &(msg_states[i]); 144 | } 145 | } 146 | 147 | // no more states, why are there so many threads?! 148 | return NULL; 149 | } 150 | 151 | void deallocate_state(thread_state* state) { 152 | state->in_use = 0; 153 | } 154 | 155 | thread_state* find_state(mach_port_t machTID) { 156 | for(int i = 0; i < NUM_STATES; i++) { 157 | if(msg_states[i].in_use != 0 && msg_states[i].machTID == machTID) { 158 | return &(msg_states[i]); 159 | } 160 | } 161 | 162 | // not found 163 | return NULL; 164 | } 165 | 166 | void* hook_mach_msg_post(void* a1) { 167 | thread_state* state = NULL; 168 | mach_port_t machTID = pthread_mach_thread_np(pthread_self()); 169 | 170 | pthread_mutex_lock(&states_lock); 171 | state = find_state(machTID); 172 | 173 | fprintf(output, "MACH: {\"tid\":%d, \"return\":\"0x%016X\", \"resp_msg\":\"", machTID, a1); 174 | 175 | if(state != NULL) { 176 | char* byt_str = (char*)state->msg; 177 | 178 | for(int i = 0; i < state->receive_limit; ++i) { 179 | fprintf(output, "%02X", *byt_str); 180 | 181 | byt_str++; 182 | } 183 | 184 | deallocate_state(state); 185 | } else { 186 | fprintf(output, "no state"); 187 | } 188 | 189 | fprintf(output, "\"}\n"); 190 | 191 | pthread_mutex_unlock(&states_lock); 192 | 193 | return a1; 194 | } 195 | 196 | void* hook_mach_msg_pre(mach_msg_header_t* msg, 197 | mach_msg_option_t option, 198 | mach_msg_size_t send_size, 199 | mach_msg_size_t receive_limit, 200 | mach_port_t receive_name, 201 | mach_msg_timeout_t timeout, 202 | mach_port_t notify) { 203 | 204 | mach_port_t machTID = pthread_mach_thread_np(pthread_self()); 205 | thread_state* state = NULL; 206 | 207 | pthread_mutex_lock(&states_lock); 208 | state = allocate_state(machTID); 209 | 210 | if(state != NULL) { 211 | state->msg = msg; 212 | state->receive_limit = receive_limit; 213 | } 214 | 215 | char* byt_str = (char*)msg; 216 | 217 | fprintf(output, "MACH: {\"msg\":\""); 218 | for(int i = 0; i < send_size; ++i) { 219 | fprintf(output, "%02X", *byt_str); 220 | 221 | byt_str++; 222 | } 223 | 224 | fprintf(output, "\", \"msg_option\":\"0x%016X\", \"notify\":%d, \"rcv_name\":%d, \"recv_msg_size\":%d, \"send_msg_size\":%d, \"timeout\":%d, \"tid\":%d}\n", option, notify, receive_name, receive_limit, send_size, timeout, machTID); 225 | 226 | pthread_mutex_unlock(&states_lock); 227 | 228 | return original_mach_msg; 229 | } 230 | 231 | typedef IMP (*p_cache_getImp)(Class cls, SEL sel); 232 | p_cache_getImp c_cache_getImp = NULL; 233 | 234 | void* hook_callback64_pre(id self, SEL op, void* a1, void* a2, void* a3, void* a4, void* a5) { 235 | // get the important bits: class, method 236 | char* classname = (char*) object_getClassName( self ); 237 | Class cls = object_getClass(self); 238 | 239 | IMP cacheImp = NULL; 240 | 241 | if(cls != NULL && op != NULL) { 242 | cacheImp = c_cache_getImp(cls, op); 243 | } 244 | 245 | if(!cacheImp) { 246 | // not in cache, never been called, record the call. 247 | 248 | if(classname == NULL) { 249 | classname = "nil"; 250 | } 251 | 252 | char* opname = (char*) op; 253 | int namelen = strlen(opname); 254 | int classlen = strlen(classname); 255 | 256 | if(classlen > 1024) { 257 | // something is wrong, we really shouldn't have such long names 258 | goto bail; 259 | } 260 | 261 | pthread_mutex_lock(&states_lock); 262 | 263 | // print some useful info. 264 | fprintf(output, "OBJC: %016x: [%s %s (", pthread_self(), classname, (char*)opname); 265 | 266 | int printParam = 0; 267 | for(int i = 0; i < namelen; i++) { 268 | if(opname[i] == ':') { 269 | printParam += 1; 270 | 271 | fprintf(output, "%p ", getParam(printParam, a1, a2, a3, a4, a5)); 272 | } 273 | } 274 | 275 | fprintf(output, ")]\n"); 276 | 277 | pthread_mutex_unlock(&states_lock); 278 | } 279 | 280 | bail: 281 | return original_msgSend; 282 | } 283 | 284 | typedef uint32_t instruction_t; 285 | typedef uint64_t address_t; 286 | 287 | typedef struct { 288 | instruction_t i1_ldr; 289 | instruction_t i2_br; 290 | address_t jmp_addr; 291 | } s_jump_patch; 292 | 293 | __attribute__((naked)) 294 | void d_jump_patch() { 295 | __asm__ __volatile__( 296 | // trampoline to somewhere else. 297 | "ldr x16, #8;\n" 298 | "br x16;\n" 299 | ".long 0;\n" // place for jump address 300 | ".long 0;\n" 301 | ); 302 | } 303 | 304 | s_jump_patch* jump_patch(){ 305 | return (s_jump_patch*)d_jump_patch; 306 | } 307 | 308 | typedef struct { 309 | instruction_t inst[4]; 310 | s_jump_patch jump_patch[5]; 311 | instruction_t backup[4]; 312 | } s_jump_page; 313 | 314 | __attribute__((naked)) 315 | void d_jump_page() { 316 | __asm__ __volatile__( 317 | // placeholder for original instructions 318 | "B INST1;\n" 319 | "B INST2;\n" 320 | "B INST3;\n" 321 | "B INST4;\n" 322 | 323 | // jump holder, this is the default case 324 | "ldr x16, #8;\n" 325 | "br x16;\n" 326 | ".long 0;\n" // place for jump address 327 | ".long 0;\n" 328 | 329 | // jump holder 330 | // this and following are instruction cases 331 | "INST1:;\n" 332 | "ldr x16, #8;\n" 333 | "br x16;\n" 334 | ".long 0;\n" 335 | ".long 0;\n" 336 | 337 | // jump holder 338 | "INST2:;\n" 339 | "ldr x16, #8;\n" 340 | "br x16;\n" 341 | ".long 0;\n" 342 | ".long 0;\n" 343 | 344 | // jump holder 345 | "INST3:;\n" 346 | "ldr x16, #8;\n" 347 | "br x16;\n" 348 | ".long 0;\n" 349 | ".long 0;\n" 350 | 351 | // jump holder 352 | "INST4:;\n" 353 | "ldr x16, #8;\n" 354 | "br x16;\n" 355 | ".long 0;\n" 356 | ".long 0;\n" 357 | 358 | // placeholder for original instructions 359 | // above originals might get modified 360 | "B INST1;\n" 361 | "B INST2;\n" 362 | "B INST3;\n" 363 | "B INST4;\n" 364 | 365 | ); 366 | } 367 | 368 | s_jump_page* jump_page() { 369 | return (s_jump_page*)d_jump_page; 370 | } 371 | 372 | void write_jmp_patch(void* buffer, void* dst) { 373 | s_jump_patch patch = *(jump_patch()); 374 | 375 | patch.jmp_addr = (address_t)dst; 376 | 377 | *(s_jump_patch*)buffer = patch; 378 | } 379 | 380 | typedef struct { 381 | uint32_t offset : 26; 382 | uint32_t inst_num : 6; 383 | } inst_b; 384 | 385 | typedef struct { 386 | uint32_t condition: 4; 387 | uint32_t reserved : 1; 388 | uint32_t offset : 19; 389 | uint32_t inst_num : 8; 390 | } inst_b_cond; 391 | 392 | void check_branches(s_jump_page* t_func, instruction_t* o_func) { 393 | int use_jump_patch = 1; 394 | 395 | for(int i = 0; i < 4; i++) { 396 | address_t branch_offset = 0; 397 | address_t patch_offset = ((address_t)&t_func->jump_patch[use_jump_patch] - (address_t)&t_func->inst[i]) / 4; 398 | 399 | instruction_t inst = t_func->inst[i]; 400 | inst_b* i_b = (inst_b*)&inst; 401 | inst_b_cond* i_b_cond = (inst_b_cond*)&inst; 402 | 403 | if(i_b->inst_num == 0x5) { 404 | // unconditional branch 405 | 406 | // save the original branch offset 407 | branch_offset = i_b->offset; 408 | i_b->offset = patch_offset; 409 | 410 | } else if(i_b_cond->inst_num == 0x54) { 411 | // conditional branch 412 | 413 | // save the original branch offset 414 | branch_offset = i_b_cond->offset; 415 | i_b_cond->offset = patch_offset; 416 | } 417 | 418 | if(branch_offset > 0) { 419 | // put instruction back in 420 | t_func->inst[i] = inst; 421 | 422 | // set jump point into the original function, don't forget that it is PC relative 423 | t_func->jump_patch[use_jump_patch].jmp_addr = (address_t)( ((instruction_t*)o_func) + branch_offset + i); 424 | 425 | // use following patch next time. 426 | use_jump_patch++; 427 | } 428 | } 429 | } 430 | 431 | void* hook_function(void* original, void* replacement) { 432 | instruction_t* o_func = original; 433 | s_jump_page* t_func = (s_jump_page*)mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); 434 | 435 | mach_port_t self_task = mach_task_self(); 436 | 437 | if(t_func == MAP_FAILED) { 438 | perror("Unable to allocate trampoline page"); 439 | return NULL; 440 | } 441 | 442 | if(vm_protect(self_task, (vm_address_t)o_func, 4096, true, VM_PROT_READ | VM_PROT_WRITE) != KERN_SUCCESS || 443 | vm_protect(self_task, (vm_address_t)o_func, 4096, false, VM_PROT_READ | VM_PROT_WRITE) != KERN_SUCCESS) { 444 | perror("Unable set PROT_READ | PROT_WRITE on original"); 445 | return NULL; 446 | } 447 | 448 | // Building the Trampoline 449 | *t_func = *(jump_page()); 450 | // save first 4 32bit instructions 451 | // original -> trampoline 452 | instruction_t* orig_preamble = (instruction_t*)o_func; 453 | for(int i = 0; i < 4; i++) { 454 | t_func->inst [i] = orig_preamble[i]; 455 | t_func->backup[i] = orig_preamble[i]; 456 | } 457 | 458 | // Set the default case to return to the original 459 | // function after preamble 460 | write_jmp_patch(&t_func->jump_patch[0], (o_func + 4)); 461 | 462 | // check that we handle preable branches. 463 | check_branches(t_func, o_func); 464 | 465 | 466 | // Modifying the original 467 | 468 | // in origninal function 469 | // set jump point target to the hook function 470 | write_jmp_patch(o_func, replacement); 471 | 472 | 473 | // set permissions to exec 474 | if(mprotect((void*)t_func, 4096, PROT_READ | PROT_EXEC) != 0) { 475 | perror("Unable to change trampoline permissions to exec"); 476 | return NULL; 477 | } 478 | 479 | if(vm_protect(self_task, (vm_address_t)o_func, 4096, true, VM_PROT_READ | VM_PROT_EXECUTE) != KERN_SUCCESS || 480 | vm_protect(self_task, (vm_address_t)o_func, 4096, false, VM_PROT_READ | VM_PROT_EXECUTE) != KERN_SUCCESS) { 481 | perror("Unable set PROT_READ | PROT_EXEC on original"); 482 | return NULL; 483 | } 484 | 485 | return t_func; 486 | } 487 | 488 | void* unhook_function(void* _jump_page) { 489 | s_jump_page* jump_page = (s_jump_page*)_jump_page; 490 | instruction_t* o_func = ((instruction_t*)(jump_page->jump_patch[0].jmp_addr)) - 4; 491 | 492 | for(int i = 0; i < 4; i++) { 493 | o_func[i] = jump_page->backup[i]; 494 | } 495 | 496 | munmap(_jump_page, 4096); 497 | } 498 | 499 | const struct mach_header* libobjc_dylib_base(); 500 | uint64_t findSymbol64(uint8_t* buffer, const int size, char* symbol, const int symsize); 501 | 502 | // Work like an injected library. 503 | __attribute__((constructor)) 504 | static void init_hook(int argc, const char **argv) { 505 | sleep(10); 506 | 507 | output = stderr; 508 | 509 | const struct mach_header* libobjc_base = libobjc_dylib_base(); 510 | // static offset because symlook up was broken, would be nice to fix :) 511 | c_cache_getImp = (p_cache_getImp)((uint8_t*)libobjc_base) + 97792 + 0x4000; 512 | 513 | pthread_mutex_init(&states_lock, NULL); 514 | 515 | for(int i = i; i < NUM_STATES; i++) { 516 | msg_states[i].in_use = 0; 517 | } 518 | 519 | // objc_msgSend 520 | void* p_objc_msgSend = dlsym( RTLD_DEFAULT , "objc_msgSend" ); 521 | 522 | if(p_objc_msgSend != NULL){ 523 | original_msgSend = hook_function(p_objc_msgSend, objc_msgSend_trace); 524 | 525 | fprintf(output, "objc_msgSend function substrated from %p to %p, trampoline %p\n", p_objc_msgSend, objc_msgSend_trace, original_msgSend); 526 | } else { 527 | fprintf(output, "Failed to find objc_msgSend address\n"); 528 | } 529 | 530 | 531 | // mach_msg 532 | void* p_mach_msg = dlsym( RTLD_DEFAULT , "mach_msg" ); 533 | 534 | if(p_mach_msg != NULL){ 535 | original_mach_msg = hook_function(p_mach_msg, mach_msg_trace); 536 | 537 | fprintf(output, "mach_msg function substrated from %p to %p, trampoline %p\n", p_mach_msg, mach_msg_trace, original_mach_msg); 538 | } else { 539 | fprintf(output, "Failed to find mach_msg address"); 540 | } 541 | } 542 | 543 | __attribute__((destructor)) 544 | void clean_hook() { 545 | if(original_msgSend != NULL){ 546 | unhook_function(original_msgSend); 547 | } 548 | 549 | if(original_mach_msg != NULL){ 550 | unhook_function(original_mach_msg); 551 | } 552 | } 553 | 554 | 555 | // poor man's dlsym function, used to locate dlsym and dyld::loadFromMemory on /usr/lib/dyld. 556 | // this version of the procedure looks for 64bit symbols. 557 | uint64_t findSymbol64(uint8_t* buffer, const int size, char* symbol, const int symsize) { 558 | // does not appear to be working with ios cached libraries unfortunately. 559 | 560 | // We assume that our target has a FAT file for dyld. Since we are targeting 561 | // OSX/iOS, they will have dyld for 32/64 bit architectures in one file. 562 | int offset = 0; 563 | 564 | #if 0 565 | struct fat_header* fatheader = (struct fat_header*)buffer; 566 | struct fat_arch* archs = (struct fat_arch*)(buffer + sizeof(struct fat_header)); 567 | 568 | // Iterate the FAT file architecture, looking for the architecture we want. 569 | for(int i = 0; i < fatheader->nfat_arch; ++i) { 570 | struct fat_arch* arch = &archs[i]; 571 | struct mach_header_64* hdr = (struct mach_header_64*)(buffer + OSSwapBigToHostInt32(arch->offset)); 572 | 573 | // Once we have found the 64-bit version, we assume this is the one we want. 574 | if(hdr->magic == MH_MAGIC_64) { 575 | // Fix up the buffer to allow the rest of the procedure to work on the 576 | // mach-o file. 577 | buffer = hdr; 578 | break; 579 | } 580 | } 581 | #endif 582 | 583 | // top of the Mach-o file is the header structure. 584 | struct mach_header_64* header = (struct mach_header_64*)buffer; 585 | 586 | // The structure must have a magic value that will match the 64bit architecture. 587 | if(header->magic != MH_MAGIC_64) { 588 | return -1; 589 | } 590 | 591 | // we will need to skip the header. 592 | offset = sizeof(struct mach_header_64); 593 | 594 | // get the number of commands available in the header of the Mach-o. 595 | int ncmds = header->ncmds; 596 | 597 | // Iterate through all commands. 598 | while(ncmds--) { 599 | struct load_command * lcp = (struct load_command *)(buffer + offset); 600 | offset += lcp->cmdsize; 601 | 602 | // we are only interested in the symbol table command because it will enable us 603 | // to find the symbol we are interested in. 604 | if(lcp->cmd == LC_SYMTAB) { 605 | struct symtab_command *symtab = (struct symtab_command *)lcp; 606 | 607 | // obtain the begining of the symbol table. 608 | struct nlist_64 *ns = (struct nlist_64 *)(buffer + symtab->symoff); 609 | char *strtable = buffer + symtab->stroff; 610 | 611 | // iterate through all symbol names. 612 | for (int j = 0; j < symtab->nsyms; ++j) { 613 | char* checkName = strtable + ns[j].n_un.n_strx; 614 | int isMatch = 1; 615 | 616 | // this is out custom strncmp which will look for the match. 617 | for(int i = 0; i < symsize && checkName[i] != '\0'; ++i) { 618 | if(symbol[i] != checkName[i]) { 619 | isMatch = 0; 620 | break; 621 | } 622 | } 623 | 624 | // Once matched we make sure that this isn't just a starts with match. 625 | if(isMatch && (checkName[symsize] == '\0')) { 626 | // if it is a full match then return the address of the symbol. 627 | return ns[j].n_value; 628 | } 629 | } 630 | } 631 | } 632 | 633 | // return zero if the symbol was not found. 634 | return 0; 635 | } 636 | 637 | 638 | //http://stackoverflow.com/a/33898317 639 | 640 | const struct mach_header* libobjc_dylib_base() { 641 | struct task_dyld_info dyld_info; 642 | mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; 643 | 644 | if (task_info(mach_task_self(), TASK_DYLD_INFO, (task_info_t) &dyld_info, &count) == KERN_SUCCESS) { 645 | struct dyld_all_image_infos* infos = (struct dyld_all_image_infos *) dyld_info.all_image_info_addr; 646 | struct dyld_image_info* info = (struct dyld_image_info*) infos->infoArray; 647 | 648 | for (int i=0; i < infos->infoArrayCount; i++) { 649 | if(strcmp(info[i].imageFilePath, "/usr/lib/libobjc.A.dylib") == 0) { 650 | printf("path: %p %s\n", info[i].imageLoadAddress, info[i].imageFilePath); 651 | 652 | return info[i].imageLoadAddress; 653 | } 654 | } 655 | } else { 656 | printf("Not success!\n"); 657 | } 658 | 659 | return 0; 660 | } 661 | --------------------------------------------------------------------------------