简体   繁体   English

C程序的主要功能是否曾经回收过堆栈?

[英]Does the main function of a C program ever reclaim the stack?

I am working through the OverTheWire wargames and one of my exploits overwrites the return address of main with the address of system . 我正在OverTheWire战争游戏中工作,我的一项攻击行为是用system地址覆盖main的返回地址。 I have then used the fact that at the point main returns, esp is still pointing at one of my local variables and hence I can fill it with the command I want system to run (eg sh;# ). 然后,我使用了这样一个事实,即在main返回时, esp仍指向我的局部变量之一,因此我可以用我希望system运行的命令(例如sh;# )填充它。

My confusion comes from that I thought functions in C reclaim the stack before returning and hence at the point the return address is called the stack pointer would be pointing at the return address rather than at the local variables. 我的困惑来自于我认为C语言中的函数在返回之前会回收堆栈,因此在该点返回地址称为堆栈指针将指向返回地址,而不是局部变量。 However, my exploit works so it seems that my stack pointer is pointing at the local variables when the return address is called. 但是,我的漏洞利用仍然有效,因此似乎在调用返回地址时,我的堆栈指针指向了局部变量。

The main thing I have noticed about this particular challenge compared to others is that it calls exit(0) at the end, instead of just ending, so the assembly doesn't end with leave , which may be the reason for this behaviour. 与其他挑战相比,与该挑战相比,我注意到的主要事情是,它在结尾处调用exit(0)而不是仅仅结束,因此程序集不会以leave结尾,这可能就是这种行为的原因。

I haven't included the actual code since it's quite long and I was hoping there was a general explanation for what I am seeing, but please let me know if the assembly would be useful. 我没有包含实际的代码,因为它已经很长了,我希望对所看到的内容有一个一般的解释,但是请告诉我汇编是否有用。

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

the interesting relevant parts. 有趣的相关部分。

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)

For the most part there is nothing special about main nor printf, etc these are just functions that conform to the calling convention. 在大多数情况下,main和printf都没有什么特别的,等等,这些只是符合调用约定的函数。 As re-asked SO questions will show sometimes the compiler will add extra stack or other calls when it sees a main() that it doesnt otherwise. 正如重新提出的SO问题将显示的那样,有时编译器在看到main()否则会添加额外的堆栈或其他调用。 but still it is a function that needs to conform to the calling convention. 但是它仍然是一个需要符合调用约定的函数。 As seen in this case where the stack pointer is put back where it was found. 如本例所示,将堆栈指针放回找到的位置。

Before an operating system (Linux, Windows, MacOS, etc) can even think about running a program it needs to allocate some space for that program and tag that memory for that program in some way depending on the features of the processor and the OS, etc. Then you load the program from whatever media, and launch it at the binary file specified and/or well known entry point. 在操作系统(Linux,Windows,MacOS等)甚至无法考虑运行某个程序之前,它需要为该程序分配一些空间并根据处理器和OS的功能以某种方式为该程序标记该内存,等。然后从任何媒体加载程序,然后在指定的二进制文件和/或众所周知的入口点启动它。 A clean exit of the program will cause the operating system to free that memory, which the .text, .data, .bss and stack are the trivial/obvious ones that just go away as their memory just goes away. 程序的干净退出将使操作系统释放该内存,.text,.data,.bss和stack是琐碎/显而易见的,随着内存的消失而消失。 Other items that may have been allocated and associated with this program, open files, runtime allocated (not stack) memory, etc can/should also be freed, depends on the design of the os and/or the C library as to how that happens. 可以/应该释放可能已经分配并与此程序相关联的其他项目,打开文件,运行时分配的(而非堆栈)内存等,这取决于os和/或C库的设计,如何实现。

In the above case we see the bootstrap calls main and main returns then hlt is hit which this is an application not kernel code so that should cause a trap that causes the OS to clean up. 在上述情况下,我们看到引导程序调用main和main返回,然后命中了hlt,这是一个应用程序而不是内核代码,因此应该引起陷阱,导致操作系统清理。 An explicit exit() should be no different than a printf() or puts() or fopen() or any other function that ultimately makes one or more syscalls to the operating system. 显式exit()应该与printf()或puts()或fopen()或最终对操作系统进行一个或多个syscall的任何其他函数没有什么不同。 All that you can possibly find for these types of operating systems (Linux, Windows, MacOS) is the syscall. 对于这些类型的操作系统(Linux,Windows,MacOS),您可能会找到的只是syscall。 The release of memory happens outside the program as the program does not have control over it, would be a chicken and egg problem, program frees mmu tables that is using to free mmu tables... 内存的释放发生在程序外部,因为程序无法控制它,这可能是鸡和鸡蛋的问题,程序会释放用于释放mmu表的mmu表...

compile and disassemble the object for main rather than the whole program 为主要而不是整个程序编译和反汇编对象

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 

no surprise there same as before, all we needed to see to understand that the stack was cleaned up before return. 与以前一样,这并不奇怪,我们所需要了解的是,在返回之前已清除了堆栈。 and that main is not special: 而且那个主并不特殊:

#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   

Now if you are asking if there is an exit() within main then sure it wont hit the return point in main so the stack pointer is offset by whatever amount. 现在,如果您要问main中是否存在exit(),请确保它不会命中main中的返回点,以便使堆栈指针偏移任意量。 but if main calls some function and that function calls some function then that function calls exit() then the stack pointer is left at the stack frame point of function number two plus whatever the call (this is an x86) plus the exit() stack frame adds to it. 但是,如果main调用某个函数,而该函数调用某个函数,则该函数调用exit(),则将堆栈指针留在第二个函数的堆栈帧点处,再加上任何调用(这是x86)加上exit()堆栈框架添加到它。 You cannot simply assume that when exit() is called, if it is called, what the stack pointer is pointing at. 您不能简单地假设在调用exit()时(如果调用)是堆栈指针指向的内容。 You would have to examine the disassembly around that call to exit() plus the exit() code and anything it calls, to figure this out. 您必须检查对exit()的调用,exit()代码及其所调用的任何内容的反汇编,才能弄清楚这一点。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM