ARM INLINE HOOK (一)

本文详细介绍在ARM指令集上的InlineHook技术,包括其原理、指令修复难点及应用案例,如性能监控和安全防护。

运行在ARM指令集的CPU上(比如Android手机),通过HOOK机制,对一些关键的方法进行监控,从而达到一些特殊目的,比如性能监控、安全等。

常用的HOOK方法有:GOT表Hook和Inline Hook。而inline hook具有更广泛的适用性,几乎可以Hook任何函数(当然也有特殊情况无法进行inline hook,后面会提到)。相较于GOT表hook,inline hook由于需要能读懂ARM指令,而且还需要会对修改过的指令进行修复,会写一些基本的ARM指令,所以门槛较高。如果您有兴趣研究底层的技术,可以好好读一下我这篇文章。

inline hook的原理:

如图:

这张图很清晰表达了inline hook的原理,Caller是调用者,Callee是被调用函数,inline hook修改被调用函数头,将其修改为jmp指令,跳转到Hook进去的方法里,在Hook方法里可以做一些工作,比如记录函数参数值,或者对传参机型一些合法校验等,然后执行Trampoline,Trampoline其实就是Callee方法里被覆盖为jmp的内容,但是注意,不是简单的把jmp覆盖的内容直接拷贝到Trampoline中执行,这里要做指令的修复(为什么?后面会详细解释),执行完Trampoline后继续执行Callee后面的指令,然后返回调用者,这就是一个完整的inline hook的执行流程。

所以实际的执行步骤如下:

     1、将需要hook的方法的头两个指令替换为跳转指令,并保存原指令;

     2、将保存的原指令等价搬迁到Trampoline,注意是等价搬迁不是拷贝,这样在执行hook的内容后继续执行原来的逻辑;

 要解决上述两个问题之前,我们必须先要了解:

     1、ARM和Thumb指令的区别,ARM指令是按照固定长度的四字节进行编码,需要四字节对齐,Thumb指令是可变长度的两字节或四字节编码,需要两字节对齐,Thumb指令大部分都是两字节编码,但是也有部分指令是四字节编码,比如:bl和blx指令就是四字节编码。

     2、Arm处理器采用3级流水线来增加处理器指令流的速度,也就是说程序计数器R15(PC)总是指向“正在取指”的指令,而不是指向“正在执行”的,即PC总是指向当前正在执行的指令地址再加2条指令的地址。

  所以被hook的方法的头两个指令被替换为如下:

        LDR PC, [PC, #-4]

        addr (自己编写的hook方法的地址)

   下面举例进行说明:

     需求:需要hook libc库中的read方法,在执行read方法前先跳转到my_read方法里,记录read的参数fd,*buf等内容,然后返回继续执行。

     那这里的addr就是my_read方法的地址。为什么要将PC寄存器减去4呢?因为PC当前的地址为PC+8,所以-4刚好执行下一条指令地址,然后通过LDR将addr的内容load进PC寄存器中,达到跳转的目的。

     1、首先将LDR PC, [PC, #-4]这条指令转化为16进制的机器码,这里推荐一个网站,可以提供arm指令和十六进制机器码之间的转换:http://svr-acjf3-armie.cl.cam.ac.uk/main.cgi,如图:

 2、备份read方法的头两条指令,在Assembly code中输入LDR PC, [PC, #-4]后,machine code部分就显示出十六进制的机器码,然后将0xE51FF004写入第一个指令,通过取值符获得my_read方法的地址,将该地址(四字节)写入下一条指令。

   OK,现在执行read方法时,会首先跳转到my_read方法中,记录相关数据。那么是不是大工告成了呢?远远没有!万里长征刚开始,下面就是最复杂的部分:指令修复

  在了解指令修复之前,首先需要熟悉ARM指令的结构,ARM指令的的summary图如下:

        这张图基本上把ARM所以的指令结构都表示出来了(不包括Thumb),根据该图我们试着转化一条指令,该指令的格式为:

        LDR RX, [PC, #xx],其中RX代表R0、R1...等寄存器#xx表示任意一个立即数,那么这条指令翻译成16进制后有什么特点呢?         1、31-28位,Cond:这个四位是固定条件位不用管;
       2、27-25位,101,Load指令标识
       3、24位,Load指令为1:add offset before transfer;

       4、23-20位:1001

       5、19-16位:1111:PC寄存器是R15

       从上述分析可以得出,将该指令 & 0xfff0000 等于0x59f0000,这个运算就把LDR RX, [PC, #xx]这个类型的指令进行了归类。

       下面就引出指令修复中的一个难题:因为所有的PC地址都只想当前指令地址向下偏移两个字节的地址,如果我们把该指令迁移到另外一个地址,那么执行该指令时,PC寄存器指向的是迁移的新地址加上两个字节的偏移地址,所以在迁移前需要判断一下,该指令是否包含PC寄存器:  例如:if ( (*instruction & 0x0fff0000) == 0x59f0000) ,如果包含PC寄存器,则首先将pc向下偏移两条指令的地址加上指令后的立即数,得到绝对地址。得到四个字节绝对地址后,可以保存起来,然后通过指令load指令进行加载。

       思路就是如此,但是实现起来有很多细节要考虑,比如Thumb,Thumb-2指令的支持,处理的细节也很麻烦,稍微出错就可能引起崩溃,而且很难定位。笔者花了大量的经历,基本上将涉及到PC寄存器的ARM、THUMB、THUMB-2指令都进行了修复。

      时间问题,这篇文章暂时写到这儿,后面我还会具体讲一些实例,如何做指令的解析、如何修复指令,ARM指令的操作很有意思,需要程序员有扎实的技术功底和耐心,比如要对位操作非常熟,我也打算为位操作写一篇专门的文章。到时候您会发现位操作有多好玩儿,有多强大。

     最后,推荐我写的一个抓包工具,WebSee,能抓取网页的http/https数据,无需root,无需中间人攻击,可以在华为应用商城下载:https://appstore.huawei.com/app/C101448345

<think>嗯,用户问的是在Linux ARM64架构下如何实现inline hook的技术或方法。首先,我得回忆inline hook的基本概念。Inline hook通常是指通过修改目标函数的机器码,插入跳转指令,从而拦截或重定向函数执行流程。这对于调试、性能分析或安全检测很有用。 接下来要考虑的是ARM64架构的特点。ARM64使用AArch64指令集,指令长度固定为4字节,所以跳转指令可能需要更多的空间。比如,ARM64的B指令只能跳转±128MB的范围,如果目标地址超出这个范围,可能需要使用LDR+BR组合或者ADR指令来构造长跳转。 然后,权限问题也很重要。修改内存中的代码需要目标内存页具有可写和可执行的权限。通常,内核模块可以通过修改页表属性来调整权限,比如使用set_memory_rw函数。但用户空间的话可能需要使用mprotect系统调用来调整权限。 还有指令修复的问题。覆盖原有指令时,如果原来的指令不是4字节对齐的,可能会导致执行错误。比如,如果原函数的前两条指令被覆盖,可能需要将这两条指令复制到跳板(trampoline)中,并在跳转回来时执行它们,保证程序正确性。 接着要考虑的是同步问题。在修改指令时,如果有其他线程正在执行该函数,可能会导致竞争条件。需要暂停所有相关线程,或者在单核环境下确保原子操作。内核中可能需要使用stop_machine机制,用户空间可能需要信号处理或其他同步方法。 此外,还需要处理缓存致性问题。ARM架构有指令缓存和数据缓存,修改代码后需要刷新缓存,确保CPU读取到最新的指令。可以使用__builtin___clear_cache函数来刷新缓存。 然后,用户可能需要具体的步骤示例。比如,如何编写内核模块或用户空间的代码来实现hook。比如在内核模块中,找到目标函数的地址,修改内存权限,写入跳转指令,处理指令修复,刷新缓存。用户空间的话,可能需要使用mprotect和memcpy来修改函数代码。 安全性也是个问题,inline hook可能被安全软件检测到,因此可能需要更隐蔽的方法,比如使用硬件断点或修改函数指针。不过用户可能只是需要基本的方法。 最后,生成相关问题的时候,要考虑用户可能进步的问题,比如如何调试、如何处理不同内核版本、如何绕过保护机制等等。</think>### Linux ARM64架构下实现Inline Hook的技术实现步骤 #### 、技术原理 Inline Hook通过修改目标函数的机器指令,插入跳转指令到自定义函数。在ARM64架构中需注意: 1. 指令长度固定为4字节,跳转指令需适配$2^{28}$地址范围限制 2. 需要处理指令缓存(ICache)与数据缓存(DCache)致性[^1] 3. 必须确保内存页可写(通过`mprotect`或`set_memory_rw`) #### 二、具体实现步骤 1. **定位目标函数地址** - 内核空间:通过`kallsyms_lookup_name` - 用户空间:使用`dlopen/dlsym` 2. **构造跳转指令** ```c // ARM64长跳转指令模板 uint32_t hook_code[] = { 0x58000050, // LDR X16, =hook_function 0xD61F0200 // BR X16 }; ``` 3. **修改内存权限** ```c // 内核模块示例 set_memory_rw((unsigned long)target_addr, 1); ``` 4. **写入Hook指令** ```c memcpy(target_addr, hook_code, sizeof(hook_code)); ``` 5. **处理指令缓存** ```c __builtin___clear_cache((char*)target_addr, (char*)target_addr + sizeof(hook_code)); ``` 6. **跳板函数实现** ```c void trampoline(void) { // 执行被覆盖的原指令 asm volatile("nop \n nop"); // 跳转回原函数+8字节处 } ``` #### 三、关键注意事项 1. **指令对齐**:ARM64要求所有指令必须4字节对齐 2. **原子操作**:建议使用`stop_machine`防止竞争条件[^2] 3. **异常处理**:需捕获可能产生的缺页异常(用户空间) #### 四、完整代码示例(内核模块) ```c #include <linux/module.h> #include <linux/kallsyms.h> typedef void (*target_func_type)(void); static void hook_function(void) { printk(KERN_INFO "Hook triggered!\n"); ((target_func_type)(trampoline))(); } static void install_hook(void) { unsigned long target_addr = (unsigned long)kallsyms_lookup_name("target_function"); uint32_t jump_code[2] = { /* 构造的跳转指令 */ }; set_memory_rw(target_addr, 1); memcpy((void*)target_addr, jump_code, sizeof(jump_code)); __builtin___clear_cache((void*)target_addr, (void*)target_addr+8); } ``` #### 五、检测与防御 1. 完整性校验:检查关键函数首指令 2. 陷阱标志:在关键内存区域设置写保护 3. 监控系统调用:拦截`mprotect`和`ptrace`调用[^3]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值