HanDs
管理员

[Visual Studio文章] 编写QQ显IP外挂插件及原理分析 





学习中请遵循国家相关法律法规,黑客不作恶。没有网络安全就没有国家安全

本站需要登陆后才能查看

鉴于目前网上关于这方面的文章少之又少,一般能找到的应该就这下面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]
CQQApplication.dll通俗来说就是负责显示和实现QQ聊天窗口的模块。就是那些和xxx聊天中的窗口,所以这就是为什么要在其中寻找这样的代码的依据。

从上面的汇编来看,显然是调用了2个thiscall规范的函数,也就是我们所说的C++类成员函数。

2个成员函数的的大致形式是this->Func(void *ptr1,char *cmd,DWORD *ptr2);其中cmd就是上面dwIP、wPort这些字符串,而ptr2也很容易知道是函数返回值得存储指针。现在关键是要获取this指针,也就是ecx寄存器的数据和ptr1这个神秘指针的数据。

如果你有兴趣反汇编CoralQQ中相关的代码,也会发现与上面类似的调用部分。

这里暂时不深入这些函数的作用和那个cmd指针的细节,我们先来研究如何获取this指针和ptr2吧。

注意

027832DF      50                     push eax
027832E0      8B08                 mov ecx,dword ptr ds:[eax]

这2段代码,也就是说ptr2获取了,那么this指针也可以得到。所以现在一切的关键就是找出ptr2的来历。这样我们就能很轻松的实现显示ip了。

2.2 神秘的ptr2指针

为了能更快的说明问题,这里就不厚道的引用CoralQQ.dll的汇编了~

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

以上代码正式coralQQ 4.5版获取IP信息的片断。我们只需要关注上面的0056D982和0056D9B6地址的2个调用函数。

为什么这样说了,先看下面获取dwRecentIP数据的代码,它和上面提到的那个成员函数是属于一个类的(这里没提供出完整代码,你可以自己验证下:-P)。那么这里的this指针从哪里来呢?

0056D9DA      8B08                       mov ecx,dword ptr ds:[eax]

0056D9D6      8B4424 14               mov eax,dword ptr ss:[esp+14]

按照thiscall规范,ecx就保存了this指针,上面代码说明ecx是来自[esp+14]的,我们在往上看:

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

看到么0056D99C lea edx,dword ptr ss:[esp+14]!!

也就是说this指针和ptr2时由这个函数获得的,我们暂时以它的一个参数命名:QQUSER_DYNAMIC_DATA。

但不幸的是,这个函数同样也是thiscall调用规范的,也就说也是需要得到this指针……不过不慌:

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]

注意上面2段代码,QQUSER_DYNAMIC_DATA函数的this指针最终是[esp+14],而esp+14的数据是

0056D97F      51                              push ecx
这段代码压入的。所幸的是GetFriendQQData是个导出函数(位于BasicCtrDll.dll),我们看看他的申明:

int GetFriendQQData(struct IQQCore *,unsigned long,struct IQQData * *);

上面这个push ecx实际上是压入了参数struct IQQData * *。

所以现在的只要获得struct IQQCore *,和第二个神秘参数的含义就能实现显示IP的功能了。

-----------------------------
下回将说明IQQCore 的具体含义和如何去获得,以及第二的神秘参数的作用,以及IQQData 的含义。同时具体介绍如何编写一个具有实际意义的外挂dll插件

写教程是需要激情的,所以趁激情还在继续写……

发现自己没有把文章组织好,可能一开始看我写得会觉得头大,没事,最后具体讲编写插件时会好的……抱歉

上次最后说到了从调试的角度来看,如果要调用显示IP等信息的QQ内部函数,关键问题就是通过调用

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)

那么我们就拦截其中的一部分函数,看看提供给他们的这个struct IQQCore *参数具体是什么。

如果你的确这样做了,那么会发现所有的函数,无论在什么时候,这个struct IQQCore *的值是确定的唯一的。如果你这个IQQCore *指针的地址区域下内存写入断点的话会发现struct IQQCore *实际上在QQ进行登录初始化时就创建了,以后就不再被修改。

