天天看點

解決LD_PRELOAD無法截獲printf的問題

    前面博文​​ Linux下入門級導出函數截獲-使用LD_PRELOAD環境變量​​ 中說道用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接口(未驗證)

繼續閱讀