ptrace使用方法


基礎

操作系統通過一個叫做“系統調用”的標准機制來對上層提供服務,他們提供了一系列標准的API來讓上層應用程序獲取底層的硬件和服務,比如文件系統。當一個進程想要進行一個系統調用的時候,它會把該系統調用所需要用到的參數放到寄存器里,然后執行軟中斷指令0x80. 這個軟中斷就像是一個門,通過它就能進入內核模式,進入內核模式后,內核將會檢查系統調用的參數,然后執行該系統調用。

在 i386 平台下(本文所有代碼都基於 i386), 系統調用的編號會被放在寄存器 %eax 中,而系統調用的參數會被依次放到 %ebx,%ecx,%edx,%exi 和 %edi中,比如說,對於下面的系統調用:

write(2, "Hello", 5)

編譯后,它最后大概會被轉化成下面這樣子:

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

其中 $hello 指向字符串 "Hello"。

這里是基於32位系統的寄存器名稱,64位系統的寄存器名稱為rax,rbx

看完上面簡單的例子,現在我們來看看 ptrace 又是怎樣執行的。首先,我們假設進程 A 要 ptrace 進程 B。在 ptrace 系統調用真正開始前,內核會檢查一下我們將要 trace 的進程 B 是否當前已經正在被 traced 了,如果是,內核就會把該進程 B 停下來,並把控制權交給調用進程 A (任何時候,子進程只能被父進程這唯一一個進程所trace),這使得進程A有機會去檢查和修改進程B的寄存器的值。

ptrace 的使用流程一般是這樣的:父進程 fork() 出子進程,子進程中執行我們所想要 trace 的程序,在子進程調用 exec() 之前,子進程需要先調用一次 ptrace,以 PTRACE_TRACEME 為參數。這個調用是為了告訴內核,當前進程已經正在被 traced,當子進程執行 execve() 之后,子進程會進入暫停狀態,把控制權轉給它的父進程(SIG_CHLD信號), 而父進程在fork()之后,就調用 wait() 等子進程停下來,當 wait() 返回后,父進程就可以去查看子進程的寄存器或者對子進程做其它的事情了。

當系統調用發生時,內核會把當前的%eax中的內容(即系統調用的編號)保存到子進程的用戶態代碼段中(USER SEGMENT or USER CODE),我們可以像上面的例子那樣通過調用Ptrace(傳入PTRACE_PEEKUSER作為第一個參數)來讀取這個%eax的值,當我們做完這些檢查數據的事情之后,通過調用ptrace(PTRACE_CONT),可以讓子進程重新恢復運行。

#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>

#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
// #include <linux/user.h>
#include <sys/user.h>
#include <sys/reg.h>
#include <sys/syscall.h>
const int long_size = sizeof(long);
#define LONG_SIZE 8
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);
        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';
    // printf("getdata str=%s\n", str);
}
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) {
        memcpy(data.chars, laddr, j);
        ptrace(PTRACE_POKEDATA, child,
               addr + i * LONG_SIZE, data.val);
    }
}

int main(int argc, char *argv[])
{
    int fd = 0;
    char acBuf[4096] = {0};
    fd = open("./model", O_WRONLY | O_TRUNC);
    if (-1 == fd)
    {
        printf("open error!\n");
        return 0;
    }
    sprintf(acBuf, "test model");
    write(fd, acBuf, strlen(acBuf));
    close(fd);
}

int main(int argc, char *argv[])
{
    pid_t child;
    child = fork();
    if (child == 0)
    {
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        printf("before\n");
        execl("/bin/ls", "ls", NULL);
        printf("after\n");
    }
    else
    {
        long orig_eax;
        long params[3];
        int status;
        char *str, *laddr;
        int toggle = 0;
        printf("1\n");
        while (1)
        {
            wait(&status);
            printf("status=%d\n", status);
            if (WIFEXITED(status))
                break;
            struct user_regs_struct regs;
            ptrace(PTRACE_GETREGS,child,NULL,&regs);
            printf("Write called with %ld,%ld,%ld,%ld\n",regs.orig_rax,regs.rbx,regs.rcx,regs.rdx);
            orig_eax = ptrace(PTRACE_PEEKUSER,
                              child, LONG_SIZE * ORIG_RAX,
                              NULL);
            if (orig_eax == SYS_write)
            {
        printf("2\n");
                if (toggle == 0)
                {
                    toggle = 1;
                    params[0] = ptrace(PTRACE_PEEKUSER,
                                       child, LONG_SIZE * RBX,
                                       NULL);
                    params[1] = ptrace(PTRACE_PEEKUSER,
                                       child, LONG_SIZE * RSI,
                                       NULL);
                    params[2] = ptrace(PTRACE_PEEKUSER,
                                       child, LONG_SIZE * RDX,
                                       NULL);
                    printf("%d,%d,%d\n", params[0],params[1],params[2]);
                    str = (char *)calloc((params[2] + 1), sizeof(char));
                    getdata(child, params[1], str,
                            params[2]);
                    printf("str=%s\n", str);
                    reverse(str);
                    putdata(child, params[1], str,
                            params[2]);
                }
                else
                {
                    toggle = 0;
                }
            }
            ptrace(PTRACE_SYSCALL, child, NULL, NULL);
        }
    }
    return 0;
}

這里做的操作是,子進程會調用ls命令,而父進程會將子進程的結果反轉后輸出。

參考:https://www.cnblogs.com/catch/p/3476280.html

https://omasko.github.io/2018/04/19/ptrace%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0I/

https://www.linuxjournal.com/article/6100

https://www.linuxjournal.com/article/6210


免責聲明!

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



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