[TOC]
Android Native Hook 分成
- GOT/PLT Hook
- Inline Hook
本篇讲述 GOT/PLT Hook 的原理、具体实现以及它的优缺点
如果想在 mac 上使用 readelf, objdump 等命令,需要将 NDK 里面的路径配置到 path
在 ~./bashrc
文件中添加路径
1 | export PATH="$PATH:/..(自己的目录)/android/sdk/ndk/21.0.6113669/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/" |
保存,使用命令
source ~/.bashrc
使其生效
一、原理
Linux 在执行动态链接 ELF 的时候,为了优化性能使用了延迟绑定策略;
当动态链接的 ELF 程序里调用共享库的函数时,第一次调用时会先去查找 PTL 表中相应的项目, 而 PTL 表中再跳跃到 GOT 表中希望得到该函数的实际地址,但这是 GOT 表中指向的是 PLT 中那条跳跃指令下面的代码,最终会执行 _dl_runtime_resolve() 并执行目标函数。
第二次调用时也是 PTL 跳跃到 GOT 表,但是 GOT 中对应项目已经在第一次 _dl_runtime_relove() 中被修改为函数实际地址,因此第二次及以后的调用直接去执行目标函数,不再执行 _dl_runtime_relove() 了。
因此,PTL 通过修改 GOT 表,使得在调用该共享库的函数时跳转的是用户自定义的 Hook 功能代码。
外部函数第一次调用流程
外部函数第二次调用流程
二、具体实现
2.1 总体实现方案
总体的步骤:
- 通过读取 /proc/
/maps 文件找到链接库的基地址; - 读取动态库,解析 ELF 文件,找到符号(需要对 ELF 文件格式有了解)
- 计算目标函数绝对地址
目标进程函数绝对地址 = 动态库基地址 + 函数地址
具体的步骤:
- 1.获取动态库的基地址;
- 2.计算 .so 文件中的 program header table 程序头地址;
- 3.遍历程序头部表,获取动态段(.dynamic)地址;
- 4.找到 GOT 表地址;
- 5.修改内存属性为可写;
- 6.遍历 got 表,修改要替换的函数;
- 7.恢复内存属性为可读可执行。
2.2 方案的具体实现
下面具体实现的代码参考文章 android中基于plt/got的hook实现原理
我们在 Demo 里使用 CMake 将 Native 代码编译成 native_write.so 文件,放入到 libs 对应的架构目录下,然后通过 hook 住 so 文件中的 fwrite 函数。
先看我们要 hook 的目标,是在 native_write.c
中的 fwrite 函数
1 | /** |
我们把自己 hook 的 Native 也编译成 native_hook.so 文件,让 Android 把它加载
hook 的代码, hook 的代码在 native_hook.c
中
1 | size_t new_fwrite(const void *buf, size_t size, size_t count, FILE *fp) { |
上面是的 new_fwrite
函数就是在原来 fwrite
函数之前先写入 ‘hello native’ 的内容
- 第一步:获取动态库的基地址
1 | // 1. 获取目标进程中模块的加载地址 |
- 第二步:计算 .so 文件中的 program head table 程序头地址;
1 | // 2.计算 header table 的实际地址 |
- 第三步:遍历程序头部表,获取动态段(.dynamic)地址;
1 | // .dynamic 段的结构 /* |
- 第四步:找到 GOT 表地址;
1 | // 重定位表 |
- 第五步:找到被 hook 函数的
1 | // 4.遍历 got 表 |
- 第六步:修改内存属性为可写
1 | // 6. 修改内存属性为可写 |
- 第七步:替换 hook 的函数
1 | // 7. 替换 |
- 第八步:恢复存属性为可读可执行(清除指令缓存)
1 | // 6. 清除指令缓存 |
导入表Hook流程图, 来源 Android平台导入表Hook方式实现
完整的代码,已放到 github
三、优缺点及应用
3.1 PTL/GOT hook 的特点
- 由于修改 GOT 表中的数据,因此修改后,所有对该函数进行调用的地方都会被 hook 到。影响范围是该 PLT 和 GOT 所处的整个 so 库;
- PTL 与 GOT 表中仅仅包含本 ELF 需要调用的共享库函数,因此不在 PTL 中的函数无法 hook 到。
3.2 使用场景
- 可以大量 hook 系统 API, 但是难以精准 hook 某次函数调用。因此 PLT/GOT hook 适用开发者自家 APP 性能监控的需求;
- 一些函数不在 PLT 表和 GOT 表里,因此对这些 so 内部自定义的函数无法 hook 到;
- 在回调原函数方面, PLT hook 在 hook 目标函数时,如果需要回调原来的函数,那么在hook 之后的功能函数中直接调用目标函数即可
1 | size_t new_fwrite(const void *buf, size_t size, size_t count, FILE *fp) { |
四、其他
4.1 使用 xhook
上面是具体是实现,如果是在正常的日常使用中,可以使用 xhook
使用 xhook 的步骤:
- 将 xhook 以 lib 形式集成
- 在主项目中引用这个 lib, 并将 xhook.h 文件放入都自己 cpp 路径中
- 注册要 hook 的 so 和 so 里面的函数
1 | JNIEXPORT void JNICALL |
- 提供替换 hook 的函数
1 | /** |
5.将生成的 hook 的 so 加载
4.2 xhook hook 的简要流程
1 | xhook_register --> xh_core_register(...) |