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