破解堆栈示例3:Aleph One式

18

我已经在Linux x86_64上重现了Smashing the Stack for Fun and Profit的第三个示例。然而,我不明白应该增加多少字节到返回地址以跳过指令。

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个字节或7个字节来替代5个字节时一样。

问题3

就在堆栈上的buffer1[]之前是SFP,在它之前是返回地址。 这超出了buffer1[]的4个字节。但要记住,buffer1[]实际上是2个字, 因此它有8个字节长。所以返回地址距离buffer1[]的开头12个字节。

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

4
内存对齐20年前和现在的情况有关的内容可能有助于理解从那时起发生了哪些变化。 - Shafik Yaghmour
1个回答

2
首先需要注意的是:所有数字和偏移量都非常依赖于编译器。不同的编译器,甚至相同的编译器在不同的设置下,可能会产生截然不同的汇编代码。例如,许多编译器可以(并且会)删除buf2,因为它没有被使用。它们也可以删除x = 0,因为它的效果没有被使用并被后面的代码覆盖了。它们也可以删除x = 1并将所有出现的x替换为常量1等等。
话虽如此,你确实需要根据你的编译器及其设置来生成特定汇编代码所需的数字。 问题1 由于你提供了main()的汇编代码,我可以确定你需要在返回地址上添加7个字节,这通常是0x0000000000400595,以跳过x=1并转到0x000000000040059c,该指令将x加载到寄存器以供以后使用。0x000000000040059c - 0x0000000000400595 = 7问题2 只添加5个字节而不是7个字节将确实跳到指令的中间。但是,这个指令的2个字节尾部(由于纯粹的偶然)恰好是另一个有效的指令代码。这就是为什么它不会崩溃的原因。 问题3 这又取决于编译器和设置。几乎所有事情都可能发生。由于你没有提供反汇编,我只能猜测。猜测是:将bufbuf2舍入到下一个堆栈单元边界(在x64上为8个字节)。buf变成了8个字节,而buf2变成了16个字节。在x64上,帧指针不保存到堆栈中,因此没有"SFP"。总共是24个字节。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接