ELF是Linux系统下可执行文件、目标文件、共享库的标准格式,字符串表是ELF中存储各类字符串的段,常见的包括存放段名的.shstrtab段和存放符号名的.strtab段,每个字符串以空字符结尾,字符串表开头第一个字节为0,用于标识空字符串。

ELF字符串表的结构基础
ELF文件的字符串表本质是一段连续的字节区域,内部没有复杂的头部结构,仅通过偏移量来定位不同的字符串。比如偏移量0对应空字符串,偏移量1对应从第一个非0字节开始到下一个空字符结束的字符串。
要操作字符串表,首先需要获取字符串表所在的段信息。ELF的段表头(Elf32_Shdr或Elf64_Shdr)中,sh_name字段存储的是段名在.shstrtab字符串表中的偏移,sh_offset是段在文件中的偏移,sh_size是段的大小。
用C++读取ELF字符串表内容
读取字符串表的核心步骤是:打开ELF文件、解析ELF头部、找到目标字符串表对应的段表项、根据段表项的偏移和大小读取字符串表数据、通过偏移量提取目标字符串。
以下是32位ELF读取.shstrtab字符串表的示例代码:
#include <iostream>
#include <fstream>
#include <vector>
#include <elf.h>
using namespace std;
// 读取ELF字符串表内容
vector<char> read_string_table(const char* file_path, Elf32_Off strtab_offset, Elf32_Word strtab_size) {
ifstream file(file_path, ios::binary);
if (!file.is_open()) {
cerr << "无法打开文件" << endl;
return {};
}
// 移动文件指针到字符串表起始位置
file.seekg(strtab_offset, ios::beg);
vector<char> strtab(strtab_size);
file.read(strtab.data(), strtab_size);
file.close();
return strtab;
}
// 根据偏移从字符串表获取字符串
string get_string_from_strtab(const vector<char>& strtab, Elf32_Word offset) {
if (offset >= strtab.size()) {
return "";
}
// 从偏移位置开始读取直到遇到空字符
const char* str_start = strtab.data() + offset;
return string(str_start);
}
int main() {
const char* elf_file = "test.o";
ifstream file(elf_file, ios::binary);
if (!file.is_open()) {
cerr << "无法打开ELF文件" << endl;
return 1;
}
// 读取ELF头部
Elf32_Ehdr ehdr;
file.read(reinterpret_cast<char*>(&ehdr), sizeof(ehdr));
// 校验ELF魔数
if (ehdr.e_ident[EI_MAG0] != ELFMAG0 || ehdr.e_ident[EI_MAG1] != ELFMAG1 ||
ehdr.e_ident[EI_MAG2] != ELFMAG2 || ehdr.e_ident[EI_MAG3] != ELFMAG3) {
cerr << "不是有效的ELF文件" << endl;
return 1;
}
// 读取段表头
file.seekg(ehdr.e_shoff, ios::beg);
vector<Elf32_Shdr> shdrs(ehdr.e_shnum);
file.read(reinterpret_cast<char*>(shdrs.data()), sizeof(Elf32_Shdr) * ehdr.e_shnum);
// 找到.shstrtab段(段表头的e_shstrndx索引对应的段)
Elf32_Shdr& strtab_shdr = shdrs[ehdr.e_shstrndx];
vector<char> strtab = read_string_table(elf_file, strtab_shdr.sh_offset, strtab_shdr.sh_size);
// 打印所有段名
cout << "所有段名:" << endl;
for (int i = 0; i < ehdr.e_shnum; i++) {
string sec_name = get_string_from_strtab(strtab, shdrs[i].sh_name);
cout << "段" << i << ": " << sec_name << endl;
}
file.close();
return 0;
}
修改ELF字符串表内容并写回文件
修改字符串表需要注意两个核心问题:一是新字符串的长度不能超过原字符串的长度,否则会覆盖后续内容破坏文件结构;二是如果必须修改长度,需要整体调整ELF文件的结构,重新计算所有相关段的偏移,操作复杂度较高。
以下是将字符串表中某个指定偏移的字符串替换为等长新字符串的示例代码:
#include <iostream>
#include <fstream>
#include <vector>
#include <cstring>
#include <elf.h>
using namespace std;
// 修改字符串表中指定偏移的字符串(要求新字符串长度不超过原字符串长度)
bool modify_string_in_strtab(const char* file_path, Elf32_Off strtab_offset, Elf32_Word target_offset, const char* new_str) {
fstream file(file_path, ios::binary | ios::in | ios::out);
if (!file.is_open()) {
cerr << "无法打开文件" << endl;
return false;
}
// 先读取原字符串,校验新字符串长度
file.seekg(strtab_offset + target_offset, ios::beg);
string old_str;
char c;
while (file.get(c) && c != ' ') {
old_str.push_back(c);
}
if (strlen(new_str) > old_str.size()) {
cerr << "新字符串长度超过原字符串,无法修改" << endl;
return false;
}
// 移动指针到目标偏移位置,写入新字符串和结尾空字符
file.seekp(strtab_offset + target_offset, ios::beg);
file.write(new_str, strlen(new_str));
file.put(' ');
file.close();
return true;
}
int main() {
const char* elf_file = "test.o";
// 假设我们已经知道要修改的字符串在.shstrtab中的偏移是5,原字符串是.text
// 将其修改为.texa(长度相同)
if (modify_string_in_strtab(elf_file, 0x1a8, 5, "texa")) {
cout << "字符串修改成功" << endl;
} else {
cout << "字符串修改失败" << endl;
}
return 0;
}
操作注意事项
- 操作前一定要备份原始ELF文件,避免修改错误导致文件损坏无法恢复。
- 64位ELF的结构体和32位不同,需要将Elf32_前缀替换为Elf64_,同时注意字段类型的长度差异。
- 如果要修改的字符串需要变长,必须重新调整ELF文件的所有段偏移,同时更新段表头、程序头中的相关偏移字段,否则文件会无法正常运行。
- 修改字符串表后,如果字符串被符号表引用,需要同步检查符号表的内容是否需要调整,避免符号引用失效。
ELFString_TableC++修改时间:2026-07-03 15:39:49