Android 加壳应用运行流程 与 生命周期类处理方案

版权归作者所有,如有转发,请注明文章出处:cyrus-studio.github.io/blog/

DexClassLoader

DexClassLoader 可以加载任意路径下的 dex,或者 jar、apk、zip 文件(包含classes.dex)。常用于插件化、热修复以及 dex 加壳。

源码如下:

scala 复制代码
public class DexClassLoader extends BaseDexClassLoader {

  public DexClassLoader(String dexPath, String optimizedDirectory,
          String librarySearchPath, ClassLoader parent) {
      super(dexPath, null, librarySearchPath, parent);
  }
}

aospxref.com/android-10....

参数说明

参数 类型 说明
dexPath String 需要加载的 dex、apk、jar、zip 文件的路径,多个路径用 : 分隔。支持任意目录下的文件。
optimizedDirectory String 用于存放优化后的 dex 文件(即 .odex),在 API level 26 已弃用(Android 8.0 Oreo)。
librarySearchPath String 指定本地库(native library,.so 文件)搜索路径,多个路径用 : 分隔。
parent ClassLoader 父类加载器,用于实现类加载的委托机制。

动态加载

动态加载 = 运行时按需加载代码或资源

动态加载 是实现 dex加壳、插件化、热更新、热修复 的基础。比如阿里的 AndFix 、腾讯 tinker、美团 Robust 等热修复框架的基础。

使用 DexClassLoader 加载一个外部 .dex 或 .apk 文件,然后反射调用里面的类和方法。

步骤如下:

  1. 准备好外部的 dex / apk / jar;

  2. 将它放在你 app 可以访问的路径(如 /data/data/包名/files/);

  3. 用 DexClassLoader 加载它;

  4. 使用反射调用其中的类和方法。

1. 插件工程示例

插件工程主要包含这两个类:

PluginActivity 源码:使用 Jetpack Compose 创建一个白色背景、居中显示文本的界面,文本内容为 "PluginActivity from plugin" 加上 ClassLoader 信息

kotlin 复制代码
package com.cyrus.example.plugin

import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

class PluginActivity : ComponentActivity() {

    private val TAG = "PluginActivity"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d(TAG, "onCreate")

        val classLoaderInfo = this.javaClass.classLoader.toString()

        setContent {
            PluginActivityContent(classLoaderInfo)
        }
    }


    @Composable
    fun PluginActivityContent(classLoaderInfo: String) {
        Box(
            modifier = Modifier
                .fillMaxSize()
                .background(Color.White)
                .padding(horizontal = 16.dp),
            contentAlignment = Alignment.Center
        ) {
            Text(
                text = "PluginActivity from plugin\n\n$classLoaderInfo",
                fontSize = 18.sp,
                color = Color.Black
            )
        }
    }

    override fun onStart() {
        super.onStart()
        Log.d(TAG, "onStart")
    }

    override fun onResume() {
        super.onResume()
        Log.d(TAG, "onResume")
    }

    override fun onPause() {
        super.onPause()
        Log.d(TAG, "onPause")
    }

    override fun onStop() {
        super.onStop()
        Log.d(TAG, "onStop")
    }

    override fun onRestart() {
        super.onRestart()
        Log.d(TAG, "onRestart")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "onDestroy")
    }
    
}

PluginClass 源码:

kotlin 复制代码
package com.cyrus.example.plugin

class PluginClass {

    fun getString(): String {
        return "String from plugin."
    }

}

编译 apk

把 apk 推送到设备 sdcard

bash 复制代码
adb push plugin-debug.apk /sdcard/Android/data/com.cyrus.example/files

2. 动态加载示例

创建 DexClassLoader 实例,加载指定路径下的 APK/DEX 文件

less 复制代码
val apkPath = "/sdcard/Android/data/com.cyrus.example/files/plugin-debug.apk"

// 创建 DexClassLoader 加载 sdcard 上的 apk
val classLoader = DexClassLoader(
    apkPath,
    null,
    this@ClassLoaderActivity.packageResourcePath,
    context.classLoader // parent 设为当前 context 的类加载器
)

调用示例:

ini 复制代码
// classLoader 加载 com.cyrus.example.plugin.PluginClass 类并通过反射调用 getString 方法
val pluginClass = classLoader.loadClass("com.cyrus.example.plugin.PluginClass")
val constructor = pluginClass.getDeclaredConstructor()
constructor.isAccessible = true
val instance = constructor.newInstance()
val method = pluginClass.getDeclaredMethod("getString")
method.isAccessible = true
val result = method.invoke(instance) as? String

