李 红
(东北财经大学管理科学与工程学院,辽宁大连116025)
在当今移动互联网时代,安卓手机应用市场正在经历着一场前所未有的变革.安卓系统平台上运行着超过几百万的应用程序.但是,安卓系统自身所提供的安全防患措施却远远落后于其发展的速度.正因为如此,不计其数的病毒软件利用这些安卓系统本身的弱点,侵害安卓用户的隐私及安全.为了更好地保护安卓手机用户的隐私和安全,研究者研发了许多扩展框架(Framework Extension),而其中之一就是I-ARM-Droid框架,其全名为In-App Reference Monitors for Android Applications.
I-ARM-Droid框架是一个在安卓应用程序中重写Dalvik Code的框架[1].I-ARM-Droid框架的一大优点就是用户只需关心应用层,而不必修改安卓系统的中间件及Linux内核,就可以在安卓应用程序中添加安全控制措施.I-ARM-Droid框架的目标是构建一个能够通过灵活添加用户自定义的行为来解决用户对安卓应用程序安全的担心[2].
首先,I-ARM-Droid框架通过确定目标方法在应用程序中建立引用监听.一方面,为了拦截目标方法,用户可以编写Java源代码来自定义行为,之后编译成Dalvikbytecode;另外一方面,用户需要将原安卓应用程序apk反编译成Dalvikbytecode,然后修改Dalvikbytecode,使其调用用户新编写的API,然后将用户新创建的Dalvikbytecode和原应用程序修改后的Dalvikbytecode集成在一起,创建一个未签名的apk文件.在将修改后的apk安装到安卓手机之前,用户还需要签名验证修改后的apk.因为,安卓系统采用证书的方式来验证一个应用程序的开发者.在这个过程中,最重要的一点是采用I-ARM-Droid框架,用户无需关心底层的安卓系统中的中间件层以及底层的Linux内核.而且,实验证明修改后的部件的运行性能并未受到影响.I-ARM-Droid框架的结构图如图1所示.
首先,使用I-ARM-Droid框架时,用户需要确认想要拦截的目标方法.而目标方法的确定需要依据用户的特殊的需求.在Dalvikbytecode中,方法是通过全名来标识的,这就意味着,每个方法的全名都包含:包名 (package name)、类名(class name)以及返回类型 (returning types).所以,当用户想要拦截两个同名的目标方法时,不会出现混淆两个同名方法的情况.比如,方法com.dufe.security.smsser-vice.classA.methodA 和方法 com.dufe.security.gpsservice.classA.methodA,虽然这两个方法的名字都是methodA,但是可以看出,他们是在两个不同的包内.
图1 I-ARM-Droid框架结构图
当确定目标方法后,用户可以给每个目标方法创建自定义行为.在解释用户如何添加自定义行为之前,笔者认为首先要了解两个重要的概念,即Stub和Wedge方法.
1.2.1 Stub方法 在Dalvikbytecode中,有3种主要类型的方法可以被调用,静态方法(Static method),实例方法(Instance method)和构造方法(Constructor).为了拦截这3种类型的方法,使用I-ARMDroid的用户可以创建静态方法来添加用户自定义行为.这些静态方法称为Stub方法.
(1)静态方法.Java支持静态方法和静态变量.静态方法是在其方法声明时添加Static标示符.静态方法在被调用时需要与其类名一起被调用,而无需提前创建这个类的实例.例如:在I-ARM-Droid框架中拦截“java.lang.Math.sqrt”方法,用户可以创建一个同名、同返回类型的方法.这个新的方法可以是“pkgprefix.java.lang.Math.sqrt”.
(2)实例方法.与静态方法截然相反,实例方法声明时不需要使用static标示符.当调用实例方法时,用户需要创建这个类的一个实例,方可调用该实例方法.
ClassA classA=new ClassA();
classA.instanceMethodName();
图2展示了一个被拦截的实例方法“android.app.Activity.setContentView(int)”.
图 2 Stub 方法“pkgprefix.wedge.android.app.Activity.setContentView”
在图2所示的例子中,用户使用I-ARM-Droid框架拦截一个方法“android.app.Activity.setContent-View(int)”.用户创建了一个同名、同返回类型的stub方法.区别在于这个新的方法接受两个参数,而且第一个参数是被拦截的类的实例.
(3)构造方法.Java使用在一个类中的构造方法来创建这个类的实例.构造方法的定义与方法的声明方式很像,但是区别在于构造方式与类名同名,并且没有返回类型.ClassA classA=new ClassA(args)[3];图3 展示了一个被拦截的构造方法“java.net.URL(String)”.
图 3 Stub 方法“pkgprefix.java.lang.cons_java_net_URL”
1.2.2 Wedge方法 Wedge方法是在I-ARM-Droid框架中,用户可以来拦截目标方法的另外的一种方式.例如,如果用户需要拦截方法“android.app.Activity.setContentView”,用户可以创建一个Wedge类“pkgprefix.wedge.android.app.Activity”.这个类继承父类“android.app.Activity”.在这个 Wedge 类中,用户可以覆盖setContentView方法.然后,用户需要找出在这个应用程序中所有继承“android.app.Activity”的类,将这些类改为继承“pkgprefix.wedge.android.app.Activity”类.图 4 展示了拦截“android.app.activity”类.
4 Wedge 方法“pkgprefix.wedge.android.app.activity.setContentView(int var0)”
在图4所示的例子中,用户拦截了对方法“android.app.Activity.setContentView”的调用.这个Wedge方法的全名是“pkgprefix.wedge.android.app.Activity.setContentView”.这样就可以改变 MainActivity来继承新的Wedge Activity,而不是以前的Activity.
为了使用Stub和Wedge方法来拦截目标方法,用户需要在原始安卓系统应用程序中确定所有对目标方法的引用,然后重写同名来调用Stub和Wedge方法.因为多数情况下,用户只可以获得apk文件,此时,用户可以使用apktool[3]来反编译成Dalvikbytecode.相应的操作命令是:“apktool d APPNAME.apk DESTINATION_FOLDER_NAME”.接下来探讨如何修改Dalvikbytecode来拦截多种不同类型的目标方法.
1.3.1 在Dalvik bytecode中修改静态方法 在Dalvikbytecode中,静态方法是通过invoke-static来调用的,其包含多个register,每个变量都有一个register.因为新创建的Stub方法拥有同样数量的变量,用户只需要更改方法的全名,而不需关心每个register.为了跟本文之前的例子保持一致,笔者仍然使用之前的静态方法作为例子.原始的调用“java.lang.Math.sqrt()”的 Dalvikbytecode为“invoke-static{v0,v1},Ljava/lang/Math;- >sqrt(D)D”.用户可以将其改成调用“pkgprefix.java.lang.Math.sqrt()”.修改后的 Dalvikbytecode为“invoke-static{v0,v1},Lpkgprefix/java/lang/Math;- >sqrt(D)D”.
1.3.2 在Dalvik bytecode中修改实例方法 在Dalvikbytecode中,实例方法是通过“invoke-virtual”及其相关语法调用的.第一个register是对目标方法所在的实例对象的一个引用.其余的registers都作为参数传入该方法本身.在上文提到的实例方法,其Dalvik bytecode是:“invoke-virtual{p0,v2},Lcom/example/cs702/MainActivity;- >setContentView(I)V”.修改后的Dalvik bytecode是:“invoke-static{p0,v0},Lpkgprefix/wedge/android/app/Util;- >setContentView(Landroid/app/Activity;I)V”.容易看出,“Landroid/app/Activity”作为第一个变量传入给setContentView方法.
1.3.3 在Dalvik bytecode中修改构造类型 在Dalvik bytecode中,构造方法通过“invoke-direct”及其相关语法调用.第一个register指代的是在这个类被创建一个实例后,对这个实例的引用存储的目标地址,其余的registers都是作为参数传递给该构造方法.在上面的构造方法中,原始的Dalvik bytecode是
“invoke-direct{v2,v3},Ljava/net/URL;- > <init>(Ljava/lang/String;)V”.
修改后的Dalvik bytecode是:
“invoke-static{v3},Lpkgprefix/java/lang/URL;- >cons_java_net_URL(Ljava/lang/String;)Ljava/net/URL;”.容易看出invoke-direct比invoke-static多出一个register,因为在这个例子中,v2用来存储这个类实例化后该实例需要存储的地址.
1.3.4 在Dalvik bytecode中修改Wedge方法 为了拦截一个Wedge方法,用户需要找到那些继承包含Wedge方法的父类的类,然后,将其改成继承包含Wedge方法的父类.笔者还是拿之前提到的Wedge方法为例.MainActivity继承 android.app.activity.原始的 Dalvik bytecode是:“.super Landroid/app/Activity;”.修改后的 Dalvik bytecode是:“.super Lpkgprefix/wedge/android/app/Activity;”.
假设用户现在已经修改了原始应用程序的Dalvik bytecode,而且准备集成新创建的包含Stub和Wedge方法的Dalvik bytecode,用户需要做的是将其集成在一起.Apktool这个工具可以帮助创建apk文件.命令格式为“apk d FOLDER_NAME”.成功执行后,一个新的apk文件会被创建在apktoolsimpleAppdist中.这是个未被签名验证的apk文件.
如果用户想要安装未被签名验证的apk到安卓系统设备上时,用户会遇到证书验证失败的错误.笔者推荐使用signapk.jar[4]来签名验证安卓的 apk文件.命令格式为:“java -jar signapk.jar certificate.pem key.pk8 AppName - unsigned.apkAppName - signed.apk”.
概括来说,Stub和Wedge方法拦截了所有需要传递给目标方法的参数.I-ARM-Droid框架的使用者可以充分利用这些被传递的信息,以方便其监听及进行安全策略的制定.
读者可能对为什么要在不同的包中创建Stub和Wedge方法,而不是在行内添加自定义的行为感到疑惑.这是因为,把Stub和Wedge方法放在不同包里,开发者不仅可以减少代码量并且只需要对新的方法进行单次添加.换言之,它可以减少代码冗余,并使修改后的代码易于管理.
除了上面的疑问,读者可能仍然在想为什么不使用javabytecode对Stub和Wedge方法进行植入?并且为什么要选择用Dalvik bytecode进行植入?这是由于大多数可用的编译工具,例如dex2jar[5],可以将Dalvik bytecode转化成Java字节码,而不能在保证不会出错的情况下将javabytecode转化成Dalvik bytecode.
创建Stub和Wedge方法需要开发者有安卓系统平台的开发经验.虽然I-ARM-Droid的框架不会改变安卓系统的中间件层,开发者无法识别哪些目标方法是对安全性敏感的,留给开发者唯一的选择就是向有经验的软件公司寻求帮助.很显然,这样一来开发者就需要为安全性而支付额外的费用.如果开发者可以自行配置安卓系统应用的安全性能就会相对来说好得多.
此外,即使开发者有足够的开发经验,如何识别目标方法仍然不容易.识别目标方法是基于在整个应用程序的源代码上.同时,开发者也不能保证在对源代码分析的时候可以发现所有潜在的危险的目标方法.
更重要的是,注入Stub和Wedge方法要求开发者对原程序代码的结构进行修改.例如,要注入Wedge方法时,开发者需要修改原来子类来继承包含Wedge方法的类.若是对包含Wedge方法的类没有进行足够的测试,则不能保证修改之后程序可以正常地运行,很有可能会有未知异常发生.
I-ARM-Droid的框架是一个“便携式”的安卓安全扩展方式.由于I-ARM-Droid框架是集中在应用层上,一个很明显的好处是它并没有触及到在应用层之下的安卓系统中间件或Linux内核.若修改涉及到安卓中间件或Linux内核,就需要改变整个安卓系统平台的代码.在大多数情况下,这比修改应用程序代码要复杂得多.而且,通常情况下需要获取安卓系统设备上的root权限,这样会使得安卓系统本身的安全权限设置失效.截然相反的是,使用I-ARM-Droid架构,用户只需要关心对应用层的修改.
此外,注入stub和wedge API之后,对Android的应用程序性能上的影响也很小.
为了验证性能,笔者尝试使用 stub方法来拦截对“java.lang.StringBuilder.append(String str)”函数的调用.因为它是一个实例方法,笔者创建另一个静态方法“pkgprefix.java.lang.StringBuilder.append(java.lang.StringBuilder sb,Stringstr)”.图5 显示了新创建的 stub 方法.
Stub 方法“pkgprefix.java.lang.StringBuilder.append(java.lang.StringBuilder sb,String str)”
在这个例子里,笔者调用目标方法“java.lang.StringBuilder.append(String str)”10 000次.原始的调用时间为69μs.在注入新建的stub方法之后,调用时间增加到76μs.这说明平均每次调用增长的时间是0.000 7μs.图6中,Logcat显示了注入 stub方法前后各自所用的调用时间(“pkgprefix.java.lang.StringBuilder.append(java.lang.StringBuilder sb,String str)”).
图6 Logcat显示注入stub方法前后各自所用的调用时间
最后,重用性是I-ARM-Droid的框架中的一个重要特征.例如,本文中对Stub和Wedge方法的设计,可以重复使用在任何其它的安卓应用程序中.因为无论是静态的、实例或构造目标方法,都是由Android SDK中或Java SDK所提供的公共方法.在任何安卓系统应用程序中,用户都可以重复使用相同的Stub和Wedge方法来拦截相应的目标方法.
本文通过改进一个简单安卓系统应用程序并且展示了部分源代码和所需要的工具介绍分析了IARM-Droid框架.在文中,笔者选择了一些简单的方法作为目标,并创建Stub和Wedge方法,展示了修改Dalvik bytecode反编译的过程.总体来说,由于所有的Stub和Wedge方法可以拦截一切要被传递给目标方法的参数,用户可以充分利用这一优势,对目标方法加入精细度的控制.
[1]Enck William,Damien Octeau,Patrick McDaniel,et al.A Study of Android Application Security[J].USENIX security symposium,2011,2.
[2]Davis B,Sanders B,Khodaverdia A,et al.I-ARM-Droid:A rewriting framework for in - app referencemonitors for android applications[C]//In IEEE Mobile Security Technologies(MoST),San Francisco,CA,2012.
[3]Hornyack P,Han S,Jung J,et al.These aren’t the droids you’re looking for:retrofitting android to protect data from imperious applications[C]//Proceedings of the 18th ACM conference on Computer and communications security.ACM,2011.
[4]Stavrou,Angelos,Ryan Johnson,etal.Programming on Android:Best Practices for Security and Reliability[C]//Software Security and Reliability(SERE).IE 7th International Conference,2013:1 -2.
[5]Rhee,Keunwoo,Hawon Kim.Security Test Methodology for an Agent of a Mobile Device Management System[J].International Journal of Security & Its Applications,2012,6:2.