ELF解析01 - ELF头和程序头

本系列将完全是参考 https://space.bilibili.com/37877654/channel/seriesdetail?sid=1467282 这些视频而来,可以看作是一个文字版记录,建议先看视频。

工具

010 Editor

去官网下载正版,会有30天的试用期,windows不好处理,其他平台删个配置文件就可以无限续期了。

镜像

使用 boot 过的手机或者 arm64 模拟器。

脚本

由于将会对 /system/bin/ls 这个程序做修改,修改完后再推回手机查看变化,所以提供一个脚本方便使用:

复制代码
adb push ls /data/local/tmp/ls
adb shell "chmod 777 /data/local/tmp/ls"
adb shell "/data/local/tmp/ls"

需要首先将/system/bin/ls pull 出来。

elf_header

elf_header 下有一个子结构体,e_ident_t,这里面其实就第一个 file_identification 有用,它是固定的,为 .ELF。

struct e_ident_t e_ident

其他的,比如 ei_class_2_e,虽然它是用来描述该 elf 文件为 64 位的,但是实际上linker加载该 elf 文件的时候,根本不会在意这个值。

我们可以使用 010 将这个值改成 EE,然后 push 到手机上运行看看是否可以正常运行。

这里将 e_intent_t 里面除了file_identification 之外的全部改成了 EE,测试是否可以正常运行。

执行脚本,可以看到输出如下:

说明,linker确实不会关心这些字段,所以我们研究 elf 结构的时候,可以先关注重点字段,也就是elf文件加载的时候,会使用到的,这样可以集中精力。

总结,struct e_ident_t e_ident 里面只需要关心.ELF 这个值即可。

e_type

这个的值虽然是一个枚举,但是是实际上无论是 exe 还是 so,它们的值都必须是 ET_DYN (3) ,如果是一个 exe 文件,将这个值改成 ET_EXEC (2),它反而不能运行。

e_machine

这个字段说明CPU平台,比如 x86,arm32,arm64 等。

e_version

这个没啥用。

e_entry_START_ADDRESS

这个是非常重要的一个值。是当 elf 加载到内存中,其执行的起始地址,是一个虚拟地址。

e_phoff_PROGRAM_HEADER_OFFSET_IN_FILE

这个就是 program_header_table 段的偏移。因为 elf_header 后面就是 program_header_table,其实就是 elf_header 的大小。除非有人故意在这两者之间插入一些无用的数据。

e_shoff_SECTION_HEADER_OFFSET_IN_FILE

没啥用,实际上跟 section 有关的都没啥用,虽然很多书籍都对 section 部分大书特书,但是由于 elf 加载的时候根本就不使用 section 相关的东西,所以就没必要太在意这部分。

e_flags

没用

e_ehsize_ELF_HEADER_SIZE

没用,加载的时候根本不检查。所以改动 elf_header 的大小会有不可预料的后果。

e_phentsize_PROGRAM_HEADER_ENTRY_SIZE_IN_FILE

由于 program_header_table 是一个数组,这个表示数组元素的大小。

e_phnum_NUMBER_OF_PROGRAM_HEADER_ENTRIES

由于 program_header_table 是一个数组,这个表示数组的大小。

e_shentsize_SECTION_HEADER_ENTRY_SIZE

section 相关,无用。

e_shnum_NUMBER_OF_SECTION_HEADER_ENTRIES

section 相关,无用。

e_shtrndx_STRING_TABLE_INDEX

section 相关,无用。

总结:section 相关,无用,program 相关,有用。

我们可以做一个实验,将 section header 部分全部使用 EE 覆盖,push 到手机上,是依然可以运行的。

覆盖完之后,将该程序使用 ida 打开,发现 ida 根本解析不了,这是因为 IDA 是依靠 section 来解析的。

实际上加载一个 so 文件的时候,ida 的 segment view 里面就是解析的 section header。如果我们破坏了甚至是弄一个假的 .text/.data section,那么 ida 就没有办法正常解析了:

program_header_table

它是一个数组,里面的元素结构体为是 program_table_element。

