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

  一转眼,又发现很久没有写点什么东西了。几年来,诸事皆惰,不知所欲,着实令人颓丧。
  好吧,总要写点东西向别人证明一下我还喘气。
  这个成果没有什么商业价值,嘿嘿,所以可以公布一下,顺便打一打我们“鑫勇士工作室”的名号。
  讲的过程中省略了很多逆向基础知识,我假设看官都知道了。否则你需要好好补习补习去了。
  言归正传。
  在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的血啊!!
  结束,欢迎拍砖,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值