Community and modified Frida builds exist mainly to defeat simple checks: they rename the agent library, change thread names and protocol strings, move the listening port or drop the server entirely by embedding Gadget in a repackaged app. Detect them by what instrumentation must do inside your process, not by what it is called. On Android, combine memory-map inspection, thread and port artefacts and code-integrity checks on the functions you care about. Then link each signal to a defined response and a server-side decision. This page is the cross-platform overview with Android detail; iOS has its own guide.
For the iOS techniques, see detecting modified Frida builds in iOS apps. The product control is covered on anti-Frida.
How Frida reaches a process
Frida runs in three ways, and each leaves different evidence. In injected mode, frida-server runs on a rooted device and loads an agent into your running process. In embedded mode, Frida Gadget is a library packed into a modified copy of your app, so it works on an unrooted device once the app is re-signed. Gadget can also run scripts autonomously, with no server and no open connection.
A detection strategy has to cover all three. A port check catches the server. It does not catch an autonomous Gadget. A root check catches the device, not a re-signed app on a stock phone. That is why name and port checks alone fail against modified builds.
| Entry route | Typical precondition | Evidence it tends to leave |
|---|---|---|
| Injected via server | Rooted device or emulator | Agent mapped into memory, agent threads, server listening port |
| Embedded Gadget | Repackaged and re-signed app | Extra native library, changed signing certificate, agent threads |
| Autonomous Gadget script | Repackaged app, no network needed | Same as Gadget, but no listening port |
| Injection by a root framework module | Rooted device with a module loader | Agent in memory without a server process |
Android detection techniques
No single check is reliable on its own. Each technique below lists what it looks for and how a modified build tries to avoid it.
Memory maps
Read /proc/self/maps and look for executable regions that do not belong to your app, the system libraries or the runtime. Upstream builds map libraries with frida in their names, and the agent may appear as an anonymous or memfd mapping.
How variants evade it: renamed libraries, anonymous mappings and custom loaders remove the obvious strings. Look instead for executable, writable or unnamed regions that your app never creates, and for libraries loaded from unexpected paths. Read the file with direct system calls where you can, because a hooked fopen or read can filter the result.
Named threads
The agent starts its own threads. Enumerate /proc/self/task/*/comm and compare thread names with what a clean run of your app produces. Upstream builds use recognisable names such as gum-js-loop, gmain and gdbus.
How variants evade it: modified builds rename or blank these threads. Count and profile threads in a clean baseline, then treat unexpected native threads during sensitive steps as a weaker, supporting signal.
Listening ports
frida-server and Gadget listen on TCP port 27042 by default. Checking local listeners (for example from /proc/net/tcp) or probing the loopback address catches an unmodified setup.
How variants evade it: the port is configurable, and autonomous Gadget scripts open no port at all. Treat a port result as evidence of a server, never as proof of a clean process.
Inline-hook checks
Frida’s interceptor rewrites the first instructions of a native function to jump to its own code. Compare the in-memory prologue of sensitive functions with the expected bytes, or with the same function read from the library file on disk. Cover your own native functions and the system calls they depend on, such as file, socket and crypto functions.
How variants evade it: hooks can move to a less obvious function further down the call chain, and hooks on Java methods work through the Android runtime rather than native prologues, so a native prologue check does not cover them. Check integrity at the point of use, not only at launch.
Renamed or embedded Gadget
A repackaged app carries an extra native library, often with an innocent name, and a configuration file beside it. The app must also be re-signed. Check the running app’s signing certificate against your release certificate with PackageManager.GET_SIGNING_CERTIFICATES and SigningInfo, and check native library names and hashes against your build. Verify the signature server-side as well, using Play Integrity’s appRecognitionVerdict, because in-app checks can themselves be patched.
How variants evade it: the library name is free, so the signing check carries most of the weight. Repackaging detection is covered on detecting repackaging in Android apps.
Supporting checks
A debugger check on TracerPid in /proc/self/status adds context, but the injector usually detaches after loading the agent, so it is weak evidence for Frida. Root and emulator signals raise the prior probability of instrumentation without proving it.
Make the signal useful
Run checks at launch and again immediately before sensitive steps such as login, payment approval or key use. Instrumentation can attach at any time, so a launch-only check misses a late attach.
Otukan detects runtime instrumentation in Android apps and in iOS apps, combining several independent signals rather than relying on a tool name. Each platform’s checks are built and validated separately, and the signal goes to your response policy. Otukan applies the response you configure to the sensitive flow: observe, warn, restrict or terminate.
On the server, never treat missing telemetry as a clean device. Bind client signals to an attested request where you can, and keep authorisation decisions on the backend. Signal handling is covered on how to handle runtime threat signals.
Test against real variants
- Record the exact Frida release or fork, its source revision and how it enters the app. A variant’s name is not a test case; a reproducible build is.
- Run a clean baseline on the same device and build with no instrumentation. Record threads, maps and timing so later differences are meaningful.
- Prove the harness works: confirm it reaches a harmless, app-owned test function before judging detection.
- Repeat with each variant, changing one thing at a time: renamed library, renamed threads, moved port, autonomous script, embedded Gadget.
- Record each outcome as detected, harness failed, signal absent or app failed, and keep them separate.
- Remove the instrumentation and confirm the legitimate journey recovers under your policy.
Include legitimate controls
- Crash reporting, analytics, performance and test SDKs complete the sensitive journey without a false signal.
- Debug and QA builds use an observe-only profile, so engineers are not blocked.
- Flutter, React Native and native code paths each have their own test entry.
- Install, update, login and the sensitive action pass regression on physical devices.
- Every result records device, OS version, app version, tool build and reviewer.