我正在用这个把头撞到墙上。
在我的项目中,当我使用 mmap
分配内存时,映射 (/proc/self/maps
) 显示它是一个可读和可执行的区域 尽管我只要求可读内存。
在查看 strace(看起来不错)和其他调试之后,我能够确定唯一似乎可以避免这个奇怪问题的方法:从项目中删除汇编文件并只留下纯 C。(什么?!)
这是我的奇怪示例,我正在使用 Ubuntu 19.04 和默认 gcc。
如果您使用 ASM 文件(空文件)编译目标可执行文件,则 mmap
会返回一个可读且可执行的区域,如果您没有构建则它会正常运行。请参阅我在示例中嵌入的 /proc/self/maps
的输出。
example.c
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
int main()
{
void* p;
p = mmap(NULL, 8192,PROT_READ,MAP_ANONYMOUS|MAP_PRIVATE,-1,0);
{
FILE *f;
char line[512], s_search[17];
snprintf(s_search,16,"%lx",(long)p);
f = fopen("/proc/self/maps","r");
while (fgets(line,512,f))
{
if (strstr(line,s_search)) fputs(line,stderr);
}
fclose(f);
}
return 0;
}
example.s:是一个空文件!
输出
使用 ASM 包含版本
VirtualBox:~/mechanics/build$ gcc example.c example.s -o example && ./example
7f78d6e08000-7f78d6e0a000 r-xp 00000000 00:00 0
没有ASM包含的版本
VirtualBox:~/mechanics/build$ gcc example.c -o example && ./example
7f1569296000-7f1569298000 r--p 00000000 00:00 0
最佳答案
Linux 有一个 execution domain称为 READ_IMPLIES_EXEC
,这会导致分配有 PROT_READ
的所有页面也被赋予 PROT_EXEC
。较旧的 Linux 内核 used to use this对于使用等同于 gcc -z execstack
的可执行文件。该程序将向您显示它是否为自身启用:
#include <stdio.h>
#include <sys/personality.h>
int main(void) {
printf("Read-implies-exec is %s\n", personality(0xffffffff) & READ_IMPLIES_EXEC ? "true" : "false");
return 0;
}
如果您将它与一个空的 .s
文件一起编译,您会看到它已启用,但如果没有,它将被禁用。这个 comes from the ELF meta-information in your binary 的初始值.执行 readelf -Wl 示例
。在没有空 .s
文件的情况下进行编译时,您会看到这一行:
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
但是当你用它编译时这个:
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x10
注意 RWE
而不仅仅是 RW
。这样做的原因是链接器假定您的程序集文件需要 read-implies-exec 除非它被明确告知它们不需要,并且如果您的程序的任何部分需要 read-implies-exec,那么它会为您的整个程序启用. GCC 编译的汇编文件告诉它它不需要这个,用这一行(如果你用 -S
编译你会看到这个):
.section .note.GNU-stack,"",@progbits
默认部分权限不包括 ex
ec。请参阅 .section
documentation 的 ELF 部分“标志”和@attributes 的含义。
(如果您的 .s
依赖于 .text
因为文件顶部的默认部分。)
将该行放入 example.s
(以及您项目中的所有其他 .s
文件)。 .note.GNU-stack
部分的存在将用于告诉链接器该目标文件不依赖于可执行堆栈,因此链接器将使用 RW
而不是 GNU_STACK
元数据上的 RWE
,然后您的程序将按预期工作。
同样for NASM ,带有正确标志的 section
指令指定不可执行的堆栈。
5.4 和 5.8 之间的现代 Linux 内核改变了 ELF 程序加载器的行为。对于 x86-64,不再打开 READ_IMPLIES_EXEC
。最多(使用 ld
添加的 RWE GNU_STACK
),您将获得堆栈本身是可执行的,而不是每个可读页面。 (This answer 涵盖了 5.8 中的最后一个更改,但在此之前肯定还有其他更改,因为该问题显示在 x86-64 Linux 5.4 上成功执行了 .data
中的代码)
exec-all
(READ_IMPLIES_EXEC
) 仅发生在链接器未添加 GNU_STACK
header 条目的传统 32 位可执行文件中全部。但如此处所示,现代 ld
总是添加一个设置或另一个设置,即使输入的 .o
文件缺少注释。
您仍应使用此 .note
部分来指示正常程序中的不可执行堆栈。但是,如果您希望在 .data
中测试自修改代码或遵循一些关于 testing shellcode 的旧教程,这不是现代内核的选项。
关于c - 当程序集文件包含在项目中时,来自 mmap 的意外执行权限,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58260465/