菜鳥脫殼之脫殼的基礎(chǔ)知識(shí):脫殼技術(shù)
一、基礎(chǔ)知識(shí)
殼的定義:在一些計(jì)算機(jī)軟件里也有一段專門負(fù)責(zé)保護(hù)軟件不被非法修改或反編譯的程序。它們一般都是先于程序運(yùn)行,拿到控制權(quán),然后完成它們保護(hù)軟件的任務(wù)。由于這段程序和自然界的殼在功能上有很多相同的地方,基于命名的規(guī)則,大家就把這樣的程序稱為“殼”了,無非是保護(hù)、隱蔽殼內(nèi)的東西。而從技術(shù)的角度出發(fā),殼是一段執(zhí)行于原始程序前的代碼。原始程序的代碼在加殼的過程中可能被壓縮、加密……。當(dāng)加殼后的文件執(zhí)行時(shí),殼-這段代碼先于原始程序運(yùn)行,他把壓縮、加密后的代碼還原成原始程序代碼,然后再把執(zhí)行權(quán)交還給原始代碼。 軟件的殼分為加密殼、壓縮殼、偽裝殼、多層殼等類,目的都是為了隱藏程序真正的OEP(入口點(diǎn),防止被破解)。
1.1.1 殼的加載過程
1.保存入口函數(shù)
加殼的程序首先會(huì)初始化所有寄存器的值,在外殼執(zhí)行完畢后,會(huì)恢復(fù)各個(gè)寄存器的內(nèi)容,再跳到OEP去執(zhí)行程序!通常加殼程序會(huì)用pushad / pushfd進(jìn)行保存現(xiàn)場(chǎng),用popad / popfd來恢復(fù)現(xiàn)場(chǎng),例如:我用delphi7.0程序加了一個(gè)UPX的殼!他的入口就是利用Pushad來保存的現(xiàn)場(chǎng)!
004629D0 > 60 pushad //利用pushad來進(jìn)行保存現(xiàn)場(chǎng)
004629D1 BE 00F04300 mov esi, 0043F000
004629D6 8DBE 0020FCFF lea edi, dword ptr [esi+FFFC2000]
004629DC C787 9CC00400 7> mov dword ptr [edi+4C09C], 46CD167B
004629E6 57 push edi
004629E7 83CD FF or ebp, FFFFFFFF
004629EA EB 0E jmp short 004629FA
在UPX的外殼結(jié)尾:
00462B5F 8D87 1F020000 lea eax, dword ptr [edi+21F]
00462B65 8020 7F and byte ptr [eax], 7F
00462B68 8060 28 7F and byte ptr [eax+28], 7F
00462B6C 58 pop eax
00462B6D 50 push eax
00462B6E 54 push esp
00462B6F 50 push eax
00462B70 53 push ebx
00462B71 57 push edi
00462B72 FFD5 call ebp
00462B74 58 pop eax
00462B75 61 popad //利用popad把所有的寄存器都恢復(fù)!
2.獲取殼自己所需要的函數(shù)
一般外殼的輸入表都只有GetMoudleHandleA、GetProcAddress和LoadLibrary這幾個(gè)API函數(shù)!有的甚至只有Kernel32.dll以及GetProcAddress,如果殼程序需要加載其他的函數(shù),就可以調(diào)用LoadLibrary將DLL映射到調(diào)用進(jìn)程的地址空間中,函數(shù)返回的hinstance值用于標(biāo)識(shí)文件映像映射到虛擬內(nèi)存地址:我們查看MSDN,找到LoadLibrary函數(shù)的原型:
HINSTANCE LoadLibrary( LPCTSTR lpLibFileName // address of filename of executable module);
當(dāng)DLL文件已經(jīng)被映射到調(diào)用進(jìn)程的地址空間中,就可以掉用GetMoudleHandleA了,調(diào)用此函數(shù)可以活的DLL模塊的句柄,GetMoudleHandleA的函數(shù)原型是:
HMODULE GetModuleHandle (
LPCTSTR lpModuleName);
當(dāng)Dll模塊被加載后,就可以調(diào)用GetProcAddress函數(shù)來獲取輸入函數(shù)的地址,GetProcAddress函數(shù)的原型是:
FARPROC GetProcAddress( HMODULE hModule, // handle to DLL module LPCSTR lpProcName // name of function );
經(jīng)過這三個(gè)函數(shù)的調(diào)用,就可以獲得想要的函數(shù)API,外殼中所用到的其他的API基本都是利用這三個(gè)函數(shù)的搭配來進(jìn)行調(diào)用的!但是有的高強(qiáng)度的加密殼,作者可能連最基本的GetProcAddress都不用,而是自己編寫一個(gè)類似與GetProcAddress函數(shù)的函數(shù)來進(jìn)行替換,這樣可以使函數(shù)使用的隱蔽性大大提高!
舉個(gè)例子,我用Delphi7.0加的UPX的殼,我們來看看他的外殼是不是用的我們上文提到的幾個(gè)函數(shù):
UPX的外殼用到了LoadLibrary、GetProcAddresss等函數(shù),和我們上文提到的函數(shù)差不多!
3.解密各個(gè)加殼軟件的區(qū)段的數(shù)據(jù)
殼會(huì)保護(hù)被加殼程序的區(qū)塊的數(shù)據(jù)和代碼,會(huì)加密被加殼程序的各個(gè)區(qū)塊,在程序執(zhí)行時(shí),外殼會(huì)對(duì)這些被加密的數(shù)據(jù)進(jìn)行解密,從而能夠讓被加密的程序可以正常運(yùn)行!殼會(huì)按照被加密的區(qū)塊的順序進(jìn)行解密,把解密的區(qū)塊的數(shù)據(jù)按照區(qū)塊的定義放在合適的內(nèi)存位置!
4.初始化IAT
程序在加殼的時(shí)候,加殼程序自己構(gòu)造了一個(gè)輸入表,并把PE頭中的輸入表的指針指向了自己構(gòu)造了的輸入表,所以,PE裝載器會(huì)對(duì)自己建立的輸入表進(jìn)行了填寫,那么原來的IAT是通過PE裝載器來實(shí)現(xiàn)的,可是現(xiàn)在就只能依靠外殼來填寫PE輸入表,外殼只需要對(duì)新輸入表結(jié)構(gòu)從頭到尾掃描一遍,對(duì)每個(gè)DLL的引入的所有函數(shù)重新獲取地址,并填寫IAT!
5.跳到OEP
當(dāng)外殼的執(zhí)行完畢后,會(huì)跳到原來的程序的入口點(diǎn),即Entry Point,也可以稱作OEP!當(dāng)一般加密強(qiáng)度不是很大的殼,會(huì)在殼的末尾有一個(gè)大的跨段,跳向OEP,類似一個(gè)殼與程序入口點(diǎn)的“分界線”!例如:
00462B74 58 pop eax
00462B75 61 popad
00462B76 8D4424 80 lea eax, dword ptr [esp-80]
00462B7A 6A 00 push 0
00462B7C 39C4 cmp esp, eax
00462B7E ^ 75 FA jnz short 00462B7A
00462B80 83EC 80 sub esp, -80
00462B83 ^ E9 109FFEFF jmp 0044CA98 //大的跨段,此時(shí)的EIP是00462B83,而將要跳到的是0044CA98,說明這里是一個(gè)大的跨段,是個(gè)分界點(diǎn)!
有的加密殼會(huì)把程序的入口點(diǎn)挪到殼段,并把這段代碼給清除掉,就是我們稱作的“Stolen Code”,這樣殼與程序就部分彼此,加大了對(duì)脫殼的難度。(當(dāng)然,我們可以把清除掉的OEP補(bǔ)回來!然后再進(jìn)行脫殼!有的殼可能會(huì)利用虛擬機(jī)將入口出給VM掉,都是需要我們手動(dòng)修復(fù)的!)
總結(jié):
殼的加載過程:首先是保存現(xiàn)場(chǎng) ==》獲取殼程序自己所需要的函數(shù)(請(qǐng)記住這幾個(gè)函數(shù)) ==》解密數(shù)據(jù) ==》初始化IAT ==》跳到OEP ==》進(jìn)行脫殼、修復(fù)。