Technical guide

How to detect modified Frida builds in iOS apps

Detect modified Frida builds in iOS apps with dyld image checks, inline-hook tests, thread, port and exception-port artefacts, and server-side App Attest.

  • Look at the process

    Loaded images, rewritten code, extra threads and exception ports show instrumentation even when its files are renamed.

  • Two iOS routes

    Jailbroken devices run a Frida server; stock devices need a re-signed app carrying Gadget. Each route needs its own checks.

  • Confirm on the server

    In-app checks can be patched. App Attest lets your server confirm the request came from your genuine app.

To detect community and modified Frida builds in an iOS app, check what instrumentation changes inside your process rather than looking for the word “Frida”. Enumerate loaded images through dyld and compare them with your build, test the code of sensitive functions for inline hooks and trampolines, and look for agent threads, local listening ports and unexpected exception ports. Pair those checks with signature and Team ID checks, because on a stock device Frida needs a re-signed app. Confirm the result server-side with App Attest. This guide covers iOS only.

For the cross-platform overview and Android techniques, see detecting modified Frida builds on Android and iOS. The product control is covered on anti-Frida.

The two iOS routes

Frida reaches an iOS app in two different ways, and modified builds target both.

RoutePreconditionMain evidence
Frida serverJailbroken deviceAgent code in your process, agent threads, server listening port, jailbreak artefacts
Embedded Gadget (“jailed”)App unpacked, Gadget added, app re-signedExtra dynamic library, different Team ID and provisioning profile, agent threads

On a jailbroken device the server injects an agent into the running app. On a stock device there is no injection route, so the attacker repackages the app with Gadget and re-signs it with their own certificate, usually a free or paid developer account, an enterprise certificate or a sideloading tool. The signing change is unavoidable on this route, which makes it the most dependable evidence.

Gadget limits on stock devices

Stock iOS enforces code signing on executable memory. Frida documents that when Gadget runs with code_signing set to required, its Interceptor API is unavailable, so inline hooks cannot be written. Attackers work around this by launching the re-signed app under a debugger, which needs the get-task-allow entitlement in a development profile. Both facts give you checks: a development entitlement or embedded development profile in what should be an App Store build, and a debugger attached at runtime.

iOS detection techniques

Each technique below lists what it looks for and how modified builds try to evade it. Use several together.

Loaded-image enumeration

Walk the image list with _dyld_image_count and _dyld_get_image_name, and register _dyld_register_func_for_add_image to see images loaded after launch. Compare each path with the frameworks your app ships and the system libraries. An unknown dylib inside your app bundle, or one loaded from an unexpected path, is a strong signal.

How variants evade it: Gadget can be given any file name, which Frida’s own documentation names as a way to avoid name checks. Compare against an allowlist of what you ship, not a blocklist of names. Some injectors map code without registering it with dyld, so also scan for executable memory regions that do not belong to any listed image.

Inline hooks and trampolines

An inline hook replaces the first instructions of a function with a branch to the attacker’s code. Check the prologues of your sensitive functions, and of the system functions they rely on, for unexpected branches, or compare them with the bytes in the signed binary on disk. For Objective-C, check that method implementations of sensitive classes still resolve to your own image with dladdr, which catches swizzling.

How variants evade it: hooks can move to a function you do not check, or to a callee deeper in the chain. Check integrity at the point of use, just before the sensitive operation, not only at launch.

Thread and port artefacts

The agent starts its own threads. Enumerate your task’s threads and read their names with pthread_getname_np. Upstream builds use names such as gum-js-loop and gmain. A Frida server or listening Gadget accepts connections on local TCP port 27042 by default.

How variants evade it: modified builds rename threads, change the port or run Gadget scripts autonomously with no port at all. Treat these as supporting signals and compare thread counts with a clean baseline of your own app.

Exception ports

Debuggers and some instrumentation tools register their own Mach exception handlers for the task. Read the task’s exception ports with task_get_exception_ports and flag handlers your app did not install. Combine the result with a sysctl check of the P_TRACED flag for an attached debugger.

