首页
社区
课程
招聘
[原创]对某游戏发包流程的一次逆向之旅
发表于: 2016-11-11 23:11 8082

[原创]对某游戏发包流程的一次逆向之旅

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

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



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

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


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


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


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

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


五. 当定位到修改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;

做个截图记录一下分析,我将它命名为组装函数。如下图所示


六.有了第五步的分析,再次推测:既然心跳包是分步组装,那么游戏的实际行为很有可能也是分步组装成封包。那么就需要再次验证。因为游戏的心跳实在是太频繁了,于是利用条件断点过滤所有的心跳call,幸运的是心跳call就2个,很快就将过滤的条件写好了


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



由此,至少可以推断出喊话这个功能的封包是通过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次断下时的返回地址,


这4个地址相距非常近,啊哈,感觉靠谱啊!od跟过去看看,如下图所示

果然,这4次对组装函数的调用,都是发生在一个函数体内,由此,基本可以得到结论:喊话这个功能的封包组合就是在这个函数体内完成的,它逐次的调用组装函数完成了封包的组合以及加密。

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

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
免费 2
支持
分享
最新回复 (3)
雪    币: 22
活跃值: (423)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
继续!~
2016-11-11 23:45
0
雪    币: 40
活跃值: (1152)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
发点高深的!谢谢!
2016-11-12 09:12
0
雪    币: 459
活跃值: (657)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
4
没来暖气吗?
2016-11-12 09:58
0
游客
登录 | 注册 方可回帖
返回
//