APP下载

基于Smali-Java关键代码修复的Android程序逆向分析方法

2019-06-27徐国天张明星

中国刑警学院学报 2019年3期
关键词:源代码逆向语句

徐国天 张明星

(1 中国刑事警察学院网络犯罪侦查系 辽宁 沈阳 110035;2 龙岩市公安局新罗分局 福建 龙岩 364000)

1 引言

2018年8月20日,中国互联网络信息中心(CNNIC)发布了第42次《中国互联网络发展状况统计报告》(简称《报告》)。《报告》显示截至2018年6月,我国手机网民规模达7.88亿。随着移动互联网应用的普及,利用智能手机恶意程序实施的违法犯罪活动也在逐渐增多。Android系统是目前市场占有率最高的一款智能手机终端操作系统,它允许用户随意在智能终端上安装应用程序,这种灵活性在给用户带来便捷体验的同时,也给犯罪分子提供了可乘之机。目前,针对Android系统的各类恶意程序,如盗号木马、远程控制木马、勒索病毒、间谍软件等种类繁多,危害极大[1]。

目前Android程序取证主要采用以下三种方法。第一种是动态测试法。这种方法是将送检的Android程序安装到取证专用智能终端上,触发程序运行,通过观察送检程序运行之后,表现出的一些外在行为(如弹出一个信息提示界面,或控制用户的键盘输入等),完成取证分析工作。这种方法实施起来难度较低,但是对一些没有外在表现的行为(如内存访问、数据库读写行为),不能有效察觉。第二种是通过沙箱环境进行检验分析。沙箱系统一般布置在Android虚拟机上,将送检程序安装到沙箱内运行,沙箱系统会自动监控送检程序的函数调用行为,如磁盘读写函数、网络通信函数、数据库访问函数、蓝牙访问函数等。通过监控关键函数的调用情况,对程序功能进行全面分析。互联网上此类公开平台较多,但是此类平台通常不会免费提供完整的检验报告,其给出的检验分析结果仅能用于取证结论的辅助参考。第三种是通过静态源代码逆向分析方法对Android程序进行检验分析。这种方法是将APK格式的Android程序转换为Smali或Java源代码,再通过逐行阅读源代码的方式分析程序的逻辑功能,进而完成取证分析工作。此类方法可以对送检程序进行全面分析,但需要检验人员具备Android程序设计语言基础,需要阅读的代码量比较大,同时,当程序采用了加壳或加密等反取证措施时,检验分析的难度进一步加大。在检验鉴定工作中,通常是采用多种方法对送检程序进行检验分析,参考每种方法得到的取证结果,综合给出鉴定意见。

本文针对上述第三种静态源代码逆向分析方法进行研究,这种方法通过将APK格式的Android程序转换为Java代码,再通过阅读Java源代码分析程序的逻辑功能,提取出嫌疑人电子邮箱、手机号码等关键线索。但是在将APK程序转换为Java源代码的过程中,部分核心代码因为结构比较复杂,现有取证工具无法自动完成转换,会将这部分核心代码转换为错误的Java源程序,进而给取证分析工作造成严重影响。本文提出一种基于Smali-Java关键代码修复的Android程序逆向分析方法,它采用人工分析方式,根据核心代码对应的Smali源程序完成受损Java代码的修复,进而完成Android程序的取证分析工作。

2 APK程序反编译方法

2.1 两种逆向分析方法比较

APK程序使用Java语言开发。编程人员写好Java源程序之后,需要使用编译器将所有Java源代码编译、生成一个APK程序。Java源程序可读性好,通过分析Java源代码可以准确掌握程序的执行流程。APK程序由一系列二进制0、1数据组成,无法直接阅读。因此,为了准确获知APK程序的主要功能,需要将APK程序还原为对应的Java源代码,这一过程就是APK程序的反编译[2]。

如图1所示,APK程序的反编译主要有两种方法。第一种是先将APK程序转换成Smali源代码,再将Smali源代码转化为Java源程序[3]。Smali语言是Davlik寄存器语言,类似于台式计算机上的汇编语言,是一种低级语言,可读性较差,它的书写规范与Java等高级语言完全不同。调查人员直接阅读Smali源代码来分析程序功能难度较大,且效率较低[4]。但是APK转换成Smali的过程没有损失,Smali源代码可以完整再现APK程序的相关功能。

图1 反编译方法

Smali代码在转换成Java代码过程中会出现10%左右转换错误,这10%的程序代码通常是Smali源文件中最复杂的多分支语句或循环语句,这部分代码通常是APK程序的核心代码。因此调查人员在进行分析时,对于简单的程序函数可以参考Java源代码,对于无法转换的那10%左右的代码可以参考Smali源文件。

