• 标 题:手把手教你修改QQ1220 (9千字)
  • 作 者:夜月
  • 时 间:2002-3-20
  • 链 接:http://bbs.pediy.com

手把手教你修改QQ1220
             
声明:本文章仅为教学目的而作,请勿将此用于不法用途。
   
前言:Oicq是目前国内使用最广泛的聊天软件,随之也出现了许多QQ的补丁,其中最著名和最实用的
      一个补丁应该算是邹丹作的显示ip补丁,我看了邹丹写作的《为OICQ 820添加显示IP地址和
      端口号的功能》和《如何为程序打补丁(基础,提高篇)》,然后自己试验了一次。在实验的
      过程中,发现那两篇文章中存在着一些的不足,特别是《如何为程序打补丁(提高篇)》一文
      中,完全把OrignalFirstThunk和FirstThunk两个数据的作用搞反了,对初学者是一个极大的误
      导,再加上那两篇文章都没有写到具体的实现问题,给很多朋友的学习带来不便,为此,我结合
      自己修改QQ的经验,写作此文,希望能对朋友们有所帮助。

思路:我们首先必须要得到对方的ip,然后用ip作参数,调用ipsearcher.dll得到对方地址,然后显
      示在原来的广告窗口的位置。需要说明的是,这篇文章没有详细说明如何得到QQ中在线好友的
      ip地址,因为本文重点不在于此,本文只是提出一种稍微高阶一些的补丁方法——调用外部dll
      来给程序添加功能。

正文:首先考虑在何处显示ip地址的问题。现在所有的显ip,地址补丁的QQ都是利用原来QQ的广告
      窗口位置显示的,这样做是基于如下两点考虑:

  1    现在QQ的Flash广告的确很烦人,稍不小心,就容易中招,点击到腾讯的网站上去。

  2    广告窗口的hwnd值我们可以很方便地得到,这样,就为我们使用SetWindowText打下了基础。

      要使用广告窗口来显示我们的信息,当然首先要屏蔽掉广告窗口上面的图片,其次,还要屏蔽掉
      在广告窗口内部点击时,程序自动启动腾讯浏览器,进入腾讯站点。其中,屏蔽图片我们可以使
      用CreateFileA断点。屏蔽浏览器的启动,我们可以使用ShellExecuteA断点。

      使用CreateFileA以后,看到如下代码段:

:0043AEFF 895E54                  mov dword ptr [esi+54], ebx

* Reference To: KERNEL32.CreateFileA, Ord:0034h
                                  |
:0043AF02 FF1594125800            Call dword ptr [00581294]
:0043AF08 8BF8                    mov edi, eax
:0043AF0A 83FFFF                  cmp edi, FFFFFFFF
:0043AF0D 897D08                  mov dword ptr [ebp+08], edi
:0043AF10 0F84EB000000            je 0043B001                  //我们只要修改这个跳转就可以了
:0043AF16 8D45F4                  lea eax, dword ptr [ebp-0C]

      我们需要修改上述跳转到如下地点:

:0043AFDE 3945FC                  cmp dword ptr [ebp-04], eax
:0043AFE1 7427                    je 0043B00A
:0043AFE3 FFB688000000            push dword ptr [esi+00000088]  //使上面的跳转跳到这里
:0043AFE9 E8D7801000              call 005430C5
:0043AFEE 59                      pop ecx

      使用ShellExecuteA断点栏下来以后,往上看,可以看到如下代码:

:00459C7F 50                      push eax
:00459C80 FFB64C030000            push dword ptr [esi+0000034C]  // [esi+34C]就是广告窗口hwnd

* Reference To: USER32.GetWindowRect, Ord:015Ch
                                  |
:00459C86 FF1578175800            Call dword ptr [00581778]
:00459C8C 8D45DC                  lea eax, dword ptr [ebp-24]
:00459C8F 8BCE                    mov ecx, esi
:00459C91 50                      push eax
:00459C92 E8FECE0E00              call 00546B95
:00459C97 FF7510                  push [ebp+10]
:00459C9A 8D45DC                  lea eax, dword ptr [ebp-24]
:00459C9D FF750C                  push [ebp+0C]
:00459CA0 50                      push eax

* Reference To: USER32.PtInRect, Ord:01EAh
                                  |
