gdb默认是通过ptrace系统调用实现功能,本文简单了解ptrace。
ptrace的系统调用
/* kernel/ptrace.c */ #define __NR_ptrace 117 __SYSCALL(__NR_ptrace, sys_ptrace)
在使用ptrace时需要包含/sys/ptrace.h
,调用ptrace系统调用的时候,需要根据不同的request来发送请求,request的种类如下
enum __ptrace_request { /* Indicate that the process making this request should be traced. All signals received by this process can be intercepted by its parent, and its parent can use the other `ptrace' requests. */ PTRACE_TRACEME = 0, #define PT_TRACE_ME PTRACE_TRACEME /* Return the word in the process's text space at address ADDR. */ PTRACE_PEEKTEXT = 1, #define PT_READ_I PTRACE_PEEKTEXT /* Return the word in the process's data space at address ADDR. */ PTRACE_PEEKDATA = 2, #define PT_READ_D PTRACE_PEEKDATA /* Return the word in the process's user area at offset ADDR. */ PTRACE_PEEKUSER = 3, #define PT_READ_U PTRACE_PEEKUSER /* Write the word DATA into the process's text space at address ADDR. */ PTRACE_POKETEXT = 4, #define PT_WRITE_I PTRACE_POKETEXT /* Write the word DATA into the process's data space at address ADDR. */ PTRACE_POKEDATA = 5, #define PT_WRITE_D PTRACE_POKEDATA /* Write the word DATA into the process's user area at offset ADDR. */ PTRACE_POKEUSER = 6, #define PT_WRITE_U PTRACE_POKEUSER /* Continue the process. */ PTRACE_CONT = 7, #define PT_CONTINUE PTRACE_CONT /* Kill the process. */ PTRACE_KILL = 8, #define PT_KILL PTRACE_KILL /* Single step the process. */ PTRACE_SINGLESTEP = 9, #define PT_STEP PTRACE_SINGLESTEP /* Get all general purpose registers used by a processes. */ PTRACE_GETREGS = 12, #define PT_GETREGS PTRACE_GETREGS /* Set all general purpose registers used by a processes. */ PTRACE_SETREGS = 13, #define PT_SETREGS PTRACE_SETREGS /* Get all floating point registers used by a processes. */ PTRACE_GETFPREGS = 14, #define PT_GETFPREGS PTRACE_GETFPREGS /* Set all floating point registers used by a processes. */ PTRACE_SETFPREGS = 15, #define PT_SETFPREGS PTRACE_SETFPREGS /* Attach to a process that is already running. */ PTRACE_ATTACH = 16, #define PT_ATTACH PTRACE_ATTACH /* Detach from a process attached to with PTRACE_ATTACH. */ PTRACE_DETACH = 17, #define PT_DETACH PTRACE_DETACH /* Get all extended floating point registers used by a processes. */ PTRACE_GETFPXREGS = 18, #define PT_GETFPXREGS PTRACE_GETFPXREGS /* Set all extended floating point registers used by a processes. */ PTRACE_SETFPXREGS = 19, #define PT_SETFPXREGS PTRACE_SETFPXREGS /* Continue and stop at the next entry to or return from syscall. */ PTRACE_SYSCALL = 24, #define PT_SYSCALL PTRACE_SYSCALL /* Get a TLS entry in the GDT. */ PTRACE_GET_THREAD_AREA = 25, #define PT_GET_THREAD_AREA PTRACE_GET_THREAD_AREA /* Change a TLS entry in the GDT. */ PTRACE_SET_THREAD_AREA = 26, #define PT_SET_THREAD_AREA PTRACE_SET_THREAD_AREA /* Continue and stop at the next syscall, it will not be executed. */ PTRACE_SYSEMU = 31, #define PT_SYSEMU PTRACE_SYSEMU /* Single step the process, the next syscall will not be executed. */ PTRACE_SYSEMU_SINGLESTEP = 32, #define PT_SYSEMU_SINGLESTEP PTRACE_SYSEMU_SINGLESTEP /* Execute process until next taken branch. */ PTRACE_SINGLEBLOCK = 33, #define PT_STEPBLOCK PTRACE_SINGLEBLOCK /* Set ptrace filter options. */ PTRACE_SETOPTIONS = 0x4200, #define PT_SETOPTIONS PTRACE_SETOPTIONS /* Get last ptrace message. */ PTRACE_GETEVENTMSG = 0x4201, #define PT_GETEVENTMSG PTRACE_GETEVENTMSG /* Get siginfo for process. */ PTRACE_GETSIGINFO = 0x4202, #define PT_GETSIGINFO PTRACE_GETSIGINFO /* Set new siginfo for process. */ PTRACE_SETSIGINFO = 0x4203, #define PT_SETSIGINFO PTRACE_SETSIGINFO /* Get register content. */ PTRACE_GETREGSET = 0x4204, #define PTRACE_GETREGSET PTRACE_GETREGSET /* Set register content. */ PTRACE_SETREGSET = 0x4205, #define PTRACE_SETREGSET PTRACE_SETREGSET /* Like PTRACE_ATTACH, but do not force tracee to trap and do not affect signal or group stop state. */ PTRACE_SEIZE = 0x4206, #define PTRACE_SEIZE PTRACE_SEIZE /* Trap seized tracee. */ PTRACE_INTERRUPT = 0x4207, #define PTRACE_INTERRUPT PTRACE_INTERRUPT /* Wait for next group event. */ PTRACE_LISTEN = 0x4208, #define PTRACE_LISTEN PTRACE_LISTEN /* Retrieve siginfo_t structures without removing signals from a queue. */ PTRACE_PEEKSIGINFO = 0x4209, #define PTRACE_PEEKSIGINFO PTRACE_PEEKSIGINFO /* Get the mask of blocked signals. */ PTRACE_GETSIGMASK = 0x420a, #define PTRACE_GETSIGMASK PTRACE_GETSIGMASK /* Change the mask of blocked signals. */ PTRACE_SETSIGMASK = 0x420b, #define PTRACE_SETSIGMASK PTRACE_SETSIGMASK /* Get seccomp BPF filters. */ PTRACE_SECCOMP_GET_FILTER = 0x420c, #define PTRACE_SECCOMP_GET_FILTER PTRACE_SECCOMP_GET_FILTER /* Get seccomp BPF filter metadata. */ PTRACE_SECCOMP_GET_METADATA = 0x420d, #define PTRACE_SECCOMP_GET_METADATA PTRACE_SECCOMP_GET_METADATA /* Get information about system call. */ PTRACE_GET_SYSCALL_INFO = 0x420e, #define PTRACE_GET_SYSCALL_INFO PTRACE_GET_SYSCALL_INFO /* Get rseq configuration information. */ PTRACE_GET_RSEQ_CONFIGURATION = 0x420f #define PTRACE_GET_RSEQ_CONFIGURATION PTRACE_GET_RSEQ_CONFIGURATION };
不同的ptrace请求上面其实存在简要的注释,如果需要详细了解,可以man-page链接查看
针对gdb,我们可以准备一个简单的hello world程序,然后使用gdb进行调试。查看一个简单的hello world是如何进行ptrace调用的。简单的程序如下
int main() { printf("hello \n");}
因为ptrace是系统调用,可以直接使用perf trace -e
抓取,如下
gcc test.c -o test perf trace -e ptrace -p $(pidof gdb)
运行日志如下
0.000 ( 0.007 ms): gdb/6228 ptrace(request: 12, pid: 6249, data: 140731401603456) = 0 0.010 ( 0.001 ms): gdb/6228 ptrace(request: 16898, pid: 6249, data: 140731401607008) = 0 0.049 ( 0.002 ms): gdb/6228 ptrace(request: 7, pid: 6249, addr: 0x1) = 0 0.836 ( 0.002 ms): gdb/6228 ptrace(request: 12, pid: 6249, data: 140731401603456) = 0 0.840 ( 0.001 ms): gdb/6228 ptrace(request: 16898, pid: 6249, data: 140731401607008) = 0 0.854 ( 0.001 ms): gdb/6228 ptrace(request: 16896, pid: 6249, data: 1048639) = 0 0.886 ( 0.001 ms): gdb/6228 ptrace(request: 3, pid: 6249, addr: 0x88, data: 140731401607416) = 0 0.888 ( 0.001 ms): gdb/6228 ptrace(request: 3, pid: 6249, addr: 0xb8, data: 140731401607416) = 0 0.901 ( 0.001 ms): gdb/6228 ptrace(request: 12, pid: 6249, data: 140731401604736) = 0 10.369 ( 0.005 ms): gdb/6228 ptrace(request: 12, pid: 6249, data: 140731401604352) = 0 10.375 ( 0.002 ms): gdb/6228 ptrace(request: 13, pid: 6249, data: 140731401604352) = 0 10.396 ( 0.001 ms): gdb/6228 ptrace(request: 7, pid: 6249, addr: 0x1) = 0 10.560 ( 0.002 ms): gdb/6228 ptrace(request: 12, pid: 6249, data: 140731401604064) = 0 10.564 ( 0.001 ms): gdb/6228 ptrace(request: 16898, pid: 6249, data: 140731401607616) = 0 10.566 ( 0.001 ms): gdb/6228 ptrace(request: 12, pid: 6249, data: 140731401604096) = 0 10.568 ( 0.001 ms): gdb/6228 ptrace(request: 13, pid: 6249, data: 140731401604096) = 0 10.666 ( 0.002 ms): gdb/6228 ptrace(request: 12, pid: 6249, data: 140731401603440) = 0 10.670 ( 0.001 ms): gdb/6228 ptrace(request: 13, pid: 6249, data: 140731401603440) = 0 10.675 ( 0.002 ms): gdb/6228 ptrace(request: 9, pid: 6249, addr: 0x1) = 0 10.687 ( 0.001 ms): gdb/6228 ptrace(request: 12, pid: 6249, data: 140731401604064) = 0 10.690 ( 0.001 ms): gdb/6228 ptrace(request: 16898, pid: 6249, data: 140731401607616) = 0 10.692 ( 0.001 ms): gdb/6228 ptrace(request: 3, pid: 6249, addr: 0x380, data: 140731401607256) = 0 10.709 ( 0.001 ms): gdb/6228 ptrace(request: 12, pid: 6249, data: 140731401604272) = 0 10.711 ( 0.001 ms): gdb/6228 ptrace(request: 13, pid: 6249, data: 140731401604272) = 0 10.716 ( 0.001 ms): gdb/6228 ptrace(request: 3, pid: 6249, addr: 0x380, data: 140731401607768) = 0 10.721 ( 0.002 ms): gdb/6228 ptrace(request: 7, pid: 6249, addr: 0x1) = 0 10.860 ( 0.002 ms): gdb/6228 ptrace(request: 12, pid: 6249, data: 140731401604064) = 0 10.864 ( 0.001 ms): gdb/6228 ptrace(request: 16898, pid: 6249, data: 140731401607616) = 0 10.866 ( 0.001 ms): gdb/6228 ptrace(request: 12, pid: 6249, data: 140731401604096) = 0 10.869 ( 0.002 ms): gdb/6228 ptrace(request: 13, pid: 6249, data: 140731401604096) = 0 92.068 ( 0.006 ms): gdb/6228 ptrace(request: 3, pid: 6249, addr: 0xa8, data: 140731401605624) = 0 92.244 ( 0.003 ms): gdb/6228 ptrace(request: 12, pid: 6249, data: 140731401603440) = 0 92.249 ( 0.002 ms): gdb/6228 ptrace(request: 13, pid: 6249, data: 140731401603440) = 0 92.257 ( 0.003 ms): gdb/6228 ptrace(request: 9, pid: 6249, addr: 0x1) = 0 92.393 ( 0.002 ms): gdb/6228 ptrace(request: 12, pid: 6249, data: 140731401603936) = 0 92.397 ( 0.001 ms): gdb/6228 ptrace(request: 16898, pid: 6249, data: 140731401607488) = 0 92.400 ( 0.001 ms): gdb/6228 ptrace(request: 3, pid: 6249, addr: 0x380, data: 140731401607128) = 0 92.459 ( 0.001 ms): gdb/6228 ptrace(request: 3, pid: 6249, addr: 0xa8, data: 140731401607128) = 0 92.469 ( 0.001 ms): gdb/6228 ptrace(request: 12, pid: 6249, data: 140731401604272) = 0 92.471 ( 0.001 ms): gdb/6228 ptrace(request: 13, pid: 6249, data: 140731401604272) = 0 92.477 ( 0.001 ms): gdb/6228 ptrace(request: 3, pid: 6249, addr: 0x380, data: 140731401607768) = 0 92.482 ( 0.001 ms): gdb/6228 ptrace(request: 7, pid: 6249, addr: 0x1) = 0
可以看到,gdb频繁的调用ptrace来获取寄存器并修改指针寄存器的值。
根据man-page的文档,可以知道,ptrace只能被一个人去attach,所以防止程序被调试的方法很简单,那就是主动调用TRACEME让父进程来调试自己, 如下
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <signal.h> #include <sys/prctl.h> #include <sys/ptrace.h> int main() { if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0 ) { exit(42); } while(1) { printf("hello \n"); sleep(1); } return 0; }
这里使用PTRACE_TRACEME来让父进程调试自己,那么可以从程序运行后看到如下信息
# cat /proc/$(pidof hello)/status | grep TracerPid TracerPid: 5071
这里可以看到自己被attach的pid是5071,而5071是自己程序运行的父进程bash。此时因为ptrace只能被一个人trace,所有其他的程序无法调试它。此时可以尝试gdb调试此程序
# gdb attach 6526 Attaching to process 6526 Could not attach to process. If your uid matches the uid of the target process, check the setting of /proc/sys/kernel/yama/ptrace_scope, or try again as the root user. For more details, see /etc/sysctl.d/10-ptrace.conf warning: process 6526 is already traced by process 5071
可以看到其他程序无法调试此程序。
同样的程序也无法在运行时被调试,如下
# gdb ./hello (gdb) r Starting program: /root/gdb/hello [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". [Inferior 1 (process 6578) exited with code 052]
到了这里,可以知道了gdb本身是ptrace的封装,其利用内核实现的ptrace机制来完成程序的调试,从而获取当前代码运行时刻的寄存器,内存,栈等相关信息。
那么我有一个问题,如何尝试让TracerPid为0,从而伪装自身并未已被参与调试。