我用这个把头撞到墙上了.

在我的项目中,当我用mmap分配内存时,映射(/proc/self/maps)显示它是一个可读的可执行区域despite,我只请求可读内存.

在研究了strace(看起来不错)和其他调试之后,我找到了唯一能避免这个奇怪问题的方法:从项目中删除程序集文件,只留下纯C(什么?!)

这是我的一个奇怪的例子,我正在开发Ubunbtu 19.04和默认gcc.

如果使用ASM文件编译目标可执行文件(该文件为空),那么mmap将返回一个可读的可执行区域,如果不使用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:是一个空文件!

Outputs

包括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_EXECexecution domain,这使得分配了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 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

默认部分权限不包括exec.有关"标志"和@属性的含义,请参阅.section documentation的ELF部分.

(如果您的.s依赖于.text,因为文件顶部的DEFAULT部分,不要忘记切换到.section指令后面的另一个部分,比如.text.data.)

将该行放入example.s(以及项目中的每隔.s个文件)..note.GNU-stack节将用于告诉链接器该对象文件不依赖于可执行堆栈,因此链接器将在GNU_STACK元数据上使用RW而不是RWE,然后您的程序将按预期工作.

类似地,带有正确标志的section指令指定不可执行的堆栈.


Modern Linux kernels between 5.4 and 5.8 changed the behaviour的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头条目的旧式32位可执行文件发生.但如这里所示,即使输入.o文件缺少音符,调制解调器ld也总是用一个或另一个设置添加该设置.

在正常程序中,您仍然应该使用此.note节来通知不可执行的堆栈.但是,如果您希望在.data中测试自修改代码,或者在testing shellcode中遵循一些旧的教程,那么在现代内核中这不是一个选项.

C++相关问答推荐

GCC不警告隐式指针到整数转换'

InetPton()函数无效的IP地址

为什么在C中进行大量的位移位?

变量>;-1如何在C中准确求值?

使用双指针动态分配和初始化2D数组

限制不同类型的限定符

在Rust和C之间使用ffi时如何通过 struct 中的[U8;1]成员传递指针

错误Cygwin_Except::Open_stackdupfile:正在转储堆栈跟踪是什么?

在WSL关闭/重新启动后,是什么原因导致共享对象依赖关系发生更改?

如何按顺序将所有CSV文件数据读入 struct 数组?

在C中包装两个数组?

指向不同类型的指针是否与公共初始序列规则匹配?

添加函数会 destruct 嵌入式C代码(无IDE)

如何将两个uint32_t值交织成一个uint64_t?

变量值不正确的问题

C语言中的指针和多维数组

在我的第一个C语言中观察到的错误';你好世界';程序

当另一个指向 const 的指针观察到数据时,通过指针更改数据是否安全?

将数组返回到链表

有没有一种方法可以减少舍入误差?