深入理解Apk加固之Dex保护
字数 2390 2025-08-22 18:37:22
深入理解Apk加固之Dex保护
1. 预知识:Android应用启动过程
Android应用的启动过程是一个复杂的流程,理解这个过程对于掌握Dex保护技术至关重要:
-
Launcher启动阶段:
- 用户点击图标,Launcher应用程序通知ActivityManagerService开启Activity
- ActivityManagerService通过IBinder向Launcher组件发送SCHEDULE_PAUSE_ACTIVITY_TRANSACTION
- Launcher的ActivityThread处理消息,暂停Launcher组件,发送ACTIVITY_PAUSED_TRANSACTION消息给ActivityManagerService
-
进程创建阶段:
- ActivityManagerService处理Launcher消息,创建应用程序进程
- 通过zygote创建新的应用进程和新的ActivityThread
-
应用初始化阶段:
- 应用进程向ActivityManagerService发送ATTACH_APPLICATION_TRANSACTION
- ActivityManagerService初始化ProcessRecord对象
- 执行bindApplication,创建和初始化Application类
-
Activity启动阶段:
- 在启动Activity前检查Application是否创建
- 实例化Application初始化全局变量
- 通过全局变量启动Activity组件
2. Java类加载机制
2.1 类加载器类型
-
引导类加载器(Bootstrap ClassLoader):
- 加载Java核心库
- 原生代码实现,不继承自java.lang.ClassLoader
-
扩展类加载器(Extensions ClassLoader):
- 加载Java扩展库
- 在扩展库目录中查找并加载类
-
系统类加载器(System ClassLoader):
- 根据CLASSPATH加载Java类
- 通过ClassLoader.getSystemClassLoader()获取
2.2 类加载器代理模式
类加载器采用双亲委派模型:
- 子类加载器加载类时,先委托给父类加载器
- 父类加载器无法加载时,才由子类加载器加载
defineClass:真正完成类的加载工作(定义加载器)loadClass:启动类的加载过程(初始加载器)
2.3 重要特性
-
类标识:
- Java虚拟机判断类是否相同,需要类名和定义加载器都相同
- 不同类加载器加载的类是不兼容的,形成相互隔离的类空间
-
线程上下文类加载器:
- 默认使用系统类加载器
- 解决引导类加载器无法加载系统类加载器需要加载的类的问题
3. Android中的DexClassLoader
3.1 DexClassLoader工作原理
- 设置父类加载器
- 优化并加载dex文件到内存中(通过本地方法实现)
3.2 默认类加载器
Android默认使用PathClassLoader加载应用类
4. Dex保护技术原理
4.1 基本思路
- 动态加载Dex文件:防止直接反编译原始Dex
- 替换关键组件:在运行时替换系统加载的Application和类加载器
4.2 具体实现步骤
-
创建壳Application:
- 自定义一个壳Application并生成壳Dex文件
- 系统启动时先加载壳Application
-
加载原始Dex:
- 在壳Application中动态加载原始Dex文件
- 使用DexClassLoader加载原始Dex
-
替换关键对象:
- 替换ActivityThread中LoadedApk对象的mClassLoader
- 创建原始Dex文件的Application对象
- 替换ActivityThread中的关键Application引用
-
启动原始应用:
- 调用原始Application的onCreate方法
- 完成原始应用组件的启动
4.3 关键替换点
- 替换ActivityThread中mBoundApplication的mApplication属性
- 替换ActivityThread中的mInitialApplication属性
- 替换LoadedApk的className为原始Application的name
- 替换mBoundApplication里appInfo的className
- 替换mProviderMap中每个provider的mContext
- 调用原始Application的onCreate方法
5. 具体实现代码分析
5.1 类加载器替换
// 获取当前ActivityThread对象
String classActivityThread = "android.app.ActivityThread";
Object activityThread = RefInvoke.invokeStaticMethod(classActivityThread,
"currentActivityThread", new Class[]{}, new Object[]{});
// 获取当前包名和LoadedApk对象
String packageName = this.getPackageName();
Map<?,?> mPackage = (Map<?,?>)RefInvoke.getField(activityThread,
classActivityThread, "mPackages");
WeakReference<?> wr = (WeakReference<?>)mPackage.get(packageName);
// 创建DexClassLoader加载原始Dex
DexClassLoader loader = new DexClassLoader(
dexPathList.toString(),
mApp.getCacheDir().getAbsolutePath(),
nativeLib.getAbsolutePath(),
(ClassLoader)RefInvoke.getField(wr.get(), "android.app.LoadedApk", "mClassLoader"));
// 替换LoadedApk的类加载器
RefInvoke.setField(wr.get(), "android.app.LoadedApk", "mClassLoader", loader);
5.2 Application替换
// 原始Application类名
String applicationName = "com.example.RealApplication";
// 获取当前ActivityThread和LoadedApk对象
Object currentActivityThread = RefInvoke.invokeStaticMethod(
"android.app.ActivityThread", "currentActivityThread", new Class[]{}, new Object[]{});
Object mBoundApplication = RefInvoke.getField(currentActivityThread,
"android.app.ActivityThread", "mBoundApplication");
Object loadedApkInfo = RefInvoke.getField(mBoundApplication,
"android.app.ActivityThread$AppBindData", "info");
// 重置Application相关属性
RefInvoke.setField(loadedApkInfo, "android.app.LoadedApk", "mApplication", null);
// 移除旧的Application
Object mInitApplication = RefInvoke.getField(currentActivityThread,
"android.app.ActivityThread", "mInitialApplication");
List<Application> mAllApplications = (List<Application>)RefInvoke.getField(
currentActivityThread, "android.app.ActivityThread", "mAllApplications");
mAllApplications.remove(mInitApplication);
// 设置新的Application类名
((ApplicationInfo)RefInvoke.getField(loadedApkInfo,
"android.app.LoadedApk", "mApplicationInfo")).className = applicationName;
((ApplicationInfo)RefInvoke.getField(mBoundApplication,
"android.app.ActivityThread$AppBindData", "appInfo")).className = applicationName;
// 创建并设置新的Application对象
Application makeApplication = (Application)RefInvoke.invokeMethod(
loadedApkInfo, "android.app.LoadedApk", "makeApplication",
new Class[]{boolean.class, Instrumentation.class},
new Object[]{false, null});
RefInvoke.setField(currentActivityThread, "android.app.ActivityThread",
"mInitialApplication", makeApplication);
// 更新Provider的Context
Map<?,?> mProviderMap = (Map<?,?>)RefInvoke.getField(
currentActivityThread, "android.app.ActivityThread", "mProviderMap");
for(Map.Entry<?, ?> entry : mProviderMap.entrySet()) {
Object providerClientRecord = entry.getValue();
Object mLocalProvider = RefInvoke.getField(providerClientRecord,
"android.app.ActivityThread$ProviderClientRecord", "mLocalProvider");
RefInvoke.setField(mLocalProvider, "android.content.ContentProvider",
"mContext", makeApplication);
}
// 启动原始Application
makeApplication.onCreate();
6. 关键技术点总结
-
动态加载机制:
- 利用DexClassLoader动态加载加密的原始Dex
- 保持原始Dex不在APK中直接暴露
-
运行时替换:
- 通过反射替换系统关键对象
- 无缝切换壳Application和原始Application
-
组件生命周期管理:
- 正确处理Application生命周期
- 确保ContentProvider等组件使用正确的Context
-
类加载隔离:
- 维护正确的类加载器关系
- 避免类加载冲突和ClassNotFoundException
7. 参考资源
8. 实际应用注意事项
-
兼容性问题:
- 不同Android版本实现可能有差异
- 需要测试主要API版本
-
性能影响:
- 动态加载会增加启动时间
- 需要优化加载过程
-
加固强度:
- 结合其他保护措施(如Native保护、代码混淆)
- 防止内存Dump等攻击
-
反调试措施:
- 检测调试器
- 防止动态分析
通过这种Dex保护技术,可以有效防止直接反编译APK获取原始代码,提高Android应用的安全性。