程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> vc教程 >> “QQ尾巴”病毒是如何制作的

“QQ尾巴”病毒是如何制作的

編輯:vc教程

  2003這一年裡,QQ尾巴病毒可以算是風光了一陣子。它利用IE的郵件頭漏洞在QQ上瘋狂傳播。中毒者在給別人發信息時,病毒會自動在信息文本的後邊添上一句話,話的內容多種多樣,總之就是希望信息的接收者點擊這句話中的URL,成為下一個中毒者。下圖就是染毒後的QQ發送的消息,其中中毒者只打了“你好”兩個字,其它的就全是病毒的傑作了。 

  下面我將要討論的,就是QQ尾巴病毒使用的這一技術。由於病毒的源代碼無法獲得,所以以下的代碼全是我主觀臆斷所得,所幸的是效果基本與病毒本身一致。 

  注:考慮到安全方面的原因,本文某些地方用了*號來代替正確代碼。 

  粘貼尾巴 

  首先的一個最簡單的問題是如何添加文本。這一技術毫無秘密可言,就是通過剪貼板向QQ消息的那個RichEdit“貼”上一句話而已。代碼如下: 

TCHAR g_str[] = "歡迎來我的小站坐坐:http://titilima.nease.Net"; 
// 函數功能:向文本框中粘貼尾巴 
void PasteText(HWND hRich) 

HGLOBAL hMem; 
LPTSTR pStr; 
// 分配內存空間 
hMem = GlobalAlloc(GHND   GMEM_SHARE, sizeof(g_str)); 
pStr = GlobalLock(hMem); 
lstrcpy(pStr, g_str); 
GlobalUnlock(hMem); 
OpenClipboard(NULL); 
EmptyClipboard(); 
// 設置剪貼板文本 
SetClipboardData(CF_TEXT, hMem); 
CloseClipboard(); 
// 釋放內存空間 
GlobalFree(hMem); 
// 粘貼文本 
***********(*****, **_*****,*,*); 
}  
  鉤子 

  好了,那麼下面的問題是,這段文本應該在什麼時候貼呢?網上有一些研究QQ尾巴實現的文章指出,可以用計時器來控制粘貼的時間,類似這個樣子: 

void CQQTailDlg::OnTimer(UINT nIDEvent) 

PasteText(hRich); 
}  

  這的確是一種解決的手段,然而它也存在著極大的局限性——計時器的間隔如何設置?也許中毒者正在打字,尾巴文本“唰”的出現了…… 

  然而病毒本身卻不是這樣子,它能夠准確地在你單擊“發送”或按下Ctrl+Enter鍵的時候將文本粘貼上。2003年1月份我的一台P2曾經中過毒,由於系統速度較慢,所以可以很清楚地看見文本粘貼的時機。 

  講到這裡,我所陳述的這些事實一定會讓身為讀者的你說:鉤子!——對,就是鉤子,下面我所說的正是用鉤子來真實地再現“QQ尾巴病毒”的這一技術。 

  首先我對鉤子做一個簡要的介紹,已經熟悉鉤子的朋友們可以跳過這一段。所謂Win32鉤子(hook)並不是鐵鉤船長那只人工再現的手臂,而是一段子程序,它可以用來監視、檢測系統中的特定消息,並完成一些特定的功能。打個比方來說,你的程序是皇帝,Windows系統充當各省的巡撫;至於鉤子,則可以算是皇上的一個欽差。譬如皇帝下旨在全國收稅,然後派了一個欽差找到山西巡撫說:“皇上有旨,山西除正常賦稅外,加收杏花村酒十壇。”(-_-#……)正如皇帝可以用這種方法來特殊對待特定的巡撫一樣,程序員也可以用鉤子來捕獲處理Windows系統中特定的消息。 

  問題具體到了“QQ尾巴病毒”上邊,就是我們需要一個鉤子,在用戶單擊了“發送”按鈕之後,粘貼我們的文本。我所實現的這段鉤子過程為(至於如何掛接這個鉤子,我會在稍後說明): 

// 鉤子過程,監視“發送”的命令消息 
LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam) 

********* ** = (********* *)******; 
// 捕獲“發送”按鈕 
if (p->message == WM_COMMAND && LOWord(p->wParam) == 1) 
PasteText(g_hRich); 
return CallNextHookEx(g_hProc, nCode, wParam, lParam); 
}  
 在此我對這個回調過程說明幾點: 

  1、lParam是一個指向CWPSTRUCT結構的指針,這個結構的描述如下: 

