ELF解析01 - ELF头和程序头

2023-12-29 12:58:55

本系列将完全是参考 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 是没有用的。

文章来源:https://blog.csdn.net/a5right/article/details/135287853
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。