使用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
相关产品推荐
相关产品推荐

