0x01 前言作為一名安全菜鳥,,單純的了解某一個(gè)方面是并不合格的,,安全并不僅限于某一門語言、某一個(gè)OS,,現(xiàn)如今安全研究的技術(shù)棧要求的更深,、更廣。雖說 PE 文件內(nèi)存加載已經(jīng)是多年前的技術(shù),,但是招不在新,、有用就行,內(nèi)存加載技術(shù)仍然有非常廣泛的應(yīng)用(隱藏自身,,至于為什么要隱藏自身,,dddd),由于筆者之前認(rèn)知的偏差導(dǎo)致對(duì)PE相關(guān)的知識(shí)僅停留在知道的地步,,并沒有靜下心來去認(rèn)真分析學(xué)習(xí),,借此機(jī)會(huì)補(bǔ)足一下技術(shù)點(diǎn),同時(shí)順便為自己的惡意代碼分析的學(xué)習(xí)之旅開個(gè)頭,。 0x02 關(guān)鍵步驟0x1 Section 對(duì)齊因?yàn)閑xe以文件形式存儲(chǔ)的時(shí)候區(qū)段間的對(duì)齊方式與在內(nèi)存中的對(duì)齊方式不盡相同,,因此在手動(dòng)加載exe時(shí)不能單純的將文件格式的 exe 直接拷貝到內(nèi)存中,而是應(yīng)當(dāng)根據(jù)內(nèi)存區(qū)段(page size)的對(duì)齊方式做對(duì)齊處理,。 為了驗(yàn)證一下,,隨便找一個(gè) exe 文件作為學(xué)習(xí)資料。 FileAlignment 為 0x200,,實(shí)際的Section也均是以 0x200 為單位進(jìn)行對(duì)齊的,,實(shí)際調(diào)試時(shí)就會(huì)發(fā)現(xiàn)section的對(duì)齊變?yōu)榱薙ectionAlignment的大?。?x1000 0x2 導(dǎo)入表修復(fù)導(dǎo)入表是PE文件從其它第三方程序中導(dǎo)入API,以供本程序調(diào)用的機(jī)制(與導(dǎo)出表對(duì)應(yīng)),,在exe運(yùn)行起來的時(shí), 加載器會(huì)遍歷導(dǎo)入表, 將導(dǎo)入表中所有dll 都加載到進(jìn)程中,,被加載的DLL的DllMain就會(huì)被調(diào)用,通過導(dǎo)入表可以知道程序使用了哪些函數(shù),,導(dǎo)入表是一個(gè)數(shù)組,,以全為零結(jié)尾。 要理解導(dǎo)入表首先要了解以下這幾個(gè)結(jié)構(gòu): IMAGE_DATA_DIRECTORY IMAGE_DATA_DIRECTORY 位于 IMAGE_Optional_header 中的最后一個(gè)字段,,是一個(gè)由16個(gè)_IMAGE_DATA_DIRECTORY 結(jié)構(gòu)體構(gòu)成的結(jié)構(gòu)體數(shù)組,每個(gè)結(jié)構(gòu)體由兩個(gè)字段構(gòu)成,,分別為VirtualAddress和Size字段:
Offset (PE/PE32 ) Description96/112 Export table address and size104/120 Import table address and size112/128 Resource table address and size120/136 Exception table address and size128/144 Certificate table address and size136/152 Base relocation table address and size144/160 Debugging information starting address and size152/168 Architecture-specific data address and size160/176 Global pointer register relative virtual address168/184 Thread local storage (TLS) table address and size176/192 Load configuration table address and size184/200 Bound import table address and size192/208 Import address table address and size200/216 Delay import descriptor address and size208/224 The CLR header address and size216/232 Reserved 根據(jù)微軟提供的信息,,IMAGE_DATA_DIRECTORY 的第二項(xiàng)指向的就是導(dǎo)入表了,。 IMAGE_IMPORT_DESCRIPTOR 既然已經(jīng)找到了導(dǎo)入表,就需要根據(jù)導(dǎo)入表內(nèi)的元素來加載對(duì)應(yīng)的dll,,獲取不同的函數(shù)地址了,,此時(shí)就需要用到 IMAGE_IMPORT_DESCRIPTOR 結(jié)構(gòu)了,該結(jié)構(gòu)的詳細(xì)內(nèi)容如下:
在修復(fù)IAT的過程中最重要的兩個(gè)字段就是 OriginalFirstThunk 和 FirstThunk了: 0x03 具體分析為了方便理解和記憶,,默認(rèn)讀取的 PE 文件格式不存在問題,,不做錯(cuò)誤處理。 文件讀取文件讀取步驟基本上可以說條條大路通羅馬,,只要將 PE 文件完整的讀取到內(nèi)存中可供后續(xù)處理即可,,除了把一個(gè)文件放在目錄中進(jìn)行讀取以外還有很多種方式,比如將要加載的 exe 轉(zhuǎn)換成shellcode進(jìn)行加載,、將shellcode進(jìn)行簡單xor后在內(nèi)存xor回來再加載,。。,。 ifstream inFile('nc.exe', ios::in | ios::binary);stringstream tmp;tmp << inFile.rdbuf();unsigned char* content = (unsigned char*)tmp.str().c_str(); 初始內(nèi)存分配因?yàn)?exe 文件默認(rèn)有加載基址,,一般情況下在運(yùn)行 exe 的時(shí)候會(huì)首先嘗試加載到默認(rèn)地址上去,因此就要根據(jù) exe 的默認(rèn)加載基址和映像大?。╡xe加載到內(nèi)存后的大?。篠izeOfImage)來分配內(nèi)存
分配內(nèi)存時(shí)的 VirtualAlloc指定的頁類型為 MEM_COMMIT|MEM_RESERVE這里是一個(gè)小小的延遲分配的知識(shí)點(diǎn),如果是 MEM_RESERVE的話只有當(dāng)對(duì)該段內(nèi)存進(jìn)行內(nèi)存操作時(shí)才會(huì)被真正Load進(jìn)入物理內(nèi)存中,。頁權(quán)限使用的是PAGE_EXECUTE_READWRITE,,這在實(shí)際編碼過程中是一個(gè)很不好的習(xí)慣,為了更清晰的理解 exe 內(nèi)存加載的核心流程,,就省略了根據(jù)section來確定內(nèi)存權(quán)限的步驟,。 拷貝Header分配內(nèi)存完畢后首先要將 PE header 拷貝到相應(yīng)的地址空間去,,因?yàn)楹罄m(xù)的操作均需要用到。 memcpy(memExeBase, content, pFileNtHeader->OptionalHeader.SizeOfHeaders);PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)memExeBase;PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(memExeBase pDosHeader->e_lfanew); 之后要根據(jù)新分配的內(nèi)存地址計(jì)算新的 DOS頭 和 NT頭,。 修復(fù)ImageBase因?yàn)橐呀?jīng)根據(jù)ImageBase分配了內(nèi)存,,所以需要將拷貝后的OptionalHeader中的ImageBase字段根據(jù)實(shí)際內(nèi)存地址進(jìn)行更新,如果開啟了aslr的話需要根據(jù)實(shí)際的內(nèi)存地址更新ImageBase,,分配到默認(rèn)基址上的話沒有必要,。
拷貝區(qū)段拷貝區(qū)段這部分是內(nèi)存加載的第一個(gè)關(guān)鍵點(diǎn),要根據(jù)內(nèi)存頁的大小來將原本的文件區(qū)段進(jìn)行處理,。在文件中Section通常以 0x200 進(jìn)行對(duì)齊,,內(nèi)存中頁大小單位為 0x1000, 因此內(nèi)存對(duì)齊單位為 0x1000,,所以當(dāng)PE文件加載到內(nèi)存中后需要對(duì)Section進(jìn)行變換,。 // 拷貝區(qū)段 PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(pNtHeader); int sectionSize; for (int i = 0; i < pNtHeader->FileHeader.NumberOfSections; i , section ) { if (section->SizeOfRawData == 0) { sectionSize = pNtHeader->OptionalHeader.SectionAlignment; // 最小內(nèi)存Seciton單位為 SectionAlignment的大小 } else { sectionSize = section->SizeOfRawData; } if (sectionSize > 0) { void* dest = memExeBase section->VirtualAddress; memcpy(dest, content section->PointerToRawData, sectionSize); } } 修復(fù)導(dǎo)入表OriginalFirstChunk指向的INT表表項(xiàng)以4字節(jié)為單位,全0結(jié)尾,,如果最高位為1則代表是函數(shù)序號(hào),,反之則是一個(gè)RVA,指向IMAGE_IMPORT_BY_NAME結(jié)構(gòu),,INT表實(shí)際上的功能是獲取要導(dǎo)入的函數(shù)名,。目前沒有遇到最高位為1的情況。
IAT表項(xiàng)則直接是一個(gè)指向真實(shí)函數(shù)地址的指針,。 PIMAGE_IMPORT_DESCRIPTOR pImportDesc;bool result = true;PIMAGE_DATA_DIRECTORY pDataDir = (PIMAGE_DATA_DIRECTORY)(&pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]); // 獲取IMAGE_DATA_DIRECTORY 位置pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)(memExeBase pDataDir->VirtualAddress);// 獲取第一個(gè)IMAGE_IMPORT_DESCRIPTORfor (;!IsBadReadPtr(pImportDesc,sizeof(IMAGE_IMPORT_DESCRIPTOR)) && pImportDesc->Name;pImportDesc ) { uintptr_t* thunkRef; FARPROC* funcRef; HMODULE handle = LoadLibraryA((LPCSTR)(memExeBase pImportDesc->Name)); // 加載dll(此處也是可以手工加載的) if (pImportDesc->OriginalFirstThunk) { thunkRef = (uintptr_t*)(memExeBase pImportDesc->OriginalFirstThunk); funcRef = (FARPROC*)(memExeBase pImportDesc->FirstThunk); } else { thunkRef = (uintptr_t*)(memExeBase pImportDesc->FirstThunk); funcRef = (FARPROC*)(memExeBase pImportDesc->FirstThunk); } for (; *thunkRef; thunkRef , funcRef ) { if (IMAGE_SNAP_BY_ORDINAL(*thunkRef)) { // 判斷OriginalFirstThunk表項(xiàng)最高位為1的情況 *funcRef = GetProcAddress(handle, (LPCSTR)(IMAGE_ORDINAL(*thunkRef)));//修復(fù)導(dǎo)入表 } else { PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME)(memExeBase (*thunkRef)); *funcRef = GetProcAddress(handle, (LPCSTR)&thunkData->Name); //修復(fù)導(dǎo)入表 } if (*funcRef == 0) { cout << ' error import ' << endl; exit(0); } }} 上述修復(fù)導(dǎo)入表的代碼實(shí)際完成的工作就是遍歷導(dǎo)入表中的 IMAGE_IMPORT_DESCRIPTOR結(jié)構(gòu),,根據(jù)dll名稱將對(duì)應(yīng)的dll加載到內(nèi)存中,并根據(jù)OriginalFirstThunk字段來獲取所需的函數(shù)名進(jìn)而使用GetProcAddress來獲取該函數(shù)在內(nèi)存中的實(shí)際地址填充到FirstThunk字段指向的空間中,。 獲取入口點(diǎn)啟動(dòng)程序
結(jié)語至此exe的內(nèi)存加載就已經(jīng)結(jié)束了,,誘發(fā)我寫下這篇文章的一個(gè)主要原因是回憶起之前看過的幾篇APT相關(guān)的分析文章,涉及到主機(jī)的遠(yuǎn)控目前內(nèi)存加載已經(jīng)是標(biāo)配,,殺軟動(dòng)態(tài)檢測的對(duì)抗方式種類繁多,,靜態(tài)對(duì)抗的方法以內(nèi)存加載為王。 參考鏈接https://www.cnblogs.com/iBinary/p/9740757.html 本文由D4ck原創(chuàng)發(fā)布 |
|