如何正确的hook方法objc_msgSend · jmpews

如何正确的hook方法objc_msgSend

前言

如果希望对 Objective-C 的方法调用进行 log, 一个很好的解决方法就是 hook 方法 objc_msgSend, 当然想到的就是利用 InlinkHook 直接 hook 完事, 然而 objc_msgSend 是一个可变参数函数, 这就有点蛋疼了.

objc4-680, 和目前的 objc4-709 没有有很大出入.

以下 在举例 arm 相关时使用 objc4-680, 说明 x64 时使用 objc4-709

整个代码使用 c++, 所以有些地方需要参考 objc 的源码去造一个*, 比如 object_getClass 等.

这篇文章假设读者对以下有了解.

  1. OC 类的内存布局模型
  2. OC 实例的内存布局模型
  3. OC 函数调用与消息传递机制
  4. macho 文件格式
  5. 基本 x64, arm 汇编指令
  6. 函数调用与参数传递的 x64, arm 汇编实现机制(函数调用约定)
  7. inlinehook 机制

Hook 思路

这里首先明确, objc_msgSend 的第三个参数是不定参数, 无法确定 objc_msgSend 的具体函数签名, 也就无法通过传参来调用原函数, 所以只能上暴力的方法, 通过保存/恢复栈和寄存器方法调用原函数, 之后在汇编指令中实现原函数的跳转调用.

objc_msgSend 的函数声明

12
// runtime/message.hOBJC_EXPORT id objc_msgSend(id self, SEL op, ...)

objc_msgSend 的函数实现, 这里以 x64 和 arm64 举例.

ARM64 下 ojbc_msgSend 实现机制

_objc_msgSend 首先会取得基本的参数, 比如 isa, class, 之后会使用宏 CacheLookup 先进行缓存查找, 如果没有命中, 会触发 JumpMiss 宏处理, 跳转到 __objc_msgSend_uncached_impcache, 这个方法是了解如何正确 hook 的关键.

12345678910
// objc4-680/runtime/Messengers.subproj/objc-msg-arm64.s	ENTRY _objc_msgSend	MESSENGER_START	cmp	x0, #0			// nil check and tagged pointer check	b.le	LNilOrTagged		//  (MSB tagged pointer looks negative)	ldr	x13, [x0]		// x13 = isa	and	x9, x13, #ISA_MASK	// x9 = class	LGetIsaDone:	CacheLookup NORMAL		// calls imp or objc_msgSend_uncached
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
// objc4-680/runtime/Messengers.subproj/objc-msg-arm64.s.macro JumpMiss.if $0 == NORMAL	b	__objc_msgSend_uncached_impcache.else	b	LGetImpMiss.endif.endmacro.macro CacheLookup	// x1 = SEL, x9 = isa	ldp	x10, x11, [x9, #CACHE]	// x10 = buckets, x11 = occupied|mask	and	w12, w1, w11		// x12 = _cmd & mask	add	x12, x10, x12, LSL #4	// x12 = buckets + ((_cmd & mask)<<4)	ldp	x16, x17, [x12]		// {x16, x17} = *bucket1:	cmp	x16, x1			// if (bucket->sel != _cmd)	b.ne	2f			//     scan more	CacheHit $0			// call or return imp	2:	// not hit: x12 = not-hit bucket	CheckMiss $0			// miss if bucket->cls == 0	cmp	x12, x10		// wrap if bucket == buckets	b.eq	3f	ldp	x16, x17, [x12, #-16]!	// {x16, x17} = *--bucket	b	1b			// loop3:	// wrap: x12 = first bucket, w11 = mask	add	x12, x12, w11, UXTW #4	// x12 = buckets+(mask<<4)	// Clone scanning loop to miss instead of hang when cache is corrupt.	// The slow path may detect any corruption and halt later.	ldp	x16, x17, [x12]		// {x16, x17} = *bucket1:	cmp	x16, x1			// if (bucket->sel != _cmd)	b.ne	2f			//     scan more	CacheHit $0			// call or return imp	2:	// not hit: x12 = not-hit bucket	CheckMiss $0			// miss if bucket->cls == 0	cmp	x12, x10		// wrap if bucket == buckets	b.eq	3f	ldp	x16, x17, [x12, #-16]!	// {x16, x17} = *--bucket	b	1b			// loop3:	// double wrap	JumpMiss $0	.endmacro

