【问题标题】:Does the main function of a C program ever reclaim the stack?C 程序的 main 函数是否曾经回收堆栈?
【发布时间】:2017-05-16 22:36:04
【问题描述】:

我正在研究 OverTheWire 兵棋推演,我的一个漏洞利用 system 的地址覆盖了 main 的返回地址。然后我使用了这样一个事实,即在main 返回时,esp 仍然指向我的一个局部变量,因此我可以用我希望system 运行的命令填充它(例如sh;#)。

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

与其他挑战相比,我注意到这个特殊挑战的主要内容是它在最后调用exit(0),而不是仅仅结束,因此程序集不会以leave 结束,这可能是这种行为。

我没有包含实际代码,因为它很长,我希望对我所看到的内容有一个一般性的解释,但请让我知道该程序集是否有用。

【问题讨论】:

  • 是的,你没看错。在main 有机会进行清理工作之前,调用exit 会终止程序。
  • 啊太棒了,你知道我在哪里可以读到这个吗?还是我应该直接通过程序集让exit 看到它?
  • 它不会是退出的东西,它会在操作系统中。操作系统在程序退出时为程序分配内存,操作系统释放内存,全部是程序、数据、栈……
  • exit 应该只是导致对操作系统的系统调用......也许它会进行其他清理,例如关闭所有文件和它可以管理的东西,或者操作系统可能会这样做。
  • exit 的调用应该类似于push 0; call exit;,此时堆栈中的内容并不重要,因为我认为它没有理由使用其他任何东西。除非在 C 库在 exit 中使用的堆栈中 main 之上有一些东西。您可以显示代码的任何部分吗?

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


【解决方案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 

毫不奇怪,这里和以前一样,我们需要看到的就是了解堆栈在返回之前被清理了。并且那个 main 并不特别:

#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() 代码及其调用的任何内容的反汇编,才能弄清楚这一点。

【讨论】:

  • exit() 没有什么特别之处,要么它只是一个函数,如果编译它也符合调用约定,并且可能有一个堆栈框架等,如果 asm 那么他们做了他们所做的,取决于您链接的操作系统和库。因此,没有理由假设您可以检查 exit() 调用上的堆栈指针并对其上方/下方的内容做出任何假设。编译器和编译器选项在这方面也发挥着重要作用,编译后的函数总是被清理,但如果有任何不一致,实际在堆栈上的内容。
猜你喜欢
  • 2014-01-06
  • 2011-07-18
  • 2014-01-05
  • 2021-01-17
  • 1970-01-01
  • 1970-01-01
  • 2014-10-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多