Linux逆向之调试&反调试
字数 953 2025-08-24 20:49:31

Linux逆向工程:调试与反调试技术详解

调试器实现原理

ptrace系统调用

Linux下的调试器如GDB都是基于ptrace系统调用实现的。ptrace是Linux提供的用于调试的系统调用,其函数原型如下:

#include <sys/ptrace.h>
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

ptrace允许父进程观察和控制其他进程的执行,检查和修改其核心映像和寄存器,主要用于实现断点调试和系统调用跟踪。

常见request参数

  1. PTRACE_TRACEME:表示本进程将被其父进程跟踪,只能用于被调试的进程中
  2. PTRACE_ATTACH:附加到指定进程,使其成为当前进程跟踪的子进程
  3. PTRACE_CONT:继续运行之前停止的子进程,相当于gdb的continue命令
  4. PTRACE_PEEKUSER:读取子进程的用户空间数据
  5. PTRACE_POKEUSER:修改子进程的用户空间数据
  6. PTRACE_GETREGS:获取子进程的寄存器值
  7. PTRACE_SETREGS:设置子进程的寄存器值
  8. PTRACE_SYSCALL:使内核在子进程进入和退出系统调用时都将其暂停

ptrace示例代码

#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/reg.h>
#include <sys/user.h>
#include <sys/syscall.h>
#include <stdio.h>

int main() {
    pid_t child;
    long orig_rax;
    int status;
    int iscalling = 0;
    struct user_regs_struct regs;
    
    child = fork();
    if (child == 0) {
        ptrace(PTRACE_TRACEME, 0, 0, 0);
        execl("/bin/ls", "ls", "-l", "-h", 0);
    } else {
        while(1) {
            wait(&status);
            if (WIFEXITED(status)) break;
            
            orig_rax = ptrace(PTRACE_PEEKUSER, child, 8*ORIG_RAX, 0);
            if (orig_rax == SYS_write) {
                ptrace(PTRACE_GETREGS, child, 0, &regs);
                if (!iscalling) {
                    iscalling = 1;
                    printf("SYS_write call with %p, %p, %p\n", regs.rdi, regs.rsi, regs.rdx);
                } else {
                    printf("SYS_write call return %p\n", regs.rax);
                    iscalling = 0;
                }
            }
            ptrace(PTRACE_SYSCALL, child, 0, 0);
        }
    }
    return 0;
}

反调试技术

基础反调试方法

最简单的反调试方法是利用ptrace的特性:

#include <sys/ptrace.h>
#include <stdio.h>

int main() {
    if (ptrace(PTRACE_TRACEME, 0, 0, 0) == -1) {
        printf("don't trace me:(\n");
        return 1;
    }
    printf("no one trace me:)\n");
    return 0;
}

原理:每个进程只能被PTRACE_TRACEME一次,提前执行会阻止调试器附加。

绕过基础反调试的方法

  1. 打patch:将ptrace函数相关部分nop掉
  2. hook技术:替换ptrace函数为自定义实现
  3. gdb catch命令
    catch syscall ptrace
    # 在第二次停住时
    set $rax=0
    

增强型反调试

通过父子进程交互实现更复杂的反调试:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <sys/user.h>

#define SYS_CALL_myread 12345
#define SYS_CALL_mywrite 67890

void myread(char *str, int len) {
    syscall(SYS_CALL_myread, str, len, 0);
}

void mywrite(char *str) {
    syscall(SYS_CALL_mywrite, str, strlen(str), 1);
}

void tracee() {
    ptrace(PTRACE_TRACEME, 0, 0, 0);
    raise(SIGCONT);
    char *str1 = "what is your name?\n";
    static char name[0x10];
    char *ptr_name = name;
    mywrite(str1);
    myread(ptr_name, 0x10);
    puts("welcome!");
    mywrite(ptr_name);
}

void tracer(pid_t child_pid) {
    int status;
    struct user_regs_struct regs;
    waitpid(child_pid, &status, 0);
    if (!WIFSTOPPED(status)) {
        printf("gg\n");
        exit(1);
    }
    ptrace(PTRACE_SETOPTIONS, child_pid, 0, PTRACE_O_EXITKILL);
    
    while(WIFSTOPPED(status)) {
        ptrace(PTRACE_SYSCALL, child_pid, 0, 0);
        waitpid(child_pid, &status, 0);
        ptrace(PTRACE_GETREGS, child_pid, 0, &regs);
        
        if (regs.orig_rax == SYS_CALL_mywrite) {
            regs.orig_rax = SYS_write;
            unsigned long long int tmp = regs.rdx;
            regs.rdx = regs.rsi;
            regs.rsi = regs.rdi;
            regs.rdi = tmp;
            ptrace(PTRACE_SETREGS, child_pid, 0, &regs);
        }
        
        if (regs.orig_rax == SYS_CALL_myread) {
            regs.orig_rax = SYS_read;
            unsigned long long int tmp = regs.rdx;
            regs.rdx = regs.rsi;
            regs.rsi = regs.rdi;
            regs.rdi = tmp;
            ptrace(PTRACE_SETREGS, child_pid, 0, &regs);
        }
        
        ptrace(PTRACE_SYSCALL, child_pid, 0, 0);
        waitpid(child_pid, &status, 0);
    }
}

