红联Linux门户
Linux帮助

解决LD_PRELOAD无法截获printf的问题

发布时间:2016-12-29 10:13:26来源:linux网站作者:Yuri800
前面 Linux下入门级导出函数截获-使用LD_PRELOAD环境变量(http://www.linuxdiyf.com/linux/18310.html) 中说道用LD_PRELOAD的方法截获动态库中的函数,有人问我不能截获printf,我就在此文中回答这个问题吧。
 
首先看下他写的用于拦截的代码和测试代码
拦截代码  
#include <unistd.h>  
extern void printf(const char *format,...);  
void printf(const char *format,...)  
{  
write(0,"abc",4);  
return;  
}
编译及连接命令  
gcc -g3 -O0 -shared -fPIC -o fake.so fake.c  
测试代码  
#include <stdio.h>  
#include <unistd.h>  
int main()  
{  
printf("123\n");  
while(1)  
sleep(1);  
return 0;  
}  
gcc -g3 -O0 -o test test.c
 
结果程序输出为:
[root@localhost Desktop]# export LD_PRELOAD=/root/Desktop/fake.so   
[root@localhost Desktop]# ./test   
123
果然不尽人意,跟没有拦截似得。
 
出现这种情况,可以猜测有以下几种情况:
1).动态库导出的函数名和被调用的函数名不一致,比如用g++编译生成的动态库就可能出现这种情况。但是这里并非如此,用nm查看一下动态库导出的函数名:
[root@localhost Desktop]# nm -Do fake.so  
fake.so:                 w _Jv_RegisterClasses  
fake.so:00000000002008a0 A __bss_start  
fake.so:                 w __cxa_finalize  
fake.so:                 w __gmon_start__  
fake.so:00000000002008a0 A _edata  
fake.so:00000000002008b0 A _end  
fake.so:0000000000000648 T _fini  
fake.so:0000000000000460 T _init  
fake.so:000000000000057c T printf  
fake.so:                 U write
好吧,看似没问题,看看是不是其他情况。
 
2).动态库没有注入到进程空间。Linux下可以通过/proc文件系统查看进程加载的动态库,如下:
[root@localhost ~]# ps x|grep test  
3879 pts/12   S+     0:00 ./test  
3895 pts/13   S+     0:00 grep test  
[root@localhost ~]# cat /proc/3879/maps  
00400000-00401000 r-xp 00000000 fd:00 132597                             /root/Desktop/test  
00600000-00601000 rw-p 00000000 fd:00 132597                             /root/Desktop/test  
3326400000-3326420000 r-xp 00000000 fd:00 75830                          /lib64/ld-2.12.so  
332661f000-3326620000 r--p 0001f000 fd:00 75830                          /lib64/ld-2.12.so  
3326620000-3326621000 rw-p 00020000 fd:00 75830                          /lib64/ld-2.12.so  
3326621000-3326622000 rw-p 00000000 00:00 0   
3326c00000-3326d8b000 r-xp 00000000 fd:00 75831                          /lib64/libc-2.12.so  
3326d8b000-3326f8a000 ---p 0018b000 fd:00 75831                          /lib64/libc-2.12.so  
3326f8a000-3326f8e000 r--p 0018a000 fd:00 75831                          /lib64/libc-2.12.so  
3326f8e000-3326f8f000 rw-p 0018e000 fd:00 75831                          /lib64/libc-2.12.so  
3326f8f000-3326f94000 rw-p 00000000 00:00 0   
7f0465071000-7f0465074000 rw-p 00000000 00:00 0   
7f0465081000-7f0465082000 rw-p 00000000 00:00 0   
7f0465082000-7f0465083000 r-xp 00000000 fd:00 131339                     /root/Desktop/fake.so  
7f0465083000-7f0465282000 ---p 00001000 fd:00 131339                     /root/Desktop/fake.so  
7f0465282000-7f0465283000 rw-p 00000000 fd:00 131339                     /root/Desktop/fake.so  
7f0465283000-7f0465284000 rw-p 00000000 00:00 0   
7fff2c239000-7fff2c24e000 rw-p 00000000 00:00 0   [stack]  
7fff2c34b000-7fff2c34c000 r-xp 00000000 00:00 0   [vdso]  
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0   [vsyscall]
从此处可以看到fake.so在目标进程的地址空间中,换句话说就是成功的注入了。那会是什么情况?
 
3).目标进程没有调用目标接口,即printf。来看一下test的符号信息:
root@localhost Desktop]# nm -Do test  
test:                 w __gmon_start__  
test:                 U __libc_start_main  
test:                 U puts  
test:                 U sleep
test并没有调用printf,但从输出结果看,test调用了puts~什么情况?
 
其实,这是gcc干的好事,用内建函数puts代替printf。这是一种优化。那怎么解决这个问题?
有2种解决办法:
1)禁止gcc使用内建函数,或者指定gcc连接指定函数,方法如下:
gcc -g3 -O0 -o nbi_test nbi_test.c -fno-builtin-printf  
这样生成的nbi_test其printf不会被puts代替,因此能有正确的执行结果:
[root@localhost Desktop]# nm -Do nbi_test  
nbi_test:                 w __gmon_start__  
nbi_test:                 U __libc_start_main  
nbi_test:                 U printf  
nbi_test:                 U sleep  
[root@localhost Desktop]# export LD_PRELOAD=/root/Desktop/fake.so   
[root@localhost Desktop]# ./nbi_test   
abc
2)截获puts接口(未验证)
 
本文永久更新地址:http://www.linuxdiyf.com/linux/27349.html