Android 开发工作生活

VirtualApp 框架浅析

2019-07-04  本文已影响0人  Cat9527

Github地址:VirtualApp

简介

VirtualApp是一款运行于Android系统的沙盒产品,可以理解为轻量级的“Android虚拟机”。其产品形态为高可扩展,可定制的集成SDK,您可以基于VA或者使用VA定制开发各种看似不可能完成的项目。VA目前被广泛应用于插件化开发、无感知热更新、云控自动化、多开、手游租号、手游手柄免激活、区块链、移动办公安全、军队政府保密、手机模拟信息、脚本自动化、自动化测试等技术领域。

VirtualApp可以创建一个虚拟空间,你可以在虚拟空间内任意的安装、启动和卸载APK,这一切都与外部隔离,如同一个沙盒,APK无需在外部安装。

VirtualApp的特有能力

运行机制

首先,我们来看一下它在开启APP后的进程信息。

USER           PID  PPID     VSZ    RSS WCHAN            ADDR S NAME                       
u0_a645        435   501 2125236 285608 0                   0 S com.duowan.kiwi:yyPushService
u0_a645      24705   501 1875532  22620 0                   0 S io.virtualapp
u0_a645      24761   501 1831868  22728 0                   0 S io.virtualapp:x
u0_a645      26243   501 2770772 147752 0                   0 S com.duowan.kiwi

可以看到,所有被ViralApp打开的应用,都和VirtalApp属于同一个uid:u0_a645。其中,VirtualApp本身有两个进程:io.virtualappio.virtualapp:x

以这里安装的虎牙直播为例,查看一下它的进程的内存空间,可以看到相关路径全都被映射到了/data/data/io.virtualapp/virtual下面。

7f8a8000-7f8a9000 rw-p 00095000 b3:1c 261396     /data/data/io.virtualapp/virtual/data/app/com.duowan.kiwi/lib/libtrustdevice.so
8393a000-83980000 r--s 00000000 b3:1c 263573     /data/data/io.virtualapp/virtual/data/user/0/com.duowan.kiwi/app_tbs/core_share/libresources.so
840fe000-840ff000 rw-p 00027000 b3:1c 261344     /data/data/io.virtualapp/virtual/data/app/com.duowan.kiwi/lib/libjscexecutor.so
86905000-8691e000 rw-p 0256f000 b3:1c 263574     /data/data/io.virtualapp/virtual/data/user/0/com.duowan.kiwi/app_tbs/core_share/libmttwebview.so
86c10000-86c13000 r-xp 00000000 b3:1c 263579     /data/data/io.virtualapp/virtual/data/user/0/com.duowan.kiwi/app_tbs/core_share/libqb_keystore.so
86d3e000-86d56000 r-xp 00000000 b3:1c 261313     /data/data/io.virtualapp/virtual/data/app/com.duowan.kiwi/lib/libsecurityenv.so
8704d000-87050000 r-xp 00000000 b3:1c 263567     /data/data/io.virtualapp/virtual/data/user/0/com.duowan.kiwi/app_tbs/core_share/libmttwebview_plat_support.so
87375000-87421000 r-xp 00000000 b3:1c 261301     /data/data/io.virtualapp/virtual/data/app/com.duowan.kiwi/lib/libgnustl_shared.so
87531000-87c29000 r-xp 00000000 b3:1c 261321     /data/data/io.virtualapp/virtual/data/app/com.duowan.kiwi/lib/libjsc.so
88386000-883a1000 r--p 00000000 b3:1c 263621     /data/data/io.virtualapp/virtual/data/user/0/com.duowan.kiwi/app_tbs/core_share/asr_base_dex.dex
89e4f000-8a6cb000 r--p 00000000 b3:1c 263600     /data/data/io.virtualapp/virtual/data/user/0/com.duowan.kiwi/app_tbs/core_share/tbs_jars_fusion_dex.dex

可见,这里面对路径做过了重新映射。

注入逻辑

要想实现对一个APP的虚拟化,就是不直接把APP安装进系统,同时又要提供APP运行过程中所需的一切,从而可以让它误以为自己是运行在正常系统中。这里就需要实现系统服务的虚拟化和相关路径的虚拟化。

其中,系统服务的虚拟化主要靠注入大量framework组件来实现的。

