一、前言
正常hook方案都是大家熟知的使用LD_PRELOAD=xxx.so
,将其中的某个动态库的函数整个声明全部覆盖掉,利用比其so库先加载的特权将此函数覆盖掉。
但是此方法只能对于动态库的函数生效,如果一个函数是二进制内部实现的函数,此方法就无效了。
本文讨论如何hook二进制内部函数,并且实现回调。
二、原理
1. 基础知识补充
1.1. 二进制符号
2. 困难点
- 由于二进制符号都会调用本地的函数,使用
LD_PRELOAD
并不能控制二进制调用函数的地址 - 如何查找二进制的本地函数地址
- 汇编代码如何插入
- 如何调用回原函数
3. 解决方案
1) 无法直接控制二进制调用函数地址
- 无法通过正常手段进行控制,就使用更加底层的汇编进行实现
- 在函数的进入位置插入汇编代码,跳转到自己的函数地址,实现调用
2) 如何查找二进制的本地函数地址
- 这一步比较简单,直接
readelf -Ws xxx | grep [func_name]
就可以找到函数的地址
3) 汇编代码如何插入
- 程序段是无法写的,数据段才可以写
- 程序告诉你不能写,程序解决,直接改成可写
- 插入跳转到新的函数地址
- 插入这一步,需要先进行
LD_PRELOAD
hook一个进入函数,然后才能在里面进行初始化修改汇编代码
4) 如何调用原函数
- 由于插入了汇编代码,把之前的汇编代码破坏了,咋调回去
- 不破坏,先拷贝一份到新内存地址,然后调用原函数时跳转到新内存地址
- 新内存地址的最后,跳转到原来的地址的剩余部分
三、实现
1. 插入汇编代码,支持调用原函数
- emmmm,这种事情不能自己做是吧(其实是不会),找开源代码实现 subhook
2. 问题1 部分函数hook失败
- 怎么说呢,毕竟用的是第三方的库,作者好像是自己实现的汇编解析器
- 需要解析每一个汇编指令的长度,并不是所有的汇编指令长度都是固定的
- 根据测试,这个库的汇编指令解析精确度只有不到30%,如果自己的函数测试可以用还是可控的
- 害怕出现问题,就对着汇编指令手册,将作者的代码优化一边就好了
2. 问题2 函数地址是不固定的
- 高版本内核里面的函数地址是每次启动都会随机
- 可是这是linux,存在一个神奇的
/proc/self/maps
文件 - 根据测试,文件第一行的第一个地址是程序的基地址
1 | // xxx.so::xxx.c |