十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

VirtualApp 开发实战指南:源码架构、SDK 接入与 Hook 能力全解析

VirtualApp 开发实战指南:源码架构、SDK 接入与 Hook 能力全解析 VirtualApp 开发实战指南源码架构、SDK 接入与 Hook 能力全解析【免费下载链接】VirtualAppVirtual Engine for Android(Support 14.0 in business version)项目地址: https://gitcode.com/GitHub_Trending/vi/VirtualAppVirtualAppVA是一个面向 Android 的虚拟化引擎框架本文以仓库内 doc/VADev.md 为基础开发文档为主线先拆解 VA 的源码目录结构与核心包职责再完整讲解从工程接入、安装/启动/卸载 App到 Java HookXposed 风格与 Native HookCydia Substrate 风格的实战用法。读完本文你将掌握 VA 框架的进程模型、四大源码目录的作用、VirtualCore与VActivityManager的核心 API 调用方式并能独立完成一个基于 VA 的多开/虚拟化应用接入。1. VA 源码结构介绍1.1 源码目录一览VA 工程源码根目录下共有 4 个源码目录各自职责如下目录名称作用appVA Demo 主包源码所在目录app-extVA Demo 插件包源码所在目录libVA 库源码所在目录lib-extVA 插件库源码所在目录在本仓库中对应结构即VirtualApp/appDemo 主包与VirtualApp/lib核心库其中lib内含src/main/java/com/lody/virtual/...的全部框架代码、src/main/aidl的 AIDL 接口以及src/main/jni的 Native 层实现。app-ext与lib-ext属于商业版 VA 的插件包体系当前开源仓库未包含对应目录。1.2 编译配置文件 VAConfig.gradleVA 的编译配置集中在VAConfig.gradle中开发者接入时需要根据自身业务修改以下配置项配置名称作用PACKAGE_NAME配置 VA 主包的包名EXT_PACKAGE_NAME配置 VA 插件包的包名VA_MAIN_PACKAGE_32BIT配置 VA 主包是 32 位还是 64 位true为 32 位false为 64 位VA_ACCESS_PERMISSION_NAME配置 VA 中四大组件Activity/Service/Receiver/Provider的权限名称VA_AUTHORITY_PREFIX配置 VA 主包中 ContentProvider 的authoritiesVA_EXT_AUTHORITY_PREFIX配置 VA 插件包中 ContentProvider 的authoritiesVA_VERSION配置 VA 库版本开发者一般不需要关心VA_VERSION_CODE配置 VA 库版本代码开发者一般不需要关心注意VA_AUTHORITY_PREFIX与VA_EXT_AUTHORITY_PREFIX在设备上全局唯一不能与其他已安装 App 的 authority 重复否则会导致 ContentProvider 冲突。1.3 核心代码包解析VA 框架的核心逻辑分布在几个关键包中理解这些包的进程归属与职责是读懂 VA 架构的第一步1com.lody.virtual.client包 —— VAPP Client 进程运行在 VAPPVirtual AppClient 进程中主要用于 VA Framework 中的 App Hook 部分完成对系统各个 Service 的 Hook 处理。从源码结构看该包下包含hook/proxies对ActivityManager、PackageManager、Clipboard、MountService等系统服务的 Hook 代理例如VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/am/ActivityManagerStub.java、ipcVActivityManager等客户端 IPC 封装以及NativeEngine.java等入口。2com.lody.virtual.server包 —— VA Server 进程运行在 VA Server 进程中主要用于 VA Framework 中的 App Server 部分实现 App 安装以及其他不交给 Android 系统处理的 App 请求。该包内含interfacesAIDL 接口定义如IActivityManager、pm包管理、amActivity 管理、accounts、notification等子模块对应VirtualApp/lib/src/main/java/com/lody/virtual/server/目录。3mirror包 —— 系统隐藏类引用mirror包下的代码主要用于对系统隐藏类的引用属于工具类用于减少大量反射代码的编写。仓库中VirtualApp/lib/src/main/java/mirror/目录下对android.app.ActivityThread、android.content.pm.PackageParser、android.os.ServiceManager等系统内部类做了镜像封装配合RefClass/RefMethod/RefStaticObject等注解式反射基类使用。4cpp包 —— VA Native 部分运行在 VAPP Client 进程中主要用于 VA Native 部分实现 IO 重定向和 JNI 函数 Hook。其中substrate中实现了针对 arm32 和 arm64 的 Hookvfs.cpp中实现了 VA 的虚拟文件系统用于控制 App 文件访问限制syscall_hook.cpp中实现了对 IO 的 Hook。对应到本仓库Native 层位于VirtualApp/lib/src/main/jni/其中Substrate/目录含CydiaSubstrate.h、SubstrateHook.cpp等实现跨架构 inline hookFoundation/目录含IOUniformer.cpp、SandboxFs.cpp、VMPatch.cpp等实现 IO 重定向与符号查找HookZz/目录提供另一套跨平台 Hook 引擎。5DelegateApplicationExt.java—— VA Host Plugin 进程运行在 VA Host Plugin 进程中用于 VA 插件包实现主包代码的加载执行。它属于商业版插件体系的宿主代理 Application开源版中与之对应的是com.lody.virtual.DelegateApplication64Bit见VirtualApp/lib/src/main/java/com/lody/virtual/DelegateApplication64Bit.java。2. VA SDK 使用介绍2.1 VA 工程接入用 Android Studio 打开项目将 VirtualApp-Priv 项目用 Android Studio 打开后可见多个模块appapp-extliblib-ext其中lib和lib-ext属于 VirtualApp核心库以及扩展库app和app-ext则属于示例 App。对本仓库而言VirtualApp/app是可直接运行的 DemoVirtualApp/lib是要依赖的核心库。创建自己的 App新建一个application类型的 module并添加lib模块为依赖implementation project(:lib)根据需求修改 VAConfig.gradleext { VA_MAIN_PACKAGE_32BIT true // 主包为32位 VA_ACCESS_PERMISSION_NAME io.busniess.va.permission.SAFE_ACCESS // VirtualApp组件用到的权限名称 VA_AUTHORITY_PREFIX io.busniess.va // VirtualApp中ContentProvider用到的authority不能与其他app重复 VA_EXT_AUTHORITY_PREFIX io.busniess.va.ext // VirtualApp扩展包中ContentProvider用到的authority不能与其他app重复 // ... }在 AndroidManifest.xml 添加所需权限uses-permission android:name${VA_ACCESS_PERMISSION_NAME} /权限名称必须与VAConfig.gradle中所声明的保持一致。可以在build.gradle中添加Placeholder来防止出错android { // ... manifestPlaceholders [ VA_ACCESS_PERMISSION_NAME: rootProject.ext.VA_ACCESS_PERMISSION_NAME, ] }2.2 创建 Application 并初始化 VirtualApp复写attachBaseContext添加引导 VirtualApp 的代码VA 需要在attachBaseContext阶段完成早期初始化此时onCreate尚未执行Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); try { VirtualCore.get().startup(base, mConfig); } catch (Throwable e) { e.printStackTrace(); } }这里传入了一个SettingConfig配置对象mConfigprivate SettingConfig mConfig new SettingConfig() { Override public String getMainPackageName() { // 主包的包名 return BuildConfig.APPLICATION_ID; } Override public String getExtPackageName() { // 扩展包包名 return BuildConfig.EXT_PACKAGE_NAME; } Override public boolean isEnableIORedirect() { // 是否启用IO重定向建议开启 return true; } Override public Intent onHandleLauncherIntent(Intent originIntent) { // 回到桌面的 Intent 拦截操作这里把回到桌面的动作改成回到主包的BackHomeActivity页面 Intent intent new Intent(); ComponentName component new ComponentName(getMainPackageName(), BackHomeActivity.class.getName()); intent.setComponent(component); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); return intent; } Override public boolean isUseRealDataDir(String packageName) { // data路径模拟真实路径格式需要启用IO重定向。部分加固会校该验路径格式 return false; } Override public boolean isOutsidePackage(String packageName) { // 是否是外部app。 设置外部 app 对内部app看见 return false; } Override public boolean isAllowCreateShortcut() { // 是否允许创建桌面快捷图标。建议关闭false自己实现桌面快捷方式 return false; } Override public boolean isHostIntent(Intent intent) { // 是否由VirtualApp处理的Intent return intent.getData() ! null market.equals(intent.getData().getScheme()); } Override public boolean isUseRealApkPath(String packageName) { // 安装apk路径模拟真实路径需要启用IO重定向。部分加固会校验该路径格式 return false; } Override public boolean isEnableVirtualSdcardAndroidData() { // 启用外置存储下的 Android/data 目录的重定向 // 需要重定向支持 // Android 11 之后必须启用 return BuildCompat.isR(); } Override public String getVirtualSdcardAndroidDataName() { // 设置外置存储下的 Android/data 目录的重定向路径 // /sdcard/Android/data/com.example.test/ /sdcard/{VirtualSdcardAndroidDataName}/{user_id}/Android/data/com.example.test/ return Android_va; } Override public FakeWifiStatus getFakeWifiStatus() { // 修改wifi信息。 null 则不修改 return null; } Override public boolean isHideForegroundNotification() { // 隐藏前台消息不建议隐藏 return false; } Override public boolean isOutsideAction(String action) { // 外部 Intent 的 action 事件响应 return MediaStore.ACTION_IMAGE_CAPTURE.equals(action) || MediaStore.ACTION_VIDEO_CAPTURE.equals(action) || Intent.ACTION_PICK.equals(action); } Override public boolean isDisableDrawOverlays(String packageName) { // 禁用 VAPP 的顶层覆盖浮窗。 return false; } };关键配置项速查配置项默认建议说明isEnableIORedirecttrue是否启用 IO 重定向建议开启isUseRealDataDirfalsedata 路径模拟真实路径格式需配合 IO 重定向部分加固会校验路径格式isUseRealApkPathfalse安装 apk 路径模拟真实路径需配合 IO 重定向isAllowCreateShortcutfalse建议关闭自行实现桌面快捷方式isEnableVirtualSdcardAndroidDataBuildCompat.isR()外置存储Android/data重定向Android 11 之后必须启用getVirtualSdcardAndroidDataNameAndroid_va重定向目录名规则/sdcard/{VirtualSdcardAndroidDataName}/{user_id}/Android/data/pkg/getFakeWifiStatusnull修改 WiFi 信息null表示不修改isHideForegroundNotificationfalse隐藏前台通知不建议隐藏isOutsideAction拍照/录像/PICK外部 Intent 的 action 事件响应其中BuildCompat.isR()对应仓库 BuildCompat.java 中的系统版本判断逻辑用于在 Android 11API 30及以上自动开启Android/data重定向。复写onCreate添加初始化 VirtualApp 的代码Override public void onCreate() { super.onCreate(); VirtualCore virtualCore VirtualCore.get(); virtualCore.initialize(new VirtualCore.VirtualInitializer() { Override public void onMainProcess() { // 主进程回调 } Override public void onVirtualProcess() { // 虚拟App进程回调 } Override public void onServerProcess() { // 服务端进程回调 } Override public void onChildProcess() { // 其他子进程回调 } }); }由于 VirtualApp 会启动多个进程Application 会进入 N 次不同的进程会走到VirtualInitializer不同的回调可以在这里根据进程类型添加额外的初始化代码。四个回调对应四类进程主进程宿主自身、虚拟 App 进程VAPP被 Hook 的插件运行进程、服务端进程VA Server以及其余子进程。3. 安装 APP3.1 安装 API 与 Uri 来源安装 App 的核心 API 定义在VirtualCore中VirtualCore.java public VAppInstallerResult installPackage(Uri uri, VAppInstallerParams params);开源版VirtualCore中对应的底层方法为installPackage(String apkPath, int flags)见 VirtualCore.java商业版在VAppInstallerParams上扩展了更丰富的安装参数。Uri决定了需要安装的 apk 的来源目前支持package和file两种协议。Package Uri 示例Uri packageUri Uri.parse(package:com.hello.world);File Uri 示例File apkFile new File(/sdcard/test.apk); Uri packageUri Uri.fromFile(apkFile);3.2 两种 Uri 安装 App 有何区别package协议安装 app只需要传入包名不需要具体的 APK 路径。以这种协议安装的 app相当于双开app 会随外部版本的升级而自动升级随外部版本的卸载而自动卸载。PackageSetting中的dynamic为true。file协议则是内部安装apk 会被复制到容器内部与外部版本完全独立。PackageSetting中的dynamic为false。3.3 安装参数 VAppInstallerParams安装标志 installFlagsFLAG说明FLAG_INSTALL_OVERRIDE_NO_CHECK允许覆盖安装FLAG_INSTALL_OVERRIDE_FORBIDDEN禁止覆盖安装FLAG_INSTALL_OVERRIDE_DONT_KILL_APP覆盖安装不 kill 已经启动的 APP安装模式 modeFLAG说明MODE_FULL_INSTALL完整安装MODE_INHERIT_EXISTING已安装的安装模式。预留mode 为预留参数暂时未使用目前不管设置哪种都一样。cpuAbiOverride指定 app 的 abi。特殊需求下可以强制指定 app 在指定 abi 下运行不指定时默认根据系统规则来决定运行的 abi。可选参数armeabiarmeabi-v7aarm64-v8a双开 app 实例代码VAppInstallerParams params new VAppInstallerParams(VAppInstallerParams.FLAG_INSTALL_OVERRIDE_NO_CHECK); VAppInstallerResult result VirtualCore.get().installPackage(Uri.parse(package:com.tencent.mobileqq), params); if (result.status VAppInstallerResult.STATUS_SUCCESS) { Log.e(test, install apk success.); }从 sd 卡安装 apk 实例代码VAppInstallerParams params new VAppInstallerParams(VAppInstallerParams.FLAG_INSTALL_OVERRIDE_NO_CHECK); VAppInstallerResult result VirtualCore.get().installPackage(Uri.fromFile(new File(/sdcard/test.apk)), params); if (result.status VAppInstallerResult.STATUS_SUCCESS) { Log.e(test, install apk success.); }安装 Split apk先安装 base 包然后再安装所有 split 包即可File dir new File(/sdcard/YouTube_XAPK_Unzip/); VAppInstallerParams params new VAppInstallerParams(VAppInstallerParams.FLAG_INSTALL_OVERRIDE_NO_CHECK); VAppInstallerResult result VirtualCore.get().installPackage( Uri.fromFile(new File(dir,com.google.android.youtube.apk)), params); for (File file : dir.listFiles()) { String name file.getName(); if (name.startsWith(config.) name.endsWith(.apk)) { result VirtualCore.get().installPackage( Uri.fromFile(file), params); } }4. 启动及管理 Application4.1 启动 App// class VActivityManager public boolean launchApp(final int userId, String packageName)实例代码VActivityManager.get().launchApp(0, com.tencent.mobileqq);在开源版中VActivityManager位于 VActivityManager.java通过getService()向 VA Server 侧的IActivityManager发起 IPC 调用完成启动。4.2 杀死 App// class VActivityManager public void killAppByPkg(String pkg, int userId) public void killAllApps()实例代码// 杀死userid为0的QQ程序进程 VActivityManager.get().killAppByPkg(com.tencent.mobileqq, 0);// 杀死所有App进程 VActivityManager.get().killAllApps();开源版中对应实现见 VActivityManager.javakillAllApps与 VActivityManager.javakillAppByPkg。4.3 卸载 App// class VirtualCore public boolean uninstallPackageAsUser(String pkgName, int userId) public boolean uninstallPackage(String pkgName)实例代码// 卸载userid为0的QQ程序 VirtualCore.get().uninstallPackageAsUser(com.tencent.mobileqq, 0); // 卸载所有user下安装的QQ程序 VirtualCore.get().uninstallPackage(com.tencent.mobileqq);开源版对应实现见 VirtualCore.java。4.4 查询已安装的 App// class VirtualCore public ListInstalledAppInfo getInstalledApps(int flags)开源版还提供了按用户查询的变体getInstalledAppsAsUser(int userId, int flags)见 VirtualCore.java。InstalledAppInfo定义于VirtualApp/lib/src/main/java/com/lody/virtual/remote/InstalledAppInfo.java包含包名、安装路径、用户信息等字段。5. Java Hook 使用VirtualApp 中实现了一套 Xposed 风格的接口用户只要会使用 Xposed 就可以做到原本需要系统内置 Xposed 才能做到的事情。但需要明确VA 中 Xposed 的作用域是 VA 这个 App 中的不能越权控制系统或其他外部 App。5.1 AppCallback 接口VA 中提供了一个 App 创建启动的回调接口com.lody.virtual.client.core.AppCallbackpublic interface AppCallback { void beforeStartApplication(String packageName, String processName, Context context); void beforeApplicationCreate(String packageName, String processName, Application application); void afterApplicationCreate(String packageName, String processName, Application application); }接口说明名称说明beforeStartApplicationAPP 启动之前创建之后beforeApplicationCreateAPP 被创建之前Application 已经准备完毕Application.onCreate 未执行afterApplicationCreateAPP 被创建之后Application.onCreate 已被执行参数说明名称说明packageNameVAPP 的包名processNameVAPP 的进程名contextVAPP 的 Application contextapplicationVAPP 的 Application注APP 的创建指的是Application被创建。5.2 注册 AppCallback结合前面的进程回调知识只需要在VAPP 进程回调onVirtualProcess里设置 App 回调AppCallback即可Override public void onCreate() { super.onCreate(); VirtualCore virtualCore VirtualCore.get(); virtualCore.initialize(new VirtualCore.VirtualInitializer() { Override public void onVirtualProcess() { // 设置VAPP启动回调 virtualCore.setAppCallback(new MyComponentDelegate()); } }); }5.3 使用 Xposed API 进行 HookMyComponentDelegate实现AppCallback并在beforeApplicationCreate阶段使用XposedHelpers进行方法 Hookpublic class MyComponentDelegate implements AppCallback { Override public void beforeStartApplication(String packageName, String processName, Context context) { } Override public void beforeApplicationCreate(String packageName, String processName, Application application) { XposedHelpers.findAndHookMethod(android.app.ContextImpl, ClassLoader.getSystemClassLoader(), getOpPackageName, new XC_MethodHook() { Override protected void beforeHookedMethod(MethodHookParam param) { VLog.printStackTrace(getOpPackageName); param.setResult(VirtualCore.get().getHostPkg()); } }); } Override public void afterApplicationCreate(String packageName, String processName, Application application) { } }上面示例中已经展示了一个 Xposed 使用案例通过XposedHelpers.findAndHookMethod定位android.app.ContextImpl#getOpPackageName并在beforeHookedMethod中把返回值替换为宿主包名。关于入口Xposed 的入口是一个IXposedHookLoadPackage的实例它提供了一个void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam)接口参数为XC_LoadPackage.LoadPackageParam。在 VA 中虽然不能与系统 Xposed 完全一一对应但已完全够用。loadPackageParam.classLoader可以用context.getClassLoader()或application.getClassLoader()代替。后续XposedHelpers、XposedBridge原本怎么用在 VA 里也一样使用。结合仓库源码VA 的 Xposed 兼容层位于VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/目录下包含XposedHelpers等工具与 Hook 代理实现可直接在 VAPP 进程内生效。6. Native Hook 使用对于 ARM 32 和 ARM 64 的 Hook只需要引入头文件CydiaSubstrate.h即可MSHookFunction(Type_ *symbol, Type_ *replace, Type_ **result)参数说明名称说明symbol要 Hook 的地址replace你自定义的 hook 函数result被 hook 函数的备份参考syscall_hook.cpp代码auto is_accessible_str __dl__ZN19android_namespace_t13is_accessibleERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEE; void *is_accessible_addr getSym(linker_path, is_accessible_str); if (is_accessible_addr) { MSHookFunction(is_accessible_addr, (void *) new_is_accessible,(void **) orig_is_accessible); }在MSHookFunction内部会自动判断当前是 ARM32 还是 ARM64_extern void MSHookFunction(void *symbol, void *replace, void **result) { if (*result ! nullptr) { return; } // ALOGE([MSHookFunction] symbol(%p) replace(%p) result(%p), symbol, replace, *result); #ifdef __aarch64__ A64HookFunction(symbol, replace, result); #else SubstrateHookFunction(NULL, symbol, replace, result); #endif }结合仓库源码以上 Native Hook 能力对应VirtualApp/lib/src/main/jni/Substrate/目录中的实现其中 CydiaSubstrate.h 声明了跨架构 Hook APISubstrateHook.cpp 提供SubstrateHookFunction底层实现IO 重定向与虚拟文件系统对应VirtualApp/lib/src/main/jni/Foundation/目录IOUniformer.cpp、SandboxFs.cpp。整个 JNI 层的注册入口在VirtualApp/lib/src/main/jni/Jni/VAJni.cpp。7. 总结VA 的开发可以概括为一条主线先用VAConfig.gradle完成包名、authority、权限等编译期配置再在宿主Application的attachBaseContext中调用VirtualCore.get().startup()引导框架、在onCreate中通过VirtualInitializer按进程类型初始化随后用VirtualCore.installPackage安装package协议双开 /file协议独立安装、用VActivityManager启动与管控进程最后按需在 VAPP 进程内通过AppCallback Xposed 兼容 API 做 Java Hook或通过MSHookFunction做 Native Hook。四个源码目录app/app-ext/lib/lib-ext、两个进程角色VAPP Client 与 VA Server、两类 HookJava Xposed 风格与 Native Substrate 风格构成了 VA 框架最核心的开发知识体系。本文所有 API 与配置均可在本仓库VirtualApp/lib与VirtualApp/app中找到对应源码作为参考。【免费下载链接】VirtualAppVirtual Engine for Android(Support 14.0 in business version)项目地址: https://gitcode.com/GitHub_Trending/vi/VirtualApp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表