青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

elva

玩轉ptrace(一)

by Pradeep Padala
Created 2002-11-01 02:00
翻譯: Magic.D E-mail: adamgic@163.com
譯者序:在開發Hust Online Judge的過程中,查閱了不少資料,關于調試器技術的資料在網上是很少,即便是UNIX編程巨著《UNIX環境高級編程》中,相關內容也不多,直到我在http://www.linuxjournal.com上找到這篇文章,如獲至寶,特翻譯之,作為鄙人翻譯技術文檔的第一次嘗試,必定會有不少蹩腳之處,各位就將就一下吧,歡迎大力拍磚。

你想過怎么實現對系統調用的攔截嗎?你嘗試過通過改變系統調用的參數來愚弄你的系統kernel嗎?你想過調試器是如何使運行中的進程暫停并且控制它嗎?
你 可能會開始考慮怎么使用復雜的kernel編程來達到目的,那么,你錯了。實際上Linux提供了一種優雅的機制來完成這些:ptrace系統函數。 ptrace提供了一種使父進程得以監視和控制其它進程的方式,它還能夠改變子進程中的寄存器和內核映像,因而可以實現斷點調試和系統調用的跟蹤。
使用ptrace,你可以在用戶層攔截和修改系統調用(sys call)
在這篇文章中,我們將學習如何攔截一個系統調用,然后修改它的參數。在本文的第二部分我們將學習更先進的技術:設置斷點,插入代碼到一個正在運行的程序中;我們將潛入到機器內部,偷窺和纂改進程的寄存器和數據段。
 
基本知識
操 作系統提供了一種標準的服務來讓程序員實現對底層硬件和服務的控制(比如文件系統),叫做系統調用(system calls)。當一個程序需要作系統調用的時候,它將相關參數放進系統調用相關的寄存器,然后調用軟中斷0x80,這個中斷就像一個讓程序得以接觸到內核 模式的窗口,程序將參數和系統調用號交給內核,內核來完成系統調用的執行。
 
在i386體系中(本文中所有的代碼都是面向i386體系),系統調用號將放入%eax,它的參數則依次放入%ebx, %ecx, %edx, %esi 和 %edi。 比如,在以下的調用
 
       Write(2, “Hello”, 5)
 
的匯編形式大概是這樣的


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

這里的$hello指向的是標準字符串”Hello”。
 
那么,ptrace會在什么時候出現呢?在執行系統調用之前,內核會先檢查當前進程是否處于被“跟蹤”(traced)的狀態。如果是的話,內核暫停當前進程并將控制權交給跟蹤進程,使跟蹤進程得以察看或者修改被跟蹤進程的寄存器。
 
讓我們來看一個例子,演示這個跟蹤程序的過程

 

#include <sys/ptrace.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
  #include <linux/user.h> /* For constants
                                    ORIG_EAX etc */

 int main()
  {
    pid_t child;
     long orig_eax;
     child = fork();
      if(child == 0) {
         ptrace(PTRACE_TRACEME, 0, NULL, NULL);
         execl("/bin/ls", "ls", NULL);
     }
      else {
         wait(NULL);
         orig_eax = ptrace(PTRACE_PEEKUSER,
                           child, 4 * ORIG_EAX,
                           NULL);
         printf("The child made a "
                "system call %ld ", orig_eax);
         ptrace(PTRACE_CONT, child, NULL, NULL);
     }
     return 0;
 }

運行這個程序,將會在輸出ls命令的結果的同時,輸出:
 
The child made a system call 11
 
說明:11是execve的系統調用號,這是該程序調用的第一個系統調用。
想知道系統調用號的詳細內容,察看 /usr/include/asm/unistd.h。
 
在 以上的示例中,父進程fork出了一個子進程,然后跟蹤它。在調用exec函數之前,子進程用PTRACE_TRACEME作為第一個參數調用了 ptrace函數,它告訴內核:讓別人跟蹤我吧!然后,在子進程調用了execve()之后,它將控制權交還給父進程。當時父進程正使用wait()函數 來等待來自內核的通知,現在它得到了通知,于是它可以開始察看子進程都作了些什么,比如看看寄存器的值之類。
 
出現系統調用之后,內核會將eax中的值(此時存的是系統調用號)保存起來,我們可以使用PTRACE_PEEKUSER作為ptrace的第一個參數來讀到這個值。
我們察看完系統調用的信息后,可以使用PTRACE_CONT作為ptrace的第一個參數,調用ptrace使子進程繼續系統調用的過程。
 
ptrace函數的參數
 
