直接修改elf符号表几乎必然破坏可执行性,因.dynsym、.dynstr等节区的任何错位都会导致动态链接器跳转错误地址或报symbol解析失败。

直接修改 ELF 符号表几乎必然破坏可执行性
ELF 符号表(.symtab 和 .dynsym)本身不参与运行时加载或链接逻辑——它只是调试、动态链接器查找符号(如 dlsym)或重定位解析的辅助信息。你改了 .symtab,readelf -s 会显示新名字,但程序照样跑;但一旦动到 .dynsym 或关联的 .dynstr、.rela.dyn、.hash/.gnu.hash,极大概率触发 Segmentation fault 或 dynamic linker failed to resolve symbol。真正影响执行的是符号名在字符串表中的偏移、符号表项的 st_name、st_value(地址)、st_size 和绑定/类型字段,任意一项错位都会让动态链接器跳转到错误地址。
用 libelf + libbfd 读取符号表是安全的,但写入需绕过高层 API
libelf(如 elfutils 的 libelf)提供 elf_getdata + gelf_getsym 读取符号,但它的 gelf_update_sym 只更新内存副本,不自动同步到磁盘——你得手动计算符号表节区(.symtab)的文件偏移、校验节头 sh_offset/sh_size,再用 pwrite 覆盖原始字节。更麻烦的是:.dynstr 字符串表不可随意增删:新增符号名必须追加到字符串表末尾并更新 sh_size,否则后续所有字符串索引全错;若原字符串表已满,你还得移动整个节区甚至重排程序头(PHDR),这已超出“修改符号表”范畴,属于完整 ELF 重写。
-
readelf -S a.out先确认.symtab和.dynstr的Offset和Size - 用
elf_begin(fd, ELF_C_READ, NULL)打开,再elf_flagelf(elf, ELF_C_SET, ELF_F_LAYOUT)禁用自动布局,否则elf_update可能重排节区 - 修改
.dynstr内容后,必须显式调用gelf_update_shdr更新对应节头的sh_size,否则readelf仍按旧长度读字符串
修改函数符号名?别碰 .dynsym ——用 LD_PRELOAD 或 PLT hook 更可靠
想让 printf 调用变成你的 my_printf?直接改 .dynsym 中 printf 的 st_name 指向新字符串,同时确保 .plt 条目仍跳转到原 printf@GLIBC 地址——这根本行不通:PLT 条目由动态链接器在加载时填充,符号名变更不会触发重写 PLT。实际生效的方式只有两种:LD_PRELOAD 注入共享库覆盖符号,或用 patchelf --replace-needed 替换依赖库再配合自定义 .so 实现劫持。试图靠二进制 patch 改 .dynsym 来“重命名导出函数”,99% 场景下只会让 ldd 报 undefined symbol 或运行时报 cannot resolve symbol。
真正可行的符号表操作场景极其有限
唯一较安全的实践是:只读取 .symtab 做静态分析(比如提取所有全局函数地址用于 fuzzing),或对未剥离的调试版二进制,在不改变任何地址和大小的前提下,仅替换 .strtab 中的符号名字符串(例如把 "func_v1" 改成等长的 "func_v2")。这种操作要求新旧字符串严格等长,且不能涉及 .dynsym——因为 .dynsym 引用的字符串表是 .dynstr,而它通常比 .strtab 小得多,几乎没有冗余空间。
- 检查字符串长度:
readelf -x .strtab a.out | grep -A1 "func_v1"确认偏移和长度 - 用
dd或xxd -r覆盖等长字节,避免截断或溢出 - 绝不修改
.dynsym表项的st_name字段值,除非你同时精确重算并重写整个.dynstr和所有依赖节区
ELF 是设计给链接器和加载器消费的格式,不是为人工 patch 准备的。哪怕只是挪动一个符号的 st_size 字段,也可能让 objdump 解析出错,或导致 GDB 无法映射源码行号。真要改行为,优先走动态注入或重新编译。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











