库打桩机制
Linux 链接器截获对共享库函数调用,转而执行自己的代码。
创建一个包装函数,对库函数进行包装(装饰器模式)。利用打桩机制欺骗系统去调用包装函数。
编译时打桩
// malloc.c
// 对 malloc、free 的包装函数
#ifdef COMPILETIME
#include <stdio.h>
#include <malloc.h>
void *my_malloc(size_t size) {
void *ptr = malloc(size);
printf("malloc(%d) = %p\n", (int) size, ptr);
return ptr;
}
void my_free(void *ptr) {
free(ptr);
printf("free(%p)\n", ptr);
}
#endif
// malloc.h
// 包装函数声明,并且利用宏替换目标函数和包装函数
#define malloc(size) my_malloc(size)
#define free(ptr) my_free(ptr)
void *my_malloc(size_t);
void my_free(void *);
// main.c
// 测试代码
#include <stdio.h>
#include <malloc.h>
int main() {
int *p = malloc(32);
free(p);
return 0;
}
$ gcc -c -DCOMPILETIME malloc.c # 编译模块
$ gcc -I. -o main main.c malloc.o # 进行链接,-I. 先从当前目录开始找头文件
$ ./main
# 执行结果如下
malloc(32) = 0xf5e2a0
free(0xf5e2a0)
在链接时引入的是自己的头文件,所以会执行宏替换,转而执行自己的包装函数。
链接时打桩
// malloc.c
// 对 malloc、free 的包装函数
#ifdef LINKTIME
#include <stdio.h>
void *__real_malloc(size_t);
void __real_free(void *);
void *__wrap_malloc(size_t size) {
void *ptr = __real_malloc(size);
printf("malloc(%d) = %p\n", (int) size, ptr);
return ptr;
}
void __wrap_free(void *ptr) {
__real_free(ptr);
printf("free(%p)\n", ptr);
}
#endif
$ gcc -DLINKTIME malloc.c -c
$ gcc -c main.c
# -W1 对链接器传递参数,`,` 会被转义为空格
$ gcc -Wl,--wrap,malloc -Wl,--wrap,free -o main main.o malloc.o
$ ./main
# 执行结果如下
malloc(32) = 0x22342a0
free(0x22342a0)
--wrap f
告诉链接器把 f
解析成 __wrap_f
,然后把 __real_f
解析成原本的 f
。
换句话说,就是原来的 f
就是 __real_f
,而别人调用的 f
就成了包装后的 __wrap_f
。
所以 main
中调用的 malloc
free
就成了包装后的函数。
运行时打桩
// malloc.c
// 对 malloc、free 的包装函数
#ifdef RUNTIME
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
void *malloc(size_t size) {
// printf 函数会调 malloc,所以要判断一下,防止无限递归爆栈
static __thread int cnt = 0;
cnt++;
void *(*mallocp)(size_t) = dlsym(RTLD_NEXT, "malloc");
void *ptr = mallocp(size);
if (cnt == 1)
printf("malloc(%d) = %p\n", (int) size, ptr);
return ptr;
}
void free(void *ptr) {
void *(*freep)(void *) = dlsym(RTLD_NEXT, "free");
freep(ptr);
printf("free(%p)\n", ptr);
}
#endif
$ gcc -DRUNTIME -shared -fpic -o malloc.so malloc.c -ldl
$ gcc -o main main.c
$ ./main
# 没有输出
$ LD_PRLOAD="./malloc.so" # 将路径加入环境变量下
$ ./main
# 执行结果如下
malloc(32) = 0x8b12a0
free(0x8b12a0)
动态链接器会先搜索环境变量 LD_PRELOAD
下的库,再搜索其他库。所以在 LD_PRELOAD
中放入自己实现的库,就可以实现运行时打桩。
总结
编译时打桩,需要访问源码,属于硬编码。
链接时打桩,虽然不用硬编码,但是必须要用到可重定位文件,再将其链接。也不够灵活。
运行时打桩,只需要在 LD_PRELOAD
环境变量下加入自己的动态链接库地址。非常灵活,也容易对别人的设备搞破坏。