声明,本文章未经过疯狂绅士与runjin的同意不得转载。
浅谈IPQQ软件的技术发展轨迹
疯狂绅士、RunJin
摘要:本文先给出了IPQQ的界定,并介绍了不同历史时期具有里程碑意义的IPQQ。根据几个具有里程碑意义的IPQQ,作者简要的分析了其技术特点,并以此阐述IPQQ软件的发展轨迹。
关键字: IPQQ、破解、木子QQ、外挂、珊瑚虫外挂
一、IPQQ历史回顾
QQ作为国内用户量最大的IM,专门针对其发布一些附加功能是一个古老的话题。在QQ的弹出窗口上显示发消息用户IP的变种QQ成为大家最常用的工具,这种工具我们称为IPQQ。
1999年,IPQQ的始祖出现了,是在QQ的前身OICQ时候出现的。邹丹制作的破解版,可以说形式上开创了一个时代。尽管时间过去了好几年,邹丹创立的模式几乎成为了一种标准——密界的标准,以后所有QQ显IP的方式都是继承邹丹显示IP的方式。
邹丹的这一举动,在当时引起了一大片争议声。邹丹也收到了腾讯的律师函,邹丹考虑再三后,退出了为制作IPQQ的行列。
接下来又有好多个勇士站了出来,其中最有名的是木子。木子的软件非常流行,与邹丹的软件相比,它的框架更好,更稳定,同时支持不同版本的QQ。木子的这个框架影响了大量的刚出道的Cracker,包括本文的作者之一 ——RunJin,其发布的飘云版QQ在一段时期里面被人骂成了剽窃。期间有很多显IP的QQ,说白了都是参考木子的,其中绝大部分根本就是木子的版本,不过是改了几个字或很取巧的换上自己的标签而已。
珊瑚虫前身的出现又是一个里程碑,这东西由Cygwin捣腾出来的。先揭一下Cygwin的老底,此人与疯狂绅士同是水木清华黑客版版主。
珊瑚虫QQ外挂的原型是Cygwin大概用了两天写出来的。这东西的出现让腾讯大为挠头,因为这个软件本身并没有修改QQ的任何程序。它是一个单存的外挂形式,因此避免了相关的法律问题。要说明的是珊瑚虫的历史比木子更早。可以说,珊瑚虫就是紧接着邹丹的版本出现的。这个时期的珊瑚虫完全由soff制作,不过这个时候的珊瑚虫不是外挂形式,同时质量非常高,与一年后出现的木子版相比也毫不逊色。这也是后来Quaful和cygwin找到soff合作的原因。这个时期的珊瑚虫版本因为收到了腾讯的律师信而被迫中止,所以给了后来木子兴起的机会。
由于珊瑚虫外挂不涉及到版权问题,并且其性能优越,导致了珊瑚虫外挂的下载量急剧的增大,从一些网站的分析来看,它甚至超过了QQ本身的下载量。珊瑚虫的成功在于它不断的推出新版本,作者大致统计了一下,从珊瑚虫0.1版到现在的4.0beta版,珊瑚虫一共推出了61个版本(不包括4.0以下的测试版)。正是由于珊瑚虫不懈的努力,使得珊瑚虫外挂声名远扬。
也许是精力的问题,当珊瑚虫的第二任作者soff觉得天天追赶着QQ的新版本而被迫推出一个个新版本觉得实在是很无趣的时候,内心深处对珊瑚虫来一个大变化已经变得很迫切。在外挂思维以及GAIM与Luma QQ等思想的指引下,Soff考虑一种新的方式,这个方式的执行者是Qulful,Qulful卖命的写出了珊瑚虫QQ4.0。
珊瑚虫QQ4.0又是一个里程碑,简单的说,以前的版本只要耐心去检查QQ的内存,总能找到IP地址的入口。因此可以说在珊瑚虫4.0之前的显IP的版本大都是一个简单的Cracker过程,只要能看懂段刚(看雪)《加密与解密》一书,折腾出某个IPQQ不是一见很难的事情。就是做一个外挂形式的IPQQ也并非难事。而珊瑚虫4.0可以说从根本上解决这个问题,它直接截获的是QQ的网络包,从网络包中获取其IP地址的。
从上面的简单的回顾,我们可以看出IPQQ的几个里程碑。
1、邹丹,IPQQ的开山鼻祖,IPQQ形式的确立。
2、木子,IPQQ的中兴,确立其成为修改QQ函数的地址方式的IPQQ的霸主地位。
3、Cygwin,IPQQ的转折,以外挂的形式击败了修改函数地址方式,成为了近两年来事实上IPQQ的霸主。
4、Quaful,思路的拓展,IPQQ也许只是珊瑚虫外挂的一个简单运用而已。
上述四个里程碑中,由于邹丹的版本太久远,这里不作讨论,本文仅就后3个里程碑简单的来分析一下其技术思路,尤其是后两种。
二、木子显IPQQ版本原理简介
与所有的破解软件一样,都有一个原则——点穴。木子与其它所有的IPQQ一样,都是很偷懒的,都是试图只通过修改几个字节,就达到想要的效果。
对于IPQQ来说,能利用QQ原来的功能就利用原来的功能。这似乎是一条铁律。
木子IPQQ,在网上已经可以下载到它的代码。这里只讲解木子IPQQ的部分代码。
SetIP proc
pushad
;=================取得IP地址==================;
mov eax,dword ptr [ebp-10h]
push offset dwIp
push offset szRecentip
push eax
mov ecx, [eax]
call dword ptr [ecx+34h]
;=================取得端口号==================;
……
;=================取得版本号==================;
……
not2003:
下面我们再看飘云的IPQQ的代码。
pushad
; =================取得IP地址==================;
mov eax,dword ptr [ebp-14h]
push offset dwIp
push offset szRecentip
push eax
mov ecx,[eax]
call dword ptr [ecx+34h]
cmp dwIp,0 ;是否已经取得IP
……
上面给出了两个不同IPQQ的如何获得IP地址部分的,这两段代码是针对不同版本的QQ。黑体字表示上述的不同地方。
由此我们可以看到,要制作IPQQ核心是找到显示IP的那个函数的入口。这个函数的查找是个关键。而木子的出现其意义在于他给出了一个框架。因此很多后续的IPQQ就更偷懒,他们只是参考木子的版本,在取IP的原理上只要改动一点就可以了。
木子制作的IPQQ可以说是能利用QQ的功能就尽量利用QQ的功能。而很多后来者制作的IPQQ其原则是,能利用木子就尽量利用木子。上述给出的飘云的IPQQ代码就是一个例子。
三、珊瑚虫4.0以下版本原理。
“珊瑚虫最初版很简单的,也参考了之前别人修改的方法,比如版本库就是直接盗用木子的,不过这个版本库盗用不是技术盗用,不被人介意的。”这段话是珊瑚虫的作者Cygwin所说的。
珊瑚虫外挂版本与木子版本比起来最大的不同在于——其利用了钩子的技术,不知道其是否用到了win2k的SDK包,否则这段代码都可以直接参考。是在QQ运行的时候获得了其IP地址的。而不是象木子一样直接给出一个显IP的破解版本。然而,在取得IP的原理上,珊瑚虫和木子是基本相同和飘云的更是非常相象。
下面是珊瑚虫3.16的取IP部分反汇编代码:
mov eax,dword ptr ss:[esp+10]
lea ecx,dword ptr ss:[esp+1C]
push ecx
push DUMPED.01B8D9EC ; ASCII对应的字符为"dwRecentIP"
mov edx,dword ptr ds:[eax]
push eax
call dword ptr ds:[edx+34]
………………………………………..
mov eax,dword ptr ss:[esp+10]
lea ecx,dword ptr ss:[esp+1C]
push ecx
push DUMPED.01B8D9D8 ; ASCII对应的字符为"dwC2CIP"
mov edx,dword ptr ds:[eax]
push eax
call dword ptr ds:[edx+34]
…………………………………..
mov eax,dword ptr ss:[esp+10]
lea ecx,dword ptr ss:[esp+1C]
push ecx
push DUMPED.01B8D9C8 ; ASCII对应的字符为 "dwIP"
mov edx,dword ptr ds:[eax]
push eax
call dword ptr ds:[edx+34]
虽然寄存器的具体使用不同,但动态调试的时候就会发现,其调用的函数和飘云的是一样的。
珊瑚虫的原理可以用下面的话简要来表达。
1、 绕开原来的流程
2、 获取IP
3、 把自己的窗体挂上QQ的窗体
4、 把获取的IP显示在自己的窗体上
RunJin用softice 、OllyDbg在: win 2000 sp4 , win 98操作系统对 QQ2005贺岁版珊瑚虫外挂进行了分析。
由于win nt 平台和win9x 的差异,珊瑚虫外挂因系统的不同而分开两种工作原理。珊瑚外挂是给QQ的主程序QQ.exe外挂了一个Coralqq.dll,而完成这个工作的就是coralqq.exe .要加载一个dll文件必须是qq.exe。
在win2000/xp下coralqq.exe先创建QQ的进程,同时也就创建了QQ的暂停的主线程,接着往QQ进程的内存写入代码,修改Ntdll.NtTestAlert的代码让程序跳到附加代码处执行,在执行的过程中恢复Ntdll.NtTestAlert处被改了的代码,同时加载Coralqq.dll。
在win9x下,coralqq.exe先创建QQ的进程,同时也就创建了QQ的暂停的主线程,接着在QQ.exe的内存写数据,改写QQ.exe的oep从而达到改变程序流程的目的,让其先执行附加代码,加载Coralqq.dll,加载完后再跳到原来的QQ.exe的oep的下一条指令继续执行。详见RunJin的<<珊瑚虫外挂原理分析>>一文。
从RunJin的分析可以看出,珊瑚虫外挂想得很周到的,它绕开原来QQ的流程也是非常巧妙。当然如何高效的利用钩子大家可以去看相关的文章,这里不再详述。
可以这么说,珊瑚虫最大的特色是,用C++代替原来木子的部分代码。而这段代码的前面部分又是那么的巧妙的绕开了原来QQ的流程。
正如木子版本一样,越来越多的人利用上了一个原则——能用珊瑚虫的功能就尽量利用珊瑚虫的功能。
QQ狂人DIY版是把珊瑚虫外挂注入到QQ.exe里面,以实现直接点QQ.exe就可以直接启动珊瑚虫外挂的目的。
这类把外挂形式又变成了非外挂形式。它们所用的工具:LordPE,详细的教程在网上有下载。最终出来的IPQQ部分机器可能不兼容,但是大部分是可以的。
四、珊瑚虫4.0 IPQQ原理简介
珊瑚虫4.0可以说是IPQQ有史以来一个最重大的突破。在介绍这个版本必须提到KQL库(Kwafu QQ Library)。
KQL是怎么出来的呢?全是LumaQQ给逼的。那个如来神掌的影响力实在太大了,人人都问珊瑚虫:“珊瑚虫为什么不做如来神掌啊?”当然在3.x的架构下是不可能的事情。后来Quaful就说,干脆把LumaQQ移植过来,这样就能实现了。后来Quaful用了2个月的时间完成了这个工作。Kwafu是Quaful的一个马甲的名字,所以叫做Kwafu QQ Library。至于说KQL带来的QQ版本无关的其他好处,都是附带的好处了。
Windows平台下的KQL库重点是最大限度的保持和官方QQ客户端的兼容性,所以,KQL库是做为一个“外挂”来开发,它的表面功能全都还是依附于官方QQ客户端来实现的。对于用户来说,就是不会影响传送文件、视频聊天等功能,珊瑚虫新增加的功能对原来的影响都尽可能的小。这个库很多内容是参考LumaQQ。因此Quaful把Luma等人也列为作者。
在跟Quaful通过几次邮件后,作者个人的看法是珊瑚虫4.0最重大的变化在于,原来主要是截获内存里面的东西,现在是直接截获一个个网络包。
原来的方法基本上不用了解QQ的通讯协议,背后的方法是要深入到QQ的协议里面去。
原来的方法如果QQ重新写过一个客户端,尤其是更改一下获得IP地址的函数名字等,那么IPQQ基本上要重新来过一次。而采用4.0的方式基本上没有这个后顾之忧。它所需要做的是重新钩一个窗体而已,因为腾讯不会不顾通讯协议的不兼容而抛弃大量的用户的。
从技术思想上来说,原来的方法是紧密依靠QQ,现在呢是我只是依靠QQ一下而已。
对于这款外挂我曾经建议过Quaful用驱动的方式来写,也就是我能知道QQ的任意一个包,发给谁,是什么IP与端口。因为用驱动写是直接到网络的低层,占用的系统资源更少,但开发的工作量要增加非常的多。从工程的角度上来讲并不划算。
我把这个想法告诉Quaful后,他一直打击我的看法。Quaful声称:“用驱动层拦截QQ包并不会比现在更省资源。无论是TDI还是NDIS,对系统的影响都是全局的,不仅会影响QQ,还会影响所有的应用程序。这是更加消耗系统资源的做法。”
作者曾试图在Win2k的DDK包中根据相关的例子来分析,但发现工作巨大,因此放弃。但从相关的效果上看,Quaful是对的。从驱动层来写,的确对系统的影响是全局的。但我认为这种写法更有扩充性。
五、总结
在不同时期的IPQQ中我们看到了他们之间的继承关系。如果深入到最核心的技术中去考察这个问题,我们可以看到,四个IPQQ可以用如下的话来概括。
1、邹丹,我第一个改QQ。
2、木子,我改QQ改出套路来了。
3、Cygwin,我在运行的时候改QQ。
4、Quaful,QQ你改吧,你怎么改我都能逮住你。
此外,我们也可以看到其它一些作者的思路。
1、 木子、珊瑚虫你能改QQ我能改你!
2、 木子、珊瑚虫你先改吧,我学你的样改。
在上述四个IPQQ中作者最欣赏珊瑚虫4.0,个人认为其发展的方向非常多。当然目前的版本是通过sokect来分析的。这样做相对来说比较占内存,速度也不够快。
文中涉及到如下人物:
作者: 疯狂绅士 原水木清华黑客版版主
作者:RunJin 飘云的老大 现读本科二年级
邹丹,78年出生
木子,姓名保密
Cygwin,姓名 保密 原水木清华黑客版版主
Soff 姓名 保密 北京理工大学当老师
Quaful,姓名 保密 清华大学在读博士
前一段时间研究了下QQ目前各种外挂的机理,包括著名的coralQQ。鉴于目前网上关于这方面的文章少之又少,一般能找到的应该就这下面3篇(由于可能涉及版权问题,我链接就不给出了:
a.木子版显IPQQ的制作教程
b.关于QQ外挂DLL的加载原理的分析
c.明日帝国(sunwangme)写的我是这样来做破解qq,做QQ外挂的系列
在开始我的分析前我简要对上面这些资料作下评价,首先我觉得如果你也想写个类似的外挂插件,他们的文章你是必看的,而且特别是你想真的写出什么有用的东西的话,明日帝国得文章一定要看,而且必须看懂。对于木子版的教程应该说是最早“公开”的资料了,很多人都是看了这个教程开始写自己的外挂的。但是他通过直接修改QQ来做显IP补丁,可能引起的法律问题不说(如果你只是自娱自乐的话),他不能适应不同版本的QQ,而且用户也不太能接受直接的修改,而且教程已经不能直接用于目前版本的QQ了。
第二个教程是做外挂DLL插件必看的,但是他丝毫没涉及显示IP的问题,只是简单介绍了DLL注入的问题,并对win9x环境下手动加载dll到进程空间作了分析。但是目前win9x已逐渐退出舞台,所以一般只要使用CreateRemoteThread即可。
第一部分:
1.1 主流的外挂插件如何获取IP和其他信息的?
也许你会认为他拦截了底层的Socket通讯?当然不至于,但这样肯定是最有效的办法。
让我们换个思路:如果你现在需要和一个QQ好友传输文件或者进行语音聊天或者发送了图片或自定义表情。那么QQ必须知道对方的IP地址和端口信息,这样才能把数据传给对方。
所以,很有可能QQ内部已经实现了获取IP地址和其他信息的相关函数了。的确如此。这也是木子版QQ教程里面提到的办法,调用QQ内部的函数。
下面是截至QQ组件之一的CQQApplication.dll中的汇编代码:(建议先浏览木子版QQ的教程)
027832C7 8B45 F0 mov eax,dword ptr ss:[ebp-10] 027832CA 53 push ebx 027832CB 68 38558302 push CQQAppli.02835538 ; ASCII dwIP 027832D0 50 push eax 027832D1 8B08 mov ecx,dword ptr ds:[eax] 027832D3 FF51 18 call dword ptr ds:[ecx+18] 027832D6 8B45 F0 mov eax,dword ptr ss:[ebp-10] 027832D9 53 push ebx 027832DA 68 40558302 push CQQAppli.02835540 ; ASCII wPort 027832DF 50 push eax 027832E0 8B08 mov ecx,dword ptr ds:[eax] 027832E2 FF51 14 call dword ptr ds:[ecx+14] |
0056D97F 51 push ecx 0056D980 52 push edx 0056D981 50 push eax 0056D982 FF15 38AB5A00 call dword ptr ds:[5AAB38] ; BasicCtr.GetFriendQQData 0056D988 8B4424 14 mov eax,dword ptr ss:[esp+14] 0056D98C 83C4 0C add esp,0C 0056D98F 3BC3 cmp eax,ebx 0056D991 0F84 03020000 je CoralQQ.0056DB9A 0056D997 57 push edi 0056D998 895C24 14 mov dword ptr ss:[esp+14],ebx 0056D99C 8D5424 14 lea edx,dword ptr ss:[esp+14] 0056D9A0 52 push edx 0056D9A1 68 50BB5900 push CoralQQ.0059BB50 0056D9A6 C64424 30 01 mov byte ptr ss:[esp+30],1 0056D9AB 8B08 mov ecx,dword ptr ds:[eax] 0056D9AD 68 C8BA5900 push CoralQQ.0059BAC8 ; ASCII QQUSER_DYNAMIC_DATA 0056D9B2 50 push eax 0056D9B3 8B41 54 mov eax,dword ptr ds:[ecx+54] 0056D9B6 FFD0 call eax 0056D9B8 8B4424 14 mov eax,dword ptr ss:[esp+14] 0056D9BC 3BC3 cmp eax,ebx 0056D9BE 0F84 F9000000 je CoralQQ.0056DABD 0056D9C4 8B08 mov ecx,dword ptr ds:[eax] 0056D9C6 8D5424 1C lea edx,dword ptr ss:[esp+1C] 0056D9CA 52 push edx 0056D9CB 68 ACA15900 push CoralQQ.0059A1AC ; ASCII wProcotol 0056D9D0 50 push eax 0056D9D1 8B41 30 mov eax,dword ptr ds:[ecx+30] 0056D9D4 FFD0 call eax 0056D9D6 8B4424 14 mov eax,dword ptr ss:[esp+14] 0056D9DA 8B08 mov ecx,dword ptr ds:[eax] 0056D9DC 8D5424 10 lea edx,dword ptr ss:[esp+10] 0056D9E0 52 push edx 0056D9E1 68 94A15900 push CoralQQ.0059A194 ; ASCII dwRecentIP 0056D9E6 50 push eax 0056D9E7 8B41 34 mov eax,dword ptr ds:[ecx+34] 0056D9EA FFD0 call eax |
0056D99C 8D5424 14 lea edx,dword ptr ss:[esp+14] 0056D9A0 52 push edx 0056D9A1 68 50BB5900 push CoralQQ.0059BB50 0056D9A6 C64424 30 01 mov byte ptr ss:[esp+30],1 0056D9AB 8B08 mov ecx,dword ptr ds:[eax] 0056D9AD 68 C8BA5900 push CoralQQ.0059BAC8 ; ASCII QQUSER_DYNAMIC_DATA 0056D9B2 50 push eax 0056D9B3 8B41 54 mov eax,dword ptr ds:[ecx+54] 0056D9B6 FFD0 call eax |
0056D97F 51 push ecx 0056D980 52 push edx 0056D981 50 push eax 0056D982 FF15 38AB5A00 call dword ptr ds:[5AAB38] ; BasicCtr.GetFriendQQData 0056D988 8B4424 14 mov eax,dword ptr ss:[esp+14] |
0056D9AB 8B08 mov ecx,dword ptr ds:[eax] |
1.3 IQQCore和Uin
int GetFriendQQData(struct IQQCore *,unsigned long,struct IQQData * *);
这个函数,从而获得那个struct IQQData *指针。但问题就是要调用这个函数必须要提供2个参数:IQQCore *和一个unsigned long(DWORD)的神秘数据。
在开始正式分析前请各位思考下,如果要你编写一个能显示好友IP的函数,你需要先知道什么呢?
至少需要知道要去获取哪个QQ好友吧。这个肯定是必须的,否则函数就没有执行的意义了
现在我们跟踪下上面这个GetFriendQQData函数。在其入口点下断点。然后小心的把鼠标移动到QQ好友列表中某个头像上(需要使用CoralQQ……有点不厚道)。这时候应该程序就会被断下。
因为正常理鼠标移至好友头像会显示信息卡片,CoralQQ会在下面显示IP信息,所以按照上一篇文章反汇编的代码,GetFriendQQData必然会调用。我们从堆栈里面找到这个unsigned long对应的数据:
0x1A53836
因为是DWORD数据,把它转化为10进制看看:27605046
这不是我的QQ号码么……的确,先前鼠标是移动在我的头像上了。
所以可以猜测这个unsigned long就是好友的QQ号码。
经过多次验证,的确如此。
所以,这个神秘的unsigned long明确:他是要获取好友信息的号码,顺便补充下,这个unsigned long在QQ中可是有专门名字的:Uin
接下来就是struct IQQCore*,我想他的作用从名字中应该就能猜出大概来。虽然具体他的结构我还没弄清,但可以肯定他好比是QQ程序内核信息的指针。而现在最关键的问题是如何去获得它。
如果你研究过BasicCtrlDll.dll中导出的函数,你会发现几乎一半的函数的参数都由这个IQQCore,比如:
int GetCurrentStatus(struct IQQCore *,int *) int GetCurrentUin(struct IQQCore *,unsigned long *) int GetFriendStat(struct IQQCore *,unsigned long) |
long GetQQDataBuf(struct IQQData *,char const *,class CString &) long GetQQDataStr(struct IQQData *,char const *,class CString &) |
0056DAC4 57 push edi 0056DAC5 68 DCBA5900 push CoralQQ.0059BADC ; ASCII NAME 0056DACA 52 push edx 0056DACB FF15 44AB5A00 call dword ptr ds:[5AAB44] ; BasicCtr.GetQQDataStr 0056DAE5 8B4424 24 mov eax,dword ptr ss:[esp+24] 0056DAE9 8D56 34 lea edx,dword ptr ds:[esi+34] 0056DAEC 52 push edx 0056DAED 68 ECBA5900 push CoralQQ.0059BAEC ; ASCII REMARK_REALNAME 0056DAF2 50 push eax 0056DAF3 FF15 44AB5A00 call dword ptr ds:[5AAB44] ; BasicCtr.GetQQDataStr 0056DAF9 8B5424 30 mov edx,dword ptr ss:[esp+30] 0056DAFD 8D4E 38 lea ecx,dword ptr ds:[esi+38] 0056DB00 51 push ecx 0056DB01 68 FCBA5900 push CoralQQ.0059BAFC ; ASCII COUNTRY 0056DB06 52 push edx 0056DB07 FF15 44AB5A00 call dword ptr ds:[5AAB44] ; BasicCtr.GetQQDataStr 0056DB0D 8B4C24 3C mov ecx,dword ptr ss:[esp+3C] 0056DB11 8D46 3C lea eax,dword ptr ds:[esi+3C] 0056DB14 50 push eax 0056DB15 68 04BB5900 push CoralQQ.0059BB04 ; ASCII PROVINCE 0056DB1A 51 push ecx 0056DB1B FF15 44AB5A00 call dword ptr ds:[5AAB44] ; BasicCtr.GetQQDataStr |
2.编写QQ显IP插件
2.1 编写加载外挂dll的程序
这里采用VC作为开发环境,我使用的是VS2005
首先说下大体的思路:
这里仿造coralQQ一样,将真正的外挂代码写入dll,然后编写一个exe文件去加载qq.exe,并把我们写得dll注入。
这一步在前一篇文章中已经给出了具体代码。
2.2 编写插件主体
感觉自己如果一涉及具体代码就回说不来话,所以这次就先谈思路,然后给出部分代码。
dll要做的事:
将我们前面分析过的关键API挂钩,以便得到IQQCore *,Uin,以及在qq聊天窗口弹出时进行捕获以便显示IP信息。
要做到上面的要求,首先就是要在dll加载后开始做API hook的工作。
不过这件事情不能在DllMain里面写,推荐的做法是在DllMain里面创建一个新的线程,线程的执行函数我们设为:WorkerProc(VC编写的话推荐用_beginthread创建这个线程,否则无法使用CRT函数),为什么不能直接在DllMain里面写我稍候分析
然后就是在WorkerProc里面写入具体的hook代码。
1.拦截QQHelperDll.dll中的IsLogin函数
前面分析过了,IQQCore *指针可以通过拦截这个API来获取。
这里我们采用的是《windows核心编程》中推荐的拦截API hook的办法(修改入口地址跳转相对他烦了些,而且我们这里只是想做hook):通过动态修改API调用者模块的IAT表,将原先API的入口地址替换成我们函数的。具体的替换代码我们就采用书中提供的了的ReplaceIATEntryInOneMod(见前一篇)。
现在要注意一个问题,我们用来替换原先API的函数必须和原函数采用同样的调用规范,这个IsLogin本身就是cdecl得,所以只要用个形参和返回值一样的函数取替换即可:
给出实现本功能的代码:
typedef int (*OrgIsLogin)(DWORD ptrIQQCore); int PokeIsLogin(DWORD ptrIQQCore) { global_ptrIQQCore = ptrIQQCore; return ((OrgIsLogin)(PROC)OrgIsLoginProc)(ptrIQQCore); } ////下面代码在WorkerProc函数中/// OrgIsLoginProc = GetProcAddress( GetModuleHandleA(QQHelperDll.dll),?IsLogin@@YAHPAUIQQCore@@@Z); if (ReplaceIATEntryInOneMod(QQHelperDll.dll,OrgIsLoginProc,(PROC)&PokeIsLogin,GetModuleHandleA(qq.exe))) { //替换成功 } // |
typedef BOOL ( __stdcall *OrgSetForegrandWindow)(HWND hWnd); extern C BOOL APIENTRY OnQQWndShow(HWND hWnd) { bool trueResult = ((OrgSetForegrandWindow)(PROC)OrgFuncProc)(hWnd); return trueResult; } //下面代码在WorkerProc函数或由其调用的函数中 OrgFuncProc = GetProcAddress( GetModuleHandleA(user32),SetForegroundWindow); if (ReplaceIATEntryInOneMod(user32.dll,OrgFuncProc,(PROC)&OnQQWndShow,global_hCQQAppModule)) { //替换函数入口成功 } |
bool WaitforLogon() { while(!bIsDllUnload) { global_hCQQAppModule = GetModuleHandleA(CQQApplication.dll); if (global_hCQQAppModule) return true; Sleep(300); } return false; } |
3.获取Uin
现在IQQCore *已经获得了,同时只要在OnQQWndShow中写入得到用户IP信息的代码,再使用FindWindow的方法把原先的广告去除,再创建个Edit或者Static来显示我们的数据即可。
但之前还要得到Uin,也就是对方好友的QQ号。
其实这里有个很笨的办法:QQ聊天对话框中有对方的号码的:比如“&heaven(27605046)(后面是个性签名)。的确可以用FindWindow+GetWindowText获取,然后得到括号里面的数据就好了,但是是否有更简单办法呢?有
这里要感谢明日帝国(sunwangme)的教程,这里就是用他的方法了,具体原理还是大家去他blog看吧(google一下)
大体的方法是:在QQ准备显示聊天对话框时,上面“&heaven(27605046)(后面是个性签名)这段文字会调用位于QQBaseClassInDll.dll中的CAllInOneStatusBar::SetUin(unsigned long);这个函数,其中参数就是我们要的QQ号了。而且可以保证CQQApplication.dll仅仅在要显示对话框前才会调用它。(再次感谢明日帝国)
所以和上面一样,这次拦截CAllInOneStatusBar::SetUin(unsigned long);
OrgSetUINProc = GetProcAddress(GetModuleHandleA(QQBaseClassInDll.dll),?SetUin@CAllInOneStatusBar@@QAEX_J@Z); if (ReplaceIATEntryInOneMod(QQBaseClassInDll.dll,OrgSetUINProc,(PROC)&Poke_GETUIDA,global_hCQQAllInOne)){ } |
__declspec( naked ) int Poke_GETUIDA() { _asm { push ecx mov ecx,[esp+8] mov dwRecentUINA,ecx pop ecx jmp OrgSetUINProc } } |
DWORD MainHandle; if (ptrBasicCtr_GetFriendQQData==NULL || global_ptrIQQCore==NULL) return 0; _asm { mov edx,dwID mov eax,global_ptrIQQCore lea ecx,MainHandle push ecx push edx push eax call ptrBasicCtr_GetFriendQQData ; BasicCtr.GetFriendQQData add esp,0xC } |
char *szQQUSER_DYNAMIC_DATA = QQUSER_DYNAMIC_DATA; DWORD tmpInfo; DWORD returnVal; tmpInfo = 0xba863a1e; if (mainHandle == NULL) { return NULL; } _asm { mov eax,mainHandle lea edx,returnVal push edx lea edx,tmpInfo push edx mov ecx,[eax] push szQQUSER_DYNAMIC_DATA push eax call [ecx+54h] } |
char *szwProcotol = wProcotol; char *szRecentip=dwRecentIP; char *szwRecentPort=wRecentPort; char *szdwC2CIP=dwC2CIP; char *szwC2CPort=wC2CPort; char *szdwIP=dwIP; char *szwPort=wPort; bool GetDestIPInfo(DWORD ptrClassHandle,DWORD *ptrDestIp,DWORD *ptrDestPort) { if (ptrClassHandle==NULL || ptrDestIp==NULL || ptrDestPort==NULL) return false; //Using Std Info Buffer _asm { pushad pushf mov eax,ptrClassHandle mov ecx,[eax] mov edx,ptrDestIp push edx push szRecentip push eax mov eax,[ecx+34h] call eax mov eax,ptrClassHandle mov ecx,[eax] mov edx,ptrDestPort push edx push szwRecentPort push eax mov eax,[ecx+30h] call eax popf popad } (*ptrDestPort) &= 0xFFFF; if ((*ptrDestIp) != NULL && (*ptrDestPort) != NULL) return true; _asm { pushad pushf mov eax,ptrClassHandle mov ecx,[eax] mov edx,ptrDestIp push edx push szdwC2CIP push eax mov eax,[ecx+34h] call eax mov eax,ptrClassHandle mov ecx,[eax] mov edx,ptrDestPort push edx push szwC2CPort push eax mov eax,[ecx+30h] call eax popf popad } (*ptrDestPort) &= 0xFFFF; if ((*ptrDestIp) != NULL && (*ptrDestPort) != NULL) return true; _asm { pushad pushf mov eax,ptrClassHandle mov ecx,[eax] mov edx,ptrDestIp push edx push szdwIP push eax mov eax,[ecx+34h] call eax mov eax,ptrClassHandle mov ecx,[eax] mov edx,ptrDestPort push edx push szwPort push eax mov eax,[ecx+30h] call eax popf popad } (*ptrDestPort) &= 0xFFFF; return ((*ptrDestIp) != NULL && (*ptrDestPort) != NULL); } |