Linux 二進制文件格式 ELF 入門
markdown 格式地址:https://github.com/ForceInjection/linux-from-beginner-to-master/blob/main/elf_101.md
Linux 二進制文件格式 ELF 101
什麼是 ELF 文件?
ELF 是 可執行和可鏈接格式(Executable and Linkable Format)的縮寫,它定義了二進制文件、庫和核心文件的結構。正式的規範允許操作系統正確解釋其底層機器指令。ELF 文件通常是編譯器或鏈接器的輸出,並且是一種二進制格式。有了合適的工具,這樣的文件就可以被分析和更好地理解。
爲什麼要學習 ELF 的細節?
在深入更技術性的細節之前,解釋一下爲什麼理解 ELF 格式是有用的。首先,它有助於學習我們操作系統的內部工作方式。當出現問題時,我們可能能更好地理解發生了什麼(或爲什麼)。然後是能夠研究 ELF 文件的價值,特別是在安全漏洞或發現可疑文件之後。最後但並非最不重要的是,在開發過程中爲了更好地理解。即使大家使用像 Golang 這樣的高級語言編程,我們仍然可能從瞭解幕後發生的事情中受益。
那麼,爲什麼要學習更多關於 ELF 的知識呢?
-
• 對操作系統工作方式的通用理解
-
• 軟件開發
-
• 數字取證和事件響應(DFIR)
-
• 惡意軟件研究(二進制分析)
從源代碼到進程
無論我們運行什麼操作系統,它都需要將通用功能轉換爲 CPU 的語言,也稱爲機器代碼。一個函數可能是一些基本的事情,比如在磁盤上打開一個文件或在屏幕上顯示一些東西。我們不是直接與 CPU 對話,而是使用編程語言,使用內部函數。然後編譯器將這些函數翻譯成目標代碼。這個目標代碼隨後被鏈接器工具鏈接成一個完整的程序。結果是一個二進制文件,然後可以在特定的平臺和 CPU 類型上執行。
注意事項
不要在生產系統上運行相關命令。最好在測試機器上覆制一個現有的二進制文件並使用相關命令。此外,我們提供了一個小型的 C 程序,我們可以編譯它來進行驗證。
相關工具安裝:
sudo yum install binutils
sudo yum install pax-utils
sudo yum install prelink
ELF 文件的解剖
一個常見的誤解是 ELF 文件只是用於二進制文件或可執行文件。我們已經看到它們可以用於部分片段(目標代碼)。另一個例子是共享庫或核心轉儲(那些核心或 a.out 文件)。ELF 規範甚至在 Linux 上用於內核本身和 Linux 內核模塊。
file test
test: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=e0e7da04675930bdf7bd0e9fb5a5e1701a8a32b9, not stripped
文件命令顯示了這個二進制文件的一些基本信息
文件結構
由於 ELF 文件的可擴展設計,每個文件的結構都不同。一個 ELF 文件由以下部分組成:
-
- ELF 頭部
-
- 文件數據
使用 readelf 命令,我們可以查看文件的結構,它看起來像這樣:
readelf -h /usr/bin/ps
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x402f83
Start of program headers: 64 (bytes into file)
Start of section headers: 98256 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 29
Section header string table index: 28
ELF 二進制文件的細節
ELF 頭部
如上述命令輸出所示,ELF 頭部以一些魔術開始。這個 ELF 頭部魔術提供了關於文件的信息。前四個十六進制部分定義了這是一個 ELF 文件(45=E,4c=L,46=F),前綴是 7f 值。
這個 ELF 頭部是強制性的。它確保在鏈接或執行期間數據被正確解釋。爲了更好地理解 ELF 文件的內部工作方式,瞭解這個頭部信息是有用的。
類別
在 ELF 類型聲明之後,定義了一個類別字段。這個值決定了文件的架構。它可以是 32 位(=01)或 64 位(=02)架構。魔術顯示了一個 02,被 readelf 命令翻譯爲 ELF64 文件。換句話說,使用 64 位架構的 ELF 文件。
數據
下一部分是數據字段。它知道兩個選項:01 用於 LSB 最低有效位,也稱爲小端。然後是值 02,用於 MSB(最高有效位,大端)。這個特定值有助於正確解釋文件內剩餘的對象。這很重要,因爲不同類型的處理器以不同的方式處理傳入的指令和數據結構。在這種情況下,使用了 LSB,這對於 AMD64 類型的處理器來說是常見的。
LSB 的效果在使用 hexdump 對二進制文件進行操作時變得明顯。讓我們展示一下 /usr/bin/ps
的 ELF 頭部細節。
hexdump -n 16 /usr/bin/ps
0000000 457f 464c 0102 0001 0000 0000 0000 0000
0000010
我們可以看到值對是不同的,這是由於字節順序的正確解釋造成的。
版本
接下來是魔術中的另一個 “01”,這是版本號。目前,只有一個版本類型:當前版本,其值是 “01”。
OS/ABI
每個操作系統在公共功能上有很大一部分重疊。此外,它們都有特定的功能,或者至少在它們之間有細微的差別。正確的設置定義是通過 應用程序二進制接口 (ABI)。這樣,操作系統和應用程序都知道該期待什麼,函數被正確轉發。這兩個字段描述了使用了什麼 ABI 以及相關的版本。在這種情況下,值是 00,這意味着沒有使用特定的擴展。輸出顯示爲 System V。
ABI 版本
如果需要,可以指定 ABI 的版本。
機器
我們還可以在頭部找到預期的機器類型。
類型
類型 字段告訴我們文件的目的是什麼。有一些常見的文件類型。
-
• CORE(值 4)
-
• DYN(共享對象文件),用於庫(值 3)
-
• EXEC(可執行文件),用於二進制文件(值 2)
-
• REL(可重定位文件),在鏈接成可執行文件之前(值 1)
查看完整標頭詳細信息
雖然一些字段已經可以通過 readelf 輸出的魔術值顯示,但還有更多。例如,文件是針對什麼特定的處理器類型。使用 hexdump 我們可以看到完整的 ELF 頭部及其值。
hexdump -C -n 64 /usr/bin/ps
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 3e 00 01 00 00 00 83 2f 40 00 00 00 00 00 |..>....../@.....|
00000020 40 00 00 00 00 00 00 00 d0 7f 01 00 00 00 00 00 |@...............|
00000030 00 00 00 00 40 00 38 00 09 00 40 00 1d 00 1c 00 |....@.8...@.....|
00000040
上面的高亮字段是定義機器類型的地方。值 3e
是十進制的 62
,等於 AMD64。要了解所有機器類型,可以查看這個 ELF 頭部文件。
雖然我們可以用十六進制轉儲做很多事情,但讓工具爲大家工作是有意義的。dumpelf 工具在這方面可以提供幫助。它顯示了一個格式化的輸出,非常類似於 ELF 頭部文件。非常適合學習哪些字段被使用以及它們的典型值。
澄清了所有這些字段之後,是時候看看真正的魔法發生在哪裏,進入下一個頭部了!
文件數據
除了 ELF 頭部,ELF 文件由三個部分組成。
-
• 程序頭部 或 段(9)
-
• 節頭部 或 節(28)
-
• 數據
在我們深入這些頭部之前,最好知道 ELF 有兩個互補的 “視圖”。一個用於鏈接器允許執行(段)。另一個用於對指令和數據進行分類(節)。因此,根據目標,使用相關的頭部類型。讓我們從程序頭部開始,我們在 ELF 二進制文件中找到它們。
程序頭
一個 ELF 文件由零個或多個段(Segments
)組成,並描述瞭如何創建運行時執行的進程 / 內存映像。當內核看到這些段時,它使用它們將它們映射到虛擬地址空間,使用 mmap(2) 系統調用。換句話說,它將預定義的指令轉換爲內存映像。如果 ELF 文件是一個普通的二進制文件,它需要這些程序頭部。否則,它根本不能運行。它使用這些頭部和底層的數據結構來形成一個進程。對於共享庫,這個過程是類似的。
readelf -l /usr/bin/ps
Elf file type is EXEC (Executable file)
Entry point 0x402f83
There are 9 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001f8 0x00000000000001f8 R E 8
INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238
0x000000000000001c 0x000000000000001c R 1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x0000000000016844 0x0000000000016844 R E 200000
LOAD 0x0000000000016de0 0x0000000000616de0 0x0000000000616de0
0x0000000000000640 0x00000000000217a8 RW 200000
DYNAMIC 0x0000000000016df8 0x0000000000616df8 0x0000000000616df8
0x0000000000000200 0x0000000000000200 RW 8
NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x00000000000141fc 0x00000000004141fc 0x00000000004141fc
0x0000000000000784 0x0000000000000784 R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 10
GNU_RELRO 0x0000000000016de0 0x0000000000616de0 0x0000000000616de0
0x0000000000000220 0x0000000000000220 R 1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .jcr .dynamic .got
ELF 二進制文件中的程序頭部概覽
我們在示例中看到有 9 個程序頭部。
PT_LOAD
這是最常見的段類型,用於定義需要加載到內存的部分。通常分爲兩個區域:一個只讀區域(通常包含代碼)和一個可讀寫區域(通常包含數據)。
PT_DYNAMIC
包含與動態鏈接有關的信息,例如共享庫的路徑、符號表等。動態鏈接器會使用這個段來加載動態庫。
PT_INTERP
如果程序需要由解釋器(例如動態鏈接器)執行,這個段會指定解釋器的路徑。
GNU_EH_FRAME
這是一個由 GNU C 編譯器(gcc)使用的排序隊列。它存儲異常處理程序。所以當出現問題時,它可以利用這個區域正確處理。
GNU_STACK
這個頭部用於存儲堆棧信息。堆棧是一個緩衝區,或臨時存放的地方,存儲像局部變量這樣的項目。這將以 LIFO(後進先出)的方式發生,類似於將箱子堆疊在一起。當一個進程函數開始時,會保留一個塊。當函數結束時,它將被標記爲再次空閒。現在有趣的部分是堆棧不應該是可執行的,因爲這可能會引入安全漏洞。通過操縱內存,人們可以引用這個可執行堆棧並運行預期的指令。
如果 GNU_STACK 段不可用,那麼通常使用可執行堆棧。scanelf 和 execstack 是兩個展示堆棧詳細信息的工具。
scanelf -e /usr/bin/ps
TYPE STK/REL/PTL FILE
ET_EXEC RW- R-- RW- /usr/bin/ps
execstack -q /usr/bin/ps
- /usr/bin/ps
查看程序頭部的命令
-
•
dumpelf(pax-utils)
-
•
elfls -S /usr/bin/ps
-
•
eu-readelf –program-headers /usr/bin/ps
ELF 節(Section)
節頭部
節頭部定義了文件中的所有節。正如所說,這個 “視圖” 用於鏈接和重定位。
節可以在 ELF 二進制文件中找到,在 GNU C 編譯器將 C 代碼轉換爲彙編語言之後,然後是 GNU 彙編器,它創建了它的對象。
如例子所示,一個段可以有 0 個或多個節。對於可執行文件,有四個主要節:.text,.data,.rodata 和 .bss。每個這些節都以不同的訪問權限加載,這可以通過 readelf -S 看到。
readelf -S /usr/bin/ps
There are 29 section headers, starting at offset 0x17fd0:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400238 00000238
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000400254 00000254
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 0000000000400274 00000274
0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400298 00000298
0000000000000050 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000004002e8 000002e8
0000000000000a20 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000400d08 00000d08
000000000000042d 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000401136 00001136
00000000000000d8 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000401210 00001210
00000000000000a0 0000000000000000 A 6 3 8
[ 9] .rela.dyn RELA 00000000004012b0 000012b0
00000000000000a8 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000401358 00001358
0000000000000900 0000000000000018 AI 5 23 8
[11] .init PROGBITS 0000000000401c58 00001c58
000000000000001a 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 0000000000401c80 00001c80
0000000000000610 0000000000000010 AX 0 0 16
[13] .text PROGBITS 0000000000402290 00002290
000000000000a4fa 0000000000000000 AX 0 0 16
[14] .fini PROGBITS 000000000040c78c 0000c78c
0000000000000009 0000000000000000 AX 0 0 4
[15] .rodata PROGBITS 000000000040c7a0 0000c7a0
0000000000007a5a 0000000000000000 A 0 0 32
[16] .eh_frame_hdr PROGBITS 00000000004141fc 000141fc
0000000000000784 0000000000000000 A 0 0 4
[17] .eh_frame PROGBITS 0000000000414980 00014980
0000000000001ec4 0000000000000000 A 0 0 8
[18] .init_array INIT_ARRAY 0000000000616de0 00016de0
0000000000000008 0000000000000008 WA 0 0 8
[19] .fini_array FINI_ARRAY 0000000000616de8 00016de8
0000000000000008 0000000000000008 WA 0 0 8
[20] .jcr PROGBITS 0000000000616df0 00016df0
0000000000000008 0000000000000000 WA 0 0 8
[21] .dynamic DYNAMIC 0000000000616df8 00016df8
0000000000000200 0000000000000010 WA 6 0 8
[22] .got PROGBITS 0000000000616ff8 00016ff8
0000000000000008 0000000000000008 WA 0 0 8
[23] .got.plt PROGBITS 0000000000617000 00017000
0000000000000318 0000000000000008 WA 0 0 8
[24] .data PROGBITS 0000000000617318 00017318
0000000000000108 0000000000000000 WA 0 0 8
[25] .bss NOBITS 0000000000617420 00017420
0000000000021168 0000000000000000 WA 0 0 32
[26] .gnu_debuglink PROGBITS 0000000000000000 00017420
0000000000000010 0000000000000000 0 0 4
[27] .gnu_debugdata PROGBITS 0000000000000000 00017430
0000000000000a90 0000000000000000 0 0 1
[28] .shstrtab STRTAB 0000000000000000 00017ec0
000000000000010d 0000000000000000 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),
l (large), p (processor specific)
.text
包含可執行代碼。它將被打包到一個具有讀寫權限的段中。它只加載一次,因爲內容不會改變。這可以通過 objdump 實用程序看到。
objdump -d /usr/bin/ps -j .text | head
/usr/bin/ps: file format elf64-x86-64
Disassembly of section .text:
0000000000402290 <.text>:
402290: 41 55 push %r13
402292: be c0 c4 40 00 mov $0x40c4c0,%esi
402297: 41 54 push %r12
.data
初始化數據,具有讀寫訪問權限
.rodata
初始化數據,僅具有讀取訪問權限(=A)。
.bss
未初始化數據,具有讀寫訪問權限(=WA)
[24] .data PROGBITS 0000000000617318 00017318
0000000000000108 0000000000000000 WA 0 0 8
[25] .bss NOBITS 0000000000617420 00017420
0000000000021168 0000000000000000 WA 0 0 32
查看節和頭部的命令
-
•
dumpelf
-
•
elfls -p /usr/bin/ps
-
•
eu-readelf –section-headers /usr/bin/ps
-
•
readelf -S /usr/bin/ps
-
•
objdump -h /usr/bin/ps
節組
一些節可以被分組,因爲它們形成一個整體,或者換句話說是一個依賴關係。較新的鏈接器支持這個功能。儘管如此,這並不常見於發現這種情況:
readelf -g /usr/bin/ps
There are no section groups in this file.
雖然這看起來可能不太有趣,但它清楚地表明瞭研究可用的 ELF 工具包的好處,用於分析。因此,在本文的末尾包含了工具及其主要目標的概述。
靜態 vs. 動態二進制文件
在處理 ELF 二進制文件時,瞭解有兩種類型以及它們的鏈接方式是很好的。類型是靜態的還是動態的,指的是所使用的庫。出於優化目的,我們經常看到二進制文件是 “動態的”,這意味着它需要外部組件才能正確運行。通常這些外部組件是正常的庫,包含像打開文件或創建網絡套接字這樣的公共函數。另一方面,靜態二進制文件包含了所有庫。這使它們變得更大,但更具可移植性(例如,在另一個系統上使用它們)。
如果我們想檢查一個文件是靜態編譯還是動態編譯,使用 file 命令。如果它顯示類似的東西:
file /usr/bin/ps
/usr/bin/ps: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=36fb21d67d92790800dc4212bd9f610602e3dc08, stripped
要確定使用了哪些外部庫,只需對同一二進制文件使用 ldd:
ldd /usr/bin/ps
linux-vdso.so.1 (0x00007fff72f72000)
libprocps.so.4 => /lib64/libprocps.so.4 (0x00007f4e1e354000)
libsystemd.so.0 => /lib64/libsystemd.so.0 (0x00007f4e1e123000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007f4e1df1f000)
libc.so.6 => /lib64/libc.so.6 (0x00007f4e1db65000)
libcap.so.2 => /lib64/libcap.so.2 (0x00007f4e1d960000)
libm.so.6 => /lib64/libm.so.6 (0x00007f4e1d5e2000)
librt.so.1 => /lib64/librt.so.1 (0x00007f4e1d3da000)
...
提示: 要查看底層依賴關係,使用 lddtree
實用程序可能更好。
段與節的區別
-
• 用途不同:段是操作系統加載和運行程序時使用的,而節主要是編譯器和鏈接器在生成和管理可執行文件時使用的。段更關注的是程序運行時的內存佈局,而節關注的是文件的組織結構。
-
• 對應關係:多個節可以被映射到同一個段中。例如,
.text
節和.rodata
節可以映射到同一個只讀的PT_LOAD
段中。而.data
和.bss
節可以被映射到同一個可讀寫的PT_LOAD
段中。 -
• 操作系統處理:操作系統加載可執行文件時只關心段,它會將段映射到進程的地址空間中,並根據需要設置段的權限(如可執行、只讀、可寫)。而節主要在鏈接和調試過程中使用,操作系統不會直接操作節。
假設一個簡單的 ELF 文件有以下節和段:
-
•
.text
節存放程序的代碼 -
•
.data
節存放已初始化的數據 -
•
.bss
節存放未初始化的數據
在運行時,操作系統會將這些節組織成段:
-
•
.text
節被加載到一個只讀且可執行的PT_LOAD
段中 -
•
.data
和.bss
節則被加載到一個可讀寫的PT_LOAD
段中
二進制分析工具
當我們想要分析 ELF 文件時,首先尋找可用的工具肯定是有用的。一些軟件包提供了一個工具包,用於逆向工程二進制文件或可執行代碼。如果讀者是分析 ELF 惡意軟件或固件的新手,請考慮首先學習 靜態分析。這意味着我們在不實際執行文件的情況下檢查文件。當大家更好地瞭解它們的工作原理時,然後轉向 動態分析。現在我們將運行文件樣本,並在低級代碼作爲實際處理器指令執行時查看它們的實際行爲。無論我們進行什麼類型的分析,請確保在專用系統上進行,最好有嚴格的網絡規則。這特別是當我們處理未知樣本或與惡意軟件相關時。
流行工具
Radare2
Radare2 工具包由 Sergi Alvarez 創建。版本中的 “2” 指的是與第一版本相比的完整重寫。它現在被許多逆向工程師用來了解二進制文件的工作原理。它可以用來解剖固件、惡意軟件和任何其他看起來是可執行格式的東西。
軟件包
大多數 Linux 系統已經安裝了 binutils 包。其他包可能有助於顯示更多細節。擁有正確的工具包可能會簡化大家的工作,特別是當我們進行分析或更多地瞭解 ELF 文件時。因此,我們收集了一個包列表和相關實用程序。
elfutils
-
•
/usr/bin/eu-addr2line
-
•
/usr/bin/eu-ar – ar
的替代品,用於創建、操作存檔文件 -
•
/usr/bin/eu-elfcmp
-
•
/usr/bin/eu-elflint
– 符合 gABI 和 psABI 規範的合規性檢查 -
•
/usr/bin/eu-findtextrel
– 查找文本重定位 -
•
/usr/bin/eu-ld
– 組合對象和存檔文件 -
•
/usr/bin/eu-make-debug-archive
-
•
/usr/bin/eu-nm
– 從對象 / 可執行文件顯示符號 -
•
/usr/bin/eu-objdump
– 顯示對象文件的信息 -
•
/usr/bin/eu-ranlib
– 爲存檔創建索引以提高性能 -
•
/usr/bin/eu-readelf
– 以人類可讀的方式顯示 ELF 文件 -
•
/usr/bin/eu-size
– 顯示每個節的大小(文本、數據、bss 等) -
•
/usr/bin/eu-stack
– 顯示正在運行的進程的堆棧,或核心轉儲 -
•
/usr/bin/eu-strings
– 顯示文本字符串(類似於 strings 實用程序) -
•
/usr/bin/eu-strip
– 從符號表中剝離 ELF 文件 -
•
/usr/bin/eu-unstrip
– 向剝離的二進制文件添加符號和調試信息
洞見:elfutils
包是一個很好的開始,因爲它包含了大多數用於執行分析的實用程序。
elfkickers
-
•
/usr/bin/ebfc
– Brainfuck 編程語言的編譯器 -
•
/usr/bin/elfls
– 顯示帶有標誌的程序頭部和節頭部 -
•
/usr/bin/elftoc
– 將二進制文件轉換爲 C 程序 -
•
/usr/bin/infect
– 注入一個 dropper 的工具,在 /tmp 中創建 setuid 文件 -
•
/usr/bin/objres
– 從普通或二進制數據創建對象 -
•
/usr/bin/rebind
– 更改 ELF 文件中符號的綁定 / 可見性 -
•
/usr/bin/sstrip
– 從 ELF 文件中剝離不需要的組件
洞見:ELFKickers
包的作者專注於 ELF
文件的操作,這可能對於我們在發現畸形 ELF
二進制文件時瞭解更多非常有幫助。
pax-utils
-
•
/usr/bin/dumpelf
– 轉儲內部 ELF 結構 -
•
/usr/bin/lddtree
– 像 ldd 一樣,顯示依賴關係的級別 -
•
/usr/bin/pspax
– 顯示運行中的進程的 ELF/PaX 信息 -
•
/usr/bin/scanelf
– 廣泛的信息,包括 PaX 詳細信息 -
•
/usr/bin/scanmacho
– 顯示Mach-O
二進制文件(Mac OS X)的詳細信息 -
•
/usr/bin/symtree
– 顯示符號的分級輸出
注意:此包中的一些實用程序可以遞歸掃描整個目錄。適合目錄的大規模分析。工具的重點是收集 PaX
詳細信息。除了 ELF
支持外,還可以提取有關 Mach-O
二進制文件的一些詳細信息。
示例輸出:
scanelf -a /usr/bin/ps
TYPE PAX PERM ENDIAN STK/REL/PTL TEXTREL RPATH BIND FILE
ET_EXEC PeMRxS 0755 LE RW- R-- RW- - - LAZY /usr/bin/ps
prelink
-
•
/usr/bin/execstack
– 顯示或更改堆棧是否可執行 -
•
/usr/bin/prelink
– 重新映射 / 重新定位 ELF 文件中的調用,以加快進程
示例二進制文件
如果我們想自己創建一個二進制文件,只需創建一個小的 C 程序並編譯它。這裏有一個示例,它打開 /tmp/test.txt
,將內容讀入緩衝區並顯示它。確保創建相關的 /tmp/test.txt
文件。
#include <stdio.h>
int main(int argc, char **argv)
{
FILE *fp;
char buff[255];
fp = fopen("/tmp/test.txt", "r");
fgets(buff, 255, fp);
printf("%s\n", buff);
fclose(fp);
return 0;
}
這個程序可以用:gcc -o test test.c
來編譯
常見問題解答
什麼是 ABI?
ABI 是 Application Binary Interface 的縮寫,它指定了操作系統和一段可執行代碼之間的低級接口。
什麼是 ELF?
ELF 是 Executable and Linkable Format 的縮寫。它是一個正式的規範,定義了指令在可執行代碼中的存儲方式。
我怎樣才能看到一個未知文件的文件類型?
使用 file 命令進行第一輪分析。該命令可能能夠根據頭部信息或魔術數據顯示詳細信息。
結論
ELF 文件用於執行或鏈接。根據主要目標,它包含所需的段或節。段由內核查看並映射到內存中(使用 mmap)。節由鏈接器查看,以創建可執行代碼或共享對象。
ELF 文件類型非常靈活,爲多種 CPU 類型、機器架構和操作系統提供支持。它也非常可擴展:每個文件的構建方式不同,取決於所需的部分。
頭部是文件的一個重要部分,準確描述了 ELF 文件的內容。通過使用正確的工具,我們可以對文件的目的有一個基本的瞭解。從那裏開始,我們可以進一步檢查二進制文件。這可以通過確定它使用的函數或存儲在文件中的字符串來完成。對於那些從事惡意軟件研究或想要更好地瞭解進程行爲(或不行爲!)的人來說,這是一個很好的開始。
參考文章
-
• wikipedia
-
• The 101 of ELF files on Linux: Understanding and Analysis
-
• ELF Diagram
-
• System V Application Binary Interface - DRAFT - 10 June 2013
-
• ELF format cheatsheet
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/8L04s20E-9Qyz0DKhlOrKQ