WebView运行在系统进程出现的问题 WebView is not allowed in privileged processes

来源:互联网 发布:网络摄像头录像机 编辑:程序博客网 时间:2024/04/29 18:11
WebView运行在系统进程的问题
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层!
2 0
原创粉丝点击