LD_PRELOAD是个环境变量,用于动态库的加载,动态库加载的优先级最高,一般情况下,其加载顺序为LD_PRELOAD > LD_LIBRARY_PATH > /etc/ld.so.cache > /lib>/usr/lib。程序中我们经常要调用一些外部库的函数.
以malloc/free为例,如果我们有个自定义的rand函数,把它编译成动态库后,通过LD_PRELOAD加载,当程序中调用malloc/free函数时,调用的其实是我们自定义的函数,下面以一个例子说明。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <stddef.h> #include <stdint.h> #include <fcntl.h> #include <unistd.h> #include <math.h> #include <sys/ioctl.h> #define DBG(fmt, ...) do { printf("%s line %d, "fmt"\n", __func__, __LINE__, ##__VA_ARGS__); } while (0) //extern unsigned int abcdef; int main(void) { void *p = malloc(256); //DBG("abcdef = 0x%x", abcdef); if(p == NULL) { DBG("malloc p is null."); } else { DBG("p = %p.", p); } free(p); //DBG("abcdef = 0x%x", abcdef); p = NULL; return 0; }
编译后运行,结果如下:
结果不出所料,当前的调用逻辑可以图形化表示如下,main.c直接调用到C库里面。
利用LD_PRELOAD加入HOOK:
创建一个新的wrapper.so文件,内部实现malloc/free,并保证函数原型和C库完全一致.
#define _GNU_SOURCE #include <stdio.h> #include <stddef.h> #include <stdint.h> #include <dlfcn.h> #define DBG(fmt, ...) do { printf("%s line %d, "fmt"\n", __func__, __LINE__, ##__VA_ARGS__); } while (0) unsigned int abcdef = 0; void *malloc(size_t size) { void *ret; static void* (*realmalloc)(size_t size) = NULL; if(realmalloc == NULL) { realmalloc = dlsym(RTLD_NEXT, "malloc"); } if(realmalloc == NULL) { return NULL; } //DBG("malloc"); ret = realmalloc(size); abcdef = 0xdeadbeef; return ret; } void free(void *p) { static void* (*realfree)(void* p) = NULL; if(realfree == NULL) { realfree = dlsym(RTLD_NEXT, "free"); } if(realfree == NULL) { return; } realfree(p); abcdef = 0xbeefdead; return; }
修改main.c,加上对abcdef变量的打印逻辑,目的是验证流程是否走到我们期望的HOOK库里面,经过测试,wrapper.c中如果添加打印语句,在运行时会出现段错误,所以这也是不得已而为之。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <stddef.h> #include <stdint.h> #include <fcntl.h> #include <unistd.h> #include <math.h> #include <sys/ioctl.h> #define DBG(fmt, ...) do { printf("%s line %d, "fmt"\n", __func__, __LINE__, ##__VA_ARGS__); } while (0) extern unsigned int abcdef; int main(void) { void *p = malloc(256); DBG("abcdef = 0x%x", abcdef); if(p == NULL) { DBG("malloc p is null."); } else { DBG("p = %p.", p); } free(p); DBG("abcdef = 0x%x", abcdef); p = NULL; return 0; }
编译wrapper.so,必须添加-ldl选项,否则会出现段错误,原因后面解释,也必须添加-fPIC,因为wrapper.so中有对全局变量的引用。
gcc --shared wrapper.c -o wrapper.so -ldl -fPIC
编译主函数,导出环境变量
$ gcc main.c -L./ wrapper.so $ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/caozilong/Workspace/hook $ LD_PRELOAD=./wrapper.so ./a.out
根据程序流程以及打印可以看出,主程序首先进入的是wrapper.c中的malloc/free实现,之后再通过后者调用C库中真正的malloc/free实现,由于中间多了一层中转,我们可以在此做手脚,加入一些调试,分析信息,解决具体的问题。图形表示如下:
如下图所示,如果编译wrapper.so不加入-ldl库,运行例子会出现段错误:
分析原因,很可能是dlsym函数在两种情况下,绑定的地址不同导致,出错的时候,绑定的是错误的地址。
而正常情况下,绑定到了GLIBC库中的符号:
在错误的情况下,dlsym绑定地址为0,导致执行出现段错误。