为什么Linux无法映射这个PT_LOAD ELF段?

问题描述 投票:0回答:1

我正在尝试将任意数据嵌入到 ELF 可执行文件中,并让 Linux 在加载时自动映射它。最近询问了关于此的另一个问题,最终支持将此用例添加到

mold
链接器中。

我编写了一个工具,可以在可执行文件末尾附加任意数据,并在指向附加数据的

PT_LOAD
ELF 程序头中进行修补。这是修补逻辑:

appended_data_file_offset = /* ... seek(elf file, SEEK_END) ... */;
appended_data_size = /* ... stat(data file) ... */;

phdr->p_type = PT_LOAD;

phdr->p_filesz = phdr->p_memsz = appended_data_size;

size_t base = phdr->p_vaddr - phdr->p_offset; // calculate program's base load address
phdr->p_vaddr = phdr->p_paddr = base + appended_data_file_offset;
phdr->p_offset = appended_data_file_offset;

phdr->p_align = 1;
phdr->p_flags = PF_R;

运行我的修补程序会生成一个 ELF 文件,并在偏移量处附加此数据

0xAD78

0000ad70: 00 00 00 00 00 00 00 00 74 65 73 74 20 64 61 74  ........test dat
0000ad80: 61 0a                                            a.

添加了这个

PT_LOAD
段:

 Program Headers:
   Type           Offset             VirtAddr           PhysAddr
                  FileSiz            MemSiz              Flags  Align
   LOAD           0x000000000000ad78 0x000000000020ad78 0x000000000020ad78
                  0x000000000000000a 0x000000000000000a  R      0x1

这个新段和末尾的 10 字节块是对完美运行的 ELF 可执行文件所做的唯一更改。通过二进制比较确认。

在运行时,程序应该达到该数据。它通过辅助向量来实现:

Elf64_Phdr *header = (Elf64_Phdr *) getauxval(AT_PHDR);
size_t count = getauxval(AT_PHNUM);
size_t size = getauxval(AT_PHENT);
assert(size == sizeof(Elf64_Phdr));

for (size_t i = 0; i < count; ++header, ++i) {
    if (header->p_type != PT_LOAD) { continue; }
    if (0 == memcmp(header->p_vaddr, "test", sizeof("test") - 1)) {
        // found it
    }
}

为了清楚起见,我使用了

libc
函数。我的实际程序是一个用独立 C 编写的静态
EXEC
ELF 文件。它不链接到
libc
并直接使用 Linux 系统调用。

以这种方式修补可执行文件后,我打算发生这种情况:

  1. Linux 自动将附加到可执行文件的数据加载到内存中。
    • 位于文件中偏移量
      0xAD78
      的 10 字节块。
  2. 程序通过辅助向量中的
    AT_PHDR
    值找到程序头表。
  3. 程序扫描
    PT_LOAD
    段,直到找到数据。
    • 这些标头之一的
      p_vaddr
      应指向包含
      "test data\n"
    • 的内存块

相反,这个程序完全崩溃了。不执行任何一条指令。甚至没有到达入口点。连

gdb
都无法调试它:

(gdb) run
Starting program: exe.patched
During startup program terminated with signal SIGSEGV, Segmentation fault.
(gdb) info registers
The program has no registers now.
(gdb) step
The program is not being run.

尽管没有

PT_LOAD
标头,它运行没有任何问题。如果我将类型更改为
PT_LOOS
或任何其他类型,它也有效。

我想不通。我到底做错了什么?


完整的

readelf
打印输出:

$ readelf --file-header --program-headers program.patched
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           AArch64
  Version:                           0x1
  Entry point address:               0x2037d8
  Start of program headers:          64 (bytes into file)
  Start of section headers:          43512 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         5
  Size of section headers:           64 (bytes)
  Number of section headers:         8
  Section header string table index: 6

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x000000000000abf8 0x000000000020abf8 0x000000000020abf8
                 0x000000000000000a 0x000000000000000a  R      0x1
  LOAD           0x0000000000000000 0x0000000000200000 0x0000000000200000
                 0x00000000000027d8 0x00000000000027d8  R      0x1000
  LOAD           0x00000000000027d8 0x00000000002037d8 0x00000000002037d8
                 0x0000000000005ed8 0x0000000000005ed8  R E    0x1000
  LOAD           0x00000000000086b0 0x000000000020a6b0 0x000000000020a6b0
                 0x0000000000000000 0x0000000000100015  RW     0x1000
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x0

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .rodata 
   02     .text 
   03     .bss 
   04    
c linux elf embedded-resource embedding
1个回答
1
投票

PT_LOAD 标头必须按虚拟地址的升序排列。您的新程序标头具有比以下所有 PT_LOAD 标头更高的

p_vaddr

此外,该段的虚拟地址范围不应重叠,但您的新段位于最后一个段内。映射段的相关大小是

p_filesz
p_memsz
中的较大者。

这记录在 man 5 elf中。

© www.soinside.com 2019 - 2024. All rights reserved.