c - C 程序的主函数是否会回收堆栈?

标签 c assembly x86 buffer-overflow calling-convention

我正在研究 OverTheWire war 游戏,我的一个漏洞利用了 system 的地址覆盖了 main 的返回地址。然后我利用了这样一个事实:在 main 返回时,esp 仍然指向我的局部变量之一,因此我可以用我想要的命令填充它system 运行(例如 sh;#)。

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

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

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

最佳答案

#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 没有什么特别的,这些只是符合调用约定的函数。正如重新提出的问题所示,有时编译器会在看到 main() 时添加额外的堆栈或其他调用,否则它不会添加。但它仍然是一个需要符合调用约定的函数。正如本例中所见,堆栈指针被放回原来的位置。

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

在上面的情况下,我们看到 Bootstrap 调用 main 和 main 返回,然后 hlt 被命中,这是一个应用程序而不是内核代码,因此应该会导致一个陷阱,从而导致操作系统进行清理。显式 exit() 应该与 printf() 、 puts() 或 fopen() 或最终对操作系统进行一次或多次系统调用的任何其他函数没有什么不同。对于这些类型的操作系统(Linux、Windows、MacOS),您可能找到的就是系统调用。内存的释放发生在程序之外,因为程序无法控制它,这将是一个先有鸡还是先有蛋的问题,程序释放用于释放 mmu 表的 mmu 表...

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

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() ,那么堆栈指针将留在第二个函数的堆栈帧点加上任何调用(这是一个 x86)加上 exit() 堆栈框架添加到它。你不能简单地假设当 exit() 被调用时,如果它被调用,堆栈指针指向什么。您必须检查对 exit() 调用的反汇编以及 exit() 代码及其调用的任何内容,才能弄清楚这一点。

关于c - C 程序的主函数是否会回收堆栈?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44012695/

相关文章:

c++ - 对于大于 32,767 的值,强制转换为 "signed int"并返回 "signed short"如何工作?

c - 将具有不同数据类型的文本文件放入结构数组

c - 在链接列表的函数中传递指针

c - 在 C 中返回 char* 的 x86 函数

assembly - 在 ISO 9660 文件系统上加载文件

c - C 代码(.h 文件)中的解析错误

c++ - 在 C/C++ 中是否有类似极度优化的 memcpy2d 之类的东西?

c - 学习汇编——全部注释掉,需要生成伪代码

assembly - 在 DOS 中获取没有回显的键盘输入

c - 编译器会把跳转表放在函数的末尾吗?