打破了堆栈的例子,ala Aleph。

时间:2021-12-20 04:30:53

I've reproduced Example 3 from Smashing the Stack for Fun and Profit on Linux x86_64. However I'm having trouble understanding what is the correct number of bytes that should be incremented to the return address in order to skip past the instruction:

我在Linux x86_64上复制了示例3,它是为了好玩和利润而打破堆栈的。但是,我不知道应该增加到返回地址的正确字节数是多少,以便跳过指令:

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

which is where I think the x = 1 instruction is. I've written the following:

这就是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);
}

and disassembled it in gdb. I have disabled address randomization and compiled the program with the -fno-stack-protector option.

然后把它分解成gdb。我已经禁用了地址随机化,并使用-fno-stack- protected选项编译了程序。

Question 1

I can see from the disassembler output below that I want to skip past the instruction at address 0x0000000000400595: both the return address from callq <fn> and the address of the movl instruction. Therefore, if the return address is 0x0000000000400595, and the next instruction is 0x000000000040059c, I should add 7 bytes to the return address?

从下面的反汇编器输出可以看出,我想跳过地址为0x00000000000000400595的指令:callq 的返回地址和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 

Question 2

I notice that I can add 5 bytes to the return address in place of 7 and achieve the same result. When I do so, am I not jumping into the middle of the instruction 0x0000000000400595 <+35>: movl $0x1,-0x4(%rbp)? In which case, why does this not crash the program, like when I add 6 bytes to the return address in place of 5 bytes or 7 bytes.

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

Question 3

Just before buffer1[] on the stack is SFP, and before it, the return address. That is 4 bytes pass the end of buffer1[]. But remember that buffer1[] is really 2 word so its 8 bytes long. So the return address is 12 bytes from the start of buffer1[].

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

In the example by Aleph 1, he/she calculates the offset of the return address as 12 bytes from the start of buffer1[]. Since I am on x86_64, and not x86_32, I need to recalculate the offset to the return address. When on x86_64, is it the case that buffer1[] is still 2 words, which is 16 bytes; and the SFP and return address are 8 bytes each (as we're on 64 bit) and therefore the return address is at: buf1 + (8 * 2) + 8 which is equivalent to buf1 + 24?

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

1 个解决方案

#1


1  

The first, and very important, thing to note: all numbers and offsets are very compiler-dependent. Different compilers, and even the same compiler with different settings, can produce drastically different assemblies. For example, many compilers can (and will) remove buf2 because it's not used. They can also remove x = 0 as its effect is not used and later overwritten. They can also remove x = 1 and replace all occurences of x with a constant 1, etc, etc.

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

That said, you absolutely need to make numbers for a specific assembly you're getting on your specific compiler and its settings.

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

Question 1 Since you provided the assembly for main(), I can confirm that you need to add 7 bytes to the return address, which would normally be 0x0000000000400595, to skip over x=1 and go to 0x000000000040059c which loads x into register for later use. 0x000000000040059c - 0x0000000000400595 = 7.

问题1因为您为main()提供了程序集,所以我可以确认您需要向返回地址添加7个字节(通常是0x00000000000000400595),以便跳过x=1,转到0x000000000040059c,该地址将x加载到register中以备以后使用。0x000000000040059c - 0x0000000000400595 = 7。

Question 2 Adding just 5 bytes instead of 7 will indeed jump into middle of instruction. However, this 2-byte tail of instruction happen (by pure chance) to be another valid instruction code. This is why it doesn't crash.

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

Question 3 This is again very compiler and settings dependent. Pretty much everything can happen there. Since you didn't provide disassembly, I can only make guesses. The guess would be the following: buf and buf2 are rounded up to the next stack unit boundary (8 bytes on x64). buf becomes 8 bytes, and buf2 becomes 16 bytes. Frame pointers are not saved to stack on x64, so no "SFP". That's 24 bytes total.

问题3这又是一个非常依赖编译器和设置的问题。那里几乎什么都可能发生。既然你没有拆解,我只能猜测。猜测如下:buf和buf2被四舍五入到下一个堆栈单元边界(x64上为8字节)。buf变成8字节,buf2变成16字节。没有将帧指针保存到x64上的堆栈中,所以没有“SFP”。这是24字节总数。

#1


1  

The first, and very important, thing to note: all numbers and offsets are very compiler-dependent. Different compilers, and even the same compiler with different settings, can produce drastically different assemblies. For example, many compilers can (and will) remove buf2 because it's not used. They can also remove x = 0 as its effect is not used and later overwritten. They can also remove x = 1 and replace all occurences of x with a constant 1, etc, etc.

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

That said, you absolutely need to make numbers for a specific assembly you're getting on your specific compiler and its settings.

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

Question 1 Since you provided the assembly for main(), I can confirm that you need to add 7 bytes to the return address, which would normally be 0x0000000000400595, to skip over x=1 and go to 0x000000000040059c which loads x into register for later use. 0x000000000040059c - 0x0000000000400595 = 7.

问题1因为您为main()提供了程序集,所以我可以确认您需要向返回地址添加7个字节(通常是0x00000000000000400595),以便跳过x=1,转到0x000000000040059c,该地址将x加载到register中以备以后使用。0x000000000040059c - 0x0000000000400595 = 7。

Question 2 Adding just 5 bytes instead of 7 will indeed jump into middle of instruction. However, this 2-byte tail of instruction happen (by pure chance) to be another valid instruction code. This is why it doesn't crash.

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

Question 3 This is again very compiler and settings dependent. Pretty much everything can happen there. Since you didn't provide disassembly, I can only make guesses. The guess would be the following: buf and buf2 are rounded up to the next stack unit boundary (8 bytes on x64). buf becomes 8 bytes, and buf2 becomes 16 bytes. Frame pointers are not saved to stack on x64, so no "SFP". That's 24 bytes total.

问题3这又是一个非常依赖编译器和设置的问题。那里几乎什么都可能发生。既然你没有拆解,我只能猜测。猜测如下:buf和buf2被四舍五入到下一个堆栈单元边界(x64上为8字节)。buf变成8字节,buf2变成16字节。没有将帧指针保存到x64上的堆栈中,所以没有“SFP”。这是24字节总数。