博文篇首要感谢我的同事zxb,他曾经提示我有这种简便的截获方法。
近期要做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.0nm显示virDomainCreateXML在virsh中是未解析的符号名,意味着,virsh中没有对应的实现,那必须在其他模块中实现否则都不会通过链接的步骤。可以猜测,virDomainCreateXML肯定在libvirt中实现(我看了源码,当然知道在这个so中实现。你不服,可在ldd virsh输出的所有动态库中搜索这个API)。
root@ubuntu:~# nm /usr/lib/libvirt.so.0 | grep virDomainCreateXML 000f7c40 T virDomainCreateXMLnm显示virDomainCreateXML的确在libvirt.so中实现。如此可以确定virsh以至于其他客户端工具,要使用libvirt提供的虚拟化操作,都依赖这个库的实现。咩哈哈哈哈哈,后面都用LD_PRELOAD来截获对该动态库的调用了,以此来实现对libvirt的审计。
2) 扯了这么久,还没怎么利用LD_PRELOAD。这不,思路很重要么,至少看到这知道了审计事件时,可以用这种方式去实现么。好,先放个链接:http://blog.csdn.net/haoel/article/details/1602108 这位博主详细介绍了什么是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是被调用过了