How variants evade it: an attacker with code execution can hook the query itself. Run the check from code that is integrity-checked, and send the result to your server rather than deciding only on the device.

Signing and environment checks

Read the embedded embedded.mobileprovision and the code-signing Team ID at runtime. An App Store build carries no embedded provisioning profile, and its Team ID is yours. A profile with get-task-allow, a foreign Team ID or a changed bundle ID points to a re-signed copy. Jailbreak checks add context for the server route but do not prove instrumentation on their own.

Confirm on the server

Every in-app check can be patched by someone who controls the process. Use App Attest (DCAppAttestService) so your server receives an Apple-backed assertion that a request came from a genuine instance of your app, then combine that with the in-app instrumentation signal. App Attest is unavailable in some contexts, such as the simulator and some app extensions, so check isSupported and decide what the server does without it.

Otukan detects runtime instrumentation in iOS apps and pairs those checks with signature checks, so a re-signed copy carrying Gadget is caught on a stock device. The signal goes to your response policy, and Otukan applies the response you configure to the sensitive flow. Signal handling is covered on how to handle runtime threat signals, and re-signing on preventing mods that require re-signing iOS apps.

Test against real variants

  1. Record the exact tool release or fork, how it was built and which route it uses. Note whether the device is jailbroken and whether a debugger is attached.
  2. Run your signed release build without instrumentation and record images, threads and timing as the clean baseline.
  3. Prove the harness reaches a harmless, app-owned test function before judging detection.
  4. Repeat for each variant, changing one thing at a time: renamed dylib, renamed threads, moved port, autonomous script, re-signed Gadget build.
  5. Record each outcome as detected, harness failed, signal absent or app failed.
  6. Remove the instrumentation and confirm recovery under your policy.
  • Swift, Objective-C and native code paths each have their own test entry.
  • App extensions that touch sensitive data are tested separately from the main app.
  • Crash reporting, analytics and approved SDKs run without a false signal.
  • Simulator results are kept apart from physical-device results.
  • Each result records device, iOS version, app version, tool build and reviewer.

Further reading

Common questions

  • Can I detect a modified Frida build by looking for the name “Frida”?

    No. Modified builds rename files, threads and ports, so check what instrumentation changes inside your process instead: loaded images compared with an allowlist of what you ship, inline hooks in sensitive functions, agent threads, local listening ports and unexpected exception ports. Use several of these checks together.

  • How does Frida reach an app on a stock, non-jailbroken iPhone?

    Through an embedded Gadget. A stock device offers no injection route, so the attacker unpacks the app, adds Gadget and re-signs it with their own certificate. That signing change is unavoidable, which makes a foreign Team ID, a changed bundle ID or an embedded development profile the most dependable evidence.

  • Are thread names and port 27042 reliable signs of Frida on iOS?

    Only as supporting signals. Upstream builds use thread names such as gum-js-loop and gmain and listen on local TCP port 27042 by default, but modified builds rename threads, change the port or run Gadget scripts with no port at all. Compare thread counts with a clean baseline of your own app.

  • Why pair in-app detection with App Attest?

    Because every in-app check can be patched by someone who controls the process. App Attest (DCAppAttestService) gives your server an Apple-backed assertion that a request came from a genuine instance of your app. It is unavailable in some contexts, such as the simulator and some app extensions, so check isSupported and decide the fallback.

  • What does Otukan do with an instrumentation signal on iOS?

    Otukan detects runtime instrumentation in iOS apps and pairs those checks with signature checks, so a re-signed copy carrying Gadget can be detected on a stock device. The signal goes to your response policy, and Otukan applies the response you configure to the sensitive flow. This guide covers iOS only.

Validate instrumentation detection on your app

Tell us your iOS release and distribution scope, app framework and the sensitive flow you need to protect. We do not ask for binaries, signing keys, credentials or production data.

Or request a technical demo