所以现在的问题就很简单了,我们可以暂时不用理会struct IQQCore *到底是什么,只要能获取到他就ok

1.4 如何获取struct IQQCore *?

如果只是调试个QQ,得到struct IQQCore *是非常容易的,但记住我们是要编写外挂。所以就是说我们要问:外挂如果通过程序来获取这个指针呢?

最野蛮的办法:把外挂写成一个调试器,模拟手工调试的过程,获得这个IQQCore *。ok,我很佩服你这样做,这也是我原先的想法。虽然写这个一个调试器是很简单的,但是他基本上没有实际意义,因为不能应对各种版本的QQ程序,而且也就无法再使用OD这些调试器来调试你的程序了

下面的方法要感谢明日帝国(sunwangme)写的教程了,可能一开始你看他教程会不知所云,但相信你现在去看他的文章就会很有感触。

上面说过这个IQQCore 是不会改变的,而且BasicCtrlDll.dll导出的那么多函数又偏偏要用到他,为什么不去拦截一个有IQQCore *参数的函数来获取这个IQQCore *呢?

具体的做法我会在编写插件时说明。可能你会想拦截导出函数(也就是API)不也是调试器作的事么?其实也有别的办法,这里就是用API Hook技术来实现的(建议先了解下win32的hook技术)。

问题是API SetWindowsHookEx是不可能hook一个API的。所以这里要用比较“底层”的办法:

一个导出函数的入口内存地址可以用GetProcAddress API获取,我们只要修改程序的代码,使得他在原先函数入口点执行时跳入我们的函数取执行,然后再跳转回来即可。(实际做法不是这样,在说明如何编写插件时我会说到)

现在问题就是要在BasicCtrlDll.dll导出函数中选取一个比较理想的函数取拦截,得到IQQCore

要拦截的函数应该具有如下特点:

函数形参简单,最好只有IQQCore *一个参数
函数能尽早被调用,这样能及早的获取IQQCore *
函数不能是thiscall规范的,也就是说函数必须是全局函数,不是一个成员函数
为何要这些特点应该都能理解,我对最后一个做下说明,thiscall规范的函数还需要一个类的this指针地址,这会给拦截造成一定麻烦(今后就会遇到这样的情况,以后再讨论)。

最终我们选取的函数是QQHelperDll.dll中的一个导出函数:

int IsLogin(struct IQQCore *);
很满足我们的要求,而且用OD跟踪发现,他在QQ登录后就不停的调用,太爽了……

总结一下:要获取struct IQQCore *通过拦截API获取参数实现,我们拦截的函数选用了IsLogin()。

1.5 Uin和struct IQQData *

现在还有2个问题要去研究,第一,在调用GetFriendQQData时候,Uin(就是那个unsigned long参数)如何确定呢?

这个问题要等到我介绍插件编写时在讨论,但可以先做下暗示,我们编写的插件是需要在打开和好友聊天的对话框以及将鼠标移动到好友头像上时,显示对方的IP信息,就第一个情况:QQ的聊天对话框里面不就有好友的QQ号码(Uin)么?第二个情况:虽然探出的信息卡片没有号码,但可以猜测QQ也是先需要获取相关信息的。

现在我们再说说这个IQQData *的作用,看看BasicCtrlDll.dll的一些导出函数:

long GetQQDataBuf(struct IQQData *,char const *,class CString &)

long GetQQDataStr(struct IQQData *,char const *,class CString &)

其中需要IQQData *参数,那么我们看看这些函数有什么作用呢?以GetQQDataStr在CoralQQ中的使用情况为例:

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
这些代码是在获取IP信息后出现的,猜猜在做什么呢?NAME,COUNTRY,PROVINCE这些词汇来看,应该是在获取当前要显示ip好友的名字、国籍、省份。而具体的执行函数就是GetQQDataStr。

再结合这个函数的参数来看,IQQData *,很有可能就是存放着一个用户相关信息的结构

事实也是如此的,到目前为止编写QQ外挂插件的条件已经具备

