• <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>

            elva

            玩轉(zhuǎn)ptrace(一)

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

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


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

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

             

            #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命令的結(jié)果的同時,輸出:
             
            The child made a system call 11
             
            說明:11是execve的系統(tǒng)調(diào)用號,這是該程序調(diào)用的第一個系統(tǒng)調(diào)用。
            想知道系統(tǒng)調(diào)用號的詳細內(nèi)容,察看 /usr/include/asm/unistd.h。
             
            在 以上的示例中,父進程fork出了一個子進程,然后跟蹤它。在調(diào)用exec函數(shù)之前,子進程用PTRACE_TRACEME作為第一個參數(shù)調(diào)用了 ptrace函數(shù),它告訴內(nèi)核:讓別人跟蹤我吧!然后,在子進程調(diào)用了execve()之后,它將控制權(quán)交還給父進程。當時父進程正使用wait()函數(shù) 來等待來自內(nèi)核的通知,現(xiàn)在它得到了通知,于是它可以開始察看子進程都作了些什么,比如看看寄存器的值之類。
             
            出現(xiàn)系統(tǒng)調(diào)用之后,內(nèi)核會將eax中的值(此時存的是系統(tǒng)調(diào)用號)保存起來,我們可以使用PTRACE_PEEKUSER作為ptrace的第一個參數(shù)來讀到這個值。
            我們察看完系統(tǒng)調(diào)用的信息后,可以使用PTRACE_CONT作為ptrace的第一個參數(shù),調(diào)用ptrace使子進程繼續(xù)系統(tǒng)調(diào)用的過程。
             
            ptrace函數(shù)的參數(shù)
             
            Ptrace有四個參數(shù)
            long ptrace(enum __ptrace_request request,
                        pid_t pid,
                        void *addr,
                        void *data);
             
            第一個參數(shù)決定了ptrace的行為與其它參數(shù)的使用方法,可取的值有:
            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
            在下文中將對這些常量的用法進行說明。
             
            讀取系統(tǒng)調(diào)用的參數(shù)
             
            通過將PTRACE_PEEKUSER作為ptrace 的第一個參數(shù)進行調(diào)用,可以取得與子進程相關(guān)的寄存器值。
             
            先看下面這個例子

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

             

            #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結(jié)構(gòu)是在<linux/user.h>中定義的。
             
             
            來點好玩的
             
            現(xiàn)在該做點有意思的事情了,我們將要把傳給write系統(tǒng)調(diào)用的字符串給反轉(zhuǎn)。

             

            #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

            這個例子中涵蓋了前面討論過的所有知識點,當然還有些新的內(nèi)容。這里我們用PTRACE_POKEDATA作為第一個參數(shù),以此來改變子進程中的變量值。它以與PTRACE_PEEKDATA相似的方式工作,當然,它不只是偷窺變量的值了,它可以修改它們。
             
            單步
             
            ptrace 提供了對子進程進行單步的功能。 ptrace(PTRACE_SINGLESTEP, …) 會使內(nèi)核在子進程的每一條指令執(zhí)行前先將其阻塞,然后將控制權(quán)交給父進程。下面的例子可以查出子進程當前將要執(zhí)行的指令。為了便于理解,我用匯編寫了這個 受控程序,而不是讓你為c的庫函數(shù)到底會作那些系統(tǒng)調(diào)用而頭痛。
             
            以下是被控程序的代碼 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的用戶手冊來了解這些指令代碼的意思。
            更復雜的單步,比如設(shè)置斷點,則需要很仔細的設(shè)計和更復雜的代碼才可以實現(xiàn)。
             
             
            在第二部分,我們將會看到如何在程序中加入斷點,以及將代碼插入到已經(jīng)在運行的程序中

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

            Feedback

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

            good job, guys  回復  更多評論   

            # re: 玩轉(zhuǎn)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.   回復  更多評論   

            久久亚洲精品国产亚洲老地址| 亚洲狠狠久久综合一区77777| 国产成人综合久久精品红| 99久久国产主播综合精品| 一日本道伊人久久综合影| 免费无码国产欧美久久18| 91精品国产色综久久| 亚洲精品99久久久久中文字幕| 狠狠色婷婷久久一区二区| 国产999精品久久久久久| 国产精品中文久久久久久久| 婷婷综合久久狠狠色99h| 99久久精品国产一区二区| 精品久久久久久久中文字幕| 俺来也俺去啦久久综合网| 欧美日韩久久中文字幕| 久久久久久毛片免费看| 久久免费精品视频| 久久精品国产精品亚洲| 国产精品久久永久免费| 99久久综合狠狠综合久久| 婷婷伊人久久大香线蕉AV| 久久精品国产亚洲欧美| 99久久久精品| 热综合一本伊人久久精品| 天天影视色香欲综合久久| 久久精品毛片免费观看| 久久精品国产99久久丝袜| 18禁黄久久久AAA片| 久久99国产综合精品| 日韩美女18网站久久精品| 精品免费tv久久久久久久| 欧美精品一区二区久久| 69久久夜色精品国产69| 午夜视频久久久久一区 | 日韩一区二区久久久久久| 亚洲Av无码国产情品久久| 久久久久久久尹人综合网亚洲| 久久丝袜精品中文字幕| 7国产欧美日韩综合天堂中文久久久久| 少妇熟女久久综合网色欲|