VirtualApp 框架淺析

Github地址:VirtualApp

簡(jiǎn)介

VirtualApp是一款運(yùn)行于Android系統(tǒng)的沙盒產(chǎn)品亏拉,可以理解為輕量級(jí)的“Android虛擬機(jī)”确憨。其產(chǎn)品形態(tài)為高可擴(kuò)展谱姓,可定制的集成SDK,您可以基于VA或者使用VA定制開發(fā)各種看似不可能完成的項(xiàng)目蜒蕾。VA目前被廣泛應(yīng)用于插件化開發(fā)及汉、無(wú)感知熱更新沮趣、云控自動(dòng)化、多開坷随、手游租號(hào)房铭、手游手柄免激活驻龟、區(qū)塊鏈、移動(dòng)辦公安全缸匪、軍隊(duì)政府保密翁狐、手機(jī)模擬信息、腳本自動(dòng)化凌蔬、自動(dòng)化測(cè)試等技術(shù)領(lǐng)域露懒。

VirtualApp可以創(chuàng)建一個(gè)虛擬空間,你可以在虛擬空間內(nèi)任意的安裝砂心、啟動(dòng)和卸載APK隐锭,這一切都與外部隔離,如同一個(gè)沙盒计贰,APK無(wú)需在外部安裝。

VirtualApp的特有能力

  • 克隆能力
    可以克隆外部系統(tǒng)中已經(jīng)安裝的App蒂窒,并在內(nèi)部運(yùn)行躁倒,互不干擾。典型應(yīng)用場(chǎng)景為App雙開洒琢。

  • 免安裝能力
    除了克隆已安裝App之外秧秉,VA可以直接在內(nèi)部安裝(外部無(wú)感知)apk,并在內(nèi)部直接運(yùn)行衰抑。典型應(yīng)用場(chǎng)景為插件化象迎,獨(dú)立應(yīng)用市場(chǎng)等。

  • 多開能力
    VA不僅可以“雙開”呛踊,獨(dú)特的多用戶模式支持用戶在內(nèi)部無(wú)限多開同一個(gè)App砾淌。

  • 內(nèi)外隔離能力
    VA是一個(gè)標(biāo)準(zhǔn)的沙盒,或者說(shuō)“虛擬機(jī)”谭网,提供了一整套內(nèi)部與外部的隔離機(jī)制汪厨,包括但不限于(文件隔離/組件隔離/進(jìn)程通訊隔離),簡(jiǎn)單的說(shuō)VA內(nèi)部就是一個(gè)“完全獨(dú)立的空間”愉择。在此基礎(chǔ)之上劫乱,稍作定制即可實(shí)現(xiàn)一部手機(jī)上的“虛擬手機(jī)”。當(dāng)然您也可以發(fā)揮想象锥涕,定制成應(yīng)用于數(shù)據(jù)加密衷戈,數(shù)據(jù)隔離,隱私保護(hù)层坠,企業(yè)管理的應(yīng)用系統(tǒng)殖妇。

  • 對(duì)于內(nèi)部App的完全控制能力
    VA對(duì)于內(nèi)部的App具有完全的監(jiān)控和控制能力,這點(diǎn)在未Root的外部環(huán)境中是絕對(duì)無(wú)法實(shí)現(xiàn)的窿春。

運(yùn)行機(jī)制

首先拉一,我們來(lái)看一下它在開啟APP后的進(jìn)程信息采盒。

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打開的應(yīng)用蔚润,都和VirtalApp屬于同一個(gè)uid:u0_a645磅氨。其中,VirtualApp本身有兩個(gè)進(jìn)程:io.virtualappio.virtualapp:x

  • io.virtualapp 就是可見的交互界面嫡纠,同時(shí)也負(fù)責(zé)APK包的管理和安裝烦租。
  • io.virtualapp:x 作為一個(gè)單獨(dú)的服務(wù)進(jìn)程,虛擬了一些系統(tǒng)服務(wù)除盏。

以這里安裝的虎牙直播為例叉橱,查看一下它的進(jìn)程的內(nèi)存空間,可以看到相關(guān)路徑全都被映射到了/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

可見窃祝,這里面對(duì)路徑做過(guò)了重新映射。

注入邏輯