:00459CA1 FF15B0175800            Call dword ptr [005817B0]
:00459CA7 85C0                    test eax, eax      //修改这一句为 xor eax,eax,正好两字节
:00459CA9 0F844C010000            je 00459DFB         

        上面是发送消息的时候的判断代码,接受消息的窗口,也有类似的代码如下:

:004B02CC FF750C                  push [ebp+0C]
:004B02CF 50                      push eax

* Reference To: USER32.PtInRect, Ord:01EAh
                                  |
:004B02D0 FF15B0175800            Call dword ptr [005817B0]
:004B02D6 85C0                    test eax, eax        //同样改为xor eax,eax 两字节
:004B02D8 0F8409010000            je 004B03E7

        如此,我们让广告窗口消失了,然后,到了我们如何得到在线好友的ip,457A4A处代码如下:

:00457A44 6824010000              push 00000124
:00457A49 57                      push edi
:00457A4A E8CB350F00              call 0054B01A
:00457A4F 8D86E4030000            lea eax, dword ptr [esi+000003E4]
:00457A55 50                      push eax
:00457A56 53                      push ebx

        当我们执行到457A4F处时,健入命令:d *(*(esi+414)+358),就可以看到我们所选择的在线
     好友的ip地址,如何得到414和358这两个数,需要大家的经验和运气,基本上QQ的每一个版本
     这两个数得值都会不同。我们现在得到了我们需要的ip地址,下面来到了本文最核心的地方:
     如何把这个ip转换成为地址数据,显示在广告窗口。

     在任何一个可以显示地址的QQ中,都可以看到两个文件是原版的QQ所不具有的,1个是
     ipsearcher.dll,一个是wry.dll,其中,wry.dll是著名的根据ip查地址软件“追捕”所使用的
     数据库,ipsearcher.dll不过是它的一个界面,让你可以更加方便的使用wry.dll,ipsearcher.dll
     的使用方法及其简单(后面可以看到它的使用),它里面只有一个引出函数_GetAddress,我们
     需要作的事情就是让QQ启动的时候,自动加载ipsearcher.dll。这里需要PE文件格式的知识,而
     且极易出错,操作的时候务必小心。操作步骤如下:

  1    用PEditor打开QQ2000b.exe,点击director,可以看到imp table的RVA是1CCE28,Size是17C。
       
  2    使用PEditor的FLC功能,得到RVA 1CCE28==File offset 1CCE28,好,方便了我们的工作。

  3    用WinHex打开QQ2000b.exe,Alt+G,填入1CCE28,然后拖动鼠标选择,看着右下方有Size标注
        出你选择的块的大小,当选择的区域达到17C,停止拖动,Ctrl+C拷贝选择区域到剪贴版。

  4    再次用PEditor打开QQ2000b.exe,点击section,看到.data段的起始地址为:1D1000

  5    回到WinHex,Alt+G,填入1D1000,看到上面有很大一篇空间为0,于是在1D0150处Ctrl+B,把
        剪贴版上的数据填充到此处(注意是Ctrl+B,不是Ctrl+V,Ctrl+V是拷贝,会增加文件的长度)
        Ctrl+S存盘。

  6    PEditor的director中,手动修改imp table的RVA为:1D0150(因为QQ的File offset==RVA)

  7    在WinHex中,按照下图写入新的引入函数项。



        在此,对OrignalFirstThunk和 FirstThunk项作一次说明:
    
     系统在程序初始化时根据OrignalFirstThunk的值找到函数名,然后调用GetProcAddress函数根
     据函数名取得函数的入口地址,然后用函数入口地址取代FirstThunk指向的地址串中对应的值。
     有的程序的OrignalFirstThunk的值为0,则初始化时系统根据FirstThunk的值找到指向函数名的
     地址串,由地址串找到函数名再根据函数名得到入口地址,然后用入口地址取代FirstThunk指向
     的地址串中的原值。

     根据这个说明我们可以看出,在添加新的引入函数的时候,真正重要的是FirstThunk处的值,
     OrignalFirstThunk填不填写无关紧要,要填的话,就得填入一个真正有效的值,如果嫌麻烦,
     可以使其为0。具体到QQ中,也就是说1D02B8处,我们完全可以不填写任何数据,直接写0。
     而FirstThunk处的值,一定要填写正确,我们才能调用该Dll中的函数,在这里,FirstThunk
     的指针关系是这样的:

     1D0310-->1D02E0-->(0100)_GetAddress

     而RVA=1D0310在程序执行时的VA是5D0310,所以,我们的代码中,写入Call dword [5D0310]
     就是写入CALL IPSEARCHER!_GetAddress。我们的程序中,还要使用到其它的几个函数,都可
     以用这种方式引入,现列表如下:

     Call dword [5D0310]    ==    Call  IPSEARCHER!_GetAddress

     Call dword [5814D4]    ==    Call  USER32!SetWindowTextA

     Call dword [5816E4]    ==    Call  USER32!wsprintfA

     最后就是QQ显地址的最后实现代码了,由于我们需要写的代码并不是很多,所以我就直接利用
     了前面修改CreateFileA时,空下来的代码空间,写的时候要注意,最好在PEditor里面把代码
     段的属性改为可执行的(也就是最高一位为1)

     457A4A处如此修改:

