直接用 fread 修改 .strtab 会破坏 ELF 文件,因其字符串表被其他节区通过偏移量引用,长度变化或越界写入将导致偏移错位、数据覆盖及符号解析失败。

为什么直接用 fread 修改 .strtab 会破坏 ELF 文件
ELF 的字符串表(.strtab 或 .dynstr)不是独立文件块,它被其他节区(如 .symtab、.dynamic)通过偏移量(st_name、d_tag 等字段)引用。直接覆盖字符串内容会导致:长度变化 → 后续字符串偏移错位 → 符号解析失败;或者越界写入 → 覆盖相邻节区数据(比如把 .symtab 的第一个符号名写爆到下一个符号的 st_value 字段里)。这不是“改文本”,是动内存布局。
- 字符串表必须保持「所有字符串以
\0结尾 + 整体连续 + 无空洞」,新增字符串只能追加到末尾 - 修改已有字符串只能等长替换(包括结尾
\0),否则必须重写整个.strtab并更新所有引用它的节区头和字段 -
readelf -S显示的.strtabsh_offset和sh_size是磁盘映像位置,不是内存加载地址,操作时必须按文件 offset 计算,不能按mmap后的虚拟地址硬算
如何安全地在 ELF 中新增一个字符串并获取其偏移
新增字符串必须追加到 .strtab 末尾,并更新节头中 sh_size,同时确保文件后续内容(如下一节区)不被覆盖。关键不是“写进去”,而是“让整个 ELF 结构还认得它”。
- 用
lseek定位到原.strtab末尾(sh_offset + sh_size),再write新字符串(含结尾\0) - 用
read读取原始 ELF 头和节头表,找到.strtab对应的Elf64_Shdr,将其sh_size增加新字符串长度(含\0),再pwrite回原位置 - 如果新增后导致节头表本身被推移(例如文件末尾扩展触发重排),必须同步更新 ELF 头中
e_shoff—— 这种情况极少见,但真实存在,尤其当原文件无足够 padding 时 - 不要用
ftruncate扩展文件后再写:某些系统对只读 mmap 区域或签名验证过的二进制(如 Android 的 AVB)会拒绝加载已扩容的 ELF
修改符号名时,必须同步更新哪些字段
改符号名不只是动 .strtab,符号表(.symtab)里的每个 Elf64_Sym 都有 st_name 字段,它是相对于 .strtab 起始的字节偏移。改名即改这个偏移值,而偏移值取决于你把新字符串放在 .strtab 的哪个位置。
- 先确定新字符串在
.strtab中的绝对文件偏移:old_strtab_offset + old_strtab_size - 遍历
.symtab中所有Elf64_Sym,找到要修改的符号(比如st_name != 0 && strcmp(get_string_at(strtab, sym->st_name), "old_name") == 0) - 将该符号的
st_name改为新字符串在.strtab中的偏移(不是索引,是文件内 byte offset) - 注意:
.symtab本身可能被strip掉,此时只能改.dynsym,逻辑相同,但需确认.dynstr是否可写(有些发行版设为PROT_READ)
用 libelf 时最容易忽略的三个细节
libelf 封装了大部分结构解析,但底层仍是文件操作,它不会自动帮你处理“改了字符串表就要重算所有引用”的逻辑,更不会保证你写的偏移合法。
立即学习“C++免费学习笔记(深入)”;
- 调用
elf_update(elf, ELF_C_WRITE)前,必须手动调用gelf_update_sym更新每个改过的符号,否则.symtab内存缓存不变,写回文件还是旧的 -
elf_getdata返回的data->d_buf指向的是内存副本,直接memcpy写字符串进去没用——得用gelf_getsym/gelf_update_sym操作符号,用data->d_buf+ 偏移操作字符串表原始字节 -
elf_begin(..., ELF_C_RDWR, ...)不等于“可以随便写”:若原 ELF 文件以只读方式打开(open(path, O_RDONLY)),即使传ELF_C_RDWR,elf_update也会失败并返回NULL—— 必须用O_RDWR打开文件描述符
真正难的从来不是“怎么找到字符串表”,而是改完之后,所有依赖它的地方是否还指向有效位置。一个偏移算错,整个符号解析链就断了,loader 会静默跳过符号或直接报 Invalid ELF image。



















