本文作者:七少月(此篇更偏向于论文,转载请注明作者) 感谢帮助我研究的朋友们,小白,无痕,Magic等三位兄弟。 现今,安卓游戏在Unity3d上开发已成主流,游戏保护DLL的方法层出不穷,隐藏属性、转移重点,而更加常见的还是DLL加密或混淆,又或者是两者都有。到目前为止,更是不单单加密一个Assemly-csharp.dll。本文主要是总结近日来发展极快的方式方法,建立Unity3d游戏中如何利用动态调式应对DLL加密的逆向分析脱壳的体系架构,并给予成功实例展示,证明我所要建立的架构体系。但此文方法不仅可以用于DLL脱壳,对于普通的APK的加固加壳也应适用。由于本人时间精力有限,该文仅是一个指导,希望起到抛砖引玉的作用,给有关的研究添加一笔,但一定会给予论据的实例。 在2个月前,我和sakary法总两个人还都在思考,Unity3d如何进行动态调式。在这之前,我们一直是利用reflector静态分析并修改Assemly-csharp.dll这种方法来进行逆向。然而,当我们发现全民奇迹的DLL的MZ头消失不见变为乱码时,就知道单单静态分析已经无法满足需要,我们开始希望可以动态调式。本文一个核心也就是想阐明如何进行Unity3d的动态调式,而动态调式最终的目地就是为了把加密的DLL脱壳,最终替换使之可以正常运行。在我看来,动态调式主要适用于几下几种重要情况: 1、 我知道这里有一个加密后的字符串,我想知道这串加密字符串的明文,可是自身静态分析不足,编程能力也不够,并且加密相关的函数太多; 2、 我已经修改了一个软件,可是打开后出现异常,卡在了启动界面,或直接闪退,我想知道异常出现的原因,是做了保护还是修改错误,保护或错误发生的地方在哪里,是什么样子的原理,但是静态分析太多太杂,如果可以知道该软件运行到哪个函数时出现异常就好了; 3、 我在so中发现了一个关键函数,非常肯定它就是关键,我想进一步弄清这个函数的作用和在整个APK中的意义,以及在程序启动时什么时候被调用,又被大概哪些其他函数调用,该函数运行时其堆栈,内存等情况又大概是什么样的; 4、 我在逆向一个Unity3D安卓游戏时,发现DLL是被加密的,或者逆向一个APK时,发现DEX是被加密的,我想在内存中,把解密后的DLL或DEX抠出来,如果需要修复就修复,并且我还想知道DLL或DEX是在哪个函数解密的,总之最终可以修改并替换; 5、 我手里有一个APK软件,这个软件名为XX神器,应该是个病毒,但是它的原理是什么,通过什么手段来进行注入,感染,盗取等行为,此刻我想通过类似OD跟踪EXE的API函数方法,快速知道这个APK调用了哪些系统的linux内核函数接口,这样我就能很快知道这个病毒的基本原理和作用; 6、 我在玩一款安卓游戏或软件时,对一个按钮的点击事件非常感兴趣,想对点击事件的执行代码进行修改,或者想知道在这个按钮点击时,软件到底调用了哪些函数来完成功能,具体的流程和原理是什么; 7、 我遇到了一个APK,它好像保护做的不错,对GDB进行了检验,防止调式,一旦发现自己被调式就退出,我怎么能找到防调式代码,这段代码又是怎样进行反调式的,最终我们怎么进行反-反调式;
以上是我进行粗略小结的动态调式几种重要情况和用途。下面我们再来说说对于加密DLL的几种处理办法: 1、 libmono.so动静结合解密法 我们都知道,往往如果是Assembly-CSharp.dll单一加密的话,其解密的函数在libmono.so的mono_image_open_from_data_with_name这个函数中,可以进行静态分析,就像对于三国KILL最新版加密字符串的分析解密算法一样,配合动态进一步加深理解,最后自己编写程序把DLL解密,修改后再加密回去替换。这种方法可能大部分会觉得麻烦,还得再编程,但这种方法在后期是一劳永逸的。因为当DLL解密修改之后,直接放在游戏里,肯定是不能运行的,如果我自己写了加密算法,再加密回去,直接就可以用了。以Ericky静态分析三国KILL3.7.1的字符串解密算法为例: 2、 动态调式脱壳法 我们知道这么一件事情,无论是混淆还是加密,只要是这样的保护,都可以把它看成一个壳,只要是壳,在内存中就必然会有脱下去,显示它本来面目的那一刻。如果我们在壳的入口点处下断(当然不同情况有更好的断点,这里不做延伸),然后不断debug step by step,发现某一时刻我们需要的东西已经褪去了壳,这时我们从HEX数据中DUMP抠出来,不就是解密了吗?以 IDA动态调式Unity3d的DLL为例,我们要注意的是解密DLL的开头和结尾,DLL都是以MZ开头,所以要从MZ头这个地方开始DUMP。这和DEX脱壳是类似的,其实就算是EXE脱壳,原理也都是类似的。就在前两天,已有成功案例: http://www.52pojie.cn/thread-398266-1-1.html。这里我要多说几句,我们常见的安卓端动态调式脱壳有三大神器,分别是IDA,GDB,和Gikdbg。在这里,我有必要对这三大神器做1个亲身经历的比较。IDA操作较难,也毕竟麻烦,需要考虑很多问题,比如断点在哪下,但是它脱壳非常快,并且DUMP时间短,可以说是你想抠哪个地方就能抠哪个地方,更大的好处是,只要你技术高,一般的反调试是难不倒你的,而且针对IDA动态调试的目前不多。GDB正好相反,它操作简单,但它要抠就是几乎把内存里东西都抠出来,比如全部的DLL,无论原始这个DLL是不是加密了,而且针对GDB的防调式现在很普遍。往往我们使用IDA+GDB,但我认为这不够,如果你也学过EXE 破解的话,肯定知道动态调式的OD这个神器,安卓端类似并拥有OD功能的就是Gikdbg,它基于GDB,需要安装gikdbg.apk并连接才能工作,但它的动态调式功能更加标准和强大。我推荐,IDA+GDB+Gikdgb的方式,当然很多新手可能要问到DDMS哪去了,DDMS和JDB等其实已经容纳在了IDA动态调式之中,不明白的请认真学习基础。下面是两个图例: IDA准备DUMP加密DLL: GDB抠出的DLL结果: 3、 libmono.so关键函数HOOK法 该方法是由oraclex首创,初次见于http://www.jianshu.com/p/5bba57045b09这篇文章。其精髓就是对libmono.so的关键函数进行hook,这里我们以HOOK该so中的一个构造函数mono_class_from_name为例。先编写一个so,在这个so里调用libsubtrate.so中MSGetImageByName,MSFindSymbol,MSHookFunction这三个函数来进行HOOK。最后在JAVA层调用自己编写的so。在该so里,我们HOOK的mono_class_from_name编写如下: int mymonoclass(void image,const char name_space, const char name) { intimageAddr=(int)(image+8); intimageLen=(int*)(image+12); if(imageLen!=2590208 &imageLen!=647680){//mscorlib.dll和unityengine.dll的大小 LOGE(name_space); LOGE(name); printf("发现DLL"); printf("imageAddr:%p",imageAddr); printf("imageLength:%d\n",imageLen); } //LOGE(imageAddr); //LOGE(imageLen); returnoldmonoclass(image,name_space,name); } 看了以上,我们来比较一下这种方法和第二种方法。首先,可想而知,利用libsubtrate.so肯定更快,其次,如果我们稍有不注意,或者软件做了什么保护,我们可能会出现DUMP不全的情况,如果我们先HOOK这两个函数mono_image_open_from_data_full、mono_image_open_from_data、mono_image_open_from_data_with_name,打印出地址长度,总之就是明确地址,那么我们DUMP的完整性和准确性肯定会得到提高,并且HOOK了这两个函数,就可以把所有DLL一下全部DUMP出来。当然,如果我们需要全部DUMP出DLL,用GDB更常见。但GDB的效率,准确度都是没法和这种HOOK方法比的。 4、 Dex抽取脱壳法 该方法首次见于bunnyrene的文章,实际上他主要是做了一款工具,已经在github上发布,工具的名称为DexExtractor,就是DEX抽取工具。这个工具相当具有创意,几乎是无视什么壳的具体实现,而是通过使用修改了系统镜像,配合它的抽取工具对DEX进行抽取。我们先进入谷歌SDK的\android-19\default\armeabi-v7a目录,替换成作者修改后的镜像。然后启动模拟器,安装运行APK,这时候可以在sd卡看到dex文件包名+随机数字.dex,(用adb shell 进去 cd 到sd卡目录)这个时候利用ls命令查看sd卡中的文件,然后pull dex文件到电脑上,然后base64 解码下文件即可。这是一种很巧妙的方法,其实对于Unity3d的加密DLL脱壳处理以上三种就是主流方法,但是我认为这种方法也可以用于抽取DLL。在观察某杀气这个游戏时,我发现实际上,它的Assemly-csharp.dll分几个部分,,而通过分析libmono.so中mono_image_open_from_data_with_name函数可以知道,这是Assemly-csharp.dll的分解的几个组成部分,由此我在想能不能模仿这种抽取的方法,先抽取再拼接到一块。
5、 DLL未加密时关键函数的HOOK和动态调式 上文一直在说的是当Unity3d游戏的DLL被加密时,我们应该怎么处理,大概有哪几种思路和办法,并做了一些比较,而现在我们想说说动态调式DLL。因为我们知道,Unity3d游戏是so去处理DLL,那么其实我们的问题就转变为如何逆向和处理so这个安卓的动态链接库。然而,如果DLL没有加密的话,我们假如可以直接用reflector成功打开,那么除了法总的那种静态分析处理DLL方法。能否动态调式呢?我认为可以的,可以自己重写一个HOOK.DLL,我们可以模仿HOOK SO、HOOK JAVA 的方法,在自编写的DLL中先调用一些东西来进行HOOK,再写入我们要修改Assemly-csharp.dll的关键函数代码,然后找到so里面调用这个DLL的地方,让它调用我们自己的HOOK.DLL。当然,目前这个思路没有得到验证,但应该可行。还有另一种,就是在一些可能的关键代码处添加“Messagebox”这一句,相当于进行简单的动态下断调式。
最后,我们必须要知道现今及不远的以后,我们要面对更加强大的保护措施,我大概总结了一下,大概以下2个方面着手: 1. 解密DLL的函数不在libmono.so中,且加密的DLL不止一个,几乎只要稍微重要就被加密,甚至说每个DLL加密的算法还不同。这个时候,我们就必须弄清楚到底软件是什么时候在哪里解密DLL的,弄清这个,问题就迎刃而解。 2. 更强大的反动态调式功能,比如让GDB可以运行,但就是抠不出来,或者自检验,一发现断点程序就退出,等等,这就需要我们增强技术,应对反调式。
|