0x1 前言
前篇 说到,BlackDex是基于虚拟化技术进行的,本项目实际上是基于 BlackBox 的基础下进行开发,很可惜的是由于某些原因此项目没能继续下去。
本系列将选定发表时最后的提交,朋友们可以Clone下来跟着本文走:https://github.com/CodingGay/BlackDex/tree/5580fa8f5d658afae4eb667f8c8d6632be5b9aaf
0x2 启动
本文主要分析如何从点击BlackDex图标之后,是如何进行启动APP的进程,此处不会过度深究虚拟化的实现过程,有机会更完这个系列之后会再出一系列虚拟化技术的文章。
核心启动dump方法:top.niunaijun.blackbox.BlackDexCore#dumpDex(java.lang.String)
public InstallResult dumpDex(String packageName) {
// 将软件安装至BlackBox中
InstallResult installResult = BlackBoxCore.get().installPackage(packageName);
if (installResult.success) {
// 安装成功则启动,否则卸载并且返回失败。
boolean b = BlackBoxCore.get().launchApk(packageName);
if (!b) {
BlackBoxCore.get().uninstallPackage(installResult.packageName);
return null;
}
return installResult;
} else {
return null;
}
}
两步事情:
- 将软件安装至BlackBox
- 如果安装成功则启动,不成功则返回失败
内部实现原理涉及虚拟化相关知识,此处不多深究,应用启动时,程序感知到的第一个方法就是Application#attachBaseContext,在虚拟环境内也是一样的,在经过一系列调度后。我们直接跟进到虚拟化进程的启动的方法:top.niunaijun.blackbox.app.BActivityThread#handleBindApplication
此函数比较长,代码内注释逐步解析
private synchronized void handleBindApplication(String packageName, String processName) {
// 初始化Dump的返回的信息
DumpResult result = new DumpResult();
result.packageName = packageName;
result.dir = new File(BlackBoxCore.get().getDexDumpDir(), packageName).getAbsolutePath();
try {
// 以下是获取需要多开应用的信息,然后对当前进程进行重新设置,因为本进程信息是宿主的。
PackageInfo packageInfo = BlackBoxCore.getBPackageManager().getPackageInfo(packageName, PackageManager.GET_PROVIDERS, BActivityThread.getUserId());
if (packageInfo == null)
return;
.........
// 以上省略部分虚拟化的代码。
// 此处清除dump目录,防止多次脱壳的dex文件乱窜
// clear dump file
FileUtils.deleteDir(new File(BlackBoxCore.get().getDexDumpDir(), packageName));
// 初始化native层代码
VMCore.init(Build.VERSION.SDK_INT);
// 启用IO重定向,支持虚拟应用运行环境
IOCore.get().enableRedirect(packageContext);
......
// 以上省略部分虚拟化代码
// 反射LoadedApk获取多开应用的classloader,并且反射LoadedApk#makeApplication函数,makeApplication中会初始化Application,调用其attachBaseContext、onCreate函数,完成Application的初始化。
try {
ClassLoader call = LoadedApk.getClassloader.call(loadedApk);
application = LoadedApk.makeApplication.call(loadedApk, false, null);
} catch (Throwable e) {
Slog.e(TAG, "Unable to makeApplication");
e.printStackTrace();
}
// 如果走到此处没有发生异常,说明Application已经完成启动,一般在这种时候从理论上来说,应用已经运行起来了,那么自然加固也已经解密完成,我们接下来进行核心的dex的dump工作。
if (Objects.equals(packageName, processName)) {
ClassLoader loader;
// 此处获取需要脱壳的app的classloader
if (application == null) {
loader = LoadedApk.getClassloader.call(loadedApk);
} else {
// 实际上走到这里,理论是启动失败了。不过还可以挣扎一下。
loader = application.getClassLoader();
}
// 调用核心DumpDex方法,进行dex的dump工作
handleDumpDex(packageName, result, loader);
}
} catch (Throwable e) {
// 如果发生异常,通知UI并且从BlackBox中卸载该应用
e.printStackTrace();
mAppConfig = null;
BlackBoxCore.getBDumpManager().noticeMonitor(result.dumpError(e.getMessage()));
BlackBoxCore.get().uninstallPackage(packageName);
}
}
其实从上述代码分析下来,整体的流程已经非常的顺畅了,汇总整理一下
- 使用虚拟化技术将应用运行起来,初始化Application。
- 一般来说,基本上99%的加固整体工作在Application已经完成,所以此时应用真实的Dex文件已经被解密释放并且加载到内存中。
- 在启动成功后,调用handleDumpDex核心方法进行dex的dump工作,即可达到脱壳的目的。
本系列将慢慢梳理每一个流程,本篇讲了BlackDex是如何启动且执行脱壳的,剩下的在后续的文章內继续深扒。
支持牛奶君!