p_type

段类型

p_flags

段属性,可读,可写,可执行。

p_offset_FROM_FILE_BEGIN

段在文件中的偏移。

p_vaddr_VIRTUAL_ADDRESS

段的虚拟地址。这个非常有用,这里表示的是一个相对偏移,因为段被加载到的虚拟地址是不确定的,所以真实的虚拟地址 = 加载的虚拟地址的基址 + 这个虚拟地址

p_paddr_PHYSICAL_ADDRESS

段的物理地址,这个没啥用,段只有虚拟地址才有意义。

p_filesz_SEGMENT_FILE_LENGTH

段在文件中的长度。

p_memsz_SEGMENT_RAM_LENGTH

段在内存中的长度。

p_align

段的对齐方式,没啥用,只能说是一个建议,os 一般不管这个值。

上面这几个字段,描述的其实是将段加载到内存中时,elf文件中的段映射到了内存中。

p_offset_FROM_FILE_BEGIN 与 p_filesz_SEGMENT_FILE_LENGTH 表示了文件中的段。

p_vaddr_VIRTUAL_ADDRESS 与 p_memsz_SEGMENT_RAM_LENGTH 表示了内存中的段。

这两者构成一个映射关系,linker 在加载 elf 的时候也是采用的 mmap。

p_filesz_SEGMENT_FILE_LENGTH 与 p_memsz_SEGMENT_RAM_LENGTH 的大小不一定一样,因为为了节省 elf 文件大小,有些值为0的段,比如 .bss 就不占文件空间。但是加载到内存后,还是要分配空间的。

整体看下段信息:

发现,可加载段实际上只有2个,这2个就是我们需要重点关注的段。

看其权限,可知,R_X 这个段必定是代码段。

根据,elf_header 的 e_entry_START_ADDRESS 值,我们知道其代码执行的入口在 0x000000000000CCC4 这个地址。

这个地址,加上段的虚拟地址的偏移 0,所以,我们知道其入口代码的虚拟地址是 CCC4。我们再看其文件映射关系:

是1对1映射,起始地址都为0,也就是说入口代码的文件地址也是 CCC4:

那么我们将这个入口指令改成一个死循环,就可以将 ls 卡住。

我们查看其 maps 文件:

发现,这里有3段,对比一下权限与大小,可以看出中间一段是用于填充的(有误,看下一篇),没啥用。

我们也可以更改elf文件段的 p_flags,它会影响到 maps 里面段的属性,因为它们会保持一致。但是对于非可加载段,这个 p_flags 是没有用的。

相关推荐
芝士就是力量啊 ೄ೨6 分钟前
Python如何编写一个简单的类
开发语言·python
橘子海全栈攻城狮11 分钟前
【最新源码】养老院系统管理A013
java·spring boot·后端·web安全·微信小程序
逻辑驱动的ken17 分钟前
Java高频面试考点18
java·开发语言·数据库·算法·面试·职场和发展·哈希算法
MoonBit月兔18 分钟前
「Why MoonBit 」第一期——Singularity Note AI 学习助手
开发语言·人工智能·moonbit
广州华水科技29 分钟前
单北斗GNSS变形监测系统在水利工程安全保障中的应用与优势分析
前端
木木_王33 分钟前
嵌入式Linux学习 | 数据结构 (Day05) 栈与队列详解(原理 + C 语言实现 + 实战实验 + 易错点剖析)
linux·c语言·开发语言·数据结构·笔记·学习
yqcoder39 分钟前
CSS 外边距重叠(Margin Collapsing):现象、原理与完美解决方案
前端·css
冷雨夜中漫步1 小时前
Claude Code源码分析——Claude Code Agent Loop 详细设计文档
java·开发语言·人工智能·ai
超龄编码人1 小时前
Qt Widgets Designer QTabWidget无法添加布局
开发语言·qt
直奔標竿1 小时前
Java开发者AI转型第二十六课!Spring AI 个人知识库实战(五)——联网搜索增强实战
java·开发语言·人工智能·spring boot·后端·spring