(PE修改)QQ华夏显示对方生命值与法术值

来源:互联网 发布:mac怎么卸载输入法软件 编辑:程序博客网 时间:2024/04/28 10:18

  一转眼,又发现很久没有写点什么东西了。几年来,诸事皆惰,不知所欲,着实令人颓丧。
  好吧,总要写点东西向别人证明一下我还喘气。
  这个成果没有什么商业价值,嘿嘿,所以可以公布一下,顺便打一打我们“鑫勇士工作室”的名号。
  讲的过程中省略了很多逆向基础知识,我假设看官都知道了。否则你需要好好补习补习去了。
  言归正传。
  在QQ华夏这个游戏中(其实华夏II也一样),当前选中的对象,只能看见血条蓝条,却不知道具体量多少,100血与10000血都是一样长的条条。在打高级怪的时候特别郁闷,没打不知道怪有多皮实,打不动的时候才知道,原来怪血这么厚!
  于是,我们有必要知道当前对象有多少血嘛。但是游戏公司愣是不这么做,也不知道是出去什么目的。
  OK,那我们自己来打造一个补丁,显示当前对象的红与蓝。
  关注到:鼠标移到自己的血条与蓝条上面,会有相应的Tooltips显示“生命值:XXX”“法术值:XXX”,而鼠标移动对方的血条蓝条上没有一点动静。
  那么,究竟这两个类型相同的事件在触发上有什么区别?
  通过跟踪发现,区别就在于,鼠标移动到对方的血条蓝条上触发的函数是一个空函数,所以你什么都看不到(这个函数下面会被展示)。
  而显示自身的血量与蓝量的函数哩?显然,这个函数做了不少事。使,我们看到了想要的结果。
  怎么跟到这个函数?关键字符串定位喽--“生命值”,定位到WndMgr.dll中的函数如下(我们这里暂且把它叫做func1):


IDA
=
.text:
10045965                 push    ebp
.text:
10045966                 mov     ebp, esp
.text:
10045968                 sub     esp, 64h
.text:1004596B                 mov     
[ebp+var_64], ecx
.text:1004596E                 mov     ecx
, [ebp+var_64]
.text:
10045971                 call    ds:XWindow::GetDesktop(void)
.text:
10045977                 mov     [ebp+var_14], eax
.text:1004597A                 cmp     
[ebp+var_14], 0
.text:1004597E                 jnz     short loc_10045985
.text:1004597E
.text:
10045980                 jmp     loc_10045A4D
.text:
10045980
....................................................
.text:100459AA                 mov     edx
, [ebp+var_64]
.text:100459AD                 mov     ecx
, [edx+0ECh]
.text:100459B3                 call    sub_1003017A
.text:100459B3
.text:100459B8                 push    eax
.text:100459B9                 mov     eax
, [ebp+var_64]
.text:100459BC                 mov     ecx
, [eax+0ECh]
.text:100459C2                 call    sub_100300D8
.text:100459C2
.text:100459C7                 push    eax
.text:100459C8                 push    offset s_DD_0   
; "生命值: %d / %d"
.text:100459CD                 lea     ecx, [ebp+var_54]
.text:100459D0                 push    ecx             
; char *
.text:100459D1                 call    _sprintf
.text:100459D1
.text:100459D6                 add     esp
, 10h
.text:100459D9                 jmp     short loc_10045A18
....................................................

 


  而另外一个触发函数却在哪里哩?定位的思想:他们是同一个类型的对象,至少,他们的父类是相同的,那么这个触发的成员函数应该都是从父类派生的。
  自己对象的成员函数被重构了,而对方对象的成员函数却没有被重构或被重构成空函数。这说明,他们的成员函数调用应该是一样的。
  断点自身对象显示触发:+45965h,函数返回,可以看见一个call dword ptr [edx+XX]的指令,断点取消,在这条指令下断。
  我们可以猜测这条指令其实就是调用鼠标触发显示Tooltips的。事实证明的确如此。
  现在可以鼠标移动到当前对象的血条去触发当前对象的成员函数。(注意小心触发别的Tooltips的显示而干扰定位的准确)
  中断跟进,得到成员函数如下(我们这里暂且把它叫做func2):

