Android 逆向启动 DEX 字节码中的 Activity 组件 ( 替换 LoadedApk 中的类加载器 | 加载 DEX 文件中的 Activity 类并启动成功 )

Posted 韩曙亮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 逆向启动 DEX 字节码中的 Activity 组件 ( 替换 LoadedApk 中的类加载器 | 加载 DEX 文件中的 Activity 类并启动成功 )相关的知识,希望对你有一定的参考价值。





前言



在 上一篇博客 【Android 逆向】启动 DEX 字节码中的 Activity 组件 ( DEX 文件准备 | 拷贝资源目录下的文件到内置存储区 | 配置清单文件 | 启动 DEX 文件中的组件 | 执行结果 ) 的代码基础上 , 使用类加载器加载 com.example.dex_demo.MainActivity2 组件前 , 先替换 LoadedApk 的类加载器 , 就可以成功加载 DEX 文件了 , 该操作类似于热修复 ;

    /**
     * 不修改类加载器的前提下 , 运行 Dex 字节码文件中的组件
     *
     * @param context
     * @param dexFilePath
     */
    private void startDexActivityWithoutClassLoader(Context context, String dexFilePath) 
        // 优化目录
        File optFile = new File(getFilesDir(), "opt_dex");
        // 依赖库目录 , 用于存放 so 文件
        File libFile = new File(getFilesDir(), "lib_path");

        // 初始化 DexClassLoader
        DexClassLoader dexClassLoader = new DexClassLoader(
                dexFilePath,                    // Dex 字节码文件路径
                optFile.getAbsolutePath(),      // 优化目录
                libFile.getAbsolutePath(),      // 依赖库目录
                context.getClassLoader()        // 父节点类加载器
        );

        // 加载 com.example.dex_demo.DexTest 类
        // 该类中有可执行方法 test()
        Class<?> clazz = null;
        try 
            clazz = dexClassLoader.loadClass("com.example.dex_demo.MainActivity2");
         catch (ClassNotFoundException e) 
            e.printStackTrace();
        

        // 启动 com.example.dex_demo.MainActivity2 组件
        if (clazz != null) 
            context.startActivity(new Intent(context, clazz));
        
    




一、替换 LoadedApk 中的类加载器



参考 【Android 逆向】加壳的 Android 应用启动流程 | 使用反射替换 LoadedApk 中的类加载器流程 二、使用反射替换 LoadedApk 中的类加载器流程 博客章节 , 使用反射替换 LoadedApk 的类加载器 ;


1、获取 ActivityThread 实例对象


首先 , 获取 ActivityThread 实例对象 , 该类全局单例 , 获取其类 , 就可以获取实例对象 ;

        // I. 获取 ActivityThread 实例对象


        // 获取 ActivityThread 字节码类 , 这里可以使用自定义的类加载器加载
        // 原因是 基于 双亲委派机制 , 自定义的 DexClassLoader 无法加载 , 但是其父类可以加载
        // 即使父类不可加载 , 父类的父类也可以加载
        Class<?> ActivityThreadClass = null;
        try 
            ActivityThreadClass = dexClassLoader.loadClass(
                    "android.app.ActivityThread");
         catch (ClassNotFoundException e) 
            e.printStackTrace();
        

        // 获取 ActivityThread 中的 sCurrentActivityThread 成员
        // 获取的字段如下 :
        // private static volatile ActivityThread sCurrentActivityThread;
        // 获取字段的方法如下 :
        // public static ActivityThread currentActivityThread() return sCurrentActivityThread;
        Method currentActivityThreadMethod = null;
        try 
            currentActivityThreadMethod = ActivityThreadClass.getDeclaredMethod(
                    "currentActivityThread");

            // 设置可访问性 , 所有的 方法 , 字段 反射 , 都要设置可访问性
            currentActivityThreadMethod.setAccessible(true);
         catch (NoSuchMethodException e) 
            e.printStackTrace();
        

        // 执行 ActivityThread 的 currentActivityThread() 方法 , 传入参数 null
        Object activityThreadObject = null;
        try 
            activityThreadObject = currentActivityThreadMethod.invoke(null);
         catch (IllegalAccessException e) 
            e.printStackTrace();
         catch (InvocationTargetException e) 
            e.printStackTrace();
        

2、获取 LoadedApk 实例对象


然后 , 从 ActivityThread 实例对象中 , 获取 LoadedApk 成员 ;

        // II. 获取 LoadedApk 实例对象


        // 获取 ActivityThread 实例对象的 mPackages 成员
        // final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<>();
        Field mPackagesField = null;
        try 
            mPackagesField = ActivityThreadClass.getDeclaredField("mPackages");

            // 设置可访问性 , 所有的 方法 , 字段 反射 , 都要设置可访问性
            mPackagesField.setAccessible(true);
         catch (NoSuchFieldException e) 
            e.printStackTrace();
        

        // 从 ActivityThread 实例对象 activityThreadObject 中
        // 获取 mPackages 成员
        ArrayMap mPackagesObject = null;
        try 
            mPackagesObject = (ArrayMap) mPackagesField.get(activityThreadObject);
         catch (IllegalAccessException e) 
            e.printStackTrace();
        

        // 获取 WeakReference<LoadedApk> 弱引用对象
        WeakReference weakReference = (WeakReference) mPackagesObject.get(this.getPackageName());
        // 获取 LoadedApk 实例对象
        Object loadedApkObject = weakReference.get();

