C程序的主要功能是否会回收堆栈?

时间:2017-05-16 22:36:05

标签: c assembly x86 buffer-overflow calling-convention

我正在浏览OverTheWire战争游戏,我的一个漏洞会覆盖main的地址为system的地址。然后我使用了main返回的事实,esp仍然指向我的一个局部变量,因此我可以用我希望system运行的命令填充它(例如sh;#)。

我的困惑来自于我认为C中的函数在返回之前回收堆栈,因此在返回地址被调用时,堆栈指针将指向返回地址而不是局部变量。但是,我的漏洞利用工作原因似乎我的堆栈指针在调用返回地址时指向局部变量。

与其他人相比,我注意到这个特殊挑战的主要原因是它最后调用了exit(0),而不仅仅是结束,因此程序集不会以leave结尾,这可能是func myconstrainer(_ views: [UIView], spacing: CGFloat) { myconstrainer(views, spacings: repeatElement(spacing, count: views.count)) } func myconstrainer<C: Collection>(_ views : [UIView], spacings: C) where C.Iterator.Element == CGFloat { // actual implementation... } 是这种行为的原因。

我没有包含实际的代码,因为它很长,而且我希望对我所看到的内容有一般性的解释,但是如果程序集有用,请告诉我。

1 个答案:

答案 0 :(得分:1)

#include <stdio.h>
int main ( void )
{
    printf("hello\n");
    return(0);
}

有趣的相关部分。

0000000000400430 <main>:
  400430:   48 83 ec 08             sub    $0x8,%rsp
  400434:   bf d4 05 40 00          mov    $0x4005d4,%edi
  400439:   e8 c2 ff ff ff          callq  400400 <puts@plt>
  40043e:   31 c0                   xor    %eax,%eax
  400440:   48 83 c4 08             add    $0x8,%rsp
  400444:   c3                      retq   
  400445:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
  40044c:   00 00 00 
  40044f:   90                      nop



0000000000400450 <_start>:
  400450:   31 ed                   xor    %ebp,%ebp
  400452:   49 89 d1                mov    %rdx,%r9
  400455:   5e                      pop    %rsi
  400456:   48 89 e2                mov    %rsp,%rdx
  400459:   48 83 e4 f0             and    $0xfffffffffffffff0,%rsp
  40045d:   50                      push   %rax
  40045e:   54                      push   %rsp
  40045f:   49 c7 c0 c0 05 40 00    mov    $0x4005c0,%r8
  400466:   48 c7 c1 50 05 40 00    mov    $0x400550,%rcx
  40046d:   48 c7 c7 30 04 40 00    mov    $0x400430,%rdi
  400474:   e8 97 ff ff ff          callq  400410 <__libc_start_main@plt>
  400479:   f4                      hlt    
  40047a:   66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)

在大多数情况下,main和printf等都没有什么特别之处,这些只是符合调用约定的函数。正如重新询问的SO问题有时会显示编译器在看到main()时会添加额外的堆栈或其他调用。但它仍然是一个需要符合调用约定的函数。正如在这种情况下所见,堆栈指针被放回原位。

在操作系统(Linux,Windows,MacOS等)甚至可以考虑运行程序之前,需要为该程序分配一些空间,并以某种方式为该程序标记该内存,具体取决于处理器的功能和操作系统等。然后从任何媒体加载程序,并在指定的二进制文件和/或众所周知的入口点启动它。一个干净的程序退出将导致操作系统释放该内存,其中.text,.data,.bss和堆栈是琐碎/明显的内存,它们的内存消失就会消失。其他可能已经分配并与此程序相关联的项目,打开文件,运行时分配(非堆栈)内存等也可以/应该被释放,这取决于操作系统和/或C库的设计如何发生

在上面的例子中,我们看到bootstrap调用main和main返回,然后hlt被命中,这是一个应用程序而不是内核代码,因此应该导致一个导致操作系统清理的陷阱。显式exit()应该与printf()或puts()或fopen()或最终使一个或多个系统调用进入操作系统的任何其他函数没有区别。您可以在这些类型的操作系统(Linux,Windows,MacOS)中找到的所有内容都是系统调用。内存的释放发生在程序之外,因为程序无法控制它,这将是鸡和蛋的问题,程序释放了用于释放mmu表的mmu表......

为main而不是整个程序编译和反汇编对象

0000000000000000 <main>:
   0:   48 83 ec 08             sub    $0x8,%rsp
   4:   bf 00 00 00 00          mov    $0x0,%edi
   9:   e8 00 00 00 00          callq  e <main+0xe>
   e:   31 c0                   xor    %eax,%eax
  10:   48 83 c4 08             add    $0x8,%rsp
  14:   c3                      retq 

与以前一样令人惊讶,我们需要了解堆栈在返回之前已经清理过。那主要不是特别的:

#include <stdio.h>
int notmain ( void )
{
    printf("hello\n");
    return(0);
}

0000000000000000 <notmain>:
   0:   48 83 ec 08             sub    $0x8,%rsp
   4:   bf 00 00 00 00          mov    $0x0,%edi
   9:   e8 00 00 00 00          callq  e <notmain+0xe>
   e:   31 c0                   xor    %eax,%eax
  10:   48 83 c4 08             add    $0x8,%rsp
  14:   c3                      retq   

现在,如果你在询问main中是否有exit(),那么确定它不会点击main中的返回点,因此堆栈指针会以任何数量偏移。但是如果main调用某个函数并且该函数调用某个函数,那么该函数调用exit()然后堆栈指针留在函数2的堆栈帧点加上无论调用(这是x86)加上exit()堆栈框架增加了它。你不能简单地假设当调用exit()时,如果调用它,那么堆栈指针指向的是什么。您必须检查对exit()的调用以及exit()代码及其调用的任何内容的反汇编,以解决这个问题。