@VirtualApp/lib/src/main/java/com/lody/virtual/client/core/InvocationStubManager.java
private void injectInternal() throws Throwable {
  if (VirtualCore.get().isMainProcess()) {
    return;
  }
  if (VirtualCore.get().isServerProcess()) {
    addInjector(new ActivityManagerStub());
    addInjector(new PackageManagerStub());
    return;
  }
  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());
    addInjector(new NotificationManagerStub());
    addInjector(new LocationManagerStub());
    addInjector(new WindowManagerStub());
    addInjector(new ClipBoardStub());
    addInjector(new MountServiceStub());
    addInjector(new BackupManagerStub());
    addInjector(new TelephonyStub());
    addInjector(new TelephonyRegistryStub());
    addInjector(new PhoneSubInfoStub());
    addInjector(new PowerManagerStub());
    addInjector(new AppWidgetManagerStub());
    addInjector(new AccountManagerStub());
    addInjector(new AudioManagerStub());
    addInjector(new SearchManagerStub());
    addInjector(new ContentServiceStub());
    addInjector(new ConnectivityStub());

    if (Build.VERSION.SDK_INT >= JELLY_BEAN_MR2) {
      addInjector(new VibratorStub());
      addInjector(new WifiManagerStub());
      addInjector(new BluetoothStub());
      addInjector(new ContextHubServiceStub());
    }
    if (Build.VERSION.SDK_INT >= JELLY_BEAN_MR1) {
      addInjector(new UserManagerStub());
    }

    if (Build.VERSION.SDK_INT >= JELLY_BEAN_MR1) {
      addInjector(new DisplayStub());
    }
    if (Build.VERSION.SDK_INT >= LOLLIPOP) {
      addInjector(new PersistentDataBlockServiceStub());
      addInjector(new InputMethodManagerStub());
      addInjector(new MmsStub());
      addInjector(new SessionManagerStub());
      addInjector(new JobServiceStub());
      addInjector(new RestrictionStub());
    }
    if (Build.VERSION.SDK_INT >= KITKAT) {
      addInjector(new AlarmManagerStub());
      addInjector(new AppOpsManagerStub());
      addInjector(new MediaRouterServiceStub());
    }
    if (Build.VERSION.SDK_INT >= LOLLIPOP_MR1) {
      addInjector(new GraphicsStatsStub());
    }
    if (Build.VERSION.SDK_INT >= M) {
      addInjector(new NetworkManagementStub());
    }
    if (Build.VERSION.SDK_INT >= N) {
              addInjector(new WifiScannerStub());
              addInjector(new ShortcutServiceStub());
          }
  }
}

这个注入过程是发生在io.virtualapp.VApp.attachBaseContext中,因此,每次启动一个子进程都会执行到这里,这会区分是isMainProcess(io.virtualapp)或者isServerProcess(io.virtualapp:x)或者isVAppProcess(被安装APP)来进行不同的注入,可以看到,注入最多的还是在被安装APP的进程中。

可以看到,之前在injectInternal 中addInjector的所有Stub都会调用它的inject方法。

VirtualApp/lib/src/main/java/com/lody/virtual/client/core/InvocationStubManager.java

void injectAll() throws Throwable {
  for (IInjector injector : mInjectors.values()) {
    injector.inject();
  }
  // XXX: Lazy inject the Instrumentation,
  addInjector(AppInstrumentation.getDefault());
}

由此实现对各个系统类的替换。

而在底层,VirtualApp还实现了对原本路径的替换,在java层传入需要重定向的所有路径。

