VA基础开发文档
本文档主要介绍2部分。
第一部分是VA的源码结构介绍,这部分是为了让开发者能快速了解掌握VA源码框架。
第二部分是VA的基础SDK使用说明。
其他更多的开发文档见:[VA私有库Wiki](https://github.com/asLody/VirtualApp-Priv/wiki)
VA产品说明:[文档](../README.md)
**下面开始第一部分,VA源码结构介绍:**
## 1. VA源码目录介绍 ##
下图是VA源码根目录:
![](https://cdn.jsdelivr.net/gh/xxxyanchenxxx/temp@1.0/doc/1.png)
可以看到VA一共有4个源码目录,各个目录介绍如下:
目录名称 | 作用
---- | ---
app | VA Demo主包源码所在目录
app-ext | VA Demo插件包源码所在目录
lib | VA库源码所在目录
lib-ext | VA插件库源码所在目录
## 2. VA编译配置文件介绍 ##
VA的编译配置文件是VAConfig.gradle:
![](https://cdn.jsdelivr.net/gh/xxxyanchenxxx/temp@1.0/doc/2_1.jpg)
配置解释:
配置名称 | 作用
---- | ---
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处理
![](https://cdn.jsdelivr.net/gh/xxxyanchenxxx/temp@1.0/doc/3_1.png)
2. `com.lody.virtual.server`包下的代码运行在VA Server进程中,代码主要用于VA Framework中的APP Server部分,实现处理APP安装以及其他不给Android系统处理的APP请求
![](https://cdn.jsdelivr.net/gh/xxxyanchenxxx/temp@1.0/doc/3_2.png)
3. `mirror`包下的代码主要用于对系统隐藏类的引用,属于工具类,减少大量反射代码的编写
![](https://cdn.jsdelivr.net/gh/xxxyanchenxxx/temp@1.0/doc/3_3.png)
4. `cpp`包下的代码进行在VAPP Client进程中,主要用于VA Native部分,实现IO重定向和jni函数HOOK。其中:
- `substrate`中实现了针对arm32和arm64的hook
- `vfs.cpp`中实现了VA的虚拟文件系统,用于控制APP文件访问限制
- `syscall_hook.cpp`中实现了对IO的Hook
![](https://cdn.jsdelivr.net/gh/xxxyanchenxxx/temp@1.0/doc/3_4.png)
5. `DelegateApplicationExt.java`运行在VA Host Plugin进程中,用于VA插件包,实现了对主包代码的加载执行
![](https://cdn.jsdelivr.net/gh/xxxyanchenxxx/temp@1.0/doc/3_5.png)
**下面开始第二部分,VA SDK使用介绍:**
## 1. VA工程接入 ##
### 用Android Studio打开VirtualApp-Priv项目
可见多个模块:
* app
* app-ext
* lib
* lib-ext
其中**lib**和**lib-ext**属于VirtualApp`核心库`以及`扩展库`,**app**和**app-ext**则属于`示例app`。
### 创建自己的App
新建一个application类型的module,并添加lib模块为依赖
```gradle
implementation project(':lib')
```
### 根据需求修改VAConfig.gradle:
```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添加所需的权限
```xml
```
权限名称必须与**VAConfig.gradle**中所声明的保持一致,可以在**build.gradle**中添加**Placeholder**来防止出错。
``` gradle
android {
// ...
manifestPlaceholders = [
VA_ACCESS_PERMISSION_NAME: rootProject.ext.VA_ACCESS_PERMISSION_NAME,
]
}
```
### 创建一个Application
#### 复写attachBaseContext方法,添加引导VirtualApp的代码:
```java
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
try {
VirtualCore.get().startup(base, mConfig);
} catch (Throwable e) {
e.printStackTrace();
}
}
```
#### 这里传入了一个VirtualApp的一个配置 mConfig
```java
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的代码:
```java
@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:
```java
VirtualCore.java
public VAppInstallerResult installPackage(Uri uri, VAppInstallerParams params);
```
## 参数Uri是什么?
Uri决定了**需要安装的apk**的来源,目前支持 package 和 file 协议。
### Package Uri 示例:
```java
Uri packageUri = Uri.parse("package:com.hello.world");
```
### File Uri 示例:
```java
File apkFile = new File("/sdcard/test.apk");
Uri packageUri = Uri.fromFile(apkFile);
```
## 两种Uri安装app有何区别?
**package协议** 安装app,只需要传入包名,不需要具体的APK路径,所以以这种协议安装的app,**相当于双开**。
app会随外部版本的升级而自动升级,随外部版本的卸载而自动卸载。`PackageSetting` 中的 `dynamic` 为 `true`。
**file协议** 则是内部安装,apk会被复制到容器内部,与外部版本完全独立. `PackageSetting` 中的 `dynamic` 为 `false`。
## 安装参数 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实例代码:
```java
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实例代码:
```java
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包即可。
```java
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
```java
// class VActivityManager
public boolean launchApp(final int userId, String packageName)
````
实例代码:
```java
VActivityManager.get().launchApp(0, "com.tencent.mobileqq");
```
# 杀死App
```java
// class VActivityManager
public void killAppByPkg(String pkg, int userId)
public void killAllApps()
```
实例代码:
```java
// 杀死userid为0的QQ程序进程
VActivityManager.get().killAppByPkg("com.tencent.mobileqq", 0);
```
```java
// 杀死所有App进程
VActivityManager.get().killAllApps();
```
# 卸载App
```java
// class VirtualCore
public boolean uninstallPackageAsUser(String pkgName, int userId)
public boolean uninstallPackage(String pkgName)
```
实例代码:
```java
// 卸载userid为0的QQ程序
VirtualCore.get().uninstallPackageAsUser("com.tencent.mobileqq", 0);
// 卸载所有user下安装的QQ程序
VirtualCore.get().uninstallPackage("com.tencent.mobileqq");
```
# 查询已安装的App
```java
// class VirtualCore
public List getInstalledApps(int flags)
```
## 4. Java Hook使用 ##
VirtualApp中实现了一套Xposed接口,用户只要会使用Xposed就可以做到原本需要系统内置Xposed才能做到的事情.
但是用户也需要明白,VA中Xposed的作用域是VA这个APP中的,不能越权控制系统或其他外部App.
VA中提供了一个App创建启动的回调接口`com.lody.virtual.client.core.AppCallback`,接口如下:
```java
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进程说明`](VirualApp进程说明.md),可以知道,
我们只需要在`VAPP进程`回调里(`onVirtualProcess`) 设置App回调 `AppCallback` 就可以达到目的.
> 宿主Application代码,参考[io/busniess/va/App.java](https://github.com/asLody/VirtualApp-Priv/blob/v2.1/VirtualApp/app/src/main/java/io/busniess/va/App.java)
```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](https://github.com/asLody/VirtualApp-Priv/blob/v2.1/VirtualApp/app/src/main/java/io/busniess/va/delegate/MyComponentDelegate.java)类代码
```java
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```代码
```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:
```cpp
_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](https://github.com/asLody/VirtualApp-Priv/wiki)