当前位置: 首页 > article >正文

PTRACE_TRACEME 与反调试

文章目录

  • PTRACE_TRACEME 与反调试
    • TRACEME 的底层机制
      • 内核级标记
      • 后续 - 触发中断
        • 使用 ptrace 单步调试的代码模式:
    • 反调试:抢占调试器插槽
      • 绕过 TRACEME 反调试
        • 对抗手段:

PTRACE_TRACEME 与反调试

设置 PTRACE_TRACEME 是 Linux 的经典反调试手段,因为系统限制调试是独占的,一个进程只能有一个 debugger。我们看下面两种 Linux 系统调用模式,其中 PTRACE_TRACEME 适用于让父进程调试自己,PTRACE_ATTACH 适用于调试器附加到其他进程。

// TRACEME demo
	pid_t child_pid;
    child_pid = fork();
    if (child_pid == 0) {
        // 子进程:声明自己被跟踪,允许父进程跟踪
        if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) < 0) { /* err */ }
        printf("Child: I'm being traced!\n");
        // 子进程执行一些操作
        execl("/bin/ls", "ls", NULL);  // 替换为 ls 命令
    } else if (child_pid > 0) {
        // 父进程:等待子进程停止
        int status;
        waitpid(child_pid, &status, 0);
        if (WIFSTOPPED(status)) printf("Parent: Child is stopped by ptrace.\n");
        // 父进程继续跟踪子进程
        ptrace(PTRACE_CONT, child_pid, NULL, NULL);
        // 等待子进程结束
        waitpid(child_pid, &status, 0);
        printf("Parent: Child process exited.\n");
    } 
 
// output 
Child: I'm being traced!
Parent: Child is stopped by ptrace.
trace-me  trace-me.c
Parent: Child process exited.
// PTRACE_ATTACH demo
	pid_t target_pid;
    printf("Enter the PID of the process to attach: ");
    scanf("%d", &target_pid);

    // 附加到目标进程,目标进程会受到 SIGSTOP 停止
    if (ptrace(PTRACE_ATTACH, target_pid, NULL, NULL) < 0) { /* err */ }
    printf("Attached to process %d\n", target_pid);

    // 等待目标进程停止
    int status;
    waitpid(target_pid, &status, 0);
    if (WIFSTOPPED(status)) printf("Process %d is stopped.\n", target_pid);

    // 继续运行目标进程
    if (ptrace(PTRACE_CONT, target_pid, NULL, NULL) < 0) { /* err */ }
    printf("Process %d continued.\n", target_pid);

    // 分离目标进程
    if (ptrace(PTRACE_DETACH, target_pid, NULL, NULL) < 0) { /* err */ }
    printf("Detached from process %d\n", target_pid);
}

TRACEME 的底层机制

内核级标记

当进程调用 ptrace(PTRACE_TRACEME, 0, 0, 0) 时,内核会在该进程的 task_struct 中设置 PT_PTRACED 标志位。此标志表示该进程已主动声明 “允许被父进程跟踪”,同时隐式触发以下规则:

  • 一个进程同一时间只能被一个调试器跟踪(无论是通过 PTRACE_TRACEME 还是 PTRACE_ATTACH)。
  • 若后续有其他调试器尝试附加(如 gdb 的 attach 命令),内核会拒绝并返回 EPERM 错误。

后续 - 触发中断

设置标记不代表子进程立刻就会被中断,必须收到停止信号时才会被父进程捕获。通常的做法是调用 exec() 触发一个 SIGTRAP。当前进程被标记为 PT_PTRACED 后,在调用 exec 系列函数时,内部的 load_elf_binary 函数会给自己发送 SIGTRAP 信号触发信号处理。

通用的 do_signal 函数在处理 PT_PTRACED 进程时会触发特殊逻辑。它会停止 current_thread,给父进程发送 SIGCHLD 信号,然后调用 schedule 切换时间片。此时子进程被父进程通过 waitpid 捕获。父进程之后可以使用 PTRACE_CONT, PTRACE_SINGLESTEP 调试子进程。