private void startIOUniformer() {
        ApplicationInfo info = mBoundApplication.appInfo;
        int userId = VUserHandle.myUserId();
        String wifiMacAddressFile = deviceInfo.getWifiFile(userId).getPath();
        NativeEngine.redirectDirectory("/sys/class/net/wlan0/address", wifiMacAddressFile);
        NativeEngine.redirectDirectory("/sys/class/net/eth0/address", wifiMacAddressFile);
        NativeEngine.redirectDirectory("/sys/class/net/wifi/address", wifiMacAddressFile);
        NativeEngine.redirectDirectory("/data/data/" + info.packageName, info.dataDir);
        NativeEngine.redirectDirectory("/data/user/0/" + info.packageName, info.dataDir);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            NativeEngine.redirectDirectory("/data/user_de/0/" + info.packageName, info.dataDir);
        }
        String libPath = new File(VEnvironment.getDataAppPackageDirectory(info.packageName), "lib").getAbsolutePath();
        String userLibPath = new File(VEnvironment.getUserSystemDirectory(userId), "lib").getAbsolutePath();
        NativeEngine.redirectDirectory(userLibPath, libPath);
        NativeEngine.redirectDirectory("/data/data/" + info.packageName + "/lib/", libPath);
        NativeEngine.redirectDirectory("/data/user/0/" + info.packageName + "/lib/", libPath);

        NativeEngine.readOnly(VEnvironment.getDataAppDirectory().getPath());
        VirtualStorageManager vsManager = VirtualStorageManager.get();
        String vsPath = vsManager.getVirtualStorage(info.packageName, userId);
        boolean enable = vsManager.isVirtualStorageEnable(info.packageName, userId);
        if (enable && vsPath != null) {
            File vsDirectory = new File(vsPath);
            if (vsDirectory.exists() || vsDirectory.mkdirs()) {
                HashSet<String> mountPoints = getMountPoints();
                for (String mountPoint : mountPoints) {
                    NativeEngine.redirectDirectory(mountPoint, vsPath);
                }
            }
        }
        NativeEngine.hook();
    }

这些路径最终会添加进JNI层的一个映射表中

void IOUniformer::redirect(const char *orig_path, const char *new_path) {
    LOGI("Start Java_nativeRedirect : from %s to %s", orig_path, new_path);
    add_pair(orig_path, new_path);
}

static void add_pair(const char *_orig_path, const char *_new_path) {
    std::string origPath = std::string(_orig_path);
    std::string newPath = std::string(_new_path);
    IORedirectMap.insert(std::pair<std::string, std::string>(origPath, newPath));
    if (endWith(origPath, '/')) {
        RootIORedirectMap.insert(
                std::pair<std::string, std::string>(
                        origPath.substr(0, origPath.length() - 1),
                        newPath.substr(0, newPath.length() - 1))
        );
    }
}

然后,会hook所有的c库函数,这些函数在调用的时候,就会替换路径为新路径。由于hook的是libc的函数,java层和虚拟机的文件访问最终也会调用到这里,从而受到影响。

void IOUniformer::startUniformer(int api_level, int preview_api_level) {
    gVars.hooked_process = true;
    HOOK_SYMBOL(RTLD_DEFAULT, vfork);
    HOOK_SYMBOL(RTLD_DEFAULT, kill);
    HOOK_SYMBOL(RTLD_DEFAULT, __getcwd);
    HOOK_SYMBOL(RTLD_DEFAULT, truncate);
    HOOK_SYMBOL(RTLD_DEFAULT, __statfs64);
    HOOK_SYMBOL(RTLD_DEFAULT, execve);
    HOOK_SYMBOL(RTLD_DEFAULT, __open);
    if ((api_level < 25) || (api_level == 25 && preview_api_level == 0)) {
        HOOK_SYMBOL(RTLD_DEFAULT, utimes);
        HOOK_SYMBOL(RTLD_DEFAULT, mkdir);
        HOOK_SYMBOL(RTLD_DEFAULT, chmod);
        HOOK_SYMBOL(RTLD_DEFAULT, lstat);
        HOOK_SYMBOL(RTLD_DEFAULT, link);
        HOOK_SYMBOL(RTLD_DEFAULT, symlink);
        HOOK_SYMBOL(RTLD_DEFAULT, mknod);
        HOOK_SYMBOL(RTLD_DEFAULT, rmdir);
        HOOK_SYMBOL(RTLD_DEFAULT, chown);
        HOOK_SYMBOL(RTLD_DEFAULT, rename);
        HOOK_SYMBOL(RTLD_DEFAULT, stat);
        HOOK_SYMBOL(RTLD_DEFAULT, chdir);
        HOOK_SYMBOL(RTLD_DEFAULT, access);
        HOOK_SYMBOL(RTLD_DEFAULT, readlink);
        HOOK_SYMBOL(RTLD_DEFAULT, unlink);
    }
    HOOK_SYMBOL(RTLD_DEFAULT, fstatat);
    HOOK_SYMBOL(RTLD_DEFAULT, fchmodat);
    HOOK_SYMBOL(RTLD_DEFAULT, symlinkat);
    HOOK_SYMBOL(RTLD_DEFAULT, readlinkat);
    HOOK_SYMBOL(RTLD_DEFAULT, unlinkat);
    HOOK_SYMBOL(RTLD_DEFAULT, linkat);
    HOOK_SYMBOL(RTLD_DEFAULT, utimensat);
    HOOK_SYMBOL(RTLD_DEFAULT, __openat);
    HOOK_SYMBOL(RTLD_DEFAULT, faccessat);
    HOOK_SYMBOL(RTLD_DEFAULT, mkdirat);
    HOOK_SYMBOL(RTLD_DEFAULT, renameat);
    HOOK_SYMBOL(RTLD_DEFAULT, fchownat);
    HOOK_SYMBOL(RTLD_DEFAULT, mknodat);
//    hook_dlopen(api_level);

#if defined(__i386__) || defined(__x86_64__)
    // Do nothing
#else
    GodinHook::NativeHook::hookAllRegistered();
#endif
}

