Linux 系统调用 Ptrace 详解
?
From:https://blog.csdn.net/u012417380/article/details/60470075
Ptrace 詳解:https://www.cnblogs.com/tangr206/articles/3094358.html
ptrace運行原理及使用詳解:https://blog.csdn.net/edonlii/article/details/8717029
?
ptrace 函數代碼分析
ptrace 函數深入分析:https://www.cnblogs.com/heixiang/p/10988992.html
Linux 源碼分析之 Ptrace:https://blog.csdn.net/u012417380/article/details/60468697
?
?
?
一、系統調用
?
操作系統提供一系列 系統調用?函數 來為應用程序提供服務。關于系統調用的詳細相關知識,可以查看 《程序員的自我修養》 第十二章。對于 x86 操作系統來說,用中斷命令 "int 0x80"?來進行系統調用,系統調用前,需要將系統調用號放入到 %EAX 寄存器中,將系統的參數依次放入到寄存器 %ebx、%ecx、%edx 以及 %esi 和 %edi 中。
以 write 系統調用 為例:
write(2,"Hello",5);在 32位系統中會轉換成:
movl $1,%eax movl $2,%ebx movl $hello,%ecx movl $5,%edx int $0x80其中 1 為 write 的系統調用號,所有的系統調用號定義在 unistd.h 文件中,$hello 表示字符串 "Hello"?的地址;
32位 Linux 系統通過 0x80 中斷來進行系統調用。
64位系統用戶應用層用整數寄存器 %rdi、%rsi、%rdx、%rcx、%r8 以及 %r9 來傳參。
而內核接口用 %rdi 、%rsi、%rdx、%r10、&r8 以及 %r10 來傳參,并且用 syscall 指令而不是 80 中斷進行系統調用。
x86 和 x64 都用寄存器 rax 來保存調用號和返回值。
?
?
二、ptrace 函數簡介
?
【 ptrace系統調用 】
功能描述:
提供父進程觀察和控制另一個進程執行的機制,同時提供查詢和修改另一進程的核心影像與寄存器的能力。主要用于執行斷點調試和系統調用跟蹤。父進程可通過調用fork,接著指定所產生的子進程的PTRACE_TRACEME行為,最后使用exec等操作來初始化一個進程跟蹤。可替代的做法是,父進程通過PTRACE_ATTACH請求跟蹤一個現存進程的執行。
當子進程被跟蹤時,每次接收到信號都會停止執行,即使不對信號進行處理(SIGKILL信號除外)。父進程下次執行wait調用時,會接收到核心的通告,并可能檢查和修改已停止的子進程。父進程使子進程繼續執行,并有可能忽略接收到的信號。
?
ptrace() 系統調用函數提供了一個進程(the “tracer”)監察和控制另一個進程(the “tracee”)的方法。并且可以檢查和改變“tracee”進程的內存和寄存器里的數據。它可以用來實現斷點調試和系統調用跟蹤。
tracee首先需要被附著到tracer。在多線程進程中,每個線程都可以被附著到一個tracer。ptrace命令總是以ptrace(PTARCE_foo,pid,..)的形式發送到tracee進程。pid是tracee線程ID。
當一個進程可以開始跟蹤進程通過調用fork函數創建子進程并讓子進程執行PTRACE_TRACEME,然后子進程再調用execve()(如果當前進程被ptrace,execve()成功執行后 SIGTRAP信號量會被發送到該進程)。一個進程也可以使用”PTRACE_ATTACH”或者”PTRACE_SEIZE”來跟蹤另一個進程。
當進程被跟蹤后,每當信號量傳來,甚至信號量會被忽略時,tracee會暫停。tracer會在下次調用waitpid(wstatus)(或者其它wait系統調用)處被通知。該調用會返回一個包含tracee暫停原因信息的狀態碼。當tracee暫停后,tracer可以使用一系列ptrace請求來查看和修改tracee中的信息。tracer接著可以讓tracee繼續執行。tracee傳遞給tracer中的信號量通常被忽略。
當PTRACE_O_TRACEEXEC項未起作用時,所有成功執行execve()的tracee進程會被發送一個 SIGTRAP信號量后暫停,在新程序執行之前,父進程將會取得該進程的控制權。
當tracer結束跟蹤后,可以通過調用 PTRACE_DETACH 繼續讓 tracee 執行。
prace更多相關信息可以查看: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);?
參數:
- request:請求執行的行為,可能選擇有
PTRACE_TRACEME //指示父進程跟蹤某個子進程的執行。任何傳給子進程的信號將導致其停止執行,同時父進程調用wait()時會得到通告。之后,子進程調用exec()時,核心會給它傳送SIGTRAP信號,在新程序開始執行前,給予父進程控制的機會。pid, addr, 和 data參數被忽略。以上是唯一由子進程使用的請求,剩下部分將由父進程使用的請求。
PTRACE_PEEKTEXT, PTRACE_PEEKDATA //從子進程內存空間addr指向的位置讀取一個字,并作為調用的結果返回。Linux內部對文本段和數據段不加區分,所以目前這兩個請求相等。data參數被忽略。
PTRACE_PEEKUSR //從子進程的用戶區addr指向的位置讀取一個字,并作為調用的結果返回。
PTRACE_POKETEXT, PTRACE_POKEDATA //將data指向的字拷貝到子進程內存空間由addr指向的位置。
PTRACE_POKEUSR //將data指向的字拷貝到子進程用戶區由addr指向的位置。
PTRACE_GETREGS, PTRACE_GETFPREGS //將子進程通用和浮點寄存器的值拷貝到父進程內由data指向的位置。addr參數被忽略。
PTRACE_GETSIGINFO //獲取導致子進程停止執行的信號信息,并將其存放在父進程內由data指向的位置。addr參數被忽略。
PTRACE_SETREGS, PTRACE_SETFPREGS //從父進程內將data指向的數據拷貝到子進程的通用和浮點寄存器。addr參數被忽略。
PTRACE_SETSIGINFO //將父進程內由data指向的數據作為siginfo_t結構體拷貝到子進程。addr參數被忽略。
PTRACE_SETOPTIONS //將父進程內由data指向的值設定為ptrace選項,data作為位掩碼來解釋,由下面的標志指定
PTRACE_O_TRACESYSGOOD //當轉發syscall陷阱(traps)時,在信號編碼中設置位7,即第一個字節的最高位。例如:SIGTRAP | 0x80。這有利于追蹤者識別一般的陷阱和那些由syscall引起的陷阱。
PTRACE_O_TRACEFORK //通過 (SIGTRAP | PTRACE_EVENT_FORK << 8) 使子進程下次調用fork()時停止其執行,并自動跟蹤開始執行時就已設置SIGSTOP信號的新進程。新進程的PID可以通過PTRACE_GETEVENTMSG獲取。
PTRACE_O_TRACEVFORK //通過 (SIGTRAP | PTRACE_EVENT_VFORK << 8) 使子進程下次調用vfork()時停止其執行,并自動跟蹤開始執行時就已設置SIGSTOP信號的新進程。新進程的PID可以通過PTRACE_GETEVENTMSG獲取。
PTRACE_O_TRACECLONE //通過 (SIGTRAP | PTRACE_EVENT_CLONE << 8) 使子進程下次調用clone()時停止其執行,并自動跟蹤開始執行時就已設置SIGSTOP信號的新進程。新進程的PID可以通過PTRACE_GETEVENTMSG獲取。
PTRACE_O_TRACEEXEC //通過 (IGTRAP | PTRACE_EVENT_EXEC << 8) 使子進程下次調用exec()時停止其執行。
PTRACE_O_TRACEVFORKDONE //通過 (SIGTRAP | PTRACE_EVENT_VFORK_DONE << 8) 使子進程下次調用exec()并完成時停止其執行。
PTRACE_O_TRACEEXIT //通過 (SIGTRAP | PTRACE_EVENT_EXIT << 8) 使子進程退出時停止其執行。子進程的退出狀態可通過PTRACE_GETEVENTMSG。
PTRACE_GETEVENTMSG //獲取剛發生的ptrace事件消息,并存放在父進程內由data指向的位置。addr參數被忽略。
PTRACE_CONT //重啟動已停止的進程。如果data指向的數據并非0,同時也不是SIGSTOP信號,將會作為傳遞給子進程的信號來解釋。那樣,父進程可以控制是否將一個信號發送給子進程。 addr參數被忽略。
PTRACE_SYSCALL, PTRACE_SINGLESTEP //如同PTRACE_CONT一樣重啟子進程的執行,但指定子進程在下個入口或從系統調用退出時,或者執行單個指令后停止執行,這可用于實現單步調試。addr參數被忽略。
PTRACE_SYSEMU, PTRACE_SYSEMU_SINGLESTEP //用于用戶模式的程序仿真子進程的所有系統調用。
PTRACE_KILL //給子進程發送SIGKILL信號,從而終止其執行。data,addr參數被忽略。
PTRACE_ATTACH //銜接到pid指定的進程,從而使其成為當前進程的追蹤目標。
PTRACE_DETACH //PTRACE_ATTACH的反向操作。 -
pid:目標進程標識。
-
addr:執行 peek 和 poke 操作的目標地址。
-
data:對于 poke 操作,存放數據的地方。對于 peek 操作,獲取數據的地方。
?
返回說明:
成功執行時,PTRACE_PEEK*請求返回所請求的數據,其它返回0。失敗返回-1,errno被設為以下的某個值。由于一個成功的PTRACE_PEEK*請求可能返回-1,決定錯誤是否發生前,調用者應檢查errno。
EBUSY:分配和釋放調試寄存器時出錯
EFAULT:讀寫不可訪問的內存空間
EINVAL:嘗試設置無效選項
EIO:請求無效,或者嘗試讀寫父子進程不可訪問的空間
EPERM:沒有權限追蹤指定的進程
ESRCH:指定的子進程不存在,或者當前正由調用者追蹤
?
?
三、示例
?
1. ptrace 追蹤子進程執行 exec()
#include <stdio.h> #include <unistd.h> #include <sys/ptrace.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/reg.h> /* For constants ORIG_RAX etc */ int main(){pid_t child;long orig_rax;child=fork();if(child==0){ptrace(PTRACE_TRACEME,0,NULL,NULL);execl("/bin/ls","ls",NULL);}else{wait(NULL);orig_rax = ptrace(PTRACE_PEEKUSER,child,8*ORIG_RAX,NULL);printf("The child made a system call %ld\n",orig_rax);ptrace(PTRACE_CONT,child,NULL,NULL);}}編譯后輸出:
The child made a system call 59 user1@user-virtual-machine:~/hookTest$ a.out attach.c~ ex1.c ex1.o ex2.c~ ex3.c ex3.o ex4.c~ victim.c~ attach.c attach.o ex1.c~ ex2.c ex2.o ex3.c~ ex4.c victim.c victim.oexecl()函數對應的系統調用為__NR_execve,系統調用值為59。父進程通過調用fork()來創建子進程。在子進程中,先運行patrce().請求參數設為PTRACE_TRACE,來告訴內核當前進程被父進程trace,每當有信號量傳遞到當前進程,該進程會暫停,提醒父進程在wait()調用處繼續執行。然后再調用execl()。當execl()函數成功執行后,新程序運行之前,SIGTRAP信號量會被發送到該進程,讓子進程停止,這時父進程會在wait相關調用處被通知,獲取子進程的控制權,可以查看子進程內存和寄存器相關信息。
當進程進行系統調用時,int會在內核棧中依次壓入用戶態的寄存器SS、ESP、EFLAGS、CS、EIP.中斷處理程序的SAVE_ALL宏會將 依次將EAX、EBP、EDI、ESI、EDX、ECX、EBX寄存器值壓入內核棧。調用ptrace(PTRACE_PEEKUSER,child,8*ORIG_RAX,NULL) 獲取USER area信息時<sys/reg.h>文件定義了與內核棧寄存器數組順序相同的下標:
#ifndef _SYS_REG_H #define _SYS_REG_H 1#ifdef __x86_64__ /* Index into an array of 8 byte longs returned from ptrace forlocation of the users' stored general purpose registers. */# define R15 0 # define R14 1 # define R13 2 # define R12 3 # define RBP 4 # define RBX 5 # define R11 6 # define R10 7 # define R9 8 # define R8 9 # define RAX 10 # define RCX 11 # define RDX 12 # define RSI 13 # define RDI 14 # define ORIG_RAX 15 # define RIP 16 # define CS 17 # define EFLAGS 18 # define RSP 19 # define SS 20 # define FS_BASE 21 # define GS_BASE 22 # define DS 23 # define ES 24 # define FS 25 # define GS 26 #else/* Index into an array of 4 byte integers returned from ptrace for* location of the users' stored general purpose registers. */# define EBX 0 # define ECX 1 # define EDX 2 # define ESI 3 # define EDI 4 # define EBP 5 # define EAX 6 # define DS 7 # define ES 8 # define FS 9 # define GS 10 # define ORIG_EAX 11 # define EIP 12 # define CS 13 # define EFL 14 # define UESP 15 # define SS 16 #endif這樣8*ORIG_RAX就找到USER area 中?ORIG_RAX?寄存器值的保存地址。ORIG_RAX保存了系統調用號。
當檢查完系統調用之后,可以調用ptrace并設置參數PTRACE_CONT讓子進程繼續進行。
?
?
2.讀取子進程系統調用參數
?
//64位下烏班圖程序#include <sys/ptrace.h> #include <sys/wait.h> #include <sys/reg.h> #include <sys/user.h> #include <sys/syscall.h> #include <stdio.h> int main(){pid_t child;long orig_rax;int status;int iscalling=0;struct user_regs_struct regs;child = fork();if(child==0){ptrace(PTRACE_TRACEME,0,NULL,NULL);execl("/bin/ls","ls","-l","-h",NULL);}else{while(1){wait(&status);if(WIFEXITED(status))break;orig_rax=ptrace(PTRACE_PEEKUSER,child,8*ORIG_RAX,NULL);if(orig_rax == SYS_write){ptrace(PTRACE_GETREGS,child,NULL,®s);if(!iscalling){iscalling =1;printf("SYS_write call with %lld, %lld, %lld\n",regs.rdi,regs.rsi,regs.rdx);} else{printf("SYS_write call return %lld\n",regs.rax);iscalling = 0;} }ptrace(PTRACE_SYSCALL,child,NULL,NULL);}}return 0; }編譯后輸出:
SYS_write call with 1, 140179049189376, 14 總用量 28K SYS_write call return 14 SYS_write call with 1, 140179049189376, 51 -rw-rw-r-- 1 user1 user1 534 2月 26 18:02 ex1.c SYS_write call return 51 SYS_write call with 1, 140179049189376, 52 -rw-rw-r-- 1 user1 user1 534 2月 26 18:02 ex1.c~ SYS_write call return 52 SYS_write call with 1, 140179049189376, 53 -rw-rw-r-- 1 user1 user1 1.1K 3月 2 13:02 hook2.c SYS_write call return 53 SYS_write call with 1, 140179049189376, 54 -rw-rw-r-- 1 user1 user1 1.1K 3月 2 13:02 hook2.c~ SYS_write call return 54 SYS_write call with 1, 140179049189376, 53 -rwxrwxr-x 1 user1 user1 8.6K 3月 2 13:02 hook2.o SYS_write call return 53可以看到ls -l -h 執行了六次SYS_write系統調用。
讀取寄存器中的參數時,可以使用PTRACE_PEEKUSER一個字一個字讀取,也可以使用PTRACE_GETREGS參數直接將寄存器的值讀取到結構體user_regs_struct 中,該結構體定義在sys/user.h中
對于PTRACE_STSCALL參數,該參數會像PTRACE_CONT一樣使暫停的子進程繼續執行,并在子進程下次進行系統調用前或系統調后,向子進程發送SINTRAP信號量,讓子進程暫停。
WIFEXITED函數(宏)函數用來檢查子進程是暫停還準備退出。
?
3.修改子進程系統調用參數
?
val = ptrace(PTRACE_PEEKDATA,child,addr,NULL)PTRACE_PEEKDATA、PTRACE_PEEKTEXT參數是在tracee內存的addr地址處讀取一個字(sizeof(long))的數據,反回值是long 型的,可多次讀取addr
+i*sizeof(long)然后再合并得到最終字符串的內容。
現在,我們對系統調用write 輸出的字符串參數進行反轉:
#include <sys/ptrace.h> #include <sys/wait.h> #include <sys/reg.h> #include <sys/syscall.h> #include <sys/user.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <stdlib.h> #define long_size sizeof(long)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);if(data.val == -1){if(errno){printf("READ error: %s\n",strerror(errno));}}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'; }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){ //注意:由于寫入時也是按字寫入的,所以正確的做法是先將該字的高地址數據讀出保存在data的高地址上 ,然后將該字再寫入memcpy(data.chars,laddr,j);ptrace(PTRACE_POKEDATA,child,addr +i*long_size,data.val);}}int main(){pid_t child;int status;struct user_regs_struct regs;child =fork();if(child ==0){ptrace(PTRACE_TRACEME,0,NULL,NULL);execl("/bin/ls","ls",NULL);}else{long orig_eax;char *str,*laddr;int toggle =0;while(1){wait(&status);if(WIFEXITED(status))break;orig_eax = ptrace(PTRACE_PEEKUSER,child,8*ORIG_RAX,NULL);if(orig_eax == SYS_write){if(toggle == 0){toggle =1;ptrace(PTRACE_GETREGS,child,NULL,®s);str=(char * )calloc((regs.rdx+1),sizeof(char));getdata(child,regs.rsi,str,regs.rdx);reverse(str);putdata(child,regs.rsi,str,regs.rdx);}else{toggle =0;}}ptrace(PTRACE_SYSCALL,child,NULL,NULL);}}return 0; }輸出:
user1@user-virtual-machine:~/hookTest$ ./hook3.o o.3kooh ~c.3kooh c.3kooh o.2kooh ~c.2kooh c.2kooh ~c.1xe c.1xe?
?
4. 向其它程序注入指令
我們追蹤其它獨立運行的進程時,需要使用下面的命令:
ptrace(PTRACE_ATTACH, pid, NULL, NULL)使pid進程成為被追蹤的tracee進程。tracee進程會被發送一個SIGTOP信號量,tracee進程不會立即停止,直到完成本次系統調用。如果要結束追蹤,則調用PTRACE_DETACH即可。
debug 設置斷點的功能可以通過ptrace實現。原理是ATTACH正在運行的進程使其停止。然后讀取該進程的指令寄存器IR(32位x86為EIP,64w的是RIP)內容所指向的指令,備份后替換成目標指令,再使其繼續執行,此時被追蹤進程就會執行我們替換的指令,運行完注入的指令之后,我們再恢復原進程的IR
,從而達到改變原程序運行邏輯的目的。
tracee進程代碼:
stdio.h>int main(){int i=0;while(1){printf("Hello,ptrace! [pid:%d]! num is %d\n",getpid(),i++);sleep(2);}return 0; }tracer進程代碼
#include<sys/ptrace.h> #include<sys/reg.h> #include<sys/wait.h> #include<sys/user.h> #include<stdlib.h> #include<errno.h> #include<string.h> #include<stdio.h>#define long_size sizeof(long)void getdata(pid_t child, long addr ,char * str,int len){char * laddr =str;int i,j;union u{long val;char chars [long_size] ;} data;i=0;j=len/long_size;while(i<j){data.val=ptrace(PTRACE_PEEKDATA,child,addr + long_size*i,NULL);if(data.val==-1){if(errno){printf("READ error: %s\n",strerror(errno));}}memcpy(laddr,data.chars,long_size);++i; laddr=laddr+long_size;}j= len %long_size;if(j!=0){data.val=ptrace(PTRACE_PEEKDATA,child,addr + long_size*i,NULL);if(data.val==-1){if(errno){printf("READ error: %s\n",strerror(errno));}}memcpy(laddr,data.chars,j);}str[len]='\0'; }void putdata(pid_t child , long addr,char * str,int len){char * laddr =str;int i,j;j=len/long_size;i=0;union u{long val;char chars [long_size] ;} data;while(i<j){memcpy(data.chars,laddr,long_size);ptrace(PTRACE_POKEDATA,child,addr + long_size*i,data.val);++i;laddr=laddr+long_size;}j=len%long_size;if(j!=0){data.val= ptrace(PTRACE_PEEKDATA,child,addr + long_size*i,NULL);if(data.val==-1){if(errno){printf("READ error: %s\n",strerror(errno));}}memcpy(data.chars,laddr,j);ptrace(PTRACE_POKEDATA,child,addr + long_size*i,data.val); } }int main(int argc,char * argv[]){if(argc!=2){printf("Usage: %s pid\n",argv[0]);}pid_t tracee = atoi(argv[1]);struct user_regs_struct regs;/*int 80(系統調用) int 3(斷點)*/unsigned char code[]={0xcd,0x80,0xcc,0x00,0,0,0,0}; //八個字節,等于long 型的長度char backup[8]; //備份讀取的指令ptrace(PTRACE_ATTACH,tracee,NULL,NULL);long inst; //用于保存指令寄存器所指向的下一條將要執行的指令的內存地址 wait(NULL);ptrace(PTRACE_GETREGS,tracee,NULL,®s);inst =ptrace(PTRACE_PEEKTEXT,tracee,regs.rip,NULL);printf("tracee:RIP:0x%llx INST: 0x%lx\n",regs.rip,inst);//讀取子進程將要執行的 7 bytes指令并備份getdata(tracee,regs.rip,backup,7);//設置斷點putdata(tracee,regs.rip,code,7);//讓子進程繼續執行并執行“int 3”斷點指令停止ptrace(PTRACE_CONT,tracee,NULL,NULL);wait(NULL);long rip=ptrace(PTRACE_PEEKUSER,tracee,8*RIP,NULL);//獲取子進程停止時,rip的值long inst2=ptrace(PTRACE_PEEKTEXT,tracee,rip,NULL);printf("tracee:RIP:0x%lx INST: 0x%lx\n",rip,inst2);printf("Press Enter to continue tracee process\n");getchar();putdata(tracee,regs.rip,backup,7); //重新將備份的指令寫回寄存器ptrace(PTRACE_SETREGS,tracee,NULL,®s);//設置會原來的寄存器值ptrace(PTRACE_CONT,tracee,NULL,NULL);ptrace(PTRACE_DETACH,tracee,NULL,NULL);return 0;}先運行tracee.o 文件
$ ./tracee.o此時tracee.o輸出:
Hello,ptrace! [pid:14384]! num is 0 Hello,ptrace! [pid:14384]! num is 1 Hello,ptrace! [pid:14384]! num is 2 Hello,ptrace! [pid:14384]! num is 3 ......再另打開一個shell運行attach.o文件
$ ./.attach.o 14384 //pid此時tracee.o執行到int 3斷點指令停止,attach1,o輸出:
tracee:RIP:0x7f48b0394f20 INST: 0x3173fffff0013d48 tracee:RIP:0x7f48b0394f23 INST: 0x8348c33100000000 Press Enter to continue tracee process按任意鍵tracee.o恢復執行
參考:http://www.cnblogs.com/pannengzhi/p/5203467.html
?
?
?
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的Linux 系统调用 Ptrace 详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 浅谈C语言指针
- 下一篇: Windows 10 使用 Kali L