從“生成”的這個事實來看,我們可以猜想客戶端程序可能本身綁有一段自定義的二進制數據,這段二進制數據實質上就是一個服務端的模板。在用戶進行了設置工作之后,客戶端就會將設置完成的這些特定數據填充到這個模板之中,然后再通過寫文件的操作將這段已經配置完畢的二進制數據模板生成一個特定的服務端程序,如下圖所示:
打個比方來說,郵局有很多空白的信封。你在買下一個信封之后,在信封上填寫郵政編碼、收件人地址和姓名,然后再將你的信件裝入信封糊好,這就成為了你所特有的一封信件了。——希望我這么解釋能夠讓你明白我下面將要在操作的大致過程。
下面我要實現一個名為“MsgBox生成器”的演示程序,你可以在客戶端進行MessageBox的標題和文本的設置,然后程序就會在C:\下生成一個名為MsgBox.exe的“Hello, World”程序,這個彈出的MessageBox就是你先前在客戶端所設置的那樣。運行界面如下圖:
好了,那么我先來設計生成服務端的功能。為了十足地模仿木馬程序,我使用Win32 ASM來編寫服務端程序,代碼如下:
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.data
szTitle db "Hello", 0
szText db "Hello, World", 0
.code
start:
invoke MessageBox, NULL, addr szText, addr szTitle, MB_OK or MB_ICONINFORMATION
invoke ExitProcess, NULL
end start |
如你所見,這正是Iczelion在他的Win32匯編教程中編寫的那個“Hello, World”,你可以利用MASM32將這段源代碼編譯鏈接,生成一個MsgBox.exe,在此我就利用這個程序來充當我要生成的服務端。
現在需要考慮的問題是,如何將這個MsgBox.exe嵌入到客戶端程序中?一個最好想,也是最直觀的辦法是利用一個BYTE數組來放置MsgBox.exe的二進制數據(CIH病毒就是這樣,它專門準備了一個PE文件頭),然后再使用API函數CreateFile將這段二進制數據寫成文件。部分代碼如下:
BYTE bySrvData[] = {
0x4d, 0x5a, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 由于篇幅略去以下所有數據-_-#...
};
BOOL CreateServer()
{
HANDLE hFile;
DWORD dwWritten;
hFile = CreateFile( "C:\\MsgBox.exe", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL );
if
( hFile == NULL )
return
FALSE;
WriteFile( hFile, (LPCVOID)bySrvData,
sizeof
( bySrvData ), &dwWritten, NULL );
CloseHandle( hFile );
return
TRUE;
} |
好了,代碼到此為止。雖然它可以勝利完成任務,但是你一定不愿意編寫這樣的代碼——對于MsgBox.exe的數據部分,它實在太冗長了(我算了一筆賬,對于編譯完成的MsgBox.exe,它只有不到3KB的大小,但是如果把它的二進制數據像上面那樣保存成文本文件,這個文本文件就會擁有15KB的體積)。況且,這僅僅是一個“Hello, World!”程序,那么換成真正的木馬服務端程序又會怎么樣呢?而且,很多的服務端是用高級語言編寫的,它們動輒會擁有幾十KB的體積!
現在我們只能別無選擇地換一個角度考慮如何來解決這個問題:除了用BYTE數組,還有別的方法可以將MsgBox.exe放入客戶端程序中嗎?
答案是肯定的,那就是利用EXE文件的自定義資源。在解說如何使用自定義資源之前,我先大體介紹一下我要使用的幾個API:
FindResource:查找一個資源。由于我們是要將這個服務端作為EXE的自定義資源形式放在EXE之中,所以在釋放它之前必須首先找到它才可以。
SizeofResource:獲得資源的尺寸,裝載資源時使用。
LoadResource:裝載資源,將資源的二進制數據裝載到內存中。
LockResource:鎖定資源,將內存中的資源數據鎖定。
好了,你可以通過查閱MSDN來了解這幾個函數的詳細功能及參數,這里我就不贅述了。我的整個思路如下:
將MsgBox.exe作為客戶端程序的二進制資源一同編譯。
在生成MsgBox.exe服務端的時候,使用以上的幾個API函數讀取這一段二進制資源數據。
將這段二進制數據保存為文件。
現在我來實現第1步。首先你將MsgBox.exe改名為MsgBox.bin作為一個二進制文件,并將這個文件放入客戶端源代碼的文件夾下。然后,向客戶端的資源腳本(.rc文件)中導入這個二進制資源,如下圖所示:
這時候,VC會彈出一個對話框提示,如下圖:
你可以在“Resource Type”中隨意填寫你的資源類型,這個類型名稱就是我們將要在FindResource函數的第三個參數lpType中使用的資源類型,我這里以“Server”為例。
這樣,我就能夠以資源的方式來使用這段數據了。我的代碼如下:
BOOL CreateServer()
{
HRSRC hResInfo;
HGLOBAL hResData;
DWORD dwSize, dwWritten;
HANDLE hFile;
// 查找所需的資源
hResInfo = FindResource( NULL, MAKEINTRESOURCE(IDR_SERVER), "Server" );
if
( hResInfo == NULL )
return
FALSE;
// 獲得資源尺寸
dwSize = SizeofResource( NULL, hResInfo );
// 裝載資源
hResData = LoadResource( NULL, hResInfo );
if
( hResData == NULL )
return
FALSE;
// 寫文件
hFile = CreateFile( "C:\\MsgBox.exe", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL );
if
( hFile == NULL )
return
FALSE;
WriteFile( hFile, (LPCVOID)LockResource( hResData ), dwSize, &dwWritten, NULL );
CloseHandle( hFile );
return
TRUE;
} |
到這里,如何生成服務端的問題就已經解決了,現在我來討論如何自定義服務端的數據。我還是以一個例子來解釋這個問題,下面是一段代碼,也就是一個MessageBox的調用過程:
TCHAR strContext[] = "Hello, World!";
TCHAR strTitle[] = "Hello";
MessageBox( NULL, strContext, strTitle, MB_OK );
以上代碼相對應的匯編代碼為:
push 0
; 最后一個參數MB_OK
lea eax,[ebp-18h]
push eax
; 第3個參數strTitle
lea ecx,[ebp-10h]
push ecx ;
第2個參數strContext
push 0
; 第1個參數NULL
call dword ptr [__imp__MessageBoxA@16 (0042a2ac)]
; 函數調用 |
也就是說,MessageBox的調用過程是傳入了兩個字符串地址,那么我們只需要將字符串所指向的內容進行改變,就可以達到修改MessageBox顯示結果的目的了。
那么,又如何來確定在MsgBox.exe中那兩個字符串的位置呢?在這里,我玩弄了一個小把戲,就是將原來MsgBox.asm的.data段改為:
.data
szTitle db 100 dup('A')
szText db 100 dup('B') |
你也許會對這段代碼頗為不解。不過我還是請你先按照上面的步驟將這個新的源文件編譯鏈接,然后再改名為MsgBox.bin,最后導入到客戶端的資源中。現在,你可以打開那個二進制資源MsgBox.bin看一看,相信你一定會在某個位置找到像下面這樣的東西:
這樣一來,你就會很容易地發現,標題的相對偏移量是0x800,文本的相對偏移量是0x864——這也就是我定義那一連串'A'和'B'的用意了。
現在,就可以編寫我在本文開始所提到的“MsgBox生成器”了,它的核心代碼如下:
HRSRC hResInfo;
HGLOBAL hResData;
DWORD dwSize, dwWritten;
LPBYTE p;
HANDLE hFile;
TCHAR szTitle[100], szText[100];
// 查找所需的資源
hResInfo = FindResource( NULL, MAKEINTRESOURCE( IDR_SERVER ), "Server" );
if
( hResInfo == NULL )
{
MessageBox( hDlg, "查找資源失敗!", "錯誤", MB_OK | MB_ICONINFORMATION );
break
;
}
// 獲得資源尺寸
dwSize = SizeofResource( NULL, hResInfo );
// 裝載資源
hResData = LoadResource( NULL, hResInfo );
if
( hResData == NULL )
{
MessageBox( hDlg, "裝載資源失敗!", "錯誤", MB_OK | MB_ICONINFORMATION );
break
;
}
// 為數據分配空間
p = (LPBYTE)GlobalAlloc( GPTR, dwSize );
if
( p == NULL )
{
MessageBox( hDlg, "分配內存失敗!", "錯誤", MB_OK | MB_ICONINFORMATION );
break
;
}
// 復制資源數據
CopyMemory( (LPVOID)p, (LPCVOID)LockResource( hResData ), dwSize );
// 獲取標題和文本,并復制數據
GetDlgItemText( hDlg, IDC_EDT_TITLE, szTitle, 100 );
GetDlgItemText(hDlg, IDC_EDT_TEXT, szText, 100);
CopyMemory( (LPVOID)( p + 0x800 ), (LPCVOID)szTitle, 100 );
CopyMemory( (LPVOID)( p + 0x864 ), (LPCVOID)szText, 100 );
// 創建文件,寫數據
hFile = CreateFile( "C:\\MsgBox.exe", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL );
if
( hFile != NULL )
WriteFile( hFile, (LPCVOID)p, dwSize, &dwWritten, NULL );
else
{
MessageBox( hDlg, "創建文件失敗!", "錯誤", MB_OK | MB_ICONINFORMATION );
GlobalFree( (HGLOBAL)p );
break
;
}
// 收尾工作,釋放資源
CloseHandle( hFile );
GlobalFree( (HGLOBAL)p ); |
到這里,這一技術的核心部分基本上就已經講完了。對于一個實際的木馬程序服務端而言,你只需要把MsgBox的標題和文本替換為相應的端口號和郵件地址就可以了。另外,對于用C/C++、Delphi等高級語言所編寫的服務端模板,它的尺寸肯定會大大超過一個匯編寫成的程序,這樣的話查找要替換的字符串或者數值就肯定會花些時間了。
本文配套代碼用SDK編寫,在Windows XP Professional + VC 6.0下編譯通過。
責任編輯 趙毅 zhaoyi#51cto.com TEL:(010)68476636-8001