Ptrace有四個參數
long ptrace(enum __ptrace_request request,
            pid_t pid,
            void *addr,
            void *data);
 
第一個參數決定了ptrace的行為與其它參數的使用方法,可取的值有:
PTRACE_ME
PTRACE_PEEKTEXT
PTRACE_PEEKDATA
PTRACE_PEEKUSER
PTRACE_POKETEXT
PTRACE_POKEDATA
PTRACE_POKEUSER
PTRACE_GETREGS
PTRACE_GETFPREGS,
PTRACE_SETREGS
PTRACE_SETFPREGS
PTRACE_CONT
PTRACE_SYSCALL,
PTRACE_SINGLESTEP
PTRACE_DETACH
在下文中將對這些常量的用法進行說明。
 
讀取系統調用的參數
 
通過將PTRACE_PEEKUSER作為ptrace 的第一個參數進行調用,可以取得與子進程相關的寄存器值。
 
先看下面這個例子

#include <sys/ptrace.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
 #include <linux/user.h>
  #include <sys/syscall.h> /* For SYS_write etc */
 
 int main()
  {
     pid_t child;
     long orig_eax, eax;
     long params[3];
     int status;
     int insyscall = 0;
     child = fork();
      if(child == 0) {
         ptrace(PTRACE_TRACEME, 0, NULL, NULL);
         execl("/bin/ls", "ls", NULL);
     }
      else {
         while(1) {
           wait(&status);
           if(WIFEXITED(status))
               break;
           orig_eax = ptrace(PTRACE_PEEKUSER,
                      child, 4 * ORIG_EAX, NULL);
            if(orig_eax == SYS_write) {
               if(insyscall == 0) {
                  /* Syscall entry */
                 insyscall = 1;
                 params[0] = ptrace(PTRACE_PEEKUSER,
                                    child, 4 * EBX,
                                    NULL);
                 params[1] = ptrace(PTRACE_PEEKUSER,
                                    child, 4 * ECX,
                                    NULL);
                 params[2] = ptrace(PTRACE_PEEKUSER,
                                    child, 4 * EDX,
                                    NULL);
                 printf("Write called with "
                        "%ld, %ld, %ld ",
                        params[0], params[1],
                        params[2]);
                 }
            else { /* Syscall exit */
                 eax = ptrace(PTRACE_PEEKUSER,
                              child, 4 * EAX, NULL);
                     printf("Write returned "
                            "with %ld ", eax);
                     insyscall = 0;
                 }
             }
             ptrace(PTRACE_SYSCALL,
                    child, NULL, NULL);
         }
     }
     return 0;
 }

這個程序的輸出是這樣的
 
ppadala@linux:~/ptrace > ls
a.out dummy.s ptrace.txt
libgpm.html registers.c syscallparams.c
dummy ptrace.html simple.c
ppadala@linux:~/ptrace > ./a.out
Write called with 1, 1075154944, 48
a.out dummy.s ptrace.txt
Write returned with 48
Write called with 1, 1075154944, 59
libgpm.html registers.c syscallparams.c
Write returned with 59
Write called with 1, 1075154944, 30
dummy ptrace.html simple.c
Write returned with 30
 

以上的例子中我們跟蹤了write系統調用,而ls命令的執行將產生三個write系統調用。使用PTRACE_SYSCALL作為ptrace的 第一個參數,使內核在子進程做出系統調用或者準備退出的時候暫停它。這種行為與使用PTRACE_CONT,然后在下一個系統調用/進程退出時暫停它是等 價的。
 
在前一個例子中,我們用PTRACE_PEEKUSER來察看write系統調用的參數。系統調用的返回值會被放入%eax。
 
wait函數使用status變量來檢查子進程是否已退出。它是用來判斷子進程是被ptrace暫停掉還是已經運行結束并退出。有一組宏可以通過status的值來判斷進程的狀態,比如WIFEXITED等,詳情可以察看wait(2) man。
 
讀取寄存器的值
 
如果你想在系統調用或者進程終止的時候讀取它的寄存器,使用前面那個例子的方法是可以的,但是這是笨拙的方法。使用PRACE_GETREGS作為ptrace的第一個參數來調用,可以只需一次函數調用就取得所有的相關寄存器值。
獲得寄存器值得例子如下:

 

