1. windows PE文件與Linux ELF文件概述
在windows中可執行文件是pe文件格式,Linux中可執行文件是ELF文件,其文件格式是ELF文件格式,在Linux下的ELF文件除了可執行文件(Excutable File),可重定位目標文件(RellocatableObject File)、共享目標文件(SharedObject File)、核心轉儲文件(CoreDump File)也都是ELF格式文件。
2.ELF文件結構分析
2.1 ELF文件查看
使用Linux下專用工具——readelf來查看elf文件信息
查看readelf中的源碼
一個典型的ELF文件大致的結構如下:
ELF文件大致的結構文件頭(ELF Header)
程序頭表(Program Header Table)
代碼段(.text)
數據段(.data)
bss段(.bss)
段表字符串表(.shstrtab)
段表(Section Header Table)
符號表(.symtab)
字符串表(.strtab)
重定位表(.rel.text)
重定位表(.rel.data)
2.2 FLF文件組成
2.2.1文件頭
用于記錄一個ELF文件的信息(多少位?能夠運行的CPU平臺是什么?程序的入口點在哪里)
查看ELF頭
在readelf的源碼中變量類型Elf_Internal_Ehdr_,定義在internal頭文件中
#define
EI_NIDENT??????? 16??????????????? /* Size of e_ident[] */
typedef
struct elf_internal_ehdr {
unsigned char??????????????? e_ident[EI_NIDENT]; ??/* ELF "magic
number" */
bfd_vma??????????????? ??????e_entry;??????? ??????/* Entry point virtual address */
bfd_size_type??????????????? e_phoff;??????? ??????/* Program header table file offset */
bfd_size_type??????????????? e_shoff;??????? ??????/* Section header table file offset */
unsigned long??????????????? e_version;??????? ????/* Identifies object file version */
unsigned long??????????????? e_flags;??????? ??????/* Processor-specific flags */
unsigned short??????? ???????e_type;????????????? ?/* Identifies object file type */
unsigned short??????? ???????e_machine;??????? ????/* Specifies required architecture */
unsigned int ????????????????e_ehsize;??????? ?????/* ELF header size in bytes */
unsigned int ????????????????e_phentsize;????????? /* Program header table entry size */
unsigned int ????????????????e_phnum;
/* Program header table
entry count */
unsigned int ????????????????e_shentsize;??????? ???/* Section header table entry size */
unsigned int ????????????????e_shnum;
/* Section header table
entry count */
unsigned int ????????????????e_shstrndx;??????? ????/* Section header string table index */
}
Elf_Internal_Ehdr;
在Linux自帶的頭文件中查看,源碼文件頭的結構中一共有14個字段,對應到文件16進制中。
#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;
2.2.2 程序頭表
記錄了每個Segment的相關信息,比如類型、對應文件的偏移、大小、屬性等。
程序頭表和段頭表相對獨立,它們是由ELF文件頭統一管理,程序頭表管理ELF文件加載后,ELF文件內可加載段到內存映像的映射關系,一般只有可執行文件中,包含程序頭表。程序頭表包含多個程序頭表項,程序頭表描述的對象稱為“Segment”,Segment描述的是ELF文件加載后的數據塊,段(Section)描述的是ELF文件加載前的數據塊。一般來說,來說兩者會存在一定的對應關系,比如代碼段.text的加載信息保存在程序頭表項對應存放代碼的Segment中,數據段.data的加載信息保存在程序頭表項對應存放數據的Segment中。有時候為了簡化程序頭表項的個數,會把同類型的多個段,設置整個ELF文件作為一個Segment。
程序頭表的數據結構/* Program segment header.? */
typedef struct
{
Elf32_Word? ? ? ? p_type;? ? ? ? ? ? ? ? ?/* Segment type */
Elf32_Off? ? ? ? ?p_offset;? ? ? ? ? ? ? ?/* Segment file offset? Segment對應的內容在文件的偏移*/
Elf32_Addr? ? ? ? p_vaddr;? ? ? ? ? ? ? ? /* Segment virtual address Segment在內存中的線性地址*/
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;
#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 */
p_flag權限屬性標志
值 說明 宏1 可執行 PE_X
2 可寫 PE_W
3 可讀 PE_R
2.2.3 區段頭表
用于記錄ELF文件的主要的數據
查看區段
區段表頭的數據結構:
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;
區段頭表一共有10個字段,含義如下:
(1)sh_name段名,是一個是一個4字節的偏移,記錄了段名字符串在段表字符串表(“.shstrtab”段)內的偏移。段表字符串并非表的形式,而是一個文件塊,保存了所有的段表字符串內容,存儲在“.shstrtab”的段中,根據“.shstrtab”的偏移,加上sh_name便可以訪問到每個段對應的段名字符串。
起始地址是000017ac,第一個段表項全0,sh_name在段表項中的偏移是001b,由上圖可以得到“.shstrtab”段的偏移是0016ae,
所以,計算段名的偏移應該是0x0000001b+ 0x000016ae = 0x000016c9
根據計算的結果,查看0x000016c9處:
(2)sh_type,表示段的類型。段的類型有很多,常見的有SHT_PROGBITS,表示程序數據,SHT_SYMTAB表示符號表,SHT_STRTAB表示字符串表,還有專門存放構造函數數組段SHT_INIT_ARRAY,析構函數數組段SHT_FINI_ARRAY。
a? ?.txt 代碼段
b? ?.data 數據段
c? ?.radata記錄常量數據
d? ?.symtab記錄符號表(相當于PE文件的導出表)的數據
e? ?.strtab 串表段
f? ?.shstrtab 有段表 字符串表段
g? ?.rel .plt記錄某個區段的重定位內容(相當于PE文件的導入表)
對應的宏如下:
/* Legal values for sh_type (section type).? */
#define SHT_NULL? ? ? ? ? ? ? 0? ? ? ?/* Section header table entry unused */
#define SHT_PROGBITS? ? ? ? ? 1? ? ? ?/* Program data */
#define SHT_SYMTAB? ? ? ? ? ? 2? ? ? ?/* Symbol table */
#define SHT_STRTAB? ? ? ? ? ? 3? ? ? ?/* String table */
#define SHT_RELA? ? ? ? ? ? ? 4? ? ? ?/* Relocation entries with addends */
#define SHT_HASH? ? ? ? ? ? ? 5? ? ? ?/* Symbol hash table */
#define SHT_DYNAMIC? ? ? ? ? ?6? ? ? ?/* Dynamic linking information */
#define SHT_NOTE? ? ? ? ? ? ? 7? ? ? ?/* Notes */
#define SHT_NOBITS? ? ? ? ? ? 8? ? ? ?/* Program space with no data (bss) */
#define SHT_REL? ? ? ? ? ? ? ?9? ? ? ?/* Relocation entries, no addends */
#define SHT_SHLIB? ? ? ? ? ? ?10? ? ? /* Reserved */
#define SHT_DYNSYM? ? ? ? ? ? 11? ? ? /* Dynamic linker symbol table */
#define SHT_INIT_ARRAY? ? ? ? 14? ? ? /* Array of constructors */
#define SHT_FINI_ARRAY? ? ? ? 15? ? ? /* Array of destructors */
#define SHT_PREINIT_ARRAY? ? ?16? ? ? /* Array of pre-constructors */
(3)sh_flags,表示段標志,記錄段的屬性。其中0表示默認屬性,1表示段可寫,取值位SHF_WRITE。2表示段加載后需要為之分配內存空間,取值為SHF_ALLOC。4表示可執行,取值為SHF_EXECINSTR,段標志屬性可以疊加。
(4)sh_addr,表示段加載后的線性地址
(5)sh_offset,表示段在文件內的偏移,根據此偏移可確定段的位置,讀取段的內容。
(6)sh_size,表示段的大小,單位為字節。需要注意的是,如果段類型為SHT_NOBITS,段內沒有數據,那么段的大小并非指文件塊的大小,而是指段加載后占用內存的大小。
(7~8)sh_link和sh_info表示段的鏈接信息,一般用于描述符號表段和重定位表段的鏈接信息。對于符號表段(SHT_SYMTAB),sh_link記錄的是符號表使用的串表所在段(一般是,.strtab)對應段表項在段表內的索引。
sh_info記錄的是符號表最后一個局部符號的符號表項在符號表內的索引加1,一般恰好是第一個全局符號的符號表項索引,這樣可以幫助連接器更快的地定位到第一個全局符號。如下圖:段中符號表段的信息sh_info,剛好是局部符號+1的索引。
對于重定位表表段(段類型是SHT_REL),sh_link記錄重定位所作用的符號表段表項早段內的索引,而sh_info記錄重定位所作用的段對應的段表項在段表中的索引。
sh_type sh_link sh_infoSHT_DYNAMIC 此表項中條目所用到的字符串表在段表中的索引
SHT_HASH 此哈希表所適用的符號表的段表索引
SHT_REL 相關符號表的段表索引 重定位所使用的段的段表索引
SHT_RELA 相關聯的字符串表的段表索引 最后一個局部符號的符號表索引值+1
其它 SHN_UNDEF 0
(9)sh_addralign,表示段的對齊方式,對齊規則為 sh_offset %sh_addralign = 0,即段的文件偏移必須是sh_addralign的整數倍,sh_addralign的取值必須是2的整數倍,入1、2、4、8等。
對齊值 對齊方式 說明0 無對齊要求
1 無對齊要求
4 對齊4 滿足sh_iffset % 4 = 0
16 對齊16 滿足sh_iffset % 16 = 0
32 對齊32 滿足sh_iffset % 32 = 0
(10) sh_entsize,一般用于保存注入符號表段,重定位表段時,表示段內保存表的表項大小。例如符號表段“.symtab”內保存的符號表的表項大小為sizeof(Elf32_sym)= 16字節,重定位表段“.rel.plt”內保存的重定位表的表項大小為sizeof(Elf32_rel)= 8字節。
2.2.4 ELF符號表(Symbol Table)
ELF文件的符號表保存了程序中的符號信息,包括程序中的文件名、函數名、全局變量名等,符號表一般保存在名為“.strtab”的段內,該段對應段表項的類型為SHT_SYMTAB。符號表包含多個符號表項,每個符號表項記錄了符號的名稱、位置、類型等信息。
符號表象的數據結構: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;
2.2.5 ELF重定位表(Reloc Table)
重定位表常見于可重定位目標文件內,對于靜態鏈接生成的可可執行文件,一般不包括重定位表,動態鏈接生成的可執行文件暫時不討論。重定位表一般保存在以名為“.rel”開頭的段內,該段對應段表項的類型為SHT_REL,ELF文件需要重定位的段,一般都對應一個重定位表,比如代碼段“.txt”的重定位表保持在“.rel.text”內,數據段“.data”的重定位表保持在“.rel.data”內。
重定位表包含多個重定位表項,每個重定位表項記錄一條重定位信息,包括重定位的符號、位置、類型等。
/* Relocation table entry without addend (in section of type SHT_REL). */
typedef struct
{
Elf32_Addr r_offset; /* Address */
Elf32_Word r_info; /* Relocation type and symbol index */
} Elf32_Rel;
2.2.6 ELF串表(String Table)
ELF文件內的段表和符號表需要記錄段名和符號名,這些名稱都是字符串。然而,段表項和符號表項都是固定長度的數據結構,無法存儲不定長的字符串。因此FLE文件將名稱字符串內容集中存放在一個段內,稱為串表。這些段表項和符號表項只需記錄段名字符串或符號名字符串在對應串表項的位置即可。
雖然存儲的字符串表的內容稱為串表,但是并非表的形式,而是一個文件區域。
-End-