什么是Inline Hook
Inline Hook即内部跳转Hook,通过替换函数开始处的指令为跳转指令,使得原函数跳转到自己的函数,通常还会保留原函数的调用接口。与GOT表Hook相比,Inline Hook具有更广泛的适用性,几乎可以Hook任何函数,不过其实现更为复杂,考虑的情况更多,并且无法对一些太短的函数Hook。
其基本原理请参阅网上其他资料。
需要解决的问题
1.Arm模式与Thumb模式的区别
2.跳转指令的构造
3.PC相关指令的修正
4.线程处理
5.其他一些细节
Arm模式与Thumb模式的区别
arm版本7及以上的体系中,其指令集分为ARM指令集和Thumb指令集。Arm指令为4字节对齐,每条指令长度均为32位;Thumb指令为2字节对齐,又分为Thumb16、Thumb32,其中Thumb16指令长度为16位,Thumb32指令长度为32位。
在对一个函数进行Inline Hook时,首先需要判断当前函数指令是Arm指令还是Thumb指令,指令使用目标地址值的bit[0]来确定目标地址的指令类型。bit[0]的值为1时,目标程序为Thumb指令;bit[0]值为0时,目标程序为ARM指令。其相关实现代码为以下宏:
// 设置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)
跳转指令的构造
跳转指令主要分为以下两种:
1.B系列指令:B、BL、BX、BLX
2.直接写PC寄存器
Arm的B系列指令跳转范围只有4M,Thumb的B系列指令跳转范围只有256字节,然而大多数情况下跳转范围都会大于4M,故我们采用LDR PC, [PC, ?]构造跳转指令。另外Thumb16指令中并没有合适的跳转指令,如果单独使用Thumb16指令构造跳转指令,需要使用更多的指令完成,并且在后续对PC相关指令的修正也更加繁琐,故综合考虑下,决定放弃对ARMv5的支持。
另外,Arm处理器采用3级流水线来增加处理器指令流的速度,也就是说程序计数器R15(PC)总是指向“正在取指”的指令,而不是指向“正在执行”的,即PC总是指向当前正在执行的指令地址再加2条指令的地址。比如当前指令地址是0×8000, 那么当前pc的值,在thumb下面是0×8000 + 2 2, 在arm下面是0×8000 + 4 2。
对于Arm指令集,跳转指令为:
LDR PC, [PC, #-4]
addr
LDR PC, [PC, #-4]对应的机器码为:0xE51FF004,addr为要跳转的地址。该跳转指令范围为32位,对于32位系统来说即为全地址跳转。
对于Thumb32指令集,跳转指令为:
LDR.W PC, [PC, #0]
addr
LDR.W PC, [PC, #0]对应的机器码为:0x00F0DFF8,addr为要跳转的地址。同样支持任意地址跳转。
其相关实现代码为:
// Arm Mode
if (TEST_BIT0(item->target_addr)) {
int i;
i = 0;
if (CLEAR_BIT0(item->target_addr) % 4 != 0) {
((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = 0xBF00; // NOP
}
((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = 0xF8DF;
((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = 0xF000; // LDR.W PC, [PC]
((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = item->new_addr & 0xFFFF;
((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = item->new_addr >> 16;
}
// Thumb Mode
else {
((uint32_t *) (item->target_addr))[0] = 0xe51ff004; // LDR PC, [PC, #-4]
((uint32_t *) (item->target_addr))[1] = item->new_addr;
}
首先通过TEST_BIT0宏判断目标函数的指令集类型,其中若为Thumb指令集,多了下面一个额外处理:
if (CLEAR_BIT0(item->target_addr) % 4 != 0) {
((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = 0xBF00; // NOP
}
对bit[0]的值清零,若其值4字节不对齐,则添加一个2字节的NOP指令,使得后续的指令4字节对齐。这是因为在Thumb32指令中,若该指令对PC寄存器的值进行了修改,则该指令必须是4字节对齐的,否则为非法指令。
ARM HOOK 图解
PC相关指令的修正