BlackDex大法,是如何运作的?:启动篇(二)

Android·BlackDex · 2022-08-10 · 7916 人浏览

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;
        }
    }

两步事情:

  1. 将软件安装至BlackBox
  2. 如果安装成功则启动,不成功则返回失败

内部实现原理涉及虚拟化相关知识,此处不多深究,应用启动时,程序感知到的第一个方法就是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);
        }
    }

其实从上述代码分析下来,整体的流程已经非常的顺畅了,汇总整理一下

  1. 使用虚拟化技术将应用运行起来,初始化Application。
  2. 一般来说,基本上99%的加固整体工作在Application已经完成,所以此时应用真实的Dex文件已经被解密释放并且加载到内存中。
  3. 在启动成功后,调用handleDumpDex核心方法进行dex的dump工作,即可达到脱壳的目的。

本系列将慢慢梳理每一个流程,本篇讲了BlackDex是如何启动且执行脱壳的,剩下的在后续的文章內继续深扒。

Android 脱壳 逆向 BlackDex
  1. Quincy 2022-08-14

    支持牛奶君!

Theme Jasmine by Kent Liao