arm平台与其他平台的inline hook原理一致,均为函数arm字节码替换
由于指令集的特点,arm平台的inline hook较为复杂:
1、存在arm指令集和thumb指令集
2、无法单纯使用B指令直接跳转,因为b指令跳转范围优先
3、存在literal指令,与当前PC值相关,需要对指令进行修正
以arm指令集作为hook实例
由于arm架构特性,每次修改代码需要刷新cache,因为指令icache和数据dchche是分开的。
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)
跳转指令
因为b系列指令跳转范围有限,所以我们使用LDR PC, [PC, ?]
构造跳转指令
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
对应的机器码为:0xE51FF004,addr
为要跳转的地址。该跳转指令范围为32位,对于32位系统来说即为全地址跳转。
对于Thumb32指令集,跳转指令为:
LDR.W PC, [PC, #0]
addr
LDR.W PC, [PC, #0]
对应的机器码为:0x00F0DFF8,addr
为要跳转的地址。同样支持任意地址跳转。
注意arm要4字节对齐,thumb要2字节对齐。不是对齐的要填充nop对齐。
// thumb Mode
if (TEST_BIT0(item->target_addr)) {
int i;
i = 0;
//首先通过TEST_BIT0宏判断目标函数的指令集类型,其中若为Thumb指令集,多了下面一个额外处理,对bit[0]的值清零,若其值4字节不对齐,则添加一个2字节的NOP指令,使得后续的指令4字节对齐。这是因为在Thumb32指令中,若该指令对PC寄存器的值进行了修改,则该指令必须是4字节对齐的,否则为非法指令。
if (CLEAR_BIT0(item->target_addr) % 4 != 0) {
((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = 0xBF00; // NOP
}
//下面这4行,前2行是LDR.W PC, [PC, #0]对应的机器码为:0x00F0DFF8,后两行是addr,要跳转的地址
((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = 0xF8DF;//litterending
((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;
}
// arm Mode
else {
((uint32_t *) (item->target_addr))[0] = 0xe51ff004; // LDR PC, [PC, #-4]
((uint32_t *) (item->target_addr))[1] = item->new_addr;
}
然后就是该内存属性为可写,刷新cache
mprotect((void*)(target_addr & ~4095), 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC);//改权限,加写
clearcache((void*)target_addr, (void*)(target_addr + 4));
后续如果比如需要修正PC,因为如果hook替换函数要在我们代码里写原来被hook覆盖代码使用到了pc相关内容,需要修复,可以参考博客