elf文件結構
前言
在研究360加固時,發現自己對elf文件完全不理解,於是決定先好好學下elf文件結構。
本文以AOSP版本Oreo8.1.0_r33
作為研究對象,由上到下逐漸解析一個so文件。
Elf Header
32位elf文件的Elf Header的結構體是Elf32_Ehdr
,64位基本一致,除了e_ident[4]
。
同時也列出一些會用到的常量( 宏定義/枚舉值 )如下:
1 | /*=================== art/runtime/elf.h ===================*/ |
一些個人認為比較重要的字段:
e_ident
:長度為EI_NIDENT
( 定義為16
),e_ident[0~3]
是elf文件的標誌,固定為7F 45 4C 46
,第e_ident[4]
用來表示該ELF是32位/64位,1
是前者2
是後者。e_type
:文件類型,取值範圍是ET_
開頭的枚舉值,如so文件就是3
。e_machine
:該文件所需的架構,取值範圍是EM_開頭的枚舉值,如arm64是183
。e_entry
:是一個相對偏移,指向程序的起始地址,對於一個可執行的elf文件來說,它指向start
函數的起始地址,而對於so文件來說,它為0
。e_phoff
:Program Header Table的偏移。e_shoff
:Section Header Table的偏移。e_ehsize
:Elf Header的大小。e_phentsize
:Program Header Table每個元素的大小。e_phnum
:Program Header Table中元素的數量。e_shentsize
:Section Header Table每個元素的大小。e_shnum
:Section Header Table中元素的數量。e_shstrndx
:是Section Header Table的一個索引值,指向了.shstrtab
節區( 這個節區存儲著所有節區的名字,例如.text
)在Section Header Table裡的索引值。
Program Header
1 | /*=================== art/runtime/elf.h ===================*/ |
一些個人認為比較重要的字段:
p_type
:當前segment的類型,如PT_LOAD
、PT_DYNAMIC
。p_offset
:當前segment的文件偏移。p_vaddr
:加載進內存後的虛擬地址。p_paddr
:加載進內存後的實際物理地址。p_filesz
:當前segment在文件中的大小( 單位:byte )。p_memsz
:當前segment在內存中的大小( 單位:byte )。p_flags
:當前段的屬性,如可讀PF_R
、可寫PF_X
等等。p_align
:內存對齊的字節數。
Segment的類型
記錄一些常見p_type
PT_LOAD
:一個可執行文件最少要有一個該類型的segment,該類型描述表示當前segment是可裝載的,即當前segment會被裝載或映射到內存中。一段來說elf文件通常會有2個PT_LOAD
的segment,分別是存放代碼的text
段和存放全局變量和動態鏈接信息的data
段。
PT_PHDR
:表示program header table本身。
PT_DYNAMIC
:該類型的segment header指定動態鏈接的一些信息。
Section Header
1 | /*=================== art/runtime/elf.h ===================*/ |
一些個人認為比較重要的字段:
sh_name
:是shstrtab
表( 見Elf Header的e_shstrndx
字段 )的一個索引,該索引指向了當前section的名字。sh_type
:當前section的類型,取值範圍是SHT_
開頭的枚舉值,如SHT_DYNAMIC
代表section與動態鏈接有關。sh_flags
:當前section的屬性,取值範圍是SHF_
開頭的枚舉值,如SHF_WRITE
代表section在執行過程中應是可寫的。sh_addr
:當前section的加載進內存的地址( 內存偏移 )。sh_offset
:當前section的文件偏移。sh_size
:當前section大小( 單位:byte )。sh_addralign
:內存對齊的字節大小( 某些Section帶有地址對齊約束,例如某個節區保存了一個DWROD,那麼系統必須保證整個節區能夠按雙字對齊。sh_addr%sh_addralign
必須為0
,目前僅允許取值為0和2的冪次數。數值為0
、1
表示節區沒有對齊約束 )。sh_entsize
:當前節區中每個項占用的字節數。
補充:Segment與Section
參考:https://www.cnblogs.com/jiqingwu/p/elf_format_research_01.html
ELF全稱是Executable and Linking Format,即可執行和可鏈接的格式,下圖分別是ELF文件的鏈接視圖和執行視圖。
可以看到,鏈接視圖由Section組成,而執行視圖由Segment組成。對前者來說Program header table( Segment )不是必要的,對後者來說Section header table不是必要的。
鏈接過程:鏈接器將目標文件中屬性相同的Section合並成一個集合,此集合便稱為Segment
Sections
記錄幾個比較重要的section,以後有需要再補充😎
Symbol Table ( .symtab
)
Symbol Table是一個特殊的Section( 是名為.symtab
的Section Header指向的那個節區 ),它包含了所有的符號信息,包括已定義的符號和未定義的符號。
1 | /*=================== art/runtime/elf.h ===================*/ |
一些個人認為比較重要的字段:
st_name
:.strtab
節區( 由Section Header定位到對應節區 )的一個索引值,指向某符號名。st_value
:這個值在不同的上下文中有不同的意義:- 在可重定位文件中,若符號的
st_shndx
等於SHN_COMMON
,剛st_value
表示該符號的對齊字節數。 - 在可重定位文件中,若一個符號是已定義時( 何為已定義?後面會給出 ),
st_value
表示section內的偏移( section由st_shndx
指定 )。 - 在可執行文件和so文件中,
st_value
是一個虛擬地址,這時不需要關心st_shndx
的值。
- 在可重定位文件中,若符號的
st_info
:符號的綁定屬性,取值範圍是STB_
開頭的枚舉值,如STB_LOCAL
是局部符號,局部符號就是一種未定義的符號。st_shndx
:當符號已定義時,它代表Section Header Table的某個下標索引,配合st_value
定位到對應該符號。
何為已定義的符號?GPT給出的回答如下,僅供參考:
.dynamic
Section Header Table中名為.dynamic
的那項指向的節表,其實是一個Elf32_Dyn
數組,32位每個元素占8字節,64位則占16字節,以下列出了32位的。
1 | /*=================== art/runtime/elf.h ===================*/ |
字段解析:
d_tag
:該字段決定了這個是什麼類別,以及如何解析d_un
,取值範圍是DT_
開頭的枚舉值。d_un
:根據d_tag
決定是使用d_val
或d_ptr
。
例子:
當d_tag
為DT_NEEDED
時,使用d_un
的d_val
,而這時d_val
代表字符表的下標索引( 這個字符表是由DT_STRTAB
確定 )
其他更多可以參考:
- https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-42444.html
- https://ctf-wiki.org/executable/elf/structure/dynamic-sections/
- https://cloud.tencent.com/developer/article/2216942