我这里有一些示例代码,用于理解初学者CTF的某些C行为:
// example.c
#include <stdio.h>
void main() {
void (*print)();
print = getenv("EGG");
print();
}
编译:{{1}}
用法:gcc -z execstack -g -m32 -o example example.c
如果我使用EGG=$(echo -ne '\x90\xc3) ./example
标志编译代码,则该程序将执行我上面注入的操作码。没有该标志,该程序将由于分段错误而崩溃。
这到底是为什么?是因为execstack
在堆栈上存储了实际的操作码,而execstack标志允许跳转到堆栈吗?还是getenv
将指针推到堆栈上,还有其他一些规则可以执行哪些内存部分?我阅读了联机帮助页,但无法确切了解规则是什么以及如何执行这些规则。
另一个问题是,我认为我也确实缺少在调试时可视化内存的好工具,因此很难弄清楚这一点。任何建议将不胜感激。
答案 0 :(得分:6)
getenv
不会在堆栈中存储 env var的值。从进程启动开始,它已经在堆栈中 ,并且getenv
获得指向它的指针。
请参见i386 System V ABI对argv []和envp []在进程启动时位于何处的描述:[esp]
上。
_start
不会在调用main
之前复制它们,只是计算指向它们的指针作为args传递给main
。 (链接到https://github.com/hjl-tools/x86-psABI/wiki/X86-psABI的最新版本,其中保留了当前的官方版本。)
您的代码正在将指向堆栈内存的指针(包含env var的值)转换为函数指针并对其进行调用。查看编译器生成的asm(例如,在https://godbolt.org/上):它将类似于call getenv
/ call eax
。
-zexecstack
使您所有的页面都可执行,而不仅仅是堆栈。它也适用于.data
,.bss
和.rodata
部分,以及用malloc
/ new
分配的内存。
在2018年底左右之前,使用binutils的GNU / Linux ld
将.rodata
链接到与.text
相同的ELF段中,因此const char code[] = {0xc3}
或字符串文字是可执行文件。
当前的ld
赋予.rodata
自己的段,该段被映射为读取而没有exec,因此,除非您使用-zexecstack
,否则不再可能在数据中查找ROP / Spectre“小工具”。 >
手动使用mmap
或mprotect
仍会给您不可执行的页面; -zexecstack
影响ELF标头。 Glibc malloc
使用brk
进行少量分配,因此它扩展了现有的可写映射。大型分配(它使用mmap
)可能不是可执行的,除非-zexecstack
也改变了在main
之前调用glibc初始化函数的方式。>