第一节 -- 寻找当前地图的寻路call

第一部分 堆栈入门

1.封包断点

bp WS2_32.send

2.硬件写入断点:7E8C0040

堆栈数据:

0018F844 008182FB /CALL 到 send 来自 Game.008182F5
0018F848 00000898 |Socket = 898
0018F84C 7E8C0040 |Data = 7E8C0040
0018F850 00000042 |DataSize = 42 (66.)
0018F854 00000004 \Flags = MSG_DONTROUTE

2-1 为什么要对 7E8C0040 下硬件写入断点

游戏内核的处理机制:

Created with Raphaël 2.1.0 主线程 主线程 消息队列 消息队列 消息执行 消息执行 发送消息(将消息的内容写入:7E8C0040) 从消息循环弹出消息 (调用win32 提供的 WS2_32.send,将7E8C0040的内容发送给服务端 ) 查看消息队列是否有消息

名词解释:

消息队列:并不是windows里面的消息队列,而是游戏内部的消息队列,使用quene 的方式,
先进去的消息就先执行

3.获取调用堆栈

通过硬件写入断点,可以找到一个调用堆栈,如果在还没有移动的时候就已经断点了,
说明不是当前断点,一定要在进行移动之后断点下来的,才是真正执行的地方

小提示: 通过Ctrl+K 获取调用堆栈列表,可以复制到剪切板

地址 堆栈 函数过程 / 参数 调用来自 结构
0018F66C 00716477 包含Game.00819624 Game.00716474
0018F724 004569D7 ? Game.007163A0 Game.004569D2
0018F814 00457EBB Game.00456840 Game.00457EB6
0018F954 004598C9 Game.00457D10 Game.004598C4 0018F950
0018F960 0043BAD4 包含Game.004598C9 Game.0043BAD1 0018F9AC

4.确认顶层调用点

如何确认是那一层是真正的调用点

可以从 ( 调用来自) 00716474->004569D2->00457EB6->004598C4->0043BAD1 一层一层的找

1.
在 00716474 下断点,移动后断点一次,说明是移动的断点
2.
在 004569D2下断点,移动后断点一次,说明是移动的断点
3.
在 00457EB6 下断点,移动后断点一次,说明是移动的断点,通过下一层04598C4 的死循环,判断出当前调用层是顶层调用点
4.
在 004598C4 下断点,一直断点,说明已经进入了游戏引擎的死循环,那么上一层就是顶层调用点

得到调用点 00457EB6
0018F814 00457EBB Game.00456840 Game.00457EB6 //顶层调用点

第二部分,汇编分析

注意 第一部分未完成的读者,请勿进行接下来的学习

前置思路介绍

分析游戏内部如何实现寻路

c++ 代码模拟

    //定义目标点结构体
    typedef tagPoint{
        float x;
        float y;
    }POINT,LPPOINT;
    // 游戏内部的用户对象
    Player player;
    POINT tagetPoint = {};
    targetPoint.x = 155;
    targetPoint.y = 253;
    //执行移动
    player.Move(targetPoint)

c++ 汇编为win32汇编后的代码:

    sub esp,20H
    mov eax,431b0000H  ;155 的float的值
    mov [esp],eax ; 不能直接给非通用寄存器赋值
    mov eax,437d0000 ;253 float 的值
    mov [esp+4],eax 
    mov ecx,[7EF30010] ;游戏内部的用户对象的内存地址,本章节不做用户基址的搜索
    push esp  ;结构体入栈
    call 03105121  ;游戏内部通常使用_stdcall ,不用自己进行add esp,n 堆栈平衡
    add esp,20H  ; 这个是为了恢复自己申请的栈空间

游戏执行移动的流程分析(正向猜测,非逆向分析)

Created with Raphaël 2.1.0 开始 界面点击坐标调用移动 移动函数开始 是否正在移动? 结束 移动 yes no

用c++ 代码模拟流程图

//假设游戏玩家类有这些字段,n[0xm]表示我们不知道的内容
class Player{
    char nothing[0x160];//占位0x160个字节,不知道前面放的是什么,只知道0x164是状态
    int state;  //占位4
    char nothing[0x18];// 占位 0x18
    bit isAlive ;第0x180位的状态
};
//流程图中的  `移动函数开始` 

    define MOVE_STATE  2
    if(player.state == MOVE_STATE && player.isAlive){
        ... dosomething
        player.Move(point);
    }

汇编模拟c++的代码


    cmp [ecx+164],3;汇编的数字都是16进制,164 表示 0x164(10进制356),而不是10进制的164
    je 07aa0110 ;判断用户的状态是不是移动,是移动就跳转到结束
    cmp [ecx+180],1;判断是否活着
    je 07aa0110 ;死亡就结束
    call move;判断执行完成之后,就执行移动

5.函数体内部分析:

现在就是通过第4节的 顶层call 00457EB6
在这里向上回溯,知道找到条件判断,进入一小段函数体的判断

两个判断,如果都成功才会进入走路call

