0%

二进制行内hook方案(C/C++)

一、前言

正常hook方案都是大家熟知的使用LD_PRELOAD=xxx.so,将其中的某个动态库的函数整个声明全部覆盖掉,利用比其so库先加载的特权将此函数覆盖掉。
但是此方法只能对于动态库的函数生效,如果一个函数是二进制内部实现的函数,此方法就无效了。
本文讨论如何hook二进制内部函数,并且实现回调。

二、原理

1. 基础知识补充

1.1. 二进制符号

二进制符号

2. 困难点

  1. 由于二进制符号都会调用本地的函数,使用LD_PRELOAD并不能控制二进制调用函数的地址
  2. 如何查找二进制的本地函数地址
  3. 汇编代码如何插入
  4. 如何调用回原函数

3. 解决方案

1) 无法直接控制二进制调用函数地址

  • 无法通过正常手段进行控制,就使用更加底层的汇编进行实现
  • 在函数的进入位置插入汇编代码,跳转到自己的函数地址,实现调用

2) 如何查找二进制的本地函数地址

  • 这一步比较简单,直接readelf -Ws xxx | grep [func_name]就可以找到函数的地址

3) 汇编代码如何插入

  • 程序段是无法写的,数据段才可以写
  • 程序告诉你不能写,程序解决,直接改成可写
  • 插入跳转到新的函数地址
  • 插入这一步,需要先进行LD_PRELOADhook一个进入函数,然后才能在里面进行初始化修改汇编代码

4) 如何调用原函数

  • 由于插入了汇编代码,把之前的汇编代码破坏了,咋调回去
  • 不破坏,先拷贝一份到新内存地址,然后调用原函数时跳转到新内存地址
  • 新内存地址的最后,跳转到原来的地址的剩余部分

三、实现

1. 插入汇编代码,支持调用原函数

  • emmmm,这种事情不能自己做是吧(其实是不会),找开源代码实现 subhook

2. 问题1 部分函数hook失败

  • 怎么说呢,毕竟用的是第三方的库,作者好像是自己实现的汇编解析器
  • 需要解析每一个汇编指令的长度,并不是所有的汇编指令长度都是固定的
  • 根据测试,这个库的汇编指令解析精确度只有不到30%,如果自己的函数测试可以用还是可控的
  • 害怕出现问题,就对着汇编指令手册,将作者的代码优化一边就好了

2. 问题2 函数地址是不固定的

  • 高版本内核里面的函数地址是每次启动都会随机
  • 可是这是linux,存在一个神奇的/proc/self/maps文件
  • 根据测试,文件第一行的第一个地址是程序的基地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// xxx.so::xxx.c
void testFunc(int a) {
// 打开maps文件读取进程基地址
auto file_handle = fopen("/proc/self/maps", "r");
if (file_handle == NULL) {
LOG_INFO("%s open failed, err %s", strerror(errno));
return;
}

char buf[65] = {0};
auto ret = fread(buf, 1, 64, file_handle);
fclose(file_handle);

// 截取第一个-,前面的为基地址
for (int i = 0; i < 64; i++) {
if (buf[i] == '-') {
buf[i] = '\0';
break;
}
}
LOG_HEX(buf, 64);
unsigned long long mem_addr = 0;
sscanf(buf, "%llx", &mem_addr);
LOG_INFO("mem_addr %llx", mem_addr);

// 拿到基地址,加上函数的偏移量,得到和二进制中打印的一样的函数地址
LOG_INFO("get_svpn_rand addr %llx", mem_addr + 0x43e2);
}