[code]
07B3B72A  /.  
55                  push    ebp
07B3B72B  |.  8BEC                mov     ebp
, esp
07B3B72D  |.  
51                  push    ecx
07B3B72E  |.  894D FC             mov     
[local.1], ecx
07B3B731  |.  8BE5                mov     esp
, ebp
07B3B733  |.  5D                  pop     ebp
07B3B734  .  C2 
1000             retn    10
[/code]


  看到吧,什么事都不做。当然不能显示当前对象的血量与蓝量喽。
  改造它!怎么改?我们能不能利用现有的函数?
  由于两个成员函数都是从父类派生的(当然是我们自己想当然的啦,不过也许事实就是如此呢?嘿嘿),它们的调用格式(参数、类型等)是一致的。故而,我们可以在+3B72Ah处来个jmp,直接跳到自身对象的成员函数去嘛!
  然而,既然是作为两个类众父类派生,这类之间总是有点区别的嘛,不能一个jmp就完事啊,还得看看jmp后能不能如你所愿地干活。
  自然不行,一个jmp就能搞定,你也太小看逆向事业了。
  为什么不行?只能看见一个空的tooltip被显示出来了。这只说明,成功了一半。
  为什么?动态跟啦。跟踪过程略。
  首先,传递进来的this指针有差异。自身触发时,自身对象存在于this+88h处。而对方触发时,对方对象存在于this+0E0h处。
  看来,func1的取值部分要改写了。
  分析func1的流程,发现对鼠标位置有判断,然后在对方触发时,这个判断会失效。
  怎么办?折衷。干脆,把这两个判断都去掉,不管是指向血条还是蓝条,血量与蓝量都显示不就结了?嘿嘿。
  那么血量和蓝值哪里去取?这里点一下:[this]+5Ch处的成员函数可以给你一些帮助哦。压参7.8.9.10分别返回血量、血量上限,蓝量,蓝量上限。(其它参数自己琢磨去,不透露,嘿嘿)
  取值也准备好了,那格式化字符串的事呢?这事还是让程序原来的代码来做这事吧。
  但是问题又出来了:生命与法术是两个字符串啊。--这是一个很小的问题啦,这两个字符串不是紧挨着嘛,第一串的null换掉不就结了。。。
  但是换什么好呢?空格?这样显示的结果太长了。分行?0Dh与0Ah在sprintfA中都被忽略。想要分行还得给一个"/ n"。但是它有两个byte啊。。。
  变通一下老大,那么我们就把字符串合并且缩减嘛。于是把字符串改造成:“生命:%d / %d /n法术:%d / %d”后面补0。
  改造代码如下:

 


0766040F   > 
55                  push    ebp
07660410   .  8BEC                mov     ebp, esp
07660412   .  83EC 64             sub     esp, 64
07660415   .  894D 9C             mov     dword ptr [ebp-64], ecx
07660418   .  8B4D 9C             mov     ecx, dword ptr [ebp-64]
0766041B   .  FF15 18C46A07       call    dword ptr 
[<&WndSys.XWindow::GetDesktop>]     ;  WndSys.XWindow::GetDesktop
07660421   .  8945 EC             mov     dword ptr [ebp-14], eax
07660424   .  837D EC 00          cmp     dword ptr [ebp-14], 0
07660428   .  75 05               jnz     short 0766042F
0766042A   .  E9 C8000000         jmp     076604F7
0766042F   >  8B4D EC             mov     ecx
, dword ptr [ebp-14]
07660432   .  FF15 28C46A07       call    dword ptr [<&WndSys.XDesktop::GetSysToolTip>] ;  WndSys.XEdit::GetValidHeight
07660438   .  8945 A8             mov     dword ptr [ebp-58], eax
0766043B   .  837D A8 
00          cmp     dword ptr [ebp-58], 0
0766043F   .  
75 05               jnz     short 07660446
07660441   .  E9 B1000000         jmp     076604F7                        ;从这个后面开始修改
07660446   >  8B4D 9C             mov     ecx, dword ptr [ebp-64]        ;取到前面保存的this
07660449   .  8B81 88000000       mov     eax, dword ptr [ecx+88]        ;先到+88处取值
0766044F      3D FFFFFF00         cmp     eax, 0FFFFFF        ;增加代码稳定性,因为如果是当前对象触发,+88取到的值不定,也不一定为0
07660454      7F 0A               jg      short 07660460
07660456      8B81 E0000000       mov     eax, dword ptr [ecx+E0]        ;如果+88取不到值,就往+E0处取
0766045C      85C0                test    eax, eax
0766045E      
74 65               je      short 076604C5                ;如果+E0处也取不到值,那就全跳过吧
07660460      8BC8                mov     ecx, eax
07660462      894D A0             mov     dword ptr [ebp-60], ecx        ;保存取到的对象
07660465      8B4D A0             mov     ecx, dword ptr [ebp-60]
07660468      6A 0A               push    0A
0766046A      8B01                mov     eax
, dword ptr [ecx]
0766046C      FF50 
64             call    dword ptr [eax+64]            ;取蓝上限
0766046F      50                  push    eax                    ;蓝上限入栈,__cdel调用格式,参数反入栈
07660470      EB 05               jmp     short 07660477;后面五个字节不能用。因为这里原来是:push    offset s_DD_0   ; "生命值: %d / %d"
07660472      90                  db      90    ;因为是全局变量,PE加载器会在DLL加载的时候进行重定位,跟踪的时候不会错,但修改PE文件的时候,
07660473   .  9090F287            dd      87F29090    ;会因为代码改变而使程序崩溃,为了懒得去动DLL的重定位表,我们干脆就放弃使用这5字节,爱咋咋地
07660477   >  8B4D A0             mov     ecx, dword ptr [ebp-60]
0766047A   .  6A 
09               push    9
0766047C   .  8B01                mov     eax
, dword ptr [ecx]    ;取蓝值
0766047E   .  FF50 64             call    dword ptr [eax+64]
07660481   .  50                  push    eax
07660482   .  8B4D A0             mov     ecx, dword ptr [ebp-60]
07660485   .  6A 08               push    8
07660487   .  8B01                mov     eax, dword ptr [ecx]
07660489   .  8B01                mov     eax, dword ptr [ecx]
0766048B   .  FF50 
64             call    dword ptr [eax+64]    ;取血值上限
0766048E   .  50                  push    eax
0766048F   .  8B4D A0             mov     ecx
, dword ptr [ebp-60]
07660492   .  6A 07               push    7
07660494   .  8B01                mov     eax, dword ptr [ecx]
07660496   .  FF50 64             call    dword ptr [eax+64]    ;取血值
07660499   .  50                  push    eax
0766049A   .  EB 
15               jmp     short 076604B1        ;四个参数压完后直接跳到后面让原来的代码给我们字符串格式化了
0766049C   .  A0 6A078B01         mov     al, byte ptr [18B076A]
076604A1   .  FF50 
64             call    dword ptr [eax+64]
076604A4   .  
50                  push    eax
076604A5   .  EB 0A               jmp     short 076604B1
076604A7      D0                  db      D0
076604A8      
00                  db      00
076604A9      
00                  db      00
076604AA      
00                  db      00
076604AB   .  E8 F8BAFEFF         call    0764BFA8
076604B0   .  
50                  push    eax
076604B1   >  
68 78616C07         push    076C6178                                      ;  生命:%d / %d 法术:%d / %d
076604B6   .  8D45 AC             lea     eax, dword ptr [ebp-54]    ;注意,前面的全局变量的相对偏移要改了吧。原来它是指向“法术值...”
076604B9   .  50                  push    eax
076604BA   .  E8 0DBF0300         call    0769C3CC
076604BF   .  83C4 
10             add     esp, 10
076604C2   >  8D4D AC             lea     ecx
, dword ptr [ebp-54]
076604C5   .  
51                  push    ecx
076604C6   .  8B4D A8             mov     ecx
, dword ptr [ebp-58]
076604C9   .  FF15 A8C46A07       call    dword ptr 
[<&WndSys.XToolTip::SetText>]       ;  WndSys.XToolTip::SetText


  好了,累人,总结一下:
