今天咱們來點實戰(zhàn)的話題,對于前面我們研究的那么多知識,做一次總結.來看看這些節(jié)表之間是怎么聯(lián)系起來的.最后我們將經過一個簡單的攔截printf函數,修改它的參數,來輸出我指定的字符串,這樣就達到簡單的hook api的目的.好了廢話不多說.先看看test5.c里面的代碼.
#include <stdio.h>
#include <fcntl.h>
int main()
{
while (1)
{
getchar();
printf("hello");
fflush(stdout);
}
return 0;
}
這段代碼很簡單,敲一個回車就輸出一個hello,我們的目的就是實現,敲一個hello讓他輸出我們指定的字符串.
首先先運行這個main程序.在另一個終端里面輸入ps -a 查看main 的進程ID.一會我們用GDB手動修改的時候要attach 連接他的進程 ID.
$ ps -a
這里是獲取main 程序的進程空間的映射,找到未被使用的空間來寫入我們指定的字符串,這里使用的指令是:
cat /proc/3963/maps (3963為test5的pid)
這里我們選用7fca11c2f000-7fca11c33000 rw-p 00000000 00:00 0在下面我們會向這個地址空間.寫入我們需要的字符串.(還可以選擇7fca11e42000-7fca11e44000 rw-p 00000000 00:00 0 或者7fca11e5c000-7fca11e5d000 rw-p 00000000 00:00 0 )
這里我們上面只是做了個提前預報會使用這個來獲取未使用空間,下面我們開始通過手動方式來攔截printf這個函數的參數.
啟動我們的調試器GDB(建議使用sudo gdb而不是直接gdb啟動)
直接啟動gdb可能會因為權限問題無法attach(attach是GDB一種重要的debug模式,在MPI程序debug中發(fā)揮重要的作用洗出。)
各個區(qū)域段的情況
首先萬事都是從main開始,所以先查看main函數處的代碼,距離我們的目標又近了一步,看到printf函數的調用的地方了.根據源代碼的分析,這里是唯一一個使用printf的函數調用的地方.
(gdb) disassemble main
根據ELF文件格式,printf跳轉的地址是PLT表內,所以查看PLT內的printf地址處的代碼,可見這里jmp跳轉的地址就是根據前面介紹的GOT表了.那么理論上,就應該跳轉到printf函數內去執(zhí)行了.但是其實不是的.linux采用了一個叫"懶模式"的加載方式,當某個函數第一次被調用,它才會把真正的地址放到GOT表內去.
(gdb) x/10i 0x555e3b0005d0
所以我們查看下GOT表內的數據,發(fā)現這個地址很熟悉,這個0x555e3b0005db不正是plt表內的JMP下面的地址嘛.由此可見,當第一次加載函數的時候,其實GOT表內還沒有得到真正的函數地址,而是返回去支持PLT表內下面的之類,push跟jmp 然后push會把printf函數的相關偏移數據送入棧,jmp去尋找這個函數的地址,然后修改got表對應的地址處的值,這樣,當下次再調用printf的時候,那么GOT表內才是printf真正的函數地址
x/10xw 0x555e3b200fc0
首先我們對plt表內的0x555e3b0005d0地址下一個斷點
x/10b 0x555e3b0005d0
break *0x5579d2f305d0
然后讓程序繼續(xù)執(zhí)行.
continue
當你在main程序內輸入一個回車的時候,GDB就會收到信號通知,表示我們下的斷點起作用了.程序調用了printf函數.
Program received signal SIGTRAP, Trace/breakpoint trap.
這里查看下eip的值,把eip指向0x5579d2f305d0 printf@plt: jmp *0x5579d3130fc0
這條指令
info register rip
info register esp
x/10xw 0x7ffec7ba27e8