对某游戏发包流程的一次逆向之旅

来源:互联网 发布:重庆固守大数据怎么样 编辑:程序博客网 时间:2024/05/16 01:18

目标程序:某游戏
逆向的目的:摸清他的发包流程
逆向的思路: 利用喊话的功能去测试,因为喊话有明文,方便看

一. 游戏运行后,OD附加该游戏并且在send处设置断点,其他先不管,
先摸清楚他的发包缓冲区再说。因此第一件事找到他的send所在地址。
反复断了若干次,发现游戏进程中send就在如下图所示的地方

点击图片以查看大图图片名称:1.png查看次数:0文件大小:14.3 KB文件 ID :108617

发包的流程多种多样,个人认为要么就是线程发包:在线程中搞一块内
存空间当做buff,填充buff后在线程中发包,要么就是非线程发包;
线程发包时栈回溯看不到什么玩意,需要想办法跳出线程才能去继续追踪,
非线程发包时栈回溯能一路向上找到具体的行为call;
光知道他的发包所在地址只是第一步,下面要看看他的buff是固定的还
是动态变化的。固定的好办些,动态改变的麻烦!


二.游戏的心跳包非常之频繁,可以说令人发指。send处的断点不取消,
多次F9同时观察它的心跳包,发现,该游戏的心跳包始终在使用同一个
buff,如下图所示,send的参数还是很好判断的
名称:  2.png查看次数: 0文件大小:  3.9 KB


三.现在已经获知游戏的心跳发包buff是固定的。开始分析这个buff,
从步骤二看出来他的心跳包大小几乎都是6,而且都是同一块buff。
那么我就思考一个问题,会不会游戏的行为通信包和心跳包也是同一
个buff呢?如果是同一个buff则分析起来相对会轻松一点。有了推想
就要验证,直接在send设置条件断点,当buff长度!=6时才断,如下
图所示,长度即为当前call的[esp+8]处的数据,因此条件就是[esp+8]!=6
点击图片以查看大图图片名称:3.png查看次数:0文件大小:9.6 KB文件 ID :108618


四.条件断点设置好之后,在游戏中走路,产生一个行为,od立刻断住了,
查看现在buff有什么变化。如下图所示,发现buff的地址是一样的,仅仅
buff的长度发生了改变,在此处长度是0f了。
名称:  4.png查看次数: 0文件大小:  3.8 KB

经过这里,就可以大致的得到一个推论了:

游戏的心跳和行为使用同一块buff,不同点是buff的内容以及长
度不一样。既然能够确定这一点,现在就要拿buff下手了.也就
是要定位到buff数据是从何处获取的。依然使用OD,下写入断点。
写入断点我选择对buff的长度数据下写入断点,也就是说,我要
找是什么代码改写了buff的长度。
通常来讲,当代码写到封装数据包的时候,应该会使用结构体吧,
其结构可能是:1:包长+数据 2:数据+包长等等诸如此类的。因此
如果找到修正buff长度的代码,那么负责填充buff实际数据的代码
理应就在不远处。选择buff长度作为突破口,是我个人认为它也就是
一句汇编代码完事的活,总比看n句给buff赋实际数据的汇编代码来的
爽。于是乎,直接用心跳包开搞。在记录buff长度的内存处下硬件写
入断点,因为心跳的频率令人发指,很快就断住了,定位到的代码
如下图所示
点击图片以查看大图图片名称:5.png查看次数:3文件大小:25.8 KB文件 ID :108620

五. 当定位到修改buff长度的代码之后,通过观察发现edi=2,由之前的步骤可知,
心跳的包长应该是6.这是咋回事?不着急,od还断着呢,说明没跑完啊.再来一
次F9,再次在这句代码断住了,此时edi=4了。然后游戏跑起来了.啊哈,原来他的
心跳包是分2次组装的,第一次写入2字节,第2次4字节。先将数据写入封包的buff
中,然后更新封包的长度。正如步骤四的图所示,他的流程用伪代码表示就是

memcpy(buff,src,2)  //第一次调用,也就是第一次组装
length = 2;
memcpy(butt+2,src,4)  //第二次调用,也就是第二次组装
length += 4;

做个截图记录一下分析,我将它命名为组装函数。如下图所示
点击图片以查看大图图片名称:6.png查看次数:3文件大小:31.2 KB文件 ID :108621


六.有了第五步的分析,再次推测:既然心跳包是分步组装,那么游戏的实际行
为很有可能也是分步组装成封包。那么就需要再次验证。因为游戏的心跳实在
是太频繁了,于是利用条件断点过滤所有的心跳call,幸运的是心跳call就2个,
很快就将过滤的条件写好了
点击图片以查看大图图片名称:7.png查看次数:1文件大小:31.9 KB文件 ID :108622


七.将过滤心跳call的条件断点设置好以后,回到游戏中,这次准备利用游戏的
喊话功能来验证步骤六的推测。随便输入"123",按下回车,OD停在了断点处,心
跳包组装了2次,那么这个标准的通信行为要组装几次呢?反复按下4次F9键,游
戏跑起来了。并且关键在于断点第3次到来的时候,堆栈中出现了我们输入的明
文"123",如下图所示

点击图片以查看大图图片名称:8.png查看次数:1文件大小:68.2 KB文件 ID :108612

由此,至少可以推断出喊话这个功能的封包是通过4次组装实现了完整封包。
那么,伪代码就应该是:
memcpy(buff,src,2)  //第一次调用,也就是第一次组装
length = 2;
memcpy(butt+2,src,x)  //第二次调用,也就是第二次组装
length += x;
memcpy(butt+length ,src,x)  //第三次调用,也就是第三次组装
length += x;
memcpy(butt+length ,src,x)  //第四次调用,也就是第四次组装
length += x;

并且在出现明文"123"的时候,对封包buff进行跟踪,发现封包buff中
没有出现"123"字样,说明在将数据写入buff时同步实现了数据的加密.
既然有了如上的推断,那么联想到实际的代码,推测代码应该是在某个
功能函数中,根据不同的条件或者参数,调用了4次组装函数,也即
如下的

void 组装(Arg...)  //组装函数
{
//实现组合封包并且加密数据
}

void func(Arg....)  //功能函数
{
   组装(参数...);
   组装(参数...);
   组装(参数...);
   组装(参数...);
}

组装函数目前已经知道了是哪个了,那么功能函数是什么呢?找到它这次的
逆向任务才算有点成果。前面说到,在用喊话功能的时候,组装函数连续断
了4次。依次记下这4次断下时的返回地址,
名称:  9.png查看次数: 0文件大小:  5.8 KB

这4个地址相距非常近,啊哈,感觉靠谱啊!od跟过去看看,如下图所示
点击图片以查看大图图片名称:10.png查看次数:1文件大小:47.5 KB文件 ID :108615
果然,这4次对组装函数的调用,都是发生在一个函数体内,由此,基本可以得
到结论:喊话这个功能的封包组合就是在这个函数体内完成的,它逐次的调用组
装函数完成了封包的组合以及加密。


八.至此,该游戏的喊话发包大概的流程已经搞清楚了,由功能函数调用组装函数
完成封包的组合以及加密,然后发包。其他功能不出意外的话应该也是如此的调用
方式,在组装函数处下断点,依次测试不同的功能应可获取其他功能函数,因此不
想继续测试下去了。而实际组装时,源数据的内容如何加密,也不看了,毕竟有点
冷,坐着发抖了,看起来写的不长,调试却花了很久时间。最后说一句,一入码门
深似海,从此踏上不归路!与君共勉!

2 0