#include <sys/ptrace.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
 #include <linux/user.h>
 #include <sys/syscall.h>
 
 int main()
  {
     pid_t child;
     long orig_eax, eax;
     long params[3];
     int status;
     int insyscall = 0;
     struct user_regs_struct regs;
     child = fork();
      if(child == 0) {
         ptrace(PTRACE_TRACEME, 0, NULL, NULL);
         execl("/bin/ls", "ls", NULL);
     }
      else {
         while(1) {
           wait(&status);
           if(WIFEXITED(status))
               break;
           orig_eax = ptrace(PTRACE_PEEKUSER,
                             child, 4 * ORIG_EAX,
                             NULL);
            if(orig_eax == SYS_write) {
                if(insyscall == 0) {
                   /* Syscall entry */
                  insyscall = 1;
                  ptrace(PTRACE_GETREGS, child,
                         NULL, &regs);
                  printf("Write called with "
                         "%ld, %ld, %ld ",
                         regs.ebx, regs.ecx,
                         regs.edx);
              }
               else { /* Syscall exit */
                  eax = ptrace(PTRACE_PEEKUSER,
                               child, 4 * EAX,
                               NULL);
                  printf("Write returned "
                         "with %ld ", eax);
                  insyscall = 0;
              }
           }
           ptrace(PTRACE_SYSCALL, child,
                  NULL, NULL);
        }
    }
    return 0;
 }

這段代碼與前面的例子是比較相似的,不同的是它使用了PTRACE_GETREGS。 其中的user_regs_struct結構是在<linux/user.h>中定義的。
 
 
來點好玩的
 
現在該做點有意思的事情了,我們將要把傳給write系統調用的字符串給反轉。

 

#include <sys/ptrace.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
 #include <linux/user.h>
 #include <sys/syscall.h>
 
 const int 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 * 4,
                           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 * 4,
                           NULL);
         memcpy(laddr, data.chars, j);
     }
     str[len] = '';
 }
 
 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 * 4, data.val);
         ++i;
         laddr += long_size;
     }
     j = len % long_size;
      if(j != 0) {
         memcpy(data.chars, laddr, j);
         ptrace(PTRACE_POKEDATA, child,
                addr + i * 4, data.val);
     }
 }
 
 int main()
  {
    pid_t child;
    child = fork();
     if(child == 0) {
       ptrace(PTRACE_TRACEME, 0, NULL, NULL);
       execl("/bin/ls", "ls", NULL);
    }
     else {
       long orig_eax;
       long params[3];
       int status;
       char *str, *laddr;
       int toggle = 0;
        while(1) {
          wait(&status);
          if(WIFEXITED(status))
              break;
          orig_eax = ptrace(PTRACE_PEEKUSER,
                            child, 4 * ORIG_EAX,
                            NULL);
           if(orig_eax == SYS_write) {
              if(toggle == 0) {
                toggle = 1;
                params[0] = ptrace(PTRACE_PEEKUSER,
                                   child, 4 * EBX,
                                   NULL);
                params[1] = ptrace(PTRACE_PEEKUSER,
                                   child, 4 * ECX,
                                   NULL);
                params[2] = ptrace(PTRACE_PEEKUSER,
                                   child, 4 * EDX,
                                   NULL);
                str = (char *)calloc((params[2]+1)
                                  * sizeof(char));
                getdata(child, params[1], str,
                        params[2]);
                reverse(str);
                putdata(child, params[1], str,
                        params[2]);
             }
              else {
                toggle = 0;
             }
          }
       ptrace(PTRACE_SYSCALL, child, NULL, NULL);
       }
    }
    return 0;
 }
輸出是這樣的:
 
ppadala@linux:~/ptrace > ls
a.out dummy.s ptrace.txt
libgpm.html registers.c syscallparams.c
dummy ptrace.html simple.c
ppadala@linux:~/ptrace > ./a.out
txt.ecartp s.ymmud tuo.a
c.sretsiger lmth.mpgbil c.llacys_egnahc
c.elpmis lmth.ecartp ymmud

這個例子中涵蓋了前面討論過的所有知識點,當然還有些新的內容。這里我們用PTRACE_POKEDATA作為第一個參數,以此來改變子進程中的變量值。它以與PTRACE_PEEKDATA相似的方式工作,當然,它不只是偷窺變量的值了,它可以修改它們。
 
單步
 
ptrace 提供了對子進程進行單步的功能。 ptrace(PTRACE_SINGLESTEP, …) 會使內核在子進程的每一條指令執行前先將其阻塞,然后將控制權交給父進程。下面的例子可以查出子進程當前將要執行的指令。為了便于理解,我用匯編寫了這個 受控程序,而不是讓你為c的庫函數到底會作那些系統調用而頭痛。
 
以下是被控程序的代碼 dummy1.s,使用gcc  –o dummy1 dummy1.s來編譯

 

