基礎
操作系統通過一個叫做“系統調用”的標准機制來對上層提供服務,他們提供了一系列標准的API來讓上層應用程序獲取底層的硬件和服務,比如文件系統。當一個進程想要進行一個系統調用的時候,它會把該系統調用所需要用到的參數放到寄存器里,然后執行軟中斷指令0x80. 這個軟中斷就像是一個門,通過它就能進入內核模式,進入內核模式后,內核將會檢查系統調用的參數,然后執行該系統調用。
在 i386 平台下(本文所有代碼都基於 i386), 系統調用的編號會被放在寄存器 %eax 中,而系統調用的參數會被依次放到 %ebx,%ecx,%edx,%exi 和 %edi中,比如說,對於下面的系統調用:
write(2, "Hello", 5)
編譯后,它最后大概會被轉化成下面這樣子:
movl $4, %eax movl $2, %ebx movl $hello,%ecx movl $5, %edx int $0x80
其中 $hello 指向字符串 "Hello"。
這里是基於32位系統的寄存器名稱,64位系統的寄存器名稱為rax,rbx
看完上面簡單的例子,現在我們來看看 ptrace 又是怎樣執行的。首先,我們假設進程 A 要 ptrace 進程 B。在 ptrace 系統調用真正開始前,內核會檢查一下我們將要 trace 的進程 B 是否當前已經正在被 traced 了,如果是,內核就會把該進程 B 停下來,並把控制權交給調用進程 A (任何時候,子進程只能被父進程這唯一一個進程所trace),這使得進程A有機會去檢查和修改進程B的寄存器的值。
ptrace 的使用流程一般是這樣的:父進程 fork() 出子進程,子進程中執行我們所想要 trace 的程序,在子進程調用 exec() 之前,子進程需要先調用一次 ptrace,以 PTRACE_TRACEME 為參數。這個調用是為了告訴內核,當前進程已經正在被 traced,當子進程執行 execve() 之后,子進程會進入暫停狀態,把控制權轉給它的父進程(SIG_CHLD信號), 而父進程在fork()之后,就調用 wait() 等子進程停下來,當 wait() 返回后,父進程就可以去查看子進程的寄存器或者對子進程做其它的事情了。
當系統調用發生時,內核會把當前的%eax中的內容(即系統調用的編號)保存到子進程的用戶態代碼段中(USER SEGMENT or USER CODE),我們可以像上面的例子那樣通過調用Ptrace(傳入PTRACE_PEEKUSER作為第一個參數)來讀取這個%eax的值,當我們做完這些檢查數據的事情之后,通過調用ptrace(PTRACE_CONT),可以讓子進程重新恢復運行。
#include<stdio.h> #include<unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <stdlib.h> #include <sys/ptrace.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> // #include <linux/user.h> #include <sys/user.h> #include <sys/reg.h> #include <sys/syscall.h> const int long_size = sizeof(long); #define LONG_SIZE 8 void reverse(char *str) { int i, j; char temp; for(i = 0, j = strlen(str) - 2; i <= j; ++i, --j) { temp = str[i]; str[i] = str[j]; str[j] = temp; } } void getdata(pid_t child, long addr, char *str, int len) { char *laddr; int i, j; union u { long val; char chars[long_size]; }data; i = 0; j = len / long_size; laddr = str; while(i < j) { data.val = ptrace(PTRACE_PEEKDATA, child, addr + i * LONG_SIZE, NULL); memcpy(laddr, data.chars, long_size); ++i; laddr += long_size; } j = len % long_size; if(j != 0) { data.val = ptrace(PTRACE_PEEKDATA, child, addr + i * LONG_SIZE, NULL); memcpy(laddr, data.chars, j); } str[len] = '\0'; // printf("getdata str=%s\n", str); } void putdata(pid_t child, long addr, char *str, int len) { char *laddr; int i, j; union u { long val; char chars[long_size]; }data; i = 0; j = len / long_size; laddr = str; while(i < j) { memcpy(data.chars, laddr, long_size); ptrace(PTRACE_POKEDATA, child, addr + i * LONG_SIZE, data.val); ++i; laddr += long_size; } j = len % long_size; if(j != 0) { memcpy(data.chars, laddr, j); ptrace(PTRACE_POKEDATA, child, addr + i * LONG_SIZE, data.val); } } int main(int argc, char *argv[]) { int fd = 0; char acBuf[4096] = {0}; fd = open("./model", O_WRONLY | O_TRUNC); if (-1 == fd) { printf("open error!\n"); return 0; } sprintf(acBuf, "test model"); write(fd, acBuf, strlen(acBuf)); close(fd); } int main(int argc, char *argv[]) { pid_t child; child = fork(); if (child == 0) { ptrace(PTRACE_TRACEME, 0, NULL, NULL); printf("before\n"); execl("/bin/ls", "ls", NULL); printf("after\n"); } else { long orig_eax; long params[3]; int status; char *str, *laddr; int toggle = 0; printf("1\n"); while (1) { wait(&status); printf("status=%d\n", status); if (WIFEXITED(status)) break; struct user_regs_struct regs; ptrace(PTRACE_GETREGS,child,NULL,®s); printf("Write called with %ld,%ld,%ld,%ld\n",regs.orig_rax,regs.rbx,regs.rcx,regs.rdx); orig_eax = ptrace(PTRACE_PEEKUSER, child, LONG_SIZE * ORIG_RAX, NULL); if (orig_eax == SYS_write) { printf("2\n"); if (toggle == 0) { toggle = 1; params[0] = ptrace(PTRACE_PEEKUSER, child, LONG_SIZE * RBX, NULL); params[1] = ptrace(PTRACE_PEEKUSER, child, LONG_SIZE * RSI, NULL); params[2] = ptrace(PTRACE_PEEKUSER, child, LONG_SIZE * RDX, NULL); printf("%d,%d,%d\n", params[0],params[1],params[2]); str = (char *)calloc((params[2] + 1), sizeof(char)); getdata(child, params[1], str, params[2]); printf("str=%s\n", str); reverse(str); putdata(child, params[1], str, params[2]); } else { toggle = 0; } } ptrace(PTRACE_SYSCALL, child, NULL, NULL); } } return 0; }
這里做的操作是,子進程會調用ls命令,而父進程會將子進程的結果反轉后輸出。
參考:https://www.cnblogs.com/catch/p/3476280.html
https://omasko.github.io/2018/04/19/ptrace%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0I/