前面说了一大堆的原理,现在终于能具体介绍插件的编写了。

2 编写QQ显外挂插件

2.1 插件存在形式

这里说的插件是编写一个外部的dll文件,至于为何要用dll我就不想多说了。QQ自身不支持插件特性,所以做出显ip外挂的可能无非有3

直接修改QQ本身的程序文件->不具备版本无关性,容易引发法律纠纷
使用外部exe进程->必须采用调试进程的办法,效率低且浪费系统资源
编写dll文件让QQ加载->效率高,稳定
好了,现在就不讨论前面2种情况了。

2.2 如何加载dll

dll文件是不可能自己执行的,必须要又调用它的程序主动去加载。这应该是常识。那么如何让qq.exe去“主动”加载我们的插件呢?

同样采用外部插件的coralQQ,你会发现实际上它包含2个文件

coralQQ.exe和coralqq.dll

而平时点击第一个文件就是用来加载珊瑚虫版QQ的,但你千万别认为这个exe文件就是外挂的核心,核心是那个dll。

CoralQQ.exe实际上只做了一件事:加载qq.exe,然后把coralqq.dll注入到qq.exe的进程中去。

关于如何实现该方法我不多说了,可以参考网上关于珊瑚虫外挂原理的文章(当然实际上它没有介绍原理……)

将dll文件注入到程序的方法有2

1.首先通过CreateProcess创建进程,然后模拟windows加载一个dll的全过程,在加载程序主体

2.采用远程线程植入技术,即使用CreateRemoteThread

我们采用后者。

2.3 开始具体编写

2.3.1 插件加载程序

编写一个用于加载我们插件dll的exe,好比coralQQ.exe

其流程伪代码:

int WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPTSTR     lpCmdLine,int nCmdShow){

pinfo=CreateProcess(<加载qq.exe> );

if (hProcess){
   HANDLE processHandle;
   DWORD Exitcode;
   processHandle=pinfo.hProcess;
   HANDLE hThread;
   char     szLibPath[MAX_PATH];  
   void*    pLibRemote;
   DWORD    hLibModule;
   HMODULE hKernel32 = GetModuleHandle(TEXT(Kernel32));
   strcpy(szLibPath,<插件名称> );

   pLibRemote = VirtualAllocEx( processHandle, NULL, sizeof(szLibPath),MEM_COMMIT, PAGE_READWRITE );
   WriteProcessMemory( processHandle, pLibRemote, (void*)szLibPath,sizeof(szLibPath), NULL );
   hThread = CreateRemoteThread( processHandle, NULL, 0,(LPTHREAD_START_ROUTINE)GetProcAddress( hKernel32,LoadLibraryA ),pLibRemote, 0, NULL );
   WaitForSingleObject( hThread, INFINITE );

   GetExitCodeThread( hThread, &hLibModule );
   CloseHandle( hThread );
   VirtualFreeEx( processHandle, pLibRemote, sizeof(szLibPath), MEM_RELEASE );
}
else
{
   MessageBox(NULL,TEXT(Err,Cannot Load qq.exe),TEXT(Err!),MB_OK);
}}

该部分的代码实际上是通过CreateRemoteThread将LoadLibraryA(<插件dll文件名> );语句注入了qq.exe执行。这样就做到了qq主动加载我们插件的目的。

2.3.2 编写具体插件

这里部分参考了目子版qq的方法。我们拦截CQQApplication.dll中对user32.dll SetForegroundWindow API的调用来判断QQ聊天对话框弹出的时机,以便显示IP信息。

主要的思路是:

DllMain函数:

创建一个多线程(VC环境推荐采用_beginthread这个crt函数,否则将无法在新创建的线程中调用crt函数)。新创建线程的函数名比如为workproc,他将在我们dll插件被植入qq.exe后开始执行

workproc函数:

主要的功能就是去修改qq.exe内部程序,使得我们能够拦截相关的函数,比如以前提到的获取IQQCore和Uin的函数,以及上面说的SetForegroundWindow 函数。

