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
函数断点后,拿到堆栈窗口中的 DataSize
为 0x10
,小括号里是10进制所以是16:
然后给 send
函数添加条件断点,条件为 [ESP+0x0C]==0x10
:
再次测试,发现走路、使用物品等操作和心跳包已经不会触发 send
函数断点了,但发送公屏喊话时还是会触发,这样我们就成功过滤掉了部分干扰。