通過簡單的一些 Case骏庸,通過實際操作對 ELF32-i386 文件做概要分析者吁。
使用 Docker 來統(tǒng)一整體的開發(fā)窘俺、測試環(huán)境,適用于 Linux复凳、Windows瘤泪、MacOS(包括 Intel、M1 芯片)育八。
如有排版上的問題对途,可訪問:https://zhoukekestar.github.io/
ELF
The Executable and Linking Format (中文名為:可執(zhí)行與可鏈接格式)是一種在計算機領域,通用的標準化的可執(zhí)行文件(同時髓棋,也用于目標代碼 obj实檀、共享庫 so 等文件)。
ELF 文件是 Linux 下最常用的可執(zhí)行文件的格式按声。在 gcc hello.cc
后生成的 a.out
可執(zhí)行文件就是 ELF
格式的膳犹。
ELF 文件格式
ELF 文件的總體格式如下:
<img src="https://github.com/user-attachments/assets/a06a37f6-f6cb-4b0f-a16f-1a66c916aafb" style="width: 200px"></img>
因為 ELF 格式的目標文件,是參與了程序構建签则、程序執(zhí)行兩個流程须床,所以 ELF 文件也分別對這兩個流程,有兩種視圖:一種是鏈接視圖(Linking View)渐裂,一種是執(zhí)行視圖(Executing View)豺旬。[5]
同時也對應了上圖的不同方向的箭頭指向。
<img src="https://github.com/user-attachments/assets/ba2daefa-3826-45ce-a094-fabc76479743" style="width: 400px"></img>
ELF 格式結構
根據(jù) Tool Interface Standard (TIS) Executable and Linking Format (ELF) Specification 的規(guī)范描述柒凉。
約定的基礎類型有:
Name | Size | Alignment | Purpose |
---|---|---|---|
Elf32_Addr | 4 | 4 | Unsigned program address |
Elf32_Half | 2 | 2 | Unsigned medium integer |
Elf32_Off | 4 | 4 | Unsigned file offset |
Elf32_Sword | 4 | 4 | Signed large integer |
Elf32_Word | 4 | 4 | Unsigned large integer |
unsigned char | 1 | 1 | Unsigned small integer |
ELF Header
#define EI_NIDENT 16
typedef struct {
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} Elf32_Ehdr;
Program Header
typedef struct {
Elf32_Word p_type;
Elf32_Off p_offset;
Elf32_Addr p_vaddr;
Elf32_Addr p_paddr;
Elf32_Word p_filesz;
Elf32_Word p_memsz;
Elf32_Word p_flags;
Elf32_Word p_align;
} Elf32_Phdr;
Selection Header
typedef struct {
Elf32_Word sh_name;
Elf32_Word sh_type;
Elf32_Word sh_flags;
Elf32_Addr sh_addr;
Elf32_Off sh_offset;
Elf32_Word sh_size;
Elf32_Word sh_link;
Elf32_Word sh_info;
Elf32_Word sh_addralign;
Elf32_Word sh_entsize;
} Elf32_Shdr;
構建環(huán)境
構建鏡像
本文采用 docker 來進行環(huán)境的統(tǒng)一族阅,避免各種操作系統(tǒng)、各種版本膝捞、環(huán)境坦刀、CPU 所帶來的差異。
新建并編輯 Dockerfile 如下:
# 指定 AMD64
# 按需替換為自己的加速站點,比如 dockerpull.com/ubuntu:22.04
FROM --platform=linux/amd64 mirror.gcr.io/ubuntu:22.04
# 使用清華源
COPY <<EOF /etc/apt/sources.list
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
deb http://security.ubuntu.com/ubuntu/ jammy-security main restricted universe multiverse
EOF
RUN apt-get update
# 安裝必要的基礎工具
RUN apt-get install gcc nasm make xxd -y
WORKDIR /home
執(zhí)行構建命令
$ docker build -t gcc-amd64 .
別忘了最后的
.
鲤遥,用于指定是當前目錄的Dockerfile
文件央渣。
運行鏡像
為了方便文件的操作,一般會新建一個文件夾和 docker 環(huán)境進行共享渴频,這樣復制、添加北启、刪除比較多的文件時卜朗,就可以直接在非 docker 命令行下操作。
$ docker run -it --rm -v .:/home gcc-amd64
-
-it
interactive & terminal咕村,進入交互式命令行終端 -
--rm
運行后刪除场钉,避免退出容器后,保留過多不必要的運行容器 -
-v
掛載當面目錄至 docker /home 目錄下
最簡單的匯編程序
為避免傳統(tǒng) Hello World 較為復雜的字符串數(shù)據(jù)存儲懈涛、中斷逛万、標準輸入輸出等概念,本文使用更為簡單的加法程序批钠,并使用 return 進行輸出返回宇植。
加法匯編源碼
新建 plus.s
文件,參考 Assembly Programming Tutorial埋心,編輯代碼如下:
注意此處為 nasm 匯編指郁,非 GAS 匯編,相關內(nèi)容參考:Linux assemblers: A comparison of GAS and NASM
section .text ; .text 段落拷呆,用于存放具體執(zhí)行代碼
global _start ; 聲明入口函數(shù)
_start: ;
mov ax, 1 ; ax 寄存器放入 1
mov bx, 2 ; bx 寄存器放入 2
add bx, ax ; 執(zhí)行加法闲坎,并將結果放入 bx 寄存器
; 執(zhí)行程序退出
mov ax,1 ; 中斷號 1
int 0x80 ; 執(zhí)行內(nèi)核中斷,中斷號為 ax 的 1
編譯執(zhí)行
此處都在 docker 環(huán)境中執(zhí)行茬斧。
# 編譯匯編代碼為 obj 代碼
$ nasm -f elf plus.s
# 鏈接生成可執(zhí)行文件
$ ld -m elf_i386 -s -o ./plus ./plus.o
# 執(zhí)行代碼
$ ./plus
# 打印程序 exit code腰懂,此處打印出 3,說明正確執(zhí)行了 1 + 2
$ echo $?
查看程序執(zhí)行完成后的 exit code项秉,可以使用
echo $?
命令绣溜。
ELF 格式簡析
使用 xxd ./plus
查看可執(zhí)行加法程序的二進制文件格式。
# 解析 plus 可執(zhí)行程序為二進制伙狐,并保持到 ./plus.txt
$ xxd ./plus > ./plus.txt
后續(xù)就可以基于這個文件涮毫,分字節(jié)來做解析了。
另外贷屎,可以通過 readelf
命令罢防,來快速查看相關二進制的說明。
$ readelf -a ./plus
ELF Header
ELF 文件頭解析唉侄,一般為 52 個字節(jié)咒吐。
[圖片上傳失敗...(image-25191e-1729842189078)]
0x00
- e_ident[EI_NIDENT]
-
.ELF
標記 -
class
ELF320x01
, 2 為 64 位 -
data
data 2's complement, little endian0x01
,小端編碼 -
version
1 (current)0x01
-
padding
, 補齊到 16 位0x00 0000 0000 0000
-
0x10
-
e_type
EXEC (Executable file)0x02
-
e_machine
Intel 803860x03
-
e_version
version0x01
-
e_entry
entry0x08049000
-
e_phoff
Start of program headers 52 (bytes into file)0x34
0x20
-
e_shoff
Start of section headers 4132 (bytes into file),0x1024
-
e_flags
flags0x00
-
e_ehsize
Size of this header): 52 (bytes)0x34
-
e_phentsize
Size of program headers):32 (bytes)0x20
-
e_phnum
Number of program headers): 20x02
-
e_shentsize
Size of section headers): 40 (bytes)0x28
0x30
-
e_shnum
Number of section headers): 3 0x03 -
e_shstrndx
Section header string table index):2 0x02
readelf
$ readelf -h ./plus
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x8049000
Start of program headers: 52 (bytes into file)
Start of section headers: 4132 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 2
Size of section headers: 40 (bytes)
Number of section headers: 3
Section header string table index: 2
Program Header
Program Header 為以下兩者相乘:
Size of program headers: 32 (bytes)
-
Number of program headers: 2
即 64 個字節(jié)恬叹,并有兩個 Program Headers
[圖片上傳失敗...(image-457c85-1729842189078)]
0100 0000 : p_type: TYPE LOAD
0000 0000 : p_offset 0x00000000
0080 0408 : p_vaddr
0080 0408 : p_paddr
7400 0000 : p_filesz 0x74
7400 0000 : p_memsz
0400 0000 : p_flags
0010 0000 : p_align
0100 0000 : p_type: TYPE LOAD
0010 0000 : p_offset 0x00001000
0090 0408 : p_vaddr
0090 0408 : p_paddr
1100 0000 : p_filesz 0x11 17字節(jié)
1100 0000 : p_memsz
0500 0000 : p_flags
0010 0000 : p_align
readelf
$ readelf -l ./plus
Elf file type is EXEC (Executable file)
Entry point 0x8049000
There are 2 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x08048000 0x08048000 0x00074 0x00074 R 0x1000
LOAD 0x001000 0x08049000 0x08049000 0x00011 0x00011 R E 0x1000
Section to Segment mapping:
Segment Sections...
00
01 .text
- 第一個程序頭起始為
0x00
候生,大小為0x74
= 52 (ELF Header) + 32 * 2 (Program Headers),也就是 ELF Header + Program Headers 的長度 - 第二個程序頭起始為
0x001000
, 長度為0x11
绽昼,也就是真正的加法程序
Program
真正的程序代碼在文件位置的 0x00001000
唯鸭,虛擬地址的 0x08049000
,也就是讀取 .text
段落的代碼:
讀取文件地址的二進制(指定長度為 0x11硅确,即為所有程序代碼):
$ xxd -s 0x1000 -l 0x11 ./plus
00001000: 66b8 0100 66bb 0200 6601 c366 b801 00cd f...f...f..f....
00001010: 80
使用 readelf 讀取并轉為虛擬地址:
$ readelf --hex-dump=.text ./plus
Hex dump of section '.text':
0x08049000 66b80100 66bb0200 6601c366 b80100cd f...f...f..f....
0x08049010 80 .
另外目溉,可以通過反編譯,將二進制轉換為匯編代碼:
$ objdump -S ./plus
./plus: file format elf32-i386
Disassembly of section .text:
08049000 <.text>:
8049000: 66 b8 01 00 mov $0x1,%ax
8049004: 66 bb 02 00 mov $0x2,%bx
8049008: 66 01 c3 add %ax,%bx
804900b: 66 b8 01 00 mov $0x1,%ax
804900f: cd 80 int $0x80
String Table
[圖片上傳失敗...(image-6bd106-1729842189078)]
根據(jù)后面的 section headers 中 STRTAB 的描述菱农,offset 為 0x1011
, length 為 0x11
:
$ xxd -s 0x1011 -l 0x11 ./plus
00001011: 002e 7368 7374 7274 6162 002e 7465 7874 ..shstrtab..text
00001021: 00
Section Headers
[圖片上傳失敗...(image-fe0743-1729842189078)]
# section 0
0000 0000 sh_name
0000 0000 sh_type
0000 0000 sh_flags
0000 0000 sh_addr
0000 0000 sh_offset
0000 0000 sh_size
0000 0000 sh_link
0000 0000 sh_info
0000 0000 sh_addralign
0000 0000 sh_entsize
# section 1
0b00 0000 sh_name
0100 0000 sh_type 0x01 PROGBITS
0600 0000 sh_flags
0090 0408 sh_addr: 0x08049000
0010 0000 sh_offset: 0x00001000
1100 0000 sh_size: 0x000011
0000 0000 sh_link
0000 0000 sh_info
1000 0000 sh_addralign: 0x10
0000 0000 sh_entsize
# section 2
0100 0000 sh_name 0x01 => .shstrtab
0300 0000 sh_type 0x03 STRTAB
0000 0000 sh_flags
0000 0000 sh_addr
1110 0000 sh_offset 0x00001011
1100 0000 sh_size 0x11
0000 0000 sh_link
0000 0000 sh_info
0100 0000 sh_addralign 0x01
0000 0000 sh_entsize
readelf
$ readelf -S ./plus
There are 3 section headers, starting at offset 0x1024:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 08049000 001000 000011 00 AX 0 0 16
[ 2] .shstrtab STRTAB 00000000 001011 000011 00 0 0 1
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),
D (mbind), p (processor specific)
小結
至此缭付,通過一個最簡單的加法,解析了一個最簡單的 ELF 文件循未,從前到后分別是:
ELF Header
(52字節(jié))文件起始位置:0x00
Program Header
(32*2=64字節(jié))文件起始位置:0x34
Program
(11字節(jié))文件起始位置:0x1000
(由 Program Header 指定)String Table
(11字節(jié))文件起始位置:0x1011
(由 Section Table 指定)-
Section Table
(40*3=120字節(jié))文件起始位置:0x1024
(由 ELF Header 指定)最小的一個 ELF 文件由上述組成