使用 ptrace 单步调试的代码模式:
ptrace 请求作用
PTRACE_TRACEME设置当前进程为可被跟踪状态,并在 execve() 后触发 SIGTRAP
PTRACE_GETREGS获取寄存器信息
PTRACE_PEEKDATA读取进程内存
PTRACE_POKEDATA修改进程内存
PTRACE_SETREGS修改寄存器信息
PTRACE_SINGLESTEP单步执行一条指令
PTRACE_SYSCALL在系统调用进入和退出时暂停

具体操作流程

  1. 父进程 fork 出一个子进程
  2. 子进程调用 PTRACE_TRACEME 并执行 execve()
  3. 父进程 wait 子进程 SIGTRAP
  4. 父进程循环调用 ptrace(PTRACE_SYSCALL, ...)PTRACE_SINGLESTEP 进行调试**
    • PTRACE_GETREGS 获取 rip(指令地址)、rax(返回值)、orig_rax(系统调用号)
    • PTRACE_PEEKDATA 读取内存内容( get/set 内存的数据和指令)
    • PTRACE_POKEDATA 修改指令执行行为,配合单步 rip 可以解析当前汇编,执行到特定地址时修改寄存器。
  5. SYSCALL 分别在 syscall 的 enter 和 exit 时触发,配合 orig_rax, rax 可以判断在哪个 syscall

反调试:抢占调试器插槽

子进程在启动时立即调用 PTRACE_TRACEME,相当于提前占用了唯一的调试器插槽。即使父进程不进行任何实际跟踪操作,该插槽已被占用,导致外部调试器无法通过常规手段附加。

例如 XCTF 的 LoopCrypto 首先调用 TRACEME,然后检查 /proc/pid/status 的 TracerPid 字段等于零,来保证成功设置 TRACEME

感觉应该类似判断返回值小于零
(感觉应该类似判断返回值小于零?)

绕过 TRACEME 反调试

利用时间差:在子进程调用 PTRACE_TRACEME 前,父进程或外部程序能快速附加调试器,仍可拦截子进程。

静默跟踪:父进程可通过 PTRACE_SEIZE(非侵入式跟踪)或直接忽略跟踪事件,避免触发信号暂停,使子进程看似未被调试。

对抗手段:
  • 代码修补:修改二进制,移除 ptrace 调用或跳过相关代码。
  • 内核模块:通过自定义内核模块强制清除 PT_PTRACED 标志。
  • Frida 等工具:使用 frida-server 或 LD_PRELOAD 注入,在进程内存中动态禁用反调试代码。例如在二进制加载后运行前直接 hook 掉 init array 中的反调试函数。(XCTF LoopCrypto)

http://www.kler.cn/a/534640.html

相关文章:

  • PHP 中 `foreach` 循环结合引用使用时可能出现的问题
  • 游戏引擎学习第88天
  • DeepSeek+Ollama+AnythingLLM 本地部署完全指南,打造专属知识库
  • 新春贺岁,共赴AGI之旅
  • C++游戏开发实战:从引擎架构到物理碰撞
  • 图漾相机——Sample_V1示例程序
  • MongoDB管道操作符(二)
  • PHP-回溯
  • HTML中的图片标签详解及路径使用【学术投稿-第五届环境资源与能源工程国际学术会议(ICEREE 2025)】
  • 使用多模态大语言模型进行深度学习的图像、文本和语音数据增强
  • Linux提权--John碰撞密码提权
  • K8S Deployment 实现 金丝雀(灰度) 发布
  • 用pytorch实现一个简单的图片预测类别
  • 原生redis实现分布式锁
  • web 第二次作业
  • 关于Vue.js组件开发
  • 基于keepalived+GTID半同步主从复制的高可用MySQL集群
  • python学opencv|读取图像(五十七)使用cv2.bilateralFilter()函数实现图像像素双边滤波处理
  • 报错解决方案笔记01
  • 为什么使用nohup 和 启动的python脚本,日志没有在nohup.out中
  • 迅为RK3568开发板篇OpenHarmony实操HDF驱动控制LED-编写应用APP
  • 【ROS视频推流】使用web_video_server完成视频推流
  • LLMs之data:synthetic-data-generator的简介、安装和使用方法、案例应用之详细攻略
  • Ubuntu24登录PostgreSql数据库的一般方法
  • 常用的TS类型工具
  • lambda表达式写java比较器