通过发送数据封包定位call

/ 5

call简介

call 是 win32 汇编中的一个调用子程序的汇编指令,类似 ARM 汇编中的 bl 指令。

可以理解成易语言中调用子程序,或者 c/c++ 语言中调用函数。

封包发送

在 win32 编程中,网络游戏发送数据包最终都会调用 send 函数来发送数据:

int send(SOCKET s, const char FAR *buf, int len, int flags);

OD 快捷键表:https://www.cnblogs.com/YiShen/p/9742872.html

打开 OD 工具并附加到测试程序(以口袋西游为例),Ctrl+G 跳转到 send 发包函数处打一个断点。由于网络游戏会不断发送数据包(比如心跳包),所以这个断点会不断的被命中并让程序暂停。

如果在 Ctrl+G 的弹窗中输入 send 后点击 OK 跳转不了,则需要加上 DLL 模块名称,比如 ws2_32.send

如果附加进程并 F9 运行后,还是暂停状态,就点击图标栏的 T 图标显示线程,让所有线程恢复激活状态。

假如下面伪代码是某游戏中使用血瓶的函数调用过程:

void 点击背包中的血瓶()
{
    if (玩家血量没满) 
    {
        封装数据包()
    }
}

void 封装数据包()
{
    send()
}

int send(SOCKET s, const char FAR *buf, int len, int flags)
{
    最终通过send函数将数据包发送给服务器
}

send 函数的断点被命中后,我们可以快捷键 Ctrl+F9 跳转到上一层调用它的地方,以此来查找 send 函数的调用堆栈,定位我们想要的 call(函数)。

寻找公屏喊话call

先在 send 函数打一个断点,然后公屏发送一条消息,断点被命中。

使用快捷键 Alt+K 显示调用堆栈:

可以看到在调用来自的列中,就是我们想定位的 call 的地址:

ELEMENTC.0067ABDD
ELEMENTC.0067D805
ELEMENTC.0051FCBC

我们给这3个地址加上备注并打上断点,Alt+B 可以查看断点列表窗口:

然后发现什么都没干,调用ELEMENTC.0067ABDD 的断点也被命中,说明这个 call 可能是封包发送调用栈里都会去调用的通用 call,并不是只有公屏喊话才会调用的 call,所以我们先取消这个断点。

然后再在公屏发送一条消息 123123,剩下的2个断点都被命中,可以从寄存器和堆栈信息中看到 UNICODE "123123",说明这2个 call 就是公屏喊话 call

发送公屏喊话后,先调用 ELEMENTC.0051FCBC,猜测是输入公屏内容后,按回车键的事件函数,在这里拿到了输入的内容并调用发送公屏函数:

然后调用 ELEMENTC.0051FCBC,猜测是发送公屏函数,在这个函数里处理公屏相关的数据包内容:

再然后调用 ELEMENTC.0067ABDD,猜测是封包数据通用处理的函数,可能是通用的数据包处理函数。

最后通过 send 函数发送数据给服务器,这就是从输入公屏消息到发送给服务器的整个调用流程。

条件断点

先给 send 函数打一个断点,断点命中后,可以看到寄存器 ESP 中的数据就是堆栈窗口中第一行的地址,然后下面4个地址则是 send 函数的4个参数:

双击第一行地址,切换到偏移量的列表:

可以得出 [ESP+0x0C] = DateSize 里面的数据,而 DateSize 是数据封包的长度,所以我们就可以根据这个长度来大概判断是什么 call 发送的数据包。通过多次测试,我们就可以给 send 函数添加断点条件,使命中断点的 call 尽量只是我们想调试的 call

比如我只想调试公屏喊话 call,命中 send 函数断点后,拿到堆栈窗口中的 DataSize0x10,小括号里是10进制所以是16:

然后给 send 函数添加条件断点,条件为 [ESP+0x0C]==0x10

再次测试,发现走路、使用物品等操作和心跳包已经不会触发 send 函数断点了,但发送公屏喊话时还是会触发,这样我们就成功过滤掉了部分干扰。