1. 程式人生 > >WebView執行在系統程序出現的問題 WebView is not allowed in privileged processes

WebView執行在系統程序出現的問題 WebView is not allowed in privileged processes

WebView在Android4.4之前使用的Webkit核心,在Android4.4以後切換到了Chromium核心。本文的內容主要不是講解Chromium核心上WebView的特性。關鍵是要講解webview切換到Chromium核心後我遇到的一個坑(實際上可能叫做坑不合適,因為這是安卓為了安全著想才這麼做的),並提出解決這個坑的一個方法。

    這個問題的情形是這樣的,當時需要在一個系統應用中使用webview,系統應用因為需要使用一些特殊的系統許可權,所有配置了android:sharedUserId="android.uid.system"  ,那麼當你執行webview的時候,就會發現程式crash並丟擲以下異常:

Caused by: java.lang.UnsupportedOperationException: For security reasons, WebView is not allowed in privileged processes

at android.webkit.WebViewFactory.getProvider(WebViewFactory.java:155)

at android.webkit.CookieManager.getInstance(CookieManager.java:42)

   接下來我們來跟蹤原始碼看下這個異常是怎麼丟擲來的根據crash資訊,我們來看WebViewFactory中的getProvider方法,在這裡提一下,在切換WebView的核心之前,Google就已經修改了WebView的程式碼架構,使用了工廠模式來決定WebView的具體實現,目的就是為了日後可以方便的切換WebView核心。在這裡以Android-22的原始碼為例,每個Android版本的WebViewFactory原始碼都有所出入,不過本文的關鍵是描述解決這個問題的思路。

static WebViewFactoryProvider getProvider() {

synchronized (sProviderLock) {

// For now the main purpose of this function (and the factory abstraction) is to keep

// us honest and minimize usage of WebView internals when binding the proxy.

if (sProviderInstance != null) return sProviderInstance;

final int uid = android.os.Process.myUid();

if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID) {

throw new UnsupportedOperationException(

"For security reasons, WebView is not allowed in privileged processes");

}

Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");

try {

Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");

loadNativeLibrary();

Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);

Class<WebViewFactoryProvider> providerClass;

Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getFactoryClass()");

try {

providerClass = getFactoryClass();

} catch (ClassNotFoundException e) {

Log.e(LOGTAG, "error loading provider", e);

throw new AndroidRuntimeException(e);

} finally {

Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);

}

StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();

Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "providerClass.newInstance()");

try {

try {

sProviderInstance = providerClass.getConstructor(WebViewDelegate.class)

.newInstance(new WebViewDelegate());

} catch (Exception e) {

sProviderInstance = providerClass.newInstance();

}

if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);

return sProviderInstance;

} catch (Exception e) {

Log.e(LOGTAG, "error instantiating provider", e);

throw new AndroidRuntimeException(e);

} finally {

Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);

StrictMode.setThreadPolicy(oldPolicy);

}

} finally {

Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);

}

}

}

原來這個異常是這句程式碼丟擲的:

final int uid = android.os.Process.myUid();

if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID) {

throw new UnsupportedOperationException(

"For security reasons, WebView is not allowed in privileged processes");

}

那麼問題來了,我們有什麼辦法可以讓它不丟擲這個異常嗎?我們看原始碼,可以發現在走到這一步之前,有這麼一句:

if (sProviderInstance != null) return sProviderInstance;

我們再來看sProviderInstance是什麼:

private static WebViewFactoryProvider sProviderInstance;

原來只要sProviderInstance被建立過一次,那麼以後再進入getProvider()時,就會直接返回以建立的例項,也就是說後面丟擲異常的語句就不會被執行啦!

    那麼,我們的目的就明確了,就是要在程式執行getProvider()之前,人為的給sProviderInstance賦值。這就要借鑑Hook的思想,什麼是Hook?請自行百度,這裡不做講解。

要Hook的話,必須要有合適的Hook點,比如在方法內部實時new的物件,非static變數,static方法,這些都是無法Hook的,或者也可以說是就算Hook了也沒有任意意義,而我們看到sProviderInstance是一個static變數,也就是說所有的路徑都是讀取這一個物件,那麼這裡剛好就是一個很好的Hook點,真是天助我也!

    我們再看getProvider()的程式碼,檢測完uid之後,後面的程式碼就開始建立sProviderInstance了,那麼我們的工作就是要想辦法來執行它們。這裡我有兩個方案,(1)通過反射呼叫裡面被呼叫的方法,(2)採用欺騙Api的方式。反射呼叫很好理解,欺騙Api又是什麼呢?其實就是在我們的專案工程中,按照需要呼叫的類和方法建立一個class檔案,方法可以什麼都不實現,然後我們打包apk的時候,把這些人為建立的類給幹掉,也就是說只引用,不打包。這樣App最終編譯安裝呼叫的時候,會連線到系統中的這個類裡面去。

    但是要注意,無論是反射也要,欺騙Api也好,不是任何類和方法都可以被建立和呼叫的,我們看原始碼的時候會發現有些類和方法會被@SystemApi或者@Hide註解,這兩個東西又是什麼呢?@Hide註解的類,說明這個類是不對外開放的,也就是說Android Sdk中是無法直接呼叫的,但是可以通過反射去呼叫。@SystemApi註解包括了@Hide的隱藏功能,但是區別在於@SystemApi註解的類和方法只有系統App才能去呼叫,非系統App是不允許連線到這些類和方法的,如果非系統App非要去呼叫@SystemApi註解的類,那麼會丟擲NoClassDefFoundError的異常,這個異常的意思是這個類在編譯時是可以找得到的,但是在執行時沒有這個類了。所以這個辦法只能針對系統App來用。

    當我們用各種反射,各種欺騙Api,終於仿照getProvider的流程弄出一個WebViewFactoryProvider型別的物件後,我們要做的就是把它賦值給sProviderInstance

    以下貼上解決這個問題的程式碼,用的是反射的方法,找到WebViewDelegate的構造,設定訪問級別,然後newinstance就行。WebViewDelegate的構造方法是package級別的,因此用欺騙API的方式是行不通的。

private void hookWebView(){

        Class<?> factoryClass = null;

        try {

            factoryClass = Class.forName("android.webkit.WebViewFactory");

            Method getProviderClassMethod = null;

            Object sProviderInstance = null;

            if (Build.VERSION.SDK_INT == 23) {

                getProviderClassMethod = factoryClass.getDeclaredMethod("getProviderClass");

                getProviderClassMethod.setAccessible(true);

                Class<?> providerClass = (Class<?>) getProviderClassMethod.invoke(factoryClass);

                Class<?> delegateClass = Class.forName("android.webkit.WebViewDelegate");

                Constructor<?> constructor = providerClass.getConstructor(delegateClass);

                if (constructor != null) {

                    constructor.setAccessible(true);

                    Constructor<?> constructor2 = delegateClass.getDeclaredConstructor();

                    constructor2.setAccessible(true);

                    sProviderInstance = constructor.newInstance(constructor2.newInstance());

                }

            } else if (Build.VERSION.SDK_INT == 22) {

                getProviderClassMethod = factoryClass.getDeclaredMethod("getFactoryClass");

                getProviderClassMethod.setAccessible(true);

                Class<?> providerClass = (Class<?>) getProviderClassMethod.invoke(factoryClass);

                Class<?> delegateClass = Class.forName("android.webkit.WebViewDelegate");

                Constructor<?> constructor = providerClass.getConstructor(delegateClass);

                if (constructor != null) {

                    constructor.setAccessible(true);

                    Constructor<?> constructor2 = delegateClass.getDeclaredConstructor();

                    constructor2.setAccessible(true);

                    sProviderInstance = constructor.newInstance(constructor2.newInstance());

                }

            } else if (Build.VERSION.SDK_INT == 21) {//Android 21無WebView安全限制

                getProviderClassMethod = factoryClass.getDeclaredMethod("getFactoryClass");

                getProviderClassMethod.setAccessible(true);

                Class<?> providerClass = (Class<?>) getProviderClassMethod.invoke(factoryClass);

                sProviderInstance = providerClass.newInstance();

            }

            if (sProviderInstance != null) {

                Log.i("cym", sProviderInstance.toString());

                Field field = factoryClass.getDeclaredField("sProviderInstance");

                field.setAccessible(true);

                field.set("sProviderInstance", sProviderInstance);

            }

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

 本文的宗旨是讓大家對Hook的實際用途有個概念,在開發中如果遇到一些問題時,解決問題的武器庫中多一把利刃。當Hook配合動態代理來使用時,就可以做到很多一般情況下做不到的事情!比如360的DroidPlugin主要就是使用了動態代理來Hook住FrameWork層!

最新的寫法相容了了8.0及以上的android版本

// 如果是非系統程序則按正常程式走
if (Process.myUid() != Process.SYSTEM_UID) {
    return;
}
int sdkInt = Build.VERSION.SDK_INT;
try {
    Class<?> factoryClass = Class.forName("android.webkit.WebViewFactory");
    Field field = factoryClass.getDeclaredField("sProviderInstance");
    field.setAccessible(true);
    Object sProviderInstance = field.get(null);
    if (sProviderInstance != null) {
        VLog.e("sProviderInstance isn't null");
        return;
    }

    Method getProviderClassMethod;
    if (sdkInt > 22) {
        getProviderClassMethod = factoryClass.getDeclaredMethod("getProviderClass");
    } else if (sdkInt == 22) {
        getProviderClassMethod = factoryClass.getDeclaredMethod("getFactoryClass");
    } else {
        VLog.e("Don't need to Hook WebView");
        return;
    }
    getProviderClassMethod.setAccessible(true);
    Class<?> factoryProviderClass = (Class<?>) getProviderClassMethod.invoke(factoryClass);
    Class<?> delegateClass = Class.forName("android.webkit.WebViewDelegate");
    Constructor<?> delegateConstructor = delegateClass.getDeclaredConstructor();
    delegateConstructor.setAccessible(true);
    if (sdkInt < 26) {//低於Android O版本
        Constructor<?> providerConstructor = factoryProviderClass.getConstructor(delegateClass);
        if (providerConstructor != null) {
            providerConstructor.setAccessible(true);
            sProviderInstance = providerConstructor.newInstance(delegateConstructor.newInstance());
        }
    } else {
        Field chromiumMethodName = factoryClass.getDeclaredField("CHROMIUM_WEBVIEW_FACTORY_METHOD");
        chromiumMethodName.setAccessible(true);
        String chromiumMethodNameStr = (String) chromiumMethodName.get(null);
        if (chromiumMethodNameStr == null) {
            chromiumMethodNameStr = "create";
        }
        Method staticFactory = factoryProviderClass.getMethod(chromiumMethodNameStr, delegateClass);
        if (staticFactory != null) {
            sProviderInstance = staticFactory.invoke(null, delegateConstructor.newInstance());
        }
    }

    if (sProviderInstance != null) {
        field.set("sProviderInstance", sProviderInstance);
        VLog.e("Hook success!");
    } else {
        VLog.e("Hook failed!");
    }
} catch (Exception e) {
    LogUtil.e("", e);
}

--------------------- 本文來自 mgdbaby 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/qq_33890656/article/details/69656285?utm_source=copy