output = "动态加载:${pluginPath}\n\ncall ${method}\n\nreuslt=${result}"

效果如下,可以看到正常调用了 apk 中 PluginClass 的 getString 方法并拿到了返回值

组件类 ClassNotFoundException

在 AndroidManifest.xml 中声明 PluginActivity

ini 复制代码
<activity
    android:name="com.cyrus.example.plugin.PluginActivity"
    android:exported="true">
</activity>

通过自定义 ClassLoader 加载 PluginActivity 类并启动

less 复制代码
// 通过 classLoader 加载 PluginActivity 类并启动
val pluginActivityClass = classLoader.loadClass("com.cyrus.example.plugin.PluginActivity")
val intent = Intent(this@ClassLoaderActivity, pluginActivityClass)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
this@ClassLoaderActivity.startActivity(intent)

报错如下:

less 复制代码
java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.cyrus.example/com.cyrus.example.plugin.PluginActivity}: java.lang.ClassNotFoundException: Didn't find class "com.cyrus.example.plugin.PluginActivity" on path: DexPathList[[zip file "/data/app/com.cyrus.example-MZoMs5LmgjwUZ_FiJ-u0fQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.cyrus.example-MZoMs5LmgjwUZ_FiJ-u0fQ==/lib/arm64, /data/app/com.cyrus.example-MZoMs5LmgjwUZ_FiJ-u0fQ==/base.apk!/lib/arm64-v8a, /system/lib64, /system/product/lib64]]
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3194)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:491)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:940)
Caused by: java.lang.ClassNotFoundException: Didn't find class "com.cyrus.example.plugin.PluginActivity" on path: DexPathList[[zip file "/data/app/com.cyrus.example-MZoMs5LmgjwUZ_FiJ-u0fQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.cyrus.example-MZoMs5LmgjwUZ_FiJ-u0fQ==/lib/arm64, /data/app/com.cyrus.example-MZoMs5LmgjwUZ_FiJ-u0fQ==/base.apk!/lib/arm64-v8a, /system/lib64, /system/product/lib64]]
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:196)
at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
at android.app.AppComponentFactory.instantiateActivity(AppComponentFactory.java:95)
at androidx.core.app.CoreComponentFactory.instantiateActivity(CoreComponentFactory.java:44)
at android.app.Instrumentation.newActivity(Instrumentation.java:1250)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3182)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409) 
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83) 
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016) 
at android.os.Handler.dispatchMessage(Handler.java:107) 
at android.os.Looper.loop(Looper.java:214) 
at android.app.ActivityThread.main(ActivityThread.java:7356) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:491) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:940) 

断点调试看看,PluginActivity 类是正常加载了的

原因是:

  • 动态加载的 dex 不具有生命周期特征,APP 中的 Activity 、Service 等组件无法正常工作,只能完成一般函数的调用;

  • 需要对 ClassLoader 进行修正,APP 才能够正常运行。

生命周期类处理

DexClassLoader 加载的类是没有组件生命周期的,也就是说即使 DexClassLoader 通过对 APK 动态加载完成了对组件类的加载,当系统启动该组件时,依然会出现加载类失败的异常。

为什么组件类被动态加载入虚拟机,但系统却出现加载类失败呢?

因为 系统在启动组件(如 Activity、Service)时,是通过 AMS → ActivityThread → Instrumentation 最终调用 Class.forName(组件类名) 来加载组件类的,而这个过程默认使用的是系统的 ClassLoader(通常是 PathClassLoader),而不是我们自定义的 DexClassLoader。

从 ClassLoader 来看,两种解决方案:

  1. 替换系统组件类加载器为我们的 DexClassLoader,同时设置 DexClassLoader 的 parent 为系统组件类加载器

  2. 打破原有的双亲关系,在系统组件类加载器和 BootClassLoader 的中间插入我们自己的 DexClassLoader 即可

或者可以对 PathClassLoader 中的 Elements 进行合并(常用于热修复框架(如 Tinker、Robust))。

相关文章:Android 下的 ClassLoader 与 双亲委派机制

方案 1:替换 ClassLoader 为 自定义ClassLoader

变化前结构(系统默认)

csharp 复制代码
[BootClassLoader]
        ↑
[PathClassLoader]    ← 原来的 ClassLoader
        ↑
     Activity、Application 加载组件类

