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

posted @ 2021-05-11 19:32  鸭子船长  阅读(1479)  评论(0编辑  收藏  举报