3、替换 LoadedApk 实例对象中的 mClassLoader 类加载器


最后 , 替换 LoadedApk 实例对象中的 mClassLoader 类加载器 ;

        // III. 替换 LoadedApk 实例对象中的 mClassLoader 类加载器


        // 加载 android.app.LoadedApk 类
        Class LoadedApkClass = null;
        try 
            LoadedApkClass = dexClassLoader.loadClass("android.app.LoadedApk");
         catch (ClassNotFoundException e) 
            e.printStackTrace();
        

        // 通过反射获取 private ClassLoader mClassLoader; 类加载器对象
        Field mClassLoaderField = null;
        try 
            mClassLoaderField = LoadedApkClass.getDeclaredField("mClassLoader");

            // 设置可访问性
            mClassLoaderField.setAccessible(true);
         catch (NoSuchFieldException e) 
            e.printStackTrace();
        

        // 替换 mClassLoader 成员
        try 
            mClassLoaderField.set(loadedApkObject, dexClassLoader);
         catch (IllegalAccessException e) 
            e.printStackTrace();
        




二、完整代码示例



下面代码中

        // 替换 LoadedApk 中的 类加载器 ClassLoader
        // 然后使用替换的类加载器加载 DEX 字节码文件中的 Activity 组件
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) 
            startDexActivityWithoutReplacedClassLoader(this, mDexPath);
        

就是先替换 LoadedApk 中的 类加载器 ClassLoader , 然后使用替换的类加载器加载 DEX 字节码文件中的 Activity 组件 ;


完整代码示例 :

package com.example.classloader_demo;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.util.ArrayMap;
import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import dalvik.system.DexClassLoader;

