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参数
- PTRACE_TRACEME:表示本进程将被其父进程跟踪,只能用于被调试的进程中
- PTRACE_ATTACH:附加到指定进程,使其成为当前进程跟踪的子进程
- PTRACE_CONT:继续运行之前停止的子进程,相当于gdb的continue命令
- PTRACE_PEEKUSER:读取子进程的用户空间数据
- PTRACE_POKEUSER:修改子进程的用户空间数据
- PTRACE_GETREGS:获取子进程的寄存器值
- PTRACE_SETREGS:设置子进程的寄存器值
- 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, ®s);
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一次,提前执行会阻止调试器附加。
绕过基础反调试的方法
- 打patch:将ptrace函数相关部分nop掉
- hook技术:替换ptrace函数为自定义实现
- 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, ®s);
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, ®s);
}
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, ®s);
}
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;
}
增强方法:
- 定义更多自定义syscall替代libc函数
- 添加数据交互和加密解密
- 增加代码混淆
其他反调试技巧
-
检测/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!"); } } } -
检测/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; } -
忽略SIGTRAP信号:
signal(SIGTRAP, SIG_IGN); -
设置时间间隔:
void alarmHandler(int sig) { printf("don't debug me"); exit(1); } void __attribute__((constructor)) setupSig(void) { signal(SIGALRM, alarmHandler); alarm(3); }
总结
调试与反调试是二进制安全领域的重要技术:
- 调试原理:基于ptrace系统调用实现进程控制和数据访问
- 基础反调试:利用ptrace特性阻止调试器附加
- 增强反调试:通过父子进程交互和自定义系统调用增加逆向难度
- 其他技巧:状态检测、信号处理和定时中断等
攻击与防御是持续对抗的过程,理解这些技术原理有助于开发更安全的软件或进行有效的逆向分析。