VirtualApp沙盒 基本原理

轉自:http://www.voidcn.com/article/p-mhioxytj-ys.html

VirtualApp是一個開源的Android App虛擬化引擎,允許在其中創建虛擬空間,並在這個虛擬空間中運行其他應用。通過閱讀源碼及動態調試,基本瞭解了其運行原理,在此記錄。

本質

Android應用隔離是基於Linux系統的多用戶機制實現的,即每個應用在安裝時被分配了不同的Linux用戶uid/gid。而在VirtualApp中,client應用(通過VirtualApp安裝的應用)與host應用(即VirtualApp本身)是具有相同用戶uid的。

因此,VirtualApp在運行時,包含以下三部分:

  • Main Process,進程名io.virtualapp,主要負責VirtualApp用戶界面及應用管理
  • Server Process,進程名io.virtualapp:x,主要負責系統服務的代理,是通過Content Provider啓動的
  • VApp Process,進程名io.virtualapp:p[0-…],作爲將來運行client應用的進程,當client應用啓動後,其進程名會更新爲client應用的包名

下面是在VirtualApp中運行應用後通過ps命令得到的結果:

generic_x86:/ ps |grep u0_a60
u0_a60    2385  1258  996260 54456 SyS_epoll_ 00000000 S io.virtualapp
u0_a60    2412  1258  980940 48272 SyS_epoll_ 00000000 S io.virtualapp:x
u0_a60    3705  1258  993632 54472 SyS_epoll_ 00000000 S org.galaxy.simpleapp

可以看到,以上進程,均是以VirtualApp的用戶uid運行的。因此,Android應用隔離此時不再適用,我們可以對client應用進行hook而無需root權限。


運行流程

從啓動VirtualApp到運行其中的應用,大致流程如下:

啓動host應用

我們啓動VirtualApp,其Application爲io.virtualapp.VApp。在attachBaseContext()方法中會調用到com.lody.virtual.client.core.PatchManager#injectInternal,但此時爲Main Process,不進行系統服務的替換。

啓動Server Process

host應用會進行一些初始化,其中就包括獲取全部已安裝應用,這會調用到com.lody.virtual.client.core.VirtualCore#getAllApps。而這一方法最終會訪問com.lody.virtual.server.BinderProvider。由AndroidManifest.xml可知,該provider會運行在新進程io.virtualapp:x中,即Server Process。

由於在新進程中啓動組件,同樣會首先創建該應用的Application,因此也會調用到com.lody.virtual.client.core.PatchManager#injectInternal。此時,會進行相應系統服務(ActivityManager和PackageManager)的代理構造和替換。

啓動VApp Process

點擊一個已安裝應用,此時會通過替換掉的系統服務訪問真實的系統服務(主要是ActivityManager),並在新進程中啓動組件com.lody.virtual.client.stub.StubActivity.C0。由AndroidManifest.xml可知,該進程具有後綴:p0。

同樣的,在該Activity組件啓動之前會初始化io.virtualapp.VApp,並在com.lody.virtual.client.core.PatchManager#injectInternal中完成系統服務的代理構造和替換。

啓動client應用

此時,真正的client應用尚未啓動,進程io.virtualapp:p0僅僅是作爲一個placeholder。StubActivity會從Intent中獲取到client應用的相關信息,並修改自身ActivityThread的handler。隨後調用startActivity啓動client應用。

由於之前Server Process和VApp Process都已完成了相關係統服務的替換,這裏會完成client應用的bindApplication調用、構造client應用的LoadedApk,並通過反射完成真正的Application和Activity的創建。

最終,client應用便運行在了我們的VApp Process中。


系統服務的代理和替換

VirtualApp之所以能夠實現虛擬空間,是因爲其對許多系統服務進行了代理和替換。因此,這部分便是整個框架的核心。系統服務運行在system_server中,Android應用調用系統服務,是通過Binder機制進行IPC。因此,應用所持有的是系統服務的BinderProxy,通過對這些BinderProxer構造代理並替換,便實現了對系統服務的代理和替換。

具體地,我們以com.lody.virtual.client.hook.patchs.am.ActivityManagerPatch爲例,這個類實現了對ActivityManager服務的代理和替換。

代理的構造

可以看到,這個類的註記中包含了大量類名:

