參考
https://blog.csdn.net/xuehuafeiwu123/article/details/72963229
https://felixzhang00.github.io/2016/12/24/2016-12-24-ELF%E6%96%87%E4%BB%B6%E8%A3%85%E8%BD%BD%E9%93%BE%E6%8E%A5%E8%BF%87%E7%A8%8B%E5%8F%8Ahook%E5%8E%9F%E7%90%86/
ELF文件組成
ELF頭部(ELF_Header): 每個ELF文件都必須存在一個ELF_Header,這里存放了很多重要的信息用來描述整個文件的組織,如: 版本信息,入口信息,偏移信息等。程序執行也必須依靠其提供的信息。
程序頭部表(Program_Header_Table): 可選的一個表,用于告訴系統如何在內存中創建映像,在圖中也可以看出來,有程序頭部表才有段,有段就必須有程序頭部表。其中存放各個段的基本信息(包括地址指針)。
節區頭部表(Section_Header_Table): 類似與Program_Header_Table,但與其相對應的是節區(Section)。
節區(Section): 將文件分成一個個節區,每個節區都有其對應的功能,如符號表,哈希表等。
段(Segment): 將文件分成一段一段映射到內存中。段中通常包括一個或多個節區。
ELF頭
$ readelf -h main
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x660
Start of program headers: 64 (bytes into file)
Start of section headers: 6616 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 29
Section header string table index: 28
/* ELF Header */
#define EI_NIDENT 16
typedef struct elfhdr {
unsigned char e_ident[EI_NIDENT]; /* ELF Identification */
Elf32_Half e_type; /* object file type */
Elf32_Half e_machine; /* machine */
Elf32_Word e_version; /* object file version */
Elf32_Addr e_entry; /* virtual entry point */
Elf32_Off e_phoff; /* program header table offset */
Elf32_Off e_shoff; /* section header table offset */
Elf32_Word e_flags; /* processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size */
Elf32_Half e_phentsize; /* program header entry size */
Elf32_Half e_phnum; /* number of program header entries */
Elf32_Half e_shentsize; /* section header entry size */
Elf32_Half e_shnum; /* number of section header entries */
Elf32_Half e_shstrndx; /* section header table's "section
header string table" entry offset */
} Elf32_Ehdr;
typedef struct {
unsigned char e_ident[EI_NIDENT]; /* Id bytes */
Elf64_Quarter e_type; /* file type */
Elf64_Quarter e_machine; /* machine type */
Elf64_Half e_version; /* version number */
Elf64_Addr e_entry; /* entry point */
Elf64_Off e_phoff; /* Program hdr offset */
Elf64_Off e_shoff; /* Section hdr offset */
Elf64_Half e_flags; /* Processor flags */
Elf64_Quarter e_ehsize; /* sizeof ehdr */
Elf64_Quarter e_phentsize; /* Program header entry size */
Elf64_Quarter e_phnum; /* Number of program headers */
Elf64_Quarter e_shentsize; /* Section header entry size */
Elf64_Quarter e_shnum; /* Number of section headers */
Elf64_Quarter e_shstrndx; /* String table index */
} Elf64_Ehdr;
e_ident字段含義
ELF Header 中各個字段的說明如表:
程序頭
ep@EP:/mnt/d/code/linux$ readelf -l main
Elf file type is DYN (Shared object file)
Entry point 0x660
There are 9 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
0x00000000000001f8 0x00000000000001f8 R 0x8
INTERP 0x0000000000000238 0x0000000000000238 0x0000000000000238
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x00000000000009e8 0x00000000000009e8 R E 0x200000
LOAD 0x0000000000000d98 0x0000000000200d98 0x0000000000200d98
0x0000000000000278 0x0000000000000280 RW 0x200000
DYNAMIC 0x0000000000000da8 0x0000000000200da8 0x0000000000200da8
0x00000000000001f0 0x00000000000001f0 RW 0x8
NOTE 0x0000000000000254 0x0000000000000254 0x0000000000000254
0x0000000000000044 0x0000000000000044 R 0x4
GNU_EH_FRAME 0x00000000000008a4 0x00000000000008a4 0x00000000000008a4
0x000000000000003c 0x000000000000003c R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x0000000000000d98 0x0000000000200d98 0x0000000000200d98
0x0000000000000268 0x0000000000000268 R 0x1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .dynamic .got .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .dynamic .got
可執行文件或者共享目標文件的程序頭部是一個結構數組,每個結構描述了一個段 或者系統準備程序執行所必需的其它信息。目標文件的“段”包含一個或者多個“節區”, 也就是“段內容(Segment Contents)”。程序頭部僅對于可執行文件和共享目標文件 有意義。 可執行目標文件在 ELF 頭部的 e_phentsize和e_phnum 成員中給出其自身程序頭部 的大小。程序頭部的數據結構:
/* Program Header */
typedef struct {
Elf32_Word p_type; /* segment type */
Elf32_Off p_offset; /* segment offset */
Elf32_Addr p_vaddr; /* virtual address of segment */
Elf32_Addr p_paddr; /* physical address - ignored? */
Elf32_Word p_filesz; /* number of bytes in file for seg. */
Elf32_Word p_memsz; /* number of bytes in mem. for seg. */
Elf32_Word p_flags; /* flags */
Elf32_Word p_align; /* memory alignment */
} Elf32_Phdr;
typedef struct {
Elf64_Half p_type; /* entry type */
Elf64_Half p_flags; /* flags */
Elf64_Off p_offset; /* offset */
Elf64_Addr p_vaddr; /* virtual address */
Elf64_Addr p_paddr; /* physical address */
Elf64_Xword p_filesz; /* file size */
Elf64_Xword p_memsz; /* memory size */
Elf64_Xword p_align; /* memory & file alignment */
} Elf64_Phdr;
其中各個字段說明:
- p_offset 此成員給出從文件頭到該段第一個字節的偏移。
- p_vaddr 此成員給出段的第一個字節將被放到內存中的虛擬地址。
- p_paddr 此成員僅用于與物理地址相關的系統中。因為 System V 忽略所有應用程序的物理地址信息,此字段對與可執行文件和共享目標文件而言具體內容是指定的。
- p_filesz 此成員給出段在文件映像中所占的字節數。可以為 0。
- p_memsz 此成員給出段在內存映像中占用的字節數。可以為 0。
- p_flags 此成員給出與段相關的標志。
- p_align 可加載的進程段的 p_vaddr 和 p_offset 取值必須合適,相對于對頁面大小的取模而言。此成員給出段在文件中和內存中如何 對齊。數值 0 和 1 表示不需要對齊。否則 p_align 應該是個正整數,并且是 2 的冪次數,p_vaddr 和 p_offset 對 p_align 取模后應該相等。
-
p_type 此數組元素描述的段的類型,或者如何解釋此數組元素的信息。具體如下圖。
PT_LOAD
一個可執行文件至少有一個PT_LOAD 類型的段。這類程序頭描述的是可裝載的段,也就是說,這種類型的段將被裝載或者映射到內存中。例如,一個需要動態鏈接的ELF 可執行文件通常包含以下兩個可裝載的段(類型為PT_LOAD):
- 存放程序代碼的text 段;
- 存放全局變量和動態鏈接信息的data 段。
PT_DYNAMIC——動態段的Phdr
動態段是動態鏈接可執行文件所特有的,包含了動態鏈接器所必需的一些信息。在動態段中包含了一些標記值和指針,包括但不限于以下內容:
- 運行時需要鏈接的共享庫列表;
- 全局偏移表(GOT)的地址
- 重定位條目的相關信息。
PT_INTERP
PT_INTERP 段只將位置和大小信息存放在一個以null 為終止符的字符串中,是對程序解釋器位置的描述。例如,/lib/linux-ld.so.2 一般是指動態鏈接器的位置,也即程序解釋器的位置。
PT_PHDR
PT_PHDR 段保存了程序頭表本身的位置和大小。Phdr 表保存了所有的Phdr 對文件(以及內存鏡像)中段的描述信息。
ELF節頭
ep@EP:/mnt/d/code/linux$ readelf -S main
There are 29 section headers, starting at offset 0x19d8:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000000238 00000238
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000000254 00000254
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 0000000000000274 00000274
0000000000000024 0000000000000000 A 0 0 4
......
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
節頭的數據結構:
/* Section Header */
typedef struct {
Elf32_Word sh_name; /* name - index into section header
string table section */
Elf32_Word sh_type; /* type */
Elf32_Word sh_flags; /* flags */
Elf32_Addr sh_addr; /* address */
Elf32_Off sh_offset; /* file offset */
Elf32_Word sh_size; /* section size */
Elf32_Word sh_link; /* section header table index link */
Elf32_Word sh_info; /* extra information */
Elf32_Word sh_addralign; /* address alignment */
Elf32_Word sh_entsize; /* section entry size */
} Elf32_Shdr;
typedef struct {
Elf64_Half sh_name; /* section name */
Elf64_Half sh_type; /* section type */
Elf64_Xword sh_flags; /* section flags */
Elf64_Addr sh_addr; /* virtual address */
Elf64_Off sh_offset; /* file offset */
Elf64_Xword sh_size; /* section size */
Elf64_Half sh_link; /* link to another */
Elf64_Half sh_info; /* misc info */
Elf64_Xword sh_addralign; /* memory alignment */
Elf64_Xword sh_entsize; /* table entry size */
} Elf64_Shdr;
各個字段的解釋如下:
sh_type 字段 節區類型定義:
常用節區:
節與段
節,不是段。段是程序執行的必要組成部分,在每個段中,會有代碼或者數據被劃分為不同的節。節頭表是對這些節的位置和大小的描述,主要用于鏈接和調試。節頭對于程序的執行來說不是必需的,沒有節頭表,程序仍可以正常執行,因為節頭表沒有對程序的內存布局進行描述,對程序內存布局的描述是程序頭表的任務。節頭是對程序頭的補充。readelf –l 命令可以顯示一個段對應有哪些節,可以很直觀地看到節和段之間的關系。
如果二進制文件中缺少節頭,并不意味著節就不存在。只是沒有辦法通過
節頭來引用節,對于調試器或者反編譯程序來說,只是可以參考的信息變少了而已。
字符串表(String Table)
字符串表節區包含以 NULL(ASCII 碼 0)結尾的字符序列,通常稱為字符串。ELF 目標文件通常使用字符串來表示符號和節區名稱。對字符串的引用通常以字符串在字符串表中的下標給出。
比如下面這樣:
那么偏移與他們對用的字符串如下表:
!](https://upload-images.jianshu.io/upload_images/11884068-f4e855e0a0f35dd7.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
這樣在ELF中引用字符串只需要給出一個數組下標即可。字符串表在ELF也以段的形式保存,常見的段名為”.strtab”或”.shstrtab”。這兩個字符串表分別為字符串表(String Table)和段表字符串表(Header String Table),字符串表保存的是普通的字符串,而段表字符串表用來保存段表中用到的字符串,比如段名。
在使用、分析字符串表時,要注意以下幾點:
- 字符串表索引可以引用節區中任意字節。
- 字符串可以出現多次
- 可以存在對子字符串的引用
- 同一個字符串可以被引用多次。
- 字符串表中也可以存在未引用的字符串。
符號表(Symbol Table)
目標文件的符號表中包含用來定位、重定位程序中符號定義和引用的信息。符號表 索引是對此數組的索引。索引 0 表示表中的第一表項,同時也作為 定義符號的索引。
/* Symbol Table Entry */
typedef struct elf32_sym {
Elf32_Word st_name; /* name - index into string table */
Elf32_Addr st_value; /* symbol value */
Elf32_Word st_size; /* symbol size */
unsigned char st_info; /* type and binding */
unsigned char st_other; /* 0 - no defined meaning */
Elf32_Half st_shndx; /* section header index */
} Elf32_Sym;
typedef struct {
Elf64_Half st_name; /* Symbol name index in str table */
Elf_Byte st_info; /* type / binding attrs */
Elf_Byte st_other; /* unused */
Elf64_Quarter st_shndx; /* section index of symbol */
Elf64_Xword st_value; /* value of symbol */
Elf64_Xword st_size; /* size of symbol */
} Elf64_Sym;
各個字段的含義如下:
符號是對某些類型的數據或者代碼(如全局變量或函數)的符號引用。例如,printf()函數會在動態符號表
.dynsym
中存有一個指向該函數的符號條目。在大多數共享庫和動態鏈接可執行文件中,存在兩個符號表。如前面使用readelf –S
命令輸出的內容中,可以看到有兩個節:.dynsym
和.symtab
。.dynsym
保存了引用來自外部文件符號的全局符號,如printf 這樣的庫函數,.dynsym
保存的符號是.symtab
所保存符號的子集,.symtab
中還保存了可執行文件的本地符號,如全局變量,或者代碼中定義的本地函數等。因此,.symtab
保存了所有的符號,而.dynsym
只保存動態/全局符號。因此,就存在這樣一個問題:既然
.symtab
中保存了.dynsym
中所有的符號,那么為什么還需要兩個符號表呢?使用readelf –S
命令查看可執行文件的輸出,可以看到一部分節被標記為了A(ALLOC)、WA(WRITE/ALLOC)或者AX(ALLOC/EXEC)。.dynsym
是被標記了ALLOC 的,而.symtab
則沒有標記。ALLOC 表示有該標記的節會在運行時分配并裝載進入內存,而
.symtab
不是在運行時必需的,因此不會被裝載到內存中。.dynsym
保存的符號只能在運行時被解析,因此是運行時動態鏈接器所需要的唯一符號。.dynsym
符號表對于動態鏈接可執行文件的執行來說是必需的,而.symtab
符號表只是用來進行調試和鏈接的,有時候為了節省空間,會將.symtab
符號表從生產二進制文件中刪掉。
ep@EP:/mnt/d/code/linux$ readelf -s main
Symbol table '.dynsym' contains 11 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND read@GLIBC_2.2.5 (2)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
6: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND malloc@GLIBC_2.2.5 (2)
8: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sleep@GLIBC_2.2.5 (2)
10: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2)
Symbol table '.symtab' contains 67 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000238 0 SECTION LOCAL DEFAULT 1
2: 0000000000000254 0 SECTION LOCAL DEFAULT 2
3: 0000000000000274 0 SECTION LOCAL DEFAULT 3
4: 0000000000000298 0 SECTION LOCAL DEFAULT 4
5: 00000000000002b8 0 SECTION LOCAL DEFAULT 5
6: 00000000000003c0 0 SECTION LOCAL DEFAULT 6
7: 000000000000045c 0 SECTION LOCAL DEFAULT 7
8: 0000000000000478 0 SECTION LOCAL DEFAULT 8
9: 0000000000000498 0 SECTION LOCAL DEFAULT 9
10: 0000000000000558 0 SECTION LOCAL DEFAULT 10
11: 00000000000005d0 0 SECTION LOCAL DEFAULT 11
12: 00000000000005f0 0 SECTION LOCAL DEFAULT 12
13: 0000000000000650 0 SECTION LOCAL DEFAULT 13
14: 0000000000000660 0 SECTION LOCAL DEFAULT 14
15: 0000000000000864 0 SECTION LOCAL DEFAULT 15
16: 0000000000000870 0 SECTION LOCAL DEFAULT 16
17: 00000000000008a4 0 SECTION LOCAL DEFAULT 17
18: 00000000000008e0 0 SECTION LOCAL DEFAULT 18
19: 0000000000200d98 0 SECTION LOCAL DEFAULT 19
20: 0000000000200da0 0 SECTION LOCAL DEFAULT 20
21: 0000000000200da8 0 SECTION LOCAL DEFAULT 21
22: 0000000000200f98 0 SECTION LOCAL DEFAULT 22
23: 0000000000201000 0 SECTION LOCAL DEFAULT 23
24: 0000000000201010 0 SECTION LOCAL DEFAULT 24
25: 0000000000000000 0 SECTION LOCAL DEFAULT 25
26: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
27: 0000000000000690 0 FUNC LOCAL DEFAULT 14 deregister_tm_clones
28: 00000000000006d0 0 FUNC LOCAL DEFAULT 14 register_tm_clones
29: 0000000000000720 0 FUNC LOCAL DEFAULT 14 __do_global_dtors_aux
30: 0000000000201010 1 OBJECT LOCAL DEFAULT 24 completed.7698
31: 0000000000200da0 0 OBJECT LOCAL DEFAULT 20 __do_global_dtors_aux_fin
32: 0000000000000760 0 FUNC LOCAL DEFAULT 14 frame_dummy
33: 0000000000200d98 0 OBJECT LOCAL DEFAULT 19 __frame_dummy_init_array_
34: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c
35: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
36: 00000000000009e4 0 OBJECT LOCAL DEFAULT 18 __FRAME_END__
37: 0000000000000000 0 FILE LOCAL DEFAULT ABS
38: 0000000000200da0 0 NOTYPE LOCAL DEFAULT 19 __init_array_end
39: 0000000000200da8 0 OBJECT LOCAL DEFAULT 21 _DYNAMIC
40: 0000000000200d98 0 NOTYPE LOCAL DEFAULT 19 __init_array_start
41: 00000000000008a4 0 NOTYPE LOCAL DEFAULT 17 __GNU_EH_FRAME_HDR
42: 0000000000200f98 0 OBJECT LOCAL DEFAULT 22 _GLOBAL_OFFSET_TABLE_
43: 0000000000000860 2 FUNC GLOBAL DEFAULT 14 __libc_csu_fini
44: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
45: 0000000000201000 0 NOTYPE WEAK DEFAULT 23 data_start
46: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@@GLIBC_2.2.5
47: 0000000000201010 0 NOTYPE GLOBAL DEFAULT 23 _edata
48: 0000000000000864 0 FUNC GLOBAL DEFAULT 15 _fini
49: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@@GLIBC_2.2.5
50: 0000000000000000 0 FUNC GLOBAL DEFAULT UND read@@GLIBC_2.2.5
51: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_
52: 0000000000201000 0 NOTYPE GLOBAL DEFAULT 23 __data_start
53: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
54: 0000000000201008 0 OBJECT GLOBAL HIDDEN 23 __dso_handle
55: 0000000000000870 4 OBJECT GLOBAL DEFAULT 16 _IO_stdin_used
56: 00000000000007f0 101 FUNC GLOBAL DEFAULT 14 __libc_csu_init
57: 0000000000000000 0 FUNC GLOBAL DEFAULT UND malloc@@GLIBC_2.2.5
58: 0000000000201018 0 NOTYPE GLOBAL DEFAULT 24 _end
59: 0000000000000660 43 FUNC GLOBAL DEFAULT 14 _start
60: 0000000000201010 0 NOTYPE GLOBAL DEFAULT 24 __bss_start
61: 000000000000076a 126 FUNC GLOBAL DEFAULT 14 main
62: 0000000000201010 0 OBJECT GLOBAL HIDDEN 23 __TMC_END__
63: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
64: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sleep@@GLIBC_2.2.5
65: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@@GLIBC_2.2
66: 00000000000005d0 0 FUNC GLOBAL DEFAULT 11 _init