第二种反编译方法是先通过APK程序获得dex文件,再将dex文件转化为jar文件,最后将jar文件转化为Java源代码[5]。这种方式的转换过程比第一种方法多了一个环节,但是只有5%左右的代码损失,并且得到的Java源代码更规范,可读性更好。本文提出的逆向分析方法总体思路是利用第一种方法获得Smali源文件,再以第二种方法获得Java源文件,对于未能正常解析的Java源代码,利用人工分析方式,将对应的Smali代码转换为Java源代码,进而完成逆向分析工作。

2.2 反编译APK程序得到Smali源代码

为了进行功能鉴定需要对APK程序执行反编译,将其转化为可以直接阅读的源程序。目前现有工具软件无法直接将APK程序还原为对应的Java源程序,只能转化为一种名为Smali的中间语言。这种语言与台式计算机上的汇编语言类似,可读性较差,但是可以完整还原程序功能。这里使用一种名为apktools的工具执行反编译,下面介绍具体的逆向方法。

首先,安装Java运行环境。apktools需要Java运行环境支持,因此需要先安装Java工具包。之后,安装、运行apktools,反编译目标APK程序。Apktools工具有三项主要功能。第一是将APK程序反编译成Smali代码。第二是允许用户修改Smali代码,并将修改后的Smali代码重新编译为APK程序。第三是提供了签名功能,重新编译形成的APK程序需要签名之后才能使用。

本文以一款名为9555.apk的手机木马程序为例进行逆向分析,利用apktools工具反编译成功之后,在目标路径下会形成一个名为“9555.apk.decode”的文件夹。图2是将测试文件9555.apk反编译得到的22个Smali程序文件。如图2所示,每个文件代表一个类,文件名就是类名。例如,A.Smali就是名为A的类,其中存储的就是这个类的Smali源代码。还有一些文件中带有$符号。例如,R$id.Smali。它表示在名为R的类中又定义了名为id的子类,这个文件中存储的就是id子类的Smali源代码。如图2所示,9555.apk共包含14个类定义。

图2 9555.apk反编译得到的22个Smali文件

2.3 反编译APK程序得到Java源代码

通过第二种方法反编译APK程序获得Java源代码的具体方法。首先,解压缩APK程序获得dex文件。APK程序默认采用的是ZIP压缩格式,只要将程序的扩展名由apk修改为zip,之后进行解压缩即可获得dex文件。请注意解压之后也会产生一些xml文件。例如AndroidManifest.xml,但是这些xml文件通常无法正常查看。

之后,使用dex2jar软件将dex文件转化为jar文件,这个软件也需要提前安装好Java运行环境。Dex2jar是一个DOS环境下运行的应用程序。将dex文件拷贝到dex2jar程序根目录下,之后在DOS窗口中输入dex2jar classes.dex命令。转换完成之后,在dex2jar根目录下就会生成一个名为classes_dex2jar.jar的结果文件。

将jar文件转化为Java源程序。使用jd-gui软件可以将jar文件转换为对应的Java源程序。使用jdgui软件查看classes_dex2jar.jar,会出现图3所示结果。共出现14个Java类文件,目录结构与前面获得的Smali源代码可以一一对应,便于分析。不同的是主类和子类的定义在Smali中是分开书写的,而在Java源代码中子类的程序代码已经写入到对应的主类中。可以看到在Java源程序中字符串数据不在是原始的十六进制数值而是已经转化为可以直接阅读的文字,并且Java源程序的可读性更强。

图3 由jar文件转换得到Java源程序

3 Smali和Java源代码的相互转换方法

Smali是Dalvik的寄存器语言,它与Java的关系就像汇编语言与C语言的关系。在台式计算机上,一个用C语言开发的exe文件在进行逆向分析时,首先会转化为汇编语言,之后再由汇编语言转化为C语言代码,通过分析C语言代码可以了解程序的相关功能[6]。

APK程序与exe程序的反编译过程极为相似,两者的对应关系如图4所示。不同的是汇编语言比较复杂,短时间内不易掌握。Smali语言相对比较简单,便于转化为Java源程序。但是Smali在转化为Java源代码的过程中会出现5%~10%左右代码转换错误,这部分代码通常是APK程序的核心功能,因此需要直接分析Smali文件。这就需要了解Smali的语法规则,下面我们采用Smali和Java源程序对比分析的方法来进行介绍。理解这部分内容的前提条件是读者已经具备Java语言的基础知识。

图4 exe和APK程序反编译对应关系

3.1 Smali和Java的方法定义转换

