继续

在《二》详细介绍了通过ptrace实现注入的技术方案,在这个章节里,我再介绍一种Android上特有的注入技术,我命其名为——Component Injection。顾名思义,这种方式是跟Android的组件相关的,详细见下面叙述。

Component Injection

原理

在android的开发者文档里,对android:process的描述是这样的:
android:process

The name of a process where all components of the application should run. Each component can override this default by setting its own processattribute.
By default, Android creates a process for an application when the first of its components needs to run. All components then run in that process. The name of the default process matches the package name set by the <manifest>element.

By setting this attribute to a process name that's shared with another application, you can arrange for components of both applications to run in the same process — but only if the two applications also share a user ID and be signed with the same certificate.

If the name assigned to this attribute begins with a colon (':'), a new process, private to the application, is created when it's needed. If the process name begins with a lowercase character, a global process of that name is created. A global process can be shared with other applications, reducing resource usage.

从描述上可以发现,当两个应用,它们签名同样且具备相同的shareduserID,它们之间只有一个组件的android:process是相同的,那么这两个组件之间的互动可以发生在同一个进程里。这里所说的同一个进程,其实就是进程注入的效果的了。

示例二

示例二同样包含两部分代码,分别是com.demo.host和com.demo.inject,它们的代码都非常简单,如下所示:

com.demo.host

先看看host的manifest.xml的配置
<manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.demo.host"    android:sharedUserId="com.demo"    android:versionCode="1"    android:versionName="1.0" >    <application        android:name=".DemoApplication"        android:allowBackup="true"        android:icon="@drawable/ic_launcher"        android:label="@string/app_name"        android:process="com.demo"        android:theme="@style/AppTheme" >        <activity android:name=".MainActivity" >            <intent-filter>                <action android:name="android.intent.action.MAIN" />                <category android:name="android.intent.category.LAUNCHER" />            </intent-filter>        </activity>    </application>    <uses-sdk        android:minSdkVersion="8"        android:targetSdkVersion="9" /></manifest>
关键代码
package com.demo.host;import android.app.Activity;import android.content.ContentResolver;import android.net.Uri;import android.os.Bundle;import android.util.Log;/** *  * @author boyliang *  */public final class MainActivity extends Activity {private static int sA = 1;public static void setA(int a) {sA = a;}public static int getA() {return sA;}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);ContentResolver resolver = getContentResolver();Uri uri = Uri.parse("content://demo_contentprovider");resolver.query(uri, null, null, null, null);new Thread() {public void run() {while (true) {Log.i("TTT", "" + getA());setA(getA() + 1);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}};}.start();}}
host一启动,就马上调用ContentResolver的query,这个正是Inject里的ContentProvider组件。

com.demo.inject

manifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.demo.inject"    android:sharedUserId="com.demo"    android:versionCode="1"    android:versionName="1.0" >    <application        android:allowBackup="true"        android:icon="@drawable/ic_launcher"        android:label="@string/app_name"        android:process="com.demo"        android:theme="@style/AppTheme" >                <provider            android:name=".DemoContentProvider"            android:authorities="demo_contentprovider"            android:exported="false" />            </application>        <uses-sdk        android:minSdkVersion="8"        android:targetSdkVersion="9" /></manifest>
关键代码
<span style="white-space:pre"></span>@Overridepublic Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, String arg4) {final Timer timer = new Timer("demo");timer.schedule(new TimerTask() {@Overridepublic void run() {try {Log.i("TTT", ">>>>>>>>>>>>>I am in, I am a bad boy!!!!<<<<<<<<<<<<<<\n");//Class<?> MainActivity_class = Class.forName("com.demo.host.MainActivity");Context context = ContexHunter.getContext();ClassLoader classloader = context.getClass().getClassLoader();Class<?> MainActivity_class = classloader.loadClass("com.demo.host.MainActivity");Method setA_method = MainActivity_class.getDeclaredMethod("setA", int.class);setA_method.invoke(null, 998);} catch (Exception e) {e.printStackTrace();}timer.cancel();}}, 5000);return null;}
inject中,当query被调用后,会等待5s,然后通过反射调用host的MainActivity.setA方法,修改打印的数值。

绕过ClassLoader双亲委托

细心的朋友会发现,inject的代码中,获取MainActivity的Class,并不是直接通过Class.forName("com.demo.host.MainActivity")获取到,而是先获取到全局Context(即Application对象),然后再调用其ClassLoader来间接获取得的,为什么要这样呢?我我们知道,Java中每个class都是通过双亲委托机制加载的,这方面的内容可以参考http://blog.csdn.net/xyang81/article/details/7292380,下面我画出示意图:


当我们尝试在DemoContentProvider通过Class.forNmae寻找MainActivity时,必然会抛ClassNotFoundException。唯一可行的方案是找到host的PathClassLoader,然后通过这个ClassLoader寻找MainActivity。我们需要寻找的变量需要满足如下条件:
  • 这个变量必须由host产生的;
  • 这个变量必须是全局的,而且其引用会保存在BootClassLoader(也就是Android SDK中的某个引用);
  • 可以通过反射机制读取到;
很自然的,想到了host的Application对象。通过阅读源码,发现可以通过下面的方式读取到Application对象:
  • 如果是System_Process,可以通过如下方式获取
Context context = ActivityThread.mSystemContext
  • 如果是非System_Process(即普通的Android进程),可以通过如下方式获取
Context context = ((ApplicationThread)RuntimeInit.getApplicationObject()).app_obj.this$0

输出

理解了上述的原理之后,我们再看看示例的输出:
I/TTT     (  633): com.demo.inject starts.I/TTT     (  633): com.demo.host startsI/TTT     (  633): 1I/TTT     (  633): 2I/TTT     (  633): 3I/TTT     (  633): 4I/TTT     (  633): 5I/TTT     (  633): >>>>>>>>>>>>>I am in, I am a bad boy!!!!<<<<<<<<<<<<<<I/TTT     (  633): 998I/TTT     (  633): 999I/TTT     (  633): 1000I/TTT     (  633): 1001I/TTT     (  633): 1002I/TTT     (  633): 1003
从前二行就可以看出,这两个组件都是运行在同一个进程的。从第5秒开始,打印的数据开始发生变化,证明我们的注入逻辑生效了。 文中的示例代码,大家可以到https://github.com/boyliang/Component_Injection下载

最后

ComponentInjection的好处是不需要ROOT权限,但其使用限制也非常多。但如果跟MaskterKey漏洞结合起来用,那效果还是相当惊艳的。我们知道,Zygote进程会接收来自system_process的命令,其中比较关键的信息有uid, gid, gids, classpath, runtime-init等等,这些信息是决定了Zygote子进程的加载容器以及所从属的uid。
通过MasterKey漏洞我们可以伪造系统的Setting包,Setting与system_process的配置正好符合我所说的ComponentInjection条件,因此利用这种方式,可以注入到system_process进程,进而控制传递给Zygote的参数。其中classpath和runtime-init是加载容器的配置,classpath是指向一个dex文件的路径,runtime-init是其main函数所在的类名,通过指定每个App的加载容器,就可以很巧妙的控制了所有普通用户的进程的环境。
LBE 曾经就是利用这种技术实现主动防御的,更详细的介绍可访问http://safe.baidu.com/2013-10/lbe-root.html,不过这个文章分析得并不到位,最关键的环节即ComponentInjection并没有提及,结合的我分享,算是做一个完美的补充吧。
这一章节里,介绍了一种Android特有的注入技术,通过一些小技巧绕过了Java的双父委托机制。而且找到了可以轻松找到Application对象的方法,这个对象在Android开发中可以是至关重要的。在接下来的 《四》里,我会详细介绍如何利用JNI获取JNIEnv指针,再通过JNI找到DexCloassLoader加载DEX文件。

更多相关文章

  1. Android(安卓)动态加载(五) - 借尸还魂之代理Activity模式
  2. 【Android】Android取消EditText自动获取焦点
  3. struts2服务端与android交互
  4. android检测当前网络是否可用
  5. Android的ps命令介绍和技巧
  6. Android进程 与 消息模型
  7. Android的ps命令介绍和技巧
  8. Android:关于声明文件中android:process属性说明
  9. AndroidUI线程机制

随机推荐

  1. android ImageView.getDrawingCache retu
  2. android开发环境的发展演变
  3. web页面(HTML5)实现发送短信的功能
  4. 安卓009SQLite数据库
  5. android2.1之后appwidget的一些问题
  6. ndk初体验
  7. Android(安卓)Tasks and Back Stack
  8. 在新建android工程的时候出现This templa
  9. Android(安卓)Studio的使用小贴士
  10. Failed to finalize session : INSTALL_F