1. 程式人生 > 實用技巧 >C/C++ 動態解密釋放ShellCode

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工具依次輸入加密開始結束位置與金鑰,這裡設定如下即可。

開啟程式執行,會首先經過解密函式將加密後的程式碼片段釋放到記憶體中,然後才會執行彈窗,非常的安全。

反彙編看一下,解密前,程式碼是混亂的,根本不是程式碼。

而執行解密後,記憶體中立刻恢復到了可以執行的程式碼狀態,然後就可以開心的執行下去了。

此方法也可以規避部分逆向分析,由於不是彙編程式碼,所以也就無法分析出到底是做什麼的了,當然了,如果能找到加密演算法的金鑰,同樣可以解密出來,此處我們並不是用來防範解密者的,而是用來切斷程式中的病毒特徵的。