typedef struct { 
LPARAM lParam; 
WPARAM wParam; 
UINT message; 
**** ****; 
} CWPSTRUCT, *PCWPSTRUCT;  

  這時候像我一樣的SDK fans也許會會心一笑:這不是窗口回調的那四個鐵桿參數麼?如你所說,的確是這樣,你甚至可以使用switch (p->message) { /* ... */ }這樣的代碼寫成的鉤子函數來全面接管QQ窗口。 

  2、g_hRich是一個全局變量,它保存的是QQ消息文本框的句柄。這裡之所以采用全局變量,是因為我無法從鍵盤鉤子回調函數的參數中獲得這個句柄。至於如何獲得這個句柄以及這個全局變量的特殊位置,我會在稍後說明。

  3、CallNextHookEx是調用鉤子鏈中的下一個處理過程,換了欽差就會說:“十壇杏花村酒本欽差已經替皇上收下了,現在請巡撫大人把貴省正常的賦稅交上來吧。”(-_-#……)這是書寫鉤子函數中很重要的一個環節,如果少了這一句,那麼可能會導致系統的鉤子鏈出現錯誤,某些程序也會沒有響應——事實上我在編寫這個仿真程序的時候QQ就當掉了幾回。 

  4、你也許會問為什麼我捕獲的是WM_COMMAND消息,這個原因讓我來用下面的SDK代碼(雖然QQ是用MFC寫的,但是用SDK代碼才能說明WM_COMMAND和“發送”按鈕的關系)來說明: 

#define IDC_BTN_SENDMSG 1 // “發送”按鈕ID的宏定義 
// QQ發送消息對話框回調過程·李馬偽造版 
LRESULT CALLBACK ProcSendDlg(HWND hDlg, UINT Msg, WPARAM wParam, LPARAM lParam) 

switch (Msg) 

case WM_CLOSE: 
EndDialog(hDlg, 0); 
break; 
case WM_COMMAND: 

switch (LOWord(wParam)) 

case IDC_BTN_SENDMSG: 
// 發送消息... 
*****; 
// 其它的命令按鈕處理部分... 


break; 
// 其它的case部分... 

****** *; 
}  

  消息發送的整個過程是:當用戶單擊了“發送”按鈕後,這個按鈕的父窗口(也就是“發送消息”的對話框)會收到一條WM_COMMAND的通知消息,其中wParam的低位字(即LOWord(wParam))為這個按鈕的ID,然後再調用代碼中發送的部分:   所以,在此我捕獲WM_COMMAND消息要比捕獲其它消息或掛接鼠標鉤子要有效得多。 

  好了,現在這個鉤子已經可以勝利地完成任務了。但是請不要忘記:有更多的用戶更偏愛於用“Ctrl+Enter”熱鍵來發送消息,所以程序中還需要掛上一個鍵盤鉤子: 

// 鍵盤鉤子過程,監視“發送”的熱鍵消息 
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) 

// 捕獲熱鍵消息 
if (wParam == VK_RETURN && GetAsyncKeyState(VK_CONTROL) < 0 && ******* >= 0) 
PasteText(g_hRich); 
return CallNextHookEx(g_hKey, nCode, wParam, lParam); 
}  

  在這裡唯一要解釋的一點就是lParam >= 0子句。很明顯這個if判斷是在判斷熱鍵Ctrl+Enter的輸入,那麼lParam >= 0又是什麼呢?事實上在鍵盤鉤子的回調之中,lParam是一個很重要的參數,它包含了擊鍵的重復次數、掃描碼、擴展鍵標志等等的信息。其中lParam的最高位(0x80000000)則表示了當前這個鍵是否被按下,如果這個位正在被按下,這個位就是0,反之為1。所以lParam >= 0的意思就是在WM_KEYDOWN的時候調用PasteText,也就是說,如果去掉這個條件,PasteText將會被調用兩次(連同WM_KEYUP的一次)。 

  掛接鉤子和查找窗口 

  接下來就是如何掛接這兩個鉤子了。對於掛接鉤子,要解決的問題是:往哪裡掛接鉤子,以及如何掛接? 

  掛接鉤子的目標,肯定是QQ“發送信息”窗口的所屬線程。我的代碼就是將這個窗口的句柄傳入之後來進行鉤子的掛接: 

// 掛接鉤子 
BOOL WINAPI SetHook(HWND hQQ) 

BOOL bRet = FALSE; 
if (hQQ != NULL) 

DWord dwThreadID = GetWindowThreadProcessId(hQQ, NULL); 
// 感謝好友hottey的查找代碼,省去了我使用Spy++的麻煩 
g_hRich = GetWindow(GetDlgItem(hQQ, 0), GW_CHILD); 
if (g_hRich == NULL) 
****** *****; 
// 掛接鉤子 
g_hProc = SetWindowsHookEx(WH_CALLWNDPROC, CallWndProc, g_hInstDLL, dwThreadID); 
g_hKey = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstDLL, dwThreadID); 
bRet = (g_hProc != NULL) && (g_hKey != NULL); 

