【问题标题】:Make all pages readable/writable/executable使所有页面可读/可写/可执行
【发布时间】:2017-05-15 04:54:33
【问题描述】:

我想授予 ELF 二进制文件中所有内存页的完全权限(读取、写入和执行)。理想情况下,我希望能够将其作为二进制文件或目标文件的转换来执行,就像可以使用objcopy 更改符号一样。我还没有找到一个很好的方法来做到这一点。我也可以接受涉及在启动时运行代码的解决方案,该代码在每个页面上调用mprotect,并带有标志PROT_READ | PROT_WRITE | PROT_EXEC。我已经简单地尝试过,但我还没有找到一个很好的方法来知道哪些页面被映射,因此哪些页面需要mprotected。

动态分配的页面不需要所有权限,只需要程序启动时映射的页面即可。

【问题讨论】:

  • 既然ELF 文件格式与语言无关,为什么要使用C++ 标签?
  • 因为如果我最终不得不在每个页面上调用 mprotect,我将在 C++ 中这样做。

标签: elf virtual-memory mprotect


【解决方案1】:

以下脚本在代码中实现了 Employed Russian 的回答:

  • RELRO 段的p_type 设置为PT_NULL
  • LOAD 段上的Flags 设置为PF_X|PF_W|PF_R

python3依赖pyelftools,可以用pip3 install pyelftools安装。

#!/usr/bin/env python3

import sys

from elftools.elf.elffile import ELFFile
from elftools.elf.descriptions import describe_p_type

if len(sys.argv) != 2:
    print("Usage: elf_rwe <elf>")

name = sys.argv[1]
with open(name, "rb") as f:
    elf = ELFFile(f)

    rwe_offsets = []
    relro_offsets = []
    for i in range(elf['e_phnum']):
        program_offset = elf['e_phoff'] + i * elf['e_phentsize']
        f.seek(program_offset)
        program_header = elf.structs.Elf_Phdr.parse_stream(f)

        if program_header['p_type'] == "PT_LOAD":
            rwe_offsets.append(program_offset)
        if program_header['p_type'] == "PT_GNU_RELRO":
            relro_offsets.append(program_offset)

    f.seek(0)
    b = list(f.read())

    # Zap RELRO
    pt_null = 0
    for off in relro_offsets:
        b[off] = pt_null

    # Fix permissions
    p_flags_offset = 4
    for off in rwe_offsets:
        b[off + p_flags_offset] = 0x7 # PF_X|PF_W|PF_R

with open(name, "wb") as f:
    f.write(bytes(b)) 

【讨论】:

    【解决方案2】:

    我想授予对 ELF 二进制文件中所有内存页面的完全权限(读取、写入和执行)。

    请注意,某些安全策略,例如 selinux 中的W^X 会阻止您的二进制文件运行。

    理想情况下,我希望能够将其作为二进制文件或目标文件的转换来完成

    在您的二进制文件上运行 readelf -Wl。您会看到类似以下内容:

    $ readelf -Wl /bin/date
    
    Elf file type is EXEC (Executable file)
    Entry point 0x4021cf
    There are 9 program headers, starting at offset 64
    
    Program Headers:
      Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
      PHDR           0x000040 0x0000000000400040 0x0000000000400040 0x0001f8 0x0001f8 R E 0x8
      INTERP         0x000238 0x0000000000400238 0x0000000000400238 0x00001c 0x00001c R   0x1
          [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
      LOAD           0x000000 0x0000000000400000 0x0000000000400000 0x00dde4 0x00dde4 R E 0x200000
      LOAD           0x00de10 0x000000000060de10 0x000000000060de10 0x0004e4 0x0006b0 RW  0x200000
      DYNAMIC        0x00de28 0x000000000060de28 0x000000000060de28 0x0001d0 0x0001d0 RW  0x8
      NOTE           0x000254 0x0000000000400254 0x0000000000400254 0x000044 0x000044 R   0x4
      GNU_EH_FRAME   0x00cb8c 0x000000000040cb8c 0x000000000040cb8c 0x0002f4 0x0002f4 R   0x4
      GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10
      GNU_RELRO      0x00de10 0x000000000060de10 0x000000000060de10 0x0001f0 0x0001f0 R   0x1
    

    然后您要做的是将LOAD 段上的Flags 更改为PF_X|PF_W|PF_R。标志是Elf{32,64}_Phdr 表的一部分,表的偏移量存储在Elf{32,64}_Ehdre_phoff 中(存储在每个ELF 文件的开头)。

    查看/usr/include/elf.h。解析这里涉及的固定大小的 ELF 结构并不复杂。

    您不太可能找到任何标准工具可以为您执行此操作(考虑到这是一件非常不寻常且不安全的事情),但是在CPython 中编写更改标志的程序是微不足道的或Perl

    附:您可能还需要“删除”RELRO 段,这可以通过将其 p_type 更改为 PT_NULL 来完成。

    我还没有找到一个很好的方法来知道哪些页面被映射,因此哪些页面需要mprotected

    在 Linux 上,您可以解析 /proc/self/maps 以获取该信息。其他操作系统可能会提供不同的方法来实现相同的目标。

    【讨论】: