paint-brush
Uncrackable - Level 1 | OWASPby@atul

Uncrackable - Level 1 | OWASP

by Atul (iamatulsingh)October 8th, 2024
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Let’s dive into analyzing the`OWASP Uncrackable Level 1` app. This can be solved in two ways, using frida (not required here!) Using normal code analysis and wite your own code from that. I’ll show you both ways.
featured image - Uncrackable - Level 1 | OWASP
Atul (iamatulsingh) HackerNoon profile picture

Let’s dive into analyzing the OWASP Uncrackable Level 1 app!


This can be solved in two ways,

  1. Using Frida (not required here!)
  2. Using normal code analysis and writing your own code from that.


I’ll show you both ways. So, let’s buckle up for both ways to solve this challenge.


Using Frida

Root Detection

Upon opening the app, it closes due to root detection, as shown below:


initial check

To understand why, we can decompile the APK using jadx. In the AndroidManifest.xml, the Launcher activity is defined as owasp.mstg.uncrackable1.


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    android:versionCode="1"
    android:versionName="1.0"
    package="owasp.mstg.uncrackable1">
    <uses-sdk
        android:minSdkVersion="19"
        android:targetSdkVersion="28"/>
    <application
        android:theme="@style/AppTheme"
        android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher"
        android:allowBackup="true">
        <activity
            android:label="@string/app_name"
            android:name="sg.vantagepoint.uncrackable1.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>
</manifest>


In MainActivity, we find the code responsible for detecting the root and closing the app using System.exit(0);.


MainActivity


private void a(String str) {
    AlertDialog create = new AlertDialog.Builder(this).create();
    create.setTitle(str);
    create.setMessage("This is unacceptable. The app is now going to exit.");
    create.setButton(-3, "OK", new DialogInterface.OnClickListener() { // from class: sg.vantagepoint.uncrackable1.MainActivity.1
        @Override // android.content.DialogInterface.OnClickListener
        public void onClick(DialogInterface dialogInterface, int i) {
            System.exit(0);
        }
    });
    create.setCancelable(false);
    create.show();
}


@Override // android.app.Activity
protected void onCreate(Bundle bundle) {
    if (c.a() || c.b() || c.c()) {
        a("Root detected!");
    }
    if (b.a(getApplicationContext())) {
        a("App is debuggable!");
    }
    super.onCreate(bundle);
    setContentView(R.layout.activity_main);
}


Bypassing Root Detection

Let’s write a frida script to hook and bypass this check.


Java.perform(function() {
    var hook = Java.use("java.lang.System");
    hook.exit.implementation = function() {
        console.log("Root Check Bypassed!!! 😎");
    };
});


App Functionality

Once bypassed, the app presents a text field and a verify button. Clickingverify shows a message: That's not it. Try again.


By searching for this string in the code, we find the verification logic:


root detection bypass


Let’s take this as a reference to move ahead and find this string in the code. After searching this out, you can see a code like below which seems like a comparison between input value and some hard-coded value.


verify function


Analyzing the Verification Logic

We need to inspect the a method to understand the comparison.


verification logic


public class a {
    public static boolean a(String str) {
        byte[] bArr;
        byte[] bArr2 = new byte[0];
        try {
            bArr = sg.vantagepoint.a.a.a(b("8d127684cbc37c17616d806cf50473cc"), Base64.decode("5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=", 0));
        } catch (Exception e) {
            Log.d("CodeCheck", "AES error:" + e.getMessage());
            bArr = bArr2;
        }
        return str.equals(new String(bArr));
    }

    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;
    }
}


The bArr value is what our input is being compared to. The sg.vantagepoint.a.a.a method is an AES decryption method.


encryption function


public class a {
    public static byte[] a(byte[] bArr, byte[] bArr2) {
        SecretKeySpec secretKeySpec = new SecretKeySpec(bArr, "AES/ECB/PKCS7Padding");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(2, secretKeySpec);
        return cipher.doFinal(bArr2);
    }
}


Hooking the AES Cipher Function

We can hook this function to get the string value

var a = Java.use('sg.vantagepoint.a.a');
a.a.implementation = function (p0, p1) {
    console.log('p0 (byte array): ' + bytesToString(p0));
    console.log('p1 (byte array): ' + bytesToString(p1));
    
    var result = this.a(p0, p1);
    console.log("Result ->", bytesToString(result))
    return result;
};

function bytesToString(bytes) {
    var result = '';
    for (var i = 0; i < bytes.length; ++i) {
        result += String.fromCharCode(bytes[i]);
    }
    return result;
}


Running the Script

Execute the script to see the byte array as a string.


(base) C:\Users\booyaa\uncrackable\level> frida -U -l ./hook_level1.js -f owasp.mstg.uncrackable1
     ____
    / _  |   Frida 16.2.3 - 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 LE2001 (id=4a298ca9)
Spawned `owasp.mstg.uncrackable1`. Resuming main thread!
[LE2101::owasp.mstg.uncrackable1 ]-> Hooked exit()
Root Check Bypassed!!! 😎
p0 (byte array): ヘvトᅨᅢ|amタl￵sᅩ
p1 (byte array): ¥Bbᅨ[レᅢᅠᄉ₩ᄂᄑvレI│￰t￸.￿ユᆱ|v￧
Result -> I want to believe
Process terminated
[LE2101::owasp.mstg.uncrackable1 ]->

Thank you for using Frida!
(base) C:\Users\booyaa\uncrackable\level>


final output


And there we have it; the final string: I want to believe.


Using just Python and no rooted device or Frida.


# level1.py

import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

def a(key, cipher_text):
    cipher = AES.new(key, AES.MODE_ECB)
    return unpad(cipher.decrypt(cipher_text), AES.block_size)

def b(hex_string):
    return bytes.fromhex(hex_string)

try:
    key = b("8d127684cbc37c17616d806cf50473cc")
    cipher_text = base64.b64decode("5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=")
    returned_bytes = a(key, cipher_text)
    print("Solved: ", returned_bytes.decode())
except Exception as e:
    print("AES error: ", str(e))


Thanks for following along! Cheers 🍺