>  기사  >  시스템 튜토리얼  >  Linux의 ptrace: 강력한 프로세스 추적 및 제어 메커니즘

Linux의 ptrace: 강력한 프로세스 추적 및 제어 메커니즘

WBOY
WBOY앞으로
2024-02-09 20:57:24834검색

ptrace는 Linux 시스템의 특수 시스템 호출로, 하나의 프로세스가 메모리, 레지스터, 신호 등을 읽고 쓰는 것을 포함하여 다른 프로세스의 실행을 추적하고 제어할 수 있습니다. ptrace는 매우 강력하며 디버거, 추적기, 인젝터 등과 같은 도구를 구현하는 데 사용할 수 있습니다. 하지만 ptrace가 어떻게 작동하는지 정말로 이해하고 있나요? Linux에서 ptrace를 사용하는 방법을 알고 있나요? ptrace의 장점과 단점을 알고 있나요? 이 기사에서는 Linux에서 ptrace에 대한 관련 지식을 자세히 소개하여 Linux에서 이 강력한 프로세스 추적 및 제어 메커니즘을 더 잘 사용하고 이해할 수 있도록 합니다.

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

ptrace는 제어되는 프로세스의 코드, 데이터 및 레지스터를 감지하고 수정하는 등 한 프로세스가 다른 프로세스를 제어하는 ​​기능을 제공하여 중단점 설정, 코드 삽입 및 시스템 호출 추적 기능을 활성화합니다.

여기서 ptrace 함수를 사용하는 프로세스를 트레이서(tracer)라고 하고, 제어되는 프로세스를 트레이시(tracee)라고 합니다.

ptrace 함수를 사용하여 시스템 호출을 가로채세요

운영 체제는 기본 하드웨어와 상호 작용하는 작업을 수행하기 위해 상위 계층에 표준 API를 제공합니다. 이러한 표준 API를 시스템 호출이라고 합니다. unistd.h에서 쿼리할 수 있는 호출 번호가 있습니다. 프로세스가 시스템 호출을 트리거하면 매개변수를 레지스터에 넣은 다음 소프트 인터럽트를 통해 커널 모드로 들어가고 커널을 통해 시스템 호출 코드를 실행합니다.

ebx, ecx, edx, esi에 저장하세요

예를 들어 콘솔 인쇄로 실행되는 시스템 호출은

으아아아

는 어셈블리 코드로

로 번역됩니다. 으아아아

시스템 호출을 실행할 때 커널은 먼저 프로세스가 피추적자인지 여부를 감지합니다. 그렇다면 커널은 프로세스를 일시 중지한 다음 추적기로 제어권을 넘깁니다. 그러면 추적자는 피추적자의 레지스터를 보거나 수정할 수 있습니다.

샘플 코드는 다음과 같습니다

으아아아

이 프로그램은 포크를 통해 추적(trace)할 하위 프로세스를 생성합니다. execl을 실행하기 전에 하위 프로세스는 ptrace 함수의 PTRACE_TRACEME 매개변수를 통해 추적할 것임을 커널에 알립니다.

execl의 경우 이 함수는 실제로 execve 시스템 호출을 트리거합니다. 이때 커널은 프로세스가 피추적자임을 확인한 다음 이를 일시 중지하고 대기 중인 추적 프로그램(이 프로그램의 기본 스레드)을 깨우기 위해 신호를 보냅니다.

시스템 호출이 트리거되면 커널은 호출 번호가 들어 있는 rax 레지스터의 내용을 orig_rax에 저장합니다. 이는 ptrace의 PTRACE_PEEKUSER 매개변수를 통해 읽을 수 있습니다.

ORIG_RAX는 sys/reg.h에 저장되는 레지스터 번호입니다. 64비트 시스템에서 각 레지스터의 크기는 8바이트이므로 여기서는 8*ORIG_RAX를 사용하여 레지스터 주소를 얻습니다.

시스템 호출 번호를 얻은 후 ptrace의 PTRACE_CONT 매개변수를 통해 일시 중지된 하위 프로세스를 깨우고 계속 실행되도록 할 수 있습니다.

ptrace 매개변수

으아아아

매개변수 요청은 ptrace 함수의 동작을 제어하며 sys/ptrace.h에 정의되어 있습니다.

매개변수 pid는 피추적자의 프로세스 번호를 지정합니다.

위 두 매개변수는 필수이며, 다음 두 매개변수는 각각 주소와 데이터이며 그 의미는 매개변수 요청에 의해 제어됩니다.

특정 요청 매개변수의 값과 의미는 도움말 문서를 참조하세요(콘솔 입력: man ptrace)

반환 값에 주의하세요. 매뉴얼에는 워드의 데이터 크기가 반환된다고 명시되어 있는데, 이는 32비트 시스템에서는 4바이트, 64비트 시스템에서는 8바이트입니다. 긴. Baidu에는 1바이트의 데이터를 반환하는 것이 잘못되었다는 무책임한 게시물이 많이 있습니다!

시스템 호출 매개변수 읽기

ptrace의 PTRACE_PEEKUSER 매개변수를 통해 레지스터 값 등 USER 영역의 내용을 볼 수 있습니다. USER 영역은 구조체(sys/user.h에 정의된 사용자 구조체)입니다.

커널은 이 구조에 레지스터 값을 저장하는데, 이는 추적자가 ptrace 함수를 통해 보기에 편리합니다.

샘플 코드는 다음과 같습니다

으아아아

위 코드에서는 write 시스템 호출(콘솔에 텍스트를 인쇄하는 ls 명령에 의해 트리거됨)의 매개변수를 확인합니다.

시스템 호출을 추적하기 위해 우리는 ptrace의 PTRACE_SYSCALL 매개변수를 사용합니다. 이는 시스템 호출이 트리거되거나 종료될 때 피추적자가 일시 중지되도록 하고 동시에 추적자에게 신호를 보냅니다.

이전 예제에서는 PTRACE_PEEKUSER 매개변수를 사용하여 시스템 호출 매개변수를 확인했습니다. 마찬가지로 RAX 레지스터에 저장된 시스템 호출 반환 값도 볼 수 있습니다.

위 코드의 상태 변수는 피추적자가 실행을 마쳤는지 여부와 피추적자가 실행될 때까지 계속 기다려야 하는지 여부를 감지하는 데 사용됩니다.

모든 레지스터의 값 읽기

이 예는 레지스터 값을 얻는 간단한 방법을 보여줍니다

으아아아

이 예에서는 모든 레지스터 값이 PTRACE_GETREGS 매개변수를 통해 얻어집니다. user_regs_struct 구조는 sys/user.h에 정의되어 있습니다.

시스템 호출 매개변수 수정

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

#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
***/

这个例子中,综合了以上我们提到的所有知识。进一步得,我们使用了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);
            }
        }
    }
}

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

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

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

위 내용은 Linux의 ptrace: 강력한 프로세스 추적 및 제어 메커니즘의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 lxlinux.net에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제