以chmod函数为例

// int chmod(const char *path, mode_t mode);
HOOK_DEF(int, chmod, const char *pathname, mode_t mode) {
    const char *redirect_path = match_redirected_path(pathname);
    if (isReadOnlyPath(redirect_path)) {
        return -1;
    }
    int ret = syscall(__NR_chmod, redirect_path, mode);
    FREE(redirect_path, pathname);
    return ret;
}

可以看到,它会把原先的pathname,通过match_redirected_path找到映射后的新路径,然后用syscall来调用它,这样就实现了所有路径的重定向。

运行时结构

VA 参照原生系统 framework 仿造了一套 framework service,还有配套在 client 端的 framework 库。

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

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());
            .....................
         }
    }

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

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 的安装主要完成以下几件事情:

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 的组件信息。

Client App 启动

首先要了解的是 Android App 是组件化的,Apk 其实是 N 多个组件的集合,以及一些资源文件和 Assert,App 的启动有多种情况,只要在一个新的进程中调起了 apk 中任何一个组件,App 将被初始化,Application 将被初始化。

Activity 启动

Hook startActivity(重定位 Intent 到 StubActivity)

首先在 Client App 中,startActivity 方法必须被 Hook 掉,不然 Client App 调用 startActivity 就直指外部 Activity 去了。

这部分的原理其实与 DroidPlugin 大同小异,由于插件(Client App)中的 Activity 是没有在 AMS 中注册的,AMS 自然无法找到我们的插件 Activity。

Hook 的目的是我们拿到用户的 Intent,把他替换成指向 VA 在 Menifest 中站好坑的 StubActivity 的 Intent,然后将原 Intent 当作 data 打包进新 Intent 以便以后流程再次进入 VA 时恢复。

Hook 的方法就是用我们动态代理生成的代理类对象替换系统原来的 ActiityManagerNative.geDefault 对象。

public void inject() throws Throwable {
        if (BuildCompat.isOreo()) {
            //Android Oreo(8.X)
            Object singleton = ActivityManagerOreo.IActivityManagerSingleton.get();
            Singleton.mInstance.set(singleton, getInvocationStub().getProxyInterface());
        } else {
            if (ActivityManagerNative.gDefault.type() == IActivityManager.TYPE) {
                ActivityManagerNative.gDefault.set(getInvocationStub().getProxyInterface());
            } else if (ActivityManagerNative.gDefault.type() == Singleton.TYPE) {
                Object gDefault = ActivityManagerNative.gDefault.get();
                Singleton.mInstance.set(gDefault, getInvocationStub().getProxyInterface());
            }
        }
        BinderInvocationStub hookAMBinder = new BinderInvocationStub(getInvocationStub().getBaseInterface());
        hookAMBinder.copyMethodProxies(getInvocationStub());
        ServiceManager.sCache.get().put(Context.ACTIVITY_SERVICE, hookAMBinder);
    }

好了,下面只要调用到 startActivity 就会被 Hook 到 call。
这个函数需要注意以下几点:

static class StartActivity extends MethodProxy {

        private static final String SCHEME_FILE = "file";
        private static final String SCHEME_PACKAGE = "package";

        @Override
        public String getMethodName() {
            return "startActivity";
        }