所谓API函数拦截前一篇文章已经说过了大致的方法,这里我们采用《windows核心编程》中推荐的ReplaceIATEntryInOneMod函数。为了方便,我把函数代码给出:

bool ReplaceIATEntryInOneMod(PCSTR pszCalleeModName,
         PROC pfnCurrent, PROC pfnNew,HMODULE hmodCaller)
{
ULONG ulSize;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)
   ImageDirectoryEntryToData(hmodCaller,TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT,
   &ulSize);
if (pImportDesc == NULL)
{
   return false;
}

for (;pImportDesc->Name;pImportDesc++)
{
   PSTR pszModName = (PSTR)
    ((PBYTE) hmodCaller + pImportDesc->Name);
   if (lstrcmpiA(pszModName,pszCalleeModName)==0)
    break;
}

if (pImportDesc->Name == 0)
{
   return false;
}
PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)
   ((PBYTE)hmodCaller + pImportDesc->FirstThunk);
for (;pThunk->u1.Function;pThunk++)
{
   PROC *ppfn = (PROC *)&pThunk->u1.Function;
   BOOL fFound = (*ppfn == pfnCurrent);
   if (fFound)
   {
    HANDLE hProcess = GetCurrentProcess();
    DWORD oldAttr;
    VirtualProtectEx(hProcess, ppfn, sizeof(pfnNew), PAGE_READWRITE, &oldAttr);
    if (WriteProcessMemory(hProcess,ppfn,&pfnNew,sizeof(pfnNew),NULL)==TRUE)
    {
     VirtualProtectEx(hProcess, ppfn, sizeof(pfnNew), oldAttr, &oldAttr);
     return true;
    }
    else
    {
     VirtualProtectEx(hProcess, ppfn, sizeof(pfnNew), oldAttr, &oldAttr);
     return false;
    }

   }
}
return false;
}

函数的作用是将所用在hmodCaller句柄指向模块中对pszCalleeModName指向文件名dll中提供的入口地址在pfnCurrent的函数的调用,用pfnNew指向的函数去替换。也就是说原先要调用pfnCurrent函数的,现在就会执行pfnNew指向的函数。

要拦截上面说的SetForegroundWindow,就用如下代码:

   OrgFuncProc = GetProcAddress( GetModuleHandleA(user32),SetForegroundWindow);
   if (ReplaceIATEntryInOneMod(user32.dll,OrgFuncProc,(PROC)&OnQQWndShow,global_hCQQAppModule))
   {   

//替换成功
     }

其中global_hCQQAppModule是通过GetModuleHandle获取的CQQApplication.dll文件在qq.exe中的句柄。上面代码调用成功后,以后CQQApplication.dll中代码要调用SetForegroundWindow时,实际上将执行我们编写的OnQQWndShow函数。

现在我们看OnQQWndShow的申明:

extern C   BOOL   APIENTRY OnQQWndShow(HWND hWnd);

为什么要有extern C 和APIENTRY (也就是__stdcall)前缀呢?因为原先的SetForegroundWindow是采用stdcall调用规范的,如果替换函数的调用规范不同,将引发程序崩溃。

同时替换函数的参数也要和原函数一致。

经过这样的替换后,以后每当新弹出一个qq聊天窗口,我们的OnQQWndShow就会被执行,在其中我们就可以负责获取ip,并创建一个edit或者static窗口在qq聊天窗口上显示ip信息

OnQQWndShow函数:

前面已经说了它的作用,具体流程如下:

获取对应好友QQ号码

获取对应的IP信息

在qq聊天窗口,也就是函数hWnd参数指出的窗口中寻找窗口上的广告框,然后把它销毁,创建一个edit或者static窗口,显示ip信息。

最后调用原先的SetForegroundWindow函数(这步很重要,否则正常的代码逻辑会破坏)


学习中请遵守法律法规,本网站内容均来自于互联网,本网站不负担法律责任
编写 Q Q I P 外挂 插件 原理 分析
#1楼
发帖时间:2016-7-9   |   查看数:0   |   回复数:0
游客组
快速回复