標簽(空格分隔): 20135321余佳源
一、基礎知識
ELF全稱Executable and Linkable Format,可執行連接格式,ELF格式的文件用於存儲Linux程序。ELF文件(目標文件)格式主要三種:
- 可重定向文件:文件保存着代碼和適當的數據,用來和其他的目標文件一起來創建一個可執行文件或者是一個共享目標文件。(目標文件或者靜態庫文件,即linux通常后綴為.a和.o的文件)
- 可執行文件:文件保存着一個用來執行的程序。(例如bash,gcc等)
- 共享目標文件:共享庫。文件保存着代碼和合適的數據,用來被下連接編輯器和動態鏈接器鏈接。(linux下后綴為.so的文件。)
一般的 ELF 文件包括三個索引表:
- ELF header ELF頭,在文件的開始,保存了路線圖,描述了該文件的組織情況。
- Program header table 程序頭表,告訴系統如何創建進程映像。用來構造進程映像的目標文件必須具有程序頭部表,可重定位文件不需要這個表。
- Section header table 段節頭表,包含了描述文件節區的信息,每個節區在表中都有一項,每一項給出諸如節區名稱、節區大小這類信息。用於鏈接的目標文件必須包含節區頭部表,其他目標文件可以有,也可以沒有這個表。
二、ELF文件頭(ELF header)的分析
進入終端輸入:cd /usr/include
進入include文件夾后查看elf.h文件,查看ELF的文件頭包含整個文件的控制結構,這里看到的是32位系統的elf.h
-
e_ident
這個最初的字段標示了該文件為一個可執行的object文件,提供了一個機器無關的數據,解釋文件的內容。 -
e_type
確定了object的類型 -
e_version
確定object的文件版本 -
e_entry
是系統第一個傳輸控制 的虛擬地址,在那啟動進程,假如文件沒有如何關聯的入口點,該值為0 -
e_phoff
program header offset, 保持了程序頭表在文件中的偏移量(bytes),假如沒有程序頭表的話,該值為0 -
e_shoff
section header offset,保持着段節頭表在文件中的偏移量(bytes),如果沒有段節頭表的話,該值為0 -
e_flags
保存着相關文件的處理器標志 -
e_ehsize
elf header size,保存着ELF頭大小(bytes) -
e_phentsize
program header entry size,保存着在文件的程序頭表中一個入口的大小(bytes),所有入口大小都一樣。 -
e_phnum
program header number,保存着程序頭表的個數,也就是說和e_phentsize的乘積就是表的大小(bytes),如果沒有程序頭表, -
e_shentsize
section header entry size,section段節頭大小(bytes),一個段節 頭在段節頭表中的一個入口,所有入口同樣大小 -
e_shnum
section header number,保存着在段節頭表中的入口數目,與e_shentsize乘積是section頭表的大小,如果沒有section頭表,該值為0 -
e_shstrndx
section header string index,保存跟段節section名字字符表相關入口的section頭表索引,假如沒有section名字字符表,該值就會變成SHN_UNDEF,section header null,undefine
接下來對fish的elf文件頭進行分析
又圖可以看出elf頭大小為52bytes,接下來可以使用hexdump -x fish -n 52
來查看fish文件頭的前52bytes並分析其格式
第一行,本系統是小端法顯示,對應e_ident前四個字節457f464c,就是7f454c46,就是7f elfd對應的ASCII碼,接下來一個01就是表示32位機器,接下來一個01,就是小端法的表示,再接着一個01,表示文件頭版本,剩下默認設置為0。
第二行,e_type值為0x0002,表示這是一個可執行文件,e_machine值為0x0003,表示是intel80386的處理器體系結構,e_version值為0x00000001,表示當前版本,e_entry為0x08048370,表示其入口地址,e_phoff值為0x00000034,表示該程序頭為52bytes
第三行,e_shoff值為0x00001158,表示的是段表的偏移地址為4440bytes,e_flags為0x00000000,表示未定的處理器標志,e_ehsize值為0x0034,表示了elf頭的大小是52bytes,e_phentsize,值為0x0020表示了其中一個程序頭表的入口大小是32bytes,e_phnum值是0x0009,表示程序頭表的入口數為9個,e_ehentsize值為0x0028,表示了該段節的頭大小為40bytes。
第四行,e_shnum值為0x001e,表示了段表入口有30個,e_shstrndx值為0x001b,表示了該段在段名字字符表中索引號是27號
三、通過文件頭找到section header table,理解其內容
輸入readelf -S fish
查看fish的section header table內容
- [Nr]表示對應的section索引值
- Name,名稱
- Type,section的類型
- Addr,起始地址
- Off,section偏移地址
- size,section大小
輸入readelf -s fish
查看fish的符號表
四、通過section header table找到各section
在一個ELF文件中有一個section header table,通過它我們可以定位到所有的 section,而 ELF header 中的e_shoff 變量就是保存 section header table 入口對文件頭的偏移量。而每個 section 都會對應一個 section header ,所以只要在 section header table 中找到每個 section header,就可以通過 section header 找到你想要的 section。
下面以可執行文件fish為例,以保存代碼段的 section 為例來講解讀取某個section 的過程。
由上面的圖可以看出32位系統的section header結構體
看到e_shoff值0x00001158可以知道段表地址偏移為0x1158,e_shnum值為0x001e可知段表入口30個,即從0x1158開始有30個段占了40bytes
接下來開始輸入hexdump fish
查看全部的16進制信息,並找到第一段開始讀取
不過這里第一段全為0。
接下來是第二段
可以對應section header table看看發現是符合的,第二段是.interp段,起始地址0x08048154,偏移量是0x000154,大小是13
接着是第三段
是.note.ABI-tag段,起始地址是0x08048168,偏移量是0x000168,大小是20
同理可以看其他的段
比如說.text段,即可執行指令的集合,起始地址是0x08048370,偏移地址370,大小是1e2,換算十進制后再使用hexdump -s 880 -n 472 -C fish
查看對應的數據
這時可以使用readelf -x 13 fish
查看.text段的數據:
兩者對比發現數據一致,說明通過section header table 成功找到了.text數據節,然后使用objdump -d fish
找到.text段的數據並和section header table與readelf兩者找到的一樣
五、理解常見的.text .strtab .symtab .rodata等section
-
.text
代碼段,存儲二進制的機器指令,這些指令可以被機器直接執行 -
.rodata
read only data,只讀數據段,存儲程序中所使用的復雜常量,比如字符串。 -
.data
數據段,存儲程序中已經被明確初始化的全局數據,包括C語言的全局變量和靜態變量,如果全局數據被初始化為0,則不存儲在數據段中,而是存儲在塊數據段中,C語言局部數據存儲在棧中,不出現在數據段中。 -
.bss
塊數據段,存儲未被明確初始化的全局變量,在目標文件中,這個段並不占有空間,而僅僅是一個占位符,以告知指定位置上應當預留全局數據的空間,塊緩存段存在的原因是為了提高磁盤的空間利用率 -
.symtab
一個符號表,存放在程序中被定義和引用的函數和全局變量的信息,但是不包括局部變量的表目 -
.strtab
string table,字符串表,其內容包括了.symtab和.debug節中的符號表,以及節頭部中的節名稱,字符串表就是以null結尾的字符串序列