Technical guide

How to detect modified Frida builds on Android and iOS

Detect modified Frida builds on Android and iOS by checking what instrumentation does in the process, not its name: memory maps, threads, ports and hooks.

  • Behaviour over names

    Modified builds rename files, threads and strings. Checks that look at what instrumentation does survive renaming far better than name matching.

  • Several independent signals

    Memory maps, threads, ports and code integrity fail in different ways. Combine them so one evasion does not hide the whole picture.

  • Per-platform evidence

    Android and iOS expose different artefacts. Build, test and record each platform on its own matrix.

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 routeTypical preconditionEvidence it tends to leave
Injected via serverRooted device or emulatorAgent mapped into memory, agent threads, server listening port
Embedded GadgetRepackaged and re-signed appExtra native library, changed signing certificate, agent threads
Autonomous Gadget scriptRepackaged app, no network neededSame as Gadget, but no listening port
Injection by a root framework moduleRooted device with a module loaderAgent 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

  1. 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.
  2. Run a clean baseline on the same device and build with no instrumentation. Record threads, maps and timing so later differences are meaningful.
  3. Prove the harness works: confirm it reaches a harmless, app-owned test function before judging detection.
  4. Repeat with each variant, changing one thing at a time: renamed library, renamed threads, moved port, autonomous script, embedded Gadget.
  5. Record each outcome as detected, harness failed, signal absent or app failed, and keep them separate.
  6. 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.

Further reading

Common questions

  • Why do name and port checks fail against modified Frida builds?

    Because modified builds rename the agent library, change thread names, move the port or embed Gadget in a repackaged app. A port check catches the server but not an autonomous Gadget, and a root check catches the device but not a re-signed app on a stock phone. Detect what instrumentation does in your process instead.

  • Which Frida checks does the guide recommend on Android?

    Several together. Inspect /proc/self/maps for executable regions your app never creates, compare thread names in /proc/self/task/*/comm with a clean baseline, check local listeners on TCP port 27042 and compare in-memory prologues of sensitive functions with expected bytes. For embedded Gadget, check the signing certificate against your release certificate.

  • Does a native prologue check catch hooks on Java methods?

    No. Hooks on Java methods work through the Android runtime rather than native prologues, so a native prologue check does not cover them. Hooks can also move to a less obvious function further down the call chain, which is why integrity should be checked at the point of use, not only at launch.

  • When should instrumentation checks run?

    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. On the server, never treat missing telemetry as a clean device, and keep authorisation decisions on the backend.

  • Does Otukan use the same Frida checks on Android and iOS?

    No. Otukan detects runtime instrumentation in Android apps and in iOS apps, combining several independent signals rather than relying on a tool name, and each platform's checks are built and validated separately. The signal goes to your response policy: observe, warn, restrict or terminate. iOS techniques have their own guide.

Test the behaviour behind the variant

Tell us your Android and iOS scope, framework, authorised test context and the sensitive flow to protect. We do not ask for binaries, signing keys, credentials or production data.

Or request a technical demo