要想實(shí)現(xiàn)對(duì)一個(gè)APP的虛擬化踱侣,就是不直接把APP安裝進(jìn)系統(tǒng)粪小,同時(shí)又要提供APP運(yùn)行過(guò)程中所需的一切,從而可以讓它誤以為自己是運(yùn)行在正常系統(tǒng)中抡句。這里就需要實(shí)現(xiàn)系統(tǒng)服務(wù)的虛擬化和相關(guān)路徑的虛擬化探膊。

其中,系統(tǒng)服務(wù)的虛擬化主要靠注入大量framework組件來(lái)實(shí)現(xiàn)的待榔。

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

這個(gè)注入過(guò)程是發(fā)生在io.virtualapp.VApp.attachBaseContext中逞壁,因此,每次啟動(dòng)一個(gè)子進(jìn)程都會(huì)執(zhí)行到這里锐锣,這會(huì)區(qū)分是isMainProcess(io.virtualapp)或者isServerProcess(io.virtualapp:x)或者isVAppProcess(被安裝APP)來(lái)進(jìn)行不同的注入腌闯,可以看到,注入最多的還是在被安裝APP的進(jìn)程中刺下。

可以看到绑嘹,之前在injectInternal 中addInjector的所有Stub都會(huì)調(diào)用它的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());
}

由此實(shí)現(xiàn)對(duì)各個(gè)系統(tǒng)類的替換橘茉。

而在底層工腋,VirtualApp還實(shí)現(xiàn)了對(duì)原本路徑的替換,在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();
    }

這些路徑最終會(huì)添加進(jìn)JNI層的一個(gè)映射表中

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

然后擅腰,會(huì)hook所有的c庫(kù)函數(shù),這些函數(shù)在調(diào)用的時(shí)候翁潘,就會(huì)替換路徑為新路徑趁冈。由于hook的是libc的函數(shù),java層和虛擬機(jī)的文件訪問(wèn)最終也會(huì)調(diào)用到這里,從而受到影響渗勘。

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函數(shù)為例

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

可以看到沐绒,它會(huì)把原先的pathname,通過(guò)match_redirected_path找到映射后的新路徑旺坠,然后用syscall來(lái)調(diào)用它乔遮,這樣就實(shí)現(xiàn)了所有路徑的重定向。

運(yùn)行時(shí)結(jié)構(gòu)

VA 參照原生系統(tǒng) framework 仿造了一套 framework service取刃,還有配套在 client 端的 framework 庫(kù)蹋肮。

  • 系統(tǒng)原生的 framework 運(yùn)作方式
    簡(jiǎn)單來(lái)說(shuō),我們平時(shí)所用到的 app 運(yùn)行空間中的 framework api 最終會(huì)通過(guò) Binder 遠(yuǎn)程調(diào)用到 framework service 空間的遠(yuǎn)程服務(wù)璧疗。
    而遠(yuǎn)程服務(wù)類似 AMS 中的 Recoder 中會(huì)持有 app 空間的 Ibinder token 句柄坯辩,通過(guò) token 也可以讓 framework service 遠(yuǎn)程調(diào)用到 app 空間。
  • VA 環(huán)境下framework 運(yùn)作方式
    而在 VA 環(huán)境下崩侠,情況其實(shí)也是類似漆魔,只不過(guò)在 framework service 和 client app 之間還有另外一個(gè) VA 實(shí)現(xiàn)的 VAService,VAService 仿造了 framework service 的一些功能却音。
    因?yàn)樵?VA 中運(yùn)行的 Client App 都是沒有(也不能注冊(cè))在 framework service 的有送,注冊(cè)的只有 VA 預(yù)先注冊(cè)在 Menifest 中的 Stub 而已。所以 frameservice 是無(wú)法像普通 App 一樣管理 VA Client App 的會(huì)話的僧家。
    這就要依靠 VA 仿造的另外一套 VAService 完成對(duì) VA 中 Client App 的會(huì)話管理了。

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 實(shí)例
            mainThread = ActivityThread.currentActivityThread.call();
            unHookPackageManager = context.getPackageManager();
            hostPkgInfo = unHookPackageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_PROVIDERS);
            detectProcessType();
            // hook 系統(tǒng)類
            InvocationStubManager invocationStubManager = InvocationStubManager.getInstance();
            invocationStubManager.init();
            invocationStubManager.injectAll();
            // 修復(fù)權(quán)限管理
            ContextFixer.fixContext(context);
            isStartUp = true;
            if (initLock != null) {
                initLock.open();
                initLock = null;
            }
        }
    }

