前言:
熟悉elf文件結構是一件很不錯的事,因為安卓中的so加固以及修復都是需要這些知識的,包括pwn里面的rop之類的,也都是
和got節,plt節息息相關的,個人建議是在搞懂elf文件結構后,自己實現一個解析器,把注釋寫好,方便忘了再進一步重溫,寫的不好
見諒。
一. elf文件概述
elf文件包括了可執行文件,共享文件,目標文件這三類,其中安卓中涉及到的就是so文件,這個其實就是一個共享文件,類似
windows上的dll文件,目標文件是匯編文件,后綴為.o的文件,與可執行文件不同的是,並沒有段頭表,因為段是由相同功能的
節組合成的,而目標文件只是一個模塊,並沒有和其他模塊進行鏈接,也就是節也沒有合並,所以不存在段這個概念,程序的入口點
地址也是為空的,可執行文件和共享文件的話,大體結構和目標文件相同,多了段的概念,然后提供了兩種視圖(鏈接視圖和裝載視圖)
二.elf文件結構
看起來其實不復雜,文件頭,程序頭表,節頭表,節,段(其實就是相同功能節的組合體),接下來單獨說說各個部分
三.elf文件頭
這里搬出來010editor來看,這里是我導入之前做的crackme的so文件
好像名字奇奇怪怪的,我直接搬上,elf結構體定義
typedef struct { unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ Elf32_Half e_type; /* Object file type */ Elf32_Half e_machine; /* Architecture */ Elf32_Word e_version; /* Object file version */ Elf32_Addr e_entry; /* Entry point virtual address */ Elf32_Off e_phoff; /* Program header table file offset */ Elf32_Off e_shoff; /* Section header table file offset */ Elf32_Word e_flags; /* Processor-specific flags */ Elf32_Half e_ehsize; /* ELF header size in bytes */ Elf32_Half e_phentsize; /* Program header table entry size */ Elf32_Half e_phnum; /* Program header table entry count */ Elf32_Half e_shentsize; /* Section header table entry size */ Elf32_Half e_shnum; /* Section header table entry count */ Elf32_Half e_shstrndx; /* Section header string table index */ } Elf32_Ehdr;
重點說說幾個字段,沒說的說明比較簡單易懂
1. e_ident:魔數,標識是哪個文件
2. e_phoff:程序頭表在文件中的偏移
3. e_shoff: 程序節表在文件中的編譯
4.e_phentsize: elf頭占多少字節,32位的so,一般為52個字節
5. e_phnum: 程序頭表中有關程序頭的結構體個數,類似一個int數組,中int的個數
6. e_shnum: 和e_phnum差不多,把主語換成節頭表
7. e_shstrndx: .strtab節在節頭表的下標,因為里面存着所有節的名字
四.節頭表解析
typedef struct { Elf32_Word sh_name; /* Section name (string tbl index) */ Elf32_Word sh_type; /* Section type */ Elf32_Word sh_flags; /* Section flags */ Elf32_Addr sh_addr; /* Section virtual addr at execution */ Elf32_Off sh_offset; /* Section file offset */ Elf32_Word sh_size; /* Section size in bytes */ Elf32_Word sh_link; /* Link to another section */ Elf32_Word sh_info; /* Additional section information */ Elf32_Word sh_addralign; /* Section alignment */ Elf32_Word sh_entsize; /* Entry size if section holds table */ } Elf32_Shdr;
節頭表的結構如上圖,實際上節頭表就是上圖結構體的數組,里面的數量是上面elf文件頭中的e_shnum決定的,但是注意這個結構體並不是我們所想的節
也只是一個中間過渡的東西,只是定義了每個節在文件的哪個位置,名字叫什么,大小,類型是什么,具體的內容,還要根據其中定義好的偏移和大小再
去查找,接下來說說每個字段的含義
1.sh_name 是字符串節的下表,通常是先根據文件頭中的strndx字段找到字符串節,然后再根據這個sh_name,找到節的名字
2. sh_type 表明這個節的類型是什么,內容比較多,直接上圖
3.sh_flag 表明這個節是否可讀可寫可執行,記憶性的東西,直接上圖
4.sh_addr 將會映射到虛擬內存空間中的地址
5.sh_size 和sh_off: 前一個是節的大小,后一個是節在文件中的偏移
在linux上也可以通過readelf -S xxx(文件名)進行查看
五. 特殊的節
五.1 .symtab
符號表的節,一般的so文件都會被抹去,怕被反編譯,直接還原出符號名,也是一個結構體數組
st_name: 在字符串表中的下標
st_value :真正的值
st_size: 大小
st_info: 符號類別
畢竟符號分為局部符號,全局符號,還得標記是不是動態鏈接的
st_stndx: 符號屬於哪個段,那個段在節頭表的下標
挑了個so文件,發現里面的符號表已經被抹去了,-s只能查看.dynsym這個動態鏈接的節了
2..dynsym和.dynstr
動態鏈接符號表,里面主要存放着動態鏈接的符號,.dynstr里面主要存放動態鏈接符號的字符串名,
3. .rel.節名
重定位的表,很重要,因為這個表需要告訴linker哪個符號需要重定位
4. .plt節和.got節
出現.plt節的原因是有延遲綁定機制,因為動態鏈接中,符號很多,而且有些符號還沒用到
那么重定位的負擔就很重,所以就出現了延遲綁定,只有用到了該符號再進行綁定(這里的綁定主要說的是got表中填入符號的地址)
所以匯編代碼大概是這樣的:
jmp 后面的地址,是got表中的內容,但是如果沒用過這個符號,里面填寫的是push n的地址,也就是跳轉到下一條指令了,
然后在把got表的下標和模塊的id入棧,調用符號綁定的函數,實現延遲綁定,所以.plt節實際上就是一個got表的跳板,
六.程序頭表(segment)
elf可分為兩種視圖,一種是鏈接視圖,還有一種是裝載視圖
實際上不需要想的很復雜,段實際上就是相同功能的節的集合,本質上還是節,不過裝載過程中所需的信息只和段有關,這也是為什么so加固中,可以去動節的一些信息,為我們解殼提供便利
typedef struct
{
Elf32_Word p_type;
/
*
Segment
type
*
/
Elf32_Off p_offset;
/
*
Segment
file
offset
*
/
Elf32_Addr p_vaddr;
/
*
Segment virtual address
*
/
Elf32_Addr p_paddr;
/
*
Segment physical address
*
/
Elf32_Word p_filesz;
/
*
Segment size
in
file
*
/
Elf32_Word p_memsz;
/
*
Segment size
in
memory
*
/
Elf32_Word p_flags;
/
*
Segment flags
*
/
Elf32_Word p_align;
/
*
Segment alignment
*
/
} Elf32_Phdr;
也是一個結構體數組,注釋也寫得很清楚了。略
總結: 重心還是在節那塊,參考了程序員自我修養,和看雪的幾篇文章,但是看雪文章明顯不如書有精華了,建議還是看看書