├── 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 |
--------------------------------------------------------------------------------