天天看點

Android虛拟化引擎VirtualApp探究

介紹

首先需要說明的是,VirtualApp并不是前些陣子滴滴開源的插件化架構VirtualApk。

VirtualApp

是一個更加黑科技的東西,他可以建立一個虛拟空間,你可以在虛拟空間内任意的安裝、啟動和解除安裝APK,這一切都與外部隔離,如同一個沙盒,APK無需在外部安裝。

小試牛刀

啟動VirtualApp後,界面是這樣的。

Android虛拟化引擎VirtualApp探究

顯示的是已經通過VirtualApp安裝的APK,可以直接從SD卡或者系統中已有的APK中選擇安裝。安裝後直接點開圖示,就能跟安裝在外部的應用一樣打開APP。簡單嘗試了一下,Nexus 6P,Android 7.0,知乎和微網誌都能正常工作,并且運作速度跟外部安裝的差異不大。而且還可以安裝多個相同的應用,實作多開的效果。

Android虛拟化引擎VirtualApp探究

粗略觀察

首先,我們來看一下它在開啟APP後的程序資訊,

u0_a200   22932 494   1034396 84008 SyS_epoll_ 0000000000 S io.virtualapp
u0_a200   22955 494   1064388 70408 SyS_epoll_ 0000000000 S io.virtualapp:x
u0_a200   22983 494   1530416 266948            0000000000 R com.zhihu.android
u0_a200   23320 494   1410736 214680 SyS_epoll_ 0000000000 S com.sina.weibo
u0_a200   23387 494   1174928 76848 SyS_epoll_ 0000000000 S com.sina.weibo.image
u0_a200   23415 494   1186076 81648 SyS_epoll_ 0000000000 S com.sina.weibo:remote
u0_a200   23455 494   1173888 76572 SyS_epoll_ 0000000000 S com.sina.weibo.imageservant
u0_a200   24028 494   1182780 74408 SyS_epoll_ 0000000000 S com.sina.weibo.servant
u0_a200   24425 494   1027636 66116 SyS_epoll_ 0000000000 S com.taobao.sophix_android
u0_a200   24492 494   1334412 174708 SyS_epoll_ 0000000000 S com.zhihu.android           

可以看到,所有被ViralApp打開的應用,都和VirtalApp屬于同一個uid:u0_a200。其中,VirtualApp本身有兩個程序:io.virtualapp 和 io.virtualapp:x

io.virtualapp 就是可見的互動界面,同時也負責APK包的管理和安裝。

io.virtualapp:x 作為一個單獨的服務程序,虛拟了一些系統服務。後面我們還會提到。

以這裡安裝的微網誌為例,檢視一下它的程序的記憶體空間,可以看到相關路徑全都被映射到了

/data/data/io.virtualapp/virtual

下面,

... ...
b6d0f000-b7017000 r--p 00000000 fd:00 410335 /data/data/io.virtualapp/virtual/data/user/0/com.sina.weibo/Plugin/com.weibo.app.movie/dalvik-cache/base-1.dex
b7017000-b71d4000 r-xp 00308000 fd:00 410335 /data/data/io.virtualapp/virtual/data/user/0/com.sina.weibo/Plugin/com.weibo.app.movie/dalvik-cache/base-1.dex
... ...
bb745000-bb831000 r--p 00000000 fd:00 410247 /data/data/io.virtualapp/virtual/data/user/0/com.sina.weibo/code_cache/secondary-dexes/composer1312fd1cbada0e5074c9f9961b16aefb.dex
bb831000-bb8f0000 r-xp 000ec000 fd:00 410247 /data/data/io.virtualapp/virtual/data/user/0/com.sina.weibo/code_cache/secondary-dexes/composer1312fd1cbada0e5074c9f9961b16aefb.dex
... ...
bf448000-bf978000 r-xp 00000000 fd:00 410129 /data/data/io.virtualapp/virtual/data/app/com.sina.weibo/lib/libweiboffmpeg.so
bf978000-bf979000 ---p 00000000 00:00 0
bf979000-bf9ab000 r--p 00530000 fd:00 410129 /data/data/io.virtualapp/virtual/data/app/com.sina.weibo/lib/libweiboffmpeg.so
bf9ab000-bf9af000 rw-p 00562000 fd:00 410129 /data/data/io.virtualapp/virtual/data/app/com.sina.weibo/lib/libweiboffmpeg.so
... ...
c335a000-c33a9000 r-xp 00000000 fd:00 410127 /data/data/io.virtualapp/virtual/data/app/com.sina.weibo/lib/libweiboplayer.so
c33aa000-c33ad000 r--p 0004f000 fd:00 410127 /data/data/io.virtualapp/virtual/data/app/com.sina.weibo/lib/libweiboplayer.so
c33ad000-c33ae000 rw-p 00052000 fd:00 410127 /data/data/io.virtualapp/virtual/data/app/com.sina.weibo/lib/libweiboplayer.so
... ...           

