Linux逆向---ELF格式分析之節頭
1.檢視節頭
段是程式執行的必要組成部分,段可以被分割成若干個節,而節頭表是對這些節的位置和大小的描述,主要是連結和除錯使用的,而對程式的執行卻不是必需的。因為對程式記憶體佈局的描述已經由程式頭表描述了,而節頭表則是對其的補充。即使節頭不存在,節依然存在,只是無法通過節頭去引用。
檢視程式的節頭:
readelf -S hello.out
輸出:
共有 31 個節頭,從偏移量 0x19e0 開始:
節頭:
[號] 名稱 型別 地址 偏移量
大小 全體大小 旗標 連結 資訊 對齊
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400238 00000238
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000400254 00000254
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 0000000000400274 00000274
0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400298 00000298
000000000000001c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000004002b8 000002b8
0000000000000060 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000400318 00000318
000000000000003f 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000400358 00000358
0000000000000008 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000400360 00000360
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000400380 00000380
0000000000000018 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000400398 00000398
0000000000000030 0000000000000018 AI 5 24 8
[11] .init PROGBITS 00000000004003c8 000003c8
000000000000001a 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 00000000004003f0 000003f0
0000000000000030 0000000000000010 AX 0 0 16
[13] .plt.got PROGBITS 0000000000400420 00000420
0000000000000008 0000000000000000 AX 0 0 8
[14] .text PROGBITS 0000000000400430 00000430
0000000000000182 0000000000000000 AX 0 0 16
[15] .fini PROGBITS 00000000004005b4 000005b4
0000000000000009 0000000000000000 AX 0 0 4
[16] .rodata PROGBITS 00000000004005c0 000005c0
0000000000000010 0000000000000000 A 0 0 4
[17] .eh_frame_hdr PROGBITS 00000000004005d0 000005d0
0000000000000034 0000000000000000 A 0 0 4
[18] .eh_frame PROGBITS 0000000000400608 00000608
00000000000000f4 0000000000000000 A 0 0 8
[19] .init_array INIT_ARRAY 0000000000600e10 00000e10
0000000000000008 0000000000000000 WA 0 0 8
[20] .fini_array FINI_ARRAY 0000000000600e18 00000e18
0000000000000008 0000000000000000 WA 0 0 8
[21] .jcr PROGBITS 0000000000600e20 00000e20
0000000000000008 0000000000000000 WA 0 0 8
[22] .dynamic DYNAMIC 0000000000600e28 00000e28
00000000000001d0 0000000000000010 WA 6 0 8
[23] .got PROGBITS 0000000000600ff8 00000ff8
0000000000000008 0000000000000008 WA 0 0 8
[24] .got.plt PROGBITS 0000000000601000 00001000
0000000000000028 0000000000000008 WA 0 0 8
[25] .data PROGBITS 0000000000601028 00001028
0000000000000010 0000000000000000 WA 0 0 8
[26] .bss NOBITS 0000000000601038 00001038
0000000000000008 0000000000000000 WA 0 0 1
[27] .comment PROGBITS 0000000000000000 00001038
0000000000000035 0000000000000001 MS 0 0 1
[28] .shstrtab STRTAB 0000000000000000 000018ce
000000000000010c 0000000000000000 0 0 1
[29] .symtab SYMTAB 0000000000000000 00001070
0000000000000648 0000000000000018 30 47 8
[30] .strtab STRTAB 0000000000000000 000016b8
0000000000000216 0000000000000000 0 0 1
2.分析輸出內容
下面結合輸出來分析十六進位制的程式程式碼。
.interp
例項中這一段的偏移量為0x238~0x254,可以用hexedit工具來檢視這一節的內容:
2F 6C 69 62 36 34 2F 6C ......../lib64/l
00000240 64 2D 6C 69 6E 75 78 2D 78 38 36 2D 36 34 2E 73 d-linux-x86-64.s
00000250 6F 2E 32 00 o.2.
可以看出,這一節儲存的是程式直譯器的位置。
.note.ABI-tag
例項中這一段的偏移量為0x254~0x274,內容中有GNU三個可見字元,應該是指明執行環境的相關資訊。
.note.gnu.build-i
例項中這一段偏移量為0x274~0x298,內容中仍然只有GNU三個可見字元。
.gun.hash
例項中這一段偏移量為0x298~0x2b4,內容中的的仍然只有GNU三個字元,這裡儲存了一個用來查詢符號的散列表。
.dynsym
例項中這一段偏移量為0x2b8~0x318,這裡儲存了從共享庫匯入的動態符號資訊。
.dynstr
例項中這一段偏移量為0x318~0x357,這裡儲存了動態符號字元表,可以檢視一下這一段的內容:
00 6C 69 62 63 2E 73 6F .........libc.so
00000320 2E 36 00 70 72 69 6E 74 66 00 5F 5F 6C 69 62 63 .6.printf.__libc
00000330 5F 73 74 61 72 74 5F 6D 61 69 6E 00 5F 5F 67 6D _start_main.__gm
00000340 6F 6E 5F 73 74 61 72 74 5F 5F 00 47 4C 49 42 43 on_start__.GLIBC
00000350 5F 32 2E 32 2E 35 00 00 00 00 02 00 02 00 00 00 _2.2.5..........
.gnu.version與.gnu.version_r
例項中這一段偏移量為0x358~0x360 和 0x360~0x380,含義不是很懂,不過似乎參考書中也沒有提及,以後用到了再說吧。
.rela.dyn與.real.plt
例項中這一段偏移量為0x380~0x398 和 0x398~0x3c8。這裡儲存了重定位相關的資訊,這些資訊描述瞭如何在連結或者執行時,對ELF目標檔案的某部分內容或者程序映象進行補充或者修改。
.init
例項中這一段偏移量為0x3c8~0x3e2。這一節是可執行的程序初始化程式碼,程序進入主方法之前會執行這一段程式碼。
這一段通過objdump -d hello.out可以檢視到相關的程式碼段:
00000000004003c8 <_init>:
4003c8: 48 83 ec 08 sub $0x8,%rsp
4003cc: 48 8b 05 25 0c 20 00 mov 0x200c25(%rip),%rax # 600ff8 <_DYNAMIC+0x1d0>
4003d3: 48 85 c0 test %rax,%rax
4003d6: 74 05 je 4003dd <_init+0x15>
4003d8: e8 43 00 00 00 callq 400420 <[email protected]+0x10>
4003dd: 48 83 c4 08 add $0x8,%rsp
4003e1: c3 retq
.plt
例項中這一段偏移量為0x3f0~0x420。這一節是過程連結表,包含了動態連結器呼叫從共享庫匯入的函式所必需的相關程式碼。
也可以用objdump來檢視相關程式碼:
00000000004003f0 <[email protected]>:
4003f0: ff 35 12 0c 20 00 pushq 0x200c12(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
4003f6: ff 25 14 0c 20 00 jmpq *0x200c14(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
4003fc: 0f 1f 40 00 nopl 0x0(%rax)
0000000000400400 <[email protected]>:
400400: ff 25 12 0c 20 00 jmpq *0x200c12(%rip) # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
400406: 68 00 00 00 00 pushq $0x0
40040b: e9 e0 ff ff ff jmpq 4003f0 <_init+0x28>
0000000000400410 <[email protected]>:
400410: ff 25 0a 0c 20 00 jmpq *0x200c0a(%rip) # 601020 <_GLOBAL_OFFSET_TABLE_+0x20>
400416: 68 01 00 00 00 pushq $0x1
40041b: e9 d0 ff ff ff jmpq 4003f0 <_init+0x28>
.plt.got
例項中這一段偏移量為0x420~0x428。這一節提供了對共享庫函式的訪問入口,由動態連結器在執行時進行修改。
也可以用objdump來檢視相關程式碼:
0000000000400420 <.plt.got>:
400420: ff 25 d2 0b 20 00 jmpq *0x200bd2(%rip) # 600ff8 <_DYNAMIC+0x1d0>
400426: 66 90 xchg %ax,%ax
.text
儲存了程式程式碼,在例項中偏移量為0x430~0x5b2
可以用objdump來檢視:
0000000000400430 <_start>:
400430: 31 ed xor %ebp,%ebp
400432: 49 89 d1 mov %rdx,%r9
400435: 5e pop %rsi
400436: 48 89 e2 mov %rsp,%rdx
400439: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
40043d: 50 push %rax
40043e: 54 push %rsp
40043f: 49 c7 c0 b0 05 40 00 mov $0x4005b0,%r8
.........
40059b: 5d pop %rbp
40059c: 41 5c pop %r12
40059e: 41 5d pop %r13
4005a0: 41 5e pop %r14
4005a2: 41 5f pop %r15
4005a4: c3 retq
4005a5: 90 nop
4005a6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
4005ad: 00 00 00
00000000004005b0 <__libc_csu_fini>:
4005b0: f3 c3 repz retq
.fini
例項中偏移量為0x5b4~0x5bd,當程式終止時會執行這一段程式碼。
00000000004005b4 <_fini>:
4005b4: 48 83 ec 08 sub $0x8,%rsp
4005b8: 48 83 c4 08 add $0x8,%rsp
4005bc: c3 retq
.rodata
例項中偏移量為0x5c0~0x5d0,儲存了只讀資料,對於這個程式的只讀資料就是"hello world",可以用hexedit來檢視:
000005C0 01 00 02 00 68 65 6C 6C 6F 20 77 6F 72 6C 64 00 ....hello world.
.eh_frame_hdr與.eh_frame
例項中偏移量為0x5d0~0x604和0x608 ~0x6fc。通過對相關資料的查詢,這部分似乎是和異常處理有關,記錄函式呼叫堆疊的,以後要是用到了再瞭解吧。
到這裡程式段的部分也結束了,從這裡到資料段的起點都是用0填充的了。
.init_array與.fini_array
例項中偏移量為0xe10~0xe18與0xe18 ~0xe20。不過並不清楚這兩個節是做什麼用的,但是從名字上看可能與程式.init節和.fini節有某種聯絡。
.jcr
例項中偏移量為0xe20~0xe28。又是一個不明物體emmm。
.dynamic
例項中偏移量為0xe28~0xff0。儲存著動態連結資訊。
.got
例項中偏移量為0xff8~0xfff。儲存著全域性偏移表。
.got.plt
例項中偏移量為0x1000~0x1028。它和plt節一起提供了對匯入的共享庫函式的訪問入口。可以看一下它的內容:
00001000 28 0E 60 00 00 00 00 00 00 00 00 00 00 00 00 00 (.`.............
00001010 00 00 00 00 00 00 00 00 06 04 40 00 00 00 00 00 [email protected]
00001020 16 04 40 00 00 00 00 00
這個字元不可見,但是從數值上有三個有意義的數 0x600e28 0x400406 0x400416,分別表示一個數據段和兩個程式碼:
0000000000400400 <[email protected]lt>:
400400: ff 25 12 0c 20 00 jmpq *0x200c12(%rip) # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
400406: 68 00 00 00 00 pushq $0x0
40040b: e9 e0 ff ff ff jmpq 4003f0 <_init+0x28>
0000000000400410 <[email protected]>:
400410: ff 25 0a 0c 20 00 jmpq *0x200c0a(%rip) # 601020 <_GLOBAL_OFFSET_TABLE_+0x20>
400416: 68 01 00 00 00 pushq $0x1
40041b: e9 d0 ff ff ff jmpq 4003f0 <_init+0x28>
0x600e28則指向的是.dynamic節的內容。
.data
例項中偏移量為0x1028~0x1038。它儲存著初始化的全域性變數等資料。
.bss
例項中偏移量為0x1038~0x1038…,它儲存的是未進行初始化的全域性資料,程式被載入時資料被初始化為0。
到這裡資料段的部分也都結束了,剩下的應該是和節頭自身相關的東西了。
.comment
例項中偏移量為0x1038~0x106d。這一節儲存了版本控制資訊,並且也是可見的:
00001030 00 00 00 00 00 00 00 00 47 43 43 3A 20 28 55 62 ........GCC: (Ub
00001040 75 6E 74 75 20 35 2E 34 2E 30 2D 36 75 62 75 6E untu 5.4.0-6ubun
00001050 74 75 31 7E 31 36 2E 30 34 2E 31 30 29 20 35 2E tu1~16.04.10) 5.
00001060 34 2E 30 20 32 30 31 36 30 36 30 39 00 00 00 00 4.0 20160609....
.symtab
例項中偏移量為0x1070~0x16b8。這節儲存了符號表,與ELF符號和重定位有關。
.symtab
例項中偏移量為0x16b8~0x18ce。這部分儲存了符號字串表,表中的內容會被.symtab的ElfN_Sym結構中的st_name條目引用。
00 63 72 74 73 74 75 66 .........crtstuf
000016C0 66 2E 63 00 5F 5F 4A 43 52 5F 4C 49 53 54 5F 5F f.c.__JCR_LIST__
000016D0 00 64 65 72 65 67 69 73 74 65 72 5F 74 6D 5F 63 .deregister_tm_c
000016E0 6C 6F 6E 65 73 00 5F 5F 64 6F 5F 67 6C 6F 62 61 lones.__do_globa
000016F0 6C 5F 64 74 6F 72 73 5F 61 75 78 00 63 6F 6D 70 l_dtors_aux.comp
......
00001860 64 6C 65 00 5F 49 4F 5F 73 74 64 69 6E 5F 75 73 dle._IO_stdin_us
00001870 65 64 00 5F 5F 6C 69 62 63 5F 63 73 75 5F 69 6E ed.__libc_csu_in
00001880 69 74 00 5F 5F 62 73 73 5F 73 74 61 72 74 00 6D it.__bss_start.m
00001890 61 69 6E 00 5F 4A 76 5F 52 65 67 69 73 74 65 72 ain._Jv_Register
000018A0 43 6C 61 73 73 65 73 00 5F 5F 54 4D 43 5F 45 4E Classes.__TMC_EN
000018B0 44 5F 5F 00 5F 49 54 4D 5F 72 65 67 69 73 74 65 D__._ITM_registe
000018C0 72 54 4D 43 6C 6F 6E 65 54 61 62 6C 65 00 00 2E rTMCloneTable...
.shstrtab
例項中偏移量為0x18ce~0x19da。這裡儲存的是節頭字串表,該字串儲存了每個節的節名。e_shstrndx中儲存了.shstrtab的偏移量,前者在readelf -h hello.out的時候能夠找到。
Number of program headers: 9
節頭大小: 64 (位元組)
節頭數量: 31
字串表索引節頭: 28
剩餘還有地址段為0x19e0(19da向下取整)~0x21a0的部分
為了理解這部分,首先需要知道節頭的結構體,這裡我展示64位下的結構體:
typedef struct {
uint32_t sh_name;
uint32_t sh_type;
uint64_t sh_flags;
Elf64_Addr sh_addr;
Elf64_Off sh_offset;
uint64_t sh_size;
uint32_t sh_link;
uint32_t sh_info;
uint64_t sh_addralign;
uint64_t sh_entsize;
} Elf64_Shdr;
可以進行一個簡單的計算:
(0x21a0-0x19e0)/64(節頭專案大小)=31,也就是說,剩下的部分應該就是節頭的內容。
可以看一下最後64個位元組的內容:
00002160 09 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 ................
00002170 00 00 00 00 00 00 00 00 B8 16 00 00 00 00 00 00 ................
00002180 16 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00002190 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
在這裡出現了熟悉的0x16b8,也就是.strtab的偏移量。這也說明最後的部分就是節頭表的內容。
3.目標檔案與可執行檔案的節頭對比
目標檔案指的是.o檔案,而可執行檔案則指的是.out檔案,通過對他們所擁有的節的對比也可以知道哪些節是一個可執行程式所必須的:
1.獲取目標檔案
gcc -c hello.c
執行上面的編譯命令就可以看到目錄中出現了hello.o。
### 2.目標檔案與可執行檔案節頭對比
可執行檔案的檔案頭如下:
節頭:
[號] 名稱 型別 地址 偏移量
大小 全體大小 旗標 連結 資訊 對齊
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
000000000000001a 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 000001f8
0000000000000030 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 0000005a
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 0000005a
0000000000000000 0000000000000000 WA 0 0 1
[ 5] .rodata PROGBITS 0000000000000000 0000005a
000000000000000c 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 00000066
0000000000000036 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 0000009c
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 000000a0
0000000000000038 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 00000228
0000000000000018 0000000000000018 I 11 8 8
[10] .shstrtab STRTAB 0000000000000000 00000240
0000000000000061 0000000000000000 0 0 1
[11] .symtab SYMTAB 0000000000000000 000000d8
0000000000000108 0000000000000018 12 9 8
[12] .strtab STRTAB 0000000000000000 000001e0
0000000000000015 0000000000000000 0 0 1
通過對比可得到如下結果:
.o檔案特有
- .rela.text
- .rela.eh_frame
- .note.GNU-stack
.out檔案特有
- .interp
- .note.ABI-tag
- .note.gnu.build-i
- .gnu.hash
- .dynsym
- .dynstr
- .gnu.version
- .gnu.version_r
- .rela.dyn
- .rela.plt
- .init
- .plt
- .plt.got
- .fini
- .eh_frame_hdr
- .init_array
- .fini_array
- .jcr
- .dynamic
- .got
- .got.plt
.o和.out檔案共有
-
.text 程式碼節
-
.rodata 只讀資料節
-
.data 初始化的全域性資料節
-
.bss 未初始化的全域性資料節
-
.comment 版本控制資訊
-
.eh_frame
-
.shstrtab 節頭字串表
-
.symtab 符號節
-
.strtab 符號字串表節