先锋军Android注射技术《三》

继续

于《两》通过专门出台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 process attribute.
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,它们的代码都很easy,例如以下所看到的:

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;
	}

	@Override
	protected 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>@Override
	public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, String arg4) {

		final Timer timer = new Timer("demo");
		timer.schedule(new TimerTask() {

			@Override
			public void run() {
				try {
					Log.i("TTT", ">>>>>>>>>>>>>I am in, I am a bad boy!!!!<<<<<<<<<<<<<<
");
					//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 starts
I/TTT     (  633): 1
I/TTT     (  633): 2
I/TTT     (  633): 3
I/TTT     (  633): 4
I/TTT     (  633): 5
I/TTT     (  633): >>>>>>>>>>>>>I am in, I am a bad boy!!!!<<<<<<<<<<<<<<
I/TTT     (  633): 998
I/TTT     (  633): 999
I/TTT     (  633): 1000
I/TTT     (  633): 1001
I/TTT     (  633): 1002
I/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档。


版权声明:本文博主原创文章,博客,未经同意不得转载。

原文地址:https://www.cnblogs.com/zfyouxi/p/4828482.html