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

使用personality系统调用设置栈可执行时出现段错误,求排查

问题排查:使用personality系统调用让栈可执行失败导致段错误

问题描述

我正尝试理解如何使用personality系统调用使进程栈具备可执行权限,因此编写了一段代码,创建新进程并在栈上运行bash,但因栈无执行权限出现段错误。请问我哪里出错了?

原代码

#include <stdio.h>
#include <sys/personality.h>

int main() 
{
    setvbuf(stdout, 0, 2, 0);
    unsigned char shellcode[] = "\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05"; // open bash
    
    if(personality(READ_IMPLIES_EXEC | ADDR_NO_RANDOMIZE) == -1) // return 0
    {
        printf("personality failed");
        exit(0);
    }

    int (*ret)() = (int(*)())shellcode;
    if(fork() == 0) // child proces
        ret();  
    
    return 0;
}

编译命令(原命令存在小问题)

原命令:gcc file.c -o file.o
正确命令:gcc file.c -o file(file.o是目标文件后缀,不是可执行文件)

环境信息

$ uname -r
4.4.179-0404179-generic

$ readelf -l
...
GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10

错误原因分析

1. personality调用时机错误,无法修改已存在的栈权限

READ_IMPLIES_EXEC标志的作用是:让后续新创建的内存映射页面,只要具备可读权限就自动附带可执行权限。但程序启动时,栈已经被映射为RW(可读可写)权限,之后调用personality()不会修改已存在的栈映射权限——这个标志对已加载的内存区域无效。

你在父进程中提前调用personality(),然后fork子进程,子进程会继承父进程已有的栈映射权限(依然是RW不可执行),所以执行栈上的shellcode会触发段错误。

2. 编译命令的小问题

原命令将输出文件命名为file.o,这是编译生成的目标文件后缀,正确的可执行文件命名应该去掉.o后缀,否则需要额外链接步骤才能运行。

修正方案

方案一:使用mprotect修改栈页面权限

直接修改shellcode所在栈页面的权限,添加可执行属性:

#include <stdio.h>
#include <sys/personality.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <string.h>

int main() 
{
    setvbuf(stdout, NULL, _IONBF, 0);
    unsigned char shellcode[] = "\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05";
    
    if (fork() == 0) {
        // 关闭ASLR(可选,方便调试)
        if (personality(ADDR_NO_RANDOMIZE) == -1) {
            perror("personality failed");
            exit(1);
        }

        // 获取系统页大小,计算shellcode所在页面的起始地址(对齐到页边界)
        size_t page_size = sysconf(_SC_PAGESIZE);
        void *page_start = (void *)((unsigned long)shellcode & ~(page_size - 1));

        // 修改页面权限为可读、可写、可执行
        if (mprotect(page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) == -1) {
            perror("mprotect failed");
            exit(1);
        }

        // 执行shellcode
        int (*ret)() = (int (*)())shellcode;
        ret();
    }

    return 0;
}

方案二:结合personality与堆内存分配

利用READ_IMPLIES_EXEC对新内存映射的作用,在子进程中设置personality后,分配堆内存并复制shellcode到堆上执行:

#include <stdio.h>
#include <sys/personality.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main() 
{
    setvbuf(stdout, NULL, _IONBF, 0);
    unsigned char shellcode[] = "\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05";
    
    if (fork() == 0) {
        // 设置personality:关闭ASLR + 新映射可读即隐含可执行
        if (personality(READ_IMPLIES_EXEC | ADDR_NO_RANDOMIZE) == -1) {
            perror("personality failed");
            exit(1);
        }

        // 分配堆内存(此时因READ_IMPLIES_EXEC,内存具备可执行权限)
        unsigned char *exec_buf = malloc(sizeof(shellcode));
        if (!exec_buf) {
            perror("malloc failed");
            exit(1);
        }
        memcpy(exec_buf, shellcode, sizeof(shellcode));

        // 执行shellcode
        int (*ret)() = (int (*)())exec_buf;
        ret();
    }

    return 0;
}

验证

编译运行修正后的代码,即可正常执行bash shell,不会触发段错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 05:05:19