粉碎堆栈example3 ala Aleph One

时间:2015-06-02 14:23:58

标签: c security pointers stack-smash

我在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 <fn>的返回地址和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 <+35>: 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上,是否缓冲区[]仍然是2个字,即16个字节;并且SFP和返回地址各为8个字节(因为我们是64位),因此返回地址位于:buf1 + (8 * 2) + 8,相当于buf1 + 24

1 个答案:

答案 0 :(得分:2)

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

也就是说,您绝对需要为特定编译器及其设置获取的特定程序集编号。

问题1 由于您为main()提供了程序集,我可以确认您需要在返回地址(通常为0x0000000000400595)中添加7个字节,以跳过x=1并转到{{ 1}}将0x000000000040059c加载到寄存器中供以后使用。 x

问题2 仅添加5个字节而不是7个字节确实会跳转到指令中间。但是,这个2字节的指令尾部(纯粹偶然)发生了另一个有效的指令代码。这就是为什么它不会崩溃。

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