public class MainActivity extends AppCompatActivity 

    private static final String TAG = "MainActivity";

    /**
     * Dex 文件路径
     */
    private String mDexPath;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 打印类加载器及父节点
        classloaderLog();

        // 拷贝 dex 文件
        mDexPath = copyFile2();
        // 测试 DEX 文件中的方法
        testDex(this, mDexPath);

        // 拷贝 dex2 文件
        //mDexPath = copyFile2();
        // 启动 DEX 中的 Activity 组件 , 此处启动会失败
        //startDexActivityWithoutClassLoader(this, mDexPath);

        // 替换 LoadedApk 中的 类加载器 ClassLoader
        // 然后使用替换的类加载器加载 DEX 字节码文件中的 Activity 组件
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) 
            startDexActivityWithoutReplacedClassLoader(this, mDexPath);
        
    

    /**
     * 打印当前的类加载器及父节点
     */
    private void classloaderLog() 
        // 获取当前 Activity 的 类加载器 ClassLoader
        ClassLoader classLoader = MainActivity.class.getClassLoader();

        // 打印当前 Activity 的 ClassLoader 类加载器
        Log.i(TAG, "MainActivity ClassLoader : " + classLoader);

        // 获取 类加载器 父类
        ClassLoader parentClassLoader = classLoader.getParent();

        // 打印当前 Activity 的 ClassLoader 类加载器 的父类
        Log.i(TAG, "MainActivity Parent ClassLoader : " + parentClassLoader);
    

    /**
     * 将 app\\src\\main\\assets\\classes.dex 文件 ,
     * 拷贝到 /data/user/0/com.example.classloader_demo/files/classes.dex 位置
     */
    private String copyFile() 
        // DEX 文件
        File dexFile = new File(getFilesDir(), "classes.dex");
        // DEX 文件路径
        String dexPath = dexFile.getAbsolutePath();

        Log.i(TAG, "开始拷贝文件 dexPath : " + dexPath);

        // 如果之前已经加载过 , 则退出
        if (dexFile.exists()) 
            Log.i(TAG, "文件已经拷贝 , 退出");
            return dexPath;
        

        try 
            InputStream inputStream = getAssets().open("classes.dex");
            FileOutputStream fileOutputStream = new FileOutputStream(dexPath);

            byte[] buffer = new byte[1024 * 4];
            int readLen = 0;
            while ((readLen = inputStream.read(buffer)) != -1) 
                fileOutputStream.write(buffer, 0, readLen);
            

            inputStream.close();
            fileOutputStream.close();

         catch (IOException e) 
            e.printStackTrace();
         finally 
            Log.i("HSL", "classes.dex 文件拷贝完毕");
        
        return dexPath;
    

    /**
     * 将 app\\src\\main\\assets\\classes2.dex 文件 ,
     * 拷贝到 /data/user/0/com.example.classloader_demo/files/classes2.dex 位置
     */
    private String copyFile2() 
        // DEX 文件
        File dexFile = new File(getFilesDir(), "classes2.dex");
        // DEX 文件路径
        String dexPath = dexFile.getAbsolutePath();

        Log.i(TAG, "开始拷贝文件 dexPath : " + dexPath);

        // 如果之前已经加载过 , 则退出
        if (dexFile.exists()) 
            Log.i(TAG, "文件已经拷贝 , 退出");
            return dexPath;
        

        try 
            InputStream inputStream = getAssets().open("classes2.dex");
            FileOutputStream fileOutputStream = new FileOutputStream(dexPath);

            byte[] buffer = new byte[1024 * 4];
            int readLen = 0;
            while ((readLen = inputStream.read(buffer)) != -1) 
                fileOutputStream.write(buffer, 0, readLen);
            

            inputStream.close();
            fileOutputStream.close();

         catch (IOException e) 
            e.printStackTrace();
         finally 
            Log.i("HSL", "classes2.dex 文件拷贝完毕");
        
        return dexPath;
    

    /**
     * 测试调用 Dex 字节码文件中的方法
     *
     * @param context
     * @param dexFilePath
     */
    private void testDex(Context context, String dexFilePath) 
        // 优化目录
        File optFile = new File(getFilesDir(), "opt_dex");
        // 依赖库目录 , 用于存放 so 文件
        File libFile = new File(getFilesDir(), "lib_path");

        // 初始化 DexClassLoader
        DexClassLoader dexClassLoader = new DexClassLoader(
                dexFilePath,                    // Dex 字节码文件路径
                optFile.getAbsolutePath(),      // 优化目录
                libFile.getAbsolutePath(),      // 依赖库目录
                context.getClassLoader()        // 父节点类加载器
        );

        // 加载 com.example.dex_demo.DexTest 类
        // 该类中有可执行方法 test()
        Class<?> clazz = null;
        try 
            clazz = dexClassLoader.loadClass("com.example.dex_demo.DexTest");
         catch (ClassNotFoundException e) 
            e.printStackTrace();
        

        // 获取 com.example.dex_demo.DexTest 类 中的 test() 方法
        if (clazz != null) 
            try 
                // 获取 test 方法
                Method method = clazz.getDeclaredMethod("test");
                // 获取 Object 对象
                Object object = clazz.newInstance();
                // 调用 test() 方法
                method.invoke(object);
             catch (NoSuchMethodException e) 
                e.printStackTrace();
             catch (IllegalAccessException e) 
                e.printStackTrace();
             catch (InstantiationException e) 
                e.printStackTrace();
             catch (InvocationTargetException e) 
                e.printStackTrace();
            
        
    

    /**
     * 不修改类加载器的前提下 , 运行 Dex 字节码文件中的组件
     *
     * @param context
     * @param dexFilePath
     */
    private void startDexActivityWithoutClassLoader(Context context, String dexFilePath) 
        // 优化目录
        File optFile = new File(getFilesDir(), "opt_dex");
        // 依赖库目录 , 用于存放 so 文件
        File libFile = new File(getFilesDir(), "lib_path");

        // 初始化 DexClassLoader
        DexClassLoader dexClassLoader = new DexClassLoader(
                dexFilePath,                    // Dex 字节码文件路径
                optFile.getAbsolutePath(),      // 优化目录
                libFile.getAbsolutePath(),      // 依赖库目录
                context.getClassLoader()        // 父节点类加载器
        );

        // 加载 com.example.dex_demo.DexTest 类
        // 该类中有可执行方法 test()
        Class<?> clazz = null;
        try 
            clazz = dexClassLoader.loadClass("com.example.dex_demo.MainActivity2");
         catch (ClassNotFoundException e) 
            e.printStackTrace();
        

        // 启动 com.example.dex_demo.MainActivity2 组件
        if (clazz != null) 
            context.startActivity(new Intent(context, clazz));
        
    


    /**
     * 替换 LoadedApk 中的 类加载器 ClassLoader
     *
     * @param context
     * @param dexFilePath
     */
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    private void startDexActivityWithoutReplacedClassLoader(Context context, String dexFilePath) 
        // 优化目录
        File optFile Android 逆向启动 DEX 字节码中的 Activity 组件 ( 使用 DexClassLoader 获取组件类失败 | 失败原因分析 | 自定义类加载器没有加载组件类的权限 )

Android 逆向启动 DEX 字节码中的 Activity 组件 ( DEX 文件准备 | 拷贝资源目录下的文件到内置存储区 | 配置清单文件 | 启动 DEX 文件中的组件 | 执行结果 )(代

Android 逆向启动 DEX 字节码中的 Activity 组件 ( DEX 文件准备 | 拷贝资源目录下的文件到内置存储区 | 配置清单文件 | 启动 DEX 文件中的组件 | 执行结果 )(代

Android 逆向类加载器 ClassLoader ( 使用 DexClassLoader 动态加载字节码文件 | 拷贝 DEX 文件到内置存储 | 加载并执行 DEX 字节码文件 )

Android 逆向类加载器 ClassLoader ( 使用 DexClassLoader 动态加载字节码文件 | 准备 DEX 字节码文件 )

Android 逆向ART 函数抽取加壳 ② ( 禁用 dex2oat 简介 | TurboDex 中禁用 dex2oat 参考示例 )