网络游戏逆向分析-3-通过发包函数找功能call

网络游戏和单机游戏的分析有相似点,但是区别还是很大的。

网络游戏和单机游戏的区别:

网络游戏是需要和服务器进行交互的,网游中的所有功能几乎都会先发送封包数据到服务器,然后有服务器做出判断后反馈给客户端,客户端才会产生对应的相关功能。

 

找功能call的办法:

由于网游和单机游戏的区别,所以在网络游戏中要寻找功能call可以通过在发包函数处下断点来回溯找功能call。

相当于服务器是一个皇帝,客户端每想干什么事情前都得先给服务器进行交互,直到服务器同意才行。

大概是以下这个逻辑:

 

 

那么通过发包函数往上找,就可以找到可能是真实的吃药函数,当然也可能是吃药函数到发包函数的中间过程,但是没有关系能调用就好。

发包函数:

Windows的socket发包函数总共有三个:

//ws2_32.send
int WSAAPI send(
SOCKET     s,
const char *buf,
int       len,
int       flags
);
int WSAAPI WSASend(
SOCKET                             s,
LPWSABUF                           lpBuffers,
DWORD                             dwBufferCount,
LPDWORD                           lpNumberOfBytesSent,
DWORD                             dwFlags,
LPWSAOVERLAPPED                   lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
int WSAAPI sendto(
SOCKET         s,
const char     *buf,
int           len,
int           flags,
const sockaddr *to,
int           tolen
);

寻找喊话的功能call

首先查找发包函数,然后给发包函数打断点后再喊话,来查看是调用的哪一个发包函数。

这里通过查找后,发现只有send是可以断下来的,WSASend和sendto都没有用。

但是这里的send和我们想的不一样:

 

 

它打上断点后一直就卡这里,还没有喊话就断在这里了。

这里就需要引入一个新的内容:心跳包。

心跳包:

心跳包顾名思义,字面意思是按照心跳来发包,实际意思就是每时每刻都给服务器发个包,借此服务器可以用来检测,比如你是否还在线,是否掉线了,或者你有没有干别的坏事之类的,时刻让客户端和服务器保持联系。

心跳包的特点:心跳包只需要有特定的验证字符就好了,不需要发一堆东西来浪费资源,所以心跳包的长度len一般都比较小,而且长度是固定的。

 

还好这里心跳包频率不快,还是会隔一会才心跳的,还是可以按照之前的逻辑来找函数call。

假设:A调用B,B调用C,C调用发包函数,那么发包函数执行到函数返回再往上一条的call指令就是call的发包函数,然后再在调用发包函数这里使用执行到函数返回再往上一个指令就是call调用C函数,这样一直往上找六到七个函数来先分析一下看看

这里采用xdbg的办法来给每个函数上注释,通过找到发包函数,然后运行到函数返回回到上一层,然后这样来实现往上找函数。

 

 

 

通过xdbg的视图->注释可以查看到自己添加的注释的位置,以此来方便查看。

通过从send 到1然后到8从下往上给函数打断点,打完断点后人物再喊话来找到函数的call函数。(可以在注释这个窗口打断点)

打完断点后还得测试,测试是否是只有喊话的时候才会停下来,否则就不能用,这里我是在对应注释3这个函数找到的唯一可以在喊话后断下来的函数。

找到喊话功能call函数后进行分析

前面我们找到了喊话的函数,现在对其进行分析,一个函数必不可少的有,返回类型,函数参数的结构。返回类型不太好说明,但是函数参数可以先考虑,首先分析函数参数:

 

 

进入这个函数查看,最后有一个ret10,而且前面也给开辟的堆栈平衡了,说明函数的参数栈空间有十六位数的 10个,就是十位数的16,在看该函数前面有四个push,说明就是有四个参数了。

 

 

分析这四个参数来解剖该函数:

首先这里有4个push和一个mov ecx,eax。前面在学习反汇编C++的时候就得注意这个ecx,ecx很有可能是类里面的this指针,所以这次给第一个push edx打断点然后一直分析到调用函数:

edx == 00000000
ebx == 00000000
ecx == 字符串的首地址
edx == 6A1E8600
ecx == 2334FD68
//前面的edx ebx ecx edx都是入栈,ecx应该是一个对象的值把,这里猜测一下。
//然后再多调用几次看看有啥变化
//结论是不管多少次都只有ecx == 字符串首地址这个值做了变化

这里先利用代码注入器试一下:

注意:这里需要在一个空白的地方先放置字符串的数据,因为一会要用到字符串的首地址:

 

 

 

但是跟我们输入的字符串不一样,很有可能是编码的问题,这里改成unicode试试:

 

 

这样就ok了。

但是这个同样也只能临时用一下,所以还得往上找基址。

需要找的只有edx和ecx,因为别的都是0,或者是内存里的字符串,还有一个就是call的地址是一个静态地址也不用找。

就用前面的教程继续找edx和ecx的基址:https://www.cnblogs.com/Sna1lGo/p/14897870.html

总结

由于网络游戏的特性,是必须要和服务器进行交互的,所以可以借此原由,通过发包函数来获取得到对应的调用的函数call。