利用LD_PRELOAD 分析HACK Linux用户态内存使用问题

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;
}

编译后运行,结果如下:

利用LD_PRELOAD 分析HACK Linux用户态内存使用问题

结果不出所料,当前的调用逻辑可以图形化表示如下,main.c直接调用到C库里面。

利用LD_PRELOAD 分析HACK Linux用户态内存使用问题

利用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

利用LD_PRELOAD 分析HACK Linux用户态内存使用问题

根据程序流程以及打印可以看出,主程序首先进入的是wrapper.c中的malloc/free实现,之后再通过后者调用C库中真正的malloc/free实现,由于中间多了一层中转,我们可以在此做手脚,加入一些调试,分析信息,解决具体的问题。图形表示如下:

利用LD_PRELOAD 分析HACK Linux用户态内存使用问题

为何不加-ldl会段错误?

 如下图所示,如果编译wrapper.so不加入-ldl库,运行例子会出现段错误:

利用LD_PRELOAD 分析HACK Linux用户态内存使用问题

分析原因,很可能是dlsym函数在两种情况下,绑定的地址不同导致,出错的时候,绑定的是错误的地址。

利用LD_PRELOAD 分析HACK Linux用户态内存使用问题

利用LD_PRELOAD 分析HACK Linux用户态内存使用问题

而正常情况下,绑定到了GLIBC库中的符号:

利用LD_PRELOAD 分析HACK Linux用户态内存使用问题

在错误的情况下,dlsym绑定地址为0,导致执行出现段错误。


结束!

上一篇:02-Hadoop完全分布式


下一篇:malloc函数