0x00 前置信息
在用substrate
对某个函数进行HOOK的时候发现会奔溃,并且崩溃在了一个很奇怪的地址上。在排除了参数列表和调用约定的问题之后,本文着手分析产生该奔溃的原因。在分析之前,首先会简要说明Inline HOOK
的实现原理,接着借助崩溃信息和动态调试进行分析,最后总结崩溃的一般性原因。
0x01 Inline HOOK 的原理
Inline HOOK
是指通过修改函数前N个汇编指令跳转到HOOK函数从而实现API HOOK
的一种方式。具体修改的汇编指令与函数编译后的指令集相关,本文讨论范围限定在thumb
指令集。substrate
在thumb
模式下进行Inline HOOK
时,会修改函数前12个字节的指令,修改后的指令形式如下:
0000: BX PC 0002: ... 0004: LDR PC, [PC, #-4] 0008: the address to jump(hook function)
首先通过BX PC
指令跳转到0x0004
的位置(thumb
模式下PC寄存器的地址是当前运行指令地址加4),并且把指令集从thumb
切换到arm
。在0x0004
处执行LDR PC, [PC, #-4]
指令,即把0x0008
处的地址保存到PC
寄存器中,0x0008
处保存的就是HOOK
函数的地址,此时就实现控制流的跳转。
第二步就是如何从HOOK
函数中跳回原始函数,在HOOK
函数中调用原始函数时,首先会跳入到一块新分配的内存区域代码段,此代码段保存了原始函数的前12个字节的指令,执行完后,再通过跳转指令跳回到原始函数12个字节之后的位置。这样,就完成了全部Inline HOOK
的过程。
0x02 崩溃分析
奔溃日志如下:
I/DEBUG: signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 74daa000 I/DEBUG: r0 00000000 r1 74f14fb8 r2 75cbebe8 r3 75cbebe8 I/DEBUG: r4 74da8001 r5 00000000 r6 00001be0 r7 be8da0c8 I/DEBUG: r8 758f40ec r9 00005f52 sl 00000c30 fp 4199d0d8 I/DEBUG: ip 74f14fb8 sp be8da090 lr 41589293 pc 74daa000 cpsr 40070030
这个崩溃地址0x74daa000
不在我们要HOOK
函数所在动态库的地址范围内,另外可知substrate
HOOK时新开辟的内存段起始地址是0x74da9000
,也就是崩在内存段起始地址+0x1000
处,不过知道这些并不能判断出到底为什么奔溃的,下面根据动态调试的信息开始分析原因。
首先看一下我们要HOOK
的原始函数前若干字节的汇编指令:
.text:0000BF78 07 B5 PUSH {R0-R2,LR} .text:0000BF7A 08 A1 ADR R1, sub_BF9C .text:0000BF7C 09 46 MOV R1, R1 .text:0000BF7E A1 F1 05 01 SUB.W R1, R1, #5 .text:0000BF82 00 46 MOV R0, R0 .text:0000BF84 08 46 MOV R0, R1 .text:0000BF86 12 46 MOV R2, R2 .text:0000BF88 00 F1 2C 00 ADD.W R0, R0, #0x2C .text:0000BF8C 03 90 STR R0, [SP,#0x10+var_4] .text:0000BF8E 07 BD POP {R0-R2,PC}
函数开始保存寄存器环境之后,用ADR
指令获取一个地址,然后通过STR
把R0
中存储的地址保存到栈中保存LR
寄存器所在的位置,最后通过POP PC
实现跳转到0xBFC2(0xBF9C - 5 + 0x2C)
的位置。这里实际就是用代码混淆技术实现了一个跳转。
HOOK
后,原函数的汇编指令如下所示:
xxx.so:75A7DF78 DCB 0x78 ; x xxx.so:75A7DF79 DCB 0x47 ; G xxx.so:75A7DF7A DCB 0xC0 ; xxx.so:75A7DF7B DCB 0x46 ; F xxx.so:75A7DF7C DCB 4 xxx.so:75A7DF7D DCB 0xF0 ; xxx.so:75A7DF7E DCB 0x1F xxx.so:75A7DF7F DCB 0xE5 ; xxx.so:75A7DF80 DCB 0x5D ; ] xxx.so:75A7DF81 DCB 0x92 ; xxx.so:75A7DF82 DCB 0x58 ; X xxx.so:75A7DF83 DCB 0x41 ; A xxx.so:75A7DF84 ; --------------------------------------------------------------------------- xxx.so:75A7DF84 CODE16 xxx.so:75A7DF84 xxx.so:75A7DF84 loc_75A7DF84 ; CODE XREF: debug107:loc_74DA9010•j xxx.so:75A7DF84 ; DATA XREF: debug107:loc_74DA9010•o ... xxx.so:75A7DF84 MOV R0, R1 xxx.so:75A7DF86 MOV R2, R2 xxx.so:75A7DF88 ADD.W R0, R0, #0x2C xxx.so:75A7DF8C STR R0, [SP,#0xC] xxx.so:75A7DF8E POP {R0-R2,PC} xxx.so:75A7DF8E ; ---------------------------------------------------------------------------
修改后的开始12个字节的指令含义IDA分析不清楚,可根据第一节描述的指令形式去理解,可知从第12字节之后位置的指令没有变化。
如下是substrate
开辟的内存空间,即调用void MSHookFunction(Type_ *symbol, Type_ *replace, Type_ **result)
第三个参数返回给用户的地址,从0x74DA9000
开始的前12个字节即是原函数的前12个字节,这里关注一下0x74DA9002
处的ADR R1, unk_74DA9024
,执行后R1的值变成0x74DA9024
。
debug107:74DA9000 AREA debug107, CODE, ALIGN=0 debug107:74DA9000 ; ORG 0x74DA9000 debug107:74DA9000 CODE16 debug107:74DA9000 PUSH {R0-R2,LR} debug107:74DA9002 ADR R1, unk_74DA9024 debug107:74DA9004 MOV R1, R1 debug107:74DA9006 SUB.W R1, R1, #5 debug107:74DA900A MOV R0, R0 debug107:74DA900C BX PC debug107:74DA900C ; --------------------------------------------------------------------------- debug107:74DA900E DCB 0xC0 ; debug107:74DA900F DCB 0x46 ; F debug107:74DA9010 ; --------------------------------------------------------------------------- debug107:74DA9010 CODE32 debug107:74DA9010 debug107:74DA9010 loc_74DA9010 ; CODE XREF: debug107:74DA900C•j debug107:74DA9010 LDR PC, =(loc_75A7DF84+1) debug107:74DA9010 ; --------------------------------------------------------------------------- debug107:74DA9014 off_74DA9014 DCD loc_75A7DF84+1 ; DATA XREF: debug107:loc_74DA9010•r debug107:74DA9018 DCB 0 debug107:74DA9019 DCB 0 debug107:74DA901A DCB 0 debug107:74DA901B DCB 0 debug107:74DA901C DCB 0 debug107:74DA901D DCB 0 debug107:74DA901E DCB 0 debug107:74DA901F DCB 0 debug107:74DA9020 DCB 0 debug107:74DA9021 DCB 0 debug107:74DA9022 DCB 0 debug107:74DA9023 DCB 0 debug107:74DA9024 unk_74DA9024 DCB 0 ; DATA XREF: debug107:74DA9002•o
当从0x74DA9010
跳回原函数时,原函数的指令如下所示:
xxx.so:75A7DF84 MOV R0, R1 xxx.so:75A7DF86 MOV R2, R2 xxx.so:75A7DF88 ADD.W R0, R0, #0x2C xxx.so:75A7DF8C STR R0, [SP,#0xC] xxx.so:75A7DF8E POP {R0-R2,PC} xxx.so:75A7DF8E ; ----------------------------------------------------------------------
注意此刻R1的地址已经是0x74DA9024
了,经过2次运算后值变成了0x74da903b
并保存在栈中,那么在执行POP {R0-R2,PC}
后,PC中的指令就变成了0x74da903b
,控制流又跳转到了0x74da903b
,我发现从0x74da9018 - 0x74daa000
这部分的内存值全是0,猜想应该是substrate
开辟了0x1000
字节大小的空间,以至于进一步奔溃在了0x94daa000
。至此,根据我们动态调试分析的结果与崩溃日志一致。
综上,发现在HOOK
完后,程序控制流在返回原函数之后跳转到了与原函数不一致的地址导致程序崩溃。究其原因,就是ADR
指令取到了一个“错误”的地址,这里错误只是一个相对的概念。因为ADR
是一个PC相关的指令,当运行环境变化时PC寄存器的值不一样了,导致ADR
取到的地址发送变化。
0x03 总结
当做Inline HOOK
时,如果前12个字节中出现PC相关的指令就需要做指令修正。如下是几个常见的PC相关指令:
1. ADR RD, <LABEL> 2. LDR RD, <LABEL> 3. MOV RD, PC
当然,这只是substrate
的Bug,并不是说如果前12字节存在PC相关指令时就无法进行Inline HOOK
,只是需要额外的指令修正逻辑。