
This is a write-up on UnCrackable Level 1, one of OWASP MSTG’s Crackmes, namely a collection of mobile reverse engineering challenges intended at teaching how to perform mobile application reverse engineering.
First, we obtain the APK from https://github.com/OWASP/owasp-mstg/tree/master/Crackmes/Android/Level_01:

Next we install the application and launch it to take a look at how it behaves:


It looks like in order to solve the challenge we’re supposed to enter the correct string into the input field. If we submit the wrong string, we get an error message:

So in order to find the correct string we reverse engineer the app. I like to use APK Studio for tasks like this. After importing the APK and reversing it to Java code, we can try to understand how the app behaves based on code review. From activity_main.xml
, we can see that verify
if the method that gets called after submitting a string for validation (line 6):

Taking a look at verify now, we can infer that line 43 seems to be the one that triggers the entire comparison between input string and expected string, which leads to a success message in case validation succeeds:

Next we take a look at class a
(from the same package, namely sg.vantagepoint.uncrackable1
) to find that it is the one responsible for deciphering what seems to be a ciphered version of the secret string:

Since on method verify
the return value of a.a
(a boolean) seems to perform the validation of whether the correct string was provided or not, then variable bArr
must contain the deciphered text, since it is compared to the input string on line 16.
There were other ways to solve this challenge, but I decided to simply copy the implementation to an Android app on Android Studio I could modify more simply instead of instrumenting UnCrackable Level 1’s APK. Notice bArr
is generated through a call to sg.vantagepoint.a.a.a.
I have also copied this method (shown below) to my app on Android Studio:

So the main activity of my app on Android Studio looked as follows. Notice I changed the return value of what would be sg.vantagepoint.uncrackable1.a.a
for a log line tagged as “Solved” (line 34). This activity deciphers the secret string and logs it:
package xyz.esmev.mymstgpoc;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Base64;
import android.util.Log;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
byte[] bArr;
byte[] bArr2 = new byte[0];
try {
bArr = a(b("8d127684cbc37c17616d806cf50473cc"), Base64.decode("5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=", 0));
} catch (Exception e) {
Log.d("CodeCheck", "AES error:" + e.getMessage());
bArr = bArr2;
}
Log.d("Solved", new String(bArr));
}
public static byte[] a(byte[] bArr, byte[] bArr2) throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException {
SecretKeySpec secretKeySpec = new SecretKeySpec(bArr, "AES/ECB/PKCS7Padding");
Cipher instance = Cipher.getInstance("AES");
instance.init(2, secretKeySpec);
return instance.doFinal(bArr2);
}
public static byte[] b(String str) {
int length = str.length();
byte[] bArr = new byte[(length / 2)];
for (int i = 0; i < length; i += 2) {
bArr[i / 2] = (byte) ((Character.digit(str.charAt(i), 16) << 4) + Character.digit(str.charAt(i + 1), 16));
}
return bArr;
}
}
After running the app, in fact, here’s what we get:

So the secret string should be “I want to believe”. Trying this, we verify that it indeed is the secret:
