菜狗雜談----深入理解計算機系統

ELF

0x1.簡介

首先,有一種文件格式叫做對象文件(Object File),ELF(Executable Linkable Format)是Linux下對象文件一種存儲格式,同時,遵從這種格式的文件也被稱爲ELF。順便提一句,在windows下文件格式爲PE,兩者統一等級。
ELF包括二進制文件、可執行文件、目標代碼、共享庫和核心轉儲格式文件,Linux下的目標文件和可執行文件都按照該格式進行存儲

ELF分爲3種類型:
可重定位文件(Relocatable file):
編譯器生成的.o文件,由鏈接器處理
包含代碼與數據,可以和其他目標文件鏈接生成一個可執行文件或共享目標文件(不同命令編譯生成靜態庫或動態庫)
可執行文件(Executable file):
鏈接器對.o文件進行處理輸出的文件,進程映像
在Linux中,可執行文件分爲兩種:

  1. Executable Object File,如編輯器vim,調試神器gdb等
  2. 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部分:
  1. 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)
在這裏插入圖片描述

  1. 程序頭表(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命令查看程序頭表
在這裏插入圖片描述

  1. 段(Section) ---- 包含鏈接視圖中大量的對象文件信息

  2. 段表(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查看段表信息
在這裏插入圖片描述

0x3示例

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章