ptrace 信号注入与抑制

ptrace 函数原型​

#include <sys/ptrace.h>
long ptrace(int request, pid_t pid, void *addr, void *data);
  • request​:指定操作类型(如读取内存、设置断点等)。
  • pid​:目标进程的 PID(进程 ID)。
  • addr​:内存地址或偏移量(取决于 request)。
  • data​:要写入的数据(某些 request 需要)。

request 参数详解​​

​Request​​功能​
PTRACE_TRACEME​让当前进程被父进程跟踪​​(调试器启动子进程时使用)
PTRACE_ATTACH​附加到目标进程​​(调试器附加到正在运行的进程)
PTRACE_DETACH​解除跟踪​
PTRACE_PEEKTEXT / PTRACE_PEEKDATA​读取目标进程的内存​​(addr 是内存地址)
PTRACE_POKETEXT / PTRACE_POKEDATA​写入目标进程的内存​
PTRACE_GETREGS​读取目标进程的寄存器​​(data 指向 struct user_regs_struct
PTRACE_SETREGS​修改目标进程的寄存器​
PTRACE_SINGLESTEP​单步执行​​(类似 gdb 的 si
PTRACE_CONT​继续执行​​(类似 gdb 的 continue
PTRACE_SYSCALL​在进入/退出系统调用时暂停​​(strace 使用)
PTRACE_KILL​终止目标进程​

基本工作流程​

  1. ​调试器(父进程)启动被调试进程(子进程)​
    • 子进程调用 PTRACE_TRACEME,表示愿意被父进程跟踪。
    • 父进程使用 waitpidwaitpid- 父进程使用 waitpidwaitpid() 等待子进程暂停(如遇到断点)。() 等待子进程暂停(如遇到断点)。
  2. ​调试器附加到正在运行的进程​
    • 父进程调用 PTRACE_ATTACH 附加到目标进程。
    • 目标进程会收到 SIGSTOP 信号并暂停。
  3. ​调试器读取/修改目标进程​
    • 使用 PTRACE_PEEKTEXT 读取内存。
    • 使用 PTRACE_POKETEXT 修改内存(如设置断点)。
    • 使用 PTRACE_GETREGS 读取寄存器。
  4. ​控制目标进程执行​
    • PTRACE_CONT 继续执行。
    • PTRACE_SINGLESTEP 单步执行。
  5. ​结束调试​
    •  解除跟踪,让目标进程继续运行。

exec 函数族


traceMe

子进程调用 PTRACE_TRACEME,表明这个进程由它的父进程来跟踪。任何发给这个进程的信号 signal(除了 SIGKILL)将导致该进程停止运行,而它的父进程会通过 wait() 获得通知。另外,该进程之后所有对 exec() 的调用都将使操作系统产生一个 SIGTRAP 信号发送给它,这让父进程有机会在新程序开始执行之前获得对子进程的控制权。


PTRACE_PEEKUSER

PTRACE_PEEKUSER Read a word at offset addr in the tracee’s USER area, which holds the registers and other information about the process (see <sys/user.h>). The word is returned as the result of the ptrace() call. Typically, the offset must be word- aligned, though this might vary by architecture. See NOTES. (data is ignored; but see NOTES.)

PTRACE_PEEKUSER 读取被跟踪进程 USER 区域(包含寄存器和其他进程信息,参见)中偏移量 addr 处的一个字,并将该字作为 ptrace() 调用的结果返回。通常,偏移量必须按字对齐,但这可能因架构而异。参见注意事项。(data 被忽略;但请参阅注意事项。)

在 arm64 下的 user.h

struct user_regs_struct
{
  unsigned long long regs[31];
  unsigned long long sp;
  unsigned long long pc;
  unsigned long long pstate;
};
 
struct user_fpsimd_struct
{
  __uint128_t  vregs[32];
  unsigned int fpsr;
  unsigned int fpcr;
};

offset of 宏

还是没看懂怎么用,还是用下面这个👇


PTRACE_GETREGSET

这么 get 寄存器组:

#include <linux/elf.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <sys/fcntl.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <unistd.h>
 
int main() {
    pid_t child;
    child = fork();
 
    if (child == 0) {
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        execl("/bin/ls", "ls", NULL); // start
    } else {
        int status;
        ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD);
 
        while (1) {
            ptrace(PTRACE_SYSCALL, child, 0, 0);
            waitpid(child, &status, 0);
 
            if (WIFEXITED(status)) {
                printf("Child exited with status %d\n", WEXITSTATUS(status));
                break;
            }
 
            if ((status >> 8) == (SIGTRAP)) {
                struct user_regs_struct regs;
                struct iovec io = {.iov_base = &regs, .iov_len = sizeof(regs)};
 
                // 使用 PTRACE_GETREGSET 读取寄存器
                if (ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, &io) == 0) {
                    printf("System call number: %lld\n",
                           (long long)regs.regs[8]); // arm
                } else {
                    perror("ptrace(GETREGSET)");
                }
            }
        }
    }
 
    return 0;
}

要用头文件 <sys/uio.h> 中的 iovec 中转,提高兼容性