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