ptrace反调试之抢占ptrace

The ptrace() system call provides a means by which one process (the”tracer”) may observe and control the execution of another process(the “tracee”), and examine and change the tracee’s memory andregisters. It is primarily used to implement breakpoint debugging and system call tracing.

帮助文档

ptrace和debugger原理

ptrace

ptrace可以让一个进程监视和控制另一个进程的执行,并且修改被监视进程的内存、寄存器等,主要应用于断点调试和系统调用跟踪.
函数原型:
long ptrace(int request, pid_t pid, void * addr, void * data)
其中,request代表请求类型,pid代表被调试进程的pid.

部分ptrace request和wait

  • PTRACE_TRACEME
    表示本进程将被其父进程跟踪,交付给这个进程的所有信号(除SIGKILL之外),都将使其停止,父进程将通过wait()获知这一情况。
    它通常总是与 fork/exec 一起使用。对于每一个进程,PTRACE_TRACEME 只能被调用一次。
  • PTRACE_ATTACH
    根据pid将调试进程附加到被调试进程上,PTRACE_ATTACH向被调试进程发送SIGSTOP信号使之停下.
    但是在ptrace(PTRACE_ATTACH,pid,0,0)执行完毕时被调试进程可能还没有暂停,可以使用waitpid()等待其停下.
  • PTRACE_DETACH
    将被调试进程与调试进程分离,使被调试进程正常运行.
  • PTRACE_SYSCALL
    使被调试进程继续运行,但是在下一个系统调用的入口处或出口处停下,或者是执行完一条指令后停下.
    例如,调试进程可以监视被调试进程系统调用入口处的参数,接着再使用SYSCALL,监视系统调用的返回值.
  • wait()
    wait函数会延迟父进程的执行,直到被调试的进程切换为停止状态或者终止为止.

调试器建立调试关系的两种方式:

用gdb调试程序,可以直接gdb ./test,也可以gdb (test的进程号)。这对应着使用ptrace建立跟踪关系的两种方式:

  • fork:利用fork+execve执行被测试的程序,子进程在执行execve之前调用ptrace(PTRACE_TRACEME),建立了与父进程(debugger)的跟踪关系。
  • attach: debugger可以调用ptrace(PTRACE_ATTACH,pid,…),建立自己与进程号为pid的进程间的跟踪关系。即利用PTRACE_ATTACH,使自己变成被调试程序的父进程(用ps可以看到)。用attach建立起来的跟踪关系,可以调用ptrace(PTRACE_DETACH,pid,…)来解除。注意attach进程时的权限问题,如一个非root权限的进程是不能attach到一个root进程上的。

反调试

ptrace被广泛用于反调试,因为一个进程只能被ptrace一次,如果事先调用了ptrace方法,那就可以防止别人调试我们的程序.

测试

测试程序:

1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>
#include<stdlib.h>
#include<sys/ptrace.h>
int main(int argc, char* argv[])
{
ptrace(PTRACE_TRACEME);
while(1){
printf("Hello Ptrace!\n");
sleep(1);
}
return 0;
}

编译:gcc -g -o hello-ptrace hello-ptrace.c

运行并用gdb调试,发现已经被抢占,不能attach

已经由于PTRACE_TRACEME,被父进程trace


检测

1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>
#include<sys/ptrace.h>
int main(int argc, char* argv[])
{
if(-1 == ptrace(PTRACE_TRACEME))
{
printf("Debugger!\n");
return 1;
}
printf("Hello Ptrace!\n");
return 0;
}

反反调试

ptrace用户态源码(位于bionic/libc/bionic/ptrace.c):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
long ptrace(int request, pid_t pid, void * addr, void * data)
{
switch (request) {
case PTRACE_PEEKUSR:
case PTRACE_PEEKTEXT:
case PTRACE_PEEKDATA:
{
long word;
long ret;
ret = __ptrace(request, pid, addr, &word);
if (ret == 0) {
return word;
} else {
// __ptrace will set errno for us
return -1;
}
}
default:
return __ptrace(request, pid, addr, data);
}
}

0表示成功,-1表示错误.

  • 单个应用可在ptrace下断点.
  • 定制ROM,可以将ptrace源代码修改为如果是自己的pid调用ptrace,返回-1;否则返回0.