簡介
Executable and Linking Format (ELF)這個格式包含了執行與連結用的檔案,可以搭配文件交叉看
- elf.pdf
 - 原始碼elf.h
 - linux載入elf行為-load_elf_binary
 - man
 - eheader
 - ELF文件可执行栈的深入分析
 - Oracle Solaris 11.1 Linkers and Libraries Guide
 
ELF header
利用指令查看
 |  | 
原始碼elf.h
 |  | 
e_ident
| Name | e_ident第幾個bytes | Purpose | 
|---|---|---|
| EI_MAG0 | 0 | 0x7f | 
| EI_MAG1 | 1 | ‘E’ | 
| EI_MAG2 | 2 | ‘L’ | 
| EI_MAG3 | 3 | ‘F’ | 
| EI_CLASS | 4 | ELFCLASSNONE=0ELFCLASS32=1ELFCLASS64=2 | 
| EI_DATA | 5 | ELFDATANONE=0ELFDATA2LSB=1 又稱little endianELFDATA2MSB=2 又稱big endian | 
| EI_VERSION | 6 | 原始的ELF specification規範這裡固定是1,除非你要設計自己客製擴展ELF的規範不然e_version與EI_VERSION都是固定1 | 
| EI_OSABI | 7 | ELFOSABI_NONE/ELFOSABI_SYSV 0 UNIX System V ABIELFOSABI_HPUX 1 Hewlett-Packard HP-UXELFOSABI_NETBSD 2 NetBSDELFOSABI_LINUX 3 LinuxELFOSABI_SOLARIS 6 Sun SolarisELFOSABI_AIX 7 AIXELFOSABI_IRIX 8 IRIXELFOSABI_FREEBSD 9 FreeBSDELFOSABI_TRU64 10 Compaq TRU64 UNIXELFOSABI_MODESTO 11 Novell ModestoELFOSABI_OPENBSD 12 Open BSDELFOSABI_OPENVMS 13 Open VMSELFOSABI_NSK 14 Hewlett-Packard Non-Stop Kernel | 
| EI_ABIVERSION | 8 | ABI version | 
| EI_PAD | 9 | Start of padding bytes 從這裡開始就補0直到最後 關於padding請看(你所不知道的 C 語言:記憶體管理、對齊及硬體特性)[https://hackmd.io/@sysprog/c-memory#data-alignment] | 
| EI_NIDENT | 16 | Size of e_ident[] | 
e_type
這邊描述elf檔案是什麼型態包含了ET_REL,ET_EXEC,ET_DYN,ET_CORE,接下來簡單
| e_type | 數值(unsigned short) | 用途 | 
|---|---|---|
| ET_NONE | 0 | No file type 未知格式(我在linux核心找不到有啥特別用途) | 
| ET_REL | 1 | Relocatable file 靜態連結 .o(object file) .a(靜態連結庫) .ko(kernel module) | 
| ET_EXEC | 2 | Executable file 可以執行的檔案 | 
| ET_DYN | 3 | Shared object file 動態連結檔案.so,或是position-independent executable | 
| ET_CORE | 4 | Core file 當發生core dumped如果有設定linux會輸出這個type的elf檔案讓你追蹤為什麼程式崩潰了 | 
| ET_LOPROC | 0xff00 | Processor-specific ET_LOPROC~ET_HIPROC範圍的數值 | 
| ET_HIPROC | 0xffff | Processor-specific 同上 | 
ET_REL
object file
ET_REL型態的elf最主要是object file
下載ET_REL範例,裡面有2個檔案function.c和main.c
- 產生 function.c object file
gcc -fPIC -c function.c - 產生 main.c object file
gcc -fPIC -c main.c - 觀察object file的elf type
readelf -h function.o``
 
kernel module
編譯
make
載入kernel module
sudo insmod hello.ko
查詢kernel module已經載入了哪些
sudo lsmod | grep "hello"
刪除kernel module
sudo rmmod hello.ko
查詢printk輸出什麼
dmesg
我們來觀察kernel module的elf type
readelf -h hello.ko
ET_EXEC
這種類型的執行檔載入到固定虛擬記憶體位置,所以e_entry的位置就是固定的位置
 |  | 
編譯
gcc -no-pie main.c
觀察型態
readelf-h a.out 
ET_DYN
position-independent executable
這種類型的執行檔才可以被作業系隨機載入到不同虛擬記憶體位置
 |  | 
編譯
gcc -pie main.c
觀察型態
readelf-h a.out 
share libary
接下來繼續用剛才生成a.out來查詢share libary是哪種elf型態
查詢a.out需要動態連結哪些share libary
ldd a.out
我的電腦就出現/lib/x86_64-linux-gnu/libc.so.6
linux-vdso.so.1 (0x00007ffcb0cba000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5f6cd3a000)
/lib64/ld-linux-x86-64.so.2 (0x00007f5f6cf4e000)
分析看看是什麼型態
readelf -h /lib/x86_64-linux-gnu/libc.so.6
ET_CORE
這種型態的elf是當你的程式發生core dumped,作業系統就會吐出來ET_CORE的elf讓你用gdb來分析
設定core dump檔案的大小不限制
ulimit -c unlimited
我們故意寫一個會出錯的程式
 |  | 
編譯
gcc -g main.c
執行a.out
./a.out
發生Segmentation fault (core dumped)並且看到core這個檔案
我們來觀察core的type
readelf -h core
我們來利用gdb與core來幫助我們分析哪裡出錯了
gdb a.out core
e_machine
這個欄位是可以知道此elf檔案是屬於哪個架構的
細節請看elf-em.h
其中比較特別的是近幾年比較紅的RISC-V,BPF(linux核心裡面的虛擬機,指令就是虛擬機專用的bytecode)
 |  | 
簡單bpf xdp
這裡小範例讓大家體會什麼是XDP
安裝
 |  | 
創建檔案
 |  | 
編譯bpf
clang編譯c語言 先加入include的路徑-isystem 為/usr/include/x86_64-linux-gnu 並且優化等級為-O2 編譯為(-target) bpf架構的執行檔 只處理到編譯尚未處理連結(-c) 的檔名xdp_show_icmp.c 輸出(-o) 檔名xdp_show_icmp.oclang -isystem /usr/include/x86_64-linux-gnu -O2 -target bpf -c xdp_show_icmp.c -o xdp_show_icmp.o
觀察架構
觀察此elf的指令架構
readelf -h xdp_show_icmp.o
設定XDP
sudo 使用ip工具 修改網卡(link) 設定(set) 網卡(dev) 名稱為lo eneric模式(xdpgeneric) 從object file(obj) 檔名為xdp_show_icmp.o 的sections(sec) 名稱為show_icmp取出我們要的程式塞入核心sudo ip link set dev lo xdpgeneric obj xdp_show_icmp.o sec show_icmp
開始實驗
開啟終端機1
sudo cat /sys/kernel/debug/tracing/trace_pipe
開啟終端機2
ping 127.0.0.1
e_version
e_version與EI_VERSION都是固定1
The value 1 signifies the original file format; extensions will create new versions with higher numbers. The value of EV_CURRENT changes as necessary to reflect the current version number.
e_entry
只有e_type為ET_EXEC的執行檔他的e_entry才值得參考e_type為ET_DYN的執行檔載入的虛擬記憶體位置會變動
實驗e_entry
先從ET_EXEC準備好環境
找一下Entry point address的數值並且確認e_type為ET_EXEC
readelf -h a.out
進入gdb
gdb a.out
中斷在Entry point address
b *0x1234
開始執行
r
_start()? 代表elf執行檔e_type為ET_EXEC的Entry point address是程式的進入點e_phoff
紀錄program header table位置在哪,如果沒有就0
e_shoff
紀錄section header table位置在哪,如果沒有就0
e_flags
processor-specific這裡定義硬體的特徵,細節的定義在不同的指令架構的規格書裡面,如果是RISC-V可以參考[RISC-V ELF psABI specification][https://github.com/riscv/riscv-elf-psabi-doc/blob/master/riscv-elf.md#-file-header]裡面關於elf的規範
e_ehsize
ELF header有多少bytes.
e_phentsize
每一個program header大小有多少bytes
e_phnum
有多少個program header,如果沒有就為0
e_shentsize
每一個section header大小有多少bytes
e_shnum
有多少個section header,如果沒有就為0
e_shstrndx
e_shstrndx的數值代表,第幾個section存放.shstrtab這個section,.shstrtab很特殊負責存放所有section人看的懂的名稱
實驗看看
準備一個elf檔案
觀察Section header string table index數值(e_shstrndx)
readelf -h a.out
確認.shstrtab的位置是不是與e_shstrndx標示的一樣
readelf -S a.out
直接看.shstrtab內容,可以發現裡存放很多字串
readelf -x .shstrtab a.out
Program Headers
segment是由一個到多個section組成利用指令查看
 |  | 
只有executable與shared object files的Program Headers才有特別的用途
Program headers are meaningful only for executable and shared object files
一個elf有0或多個Program Header,每個Program Header專門處理一個segment載入記憶體的規劃,如果你的elf檔案或是某個section不是拿來載入記憶體執行用途可能就沒有這個header,如下範例
每一個Program Header的結構如下圖
 |  | 
p_type
Program Header的型態
| p_type | Value | 用途 | 
|---|---|---|
| PT_NULL | 0 | 沒有在使用, program header table會忽略PT_NULL型態的segment,FIXME: 細節還需要在研究. | 
| PT_LOAD | 1 | 單純載入segment到虛擬記憶體,如果載入到記憶體(p_memsz)時比在檔案(p_filesz)的時候還大,多出來的地方補0,可能是為了對齊記憶體 | 
| PT_DYNAMIC | 2 | 載入這個segment裡面都是Dynamic Section去虛擬記憶體 | 
| PT_INTERP | 3 | 當程式需要動態連結 就需要載入這個PT_INTERP型態的segment去記憶體 並且裡面包含dynamic linker/loader的路徑 | 
| PT_NOTE | 4 | 載入這個segment裡面都是Note Section去虛擬記憶體 | 
| PT_SHLIB | 5 | 這個保留尚未定義 | 
| PT_PHDR | 6 | 這個型態代表載入整個Program Header Table去虛擬記憶體,裡面有1個以上Program Header | 
| PT_TLS | 7 | 載入記憶體是以PT_TLS型態,這個segment是存放Thread-local storage變數初始數值 | 
| PT_LOOS | 0x60000000 | 從0x60000000~0x6fffffff的數值保留給作業系統使用 | 
| PT_HIOS | 0x6fffffff | 同上 | 
| PT_LOPROC | 0x70000000 | 從0x70000000~0x7fffffff的數值保留給處理器使用 | 
| PT_HIPROC | 0x7fffffff | 同上 | 
PT_INTERP實驗
觀察Program Header
PT_INTERP型態的segmentreadelf -l a.out編譯此範例程式再觀察一次,可以發現
PT_INTERP型態的segment不見了,go語言也是屬於這種不需要動態連結的程式,好處是有很好的可攜性,缺點是執行檔比較大,並且e_type屬於ET_EXEC代表每次都載入到固定虛擬記憶體可能有安全疑慮修改ld-linux(
dynamic linker)名稱會發生什麼事?此實驗會毀損linux執行的過程謹慎練習
實驗中我的Requesting program interpreter是/lib64/ld-linux-x86-64.so.2
如果我把/lib64/ld-linux-x86-64.so.2名稱改掉會怎樣?
是不是只有實驗2的程式才能跑?
PT_TLS實驗
如下範例可以知道TLS(Thread Local Storage)的作法
每開起一個線程會從TLS_data(elf會定義這個變數存放的segment載入記憶體時p_type為PT_TLS)拿出變數的初始數值存放每個線程內部的TLS_data並且每個函數都可以直接使用TLS_data.
好處可以看到TLS版本不用傳遞參數給函數就可以直接使用,
 |  | 
 |  | 
編譯
gcc t.c -lpthread
p_flags
定義這個區塊的segment在記憶體的權限 最後3個bit定義 讀/寫/執行
舉例
可讀/不可寫/可執行
0x00000000000000000000000000000101(32bit)
可讀/可寫/可執行
0x00000000000000000000000000000111(32bit)
p_offset
定義這個segment在檔案哪個位置開始
p_vaddr
定義這個segment載入到虛擬記憶體的哪個位置,只有沒有經過ASLR(Address space layout randomization)載入的執行檔(e_type為ET_EXEC的執行檔),這個數值才有參考價值
p_paddr
定義這個segment載入到實體記憶體的哪個位置,因為實體記憶體被作業系統保護所以這個數值不準確.
p_filesz
定義這個segment在檔案的大小多少bytes,可為0
p_memsz
定義這個segment在虛擬記憶體的大小多少bytes,可為0
p_align
Section Headers
利用指令查看
 |  | 
 |  | 
sh_name
sh_name存放名稱的index
section的名稱字串存在.shstrtab
sh_type
| Name | Value | 用途 | 
|---|---|---|
| SHT_NULL | 0 | 這個型態代表這個section header無效 | 
| SHT_PROGBITS | 1 | 這裡面的section放程式執行用到的東西 | 
| SHT_SYMTAB | 2 | 這裡面的section放symbol table FIXME Symbol Table | 
| SHT_STRTAB | 3 | 這裡面的section放滿人可以看得懂的字串,例如可以拿來表示section的名稱, | 
| SHT_RELA | 4 | 給object file重定位資訊用的(with explicit addends) FIXME:Relocation | 
| SHT_HASH | 5 | 這個section給symbol table使用 | 
| SHT_DYNAMIC | 6 | 這裡放動態連結的訊息 FIXME:Dynamic Section | 
| SHT_NOTE | 7 | 這裡檔案的訊息 FIXME:Note Section | 
| SHT_NOBITS | 8 | 代表這個section在檔案裡面沒有佔用任何大小,它只有section header而已 | 
| SHT_REL | 9 | 給object file重定位資訊用的(without explicit addends) FIXME:Relocation | 
| SHT_SHLIB | 10 | 這個保留尚未定義 | 
| SHT_DYNSYM | 11 | 這裡面的section放symbol table | 
| SHT_INIT_ARRAY | 14 | 裡面有陣列記憶體位置,指向主程式開始前的函數FIXME:Initialization and Termination Functions | 
| SHT_FINI_ARRAY | 15 | 裡面有陣列記憶體位置,指向主程式結束後要執行的函數,atexit FIXME:Initialization and Termination Functions | 
| SHT_PREINIT_ARRAY | 16 | 裡面有陣列記憶體位置指向函數,這個函數會在SHT_INIT_ARRAY裡面所有函數執行之前執行 | 
| SHT_GROUP | 17 | |
| SHT_SYMTAB_SHNDX | 18 | |
| SHT_LOOS | 0x60000000 | 從0x60000000~0x6fffffff的數值保留給作業系統使用 | 
| SHT_HIOS | 0x6fffffff | 同上 | 
| SHT_LOPROC | 0x70000000 | 從0x70000000~0x7fffffff的數值保留給處理器使用 | 
| SHT_HIPROC | 0x7fffffff | 同上 | 
| SHT_LOUSER | 0x80000000 | 從0x80000000~0xffffffff的數值保留給應用程式使用 | 
| SHT_HIUSER | 0xffffffff | 同上 | 
Initialization and Termination Functions
尚未完成
這裡深入探討SHT_PREINIT_ARRAY,SHT_FINI_ARRAY,SHT_INIT_ARRAY
sh_flags
| Name | Value | 用途 | 
|---|---|---|
| SHF_WRITE | 0x1 | 程式執行時這個section可寫 | 
| SHF_ALLOC | 0x2 | 程式執行時這個section會佔用記憶體 | 
| SHF_EXECINSTR | 0x4 | 這個section包含機械碼 | 
| SHF_MERGE | 0x10 | |
| SHF_STRINGS | 0x20 | 包含以零結尾的字符串組成 | 
| SHF_INFO_LINK | 0x40 | |
| SHF_LINK_ORDER | 0x80 | |
| SHF_OS_NONCONFORMING | 0x100 | |
| SHF_GROUP | 0x200 | |
| SHF_TLS | 0x400 | |
| SHF_MASKOS | 0x0ff00000 | 保留給作業系統使用 | 
| SHF_MASKPROC | 0xf0000000 | 保留給處理器使用 | 
sh_addr
此section在執行時的記憶體哪個位置
0代表沒有在記憶體中
sh_offset
此section在檔案時哪個位置
sh_size
代表section在檔案的大小
sh_type為SHT_NOBITS,雖然檔案沒有存放,但是sh_size可能不為0舉例
c語言還沒初始化的變數放在
.bss section屬於SHT_NOBITS在檔案沒佔空間(只有section header)但載入記憶體後要佔空間sh_link
sh_info
sh_addralign
此section要對齊記憶體
必須讓sh_addr數值剛好,sh_addr/sh_addralign整除才能對齊
記憶體對齊可以增加效能,或是對齊cache避免False_sharing
sh_entsize
Special Sections
這個部份負責探討特殊Sections在幹麻
這個部份尚未完成
Relocation
重定位,用在靜態連結動態連結
現在流行可攜性高的程式很多程式golange
| 重定位方式 | 效能 | 檔案大小 | |
|---|---|---|---|
| 靜態連結 | 快 | 大 | |
| 動態連結 | 中 | ||
| 動態載入 | 
Types
關於Relocation Types (Processor-Specific)是屬於每個指令架構abi所定義,而我們目前只討論AMD64的細節,文件System V Application Binary Interface AMD64 Architecture Processor Supplement有定義elf-Processor-Specific的規範
procedure linkage table
進階練習
學完elf後,如何從這個範例刪減elf執行檔的大小又可以執行?
可以作答在最下面的討論區
可以參考A Whirlwind Tutorial on Creating Really Teensy ELF Executables for Linux
參考
[操作系统真象还原-5.3.3 elf 格式的二进制文件]
[Learning Linux Binary Analysis]
The Curious Case of Position Independent Executables
APP漏洞扫描器之未使用地址空间随机化
ELF之學習心得02 - ELF Header(e_ident篇)