// HackTricks · Mobile

Frida Tutorial

Frida Tutorial

Installation

Install frida tools:

pip install frida-tools
pip install frida

Download and install in the android the frida server (Download the latest release).[3]
One-liner to restart adb in root mode, connect to it, upload frida-server, give exec permissions and run it in backgroud:

adb root; adb connect localhost:6000; sleep 1; adb push frida-server /data/local/tmp/; adb shell "chmod 755 /data/local/tmp/frida-server"; adb shell "/data/local/tmp/frida-server &"

Check if it is working:

frida-ps -U #List packages and processes
frida-ps -U | grep -i <part_of_the_package_name> #Get all the package name

frida-ui (browser-based Frida controller)

frida-ui provides a web UI on http://127.0.0.1:8000 to list devices/apps and attach or spawn targets with scripts (no CLI needed).[12]

  • Install (pin frida to the device server version):
uv tool install frida-ui --with frida==16.7.19
# pipx install frida-ui
# pip install frida-ui
  • Run:
frida-ui
frida-ui --host 127.0.0.1 --port 8000 --reload
  • Features: discovers USB/local devices, add remote servers (192.168.1.x:27042), and supports Attach, Spawn, and Spawn & Run (to hook before early onCreate() logic).
  • Scripting: editor, drag & drop .js, import CodeShare, download scripts and session logs.
  • Remote servers: ./frida-server -l 0.0.0.0:27042 -D exposes it on the network so frida-ui can connect without ADB.

Frida server vs. Gadget (root vs. no-root)

Two common ways to instrument Android apps with Frida:[1]

  • Frida server (rooted devices): Push and run a native daemon that lets you attach to any process.
  • Frida Gadget (no root): Bundle Frida as a shared library inside the APK and auto-load it within the target process.

Frida server (rooted)[3]

# Download the matching frida-server binary for your device's arch
# https://github.com/frida/frida/releases
adb root
adb push frida-server-<ver>-android-<arch> /data/local/tmp/frida-server
adb shell chmod 755 /data/local/tmp/frida-server
adb shell /data/local/tmp/frida-server &    # run at boot via init/magisk if desired

# From host, list processes and attach
frida-ps -Uai
frida -U -n com.example.app

Frida Gadget (no-root)

  1. Unpack the APK, add the gadget .so and config:
  • Place libfrida-gadget.so into lib/<abi>/ (e.g., lib/arm64-v8a/)
  • Create assets/frida-gadget.config with your script loading settings[2]

Example frida-gadget.config

{
  "interaction": { "type": "script", "path": "/sdcard/ssl-bypass.js" },
  "runtime": { "logFile": "/sdcard/frida-gadget.log" }
}
  1. Reference/load the gadget so it’s initialized early:
  • Easiest: Add a small Java stub to System.loadLibrary(“frida-gadget”) in Application.onCreate(), or use native lib loading already present.
  1. Repack and sign the APK, then install:
apktool d app.apk -o app_m
# ... add gadget .so and config ...
apktool b app_m -o app_gadget.apk
uber-apk-signer -a app_gadget.apk -o out_signed
adb install -r out_signed/app_gadget-aligned-debugSigned.apk
  1. Attach from host to the gadget process:
frida-ps -Uai
frida -U -n com.example.app

Notes

  • Gadget is detected by some protections; keep names/paths stealthy and load late/conditionally if needed.
  • On hardened apps, prefer rooted testing with server + late attach, or combine with Magisk/Zygisk hiding.

JDWP-based Frida injection without root/repackaging (frida-jdwp-loader)

If the APK is debuggable (android:debuggable=“true”), you can attach over JDWP and inject a native library at a Java breakpoint. No root and no APK repackaging.[6][7][8][9]

Quick start:

git clone https://github.com/frankheat/frida-jdwp-loader.git
cd frida-jdwp-loader
# Inject frida-gadget.so into a debuggable target
python frida-jdwp-loader.py frida -n com.example.myapplication
# Keep the breakpoint thread suspended for early hooks
python frida-jdwp-loader.py frida -n com.example.myapplication -s
# Networkless: run a local agent script via Gadget "script" mode
python frida-jdwp-loader.py frida -n com.example.myapplication -i script -l script.js

