SmartDroid: an Automatic System for RevealingUI-based Trigger Conditions in Android Applications
文章语言:英文
文章来源:2nd Annual ACM CCS Workshop on Security and Privacy inSmartphones and Mobile Devices (SPSM)-2012(CCS workshop汇集)
作者信息:
1. Cong Zheng
2. Shixiong Zhu
3. Shuaifu Dai
5. Xiaorui Gong
6. Xinhui Han(简介)
发表单位:
最近几年静态分析和动态分析的演化趋势:
UI交互在Android的应用程序中是一种基本的功能,像许多的Activities都需要UI交互来触发。这种类型的UI交互能够帮助恶意软件的apps来隐藏他们的敏感行为(例如:发送短信和获取手机设备的ID号码)通过动态分析工具的检测例如TaintDroid,因为简单的运行app,但是没有正确的UI交互将不会导致敏感行为的暴露。这篇文章关注的就是通过自动的UI交互来触发特定的行为。特别的,通过混合静态分析和动态分析来揭示Android应用程序的基于UI触发的条件。首先使用静态分析来提炼出期望的Activity转换路径通过分析Activity和Function调用图,并且使用动态分析来遍历每一个UI控件并且搜索出UI交互一直到敏感API的路径。作者实现了原型系统SmartDroid并且展示出他能够自动的并且高效的检测出基于UI的触发条件暴露出的敏感行为,使用TaintDroid不能够检测出来的敏感行为。
Android的Activity是一个屏幕包括了按钮,文本框还有其他的UI元素用来做用户交互。因此,主要的思想就是使用静态分析找出期望的Activity转换路径能够涉及到敏感行为的传播;之后对于每一条路径,我们使用动态分析强迫这个应用程序按照指定的路线运行,直到敏感行为被触发。在动态分析中,SmartDroid将会尝试着和每一个UI元素进行自动的交互通过遍历当前的Activity中的view tree,将APK运行在在修改后的Android系统中。
如果目前的Activity能够跳转到下一个Activity的转换路径,则目前的UI元素就是我们期待的元素将会被保存下来。当遍历到最后一个Activity的时候敏感行为将会被触发。最后,这一系列的UI元素将会被作为触发条件,包括他的坐标以及UI的事件类型。
简要归纳为一下几点:
1.静态分析,找出每个activity之间的调用关系
2.HOOK了framework中的敏感APIs
3.Activity的代码,修改了了startActivity 以及startActivityForResult 保证了Activity的跳转是按照想要的路径来转化的。
4.修改了View
之类的含有UI事件的源代码
文章使用了Horoscope这个例子来做讲解。下载地址https://play.google.com/store/apps/details?id=fr.telemaque.horoscope
这款软件可以显示每天的和每月的运势。它可以连接Internet以及发送IMEI号通过特地的号码通过点击特定的按钮。点击每天运势和每月运势的时候,会在后台有一些行为。一个是发短信,一个是读取IMEI
在这里我们认为读取IMEI号码以及接入网络都是属于敏感行为并且接入网络能够导致隐私泄露。
使用Apktool反编译了这款软件:APKtool的使用方法可以参考:Ubuntu上安装apktool
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-feature android:name="android.hardware.wifi" android:required="false" /> <uses-permission android:name="android.permission.READ_PHONE_DATA" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.RESTART_PACKAGES" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="com.android.vending.BILLING" /> <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-feature android:name="android.hardware.location" android:required="false" /> <uses-feature android:name="android.hardware.location.network" android:required="false" /> <permission android:name="fr.telemaque.horoscope.permission.C2D_MESSAGE" android:protectionLevel="signature" /> <uses-permission android:name="fr.telemaque.horoscope.permission.C2D_MESSAGE" /> <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
#!/usr/bin/env python # coding=utf-8 """ @ AUTHOR : Chicho @ VERSION : 1.0 @ DATE : 2014-03-23 @ FUNCTION : Get the permission in AndroidManifest.xml @ FILENAME : getpermission.py @ COMMAND : python getpermission.py -d your_directory_store_APKfile """ import sys, getopt from xml.dom import minidom if __name__ == ‘__main__‘: _directory = "" # define a directory to store you target file # opts store the relevant parameters , args stores some char except parameters opts,args = getopt.getopt(sys.argv[1:], "d:h", "directory=help") # get the directory for op,value in opts: if op in ("-d", "--directory"): _directory = value xmldoc = minidom.parse(_directory + ‘/AndroidManifest.xml‘) # get all the elements in the manifest manifest = xmldoc.getElementsByTagName(‘manifest‘)[0] permission_list = manifest.getElementsByTagName(‘uses-permission‘) #print permission_list #the total number of all the permissions lens = len(permission_list) for i in range(0, lens): permission = manifest.getElementsByTagName(‘uses-permission‘)[i] permission_name = permission.getAttribute(‘android:name‘) print permission_name
当这个Horoscope启动的时候Android系统将会创建一个app 的main Activity实例如图1所示。暂停三秒钟之后启动第二个Activity如图1(b)使用了Intent来启动。在这里有两个按钮,是用来登陆FaceBook和Twitter还有其他的按钮是用来设置你自己的生日的。有12个按钮呈现出了不同的星座。当你点击12个图标中的一个图标的时候将会得到一个如图(C)所示的Activity。有两个按钮分别得到每天的还有没月的运程。当你点击两者中的任何一个的时候,它将会跳转到图1中的(d)在最后一个Activity中它可以跳转到如图1(d)中所示的Activity。在最后一个Activity中,它会读取并且发送设备的敏感APIs到远程服务器通过敏感APIs“android.telephony.TelephonyManager.getDeviceId()”以及“org.apache.http.client.HttpClient.execute()”
图2展示出了该架构的原理图,它由FCG(Function Call Graph)函数调用图和ACG(Activity Call Graph)调用图组成。在FCG中,我们可以决定所有的函数调用敏感APIs的路径。开始的路径被定义为敏感源函数。在ACG中,我们定义和敏感函数相关联的Activity叫做敏感池Activity(sensitive sink Activity).例如在图2中,Activity F它联系了两种作为敏感源函数,当目前的Activity跳转到我的ActivityF的时候它将会调用“onCreate()”函数来启动我的ActivityF。之后的控制流程序将会沿着函数调用图到达我的“getDeviceId()” API。于此同时,在Activity F中的按钮点击之后将会调用相应的onClick()函数。这个控制流就会执行“sendTextMessage()” API来发送一条SMS 短信。
我们的方法包括两部,金泰分析阶段以及动态分析阶段。在静态分析阶段能够产生我们期待的Activity路径转换并且用来指导动态分析觉得和哪一个Activity做交互。在动态分析阶段我们可以发现一系列的UI elements能够导致敏感APIs执行。这个UI元素相应的坐标以及事件类型是我们是触发条件。
在静态分析中我们首先使用FCG和ACG,我们必须要分析所有的Intents在调用“startActivity”以及“startActivityForResult”功能来获得源Activity和目标Activity它们之间通过Intent相连接。最终,我们提取出期待的Activity转换路径即从源Activity到每一个敏感池Activity。这个过程包括
(1) 获取所有的敏感函数。我们在FCG当中提取出每一个函数调用敏感API,在每一条路径的第一个first function是源function。
(2) 获取每一个敏感池Activity我们分析这些敏感源Activity是属于哪一个Activity的.
(3) 得到期望的Activity转换路径。我们在ACG当中使用深度优先遍历算法得到路径。
在动态分析阶段我们的目标是判定哪一个UI元素能够触发敏感行为。在知道了期望的Activity路径之后,目标转移的Activity知道哪一个UI可以让应用程序按照期待的Activity路径转换。我们修改了Android系统以至于我们能够遍历并且和UI进行自动的交互从一个Activity到另一个Activity。除此之外我们强迫这个运用程序能够按照期待的Activity转换路径进行,我们限制了Activity的产生通过修改Android系统。如果我们的系统和错的UI元素交互让这个运用程序转换到错误的Activity,我们将会禁止这个错误的Activity产生。最终,当这个敏感行为被触发,我们获得了一系列的UI元素。在和UI交互的时候,我们记录下来他们相应的UI坐标和UI事件类型作为UI触发的基本条件。
静态路径选择器被用来找到正确的Activity转换路径。如果一个应用程序能够按照期待的Activity调用路径运行,他有可能触发敏感行为,否则他是不可能触发敏感行为的。例如,在图2中,我们点击了按钮A这个时候Main Activity将会跳转到Activity A。第一步在这个案例中是错误的,所以不可能触发发送短信这个敏感行为。因此在静态分析中有三部:
反编译,构建FCG和ACG。
下面是由python编写的获取这个软件的包名,以及所有的Activity的方式
当然这篇论文的技术远不止这些,你必须要构建的是他们之间的调用关系,然后构建出他们之间的ACG,这个之间的连接就是我们的Intent
#!/usr/bin/env python # coding=utf-8 #filename : parseManifest.py #author : Chicho #date : 2014-03-23 """ @FUNCTION: 1. Get the package name of you APK 2. Get all the activities in you application @VERSION: 1.0 """ import sys, getopt from xml.dom import minidom if __name__ == ‘__main__‘: _directory = "" opts, args = getopt.getopt(sys.argv[1:],"d:h","directory=help") for op, value in opts: if op in ("-d", "--directory"): _directory = value xmldoc = minidom.parse(_directory + ‘/AndroidManifest.xml‘) manifest = xmldoc.getElementsByTagName(‘manifest‘)[0] package = manifest.getAttribute(‘package‘) output = open(_directory + ‘/package.txt‘, ‘w‘) output.write(package) output.close() activity_list = manifest.getElementsByTagName(‘activity‘) total = len(activity_list) activity_name = "" output = open(_directory + ‘/activity.txt‘, ‘w‘) for i in range(0, 38): activity = manifest.getElementsByTagName(‘activity‘)[i] activity_name = activity.getAttribute(‘android:name‘) print activity_name output.write(activity_name) output.write(‘\n‘) output.close()
对于一个APP,我们想知道哪一个可能的敏感行为,所以我们必须检测这个应用程序中使用的APP。为了实现这个,我们必须要反汇编这个应用程序。考虑到Dalvik bytecode-to-Java bytecode 翻译的准确性。我们更喜欢操作并且分析Dalvik字节码。这个smali code作为Dalvik字节码的中间表现。Smali code 是十分方便的相对分析 Dalvik 字节码。
在这一步当中,我们想要发现FCG,在这里,所有的孩子节点都是敏感的API。所以我们第一步是发现所有的敏感APIs的smali代码。在smali代码中我们很容易的知道函数之间的调用关系。我们之后使用交互算法来找到所有的敏感的APIs的调用路径。 但是有很多间接的调用指令以及事件驱动调用也是不能忽视的。实际上一些间接的调用指令来自于Java的多态。我们使用的方法是用Woodpecker的解决方案来解决的。他增加语义事件驱动最终获得了全部的FCG。
基于文章介绍的方法是应该完全知道全部的ACG。在Android系统中,Activity是通过Intent的,它是一个消息指明了收件人和可选的数据,并且用它来开启一个新的组件。通常一个Activity开启一个新的Activity它将会创建一个Intent并且调用“startActivity”或者是“startActivityForResult”函数。一般情况下,开发者创建一个“startActivity”或者是“startActivityForResult”调用指令。App将会使用Intents和一个或者其他的应用程序进行交互。除此之外,系统使用Intents作为一个应用程序的通知。我们的目标就是发现所有的Intents并且分析他们的源Activity以及目标Activity通过这些Intents.我们首先介绍Android的Intent之后我们介绍使用这个Intent来做分析的方法。
在Android中有两种类型的Intents:显示的Intent和隐式的Intent。一个显示的Intent能够明确的指出他的接受组件的名字,但是隐式的Activity就只申明了Action。实际上接受者是根据声明在AndroidManifest.xml内容决定的。在AndroidManifest.xml中,所有的组件(包括i.e.Activity,Receiver)在应用程序中定义的他们都能收到特定的actions.
对于一个显示声明的Intent,是十分容易的知道他的目标Activity根据他的定义。但是对于一个隐式的Activity,我们就需要匹配在AndroidManifest.xml中的actions。如果没有匹配的Intent,我们就认为Intent仅仅只能接收系统的组件(i.e.,sms box, email)或者是其他的应用程序。以下列出了6种类型的结构体来构造Intent的对象。
a) Intent()
b) Intent(Intent o)
c) Intent(String action)
d) Intent(String action, Uri uri)
e) Intent(Context packageContext,Class<?>cls)
f) Intent(String action, Uri uri, ContextpackageContext,Class<?>cls)
在构造器a)中,他初始化的是一个null的Intent。之后,可以使用setClass(),setComponent(),setAction()方法来定义目标Activity或者绑定一个目标Activity。c),d),e),f)是等效的。在构造体当中Intent是由其他的Intent产生的,这个时候我们就需要分析更多的参数。构造器c),d)他们是隐式的Intent。可以分析AndroidManifest.xml我们可以决定会启动的Activity根据actions。在e)和f)当中我们显示的声明了Intent所以可以很清楚的知道他的目标Activity是谁。有时在e)中参数“packageContext”就是目前的Activity的名字,所以我们可以很好的获得要启动的Activity,也就是Activity。
在这里举一个例子说明Activity之间的跳转关系,现在有一个比较简单的APK ,主Activity启动secondActivity,在这里使用的是隐式申明:
MainActivity的代码
package com.example.actionattr; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class MainActivity extends Activity { public final static String INTENT_ACTION = "net.chicho.intent.action.INTENT_ACTION"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button bn = (Button)findViewById(R.id.bn); bn.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub Intent intent = new Intent(); intent.setAction(MainActivity.INTENT_ACTION); startActivity(intent); } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
package com.example.actionattr; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; public class SecondActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.second); EditText show = (EditText)findViewById(R.id.ed); String action = getIntent().getAction(); show.setText("Action为:" + action); } }
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.actionattr" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.example.actionattr.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name="com.example.actionattr.SecondActivity" android:label="@string/app_name" > <intent-filter> <action android:name="net.chicho.intent.action.INTENT_ACTION" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> </application> </manifest>
<intent-filter>
<action android:name="net.chicho.intent.action.INTENT_ACTION" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
看马上MainActivity中声明的action
public final static String INTENT_ACTION= "net.chicho.intent.action.INTENT_ACTION";
Intent就好比之间的暗号一样的
Intent intent = new Intent();
intent.setAction(MainActivity.INTENT_ACTION);
startActivity(intent);
在来看一下smali代码是什么情况,大同小异:
由于在ManiActivity中实现了匿名内部类,所以出现了上面这个图片的情况。但是在smali代码中仍然是很好找出其中的对应关系
# static fields .field public static final INTENT_ACTION:Ljava/lang/String; = "net.chicho.intent.action.INTENT_ACTION"
invoke-direct {v1, p0}, Lcom/example/actionattr/MainActivity$1;-><init>(Lcom/example/actionattr/MainActivity;)V invoke-virtual {v0, v1}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V
.line 27 .local v0, intent:Landroid/content/Intent; const-string v1, "net.chicho.intent.action.INTENT_ACTION" invoke-virtual {v0, v1}, Landroid/content/Intent;->setAction(Ljava/lang/String;)Landroid/content/Intent;
.line 18 .local v1, show:Landroid/widget/EditText; invoke-virtual {p0}, Lcom/example/actionattr/SecondActivity;->getIntent()Landroid/content/Intent;这样代码实现的思路应该是很清晰了吧~
动态分析有三个组件:UI交互模拟,Activity限制以及运行时环境。
修改了源码的framework层之后在敏感APIs中插入输出日志,便于监控敏感API是否被调用。
为了让我们的动态分析自动化,我们第一部就是应该开发UI交互模拟器来和UI元素自动的交互。执行UI交互的思想就是:修改Android的framework层的Activity代码,是的可能被遍历的UI元素都被HOOKActivity被组织成一个hierarchy树。View对象是Activity的根节点。我们使用深度优先遍历算法得到每一个UI元素的信息。当遍历到UI元素的时候我们可以触发一些事件监听器。这些事件监听器都涉及到可能的传播路径。在期间我们记录下这个控件的尺寸和坐标。
目前的关键问题就是知道哪一个UI控件的交互会条跳转到正确的Activity
转载注明出处:谢谢合作~http://blog.csdn.net/chichoxian/article/details/21344357
原文:http://blog.csdn.net/chichoxian/article/details/21344357