InvocationStubManager.injectInternal
主要完成對(duì) Java 層 framework 的 Hook裸删,將其定位到 VA 偽造 VA framework 上去八拱。

private void injectInternal() throws Throwable {
        // VA 自身的 App 進(jìn)程不需要 Hook
        if (VirtualCore.get().isMainProcess()) {
            return;
        }
        // VAService 需要 Hook AMS 和 PMS
        if (VirtualCore.get().isServerProcess()) {
            addInjector(new ActivityManagerStub());
            addInjector(new PackageManagerStub());
            return;
        }
        // Client APP 需要 Hook 整個(gè) framework,來(lái)使其調(diào)用到 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 {
            // 調(diào)用遠(yuǎn)程 VAService
            return getService().installPackage(apkPath, flags);
        } catch (RemoteException e) {
            return VirtualRuntime.crash(e);
        }
    }

最終調(diào)用 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 優(yōu)化(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 {
            // 進(jìn)入解析包結(jié)構(gòu)涯塔,該結(jié)構(gòu)是可序列化的肌稻,為了持久化在磁盤上
            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.");
        }

        // 是否基于系統(tǒng)的 apk 加載,前提是安裝過(guò)的 apk 并且 dependSystem 開關(guān)打開
        boolean dependSystem = (flags & InstallStrategy.DEPEND_SYSTEM_IF_EXIST) != 0
                && VirtualCore.get().isOutsideInstalled(pkg.packageName);

        if (existSetting != null && existSetting.dependSystem) {
            dependSystem = false;
        }
        // 復(fù)制 so 到 sandbox lib
        NativeLibraryHelperCompat.copyNativeBinaries(new File(path), libDir);

        // 如果不基于系統(tǒng)匕荸,一些必要的拷貝工作
        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);
        }

        // 給上可執(zhí)行權(quán)限爹谭,5.0 之后在 SD 卡上執(zhí)行 bin 需要可執(zhí)行權(quán)限
        chmodPackageDictionary(packageFile);

        // PackageSetting 的一些配置,后面會(huì)序列化在磁盤上
        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);
        //發(fā)送通知 安裝完成
        if (notify) {
            notifyAppInstalled(ps, -1);
        }
        res.isSuccess = true;
        return res;
    }

APk 的安裝主要完成以下幾件事情:

  • 解析 menifest 拿到 apk 內(nèi)部信息榛搔,包括組件信息诺凡,權(quán)限信息等。并將這些信息序列化到磁盤和內(nèi)存中践惑,以備打開時(shí)調(diào)用腹泌。
  • 準(zhǔn)備 App 在 VA 沙箱環(huán)境中的私有空間,并且復(fù)制一些必要的 apk 和 so libs尔觉。
  • 最后通知前臺(tái)安裝完成凉袱。

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 中所有的關(guān)鍵信息,尤其是組件的數(shù)據(jù)結(jié)構(gòu)會(huì)在 app 在 VA 中運(yùn)行的時(shí)候給 VAMS,VPMS 這些 VAService 提供 apk 的組件信息专甩。

Client App 啟動(dòng)

首先要了解的是 Android App 是組件化的钟鸵,Apk 其實(shí)是 N 多個(gè)組件的集合,以及一些資源文件和 Assert涤躲,App 的啟動(dòng)有多種情況棺耍,只要在一個(gè)新的進(jìn)程中調(diào)起了 apk 中任何一個(gè)組件,App 將被初始化篓叶,Application 將被初始化烈掠。

Activity 啟動(dòng)

Hook startActivity(重定位 Intent 到 StubActivity)

首先在 Client App 中,startActivity 方法必須被 Hook 掉缸托,不然 Client App 調(diào)用 startActivity 就直指外部 Activity 去了左敌。

這部分的原理其實(shí)與 DroidPlugin 大同小異,由于插件(Client App)中的 Activity 是沒有在 AMS 中注冊(cè)的俐镐,AMS 自然無(wú)法找到我們的插件 Activity矫限。

Hook 的目的是我們拿到用戶的 Intent,把他替換成指向 VA 在 Menifest 中站好坑的 StubActivity 的 Intent佩抹,然后將原 Intent 當(dāng)作 data 打包進(jìn)新 Intent 以便以后流程再次進(jìn)入 VA 時(shí)恢復(fù)叼风。

