跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

c++如何读取和修改ELF可执行文件头_Linux二进制分析【附源码】

读取ELF文件头必须先验证魔数“\x7fELF”、EI_CLASS(64位)、EI_DATA(字节序),再安全解析;修改e_entry需确保其落在PT_LOAD段虚拟地址范围内,并整块覆写64字节头部,最后调用fsync。 读取 ELF 文件头必须先验证魔数和架构 直接用
fread
读
sizeof(Elf64_Ehdr)
不等于拿到有效头——很多“ELF 文件”其实是脚本、损坏文件或非目标架构(比如在 x86_64 上打开 arm64 的 ELF)。不检查就往下读,
e_type
、
e_machine
等字段全是垃圾值。 先读前 4 字节,确认是
"\x7fELF"
(即
ehdr.e_ident[EI_MAG0]
到
[EI_MAG3]
) 检查
ehdr.e_ident[EI_CLASS]
:值为
ELFCLASS64
才能当
Elf64_Ehdr*
解析,否则可能是 32 位 检查
ehdr.e_ident[EI_DATA]
:
ELFDATA2LSB
或
ELFDATA2MSB
决定字节序,硬按本地序 reinterpret_cast 会错读
e_entry
、
e_phoff
等字段 修改 e_entry 后必须重写整个 ELF 头,不能只改偏移处的 8 字节 ELF 头本身是固定长度(
sizeof(Elf64_Ehdr) == 64
),但它的位置不一定在文件开头之后立刻开始——虽然绝大多数情况下是,可
e_ident[EI_PAD]
区域可能被工具填充,且某些链接器(如
gold
)会在头前塞注释段。更关键的是:
e_entry
是程序入口虚拟地址,不是文件偏移;你改的是内存布局概念,但文件里它只是头中一个字段。 用
lseek(fd, 0, SEEK_SET)
定位到文件起始,再
read()
全部头数据到缓冲区 修改缓冲区里的
ehdr.e_entry
字段(注意字节序!) 用
lseek(fd, 0, SEEK_SET)
回到开头,
write(fd, &ehdr, sizeof(ehdr))
整块覆写——只写 8 字节大概率破坏
e_phnum
或
e_shnum
,导致
readelf
报
Bad value
改完后记得
fsync(fd)
,否则
chmod +x
后执行仍走旧入口 修改后 ELF 可能无法加载:内核校验和动态链接器不买账 Linux 内核在
load_elf_binary()
中会检查
e_entry
是否落在某个
PT_LOAD
段的
p_vaddr..p_vaddr+p_memsz
范围内;glibc 的
_dl_start()
也会验证程序头表合法性。你把
e_entry
改成一个没映射的地址(比如 0x1000),
execve()
直接返回
-ENOEXEC
,错误信息就是
Exec format error
,不提示具体哪错了。 C知道 CSDN推出的一款AI技术问答工具 下载 用
readelf -l your_bin
查看所有
LOAD
段的
VirtAddr
和
MemSize
,确保新
e_entry
落在其中一段内 不要改
e_phoff
、
e_phnum
这类描述程序头表位置/数量的字段——除非你同时重写了整个程序头表,否则动态链接器找不到
PT_INTERP
就放弃加载
strip
过的二进制可能删掉了
.interp
段,此时它变成静态链接,
e_entry
必须指向实际代码段起始,否则 SIGSEGV 用 libelf 比裸
read/write
更安全,但要注意版本和符号可见性 手撸解析容易漏掉对齐、扩展字段或新版 ELF 标准(如
ET_LOOS
类型),
libelf
封装了这些细节。但它默认不导出写入接口——
elf_update(elf, ELF_C_WRITE)
在较老系统(如 CentOS 7 自带 elfutils 0.160)中仅支持只读模式,调用后返回
ELF_C_NULL
,且不报错。 立即学习 “ C++免费学习笔记(深入) ”; 编译时加
-lelf
,运行时确保
libelf.so.1
版本 ≥ 0.168(Ubuntu 20.04+ / Fedora 32+ 默认满足) 必须用
elf_version(EV_CURRENT)
初始化,否则
elf64_getehdr()
返回
NULL
修改
e_entry
后,要调用
gelf_update_ehdr(elf, &ehdr)
,再
elf_update(elf, ELF_C_WRITE)
,缺一不可 别依赖
libelf
自动处理段对齐——它不会帮你把新代码段塞进文件末尾并更新
e_shoff
,那属于重链接范畴,得用
objcopy
或
ld
改 ELF 头看着只是几个字段的事,但每个字段背后都绑着内核加载逻辑、动态链接器协议和 mmap 布局规则。最常被忽略的是:你改的不是“一个数字”,而是整个程序生命周期的起点坐标。

相关文章