具体解析下 __objc_msgSend_uncached_impcache 这个函数, 在函数入口保存寄存器和返回地址, 在调用 __class_lookupMethodAndLoadCache3 之后进行恢复. __class_lookupMethodAndLoadCache3 函数返回需要调用函数的地址.

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
 // objc4-680/runtime/Messengers.subproj/objc-msg-arm64.sSTATIC_ENTRY __objc_msgSend_uncached_impcache// THIS IS NOT A CALLABLE C FUNCTION// Out-of-band x9 is the class to searchMESSENGER_START// push framestp	fp, lr, [sp, #-16]!mov	fp, spMESSENGER_END_SLOW// save parameter registers: x0..x8, q0..q7sub	sp, sp, #(10*8 + 8*16)stp	q0, q1, [sp, #(0*16)]stp	q2, q3, [sp, #(2*16)]stp	q4, q5, [sp, #(4*16)]stp	q6, q7, [sp, #(6*16)]stp	x0, x1, [sp, #(8*16+0*8)]stp	x2, x3, [sp, #(8*16+2*8)]stp	x4, x5, [sp, #(8*16+4*8)]stp	x6, x7, [sp, #(8*16+6*8)]str	x8,     [sp, #(8*16+8*8)]// receiver and selector already in x0 and x1mov	x2, x9bl	__class_lookupMethodAndLoadCache3// imp in x0mov	x17, x0// restore registers and returnldp	q0, q1, [sp, #(0*16)]ldp	q2, q3, [sp, #(2*16)]ldp	q4, q5, [sp, #(4*16)]ldp	q6, q7, [sp, #(6*16)]ldp	x0, x1, [sp, #(8*16+0*8)]ldp	x2, x3, [sp, #(8*16+2*8)]ldp	x4, x5, [sp, #(8*16+4*8)]ldp	x6, x7, [sp, #(8*16+6*8)]ldr	x8,     [sp, #(8*16+8*8)]mov	sp, fpldp	fp, lr, [sp], #16br	x17END_ENTRY __objc_msgSend_uncached_impcache

举个例子演示下, 这里可以仔细观察 lldb 的输出.

如何正确的hook方法objc_msgSend · jmpews

x64 下 ojbc_msgSend 实现机制

整体思路与 arm64 类似, 但几个关键部分不同, 这里主要关注 GetIsaFastMethodTableLookup, MethodTableLookup 是在缓存中没有命中时进行查找.

123456789
objc4-709/runtime/Messengers.subproj/objc-msg-x86_64.sENTRY _objc_msgSendUNWIND _objc_msgSend, NoFrameMESSENGER_STARTNilTest	NORMALGetIsaFast NORMAL		// r10 = self->isaCacheLookup NORMAL, CALL	// calls IMP on success
1234567891011121314151617
objc4-709/runtime/Messengers.subproj/objc-msg-x86_64.s.macro GetIsaFast.if $0 != STRET	testb	$$1, %a1b	PN	jnz	LGetIsaSlow_f	movq	$$0x00007ffffffffff8, %r10	andq	(%a1), %r10.else	testb	$$1, %a2b	PN	jnz	LGetIsaSlow_f	movq	$$0x00007ffffffffff8, %r10	andq	(%a2), %r10.endifLGetIsaDone:	.endmacro

