OWASP MSTG Crackme 2 writeup (Android)
by Davide Cioccia
To understand more about the application, we extract the source code from the APK and look into interesting classes.
The structure of the app is the following
├── owasp
│ └── mstg
│ └── uncrackable2
│ └── R.java
└── sg
└── vantagepoint
├── a
│ ├── a.java
│ └── b.java
└── uncrackable2
├── CodeCheck.java
└── MainActivity.java
As we can see, the structure is very similar to the previous challenge, but there is a new file
CodeCheck.java
. When we inspect the content of MainActivity.java
we can see how the root detection is handled (same as in the previous challenge), and how the secret is checked. The function verify
handles the secret checksMainActivity.java
public void verify(View view) {
String str;
String obj = ((EditText) findViewById(R.id.edit_text)).getText().toString();
AlertDialog create = new AlertDialog.Builder(this).create();
if (this.m.a(obj)) {
create.setTitle("Success!");
str = "This is the correct secret.";
} else {
create.setTitle("Nope...");
str = "That's not it. Try again.";
}
create.setMessage(str);
create.setButton(-3, "OK", new DialogInterface.OnClickListener() {
/* class sg.vantagepoint.uncrackable2.MainActivity.AnonymousClass3 */
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
});
create.show();
}
where
this.m.a(obj)
is the function that will check whether the secret is the right one. If we look right after the
MainActivity
class definition, we see private CodeCheck m;
static {
System.loadLibrary("foo");
}
where the
CodeCheck
class declares a function that is implemented from the native library foo
.private native boolean bar(byte[] bArr);
So, our next step is to deep dive into the native module.
To find the logic of the
bar
function we will:- rename the .apk in .zip
- extract the native module
lib/libfoo.so
- reverse it using Ghidra
Analyze the
libfoo.so

Looking inside the binary we can identify the native function
Java_sg_vantagepoint_uncrackable2_CodeCheck_bar
that will check whether - the input string has 23 chars (0x17
) - the string in input matches the secret using the strncmp
functionThe secret is directly passed to the
strncmp
function, so we could 
Now let's get to Frida, to see how we can intercept and read the inputs passed to the
strncmp
function used in libfoo.so
.Because we need to trigger the
strcmp
function, we need first to get rid of the root detection block.There are different ways of bypassing the root detection controls that will shut down the app once the OK button is clicked. A "dirty" way is to overload the
onClick
event of the OK button, to avoid that the application will call System.exit(0)
. We can achieve this using the following Frida snippet
/*
Dirty way of bypassing the root detection, avoiding the app to close.
*/
Java.perform(function() {
console.log("[*] Hijacking the onClick button")
var clazz_main = Java.use('sg.vantagepoint.uncrackable2.MainActivity$1')
clazz_main.onClick.implementation = function () {
console.log('onCLick() is replaced ');
};
});
Clicking OK will close the dialog, while the app will still run.
Root detection: bypassed
The
strncmp
function has the following signature:int strncmp(char *__s1,char *__s2,size_t __n)
and is used in our
Java_sg_vantagepoint_uncrackable2_CodeCheck_bar
function in this wayiVar1 = strncmp(__s1,(char *)&local_30,0x17);
where
*__s1
is the text passed in input from the user(char *)&local_30
is the secret we are looking for0x17
is the length (23 bytes)
To extract the secret we can read the inputs of the compare function and print them out when
*__s1
matches our string I want your secret asap
The final script looks like
function extractSecret(){
/*
To use this function, we need to pass in input an argument with 23 chars. We chose: I want your secret asap
*/
console.log()
console.log('[*] ACTION NEEDED: Insert the string "I want your secret asap" as input')
console.log()
setTimeout(function(){
Interceptor.attach(Module.findExportByName('libfoo.so', 'strncmp'),{
onEnter: function(args){
if( Memory.readUtf8String(args[1]).length == 23 && Memory.readUtf8String(args[0]).includes("I want your secret asap")){
console.log()
console.log()
console.log("*******SECRET********")
console.log(Memory.readUtf8String(args[1]))
console.log("*******SECRET********")
console.log()
console.log()
}
},
onLeave: function(retval){
}
});
},2000);
}
Once we call the function via Frida, and insert our magic string, the secret will be printed in the console

and when we insert the new secret in the input field we see

The full script can be downloaded from our repo