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

为何Shellcode在独立可执行文件中正常运行,在C程序中执行却触发段错误?

排查64位Shellcode在C程序中触发段错误的问题

我来帮你梳理下这个问题——即使开了gcc -z execstack还是触发段错误,大概率是Shellcode本身或者C程序的执行方式有几个容易忽略的坑,咱们一步步来排查:

1. 先检查Shellcode是不是**位置无关(PIC)**的

你单独编译链接的Shellcode程序,链接器会给它分配固定的内存地址,所以如果你的Shellcode里用了硬编码的绝对地址(比如mov rax, 0x401000这种),放到C程序里执行时,Shellcode是被加载到栈/堆的随机地址,原来的绝对地址就完全无效了,自然会触发段错误。

解决办法:
重写Shellcode为位置无关代码,64位下推荐用rip相对寻址:

  • 用lea rax, [rip+offset]来计算字符串(比如"/bin/sh")的地址,而不是硬编码;
  • 如果要获取当前Shellcode的地址,可以用call next; next: pop rbx这种技巧,call指令会把下一条指令的地址压栈,pop出来就是当前位置的地址,再基于这个地址做偏移。

2. 检查Shellcode有没有NULL字节

如果你的Shellcode里包含0x00字节,用C的char数组存放时,会被当作字符串结束符截断,导致Shellcode不完整,执行到一半就出错。比如mov rax, 0会生成0x48 0xc7 0xc0 0x00 0x00 0x00 0x00,这里面有一堆NULL字节,直接废了。

解决办法:

  • 用xxd your_shellcode_program查看Shellcode的十六进制内容,排查有没有00字节;
  • 替换产生NULL的指令,比如把mov rax, 0换成xor rax, rax(生成0x48 0x31 0xc0,没有NULL字节)。

3. 检查栈对齐或编译器优化问题

有些64位指令要求内存地址对齐到16字节,当Shellcode被复制到栈上时,可能刚好没对齐,导致执行指令时出错。另外,编译器的栈保护机制(比如Stack Smashing Protection)可能会干扰栈上的Shellcode。

解决办法:

  • 编译C程序时加上-fno-stack-protector关闭栈保护:gcc -z execstack -fno-stack-protector your_code.c -o test;
  • 给Shellcode数组加上对齐属性:unsigned char __attribute__((aligned(16))) shellcode[] = "...";;
  • 或者在Shellcode开头加几个NOP指令(0x90),比如加4个或8个,手动调整对齐。

4. 试试手动申请可执行内存代替栈执行

有时候即使开了execstack,栈的权限还是会有隐性限制(比如系统安全机制的额外管控),这时候可以手动用mmap申请一块可读可写可执行的内存,把Shellcode复制过去再执行,绕开栈的问题。

示例代码:

#include <sys/mman.h>
#include <string.h>
#include <stdio.h>

int main() {
    // 替换成你的Shellcode
    unsigned char shellcode[] = "\x48\x31\xc0\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05";
    
    // 申请可执行内存
    void *exec_mem = mmap(NULL, sizeof(shellcode), 
                         PROT_READ | PROT_WRITE | PROT_EXEC, 
                         MAP_ANON | MAP_PRIVATE, -1, 0);
    if (exec_mem == MAP_FAILED) {
        perror("mmap failed");
        return 1;
    }
    
    // 复制Shellcode到可执行内存
    memcpy(exec_mem, shellcode, sizeof(shellcode));
    // 执行Shellcode
    ((void(*)())exec_mem)();
    
    return 0;
}

5. 用GDB定位具体错误

如果上面的方法都没用,直接用GDB调试C程序,看段错误到底发生在哪个指令:

gdb ./test_program
run
# 触发段错误后,查看当前指令
x/20i $rip
# 查看调用栈
bt

这样能精准定位是Shellcode里的哪条指令出了问题,方便针对性修改。

另外,还要注意系统的安全机制,比如SELinux如果处于enforcing模式,可能会阻止栈上代码执行,可以临时用sudo setenforce 0关闭测试,确认是不是这个原因。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 08:42:06