// HackTricks · Mobile

Frida Tutorial 3

Frida Tutorial 3


This is a summary of the post: https://joshspicer.com/android-frida-1[1]
APK: https://github.com/OWASP/owasp-mstg/blob/master/Crackmes/Android/Level_01/UnCrackable-Level1.apk

Solution 1

Hook the exit() and decrypt functions so the flag is printed in the Frida console when you press Verify:

Java.perform(function () {
  send("Starting hooks OWASP uncrackable1...")

  function getString(data) {
    var ret = ""
    for (var i = 0; i < data.length; i++) {
      ret += "#" + data[i].toString()
    }
    return ret
  }

  var aes_decrypt = Java.use("sg.vantagepoint.a.a")
  aes_decrypt.a.overload("[B", "[B").implementation = function (var_0, var_1) {
    send(
      "sg.vantagepoint.a.a.a([B[B)[B   doFinal(enc)  // AES/ECB/PKCS7Padding"
    )
    send("Key       : " + getString(var_0))
    send("Encrypted : " + getString(var_1))
    var ret = this.a.overload("[B", "[B").call(this, var_0, var_1)
    send("Decrypted : " + getString(ret))

    var flag = ""
    for (var i = 0; i < ret.length; i++) {
      flag += String.fromCharCode(ret[i])
    }
    send("Decrypted flag: " + flag)
    return ret //[B
  }

  var sysexit = Java.use("java.lang.System")
  sysexit.exit.overload("int").implementation = function (var_0) {
    send("java.lang.System.exit(I)V  // We avoid exiting the application  :)")
  }

  send("Hooks installed.")
})

Solution 2

Hook the root checks and decrypt function so the flag is printed in the Frida console when you press Verify:

Java.perform(function () {
  send("Starting hooks OWASP uncrackable1...")

  function getString(data) {
    var ret = ""
    for (var i = 0; i < data.length; i++) {
      ret += "#" + data[i].toString()
    }
    return ret
  }

  var aes_decrypt = Java.use("sg.vantagepoint.a.a")
  aes_decrypt.a.overload("[B", "[B").implementation = function (var_0, var_1) {
    send(
      "sg.vantagepoint.a.a.a([B[B)[B   doFinal(enc)  // AES/ECB/PKCS7Padding"
    )
    send("Key       : " + getString(var_0))
    send("Encrypted : " + getString(var_1))
    var ret = this.a.overload("[B", "[B").call(this, var_0, var_1)
    send("Decrypted : " + getString(ret))

    var flag = ""
    for (var i = 0; i < ret.length; i++) {
      flag += String.fromCharCode(ret[i])
    }
    send("Decrypted flag: " + flag)
    return ret //[B
  }

  var rootcheck1 = Java.use("sg.vantagepoint.a.c")
  rootcheck1.a.overload().implementation = function () {
    send("sg.vantagepoint.a.c.a()Z   Root check 1 HIT!  su.exists()")
    return false
  }

  var rootcheck2 = Java.use("sg.vantagepoint.a.c")
  rootcheck2.b.overload().implementation = function () {
    send("sg.vantagepoint.a.c.b()Z  Root check 2 HIT!  test-keys")
    return false
  }

  var rootcheck3 = Java.use("sg.vantagepoint.a.c")
  rootcheck3.c.overload().implementation = function () {
    send("sg.vantagepoint.a.c.c()Z  Root check 3 HIT!  Root packages")
    return false
  }

  var debugcheck = Java.use("sg.vantagepoint.a.b")
  debugcheck.a.overload("android.content.Context").implementation = function (
    var_0
  ) {
    send("sg.vantagepoint.a.b.a(Landroid/content/Context;)Z  Debug check HIT! ")
    return false
  }

  send("Hooks installed.")
})

Solution 3 – frida-trace (Frida ≥ 16)

If you do not want to hand-write hooks, let Frida generate Java stubs and then edit them:[2]

# Spawn the application and automatically trace the Java method we care about
adb shell "am force-stop owasp.mstg.uncrackable1"
frida-trace -U -f owasp.mstg.uncrackable1 \
            -j 'sg.vantagepoint.a.a.a("[B","[B")[B' \
            -j 'sg.vantagepoint.a.c!*' \
            --output ./trace

# The first run will create ./trace/scripts/sg/vantagepoint/a/a/a__B_B_B.js
# Edit that file and add the logic that prints the decrypted flag or
# returns a constant for the root-checks, then:
frida -U -f owasp.mstg.uncrackable1 -l ./trace/_loader.js --no-pause

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.


Solution 4 – Objection command

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.[3]

objection -g owasp.mstg.uncrackable1 explore \
  --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'"
  • watch class prints the plaintext returned by the AES routine
  • set return_value false forces every root / debugger check to report false
  • invoke allows you to call the method directly without pressing Verify.

[!NOTE] 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.


Modern Android notes

  • 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.[4]
  • 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.[5]
  • 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.[6]

References