Anti-debugging: gdb does not write 0xcc bytes for breakpoints. Any idea why?

I am learning some debugging methods on Linux and found a piece of code to check for 0xcc byte in memory to detect breakpoints in gdb. Here is the code:

if ((*(volatile unsigned *)((unsigned)foo + 3) & 0xff) == 0xcc) { printf("BREAKPOINT\n"); exit(1); } foo(); 

But that will not work. I even tried to set a breakpoint on the foo () function and watch the contents in memory, but did not see any 0xcc byte written for the breakpoint. Here is what I did:

 (gdb) b foo Breakpoint 1 at 0x804846a: file p4.c, line 8. (gdb) x/x 0x804846a 0x804846a <foo+6>: 0xe02404c7 (gdb) x/16x 0x8048460 0x8048460 <frame_dummy+32>: 0x90c3c9d0 0x83e58955 0x04c718ec 0x0485e024 0x8048470 <foo+12>: 0xfefae808 0xc3c9ffff ..... 

As you can see, there is no byte 0xcc written to the entry point of foo (). Does anyone know what is happening or where can I be wrong? Thanks.

+6
source share
3 answers

The second part is easily explained (as correctly stated by Flortify): GDB shows the original contents of the memory, not the breakpoint "bytes". In default mode, it even removes breakpoints when the debugger pauses and reinserts them before continuing. Typically, users want to see their code, rather than the weird modified instructions used for breakpoints.

With C code, you missed a breakpoint for several bytes. GDB sets a breakpoint after the function prolog , since the function prolog is usually not what gdb users want to see. Thus, if you put break on foo, the actual breakpoint will usually be located after a few bytes (it depends on the prolog code itself, which depends on the function, since it may or may not contain a stack pointer, frame pointer, etc.). But this is easy to verify. I used this code:

 #include <stdio.h> int main() { int i,j; unsigned char *p = (unsigned char*)main; for (j=0; j<4; j++) { printf("%p: ",p); for (i=0; i<16; i++) printf("%.2x ", *p++); printf("\n"); } return 0; } 

If we run this program ourselves, it prints:

  0x40057d: 55 48 89 e5 48 83 ec 10 48 c7 45 f8 7d 05 40 00
 0x40058d: c7 45 f4 00 00 00 00 eb 5a 48 8b 45 f8 48 89 c6
 0x40059d: bf 84 06 40 00 b8 00 00 00 00 e8 b4 fe ff ff c7
 0x4005ad: 45 f0 00 00 00 00 eb 27 48 8b 45 f8 48 8d 50 01 

Now we run it in gdb (the output is reformatted for SO).

  (gdb) break main
 Breakpoint 1 at 0x400585: file ../bp.c, line 6.
 (gdb) info break
 Num Type Disp Enb Address What
 1 breakpoint keep y 0x0000000000400585 in main at ../bp.c:6
 (gdb) disas / r main, + 32
 Dump of assembler code from 0x40057d to 0x40059d:
   0x000000000040057d (main + 0): 55 push% rbp
   0x000000000040057e (main + 1): 48 89 e5 mov% rsp,% rbp
   0x0000000000400581 (main + 4): 48 83 ec 10 sub $ 0x10,% rsp
   0x0000000000400585 (main + 8): 48 c7 45 f8 7d 05 40 00 movq $ 0x40057d, -0x8 (% rbp)
   0x000000000040058d (main + 16): c7 45 f4 00 00 00 00 movl $ 0x0, -0xc (% rbp)
   0x0000000000400594 (main + 23): eb 5a jmp 0x4005f0 
   0x0000000000400596 (main + 25): 48 8b 45 f8 mov -0x8 (% rbp),% rax
   0x000000000040059a (main + 29): 48 89 c6 mov% rax,% rsi
 End of assembler dump. 

At the same time, we checked that the program prints the correct bytes. But it also shows that the breakpoint was inserted at 0x400585 (that is, after the prologue of the function), and not at the beginning of the function instruction. If we now run the program under gdb (with launch) and then continue after the breakpoint has been deleted, we get this result:

  (gdb) cont
 Continuing
 0x40057d: 55 48 89 e5 48 83 ec 10 cc c7 45 f8 7d 05 40 00 
 0x40058d: c7 45 f4 00 00 00 00 eb 5a 48 8b 45 f8 48 89 c6 
 0x40059d: bf 84 06 40 00 b8 00 00 00 00 e8 b4 fe ff ff c7 
 0x4005ad: 45 f0 00 00 00 00 eb 27 48 8b 45 f8 48 8d 50 01 

Now this shows that 0xcc is printed for the address of 9 bytes in main.

+8
source

If your hardware supports it, GDB may use Hardware Breakpoints , which do not fix the code.

Until I confirmed this through any official documents, this page indicates that

By default, gdb attempts to use fault tolerant hardware.

Since you indicate that you expect 0xCC bytes, I assume that you are working on x86 hardware, as the int3 operation int3 0xCC . X86 processors have a set of debugging registers DR0 - DR3 where you can program the data address to cause a breakpoint exception. DR7 is a DR7 that controls the behavior of breakpoints, and DR6 indicates status.

Debug registers can only be read / written from Ring 0 (kernel mode). This means that the kernel manages these registers for you (via the ptrace API, I believe.)

However, for the sake of anti-debugging, all hope is not lost! In the Windows API, GetThreadContext allows you to get a (copy) CONTEXT for a (stopped) thread. This structure includes the contents of DRx registers. This question is about how to implement the same thing on Linux.

+4
source

It could also be the white lie that GDB is talking about ... there might be a breakpoint in RAM, but GDB noted that it was there in advance (so that it can restore it later) and shows you that instead of the true contents of RAM.

Of course, he can also use Hardware Breakpoints , which is available for some processors. The h / w control points are set by indicating to the processor the address to which it should monitor (and start interrupting the breakpoint if it enters the program counter when executing the code).

+2
source

All Articles