        @Override
        public Object call(Object who, Method method, Object... args) throws Throwable {
            int intentIndex = ArrayUtils.indexOfObject(args, Intent.class, 1);
            if (intentIndex < 0) {
                return ActivityManagerCompat.START_INTENT_NOT_RESOLVED;
            }
            int resultToIndex = ArrayUtils.indexOfObject(args, IBinder.class, 2);
            String resolvedType = (String) args[intentIndex + 1];
            Intent intent = (Intent) args[intentIndex];
            intent.setDataAndType(intent.getData(), resolvedType);
            IBinder resultTo = resultToIndex >= 0 ? (IBinder) args[resultToIndex] : null;
            int userId = VUserHandle.myUserId();

            if (ComponentUtils.isStubComponent(intent)) {
                return method.invoke(who, args);
            }

            // 请求安装和卸载界面
            if (Intent.ACTION_INSTALL_PACKAGE.equals(intent.getAction())
                    || (Intent.ACTION_VIEW.equals(intent.getAction())
                    && "application/vnd.android.package-archive".equals(intent.getType()))) {
                if (handleInstallRequest(intent)) {
                    return 0;
                }
            } else if ((Intent.ACTION_UNINSTALL_PACKAGE.equals(intent.getAction())
                    || Intent.ACTION_DELETE.equals(intent.getAction()))
                    && "package".equals(intent.getScheme())) {

                if (handleUninstallRequest(intent)) {
                    return 0;
                }
            }

            String resultWho = null;
            int requestCode = 0;
            Bundle options = ArrayUtils.getFirst(args, Bundle.class);
            if (resultTo != null) {
                resultWho = (String) args[resultToIndex + 1];
                requestCode = (int) args[resultToIndex + 2];
            }
            // chooser 调用选择界面
            if (ChooserActivity.check(intent)) {
                intent.setComponent(new ComponentName(getHostContext(), ChooserActivity.class));
                intent.putExtra(Constants.EXTRA_USER_HANDLE, userId);
                intent.putExtra(ChooserActivity.EXTRA_DATA, options);
                intent.putExtra(ChooserActivity.EXTRA_WHO, resultWho);
                intent.putExtra(ChooserActivity.EXTRA_REQUEST_CODE, requestCode);
                return method.invoke(who, args);
            }

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
                args[intentIndex - 1] = getHostPkg();
            }

            //解析 ActivityInfo
            ActivityInfo activityInfo = VirtualCore.get().resolveActivityInfo(intent, userId);
            if (activityInfo == null) {
                VLog.e("VActivityManager", "Unable to resolve activityInfo : " + intent);
                if (intent.getPackage() != null && isAppPkg(intent.getPackage())) {
                    return ActivityManagerCompat.START_INTENT_NOT_RESOLVED;
                }
                return method.invoke(who, args);
            }

            // 调用远程 VAMS.startActivity
            int res = VActivityManager.get().startActivity(intent, activityInfo, resultTo, options, resultWho, requestCode, VUserHandle.myUserId());
            if (res != 0 && resultTo != null && requestCode > 0) {
                VActivityManager.get().sendActivityResult(resultTo, resultWho, requestCode);
            }