可見,這裡面對路徑做過了重新映射。具體實作我們還是來看一下代碼吧。

注入邏輯

要想實作對一個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

來調用它,這樣就實作了所有路徑的重定向。

啟動原理

最後我們來看下,一個APP是如何在VirtualApp裡啟動的。

啟動一個app是在

LoadingActivity

launch

方法

public static void launch(Context context, String packageName, int userId) {
    Intent intent = VirtualCore.get().getLaunchIntent(packageName, userId);
    if (intent != null) {
        Intent loadingPageIntent = new Intent(context, LoadingActivity.class);
        loadingPageIntent.putExtra(PKG_NAME_ARGUMENT, packageName);
        loadingPageIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        loadingPageIntent.putExtra(KEY_INTENT, intent);
        loadingPageIntent.putExtra(KEY_USER, userId);
        context.startActivity(loadingPageIntent);
    }
}           

然後會調用到

VActivityManager

startActivity

public int startActivity(Intent intent, ActivityInfo info, IBinder resultTo, Bundle options, String resultWho, int requestCode, int userId) {
    try {
        return getService().startActivity(intent, info, resultTo, options, resultWho, requestCode, userId);
    } catch (RemoteException e) {
        return VirtualRuntime.crash(e);
    }
}           

這裡的service會通過binder最終找到io.virtualapp:x程序的

VActivityManagerService

@Override
public int startActivity(Intent intent, ActivityInfo info, IBinder resultTo, Bundle options, String resultWho, int requestCode, int userId) {
    synchronized (this) {
        return mMainStack.startActivityLocked(userId, intent, info, resultTo, options, resultWho, requestCode);
    }
}
           

Service端的

startActivity

會調用

ActivityStack

startActivityLocked

,然後調用到

startActivityInNewTaskLocked

private void startActivityInNewTaskLocked(int userId, Intent intent, ActivityInfo info, Bundle options) {
    Intent destIntent = startActivityProcess(userId, null, intent, info);// 換intent
    if (destIntent != null) {
        destIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        destIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
        destIntent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            // noinspection deprecation
            destIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
        } else {
            destIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            VirtualCore.get().getContext().startActivity(destIntent, options);
        } else {
            VirtualCore.get().getContext().startActivity(destIntent);
        }
    }
}           

這裡的intent會在

startActivityProcess

的時候進行替換

private Intent startActivityProcess(int userId, ActivityRecord sourceRecord, Intent intent, ActivityInfo info) {
    intent = new Intent(intent);
    ProcessRecord targetApp = mService.startProcessIfNeedLocked(info.processName, userId, info.packageName);
    if (targetApp == null) {
        return null;
    }
    Intent targetIntent = new Intent();
=>  targetIntent.setClassName(VirtualCore.get().getHostPkg(), fetchStubActivity(targetApp.vpid, info));
    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;
}           

targetIntent.setClassName

會設定為VirtualApp的包名,同時

fetchStubActivity

用來擷取插樁Activity

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;
        if (isDialogStyle) {
            return VASettings.getStubDialogName(vpid);
        } else {
            return VASettings.getStubActivityName(vpid);
        }
    }           

這裡最終傳回的是

VASettings.getStubActivityName

public static String STUB_ACTIVITY = StubActivity.class.getName();

    public static String getStubActivityName(int index) {
        return String.format(Locale.ENGLISH, "%s$C%d", STUB_ACTIVITY, index);
    }           

可見,最終傳回的Activity名是

com.lody.virtual.client.stub.StubActivity$C?

?

表示具體數字。

而它們,是預先在AndroidManifest裡面寫好的。

