[英]Does the main function of a C program ever reclaim the stack?
我正在OverTheWire战争游戏中工作,我的一项攻击行为是用system
地址覆盖main
的返回地址。 然后,我使用了这样一个事实,即在main
返回时, esp
仍指向我的局部变量之一,因此我可以用我希望system
运行的命令(例如sh;#
)填充它。
我的困惑来自于我认为C语言中的函数在返回之前会回收堆栈,因此在该点返回地址称为堆栈指针将指向返回地址,而不是局部变量。 但是,我的漏洞利用仍然有效,因此似乎在调用返回地址时,我的堆栈指针指向了局部变量。
与其他挑战相比,与该挑战相比,我注意到的主要事情是,它在结尾处调用exit(0)
而不是仅仅结束,因此程序集不会以leave
结尾,这可能就是这种行为的原因。
我没有包含实际的代码,因为它已经很长了,我希望对所看到的内容有一个一般的解释,但是请告诉我汇编是否有用。
#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等)甚至无法考虑运行某个程序之前,它需要为该程序分配一些空间并根据处理器和OS的功能以某种方式为该程序标记该内存,等。然后从任何媒体加载程序,然后在指定的二进制文件和/或众所周知的入口点启动它。 程序的干净退出将使操作系统释放该内存,.text,.data,.bss和stack是琐碎/显而易见的,随着内存的消失而消失。 可以/应该释放可能已经分配并与此程序相关联的其他项目,打开文件,运行时分配的(而非堆栈)内存等,这取决于os和/或C库的设计,如何实现。
在上述情况下,我们看到引导程序调用main和main返回,然后命中了hlt,这是一个应用程序而不是内核代码,因此应该引起陷阱,导致操作系统清理。 显式exit()应该与printf()或puts()或fopen()或最终对操作系统进行一个或多个syscall的任何其他函数没有什么不同。 对于这些类型的操作系统(Linux,Windows,MacOS),您可能会找到的只是syscall。 内存的释放发生在程序外部,因为程序无法控制它,这可能是鸡和鸡蛋的问题,程序会释放用于释放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
与以前一样,这并不奇怪,我们所需要了解的是,在返回之前已清除了堆栈。 而且那个主并不特殊:
#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()代码及其所调用的任何内容的反汇编,才能弄清楚这一点。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.