Android開發:系統程式中使用Webview引發異常的處理

Rickon發表於2019-01-29

Android開發:系統程式中使用Webview引發異常的處理
我司開發的產品一般都是系統應用,即在AndroidManifest.xml中配置了android:sharedUserId="android.uid.system",最近在佈局開發中使用了 Webview,然而程式在執行時直接 carsh 了,檢視 log,報錯資訊如下:

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)
複製程式碼

原來是 Android 官方為了安全考慮,不允許特權程式(即系統程式)中使用 Webview。可是我們確實需要使用 Webview 該怎麼辦呢?

跟蹤原始碼發現在WebViewFactory類中的getProvider()方法中有如下程式碼(API版本:26):

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
        || uid == android.os.Process.PHONE_UID || uid == android.os.Process.NFC_UID
        || uid == android.os.Process.BLUETOOTH_UID) {
    throw new UnsupportedOperationException(
            "For security reasons, WebView is not allowed in privileged processes");
}
複製程式碼

可以看到第一次使用時,系統會檢查sProviderInstance是否為空,不為空的話直接返回建立過的例項,否則就判斷當前 uid,如果是 Root/System/Phone/NFC/Bluetooth 的話就會丟擲異常。sProviderInstanceWebViewFactoryProvider 的物件,那麼,我們可以考慮提前建立sProviderInstance例項,這樣就可以繞過系統檢查,從而避免異常的丟擲。

這時候就需要用到我們的 Hook 思想了,首先我們需要找到一個合適的 Hook 點。sProviderInstance是 static 變數,恰恰是一個非常合適的 Hook 點。這裡需要用到反射的方法,程式碼如下:

/**
     * 避免系統檢查丟擲異常
     */
    public static void checkWebView() {
        int sdkInt = Build.VERSION.SDK_INT;
        try {
            //拿到 WebViewFactory 類
            Class<?> factoryClass = Class.forName("android.webkit.WebViewFactory");
            //拿到類對應的 field
            Field field = factoryClass.getDeclaredField("sProviderInstance");
            //field為private,設定為可訪問的
            field.setAccessible(true);
            //拿到 WebViewFactory 的 sProviderInstance 例項
            //sProviderInstance 是 static 型別,不需要傳入具體物件
            Object sProviderInstance = field.get(null);
            if (sProviderInstance != null) {
                return;
            }
            Method getProviderClassMethod;
            if (sdkInt > 22) {
                getProviderClassMethod = factoryClass.getDeclaredMethod("getProviderClass");
            } else if (sdkInt == 22) {
                getProviderClassMethod = factoryClass.getDeclaredMethod("getFactoryClass");
            } else {
                return;
            }
            getProviderClassMethod.setAccessible(true);
            Class<?> providerClass = (Class<?>) getProviderClassMethod.invoke(factoryClass);
            Class<?> delegateClass = Class.forName("android.webkit.WebViewDelegate");
            Constructor<?> providerConstructor = providerClass.getConstructor(delegateClass);
            if (providerConstructor != null) {
                providerConstructor.setAccessible(true);
                Constructor<?> declaredConstructor = delegateClass.getDeclaredConstructor();
                declaredConstructor.setAccessible(true);
                //利用反射建立了 sProviderInstance
                sProviderInstance = providerConstructor.newInstance(declaredConstructor.newInstance());
                //完成 sProviderInstance 賦值
                field.set("sProviderInstance", sProviderInstance);
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
複製程式碼

這樣我們就完成了 checkWebView()方法,在使用WebView前只需要呼叫一次checkWebView()方法就可以成功繞過系統的檢查,順利在系統程式裡使用WebView。

總結: 在解決這次問題的過程中最大的收穫是瞭解並使用了 HookHook 功能十分強大,遠非三言兩語可以講清楚,大家有興趣可以進一步學習瞭解。

相關文章