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.h // 测试代码 #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
就成了包装后的函数。
#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 #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
环境变量下加入自己的动态链接库地址。及其方便,也容易对别人的设备搞破坏。