C/C++ 動態解密釋放ShellCode
今天在複習《加密與解密》時,在軟體保護這一章中有一個程式碼與資料結合的案例,其原理是將程式碼段中的程式碼進行xor異或加密處理以後回寫到原始位置,當程式執行後將此處的內容動態的進行解密,解密後回寫替換回原始記憶體位置,這樣就能實現記憶體載入。
由此案例我想到一個關於免殺的利用思路,首先殺軟的運作方式多數為特徵碼查殺,當我們程式中使用了敏感的函式時,就會存在被殺的風險,而如果將程式碼段中的程式碼進行加密,需要時直接在記憶體中解密,那麼殺軟將無法捕捉硬碟檔案的特徵,從而可以規避殺軟針對硬碟特徵的查殺手法。
經過閱讀該案例的原始碼,我首先提取出了案例中的核心程式碼,並加以改進後將其從軟體保護改為了免殺手法,其註冊碼生成工具核心程式碼如下所示,這裡我沒有動使用原始的加密工具即可。
for ( i=0;i<strlen(szBuffer);i++) { k = k*6 + szBuffer[i]; } Size=address2-address1; Size=Size/0x4; //加密時,每次異或 DWORD資料,Size是為最終需要異或的次數 offset=address1; for (i=0;i<Size;i++) { SetFilePointer(hFile,offset,NULL,FILE_BEGIN); ReadFile(hFile,szBuffer, 4, &szTemp, NULL);//讀取DWORD位元組的檔案內容 ptr=(DWORD*)szBuffer; *ptr=(*ptr)^k; SetFilePointer(hFile,offset,NULL,FILE_BEGIN); if(!WriteFile(hFile,ptr,4,&nbWritten,NULL))// 寫入檔案 { MessageBox(NULL,"Error while patching !","Patch aborted",MB_ICONEXCLAMATION); CloseHandle(hFile); return 1; } offset=offset+4; } CloseHandle(hFile); MessageBox(NULL,"Patch successfull !","Patch",MB_ICONINFORMATION); return 1; }
下面則是客戶端解密程式碼,該程式碼的原始部分是註冊機加密,我把它抽取出來改成了這個樣子,首先使用__asm mov AddressA, offset BeginOEP
定義兩個段標籤,分別用於表示段的開始與結束,也就是我們需要加密與解密的程式碼段位置,在兩個標籤內部的就是我們的惡意程式碼,將其寫入到標籤中,標籤中的__asm inc eax dec eax
則是一串標誌用於快速定位到需要加密的位置。
#include <stdio.h> #include <Windows.h> #include <tchar.h> void Decrypt(DWORD*, DWORD, DWORD); void Decrypt(DWORD* pData, DWORD Size, DWORD value) { //首先要做的是改變這一塊虛擬記憶體的記憶體保護狀態,以便可以自由存取程式碼 MEMORY_BASIC_INFORMATION mbi_thunk; //查詢頁資訊 VirtualQuery(pData, &mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION)); //改變頁保護屬性為讀寫。 VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize, PAGE_READWRITE, &mbi_thunk.Protect); Size = Size / 0x4; //對資料共需要異或的次數 //解密begindecrypt與enddecrypt標籤處的資料 while (Size--) { *pData = (*pData) ^ value; pData++; } //恢復頁的原保護屬性。 DWORD dwOldProtect; VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize, mbi_thunk.Protect, &dwOldProtect); } int main(int argc, char* argv[]) { DWORD AddressA, AddressB, Size, key; DWORD *ptr; TCHAR cCode[30] = { 0 }; __asm mov AddressA, offset BeginOEP __asm mov AddressB, offset EndOEP Size = AddressB - AddressA; ptr = (DWORD*)AddressA; _tcscpy(cCode, L"lyshark"); // 設定加密金鑰 key = 1; for (unsigned int i = 0; i< lstrlen(cCode); i++) { key = key * 6 + cCode[i]; } Decrypt(ptr, Size, key); //執行解密函式 BeginOEP: __asm inc eax // 在十六進位制工具中對應0x40 __asm dec eax // 在十六進位制工具中對應0x48 MessageBoxA(0, "hello lyshark", 0, 0); MessageBoxA(0, "hello lyshark", 0, 0); EndOEP: __asm inc eax __asm dec eax return 0; }
程式在執行時,首先會迴圈計算異或金鑰,計算完成後執行Decrypt函式,對特定的段進行解密後,釋放到原始檔中(注意是記憶體中)然後在呼叫執行,打印出一句問候語hello lyshark
程式結束。
注意:編譯時,請關閉DEP,ASLR,地址隨機化等保護,否則VA不固定,無法確定位置。
首先我們需要編譯上方魔改版的程式碼片段,然後使用winhex然後按下【ctrl+alt+X】輸入4048找到開始於結束的位置。
這裡我們記下,需要加密的開始位置是【526】結束位置是【54b】中間程式碼部分就是我們需要加密的惡意程式碼。
接著開啟Encrypter.exe工具依次輸入加密開始結束位置與金鑰,這裡設定如下即可。
開啟程式執行,會首先經過解密函式將加密後的程式碼片段釋放到記憶體中,然後才會執行彈窗,非常的安全。
反彙編看一下,解密前,程式碼是混亂的,根本不是程式碼。
而執行解密後,記憶體中立刻恢復到了可以執行的程式碼狀態,然後就可以開心的執行下去了。
此方法也可以規避部分逆向分析,由於不是彙編程式碼,所以也就無法分析出到底是做什麼的了,當然了,如果能找到加密演算法的金鑰,同樣可以解密出來,此處我們並不是用來防範解密者的,而是用來切斷程式中的病毒特徵的。