├── README.md ├── hookmsg.h ├── hookmsg.m ├── hookutils.h └── hookutils.m /README.md: -------------------------------------------------------------------------------- 1 | objc_msgSend 2 | ============ 3 | 4 | hook objc_msgSend 5 | 6 | 7 | 8 | 目标是hook掉系统的objc_msgSend函数,以检测系统调用,方便开发与调试。主要是参考itrace的源代码,做了一捏捏的修改。 9 | 10 | https://github.com/emeau/itrace 11 | 12 | 我的代码地址: 13 | 14 | 15 | 0. 目标:hook objc_msgSend函数 16 | 17 | 我们可以很容易的借助mobile substrate的MSHookFunction的功能,替换掉系统的objc_msgSend函数。 18 | 19 | typedef id (*objc_msg_function)(id self, SEL op, ...); 20 | objc_msg_function s_originalObjcMsgSend; 21 | 22 | MSHookFunction(objc_msgSend, our_msgSend, &s_originalObjcMsgSend); 23 | 24 | 这样,就用我们的our_msgSend函数替换了系统的objc_msgSend函数。 25 | 26 | id our_msgSend(id self, SEL op, ...) 27 | { 28 | //do our log 29 | id result = 调用保存在s_originalObjcMsgSend里面的原函数 30 | return result. 31 | } 32 | 33 | 在这里,我们可以看到问题变成了如何把参数正确的传入s_originalObjcMsgSend原函数。 34 | 35 | 问题1:参数传递 36 | 要正确的传递参数,需要补充一下ARM参数传递的基础知识。 37 | ARM参数传递遵循ATPCS(Arm-Thumb Procedure Call Standard),分为定参和变参两种情况。 38 | 1. 定参: 39 | ARM系统中使用寄存器(寄存器见附1)传递参数,r0, r1, r2, r3四个寄存器用来传递参数,如果参数超过4个,那么其余的参数由栈传递(入栈顺序与参数位置相反)。上面是每个参数都能用一个寄存器传递的情况,如果参数大小超过寄存器大小(4字节(32位机),8字节(64位机)),则会逐次的用r0,r1, r2, r3来存储参数,再超出的部分用栈来传递。 40 | 41 | 2. 变参 42 | 变参也同样遵循上面的规则,使用r0, r1, r2, r3四个寄存器传递,超出的部分入栈。 43 | 比如printf(“%d, %d, %d, %d”, a, b, c, d) 44 | 那么r0传递format字符串地址 45 | r1传递a 46 | r2传递b 47 | r3传递c 48 | sp传递d 49 | 50 | 根据上面的参数传递规则,由于objc_msgSend是变参函数,我们是无法知道到底有多少参数的,因此要想将参数原封不动的传递给原函数s_originalObjcMsgSend,只能保持进入我们函数的寄存器和栈指针都不变,这样才能保证原样参数调用原函数。再回过头来看我们的函数: 51 | id our_msgSend(id self, SEL op, ...) 52 | { 53 | //do our log 54 | id result = 调用保存在s_originalObjcMsgSend里面的原函数 55 | return result. 56 | } 57 | 我们必须得调用原函数,那么lr一定会发生变化(调用过程是设置lr为下一条指令地址:return result,然后跳入原函数),最后问题变成了保存lr。 58 | id our_msgSend(id self, SEL op, ...) 59 | { 60 | //保存lr 61 | //do our log 62 | //恢复所有的寄存器 63 | id result = 调用保存在s_originalObjcMsgSend里面的原函数 64 | //恢复lr 65 | return result. 66 | } 67 | 68 | 问题2:保存lr 69 | 汇编是可以使用lr的,这里使用了inline汇编,注意XCode中支持的汇编是GNU汇编,并且在编译的过程中必须连接到真机进行编译,否则汇编中的r等寄存器会报错。 70 | lr是函数调用时的返回地址,我们使用栈来保存所有的lr,调用入栈,返回出栈。 71 | 另外还要考虑线程安全的问题,这里使用了pthread_key_t——线程私有数据。 72 | 73 | 问题3:多进程使用文件的问题 74 | 多个进程使用log文件,是否会有问题? 75 | 理论上是有问题的,目前的试验结果是OK的,具体的文件在多进程环境的读写问题需要进一步研究。 76 | 77 | 78 | 附1. ARM寄存器 79 | r0, r1, r2, r3四个寄存器是用来传递参数的 80 | r4, r5, …, r11这些寄存器是通用的,在函数内部可以使用,但是用完需要恢复,所以一般函数里面会先把需要使用的寄存器入栈,比如如要使用r7作为临时变量,那么会有下面的调用: 81 | push {r7, lr} 82 | 即把r7和返回地址入栈,等到函数要返回前,再出栈恢复r7寄存器。 83 | pop {r7, lr} 84 | r12 85 | r13:sp栈指针寄存器,ARM使用FD栈,sp指向栈顶数据,且向下增长。 86 | r14:lr保存返回地址——即调用该函数后下一条指令的地址 87 | r15:pc——当前执行的指令地址 88 | 89 | 附2:ARM汇编与GNU编译属性 90 | GNU汇编与ARM标准汇编是有区别的,带小点的是GNU汇编,比如: 91 | .section .data 92 | .section .bss 93 | .section .text 94 | 上面对应数据段,0初始值的数据段和代码段 95 | 96 | __attribute__((naked)) 97 | __attribute__是GNU C提供的编译器属性,可以设置函数的属性,比如这个naked设置阻止编译器生成函数入口或者退出代码,全部由自己处理。 98 | 99 | __attribute__((constructor)) 100 | 这个相当于构造函数,在加载该动态库的时候就会被调用 101 | 102 | __attribute__((destructor)) 103 | 卸载调用 104 | 105 | __attribute__((naked)) 106 | naked属性阻止编译器生成任何函数入口,出口代码,需要自己定义入口出口代码的需要这个属性。 107 | 108 | stmdb sp!, {r0-r8,r10-r12,lr} 109 | 批量存储,相当于 110 | push lr 111 | push r12 112 | push r11 113 | push r10 114 | push r8 115 | … 116 | push r0 117 | 存储后,sp指向最后的r0 118 | 119 | bl _bi_save_context 120 | 相当于lr = 下一条指令 121 | bx _bi_save_context调用函数 122 | 123 | ldmia sp!, {r0-r8,r10-r12,lr} 124 | 与上面的stmdb相反,相当于: 125 | pop r0 126 | pop r1 127 | … 128 | pop r8 129 | pop r10 130 | .. 131 | pop lr 132 | 133 | 附3. 线程私有数据 134 | 135 | [参考] http://www.cnblogs.com/godjesse/articles/2429762.html 136 | 137 | 全局变量,在一个进程中的所有所有线程共享,有竞态问题,有时候我们需要一个线程内部的全局变量概念,典型的就是线程自己的用户栈。 138 | Posix线程库提供了这个功能——Thread Specific Data(TSD). 139 | 140 | 创建TSD: 141 | int pthread_key_create(pthread_key_t *key, void (*destruct_func)(void *)); 142 | 143 | 附4: 动态库的加载问题 144 | 在iOS平台上,我们自己生成的dylib(动态库)是每个进程都有一个加载,还是所有的进程共享加载呢? 145 | 这就涉及到了静态库和动态库的区别,所有的静态库,其实就是.o的集合,使用者把静态库里面的obj链接到自己的app可执行文件中,因此这有2个问题:1是体积过大浪费空间,比如说stdio库,所有的app都会用到,那么如果使用静态库,所有的app可执行文件中,都有这部分代码,执行中都需要加载到内存,浪费存储,浪费内存。2是如果这个部分的库需要更新,那么每个app都需要重新编译。为了解决上面的问题,动态库的概念被提出来,在链接时,只把动态库的链接信息放在可执行文件中了——比如动态库的地址等,这样就解决了第一个问题,至于第二个问题,如果动态库更新了,那么只需要操作系统重新加载更新的动态库到内存即可。这里有个问题,既然是所有的进程共享动态库,那么动态库里面的代码和数据该如何处理呢?.text代码段和只读的数据段都可以共享,那么可写的全局变量该怎么办呢?原来OS做了巧妙的设计动态库中只读数据在各个进程间是共享的,物理内存中只有一份拷贝,而可写的部分,设置成了写时拷贝(copy on write)属性,开始是共享的,一旦某个进程要进行修改,则拷贝一份,并映射到自己的内存空间。 146 | 在iOS平台中,越狱的手机是可以加载动态库的,加载的时候,初始化函数会被调用(比如用属性__attribute__((constructor))声明的函数),既然每个链接该动态库的进程都会加载动态库,那么这个初始化函数应该被每个加载他的进程调用(实验证明这个是正确的,每个进程id会各自调用这个函数)。 147 | 148 | 149 | 【参考资料】 150 | 151 | https://github.com/emeau/itrace 152 | 153 | http://networkpx.blogspot.jp/2009/09/introducing-subjective-c.html 154 | 155 | 156 | -------------------------------------------------------------------------------- /hookmsg.h: -------------------------------------------------------------------------------- 1 | /* 2 | based on the work of Stefan Esser on his dumpdecrypted.dylib stuff 3 | and libsubjc from kennytm that i couldn't build from source nor use properly (maybe my bad dude) 4 | all credz to them srsly, they've done teh work. i've just added some bullshit. 5 | 6 | usage(until fronted functionnal): 7 | root# DYLD_INSERT_LIBRARIES=itrace.dylib /Applications/Calculator.app/Calculator 8 | 9 | this dylib just hook objc_msgSend thanks to MobileSubstrate/interpos (just uncomment some 10 | commented line to switch method) & print out the class and methods called, white/blacklisti 11 | 12 | last rev: 2013/02/17 13 | Sogeti ESEC - sqwall 14 | */ 15 | 16 | /* 17 | This's a short version from(https://github.com/emeau/itrace) 18 | samuel.song.bc@gmail.com 19 | */ 20 | 21 | #ifndef __hookmsg_h 22 | #define __hookmsg_h 23 | 24 | #ifdef __cplusplus 25 | extern "C" 26 | { 27 | #endif 28 | 29 | //start to log to file 30 | //"/var/BaiduInputMethod/hookmsglog.txt" 31 | void bi_log_start(const char *fname); 32 | 33 | void bi_log_pause(void); 34 | void bi_log_stop(void); 35 | 36 | //add custom flag text for debug(coz there are too many logs in file) 37 | void bi_log_add_flag(const char *text); 38 | 39 | #ifdef __cplusplus 40 | } 41 | #endif 42 | 43 | #endif //__hookmsg_h 44 | -------------------------------------------------------------------------------- /hookmsg.m: -------------------------------------------------------------------------------- 1 | /* 2 | based on the work of Stefan Esser on his dumpdecrypted.dylib stuff 3 | and libsubjc from kennytm that i couldn't build from source nor use properly (maybe my bad dude) 4 | all credz to them srsly, they've done teh work. i've just added some bullshit. 5 | 6 | usage(until fronted functionnal): 7 | root# DYLD_INSERT_LIBRARIES=itrace.dylib /Applications/Calculator.app/Calculator 8 | 9 | this dylib just hook objc_msgSend thanks to MobileSubstrate/interpos (just uncomment some 10 | commented line to switch method) & print out the class and methods called, white/blacklisti 11 | 12 | last rev: 2013/02/17 13 | Sogeti ESEC - sqwall 14 | */ 15 | 16 | /* 17 | This's a short version from(https://github.com/emeau/itrace) 18 | samuel.song.bc@gmail.com 19 | */ 20 | 21 | /* 22 | 23 | ****************WARNING******************** 24 | 25 | scheme must be iPhone device(real iOS device) but not simulator to compile asm 26 | 必须使用真机才能编译汇编代码 27 | 28 | otherwise "Unknown register name 'r0' in asm" 29 | 30 | */ 31 | 32 | #include "hookmsg.h" 33 | #include "hookutils.h" 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | 47 | typedef id (*objc_msg_function)(id self, SEL op, ...); 48 | 49 | //mobile substrate 50 | extern void MSHookFunction(void *symbol, void *replace, void **result); 51 | 52 | //thread specific funtions 53 | //called by asm so can't be static 54 | void *bi_add_context(); 55 | uint32_t bi_save_context(); 56 | void *bi_delete_context(); 57 | 58 | static pthread_key_t bi_key; //each thread has a specific data 59 | static uint32_t log_enabled = 0; 60 | 61 | //多进程访问文件,是否需要加文件锁? 62 | //http://www.gnu.org/savannah-checkouts/gnu/libc/manual/html_node/File-Locks.html 63 | static FILE *log_fp; 64 | 65 | objc_msg_function s_originalObjcMsgSend; 66 | 67 | static void *bi_get_thread_stack(void) 68 | { 69 | void *stack = pthread_getspecific(bi_key); 70 | 71 | if (stack == 0) 72 | { 73 | stack = bi_stack_init(200); 74 | pthread_setspecific(bi_key, stack); 75 | } 76 | 77 | return stack; 78 | } 79 | 80 | static char *get_padding(char *buffer, char pad, uint32_t len) 81 | { 82 | uint32_t i; 83 | for (i = 0; i < len; i++) 84 | { 85 | buffer[i] = pad; 86 | } 87 | 88 | buffer[i] = 0; 89 | return buffer; 90 | } 91 | 92 | /* hook callback, do whatever you want to 14 DWORD on teh stack before optionnals variadic arguments 93 | ***/ 94 | void hook_callback( id self , SEL op) 95 | { 96 | if (!log_enabled) 97 | { 98 | return; 99 | } 100 | 101 | void *p = bi_get_thread_stack(); 102 | 103 | int depth = bi_stack_depth(p); 104 | const char *className = object_getClassName(self); 105 | if( !className ) 106 | { 107 | className = "nill"; 108 | } 109 | 110 | if (log_fp) 111 | { 112 | char pad[200] = {0}; 113 | char *padding = get_padding(pad, ' ', depth); 114 | fprintf(log_fp, "%s%s: \t%s\n", padding, className, sel_getName(op)); 115 | 116 | //DO not open this when hook objc_msgSend, it is too slow 117 | //NSLog(@"%s%s: \t%s\n", padding, className, sel_getName(op)); 118 | } 119 | 120 | return; 121 | } 122 | 123 | /* add a new context and return handle in r0 124 | ***/ 125 | void *bi_add_context() 126 | { 127 | void *stack = bi_get_thread_stack(); 128 | void *lr = bi_stack_push(stack); 129 | return lr; 130 | } 131 | 132 | void *bi_delete_context() 133 | { 134 | void *stack = bi_get_thread_stack(); 135 | void *lr = bi_stack_pop(stack); 136 | return lr; 137 | } 138 | 139 | //create objc_msgSend call's trace 140 | __attribute__((naked)) 141 | uint32_t bi_save_context() 142 | { 143 | __asm__ __volatile__( 144 | "stmdb sp!, {r0-r12,lr}\n" 145 | "push {r0-r11}\n" 146 | "push {r12}\n" 147 | 148 | "blx _bi_add_context\n" 149 | 150 | "mov r12, r0\n" 151 | //"add r12, #16\n" 152 | "pop {r0}\n" 153 | "str r0, [r12]!\n" //save lr to context 154 | //"add r12, #4\n" 155 | "pop {r0-r11}\n" 156 | // r9 = p_context->lr 157 | //"stmia r12!, {r0-r11}\n" //save r0-r11 to context 158 | //prologue 159 | "ldmia sp!, {r0-r12,lr}\n" 160 | //"mov pc, lr\n" 161 | ); 162 | 163 | __asm__ __volatile__( "ldr r0, %0\n" :/*output(no)*/:/*input*/"m" (s_originalObjcMsgSend) : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r10", "r11", "r12", "lr" ); 164 | } 165 | 166 | /* main hook wrapper**/ 167 | __attribute__((naked)) 168 | static id bi_itrace(id self, SEL op) 169 | { 170 | __asm__ __volatile__( 171 | "stmdb sp!, {r0-r8,r10-r12,lr}\n" //push lr, r12, r11, r10, r8, r7, ...r0 172 | 173 | "mov r12, lr\n" 174 | 175 | "bl _bi_save_context\n" //save context and return original_msgSend in r0 176 | 177 | "mov r9, r0\n" 178 | "ldmia sp!, {r0-r8,r10-r12,lr}\n" 179 | //call hook callback 180 | "stmdb sp!, {r0-r12,lr}\n" 181 | "bl _hook_callback\n" //log with the same(r0, r1, r2, r3, sp) 182 | "ldmia sp!, {r0-r12,lr}\n" 183 | 184 | "blx r9\n" //call original objc_msgSend 185 | 186 | "stmdb sp!, {r0-r12}\n" 187 | "bl _bi_delete_context\n" //delete context and return saved lr in r0 188 | "mov lr, r0\n" 189 | //epilogue 190 | "ldmia sp!, {r0-r12}\n" 191 | 192 | "bx lr\n" //return to callee 193 | ); 194 | } 195 | 196 | #pragma mark - Public 197 | 198 | id bi_hook_test(id self, SEL op) 199 | { 200 | NSLog(@"---------------------I'm hooked function------"); 201 | return self; 202 | } 203 | 204 | __attribute__((constructor)) 205 | static void bi_log_hook(void) 206 | { 207 | pthread_key_create(&bi_key, 0); 208 | 209 | NSLog(@"-----------------hook objc_msgSend is now working--------"); 210 | MSHookFunction(objc_msgSend, bi_itrace, (void**)&s_originalObjcMsgSend); 211 | //MSHookFunction(bi_hook_test, bi_itrace, (void**)&s_originalObjcMsgSend); 212 | } 213 | 214 | //"/var/BaiduInputMethod/hookmsglog.txt" 215 | void bi_log_start(const char *fname) 216 | { 217 | if (log_fp == 0) 218 | { 219 | log_fp = fopen(fname, "w+"); 220 | if (log_fp == 0) 221 | { 222 | NSLog(@"-----------bi_log_start: error open file: %s", fname); 223 | } 224 | } 225 | 226 | time_t start_time; 227 | start_time = time(&start_time); 228 | char *text = ctime(&start_time); 229 | 230 | log_enabled = 1; 231 | NSLog(@"-------------bi_log_start(%s): %s", text, fname); 232 | } 233 | 234 | void bi_log_pause(void) 235 | { 236 | log_enabled = 0; 237 | fflush(log_fp); 238 | 239 | time_t end_time; 240 | end_time = time(&end_time); 241 | char *text = ctime(&end_time); 242 | 243 | NSLog(@"-------------bi_log_pause(%s)", text); 244 | } 245 | 246 | void bi_log_stop(void) 247 | { 248 | log_enabled = 0; 249 | if (log_fp) 250 | { 251 | fclose(log_fp); 252 | log_fp = 0; 253 | } 254 | } 255 | 256 | void bi_log_add_flag(const char *text) 257 | { 258 | fprintf(log_fp, "%s\n", text); 259 | } 260 | -------------------------------------------------------------------------------- /hookutils.h: -------------------------------------------------------------------------------- 1 | /* 2 | based on the work of Stefan Esser on his dumpdecrypted.dylib stuff 3 | and libsubjc from kennytm that i couldn't build from source nor use properly (maybe my bad dude) 4 | all credz to them srsly, they've done teh work. i've just added some bullshit. 5 | 6 | usage(until fronted functionnal): 7 | root# DYLD_INSERT_LIBRARIES=itrace.dylib /Applications/Calculator.app/Calculator 8 | 9 | this dylib just hook objc_msgSend thanks to MobileSubstrate/interpos (just uncomment some 10 | commented line to switch method) & print out the class and methods called, white/blacklisti 11 | 12 | last rev: 2013/02/17 13 | Sogeti ESEC - sqwall 14 | */ 15 | 16 | /* 17 | This's a short version from(https://github.com/emeau/itrace) 18 | samuel.song.bc@gmail.com 19 | */ 20 | 21 | #ifndef __hookutils_h 22 | #define __hookutils_h 23 | 24 | #ifdef __cplusplus 25 | extern "C" 26 | { 27 | #endif 28 | 29 | //init a stack to save stack context 30 | //we just save lr 31 | //max is the max call level, you can set 200 for example 32 | void *bi_stack_init(int max); 33 | 34 | //return the addr to save hr 35 | void *bi_stack_push(void *stack); 36 | 37 | //return lr 38 | //*****return value not addr 39 | void *bi_stack_pop(void *stack); 40 | 41 | int bi_stack_depth(void *stack); 42 | 43 | //for debug 44 | void *bi_stack_top(void *stack); 45 | 46 | #ifdef __cplusplus 47 | } 48 | #endif 49 | 50 | #endif //__hookutils_h 51 | 52 | -------------------------------------------------------------------------------- /hookutils.m: -------------------------------------------------------------------------------- 1 | 2 | #include "hookutils.h" 3 | #include 4 | 5 | #import 6 | 7 | typedef struct 8 | { 9 | void *lr; 10 | 11 | //void *r0; 12 | //void *r1; 13 | 14 | void *pad; //pad for 8 bytes align 15 | 16 | } bi_thread_context; 17 | 18 | typedef struct 19 | { 20 | int max; 21 | int index; 22 | bi_thread_context *data; 23 | 24 | } bi_stack_ctrl; 25 | 26 | //init a stack to save stack context 27 | //we just save lr 28 | //max is the max call level, you can set 100 for example 29 | void *bi_stack_init(int max) 30 | { 31 | bi_stack_ctrl *ctrl = (bi_stack_ctrl *)malloc(sizeof(bi_stack_ctrl)); 32 | if (ctrl) 33 | { 34 | ctrl->index = 0; 35 | ctrl->max = max; 36 | 37 | size_t align_len = max * sizeof(bi_thread_context) + 7; 38 | void *p = calloc(align_len, 1); 39 | 40 | if (p) 41 | { 42 | char *ptr = (char *)p; 43 | size_t offset = (size_t)p % 8; 44 | if (offset != 0) 45 | { 46 | ptr += offset; //*********notice this offset(restore it when freee)************* 47 | p = (void *)ptr; 48 | } 49 | } 50 | 51 | ctrl->data = p; 52 | } 53 | 54 | return ctrl; 55 | } 56 | 57 | //return the addr to save hr 58 | void *bi_stack_push(void *stack) 59 | { 60 | bi_stack_ctrl *p = (bi_stack_ctrl *)stack; 61 | 62 | void *lr = &(p->data[p->index++]); 63 | 64 | #if 1 65 | if (p->index >= p->max) 66 | { 67 | NSLog(@"---------------------stack is full------------"); 68 | assert(0); 69 | } 70 | #endif 71 | 72 | return lr; 73 | } 74 | 75 | //return lr 76 | void *bi_stack_pop(void *stack) 77 | { 78 | bi_stack_ctrl *p = (bi_stack_ctrl *)stack; 79 | void *lr = &(p->data[--p->index]); 80 | 81 | #if 1 82 | if (p->index < 0) 83 | { 84 | NSLog(@"---------------------stack is error------------"); 85 | assert(0); 86 | } 87 | #endif 88 | 89 | return *((void **)lr); 90 | } 91 | 92 | int bi_stack_depth(void *stack) 93 | { 94 | bi_stack_ctrl *p = (bi_stack_ctrl *)stack; 95 | return p->index - 1; 96 | } 97 | 98 | void *bi_stack_top(void *stack) 99 | { 100 | bi_stack_ctrl *p = (bi_stack_ctrl *)stack; 101 | void *lr = &(p->data[p->index - 1]); 102 | return lr; 103 | } 104 | --------------------------------------------------------------------------------