定位ELF中.strtab和.dynstr需先解析e_shoff得节头表,再通过.shstrtab获取节名,筛选目标节头获sh_offset/sh_size;注意.shstrtab自引用、32/64位结构差异、mmap安全读取及字符串表不可原地修改。

如何定位 ELF 文件中的 .strtab 和 .dynstr 字符串表
ELF 文件的字符串表(.strtab 用于节头名、符号名等,.dynstr 用于动态符号)不直接暴露在文件开头,必须通过解析 ELF 头和节头表来找到。关键路径是:e_shoff → 节头表起始偏移 → 找到 .sh_name 指向的字符串表节(即 .shstrtab)→ 解析该节获取各节名 → 筛出 .strtab 或 .dynstr 对应的节头 → 得到其 sh_offset 和 sh_size。
容易踩的坑:
-
.shstrtab自身也是字符串表,它的名字(".shstrtab")存在它自己里面,所以要先读一次该节内容,再用节头的sh_name值作为索引去查字符串 - 32 位和 64 位 ELF 的
Elf32_Ehdr/Elf64_Ehdr结构体字段偏移不同,不能混用;sh_offset是相对于文件起始的绝对偏移,不是相对节头表的偏移 - 某些 stripped 文件可能没有
.strtab(仅保留.dynstr),而静态链接的二进制甚至两者都无——得先readelf -S a.out确认存在性
用 mmap + 原生指针安全读取字符串表内容
避免用 fread 逐段拷贝,直接 mmap 整个文件只读映射最高效,且能用指针算术快速跳转。字符串表本质是 null-terminated 字符序列数组,每个字符串紧挨着下一个,索引即字节偏移(如符号名在符号表中存的是 st_name 值,就是它在字符串表里的 offset)。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 用
open()+mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0)映射整文件;映射后通过(Elf64_Ehdr*)addr强转访问结构体 - 读字符串时不要假设以
\0结尾就安全:必须限制在sh_size范围内遍历,否则越界读会触发 SIGBUS(尤其 mmap 映射末尾未对齐页时) - 示例:获取第
i个字符串(已知 offsetoff):const char* str = (const char*)addr + strtab_off + off;<br>size_t len = strnlen(str, strtab_size - off);
修改字符串表需重分配空间并更新多个关联字段
字符串表是只读数据节(SHF_ALLOC | SHF_STRINGS),直接 in-place 替换字符串会导致长度变化,破坏后续所有字符串偏移——这是最常被忽略的底层约束。真正可行的做法只有两种:一是扩展文件并在新区域写入更大字符串表,二是用 patch 工具(如 patchelf)间接操作。
若坚持手改,必须同步更新:
-
.strtab节头的sh_size(新字符串表总长) - 所有引用该字符串表的结构:符号表(
.symtab)中每个st_name,重定位项(.rela.dyn)中部分字段,甚至节头表自身(如果改了节名) - ELF 头的
e_shnum和e_shstrndx一般不动,但若新增节(如补一个更大的.strtab.new),则需调整节头表位置并更新e_shoff - 最关键:修改后必须重新计算并写入 ELF 校验和(如果启用了 GNU_BUILD_ID),否则 loader 可能拒绝加载
为什么 std::string 或 iostream 完全不适用
ELF 是裸二进制格式,没有编码标识、无元数据描述、无长度前缀。所有字符串都是 C-style、null-terminated、地址连续的 raw bytes。任何依赖流状态、自动内存管理或文本编码转换的 C++ 标准库组件都会引入不可控行为。
典型错误现象:
- 用
std::ifstream::read()读字符串表,遇到中间\0就截断(因底层按文本模式处理) - 把字符串表指针传给
std::string构造函数,未指定长度,导致越界扫描直到遇到第一个\0后的随机内存值 - 修改后调用
std::ofstream::write()写回,但没保证 write() 原子性或对齐,造成文件损坏
真正可靠的方式只有:用 uint8_t* 指针做字节级操作,用 memmove 移动数据,用 pwrite 精确写入指定 offset —— 这才是和 ELF 格式对话的正确姿势。



















