認識ptrace函數
這是man對於ptrace這個系統調用的解釋
http://man7.org/linux/man-pages/man2/ptrace.2.html
#include <sys/ptrace.h>
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
ptrace系統調用提供了一種方法,這個方法可以讓一個進程監視、控制另一個進程的執行,並且可以查看和更改被追蹤進程的內存和寄存器。通常用來下斷點和調試。
常用的兩種追蹤的模式
ptrace(PTRACE_TRACEME, 0, 0, 0)
通常為被追蹤者使用,用來指示此進程將由其父進程跟蹤。下面的代碼示例演示了這個過程:
父進程fork出一個子進程。
子進程調用PTRACE_TRACEME,表明這個進程由它的父進程來跟蹤。任何發給這個進程的信號signal(除了SIGKILL)將導致該進程停止運行,而它的父進程會通過wait()獲得通知。另外,該進程之后所有對exec()的調用都將使操作系統產生一個SIGTRAP信號發送給它,這讓父進程有機會在新程序開始執行之前獲得對子進程的控制權。
然后我們通過父進程wait函數來等待接收子進程發出的信號,同時看看子進程的入口(_start函數)處有沒有初始化。
可以看到我們接收到的信號是SIGTRAP信號,正是執行exec()時產生的軟中斷(int3),子進程的入口處代碼也加載到了內存中。
ptrace(PTRACE_ATTACH, pid, NULL, NULL)
此追蹤模式將主動發送一個停止的信號給目標進程(ATTACH模式),使目標程序暫停下來,然后使用wait來等待目標程序停下來再做進一步操作。示例代碼如下,同樣是來查看wait到的信號以及入口函數是否被加載到內存。
可以看到我們接受到了兩個暫停的信號,第一個就是我們attach時主動發送的信號造成目標進程的停止,然后通知我們Stopped,此時,入口代碼還沒有加載到內存。第二個就是執行exec()時被系統軟中斷的信號,和PTRACE_ME相同,此時入口代碼已被加載到內存。
弄清楚了這兩種常用的追蹤模式后,我們來看看如何使用ptrace來查看、修改目標進程的內存和寄存器
user_regs_struct是在ptrace.h中定義的一個結構體,用來存儲各個寄存器的值,使用時通常會聲明一個全局變量struct user_regs_struct regs; 當我們可以追蹤進程之后,使用ptrace(PTRACE_GETREGS, child_pid, NULL, ®s);將此刻目標進程的寄存器信息存儲到這個regs結構體,regs.eip查看eip的值,64位就是regs.rip啦。
若要修改eip的值,只需更改regs.eip的值,然后ptrace(PTRACE_SETREGS, child_pid, NULL, ®s) 系統就會將目標程序的eip更改過來。
然后查看內存中某地址的值用ptrace(PTRACE_PEEKTEXT, child_pid, addr, NULL)
修改內存的值用ptrace(PTRACE_POKETEXT, child_pid, addr, text)
然后就是讓程序繼續運行和單步了。我們使用wait等到目標程序中斷,接着完成了我們想進行的操作后,使用ptrace(PTRACE_CONT, child_pid, NULL, NULL)來使目標進程恢復運行,想等到它再次中斷,應使用wait等待。
使用ptrace(PTRACE_SINGLESTEP, child_pid, NULL, NULL)來進行單步調試。父進程通過PTRACE_SINGLESTEP以及子進程的id號來調用ptrace。這么做是告訴操作系統——請重新啟動子進程,但當子進程執行了下一條指令后再將其停止。然后父進程再次wait()等待子進程的停止。
關於ptrace還有很多其他使用姿勢,本文僅是介紹最常用的幾種。