Hook 的方法就是用我們動(dòng)態(tài)代理生成的代理類對(duì)象替換系統(tǒng)原來(lái)的 ActiityManagerNative.geDefault 對(duì)象。

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

好了棍苹,下面只要調(diào)用到 startActivity 就會(huì)被 Hook 到 call无宿。
這個(gè)函數(shù)需要注意以下幾點(diǎn):

  • VA 有意將安裝和卸載 APP 的請(qǐng)求重定向到了卸載 VA 內(nèi)部 APK 的邏輯。
  • resolveActivityInfo 調(diào)用到了 VPM 的 resolveIntent枢里,最終會(huì)遠(yuǎn)程調(diào)用到 VPMS 的 resolveIntent孽鸡,然后 VPMS 就會(huì)去查詢 VPackage 找到目標(biāo) Activity 并將信息附加在 ResolveInfo 中返回 VPM。
  • 最后也是最重要的一點(diǎn)栏豺,startActivity 會(huì)調(diào)用到 VAM.startActivity,同樣最終會(huì)遠(yuǎn)程調(diào)用到 VAMS 的 startActivity彬碱。
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);
            }

            // 請(qǐng)求安裝和卸載界面
            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 調(diào)用選擇界面
            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);
            }

            // 調(diào)用遠(yuǎn)程 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 切換動(dòng)畫,因?yàn)榇藭r(shí)動(dòng)畫還是 Host 的 Stub Activity 默認(rèn)動(dòng)畫奥洼,需要覆蓋成子程序包的動(dòng)畫
            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 調(diào)用 ActivityStack.startActivityLocked

// 參考 framework 的實(shí)現(xiàn)
    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;

        // 忽略一大堆對(duì) Flag 的處理
        .............................

        String affinity = ComponentUtils.getTaskAffinity(info);

        // 根據(jù) 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 對(duì)應(yīng)的 ProcessRecorder灵奖,如果沒有則表示這是 Process 第一個(gè)打開的組件嚼沿,需要初始化 Application
        ProcessRecord targetApp = mService.startProcessIfNeedLocked(info.processName, userId, info.packageName);
        if (targetApp == null) {
            return null;
        }
        Intent targetIntent = new Intent();

        // 根據(jù) 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 會(huì)根據(jù)相同的進(jìn)程 id 在 VA 的 Menifest 中找到那個(gè)提前占坑的 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;

        // 根據(jù)在 Menifest 中注冊(cè)的 pid
        if (isDialogStyle) {
            return VASettings.getStubDialogName(vpid);
        } else {
            return VASettings.getStubActivityName(vpid);
        }
    }

這里需要特別注意,VA 占坑的方式和 DroidPlugin 有些小不同瓷患,VA 沒有為每個(gè) Process 注冊(cè)多個(gè) Activity伏尼,也沒有為不同的啟動(dòng)方式注冊(cè)多個(gè) Activity,這里確實(shí)是有改進(jìn)的尉尾。
這里根本原因是因?yàn)?VA 對(duì) VAMS 實(shí)現(xiàn)的更為完整爆阶,實(shí)現(xiàn)了原版 AMS 的基本功能,包括完整的 Recorder 管理,Task Stack 管理等辨图,這樣的話 StubActivity 的唯一作用便是攜帶 Client App 真正的 Intent 交給 VAMS 處理班套。這套機(jī)制衍生到其他的組件也是一樣的。

最終, VAMS 調(diào)用原生 AM 的 startActivity 向真正的 AMS 發(fā)送替換成 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);
    }

恢復(fù)原 Intent 重定向到原 Activity

當(dāng) AMS 收到偽裝的 Intent 后吱韭,就會(huì)找到 StubActivity,這時(shí)流程回到 VA 里的主線程中的消息隊(duì)列中鱼的。
Hook 過(guò)程就是用我們自己的 Handler 替換 android.os.Handler.mCallback 因?yàn)橹骶€程在這里分發(fā)一些操作理盆。

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

handlerMessage 判斷是 LAUNCH_ACTIVITY Action 后直接調(diào)用了 handlerLaunchActivity 方法,和原版其實(shí)很像凑阶。

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 剛剛啟動(dòng)第一個(gè)組件
            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.通知服務(wù)端 VAMS Activity 創(chuàng)建完成
            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 還原出來(lái)的原版 Intent 被繼續(xù)交給原生的 AM

// 將 Host Stub Activity Intent 替換為原版 Intent
ActivityThread.ActivityClientRecord.intent.set(r, intent);
// 同上
ActivityThread.ActivityClientRecord.activityInfo.set(r, info);

最后一個(gè) Hook 點(diǎn)在 Instrumentation.callActivityOnCreate:
因?yàn)?AMS 實(shí)際上啟動(dòng)的是 StubActivity 的關(guān)系宙橱,真正的 Activity 的一些信息還不是其真正的信息姨俩,比如主題之類的,所以需要在這個(gè)時(shí)機(jī)修復(fù)一下师郑,選擇這個(gè)時(shí)間修復(fù)的原因也是因?yàn)?Activity 已經(jīng)被 new 出來(lái)了环葵,而且資源已經(jīng)準(zhǔn)備完畢。

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 對(duì)象
        if (r != null) {
            r.activity = activity;
        }
        ContextFixer.fixContext(activity);
        ActivityFixer.fixActivity(activity);
        ActivityInfo info = null;
        if (r != null) {
            info = r.info;
        }
        // 設(shè)置主題和屏幕縱橫控制
        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);
    }

引用(如有侵權(quán)宝冕,即刻刪除):
Android虛擬化引擎VirtualApp探究
Android 雙開沙箱 VirtualApp 源碼分析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末张遭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子地梨,更是在濱河造成了極大的恐慌帝璧,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件湿刽,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡褐耳,警方通過(guò)查閱死者的電腦和手機(jī)诈闺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)铃芦,“玉大人雅镊,你說(shuō)我怎么就攤上這事∪凶遥” “怎么了仁烹?”我有些...
    開封第一講書人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)咧虎。 經(jīng)常有香客問(wèn)我卓缰,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任征唬,我火速辦了婚禮捌显,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘总寒。我一直安慰自己扶歪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開白布摄闸。 她就那樣靜靜地躺著善镰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪年枕。 梳的紋絲不亂的頭發(fā)上炫欺,一...
    開封第一講書人閱讀 52,337評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音画切,去河邊找鬼竣稽。 笑死,一個(gè)胖子當(dāng)著我的面吹牛霍弹,可吹牛的內(nèi)容都是我干的毫别。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼典格,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼岛宦!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起耍缴,我...
    開封第一講書人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤砾肺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后防嗡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體变汪,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年蚁趁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了裙盾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡他嫡,死狀恐怖番官,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情钢属,我是刑警寧澤徘熔,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站淆党,受9級(jí)特大地震影響酷师,放射性物質(zhì)發(fā)生泄漏讶凉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一窒升、第九天 我趴在偏房一處隱蔽的房頂上張望缀遍。 院中可真熱鬧,春花似錦饱须、人聲如沸域醇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)譬挚。三九已至,卻和暖如春酪呻,著一層夾襖步出監(jiān)牢的瞬間减宣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工玩荠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留漆腌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓阶冈,卻偏偏與公主長(zhǎng)得像闷尿,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子女坑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 一填具、知識(shí)詳解模塊 1.dex/class深入講解 2.jvm/dvm/art三個(gè)虛擬機(jī)的深入講解 3.class ...
    hanfengzqh閱讀 4,627評(píng)論 0 20
  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom閱讀 2,701評(píng)論 0 3
  • 昨夜,不知為何匆骗,有些失眠劳景,睡到2點(diǎn)突然醒來(lái),夜深人靜碉就,萬(wàn)籟俱寂盟广。翻來(lái)覆去,一時(shí)難以入睡瓮钥,便披衣坐起筋量,打開手機(jī),在全...
    一萌文化雜談閱讀 5,643評(píng)論 2 3
  • 讀經(jīng):爸爸媽媽骏庸,澤強(qiáng)沒讀。 讀經(jīng)內(nèi)容:易經(jīng)三十一卦年叮、少年兒童詩(shī)詞啟蒙01具被、英語(yǔ)45-51 因房子裝修今天搬運(yùn)工運(yùn)送...
    fsl630807閱讀 381評(píng)論 0 0
  • 常想時(shí)間是一味良藥,能讓人自渡只损,再難忘的人或事一姿,在時(shí)間面前終將釋懷七咧。 光陰的巷口,誰(shuí)沒有過(guò)年少唇紅齒白的時(shí)光叮叹,誰(shuí)不...
    不夠奇異的果閱讀 546評(píng)論 1 8