GDB Debugging Methods (2) – Ptrace

GDB uses the ptrace system call to implement its functionality. This article provides a brief overview of ptrace.

Ptrace

The ptrace system call

/* kernel/ptrace.c */
#define __NR_ptrace 117
__SYSCALL(__NR_ptrace, sys_ptrace)

When using ptrace, you need to include<span>/sys/ptrace.h</span>. When calling the ptrace system call, you need to send requests based on different requests, which are as follows:

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
};

Different ptrace requests have brief comments above them. For more detailed information, you can refer to the man-page link.

https://man7.org/linux/man-pages/man2/ptrace.2.html

How GDB Calls Ptrace

For GDB, we can prepare a simple hello world program and then use GDB for debugging. Let’s see how a simple hello world program makes a ptrace call. The simple program is as follows:

int main() { printf("hello \n");}

Since ptrace is a system call, it can be directly captured using<span>perf trace -e</span>, as follows:

gcc test.c -o test
perf trace -e ptrace -p $(pidof gdb)

The running log is as follows:

     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

As we can see, GDB frequently calls ptrace to obtain registers and modify the values of pointer registers.

How to Prevent a Program from Being Debugged

According to the man-page documentation, ptrace can only be attached by one process, so the method to prevent a program from being debugged is simple: actively call TRACEME to let the parent process debug itself, as follows:

#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;
}

Here, we use PTRACE_TRACEME to let the parent process debug itself. After running the program, we can see the following information:

# cat /proc/$(pidof hello)/status | grep TracerPid
TracerPid:      5071

Here we can see that the attached pid is 5071, which is the parent process bash running the program. Since ptrace can only be traced by one process, all other programs cannot debug it. At this point, we can try to debug this program with 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

As we can see, other programs cannot debug this program.

Similarly, the program cannot be debugged while running, as follows:

# 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]

Conclusion

At this point, we can understand that GDB is essentially a wrapper around ptrace, utilizing the ptrace mechanism implemented by the kernel to complete program debugging, thereby obtaining relevant information such as registers, memory, and stack at the current code execution moment.

Now I have a question: how can I try to make the TracerPid be 0, thus disguising myself as not being debugged?

Leave a Comment