这里需要关注 x64 保存和恢复参数的操作, 特别是这里的栈对齐.

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
objc4-709/runtime/Messengers.subproj/objc-msg-x86_64.s.macro MethodTableLookup	push	%rbp	mov	%rsp, %rbp		sub	$$0x80+8, %rsp		// +8 for alignment	movdqa	%xmm0, -0x80(%rbp)	push	%rax			// might be xmm parameter count	movdqa	%xmm1, -0x70(%rbp)	push	%a1	movdqa	%xmm2, -0x60(%rbp)	push	%a2	movdqa	%xmm3, -0x50(%rbp)	push	%a3	movdqa	%xmm4, -0x40(%rbp)	push	%a4	movdqa	%xmm5, -0x30(%rbp)	push	%a5	movdqa	%xmm6, -0x20(%rbp)	push	%a6	movdqa	%xmm7, -0x10(%rbp)	// _class_lookupMethodAndLoadCache3(receiver, selector, class).if $0 == NORMAL	// receiver already in a1	// selector already in a2.else	movq	%a2, %a1	movq	%a3, %a2.endif	movq	%r10, %a3	call	__class_lookupMethodAndLoadCache3	// IMP is now in %rax	movq	%rax, %r11	movdqa	-0x80(%rbp), %xmm0	pop	%a6	movdqa	-0x70(%rbp), %xmm1	pop	%a5	movdqa	-0x60(%rbp), %xmm2	pop	%a4	movdqa	-0x50(%rbp), %xmm3	pop	%a3	movdqa	-0x40(%rbp), %xmm4	pop	%a2	movdqa	-0x30(%rbp), %xmm5	pop	%a1	movdqa	-0x20(%rbp), %xmm6	pop	%rax	movdqa	-0x10(%rbp), %xmm7.if $0 == NORMAL	cmp	%r11, %r11		// set eq for nonstret forwarding.else	test	%r11, %r11		// set ne for stret forwarding.endif		leave.endmacro

构建 fake_objc_msgSend

在了解了 objc_msgSend 的流程, 接下来构建 fake_objc_msgSend, 需要保存保存寄存器状态, 以正确调用原 objc_msgSend, 这里解释下为什么需要保存寄存器状态, 因为参数个数不定, 所以可能会同时用到多个寄存器和栈来同时传参, 所以这里需要将可能会用到的所有寄存器以及 sp 都保存, 以确保多参数不被修改. 其实把这里的保护寄存器实现在 hook 层可能会更好一些. 最终通过把寄存器参数保存在栈中, 并以栈指针作为参数, 传给 hookBefore, 实现函数参数的完全访问.

ok, 说完了函数的参数(寄存器)保存和恢复部分, 接下来谈一谈函数调用栈的问题.

  1. 限制函数调用栈深度
  2. 记录函数调用的返回值

问题 1, 比较好理解. 问题 2, 如果需要记录函数返回值, 并且过滤了部分的函数记录, 必须要保证 hookBeforehookAfter 是对于同一个 函数 做的处理, 所以这里需要自建函数调用栈以及标记 函数 , 也就是说必须要保证自建的调用栈的 push 和 pop 操作必须对应, 这一点 InspectiveC 不同, 它是直接跟踪所有函数入栈(依赖系统标准函数栈的准确). 这里使用 sp 寄存器做标记区分函数(关于为何使用 sp 寄存器做标记区分, 希望读者能自己仔细思考函数调用本质), 以确保 push 和 pop 的对应正确.

这里总结一下 fake_objc_msgSend_safe 的工作

  1. 保存寄存器/参数
  2. hookBefore 工作(过滤/判断 调用的函数)
  3. 恢复寄存器/参数
  4. 如果经过判断不需要追踪, 则直接调用原 ojbc_msgSend
  5. 如果需要追踪, 则修改默认栈内保存的返回地址后, 继续调用 objc_msgSend (这里涉及到 trick)
  6. 保存寄存器/参数
  7. hookAfter 工作(解析返回值, 函数调用出栈, 确保两次 sp 值相同)
  8. 恢复寄存器/参数
  9. ret

下面列出 fake_objc_msgSend_safe 的核心代码.

