[置顶] 进击的Android注入术《三》
16lz
2021-01-23
继续
在《二》详细介绍了通过ptrace实现注入的技术方案,在这个章节里,我再介绍一种Android上特有的注入技术,我命其名为——Component Injection。顾名思义,这种方式是跟Android的组件相关的,详细见下面叙述。Component Injection
原理
在android的开发者文档里,对android:process的描述是这样的:android:process
process
attribute. <manifest>
element. 从描述上可以发现,当两个应用,它们签名同样且具备相同的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中的某个引用);
- 可以通过反射机制读取到;
- 如果是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文件。
更多相关文章
- Android 源代码结构
- 从零开始--系统深入学习android(实践-让我们开始写代码-Android框
- Android进程 与 消息模型
- android代码实现背景切换
- [置顶] Android 各类功能效果源代码集合
- android中使用afinal一行代码显示网络图片
- 笔记 android 代码中设置Android:layout_gravity
- 代码中设置drawableright
- 2.4.10 可展开的列表组件