最近在研究Android原生代码hook时,遇到了一个麻烦。具体来说,就是在x86架构下,方法inline hook后,在执行原方法时可能会segfault。这里简要记录下,希望之后能够解决这个问题。
inline hook的基本思路
对方法进行inline hook,基本上就是以下步骤:
- 将origin方法的起始几条指令,保存到backup
- 在backup的最后,跳转回origin方法接下来的指令
- 将origin方法的起始,修改为跳转到hook
我遇到的问题,就是调用backup时发生的。
backup方法的坑
在进行了inline hook之后,如果需要调用origin方法,必须通过backup方法来完成:
- 调用backup
- 跳转回origin方法未被修改的地方
然而,即使backup的起始几条指令是从origin复制得来的,执行backup时就真的没有副作用吗?很不幸,这样想就太简单了!例如,如果复制到backup的指令中,包含了call/jmp等相对跳转指令,那么,在backup中,还需要对这些指令的目标偏移量做相应的调整。例如,在VirtualApp的MSHook中,有如下代码:
然而,我所遇到的问题还并不止于此。
x86架构下基于pc
寄存器的内存访问
由于x86原生不支持相对于pc
寄存器的内存访问,所以当出现这类需求时,需要首先通过某些手段,将pc
寄存器复制到其他通用寄存器。例如:
这里的__x86_get_pc_thunk_bx
,便是用于将pc
复制到ebx
:
而这一workaround,就是给inline hook带来麻烦的根源。
我遇到的问题
我是在Android x86模拟器下,通过VirtualHoook的MSHook功能,对方法__system_property_get
进行hook,从而修改应用所获取到的设备属性。但是,实际运行发现,一旦在hook方法中调用backup方法,就会出现内存异常访问。
于是,通过调试和反编译,我找到了问题的原因。方法__system_property_get
的起始几条指令正如上面列出的:
然而,在进行inline hook时,由于前两条指令总长度只有4 bytes,达不到绝对跳转所需要的5 bytes,所以origin方法的前3条指令都被复制到了backup中(当然,这里有将call
替换为jmp
的操作)。
但是,在调用backup方法时,第3条指令call __x86_get_pc_thunk_bx
将ebx
设置为backup所在代码的pc
;随后,跳转回origin执行第4条指令。细心的读者一定发现了,此时通过ebx
进行基于pc
的相对偏移访问会出现错误。根本原因就是在backup方法中设置的ebx
,并不是origin方法真正希望得到的值。
如何解决
如何解决这一问题?
我现在能想到的,就是在将origin的指令复制到backup时,进一步检查其是否存在类似于__x86_get_pc_thunk_bx
这样的坑;如果存在,那么就需要对ebx
加上相应的偏移量之后,再跳转回origin。
在网上搜索后,似乎也没有找到有人遇到类似的问题。希望哪位大牛能够指点一下。
5月19日更新: 按照上述思路,实现了一个基本的解决方案。具体地,在将指令复制到backup时,检查其是否存在call
指令?如果存在,其目标方法是否是__x86_get_pc_thunk_bx
或__x86_get_pc_thunk_cx
?如果是,那么在后面再加上一条sub ebx 0xXXXXXXXX
这样的指令,减去origin方法和backup方法之间的偏移量,从而修整相应的寄存器。具体代码可见这里和这里