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实现,由于中间多了一层中转,我们可以在此做手脚,加入一些调试,分析信息,解决具体的问题。图形表示如下:
为何不加-ldl会段错误?
如下图所示,如果编译wrapper.so不加入-ldl库,运行例子会出现段错误:
分析原因,很可能是dlsym函数在两种情况下,绑定的地址不同导致,出错的时候,绑定的是错误的地址。
而正常情况下,绑定到了GLIBC库中的符号:
在错误的情况下,dlsym绑定地址为0,导致执行出现段错误。