[code]
一:主函数修改+37h
0123456789abcdef0123456789abcdef //一行16个字节,共5行又6字节

8B4D9C8B81880000003DFFFFFF007F0A
8B81E000000085C074658BC8894DA08B
4DA06A0A8B01FF506450EB0590909090
908B4DA06A098B01FF5064508B4DA06A
088B018B01FF5064508B4DA06A078B01
FF506450EB15

二、数据修改
076C6178  C9 FA C3 FC A3 BA 25 64 20 2F 20 25 64 5C 6E B7  生命:%d / %d/n
076C6188  A8 CA F5 A3 BA 25 64 20 2F 20 25 64 00 00 00 00  ㄊ酰?d / %d....

三、跳转修改
07657ADE     /E9 2C890000         jmp     0766040F
07657AE3     |90                  nop
07657AE4     |90                  nop
07657AE5  |. |8BE5                mov     esp, ebp

四、原始数据偏移修改 -10h
076604B1   >  68 78616C07         push    076C6178                                      ;  生命:%d / %d/n法术:%d / %d

[/code]
  先在OD中改一遍,跑一下成不成功,成功后把数据直接拿去改PE。RVA搞不清楚??那你还能看到这里?好好学习,天天向上去!
  修改前把原版的DLL保存一下,免得你改乱了游戏都得重新下载。
  这里不放出修改好的版本,免得不劳而获的人太高兴,而且,或有人问:“别是木马吧”,这句话噎死人。好了,只给代码,自己鉴别去。
  省略了很多,看得明白共同进步,看不明白你当我在聒噪也行。
  另外,这是2008.5.1前的版本分析,5.1后改版了,相对偏移自己跟踪去,以上代码都是从我随笔日志中cpy过来的,所以我这里懒得更新,嘿嘿。
  补丁选择一个对象,鼠标过去,看看,Tooltips显示了么,嘿嘿,有点爽吧。要知道,5.1活动,怪“藏毒”有80000的血啊!!
  结束,欢迎拍砖,谢谢!