DLL的遠(yuǎn)程注入技術(shù)是目前Win32病毒廣泛使用的一種技術(shù)。使用這種技術(shù)的病毒體通常位于一個(gè)DLL中,在系統(tǒng)啟動(dòng)的時(shí)候,一個(gè)EXE程序會(huì)將這個(gè)DLL加載至某些系統(tǒng)進(jìn)程(如Explorer.exe)中運(yùn)行。
這樣一來,普通的進(jìn)程管理器就很難發(fā)現(xiàn)這種病毒了,而且即使發(fā)現(xiàn)了也很難清除,因?yàn)橹灰《炯纳倪M(jìn)程不終止運(yùn)行,那么這個(gè)DLL就不會(huì)在內(nèi)存中卸載,用戶也就無法在資源管理器中刪除這個(gè)DLL文件,真可謂一箭雙雕哉。
記得2003年QQ尾巴病毒肆虐的時(shí)候,就已經(jīng)有些尾巴病毒的變種在使用這種技術(shù)了。
到了2004年初,我曾經(jīng)嘗試著仿真了一個(gè)QQ尾巴病毒,但獨(dú)是跳過了DLL的遠(yuǎn)程加載技術(shù)。
直到最近在學(xué)校論壇上看到了幾位朋友在探討這一技術(shù),便忍不住將這一塵封已久的技術(shù)從我的記憶中揀了出來,以滿足廣大的技術(shù)愛好者們。
必備知識(shí)
在閱讀本文之前,你需要了解以下幾個(gè)API函數(shù):
·OpenProcess - 用于打開要寄生的目標(biāo)進(jìn)程。
·VirtualAllocEx/VirtualFreeEx - 用于在目標(biāo)進(jìn)程中分配/釋放內(nèi)存空間。
·WriteProcessMemory - 用于在目標(biāo)進(jìn)程中寫入要加載的DLL名稱。
·CreateRemoteThread - 遠(yuǎn)程加載DLL的核心內(nèi)容,用于控制目標(biāo)進(jìn)程調(diào)用API函數(shù)。
·LoadLibrary - 目標(biāo)進(jìn)程通過調(diào)用此函數(shù)來加載病毒DLL。
在此我只給出了簡(jiǎn)要的函數(shù)說明,關(guān)于函數(shù)的詳細(xì)功能和介紹請(qǐng)參閱MSDN。
示例程序
我將在以下的篇幅中用一個(gè)簡(jiǎn)單的示例Virus.exe來實(shí)現(xiàn)這一技術(shù)。這個(gè)示例的界面如下圖:
首先運(yùn)行Target.exe,這個(gè)文件是一個(gè)用Win32 Application向?qū)傻摹癏ello, World”程序,
用來作為寄生的目標(biāo)進(jìn)程。
然后在界面的編輯控件中輸入進(jìn)程的名稱“Target.exe”,單擊“注入DLL”按鈕,
這時(shí)候Virus.exe就會(huì)將當(dāng)前目錄下的DLL.dll注入至Target.exe進(jìn)程中。
在注入DLL.dll之后,你也可以單擊“卸載DLL”來將已經(jīng)注入的DLL卸載。
模擬的病毒體DLL.dll
這是一個(gè)簡(jiǎn)單的Win32 DLL程序,它僅由一個(gè)入口函數(shù)DllMain組成:
BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved )
{
switch ( fdwReason )
{
case DLL_PROCESS_ATTACH:
{
MessageBox( NULL, _T("DLL已進(jìn)入目標(biāo)進(jìn)程。"), _T("信息"), MB_ICONINFORMATION );
}
break;
case DLL_PROCESS_DETACH:
{
MessageBox( NULL, _T("DLL已從目標(biāo)進(jìn)程卸載。"), _T("信息"), MB_ICONINFORMATION );
}
break;
}
return TRUE;
}
如你所見,這里我在DLL被加載和卸載的時(shí)候調(diào)用了MessageBox,這是用來顯示我的遠(yuǎn)程注入/
卸載工作是否成功完成。而對(duì)于一個(gè)真正的病毒體來說,它往往就是處理DLL_PROCESS_ATTACH事件,在其中加入了啟動(dòng)病毒代碼的部分:
case DLL_PROCESS_ATTACH:
{
StartVirus();
}
break;
注入!
現(xiàn)在要開始我們的注入工作了。首先,我們需要找到目標(biāo)進(jìn)程:
DWORD FindTarget( LPCTSTR lpszProcess )
{
DWORD dwRet = 0;
HANDLE hSnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof( PROCESSENTRY32 );
Process32First( hSnapshot, &pe32 );
do
{
if ( lstrcmpi( pe32.szExeFile, lpszProcess ) == 0 )
{
dwRet = pe32.th32ProcessID;
break;
}
} while ( Process32Next( hSnapshot, &pe32 ) );
CloseHandle( hSnapshot );
return dwRet;
}
這里我使用了Tool Help函數(shù)庫(kù),當(dāng)然如果你是NT系統(tǒng)的話,也可以選擇PSAPI函數(shù)庫(kù)。
這段代碼的目的就是通過給定的進(jìn)程名稱來在當(dāng)前系統(tǒng)中查找相應(yīng)的進(jìn)程,并返回該進(jìn)程的ID。得到進(jìn)程ID后,就可以調(diào)用OpenProcess來打開目標(biāo)進(jìn)程了:
// 打開目標(biāo)進(jìn)程
HANDLE hProcess = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION |
PROCESS_VM_WRITE, FALSE, dwProcessID );
現(xiàn)在有必要說一下OpenProcess第一個(gè)參數(shù)所指定的三種權(quán)限。在Win32系統(tǒng)下,每個(gè)進(jìn)程都擁有自己的4G虛擬地址空間,各個(gè)進(jìn)程之間都相互 獨(dú)立。如果一個(gè)進(jìn)程需要完成跨進(jìn)程的工作的話,那么它必須擁有目標(biāo)進(jìn)程的相應(yīng)操作權(quán)限。在這里,PROCESS_CREATE_THREAD表示我可以通 過返回的進(jìn)程句柄在該進(jìn)程中創(chuàng)建新的線程,也就是調(diào)用CreateRemoteThread的權(quán)限;同理,PROCESS_VM_OPERATION 則表示在該進(jìn)程中分配/釋放內(nèi)存的權(quán)限,也就是調(diào)用VirtualAllocEx/VirtualFreeEx的權(quán)限;PROCESS_VM_WRITE 表示可以向該進(jìn)程的地址空間寫入數(shù)據(jù),也就是調(diào)用WriteProcessMemory的權(quán)限。
至此目標(biāo)進(jìn)程已經(jīng)打開,那么我們?cè)撊绾蝸韺LL注入其中呢?在這之前,我請(qǐng)你看一行代碼,是如何在本進(jìn)程內(nèi)顯式加載DLL的:
HMODULE hDll = LoadLibrary( "DLL.dll" );
那么,如果能控制目標(biāo)進(jìn)程調(diào)用LoadLibrary,不就可以完成DLL的遠(yuǎn)程注入了么?的確是這樣,我們可以通過 CreateRemoteThread將LoadLibrary作為目標(biāo)進(jìn)程的一個(gè)線程來啟動(dòng),這樣就可以完成“控制目標(biāo)進(jìn)程調(diào)用 LoadLibrary”的工作了。到這里,也許你會(huì)想當(dāng)然地寫下類似這樣的代碼:
DWORD dwID;
LPVOID pFunc = LoadLibraryA;
HANDLE hThread = CreateRemoteThread( hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)pFunc,
(LPVOID)"DLL.dll", 0, &dwID );
不過結(jié)果肯定會(huì)讓你大失所望——注入DLL失?。?
那 么現(xiàn)在讓我們來分析一下失敗的原因吧。我是前說過,在Win32系統(tǒng)下,每個(gè)進(jìn)程都擁有自己的4G虛擬地址空間,各個(gè)進(jìn)程之間都是相互獨(dú)立的。在這里,我 們當(dāng)作參數(shù)傳入的字符串"DLL.dll"其實(shí)是一個(gè)數(shù)值,它表示這個(gè)字符串位于Virus.exe地址空間之中的地址,而這個(gè)地址在傳給 Target.exe之后,它指向的東西就失去了有效性。舉個(gè)例子來說,譬如A、B兩棟大樓,我住在A樓的401;那么B樓的401住的是誰我當(dāng)然不能確 定——也就是401這個(gè)門牌號(hào)在B樓失去了有效性,而且如果我想要入住B樓的話,我就必須請(qǐng)B樓的樓長(zhǎng)為我在B樓中安排新的住處(當(dāng)然這個(gè)新的住處是否 401也就不一定了)。
由此看來,我就需要做這么一系列略顯繁雜的手續(xù)——首先在Target.exe目標(biāo)進(jìn)程中分配一段內(nèi)存空間,然后向這段空間寫入我要加載的DLL名稱,最后再調(diào)用CreateRemoteThread。這段代碼就成了這樣:
// 向目標(biāo)進(jìn)程地址空間寫入DLL名稱
DWORD dwSize, dwWritten;
dwSize = lstrlenA( lpszDll ) + 1;
LPVOID lpBuf = VirtualAllocEx( hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE );
if ( NULL == lpBuf )
{
CloseHandle( hProcess );
// 失敗處理
}
if ( WriteProcessMemory( hProcess, lpBuf, (LPVOID)lpszDll, dwSize, &dwWritten ) )
{
// 要寫入字節(jié)數(shù)與實(shí)際寫入字節(jié)數(shù)不相等,仍屬失敗
if ( dwWritten != dwSize )
{
VirtualFreeEx( hProcess, lpBuf, dwSize, MEM_DECOMMIT );
CloseHandle( hProcess );
// 失敗處理
}
}
else
{
CloseHandle( hProcess );
// 失敗處理
}
// 使目標(biāo)進(jìn)程調(diào)用LoadLibrary,加載DLL
DWORD dwID;
LPVOID pFunc = LoadLibraryA;
HANDLE hThread = CreateRemoteThread( hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)pFunc, lpBuf, 0, &dwID );
需要說的有兩點(diǎn),一是由于我要在目標(biāo)進(jìn)程中為ANSI字符串來分配內(nèi)存空間,所以這里凡是和目標(biāo)進(jìn)程相關(guān)的部分,都明確使用了后綴為“A”的API函數(shù) ——當(dāng)然,如果要使用Unicode字符串的話,可以換作后綴是“W”的API;第二,在這里L(fēng)oadLibrary的指針我是取的本進(jìn)程的 LoadLibraryA的地址,這是因?yàn)長(zhǎng)oadLibraryA/LoadLibraryW位于kernel32.dll之中,而Win32下每個(gè)應(yīng) 用程序都會(huì)把kernel32.dll加載到進(jìn)程地址空間中一個(gè)固定的地址,所以這里的函數(shù)地址在Target.exe中也是有效的。
在調(diào)用LoadLibrary完畢之后,我們就可以做收尾工作了:
// 等待LoadLibrary加載完畢
WaitForSingleObject( hThread, INFINITE );
// 釋放目標(biāo)進(jìn)程中申請(qǐng)的空間
VirtualFreeEx( hProcess, lpBuf, dwSize, MEM_DECOMMIT );
CloseHandle( hThread );
CloseHandle( hProcess );
在此解釋一下WaitForSingleObject一句。由于我們是通過CreateRemoteThread在目標(biāo)進(jìn)程中另外開辟了一個(gè)LoadLibrary的線程,所以我們必須等待這個(gè)線程運(yùn)行完畢才能夠釋放那段先前申請(qǐng)的內(nèi)存。
好了,現(xiàn)在你可以嘗試著整理這些代碼并編譯運(yùn)行。運(yùn)行Target.exe,然后開啟一個(gè)有模塊查看功能的進(jìn)程查看工具(在這里我使用我的July)來查看Target.exe的模塊,你會(huì)發(fā)現(xiàn)在注入DLL之前,Target.exe中并沒有DLL.dll的存在:
在調(diào)用了注入代碼之后,DLL.dll就位于Target.exe的模塊列表之中了:
矛盾相生
記得2004年初我將QQ尾巴病毒成功仿真后,有很多網(wǎng)友詢問我如何才能殺毒,不過我都沒有回答——因?yàn)楫?dāng)時(shí)我研究的重點(diǎn)并非病毒的寄生特性。
這一寄生特性直到今天可以說我才仿真完畢,那么,我就將解毒的方法也一并公開吧。
和DLL的注入過程類似,只不過在這里使用了兩個(gè)API:GetModuleHandle和FreeLibrary。出于篇幅考慮,我略去了與注入部分相似或相同的代碼:
// 使目標(biāo)進(jìn)程調(diào)用GetModuleHandle,獲得DLL在目標(biāo)進(jìn)程中的句柄
DWORD dwHandle, dwID;
LPVOID pFunc = GetModuleHandleA;
HANDLE hThread = CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)
pFunc, lpBuf, 0, &dwID );
// 等待GetModuleHandle運(yùn)行完畢
WaitForSingleObject( hThread, INFINITE );
// 獲得GetModuleHandle的返回值
GetExitCodeThread( hThread, &dwHandle );
// 釋放目標(biāo)進(jìn)程中申請(qǐng)的空間
VirtualFreeEx( hProcess, lpBuf, dwSize, MEM_DECOMMIT );
CloseHandle( hThread );
// 使目標(biāo)進(jìn)程調(diào)用FreeLibrary,卸載DLL
pFunc = FreeLibrary;
hThread = CreateRemoteThread( hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)pFunc,
(LPVOID)dwHandle, 0, &dwID );
// 等待FreeLibrary卸載完畢
WaitForSingleObject( hThread, INFINITE );
CloseHandle( hThread );
CloseHandle( hProcess );
用這個(gè)方法可以卸載一個(gè)進(jìn)程中的DLL模塊,當(dāng)然包括那些非病毒體的DLL。
所以,這段代碼還是謹(jǐn)慎使用為好。
在完成卸載之后,如果沒有別的程序加載這個(gè)DLL,你就可以將它刪除了。
聯(lián)系客服