手把手一起写现在某热门游戏的跑钱外挂(一)(二)(三)(四)

先声明,此为技术研究,不要拿来当非法用途.面向中高级的逆向与程序编写程序员.
此游戏为某公司的某个热门回合制游戏,我会从一开始入手调试到分析,再到成品分批写文放出.....

我所用的写代码语言是masm32 就是汇编啦,哈哈...用C的高手们就不好意思了.因为地址定位用C太麻烦,又不想用C内嵌汇编,太难看..

技术层面从基本的调试分析思路,到内存搜索定位,注入,SEH搜索错误处理来定点搜索内存,内存地址位置转跳修改和修改挟持(系统API也有),由于是内存外挂,所以要处理游戏程序的自校验,过自校验方法不放出(我可不想收律师信),请自行思索..不过自校验也只是提示,还是能执行的.

外挂程序成品是该游戏的一个分支的自动挣游戏币,里面包括战斗,走路,NPC对话,地图转换,物品检测.

OK,废话完了,我们开始动手.从理论开始做..

先搞清楚我们要做的,就是利用游戏的内存定位修改来得到游戏数据,接着按照得到的数据利用鼠标键盘模仿操作(SendMessage,或者直接压参数call也行,随你喜欢),不要搞变态功能(因为为了跑钱的),实现人物走路加速和战斗加速只要修改一下断点内存就能做到,我会在后面的文章加以说明.

首先,就是OD载入(又废话了..),收集数据开始...