下面给出的是使用Java语言编写的一个方法。这个方法名称是onCreate;有一个Bundle类型的传入参数,参数名为paramBundle;方法返回值为void类型、protected属性。方法中只有一条语句,调用了父类的onCreate方法。

这个Java方法可以转化为如下Smali代码。#是Smali中的注释语句;.method和.end method相当于Java中的{ },定义了方法的起始和结束位置。Landroid/os/Bundle定义了参数类型,.parameter定义了参数名,本例值为paramBundle。如果有多个参数,可以在列表中依次增加。在Smali中参数使用p0~pn来表示,其中p0固定代表this,参数从p1开始。在本例中参数paramBundle对应的就是p1。V表示方法返回值为void类型。

.locals定义了方法用到的寄存器数量为4,寄存器名称为v0~v3。.prologue定义了代码的起始位置。.line定义了这条Smali语句是Java源程序中的第几条语句,这个值主要用于调试,不起关键作用。invoke-super {p0, p1}, La ndroid/app/Activity;-〉onCreate(Landroid/os/Bundle;)V表示调用父类的onCreate方法,V表示返回值为void类型,p1为参数,即paramBundle。Return-void表示返回值为void类型。

通过对比可以看到,Smali和Java的对应关系比较清晰,只是Smali代码比Java代码复杂一些,Java的一条语句可能对应Smali的多条语句。

3.2 Smali和Java的方法调用转换

下面这条Java语句是调用makeText方法,生成一组值为“hello,Smali”的字符串,然后调用show方法弹出这组提示信息。

这条语句在进行APK逆向分析时经常用来测试程序功能,这里以它为例来对比分析Smali和Java的方法调用。这条Java语句对应的Smali代码如下:

第一条语句const-string v0, "hello,Smali"定义了值为"hello,Smali"的字符串变量,变量值存储在v0寄存器中。

第二条语句const/4 v1, 0x1定义了值为0x1的整数变量,变量值存储在v1寄存器中。

第三条语句以静态方式调用了makeText方法,这个方法包含3个参数,类型分别是context、CharSequence和整数类型。参数值分别是p0、v0和v1,p0代表this,v0代表"hello,Smali",v1代表0x1。返回值是一个Toast类型的对象。

第四条语句是将makeText方法调用的结果送到v0寄存器中。之前v0寄存器存储的是字符串类型变量,这条语句执行之后,v0寄存器中存储的是Toast类型对象。

第五条语句是以virtual方式调用show()方法,这个方法没有参数,这里v0代表上一条语句获得的Toast对象,show()方法的返回值为void类型。通过分析,可以看到原本一条Java语句转换为四条Smali代码。

3.3 Smali和Java的switch多条件分支语句转换

下面是使用Java语言编写的一组switch多条件分支语句,这类语句在程序设计中也经常使用。这段代码的逻辑功能非常简单,根据变量i的取值,分1、2、3和其他共4种情况处理。

上述switch语句对应的Smali代码如下所示。V0寄存器表示变量i,初值为3。:cond_0是switch语句开始位置,标志就是packed-switch v0, :pswitch_data_0。Switch的结束标志是:pswitch_data_0,.packed-switch 0x1表示case匹配值从1开始递增,每次加1。即满足case 1:时,执行:pswitch_1处代码;满足case 2:时,执行:pswitch_2处代码;满足case 3:时,执行:pswitch_3处代码;如这3个条件都不满足,则执行default代码,即:goto_1位置代码。所有分支最终都跳转到:goto_0位置,执行return-void返回。Nop代表空指令。

3.4 Smali和Java的循环语句转换

循环语句主要有while和for两种类型,下面是一段while循环结构,程序的一些关键功能通常包含在循环语句中。经过实际检测,发现在将APK程序反编译为Java源代码的过程中,循环语句和switch分支语句最容易出现问题。因此,调查人员必须掌握Smali语言编写的循环语句到Java代码的手工转换方法。

下面是上述Java代码对应的Smali语句。V0表示变量i,初值为0。V2存储的是循环结束条件10。当i值小于10时,执行循环体i = i + 1。否则执行returnvoid结束循环。

4 基于Smali-Java关键代码修复的Android程序逆向分析方法

APK程序的逆向分析方法目前主要有以下几种:

方法一:信息反馈法。这种方法是指先运行目标程序,然后根据程序运行时给出的反馈信息作为突破口寻找关键代码。通常情况下,程序中用到的字符串会存储在String.xml文件或者直接写入程序代码中。如果是前者的话,字符串在程序中会以id的形式访问,只需要在反编译代码中搜索字符串的id值就可以找到代码调用处;如果是后者的话,可以在反编译代码中直接搜索字符串即可。

方法二:顺序查看法。这种方法是指从软件的启动代码开始,逐行向下分析,掌握软件的执行流程。

