Android Native Hook工具实践

参考文章

Android Native Hook 工具实践 https://paper.seebug.org/650/

ARM64下的Android Native Hook工具实践 - Galaxy Lab http://galaxylab.org/arm64下的android-native-hook工具实践/

它的这个工具实际是借鉴的诺神的inlinehook

0x01 inlinehook的原理

原文原理参考:

器对应的实现的文章如下:https://gtoad.github.io/2018/07/13/Android-Inline-Hook-Fix/

1. Arm模式与Thumb模式的区别

Arm指令为4字节对齐,每条指令长度均为32位;
Thumb指令为2字节对齐,又分为Thumb16、Thumb32,其中Thumb16指令长度为16位,Thumb32指令长度为32位。

// 设置bit[0]的值为1
#define SET_BIT0(addr)		(addr | 1)
// 设置bit[0]的值为0
#define CLEAR_BIT0(addr)	(addr & 0xFFFFFFFE)
// 测试bit[0]的值,若为1则返回真,若为0则返回假
#define TEST_BIT0(addr)		(addr & 1)

2. 跳转指令的构造

跳转指令主要分为以下两种:
B系列指令:B、BL、BX、BLX
直接写PC寄存器

Arm的B系列指令跳转范围只有4M,Thumb的B系列指令跳转范围只有256字节,然而大多数情况下跳转范围都会大于4M,故我们采用LDR PC, [PC, ?]构造跳转指令。

3. PC相关指令的修正

不论是Arm指令集还是Thumb指令集,都存在很多的与PC值相关的指令,例如:B系列指令、literal系列指令等。原有函数的前几个被跳转指令替换的指令将会被搬移到trampoline_instructions中,此时PC值已经变动,所以需要对PC相关指令进行修正(所谓修正即为计算出实际地址,并使用其他指令完成同样的功能)。相关修正代码位于relocate.c文件中。其中INSTRUCTION_TYPE描述了需要修正的指令,限于篇幅,这里仅阐述Arm指令的修正过程,对应的代码为relocateInstructionInArm函数。
函数原型如下:

/*
target_addr: 待Hook的目标函数地址,即为当前PC值,用于修正指令
orig_instructions:存放原有指令的首地址,用于修正指令和后续对原有指令的恢复
length:存放的原有指令的长度,Arm指令为8字节;Thumb指令为12字节
trampoline_instructions:存放修正后指令的首地址,用于调用原函数
orig_boundaries:存放原有指令的指令边界(所谓边界即为该条指令与起始地址的偏移量),用于后续线程处理中,对PC的迁移
trampoline_boundaries:存放修正后指令的指令边界,用途与上相同
count:处理的指令项数,用途与上相同
*/
static void relocateInstructionInArm(uint32_t target_addr, uint32_t *orig_instructions, int length, uint32_t *trampoline_instructions, int *orig_boundaries, int *trampoline_boundaries, int *count);

具体实现中,首先通过函数getTypeInArm判断当前指令的类型,本函数通过类型,共分为4个处理分支:

BLX_ARM、BL_ARM、B_ARM、BX_ARM
ADD_ARM
ADR1_ARM、ADR2_ARM、LDR_ARM、MOV_ARM
其他指令

4. 线程处理

http://ele7enxxh.com/Analysis-Of-Backtrace-And-Inline-Hook-Thread-Safety-On-The-ARM-Platform.html

通过backtrace来判断,

一个完善的Inline Hook方案必须要考虑多线程环境,即要考虑线程恰好执行到被修改指令的位置。在Window下,使用GetThreadContext和SetThreadContext枚举所有线程,迁移context到搬迁后的指令中。然而在Linux+Arm环境下,并没有直接提供相同功能的API,不过可以使用ptrace完成,主要流程如下:

解析/proc/self/task目录,获取所有线程id
创建子进程,父进程等待。子进程枚举所有线程,PTRACE_ATTACH线程,迁移线程PC寄存器,枚举完毕后,子进程给自己发SIGSTOP信号,等待父进程唤醒
父进程检测到子进程已经SIGSTOP,完成Inline Hook工作,向子进程发送SIGCONT信号,同时等待子进程退出
子进程枚举所有线程,PTRACE_DETACH线程,枚举完毕后,子进程退出
父进程继续其他工作

这里使用子进程完成线程处理工作,实际上是迫不得已的。因为,如果直接使用本进程PTRACE_ATTACH线程,会出现operation not permitted,即使赋予root权限也是同样的错误,具体原因不得而知。
具体代码请参考freeze与unFreeze两个函数。

5. 其他一些细节

页保护
页面大小为4096字节,使用mprotect函数修改页面属性,修改为PROT_READ | PROT_WRITE | PROT_EXEC。

刷新缓存
对于ARM处理器来说,缓存机制作用明显,内存中的指令已经改变,但是cache中的指令可能仍为原有指令,所以需要手动刷新cache中的内容。采用cacheflush即可实现。

一个已知的BUG
虽然本库已经把大部分工作放在了registerInlineHook函数中,但是在inlineHook、inlineUnHook函数中还是不可避免的使用了部分libc库的API函数,例如:mprotect、memcpy、munmap、free、cacheflush等。如果使用本库对上述API函数进行Hook,可能会失败甚至崩溃,这是因为此时原函数的指令已经被破坏,或者其逻辑已经改变。解决这个Bug有两个方案,第一是采用其他Hook技术;第二将本库中的这些API函数全部采用内部实现,即不依赖于libc库,可采用静态链接libc库,或者使用汇编直接调相应的系统调用号。

猜你喜欢

转载自blog.csdn.net/tangsilian/article/details/83418670