I am learning some anti-debugging techniques on Linux and found a snippet of code for checking 0xcc byte in memory to detect the breakpoints in gdb. Here is that code:
if ((*(volatile unsigned *)((unsigned)foo + 3) & 0xff) == 0xcc)
{
printf("BREAKPOINT\n");
exit(1);
}
foo();
But it does not work. I even tried to set a breakpoint on foo() function and observe the contents in memory, but did not see any 0xcc byte written for 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 seems to be no 0xcc byte written on the entry point of foo() function. Does anyone know what's going on or where I might be wrong? Thanks.
Second part is easily explained (as Flortify correctly stated): GDB shows original memory contents, not the breakpoint "bytes". In default mode it actually even removes breakpoints when debugger suspends and re-inserts them before continuing. Users typically want to see their code, not strange modified instructions used for breakpoints.
With your C code you missed breakpoint for few bytes. GDB sets breakpoint after function prologue, because function prologue is not typically what gdb users want to see. So, if you put break to foo, actual breakpoint will be typically located few bytes after that (depends on prologue code itself that is function dependent as it may or might not have to save stack pointer, frame pointer and so on). But it is easy to check. 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 by itself 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 (output re-formatted 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.
With this we verified, that program is printing correct bytes. But this also shows that breakpoint has been inserted at 0x400585 (that is after function prologue), not at first instruction of function. If we now run program under gdb (with run) and then "continue" after breakpoint is hit, we get this output:
(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
This now shows 0xcc being printed for address 9 bytes into main.
If your hardware supports it, GDB may be using Hardware Breakpoints, which do not patch the code.
While I have not confirmed this via any official docs, this page indicates that
By default, gdb attempts to use hardware-assisted break-points.
Since you indicate expecting 0xCC
bytes, I'm assuming you're running on x86 hardware, as the int3
opcode is 0xCC
. x86 processors have a set of debug registers DR0
-DR3
, where you can program the address of data to cause a breakpoint exception. DR7
is a bitfield which controls the behavior of the breakpoints, and DR6
indicates the status.
The debug registers can only be read/written from Ring 0 (kernel mode). That 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! On Windows, the GetThreadContext
API allows you to get (a copy) of the CONTEXT
for a (stopped) thread. This structure includes the contents of the DRx
registers. This question is about how to implement the same on Linux.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With