一、背景
为了更好的防止Apk被反编译,将Apk加密后打入宿主Apk包中,宿主启动时,解密Apk然后启动Apk。
二、工作原理
通过反射的方法替换ClassLoader及资源路径,使得启动的都是加固的Apk。
三、启动过程分析
加固的Apk大致的启动过程是这样的:
宿主Apk启动 -> 宿主Application中解密Apk -> 替换ClassLoader -> 替换资源路径 -> 替换Application对象(若APk中存在的话)
3.1 解密Apk
此步骤只是将asset下的加密的Apk文件拷贝到sd卡上,拷贝过程中同时解密。
3.2 替换ClassLoader
ActivityThread类里有个成员变量mPackages,mPackages存储的是LoadedApk对象,LoadedApk类的成员变量mClassLoader就是apk包类加载器,也就是要替换的ClassLoader。
3.2.1 LoadedApk创建过程
程序启动的时候,ActivitThread在handleBindApplication函数中创建Application对象。下面贴出关键代码
private void handleBindApplication(AppBindData data) {
...
// 创建LoadedApk对象
data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
...
//调用LoadedApk的makeApplication方法创建Application对象
Application app = data.info.
makeApplication(data.restrictedBackupMode, null);
mInitialApplication = app;
...
// 调用Application的onCreate方法,至此Application对象创建完毕
try {
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
if (!mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
}
}
}
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
CompatibilityInfo compatInfo) {
return getPackageInfo(ai, compatInfo, null, false, true, false);
}
private LoadedApk getPackageInfo(ApplicationInfo aInfo,
CompatibilityInfo compatInfo,
ClassLoader baseLoader, boolean securityViolation,
boolean includeCode, boolean registerPackage) {
WeakReference<LoadedApk> ref;
if (differentUser) {
// Caching not supported across users
ref = null;
} else if (includeCode) {
ref = mPackages.get(aInfo.packageName);
} else {
ref = mResourcePackages.get(aInfo.packageName);
}
// 首先从mPackages从读取
LoadedApk packageInfo = ref != null ? ref.get() : null;
// 首次启动,mPackages中是空的
if (packageInfo == null || (packageInfo.mResources != null
&& !packageInfo.mResources.getAssets().isUpToDate())){
//创建LoadedApk对象
packageInfo = new LoadedApk(this, aInfo, compatInfo,
baseLoader,securityViolation, includeCode &&
(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0,
registerPackage);
...
// 将LoadedApk对象保存到mPackages中
if (differentUser) {
// Caching not supported across users
} else if (includeCode) {
mPackages.put(aInfo.packageName,
new WeakReference<LoadedApk>(packageInfo));
} else {
mResourcePackages.put(aInfo.packageName,
new WeakReference<LoadedApk>(packageInfo));
}
}
return packageInfo;
}
至此LoadedApk对象创建完毕。
3.2.2 ClassLoader创建过程
在LoadedApk对象创建的时候,并没有创建ClassLoader,看下LoadedApk构造函数。
public LoadedApk(ActivityThread activityThread,
ApplicationInfo aInfo,CompatibilityInfo compatInfo,
ClassLoader baseLoader,boolean securityViolation,
boolean includeCode, boolean registerPackage) {
final int myUid = Process.myUid();
aInfo = adjustNativeLibraryPaths(aInfo);
mActivityThread = activityThread;
mApplicationInfo = aInfo;
mPackageName = aInfo.packageName;
mAppDir = aInfo.sourceDir;
mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir;
mSplitAppDirs = aInfo.splitSourceDirs;
mSplitResDirs = aInfo.uid == myUid ? aInfo.splitSourceDirs : aInfo.splitPublicSourceDirs;
mOverlayDirs = aInfo.resourceDirs;
mSharedLibraries = aInfo.sharedLibraryFiles;
mDataDir = aInfo.dataDir;
mDataDirFile = mDataDir != null ? new File(mDataDir) : null;
mLibDir = aInfo.nativeLibraryDir;
mBaseClassLoader = baseLoader;
mSecurityViolation = securityViolation;
mIncludeCode = includeCode;
mRegisterPackage = registerPackage;
mDisplayAdjustments.setCompatibilityInfo(compatInfo);
}
那ClassLoader对象是什么时候创建的呢?从LoadedApk源码中,看到ClassLoader对象是在getClassLoader方法中创建的。
public ClassLoader getClassLoader() {
if (mIncludeCode && !mPackageName.equals("android")) {
// apk和lib路径
final List<String> zipPaths = new ArrayList<>();
final List<String&g