.data
hello:
    .string "hello world\n"
.globl main
main:
    movl $4, %eax
    movl $2, %ebx
    movl $hello, %ecx
    movl $12, %edx
    int $0x80
    movl $1, %eax
    xorl %ebx, %ebx
    int $0x80
    ret

以下的程序則用來完成單步:

 

#include <sys/ptrace.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
 #include <linux/user.h>
 #include <sys/syscall.h>
 int main()
  {
     pid_t child;
     const int long_size = sizeof(long);
     child = fork();
      if(child == 0) {
         ptrace(PTRACE_TRACEME, 0, NULL, NULL);
         execl("./dummy1", "dummy1", NULL);
     }
      else {
         int status;
          union u {
             long val;
             char chars[long_size];
         }data;
         struct user_regs_struct regs;
         int start = 0;
         long ins;
          while(1) {
             wait(&status);
             if(WIFEXITED(status))
                 break;
             ptrace(PTRACE_GETREGS,
                    child, NULL, &regs);
              if(start == 1) {
                 ins = ptrace(PTRACE_PEEKTEXT,
                              child, regs.eip,
                              NULL);
                 printf("EIP: %lx Instruction "
                        "executed: %lx ",
                        regs.eip, ins);
             }
              if(regs.orig_eax == SYS_write) {
                 start = 1;
                 ptrace(PTRACE_SINGLESTEP, child,
                        NULL, NULL);
             }
             else
                 ptrace(PTRACE_SYSCALL, child,
                        NULL, NULL);
         }
     }
     return 0;
 }

程序的輸出是這樣的:
你可能需要察看Intel的用戶手冊來了解這些指令代碼的意思。
更復雜的單步,比如設置斷點,則需要很仔細的設計和更復雜的代碼才可以實現。
 
 
在第二部分,我們將會看到如何在程序中加入斷點,以及將代碼插入到已經在運行的程序中

posted on 2009-07-25 17:45 葉子 閱讀(1555) 評論(5)  編輯 收藏 引用 所屬分類: Unix

Feedback

# re: 玩轉ptrace(一)[未登錄] 2012-05-07 17:27 along

good job, guys  回復  更多評論   

# re: 玩轉ptrace(一) 2012-06-27 17:41 KIMBERLEYCRAWFORD

