Android 双开沙箱 VirtualApp 源码分析(二)

VirtualApp源码分析——(一)VA启动 VirtualApp(以下简称VA)是一个开源的Android app虚拟化引擎,它提供了一个虚拟空间,允许其他应用运行在这个虚拟空间中,实现应用多开(可重复安装)。 在VA中,host应用(VA自身)和client应用(通过VA安装的应用)具有相同的uid。 在运行时,一般会包含三个进程: io.virtualapp,主进程,负责VA的用户界面及应用管理; io.virtualapp:... 阅读详情

Android 双开沙箱 VirtualApp 源码分析(二)

VA 初始化

先看一下代码:
VirtualCore.startup

public void startup(Context context) throws Throwable {
        if (!isStartUp) {
            // 确保 MainThread
            if (Looper.myLooper() != Looper.getMainLooper()) {
                throw new IllegalStateException("VirtualCore.startup() must called in main thread.");
            }
            VASettings.STUB_CP_AUTHORITY = context.getPackageName() + "." + VASettings.STUB_DEF_AUTHORITY;
            ServiceManagerNative.SERVICE_CP_AUTH = context.getPackageName() + "." + ServiceManagerNative.SERVICE_DEF_AUTH;
            this.context = context;
            // 获取 ActivityThread 实例
            mainThread = ActivityThread.currentActivityThread.call();
            unHookPackageManager = context.getPackageManager();
            hostPkgInfo = unHookPackageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_PROVIDERS);
            detectProcessType();
            // hook 系统类
            InvocationStubManager invocationStubManager = InvocationStubManager.getInstance();
            invocationStubManager.init();
            invocationStubManager.injectAll();
            // 修复权限管理
            ContextFixer.fixContext(context);
            isStartUp = true;
            if (initLock != null) {
                initLock.open();
                initLock = null;
            }
        }
    }

整个 VA 会运行在四种进程

private enum ProcessType {
        /**
         * Server process
         */
        Server,
        /**
         * Virtual app process
         */
        VAppClient,
        /**
         * Main process
         */
        Main,
        /**
         * Child process
         */
        CHILD
    }

分别是前面提到的 VAService 进程,Client App 进程,VA 自身的 App 主进程,子进程。
这样的话,Application 就会被初始化多次,所以要在初始化的时候根据进程类型有选择的做对应的初始化工作。

InvocationStubManager.injectInternal():
主要完成对 Java 层 framework 的 Hook,将其定位到 VA 伪造 VA framework 上去。

