本文档主要介绍2部分。
第一部分是VA的源码结构介绍,这部分是为了让开发者能快速了解掌握VA源码框架。
第二部分是VA的基础SDK使用说明。
其他更多的开发文档见:VA私有库Wiki
VA产品说明:文档
下面开始第一部分,VA源码结构介绍:
下图是VA源码根目录:
可以看到VA一共有4个源码目录,各个目录介绍如下:
目录名称 | 作用 |
---|---|
app | VA Demo主包源码所在目录 |
app-ext | VA Demo插件包源码所在目录 |
lib | VA库源码所在目录 |
lib-ext | VA插件库源码所在目录 |
配置解释:
配置名称 | 作用 |
---|---|
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库版本代码,开发者一般不需要关心 |
com.lody.virtual.client
包下的代码运行在VAPP Client进程中,主要用于VA Framework中的APP Hook部分,完成对各个Service的HOOK处理
com.lody.virtual.server
包下的代码运行在VA Server进程中,代码主要用于VA Framework中的APP Server部分,实现处理APP安装以及其他不给Android系统处理的APP请求
mirror
包下的代码主要用于对系统隐藏类的引用,属于工具类,减少大量反射代码的编写
cpp
包下的代码进行在VAPP Client进程中,主要用于VA Native部分,实现IO重定向和jni函数HOOK。其中:DelegateApplicationExt.java
运行在VA Host Plugin进程中,用于VA插件包,实现了对主包代码的加载执行
下面开始第二部分,VA SDK使用介绍:
可见多个模块:
- app
- app-ext
- lib
- lib-ext
其中lib和lib-ext属于VirtualApp核心库
以及扩展库
,app和app-ext则属于示例app
。
新建一个application类型的module,并添加lib模块为依赖
implementation project(':lib')
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重复
// ...
}
<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,
]
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
try {
VirtualCore.get().startup(base, mConfig);
} catch (Throwable e) {
e.printStackTrace();
}
}
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;
}
};
@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不同的回调,可以在这里根据进程类型添加额外的初始化代码。
VirtualCore.java
public VAppInstallerResult installPackage(Uri uri, VAppInstallerParams params);
Uri决定了需要安装的apk的来源,目前支持 package 和 file 协议。
Uri packageUri = Uri.parse("package:com.hello.world");
File apkFile = new File("/sdcard/test.apk");
Uri packageUri = Uri.fromFile(apkFile);
package协议 安装app,只需要传入包名,不需要具体的APK路径,所以以这种协议安装的app,相当于双开。
app会随外部版本的升级而自动升级,随外部版本的卸载而自动卸载。PackageSetting
中的 dynamic
为 true
。
file协议 则是内部安装,apk会被复制到容器内部,与外部版本完全独立. PackageSetting
中的 dynamic
为 false
。
FLAG | 说明 |
---|---|
FLAG_INSTALL_OVERRIDE_NO_CHECK | 允许覆盖安装 |
FLAG_INSTALL_OVERRIDE_FORBIDDEN | 禁止覆盖安装 |
FLAG_INSTALL_OVERRIDE_DONT_KILL_APP | 覆盖安装不kill已经启动的APP |
FLAG | 说明 |
---|---|
MODE_FULL_INSTALL | 完整安装 |
MODE_INHERIT_EXISTING | 已安装的的安装模式。预留 |
预留参数,暂时未使用。目前不管设置哪种都一样。
指定app的abi。特殊需求下,可以强制指定app在指定abi下运行。不指定的情况下默认根据系统规则
来决定运行的abi。
可选参数:
- armeabi
- armeabi-v7a
- arm64-v8a
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.");
}
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.");
}
先安装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);
}
}
// class VActivityManager
public boolean launchApp(final int userId, String packageName)
实例代码:
VActivityManager.get().launchApp(0, "com.tencent.mobileqq");
// 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();
// 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");
// class VirtualCore
public List<InstalledAppInfo> getInstalledApps(int flags)
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());
}
});
}
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
原来怎么用,这里也一样使用.
对于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
}