变化后结构(方案1)

csharp 复制代码
[BootClassLoader]
        ↑
[PathClassLoader]    ← 原来的 ClassLoader
        ↑
[DexClassLoader]     ← 反射替换 LoadedApk.mClassLoader
        ↑
     Activity、Application 加载组件类

通过 反射替换掉 系统的 ClassLoader 为自定义的 ClassLoader,同时设置 parent 为 系统的 ClassLoader

示例代码:

kotlin 复制代码
private fun replaceClassLoader(context: Context): ClassLoader? {
    try {
        // 1. 创建自定义 ClassLoader 实例,加载 sdcard 上的 apk
        val classLoader = DexClassLoader(
            apkPath,
            null,
            this@ClassLoaderActivity.packageResourcePath,
            context.classLoader // 设置 parent 为 系统的 ClassLoader
        )

        // 2. 拿到 ActivityThread
        val activityThreadClass = Class.forName("android.app.ActivityThread")
        val currentActivityThread = activityThreadClass
            .getMethod("currentActivityThread")
            .invoke(null)

        // 3. 拿到 mPackages 字段: Map<String, WeakReference<LoadedApk>>
        val mPackagesField = activityThreadClass.getDeclaredField("mPackages")
        mPackagesField.isAccessible = true
        val mPackages = mPackagesField.get(currentActivityThread) as Map<*, *>

        // 4. 拿到当前包名对应的 LoadedApk 实例
        val loadedApkRef = mPackages[context.packageName] as? WeakReference<*>
            ?: throw IllegalStateException("LoadedApk not found for package: ${context.packageName}")
        val loadedApk = loadedApkRef.get()
            ?: throw IllegalStateException("LoadedApk is null")

        // 5. 替换 LoadedApk.mClassLoader
        val loadedApkClass = loadedApk.javaClass
        val mClassLoaderField = loadedApkClass.getDeclaredField("mClassLoader")
        mClassLoaderField.isAccessible = true
        mClassLoaderField.set(loadedApk, classLoader)

        // ✅ 替换成功
        Log.d(TAG, "✅ ClassLoader has been replaced successfully!")

        return classLoader

    } catch (e: Exception) {
        e.printStackTrace()
        Log.d(TAG, "❌ Failed to replace ClassLoader: ${e.message}")
    }

    return null
}
  • Android 每个 App 都有一个 LoadedApk 对象负责管理 dex 加载;

  • mClassLoader 是决定类加载的实际执行者;

  • 修改它就等于修改了整个 App 的"类查找逻辑"。

调用示例:

kotlin 复制代码
// 方案 1:替换 ClassLoader 为 自定义ClassLoader
val classLoader = replaceClassLoader(context)

if (classLoader == null) {
    Log.d(TAG, "❌ Failed to replace ClassLoader")
    return@Button
}

// classLoader 加载 com.cyrus.example.plugin.PluginClass 类并通过反射调用 getString 方法
val pluginClass = classLoader.loadClass("com.cyrus.example.plugin.PluginClass")
val constructor = pluginClass.getDeclaredConstructor()
constructor.isAccessible = true
val instance = constructor.newInstance()
val method = pluginClass.getDeclaredMethod("getString")
method.isAccessible = true
val result = method.invoke(instance) as? String

// 通过 classLoader 加载 PluginActivity 类并启动
val pluginActivityClass = classLoader.loadClass("com.cyrus.example.plugin.PluginActivity")
val intent = Intent(this@ClassLoaderActivity, pluginActivityClass)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
this@ClassLoaderActivity.startActivity(intent)

output = "动态加载:${apkPath}\n\ncall ${method}\n\nreuslt=${result}"

成功启动 PluginActivity,而且可以看到 ClassLoader 是自定义的 DexClassLoader

日志输出如下,生命周期相关方法也是正常执行

方案 2:插入中间 ClassLoader / 打破双亲委派

变化前结构

csharp 复制代码
[BootClassLoader]
        ↑
[PathClassLoader]   ← 原始 ClassLoader

变化后结构(方案2)

csharp 复制代码
[BootClassLoader]
        ↑
[DexClassLoader]     ← 反射插入自定义的 DexClassLoader
        ↑
[PathClassLoader]    ← 原始 ClassLoader

在 BootClassLoader 和 PathClassLoader 中间 通过反射插入 自定义的 DexClassLoader

通过反射修改 PathClassLoader 的 parent 为 自定义ClassLoader,并设置 自定义ClassLoader 的 parent 为 BootClassLoader

