Skip to content

Latest commit

 

History

History
538 lines (432 loc) · 17.8 KB

VADev.md

File metadata and controls

538 lines (432 loc) · 17.8 KB

VA基础开发文档

本文档主要介绍2部分。
第一部分是VA的源码结构介绍,这部分是为了让开发者能快速了解掌握VA源码框架。
第二部分是VA的基础SDK使用说明。 其他更多的开发文档见:VA私有库Wiki
VA产品说明:文档

下面开始第一部分,VA源码结构介绍:

1. VA源码目录介绍

下图是VA源码根目录:

可以看到VA一共有4个源码目录,各个目录介绍如下:

目录名称 作用
app VA Demo主包源码所在目录
app-ext VA Demo插件包源码所在目录
lib VA库源码所在目录
lib-ext VA插件库源码所在目录

2. VA编译配置文件介绍

VA的编译配置文件是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中4大组建的权限名称
VA_AUTHORITY_PREFIX 用于配置VA主包中ContentProvider的authorities
VA_EXT_AUTHORITY_PREFIX 用于配置VA插件包中ContentProvider的authorities
VA_VERSION 用于配置VA库版本,开发者一般不需要关心
VA_VERSION_CODE 用于配置VA库版本代码,开发者一般不需要关心

3. VA核心代码解释

  1. com.lody.virtual.client包下的代码运行在VAPP Client进程中,主要用于VA Framework中的APP Hook部分,完成对各个Service的HOOK处理
  2. com.lody.virtual.server包下的代码运行在VA Server进程中,代码主要用于VA Framework中的APP Server部分,实现处理APP安装以及其他不给Android系统处理的APP请求
  3. mirror包下的代码主要用于对系统隐藏类的引用,属于工具类,减少大量反射代码的编写
  4. cpp包下的代码进行在VAPP Client进程中,主要用于VA Native部分,实现IO重定向和jni函数HOOK。其中:
    • substrate中实现了针对arm32和arm64的hook
    • vfs.cpp中实现了VA的虚拟文件系统,用于控制APP文件访问限制
    • syscall_hook.cpp中实现了对IO的Hook
  5. DelegateApplicationExt.java运行在VA Host Plugin进程中,用于VA插件包,实现了对主包代码的加载执行



下面开始第二部分,VA SDK使用介绍:

1. VA工程接入

用Android Studio打开VirtualApp-Priv项目

可见多个模块:

  • app
  • app-ext
  • lib
  • lib-ext

其中liblib-ext属于VirtualApp核心库以及扩展库appapp-ext则属于示例app

创建自己的App

新建一个application类型的module,并添加lib模块为依赖

implementation project(':lib')

根据需求修改VAConfig.gradle:

ext {
    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,
    ]
}

创建一个Application

复写attachBaseContext方法,添加引导VirtualApp的代码:

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        try {
            VirtualCore.get().startup(base, mConfig);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

这里传入了一个VirtualApp的一个配置 mConfig

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

复写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不同的回调,可以在这里根据进程类型添加额外的初始化代码。

2. 安装APP

API:

VirtualCore.java

 public VAppInstallerResult installPackage(Uri uri, VAppInstallerParams params);

参数Uri是什么?

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

两种Uri安装app有何区别?

package协议 安装app,只需要传入包名,不需要具体的APK路径,所以以这种协议安装的app,相当于双开

app会随外部版本的升级而自动升级,随外部版本的卸载而自动卸载。PackageSetting 中的 dynamictrue

file协议 则是内部安装,apk会被复制到容器内部,与外部版本完全独立. PackageSetting 中的 dynamicfalse

安装参数 VAppInstallerParams

安装标志 installFlags

FLAG 说明
FLAG_INSTALL_OVERRIDE_NO_CHECK 允许覆盖安装
FLAG_INSTALL_OVERRIDE_FORBIDDEN 禁止覆盖安装
FLAG_INSTALL_OVERRIDE_DONT_KILL_APP 覆盖安装不kill已经启动的APP

安装模式 mode

FLAG 说明
MODE_FULL_INSTALL 完整安装
MODE_INHERIT_EXISTING 已安装的的安装模式。预留

预留参数,暂时未使用。目前不管设置哪种都一样。

cpuAbiOverride

指定app的abi。特殊需求下,可以强制指定app在指定abi下运行。不指定的情况下默认根据系统规则来决定运行的abi。

可选参数:

  • armeabi
  • armeabi-v7a
  • arm64-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);
    }
}

3. 启动及管理Application

启动App

// class VActivityManager
public boolean launchApp(final int userId, String packageName)

实例代码:

VActivityManager.get().launchApp(0, "com.tencent.mobileqq");

杀死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();

卸载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");

查询已安装的App

// class VirtualCore
public List<InstalledAppInfo> getInstalledApps(int flags)

4. Java Hook使用

VirtualApp中实现了一套Xposed接口,用户只要会使用Xposed就可以做到原本需要系统内置Xposed才能做到的事情. 但是用户也需要明白,VA中Xposed的作用域是VA这个APP中的,不能越权控制系统或其他外部App.

VA中提供了一个App创建启动的回调接口com.lody.virtual.client.core.AppCallback,接口如下:

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

接口说明:

名称 说明
beforeStartApplication APP启动之前,创建之后
beforeApplicationCreate APP被创建之前,Application已经准备完毕,Application.OnCreate未执行
afterApplicationCreate APP被创建之后,Application.OnCreate已被执行

参数说明:

名称 说明
packageName VAPP的包名
processName VAPP的进程名
context VAPP的Application context
application VAPP的Application

注: APP的创建指的是Application被创建.

接口有了,接下来就是怎么使用了.查看VirualApp进程说明,可以知道, 我们只需要在VAPP进程回调里(onVirtualProcess) 设置App回调 AppCallback 就可以达到目的.

宿主Application代码,参考io/busniess/va/App.java

@Override
    public void onCreate() {
        super.onCreate();
        VirtualCore virtualCore = VirtualCore.get();
        virtualCore.initialize(new VirtualCore.VirtualInitializer() {
            @Override
            public void onVirtualProcess() {
                // 设置VAPP启动回调
                virtualCore.setAppCallback(new MyComponentDelegate());
            }
        });
    }

MyComponentDelegate类代码

public 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的使用案例.Xposed的入口是一个IXposedHookLoadPackage的实例,他提供了一个void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam)的接口,有一个XC_LoadPackage.LoadPackageParam的参数.这里我们虽然不能完全一一对用,但是也完全够用了.loadPackageParam.classsload可以用context.getClassLoader()或者application.getClassLoader()都是可以的.后续XposedHelpers,XposedBridge原来怎么用,这里也一样使用.

5. Native Hook使用

对于ARM 32和ARM 64的Hook,只需要引入头文件CydiaSubstrate.h即可,Hook API:
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
}


其他更多的开发指导请见VA私有库Wiki