private void injectInternal() throws Throwable {
        // VA 自身的 App 进程不需要 Hook
        if (VirtualCore.get().isMainProcess()) {
            return;
        }
        // VAService 需要 Hook AMS 和 PMS
        if (VirtualCore.get().isServerProcess()) {
            addInjector(new ActivityManagerStub());
            addInjector(new PackageManagerStub());
            return;
        }
        // Client APP 需要 Hook 整个 framework,来使其调用到 VA framework
        if (VirtualCore.get().isVAppProcess()) {
            addInjector(new LibCoreStub());
            addInjector(new ActivityManagerStub());
            addInjector(new PackageManagerStub());
            addInjector(HCallbackStub.getDefault());
            addInjector(new ISmsStub());
            addInjector(new ISubStub());
            addInjector(new DropBoxManagerStub());
            .....................

VA 初始化主要就是这些

Client App 的安装

VirtualCore.installPackage

 public InstallResult installPackage(String apkPath, int flags) {
        try {
            // 调用远程 VAService
            return getService().installPackage(apkPath, flags);
        } catch (RemoteException e) {
            return VirtualRuntime.crash(e);
        }
    }

最终调用 VAServcie 中的 VAppManagerService.installPackage():

// 安装 apk 先于 installPackageAsUser,主要目的是生成 VPackage 结构
    public synchronized InstallResult installPackage(String path, int flags, boolean notify) {
        long installTime = System.currentTimeMillis();
        if (path == null) {
            return InstallResult.makeFailure("path = NULL");
        }
        // 是否 OPT 优化(dex -> binary)
        boolean skipDexOpt = (flags & InstallStrategy.SKIP_DEX_OPT) != 0;
        // apk path
        File packageFile = new File(path);
        if (!packageFile.exists() || !packageFile.isFile()) {
            return InstallResult.makeFailure("Package File is not exist.");
        }
        VPackage pkg = null;
        try {
            // 进入解析包结构,该结构是可序列化的,为了持久化在磁盘上
            pkg = PackageParserEx.parsePackage(packageFile);
        } catch (Throwable e) {
            e.printStackTrace();
        }
        if (pkg == null || pkg.packageName == null) {
            return InstallResult.makeFailure("Unable to parse the package.");
        }
        InstallResult res = new InstallResult();
        res.packageName = pkg.packageName;
        // PackageCache holds all packages, try to check if we need to update.
        VPackage existOne = PackageCacheManager.get(pkg.packageName);
        PackageSetting existSetting = existOne != null ? (PackageSetting) existOne.mExtras : null;
        if (existOne != null) {
            if ((flags & InstallStrategy.IGNORE_NEW_VERSION) != 0) {
                res.isUpdate = true;
                return res;
            }
            if (!canUpdate(existOne, pkg, flags)) {
                return InstallResult.makeFailure("Not allowed to update the package.");
            }
            res.isUpdate = true;
        }
        // 获得 app 安装文件夹
        File appDir = VEnvironment.getDataAppPackageDirectory(pkg.packageName);
        // so 文件夹
        File libDir = new File(appDir, "lib");
        if (res.isUpdate) {
            FileUtils.deleteDir(libDir);
            VEnvironment.getOdexFile(pkg.packageName).delete();
            VActivityManagerService.get().killAppByPkg(pkg.packageName, VUserHandle.USER_ALL);
        }
        if (!libDir.exists() && !libDir.mkdirs()) {
            return InstallResult.makeFailure("Unable to create lib dir.");
        }

        // 是否基于系统的 apk 加载,前提是安装过的 apk 并且 dependSystem 开关打开
        boolean dependSystem = (flags & InstallStrategy.DEPEND_SYSTEM_IF_EXIST) != 0
                && VirtualCore.get().isOutsideInstalled(pkg.packageName);

        if (existSetting != null && existSetting.dependSystem) {
            dependSystem = false;
        }
        // 复制 so 到 sandbox lib
        NativeLibraryHelperCompat.copyNativeBinaries(new File(path), libDir);

        // 如果不基于系统,一些必要的拷贝工作
        if (!dependSystem) {
            File privatePackageFile = new File(appDir, "base.apk");
            File parentFolder = privatePackageFile.getParentFile();
            if (!parentFolder.exists() && !parentFolder.mkdirs()) {
                VLog.w(TAG, "Warning: unable to create folder : " + privatePackageFile.getPath());
            } else if (privatePackageFile.exists() && !privatePackageFile.delete()) {
                VLog.w(TAG, "Warning: unable to delete file : " + privatePackageFile.getPath());
            }
            try {
                FileUtils.copyFile(packageFile, privatePackageFile);
            } catch (IOException e) {
                privatePackageFile.delete();
                return InstallResult.makeFailure("Unable to copy the package file.");
            }
            packageFile = privatePackageFile;
        }
        if (existOne != null) {
            PackageCacheManager.remove(pkg.packageName);
        }

        // 给上可执行权限,5.0 之后在 SD 卡上执行 bin 需要可执行权限
        chmodPackageDictionary(packageFile);

        // PackageSetting 的一些配置,后面会序列化在磁盘上
        PackageSetting ps;
        if (existSetting != null) {
            ps = existSetting;
        } else {
            ps = new PackageSetting();
        }
        ps.skipDexOpt = skipDexOpt;
        ps.dependSystem = dependSystem;
        ps.apkPath = packageFile.getPath();
        ps.libPath = libDir.getPath();
        ps.packageName = pkg.packageName;
        ps.appId = VUserHandle.getAppId(mUidSystem.getOrCreateUid(pkg));
        if (res.isUpdate) {
            ps.lastUpdateTime = installTime;
        } else {
            ps.firstInstallTime = installTime;
            ps.lastUpdateTime = installTime;
            for (int userId : VUserManagerService.get().getUserIds()) {
                boolean installed = userId == 0;
                ps.setUserState(userId, false/*launched*/, false/*hidden*/, installed);
            }
        }
        //保存 VPackage Cache 到 Disk
        PackageParserEx.savePackageCache(pkg);
        //保存到 RamCache
        PackageCacheManager.put(pkg, ps);
        mPersistenceLayer.save();
        BroadcastSystem.get().startApp(pkg);
        //发送通知 安装完成
        if (notify) {
            notifyAppInstalled(ps, -1);
        }
        res.isSuccess = true;
        return res;
    }

APk 的安装主要完成以下几件事情:

  1. 解析 menifest 拿到 apk 内部信息,包括组件信息,权限信息等。并将这些信息序列化到磁盘和内存中,以备打开时调用。
  2. 准备 App 在 VA 沙箱环境中的私有空间,并且复制一些必要的 apk 和 so libs。
  3. 最后通知前台安装完成。

解析 apk menifest:PackageParserEx.parsePackage():

 // 解析包结构
    public static VPackage parsePackage(File packageFile) throws Throwable {
        PackageParser parser = PackageParserCompat.createParser(packageFile);
        // 调用对应系统版本的 parsePackage 方法
        PackageParser.Package p = PackageParserCompat.parsePackage(parser, packageFile, 0);
        // 包含此信息代表其是 share lib
        if (p.requestedPermissions.contains("android.permission.FAKE_PACKAGE_SIGNATURE")
                && p.mAppMetaData != null
                && p.mAppMetaData.containsKey("fake-signature")) {
            String sig = p.mAppMetaData.getString("fake-signature");
            p.mSignatures = new Signature[]{new Signature(sig)};
            VLog.d(TAG, "Using fake-signature feature on : " + p.packageName);
        } else {
            // 验证签名
            PackageParserCompat.collectCertificates(parser, p, PackageParser.PARSE_IS_SYSTEM);
        }
        // 转换成可以序列化在磁盘上的 Cache
        return buildPackageCache(p);
    }

这里解析 Menifest 的方法其实是调用了 framework 隐藏方法 android.content.pm.PackageParser.parsePackage 来实现的,这个方法返回 android.content.pm.Package 结构,这个类型也是隐藏的,怎么办?可以从 sdk 中复制这个类到自己的项目中欺骗编译器。这就是上一章一开始提到的。

这里还有一个问题,就是 Package 类是不可序列化的,换句话说就是不能直接保存在磁盘上,我们需要将其转换成可以序列化的 VPackage 类型,这就是 buildPackageCache() 的作用。

VPackage:

public class VPackage implements Parcelable {

    public static final Creator<VPackage> CREATOR = new Creator<VPackage>() {
        @Override
        public VPackage createFromParcel(Parcel source) {
            return new VPackage(source);
        }

        @Override
        public VPackage[] newArray(int size) {
            return new VPackage[size];
        }
    };
    public ArrayList<ActivityComponent> activities;
    public ArrayList<ActivityComponent> receivers;
    public ArrayList<ProviderComponent> providers;
    public ArrayList<ServiceComponent> services;
    public ArrayList<InstrumentationComponent> instrumentation;
    public ArrayList<PermissionComponent> permissions;
    public ArrayList<PermissionGroupComponent> permissionGroups;
    public ArrayList<String> requestedPermissions;
    public ArrayList<String> protectedBroadcasts;
    public ApplicationInfo applicationInfo;
    public Signature[] mSignatures;
    public Bundle mAppMetaData;
    public String packageName;
    public int mPreferredOrder;
    public String mVersionName;
    public String mSharedUserId;
    public ArrayList<String> usesLibraries;
    public int mVersionCode;
    public int mSharedUserLabel;
    // Applications hardware preferences
    public ArrayList<ConfigurationInfo> configPreferences = null;
    // Applications requested features
    public ArrayList<FeatureInfo> reqFeatures = null;
    public Object mExtras;

可以看到 VPackage 几乎保存了 apk 中所有的关键信息,尤其是组件的数据结构会在 app 在 VA 中运行的时候给 VAMS,VPMS 这些 VAService 提供 apk 的组件信息。

关于是否 dependSystem 和 isInstallOutside,这个有关 apk 的动态加载,如果 dependSysytem 并且 apk 已经在外部环境安装了,那么 VA 会调用系统提供的 API 就可以动态加载 APK。反之 VA 需要做一些必要的复制工作然后再费劲的去加载 APK。

作者:OSTCB
来源:优快云
原文:https://blog.youkuaiyun.com/ganyao939543405/article/details/76150725

Android虚拟化技术方案技术评测 完成虚拟化一个应用,要做的核心事情只有一件:在运行子应用时,欺骗系统让系统误以为正在运行的是宿主,否则系统会毫不犹豫的将你杀死!! 阅读详情

相关推荐

VirtualApp源码(完整工程)

VirtualApp源码(完整工程),基于android studio (ndk,sdk)开发,实现安卓双开。

Android逆向学习(二)vscode进行双开与图标修改

双开就是在一个android机器中同时安装和运行两个相同的软件,实现双开的方法有很多,这里我们就讲最简单的一个就是修改包名,让系统认为这是两个不同的软件,这个的实现方法也很简单,我们只需要修改相应文件下的包名就可以了。

qq_52380836的博客 3059

ADRV9009的学习历程(一)

** AD9361的同胞ADRV9009 ** 终于记录一下了 距离第一次想写博客记录自己学习的点点滴滴,大概已经过去有一年有余。一直都是停留在心有余而力不足的境界。 基本每次做项目遇到问题都会来优快云寻求帮助,这里虽离庙堂较远,但高手云集。每每读来,思绪万千,次次膜拜。 趁这次又学习了一样新物件,就记录一下,望大神看见能赐教一下。 ADRV9009 最近有项目要用到ADRV9009这个芯片。就来学习一下。做过AD9361的都知道,ADI官方会给出这个芯片的参考手册、数据手册和驱动程序。当然我只是见过AD

weixin_42868560的博客 1万+

【使用VirtualApp无root对手游进行修改】

使用VirtualApp对手游进行修改

qq_45071266的博客 2929

【HSCNN解析】HSCNN:从欠采样光谱图重建高光谱图的CNN网络

本文介绍HSCNN,是一个将欠采样光谱图重建为高光谱图的纯CNN网络,2017年提出。

上个大学哪有不疯的?硬撑罢了! 1619

Windows下ArduPilot SITL仿真环境搭建与QGC地面站联调全攻略

软件在环仿真(SITL)是无人机开发中的关键一环,它通过将飞控固件运行在PC上,用数学模型模拟传感器数据,实现无硬件依赖的飞行逻辑验证。这项技术不仅降低了试错成本,更为地面站开发、自动化测试和任务规划提供了安全可控的试验场。在工程实践中,MAVLink协议作为飞控与地面站之间的通信桥梁,贯穿仿真、遥测与指令控制全程。结合QGroundControl(QGC)这一主流地面站,开发者可以直观地观察飞行状态、规划航点并记录日志。本文以Windows原生环境为例,详细介绍ArduPilot SITL的搭建流程、MA

weixin_31858171的博客 316

Android 双开沙箱 VirtualApp 源码分析(三)App 启动

上一章:Android 双开沙箱 VirtualApp 源码分析(二) 在这之前,我们还是要先了解一下 VA Client Framework 和 VAService 之间的通讯方式VAService 与通讯VAService首先,VAService 是指 VA 仿造 Android 原生 framework 层 Service 实现的一套副本,举例有 VActivityManagerServic

ganyao939543405的博客 1万+

瑞萨RH850/U2A8以太网模块mcal说明

如下所有功能的支持需要在达芬奇中开启EthGlobalTimeSupport。秒[32bit], 纳秒[32bit], 纳秒小数[20bit]每一帧发送数据的相关结构缓冲区都放在变量。每一帧接收数据的相关结构缓冲区都放在变量。

至道无难,唯嫌拣择。 1860

Android 双开沙箱 VirtualApp 源码分析(一)

欢迎使用优快云-markdown编辑器 最近发现了一个非常好的开源项目,基本实现了一个 Android 上的沙箱环境,不过应用场景最多的还是应用双开。 VA github: https://github.com/asLody/VirtualApp VA 的源码注释: https://github.com/ganyao114/VA_Doc 第一章主要是分析一下项目的整体结构。 包结构 andro...

CoderAldrich的专栏 1961

2023东华大学机试OJ进阶版1-81题

先排序。每次找两个最小的数,把其中一个置为两数之和,并累加这个和值,另一个数置为-1,然后再排序,重复操作,直到只剩下一个数。105。

m0_49263811的博客 2794

可微编程:当深度学习遇见物理定律,重塑科学计算新范式

本文探讨了可微编程如何将深度学习与物理定律相结合,重塑科学计算范式。通过自动微分技术,它将物理方程融入可微计算图,使模型在数据驱动的同时遵守物理约束。文章以神经常微分方程为例,阐述了其在连续系统建模、科学模拟器优化等领域的应用,并分析了当前面临的挑战与最佳实践路径。

weixin_29056145的博客 134

Android 双开沙箱 VirtualApp 源码分析(六)ContentProvider

Android 双开沙箱 VirtualApp 源码分析(六)ContentProvider Provider 注册 回顾前面,Activity 启动的时候会检查 Application 是否初始化,会调用 bindApplication,里面执行了安装 Provider 的方法: private void installContentProviders(Context app, List<...

CoderAldrich的专栏 616

华为OD新系统机试真题-勇攀数字高峰(Java/Py/C/C++/Js/Go)

新系统 华为OD机试新系统【勇登数字高峰】,提供C语言、Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解,以及在线OJ模拟做题。华为OD新系统机试真题【勇登数字高峰】

qq_45776114的博客 1544

Android 双开沙箱 VirtualApp 源码分析(五)BroadcastReceiver

Android 双开沙箱 VirtualApp 源码分析(五)BroadcastReceiver 方案猜测 同 Activity 一样,Client App 在 Menifest 中注册的静态广播外部 AMS 是无法知晓的,经过前几章的分析,相信大家已经是老司机了,我们可以先尝试提出自己的观点。 和 Activity 一样使用 Stub 组件占坑?仔细想一想是无法实现的,因为你无法预先确定 Cl...

CoderAldrich的专栏 657

PyTorch生成式人工智能(7)——生成对抗网络(Generative Adversarial Network, GAN)实践详解

生成对抗网络 (Generative Adversarial Network, GAN) 最早由 Ian Goodfellow 于 2014 年提出,其中生成器试图创建与真实样本无法区分的数据样本,而判别器则试图区分生成器生成的样本与真实样本。在本节中,我们将介绍 GAN 的理论基础。然后,介绍如何使用 PyTorch 从零开始构建 GAN,以便了解所有细节,深入理解 GAN 的内部工作原理,为后续讨论 GAN 的其他更高级内容奠定基础,比如生成高分辨率图像或逼真的音乐。

盼小辉丶的博客 1万+

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Android 双开沙箱 VirtualApp 源码分析(四)启动插件 Service

Android 双开沙箱 VirtualApp 源码分析(四)启动插件 Service 原生 Service 创建过程 首先有必要了解一下原生 framework 对 Service 的创建,因为在 VA 中启动 Service 和 Activity 有很大的区别。 首先入口 ContextWrapper.startService(): @Override public Component...

CoderAldrich的专栏 445

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Android adb指令实现应用双开功能

android adb 应用双开

wangjicong_215的博客 4517

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

【Android 插件化】VirtualApp 源码分析 ( 目前的 API 现状 | 安装应用源码分析 | 安装按钮执行的操作 | 返回到 HomeActivity 执行的操作 )

一、目前的 API 现状、 二、安装应用源码分析、 1、安装按钮执行的操作、 2、返回到 HomeActivity 执行的操作、

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 1946

【Android 插件化】VirtualApp 源码分析 ( 安装应用源码分析 | HomePresenterImpl 添加应用 | AppRepository.addVirtualApp )

一、安装应用源码分析、 1、HomePresenterImpl 添加应用、 2、AppRepository.addVirtualApp 安装 SD 卡 APK 应用、

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 1409
上一篇: Android 双开沙箱 VirtualApp 源码分析(一)
下一篇: Android 双开沙箱 VirtualApp 源码分析(三)App 启动
CoderAldrich
博客等级 码龄12年 431粉丝 24原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值