Developer Tools

How to Run Frida on a Non-Rooted Android Phone with Frida Gadget

Springmusk Springmusk 7 min read 3 comments
Abstract illustration of a smartphone with circuit traces and a magnifying lens, representing Frida instrumentation on a non-rooted Android phone.

TL;DR: Frida Gadget is a shared library that embeds Frida’s agent inside an APK, so you can hook and instrument an Android app on a completely stock, non-rooted phone. Patch the APK with objection patchapk for the quick route, or do it by hand with apktool: add libfrida-gadget.so and a config file, load the library from the app’s startup code, re-sign, and install. Repackaging changes the app’s signature, so only analyze apps you have permission to test.

Frida’s normal setup needs root: you push frida-server onto the device and inject code into other processes. On a non-rooted phone that door is closed. Frida Gadget is the official workaround — instead of injecting from outside, you build Frida’s agent into the app itself. The app loads it voluntarily at startup, no root and no separate server process required. This post covers both the quick way and the manual way, plus the config reference and the things that break.

How the gadget model differs from frida-server

frida-server (rooted) Frida Gadget (no root)
Root required Yes No
APK modified No — the vendor APK stays untouched Yes — repackaged with the gadget added
How the agent loads Injected into the target process via the server Loaded by the app itself at startup
App signature Unchanged Changed (resigned by you)
Visible process name when connecting The real app process Always Gadget (app id re.frida.Gadget)
Main downside Needs root; detectable by anti-tamper engines Signature checks, Play Integrity, and some pinning break

The tradeoff is clear: the gadget costs you a repackaged, re-signed APK, and anything the app checks against its own signature can notice. What you gain is dynamic instrumentation on a stock device with no root and no extra process to detect.

What you need

  • A computer with adb from the Android SDK and a device connected with USB debugging enabled.
  • Python with frida-tools installed (pip install frida-tools).
  • A gadget binary from the frida/frida releases page, matching your frida-tools version exactly — Frida does not tolerate even patch-version mismatches between client and agent. Frida 17.x is the current line; 16.x reached end of life in December 2025.
  • For the manual route: apktool, zipalign, and apksigner (or a keystore + keytool).

The quick route: objection patchapk

objection wraps the whole repackaging process in one command. Version 1.12.6 pins Frida 17.16.2 with frida-tools 14.5.0:

pip install https://github.com/Aceproulx/objection/releases/latest/download/objection-1.12.6-py3-none-any.whl
pip install frida==17.16.2 frida-tools==14.5.0

# Patch the APK to embed Frida gadget
objection patchapk -s app-release.apk

# Install the patched APK on the device
adb install app-release.objection.apk

Launch the app on the device, then explore it with objection’s REPL:

objection -g apk explore

Or attach plain Frida over USB:

frida -U Gadget -l script.js

This covers most cases. Use the manual route when objection fails — typically with split APKs (app bundles), heavy obfuscation, or apps whose build structure breaks apktool’s assumptions.

The manual route: patch the APK yourself

1. Decode the APK.

apktool d app.apk -o app_decoded

2. Add the gadget and its config. Download frida-gadget-<version>-android-arm64.so.xz from the releases page, uncompress it, and copy it into the native library folder for your device’s ABI (create it if the app ships none):

mkdir -p app_decoded/lib/arm64-v8a
cp frida-gadget-17.16.2-android-arm64.so app_decoded/lib/arm64-v8a/libfrida-gadget.so

Next to it, create the config file. It must be named libfrida-gadget.config.so — plain JSON despite the suffix — and sit in the same directory. A mismatched name is silently ignored, which is the most common “gadget loads but never listens” bug:

cat > app_decoded/lib/arm64-v8a/libfrida-gadget.config.so <<'JSON'
{"interaction": {"type": "listen", "address": "127.0.0.1", "port": 27042, "on_load": "wait"}}
JSON

3. Load the library at startup. Find the Application class in the decoded manifest (android:name on the <application> tag), map it to its smali file under smali/ (check smali_classes2/ etc. for multi-dex apps), and add a System.loadLibrary("frida-gadget") call in its static initializer or constructor, wrapped in try/catch. If the manifest declares no Application class, declare a minimal one.

4. Rebuild, align, and sign.

apktool b app_decoded -o patched-unsigned.apk
zipalign -p -f 4 patched-unsigned.apk patched-aligned.apk

keytool -genkeypair -keystore frida-test.jks -alias frida-test \
  -keyalg RSA -keysize 2048 -validity 10000
apksigner sign --ks frida-test.jks --ks-key-alias frida-test \
  --out patched-signed.apk patched-aligned.apk

apksigner verify --verbose --print-certs patched-signed.apk
adb install patched-signed.apk

Make sure the manifest sets android:extractNativeLibs="true" on the application element — without it, the system keeps native libraries compressed inside the APK and the gadget cannot read its own config.

Connecting to the gadget

The gadget listens on port 27042 inside the device. Forward it and attach:

adb forward tcp:27042 tcp:27042
frida -H 127.0.0.1:27042 Gadget -l script.js

With "on_load": "wait", the app freezes on its splash screen until you attach — that is expected, and it is what lets your hooks land before the app’s own code runs. A listen-mode gadget accepts one client per process start, so restart the app for each new session.

Gadget config reference

Key Values Meaning
interaction.type listen Gadget listens on a port and waits for a Frida client
interaction.type connect Gadget connects out to a Frida client at a given host/port
interaction.type script Loads a script file automatically at startup, no client needed
on_load wait App pauses at startup until a client attaches and resumes it
on_load resume App continues immediately; attach whenever ready
on_port_conflict fail / pick-next What to do if port 27042 is already taken

What breaks (and how to react)

  • Self-signature checks: the APK is now signed by you. Apps that verify their own signature will detect this as tampering — a separate problem from root detection. Find and neutralize that check with the same workflow from the JADX post.
  • Play Integrity / SafetyNet attestation: a re-signed APK will not pass. Apps that gate on it refuse to run; there is no gadget-side fix.
  • Split APKs (app bundles): native libraries may live in an ABI split, not the base APK. Patch every split with the same keystore and install with adb install-multiple.
  • “Gadget loads but never listens”: check the config filename (libfrida-gadget.config.so exactly), the extractNativeLibs flag, and the Frida version match between host tools and gadget.
  • “Incompatible protocol version”: almost always a version mismatch. Check frida --version against the gadget build you downloaded and make them identical.

A final boundary, stated plainly: use these techniques only on apps you own or have permission to analyze — your own apps, open-source targets, or test builds. Repackaging someone else’s app to bypass its protections is tampering, not research.

Key takeaways

  • Frida Gadget embeds the instrumentation agent inside the APK, so a stock non-rooted phone needs neither root nor frida-server.
  • objection patchapk -s app.apk is the fastest path; the manual apktool route handles split APKs and stubborn builds.
  • The config file must be named libfrida-gadget.config.so and sit next to the gadget .so; on_load: wait freezes the app until you attach.
  • Match frida-tools and gadget versions exactly, and connect to the process named Gadget on port 27042.
  • Repackaging changes the signature, so self-signature checks and Play Integrity attestation break by design.

References

3 comments

Join the discussion

Your email address will not be published. Required fields are marked *