本文轉自http://huaidan.org/pstzine/0x02/html/PSTZine_0x02_0x0A.html
==Ph4nt0m Security Team==
Issue 0x02, Phile #0x0A of 0x0A
|=---------------------------------------------------------------------------=|
|=----------------------=[ pe/elf 文件加殼時的處理 ]=----------------------=|
|=---------------------------------------------------------------------------=|
|=---------------------------------------------------------------------------=|
|=--------------------------=[ By dummy ]=--------------------------=|
|=-----------------------=[ <dummy_at_ph4nt0m.org> ]=----------------------=|
|=---------------------------------------------------------------------------=|
前言:
最初的殼是在感染型的病毒技術上發展出來的,加殼目的一般是壓縮或加密。本文主要
就x86平臺下win32 pe和linux elf 加殼程序的實現做簡單介紹和總結,以自己以前寫相關
程序做線索敘述,其中程序源碼是開源的,有興趣的朋友可以繼續進行改進。
ps: 其中有些地方很久沒碰,可能有地方描述有誤,還請見諒:)
正文:
-------------------------------------------------------
slm x86 win32 r3 pe packer
mimisys x86 win32 r0 pe packer
elfp x86 linux r3 elf packer
-------------------------------------------------------
一、一個殼的組成
一個完整的殼程序主要由 2 個部分組成 packer 和 loader。它們具體的作用分別是:
(1) packer
負責將待加殼程序壓縮和加密處理、把loader寫到待加殼程序上。以slm的pakcer
為例具體操作包括,pe有效性判斷、優化可壓縮數據、壓縮和加密、添加loader、存放
加殼參數和待加殼程序原數據(oep等等)、改寫入口點等等。
(2) loader
主要工作是解壓或解密被加殼的程序,以slm的loader為例具體的操作包括:獲取自
身位置、獲取加殼參數、進行解壓或解密、填充導入表、重定位、tls 初始化等等。
二、slm (x86 win32 r3 pe packer)
資料:
http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx
工具:
lordpe pe 文件格式查看編輯工具
dumpbin vc 自帶coff文件格式查看工具
ollydbg r3 調試工具
源碼結構:
./slm/cm 公共頭文件和模塊
./slm/pk packer 實現
./slm/sc loader 實現
在做這個時候對 pe 也是剛剛了解,所以 slm 很多地方現在看來有些問題:)。在第一
節已經簡單描述 slm 的工作流程,下面主要就我當初做的時候遇到的問題做一些描述:
(1) 資源的處理
slm 的資源處理做的比較煩瑣,當初目的是為了把可壓縮資源數據歸并到一起,進
行一次壓縮,不可壓縮單獨存放。下面簡單介紹一下資源的目錄數據格式,詳細還是看
看微軟的文檔和相關源碼:)
從IMAGE_NT_HEADERS.IMAGE_DATA_DIRECTORY[IMAGE_DIRECTORY_ENTRY_RESOURCE]
取出資源數據的地址res_rva,經過轉換后第一個結構體是IMAGE_RESOURCE_DIRECTORY
IMAGE_RESOURCE_DIRECTORY:
NumberOfIdEntries 目錄下 id 名稱入口項個數
NumberOfNamedEntries 目錄下 name 名稱入口項個數
緊跟著IMAGE_RESOURCE_DIRECTORY后面是IMAGE_RESOURCE_DIRECTORY_ENTRY結構
數組,這個數組的元素個數是 NumberOfIdEntries + NumberOfNamedEntries。
IMAGE_RESOURCE_DIRECTORY_ENTRY:
Id 目錄id,只有NameIsString非真才有效
NameIsString 目錄名稱是否是字符串,如果為真NameOffset有效
NameOffset 目錄名稱串的偏移, 偏移是相對與res_rva*的。
DataIsDirectory 如果為真 OffsetToData 有效,否則OffsetToDirectory
有效
OffsetToData 指向資源數據,偏移類型rva
OffsetToDirectory 指向子目錄,偏移類型rva
如果目錄入口名稱是字符串,通過NameOffset獲取PIMAGE_RESOURCE_DIR_STRING_U
的結構指針,目錄名是unicode格式,并且不是以零結尾的字符串。如果目錄名不是字符
串而是id, 那么其值在winnt.h 定義。常見id有RT_ICON、RT_VERSION等等。
結構大致簡單描述完了,有點要注意OffsetToDirectory、OffsetToData修改時要
進行 DWORD 對齊,否則會出現奇怪的現象。
(2) 導入表處理
從IMAGE_NT_HEADERS.IMAGE_DATA_DIRECTORY[IMAGE_DIRECTORY_ENTRY_IMPORT]
取出導入表的地址imp_rva,經過轉換后第一個結構體是IMAGE_IMPORT_DESCRIPTOR。
IMAGE_IMPORT_DESCRIPTOR:
Name 指向導入 dll 的名稱,偏移類型 rva
FirstThunk 指向 IMAGE_THUNK_DATA 結構體,偏移類型 rva
OriginalFirstThunk 指向FirstThunk 的副本, 可以為空。偏移類型 rva
導入由IMAGE_IMPORT_DESCRIPTOR結構數組組成,數組長度由一個Name域為空的結
構表明。
FirstThunk和OriginalFirstThunk都是指向以IMAGE_THUNK_DATA數組組成的數據
結構,系統的加載器在進行導入表填充時,會把FirstThunk指向的結構修改掉。
(3) TLS 處理
這里說的tls是所謂的靜態tls(在pe文件結構上進行實現),關于什么是tls可以看看
《windows 核心編程》線程那章。
1、tls 是怎樣的
比如要在vc中聲明一個tls變量需要這樣__declspec(thread) int x = 0;在鏈接
時這個變量會被鏈接器放入.tls的節中。這個節從外邊看和其他的節沒有什么不同,唯
一的區別在IMAGE_DATA_DIRECTORY[IMAGE_DIRECTORY_ENTRY_TLS]指向的一個結構會對
此節進行描述,這個結構是IMAGE_TLS_DIRECTORY。
IMAGE_TLS_DIRECTORY:
StartAddressOfRawData tls數據開始地址類型va
EndAddressOfRawData tls數據結束地址類型va
AddressOfIndex; tls slot的地址,默認tls slot為0
AddressOfCallBacks 指向一個PIMAGE_TLS_CALLBACK的數組,這個數組
以0結尾,每個PIMAGE_TLS_CALLBACK都是va類型
SizeOfZeroFill 數據區需要進行清 0 數據的大小
Characteristics
2、系統加載器怎樣處理exe的tls
系統加載器在完成重定位和輸入表填充后,就開始處理tls。如果存在tls_dir,
求出tls數據的大小EndAddressOfRawData - StartAddressOfRawData +
SizeOfZeroFill, 按照大小分配一塊內存,地址存入(PDWORD)fs:[0x2c] +
tls_slot, 接著拷貝StartAddressOfRawData -> EndAddressOfRawData之間的數
據到新分配的內存中,然后使用SizeOfZeroFill 清零剩下的數據,然后進行循環回
調AddressOfCallBacks中的函數,PIMAGE_TLS_CALLBACK函數和DllMain原型很像,
只是沒有返回值。
3、系統加載器怎樣處理dll的tls
首先明確dll是可以使用tls的,唯一的不同是AddressOfCallBacks調用方式會
有些區別。如果目標dll是被靜聽鏈接到其他文件上,在進程創建完成時即被加載,
那么他的tls callback會觸發,而LoadLibrary方式加載不會觸發。
(4) rva & raw 轉換
pe 文件中許多結構域的指針類型是rva, rva是pe文件由系統加載后,方法相關數
據的相對偏移量。而我們進行加殼處理時,是直接map的文件數據訪問都是使用文件指
針,這就需要rva進行轉換。(每次做pe相關工具時,我都會習慣寫一個這樣的函數,現在
不下10中版本,竟然沒有一個可以保證是正確的 - -)
下面這個是最新的rva2raw版本,不保證正確性。
三、mimisys (x86 win32 r0 pe packer)
資料:
Windows Research Kernel
wrk/base/ntos/mm/sysload.c:MmLoadSystemImage
工具:
syser 內核調試器,你也可以選擇其他的r0調試器
vmware 如果不想頻繁重啟,需要一個虛擬機
文件格式的一些處理參考slm, 這里主要就r0 pe和r3 pe區別做介紹:
(1) 節和頁
r0空間的內存常常很緊張,就導致sys section屬性有幾個特殊地方
1、可換出和禁止換出
在內存不足時,系統內存管理器,會枚舉已加載的section object, 如果存在
pageout屬性,那么系統內存管理器就會換出這個節對應的頁(這個節經過系統頁對
齊后換出內存)節對齊原則VirtualAddress向上、VirtualSize向下。禁止換出如
名字所名這個節將永駐內存。
2、節對齊小于一頁
大多數sys的節對齊指數都是小于一頁,系統加載器在處理這類文件時相當很
簡單。加載后文件和磁盤上的文件布局基本一致。當節對齊小于一頁時,
SizeOfRawData必須大于等于VirtualSize,即不支持未初始化節。mimisys通過增
加SizeOfImage在文件加載后分配一個未初始化的緩沖區,保證解壓過程。
(2) checksum校驗
一句話: 只有正確的checksum sys文件才允許被加載。
(3) win2k相關問題
win2k的系統加載器和其他nt系統有幾處不同,r3和r0都會有一些區別,比如r3 pe
的必須要有導入表,否則拒絕加載,r0 pe必須要有重定位信息,否則也會拒絕加載。這
種情況需要構造一個空的重定位目錄即可。
mimisys采取的是合并節,導致加殼后只剩下兩個節,第一個節是loader, 存放
loader和各種加殼參數,第二個節是原程序優化壓縮后的數據(移動重定位,移動資源等
等)。兩個節的屬性都是不允許換出的。
四、elfp (x86 linux r3 elf packer)
資料:
Tool Interface Standard (TIS) Executable and Linking Format
http://www.x86.org/ftp/manuals/tools/elf.pdf
毛德操 《漫談內核兼容》8,9 ELF映像的裝入
http://linux.insigma.com.cn/jszl.asp?docid=132762762
http://linux.insigma.com.cn/jszl.asp?docid=133617926
linux 內核源碼
linux/fs/binfmt_elf.c:load_elf_binary
工具:
objdump 進行elf文件格式的結構查看
http://www.gnu.org/software/binutils/binutils.html
ald 匯編級調試器,gdb無法調試沒有調試信息文件的。
http://ald.sourceforge.net/
elfp是在magiclinux完成的linux elf文件壓縮殼。
elf的格式是linux下主要的可執行文件格式,它也是在coff上基礎上設計的,所以它和
pe文件的格式很相似,下面的敘述過程中會和 pe 文件以對比形式進行描述。
elf文件的第一個數據結構是以Elf32_Ehdr開始
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;
e_ident 在 elf.h 中對應的 ELFMAG 宏,長度是四個字節
e_entry 入口點映像偏移(映像偏移即 pe 中所說的 rva)
e_phoff Elf32_Phdr 數組的文件偏移
e_shoff Elf32_Shdr 數組的文件偏移
e_ehsize Elf32_Ehdr 結構的大小
e_phentsize Elf32_Phdr 結構大小
e_phnum Elf32_Phdr 數組成員個數
e_shentsize Elf32_Shdr 結構大小
e_shnum Elf32_Shdr 數組成員個數
在Elf32_Ehdr之后即Elf32_Phdr數組,Elf32_Phdr的地址通過Elf32_Ehdr.e_ehsize來
確定。Elf32_Ehdr數組(或叫段表),你可以把phdr看做pe的節表。
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 */
} Elf32_Phdr;
p_type 描述這個段的加載行為屬性
p_offset 段數據在文件中偏移,類似pe節中的PointerToRawData
p_vaddr 段數據加載后在映像中偏移, 類似pe節中的VirtualAddress
p_filesz 段數據在文件中大小,類型pe節中的SizeOfRawData
p_memsz 段數據加載后在映像中大小,類型pe節中的VirtualSize
p_flags 描述這個段的內存屬性,類似pe節中的節屬性
p_align 段對齊粒度
p_type主要的類型有
PT_LOAD 這個段需要裝載到內存中
PT_PHDR 這個段存放的是Elf32_Phdr數組
PT_INTERP 這個段存放一個解釋器名,請求系統加載器把映像裝載需求轉給這
個解釋器,關于elf的解釋器問題,可以理解為windows下ntdll裝載
pe文件,elf文件的解釋器主要負責重定位、導入表填充等操作
p_flags 主要類型有
PF_X 這個段可執行
PF_W 這個段可寫
PF_R 這個段可讀
在Elf32_Ehdr(段表)之后便是Elf32_Shdr數組(節表),你可能到這里很奇怪了,怎么這
個叫節表?如果你熟悉pe應該知道節表對pe文件的重要性,但這個可不是pe中的那個節表,你
應該把它看做nt header中data_dir[]結構,加載器或調試器等工具會通過節名確定節具體
用途,比如存儲調試信息、版本信息、字符串表等等、elfp在加殼過程會丟棄節表。
下面簡單講講elfp的loader處理過程(加殼過程很簡單),在一個elf文件被加載后,它的
入口點在執行之前,堆棧中會由系統加載器push的一些參數。
// 堆棧結構:
// +-------------------+
// | return address | 返回地址
// +-------------------+
// | argc | 參數個數
// +-------------------+
// | argv[?], NULL | 參數表,以 NULL 結尾
// +-------------------+
// | envp[?], NULL | 環境表,以 NULL 結尾
// +-------------------+
// | auxv[?] | 中文不知道叫什么它,這個主要是給解釋器使用,
// +-------------------+ 存放這個elf的相關信息如果被加殼程序需要解
釋器,你需要重寫正確填寫這個參數,讓解釋器可
以正確的找到相關數據的地址。
elfp殼loader的執行流程大致如下:
申請內存-->把每個段解壓到指定的地址上-->獲取被加殼程序原始信息-->檢查原
始段表、重寫 auxv-->加載解釋器-->調用解釋器
關于 elf 的解釋器,可以參考資料鏈接上的文字,那里比我描述的完整。
五、附錄
[1] 本文代碼
./pstzine_0A_01.zip
-EOF-