Linux Ptrace 詳解


轉 https://blog.csdn.net/u012417380/article/details/60470075

 

Linux Ptrace 詳解

一、系統調用

操作系統提供一系列系統調用函數來為應用程序提供服務。關於系統調用的詳細相關知識,可以查看<<程序員的自我修養》第十二章。
對於x86操作系統來說,用中斷命令“int 0x80”來進行系統調用,系統調用前,需要將系統調用號放入到%EAX寄存器中,將系統的參數依次放入到寄存器%ebx%ecx%edx以及%esi%edi中。

以write系統調用為例:

write(2,"Hello",5);
  • 1

在32位系統中會轉換成:

movl $1,%eax movl $2,%ebx movl $hello,%ecx movl $5,%edx int $0x80
  • 1
  • 2
  • 3
  • 4
  • 5

其中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 函數簡介

#include <sys/ptrace.h> long ptrace(enum _ptrace_request request,pid_t pid,void * addr ,void *data);
  • 1
  • 2
  • 3

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官方文檔。

三、示例

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); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

編譯后輸出:

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.o 
  • 1
  • 2
  • 3
  • 4

execl()函數對應的系統調用為__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 for location 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 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

這樣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,&regs); 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; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

編譯后輸出:

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 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

可以看到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)
  • 1

PTRACE_PEEKDATAPTRACE_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,&regs); 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; } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117

輸出:

user1@user-virtual-machine:~/hookTest$ ./hook3.o o.3kooh ~c.3kooh c.3kooh o.2kooh ~c.2kooh c.2kooh ~c.1xe c.1xe 
  • 1
  • 2
  • 3

4.向其它程序注入指令

我們追蹤其它獨立運行的進程時,需要使用下面的命令:

ptrace(PTRACE_ATTACH, pid, NULL, NULL)
  • 1

使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; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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,&regs); 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,&regs);//設置會原來的寄存器值 ptrace(PTRACE_CONT,tracee,NULL,NULL); ptrace(PTRACE_DETACH,tracee,NULL,NULL); return 0; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116

先運行tracee.o 文件

$  ./tracee.o
  • 1

此時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 ......
  • 1
  • 2
  • 3
  • 4
  • 5

再另打開一個shell運行attach.o文件

$  ./.attach.o  14384 //pid
  • 1

此時tracee.o執行到int 3斷點指令停止,attach1,o輸出:

tracee:RIP:0x7f48b0394f20 INST: 0x3173fffff0013d48 tracee:RIP:0x7f48b0394f23 INST: 0x8348c33100000000 Press Enter to continue tracee process 
  • 1
  • 2
  • 3
  • 4
  • 5

按任意鍵tracee.o恢復執行

參考:

http://www.cnblogs.com/pannengzhi/p/5203467.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM