ELF
0x1.簡介
首先,有一種文件格式叫做對象文件(Object File),ELF(Executable Linkable Format)是Linux下對象文件一種存儲格式,同時,遵從這種格式的文件也被稱爲ELF。順便提一句,在windows下文件格式爲PE,兩者統一等級。
ELF包括二進制文件、可執行文件、目標代碼、共享庫和核心轉儲格式文件,Linux下的目標文件和可執行文件都按照該格式進行存儲
ELF分爲3種類型:
可重定位文件(Relocatable file):
編譯器生成的.o文件,由鏈接器處理
包含代碼與數據,可以和其他目標文件鏈接生成一個可執行文件或共享目標文件(不同命令編譯生成靜態庫或動態庫)
可執行文件(Executable file):
鏈接器對.o文件進行處理輸出的文件,進程映像
在Linux中,可執行文件分爲兩種:
- Executable Object File,如編輯器vim,調試神器gdb等
- Executable shell,也就是shell腳本。但有一點需要區分:shell腳本本身只是一個文本文件,解釋執行shell腳本的程序(如ubuntu的bash shell)纔是可執行文件
共享目標文件(Shared Object File):
即動態庫文件,後綴名爲.so
相對於靜態庫而言,擁有更強的靈活性與可更新性。將模塊化開發嵌入底層代碼,使資源共享更爲方便。但也擁有着對資源文件的強依賴性。
注意:
如今新版gcc爲了安全,默認加入命令參數–enable-default-pie,引入了pie保護。因此直接使用gcc -o編譯出的文件使用file命令查看時文件類型爲Shared Object File,該參數使程序初始運行時在內存中的位置隨機化,以防止return2libc和ROP等棧溢出攻擊。如果想生成可執行文件,可選擇手動關閉引入pie,只需在gcc命令中增加命令參數-no-pie即可
0x2.文件結構
ELF文件使用2種視圖來展示文件結構:鏈接視圖 和 執行視圖
鏈接視圖:靜態鏈接器(即編譯後參與生成最終ELF過程的鏈接器,如ld )會以鏈接視圖解析 ELF。編譯時生成的 .o(目標文件)以及鏈接後的 .so (共享庫)均可通過鏈接視圖解析
執行視圖:動態鏈接器會以執行視圖解析ELF並動態鏈接
區別:鏈接視圖的程序頭表(Program Header Table)是可選的(optional),而執行視圖的程序頭表必須存在
可視化結構圖如下:
本次介紹所使用ELF文件示範用例:main.c test.c,代碼如下:
main.c
/*main.c*/
#include <stdio.h>
int add(int, int);
int main(){
printf("1");
return add(20, 13);
}
test.c
/*test.c*/
int add(int i, int j){
int x = i + j;
return x;
}
使用gcc -s main.c test.c
生成彙編文件main.s和test.s
使用gcc -c main.s test.s
生成可重定位文件main.o和test.o
使用gcc main.o test.o - o qwer.out
生成可執行文件qwer.out(gcc默認名稱a.out,使用-o改變輸出文件名)
使用readelf
命令查看elf文件
ELF文件主要分4部分:
- ELF 文件頭(ELF Header) ---- 在對象文件格式最前面,包含描述整個文件的基本屬性
表明這是一個ELF文件,定義如下(官方文檔做了很詳細的解釋)
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 iunt */
} Elf32_Ehdr;
typedef struct{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf64_Half e_type; /* Object file type */
Elf64_Half e_machine; /* Architecture */
Elf64_Word e_version; /* Object file version */
Elf64_Addr e_entry; /* Entry point virtual address */
Elf64_Off e_phoff;/* Program header table file offset*/
Elf64_Off e_shoff;/* Section header table file offset*/
Elf64_Word e_flags; /* Processor-specific flags */
Elf64_Half e_ehsize; /* ELF header size in bytes */
Elf64_Half e_phentsize;/* Program header table entry size */
Elf64_Half e_phnum;/* Program header table entry count */
Elf64_Half e_shentsize;/* Section header table entry size */
Elf64_Half e_shnum; /* Section header table entry count */
Elf64_Half e_shstrndx; /* Section header string table index */
} Elf64_Ehdr;
e_ident 保存着 ELF 的幻數和其他信息:
最前面四個字節是幻數,用字符串表示爲 \177ELF
其後的字節如果是 32 位則是 ELFCLASS32 (1),如果是 64 位則是 ELFCLASS64 (2)
再其後的字節表示端序,小端序爲 ELFDATA2LSB (1),大端序爲 ELFDATA2LSB (2)
最後一個字節則表示 ELF 的版本
示例
使用readelf - h命令查看qwer.out的文件頭(ubuntu 18.04 LTS)
- 程序頭表(Program Header Table) ---- 告訴系統怎樣創建一個進程映像
程序頭表是由 ELF 頭的 e_phoff 和 e_phentsize 、e_phnum共同確定大小的表格組成
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 */
Elf64_Word p_type; /* Segment type */
Elf64_Word p_flags; /* Segment flags */
} Elf32_Phdr;
typedef struct{
Elf64_Off p_offset;/* Segment file offset */
Elf64_Addr p_vaddr; /* Segment virtual address */
Elf64_Addr p_paddr; /* Segment physical address */
Elf64_Xword p_filesz; /* Segment size in file */
Elf64_Xword p_memsz; /* Segment size in memory */
Elf64_Xword p_align; /* Segment alignment */
} Elf64_Phdr;
e_phoff 指定的偏移量
e_phentsize 表示表格中程序頭的大小,
e_phnum表示表格中程序頭的數量
示例
使用readelf -l qwer.out
命令查看程序頭表
-
段(Section) ---- 包含鏈接視圖中大量的對象文件信息
-
段表(Section Header Table) ---- 包含描述文件中所有段的信息
段表(Section Header Table)是一個以 Elf32_Shdr結構體爲元素的數組,每個結構體對應一個段,它描述了各個段的信息
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;
typedef struct
{
Elf64_Wordsh_name; /* Section name (string tbl index) */
Elf64_Word sh_type; /* Section type */
Elf64_Xword sh_flags; /* Section flags */
Elf64_Addr sh_addr; /* Section virtual addr at execution */
Elf64_Off sh_offset;/* Section file offset */
Elf64_Xword sh_size; /* Section size in bytes */
Elf64_Word sh_link; /* Link to another section */
Elf64_Word sh_info; /* Additional section information */
Elf64_Xword sh_addralign;/* Section alignment */
Elf64_Xword sh_entsize;/* Entry size if section holds table */
} Elf64_Shdr;
ELF 文件頭的部分成員變量給出了段表的部分屬性:
e_shoff成員給出了段表在 ELF 中的偏移
e_shnum 成員給出了段描述符的數量
e_shentsize 給出了每個段描述符的大小
使用readelf -S qwer.out
查看段表信息