【问题标题】:Smashing the stack example3 ala Aleph One粉碎堆栈 example3 ala Aleph One
【发布时间】:2015-06-02 14:23:57
【问题描述】:

我在 Linux x86_64 上从Smashing the Stack for Fun and Profit 复制了示例 3。但是,我无法理解应该递增到返回地址以跳过指令的正确字节数:

0x0000000000400595 <+35>:   movl   $0x1,-0x4(%rbp)

这是我认为x = 1 指令所在的位置。我写了以下内容:

#include <stdio.h>

void fn(int a, int b, int c) {
  char buf1[5];
  char buf2[10];
  int *ret;

  ret = buf1 + 24;
  (*ret) += 7;
}

int main() {
  int x;

  x = 0;
  fn(1, 2, 3);
  x = 1;
  printf("%d\n", x);
}

并在 gdb 中对其进行反汇编。我已禁用地址随机化并使用-fno-stack-protector 选项编译程序。

问题 1

我可以从下面的反汇编程序输出中看到,我想跳过地址 0x0000000000400595 处的指令:callq &lt;fn&gt; 的返回地址和 movl 指令的地址。所以,如果返回地址是0x0000000000400595,下一条指令是0x000000000040059c,我应该在返回地址上加7个字节?

0x0000000000400572 <+0>:    push   %rbp
0x0000000000400573 <+1>:    mov    %rsp,%rbp
0x0000000000400576 <+4>:    sub    $0x10,%rsp
0x000000000040057a <+8>:    movl   $0x0,-0x4(%rbp)
0x0000000000400581 <+15>:   mov    $0x3,%edx
0x0000000000400586 <+20>:   mov    $0x2,%esi
0x000000000040058b <+25>:   mov    $0x1,%edi
0x0000000000400590 <+30>:   callq  0x40052d <fn>
0x0000000000400595 <+35>:   movl   $0x1,-0x4(%rbp)
0x000000000040059c <+42>:   mov    -0x4(%rbp),%eax
0x000000000040059f <+45>:   mov    %eax,%esi
0x00000000004005a1 <+47>:   mov    $0x40064a,%edi
0x00000000004005a6 <+52>:   mov    $0x0,%eax
0x00000000004005ab <+57>:   callq  0x400410 <printf@plt>
0x00000000004005b0 <+62>:   leaveq 
0x00000000004005b1 <+63>:   retq 

问题 2

我注意到我可以在返回地址中添加 5 个字节来代替 7 并获得相同的结果。当我这样做时,我不是跳到指令0x0000000000400595 &lt;+35&gt;: movl $0x1,-0x4(%rbp) 的中间吗?在这种情况下,为什么这不会使程序崩溃,例如当我将 6 个字节添加到返回地址而不是 5 个字节或 7 个字节时。

问题 3

堆栈上的 buffer1[] 之前是 SFP,在它之前是返回地址。 即 4 个字节通过 buffer1[] 的末尾。但请记住 buffer1[] 是 真的是 2 个字,所以它有 8 个字节长。所以返回地址是 12 个字节 buffer1[] 的开始。

在 Aleph 1 的示例中,他/她从 buffer1[] 的开始计算返回地址的偏移量为 12 个字节。由于我使用的是 x86_64,而不是 x86_32,因此我需要重新计算返回地址的偏移量。在x86_64上,是不是buffer1[]还是2个字,也就是16个字节;并且 SFP 和返回地址各为 8 个字节(因为我们是 64 位),因此返回地址位于:buf1 + (8 * 2) + 8,相当于buf1 + 24

【问题讨论】:

标签: c security pointers stack-smash


【解决方案1】:

要注意的第一件事,也是非常重要的一点:所有数字和偏移量都非常依赖于编译器。不同的编译器,甚至是具有不同设置的同一个编译器,可以产生截然不同的程序集。例如,许多编译器可以(并且将)删除buf2,因为它没有被使用。他们还可以删除x = 0,因为它的效果未被使用并在以后被覆盖。他们还可以删除x = 1 并将所有出现的x 替换为常量1 等。

也就是说,您绝对需要为您在特定编译器及其设置上获得的特定程序集生成数字。

问题 1 由于您为main() 提供了程序集,我可以确认您需要在返回地址(通常为0x0000000000400595)中添加7 个字节,以跳过x=1 并转到加载x0x000000000040059c入注册以备后用。 0x000000000040059c - 0x0000000000400595 = 7.

问题 2 仅添加 5 个字节而不是 7 个字节确实会跳转到指令中间。然而,这个 2 字节的指令尾部恰好(纯属偶然)是另一个有效的指令代码。这就是它不会崩溃的原因。

问题 3 这又非常依赖于编译器和设置。几乎所有事情都可能发生在那里。由于您没有提供反汇编,我只能猜测。猜测如下:bufbuf2 向上舍入到下一个堆栈单元边界(x64 上为 8 个字节)。 buf 变为 8 个字节,buf2 变为 16 个字节。帧指针不会保存到 x64 上的堆栈,因此没有“SFP”。总共 24 个字节。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多