0167:00457A4A E8CB350F00      CALL    0054B01A
0167:00457A4F E9C134FEFF      JMP      0043AF15
0167:00457A54 90              NOP   

        跳到43AF15后,执行如下代码:

0167:0043AF15 51              PUSH    ECX
0167:0043AF16 52              PUSH    EDX
0167:0043AF17 56              PUSH    ESI
0167:0043AF18 8B8614040000    MOV      EAX,[ESI+0414]
0167:0043AF1E 8B8058030000    MOV      EAX,[EAX+0358]
0167:0043AF24 803800          CMP      BYTE [EAX],00
0167:0043AF27 7436            JZ      0043AF5F
0167:0043AF29 50              PUSH    EAX
0167:0043AF2A FF1510035D00    CALL    `IPSEARCHER!_GetAddress`
0167:0043AF30 83C404          ADD      ESP,BYTE +04
0167:0043AF33 FF7004          PUSH    DWORD [EAX+04]
0167:0043AF36 FF30            PUSH    DWORD [EAX]
0167:0043AF38 B890B95F00      MOV      EAX,005FB990
0167:0043AF3D 50              PUSH    EAX
0167:0043AF3E B8A0B95F00      MOV      EAX,005FB9A0
0167:0043AF43 50              PUSH    EAX
0167:0043AF44 FF15E4165800    CALL    `USER32!wsprintfA`
0167:0043AF4A 83C410          ADD      ESP,BYTE +10
0167:0043AF4D B8A0B95F00      MOV      EAX,005FB9A0
0167:0043AF52 50              PUSH    EAX
0167:0043AF53 FFB64C030000    PUSH    DWORD [ESI+034C]
0167:0043AF59 FF15D4145800    CALL    `USER32!SetWindowTextA`
0167:0043AF5F 5E              POP      ESI
0167:0043AF60 5A              POP      EDX
0167:0043AF61 59              POP      ECX
0167:0043AF62 8D86E4030000    LEA      EAX,[ESI+03E4]
0167:0043AF68 E9E8CA0100      JMP      00457A55

          上面这段代码需要注意如下几个问题:

    1    推荐使用Hiew写代码,这样修改方便,并且免除动态调试的时候各种不稳定的麻烦。TRW的A命
          令的确不是很好用。最好的一点是Hiew里面写了代码直接存盘就可以了,不象TRW里面,还得
        转几个弯才能写回文件里面。

    2    一定要注意堆栈的平衡问题。调用了_GetAddress和wsprintfA以后,都有堆栈的调整语句,这
          些语句处理不好,马上就让程序崩溃了。

    3    要注意自己的代码中,寄存器的值得保存的问题。上面众多的push,pop指令就是为此而写。

    4    从自己的代码返回到程序原来的代码中时,原来的程序有一句LEA  EAX,[ESI+03E4],这里要注
          意加上。

          修改完代码以后,我们还需要在eXeScope中修改广告窗的样式,让其看起来更加美观。

        经过以上几道手续,QQ的修改工作就算最后完成了。

注意:    这个显示ip的东西是调用的著名的“追捕”的数据库wry.dll,所以,能够分辨出来的ip的多少,取决
          于你的wry.dll的版本的新旧。想用得更爽的朋友,自己去找最新的追捕吧!

==========================================================