owaspuncrackable-1.md (7802B)
1 --- 2 title: "Frida Tutorial 3" 3 section: "Mobile" 4 sectionSlug: "mobile-pentesting" 5 sourcePath: "src/mobile-pentesting/android-app-pentesting/frida-tutorial/owaspuncrackable-1.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/mobile-pentesting/android-app-pentesting/frida-tutorial/owaspuncrackable-1.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Frida Tutorial 3 14 15 --- 16 17 **This is a summary of the post**: [https://joshspicer.com/android-frida-1](https://joshspicer.com/android-frida-1)<sup>[[1]](#references)</sup>\ 18 **APK**: [https://github.com/OWASP/owasp-mstg/blob/master/Crackmes/Android/Level_01/UnCrackable-Level1.apk](https://github.com/OWASP/owasp-mstg/blob/master/Crackmes/Android/Level_01/UnCrackable-Level1.apk) 19 20 ## Solution 1 21 22 **Hook the `exit()` and decrypt functions** so the flag is printed in the Frida console when you press **Verify**: 23 24 ```javascript 25 Java.perform(function () { 26 send("Starting hooks OWASP uncrackable1...") 27 28 function getString(data) { 29 var ret = "" 30 for (var i = 0; i < data.length; i++) { 31 ret += "#" + data[i].toString() 32 } 33 return ret 34 } 35 36 var aes_decrypt = Java.use("sg.vantagepoint.a.a") 37 aes_decrypt.a.overload("[B", "[B").implementation = function (var_0, var_1) { 38 send( 39 "sg.vantagepoint.a.a.a([B[B)[B doFinal(enc) // AES/ECB/PKCS7Padding" 40 ) 41 send("Key : " + getString(var_0)) 42 send("Encrypted : " + getString(var_1)) 43 var ret = this.a.overload("[B", "[B").call(this, var_0, var_1) 44 send("Decrypted : " + getString(ret)) 45 46 var flag = "" 47 for (var i = 0; i < ret.length; i++) { 48 flag += String.fromCharCode(ret[i]) 49 } 50 send("Decrypted flag: " + flag) 51 return ret //[B 52 } 53 54 var sysexit = Java.use("java.lang.System") 55 sysexit.exit.overload("int").implementation = function (var_0) { 56 send("java.lang.System.exit(I)V // We avoid exiting the application :)") 57 } 58 59 send("Hooks installed.") 60 }) 61 ``` 62 63 ## Solution 2 64 65 **Hook the root checks and decrypt function** so the flag is printed in the Frida console when you press **Verify**: 66 67 ```javascript 68 Java.perform(function () { 69 send("Starting hooks OWASP uncrackable1...") 70 71 function getString(data) { 72 var ret = "" 73 for (var i = 0; i < data.length; i++) { 74 ret += "#" + data[i].toString() 75 } 76 return ret 77 } 78 79 var aes_decrypt = Java.use("sg.vantagepoint.a.a") 80 aes_decrypt.a.overload("[B", "[B").implementation = function (var_0, var_1) { 81 send( 82 "sg.vantagepoint.a.a.a([B[B)[B doFinal(enc) // AES/ECB/PKCS7Padding" 83 ) 84 send("Key : " + getString(var_0)) 85 send("Encrypted : " + getString(var_1)) 86 var ret = this.a.overload("[B", "[B").call(this, var_0, var_1) 87 send("Decrypted : " + getString(ret)) 88 89 var flag = "" 90 for (var i = 0; i < ret.length; i++) { 91 flag += String.fromCharCode(ret[i]) 92 } 93 send("Decrypted flag: " + flag) 94 return ret //[B 95 } 96 97 var rootcheck1 = Java.use("sg.vantagepoint.a.c") 98 rootcheck1.a.overload().implementation = function () { 99 send("sg.vantagepoint.a.c.a()Z Root check 1 HIT! su.exists()") 100 return false 101 } 102 103 var rootcheck2 = Java.use("sg.vantagepoint.a.c") 104 rootcheck2.b.overload().implementation = function () { 105 send("sg.vantagepoint.a.c.b()Z Root check 2 HIT! test-keys") 106 return false 107 } 108 109 var rootcheck3 = Java.use("sg.vantagepoint.a.c") 110 rootcheck3.c.overload().implementation = function () { 111 send("sg.vantagepoint.a.c.c()Z Root check 3 HIT! Root packages") 112 return false 113 } 114 115 var debugcheck = Java.use("sg.vantagepoint.a.b") 116 debugcheck.a.overload("android.content.Context").implementation = function ( 117 var_0 118 ) { 119 send("sg.vantagepoint.a.b.a(Landroid/content/Context;)Z Debug check HIT! ") 120 return false 121 } 122 123 send("Hooks installed.") 124 }) 125 ``` 126 127 --- 128 129 ## Solution 3 – `frida-trace` (Frida ≥ 16) 130 131 If you do not want to hand-write hooks, let **Frida** generate Java stubs and then edit them:<sup>[[2]](#references)</sup> 132 133 ```bash 134 # Spawn the application and automatically trace the Java method we care about 135 adb shell "am force-stop owasp.mstg.uncrackable1" 136 frida-trace -U -f owasp.mstg.uncrackable1 \ 137 -j 'sg.vantagepoint.a.a.a("[B","[B")[B' \ 138 -j 'sg.vantagepoint.a.c!*' \ 139 --output ./trace 140 141 # The first run will create ./trace/scripts/sg/vantagepoint/a/a/a__B_B_B.js 142 # Edit that file and add the logic that prints the decrypted flag or 143 # returns a constant for the root-checks, then: 144 frida -U -f owasp.mstg.uncrackable1 -l ./trace/_loader.js --no-pause 145 ``` 146 147 With Frida 16+ the generated stub already uses the modern **ES6** template syntax and will compile with the built-in *QuickJS* runtime – you no longer need `frida-compile`. 148 149 --- 150 151 ## Solution 4 – Objection command 152 153 Objection wraps Frida and provides commands for watching methods and changing return values. Command names vary between Objection releases, so confirm the syntax with `help` in the installed version.<sup>[[3]](#references)</sup> 154 155 ```bash 156 objection -g owasp.mstg.uncrackable1 explore \ 157 --startup-command "android hooking watch class sg.vantagepoint.a.a method a \n && android hooking set return_value false sg.vantagepoint.a.c * \n && android hooking invoke sg.vantagepoint.a.a a '[B' '[B'" 158 ``` 159 160 * `watch class` prints the plaintext returned by the AES routine 161 * `set return_value false` forces every root / debugger check to report *false* 162 * `invoke` allows you to call the method directly without pressing **Verify**. 163 164 > [!NOTE] 165 > If attaching to an already running process fails, try spawn mode and review the device, application, and Frida logs. Android version alone does not make attach mode universally unavailable. 166 167 --- 168 169 ## Modern Android notes 170 171 * Magisk's **Zygisk** and **DenyList** features may conceal some root indicators, but Level 1's Java checks can still detect a visible path such as `/system/bin/su`. During an authorized assessment, identify the exact check first; for this exercise, hooking `java.io.File.exists()` is one way to test its effect.<sup>[[4]](#references)</sup> 172 * If Frida crashes while spawning or attaching on a recent Android release, reproduce it with matching current `frida-tools` and `frida-server` versions and inspect the device logs before changing the hook. An abort containing `missing SHADOW_OFFSET` is a useful search clue for an allocator/tooling compatibility problem reported in Android 12/13-era environments; upgrade matching Frida components rather than assuming a particular `16.1` or nightly build universally fixes it. Frida's Android guide also documents architecture matching and SELinux-related setup issues.<sup>[[5]](#references)</sup> 173 * Newer applications may use **Play Integrity** rather than SafetyNet. Unlike this Level 1 exercise, a real Play Integrity integration obtains a token through `IntegrityManager`, sends it to the application's backend, and validates the decoded verdict server-side. Testing therefore needs to cover both the app's request path and the backend's enforcement; hooking `SafetyNetClient` does not forge a Play Integrity verdict.<sup>[[6]](#references)</sup> 174 175 ## References 176 177 - [1] [Android Hacking with FRIDA](https://joshspicer.com/android-frida-1) 178 - [2] [Frida documentation – frida-trace](https://frida.re/docs/frida-trace/) 179 - [3] [Objection – Runtime Mobile Exploration](https://github.com/sensepost/objection) 180 - [4] [Magisk documentation – Zygisk and DenyList](https://topjohnwu.github.io/Magisk/guides.html#zygisk) 181 - [5] [Frida documentation – Android](https://frida.re/docs/android/) 182 - [6] [Android Developers – Make a standard API request with Play Integrity](https://developer.android.com/google/play/integrity/standard)