我正在研究 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/