菜鳥脫殼之脫殼的基礎知識:脫殼技術
一、基礎知識
殼的定義:在一些計算機軟件里也有一段專門負責保護軟件不被非法修改或反編譯的程序。它們一般都是先于程序運行,拿到控制權,然后完成它們保護軟件的任務。由于這段程序和自然界的殼在功能上有很多相同的地方,基于命名的規則,大家就把這樣的程序稱為“殼”了,無非是保護、隱蔽殼內的東西。而從技術的角度出發,殼是一段執行于原始程序前的代碼。原始程序的代碼在加殼的過程中可能被壓縮、加密……。當加殼后的文件執行時,殼-這段代碼先于原始程序運行,他把壓縮、加密后的代碼還原成原始程序代碼,然后再把執行權交還給原始代碼。 軟件的殼分為加密殼、壓縮殼、偽裝殼、多層殼等類,目的都是為了隱藏程序真正的OEP(入口點,防止被破解)。
1.1.1 殼的加載過程
1.保存入口函數
加殼的程序首先會初始化所有寄存器的值,在外殼執行完畢后,會恢復各個寄存器的內容,再跳到OEP去執行程序!通常加殼程序會用pushad / pushfd進行保存現場,用popad / popfd來恢復現場,例如:我用delphi7.0程序加了一個UPX的殼!他的入口就是利用Pushad來保存的現場!
004629D0 > 60 pushad //利用pushad來進行保存現場
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的外殼結尾:
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把所有的寄存器都恢復!
2.獲取殼自己所需要的函數
一般外殼的輸入表都只有GetMoudleHandleA、GetProcAddress和LoadLibrary這幾個API函數!有的甚至只有Kernel32.dll以及GetProcAddress,如果殼程序需要加載其他的函數,就可以調用LoadLibrary將DLL映射到調用進程的地址空間中,函數返回的hinstance值用于標識文件映像映射到虛擬內存地址:我們查看MSDN,找到LoadLibrary函數的原型:
HINSTANCE LoadLibrary( LPCTSTR lpLibFileName // address of filename of executable module);
當DLL文件已經被映射到調用進程的地址空間中,就可以掉用GetMoudleHandleA了,調用此函數可以活的DLL模塊的句柄,GetMoudleHandleA的函數原型是:
HMODULE GetModuleHandle (
LPCTSTR lpModuleName);
當Dll模塊被加載后,就可以調用GetProcAddress函數來獲取輸入函數的地址,GetProcAddress函數的原型是:
FARPROC GetProcAddress( HMODULE hModule, // handle to DLL module LPCSTR lpProcName // name of function );
經過這三個函數的調用,就可以獲得想要的函數API,外殼中所用到的其他的API基本都是利用這三個函數的搭配來進行調用的!但是有的高強度的加密殼,作者可能連最基本的GetProcAddress都不用,而是自己編寫一個類似與GetProcAddress函數的函數來進行替換,這樣可以使函數使用的隱蔽性大大提高!
舉個例子,我用Delphi7.0加的UPX的殼,我們來看看他的外殼是不是用的我們上文提到的幾個函數:
UPX的外殼用到了LoadLibrary、GetProcAddresss等函數,和我們上文提到的函數差不多!
3.解密各個加殼軟件的區段的數據
殼會保護被加殼程序的區塊的數據和代碼,會加密被加殼程序的各個區塊,在程序執行時,外殼會對這些被加密的數據進行解密,從而能夠讓被加密的程序可以正常運行!殼會按照被加密的區塊的順序進行解密,把解密的區塊的數據按照區塊的定義放在合適的內存位置!
4.初始化IAT
程序在加殼的時候,加殼程序自己構造了一個輸入表,并把PE頭中的輸入表的指針指向了自己構造了的輸入表,所以,PE裝載器會對自己建立的輸入表進行了填寫,那么原來的IAT是通過PE裝載器來實現的,可是現在就只能依靠外殼來填寫PE輸入表,外殼只需要對新輸入表結構從頭到尾掃描一遍,對每個DLL的引入的所有函數重新獲取地址,并填寫IAT!
5.跳到OEP
當外殼的執行完畢后,會跳到原來的程序的入口點,即Entry Point,也可以稱作OEP!當一般加密強度不是很大的殼,會在殼的末尾有一個大的跨段,跳向OEP,類似一個殼與程序入口點的“分界線”!例如:
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 //大的跨段,此時的EIP是00462B83,而將要跳到的是0044CA98,說明這里是一個大的跨段,是個分界點!
有的加密殼會把程序的入口點挪到殼段,并把這段代碼給清除掉,就是我們稱作的“Stolen Code”,這樣殼與程序就部分彼此,加大了對脫殼的難度。(當然,我們可以把清除掉的OEP補回來!然后再進行脫殼!有的殼可能會利用虛擬機將入口出給VM掉,都是需要我們手動修復的!)
總結:
殼的加載過程:首先是保存現場 ==》獲取殼程序自己所需要的函數(請記住這幾個函數) ==》解密數據 ==》初始化IAT ==》跳到OEP ==》進行脫殼、修復。