c - 项目中包含程序集文件时,来自 mmap 的意外 exec 权限
问题描述
我正用这个把头撞到墙上。
在我的项目中,当我使用mmap
映射 ( ) 分配内存时,尽管我只请求了可读内存/proc/self/maps
,但它显示它是一个可读和可执行的区域。
在查看 strace (看起来不错)和其他调试之后,我能够确定唯一似乎可以避免这个奇怪问题的东西:从项目中删除程序集文件并只留下纯 C。(什么?!)
所以这是我奇怪的例子,我正在使用 Ubunbtu 19.04 和默认 gcc。
如果您使用 ASM 文件(为空)编译目标可执行文件,则mmap
返回一个可读且可执行的区域,如果您不使用该文件进行构建,则它的行为正确。/proc/self/maps
请参阅我在示例中嵌入的输出。
例子.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 有一个称为的执行域READ_IMPLIES_EXEC
,它会导致所有分配的页面PROT_READ
也被分配PROT_EXEC
。较旧的 Linux 内核曾经将其用于使用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
文件一起编译,您会看到它已启用,但没有它,它将被禁用。它的初始值来自二进制文件中的 ELF 元信息。做readelf -Wl example
。.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
默认部分权限不包括 e x
ec。有关“标志”和@attributes 的含义,请参阅.section
文档的 ELF 部分。
(如果您依赖的是文件顶部的默认部分,请不要忘记切换到该指令.text
或.data
之后的另一个部分。).section
.s
.text
将该行放入example.s
(以及项目中的所有其他.s
文件)。该.note.GNU-stack
部分的存在将用于告诉链接器此目标文件不依赖于可执行堆栈,因此链接器将使用RW
而不是RWE
元GNU_STACK
数据,然后您的程序将按预期工作。
与NASM类似,section
带有正确标志的指令指定不可执行的堆栈。
5.4 和 5.8 之间的现代 Linux 内核改变了 ELF 程序加载器的行为。对于 x86-64,不再打开任何东西READ_IMPLIES_EXEC
。最多(通过GNU_STACK
添加RWE ld
),您将获得可执行的堆栈本身,而不是每个可读页面。(这个答案涵盖了 5.8 中的最后一个更改,但在此之前必须有其他更改,因为该问题显示.data
在 x86-64 Linux 5.4 上成功执行代码)
exec-all
( READ_IMPLIES_EXEC
) 仅适用于链接器根本没有添加标头条目的旧版 32 位可执行文件GNU_STACK
。但如此处所示ld
,即使输入.o
文件缺少注释,modern 总是使用一种设置或另一种设置添加它。
您仍然应该使用本.note
节来表示正常程序中的不可执行堆栈。但是,如果您希望在或遵循一些用于测试 shellcode.data
的旧教程中测试自修改代码,那么这不是现代内核的选项。
推荐阅读
- javascript - 使用谷歌地图 API setMarkers 函数时出现错误“w.getPosition 不是函数”?
- bazel - 在 Bazel 中,是否可以使用函数输出作为加载语句的输入?
- bash - ubuntu 使用参数运行脚本 - 作为键盘快捷键 gnome
- vue.js - 如何将过滤器计算属性应用于 Vuex 的 mapState 中的计算属性?
- javascript - 如何在 java 脚本循环中使用 push?
- macos - unstow 目录导致警告
- javascript - 使用 BeautifulSoup 进行网页抓取不起作用
- javascript - 未能在“IDBObjectStore”上执行“放置”:事务已完成
- javascript - 简单的 Shuffle.js 搜索不适用于 Bootstrap 4 卡
- embedded - CPU如何读取内存映射的IO?