【Android 逆向】启动 DEX 字节码中的 Activity 组件 ( 在 PathClassLoader 和 BootClassLoader 之间插入 DexClassLoader )
文章目錄
- 前言
- 一、在 PathClassLoader 和 BootClassLoader 之間插入 DexClassLoader
- 1、創建 DexClassLoader
- 2、使用 DexClassLoader 實例對象作為 PathClassLoader 的父節點
- 二、完整代碼示例
- 三、執行結果
- 四、博客資源
前言
在 上一篇博客 【Android 逆向】啟動 DEX 字節碼中的 Activity 組件 ( 替換 LoadedApk 中的類加載器 | 加載 DEX 文件中的 Activity 類并啟動成功 ) 中 , 通過 替換 LoadedApk 中的類加載器可以成功加載 DEX 字節碼文件中的 Activity 類 , 并成功啟動 Activity ;
本篇博客中嘗試使用 【Android 逆向】啟動 DEX 字節碼中的 Activity 組件 ( 使用 DexClassLoader 獲取組件類失敗 | 失敗原因分析 | 自定義類加載器沒有加載組件類的權限 ) 博客中 提出的 加載組件類的 第二種方案 ;
一、在 PathClassLoader 和 BootClassLoader 之間插入 DexClassLoader
1、創建 DexClassLoader
原來的邏輯是 PathClassLoader 用于加載組件類 , 其父節點是 BootClassLoader , 現在將 PathClassLoader 父節點設置為 DexClassLoader , DexClassLoader 父節點設置為 BootClassLoader , 相當于在 PathClassLoader 和 BootClassLoader 之間插入了一個 DexClassLoader ;
代碼示例 :
// I. 創建 DexClassLoader , 并設置其 父類節點為 BootClassLoader// 獲取 PathClassLoaderClassLoader pathClassloader = MainActivity.class.getClassLoader();// 獲取 BootClassLoaderClassLoader bootClassloader = MainActivity.class.getClassLoader().getParent();/*注意原來的邏輯是 PathClassLoader 用于加載組件類 , 其父節點是 BootClassLoader現在將 PathClassLoader 父節點設置為 DexClassLoader ,DexClassLoader 父節點設置為 BootClassLoader相當于在 PathClassLoader 和 BootClassLoader 之間插入了一個 DexClassLoader*/// 初始化 DexClassLoaderDexClassLoader dexClassLoader = new DexClassLoader(dexFilePath, // Dex 字節碼文件路徑optFile.getAbsolutePath(), // 優化目錄libFile.getAbsolutePath(), // 依賴庫目錄bootClassloader // 父節點類加載器);2、使用 DexClassLoader 實例對象作為 PathClassLoader 的父節點
首先 , 獲取 ClassLoader 的 private final ClassLoader parent; 成員 ;
然后 , 設置 PathClassLoader 的 parent 字段為 自定義的 DexClassLoader 實例對象 ;
代碼示例 :
// II. 使用 DexClassLoader 實例對象作為 PathClassLoader 的父節點// 獲取 ClassLoader 的 private final ClassLoader parent; 成員Field parentField = null;try {parentField = ClassLoader.class.getDeclaredField("parent");// 設置可訪問性parentField.setAccessible(true);} catch (NoSuchFieldException e) {e.printStackTrace();}// 設置 PathClassLoader 的 parent 字段為 自定義的 DexClassLoader 實例對象try {parentField.set(pathClassloader, dexClassLoader);} catch (IllegalAccessException e) {e.printStackTrace();}二、完整代碼示例
下面代碼中
// 在類加載器的雙親委派機制中的 PathClassLoader 和 BootClassLoader 之間// 插入 DexClassLoaderif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {startDexActivityWithInsertedClassLoader(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;@Overrideprotected 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) {//startDexActivityWithReplacedClassLoader(this, mDexPath);}// 在類加載器的雙親委派機制中的 PathClassLoader 和 BootClassLoader 之間// 插入 DexClassLoaderif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {startDexActivityWithInsertedClassLoader(this, mDexPath);}}/*** 打印當前的類加載器及父節點*/private void classloaderLog() {// 獲取當前 Activity 的 類加載器 ClassLoaderClassLoader 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");// 初始化 DexClassLoaderDexClassLoader 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");// 初始化 DexClassLoaderDexClassLoader 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 startDexActivityWithReplacedClassLoader(Context context, String dexFilePath) {// 優化目錄File optFile = new File(getFilesDir(), "opt_dex");// 依賴庫目錄 , 用于存放 so 文件File libFile = new File(getFilesDir(), "lib_path");// 初始化 DexClassLoaderDexClassLoader dexClassLoader = new DexClassLoader(dexFilePath, // Dex 字節碼文件路徑optFile.getAbsolutePath(), // 優化目錄libFile.getAbsolutePath(), // 依賴庫目錄context.getClassLoader() // 父節點類加載器);//------------------------------------------------------------------------------------------// 下面開始替換 LoadedApk 中的 ClassLoader// 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() 方法 , 傳入參數 nullObject activityThreadObject = null;try {activityThreadObject = currentActivityThreadMethod.invoke(null);} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}// 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();// 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();}//------------------------------------------------------------------------------------------// 加載 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));}}/*** 在類加載器的父類子類節點中 , 插入自定義 DexClassLoader* 基于雙親 委派機制的解決方案** @param context* @param dexFilePath*/@RequiresApi(api = Build.VERSION_CODES.KITKAT)private void startDexActivityWithInsertedClassLoader(Context context, String dexFilePath) {// 優化目錄File optFile = new File(getFilesDir(), "opt_dex");// 依賴庫目錄 , 用于存放 so 文件File libFile = new File(getFilesDir(), "lib_path");//------------------------------------------------------------------------------------------// 下面開始 在 ClassLoader 的雙親委派體系中 , 插入自定義的 DexClassLoader// I. 創建 DexClassLoader , 并設置其 父類節點為 BootClassLoader// 獲取 PathClassLoaderClassLoader pathClassloader = MainActivity.class.getClassLoader();// 獲取 BootClassLoaderClassLoader bootClassloader = MainActivity.class.getClassLoader().getParent();/*注意原來的邏輯是 PathClassLoader 用于加載組件類 , 其父節點是 BootClassLoader現在將 PathClassLoader 父節點設置為 DexClassLoader ,DexClassLoader 父節點設置為 BootClassLoader相當于在 PathClassLoader 和 BootClassLoader 之間插入了一個 DexClassLoader*/// 初始化 DexClassLoaderDexClassLoader dexClassLoader = new DexClassLoader(dexFilePath, // Dex 字節碼文件路徑optFile.getAbsolutePath(), // 優化目錄libFile.getAbsolutePath(), // 依賴庫目錄bootClassloader // 父節點類加載器);// II. 使用 DexClassLoader 實例對象作為 PathClassLoader 的父節點// 獲取 ClassLoader 的 private final ClassLoader parent; 成員Field parentField = null;try {parentField = ClassLoader.class.getDeclaredField("parent");// 設置可訪問性parentField.setAccessible(true);} catch (NoSuchFieldException e) {e.printStackTrace();}// 設置 PathClassLoader 的 parent 字段為 自定義的 DexClassLoader 實例對象try {parentField.set(pathClassloader, dexClassLoader);} catch (IllegalAccessException e) {e.printStackTrace();}//------------------------------------------------------------------------------------------// 加載 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));}}}三、執行結果
執行結果 : 參考 【錯誤記錄】Android 應用運行報錯 ( java.lang.VerifyError: Verifier rejected class androidx. | 逆向中遇到的問題 ) 博客 , 啟動 Activity 組件有報錯 , 但是使用類加載器加載 Activity 組件是成功的 ;
在 啟動 Activity 組件之前打上斷點 , 可以發現 , dexClassLoader.loadClass 操作是成功的 , 加載 Activity 組件操作是成功的 ;
// 啟動 com.example.dex_demo.MainActivity2 組件if (clazz != null) {context.startActivity(new Intent(context, clazz));}四、博客資源
GitHub 地址 : https://github.com/han1202012/ClassLoader_Demo
CSDN 下載 :
總結
以上是生活随笔為你收集整理的【Android 逆向】启动 DEX 字节码中的 Activity 组件 ( 在 PathClassLoader 和 BootClassLoader 之间插入 DexClassLoader )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【错误记录】Android 应用运行报错
- 下一篇: 【错误记录】GitHub 提交报错 (