Notes

  • Modes: spawn (break at Application.onCreate) or attach (break at Activity.onStart). Use -b to set a specific Java method, -g to select Gadget version/path, -p to choose JDWP port.
  • Listen mode: forward Gadget (default 127.0.0.1:27042) if needed: adb forward tcp:27042 tcp:27042; then frida-ps -H 127.0.0.1:27042.
  • This leverages JDWP debugging. Risk is shipping debuggable builds or exposing JDWP.

Self-contained agent + Gadget embedding (Frida 17+; automated with Objection)

Frida 17 removed the built-in Java/ObjC bridges from GumJS. If your agent hooks Java, you must include the Java bridge inside your bundle.

  1. Create a Frida agent (TypeScript) and include the Java bridge
# Scaffolding
frida-create -t agent -o mod
cd mod && npm install
# Install the Java bridge for Frida 17+
npm install frida-java-bridge
# Dev loop (optional live-reload via REPL)
npm run watch

Minimal Java hook (forces dice rolls to 1):

import Java from "frida-java-bridge";

Java.perform(function () {
  var dicer = Java.use("org.secuso.privacyfriendlydicer.dicer.Dicer");
  dicer.rollDice.implementation = function (numDice: number, numFaces: number) {
    return Array(numDice).fill(1);
  };
});

Build a single bundle for embedding:

npm run build    # produces _agent.js via frida-compile

Quick USB test (optional):

frida -U -f org.secuso.privacyfriendlydicer -l _agent.js
  1. Configure Gadget to auto-load your script Objection’s patcher expects a Gadget config; when using script mode, specify the on-disk path inside the APK lib dir:[4]
{
  "interaction": {
    "type": "script",
    "path": "libfrida-gadget.script.so"
  }
}
  1. Automate APK patching with Objection
# Embed Gadget, config, and your compiled agent into the APK; rebuild and sign
objection patchapk -s org.secuso.privacyfriendlydicer.apk \
  -c gadget-config.json \
  -l mod/_agent.js \
  --use-aapt2

What patchapk does (high level):[4][5]

  • Detects device ABI (e.g., arm64-v8a) and fetches matching Gadget
  • Optionally adds android.permission.INTERNET when needed
  • Injects a static class initializer calling System.loadLibrary(“frida-gadget”) into the launch activity
  • Places the following under lib/<abi>/:
    • libfrida-gadget.so
    • libfrida-gadget.config.so (serialized config)
    • libfrida-gadget.script.so (your _agent.js)

Example injected smali (static initializer):

.method static constructor <clinit>()V
    .locals 1
    const-string v0, "frida-gadget"
    invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
    return-void
.end method
  1. Verify the repack[5]
apktool d org.secuso.privacyfriendlydicer.apk
apktool d org.secuso.privacyfriendlydicer.objection.apk
# Inspect differences
diff -r org.secuso.privacyfriendlydicer org.secuso.privacyfriendlydicer.objection

Expected changes:

  • AndroidManifest.xml may include <uses-permission android:name="android.permission.INTERNET"/>
  • New native libs under lib/<abi>/ as above
  • Launchable activity smali contains a static <clinit> that calls System.loadLibrary(“frida-gadget”)
  1. Split APKs
  • Patch the base APK (the one that declares MAIN/LAUNCHER activity)
  • Re-sign remaining splits with the same key:
objection signapk split1.apk split2.apk ...
  • Install splits together:
adb install-multiple split1.apk split2.apk ...
  • For distribution, you can merge splits into a single APK with APKEditor, then align/sign

Clearing FLAG_SECURE during dynamic analysis

Apps that call getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE) prevent screenshots, remote displays and even Android’s recent-task snapshots. When Freedom Chat enforced this flag the only way to document the leaks was to tamper with the window at runtime.[10] A reliable pattern is:[11]

  • Hook every Window overload that can re-apply the flag (setFlags, addFlags, setAttributes) and mask out bit 0x00002000 (WindowManager.LayoutParams.FLAG_SECURE).
  • After each activity resumes, schedule a UI-thread call to clearFlags(FLAG_SECURE) so Dialogs/Fragments created later inherit the unlocked state.
  • Apps built with React Native / Flutter often create nested windows; hook android.app.Dialog/android.view.View helpers or walk getWindow().peekDecorView() if you still see black frames.
