You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

全局区shellcode转函数执行为何需-z execstack编译选项?

为什么全局区的shellcode需要-z execstack才能执行?

问题场景

在SMP Debian 4.19.260-1 (2022-09-29) x86_64环境下,使用gcc (Debian 8.3.0-6) 8.3.0编译这段代码:

#include <stdio.h>
const char
shellcode[]="\xb8\x01\x00\x00\x00\x48\xbe\x48\x65\x6c\x6c\x6f\x00\x00\x00\x56\x48\x89\xe6\xba\x05\x00\x00\x00\x0f\x05\xb8\x3c\x00\x00\x00\x0f\x05";
int main(){
    (*(void (*) ()) shellcode)();
    return 0;
}

直接编译运行会触发SIGSEGV段错误,但添加-z execstack选项编译后能正常输出Hello。调试确认shellcode位于初始化全局区(准确说是const修饰的只读数据段.rodata),并非栈区,因此产生疑惑:为什么非栈区的代码也需要这个选项?

核心原因

  1. 内存页的W^X保护机制
    现代Linux系统默认启用W^X(Write XOR Execute,写/执行互斥)安全机制,每个内存页只能具备写权限或者执行权限,不能同时拥有,以此防范代码注入类攻击。
    你的shellcode被const修饰,编译后会被归类到.rodata只读数据段,这个段对应的内存页默认权限是只读、不可执行。当你强制将这段数据当作函数指针执行时,CPU尝试在不可执行的内存页上运行指令,必然触发段错误。

  2. -z execstack的实际作用
    你找到的结论完全正确:-z execstack并非仅让栈页可执行,它会直接关闭整个程序的W^X保护,让所有内存页(包括.rodata、.data、栈、堆等)同时拥有可执行权限。这就使得原本不可执行的.rodata段里的shellcode能够被CPU正常执行。

更安全的替代方案

如果不想全局放开内存页的可执行权限,可以通过编译器属性把shellcode放到默认可执行的.text代码段:

#include <stdio.h>
const char shellcode[] __attribute__((section(".text"))) = 
"\xb8\x01\x00\x00\x00\x48\xbe\x48\x65\x6c\x6c\x6f\x00\x00\x00\x56\x48\x89\xe6\xba\x05\x00\x00\x00\x0f\x05\xb8\x3c\x00\x00\x00\x0f\x05";
int main(){
    (*(void (*) ()) shellcode)();
    return 0;
}

这样编译时无需添加-z execstack选项,shellcode也能正常运行,同时保留系统的W^X安全保护,避免全局权限放开带来的风险。

内容的提问来源于stack exchange,提问作者diciotto

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 13:18:08