0051171E > $  6A 60         push    60
00511720   .  68 F0975700   push    005797F0
00511725   .  E8 9A520000   call    005169C4
0051172A   .  BF 94000000   mov     edi, 94
0051172F   .  8BC7          mov     eax, edi
00511731   .  E8 3ADBFFFF   call    0050F270
00511736   .  8965 E8       mov     dword ptr [ebp-18], esp
00511739   .  8BF4          mov     esi, esp
0051173B   .  893E          mov     dword ptr [esi], edi
0051173D   .  56            push    esi                              ; /pVersionInformation
0051173E   .  FF15 58715500 call    dword ptr [<&KERNEL32.GetVersion>; /GetVersionExA

标准的C++,,我暴汗,以前我调试时那游戏可是加了License的,那时过检测和分析代码累死我,API全是乱序的.现在居然把壳放掉? OK,那就更简单了.
Shift+F9直接就跑起来了,汗.还真不检测了...

开始,注入方式考虑,由于是外挂,不是木马,所以是用户配合程序,所以注入方式可以有很多,DLL替换或者远线程注入,既然用户配合,那就不用DLL替换,我们来远线程注入(无视杀毒)..

下面代码是我自己写的远线程DLL加载,自己看代码处理方式,使用方法是对目标程序使用VirtualAllocEx申请内存,之后将下面代码的DllNameA填充好我们的外挂DLL的路径,用WriteProcessMemory写入目标程序,之后用CreateRemoteThread建立远线程
下面代码会在目标程序加载我们指定的DLL文件,我们在DLL文件的入口,DLL_PROCESS_ATTACH时对游戏程序做内存修改与挟持就可以了.
此代码可通用XP,2003,VISTA,WIN7.
assume   fs:nothing
mov   eax,fs:[30h]
mov   eax,[eax + 0ch]
mov   esi,[eax + 1ch]
lodsd
mov   eax,[eax + 8h]
mov  edi,eax
call  @f
loadlib  db  'LoadLibraryExA',0  ;为什么是ExA?不用A? 因为啊,要兼容WIN7.所以是ExA 再问就自己去调试WIN7的处理方式- -!..
@@:
pop  edx
mov  esi,edx
call  _test
add  edx,143          ;这里编译后的代码大小,用来定位DllNameA路径数据的 没修改代码的情况下别改
push  0
push  0
push  edx
call  esi
add  eax,1000h  ;无视掉.懒得编译修改,修改的话上面的143大小也要修改.
mov  ecx,1234  ;同上
nop      ;同上
ret
assume  esi:nothing
_test    proc  uses eax ebx ecx edx edi    ;这段代码用来搜索函数的.
    
    add     eax, [eax + 3Ch]
    assume    eax: ptr IMAGE_NT_HEADERS
    mov  ebx, edi
    add  ebx, [eax].OptionalHeader.DataDirectory.VirtualAddress    ;IMAGE_EXPORT_DIRECTORY引出表入口
    assume    eax: nothing
    mov        ecx, ebx
    mov  ebx, [ecx + 1Ch]                ;AddressOfFunctions的地址
    mov  edx, ecx
    mov  edx, [edx + 18h]                ;NumberOfNames地址
    push  edx
    mov  edx,esi
    mov  ebp, ecx      ;IMAGE_EXPORT_DIRECTORY引出表入口
    mov  ebp, [ebp + 20h]    ;AddressOfNames 地址
    
    mov  esi, ecx
    mov  esi, [esi + 24h]    ;AddressOfNameOrdinals
    push    ebx
    push    esi
    xor  ebx, ebx
    xor  ecx, ecx
    xor  eax, eax
    add  ebp, edi
    
  getApiGetModuleHandleA:
    mov  esi, edi
    add  esi, [ebp + ecx * 4]
  cmpAPI:
    mov  al, [edx + ebx]
    cmp  al, [esi + ebx]
    jne  getNext
    inc  ebx
    mov  al, 0
    cmp  al, [esi + ebx]
    jne  cmpAPI
    je  getOK
  getNext:
    xor  ebx, ebx
    inc  ecx
    cmp  ecx, [esp + 8]
    jne  getApiGetModuleHandleA
  getOK:
    pop  esi   
    pop  ebx
    add  ebx, edi
    mov  esi, [ebx + ecx * 4]
    add  esi, edi      ;esi为API地址
    pop  eax
  ret
_test endp

DllNameA  db  260 dup (0)

面向中高手,代码我就不一一说明了,只是用来加载DLL到目标进程的东西,自己看,看不懂的话下面我写的游戏调试和其它的处理估计也会看得不太懂,努力吧.
基础的远线程载入就好了,现在我们建立我们的DLL文件,从空白的开始.masm32汇编,自己看着办

    .586
    .model flat, stdcall
    option casemap :none

include    windows.inc
include    kernel32.inc
includelib  kernel32.lib

    .code
_Init    proc  uses ebx esi edi

          ;这里写入初始化的数据,用来写游戏进程的内存.先用messagebox来先测试代码的可用性吧.
    ret

_Init endp

DllEntry  proc  _hInstance,_dwReason,_dwReserved
    
    cmp  _dwReason,DLL_PROCESS_ATTACH
    jnz  @f
    push  eax
    invoke  CreateThread,0,0,offset _Init,0,0,esp
    invoke  CloseHandle,eax
    pop  eax
    @@:
    mov  eax,TRUE
    ret

DllEntry  Endp
    End  DllEntry

DLL文件加载和处理基本就上面那些,当然细节要自己搞了,哈哈  ..准备工作做好了,我们来对游戏进程调试了...GO`````
再用OD载入,为了写个程序开始的执行点,以前代码是一开始就加密了的,现在不用,我们还是定位个程序开始执行后的点吧,以后会有用的.
断点:CreateWindowExA  Shift+F9,断下来了..
77D2E4A9 >  8BFF            mov     edi, edi                ;CreateWindowExA入口
77D2E4AB    55              push    ebp
77D2E4AC    8BEC            mov     ebp, esp

断到第1次窗口建立,它有建立窗口,利用价值有了,我们利用它能知道代码已经全部解密(对已加壳的程序来说),在这里开始对游戏内存进行修改了,写一次性代码写入它,再用代码还原覆盖回去再JMP跳回CreateWindowExA让它继续跑就行了.
注入条件齐备,下一篇是游戏调试分析,每天晚上一篇不同技术,到说完出成品,呵呵.

说点题外话,什么防外挂木马的,要处理我觉得还是可行吧?即使没游戏的源代码,最可行的是写驱动进行游戏数据加密与发送数据加密,驱动进行本进程的数据校对(不让你修改看你怎么搞,干掉驱动我让你玩不了,没驱动加密的异常数据发送过来了,嘻嘻,写段代码自动封号..),其实驱动模版我写了1个,嘻嘻,通用32位的WIN7,VISTA,2003,XP
主要功能就是保护指定进程内存不被修改(一发现任何1字节内存修改马上退出)当然驱动也有自校对保护了,哈哈,功能能嘛,是用驱动限制程序启动,也就是每打开进程的话先提示进程路径,用户选择是否启动该进程(所有用户级的进程,不包含服务进程,呵呵,因为驱动HOOK的是createprocess,某种意义上说我HOOK的是系统所有内核调用- -!).

有人问到几可以看到成品,可以告诉你,,到七.等你看完就可以自己写出成品了.

接下来是调试篇,这个有点个人化和模糊,我只说重点.

还是那句,打开游戏,Shift+f9 直接跑,接着干嘛? 找有利用价值的字符串....

由于OD本来的搜索对汉字支持不太好,所以这里借助个插件,Ultra String Reference 看雪有下,嘻嘻

我们用插件,FIND ASCII 出来一大堆字符串,开始分析 有人说我了,你TM是不是调试啊,不从程序开始就分析? 我明确告诉你.我们写的是外挂,只要达到目的就行,要不要给你写个比源代码还详细的分析日志?有空没事做?

我下滚下滚下滚,发现了这么一些东西,这.不是脚本的合并串么? 猜测没用,直接断两个来试试.
Ultra String Reference, 条目 2011
 Address=00474C65
 Disassembly=push    0055B0A0
 Text String=类名=动画 脚本对象=%s 脚本参数=%s x=%d y=%d 图片=%s %s

Ultra String Reference, 条目 2014
 Address=00475195
 Disassembly=push    0055B110
 Text String=类名=复合动画 脚本对象=%s 脚本参数=%s x=%d y=%d 资源路径=%s %s

Ultra String Reference, 条目 2017
 Address=00475686
 Disassembly=push    0055B168
 Text String=类名=按钮 鼠标=3 脚本对象=%s 脚本参数=%s x=%d y=%d 图片=%s 说明=%s %s

Ultra String Reference, 条目 2019
 Address=00475A64
 Disassembly=push    0055B1B0
 Text String=类名=t按钮 鼠标=3 脚本对象=%s 脚本参数=%s x=%d y=%d 图片='%s' 颜色=%s 文字='%s' 尺寸=%d 粗细=%d alpha=%d %s

Ultra String Reference, 条目 2022
 Address=00475F8E
 Disassembly=push    0055B220
 Text String=类名=大话牌  脚本对象=%s 脚本参数=%s x=%d y=%d 编号=%d 图号=%d 边属性=%s %s

这个游戏嘛,打开时需要点击"进入游戏"的,也就是按钮,按照上面的合并串,类名=按钮 或 类名=t按钮 其中一个了,下断.
00475682  |.  8D5424 2C     lea     edx, dword ptr [esp+2C]
00475686  |.  68 68B15500   push    0055B168
0047568B  |.  52            push    edx
0047568C  |.  E8 2F900400   call    004BE6C0
断CALL那里,为什么我不用说了吧?
OD把程序重新加载,跑起来,哦,断了..
注意堆栈,资源路径,当然是被打包过的,呵呵..
0012ED1C   0012ED50
0012ED20   0055B168  
0012ED24   046789A8  ASCII "qaaYyU4.jY0EGS5"
0012ED28   01C61078
0012ED2C   0000278A
0012ED30   00000178
0012ED34   03F3BB18  ASCII "gires2/button/autoscroll2.tca"
0012ED38   021B41E0

dword ptr [0012ED50]=047AF27C
047AF27C  C0 E0 C3 FB 3D B0 B4 C5 A5 20 CA F3 B1 EA 3D 33  类名=按钮 鼠标=3
047AF28C  20 BD C5 B1 BE B6 D4 CF F3 3D 71 61 61 59 79 55   脚本对象=qaaYyU
047AF29C  34 2E 6A 59 30 45 47 53 35 20 BD C5 B1 BE B2 CE  4.jY0EGS5 脚本参
047AF2AC  CA FD 3D 31 20 78 3D 31 30 31 32 32 20 79 3D 33  数=1 x=10122 y=3
047AF2BC  37 36 20 CD BC C6 AC 3D 67 69 72 65 73 32 2F 62  76 图片=gires2/b
047AF2CC  75 74 74 6F 6E 2F 61 75 74 6F 73 63 72 6F 6C 6C  utton/autoscroll
047AF2DC  32 2E 74 63 61 20 CB B5 C3 F7 3D 20 20 B1 D8 D0  2.tca 说明=  必
047AF2EC  EB BB AD 3D 31 00                                牖?1..

这里返回值,有点与普通的字符合并有点不同,自己单步调试进去就知道,返回值为dword ptr [[esp]] 必须是调用后,dword ptr [esp]指向合并后字符串地址
既然得到了资源开始处理,要得到资源ID就是直接跟着F8就行,等它压入返回的字符串的CALL..

004756A2  |.  8D4424 38     lea     eax, dword ptr [esp+38]
004756A6  |.  50            push    eax
004756A7  |.  8D4C24 14     lea     ecx, dword ptr [esp+14]
004756AB  |.  51            push    ecx
004756AC  |.  51            push    ecx
004756AD  |.  8BC4          mov     eax, esp
004756AF  |.  896424 34     mov     dword ptr [esp+34], esp
004756B3  |.  C700 C9D9278D mov     dword ptr [eax], 8D27D9C9
004756B9  |.  8B5424 40     mov     edx, dword ptr [esp+40]
004756BD  |.  51            push    ecx
004756BE  |.  8BC4          mov     eax, esp
004756C0  |.  8910          mov     dword ptr [eax], edx
004756C2  |.  8B4C24 24     mov     ecx, dword ptr [esp+24]
004756C6  |.  896424 38     mov     dword ptr [esp+38], esp
004756CA  |.  E8 C1E8FFFF   call    00473F90
来了,一个CALL之后,接着的代码,在第3个参数压入了
0012ED30   FFFFFFFE
0012ED34   8D27D9C9
0012ED38   0012ED50
对比上面的数据,0012ED50就是这个了.里面放了字符串的值.也就是说函数里面解析这个脚本,OK,单步进去看看啥情况,要是懒人的话直接对字符串下内存读取断,接着在004756CA  |.  E8 C1E8FFFF   call    00473F90下面的返回下一个执行断,直接Shift+F9.
万一不是解析CALL,那内存读取断不生效,也就说不是这个函数,呵呵.要确定是不是的简单办法就是直接下内存读取断,又没人不让你多次重新载入程序.
我承认我是懒人,OK,下读取断,Shift+f9,ok,断了.
0046D2B5  |.  8A02          mov     al, byte ptr [edx]   ;在这里断了,猜一下,是不是解析点,我们下个条件记录断.
0046D2B7  |.  3C 20         cmp     al, 20
0046D2B9  |.  0F84 6F020000 je      0046D52E
0046D2BF  |.  3C 0A         cmp     al, 0A
0046D2C1  |.  0F84 67020000 je      0046D52E
0046D2C7  |.  3C 0D         cmp     al, 0D
0046D2C9  |.  0F84 5F020000 je      0046D52E
0046D2CF  |.  84C0          test    al, al
0046D2D1  |.  57            push    edi
断记录所有,表达式EDX,选ASCII,我们跑起来看看啥情况...注意看记录
看记录,应该偷笑了,全部脚本解析都在这里进行.
Log data, 条目 126
 地址=0046D2B5
 消息=COND: 045BD8C8 "x=-1000 y=-1000 ",BD,"疟?,B6,"韵?ip7Y3e0  ",BD,"疟?,B2,"问? 宽=0 ",B8,"?0 颜?,AB,"= 边框='' ",BB,"",AD,"",B2,"?0 模?,BD,"=0 "
Log data, 条目 112
 地址=0046D2B5
 消息=COND: 047551B4 "速",B6,"?7 n=26 ?,B7,"?,B6,"='gires/cursor/'"
Log data, 条目 68
 地址=0046D2B5
 消息=COND: 047AF27C "类名=",B0,"",B4,"钮 鼠标=3 ",BD,"疟?,B6,"韵?oNcakL2.qPnBKE2 ",BD,"疟?,B2,"问?1 x=122 y=376 图?,AC,"=gires2/button/autoscroll.tca 说?,F7,"=  必须",BB,"",AD,"=1"
Log data, 条目 34
 地址=0046D2B5
 消息=COND: 047551B4 "类名=图?,AC," x=52 y=215 图?,AC,"='gires2/login/stick.tcp'  z=4"

虽然汉字显示不太正常,不过按照我们习惯,很容易看出那是什么字吧?呵呵....
接着我们要做的,得到它建立出来的ID,这个ID在后面调试鼠标点击时用的.有人说了,为什么不直接发送MOVE消息给鼠标让它定点再发wm_lbuttonup,呵呵,因为这个游戏对鼠标捕获做了些处理,所以不能100%的得到想要的像素坐标.最后的文章我将说明怎么打内存补丁让鼠标恢复正常.
好了,拿到解析点,只要在解析点打个补丁,就能知道游戏内的所有提示资源或者程序将要做什么.走路或者战斗或者点击NPC
不过我做的是不再全部解析点做,记得上面的按钮建立吗?只要跟着那里做,可以实现自动登录了,拿ID去,怎么拿?单步一直跟到我们之前的断点,004756CA  |.  E8 C1E8FFFF   call    00473F90,发现了.
嘛,看见了没?压成浮点,将ID保存到内存中,有兴趣的可以跟进去看它保存到哪里.不过对我们作用不大.知道这里是按钮的ID就行了.
004756CA  |.  E8 C1E8FFFF   call    00473F90
004756CF  |.  DB4424 38     fild    dword ptr [esp+38]  ;这里就是该按钮的ID了,要知道详细的还是自己单步下,这是什么游戏?呵呵..SO呢.最后我会给提示的.
004756D3  |.  83EC 08       sub     esp, 8
004756D6  |.  DD5C24 30     fstp    qword ptr [esp+30]
004756DA  |.  DD4424 30     fld     qword ptr [esp+30]
004756DE  |.  DD1C24        fstp    qword ptr [esp]
004756E1  |.  E8 DA300300   call    004A87C0
004756E6  |.  50            push    eax
知道资源名,和ID,它是什么按钮也知道了吧?呵呵,接下来我们开始做坏事...
接下来就是看看鼠标左键点击处理了什么东西了,我们看窗口界面,对主窗口下断,当然就是下wm_lbuttonup了.
断到主窗口的消息处理那里了.
004C4960 > .  8B4424 04     mov     eax, dword ptr [esp+4]
004C4964   .  53            push    ebx
004C4965   .  55            push    ebp
004C4966   .  56            push    esi
004C4967   .  57            push    edi
004C4968   .  6A EB         push    -15                              ; /Index = GWL_USERDATA
004C496A   .  50            push    eax                              ; |hWnd
004C496B   .  FF15 04745500 call    dword ptr [<&USER32.GetWindowLon>; /GetWindowLongA
我们F8单步来到他的点击处理位置
004C49AC   > /8D0440        lea     eax, dword ptr [eax+eax*2]
004C49AF   .  8B4CC6 0C     mov     ecx, dword ptr [esi+eax*8+C]
004C49B3   .  8D04C6        lea     eax, dword ptr [esi+eax*8]
004C49B6   .  53            push    ebx
004C49B7   .  55            push    ebp
004C49B8   .  03CF          add     ecx, edi
004C49BA   .  FF50 08       call    dword ptr [eax+8]                ;  xy2.00467FE0
就是这个CALL了,点击弹起按钮的位置.F7进去,
00467FE0   .  56            push    esi                              ;  xy2.00593DC8
00467FE1   .  8BF1          mov     esi, ecx
00467FE3   .  8A46 49       mov     al, byte ptr [esi+49]
00467FE6   .  84C0          test    al, al
00467FE8   .  75 0B         jnz     short 00467FF5
00467FEA   .  8A46 3E       mov     al, byte ptr [esi+3E]
00467FED   .  84C0          test    al, al
00467FEF   .  0F85 90000000 jnz     00468085
00467FF5   >  8A46 3D       mov     al, byte ptr [esi+3D]
00467FF8   .  84C0          test    al, al
00467FFA   .  74 50         je      short 0046804C      ;单步发现到这里直接跳到下面去了,可以判断这个游戏的处理方式是按照标记点击进行,而不是在这个函数内处理的.
00467FFC   .  8B0D DC396000 mov     ecx, dword ptr [6039DC]
00468002   .  85C9          test    ecx, ecx
00468004   .  74 46         je      short 0046804C
00468006   .  8079 04 01    cmp     byte ptr [ecx+4], 1
0046800A   .  75 19         jnz     short 00468025
0046800C   .  A1 24365E00   mov     eax, dword ptr [5E3624]
00468011   .  8A50 3D       mov     dl, byte ptr [eax+3D]
00468014   .  84D2          test    dl, dl
00468016   .  74 0D         je      short 00468025
00468018   .  8B11          mov     edx, dword ptr [ecx]
0046801A   .  6A 00         push    0
0046801C   .  FF52 4C       call    dword ptr [edx+4C]
0046801F   .  8B0D DC396000 mov     ecx, dword ptr [6039DC]
00468025   >  8B01          mov     eax, dword ptr [ecx]
00468027   .  6A 03         push    3
00468029   .  FF50 4C       call    dword ptr [eax+4C]
0046802C   .  8B0D DC396000 mov     ecx, dword ptr [6039DC]
00468032   .  8B4424 08     mov     eax, dword ptr [esp+8]
00468036   .  8B11          mov     edx, dword ptr [ecx]
00468038   .  50            push    eax
00468039   .  FF52 54       call    dword ptr [edx+54]
0046803C   .  8B0D DC396000 mov     ecx, dword ptr [6039DC]
00468042   .  8B4424 0C     mov     eax, dword ptr [esp+C]
00468046   .  8B11          mov     edx, dword ptr [ecx]
00468048   .  50            push    eax
00468049   .  FF52 54       call    dword ptr [edx+54]
0046804C   >  FF86 80000000 inc     dword ptr [esi+80]   ;跳到这里了.看,下面有利用价值的只有mov     dword ptr [esi+560], edx和and     dword ptr [esi+A7E4], eax 它们影响了全局变量,也就说明都在这里判断,我们写入后,下硬件读取断点
00468052   .  8B96 60050000 mov     edx, dword ptr [esi+560]
00468058   .  B8 FEFFFFFF   mov     eax, -2
0046805D   .  23D0          and     edx, eax
0046805F   .  8996 60050000 mov     dword ptr [esi+560], edx
00468065   .  2186 E4A70000 and     dword ptr [esi+A7E4], eax
0046806B   .  8A46 5D       mov     al, byte ptr [esi+5D]
0046806E   .  84C0          test    al, al
00468070   .  75 0D         jnz     short 0046807F
00468072   .  8B0D 343D6000 mov     ecx, dword ptr [603D34]
00468078   .  C681 10010000>mov     byte ptr [ecx+110], 0
0046807F   >  FF15 EC735500 call    dword ptr [<&USER32.ReleaseCaptu>; [ReleaseCapture
00468085   >  33C0          xor     eax, eax
00468087   .  5E            pop     esi
00468088   .  C2 0800       retn    8
断点好了不?我们就直接Shift+F9跑起来,经过3次断点后,我们发现了..
004741F3  |. /75 67         jnz     short 0047425C
004741F5  |. |F686 C8040000>test    byte ptr [esi+4C8], 1  ;这里是我们下断点的位置,直接test判断,明白了吧?呵呵
004741FC  |. |75 5E         jnz     short 0047425C
004741FE  |. |F686 D0040000>test    byte ptr [esi+4D0], 1  ;这里是我们下断点的位置,直接test判断,明白了吧?呵呵
00474205  |. |74 55         je      short 0047425C
接着上下翻这段代码,发现..
00474268  |.  F686 C8040000>test    byte ptr [esi+4C8], 2
0047426F  |.  75 79         jnz     short 004742EA
00474271  |.  F686 D0040000>test    byte ptr [esi+4D0], 2
00474278  |.  74 70         je      short 004742EA
记得刚刚的左键点击压入值吗?没错,就是1,那么我们去断右键弹起,得到的的压入值是2,嘻嘻 也就是说左右键是这里进行判断的.接着我们就要搞清楚ID是从那里来了.OK,直接进入左键的点击,
004741F5  |.  F686 C8040000>test    byte ptr [esi+4C8], 1
004741FC  |.  75 5E         jnz     short 0047425C
004741FE  |.  F686 D0040000>test    byte ptr [esi+4D0], 1
00474205  |.  74 55         je      short 0047425C
00474207  |.  8B0D 58495900 mov     ecx, dword ptr [594958]          ;  xy2.00ACE980
0047420D  |.  894C24 10     mov     dword ptr [esp+10], ecx
00474211  |.  8B55 14       mov     edx, dword ptr [ebp+14]
单步让程序继续跑.当然要删除刚刚的全部断点了.呵呵,F7跟进去.观察寄存器和堆栈,记得上,面我们得到的ID吗?开始配对,看..EDI的值=我们的ID.猜测开始,EDI,就寄存器上说,一个程序在一个函数内是不会轻易改变的,我们往这个函数的上面看,
004740D9  |.  33FF          xor     edi, edi    ;清空.
004740DB  |.  E8 80CB0200   call    004A0C60
004740E0  |.  8B4D 08       mov     ecx, dword ptr [ebp+8]
004740E3  |.  83C4 04       add     esp, 4
004740E6  |.  85C9          test    ecx, ecx
004740E8  |.  894424 14     mov     dword ptr [esp+14], eax
004740EC  |.  74 03         je      short 004740F1
004740EE  |.  8B79 10       mov     edi, dword ptr [ecx+10]  ;写入..
004740F1  |>  8B86 E0040000 mov     eax, dword ptr [esi+4E0]
004740F7  |.  3BF8          cmp     edi, eax
发现EDI的写入只有这两句.没关系,我们暂时不鸟它,直接改掉EDI看看还能不能使用正常的EDI点击,先记录下EDI的值,一会要额外测试点其它地方把EDI改回去看情况.改成0看看.Shift+f9跑,发现.点击变无效了,
接(FFFFFFC3 我的EDI值)着我们用鼠标随便点游戏框内的一个地方,断.这次edi为FFFFFFC6,随便点游戏窗口内的位置的.接着我们把C3改进去看看效果.Shift+F9.
哦耶``搞掂,得到了按钮ID和按钮处理时的ID修改位置,只要HOOK这两个地方,就能实现只要发送WM_LBUTTONDOWN和UP就可以不用鼠标位置来操作游戏了.
按钮是这里,接着举一反三,记得上面的ID处理吧?图像和其它的ID都能拿到,顺带一提,游戏内的NPC ID也是这样获取和ID处理的,这个是什么游戏?自己看看哪个回合制游戏能种植的,就是它了.利用种植自动跑钱的.
我说出来的理由,是为了让大家更好理解上面说的,自己手动做一次就清楚点.呵呵.

今天有点晚了,因为吃饭时有约出去了,本来打算今天把调试篇全部写完,不过算了,后续留明天吧,明天是SEH处理方式的内存搜索定位(有人说内存挂不稳,因为游戏容易更新,我会用一种比较变态的方式去打补丁的,所以一般更新影响不了)和加内存补丁,我会把未说完的调试一起加进去.

补充昨天的调试部分和今天的内存搜索与补丁.高潮部分到了.想写加速的消化掉这篇你就成功了.
我即写即发的,难免会有错字或者其它错,大家要指出来哈.谢谢..
昨天调试,到了按钮部分.多次测试按钮和T按钮的分别,我们发现游戏功能性按钮是按钮那里的调用,而会改变的服务器选择和游戏内物品是T按钮.
人物对话框弹出的选择性语句,例如传送员的,里面也是T按钮,据此分析得出的结论不用说啦.哈哈.NPC对话和物品ID获取完毕.接着来的是关键点的,人物移动.与模型(NPC和游戏角色)的ID获取
记得我们之前的那个无敌脚本解析点么? 分析那里,得出这个游戏的指令是先串成文字脚本形式,再由那里把脚本解析掉做相应操作的.我们在上篇文章那里那里下条件记录断,建个角色,把记录保存到文件(记录会好多,保存下来好分析).
角色建好那我们开始OD加载,进入游戏.我们是要分析游戏其行为,所以条件记录断要一开始就让它生效来记录.哦,忘记了,这游戏新手村好多人,找个没人的房子进去闪一边那样会更容易分析.
随便让角色跑几步,我们开始分析记录文件,数据太多,我拿主要部分出来说.一句一句看吧,嘛,既然是先做成脚本串再解析的,那就试试搜索自己的角色名字看看有没发现,在log记录那里.
0046D2B5  COND: 04B0C610 "类名=t",B0,"",B4,"钮 鼠标=3 ",BD,"疟?,B6,"韵?c0FoWa1.fkEuxd2 ",BD,"疟?,B2,"问?0 x=102 y=195 图?,AC,"='' 颜?,AB,"=white 文",D7,"?'testdbyou' 尺",B4,"?11 ",B4,"窒",B8,"=400 alpha=0  ",B6,"云?4"
这句.哦,角色选择时的T按钮.类名=T按钮嘛..用ACSII来看..向下找自己角色名..OH NO 找到了..模型加载
0046D2B5  COND: 03F62AC8 "称?,BD,"='' 颜?,AB,"2='80C0FF' 名",D7,"?'testdbyou' 颜?,AB,"='00FF00' 速",B6,"?3 站?,A2,"=shape/char/0063/stand.tcp 站?,A2,"2=shape/char/0063/stand2.tcp 行",D7,"?shape/char/0063/walk.tcp ",B2,"",BD,"距=72 ",B2,"",BD,"速=24 跑",B2,
为何上面是模型加载?根据经验得出的,这个说不清楚,反正第1眼看到就觉得是重要的,还是猜的,哈哈..用上面语句,我们来在无敌记录断那里做个程序暂停的条件断点,为了得到这个模型的ID.看
设置条件断点,条件为dword ptr [edx] == 0bdcec6b3
0bdcec6b3='称谓'的ACSII逆序
04F64680  B3 C6 CE BD 3D 27 C1 F7 BD F0 CB EA D4 C2 D2 BB  称谓='流金岁月一
04F64690  D4 B1 27 20 D1 D5 C9 AB 32 3D 27 38 30 43 30 46  员' 颜色2='80C0F
04F646A0  46 27 20 C3 FB D7 D6 3D 27 74 65 73 74 64 62 79  F' 名字='testdby
04F646B0  6F 75 27 20 D1 D5 C9 AB 3D 27 30 30 46 46 30 30  ou' 颜色='00FF00
04F646C0  27 20 CB D9 B6 C8 3D 33 20 D5 BE C1 A2 3D 73 68  ' 速度=3 站立=sh
04F646D0  61 70 65 2F 63 68 61 72 2F 30 30 36 33 2F 73 74  ape/char/0063/st
04F646E0  61 6E 64 2E 74 63 70 20 D5 BE C1 A2 32 3D 73 68  and.tcp 站立2=sh
04F646F0  61 70 65 2F 63 68 61 72 2F 30 30 36 33 2F 73 74  ape/char/0063/st
04F64700  61 6E 64 32 2E 74 63 70 20 D0 D0 D7 DF 3D 73 68  and2.tcp 行走=sh
04F64710  61 70 65 2F 63 68 61 72 2F 30 30 36 33 2F 77 61  ape/char/0063/wa
04F64720  6C 6B 2E 74 63 70 20 B2 BD BE E0 3D 37 32 20 B2  lk.tcp 步距=72 
04F64730  BD CB D9 3D 32 34 20 C5 DC B2 BD 3D 73 68 61 70  剿?24 跑步=shap
04F64740  65 2F 63 68 61 72 2F 30 30 36 33 2F 72 75 6E 2E  e/char/0063/run.
04F64750  74 63 70 20 C5 DC B2 BD B2 BD BE E0 3D 31 30 38  tcp 跑步步距=108
04F64760  20 C5 DC B2 BD CB D9 B6 C8 3D 34 35 20 C5 E4 C9   跑步速度=45 配
04F64770  AB B7 BD B0 B8 3D 27 73 68 61 70 65 2F 63 68 61  桨?'shape/cha
04F64780  72 2F 30 30 36 33 2F 30 30 2E 70 70 27 00        r/0063/00.pp'.
哦,看见上面语句了吗?同学们,关键的东西来了,要搞破坏的赶快.跑步速度=45,要是修改掉这句话的这句参数的话,嘻嘻..一个外面跑步的加速挂就出现了..不过我们要的不是这种破坏式的,我们继续我们的.
看到这里,我们拿的是ID,那么我们取消掉刚刚的田间断,向下滚代码,找到RET就直接F9断,函数间隔用int3的,所以只要看到几个INT3就知道这个函数结束了.
1个返回点.继续,其实也可以用F8去单步,不过那么效率不够,嘻嘻..
0046D4DE  |.  8B4C24 1C     mov     ecx, dword ptr [esp+1C]
0046D4E2  |.  64:890D 00000>mov     dword ptr fs:[0], ecx
0046D4E9  |.  83C4 28       add     esp, 28
0046D4EC  |.  C2 0400       retn    4
0046D4EF  |>  2BF2          sub     esi, edx
2个..
0046D543  |.  5E            pop     esi
0046D544  |.  5D            pop     ebp
0046D545  |.  5B            pop     ebx
0046D546  |.  64:890D 00000>mov     dword ptr fs:[0], ecx
0046D54D  |.  83C4 28       add     esp, 28
0046D550  /.  C2 0400       retn    4
0046D553      CC            int3
0046D554      CC            int3
看见了int3了没?这个函数结束了,也就是返回点是这两个,我们Shift+f9跑起来.断到返回,F8继续跟出去.跟到哪里?记得上面的按钮和T按钮吗?
我们跟到函数返回那个位置出现浮点指令,那么那里的就是模型ID了,可以从那里获取到,嘻嘻..经过几次的ret,我们跟到了这里.
00480940   .  50            push    eax
00480941   .  56            push    esi
00480942   .  57            push    edi
00480943   .  E8 183FFEFF   call    00464860
00480948   .  894424 1C     mov     dword ptr [esp+1C], eax
0048094C   .  DB4424 1C     fild    dword ptr [esp+1C]
00480950   .  A1 3CA86200   mov     eax, dword ptr [62A83C]
00480955   .  83C4 0C       add     esp, 0C
00480958   .  DD5C24 10     fstp    qword ptr [esp+10]
0048095C   .  DD4424 10     fld     qword ptr [esp+10]
00480960   .  DD1C24        fstp    qword ptr [esp]
00480963   .  50            push    eax
00480964   .  E8 B7080200   call    004A1220
这里什么意思呢? 对比上面的按钮处理方式,这里就是保存人物ID的位置,mov     dword ptr [esp+1C], eax,也就是eax就是我们的角色ID.
为了验证一下?好,之前我们调试过哪个左键点击ID那里有ID值的,我们来下个断,点击一下自己的角色,哦,没错了吧?点击角色的ID就是上面返回的ID.到这里,上面的点,多次测试,是每个模型加载必进的定位点,也就是NPC的ID或者其它玩家的ID也在那里返回.
如何判断?名字='testdbyou' 他都告诉你了,汗.这还判断不了?人物ID我们得到了,接着是人物的移动问题,我们继续分析之前的log记录,发现
0046D2B5  COND: 0579F73C "终点X=489 终点Y=1841 跑=1"
在测试一次,这次用走路的,结果是'跑=0'.....
这个..角色移动的目标位置?还是猜测.试试吧,条件断在'终点',跟到外面看看它是干嘛的.
条件为dword ptr [edx] == 0e3b5d5d6
进去游戏走一步..断了...我们还是上面那招,让程序跑到返回那里
这次不像刚刚那样有先前的指令点,我们每返回一次,就在它的那个CALL那里下一个断点.大概返回3-4个吧,就够了,直接让程序跑起来.如果程序不断断在我们下断的CALL那里,我们就把断点去掉,排除掉那个走路的关键点,接着,排除得出只有一个CALL是要点击走路时才进入的,OK,开始分析.
看堆栈.也就是看这个CALL的压入参数和寄存器.发现了,
004813B1   .  55            push    ebp
004813B2   .  8D4C24 14     lea     ecx, dword ptr [esp+14]
004813B6   .  51            push    ecx
004813B7   .  51            push    ecx
004813B8   .  8BC4          mov     eax, esp
004813BA   .  896424 20     mov     dword ptr [esp+20], esp
004813BE   .  51            push    ecx
004813BF   .  8938          mov     dword ptr [eax], edi
004813C1   .  8BC4          mov     eax, esp
004813C3   .  8930          mov     dword ptr [eax], esi
004813C5   .  8B0D 403A6000 mov     ecx, dword ptr [603A40]
004813CB   .  C74424 30 000>mov     dword ptr [esp+30], 0
004813D3   .  896424 24     mov     dword ptr [esp+24], esp
004813D7   .  E8 9429FFFF   call    00473D70

堆栈
0012F8EC   5131B483
0012F8F0   B8FA9C1F
0012F8F4   0012F90C
0012F8F8   02201A90

压入了4个参数,看,第1个参数就是我们的角色ID,接着跟进看看其它参数,发现第3个参数.里面dword的指向值就是.
05941BEC  D6 D5 B5 E3 58 3D 31 30 33 31 20 D6 D5 B5 E3 59  终点X=1031 终点Y
05941BFC  3D 32 36 36 38 20 C5 DC 3D 31                    =2668 跑=1.
好了,到这里已经知道角色的跑位值,接下来,我们试试修改终点值,看看它能不能跑到预想的位置,,我改成0看看,哦,可以,不断向地图边缘跑,不过由于游戏的关系,在场景卡死了,哈哈.没问题,知道这里是控制角色移动的就行,经过多次断,发现其它角色的移动也是通过这里的
我们只要判断第1个参数是不是我们需要的ID,接着更改路线就行了.
跑的部分完成.这个游戏要做自动的话要从人物提示那里获取x,y值,但那些x,y和这个终点的坐标不同,差距值是乘20,Y值的对应是逆向的,发挥你们的智慧吧,要解决是很简单的.
跑,对话,两部分已经完成,差不多要写个测试程序去看看成果了,接下来,我们来搜索定位内存和给程序打补丁.
送大家一段我自己的内存定位搜索代码,用SEH错误处理搜索的,来,上代码,不止用在这程序,用在其它用途也很大的.
__________________________________________________________________________________________________________

SStart    dd  401000h

_Handler  proc  _lpExceptionRecord,_lpSEH,_lpContext,_lpDispatcherContext

    pushad
    mov  edi,_lpContext
    mov  eax,pERRORGO
    mov  [edi.CONTEXT].regEip,eax
    mov  VSize,999h
    popad
    mov  eax,ExceptionContinueExecution
    ret

_Handler  endp

_SMem    proc  _pV1,_Size  ;ebx返回搜索开始位置,edi返回搜索结束位置+1   by:nowhy
    
    assume  fs:nothing
    push  offset _Handler
    push  fs:[0]
    mov  fs:[0],esp
    lea  eax,_ERRORTONEXT
    mov  pERRORGO,eax
    mov  ebx,SStart
    _ERRORTONEXT:
    add  ebx,VSize
    mov  VSize,0
    _NEXTGO1:
    mov  ecx,_Size
    mov  esi,_pV1
    inc  ebx
    mov  edi,ebx
    _NEXTCMP:
    mov  al,byte ptr [edi]
    cmp  al,byte ptr [esi]
    jnz  _NEXTGO1
    inc  esi
    inc  edi
    dec  ecx
    test  ecx,ecx
    jnz  _NEXTCMP
    pop  fs:[0]
    pop  eax
  ret

_SMem endp
_____________________________________________________________________________________________________________

调用方式,_pV1指向要搜索的内存判断地址,_Size为该段的长度,由于没写搜索失败后的参数返回.所以小心使用,哈哈.上面有说明返回值的返回方式
上面代码慢慢消化吧,不算复杂. 接着关键点<如何在程序中准确定位到内存>
我就拿调试的'按钮'那里开始说明.开OD,看代码.介绍我的变态定位方式.
00475654  |.  E8 37BA0200   call    004A1090
00475659  |.  8B4C24 4C     mov     ecx, dword ptr [esp+4C]
0047565D  |.  83C4 34       add     esp, 34
00475660  |.  53            push    ebx
00475661  |.  6A 07         push    7
00475663  |.  51            push    ecx
00475664  |.  894424 30     mov     dword ptr [esp+30], eax
00475668  |.  E8 23BA0200   call    004A1090
0047566D  |.  8B5424 28     mov     edx, dword ptr [esp+28]
00475671  |.  8B4C24 30     mov     ecx, dword ptr [esp+30]
00475675  |.  83C4 08       add     esp, 8
00475678  |.  50            push    eax
00475679  |.  8B4424 28     mov     eax, dword ptr [esp+28]
0047567D  |.  52            push    edx
0047567E  |.  57            push    edi
0047567F  |.  56            push    esi
00475680  |.  50            push    eax
00475681  |.  51            push    ecx
00475682  |.  8D5424 2C     lea     edx, dword ptr [esp+2C]
00475686  |.  68 68B15500   push    0055B168
0047568B  |.  52            push    edx
0047568C  |.  E8 2F900400   call    004BE6C0
00475691  |.  83C4 24       add     esp, 24
00475694  |.  8D4C24 28     lea     ecx, dword ptr [esp+28]
00475698  |.  C64424 6C 01  mov     byte ptr [esp+6C], 1
0047569D  |.  E8 6E840400   call    004BDB10

就上面那段说明,0047568C 我们要定位到这里,怎么定位好呢? 我给的建议是,避开全局变量,因为重新编译程序那会改动的几率太大了,接着避开局部变量,也就是lea     edx, dword ptr [esp+2C]这种,那里函数定义的局部变量一改变就变化了,
那么,我们定位的主要就只要.
0047565D  |.  83C4 34       add     esp, 34
00475660  |.  53            push    ebx
00475661  |.  6A 07         push    7
00475663  |.  51            push    ecx
这里的几个字节,对于程序来说,是不容易改变的. 
83 C4 34 53 6A 07 51
我们用这个来CALL上面的搜索函数.
incoke  _SMem,lp,7
返回值ebx是搜索开始的点,edi是搜索结束也就是搜索内存尾部的位置.直接edi+28h 那样的话,就能定位到0047568C 如果实在找不到那些指令点,看见了我上面定义的那个SStart,按照出现次数压入覆盖调用就能定位到了
接着要做什么?呵呵,对它写入呢...把它的CALL HOOK到我们的地址.我随便说明一下,
call    004BE6C0
比如要HOOK这个地址.我们的替换函数地址为edi,这个地址位置为ebx.
mov  eax,dword ptr [ebx+1]
lea  eax,dword ptr [eax+ebx+5]
这样就能从地址得到它的目标要CALL的地址,eax=4BE6C0,将EAX到HOOK函数返回地址那里位置,预留5字节,先0e9h(jmp)之后计算目标位置.填充进后面的4字节,那么程序HOOK处理完成后就JMP跳到游戏原来CALL的位置.
接着把CALL的地址替换为我们的处理地址位置.
sub  edi,ebx
sub  edi,5
mov  dword ptr [ebx+1],edi
接着程序跑到我们HOOK的地址,该干嘛干嘛去,记得寄存器不要改变,堆栈也是.
pushad
处理....
popad
db 0e9h
dd 0  ;这句是返回计算压入的位置,就是上面eax计算得出的
搞掂...那么大家努力,明天给大家发针对这个游戏更加细化的处理方式.

喜欢汇编的理由?? 够自由啊,,我爱怎么改就怎么改,喜欢怎么搞寄存器就怎么搞.比那些高级语言的自由度大多了,总体来说也比高级语言更简单,直接,有人要反驳我也没办法.我只是想简单明白的知道程序到底在干什么,要怎么做,有可能出现哪些问题.
这些高级语言是做不到的.不过要考虑的也比高级语言的多很多,所以,个人喜欢啦.哈哈. 其实我也会用C/C++来写程序

鉴于有同学直接爆出了是哪个游戏..我没办法继续分析下去了..这次附件附上我的调试日志,大家下回来自己分析好了,
由于游戏的多次更新,里面除了定位的数据外,地址基本都是错误的,自己按照上面的定位字节和说明去用OD搜索地址位查看分析地址,

调试日志我删除了游戏保护的调试部分,这个我只能说不好意思.具体分析在日志内都有说明,我就此结束分析说明.谢谢大家支持.
迟点在安全编程区发个我自己写的程序保护驱动让大家玩玩,呵呵.就是第1篇所说的那个进程启动限制的程序.

真看不懂可以PM下我,我可以做简单的说明,个人化的东西,哈``调试日志有点乱,因为我调试时游戏有个强壳保护,将就下看吧..

未完结

 

你可能感兴趣的:(手把手一起写现在某热门游戏的跑钱外挂(一)(二)(三)(四))