反调试:gdb不为断点写入0xcc字节.知道为什么吗?

use*_*119 6 c linux debugging gdb

我正在学习Linux上的一些反调试技术,并发现了一段代码,用于检查内存中的0xcc字节以检测gdb中的断点.这是代码:

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

     foo();
Run Code Online (Sandbox Code Playgroud)

但它不起作用.我甚至试图在foo()函数上设置断点并观察内存中的内容,但没有看到为断点写的任何0xcc字节.这是我做的:

(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  .....
Run Code Online (Sandbox Code Playgroud)

如您所见,似乎没有在foo()函数的入口点上写入0xcc字节.有谁知道发生了什么或我可能错在哪里?谢谢.

dbr*_*nk0 8

第二部分很容易解释(正如Flortify正确陈述的那样):GDB显示原始内存内容,而不是断点"字节".在默认模式下,它实际上甚至会在调试器挂起时删除断点,并在继续之前重新插入它们.用户通常希望查看其代码,而不是用于断点的奇怪修改指令.

使用您的C代码,您错过了几个字节的断点.GDB 函数序言之后设置断点,因为函数序言通常不是gdb用户想要看到的.因此,如果你把break放到foo,那么实际的断点通常会在那之后位于几个字节之后(取决于与函数有关的序言代码本身,因为它可能或者可能不需要保存堆栈指针,帧指针等).但很容易检查.我用过这段代码:

#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;
}
Run Code Online (Sandbox Code Playgroud)

如果我们自己运行这个程序,它打印:

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

现在我们在gdb中运行它(输出重新格式化为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.

有了这个我们验证,该程序正在打印正确的字节.但这也表明断点已插入0x400585(即在函数序言之后),而不是在函数的第一个指令处.如果我们现在在gdb(运行)下运行程序,然后在命中断点后"继续",我们得到这个输出:

(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 

现在显示0xcc被打印为地址9字节到main.