int main() {
    pid_t child_pid = fork();
    if (child_pid < 0) {
        printf("gg\n");
        exit(1);
    }
    if (child_pid == 0) {
        tracee();
    } else {
        tracer(child_pid);
    }
    return 0;
}

增强方法:

  1. 定义更多自定义syscall替代libc函数
  2. 添加数据交互和加密解密
  3. 增加代码混淆

其他反调试技巧

  1. 检测/proc/self/status

    FILE *fp;
    int TracerPid = 0;
    fp = fopen("/proc/self/status", "r");
    static char buf[0x100];
    char *ptr = &buf;
    while(fgets(ptr, 0x100, fp)) {
        if(strstr(ptr, "TracerPid")) {
            char tmp[0x10];
            int len = strlen(ptr);
            TracerPid = atoi((char *)ptr + len - 3);
            if(TracerPid != 0) {
                puts("don't debug me!");
            }
        }
    }
    
  2. 检测/proc/self/cmdline

    char buf1[0x20], buf2[0x100];
    FILE *fp;
    snprintf(buf1, 24, "/proc/%d/cmdline", getppid());
    fp = fopen(buf1, "r");
    fgets(buf2, 0x100, fp);
    fclose(fp);
    if(!strcmp(buf2, "gdb") || !strcmp(buf2, "strace") || !strcmp(buf2, "ltrace")) {
        printf("Debugger detected");
        return 1;
    }
    
  3. 忽略SIGTRAP信号

    signal(SIGTRAP, SIG_IGN);
    
  4. 设置时间间隔

    void alarmHandler(int sig) {
        printf("don't debug me");
        exit(1);
    }
    
    void __attribute__((constructor)) setupSig(void) {
        signal(SIGALRM, alarmHandler);
        alarm(3);
    }
    

总结

调试与反调试是二进制安全领域的重要技术:

  1. 调试原理:基于ptrace系统调用实现进程控制和数据访问
  2. 基础反调试:利用ptrace特性阻止调试器附加
  3. 增强反调试:通过父子进程交互和自定义系统调用增加逆向难度
  4. 其他技巧:状态检测、信号处理和定时中断等

攻击与防御是持续对抗的过程,理解这些技术原理有助于开发更安全的软件或进行有效的逆向分析。

Linux逆向工程:调试与反调试技术详解 调试器实现原理 ptrace系统调用 Linux下的调试器如GDB都是基于ptrace系统调用实现的。ptrace是Linux提供的用于调试的系统调用,其函数原型如下: ptrace允许父进程观察和控制其他进程的执行,检查和修改其核心映像和寄存器,主要用于实现断点调试和系统调用跟踪。 常见request参数 PTRACE_ TRACEME :表示本进程将被其父进程跟踪,只能用于被调试的进程中 PTRACE_ ATTACH :附加到指定进程,使其成为当前进程跟踪的子进程 PTRACE_ CONT :继续运行之前停止的子进程,相当于gdb的continue命令 PTRACE_ PEEKUSER :读取子进程的用户空间数据 PTRACE_ POKEUSER :修改子进程的用户空间数据 PTRACE_ GETREGS :获取子进程的寄存器值 PTRACE_ SETREGS :设置子进程的寄存器值 PTRACE_ SYSCALL :使内核在子进程进入和退出系统调用时都将其暂停 ptrace示例代码 反调试技术 基础反调试方法 最简单的反调试方法是利用ptrace的特性: 原理:每个进程只能被PTRACE_ TRACEME一次,提前执行会阻止调试器附加。 绕过基础反调试的方法 打patch :将ptrace函数相关部分nop掉 hook技术 :替换ptrace函数为自定义实现 gdb catch命令 : 增强型反调试 通过父子进程交互实现更复杂的反调试: 增强方法: 定义更多自定义syscall替代libc函数 添加数据交互和加密解密 增加代码混淆 其他反调试技巧 检测/proc/self/status : 检测/proc/self/cmdline : 忽略SIGTRAP信号 : 设置时间间隔 : 总结 调试与反调试是二进制安全领域的重要技术: 调试原理 :基于ptrace系统调用实现进程控制和数据访问 基础反调试 :利用ptrace特性阻止调试器附加 增强反调试 :通过父子进程交互和自定义系统调用增加逆向难度 其他技巧 :状态检测、信号处理和定时中断等 攻击与防御是持续对抗的过程,理解这些技术原理有助于开发更安全的软件或进行有效的逆向分析。