OWASP MSTG Crackme 3 writeup (Android)
by Davide Cioccia
Last updated
by Davide Cioccia
Last updated
PENETRATION TESTS
PentestsLET'S MEET
Book 15 minutes with one of our experts@ dcodx.com
The application has the same look as the others and the same message when root detection kicks in.
Let's have a deeper look.
As usual, we extract the source code from the APK and look into interesting classes.
The main structure of the app is the following
When we inspect the content of MainActivity.java
we can quickly see that things are getting spicier and the root detection is not the only control anymore, but integrity checks are added to prevent code tampering. On line 101 we see
where tampered
is set to 31337 from the function verifyLibs
when the native libraries are hooked or manipulated.
When we try to hook the functions and instrument the application we can see the following message in the log files
and the app miserably crashes
The secret is still hidden in the same native library libfoo.so
that implements the bar()
function
We investigate further whether the native code changed.
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
Looking inside the binary we right away, see the native function Java_sg_vantagepoint_uncrackable2_CodeCheck_bar
being exported and including few small changes from the previous challenge.
In the new version:
the input string has 24 chars (line 27: if (iVar2 == 0x18)
)
every character of the string inserted by the user is XORed with each character of the xorkey
(private static final String xorkey = "pizzapizzapizzapizzapizz";
)
The specific instruction pseudocode looks like:
If the chars do not match, function LAB_00013456
is called and 0 is returned, making the check fail.
Something to note is that this challenge includes a new function Java_sg_vantagepoint_uncrackable3_MainActivity_init
that is the implementation of the init(byte[] Array)
used to initialize the XOR key(init(xorkey.getBytes());
)
Unfortunately (as expected) the script from the previous challenge does not work anymore, due to the more complex checks we mentioned above. On top of that, more checks are also implemented in the native library. When we try to bypass the root detection we receive the following stacktrace:
We can see that a goodbye()
function is defined in libfoo.so
and is making the app crash. Using Ghidra we note that there is a new functionality able to detect whether Frida
or Xposed
are used.
In case at least one is found, the app will exit calling the goodbye()
function. To bypass all the controls we are going two use two different techniques:
overload of the fgets
function to avoid file detection
overload of System.exit(int)
to stop the app to close when clicking OK
The script will look like the following:
At this point we can run our application using the following command:
where hook.js
will call the function rootAndTamperingDetectionBypass()
at execution time, preventing the app to crash.
Now it's time to extract the secret
To extract the secret we are going to use Frida, but with a slightly different approach than in the previous challenges. Looking at the flow, we can see that the function Java_sg_vantagepoint_uncrackable2_CodeCheck_bar
on line 24, uses another function that we call FUN_00010fa0
, that is used to load the secrets in memory, but unfortunately is not exported and therefore we cannot directly hook it.
This function is quite complex, but we know, that once executed, the secret will be loaded in memory. So how do we extract the secret?
First of all, we need to find a way to hook into this function. We are going to use a different method, that will calculate the base address of the libfoo.so
library and the offset of the function.
From Ghidra we can see the offset of the function being mentioned in the name. Again, we are using an x86 device and therefore using the x86 version of the library. In case an ARM device is used, the offset and the instruction will be different. The offset of the function is 0xFA0
. Let's see how we can hook it. These are the steps to extract the secret:
Calculate the base address of libfoo.so
using Module.findBaseAddress('libfoo.so')
Add the offset to the base address to get to the function using base_address.add(0xFA0)
Get the buffer onEnter
Get the first 24 bytes onLeave
XOR each byte of the secret with each byte of the well known XOR key
We introduced a timeout to make sure that the library libfoo.so
is loaded when calling the function
When we run the full script hook.js
, using the command frida -U -l hook.js -f owasp.mstg.uncrackable3 --no-pause
, the secret will be printed out as soon as the button Verify
is clicked.
And voilá, the secret making owasp great again
is printed in the console and the challenge is completed
The full Frida script can be downloaded from our repo