00457D3B    8A86 64010000   MOV     AL,BYTE PTR DS:[ESI+164]
00457D41    84C0            TEST    AL,AL
00457D43    53              PUSH    EBX
00457D44    74 17           JE      SHORT 00457D5D
00457D46    83BF D8010000 0>CMP     DWORD PTR DS:[EDI+1D8],2
00457D4D    75 0E           JNZ     SHORT 00457D5D
00457D4F    8B87 E0010000   MOV     EAX,DWORD PTR DS:[EDI+1E0]
00457D55    85C0            TEST    EAX,EAX
00457D57    0F84 7C010000   JE      00457ED9
00457D5D    8DBE 4C010000   LEA     EDI,DWORD PTR DS:[ESI+14C]

判断内容 伪代码解析:

if(esi+0x164==1)//是否活着
{
    if(edi+0x1d8!=0x2)//可能是  人物移动状态
    {

        //具体移动代码

    }
}

6.分析esi+0x164 和 edi+0x1d8 的硬件写入断点

调用堆栈

地址 堆栈 函数过程 / 参数 调用来自 结构
0018F45C 0045823F ? Game.00457AA0 Game.0045823A 0018F458
0018F47C 0045A1D1 ? Game.004581E0 Game.0045A1CC 0018F478
0018F4F8 0045983C 包含 Game.0045A1D1 Game.00459839 0018F4F4
0018F50C 00459971 Game.00459800 Game.0045996C 0018F508
0018F524 00455D33 包含 Game.00459971 Game.00455D30 0018F520
0018F5C4 0056E231 包含 Game.00455D33 Game.0056E22E 0018F5C0
0018F5E0 004649D9 包含 Game.0056E231 Game.004649D6 0018F5DC
0018F960 007ABB04 包含 Game.004649D9 Game.007ABB01 0018F95C
0018F9D4 007AF7BF Game.007AB270 Game.007AF7BA 0018F9D0
1.通过第四步 确认顶层调用点 的方式 按照

Game.0045823A-> Game.0045A1CC->Game.00459839-> Game.0045996C -> Game.00455D30 -> Game.0056E22E -> Game.004649D6 ->Game.007ABB01 -> Game.007AF7BA
依次寻找,直到找到游戏引擎的循环体的前一步

2.找到顶层call:
地址 堆栈 函数过程 / 参数 调用来自 结构
0018F5C4 0056E231 包含 Game.00455D33 Game.0056E22E

第三部分,调用测试

7.顶层call 代码区

0056E200    55              PUSH    EBP
0056E201    8BEC            MOV     EBP,ESP
0056E203    8B0D C08CB300   MOV     ECX,DWORD PTR DS:[B38CC0]
0056E209    56              PUSH    ESI
0056E20A    8B75 08         MOV     ESI,DWORD PTR SS:[EBP+8]
0056E20D    56              PUSH    ESI
0056E20E    E8 BDB72300     CALL    007A99D0
0056E213    A1 D885B200     MOV     EAX,DWORD PTR DS:[B285D8]
0056E218    8B48 5C         MOV     ECX,DWORD PTR DS:[EAX+5C]
0056E21B    8B46 04         MOV     EAX,DWORD PTR DS:[ESI+4]
0056E21E    8B89 E4010000   MOV     ECX,DWORD PTR DS:[ECX+1E4]
0056E224    8B11            MOV     EDX,DWORD PTR DS:[ECX]
0056E226    6A 00           PUSH    0
0056E228    6A 01           PUSH    1
0056E22A    50              PUSH    EAX
0056E22B    8B06            MOV     EAX,DWORD PTR DS:[ESI]
0056E22D    50              PUSH    EAX
0056E22E    FF52 10         CALL    DWORD PTR DS:[EDX+10]
0056E231    FF15 84699000   CALL    DWORD PTR DS:[<&tEngine.TdGetTim>; tEngine.TdGetTimeTickCountSafe
0056E237    A3 389CB200     MOV     DWORD PTR DS:[B29C38],EAX
0056E23C    5E              POP     ESI
0056E23D    5D              POP     EBP
0056E23E    C2 0400         RETN    4

8.模拟调用

push 0
push 1
push 0x42bc0000
push 0x438a0000
mov ecx,[0B285D8]
mov ecx,[ecx+0x5c]
mov ecx,[ecx+0x1e4]
mov edx,[ecx]
mov edx,[edx+0x10]
call edx

经过测试,可以成功进行本图寻路

ecx 演算值

[[B285D8]+0x5C]+0x1E4

ecx特征码:

8B 11 6A 00 6A 01 50 8B 06 50

不在本节介绍的地方:

1.人物基址寻找(为了课程连贯性,人物基址查找和人物信息查找,单独做一节)

2.特征码寻找 ,(用来在游戏更新之后,我们可以根据特征码自动定位基址,而不是在重新找一遍)

AYA原创,转载请务必注明出处

你可能感兴趣的:(第一节 -- 寻找当前地图的寻路call)