我在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,并获得相同的结果。当我这样做的时候,我是否没有跳进指令的中间?在这种情况下,为什么这不会使程序崩溃,就像我将6个字节添加到返回地址而不是5个字节或7个字节。
问题3
堆栈上buffer1[]前面是sfp,前面是返回地址。
即4字节通过buffer1[]的结尾。但是记住buffer1[]是
实际上是2个字,所以它有8个字节长。所以返回地址是
buffer1[]的开始。
在aleph 1的例子中,他/她将返回地址的偏移量计算为从buffer1[]开始的12个字节。因为我在x86_64上,而不是x86_32上,所以需要重新计算返回地址的偏移量。在x86_64上,buffer1[]是否仍然是2个字,即16个字节;sfp和返回地址各为8个字节(正如我们在64位上一样),因此返回地址位于:
0x0000000000400595 <+35>: movl $0x1,-0x4(%rbp)
,相当于buf1 + (8 * 2) + 8
? 最佳答案
首先,也是非常重要的一点,要注意:所有的数字和偏移量都非常依赖于编译器。不同的编译器,甚至是具有不同设置的同一个编译器,都可以生成截然不同的程序集。例如,许多编译器可以(并且将)删除buf2
,因为它未被使用。它们还可以删除x = 0
,因为它的效果不被使用,稍后会被覆盖。它们还可以移除x = 1
,并用常数x
替换所有出现的1
,等等。
也就是说,您绝对需要为您在特定编译器及其设置上获得的特定程序集生成数字。
问题1
由于您为main()
提供了程序集,我可以确认您需要向返回地址添加7个字节(通常为0x0000000000400595
),以便跳过x=1
并转到0x000000000040059c
,后者将x
加载到寄存器中以供以后使用。0x000000000040059c - 0x0000000000400595 = 7
。
问题2
只添加5个字节而不是7个字节确实会跳转到指令中间。然而,这个2字节的指令尾(纯属偶然)是另一个有效的指令代码。这就是为什么它不会崩溃。
问题3
这同样与编译器和设置有关。几乎所有的事情都有可能发生。既然你没有提供反汇编,我只能猜测。猜测如下:buf
和buf2
被舍入到下一个堆栈单元边界(x64上为8字节)。buf
变成8字节,buf2
变成16字节。帧指针未保存到x64上的堆栈,因此没有“sfp”。总共24个字节。