Mobile Hacking Lab Android Lab - Strings Writeup
Our objective for the Strings lab is to find the hidden flag by investigating the app components and by using dynamic instrumentation.
Running the provided application gives use the following.

Looking at the AndroidManifest.xml file for the application we notice that in addition to the main activity the application has another exported activity com.mobilehackinglab.challenge.Activity2.
<activity
android:name="com.mobilehackinglab.challenge.Activity2"
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="labs"/>
</intent-filter>
</activity>
Looking at the intent filter for this activity we see that to launch it we need to use the URI scheme mhl and the host labs.
We attempt to launch it using the following command.
adb shell am start -a android.intent.action.VIEW -d "mhl://labs/" -n com.mobilehackinglab.challenge/.Activity2
Doing this closes the application. Let take a closer look at Activity2 to see what it’s expecting from us to able to successfully launch it.
@Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_2);
SharedPreferences sharedPreferences = getSharedPreferences("DAD4", 0);
String u_1 = sharedPreferences.getString("UUU0133", null);
boolean isActionView = Intrinsics.areEqual(getIntent().getAction(), "android.intent.action.VIEW");
boolean isU1Matching = Intrinsics.areEqual(u_1, cd());
if (isActionView && isU1Matching) {
Uri uri = getIntent().getData();
if (uri != null && Intrinsics.areEqual(uri.getScheme(), "mhl") && Intrinsics.areEqual(uri.getHost(), "labs")) {
String base64Value = uri.getLastPathSegment();
byte[] decodedValue = Base64.decode(base64Value, 0);
if (decodedValue != null) {
String ds = new String(decodedValue, Charsets.UTF_8);
byte[] bytes = "your_secret_key_1234567890123456".getBytes(Charsets.UTF_8);
Intrinsics.checkNotNullExpressionValue(bytes, "this as java.lang.String).getBytes(charset)");
String str = decrypt("AES/CBC/PKCS5Padding", "bqGrDKdQ8zo26HflRsGvVA==", new SecretKeySpec(bytes, "AES"));
if (str.equals(ds)) {
System.loadLibrary("flag");
String s = getflag();
Toast.makeText(getApplicationContext(), s, 1).show();
return;
} else {
finishAffinity();
finish();
System.exit(0);
return;
}
}
finishAffinity();
finish();
System.exit(0);
return;
}
finishAffinity();
finish();
System.exit(0);
return;
}
finishAffinity();
finish();
System.exit(0);
}
Looking at the code we can see that we need the following in order to launch Activity2:
- Set the
UUU0133value in theDAD4shared preferences to the value returned by the methodcdwhich returns the current date in thedd/MM/yyyyformat. - Decrypt
bqGrDKdQ8zo26HflRsGvVA==with the provided keyyour_secret_key_1234567890123456. - Base64 encode the decrypted value and use it as the last fragment of our data URI.
mhl://data/<decrypted base64 encrypted value here.> - Use the data URI to launch
Activity2. This will execute thegetflagnative function that will store the flag in memory. - Scan the application memory to dump flag.
Creating the DAD4 sharedPreferences.
Looking at MainActivity code we can see that method KLOW does exactly what we need to create the required shared preferences.
public final void KLOW() {
SharedPreferences sharedPreferences = getSharedPreferences("DAD4", 0);
SharedPreferences.Editor editor = sharedPreferences.edit();
Intrinsics.checkNotNullExpressionValue(editor, "edit(...)");
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy", Locale.getDefault());
String cu_d = sdf.format(new Date());
editor.putString("UUU0133", cu_d);
editor.apply();
}
The problem is that this method is never called during normal execution. To get around this we will make use of frida to dynamically instrument the application and execute the KLOW method. The following frida script will take care of that.
// KLOW.js
if (Java.available) {
Java.perform(() => {
Java.choose("com.mobilehackinglab.challenge.MainActivity", {
onMatch: function(instance) {
send(instance.KLOW());
},
onComplete: function() {
send("done");
}
})
})
} else {
console.error("Java not available");
}
We save it to a file of our choosing and run the following command:
frida -U Strings -l KLOW.js
And with this we have created the DAD4 shared preferences and set the UUU0133 value to the current date.
Getting the value for the last fragment.
Decrypt method:
public final String decrypt(String algorithm, String cipherText, SecretKeySpec key) {
Intrinsics.checkNotNullParameter(algorithm, "algorithm");
Intrinsics.checkNotNullParameter(cipherText, "cipherText");
Intrinsics.checkNotNullParameter(key, "key");
Cipher cipher = Cipher.getInstance(algorithm);
try {
byte[] bytes = Activity2Kt.fixedIV.getBytes(Charsets.UTF_8);
Intrinsics.checkNotNullExpressionValue(bytes, "this as java.lang.String).getBytes(charset)");
IvParameterSpec ivSpec = new IvParameterSpec(bytes);
cipher.init(2, key, ivSpec);
byte[] decodedCipherText = Base64.decode(cipherText, 0);
byte[] decrypted = cipher.doFinal(decodedCipherText);
Intrinsics.checkNotNull(decrypted);
return new String(decrypted, Charsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("Decryption failed", e);
}
}
Getting our last fragment is simple enough. All we have to do is replacate the decrypt method and base64 encode the returned value. We do this using the following python script.
## pip install pycryptodome
from Crypto.Cipher import AES
import base64
from Crypto.Util.Padding import unpad
def decrypt_aes_cbc_pkcs5(ciphertext, key, iv):
"""Decrypts AES-CBC encrypted data with PKCS5 padding."""
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = cipher.decrypt(ciphertext)
return unpad(plaintext, AES.block_size)
# Example usage
iv = b'1234567890123456'
key = b'your_secret_key_1234567890123456'
ciphertext_base64 = "bqGrDKdQ8zo26HflRsGvVA=="
ciphertext = base64.b64decode(ciphertext_base64)
decrypted_text = decrypt_aes_cbc_pkcs5(ciphertext, key, iv)
print(f"decrypted: {decrypted_text.decode('utf-8')}")
print(f"base64 encoded: {base64.b64encode(decrypted_text).decode('utf-8')}")
Running our script gives us the following output:
decrypted: mhl_secret_1337
base64 encoded: bWhsX3NlY3JldF8xMzM3
Dumping the flag from memory
Now we can take the base64 encoded output and use in to build our intent:
adb shell am start -a android.intent.action.VIEW -d "mhl://labs/bWhsX3NlY3JldF8xMzM3" -n com.mobilehackinglab.challenge/.Activity2
With this command we are able to successfully launch com.mobilehackinglab.challenge.Activity2.

Now that we have successfully launched Activity2 we can proceed to use frida to dump the flag from memory using the following script.
// memoryScan.js
function stringToHex(str) {
let hex = '';
for (let i = 0; i < str.length; i++) {
hex += str.charCodeAt(i).toString(16) + " ";
}
return hex.slice(0, -1);
}
const flagModule = Process.getModuleByName("libflag.so");
console.log(JSON.stringify(flagModule));
const pattern = stringToHex("MHL{");
console.log("patern: " + pattern);
Memory.scan(flagModule.base, flagModule.size, pattern, {
onMatch(address, size) {
console.log(address.readUtf8String());
return 'stop';
},
onComplete() {
console.log("Memory.scan() complete.");
}
});
Output:
> frida frida -U Strings -l memoryScan.js
____
/ _ | Frida 16.6.4 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://frida.re/docs/home/
. . . .
. . . . Connected to Android Emulator 5554 (id=emulator-5554)
Attaching...
{"name":"libflag.so","base":"0x77ff7cb000","size":20480,"path":"/data/app/~~BkfNExZ2k0UV6MaIYQFPUQ==/com.mobilehackinglab.challenge-iejiH7VaOc4GYLrMXBcxxQ==/base.apk!/lib/arm64-v8a/libflag.so"}
patern: 4d 48 4c 7b
MHL{IN_THE_MEMORY}
Memory.scan() complete.
[Android Emulator 5554::Strings ]->
This gives us the flag: MHL{IN_THE_MEMORY}