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 classprints the plaintext returned by the AES routineset return_value falseforces every root / debugger check to report falseinvokeallows 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, hookingjava.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-toolsandfrida-serverversions and inspect the device logs before changing the hook. An abort containingmissing SHADOW_OFFSETis 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 particular16.1or 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; hookingSafetyNetClientdoes not forge a Play Integrity verdict.[6]