我正在try 将任意数据嵌入到ELF可执行文件中,并让Linux在加载时自动映射它.最近向another question询问了这一点,最终支持将这个用例添加到mold链接器中.

我已经编写了一个工具,可以在可执行文件的末尾附加任意数据,并在指向附加数据的PT_LOADELF程序头中打补丁.这是修补逻辑:

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可执行文件所做的only项更改.通过二进制比对确认.

在运行时,该程序应该能够访问该数据.它通过辅助向量来执行此操作:

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. Program scans PT_LOAD segments until it finds the data.
    • 这些标头中的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    

推荐答案

PT_LOAD标头必须按虚拟地址的升序排序.您的新程序标头的值比后面所有PT_LOAD标头的值都高.

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

这是在man 5 elf中记录的.

C++相关问答推荐

函数指针始终为零,但在解除引用和调用时有效

为什么静态说明符为内联函数生成外部定义?

返回一个包含数组的 struct

显式地将值转换为它从函数返回的类型的含义是什么?

C编译器是否遵循restrict的正式定义?

我在这里正确地解释了C操作顺序吗?

在CLANG中调试预处理器宏

Square不与Raylib一起移动

为什么用非常数指针变量改变常量静态变量时会出现分段错误?

将变量或参数打包到 struct /联合中是否会带来意想不到的性能损失?

按长度对argv中的单词进行排序

从不兼容的指针类型返回&&警告,但我看不出原因

静态初始化顺序失败是否适用于C语言?

如何摆脱-WIMPLICIT-Function-声明

为什么会导致分段故障?(C语言中的一个程序,统计文件中某个单词的出现次数)

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

GnuCobol 使用 double 类型的参数调用 C 函数

C Makefile - 如何避免重复提及文件名

运行以下 C 程序时出现分段错误

如何根据当前舍入方向将float转换为int?