本帖最后由 xiaoxin520 于 2016-4-4 02:27 编辑
本文作者七少月,文章中很多观点和技术手段为本人原创,转载请注明出处,由于本人技术水平有限,不当之处,还请斧正。
前言:
由于本人一直以来都比较忙,开班、工作等等事情太多,所以很长一段时间没有发帖。其实我确实想看一看,尤其是Unity3d安卓游戏逆向领域有没有更出彩的文章。可能有些不恰当的说,确实这一段时间以来,虽然也有一些Unity3d安卓游戏逆向的优秀文章,如对金庸群侠传X1.0的数据库解密和功能解锁修改,但无论从数量上,还是从难度上,我都难以满意。不知道是愿意共享的人少了,还是觉得U3D逆向技术太简单了,如果是后一种原因,我只想说,U3D在安全开发的投入和技术发展是难以想象的飞速,如U3D把关键函数引向lua,利用Lualib实现C#与LUA交互,不仅实现热更新,也保护了代码,而lua脚本文件则作为资源文件打包混淆。以上是后话,现在最大的问题在于,即使DLL解密脱壳后,我们依然在静态分析DLL。
难以逾越的鸿沟:
本篇技术文章需要一定的U3D安卓逆向基础,没有的推荐看法总的教程,至少你能用reflector把DLL中一个return hp语句修改为return 99999.在之前我们说过,由于U3D底层并不开源,而且DLL运行在安卓上已经是受到了很大限制,所以在给我们逆向带来比普通安卓的dex和so目标更为明确的同时,也让我们逆向手段带来很大拘束。导致的一个结果就是,我们逆向U3D的DLL,只能使用静态分析,通过搜索,然后修改,最后测试。一直以来,我们认为,只要把关键DLL拿到,即使是网游,也可以进行修改。但以后就不是这个样子,可能以后DLL确实是所谓的关键DLL,但就不做保护地摆在那里,都没有什么作用。如果你一直看我U3D逆向的相关文章,这是继U3D把DLL加密加壳阶段的下一个新的阶段,我称为关键DLL无效的阶段。回到本文,我们好像确实很为难,只能使用静态分析仿佛成了我们一道无法逾越的鸿沟。
Unity3d游戏DLL动态调式与HOOK:
能实现对U3D游戏进行动态调试,是我和法总一直以来的愿望。早先,我写过一篇理论指导性的文章《Unity3d游戏动态调试理论框架》。当然,就在那不久之后,里面的思想相继得到了实现。最具代表性的,就是利用IDA动态调试libmono.so,DUMP下来解密后的DLL。但很可惜,那种动态调试,实质是在动态调试so,离我们当初想实现的真正的DLL动态调试相距甚远,更别提对DLL进行更深层的HOOK。当然,如若想实现DLL能像dex或SO那样进行单步调式,可能还是比较遥远,首先就是没有工具,IDA对安卓DLL的效果很不理想,我们的神器reflector还不提供远程调式安卓DLL的功能,但确实可以存在一些替代性的策略。下面,我们要知道以下几点:
1. 任何一种动态调试,并非一定要实现单步调式才叫动态调式,能实现单步调式是我们最理想的状态,但动态调试都是从log开始的;
2. 把程序在运行时断下来,是动态调试的精髓所在,但也有一些简单的替换性策略,比如我在C#编写的EXE中的代码中,插入一句message.show(“运行到此处”)代码,让程序在运行到我感兴趣的那段代码时弹出对话框。但这个在U3D中实现是很困难的,远没有安卓smali中通过静态注入Alert对话框代码那样简单,因为U3D的脚本代码和组件联系太过紧密,U3D本身更贴近于一个类似于3DMAX的三维设计软件。
3. HOOK本质的意义是注入代码,也是替换程序原本函数,所以DLL的HOOK可以分为内部HOOK和外部HOOK。由于reflector的插件reflexil本身就可以对DLL进行注入类、方法、变量、属性等,通过这种方式的HOOK就是内部HOOK。而外部HOOK,就是在其他DLL,甚至是其他的非C#编写的任何文件去HOOK这个DLL。
说了这么多,理论基础终于讲完了,本篇是基础,只讲解简单的LOG和内部HOOK注入。
准备及样本:
为了方便,我们采用比较简单的berryrush游戏作为样本,该样本在法总教程中存在。
首先,我们需要游戏的关键Assembliy-csharp.dll,当然这个DLL要是解密后的,而且要是真正的关键DLL,当然它要对游戏有决定性作用,而不是里面只有不关键的支持性代码,关键性代码存在于别的地方。其次,我们要把UnityEngine.dll拿出来,一般这个类被处理的很少。然后把它们一起拉到Reflector工具中。下面,我们来认识一下,UnityEngine.dll的UnityEngine命名空间中的Debug调式类。该类有Break、Logstr等方法。如图:
1.jpg (31.23 KB, 下载次数: 2)
2016-4-4 02:24 上传
已经淘汰的U3D开发技术:
由于这些技术过于简单,非常容易被逆向,在现今的U3D中,已经被淘汰
1. 把关键变量写成属性,且可以被外部调用,表现为其关键变量是个属性,存在get_XX和set_XX方法。现今,都是对变量进行不可被调用的声明,也就是为变量,表现为在Reflector中用蓝色长方体作为图标,且往往其声明为protected或private,而不是public
2. 不再存在如GetCoins(),这样明显的关键性函数,即使有,要不代码非常复杂,逻辑特别难懂,要不就是修改了也没用的;
3. 不再存在关键性变量在使用时直接进行赋值语句,如this.coinscount = 0,与上种情况相同
以上说明只是让大家知道,本例中这些情况是存在的。
Debug类的初步使用下断:
我们选择GetCoins()这个函数,我们加一句代码Debug.Break()。那么在IL语言中怎么添加中,首先,我们要注意,在添加指令的时候,指令是call,类型是Method,操作数是Debug.Break()这个函数。
2.jpg (20.45 KB, 下载次数: 3)
2016-4-4 02:24 上传
我们的代码就是:OffsetOpCodeOperand
0ldarg.0
1ldfldSystem.Int32 StrawberryPlayer::coinsCount
6callSystem.Void UnityEngine.Debug::Break()
11ret
然后,我们回编,利用手机版的log工具(会在附件中里有),对其进行log,我们发现游戏并没有停止运行,但是在这句代码被执行时,会给予该代码执行的时间及一些状态,如果下多个这样的断点,是完全可以了解一定的程序运行逻辑的。
3.jpg (30.91 KB, 下载次数: 4)
2016-4-4 02:24 上传
DLL的内部注入HOOK:
首先,我们要知道,HOOKDLL究竟有多大的好处。HOOK就是注入,替换。我们举个例子,上面已经说过,现在关键性的变量都没有get,set,也不在函数中进行赋值,总之一切都在U3D内部逻辑完全,紧密贴合于组件。这就好比是客户端和服务端,如果一直这么双方联系着,会对我们分析和修改带来很大不便。我们注入就好比是中间人,假设我们注入是一个变量,那么这个变量可以去替换一个程序原先使用的变量在函数里出现的位置,我们根本不需要知道原本这个变量如何计算得到,我们只需要定义一下这个中间变量,然后赋值给原先的变量,然后观察程序运行的反应和效果。这样一来,我们就找到了一个突破点,顺着这个点,再继续分析下去。本例中,我选取StrawberryPlayer类中的StartRunning()函数,变量则选取该类里的coinsCount,在游戏中表示金币数,在StartRunning()函数中被赋值为0。
在进行DLL HOOK之前,我们一定要知道,我们通过静态分析,得到的关键类、关键方法、关键变量是什么,任何动态调试,如果没有静态分析时的确定目标,那就是无头苍蝇。本例的关键在刚刚已经写得非常清楚。
接下来,我们在关键类StrawberryPlayer类右键,选择Reflexil2.0(我的该插件版本是2.0),然后选择inject field,即注入变量,这里要注意,注入的变量数据类型要和原本想替换的变量数据类型一致,本例的coinsCount是一个Int32型,变量命名为coinlog。
4.jpg (47.34 KB, 下载次数: 3)
2016-4-4 02:24 上传
注入后,DLL加载会刷新一次,然后你就发现在该类下有了coinlog变量。
5.jpg (34.59 KB, 下载次数: 3)
2016-4-4 02:24 上传
下面,我们到StartRunning()函数中,在代码的尾部添加以下几句代码,也就是先赋值coinlog变量为99999,再让coinsCount=coinlog,最后Debug.Logstr(“金币已做修改”)。对于赋值语句添加是基础内容,可去学习法总教程,对于Debug类使用上述已经说过,这里只给结果:274ldarg.0
275ldc.i499999
280stfldSystem.Int32 StrawberryPlayer::coinlog
285ldarg.0
286ldarg.0
287ldfldSystem.Int32 StrawberryPlayer::coinlog
292stfldSystem.Int32 StrawberryPlayer::coinsCount
297ldstr金币已做修改
302callSystem.Void UnityEngine.Debug::Log(System.Object)
307ret
简单说一下:274-280偏移指令是this.coinlog=99999;285-292偏移指令是coinsCount=coinlog;297-302偏移指令是Debug.Logstr(“金币已做修改”)。注意一下,这里的274等数字是指令所在的相对偏移地址,也就是offset,如果简单用第几句指令的话,应该是第86句-95句。
6.jpg (55.43 KB, 下载次数: 4)
2016-4-4 02:24 上传
我们回编测试,自然游戏初始化的金币就是99999了,用log手机版查看,也非常清晰,可以看到这个函数是何时被调用的。
7.jpg (50.25 KB, 下载次数: 3)
2016-4-4 02:24 上传
下载地址:http://pan.baidu.com/s/1gfdfsbT