目标结构如下:

  • PathClassLoader.parent = 自定义ClassLoader

  • 自定义ClassLoader.parent = BootClassLoader

通过反射获取并修改 PathClassLoader 的 parent 字段

kotlin 复制代码
private fun injectClassLoader(context: Context): DexClassLoader? {
    try {
        // 拿到当前 PathClassLoader
        val appClassLoader = context.classLoader
        val pathClassLoaderClass = ClassLoader::class.java

        // 反射访问 parent 字段
        val parentField = pathClassLoaderClass.getDeclaredField("parent")
        parentField.isAccessible = true

        val bootClassLoader = ClassLoader.getSystemClassLoader().parent

        // 自定义ClassLoader.parent = BootClassLoader
        val classLoader = DexClassLoader(
            apkPath,
            null,
            this@ClassLoaderActivity.packageResourcePath,
            bootClassLoader // 设置 parent 为 BootClassLoader
        )

        // PathClassLoader.parent = 自定义ClassLoader
        parentField.set(appClassLoader, classLoader)

        Log.d(TAG, "✅ 成功将 ${classLoader} 注入到 PathClassLoader.parent")

        return classLoader

    } catch (e: Exception) {
        e.printStackTrace()
        Log.d(TAG, "❌ 注入失败:${e.message}")
    }

    return null
}

调用示例:

kotlin 复制代码
// 方案 2:插入中间 ClassLoader / 打破双亲委派
val classLoader = injectClassLoader(context)

if (classLoader == null) {
    Log.d(TAG, "❌ Failed to replace ClassLoader")
    return@Button
}

// classLoader 加载 com.cyrus.example.plugin.PluginClass 类并通过反射调用 getString 方法
val pluginClass = classLoader.loadClass("com.cyrus.example.plugin.PluginClass")
val constructor = pluginClass.getDeclaredConstructor()
constructor.isAccessible = true
val instance = constructor.newInstance()
val method = pluginClass.getDeclaredMethod("getString")
method.isAccessible = true
val result = method.invoke(instance) as? String

// 通过 classLoader 加载 PluginActivity 类并启动
val pluginActivityClass = classLoader.loadClass("com.cyrus.example.plugin.PluginActivity")
val intent = Intent(this@ClassLoaderActivity, pluginActivityClass)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
this@ClassLoaderActivity.startActivity(intent)

output = "动态加载:${apkPath}\n\ncall ${method}\n\nreuslt=${result}"

成功启动 PluginActivity,而且可以看到 ClassLoader 是自定义的 DexClassLoader

日志输出如下,生命周期相关方法也是正常执行

这样你就"无缝拦截"了类加载流程,同时也保留系统 PathClassLoader 的全部能力,比完全替换更可靠 💯

加壳应用的运行流程

参考文章:

壳执行的时机必须比 app 原来的逻辑早。

在 APP启动流程中我们最终可以得出结论,app 最先获得执行权限的是 app 中声明的 Application 类中的 attachBaseContext 和 onCreate 函数。因此,壳要想完成应用中加固代码的解密以及应用执行权的交付就都是在这两个函数上做文章。

加壳应用运行流程:

scss 复制代码
→ AMS 发起启动
→ Zygote fork → ActivityThread.main
→ ActivityThread.handleBindApplication()
→ 壳Application.attachBaseContext(base)
          ↳ 解密 dex
          ↳ 自定义 ClassLoader 加载解密 dex
          ↳ 反射设置 LoadedApk.mClassLoader 为自定义 ClassLoader
→ 壳Application.onCreate()
          ↳ 反射生成真实的 Application 对象
          ↳ 反射替换 ActivityThread.mInitialApplication 为真实 Application 对象
          ↳ 已进入原始 app 世界

完整源码

开源地址:github.com/CYRUS-STUDI...

相关推荐
千里马学框架3 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台3 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone3 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
龙亘川3 天前
一网统管AI平台民生业务实践:基于城市数字底座赋能公积金业务服务升级
大数据·安全·智慧城市·开源软件·数据可视化·政务
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
kybs19913 天前
全球灾害数据分析可视化 毕业设计-附源码66794
vue.js·spring boot·mysql·安全·django·c#·asp.net
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
LorryJovens3 天前
【LAAP科研】双系统具身AGI范式研究——基于LAAP认知架构与Jev概率决策模型的系统性技术调研与范式验证
人工智能·gpt·安全·架构