Frida hook clearing Window.FLAG_SECURE
Java.perform(function () {
  var LayoutParams = Java.use("android.view.WindowManager$LayoutParams");
  var FLAG_SECURE = LayoutParams.FLAG_SECURE.value;
  var Window = Java.use("android.view.Window");
  var Activity = Java.use("android.app.Activity");

  function strip(value) {
    var masked = value & (~FLAG_SECURE);
    if (masked !== value) {
      console.log("[-] Stripped FLAG_SECURE from 0x" + value.toString(16));
    }
    return masked;
  }

  Window.setFlags.overload('int', 'int').implementation = function (flags, mask) {
    return this.setFlags.call(this, strip(flags), strip(mask));
  };

  Window.addFlags.implementation = function (flags) {
    return this.addFlags.call(this, strip(flags));
  };

  Window.setAttributes.implementation = function (attrs) {
    attrs.flags.value = strip(attrs.flags.value);
    return this.setAttributes.call(this, attrs);
  };

  Activity.onResume.implementation = function () {
    this.onResume();
    var self = this;
    Java.scheduleOnMainThread(function () {
      try {
        self.getWindow().clearFlags(FLAG_SECURE);
        console.log("[+] Cleared FLAG_SECURE on " + self.getClass().getName());
      } catch (err) {
        console.log("[!] clearFlags failed: " + err);
      }
    });
  };
});

Run the script with frida -U -f <package> -l disable-flag-secure.js --no-pause, interact with the UI, and screenshots/recordings will work again. Because everything happens on the UI thread there is no flicker, and you can still combine the hook with HTTP Toolkit/Burp to capture the traffic that revealed the /channel PIN leak.[10]

Dynamic DEX dumping / unpacking with clsdumper (Frida)

clsdumper is a Frida-based dynamic DEX/class dumper that survives hardened apps by combining an anti-Frida pre-stage with native and Java discovery strategies (works even if Java.perform() dies). Requirements: Python 3.10+, rooted device with frida-server running, USB or --host TCP connection.[13]

Install & quick use

pip install clsdumper
# Attach to a running app
clsdumper com.example.app
# Spawn first (hooks before early loaders)
clsdumper com.example.app --spawn
# Select strategies
clsdumper com.example.app --strategies fart_dump,oat_extract

CLI options (most useful)

  • target: package name or PID.
  • --spawn: spawn instead of attach.
  • --host <ip>: connect to remote frida-server.
  • --strategies <comma>: limit/choose extractors; default is all except mmap_hook (expensive).
  • --no-scan / --deep-scan: disable or slow deep memory scan (adds CDEX scanning).
  • --extract-classes: post-process dumps into .smali via androguard.
  • --no-anti-frida: skip the pre-hook bypass stage.
  • --list / --list-apps: enumerate running processes or installed packages.

Anti-instrumentation bypass (phase 0)

  • Hooks sigaction/signal to block registration of crash/anti-debug handlers.
  • Serves a filtered /proc/self/maps via memfd_create to hide Frida regions.
  • Monitors pthread_create to catch/neutralize watchdog threads hunting Frida.

DEX discovery (phases 1–2) — multiple complementary strategies with per-hit metadata + deduplication (agent-side djb2, host-side SHA-256):

  • Native (no Java bridge needed): art_walk (walk ART Runtime→ClassLinker→DexFile), open_common_hook (hook DexFile::OpenCommon), memory_scan (DEX magic in readable maps), oat_extract (parse mapped .vdex/.oat), fart_dump (hook DefineClass + walk class_table_), dexfile_constructor (hook OatDexFile constructors), mmap_hook (watch mmap/mmap64, off by default for perf).
  • Java (when available): cookie (read mCookie from ClassLoaders), classloader_hook (monitor loadClass, DexClassLoader, InMemoryDexClassLoader).

Output layout

