红联Linux门户
Linux帮助

Linux下入门级导出函数截获-使用LD_PRELOAD环境变量

发布时间:2016-02-23 10:31:22来源:linux网站作者:Yuri800

近期要做Linux下libvirt事件审计,原计划是分析libvirt的通信数据从而进一步分析libvirt事件。这怎么看都觉得工作量浩大,第一反应就是能不能偷懒。对于一般的审计事件,首先想到的是函数截获:遍历ELF文件的导出函数,然后替换之。顺带一提,现在安卓上的进程注入就这么做的,哪天有空了我也放一篇Linux ELF文件注入的文章。不过这个工程也不小(相比原计划已经很小了),而且我也没十足的把握稳定代码。于是,退而求其次想到标题中这种方法,此法,也是首次尝试。


1) 先来整理下当时的处理思路:libvirt提供了一个命令行客户端工具--virsh,通过virsh可以使用libvirt提供的功能,如创建域。首先,可以肯定的是libvirt是个成功的项目,所以,客户端代码肯定和实际虚拟化操作是分开的,他们的唯一结合处一定是调用libvirt某个动态库导出的函数,来找一下他们的契合处:

root@ubuntu:~# which virsh 
/usr/local/bin/virsh 
root@ubuntu:~# ldd /usr/local/bin/virsh 
linux-gate.so.1 =>  (0xb770d000) 
libvirt.so.0 => /usr/lib/libvirt.so.0 (0xb743d000) 
libvirt-qemu.so.0 => /usr/lib/libvirt-qemu.so.0 (0xb7439000) 
libpthread.so.0 => /lib/i386-linux-gnu/libpthread.so.0 (0xb741d000) 

可以看到virsh依赖于libvirt.so这个动态库。再来看看这个动态库是否提供了virsh命令行所需要的接口:

virsh创建域会调用virDomainCreateXML这个API,来看下virDomainCreateXML在virsh中的符号形式:

root@ubuntu:~# nm /usr/local/bin/virsh|grep virDomainCreateXML 
U virDomainCreateXML@@LIBVIRT_0.5.0

nm显示virDomainCreateXML在virsh中是未解析的符号名,意味着,virsh中没有对应的实现,那必须在其他模块中实现否则都不会通过链接的步骤。可以猜测,virDomainCreateXML肯定在libvirt中实现(我看了源码,当然知道在这个so中实现。你不服,可在ldd virsh输出的所有动态库中搜索这个API)。

root@ubuntu:~# nm /usr/lib/libvirt.so.0 | grep virDomainCreateXML 
000f7c40 T virDomainCreateXML 

nm显示virDomainCreateXML的确在libvirt.so中实现。如此可以确定virsh以至于其他客户端工具,要使用libvirt提供的虚拟化操作,都依赖这个库的实现。后面都用LD_PRELOAD来截获对该动态库的调用了,以此来实现对libvirt的审计。


2) 扯了这么久,还没怎么利用LD_PRELOAD。这不,思路很重要么,至少看到这知道了审计事件时,可以用这种方式去实现么。好,先放个链接:http://www.linuxdiyf.com/linux/18311.html,这篇文章详细介绍了什么是LD_PRELOAD以及如何利用。但是,文章只做了一半,还有一半没做:就是,你把水截流了,还得把水放到下游去啊,要不然下游人民怎么生活!光简单粗暴的截获了strcmp,然后return 0这不是改的strcmp他妈都不认识他了么?为了实现strcmp的原有功能,这里我用到了dlopen/dlsym,用来加载动态库和获得导出函数。类似于windows的LoadLibrary和GetProcAddr。值得一提的事,linux的进程加载器也是使用这两个函数加载所依赖的so库。

下面来看下我对那篇文章代码的扩充:

hack.c 
#define _GNU_SOURCE 
#include <stdio.h> 
#include <string.h> 
#include <dlfcn.h> 
typedef int (*detour_strcmp)(const char*,const char*); 
detour_strcmp g_Strcmp; 
int strcmp(const char *s1, const char *s2) 

int res=0; 
printf("hack function invoked. s1=<%s> s2=<%s>\n", s1, s2); 
g_Strcmp = dlsym(RTLD_NEXT, "strcmp"); 
if(g_Strcmp==NULL) 

printf("load strcmp failed\n"); 
return 0; 

res = (*g_Strcmp)(s1,s2); 
printf("compare result:%d\n",res); 
return res; 

因为使用了dlopen和dlsym编译连接时,需要加上-ldl

gcc -shared -o hack.so hack.c -ldl 

最后,来看下新strcmp的输出:

root@ubuntu:~/Desktop# export LD_PRELOAD="./hack.so" 
root@ubuntu:~/Desktop# ./verifypasswd  
usage: ./verifypasswd <password>/nroot@ubuntu:~/Desktop# ./verifypasswd 1 
hack function invoked. s1=<password> s2=<1>/ncompare result:1 
Invalid Password!/n 

输出无效的密码!至少可以知道strcmp是被调用过了。


本文永久更新地址:http://www.linuxdiyf.com/linux/18310.html