方法三:代码注入法。代码注入法属于动态调试方法,它的原理是手工修改APK文件的反编译代码,加入Toast.MakeText.Show()语句,查看程序执行到特定点的状态数据。

本文提出的APK程序逆向分析方法总体流程如图5所示。这种方法以AndroidManifest.xml文件为分析入口,每次从中取出一个intent对象分析,循环进行,直至所有intent对象分析完成。对于每个intent对象,首先获得其相应处理程序的Java代码。如果Java代码完好,则直接分析,否则获取对应的Smali代码,再将Smali代码以人工方式转换为Java代码,然后分析Java代码。之后将APK程序安装在Android系统手机上测试这个intent功能。如果程序设计存在逻辑错误,则修改原始Smali代码,再重新编译成APK程序,安装测试。

图5 基于Smali-Java关键代码修复Android程序逆向分析方法的总体流程

这种分析方法综合运用了Smali、Java代码分析,APK程序的修改、编译和测试,虽然需要大量时间用于分析,但是可以完全获得程序的执行逻辑,甚至发现程序设计上的逻辑错误。

5 基于Smali-Java关键代码修复的Android程序逆向分析方法应用

5.1 无损Java代码分析

以9555.apk程序为例,9555.apk程序的AndroidManifest.xml文件中,图6代码定义了APK程序的名称和显示图标。Label变量表示应用程序名称,@string/app_name是名称字符串所在的路径,打开values文件夹下的string.xml,可以看到app_name字符串对应的值为“招商客户端”。Icon变量定义了应用程序显示的图标,@drawable/ic_launcher是图标的存储位置,打开drawable文件夹,可以看到名为ic_launcher.png的图片文件,如图6所示。

图6 定义应用程序名称和运行图标

AndroidManifest.xml文件的第一组代码规定了APK程序的入口。它的标志是字符串“android.intent.action.MAIN”,对应的代码对象名称为“A”。如图7所示,它表示当9555.apk程序被点击运行时,A中的代码将被调用执行。

图7 APK程序入口

APK程序的入口代码存储在名为A的类中,下面显示的是A类中的onCreate方法。函数首先调用了父类的onCreate方法。setContentView(0x7f030000)负责设置当前activity的显示界面,其执行过程如下所示。Java程序代码中的0x7f030000是一个id值,根据这个数值在public.xml文件中可以定位一条匹配记录。这条记录的名字是main,类型是layout布局文件。根据这两个数值可以定位到layout文件夹下的main.xml文件,在这个文件中定义了一个webview类型控件,但是运行时不显示任何内容。onResume方法负责将程序注册为一个设备管理器。具体代码此处不进行分析。这段代码在转换过程中未受到损伤,调查人员可以直接分析。

5.2 有损Java代码分析方法

9555.apk程序逆向之后得到的Java代码中,S服务的onCreate方法代码如下所示。当S服务被激活时,onCreate方法将被触发运行。这组代码实际上完成了两大功能:响应接收短信广播和当短信箱发生变化时处理短信。通过逆向得到S服务onCreate方法的Java代码,但是通过阅读程序发现存在如下局部代码,不难发现这段代码存在逻辑错误。这是因为原始Smali代码中存在循环或多分支语句造成的,需要通过人工修正错误。

循环语句的提取、转换方法如下所示。const/4 v1, 0x0是将v1寄存器的值设置为0,这个寄存器就是变量i,之后v3寄存器被设置为20,这是循环结束值。if-lt v1, v3, :cond_0比较v1和v3的大小,如果v1小于20,则跳转到:cond_0标签地址。否则执行return-void结束程序的执行。:cond_0标签地址的packed-switch v1, :pswitch_data_0是switch语句的起始标志。Switch语句中的多个case子句这里不进行提取。add-int/lit8 v1, v1, 0x1对应的是i = i + 1命令。goto :goto_0重新返回到循环起始位置。修正之后的Java循环结构如下所示。利用人工分析方法,对受损的Java代码进行修复。

6 结语

本文提出的基于Smali-Java关键代码修复的Android程序逆向分析方法,可以对受损的核心代码进行修复,完成取证分析工作。但是在分析过程中,要求取证人员通过人工分析方式定位受损代码,在完成受损代码的修复。工作效率偏低,且难度较大,下一步计划研究受损代码的自动定位和修复方法,以提高取证分析效率。

猜你喜欢

源代码逆向语句
逆向而行
基于TXL的源代码插桩技术研究
重点:语句衔接
逆向思维天地宽
基于语法和语义结合的源代码精确搜索方法
解密别克安全“源代码”
我喜欢
作文语句实录