elf文件格式实例解析
?
試驗環境:archlinux 速龍3000+(即x86兼容32位處理器)
必須軟件:gcc binutils
參考資料:
System V application binary interface
ELF Format (mirror txt format )
Hello,world in less than 20 bytes
Tutorial on creating teensy ELF file on linux (中文翻譯版本 ,also see (smallest elf32 hello,world ))
Introduction to reverse engineering on linux ? (also see crackz reverse engineering page(windows),resources )
Deconstructing an ELF file
The ELF virus writing howto
Playing with binary format
ELF hackery (many links)
ELF or assembly reference (on skyeye)
linkers and loaders
linkers(part 1 ,part 2 , part 3 , part 4 , part 5 , part 6 , part 7 , part 8 ,part 9 ,part 10
????? part 11 ,part 12 , part 13 , part 14 , part 15 , part 16 , part 17 , part 18 , part 19 , part 20 )
hacker's wisdom
還可用百度搜索"ELF site:ibm.com",能搜索到很多關于ELF中文翻譯教程,其中文后的參考文獻也很值得看。
pe(window下的庫文件和可執行文件格式)相關鏈接可以在wikipedia上找到 。
?
ELF 文件分為三類:(1)可重定位文件(目標文件或者靜態庫文件,即linux通常后綴為.a和.o的文件) (2)可執行文件(即可以運行的二進制文件,例如bash,gcc等)(3)共享目標文件(即linux下后綴為so的文件)。Elf文件格式(參見System V Application Binary interface 第46頁)的布局如下:
?
-----------------------------
ELF文件頭(即ELF Header)
----------------------------
程序文件頭表(Program header table)
-----------------------------------
Section 1
-----------------------------------
???? ...
-----------------------------------
Section n
----------------------------------
???? ...
----------------------------------
段表(Section header table)
-----------------------------------
?
其中程序文件頭表對于可重定位文件是可選項(對另外兩類文件是必需項),而段表對于可執行文件是可選項(對另外兩類文件是必需項)。另外,Section可以是.text,.data,.bss(即代碼段,數據段(用來存放已經初始化的全局變量和靜態變量)和BSS段(用來存放未初始化的全局變量和靜態變量))等
?
使用《程序員的自我修養--鏈接 裝載和庫》中第三章的例子來說明elf的具體格式
/** SimpleSection.c* * Linux:* gcc -c SimpleSection.c* Windows:* cl SimpleSection.c /c /Za*/ int printf(const char* format, ...); int global_init_var = 84; int global_uinit_var;void func1(int i) {printf("%d\n", i); }int main(void) {static int static_var = 85;static int static_var2;int a = 1;int b;func1(static_var + static_var2 + a + b);return a; }?使用下面命令來編譯:
?
gcc -c SimpleSection.c?再使用下面命令來顯示生成的目標文件(SimpleSection.c)的類型
?
file SimpleSection.o?輸出下列內容:
SimpleSection.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped?說明SimpleSection.o是一個重定位文件
使用下面命令查看SimpleSection.o的大小:
?
ls -l SimpleSection.o?輸出結果為:
?
-rw-r--r-- 1 host users 1092 11月 5 14:38 SimpleSection.o?根據輸出結果可以知道,SimpleSection.o大小為1092字節。
使用下面命令用16進制的數字來顯示SimpleSection.o的內容(也可以用od -x SimpleSection.o命令)
?
hexdump -x SimpleSection.o?輸出結果為:
0000000 457f 464c 0101 0001 0000 0000 0000 0000 0000010 0001 0003 0001 0000 0000 0000 0000 0000 0000020 010c 0000 0000 0000 0034 0000 0000 0028 0000030 000b 0008 8955 83e5 18ec 458b 8908 2444 0000040 c704 2404 0000 0000 fce8 ffff c9ff 55c3 0000050 e589 e483 83f0 20ec 44c7 1c24 0001 0000 0000060 158b 0004 0000 00a1 0000 8d00 0204 4403 0000070 1c24 4403 1824 0489 e824 fffc ffff 448b 0000080 1c24 c3c9 0054 0000 0055 0000 6425 000a? 0000090 4700 4343 203a 4728 554e 2029 2e34 2e35 00000a0 2032 3032 3131 3130 3732 2820 7270 7265 00000b0 6c65 6165 6573 0029 2e00 7973 746d 6261 00000c0 2e00 7473 7472 6261 2e00 6873 7473 7472 00000d0 6261 2e00 6572 2e6c 6574 7478 2e00 6164 00000e0 6174 2e00 7362 0073 722e 646f 7461 0061 00000f0 632e 6d6f 656d 746e 2e00 6f6e 6574 472e 0000100 554e 732d 6174 6b63 0000 0000 0000 0000 0000110 0000 0000 0000 0000 0000 0000 0000 0000 * 0000130 0000 0000 001f 0000 0001 0000 0006 0000 0000140 0000 0000 0034 0000 0050 0000 0000 0000 0000150 0000 0000 0004 0000 0000 0000 001b 0000 0000160 0009 0000 0000 0000 0000 0000 041c 0000 0000170 0028 0000 0009 0000 0001 0000 0004 0000 0000180 0008 0000 0025 0000 0001 0000 0003 0000 0000190 0000 0000 0084 0000 0008 0000 0000 0000 00001a0 0000 0000 0004 0000 0000 0000 002b 0000 00001b0 0008 0000 0003 0000 0000 0000 008c 0000 00001c0 0004 0000 0000 0000 0000 0000 0004 0000 00001d0 0000 0000 0030 0000 0001 0000 0002 0000 00001e0 0000 0000 008c 0000 0004 0000 0000 0000 00001f0 0000 0000 0001 0000 0000 0000 0038 0000 0000200 0001 0000 0030 0000 0000 0000 0090 0000 0000210 0028 0000 0000 0000 0000 0000 0001 0000 0000220 0001 0000 0041 0000 0001 0000 0000 0000 0000230 0000 0000 00b8 0000 0000 0000 0000 0000 0000240 0000 0000 0001 0000 0000 0000 0011 0000 0000250 0003 0000 0000 0000 0000 0000 00b8 0000 0000260 0051 0000 0000 0000 0000 0000 0001 0000 0000270 0000 0000 0001 0000 0002 0000 0000 0000 0000280 0000 0000 02c4 0000 00f0 0000 000a 0000 0000290 000a 0000 0004 0000 0010 0000 0009 0000 00002a0 0003 0000 0000 0000 0000 0000 03b4 0000 00002b0 0065 0000 0000 0000 0000 0000 0001 0000 00002c0 0000 0000 0000 0000 0000 0000 0000 0000 00002d0 0000 0000 0001 0000 0000 0000 0000 0000 00002e0 0004 fff1 0000 0000 0000 0000 0000 0000 00002f0 0003 0001 0000 0000 0000 0000 0000 0000 0000300 0003 0003 0000 0000 0000 0000 0000 0000 0000310 0003 0004 0000 0000 0000 0000 0000 0000 0000320 0003 0005 0011 0000 0004 0000 0004 0000 0000330 0001 0003 0021 0000 0000 0000 0004 0000 0000340 0001 0004 0000 0000 0000 0000 0000 0000 0000350 0003 0007 0000 0000 0000 0000 0000 0000 0000360 0003 0006 0032 0000 0000 0000 0004 0000 0000370 0011 0003 0042 0000 0004 0000 0004 0000 0000380 0011 fff2 0053 0000 0000 0000 001b 0000 0000390 0012 0001 0059 0000 0000 0000 0000 0000 00003a0 0010 0000 0060 0000 001b 0000 0035 0000 00003b0 0012 0001 5300 6d69 6c70 5365 6365 6974 00003c0 6e6f 632e 7300 6174 6974 5f63 6176 2e72 00003d0 3231 3232 7300 6174 6974 5f63 6176 3272 00003e0 312e 3232 0033 6c67 626f 6c61 695f 696e 00003f0 5f74 6176 0072 6c67 626f 6c61 755f 6e69 0000400 7469 765f 7261 6600 6e75 3163 7000 6972 0000410 746e 0066 616d 6e69 0000 0000 0010 0000 0000420 0501 0000 0015 0000 0d02 0000 002e 0000 0000430 0301 0000 0033 0000 0401 0000 0046 0000 0000440 0c02 0000 0000444?上面的數據均為16進制數據(因為使用了-x選項),并且第一列為偏移地址。
使用下面命令來顯示SimpleSection.o中各個段相關信息:
?
objdump -x SimpleSection.o?輸出結果為:
?
SimpleSection.o: file format elf32-i386 SimpleSection.o architecture: i386, flags 0x00000011: HAS_RELOC, HAS_SYMS start address 0x00000000Sections: Idx Name Size VMA LMA File off Algn0 .text 00000050 00000000 00000000 00000034 2**2CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE1 .data 00000008 00000000 00000000 00000084 2**2CONTENTS, ALLOC, LOAD, DATA2 .bss 00000004 00000000 00000000 0000008c 2**2ALLOC3 .rodata 00000004 00000000 00000000 0000008c 2**0CONTENTS, ALLOC, LOAD, READONLY, DATA4 .comment 00000028 00000000 00000000 00000090 2**0CONTENTS, READONLY5 .note.GNU-stack 00000000 00000000 00000000 000000b8 2**0CONTENTS, READONLY SYMBOL TABLE: 00000000 l df *ABS* 00000000 SimpleSection.c 00000000 l d .text 00000000 .text 00000000 l d .data 00000000 .data 00000000 l d .bss 00000000 .bss 00000000 l d .rodata 00000000 .rodata 00000004 l O .data 00000004 static_var.1222 00000000 l O .bss 00000004 static_var2.1223 00000000 l d .note.GNU-stack 00000000 .note.GNU-stack 00000000 l d .comment 00000000 .comment 00000000 g O .data 00000004 global_init_var 00000004 O *COM* 00000004 global_uinit_var 00000000 g F .text 0000001b func1 00000000 *UND* 00000000 printf 0000001b g F .text 00000035 mainRELOCATION RECORDS FOR [.text]: OFFSET TYPE VALUE 00000010 R_386_32 .rodata 00000015 R_386_PC32 printf 0000002e R_386_32 .data 00000033 R_386_32 .bss 00000046 R_386_PC32 func1也可以用下面的命令來查看各個段信息:
?
readelf -a SimpleSection.o? 輸出結果為:
?
ELF Header:Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32Data: 2's complement, little endianVersion: 1 (current)OS/ABI: UNIX - System VABI Version: 0Type: REL (Relocatable file)Machine: Intel 80386Version: 0x1Entry point address: 0x0Start of program headers: 0 (bytes into file)Start of section headers: 268 (bytes into file)Flags: 0x0Size of this header: 52 (bytes)Size of program headers: 0 (bytes)Number of program headers: 0Size of section headers: 40 (bytes)Number of section headers: 11Section header string table index: 8Section Headers:[Nr] Name Type Addr Off Size ES Flg Lk Inf Al[ 0] NULL 00000000 000000 000000 00 0 0 0[ 1] .text PROGBITS 00000000 000034 000050 00 AX 0 0 4[ 2] .rel.text REL 00000000 00041c 000028 08 9 1 4[ 3] .data PROGBITS 00000000 000084 000008 00 WA 0 0 4[ 4] .bss NOBITS 00000000 00008c 000004 00 WA 0 0 4[ 5] .rodata PROGBITS 00000000 00008c 000004 00 A 0 0 1[ 6] .comment PROGBITS 00000000 000090 000028 01 MS 0 0 1[ 7] .note.GNU-stack PROGBITS 00000000 0000b8 000000 00 0 0 1[ 8] .shstrtab STRTAB 00000000 0000b8 000051 00 0 0 1[ 9] .symtab SYMTAB 00000000 0002c4 0000f0 10 10 10 4[10] .strtab STRTAB 00000000 0003b4 000065 00 0 0 1 Key to Flags:W (write), A (alloc), X (execute), M (merge), S (strings)I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)O (extra OS processing required) o (OS specific), p (processor specific)There are no section groups in this file.There are no program headers in this file.Relocation section '.rel.text' at offset 0x41c contains 5 entries:Offset Info Type Sym.Value Sym. Name 00000010 00000501 R_386_32 00000000 .rodata 00000015 00000d02 R_386_PC32 00000000 printf 0000002e 00000301 R_386_32 00000000 .data 00000033 00000401 R_386_32 00000000 .bss 00000046 00000c02 R_386_PC32 00000000 func1There are no unwind sections in this file.Symbol table '.symtab' contains 15 entries:Num: Value Size Type Bind Vis Ndx Name0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 FILE LOCAL DEFAULT ABS SimpleSection.c2: 00000000 0 SECTION LOCAL DEFAULT 1 3: 00000000 0 SECTION LOCAL DEFAULT 3 4: 00000000 0 SECTION LOCAL DEFAULT 4 5: 00000000 0 SECTION LOCAL DEFAULT 5 6: 00000004 4 OBJECT LOCAL DEFAULT 3 static_var.12227: 00000000 4 OBJECT LOCAL DEFAULT 4 static_var2.12238: 00000000 0 SECTION LOCAL DEFAULT 7 9: 00000000 0 SECTION LOCAL DEFAULT 6 10: 00000000 4 OBJECT GLOBAL DEFAULT 3 global_init_var11: 00000004 4 OBJECT GLOBAL DEFAULT COM global_uinit_var12: 00000000 27 FUNC GLOBAL DEFAULT 1 func113: 00000000 0 NOTYPE GLOBAL DEFAULT UND printf14: 0000001b 53 FUNC GLOBAL DEFAULT 1 mainNo version information found in this file. ??下面分析SimpleSection.o文件內容
? 首先是Elf文件頭,其定義為(在/usr/include/elf.h中)
#define EI_NIDENT (16) 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;?大小為52個字節(16進制表示為0x34),因此SimpleSection.o前52個字節內容為ELF文件頭,其二進制表示為:
0000000 457f 464c 0101 0001 0000 0000 0000 0000 0000010 0001 0003 0001 0000 0000 0000 0000 0000 0000020 010c 0000 0000 0000 0034 0000 0000 0028 0000030 000b 0008?因為intel及其兼容處理器使用了小端法,此處的-x命令選項是一次性輸出兩個字節,所以457f實際表示了7f45,而464c實際上是4c46,依次類推。
其前16個字節(第一行,對應e_ident[EI_NIDENT])實際表示內容為7f454c46010101000000000000000000,前四個字節7f454c46(0x45,0x4c,0x46是'e','l','f'對應的ascii編碼)是一個魔數(magic number),表示這是一個ELF對象。接下來的一個字節01表示是一個32位對象,接下來的一個字節01表示是小端法表示,再接下來的一個字節01表示文件頭版本。剩下的默認都設置為0.
接下來(第二行)e_type(兩個字節)值為0x0001,表示是一個重定位文件。e_machine(兩個字節)值為0x0003,表示是intel80386處理器體系結構。e_version(四個字節)值為0x00000001,表示是當前版本。e_entry(四個字節)值為0x00000000,表示沒有入口點。e_phoff(四個字節)值為0x00000000,表示沒有程序頭表。
接下來(第三行)e_shoff(四個字節)值為0x0000010c,表示段表的偏移地址。e_flags(四個字節)值為0x00000000,表示未知處理器特定標志(#define EF_SH_UNKNOWN??? ??? 0x0)。e_ehsize(兩個字節)值為0034,表示elf文件頭大小(正好是52個字節)。e_phentsize(兩個字節)和e_phnum(兩個字節)的值均為0x0000,因為重定位文件沒有程序頭表。e_ehentsize(兩個字節)值為0x0028表示段頭大小為40個字節。
接下來(第四行)e_shnum(兩個字節)值為0x000b,表示段表入口有11個。e_shstrndx(兩個字節)值為0x0008,表示段名串表的在段表中的索引號。
?
SimpleSection.o中緊接著ELF頭的部分是代碼段(.text)。使用下面命令對SimpleSection.o的文本段進行反匯編:
?
objdump -d SimpleSection.o?輸出結果為:
00000000 <func1>:0: 55 push %ebp1: 89 e5 mov %esp,%ebp3: 83 ec 18 sub $0x18,%esp6: 8b 45 08 mov 0x8(%ebp),%eax9: 89 44 24 04 mov %eax,0x4(%esp)d: c7 04 24 00 00 00 00 movl $0x0,(%esp)14: e8 fc ff ff ff call 15 <func1+0x15>19: c9 leave 1a: c3 ret 0000001b <main>:1b: 55 push %ebp1c: 89 e5 mov %esp,%ebp1e: 83 e4 f0 and $0xfffffff0,%esp21: 83 ec 20 sub $0x20,%esp24: c7 44 24 1c 01 00 00 movl $0x1,0x1c(%esp)2b: 00 2c: 8b 15 04 00 00 00 mov 0x4,%edx32: a1 00 00 00 00 mov 0x0,%eax37: 8d 04 02 lea (%edx,%eax,1),%eax3a: 03 44 24 1c add 0x1c(%esp),%eax3e: 03 44 24 18 add 0x18(%esp),%eax42: 89 04 24 mov %eax,(%esp)45: e8 fc ff ff ff call 46 <main+0x2b>4a: 8b 44 24 1c mov 0x1c(%esp),%eax4e: c9 leave 4f: c3 ret?代碼段剛好對應SimpleSection.o緊接著ELF頭的80(0x50)個字節代碼。即
? ? ? 8955 83e5 18ec 458b 8908 2444 0000040 c704 2404 0000 0000 fce8 ffff c9ff 55c3 0000050 e589 e483 83f0 20ec 44c7 1c24 0001 0000 0000060 158b 0004 0000 00a1 0000 8d00 0204 4403 0000070 1c24 4403 1824 0489 e824 fffc ffff 448b 0000080 1c24 c3c9 ??這段代碼是func1和main函數對應的匯編代碼
?
?
?緊接著代碼段是數據段(.data)的內容(8個字節,16進制表示為0x08),即0x00000084地址開始8個字節內容:
?
0054 0000 0055 0000?數據段是全局和靜態變量初始化數據的存放地。其中global_init_var(int類型,四個字節) 值為84(16進制表示為0x00000054),對應0054 0000.而static_var(static int類型,四個字節)值為85(16進制表示為0x00000055),對應0055 0000.
?
?
緊接著數據段的是.bss和.rodata(只讀數據段,用于存放常數串等,此處與.bss段重疊)段,大小為4個字節對應0x0000008c地址開始四個字節內容:
6425 000a?恰好是字符串"%d\n"的二進制表示(0x64對應字符'd',0x25對應字符'%',0x0a對應字符LF(換行符號,unix/linux下'\n'用與LF相對應,而在為window則需要用CR(回車)和LF兩個符號來對應與'\n'),0x00對應字符'\0'來作為串的終止符號)。
緊接著.rodata段的是.comment段,它用來存放編譯器版本信息等,此處對應0x00000090地址開始的40個字節(16進制下為0x28):
0000090 4700 4343 203a 4728 554e 2029 2e34 2e35 00000a0 2032 3032 3131 3130 3732 2820 7270 7265 00000b0 6c65 6165 6573 0029?實際對應于一個字符串"\0GCC: (GNU) 4.5.2 20110127 (prerelease)\0"(感興趣的話,可以自己將上面的16進制ascii值轉換成字符試試,剛好與使用gcc -v命令的得到的結果一致(這個結果是使用od -c SimpleSection.o和上面使用-x選項的結果對比得到的,該命令在我的電腦上輸出結果中最后一行為:gcc 版本 4.5.2 20110127 (prerelease) (GCC) )
?
?
緊接著.note.GNU-Stack段(該段大小為0,所以沒有對應的實質性內容)
?緊接著從0x000000b8地址開始81(0x51)個字節是.shstrtab段,用來存放段的名稱。對應內容為:
2e00 7973 746d 6261 00000c0 2e00 7473 7472 6261 2e00 6873 7473 7472 00000d0 6261 2e00 6572 2e6c 6574 7478 2e00 6164 00000e0 6174 2e00 7362 0073 722e 646f 7461 0061 00000f0 632e 6d6f 656d 746e 2e00 6f6e 6574 472e 0000100 554e 732d 6174 6b63 00?其對應字符串為"\0.symtab\0.strtab\0.shstrtab\0.rel.text\0.data\0.bss\0.rodata\0.comment\0.note.GNU-Stack\0"(雙引號是我手動添加的,為了看起來更好看,這個字符串是我用od -c SimpleSection.o得到的結果與前面使用-x選項得到結果對比得到的。)
?
?
緊接著.shstrtab段的是段表(Section table),從前面對ELF頭的解析可以知道段表的地址是從0x0000010c(e_shoff項)開始,而上面的.shstrtab的開始地址為0xb8,大小為0x51,所以此處段表應該是0x109開始,但是從前面使用readelf得出結果可以知道,段表對齊要求是4個字節對齊,所以地址最后兩位必須是00,這就導致是從0x10c開始,留下了三個字節的空洞(英文為hole)。再根據e_shnum=11和ehentsize=40可知,有11個段,每個段占40個字節大小,總共占據440個字節(16進制為0x1b8)。段入口的類型定義如下(/usr/include/elf.h):
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;?恰好占據了40個字節。
從0x0000010c開始有11個段,每個段占40個字節大小。
第一個段為0x0000010c-0x00000133,其中內容全部為0,所以不表示任何段。
第二個段為0x00000134-0x0000015b,對應內容為:
001f 0000 0001 0000 0006 0000 0000140 0000 0000 0034 0000 0050 0000 0000 0000 0000150 0000 0000 0004 0000 0000 0000?段中每個成員均為4個字節,所以分析起來相對簡單一些。
?sh_name值為0x0000001f,它表示該段名稱在.shstrtab中偏移量,通過計算可知該名稱為.text。sh_type值為0x00000001(對應SHT_PROGBITS),表示這個段擁有程序所定義的信息,其格式和含義完全有該程序確定。sh_flags值為0x00000006(對應于SHF_ALLOC和SHF_EXECINSTR)。sh_addr值為0x00000000,表示這個段不會出現在進程的地址鏡像中。
sh_offset值為0x00000034(偏移地址),sh_size值為0x00000050,表示代碼段大小為80(0x50)個字節。sh_link值為0x00000000,表示沒有鏈接信息。sh_info值為0x00000000,表示沒有輔助信息。sh_addalign值為0x00000004,表示4個字節對齊,sh_entsize值為0x00000000,表示沒有入口。
第三個段為0x0000015c-0x00000184,對應內容為:
001b 0000 0000160 0009 0000 0000 0000 0000 0000 041c 0000 0000170 0028 0000 0009 0000 0001 0000 0004 0000 0000180 0008 0000?該段為.rel.text段(偏移量為0x0000001b),sh_type為0x00000009(對應SHT_REL),sh_offset為0x0000041c,sh_size為0x00000028(40個字節)。sh_link和sh_info分別為9和1,分別表示相關符號表索引和重定位應用段的段頭索引。其余段的內容不再詳細分析,可以自己分析并于前面readelf輸出結果相對照。
第四個段為0x00000184-0x000001ac(0x25,即.shstrtab第37個字節偏移處,即.data段),對應內容:
0025 0000 0001 0000 0003 0000 0000190 0000 0000 0084 0000 0008 0000 0000 0000 00001a0 0000 0000 0004 0000 0000 0000第五個段為0x000001ac-0x00001d4(0x2b,即第43個字節偏移處,即.bss段),對應內容為:
002b 0000 00001b0 0008 0000 0003 0000 0000 0000 008c 0000 00001c0 0004 0000 0000 0000 0000 0000 0004 0000 00001d0 0000 0000 ?第六個段為0x000001d4-0x000001fc(0x30,即第48個字節偏移處,即.rodata段),對應內容為:
0030 0000 0001 0000 0002 0000 00001e0 0000 0000 008c 0000 0004 0000 0000 0000 00001f0 0000 0000 0001 0000 0000 0000 ?第七個段為0x000001fc-0x00000224(0x38,即第56個字節偏移處,即.comment段),對應內容為:
0038 0000 0000200 0001 0000 0030 0000 0000 0000 0090 0000 0000210 0028 0000 0000 0000 0000 0000 0001 0000 0000220 0001 0000第八個段為0x00000224-0x0000024c(0x41,即第65個字節偏移處,即.note.GNU-Stack段),對應內容為:
? ? ? ? ? ? ? ? ? ? ? ? ? 0041 0000 0001 0000 0000 0000 0000230 0000 0000 00b8 0000 0000 0000 0000 0000 0000240 0000 0000 0001 0000 0000 0000第九個段為0x0000024c-0x00000274(0x11,即第17個字節偏移處,即.shstrtab段),對應內容為:
0011 0000 0000250 0003 0000 0000 0000 0000 0000 00b8 0000 0000260 0051 0000 0000 0000 0000 0000 0001 0000 0000270 0000 0000 ?第十個段為0x00000274-0x0000029c(0x01,即第1個字節偏移處,即.symtab段),對應內容為:
0001 0000 0002 0000 0000 0000 0000280 0000 0000 02c4 0000 00f0 0000 000a 0000 0000290 000a 0000 0004 0000 0010 0000 ?第十一個段為0x0000029c-0x000002c4(0x9,即第9個字節偏移處,即.strtab段),對應內容為:
0009 0000 00002a0 0003 0000 0000 0000 0000 0000 03b4 0000 00002b0 0065 0000 0000 0000 0000 0000 0001 0000 00002c0 0000 0000所以段表中表示的段從偏移地址1開始(1-10,因為第一個段全為空)依次為.text, .rel.text, .data, .bss, .rodata, .comment, .note.GNU-Stack, .shstrtab, .symtab, .strtab)
?
?從0x000002c4開始為.symtab(符號表)段,大小為0xf0(240個字節),對應內容為:
?
0000 0000 0000 0000 0000 0000 00002d0 0000 0000 0001 0000 0000 0000 0000 0000 00002e0 0004 fff1 0000 0000 0000 0000 0000 0000 00002f0 0003 0001 0000 0000 0000 0000 0000 0000 0000300 0003 0003 0000 0000 0000 0000 0000 0000 0000310 0003 0004 0000 0000 0000 0000 0000 0000 0000320 0003 0005 0011 0000 0004 0000 0004 0000 0000330 0001 0003 0021 0000 0000 0000 0004 0000 0000340 0001 0004 0000 0000 0000 0000 0000 0000 0000350 0003 0007 0000 0000 0000 0000 0000 0000 0000360 0003 0006 0032 0000 0000 0000 0004 0000 0000370 0011 0003 0042 0000 0004 0000 0004 0000 0000380 0011 fff2 0053 0000 0000 0000 001b 0000 0000390 0012 0001 0059 0000 0000 0000 0000 0000 00003a0 0010 0000 0060 0000 001b 0000 0035 0000 00003b0 0012 0001?符號表結構定義(/usr/include/elf.h):
?
typedef struct {Elf32_Word st_name; /* Symbol name (string tbl index) */Elf32_Addr st_value; /* Symbol value */Elf32_Word st_size; /* Symbol size */unsigned char st_info; /* Symbol type and binding */unsigned char st_other; /* Symbol visibility */Elf32_Section st_shndx; /* Section index */ } Elf32_Sym;? 該結構大小為16個字節,而整個符號表段大小為240個字節,所以總共可以分成15個符號(有些符號未使用)。這15個符號剛好和前面objdump輸出的符號表(14個符號,第一個符號為空,所以被忽略)結果相對應:
?
SYMBOL TABLE: 00000000 l df *ABS* 00000000 SimpleSection.c 00000000 l d .text 00000000 .text 00000000 l d .data 00000000 .data 00000000 l d .bss 00000000 .bss 00000000 l d .rodata 00000000 .rodata 00000004 l O .data 00000004 static_var.1222 00000000 l O .bss 00000004 static_var2.1223 00000000 l d .note.GNU-stack 00000000 .note.GNU-stack 00000000 l d .comment 00000000 .comment 00000000 g O .data 00000004 global_init_var 00000004 O *COM* 00000004 global_uinit_var 00000000 g F .text 0000001b func1 00000000 *UND* 00000000 printf 0000001b g F .text 00000035 main? 也和使用readelf中符號表相關內容對應(readelf命令輸出結果比objdump輸出結果看起來更好看一些):
?
Symbol table '.symtab' contains 15 entries:Num: Value Size Type Bind Vis Ndx Name0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 FILE LOCAL DEFAULT ABS SimpleSection.c2: 00000000 0 SECTION LOCAL DEFAULT 1 3: 00000000 0 SECTION LOCAL DEFAULT 3 4: 00000000 0 SECTION LOCAL DEFAULT 4 5: 00000000 0 SECTION LOCAL DEFAULT 5 6: 00000004 4 OBJECT LOCAL DEFAULT 3 static_var.12227: 00000000 4 OBJECT LOCAL DEFAULT 4 static_var2.12238: 00000000 0 SECTION LOCAL DEFAULT 7 9: 00000000 0 SECTION LOCAL DEFAULT 6 10: 00000000 4 OBJECT GLOBAL DEFAULT 3 global_init_var11: 00000004 4 OBJECT GLOBAL DEFAULT COM global_uinit_var12: 00000000 27 FUNC GLOBAL DEFAULT 1 func113: 00000000 0 NOTYPE GLOBAL DEFAULT UND printf14: 0000001b 53 FUNC GLOBAL DEFAULT 1 main ?15個符號中分析比較重要的符號,其他部分可以自己分析。
首先是0x000002d4-0x000002e3,對應內容:
?
0001 0000 0000 0000 0000 0000 00002e0 0004 fff1?該部分內容中st_name值為0x00000001,表示在常數串表中偏移量為1,即SimpleSection.c的首地址。st_info值為0x04,表示是文件名和局部符號。st_shndx值為0xfff1(即SHN_ABS),寶石該付好包含了一個絕對值。其他成員值為0.
接下來64個字節(地址0x000002e4-0x00000323,每個符號占16個字節,4個符號)對應內容為:
?
0000 0000 0000 0000 0000 0000 00002f0 0003 0001 0000 0000 0000 0000 0000 0000 0000300 0003 0003 0000 0000 0000 0000 0000 0000 0000310 0003 0004 0000 0000 0000 0000 0000 0000 0000320 0003 0005? 這四個符號除了值有st_shndx成員的值不同(分別為1,3,4,5),其他成員均相同。而其他成員值有st_info的值有實際含義,均為0x03,表示該符號是一個段,所以1,3,4,5分別對應段的偏移,分別表示.text,.data,.bss,.rodata(可以查看readelf -a輸出結果,其中段表部分第一列即為偏移量)。
接下來比較重要的符號是0x00000324-0x00000333,對應內容:
?
0011 0000 0004 0000 0004 0000 0000330 0001 0003?這部分內容st_name值為0x00000011,表示在常數串表中偏移量為0x00000011(17),即static_var.1222的首地址。st_size的值均為0x00000004(int類型),分別表示符號值和符號大小。st_info值為0x01,表示是一個局部變量。st_value值為0x00000004,st_shndx值為0x0003,表示偏移為4的段(即.data段)中偏移地址為3的位置。
接下來的重要符號地址為0x00000334-0x00000343,對應內容:
?
0021 0000 0000 0000 0004 0000 0000340 0001 0004?該部分內容st_name值為0x00000021,表示常數串中偏移量為0x00000021(33),即static_var2.1223的首地址。st_size值為0x00000004(int類型)。st_info值為0x01,表示是一個局部變量.st_shndx值為0x0003,st_value值為0x00000000,表示偏移為0的段中偏移地址為4的未知。
另一個重要符號地址為0x00000364-0x00000373,對應內容:
?
0032 0000 0000 0000 0004 0000 0000370 0011 0003?該部分st_name值為0x00000032,表示常數串中偏移量為0x00000032(50),即global_init_var的首地址。st_info值為0x11,表示全局變量。其他成員含義與上一個符號類似。
還有一個重要符號地址為0x00000374-0x00000383,對應內容:
?
0042 0000 0004 0000 0004 0000 0000380 0011 fff2?該部分st_name值為0x00000042,表示常數串中偏移量為0x0000042(66),即global_uint_var的首地址。st_shndx值為0xfff2,表示該符號是common類型符號。st_value值為0x00000004,表示是4個字節對齊。st_size值為0x00000004,表示大小為4(int類型)。
還有重要符號地址為0x00000384-0x00000393,對應內容:
?
0053 0000 0000 0000 001b 0000 0000390 0012 0001?該部分st_name值為0x00000053,表示對應func1首地址。st_info值為0x12,表示是全局函數。st_value值為0x0000000,st_size值為0x0000001b,故表示偏移為0(.text)的段中28個字節。
?
?
從 0x000003b4開始為.strtab(字符串表)段,大小為0x65(101個字節),對應內容為:
?
? ? ? ? ? ? ? ? ? ? ? ? ?? 5300 6d69 6c70 5365 6365 6974 00003c0 6e6f 632e 7300 6174 6974 5f63 6176 2e72 00003d0 3231 3232 7300 6174 6974 5f63 6176 3272 00003e0 312e 3232 0033 6c67 626f 6c61 695f 696e 00003f0 5f74 6176 0072 6c67 626f 6c61 755f 6e69 0000400 7469 765f 7261 6600 6e75 3163 7000 6972 0000410 746e 0066 616d 6e69 00?對應字符串為"\0SimpleSection.c\0static_var.1222\0static_var2.1223\0global_init_var\0global_uint_var\0func1\0printf\0main\0"(使用od -c SimpleSection.o與-x選項得到結果對比即可)
?
?
從0x0000041c開始為.rel.text段(重定位表),大小為0x28(40個字節),對應內容為:
0010 0000 0000420 0501 0000 0015 0000 0d02 0000 002e 0000 0000430 0301 0000 0033 0000 0401 0000 0046 0000 0000440 0c02 0000重定位表數據結構(/usr/include/elf.h):
typedef struct {Elf32_Addr r_offset; /* Address */Elf32_Word r_info; /* Relocation type and symbol index */ } Elf32_Rel;?該數據結構大小為8個字節,所以40個字節應該包含5個這樣的結構。
?而需要重定位的符號可以使用下面的命令列出:
objdump -r SimpleSection.o?輸出結果:
?
SimpleSection.o: file format elf32-i386RELOCATION RECORDS FOR [.text]: OFFSET TYPE VALUE 00000010 R_386_32 .rodata 00000015 R_386_PC32 printf 0000002e R_386_32 .data 00000033 R_386_32 .bss 00000046 R_386_PC32 func1?剛好是5個符號。
?
根據上面內容,SimpleSection.o的結構形式如下:
????????? ------------------------------------- 0x00000000
????????? |?? ELF Header(e_shoff=0x10c)?????
????????? ------------------------------------- 0x00000034
0x50? | .text?????????????
????????? ------------------------------------- 0x00000084
0x08? | .data
????????? ------------------------------------- 0x0000008c
0x04? | .rodata
????????? ------------------------------------- 0x00000090
0x28? | .comment
????????? ------------------------------------- 0x000000b8
0x51? | .shsttab
????????? ------------------------------------- 0x00000109
?????????? --------------------------------------0x0000010c
0x1b8 | Section Table
?????????? -------------------------------------- 0x000002c4
0xf0?? |? .symtab
????????? -------------------------------------- 0x000003b4
0x65? | ? .strtab
????????? -------------------------------------- 0x0000041c
0x28? |?? .rel.text
????????? --------------------------------------- 0x00000444
?
根據上面的實例觀察可知,分析一個elf文件的內部構造,可以先找到elf頭,并找到其中幾個關鍵成員e_shoff(段表偏移地址), e_shentnum(段的個數), e_shsize(每個段的大小)。這樣可以到段表中再分析每個段相關信息。另外,這個例子中elf文件是重定位文件,它缺乏elf中的一個重要段(程序文件頭表),這個表只存在于可執行文件或動態鏈接庫文件中。它可以通過elf頭的e_phoff(文件頭表偏移地址),e_phnum(文件頭表個數)和e_phsize(每個表大小)來確定該文件頭表的位置及大小。分析方式和elf頭類似。文件頭表類型定義(/usr/include/elf.h):
?
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;/* Special value for e_phnum. This indicates that the real number ofprogram headers is too large to fit into e_phnum. Instead the realvalue is in the field sh_info of section 0. */#define PN_XNUM 0xffff/* Legal values for p_type (segment type). */#define PT_NULL 0 /* Program header table entry unused */ #define PT_LOAD 1 /* Loadable program segment */ #define PT_DYNAMIC 2 /* Dynamic linking information */ #define PT_INTERP 3 /* Program interpreter */ #define PT_NOTE 4 /* Auxiliary information */ #define PT_SHLIB 5 /* Reserved */ #define PT_PHDR 6 /* Entry for header table itself */ #define PT_TLS 7 /* Thread-local storage segment */ #define PT_NUM 8 /* Number of defined types */ #define PT_LOOS 0x60000000 /* Start of OS-specific */ #define PT_GNU_EH_FRAME 0x6474e550 /* GCC .eh_frame_hdr segment */ #define PT_GNU_STACK 0x6474e551 /* Indicates stack executability */ #define PT_GNU_RELRO 0x6474e552 /* Read-only after relocation */ #define PT_LOSUNW 0x6ffffffa #define PT_SUNWBSS 0x6ffffffa /* Sun Specific segment */ #define PT_SUNWSTACK 0x6ffffffb /* Stack segment */ #define PT_HISUNW 0x6fffffff #define PT_HIOS 0x6fffffff /* End of OS-specific */ #define PT_LOPROC 0x70000000 /* Start of processor-specific */ #define PT_HIPROC 0x7fffffff /* End of processor-specific *//* Legal values for p_flags (segment flags). */#define PF_X (1 << 0) /* Segment is executable */ #define PF_W (1 << 1) /* Segment is writable */ #define PF_R (1 << 2) /* Segment is readable */ #define PF_MASKOS 0x0ff00000 /* OS-specific */ #define PF_MASKPROC 0xf0000000 /* Processor-specific */?感興趣的可以自己分析,并和readelf -a得到結果對比來加深理解。
例如我寫的一個小測試程序:
?
#include <stdio.h> #include <elf.h> int main(void) {printf("%d\n",sizeof(Elf32_Sym));printf("Hello, World\n");return 0; }?使用下面命令編譯:
?
gcc -o test test.c?得到一個名為test的可執行文件。
使用下面命令得到test文件文件頭表相關部分的結構:
?
readelf -l test?該命令輸出:
?
Elf file type is EXEC (Executable file) Entry point 0x8048340 There are 8 program headers, starting at offset 52Program Headers:Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg AlignPHDR 0x000034 0x08048034 0x08048034 0x00100 0x00100 R E 0x4INTERP 0x000134 0x08048134 0x08048134 0x00013 0x00013 R 0x1[Requesting program interpreter: /lib/ld-linux.so.2]LOAD 0x000000 0x08048000 0x08048000 0x005b8 0x005b8 R E 0x1000LOAD 0x0005b8 0x080495b8 0x080495b8 0x0010c 0x00114 RW 0x1000DYNAMIC 0x0005cc 0x080495cc 0x080495cc 0x000d0 0x000d0 RW 0x4NOTE 0x000148 0x08048148 0x08048148 0x00020 0x00020 R 0x4GNU_EH_FRAME 0x000514 0x08048514 0x08048514 0x00024 0x00024 R 0x4GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4Section to Segment mapping:Segment Sections...00 01 .interp 02 .interp .note.ABI-tag .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss 04 .dynamic 05 .note.ABI-tag 06 .eh_frame_hdr 07?使用hexdump得到test文件前308個字節(前52個字節為ELF頭內容,后面256個字節(這部分是文件頭表,可以從0000001c地址開始四個字節值0x00000034(e_phoff)知道文件頭表偏移地址是從第52個字節開始,每個表32(e_phentsize,0x0000002a地址開始的2個字節,0x0020)個字節,共8(e_phnum,0x0000002c地址開始的2個字節,對應0x0008)個表)內容:
?
hexdump -x test -n 308?輸出結果:
?
0000000 457f 464c 0101 0001 0000 0000 0000 0000 0000010 0002 0003 0001 0000 8340 0804 0034 0000 0000020 07e8 0000 0000 0000 0034 0020 0008 0028 0000030 001e 001b 0006 0000 0034 0000 8034 0804 0000040 8034 0804 0100 0000 0100 0000 0005 0000 0000050 0004 0000 0003 0000 0134 0000 8134 0804 0000060 8134 0804 0013 0000 0013 0000 0004 0000 0000070 0001 0000 0001 0000 0000 0000 8000 0804 0000080 8000 0804 05b8 0000 05b8 0000 0005 0000 0000090 1000 0000 0001 0000 05b8 0000 95b8 0804 00000a0 95b8 0804 010c 0000 0114 0000 0006 0000 00000b0 1000 0000 0002 0000 05cc 0000 95cc 0804 00000c0 95cc 0804 00d0 0000 00d0 0000 0006 0000 00000d0 0004 0000 0004 0000 0148 0000 8148 0804 00000e0 8148 0804 0020 0000 0020 0000 0004 0000 00000f0 0004 0000 e550 6474 0514 0000 8514 0804 0000100 8514 0804 0024 0000 0024 0000 0004 0000 0000110 0004 0000 e551 6474 0000 0000 0000 0000 0000120 0000 0000 0000 0000 0000 0000 0006 0000 0000130 0004 0000 0000134?文件頭段第一個表(0x00000034-0x00000053)內容:
0006 0000 0034 0000 8034 0804 0000040 8034 0804 0100 0000 0100 0000 0005 0000 0000050 0004 0000?剛好有8個成員,每個成員4個字節。
?p_type值為0x00000006,表示文件頭表入口。p_offset值為0x00000034,表示其偏移地址。p_vaddr值為0x08048034,表示虛擬地址。p_paddr值為0x08048034,表示物理地址。p_filesz值為0x00000100,表示文件中段的大小256個字節。p_memsz值為0x00000100,表示內存中段大小為256個字節。p_flags值為0x00000005,表示該段可讀并且可執行。p_align值為0x00000004表示該段是4字節對齊。這個段表剛好對應文件頭段的256個字節。
其他內容不再詳細分析,感興趣的可以自己探索研究
總結
以上是生活随笔為你收集整理的elf文件格式实例解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: QT5更改应用程序图标
- 下一篇: 华为交换机S3700端口基本配置