            // 处理 Activity 切换动画,因为此时动画还是 Host 的 Stub Activity 默认动画,需要覆盖成子程序包的动画
            if (resultTo != null) {
                ActivityClientRecord r = VActivityManager.get().getActivityRecord(resultTo);
                if (r != null && r.activity != null) {
                    try {
                        TypedValue out = new TypedValue();
                        Resources.Theme theme = r.activity.getResources().newTheme();
                        theme.applyStyle(activityInfo.getThemeResource(), true);
                        if (theme.resolveAttribute(android.R.attr.windowAnimationStyle, out, true)) {

                            TypedArray array = theme.obtainStyledAttributes(out.data,
                                    new int[]{
                                            android.R.attr.activityOpenEnterAnimation,
                                            android.R.attr.activityOpenExitAnimation
                                    });

                            r.activity.overridePendingTransition(array.getResourceId(0, 0), array.getResourceId(1, 0));
                            array.recycle();
                        }
                    } catch (Throwable e) {
                        // Ignore
                    }
                }
            }
            return res;
        }


        private boolean handleInstallRequest(Intent intent) {
            IAppRequestListener listener = VirtualCore.get().getAppRequestListener();
            if (listener != null) {
                Uri packageUri = intent.getData();
                if (SCHEME_FILE.equals(packageUri.getScheme())) {
                    File sourceFile = new File(packageUri.getPath());
                    try {
                        listener.onRequestInstall(sourceFile.getPath());
                        return true;
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }

            }
            return false;
        }

        private boolean handleUninstallRequest(Intent intent) {
            IAppRequestListener listener = VirtualCore.get().getAppRequestListener();
            if (listener != null) {
                Uri packageUri = intent.getData();
                if (SCHEME_PACKAGE.equals(packageUri.getScheme())) {
                    String pkg = packageUri.getSchemeSpecificPart();
                    try {
                        listener.onRequestUninstall(pkg);
                        return true;
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }

            }
            return false;
        }

    }

逻辑最终走到 VAMS 后,VAMS 调用 ActivityStack.startActivityLocked

// 参考 framework 的实现
    int startActivityLocked(int userId, Intent intent, ActivityInfo info, IBinder resultTo, Bundle options,
                            String resultWho, int requestCode) {
        optimizeTasksLocked();

        Intent destIntent;
        ActivityRecord sourceRecord = findActivityByToken(userId, resultTo);
        TaskRecord sourceTask = sourceRecord != null ? sourceRecord.task : null;

        // 忽略一大堆对 Flag 的处理
        .............................

        String affinity = ComponentUtils.getTaskAffinity(info);

        // 根据 Flag 寻找合适的 Task
        TaskRecord reuseTask = null;
        switch (reuseTarget) {
            case AFFINITY:
                reuseTask = findTaskByAffinityLocked(userId, affinity);
                break;
            case DOCUMENT:
                reuseTask = findTaskByIntentLocked(userId, intent);
                break;
            case CURRENT:
                reuseTask = sourceTask;
                break;
            default:
                break;
        }

        boolean taskMarked = false;
        if (reuseTask == null) {
            startActivityInNewTaskLocked(userId, intent, info, options);
        } else {
            boolean delivered = false;
            mAM.moveTaskToFront(reuseTask.taskId, 0);
            boolean startTaskToFront = !clearTask && !clearTop && ComponentUtils.isSameIntent(intent, reuseTask.taskRoot);

            if (clearTarget.deliverIntent || singleTop) {
                taskMarked = markTaskByClearTarget(reuseTask, clearTarget, intent.getComponent());
                ActivityRecord topRecord = topActivityInTask(reuseTask);
                if (clearTop && !singleTop && topRecord != null && taskMarked) {
                    topRecord.marked = true;
                }
                // Target activity is on top
                if (topRecord != null && !topRecord.marked && topRecord.component.equals(intent.getComponent())) {
                    deliverNewIntentLocked(sourceRecord, topRecord, intent);
                    delivered = true;
                }
            }
            if (taskMarked) {
                synchronized (mHistory) {
                    scheduleFinishMarkedActivityLocked();
                }
            }
            if (!startTaskToFront) {
                if (!delivered) {
                    destIntent = startActivityProcess(userId, sourceRecord, intent, info);
                    if (destIntent != null) {
                        startActivityFromSourceTask(reuseTask, destIntent, info, resultWho, requestCode, options);
                    }
                }
            }
        }
        return 0;
    }

然后 call 到了 startActivityProcess ,这就是真正替换 Intent 的地方

private Intent startActivityProcess(int userId, ActivityRecord sourceRecord, Intent intent, ActivityInfo info) {
        intent = new Intent(intent);
        // 获得 Activity 对应的 ProcessRecorder,如果没有则表示这是 Process 第一个打开的组件,需要初始化 Application
        ProcessRecord targetApp = mService.startProcessIfNeedLocked(info.processName, userId, info.packageName);
        if (targetApp == null) {
            return null;
        }
        Intent targetIntent = new Intent();

        // 根据 Client App 的 PID 获取 StubActivity
        String stubActivityPath = fetchStubActivity(targetApp.vpid, info);

        Log.e("gy", "map activity:" + intent.getComponent().getClassName() + " -> " + stubActivityPath);

        targetIntent.setClassName(VirtualCore.get().getHostPkg(), stubActivityPath);
        ComponentName component = intent.getComponent();
        if (component == null) {
            component = ComponentUtils.toComponentName(info);
        }
        targetIntent.setType(component.flattenToString());
        StubActivityRecord saveInstance = new StubActivityRecord(intent, info,
                sourceRecord != null ? sourceRecord.component : null, userId);
        saveInstance.saveToIntent(targetIntent);
        return targetIntent;
    }

fetchStubActivity 会根据相同的进程 id 在 VA 的 Menifest 中找到那个提前占坑的 StubActivity

private String fetchStubActivity(int vpid, ActivityInfo targetInfo) {

        boolean isFloating = false;
        boolean isTranslucent = false;
        boolean showWallpaper = false;
        try {
            int[] R_Styleable_Window = R_Hide.styleable.Window.get();
            int R_Styleable_Window_windowIsTranslucent = R_Hide.styleable.Window_windowIsTranslucent.get();
            int R_Styleable_Window_windowIsFloating = R_Hide.styleable.Window_windowIsFloating.get();
            int R_Styleable_Window_windowShowWallpaper = R_Hide.styleable.Window_windowShowWallpaper.get();

            AttributeCache.Entry ent = AttributeCache.instance().get(targetInfo.packageName, targetInfo.theme,
                    R_Styleable_Window);
            if (ent != null && ent.array != null) {
                showWallpaper = ent.array.getBoolean(R_Styleable_Window_windowShowWallpaper, false);
                isTranslucent = ent.array.getBoolean(R_Styleable_Window_windowIsTranslucent, false);
                isFloating = ent.array.getBoolean(R_Styleable_Window_windowIsFloating, false);
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }

        boolean isDialogStyle = isFloating || isTranslucent || showWallpaper;

        // 根据在 Menifest 中注册的 pid
        if (isDialogStyle) {
            return VASettings.getStubDialogName(vpid);
        } else {
            return VASettings.getStubActivityName(vpid);
        }
    }

这里需要特别注意,VA 占坑的方式和 DroidPlugin 有些小不同,VA 没有为每个 Process 注册多个 Activity,也没有为不同的启动方式注册多个 Activity,这里确实是有改进的。
这里根本原因是因为 VA 对 VAMS 实现的更为完整,实现了原版 AMS 的基本功能,包括完整的 Recorder 管理,Task Stack 管理等,这样的话 StubActivity 的唯一作用便是携带 Client App 真正的 Intent 交给 VAMS 处理。这套机制衍生到其他的组件也是一样的。

最终, VAMS 调用原生 AM 的 startActivity 向真正的 AMS 发送替换成 StubActivity 的伪造 Intent。

private void startActivityFromSourceTask(TaskRecord task, Intent intent, ActivityInfo info, String resultWho,
                                             int requestCode, Bundle options) {
        ActivityRecord top = task.activities.isEmpty() ? null : task.activities.get(task.activities.size() - 1);
        if (top != null) {
            if (startActivityProcess(task.userId, top, intent, info) != null) {
                realStartActivityLocked(top.token, intent, resultWho, requestCode, options);
            }
        }
    }

private void realStartActivityLocked(IBinder resultTo, Intent intent, String resultWho, int requestCode,
                                         Bundle options) {
        Class<?>[] types = mirror.android.app.IActivityManager.startActivity.paramList();
        Object[] args = new Object[types.length];
        if (types[0] == IApplicationThread.TYPE) {
            args[0] = ActivityThread.getApplicationThread.call(VirtualCore.mainThread());
        }
        int intentIndex = ArrayUtils.protoIndexOf(types, Intent.class);
        int resultToIndex = ArrayUtils.protoIndexOf(types, IBinder.class, 2);
        int optionsIndex = ArrayUtils.protoIndexOf(types, Bundle.class);
        int resolvedTypeIndex = intentIndex + 1;
        int resultWhoIndex = resultToIndex + 1;
        int requestCodeIndex = resultToIndex + 2;

        args[intentIndex] = intent;
        args[resultToIndex] = resultTo;
        args[resultWhoIndex] = resultWho;
        args[requestCodeIndex] = requestCode;
        if (optionsIndex != -1) {
            args[optionsIndex] = options;
        }
        args[resolvedTypeIndex] = intent.getType();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            args[intentIndex - 1] = VirtualCore.get().getHostPkg();
        }
        ClassUtils.fixArgs(types, args);

        mirror.android.app.IActivityManager.startActivity.call(ActivityManagerNative.getDefault.call(),
                (Object[]) args);
    }

恢复原 Intent 重定向到原 Activity

当 AMS 收到伪装的 Intent 后,就会找到 StubActivity,这时流程回到 VA 里的主线程中的消息队列中。
Hook 过程就是用我们自己的 Handler 替换 android.os.Handler.mCallback 因为主线程在这里分发一些操作。

public void inject() throws Throwable {
    otherCallback = getHCallback();
    mirror.android.os.Handler.mCallback.set(getH(), this);
 }

handlerMessage 判断是 LAUNCH_ACTIVITY Action 后直接调用了 handlerLaunchActivity 方法,和原版其实很像。

private boolean handleLaunchActivity(Message msg) {
            Object r = msg.obj;
            Intent stubIntent = ActivityThread.ActivityClientRecord.intent.get(r);
            // 获取原版 Intent 信息
            StubActivityRecord saveInstance = new StubActivityRecord(stubIntent);
            if (saveInstance.intent == null) {
                return true;
            }
            // 原版 Intent
            Intent intent = saveInstance.intent;
            ComponentName caller = saveInstance.caller;
            IBinder token = ActivityThread.ActivityClientRecord.token.get(r);
            ActivityInfo info = saveInstance.info;

            // 如果 token 还没初始化,代表 App 刚刚启动第一个组件
            if (VClientImpl.get().getToken() == null) {
                VActivityManager.get().processRestarted(info.packageName, info.processName, saveInstance.userId);
                getH().sendMessageAtFrontOfQueue(Message.obtain(msg));
                return false;
            }
            // AppBindData 为空,则 App 信息不明
            if (!VClientImpl.get().isBound()) {
                // 初始化并绑定 Application
                VClientImpl.get().bindApplication(info.packageName, info.processName);
                getH().sendMessageAtFrontOfQueue(Message.obtain(msg));
                return false;
            }

            // 获取 TaskId
            int taskId = IActivityManager.getTaskForActivity.call(
                    ActivityManagerNative.getDefault.call(),
                    token,
                    false
            );

            // 1.将 ActivityRecorder 加入 mActivities 2.通知服务端 VAMS Activity 创建完成
            VActivityManager.get().onActivityCreate(ComponentUtils.toComponentName(info), caller, token, info, intent, ComponentUtils.getTaskAffinity(info), taskId, info.launchMode, info.flags);
            ClassLoader appClassLoader = VClientImpl.get().getClassLoader(info.applicationInfo);
            intent.setExtrasClassLoader(appClassLoader);
            // 将 Host Stub Activity Intent 替换为原版 Intent
            ActivityThread.ActivityClientRecord.intent.set(r, intent);
            // 同上
            ActivityThread.ActivityClientRecord.activityInfo.set(r, info);
            return true;
        }

最后成功从 StubActivity Intent 还原出来的原版 Intent 被继续交给原生的 AM

// 将 Host Stub Activity Intent 替换为原版 Intent
ActivityThread.ActivityClientRecord.intent.set(r, intent);
// 同上
ActivityThread.ActivityClientRecord.activityInfo.set(r, info);

最后一个 Hook 点在 Instrumentation.callActivityOnCreate:
因为 AMS 实际上启动的是 StubActivity 的关系,真正的 Activity 的一些信息还不是其真正的信息,比如主题之类的,所以需要在这个时机修复一下,选择这个时间修复的原因也是因为 Activity 已经被 new 出来了,而且资源已经准备完毕。

public void callActivityOnCreate(Activity activity, Bundle icicle) {
        VirtualCore.get().getComponentDelegate().beforeActivityCreate(activity);
        IBinder token = mirror.android.app.Activity.mToken.get(activity);
        ActivityClientRecord r = VActivityManager.get().getActivityRecord(token);
        // 替换 Activity 对象
        if (r != null) {
            r.activity = activity;
        }
        ContextFixer.fixContext(activity);
        ActivityFixer.fixActivity(activity);
        ActivityInfo info = null;
        if (r != null) {
            info = r.info;
        }
        // 设置主题和屏幕纵横控制
        if (info != null) {
            if (info.theme != 0) {
                activity.setTheme(info.theme);
            }
            if (activity.getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
                    && info.screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
                activity.setRequestedOrientation(info.screenOrientation);
            }
        }
        super.callActivityOnCreate(activity, icicle);
        VirtualCore.get().getComponentDelegate().afterActivityCreate(activity);
    }

引用(如有侵权,即刻删除):
Android虚拟化引擎VirtualApp探究
Android 双开沙箱 VirtualApp 源码分析

上一篇下一篇

猜你喜欢

热点阅读