一、前言
最近我们在FortiGuard实验室中遇到了加壳过的许多Android恶意软件。这个恶意软件有个有趣的特点,尽管所使用的加壳程序一直不变,但释放出的恶意软件载荷却经常发生变化。
对加壳程序的分析可能让人手足无措,我们通常很难理解程序的执行流程,并且呈现在眼前的大量垃圾信息很容易吓跑一些分析人员。
正因为如此,我们希望与大家分享我们在分析这类恶意软件过程中对某些问题的处理方式。实际上,在本文中我们将演示如何依赖开源工具,提取出当今最常见的释放器(dropper)所释放的恶意软件。
我们所分析的样本哈希为509aa4a846c6cb52e9756a282de67da3e8ec82769bceafa1265428b1289459b3
。
二、静态分析
概览
首先我们来看一下本文的主角:某个APK文件。
图1. APK中所包含的文件
如上图所示,其中有些特征非常可疑,比如MawmjulbcbEndsqku^nd.cml
文件究竟是什么?
可以先使用bash中的file
命令来分析这个文件。不幸的是,该命令无法检测到该文件的具体类型。使用十六进制编辑器(这里我们使用的是radare2,这是一款开源逆向工程框架)查看文件后,我们仍然无法确定文件的具体类型。
图2. 十六进制视图
根据这些信息,再结合看起来像是随机字符的文件名,我们认为这可能是经过主程序加密处理的一个文件。
观察Android Manifest文件后我们可能获取更多信息。
Android Manifest
AndroidManifest.xml
是一个Android二进制XML文件,包含关于应用的大量信息,例如:
- 应用包名,可通过该包名在设备上访问目标应用
- 应用所使用的activity、service以及receiver的完整列表(如果没有在该文件中声明,则后续无法使用)
- 申请的所有权限
- 在执行期间使用的intent action过滤器列表
- 其他一些信息,如图标等
我们首先注意到一件事,应用的所有组件名称都使用了完全随机的字符串。这可能是恶意行为的一种特征,但合法应用开发者可能也会使用这种技巧来避免竞争对手逆向分析自己的劳动成果。
我还注意到另一件事:除了Application类com.asgradc.troernrn.yeSACsSs
之外,反编译后的classes.dex
文件中并没有声明Android组件(activity、receiver以及service类)。这非常奇怪:声明不存在的类并跳过已有类究竟有什么用处?
后面我们发现这个APK会加载其他外部代码。此外,鉴于应用所请求的权限(如发送SMS消息的权限),我们非常确定这些代码肯定不怀好意。
图3. AndroidManifest中的SMS过滤器
逆向分析
我们可以使用许多免费工具将APK反汇编成可读代码,包括如下几种选项:
- Apktool:获取类所对应的SMALI表示法
- dex2jar:将
.dex
文件转化为jar归档文件,然后可以使用jd-gui
分析所生成的文件 - jadx:以较友好的GUI界面将所有代码反编译成java代码
我个人最喜欢的jadx,但其他方案也可以作为备选,因为在极少数情况下,只有某些工具能够反编译代码。
所以接下来我们开始使用jadx来分析APK文件。不幸的是,结果并不太理想。
图4. 加壳应用类
我们看到的是最为典型的加壳应用,充斥在眼前的是大量无用的垃圾数据:无意义的字符串、无意义的计算逻辑以及无意义的函数。我们花了一些时间尝试澄清执行流程:应用要么需要调用某些加密库或者有自己的解密函数才能解密加密文件。一旦解密完成,就需要使用ClassLoader
对象或者其他方法来加载新的文件。
不幸的是,APK导入表中并没有包含这类库,而是包含了一些反射(Reflection)方法来间接调用已加载的任何库。与此同时,应用同样使用无法理解的许多函数来动态生成这些反射方法的参数。
很明显静态分析无法搞定这个对手。这里引用George Bernard Shaw的一句名言:
很早以前我就明白,永远不要与一只猪争斗,你会把自己弄得很脏,而且猪就喜欢这样。
我们不会再陷入泥潭,必须找到更加快速的方法。
三、动态分析
Google可以让我们下载Android所有版本对应的SDK,然后通过Android Studio创建模拟器。这是测试恶意软件的最好方法,不存在被感染的风险。
因此我们选择Marshmallow 6.0模拟器来分析该样本,通过adb
(Android Debug Bridge)命令来安装APK。如果APK想加载新的可执行文件,设备(或者模拟器)的内置logger就可以捕捉到这种事件。
运行adb
命令连接到系统logger,只选择包含目标包名的输出:
$ adb logcat | grep "com.jgnxmcj.knreroaxvi"
然后运行目标应用,在各种输出结果中,我们最终找到了关键日志:
10-25 17:12:11.001 24358 24358 W dex2oat : /system/bin/dex2oat --runtime-arg -classpath --runtime-arg --instruction-set=x86 --instruction-set-features=smp,ssse3,-sse4.1,-sse4.2,-avx,-avx2 --runtime-arg -Xrelocate --boot-image=/system/framework/boot.art --runtime-arg -Xms64m --runtime-arg -Xmx512m --instruction-set-variant=x86 --instruction-set-features=default --dex-file=/data/user/0/com.jgnxmcj.knreroaxvi/app_files/rzwohkt.jar --oat-file=/data/user/0/com.jgnxmcj.knreroaxvi/app_files/rzwohkt.dex
APK的确在创建一个新的.dex
文件。非常好,我们只需要拿到这个文件,这样就无需逆向分析这个释放器(dropper)了。
不幸的是,当我们尝试获取该文件时,发现/data/user/0/com.jgnxmcj.knreroaxvi/app_files
目录中并没有这个文件。显然,这些开发者(特别是恶意软件开发者)通常会在文件使用后执行删除操作,清理战场。因此我们的下一个问题是,如何阻止释放器删除该文件?
这里让我隆重介绍一款工具:FRIDA。
FRIDA是一个非常优秀的工具包,可以在应用执行期间hook Javascript代码,也能用来修改函数、字段以及其他属性。
在本案例中,我们需要做的是阻止应用删除rzwohkt.jar
文件,以便保存到我们的主机中进一步分析。
使用FRIDA的正常思路就是先找到负责文件删除的类,然后hook相关方法并跳过代码逻辑。然而我们再也不想与猪进行摔跤比赛,因此我们准备使用动态分析来实现完全跳过这部分内容。
如果我们能够找出删除过程中用到了哪个系统调用,那么我们就可以完美绕过。不论混淆代码中具体哪个位置执行了系统调用,如果我们能正确hook系统中的原生函数,应该就能够提取出所需的载荷。
使用Strace
这里最大的问题在于,我们如何找到正确的函数?幸运的是,有一种简单的方法可以获取执行过程中涉及到的所有函数列表。
Strace是一个非常好用的Linux工具,允许用户获取进程和Linux内核之间所有交互操作的完整报告。由于Android支持该工具,因此我们借此可以找到需要hook的函数。
图5. Strace输出结果
从上图中可知,我们要寻找的函数为unlink()
。
接下来我们要做的就是将我们自己的代码hook该函数,避免文件被删除。
FRIDA代码
最后,我们已经掌握所需的所有信息,现在可以创建我们的FRIDA hook了。
首先,我们需要在移动模拟器上运行匹配目标架构的frida-server
。
既然能够hook我们的FRIDA代码,我们只需要创建脚本,然后hook并跳过unlink()
函数即可。为了完成这个任务,我们使用Interceptor.replace(target, replacement)方法来替换target
函数,将其具体实现替换为replacement
函数的实现。我们使用Module.findExportByName(module, exp)
来获取我们函数的指针,如果不知道module
名,我们可以传入null
,只不过这样会影响速度。
console.log("[*] FRIDA started");
console.log("[*] skip native unlink function");
// create a pointer to the function in the module
var unlinkPtr = Module.findExportByName(null, 'unlink');
Interceptor.replace(unlinkPtr, new NativeCallback(function (){
console.log("[*] unlink() encountered, skipping it.");
}, 'int', []));
这样操作后,一旦调用unlink()
函数,FRIDA就会拦截调用,运行我们的代码。在这个例子中,此时会简单地输出一条日志信息,通知我们该调用已被跳过。
最后,我们只要将脚本附加到目标应用进程即可。我们运行dropper_startup.py
脚本,该脚本可以启动目标应用,然后将FRIDA脚本附加到frida-server
上。
图6. FRIDA输出
从日志中我们可以看到多次unlink()
操作,这表明应用在执行过程中还会删除其他文件。第二次调用unlink()
后,我们终于可以执行如下命令:
$ adb pull /data/user/0/com.jgnxmcj.knreroaxvi/app_files/rzwohkt.jar
成功获取我们所需的文件,这是包含class.dex
载荷的一个jar归档文件。
四、总结
Android恶意软件变得越来越复杂,每天都在发展,就像成熟的Windows恶意软件一样。释放器(dropper)只是部署载荷的一种方式,但的确能行之有效。恶意软件所使用的随机字符串及无意义的函数容易欺骗反病毒引擎。Fortinet已经能识别这个恶意软件,保护客户免受其害,具体标识如下:
- 释放器: Android/Agent.CHG!tr
- 载荷:Android/Agent.ARL!tr
FortiGuard实验室会继续跟踪这类恶意软件的演化过程。
大家可以访问FortiGuard Lion Github页面下载本文使用的所有脚本。
五、IOC
加壳应用:509aa4a846c6cb52e9756a282de67da3e8ec82769bceafa1265428b1289459b3
载荷:4fa71942784c9f1d0d285dc44371d00da1f70f4da910da0ab2c41862b9e03c89