Do you understand that this is correct time to receive the <a href="http://goodfinance-blog.com">loan</a>, which can help you.   回復  更多評論   

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            欧美精品一区二区三区高清aⅴ| 亚洲成人在线网站| 亚洲欧美日韩中文在线制服| 亚洲精品在线三区| 亚洲二区在线观看| 欧美不卡在线视频| 欧美国产激情| 亚洲欧洲日本一区二区三区| 一本到12不卡视频在线dvd| 在线性视频日韩欧美| 在线中文字幕日韩| 亚洲欧美成人在线| 久久婷婷国产综合国色天香| 欧美激情中文字幕一区二区| 欧美日本高清| 国产日韩精品一区二区| 精品成人一区| av72成人在线| 久久久久免费观看| 亚洲日本成人| 久久超碰97人人做人人爱| 99视频精品全国免费| 亚洲一区精品视频| 久久久久久欧美| 亚洲精品自在在线观看| 日韩午夜在线电影| 亚洲激情在线| 亚洲永久免费av| 国模大胆一区二区三区| 日韩视频在线永久播放| 一区二区三区四区五区在线| 欧美一区1区三区3区公司| 久久精品国产99国产精品澳门| 免费亚洲电影在线| 国产酒店精品激情| 亚洲欧洲一区二区天堂久久| 午夜视频一区| 亚洲国产成人不卡| 亚洲精品黄色| 免费精品99久久国产综合精品| 国产精品乱看| 99av国产精品欲麻豆| 免费不卡在线观看av| 亚洲美女免费精品视频在线观看| 久久精品免费看| 国产老女人精品毛片久久| 亚洲精品久久久久久下一站| 久久精品导航| 亚洲女人小视频在线观看| 欧美日韩精品国产| 亚洲黄色在线| 欧美成人福利视频| 久久精品人人| 国产性色一区二区| 欧美一级理论性理论a| 日韩午夜免费视频| 欧美精品一区二区三区在线播放| 在线看国产一区| 久久视频在线看| 欧美在线3区| 国产精品免费在线| 亚洲一级网站| 亚洲图片欧洲图片av| 国产精品久久久久7777婷婷| 亚洲一区二区三区四区中文 | 欧美在线一区二区三区| 国产精品久久777777毛茸茸| 日韩视频在线一区| 亚洲国产精品久久精品怡红院| 久久精品国产96久久久香蕉| 国产精品v日韩精品| 日韩视频―中文字幕| 欧美国产丝袜视频| 91久久黄色| 亚洲欧洲视频| 欧美婷婷久久| 久久www成人_看片免费不卡| 欧美在现视频| 亚洲区中文字幕| 亚洲美女精品成人在线视频| 欧美日韩一区二区三区在线看 | 欧美激情一区二区三区在线| 亚洲乱码国产乱码精品精| 欧美mv日韩mv国产网站| 免费高清在线一区| 一本一本久久| 亚洲欧美日韩国产另类专区| 国产一区二区中文字幕免费看| 男人的天堂成人在线| 欧美人妖另类| 久久久国产精品一区| 蜜臀av性久久久久蜜臀aⅴ| 亚洲国内在线| 亚洲一区二区三区免费在线观看 | 国产精品视频一二| 久久久噜噜噜久久中文字幕色伊伊 | 亚洲三级电影在线观看| 国产精品大片免费观看| 久久九九免费视频| 欧美国产一区二区| 欧美在线观看日本一区| 欧美freesex8一10精品| 亚洲欧美日韩国产另类专区| 久久激情一区| 亚洲视频一区二区| 欧美一区影院| 在线综合亚洲| 蜜桃av噜噜一区二区三区| 亚洲欧美国产三级| 欧美成人日本| 久久久国产精品一区| 欧美视频网址| 亚洲国产成人精品女人久久久 | 一区二区91| 在线欧美一区| 亚洲男人第一av网站| 99国产一区| 久久精品一区二区| 午夜日韩在线| 国产一区亚洲一区| 国产麻豆综合| 欧美视频一区二区三区| 久久人人97超碰人人澡爱香蕉| 欧美日韩国产高清视频| 欧美成年人网| 国产一区二区三区在线观看视频 | 亚洲欧美日韩国产一区二区| 一二三四社区欧美黄| 每日更新成人在线视频| 久久天堂av综合合色| 国产免费成人在线视频| 亚洲天堂偷拍| 亚洲欧美日韩一区二区三区在线观看| 老鸭窝毛片一区二区三区| 久久精品国产视频| 国产精品一区久久| 亚洲视频图片小说| 亚洲婷婷在线| 欧美午夜片欧美片在线观看| 亚洲精品一区二区在线观看| 亚洲精品美女久久久久| 欧美黄色小视频| 亚洲精品一区在线观看香蕉| 亚洲美女淫视频| 欧美日韩一区二区三区四区在线观看| 亚洲人成亚洲人成在线观看| 亚洲大胆人体视频| 亚洲精品一区二区三区婷婷月 | 一本不卡影院| 国产午夜精品理论片a级大结局| 欧美三日本三级少妇三2023| 欧美精品自拍偷拍动漫精品| 欧美女同视频| 国产字幕视频一区二区| 伊人精品成人久久综合软件| 亚洲影院免费观看| 欧美大片第1页| 亚洲第一在线综合网站| 欧美a级片网站| 亚洲美女电影在线| 亚洲免费在线视频一区 二区| 欧美午夜不卡影院在线观看完整版免费 | 欧美1区视频| 亚洲人成网站精品片在线观看| 在线亚洲高清视频| 国产精品日韩精品| 久久久久国产精品麻豆ai换脸| 久热精品视频在线| 亚洲精品一区二区三区福利| 欧美日韩国产亚洲一区| 亚洲欧美一区二区精品久久久| 久久久精品2019中文字幕神马| 亚洲欧美日韩一区在线| 免费观看一区| 亚洲免费网站| 亚洲国产精品久久久久秋霞不卡| 国产精品少妇自拍| 亚洲视频一二区| 久久久久久一区二区| 亚洲精品国久久99热| 国产精品免费视频观看| 美女脱光内衣内裤视频久久影院 | 一区二区国产精品| 久久夜色精品国产| 亚洲欧美国产不卡| 亚洲国产精品久久久久秋霞蜜臀 | 国产精品视频不卡| 蜜臀av国产精品久久久久| 亚洲欧美电影在线观看| 亚洲国内精品在线| 久久久亚洲一区| 欧美伊人久久久久久午夜久久久久 | 欧美日本在线观看| 久久青草久久| 欧美在线观看视频一区二区| 日韩亚洲欧美在线观看| 免费在线亚洲| 久久综合狠狠综合久久激情| 久久av红桃一区二区小说| 午夜激情久久久|