dump_<target>/
  dex/classes_001.dex ...
  classes/                 # only when --extract-classes
  metadata.json            # strategy per hit + hashes

Tip: protected apps often load code from several sources (in-memory payload, vdex/oat, custom loaders). Running with the default multi-strategy set plus --spawn maximizes coverage; enable --deep-scan only when needed to avoid performance hits.

Tutorials

Tutorial 1

From: https://medium.com/infosec-adventures/introduction-to-frida-5a3f51595ca1
APK: https://github.com/t0thkr1s/frida-demo/releases
Source Code: https://github.com/t0thkr1s/frida-demo

Follow the link to read it.

Tutorial 2

From: https://11x256.github.io/Frida-hooking-android-part-2/ (Parts 2, 3 & 4)
APKs and Source code: https://github.com/11x256/frida-android-examples

Follow the link to read it.

Tutorial 3

From: https://joshspicer.com/android-frida-1
APK: https://github.com/OWASP/owasp-mstg/blob/master/Crackmes/Android/Level_01/UnCrackable-Level1.apk

Follow the link to read it.

You can find more Awesome Frida scripts here: https://codeshare.frida.re/

Quick Examples

Calling Frida from command line

frida-ps -U

#Basic frida hooking
frida -l disableRoot.js -f owasp.mstg.uncrackable1

#Hooking before starting the app
frida -U --no-pause -l disableRoot.js -f owasp.mstg.uncrackable1
#The --no-pause and -f options allow the app to be spawned automatically,
#frozen so that the instrumentation can occur, and the automatically
#continue execution with our modified code.

Basic Python Script

import frida, sys

jscode = open(sys.argv[0]).read()
process = frida.get_usb_device().attach('infosecadventures.fridademo')
script = process.create_script(jscode)
print('[ * ] Running Frida Demo application')
script.load()
sys.stdin.read()

Hooking functions without parameters

Hook the function a() of the class sg.vantagepoint.a.c

Java.perform(function () {
  rootcheck1.a.overload().implementation = function () {
    return false;
  };
});

Hook java exit()

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  :)")
}

Hook MainActivity .onStart() & .onCreate()

var mainactivity = Java.use("sg.vantagepoint.uncrackable1.MainActivity")
mainactivity.onStart.overload().implementation = function () {
  send("MainActivity.onStart() HIT!!!")
  var ret = this.onStart.overload().call(this)
}
mainactivity.onCreate.overload("android.os.Bundle").implementation = function (
  var_0
) {
  send("MainActivity.onCreate() HIT!!!")
  var ret = this.onCreate.overload("android.os.Bundle").call(this, var_0)
}

Hook android .onCreate()

var activity = Java.use("android.app.Activity")
activity.onCreate.overload("android.os.Bundle").implementation = function (
  var_0
) {
  send("Activity HIT!!!")
  var ret = this.onCreate.overload("android.os.Bundle").call(this, var_0)
}

Hooking functions with parameters and retrieving the value

Hooking a decryption function. Print the input, call the original function decrypt the input and finally, print the plain data:

Hooking a decryption function (Java) — print inputs/outputs
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 : " + ret)

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

Hooking functions and calling them with our input

Hook a function that receives a string and call it with other string (from here)

var string_class = Java.use("java.lang.String") // get a JS wrapper for java's String class

my_class.fun.overload("java.lang.String").implementation = function (x) {
  //hooking the new function
  var my_string = string_class.$new("My TeSt String#####") //creating a new String by using `new` operator
  console.log("Original arg: " + x)
  var ret = this.fun(my_string) // calling the original function with the new String, and putting its return value in ret variable
  console.log("Return value: " + ret)
  return ret
}

Getting an already created object of a class

If you want to extract some attribute of a created object you can use this.

In this example you are going to see how to get the object of the class my_activity and how to call the function .secret() that will print a private attribute of the object:

Java.choose("com.example.a11x256.frida_test.my_activity", {
  onMatch: function (instance) {
    //This function will be called for every instance found by frida
    console.log("Found instance: " + instance)
    console.log("Result of secret func: " + instance.secret())
  },
  onComplete: function () {},
})

Other Frida tutorials

References

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.