首页 > 代码库 > 【腾讯御安全】基于代理Application机制的Anddroid应用加壳方法

【腾讯御安全】基于代理Application机制的Anddroid应用加壳方法


         壳是指一个程序的外面再包裹上另外一段代码,保护里面的代码不被非法修改或反编译的程序。它们一般都先于程序运行,拿到控制权,然后完成它们保护软件的任务。对于Android APP来说,所有的实现逻辑都集成于DEX文件中,DEX文件是一个APP的核心所在,因此保护DEX文件被逆向或者修改尤其重要。Android应用的DEX加壳过程如下:

技术分享

        尽管上述DEX加壳过程中,关于如何对DEX进行加密或者利用其它方法对其进行处理的方法有很多,但是通过自定义DexClassLoader替换原DexClassLoader,并通过后者来加载被保护代码是所有加壳程序都必须实现的必经之路。本文重点介绍的就是通过一种代理Application的机制把原DexClassLoader替换成用自定义的DexClassLoader,并通过它来加载被保护DEX代码从而达到正常运行加壳后的Android应用。

         Android应用中有且仅有为一个Application组件,并且Application组件的生命周期从应用启动开始到应用退出结束, 与Android的整个应用的生命周期是相同的,因此通过在Application组件外面包裹一层壳来隐藏整个应用的DEX文件,是最简单且有效的一种方法。

         显然,系统的DexClassLoader已经不能用来加载被保护后的DEX文件,因此我们必须定义一个自定义的DexClassLoader来完成加载被保护后的DEX文件。所有的Class包括自定义Application组件(下文一律称为MyWrapperProxyApplication)都必须被自定义的ClassLoader所加载。为了要实现这个需求,需要在MyWrapperProxyApplication被加载之前,必须用自定义的DexClassLoader先替换掉API层的默认的DexClassLoader,否则MyWrapperProxyApplication就会被默认的DexClassLoader加载。但这会产生一个悖论,因为MyWrapperProxyApplication组件的onCreate函数是整个Android应用的入口,MyWrapperProxyApplication被加载之前,没有任何应用代码可以运行,因此替换ClassLoader无法办到。基于代理Application框架就是用来解决这类问题的。

首先我们需要修改AndroidManifest.xml文件:

         在我们这个框架里,加壳后的应用一共有两个Application对象,一个是我们自己自定义的MyWrapperProxyApplication,另外一个就是被保护后的Application对象,我们姑且称之为OriginalApplication,首先我们需要修改Android应用的AndroidManifest.xml文件对新加的Application组件进行重新定义。老的AndroidManifest.xml文件定义:

<application

android:name=".MyApplication"

android:icon="@drawable/icon"

android:label="@string/app_name" >

 

修改后AndroidManifest.xml文件定义为:

<application

android:name=".MyWrapperProxyApplication"

android:icon="@drawable/icon"

android:label="@string/app_name" >

<meta-data

android:name="ORIGINAL_APPLICATION_CLASS_NAME"

android:value="http://www.mamicode.com/.MyApplication" >

</meta-data>

        ....

         我们通常认为Application:onCreate()函数是整个应用的入口,所以我们直接在Application:onCreate函数里面生成自定义的DexClassLoader即可,但当应用注册有ContentProvider的时候,这并不正确的。ContentProvider:onCreate()调用优先于Application:onCreate()函数的调用,但是自定义的DexClassLoader也必须把ContentProvider组件也加载到内存里面,所以如果直接就在Application:onCreate()里面替换系统默认的DexClassLoader是行不通的。

        幸好,我们还有另一个方法:attachBaseContext()。Android的几个主要组件Application、Activity和Service都是ContextWrapper的子类。ContextWrapper一方面继承了Context,一方面又包含了一个Context对象(称为mBase),对Context的实现为转发给mBase对象处理。这一个听起来很绕的设计,是为了对这些组件中的Context功能做延迟初始化(delay init)的处理。其类图关系如下所示:

技术分享

ContextWrapper完成这个延迟化处理的方法就是attachBaseContext()。可以这样说,Application对象在刚刚构造完成时是“残废”的,访问所有Context的方法都会抛出NullPointerException。只有attachBaseContext()执行完后,它的功能才完整。在ContentProvider:onCreate()中,我们知道Application:onCreate()还没有运行,但已经可以使用getContext().getApplicationContext()函数获取Application对象,并访问其Context方法。显然,Android的API设计者不能允许此时获取的Application是“残废”的。结论是Application:attachBaseContext()必须要发生在ContentProvider:onCreate()之前,否则API将出现BUG;无论Android的系统版本如何变化,这一点也不能改变。

         于是,我们得到了最终我们想知道的结果:Application:attachBaseContext()最早执行,然后是ContentProvider:onCreate(),然后是Application:onCreate()。有了这个顺关系,我们就很容易解决了注册有ContentProvider的问题了,即我们在attachBaseContext()函数里面生成自定义DexClassLoader。具体代码实现如下所示:


技术分享

技术分享

在生成自定义DexClassLoader后,我们需要完成下面几部工作。

(1)加载originalApplication并生成对象

当子类的initProxyApplication()返回后,WrapperProxyApplication就要加载OriginalApplication,并创建Application对象。具体代码如下:

技术分享

(2)替换API层的所有Application引用。

即把API层所有保存的WrapperProxyApplication对象,都替换为新生成的originalApplication对象。以WrapperProxyApplication的baseContext作为起点顺藤摸瓜,可以找到所有的位置,使用反射一一换掉。注意最后一个mAllApplications是List,要换掉其内部的内容。

技术分享

(3)设置baseContext,并调用onCreate()

将控制权交给originalApplication。当然,后者会认为自己就是“正牌”的Application,后续的其它组件也都会这么认为。这正是我们要的效果。

技术分享

(4)再次对付ContentProvider

前面提到过,Android的组件Application、Activity和Service都是ContextWrapper,这个列表中并没有ContentProvider。ContentProvider不是ContextWrapper,甚至不是Context,而是内部有一个mContext变量,通过getContext()函数获取这个Context。

那么,ContentProvider:getContext()获取到的是哪一个Context?实验证明,ContentProvider:getContext()获取的Context是Application;准确的说,在基于代理的 Application框架里,是WrapperProxyApplication。这就不符合框架的语义了。那么,我们需要像其它处理其它WrapperProxyApplication引用一样,把它换成originalApplication吗?这是可行的:遍历API层的ContentProvider列表,将每一个ContentProvider中的mContext都替换为originalApplication。

但这种处理方式,会进一步增加对Android API层源代码依赖,是否必要?毕竟Android的API文档中,并没有规定ContentProvider:getContext()返回的必须是Application;如果要取得Application,正确的方式是getContext().getApplicationContext()。那么为什么getContext()就直接返回了Application对象?我们可以从ActivityThread:installProvider源代码中找到答案:

技术分享

从代码中容易看出,因为WrapperProxyApplication对象的getPackageName()函数与ContentProvider对应的包名相同,就会复用WrapperProxyApplication对象作为Context,而不会再创建一个新的packageContext。于是解决方案也很简单了,直接在WrapperProxyApplication里面重写getPackageName()并且返回空字符串即可。

技术分享

         当然,上面的工作只是完成DEX加固的第一步,所起到的作用就是让加壳后的Android应用能够正常启动起来,如果要做到隐藏DEX,并且是Android应用具备反编译,反篡改甚至是反调试的功能,还有很多工作要做。

        PS:腾讯御安全官网提供完整的DEX加固服务给开发者免费使用

(腾讯御安全原创,转载请注明来源)

 


本文出自 “11632030” 博客,请务必保留此出处http://11642030.blog.51cto.com/11632030/1898942

【腾讯御安全】基于代理Application机制的Anddroid应用加壳方法