<activity
            android:name="com.lody.virtual.client.stub.StubActivity$C0"
            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
            android:process=":p0"
            android:taskAffinity="com.lody.virtual.vt"
            android:theme="@style/VATheme" />

        <activity
            android:name="com.lody.virtual.client.stub.StubActivity$C1"
            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
            android:process=":p1"
            android:taskAffinity="com.lody.virtual.vt"
            android:theme="@style/VATheme" />

        <activity
            android:name="com.lody.virtual.client.stub.StubActivity$C2"
            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
            android:process=":p2"
            android:taskAffinity="com.lody.virtual.vt"
            android:theme="@style/VATheme" />

        <activity
            android:name="com.lody.virtual.client.stub.StubActivity$C3"
            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
            android:process=":p3"
            android:taskAffinity="com.lody.virtual.vt"
            android:theme="@style/VATheme" />           

這麼一來,

startActivity

最終會啟動到這裡的

StubActivity

中。

并且每次都會建立一個子程序

p?

,在開啟一個程序時,都會先執行到

io.virtualapp.VApp.attachBaseContext

中,這樣,就會走到剛才提到的

injectInternal

方法中,實作所有注入邏輯,把所有與系統互動的地方都進行替換。而

StubActivity

onCreate

的方法,雖然在代碼裡面有聲明,卻永遠執行不到:

public abstract class StubActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // The savedInstanceState's classLoader is not exist.
        super.onCreate(null);
        finish();
        // It seems that we have conflict with the other Android-Plugin-Framework.
        Intent stubIntent = getIntent();
        // Try to acquire the actually component information.
        StubActivityRecord r = new StubActivityRecord(stubIntent);
        if (r.intent != null) {
            if (TextUtils.equals(r.info.processName, VirtualRuntime.getProcessName()) && r.userId == VUserHandle.myUserId()) {
                // Retry to inject the HCallback to instead of the exist one.
                InvocationStubManager.getInstance().checkEnv(HCallbackStub.class);
                Intent intent = r.intent;
                startActivity(intent);
            } else {
                // Start the target Activity in other process.
                VActivityManager.get().startActivity(r.intent, r.userId);
            }
        }
    }

    public static class C0 extends StubActivity {
    }

    public static class C1 extends StubActivity {
    }

    public static class C2 extends StubActivity {
    }

    public static class C3 extends StubActivity {
    }
    
    ... ...
}           

在替換後,執行流就被改變了,可以看到,在執行到APP的真正

Application

前,能發現被注入的代碼:

com.taobao.sophix_app.MyApplication.onCreate(MyApplication.java:33)
android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1024)
com.lody.virtual.client.hook.delegate.InstrumentationDelegate.callApplicationOnCreate(InstrumentationDelegate.java:225)
com.lody.virtual.client.hook.delegate.AppInstrumentation.callApplicationOnCreate(AppInstrumentation.java:137)
com.lody.virtual.client.VClientImpl.bindApplicationNoCheck(VClientImpl.java:312)
com.lody.virtual.client.VClientImpl.bindApplication(VClientImpl.java:192)
com.lody.virtual.client.hook.proxies.am.HCallbackStub.handleLaunchActivity(HCallbackStub.java:114)
com.lody.virtual.client.hook.proxies.am.HCallbackStub.handleMessage(HCallbackStub.java:71)
android.os.Handler.dispatchMessage(Handler.java:98)
android.os.Looper.loop(Looper.java:154)
android.app.ActivityThread.main(ActivityThread.java:6077)
java.lang.reflect.Method.invoke(Native Method)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)           

思考

我們已經大緻分析了VirtualApp的工作原理,當然裡面有非常多細節沒有覆寫到,有興趣的同學可以自己研究一下。

個人認為,VirtualApp可以加載外部APK的特性,能夠引發無窮的想象。

比如一些簡單的脫殼,就可以用VirtualApp先加載APK,然後在運作期間直接把記憶體中脫殼後的DEX檔案dump出來。

另外,由于Sophix熱修複方案也是非侵入的,如果把Sophix內建進VirtualApp中,就能夠在啟動APP前加載一個更新檔,替換原有APP中的某些類,實作不重新打包原有APK的情況下對原先APP的邏輯的修改。

繼續閱讀