如何读取嵌入程序自身ELF的数据?如何将自定义段加入PT_LOAD?
问题背景
使用objcopy工具可将文件嵌入ELF可执行文件,命令如下:
objcopy --add-section program.file1=file1.dat \ --add-section program.file2=file2.dat \ program program+files
但生成的program+files运行时无法直接访问嵌入的file1、file2——这些文件被添加为ELF的命名section,而Linux仅加载PT_LOAD标记的segment,此类非必需section不会被操作系统加载到内存。
尝试通过ELF头指针查找section的代码会得到悬空指针,无法正常使用:
uintptr_t address = getauxval(AT_PHDR) & -4096; Elf64_Ehdr *elf = (Elf64_Ehdr *) address; // 悬空指针,操作系统未加载这些section Elf64_Shdr *sections = ((unsigned char *) elf) + elf->e_shoff;
需求为:运行时动态查找所有以program.为前缀的section,直接获取其内存指针并当作普通内存块使用,且需支持任意数量(含零个)的嵌入文件,不能依赖预定义符号。
核心思路:将嵌入section归入PT_LOAD段
Linux仅加载PT_LOAD标记的segment,只要把嵌入的program.* section加入PT_LOAD段,就能在运行时访问其内存数据。objcopy无法直接添加PT_LOAD段,但可通过以下两种方法实现:
方法1:链接阶段用脚本定义PT_LOAD段
在编译链接时,通过自定义链接脚本将program.* section归入PT_LOAD段,后续再用objcopy嵌入文件:
- 编写链接脚本(示例命名为
embed.ld):
SECTIONS { .embedded : { *(.program.*) } } PHDRS { embedded PT_LOAD FILEHDR PHDRS; }
该脚本将所有.program.*类型的section汇总到.embedded段,并标记此段为PT_LOAD。
- 编译程序时指定该链接脚本:
gcc your_program.c -Wl,-Tembed.ld -o program
- 用
objcopy嵌入目标文件:
objcopy --add-section program.file1=file1.dat \ --add-section program.file2=file2.dat \ program program+files
此时嵌入的program.* section会被包含在PT_LOAD段中,运行时会被操作系统加载到内存。
方法2:手动扩展现有PT_LOAD段(进阶)
若不想修改编译链接流程,可先添加section,再手动调整最后一个PT_LOAD段的大小以包含新section:
- 先用
objcopy添加section:
objcopy --add-section program.file1=file1.dat program program+files
- 用
readelf查看现有PT_LOAD段的信息:
readelf -l program+files
找到最后一个PT_LOAD段的Virtual Address和MemSize,计算其结束地址:end_addr = p_vaddr + p_memsz
- 查看新添加的
program.file1section的地址与大小:
readelf -S program+files
获取该section的Addr(虚拟地址)和Size。
- 若section地址在PT_LOAD段结束地址之后,用
objcopy扩展PT_LOAD段的MemSize和FileSize,同时将section标记为ALLOC(告知系统需分配内存):
objcopy --set-section-flags program.file1=ALLOC program+files program+files
此方法需手动计算地址与大小,易出错,更推荐方法1。
运行时动态查找section的实现
当section被加载到内存后,可通过以下代码动态查找所有program.* section:
#include <elf.h> #include <sys/auxv.h> #include <string.h> #include <stdlib.h> typedef struct { const char *name; void *data; size_t size; } EmbeddedFile; // 获取当前进程ELF文件的基地址 uintptr_t get_elf_base() { Elf64_Phdr *phdr = (Elf64_Phdr *)getauxval(AT_PHDR); Elf64_Ehdr *ehdr = (Elf64_Ehdr *)((uintptr_t)phdr - phdr->p_offset); return (uintptr_t)ehdr; } // 查找所有以program.为前缀的嵌入文件 void find_embedded_files(EmbeddedFile **out_files, size_t *out_count) { uintptr_t elf_base = get_elf_base(); Elf64_Ehdr *ehdr = (Elf64_Ehdr *)elf_base; Elf64_Shdr *shdr = (Elf64_Shdr *)(elf_base + ehdr->e_shoff); const char *shstrtab = (const char *)(elf_base + shdr[ehdr->e_shstrndx].sh_addr); size_t count = 0; EmbeddedFile *files = NULL; for (int i = 0; i < ehdr->e_shnum; i++) { const char *sec_name = shstrtab + shdr[i].sh_name; if (strncmp(sec_name, "program.", 8) == 0) { files = realloc(files, sizeof(EmbeddedFile) * (count + 1)); files[count].name = sec_name + 8; // 去除program.前缀 files[count].data = (void *)(elf_base + shdr[i].sh_addr); files[count].size = shdr[i].sh_size; count++; } } *out_files = files; *out_count = count; }
调用find_embedded_files即可获取所有嵌入文件的名称、内存指针和大小,直接当作普通内存块使用。
内容的提问来源于stack exchange,提问作者Matheus Moreira

