Hook
Hook在Android系统的应用根据框架层次可以分为两类,Java层和Native层,常见的实现方式如下:
框架层次 | Hook手段 |
---|---|
Java层 | 动态代理,代码、字节码织入(AspectJ、ASM等) |
Native层 | GOT/PLT Hook,Trap Hook,Inline Hook |
其中Native层的三种hook手段在应用范围、实现难度、性能等维度上有以下区别:
比较维度 | GOT/PLT Hook | Trap Hook | Inline Hook |
---|---|---|---|
实现原理 | 修改延时绑定表 | SIGTRAP断点信号 | 运行时指令替换 |
粒度 | 方法级 | 指令级 | 指令级 |
作用域 | 窄 | 广 | 广 |
性能 | 高 | 低 | 高 |
难度 | 中 | 中 | 极高 |
这三种方式在实际环境中应用较多的是GOT/PLT Hook,由于只是在ELF动态链接的默认流程上稍作修改,这种方式侵入性较低,且能保证性能,可以方便的实现对so库的方法hook,唯一的缺点是只能作用于绑定表中存在的方法,作用域有一定限制。trap hook由于使用系统中断,在性能上表现不好。Inline hook是终极hook手段,通过直接修改运行时内存的方式替换指令,完全手工的完成hook及跳回操作,理论上可以实现任意位置的hook,不过手写指令时需要考虑abi兼容等众多因素,实现难度很高,实际应用的场景不多。
汇编
在手撕汇编之前,先简单回顾下基础知识
平时用来开发的高级语言必须转换成低级语言才能被CPU执行,根据是否有中间结果的区别,完成转换的可能是编译器或虚拟机。和百花齐放的高级语言不同,低级语言只有两种,汇编和机器码(即二进制码),汇编是机器码的文本化表示,两者是一对一的对应关系。
逻辑上讲,汇编是为了解决机器码可读性的产物,汇编在执行前需要先翻译成机器码,这个过程叫assembing,所以汇编语言叫ASM。
$ gcc -S yourfile可以将c文件编译成汇编文件.s
寄存器
为了填平运算组件(CPU)和存储单元(硬盘)的性能沟壑,会在其间加几个缓冲单元,从慢到快依次是RAM、Cache、寄存器。一般CPU会自带这些缓冲层,其中寄存器直接跟CPU交互,是读取速度最快的单位。随着发展CPU的寄存器数量从几个增长到了几十个(ARM有37个),其中前16个一般会被当作通用寄存器使用(编号0-15),而编号15的寄存器又最为特殊,一般会把r15当作program counter,熟悉JVM的同学知道在虚拟机中也有类似的概念,只是一个是虚拟的,一个是真实的:
一般把r15当作PC寄存器,即program counter,也就是Instruction Pointer,指向程序当前执行到的那条指令。
Inline Hook
回到主题,指令级别的hook跟高级语言层面的实现方式在感官上有很大区别,高级语言中不管借助什么手段,只需将hook代码织入到目标代码之中即可,但这种方式在指令级别是行不通的,见下图: