Home > System Tutorial > LINUX > body text

ptrace under Linux: a powerful process tracking and control mechanism

WBOY
Release: 2024-02-09 20:57:24
forward
852 people have browsed it

ptrace is a special system call in Linux systems, which allows one process to track and control the execution of another process, including reading and writing its memory, registers, signals, etc. ptrace is very powerful and can be used to implement tools such as debuggers, tracers, injectors, etc. But, do you really understand how ptrace works? Do you know how to use ptrace under Linux? Do you know the advantages and disadvantages of ptrace? This article will introduce you to the relevant knowledge of ptrace under Linux in detail, allowing you to better use and understand this powerful process tracking and control mechanism under Linux.

Linux 下的 ptrace:一种强大的进程跟踪和控制机制

ptrace provides the ability for one process to control another process, including detecting and modifying the code, data, and registers of the controlled process, thereby enabling the functions of setting breakpoints, injecting code, and tracking system calls.

Here, the process using the ptrace function is called tracer, and the controlled process is called tracee.

Use the ptrace function to intercept system calls

The operating system provides standard APIs to the upper layer to perform operations that interact with the underlying hardware. These standard APIs are called system calls. Each system call has a call number, which can be queried in unistd.h. When the process triggers a system call, it puts the parameters into the register, then enters kernel mode through soft interrupts, and executes the code of the system call through the kernel.

In the The parameters are stored in ebx, ecx, edx, esi in turn

For example, the system call executed by console printing is

write(1,"Hello",5)
Copy after login

is translated into assembly code as

mov rax, 1
mov rdi, message
mov rdx, 5
syscall
message:
db "Hello"
Copy after login

When executing a system call, the kernel first detects whether a process is tracee. If so, the kernel will pause the process and then transfer control to tracer. After that, tracer can view or modify tracee's registers.

Sample code is as follows

#include 
#include 
#include 
#include 
#include 
#include 
#include 

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

//输出:the child made a system call 59
Copy after login

This program creates a subprocess that we will trace (trace) through fork. Before executing execl, the subprocess informs the kernel that it will be traced through the PTRACE_TRACEME parameter of the ptrace function.

For execl, this function will actually trigger the execve system call. At this time, the kernel finds that the process is tracee, then suspends it, and sends a signal to wake up the waiting tracer (the main thread in this program).

When a system call is triggered, the kernel will save the contents of the rax register holding the call number in orig_rax, which we can read through the PTRACE_PEEKUSER parameter of ptrace.

ORIG_RAX is the register number, which is stored in sys/reg.h. In a 64-bit system, each register has a size of 8 bytes, so 8*ORIG_RAX is used here to obtain the register address.

After we obtain the system call number, we can wake up the suspended child process through the PTRACE_CONT parameter of ptrace and let it continue execution.

ptrace parameters

long ptrace(enum __ptrace_request request,pid_t pid,void addr, void *data);
Copy after login

Parameter request controls the behavior of the ptrace function and is defined in sys/ptrace.h.

Parameter pid specifies the process number of tracee.

The above two parameters are required, and the next two parameters are address and data respectively, and their meaning is controlled by the parameter request.

For the values ​​and meanings of specific request parameters, please view the help documentation (console input: man ptrace)

Pay attention to the return value. The man manual states that the data size of a word is returned, which is 4 bytes on a 32-bit machine and 8 bytes on a 64-bit machine. They all correspond to the length of a long. You can find many irresponsible posts on Baidu saying that returning one byte of data is wrong!

Read system call parameters

Through the PTRACE_PEEKUSER parameter of ptrace, we can view the contents of the USER area, such as the value of the register. The USER area is a structure (user structure defined in sys/user.h).

The kernel stores the register value in this structure, which is convenient for tracer to view through the ptrace function.

Sample code is as follows

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
    pid_t child;
    long orig_rax,rax;
    long params[3]={0};
    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_rax = ptrace(PTRACE_PEEKUSER,child,8*ORIG_RAX,NULL);
            //printf("the child made a system call %ld\n",orig_rax);
            if(orig_rax == SYS_write)
            {
                if(insyscall == 0)
                {
                    insyscall = 1;
                    params[0] = ptrace(PTRACE_PEEKUSER,child,8*RDI,NULL);
                    params[1] = ptrace(PTRACE_PEEKUSER,child,8*RSI,NULL);
                    params[2] = ptrace(PTRACE_PEEKUSER,child,8*RDX,NULL);
                    printf("write called with %ld, %ld, %ld\n",params[0],params[1],params[2]);
                }
                else
                {
                    rax = ptrace(PTRACE_PEEKUSER,child,8*RAX,NULL);
                    printf("write returned with %ld\n",rax);
                    insyscall = 0;
                }
            }
            ptrace(PTRACE_SYSCALL,child,NULL,NULL);
        }
    }
    return 0;

}
/***
输出:
write called with 1, 25226320, 65
ptrace_1.c  ptrace_2.c    ptrace_3.C  ptrace_4.C    ptrace_5.c  test.c
write returned with 65
***/
Copy after login

In the above code, we check the parameters of the write system call (triggered by the ls command printing text to the console).

In order to track system calls, we use the PTRACE_SYSCALL parameter of ptrace, which will cause tracee to pause when a system call is triggered or ends, and at the same time send a signal to tracer.

In the previous example, we used the PTRACE_PEEKUSER parameter to view the parameters of the system call. Similarly, we can also view the return value of the system call stored in the RAX register.

The status variable in the above code is used to detect whether tracee has finished executing and whether it is necessary to continue waiting for tracee to execute.

Read the values ​​of all registers

This example demonstrates a simple method to obtain the register value

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
    pid_t child;
    long orig_rax ,rax;
    long params[3] = {0};
    int status = 0;
    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_rax = ptrace(PTRACE_PEEKUSER,child,8*ORIG_RAX,NULL);
            if(orig_rax == SYS_write)
            {
                if(insyscall == 0)
                {
                    insyscall = 1;
                    ptrace(PTRACE_GETREGS,child,NULL,&regs);
                    printf("write called with %llu, %llu, %llu\n",regs.rdi,regs.rsi,regs.rdx);
                }
                else
                {
                    ptrace(PTRACE_GETREGS,child,NULL,&regs);
                    printf("write returned with %ld\n",regs.rax);
                    insyscall = 0;
                }
            }
            ptrace(PTRACE_SYSCALL,child,NULL,NULL);
        }
    }
    return 0;
}
Copy after login

In this example, all register values ​​are obtained through the PTRACE_GETREGS parameter. The structure user_regs_struct is defined in sys/user.h.

Modify system call parameters

现在我们已经知道如何拦截一个系统调用并查看其参数了,接下来我们来修改它

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


#define LONG_SIZE 8
//获取参数
char* getdata(pid_t child,unsigned long addr,unsigned long len)
{
    char *str =(char*) malloc(len + 1);
    memset(str,0,len +1);
    union u{
        long int val;
        char chars[LONG_SIZE];
    }word;
    int i, j;    
    for(i = 0,j = len/LONG_SIZE; iif(word.val == -1)
            perror("trace get data error");
        memcpy(str+i*LONG_SIZE,word.chars,LONG_SIZE);
    }
    j = len % LONG_SIZE;
    if(j != 0)
    {
        word.val = ptrace(PTRACE_PEEKDATA,child,addr + i*LONG_SIZE,NULL);
        if(word.val == -1)
            perror("trace get data error");
        memcpy(str+i*LONG_SIZE,word.chars,j);
    }
    return str;
}
//提交参数
void putdata(pid_t child,unsigned long  addr,unsigned long len, char *newstr)
{
    union u
    {
        long val;
        char chars[LONG_SIZE];
    }word;
    int i,j;
    for(i = 0, j = len/LONG_SIZE; iif(ptrace(PTRACE_POKEDATA, child, addr+i*LONG_SIZE,word.val) == -1)
            perror("trace error");

    }
    j = len % LONG_SIZE;
    if(j !=0 )
    {
        memcpy(word.chars,newstr+i*LONG_SIZE,j);
        ptrace(PTRACE_POKEDATA, child, addr+i*LONG_SIZE,word.val);
    }
}

//修改参数
void reserve(char *str,unsigned int len)
{
    int i,j;
    char tmp;
    for(i=0,j=len-2; imain()
{
    pid_t child;
    child = fork();
    if(child == 0)
    {
        ptrace(PTRACE_TRACEME,0,NULL,NULL);
        execl("/bin/ls","ls",NULL);
    }
    else
    {
        struct user_regs_struct regs;
        int status = 0;
        int toggle = 0;
        while(1)
        {
            wait(&status);
            if(WIFEXITED(status))
                break;
            memset(&regs,0,sizeof(struct user_regs_struct));
            if(ptrace(PTRACE_GETREGS,child,NULL,&regs) == -1)
            {
                perror("trace error");
            }
            
            if(regs.orig_rax == SYS_write)
            {
                if(toggle == 0)
                {
                    toggle = 1;
                    //in x86_64 system call ,pass params with %rdi, %rsi, %rdx, %rcx, %r8, %r9
                    //no system call has over six params 
                    printf("make write call params %llu, %llu, %llu\n",regs.rdi,regs.rsi,regs.rdx);
                    char  *str = getdata(child,regs.rsi,regs.rdx);
                    printf("old str,len %lu:\n%s",strlen(str),str);
                    reserve(str,regs.rdx);
                    printf("hook str,len %lu:\n%s",strlen(str),str);
                    putdata(child,regs.rsi,regs.rdx,str);
                    free(str);
                }
                else
                {
                    toggle = 0;
                }
            }
            ptrace(PTRACE_SYSCALL,child,NULL,NULL);
        }
    }
    return 0;
}
/***
输出:
make write call params 1, 9493584, 66
old str,len 66:
ptrace        ptrace2    ptrace3     ptrace4    ptrace5     test    test.s
hook str,len 66:
s.tset    tset     5ecartp    4ecartp     3ecartp    2ecartp        ecartp
s.tset    tset     5ecartp    4ecartp     3ecartp    2ecartp        ecartp
make write call params 1, 9493584, 65
old str,len 65:
ptrace_1.c  ptrace_2.c    ptrace_3.C  ptrace_4.C    ptrace_5.c  test.c
hook str,len 65:
c.tset  c.5_ecartp    C.4_ecartp  C.3_ecartp    c.2_ecartp  c.1_ecartp
c.tset  c.5_ecartp    C.4_ecartp  C.3_ecartp    c.2_ecartp  c.1_ecartp
***/
Copy after login

这个例子中,综合了以上我们提到的所有知识。进一步得,我们使用了ptrace的PTRACE_POKEDATA参数来修改系统调用的参数值。

这个参数和PTRACE_PEEKDATA参数的作用相反,它可以修改tracee指定地址的数据。

单步调试

接下来介绍一个调试器中常用的操作,单步调试,它就用到了ptrace的PTRACE_SINGLESTEP参数。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define LONG_SIZE 8

void main()
{
    pid_t chid;
    chid = fork();
    if(chid == 0)
    {
        ptrace(PTRACE_TRACEME,0,NULL,NULL);
     //这里的test是一个输出hello world的小程序
        execl("./test","test",NULL);
    }
    else
    {
        int status = 0;
        struct user_regs_struct regs;
        int start = 0;
        long ins;
        while(1)
        {
            wait(&status);
            if(WIFEXITED(status))
                break;
            ptrace(PTRACE_GETREGS,chid,NULL,&regs);
            if(start == 1)
            {
                ins = ptrace(PTRACE_PEEKTEXT,chid,regs.rip,NULL);
                printf("EIP:%llx Instuction executed:%lx\n",regs.rip,ins);
            }
            if(regs.orig_rax == SYS_write)
            {
                start = 1;
                ptrace(PTRACE_SINGLESTEP,chid,NULL,NULL);
            }else{
                ptrace(PTRACE_SYSCALL,chid,NULL,NULL);
            }
        }
    }
}
Copy after login

通过rip寄存器的值来获取下一条要执行指令的地址,然后用PTRACE_PEEKDATA读取。

这样,就可以看到要执行的每条指令的机器码。

通过本文,你应该对 Linux 下的 ptrace 有了一个深入的了解,知道了它的定义、原理、用法和优点。你也应该明白了 ptrace 的适用场景和注意事项,以及如何在 Linux 下正确地使用 ptrace。我们建议你在需要跟踪和控制进程的执行时,使用 ptrace 来实现你的目标。同时,我们也提醒你在使用 ptrace 时要注意一些潜在的风险和问题,如权限、安全、兼容性等。希望本文能够帮助你更好地使用 Linux 系统,让你在 Linux 下体验 ptrace 的强大和灵活。

The above is the detailed content of ptrace under Linux: a powerful process tracking and control mechanism. For more information, please follow other related articles on the PHP Chinese website!

source:lxlinux.net
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template