daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

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)