我正在浏览OverTheWire战争游戏,我的一个漏洞会覆盖main
的地址为system
的地址。然后我使用了main
返回的事实,esp
仍然指向我的一个局部变量,因此我可以用我希望system
运行的命令填充它(例如sh;#
)。
我的困惑来自于我认为C中的函数在返回之前回收堆栈,因此在返回地址被调用时,堆栈指针将指向返回地址而不是局部变量。但是,我的漏洞利用工作原因似乎我的堆栈指针在调用返回地址时指向局部变量。
与其他人相比,我注意到这个特殊挑战的主要原因是它最后调用了exit(0)
,而不仅仅是结束,因此程序集不会以leave
结尾,这可能是func myconstrainer(_ views: [UIView], spacing: CGFloat) {
myconstrainer(views, spacings: repeatElement(spacing, count: views.count))
}
func myconstrainer<C: Collection>(_ views : [UIView], spacings: C)
where C.Iterator.Element == CGFloat {
// actual implementation...
}
是这种行为的原因。
我没有包含实际的代码,因为它很长,而且我希望对我所看到的内容有一般性的解释,但是如果程序集有用,请告诉我。
答案 0 :(得分: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
与以前一样令人惊讶,我们需要了解堆栈在返回之前已经清理过。那主要不是特别的:
#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()代码及其调用的任何内容的反汇编,以解决这个问题。