****

// 卸載鉤子 
bRet = UnhookWindowsHookEx(g_hProc) && UnhookWindowsHookEx(g_hKey); 
g_hProc = NULL; 
g_hKey = NULL; 
g_hRich = NULL; 

return bRet; 
}  

  到此為止,以上所有的代碼都位於一個Hook.dll的動態鏈接庫之中,關於DLL我就不多介紹了,請查閱MSDN上的相關資料和本文的配套源代碼。 

  DLL之中已經做好了所有重要的工作(事實上這部分工作也只能由DLL來完成,這是由Windows虛擬內存機制決定的),我們只需要在EXE之中調用導出的SetHook函數就可以了。那麼,SetHook的參數如何獲得呢?請看以下代碼: 

// 感謝好友hottey的查找代碼,省去了我使用Spy++的麻煩 
HWND hSend; 
*_*** = ****; 
SetHook(NULL); 
**

g_hQQ = FindWindowEx(NULL, g_hQQ, "#32770", NULL); 
hSend = FindWindowEx(g_hQQ, NULL, "Button", "發送(&S)"); 
} while(g_hQQ != NULL && hSend == NULL); 
if (g_hQQ != NULL) 
SetHook(g_hQQ);  

  這段代碼中的do-while循環就是用來查找“發送消息”的窗口的,QQ窗口的保密性越來越強了,窗口一層套一層,找起來十分不便,所以在此感謝好友hottey的《QQ消息炸彈隨想》一文省去了我反復使用Spy++的麻煩。我所做的,只是把他文中的Delphi代碼翻譯成了C代碼。  DLL的共享數據段 

  如果你對DLL不甚了解,那麼在你讀到我的配套源代碼之後,肯定會對下面這一段代碼有些疑問: 

// 定義共享數據段 
#pragma data_seg("shared") 
HHOOK g_hProc = NULL; // 窗口過程鉤子句柄 
HHOOK g_hKey = NULL; // 鍵盤鉤子句柄 
HWND g_hRich = NULL; // 文本框句柄 
#pragma data_seg() 
#pragma comment(linker, "/section:shared,rws")  

  這定義了一段共享的數據段,是的,因為我的注釋已經寫得很清楚了,那麼共享數據段起到了什麼作用呢?在回答這個問題之前,我請你把代碼中以#開頭的預處理指令注釋掉然後重新編譯這個DLL並運行,你會發現什麼? 

  是的,添加尾巴失敗! 

  好了,我來解釋一下這個問題。我們的這個仿真程序的EXE、DLL以及QQ的主程序事實上是下面這樣一種關系: 

  這個DLL需要將一個實例映射到EXE的地址空間之中以供其調用,還需要將另一個實例映射到QQ的地址空間之中來完成掛接鉤子的工作。也就是說,當鉤子掛接完畢之後,整個系統的模塊中,有兩個DLL實例的存在!此DLL非彼DLL也,所以它們之間是沒有任何聯系的。拿全局變量g_hRich來說,圖中左邊的DLL通過EXE的傳入獲得了文本框的句柄,然而如果沒有共享段的話,那麼右邊的DLL中,g_hRich仍然是NULL。共享段於此的意義也就體現出來了,就是為了保證EXE、DLL、QQ三者之間的聯系。這一點,和C++中static的成員變量有些相似。 

  在鉤子掛接成功之後,你可以通過一些有模塊查看功能的進程管理器看一看,就會發現Hook.dll也位於QQ.exe的模塊之中。 

  最後一些要說的 

  1、我是前說過,在2003年的1月份我就碰到了這種病毒,至今我還很清楚地記得那個病毒EXE只有16KB大小,所以從病毒本身存在的性質來說,這個東西應該是用Win32ASM來寫會更實用一些。 

  2、那個病毒我曾經是手殺的——用了一個進程查看工具就殺掉了。但是現在的“QQ尾巴”增加了復活功能——在EXE被殺掉後,DLL會將其喚醒。我曾經用我的進程查看工具分析過,發現系統中幾乎所有的進程都被病毒的DLL掛住了。這一技術是利用CreateRemoteThread在所有的進程上各插入了一個額外的復活線程,真可謂是一石二鳥——保證EXE永遠運行,同時這個正在使用中的DLL是無法被刪除的。這一技術我也已經實現了,但是穩定性方面遠不及病毒本身做得優秀,故在此也就不將其寫出了,有興趣的朋友可以參考Jeffrey Richter《Windows核心編程》的相關章節。 

  3、走筆至此想起了侯捷老師《STL源碼剖析》中的一句話——“源碼之前,了無秘密。”如果你看完本文之後也有這樣的感覺,那麼我將感到不勝榮幸。 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved