Linux Ptrace 詳解__Linux

來源:互聯網
上載者:User
一、系統調用

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

以write系統調用為例:

write(2,"Hello",5);

在32位系統中會轉換成:

movl $1,%eaxmovl $2,%ebxmovl $hello,%ecxmovl $5,%edxint $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 函數簡介

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

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);   }}

編譯後輸出:

The child made a system call 59user1@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

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

這樣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;}

編譯後輸出:

SYS_write call with 1, 140179049189376, 14總用量 28KSYS_write call return 14SYS_write call with 1, 140179049189376, 51-rw-rw-r-- 1 user1 user1  534  2月 26 18:02 ex1.cSYS_write call return 51SYS_write call with 1, 140179049189376, 52-rw-rw-r-- 1 user1 user1  534  2月 26 18:02 ex1.c~SYS_write call return 52SYS_write call with 1, 140179049189376, 53-rw-rw-r-- 1 user1 user1 1.1K  3月  2 13:02 hook2.cSYS_write call return 53SYS_write call with 1, 140179049189376, 54-rw-rw-r-- 1 user1 user1 1.1K  3月  2 13:02 hook2.c~SYS_write call return 54SYS_write call with 1, 140179049189376, 53-rwxrwxr-x 1 user1 user1 8.6K  3月  2 13:02 hook2.oSYS_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,&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;}

輸出:

user1@user-virtual-machine:~/hookTest$ ./hook3.oo.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,&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;}

先運行tracee.o 檔案

$  ./tracee.o

此時tracee.o輸出:

Hello,ptrace! [pid:14384]! num is 0Hello,ptrace! [pid:14384]! num is 1Hello,ptrace! [pid:14384]! num is 2Hello,ptrace! [pid:14384]! num is 3......

再另開啟一個shell運行attach.o檔案

$  ./.attach.o  14384 //pid

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

tracee:RIP:0x7f48b0394f20 INST: 0x3173fffff0013d48tracee:RIP:0x7f48b0394f23 INST: 0x8348c33100000000Press Enter to continue  tracee process

按任意鍵tracee.o恢複執行

參考:

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.