@Patch({StartActivity.class, StartActivityAsCaller.class,
        StartActivityAndWait.class, StartActivityWithConfig.class, StartActivityIntentSender.class,
        StartNextMatchingActivity.class, StartVoiceActivity.class,
        GetIntentSender.class, RegisterReceiver.class, GetContentProvider.class,
        GetContentProviderExternal.class,
        ...

而這些列出的每一個類,對應於一個方法的hook,例如,com.lody.virtual.client.hook.patchs.am.StartActivity是ActivityManager服務的startActivity方法的hook。這些類均繼承自com.lody.virtual.client.hook.base.Hook,包含了方法beforeCall()call()afterCall(),這些方法便是hook的具體內容。

ActivityManagerPatch在創建時,會調用到其父類的方法com.lody.virtual.client.hook.base.PatchDelegate#onBindHooks。這裏會檢查上述註記中列出的hook,並對符合條件的hook調用addHook()方法:

...
        Class<? extends PatchDelegate> clazz = getClass();
        Patch patch = clazz.getAnnotation(Patch.class);
        int version = Build.VERSION.SDK_INT;
        if (patch != null) {
            Class<?>[] hookTypes = patch.value();
            for (Class<?> hookType : hookTypes) {
                ApiLimit apiLimit = hookType.getAnnotation(ApiLimit.class);
                boolean needToAddHook = true;
                if (apiLimit != null) {
                    int apiStart = apiLimit.start();
                    int apiEnd = apiLimit.end();
                    boolean highThanStart = apiStart == -1 || version > apiStart;
                    boolean lowThanEnd = apiEnd == -1 || version < apiEnd;
                    if (!highThanStart || !lowThanEnd) {
                        needToAddHook = false;
                    }
                }
                if (needToAddHook) {
                    addHook(hookType);
                }
        ...

addHook()最終會調用到com.lody.virtual.client.hook.base.HookDelegate#addHook,其實質便是將這個hook添加至映射表internalHookTable中:

public Hook addHook(Hook hook) {
        if (hook != null && !TextUtils.isEmpty(hook.getName())) {
            if (internalHookTable.containsKey(hook.getName())) {
                VLog.w(TAG, "The Hook(%s, %s) you added has been in existence.", hook.getName(),
                        hook.getClass().getName());
                return hook;
            }
            internalHookTable.put(hook.getName(), hook);
        }
        return hook;
    }

internalHookTable維護了所有的hook,以hook的名稱(一般就是所hook的方法的名稱)作爲key。隨後,在com.lody.virtual.client.hook.base.HookDelegate.HookHandlerinvoke()方法中,查找表 internalHookTable中是否包含將要執行的方法名;如果有,則依次執行對應hook的beforeCall()call()afterCall()

private class HookHandler implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Hook hook = getHook(method.getName());
            try {
                if (hook != null && hook.isEnable()) {
                    if (hook.beforeCall(mBaseInterface, method, args)) {
                        Object res = hook.call(mBaseInterface, method, args);
                        res = hook.afterCall(mBaseInterface, method, args, res);
                        return res;
                    }
                }
                return method.invoke(mBaseInterface, args);

而這裏的類HookHandler,就是構造的Java代理的Handler:

public HookDelegate(T baseInterface, Class<?>... proxyInterfaces) {
        this.mBaseInterface = baseInterface;
        if (baseInterface != null) {
            if (proxyInterfaces == null) {
                proxyInterfaces = HookUtils.getAllInterface(baseInterface.getClass());
            }
            mProxyInterface = (T) Proxy.newProxyInstance(baseInterface.getClass().getClassLoader(), proxyInterfaces, new HookHandler());

對於ActivityManagerPatch來說,這裏的baseInterface便是原始的BinderProxy:ActivityManagerProxy

public ActivityManagerPatch() {
        super(new HookDelegate<IInterface>(ActivityManagerNative.getDefault.call()));
    }

綜上,我們根據baseInterface,爲其構造了代理mProxyInterface。從而訪問mProxyInterface時,便會執行HookHandlerinvoke()方法,進而查找internalHookTable,對設置了hook的方法執行hook。

系統服務的替換

如之前所說,對系統服務的替換,是通過對應用所持有的系統服務的BinderProxy進行替換的。以上是構造代理的基本過程,那麼如何將應用所持有的BinderProxy替換成我們構造的代理呢?回到ActivityManagerPatch,這個類的inject()方法完成了實際的替換工作:

@Override
    public void inject() throws Throwable {
        if (ActivityManagerNative.gDefault.type() == IActivityManager.TYPE) {
            ActivityManagerNative.gDefault.set(getHookDelegate().getProxyInterface());
        } else if (ActivityManagerNative.gDefault.type() == Singleton.TYPE) {
            Object gDefault = ActivityManagerNative.gDefault.get();
            Singleton.mInstance.set(gDefault, getHookDelegate().getProxyInterface());
        }
        ...

ActivityManagerNative.gDefault便是應用所持有的原始ActivityManagerProxy對象,通過Java反射,將替換成爲getHookDelegate().getProxyInterface()。而替換的內容,便是我們所構造的代理mProxyInterface

由此,我們完成了對系統服務進行代理和替換的整個過程。隨後,在調用系統服務時,便會執行以下操作:

  • 訪問BinderProxy的代理,即我們設置了hook的代理
  • 根據hook的具體內容操作,對數據進行處理;需要調用原始系統服務時,訪問原始的BinderProxy
  • 真正的系統服務接收到Binder,進行處理並返回

總結

通過以上介紹可以看到,VirtualApp在原有系統服務之上構造了代理,進而爲其中的應用搭建了一套虛擬環境,應用可以無感知地運行在這其中。更進一步,我們可以設置這套虛擬環境,使其實現應用多開、非侵入式應用hook等高級功能。


參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章