基于隱藏可執(zhí)行鏡像并注入dll保護(hù)鏡像代碼的方法
【技術(shù)領(lǐng)域】
[0001] 本發(fā)明涉及系統(tǒng)底層安全領(lǐng)域,具體涉及到隱藏可執(zhí)行鏡像、防止惡意代碼注入 和保護(hù)API。
【背景技術(shù)】
[0002] 代碼注入通常是線程注入,即另一個(gè)進(jìn)程獲有較高權(quán)限,能夠打開目標(biāo)經(jīng)常的 Token,在目標(biāo)進(jìn)程中開辟一段新的代碼執(zhí)行空間,并且在這寫入劫持dll加載的 ShellCode,然后把這段代碼作為線程函數(shù)進(jìn)行加載。劫持dll在加載的時(shí)候,系統(tǒng)將自動(dòng)調(diào) 用 D1 IMain入 口函數(shù),并且此時(shí) ul_reason_f or_cal 1 的值為DLL_PROCESS_ATTACH。如果此時(shí) DllMain中有做Hook之類的代碼,那么這個(gè)進(jìn)程就能被隨便修改,做一些開發(fā)者不希望發(fā)生 的事。
[0003] 對(duì)于代碼劫持,基本上有兩種方式,第一種是加殼,第二種是代碼虛擬化。但是加 殼只能靜態(tài)保護(hù)代碼,如果一旦殼被脫,那么保護(hù)也就沒有了。并且在加殼的時(shí)候,我們可 以去Hook進(jìn)程中系統(tǒng)dll里的API對(duì)目標(biāo)進(jìn)程進(jìn)行監(jiān)控,所以這種方法只能用于對(duì)安全要求 比較低的場(chǎng)合。然而第二種代碼虛擬化的方式有比較強(qiáng)的保護(hù),但是因?yàn)榇a是在虛擬機(jī) 中運(yùn)行的,所以性能損失很大,如果對(duì)程序運(yùn)行效率有比較高的要求,代碼虛擬化方式是不 可取的。而本發(fā)明就是在兩者之間做了一下折衷,既能動(dòng)態(tài)保護(hù)代碼,也不會(huì)大幅度降低代 碼執(zhí)行效率。
【發(fā)明內(nèi)容】
[0004] 本發(fā)明目的是提供一種基于隱藏可執(zhí)行鏡像并注入dll保護(hù)鏡像代碼的方法,即, 一種加載加密鏡像,并且使用dll動(dòng)態(tài)鏈接庫實(shí)現(xiàn)對(duì)應(yīng)用程序?qū)崿F(xiàn)保護(hù)的方法。
[0005] 所述方法包括以下步驟:
[0006] 1、將目標(biāo)鏡像在內(nèi)存中解密
[0007] 將加密鏡像讀取到內(nèi)存,讀取文件首部的文件頭(例如文件前16字節(jié))來判斷判斷 目標(biāo)鏡像是否是加密鏡像(例如判斷Magic值是否和預(yù)設(shè)值相同),1.1)如果不是指定的加 密鏡像,則放棄加載;1.2)如果是指定的加密鏡像,則執(zhí)行對(duì)應(yīng)的解密(例如,解密的算法基 于快速的xor算法,將文件頭Null作為解密Key,對(duì)文件進(jìn)行解密),然后判斷解密后的數(shù)據(jù) 是否是一個(gè)合法PE鏡像,1.2.1)如果不是一個(gè)合法PE鏡像,則退回到待解密狀態(tài),1.2.2)如 果是一個(gè)合法PE鏡像,則執(zhí)行第2步。
[0008] 其中加密鏡像的擴(kuò)展名使用*.vpe。
[0009] 根據(jù)文件前16字節(jié)判斷目標(biāo)鏡像是否是加密鏡像,如果不是則放棄加載。
[0010] 文件頭的成員列表如下所示,詳細(xì)的結(jié)構(gòu)在圖1中記錄。
[0011] Magic:為一個(gè)校檢碼,這個(gè)值被設(shè)定為(UL0NG) 'ΧΜ0Ε ',然后與常數(shù)0x4fd63al5做 一次異或運(yùn)算所得到的值。
[0012] DecompressLength:記錄了文件原始的長(zhǎng)度,在解壓文件的時(shí)候需要用到。
[0013] Imagelnfo:當(dāng)這個(gè)值為0的時(shí)候,代表目標(biāo)加密鏡像就一個(gè)exe,如果是1,就認(rèn)為 這個(gè)鏡像是dll文件。
[0014] Null:加密的使用的key
[0015] 最初的狀態(tài)下,受保護(hù)的exe是處于加密狀態(tài)的。因?yàn)榧用芎笸耆茐牧薳xe本應(yīng) 該有的PE鏡像格式,是無法使用調(diào)試器打開的。
[0016] 將加密鏡像讀取到內(nèi)存,讀取文件首部的文件頭。先判斷Magic值是否和預(yù)設(shè)值相 同,如果不同,就認(rèn)為這個(gè)鏡像含有不正確的數(shù)據(jù),加載器放棄加載。
[0017] 文件頭之后的數(shù)據(jù)就是壓縮后的文件數(shù)據(jù),這里使用的開源zlib作為壓縮方式, 使用zlib的uncompress進(jìn)行解壓,并且釋放原始的未解壓數(shù)據(jù)。
[0018] 使用Virtual Alloc根據(jù)文件頭中記錄的DecompressLength為解壓后的數(shù)據(jù)分配 內(nèi)存。使用VirtualAlloc是為了使分配的內(nèi)存是頁面對(duì)其的,防止代碼未對(duì)其訪問而造成 的中斷。
[0019] 最后對(duì)解壓后的數(shù)據(jù)做一次解密操作,解密的算法基于快速的x〇r算法,將文件頭 Nul 1作為解密Key,對(duì)文件進(jìn)行解密。
[0020] 解密算法在圖2中有詳細(xì)描述。
[0021] 2、PE鏡像的加載
[0022]裝載程序所需要的dll文件,執(zhí)行鏡像重定位,最后執(zhí)行0ΕΡ(入口點(diǎn)函數(shù)),如果上 面的過程有出錯(cuò),則返回到本步驟2的待裝載狀態(tài)(即PE鏡像的待加載狀態(tài)),如果上面的過 程都沒有出錯(cuò),就執(zhí)行第3步。
[0023]步驟2包括以下子步驟:2.1)加載器會(huì)模擬操作系統(tǒng)的加載器,依次修改PE鏡像的 區(qū)段的屬性,并且對(duì)齊區(qū)段,2.2)執(zhí)行預(yù)加載函數(shù)(比如TLS回調(diào)函數(shù)),加載IAT中對(duì)應(yīng)的 dl 1動(dòng)態(tài)鏈接庫,然后獲取到0ΕΡ(入口點(diǎn)函數(shù)),和2.3)注入保護(hù)dl 1,執(zhí)行入口點(diǎn)函數(shù),PE鏡 像的加載就完成了。
[0024] 一般,所加載的受保護(hù)的PE鏡像是基于加載器進(jìn)程執(zhí)行的,即,受保護(hù)的PE鏡像對(duì) 于操作系統(tǒng)來說是不可見的;優(yōu)選的是,在受保護(hù)的PE鏡像執(zhí)行時(shí)既不是加載器的子進(jìn)程, 又不是加載器的代碼段,只是堆上一塊可執(zhí)行區(qū)段,dumper無法將這個(gè)區(qū)段準(zhǔn)確提取出來。 [0025]此時(shí)加載器會(huì)依次加載區(qū)段。
[0026] Exe鏡像在內(nèi)存中是嚴(yán)格制定方式對(duì)其的,對(duì)其的值在OptionalHeader中的 SectionAlignment成員這里指定的。分配的內(nèi)存應(yīng)該是這個(gè)值的倍數(shù)。
[0027]為了更好地模擬系統(tǒng)PE加載器的加載過程,保護(hù)系統(tǒng)使用的加載函數(shù)都是 VirtualAlloc 這個(gè) API。
[0028] VirtualAl loc分配內(nèi)存最快,分配出的內(nèi)存也是和cpu頁面對(duì)齊的,并且支持虛擬 內(nèi)存,能夠分配超過物理內(nèi)存大小的空間,這個(gè)幾點(diǎn)都滿足現(xiàn)代應(yīng)用程序執(zhí)行的標(biāo)準(zhǔn)。 [0029]復(fù)制代碼鏡像:
[0030] OptionalHeader的詳細(xì)結(jié)構(gòu)記錄在圖3中。
[0031 ] OptionalHeader · ImageBase記錄了exe鏡像的基地址,對(duì)于沒有重定向的exe、來 說,這個(gè)值為0x00400000;
[0032] OptionalHeader · SizeOf Image記錄了 exe鏡像的大小,然后使用VirtualAl loc,在 制定的內(nèi)存地址開始分配該段內(nèi)存。這樣分配是不一定會(huì)成功的。如果加載器本身占用了 這段內(nèi)存,就要考慮讓VirtualAlloc隨機(jī)分配這段內(nèi)存,那么內(nèi)存基址也會(huì)發(fā)生改變。相應(yīng) 的,OptionalHeader中的ImageBase所記錄的值也會(huì)發(fā)生變化。
[0033] 加載Dos Header和PE Header:
[0034] 將DOS Header復(fù)制到內(nèi)存鏡像首部:
[0035] memcpy(headers,dos-header,old-header->0ptionalHeader·SizeOfHeaders);
[0036] 做重定位:
[0037] 如果分配的首地址和OptionalHeader · ImageBase的值不同的話,那么就需要做一 次重定向,不然代碼是無法執(zhí)行的。
[0038] locationDelta=(SIZE-T)(code-old-header->0ptionalHeader·ImageBase);
[0039] 如果Delta不為0,說明需要重定向:
[0040] isRelocated = PerformBaseRelocation(result,locationDelta)
[0041] 建立導(dǎo)入表:
[0042] BuildlmportTable(result)
[0043] 依次遍歷導(dǎo)入的dll項(xiàng):
[0044] !IsBadReadPtr(importDesc,sizeof(IMAGE-IMPORT-DESCRIPTOR))&& importDesc->Name;importDesc++
[0045] 通過名字把這個(gè)dll加載到內(nèi)存:
[0046] loadLibrary((LPCSTR)(codeBase+importDesc->Name),module->userdata)
[0047] 依次遍歷導(dǎo)入的API,在內(nèi)存中獲取到對(duì)應(yīng)的地址,如果地址為0,表示這個(gè)API在 當(dāng)前系統(tǒng)里是不支持的,則加載失敗。
[0048]對(duì)不同地方內(nèi)存區(qū)段做不同的內(nèi)存保護(hù),使用的API為VirtualProtect
[0049] FinalizeSections(result)
[0050] 根據(jù)CPU的頁面大小,再次對(duì)每個(gè)頁面就行對(duì)其操作,并且使用設(shè)置不同的內(nèi)存保 護(hù),讓這些內(nèi)存能以對(duì)應(yīng)的方式訪問。
[0051 ]執(zhí)行TLS回掉函數(shù)
[0052] ExecuteTLS(result)
[0053] TLS回掉函數(shù)是需要在執(zhí)行入口點(diǎn)函數(shù)(0ΕΡ)之前執(zhí)行的。
[0054]獲取到tls的偏移量。
[0055] PIMAGE_TLS_DIRECTORY tls=(PIMAGE_TLS_DIRECT0RY)(codeBase+directory-> VirtualAddress);
[0056] 獲取到TLS開始項(xiàng)的值
[0057]
[0058] 依次執(zhí)行TLS,使得在0ΕΡ被執(zhí)行之前,全部的TLS函數(shù)都被執(zhí)行過了。
[0059]
[0060]獲取exe的0ΕΡ,代碼的執(zhí)行是從這里開始的。
[0061 ] result->exeEntry=(ExeEntryProc)(code + result->headers-> OptionalHeader.AddressOfEntryPoint);
[0062]到此,exe的鏡像初始化工作已經(jīng)完成。
[00