1. 程式人生 > >Linux逆向---ELF格式分析之節頭

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 符號字串表節