# Write-up Android Crackmes Created by: Hùng Hoàng Anh Created time: July 21, 2023 11:57 PM # **Android UnCrackable L1** Using APK Lab, an extension of VS Code to reverse this app. [](https://hackmd.io/_uploads/S1muZipR2.png) There are 2 condition statements to prevent debug and login as root. I deleted smali lines to by this check. ![](https://hackmd.io/_uploads/r1iubi6Cn.png) ![](https://hackmd.io/_uploads/SyltZiTC2.png) I saw verify function to check password: ![](https://hackmd.io/_uploads/Sk4tZiaC3.png) `a.a(obj)` is a compare function to return true if `obj` equal secret key. Step into function `a.a()` ![](https://hackmd.io/_uploads/r1FFbopAh.png) As you can see, the secret key is `new String(bArr)` string object so I set a breakpoint at the smali line refer to this line. I use JADX_GUI and Genymotion to debug this app. ![](https://hackmd.io/_uploads/SJJ5-jT0n.png) Boommm I got the key ![](https://hackmd.io/_uploads/BkHc-ja0n.png) v1 is the register save value of `new String(bArr)` Secret key: `I want to believe` ## Frida approach: ```jsx Java.perform(function(){ Java.use("sg.vantagepoint.uncrackable1.MainActivity") .a .overload("java.lang.String") .implementation = function (string) { console.log("Bypass root check: " + string) }; Java.use("java.lang.String") .equals .overload("java.lang.Object") .implementation = function (arg) { if(this == "flag") console.log("Password: " + arg) return this.equals(arg); }; Java.use("sg.vantagepoint.uncrackable1.MainActivity").onResume.implementation = function () { this.onResume(); Java.use("sg.vantagepoint.uncrackable1.a").a(Java.use('java.lang.String').$new("flag")); }; }) ``` Result: ![](https://hackmd.io/_uploads/B1ysZjpR2.png) # **Android UnCrackable L2** Patch `onCreate()` to bypass root and debugger check. ```jsx .method protected onCreate(Landroid/os/Bundle;)V .locals 4 invoke-direct {p0}, Lsg/vantagepoint/uncrackable2/MainActivity;->init()V goto :goto_0 invoke-static {}, Lsg/vantagepoint/a/b;->a()Z move-result v0 if-nez v0, :cond_0 invoke-static {}, Lsg/vantagepoint/a/b;->b()Z move-result v0 if-nez v0, :cond_0 invoke-static {}, Lsg/vantagepoint/a/b;->c()Z move-result v0 if-eqz v0, :cond_1 :cond_0 const-string v0, "Root detected!" invoke-direct {p0, v0}, Lsg/vantagepoint/uncrackable2/MainActivity;->a(Ljava/lang/String;)V :cond_1 invoke-virtual {p0}, Lsg/vantagepoint/uncrackable2/MainActivity;->getApplicationContext()Landroid/content/Context; move-result-object v0 invoke-static {v0}, Lsg/vantagepoint/a/a;->a(Landroid/content/Context;)Z move-result v0 if-eqz v0, :cond_2 const-string v0, "App is debuggable!" invoke-direct {p0, v0}, Lsg/vantagepoint/uncrackable2/MainActivity;->a(Ljava/lang/String;)V :cond_2 new-instance v0, Lsg/vantagepoint/uncrackable2/MainActivity$2; invoke-direct {v0, p0}, Lsg/vantagepoint/uncrackable2/MainActivity$2;-><init>(Lsg/vantagepoint/uncrackable2/MainActivity;)V const/4 v1, 0x3 new-array v1, v1, [Ljava/lang/Void; const/4 v2, 0x0 const/4 v3, 0x0 aput-object v3, v1, v2 const/4 v2, 0x1 aput-object v3, v1, v2 const/4 v2, 0x2 aput-object v3, v1, v2 invoke-virtual {v0, v1}, Lsg/vantagepoint/uncrackable2/MainActivity$2;->execute([Ljava/lang/Object;)Landroid/os/AsyncTask; :goto_0 new-instance v0, Lsg/vantagepoint/uncrackable2/CodeCheck; invoke-direct {v0}, Lsg/vantagepoint/uncrackable2/CodeCheck;-><init>()V iput-object v0, p0, Lsg/vantagepoint/uncrackable2/MainActivity;->m:Lsg/vantagepoint/uncrackable2/CodeCheck; invoke-super {p0, p1}, Landroid/support/v7/app/c;->onCreate(Landroid/os/Bundle;)V const p1, 0x7f09001b invoke-virtual {p0, p1}, Lsg/vantagepoint/uncrackable2/MainActivity;->setContentView(I)V return-void .end method ``` Let’s dive into main code: ![](https://hackmd.io/_uploads/SkPo-o6R2.png) This app using `m4a()` from `CodeCheck` to check for the correct key. ![](https://hackmd.io/_uploads/Hy2j-iaA2.png) Jump into class `CodeCheck` I saw native funtion `bar` so I found the lib file. ![](https://hackmd.io/_uploads/HJS3biTRn.png) Lib `foo` had been loaded in `MainActivity` class. I reversed `[libfoo.so](http://libfoo.so)` using `ghidra` and I found function `bar`: ![](https://hackmd.io/_uploads/HJ53-jTAn.png) This function compare input string with an string save in the memory. But secret string has been splited into some memory addresses located side by side shown in above picture. I concatenate these variables to: `0x6873696620656874206c6c6120726f6620736b6e616854` This is the hex present of the secret string after flipped Using [cyberchef](https://cyberchef.io/) to decode it to string and reverse: ![Uploading file..._c98lh8ngn]() ![](https://hackmd.io/_uploads/HkwT-iTC2.png) The secret string is `Thanks for all the fish` ## Frida approach: ```jsx Java.perform(function () { Java.use("sg.vantagepoint.uncrackable2.MainActivity") .a .overload("java.lang.String") .implementation = function (string) { console.log("Bypass root and degguber check: " + string); }; Java.use('sg.vantagepoint.uncrackable2.MainActivity').onResume.implementation = function(){ this.onResume() Java.use("sg.vantagepoint.uncrackable2.CodeCheck").$new().a(Java.use('java.lang.String').$new("Jvc9fqeJVkbNAqpCESxr5it")); Interceptor.detachAll(); } }); Interceptor.attach(Module.findExportByName('libc.so', 'strncmp'), { onEnter: function (args) { let inputStr = "Jvc9fqeJVkbNAqpCESxr5it"; if(args[2].toInt32() != inputStr.length ) return; try{ let str1 = args[0].readCString(inputStr.length); let str2 = args[1].readCString(inputStr.length); if(str1.includes(inputStr) || str2.includes(inputStr)){ console.log(`Flag in: ${str1} || ${str2}`) } }catch(e){ console.log(e); } } }); ``` PoC: ![](https://hackmd.io/_uploads/B111MopR2.png) # **Android UnCrackable L3** Using JEB to decompile APK and its native library: `onCreate()` ![](https://hackmd.io/_uploads/r10kGjpC3.png) In order to bypass `onCreate()` function we just need to change `showDialog()` ```jsx Java.perform(function () { Java.use("sg.vantagepoint.uncrackable3.MainActivity") .showDialog .overload("java.lang.String") .implementation = function (string) { console.log("Bypassed showDialog, message: ", string); return; }; Java.use("sg.vantagepoint.uncrackable3.MainActivity").$init.implementation = function () { this.$init(); printSecretKey(); }; }); ``` But when run frida we meet an error: ![](https://hackmd.io/_uploads/BJNefi6R3.png) According to this error, `goodbye()` in native library is called in order to exit program. ![](https://hackmd.io/_uploads/Hk5gMopAh.png) Go deep into natve lib, I saw a function called `goodbye()`, when `strstr()`is call with an argument named as “frida” or “xposed”, the program will exit. `strstr()` is a function of `libc.so`, I change strstr function in order to run frida. ```jsx Interceptor.attach(Module.findExportByName('libc.so', 'strstr'), { onEnter: function (args) { this.fridaDetected = 0; if (args[1].readUtf8String().indexOf("frida") != -1) { this.fridaDetected = 1; } }, onLeave: function (retVal) { if (this.fridaDetected == 1) retVal.replace(0); } }); ``` ![](https://hackmd.io/_uploads/r1lWMop0h.png) `code_check()`using `bar()` from native lib to check password: ![](https://hackmd.io/_uploads/HJHZGj602.png) Go to `bar()`: ![](https://hackmd.io/_uploads/Syq-MjaC3.png) It use xor to check password. `password = xor__key ^ secret_key` `secret_key` is generated by `generate_secret()` Use this code to find `secret_key`: ```jsx function printSecretKey() { Interceptor.attach(Module.getBaseAddress('libfoo.so').add(0x0fa0), { onEnter: function (args) { console.log("Secret key before generate at address: " + args[0]); this.answerAdress = args[0]; console.log(hexdump(args[0], { offset: 0, length: 24, header: true, ansi: true })); }, onLeave: function (retVal) { console.log("Secret key after generate: "); console.log(hexdump(this.answerAdress, { offset: 0, length: 24, header: true, ansi: true })); } }); Java.use("sg.vantagepoint.uncrackable3.MainActivity").onResume.implementation = function () { this.onResume(); Java.choose("sg.vantagepoint.uncrackable3.CodeCheck", { onMatch: function (instance) { instance.check_code(Java.use("java.lang.String").$new("123")); return "stop"; }, onComplete: function () { } }); }; } ``` Combine above scripts: ```jsx Java.perform(function () { Java.use("sg.vantagepoint.uncrackable3.MainActivity") .showDialog .overload("java.lang.String") .implementation = function (string) { console.log("Bypassed showDialog, message: ", string); return; }; Java.use("sg.vantagepoint.uncrackable3.MainActivity").$init.implementation = function () { this.$init(); printSecretKey(); }; }); Interceptor.attach(Module.findExportByName('libc.so', 'strstr'), { onEnter: function (args) { this.fridaDetected = 0; if (args[1].readUtf8String().indexOf("frida") != -1) { this.fridaDetected = 1; } }, onLeave: function (retVal) { if (this.fridaDetected == 1) retVal.replace(0); } }); function printSecretKey() { Interceptor.attach(Module.getBaseAddress('libfoo.so').add(0x0fa0), { onEnter: function (args) { console.log("Secret key before generate at address: " + args[0]); this.answerAdress = args[0]; console.log(hexdump(args[0], { offset: 0, length: 24, header: true, ansi: true })); }, onLeave: function (retVal) { console.log("Secret key after generate: "); console.log(hexdump(this.answerAdress, { offset: 0, length: 24, header: true, ansi: true })); } }); Java.use("sg.vantagepoint.uncrackable3.MainActivity").onResume.implementation = function () { this.onResume(); Java.choose("sg.vantagepoint.uncrackable3.CodeCheck", { onMatch: function (instance) { instance.check_code(Java.use("java.lang.String").$new("123")); return "stop"; }, onComplete: function () { } }); }; } ``` Secret key as bytes array: ![](https://hackmd.io/_uploads/ryXfGi6C3.png) Find password using cyberchef: ![](https://hackmd.io/_uploads/S1PMzoTRh.png)