FridaInTheMiddle: Staying Attached to a Frida-Protected Swift iOS App
A dynamic iOS instrumentation writeup for FridaInTheMiddle, covering anti-Frida bypasses, Swift symbol discovery, and extracting a flag from a live Swift String argument.
FridaInTheMiddle is a Swift-based iOS challenge application protected with runtime tamper detection. The app attempts to detect Frida through suspicious ports, injected dynamic libraries, known instrumentation strings, environment variables, and anti-debugging behavior.

The objective was simple: keep the application alive while Frida was attached, intercept the argument passed into dummyFunction(flag:), and recover the flag dynamically. Static reverse engineering was not allowed, so the solution relied on runtime instrumentation only.
Objective
Stay attached with Frida and capture the argument passed to dummyFunction(flag:).
Final flag: CTF{you_evaded_frida_detection}Environment
The challenge was solved on a jailbroken iPhone with Frida installed through Sileo. Instead of using the default Frida port, I launched frida-server on a custom localhost port:
frida-server -l 127.0.0.1:31337

From macOS, the port was forwarded to the device using iproxy:
iproxy 31337 31337
The bundle identifier for the target app was:
com.8ksec.FridaInTheMiddle

Initial Frida Detection
Launching the application directly with Frida caused it to terminate immediately, confirming that detection logic ran early during startup.

Changing from the default Frida port 27042 to 31337 was not enough. The app still detected instrumentation, which suggested that the checks were broader than a basic port scan.
The likely detection vectors were suspicious localhost ports, injected Frida-related dynamic libraries, Frida strings in memory, anti-debugging APIs, and injection-related environment variables.
Runtime Anti-Frida Bypass
To keep the app running, I used a runtime bypass script instead of trying to remove Frida from the device. The script hooks native APIs that the app may use for detection and changes their return values at runtime.
The bypass covered these native functions:
_dyld_get_image_name strstr strcmp strncmp strcasestr ptrace getenv connect
The export resolver was written defensively because different Frida versions expose slightly different helper APIs.

The bypass also defined a suspicious keyword list covering Frida, Frida Gum, FridaGadget, jailbreak tooling, and common runtime thread names such as gum-js-loop and gmain.

For loaded dynamic libraries, _dyld_get_image_name was hooked so suspicious image names were replaced with a harmless-looking system framework path.

String-based checks were neutralized by changing substring matches to NULL and equality checks to non-zero comparison results. Anti-debugging was handled by replacing ptrace with a stub that returned success. Environment inspection was handled by hiding DYLD_INSERT_LIBRARIES. Localhost Frida port scans were blocked by hooking connect() and forcing connections to 27042, 27043, and 31337 to fail.

After installing the bypass hooks, the app stayed alive under Frida:
frida -H 127.0.0.1:31337 -f com.8ksec.FridaInTheMiddle -l bypass.js --pause

Dynamic Module and Symbol Enumeration
Once the process survived instrumentation, the next step was to locate the runtime function corresponding to dummyFunction(flag:). Since static reverse engineering was out of scope, this had to be discovered dynamically.
The discovery script enumerated loaded modules and filtered anything related to the app:
const appModules = allModules.filter(m =>
m.name.includes('FridaInTheMiddle') ||
m.path.includes('FridaInTheMiddle.app')
);
This revealed two relevant modules:
[module] FridaInTheMiddle [module] FridaInTheMiddle.debug.dylib
The main executable exposed a small number of symbols, while FridaInTheMiddle.debug.dylib exposed many more. That indicated that the Swift application logic was located in the debug dylib.
[*] FridaInTheMiddle symbols: 102 [*] FridaInTheMiddle.debug.dylib symbols: 1580
Filtering symbols with keywords such as dummy, flag, intercept, button, and ContentView revealed the target Swift symbol:
$s16FridaInTheMiddle11ContentViewV13dummyFunction4flagySS_tF
This corresponds to:
FridaInTheMiddle.ContentView.dummyFunction(flag: Swift.String)

Hooking dummyFunction(flag:)
With the symbol identified, the final hook script searched the loaded FridaInTheMiddle modules for that exact symbol and attached to the function address.

When the relevant button was pressed in the app, the hook triggered and printed the Swift String argument words:
========== dummyFunction HIT ========== [Swift.String word0/x0] 0xd00000000000001f [Swift.String word1/x1] 0x80000001048bd6c0

Understanding the Swift String Argument
On ARM64 iOS, function arguments are passed through registers such as x0, x1, x2, and x3. Frida exposes these as args[0], args[1], and so on.
A Swift String is not always a plain C string pointer. In this case, the argument was represented across two machine words. The lower portion of x0, 0x1f, indicated a 31-byte string, while x1 pointed into Swift string storage.
Reading x1 directly only showed part of a nearby string:
ected. Exiting in 3 seconds...
Because the pointer could land inside a Swift string storage region, the script stripped high tag bits, tried nearby offsets, dumped memory around the pointer, and scanned readable memory for the byte pattern of CTF{.

Recovering the Flag
Dumping memory around the stripped pointer revealed several adjacent strings:
Intercept First Argument Using Frida
Frida detected. Exiting in 3 seconds...
CTF{you_evaded_frida_detection}
127.0.0.1
FridaGadgetThe relevant hexdump region contained the flag bytes directly:
8000000104dfd6e0 43 54 46 7b 79 6f 75 5f 65 76 61 64 65 64 5f 66 CTF{you_evaded_f
8000000104dfd6f0 72 69 64 61 5f 64 65 74 65 63 74 69 6f 6e 7d 00 rida_detection}.Demo Video
Scripts Used
The final workflow used three Frida scripts:
bypass.jsto neutralize runtime Frida detection.enum_symbols.jsto dynamically discover application modules and candidate Swift symbols.hook_flag.jsto hookdummyFunction(flag:), inspect the Swift String argument, dump nearby memory, and extract the flag.
Final Thoughts
This challenge was a useful reminder that bypassing Frida detection usually requires more than changing the Frida server port. The app performed several runtime checks, so the bypass had to cover library enumeration, string matching, anti-debugging, environment inspection, and localhost socket checks.
The Swift side was equally important. The target function was located in FridaInTheMiddle.debug.dylib, and the flag was not exposed as a simple C string argument. Dumping memory around the Swift String storage at the exact function call was the key step that turned the hook into a reliable solve.