Skip to content
dante's blog
Go back

MHL Lab Strings Writeup

hi, in this blog i write a writeup about the Android MHL Lab called “Strings”.

when reversing an android application, the first step is usually analyzing the AndroidManifest.xml. In this file, we identified two exported activities

exported activities are highly interesting because they can be invoked from outside the application using tools like ADB, Drozer, or by sending an Intent from another app

exported activities

moving on to Activity2, we can see a routine responsible for processing data from the mhl://labs deep link

Activity2

the application uses the AES/CBC/PKCS5Padding cryptographic algorithm. by analyzing the code, we managed to extract the following parameters

however, to successfully decrypt the ciphertext, we still need the Initialization Vector (IV)

decrypt function

looking at the decrypt method, we will see a reference to a variable named fixedIV. tracing this variable directs us to another class, Activity2Kt, where the IV is actually defined

IV

we can head over to cyberchef to decrypt the ciphertext using the key and IV we found

mhl secret

to pass this decrypted secret back to the application via the deep link, it must be base64 encoded first

base64 mhl secret

with the payload ready, we can trigger the deep link using ADB:

adb shell am start -a android.intent.action.VIEW -n com.mobilehackinglab.challenge/com.mobilehackinglab.challenge.Activity2 -d "mhl://labs/bWhsX3NlY3JldF8xMzM3"

deep link start

but the app immediately crashes, to understand why, we can read thru the code, spesifically onCreate method in Activity2

UUU0133

before reaching the decryption logic, the app retrieves a value from SharedPreferences using the key UUU0133 and compares it against the return value of a private method named cd()

cd function

in cd() method, we can see that it initializes a SimpleDateFormat object and formats the current date into a dd/MM/yyyy string

this means the application implements a strict time-based validation. for our deep link payload to be processed, the UUU0133 key in the SharedPreferences must not be empty and must exactly match today’s date

since we invoked the activity directly via ADB without letting the app set this state naturally, the comparison failed, triggering a System.exit(0)

to figure out how to properly set this state, we can look at the MainActivity class. there, we can see a method called KLOW()

klow

reading through its logic, it became clear that this function is designed to generate the exact SharedPreferences we were missing, formatting today’s date and saving it into the UUU0133 key

but KLOW() is never executed by the app. It just sits there as an unused method

we have a few ways to bypass this, but the most elegant solution doesn’t require rebuilding the APK or having root access to modify XML files

since KLOW() is a public method, we can simply hook into the application using frida, find the active MainActivity instance in memory, and pull the trigger on KLOW() ourselves

here’s the frida script that i use to call KLOW() function

Java.perform(function () {  
   Java.choose("com.mobilehackinglab.challenge.MainActivity", {  
       onMatch: function (instance) {  
           try {  
               instance.KLOW();  
           } catch (err) {  
               console.log("Failed to call KLOW(): " + err);  
           }  
       },  
       onComplete: function () {  
           console.log("done.\n");  
       }    
   });     
});

to use frida, we must enable the frida-server first in the android

frida server

and now we can try hooking it

frida -U -n Strings -l klow.js

frida hooking klow

now we can clearly see that the shared_prefs folder in internal storage isn’t empty anymore

dad4

and now we can try to trigger the deeplink again

adb shell am start -a android.intent.action.VIEW -n com.mobilehackinglab.challenge/com.mobilehackinglab.challenge.Activity2 -d "mhl://labs/bWhsX3NlY3JldF8xMzM3"

success

the app doesn’t crash this time, meaning we successfully bypassed the date validation

but we still don’t have the flag. while Activity2 handles the initial validation and deep link decryption, the actual flag generation is offloaded to native code

if we examine the successful execution path inside Activity2, we can clearly see how the application processes the flag once our deep link payload (ds) matches the decrypted string (str)

load library

however, executing this only yields a toast message saying “Success”, not the actual flag in the MHL{...} format

the java layer is essentially a dummy wrapper. the real flag decryption and storage happen entirely within the native C/C++ layer inside libflag.so

to extract the actual flag, we must shift our focus from java to dynamic instrumentation of the native memory

Java.perform(function() {  
   var Activity2 = Java.use("com.mobilehackinglab.challenge.Activity2");  
  
   Activity2.getflag.implementation = function() {  
       var retval = this.getflag();  
       var module = Process.findModuleByName("libflag.so");  
       if (module) {  
           var pattern = "4D 48 4C 7B"; // "MHL{" Hex  
           Memory.scan(module.base, module.size, pattern, {  
               onMatch: function(address, size) {  
                   try {  
                       var flag = address.readUtf8String();  
                       console.log("flag: " + flag);  
                   } catch(e) {  
                       console.log("failed to extract string: " + e.message);  
                   }  
                   console.log("======================================\n");  
               },  
               onError: function(reason) {  
  
               },  
               onComplete: function() {  
                   console.log("done.");  
               }  
           });  
       } else {  
           console.log("libflag.so isn't load.");  
       }  
       return retval;  
   };  
});

using the above frida script, we intercept the getflag() method call. instead of blocking the execution, we let the original method run by calling this.getflag()

this is a crucial step because it allows the native C/C++ code to execute its decryption routine and load the actual flag into memory

immediately after the native method finishes, our script locates the base address of libflag.so and uses the Memory.scan API to search for the hex pattern 4D 48 4C 7B (which represents the ASCII string MHL{)

frida -U -n Strings -l memodump.js

memodump frida

with the script injected, we fire our ADB deep link command one last time

adb shell am start -a android.intent.action.VIEW -n com.mobilehackinglab.challenge/com.mobilehackinglab.challenge.Activity2 -d "mhl://labs/bWhsX3NlY3JldF8xMzM3"

flag

and we got the flag, MHL{IN_THE_MEMORY}



Previous Post
MHL Labs Food Store Writeup (simple)