【发布时间】:2015-09-23 12:45:08
【问题描述】:
在研究 Linux 内核时,我发现默认情况下,可执行文件有一个“可执行”堆栈区域。我自然认为唯一(必要的)可执行区域是文本部分。是否有任何与此相关的历史原因,或任何实际用途?
【问题讨论】:
-
GCC 的嵌套函数被实现为蹦床并且需要一个可执行堆栈。还有其他用例,其中一些描述为here。
标签: linux stack virtual-memory
在研究 Linux 内核时,我发现默认情况下,可执行文件有一个“可执行”堆栈区域。我自然认为唯一(必要的)可执行区域是文本部分。是否有任何与此相关的历史原因,或任何实际用途?
【问题讨论】:
标签: linux stack virtual-memory
在某些情况下需要可执行堆栈,例如嵌套函数的 GCC trampoline。
蹦床是在运行时获取嵌套函数的地址时创建的一小段代码。它通常驻留在堆栈上,在包含函数的堆栈框架中。这些宏告诉 GCC 如何生成代码来分配和初始化蹦床。
在大多数发行版中,由于攻击风险和使用堆栈来执行 shellcode,此功能被禁用,尽管您可以使用 -z execstack 编译代码来启用它。也可以在编译程序后使用名为execstack 的程序启用或禁用此功能。为了清楚起见,我编写了一个简单的程序来执行exit syscall,退出代码为 32。如果启用此功能,则代码可以正常工作,否则会出现分段错误。
#include <stdlib.h>
#include <unistd.h>
char shellCode[] = "\xb8\x01\x00\x00\x00" // mov $0x1,%eax
"\xbb\x20\x00\x00\x00" // mov $0x20,%ebx
"\xcd\x80"; // int $0x80
int main(){
int *ret;
ret = (int*) &ret + 2;
*ret = (int) shellCode;
return 5;
}
在这段代码中,ret 指向它的地址加 2。正如我们所知,在 IA32 系统中,指针大小为 4 字节,并且在每个函数的开头,编译后都有一个 push ebp。因此,为了达到main 的返回地址,我们需要添加2*stack_chunk_size,并在编译时将每个块设置为 4 字节,这样可以完美运行。
像这样编译代码进行测试:
gcc -mpreferred-stack-boundary=2 -z execstack -o testShellCode testShellCode.c
-mpreferred-stack-boundary=2 是将堆栈对齐为 4 字节块,-z execstack 是使用可执行堆栈进行编译。
这个简单的代码可以让我们深入了解为什么会有可执行堆栈保护之类的东西。
有关嵌套函数和execstack 命令的更多信息,请查看以下链接:
【讨论】: