ELF文件(目標文件)格式主要三種:
1)可重定向文件:文件保存着代碼和適當的數據,用來和其他的目標文件一起來創建一個可執行文件或者是一個共享目標文件。(目標文件或者靜態庫文件,即linux通常后綴為.a和.o的文件)
2)可執行文件:文件保存着一個用來執行的程序。(例如bash,gcc等)
3)共享目標文件:共享庫。文件保存着代碼和合適的數據,用來被下連接編輯器和動態鏈接器鏈接。(linux下后綴為.so的文件。)
一般的 ELF 文件包括三個索引表:
1)ELF header:在文件的開始,保存了路線圖,描述了該文件的組織情況。
2)Program header table:告訴系統如何創建進程映像。用來構造進程映像的目標文件必須具有程序頭部表,可重定位文件不需要這個表。
3)Section header table :包含了描述文件節區的信息,每個節區在表中都有一項,每一項給出諸如節區名稱、節區大小這類信息。用於鏈接的目標文件必須包含節區頭部表,其他目標文件可以有,也可以沒有這個表。
1、 分析ELF文件頭(ELF header)
進入終端輸入:cd /usr/include/elf.h,查看ELF的文件頭包含整個文件的控制結構
順便選取一個簡單代碼為例:
進行編譯運行,生成elf可執行文件。
使用‘readelf ’命令,得到下面的ELF Header頭文件的信息,如下圖:
通過上圖信息,可以得出Elf Header的Size為64bytes,所以可以使用hexdump工具將頭文件的16進制表打開。
如下圖使用:‘hexdump –x elf1 –n 64’命令來查看elf文件頭的16進制表(前64bytes)對格式進行分析。
第一行,對應e_ident[EI_NIDENT]。實際表示內容為7f454c46020101000000000000000000,前四個字節7f454c46(0x45,0x4c,46是'e','l','f'對應的ascii編碼)是一個魔數,表示這是一個ELF對象。
接下來的一個字節02表示是一個64位對象,接下來的一個字節01表示是小端法表示,再接下來的一個字節01表示文件頭版本。剩下的默認都設置為0.
第二行,e_type值為0x0002,表示是一個可執行文件。e_machine值為0x003e,表示是Advanced Micro Devices X86-64處理器體系結構。e_version值為0x00000100,表示是當前版本。e_entry值為0x 004003f0,表示入口點(下面會用到)。
第三行,e_phoff值為0x40,表示程序頭表。e_shoff值為0x1290,表示段表的偏移地址。
第四行,e_flags值為0x00000000,表示未知處理器特定標志。e_ehsize值為0x0040,表示elf文件頭大小(正好是64個字節)。e_phentsize表示一個program header表中的入口的長度,值為0x0038。e_phnum的值為0x0008,給出program header表中的入口數目。e_shentsize值為0x0040表示段頭大小為64個字節。e_shnum值為0x001f,表示段表入口有31個。e_shstrndx值為0x001c,表示段名串表的在段表中的索引號。
2、通過文件頭找到section header table,理解其內容
輸入:hexdump –x elf1來用16進制的數字來顯示elf1的內容
(其中,標紅第二列是16進制表示的偏移地址)
輸入:objdump –x elf1來顯示elf1中各個段以及符號表的相關信息:
輸入:readelf –a elf1來查看各個段信息:
ELF文件頭信息:
段表Section header table:
.text的索引值為14。
符號表 Symbol table:
3、通過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。
下面以可執行文件elf1為例,以保存代碼段的 section 為例來講解讀取某個section 的過程。
使用‘vi /usr/include/elf.h ’命令查看Sections Header的結構體:
由上面分析可知,section headers table中的每一個section header所占的size均為64字節,ELF header得到了e_shoff變量的值為0X1170,也就是table入口的偏移量,通過看e_shnum值為0x001e,表示段表入口有30個。
所以從0x00001170開始有30個段,每個段占64個字節大小,輸入 hexdump elf1查看:
第一個段,其中內容全部為0,所以不表示任何段。
第二個段,為.interp段,段偏移sh_offset為0X200,段大小sh_size為0X1c
第三個段,為.note.ABI-tag段,段偏移sh_offset為0X 21c,段大小sh_size為0X20。
第四個段,為.note.gnu.build-i段,段偏移sh_offset為0X23c(紅線), 段大小sh_size為0X 24(藍線)。
第五個段,為.gnu.hash段,段偏移sh_offset為0X 260, 段大小sh_size為0X 1c
……中間的段省略……
第十四個段,為.text段, 段偏移sh_offset為0X 3f0, 段大小sh_size為0X 182
下面用readelf –S elf1命令先去看看elf1的section table中存放的所有的 section header。
我們用readelf 命令去查看.text這個 section 中的內容,
輸入readelf –x 14 elf1,對14索引號的.text的section的內容進行查看:
下面用 hexdump 的方法去讀取.text這個 section 中的內容,通過看section header中.text中offset和size分別是0x3f0和0x182,通過16進制向10進制轉換得到offset:1008和size:386。
輸入 hexdump –s 1008 –n 386 –C elf1
得到了和上面的readelf得到的相同。
使用下面命令對elf1的文本段(.text)進行反匯編:
objdump –d elf1 得到如下圖:
可以看出,使用反匯編的16進制數據和前面查找到的是相同的。
我們可以使用相同的方法對其他section進行查看,.data 數據段 .bbs 堆棧段(存放沒有初始化的數據) .symtab(符號表)段。
① 查看.data數據段的section
比較簡單,使用readelf –x 24(索引號) elf1(文件名)來查看
同理分析其他段表
4、理解常見.text .strtab .symtab .rodata等section
①.text section是可執行指令的集合,.data和.text都是屬於PROGBITS類型的section,這是將來要運行的程序與代碼。查詢段表可知.text section的位偏移為0x0000440,size為0x0000192。
②.strtab section是屬於STRTAB類型的section,可以在文件中看到,它存着字符串,儲存着符號的名字。位偏移為0x0001f08,size為0x0000238。
③.symtab section存放所有section中定義的符號名字,比如“data_items”,“start_loop”。 .symtab section是屬於SYMTAB類型的section,它描述了.strtab中的符號在“內存”中對應的“內存地址”。 位偏移為0x00018f0,size為0x0000618。
④.rodata section,ro代表read only,即只讀數據(const)。位偏移為0x00005e0,size為0x000000c。