这里关于调用栈的操作主要放在 hookBeforehookAfter, 关于在内联汇编中调用 C 函数这里也使用了两种方法, 一种使用 nm 导出符号, 一种使用内联汇编传参.

这里关于寄存器的使用与污染问题, 请查阅相关资料或参考下方参考资料, 以了解寄存器使用约定.

这里关于如何获取到汇编的下一条指令地址的 trick, 可以参考 <程序员自我修养> 中地址无关代码中使用到的 get_pc_thunk.

使用 ‘横线’ 分割了 hookBeforehookAfter 相关处理代码, 大部分代码我都已经加了注释.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
__attribute__((__naked__)) static void fake_objc_msgSend_safe(){    // test for direct jmp    // __asm__ volatile(    //     "jmpq *%0n":    //     : "r" (orig_objc_msgSend)    //     :);    // backup registers    __asm__ volatile(        "subq $(16*8+8), %%rspn" // +8 for alignment        "movdqa	%%xmm0, (%%rsp)n"        "movdqa	%%xmm1, 0x10(%%rsp)n"        "movdqa	%%xmm2, 0x20(%%rsp)n"        "movdqa	%%xmm3, 0x30(%%rsp)n"        "movdqa	%%xmm4, 0x40(%%rsp)n"        "movdqa	%%xmm5, 0x50(%%rsp)n"        "movdqa	%%xmm6, 0x60(%%rsp)n"        "movdqa	%%xmm7, 0x70(%%rsp)n"        "pushq %%raxn" // stack align        "pushq %%r9n" // might be xmm parameter count        "pushq %%r8n"        "pushq %%rcxn"        "pushq %%rdxn"        "pushq %%rsin"        "pushq %%rdin"        "pushq %%raxn"        // origin rsp, contain `ret address`, how to use leaq, always wrong.        "movq %%rsp, %%raxn"        "addq $(16*8+8+8+7*8), %%raxn"        "pushq (%%rax)n"        "pushq %%raxn" ::            :);    // prepare args for func    __asm__ volatile(        "movq %%rsp, %%rdin"        "callq __Z10hookBeforeP9RegState_n" ::            :);    // get value from `ReturnPayload`    __asm__ volatile(        "movq (%%rax), %%r10n"        "movq 8(%%rax), %%r11n" ::            :);    // restore registers    __asm__ volatile(        "popq %%raxn"        "popq (%%rax)n"        "popq	%%raxn"        "popq	%%rdin"        "popq	%%rsin"        "popq	%%rdxn"        "popq	%%rcxn"        "popq	%%r8n"        "popq	%%r9n"        "popq %%raxn" // stack align        "movdqa	(%%rsp), %%xmm0n"        "movdqa	0x10(%%rsp), %%xmm1n"        "movdqa	0x20(%%rsp), %%xmm2n"        "movdqa	0x30(%%rsp), %%xmm3n"        "movdqa	0x40(%%rsp), %%xmm4n"        "movdqa	0x50(%%rsp), %%xmm5n"        "movdqa	0x60(%%rsp), %%xmm6n"        "movdqa	0x70(%%rsp), %%xmm7n"        "addq $(16*8+8), %%rspn" ::            :);    // go to the original objc_msgSend    __asm__ volatile(        // "br x9n"        "cmpq $0, %%r11n"        "jne Lthroughxn"        "jmpq *%%r10n"        "Lthroughx:n"        // trick to jmp        "jmp NextInstructionn"        "Begin:n"< 大专栏  如何正确的hook方法objc_msgSend · jmpews/div>        "popq %%r11n"        "movq %%r11, (%%rsp)n"        "jmpq *%%r10n"        "NextInstruction:n"        "call Begin" ::            :);    //-----------------------------------------------------------------------------    // after objc_msgSend we parse the result.    // backup registers    __asm__ volatile(        "pushq %%r10n" // stack align        "push	%%rbpn"        "movq	%%rsp, %%rbpn"        "subq	$(16*8), %%rspn" // +8 for alignment        "movdqa	%%xmm0, -0x80(%%rbp)n"        "push	%%r9n" // might be xmm parameter count        "movdqa	%%xmm1, -0x70(%%rbp)n"        "push	%%r8n"        "movdqa	%%xmm2, -0x60(%%rbp)n"        "push	%%rcxn"        "movdqa	%%xmm3, -0x50(%%rbp)n"        "push	%%rdxn"        "movdqa	%%xmm4, -0x40(%%rbp)n"        "push	%%rsin"        "movdqa	%%xmm5, -0x30(%%rbp)n"        "push	%%rdin"        "movdqa	%%xmm6, -0x20(%%rbp)n"        "push	%%raxn"        "movdqa	%%xmm7, -0x10(%%rbp)n"        "pushq 0x8(%%rbp)n"        "movq %%rbp, %%raxn"        "addq $8, %%raxn"        "pushq %%raxn" ::            :);    // prepare args for func    __asm__ volatile(        "movq %%rsp, %%rdin"        // "callq __Z9hookAfterP9RegState_n"        "callq *%0n"        "movq %%rax, %%r10n"        :        : "r"(func_ptr)        : "%rax");    // restore registers    __asm__ volatile(        "pop %%raxn"        "pop 8(%%rbp)n"        "movdqa	-0x80(%%rbp), %%xmm0n"        "pop	%%raxn"        "movdqa	-0x70(%%rbp), %%xmm1n"        "pop	%%rdin"        "movdqa	-0x60(%%rbp), %%xmm2n"        "pop	%%rsin"        "movdqa	-0x50(%%rbp), %%xmm3n"        "pop	%%rdxn"        "movdqa	-0x40(%%rbp), %%xmm4n"        "pop	%%rcxn"        "movdqa	-0x30(%%rbp), %%xmm5n"        "pop	%%r8n"        "movdqa	-0x20(%%rbp), %%xmm6n"        "pop	%%r9n"        "movdqa	-0x10(%%rbp), %%xmm7n"        "leaven"        // go to the original objc_msgSend        "movq %%r10, (%%rsp)n"        "retn" ::            :);}

虽然现在已经可以 hook 到 objc_msgSend.

利用下面的数据结构获取之前保存在栈中的参数, ps: 这个结构是参考 InspectiveC, 实现的很精巧, 在此基础上做了一些修改.

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
    -ARM64    http://infocenter.arm.com/help/topic/com.arm.doc.den0024a/DEN0024A_v8_architecture_PG.pdf (7.2.1 Floating-point) (4.6.1 Floating-point register organization in AArch64)    use struct and union to describe diagram in the above link, nice!    -X86    https://en.wikipedia.org/wiki/X86_calling_conventions    RDI, RSI, RDX, RCX, R8, R9, XMM0–7*/// x86_64 is XMM, arm64 is qtypedef union FPMReg_ {    __int128_t q;    struct {        double d1; // Holds the double (LSB).        double d2;    } d;    struct {        float f1; // Holds the float (LSB).        float f2;        float f3;        float f4;    } f;} FPReg;// just ref how to backup/restore registersstruct RegState_ {    uint64_t bp;    uint64_t ret;    union {        uint64_t arr[7];        struct {            uint64_t rax;            uint64_t rdi;            uint64_t rsi;            uint64_t rdx;            uint64_t rcx;            uint64_t r8;            uint64_t r9;        } regs;    } general;    uint64_t _; // for align        union {        FPReg arr[8];        struct {            FPReg xmm0;            FPReg xmm1;            FPReg xmm2;            FPReg xmm3;            FPReg xmm4;            FPReg xmm5;            FPReg xmm6;            FPReg xmm7;        } regs;    } floating;};typedef struct pa_list_ {    struct RegState_ *regs; // Registers saved when function is called.    unsigned char *stack; // Address of current argument.    int ngrn; // The Next General-purpose Register Number.    int nsrn; // The Next SIMD and Floating-point Register Number.} pa_list;

构建 hookBefore

hookBefore 实现了函数调用前的 trace 工作, 过滤 函数, 将 函数 压进调用栈, 解析类实例对象, 解析不定参数.

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
    arg1: object-address(need to parse >> class)    arg2: method string address    arg3: method signature*/vm_address_t hookBefore(struct RegState_ *rs){    gettimeofday(&start,NULL);    // TODO: parse args    pa_list args = (pa_list){        rs,        reinterpret_cast<unsigned char *>(rs->bp),        2,        0};    ThreadCallStack *cs = getThreadCallStack(threadKey);    ReturnPayload *rp = cs->rp;    vm_address_t xaddr = (uint64_t)orig_objc_msgSend;    rp->addr = xaddr;    rp->ret = rs->ret;    rp->bp = rs->bp;    rp->isTrace = 0;    rp->key = rs->ret & rs->bp;    if (1 || pthread_main_np())    {        /*             pay attention!!! as `objc_msgSend` invoked so often, the `filter` code snippet that use `c` to write it must be fast!!!.        */        do        {               // check method string address, can be narrow the scope.            char *methodSelector = reinterpret_cast<char *>(rs->general.regs.rsi);            if(!(methodSelector && checkAddressRange((vm_address_t)methodSelector, macho_load_addr, macho_load_end_addr)))                break;            // check object's class address            vm_address_t class_addr = macho_objc_getClass(rs->general.regs.rdi);            if (!(class_addr && checkAddressRange(class_addr, macho_load_addr, macho_load_end_addr)))                break;            // check `call count filter`            if(check_freq_filter((unsigned long)methodSelector))                break;                        // // test for wechat            // if(!strncmp(methodSelector, "onRevokeMsg", 9))            //     debug_break();            // check exclude            // check method_name first, no need parse object.            if (!checkLogFilters_MethodName(NULL, methodSelector, FT_EXINLUDE))                break;            objc_class_info_t *xobjc_class_info;            xobjc_class_info = mem->parse_OBJECT(rs->general.regs.rdi);            if(!xobjc_class_info)                break;                        // check class name, need parse object            if (!checkLogFilters_ClassName(xobjc_class_info->class_name, NULL, FT_EXINLUDE))                break;            objc_method_info_t * objc_method_info;            objc_method_info = search_method_name(&(xobjc_class_info->methods), methodSelector);            if (!objc_method_info)                break;            if(check_thread_filter(cs->thread))                break;            // add to `method call count cache`            add_freq_filter((unsigned long)methodSelector, objc_method_info);            // may be can pass into 'objc_method_info_t' without 'class_name' and 'method_name'            CallRecord *cr = pushCallRecord(xobjc_class_info->class_name, methodSelector, reinterpret_cast<void *>(rs->bp), reinterpret_cast<void *>(rs->ret), cs);            if(!cr)                break;            printCallRcord(cr, cs);            rp->isTrace = 1;        } while(0);    }    gettimeofday(&end,NULL);    time_cost += (end.tv_sec-start.tv_sec)+(end.tv_usec-start.tv_usec)/1000000.0;    return reinterpret_cast<unsigned long>(rp);}

hookBefore 的参数其实之前备份寄存器后的 sp 地址, 再通过 RegState_ 格式, 就可以取得所有寄存器的值. rs->general.regs.x0 存放的是实例地址, 但是按理说应该通过 object_getClass(rs->general.regs.x0) 应该取得类地址, 但是这里避免使用 Objective-C以及 parser 内代码格式统一 就直接使用了 c++ 去实现源码中对应的函数. rs->general.regs.x1 存放的是函数签名字符串.

Macho 的文件解析

这里用到了个人之前实现的 macho 的 parser 模块, 大致介绍下这个模块, 可以通过三种状态对 macho 格式进行解析: 1. 文件 2. pid 3. 自身进程, 这里使用第三种, 对自身进程进行解析. (这个模块实现也挺有意思, 需要考虑到 ‘文件偏移’, ‘虚拟地址’, ‘内存地址’ 三种地址的处理)

如何正确的hook方法objc_msgSend · jmpews

对于类实例的解析, 比较复杂, 这里简单介绍下, 具体可以参照 macho-ABI 文档.

构建 hookAfter

hookAfter 实现了 objc_msgSend 执行后, 函数的出栈工作, 并解析函数返回值.

1234567891011121314151617181920
vm_address_t hookAfter(struct RegState_ *rs){    pa_list args = (pa_list){        rs,        reinterpret_cast<unsigned char *>(rs->bp),        2,        0};    ThreadCallStack *cs = getThreadCallStack(threadKey);    CallRecord *cr = popCallRecordSafe(cs, (void *)rs->bp);    if (cr)    {        // printCallRcordReturnValue(cr, cs, rs);        return reinterpret_cast<unsigned long>(cr->ret);    }    else    {        cr = popCallRecord(cs);        return reinterpret_cast<unsigned long>(cr->ret);    }}

hookBefore 与 hookAfter 的数据传递.

x64 层面

关键在于函数调用栈中 函数返回地址 的传递, 也就是 (%%rsp) , 由于需要获取 objc_msgSend 的返回值, 所以需要修改栈内默认的函数返回值为下一条指令的地址(请读者思考为什么不直接 push 内存地址? ). 这涉及到如何恢复栈中函数返回地址, 采用线程私有变量的方式, 为每一个 CallRecord 添加 ret 成员.

12345678910111213141516
typedef struct{    vm_address_t addr;    unsigned long isTrace;    vm_address_t ret;    vm_address_t bp;    vm_address_t key;} ReturnPayload;// Shared structures.typedef struct CallRecord_ {    void *objc;    void *method;    void *bp;    void *ret;} CallRecord;
arm64 层面

其实关键在于 lr 寄存器值的传递, 由于需要获取 objc_msgSend 的返回值, 所以必须以 blr objc_msgSend 的方式调用, 此时之前 lr 寄存器的值被覆盖, 此时也不能进行 push 操作, 因为栈中可能已经保存了可变参数的值, 需要维持 sp 和 栈中内容不变, 那这里的 lr 如何保存恢复成了问题.

最后采用了线程私有变量的方法解决, 为每一个 CallRecord 添加 lr 成员, 在 hookAfter 是进行安全 pop, 也就是说同时需要校验 rs->fp 的值, 是否一致

12345678910111213141516
typedef struct{    vm_address_t addr;    unsigned long isTrace;    vm_address_t lr;    vm_address_t fp;    vm_address_t key;} ReturnPayload;// Shared structures.typedef struct CallRecord_ {    void *objc;    void *method;    void *fp;    void *lr;} CallRecord;

如何正确过滤 objc_msgSend

目前我是通过过滤来避免 log 太复杂.

1. 过滤函数地址

默认会过滤解析地址在 self 内存区内, 因为 MachoParser 本身就是一个耗时操作.

2.1 设置不解析类/方法

需要使用 hash 表进行快速 cache 和 search.

2.2 设置只解析的类/方法

需要使用 hash 表进行快速 cache 和 search.

3. 限制函数调用栈深度

通过自建模拟函数栈, 限制函数栈调用深度.

4. 过滤频繁调用函数 (log, monitor 之类)

对于一定时间段内频繁调用的函数添加到 hash-map 中.

应用场景

目前主要关注逆向方面, 当然 APM 方向也是可以玩的.

Thunder 逆向

参考资料

如何正确的hook方法objc_msgSend · jmpews

12
// 函数调用约定, 寄存器使用约定http://abcdxyzk.github.io/blog/2012/11/23/assembly-args/
上一篇:攻防世界 reverse SignIn


下一篇:php 把一个数组分成有n个元素的二维数组的算法