A Technical Walkthrough from Static Reversing to Dynamic Hooking
Welcome to this advanced walkthrough of the Captain Nohook iOS challenge, part of the Mobile Hacking Lab training platform.
Our mission?
✅ Analyze the binary.
✅ Identify anti-debug and anti-Frida mechanisms.
✅ Patch them.
✅ Instrument the app dynamically.
✅ Retrieve the hidden flag — and document every single step.
🧭 Objective
Bypass anti-debug and anti-Frida protections, inject FridaGadget, hook runtime components, and dynamically retrieve a hidden flag from the binary.
🛠 Tools & Environment
- Frida 16.6.6
- Radare2
- Rabin2 (from r2 toolset)
- insert_dylib
- TrollStore (for installing the patched .ipa)
- Jailbroken iPhone
- FridaGadget.dylib
📦 Step 1: Extracting the .IPA
We began by unzipping the provided .ipa
file:
unzip com.mobilehackinglab.Captain-Nohook.ipa -d captain_nohook_dev-io
Result:
The app is extracted to:
captain_nohook_dev-io/Payload/Captain Nohook.app/
We now have access to the application bundle, which includes the binary Captain Nohook
.
🔍 Step 2: Static String Inspection with Rabin2
We used the following command to find strings related to jailbreak, Frida, ptrace, and debug protection:
rabin2 -zq captain_nohook_dev-io/Payload/Captain\ Nohook.app/Captain\ Nohook | grep -iE 'frida|ptrace|sysctl|debug|jail|hook|flag'
Output (excerpt):
0x1001563a0 34 33 Arrr, find yerr hidden flag here!
0x10015641f 5 4 flag
0x100156450 22 21 T@"UILabel",N,W,Vflag
...
0x100157380 23 22 /usr/sbin/frida-server
0x1001573b8 12 11 FridaGadget
...
We found an interesting string at address 0x1001563a0
which is a candidate for the hidden flag.
🧠 Step 3: Analyzing the Binary with Radare2
We opened the binary with:
r2 -AAA captain_nohook_dev-io/Payload/Captain\ Nohook.app/Captain\ Nohook
To find references to the interesting string:
axt 0x1001563a0
Output:
sub.Captain_Nohook.ViewController.whereIsflag.allocator...ySo8UIButtonCF_100009e70 0x10000a284 [STRN:r--] add x0, x0, str.Arrr__find_yerr_hidden_flag_here_
This confirms that the string is used in a function associated with a button action. We now know what to hook dynamically.
📦 Step 4: Injecting FridaGadget (Dynamic Instrumentation)
After identifying anti-Frida protections and patching static binary checks, we instrumented the app with FridaGadget, a dynamic and injectable version of the Frida runtime.
FridaGadget is used to instrument iOS apps that cannot be easily hooked with frida-server
, especially in non-jailbroken environments or when runtime detection is active. It works by being embedded into the app’s binary as a .dylib
, which is automatically loaded on app launch and communicates with Frida via USB. This setup lets us inject scripts and hook into runtime behavior without triggering traditional detection.
🧩 Step-by-Step: Embedding FridaGadget
1. Copy the FridaGadget.dylib
We used the FridaGadget.dylib already available in our system (through Objection or manual download).
cp ~/.objection/ios/FridaGadget.dylib Captain\ Nohook.app
2. Insert the Gadget into the binary
insert_dylib --weak --all-yes @executable_path/FridaGadget.dylib Captain\ Nohook.app/Captain\ Nohook Captain\ Nohook.app/Captain\ Nohook_patched
mv Captain\ Nohook.app/Captain\ Nohook_patched Captain\ Nohook.app/Captain\ Nohook
📝 Why? This embeds the FridaGadget using a weak LC_LOAD_DYLIB command so it won’t crash if not present. It allows Frida to auto-attach once the app is started.
3. Repack the IPA
cd ..
zip -r CaptainNohook-Frida.ipa Payload
📲 4. Install the IPA on the iPhone via TrollStore
This step assumes a TrollStore-enabled device, allowing unsigned IPA installation.
scp CaptainNohook-Frida.ipa mobile@:/path/to/TrollStore/appgroup/
Once installed, the app can be launched, and Frida will auto-attach.
🧵 Frida Script: monitor_flag.js
We developed the following script to watch for UILabels and capture the real flag.
// monitor_flag.js
// This script is used to monitor a UILabel inside the Captain Nohook app and print whenever its text is updated.
// It relies on dynamic instrumentation using FridaGadget, allowing us to observe runtime behavior.
console.log("🛡️ Monitoring UILabels and checking for flag updates...");
// The Objective-C class we want to analyze is the ViewController defined in the Captain Nohook app.
const vcClass = "Captain_Nohook.ViewController";
// Schedule the script to run on the main thread, where UI updates occur.
ObjC.schedule(ObjC.mainQueue, function () {
// Find all live instances of the ViewController class in memory.
const instances = ObjC.chooseSync(ObjC.classes[vcClass]);
if (instances.length === 0) {
// If no instances were found, display an error and stop.
console.error("❌ No ViewController instance found.");
return;
}
// Use the first found instance of the ViewController.
const viewController = instances[0];
console.log("✅ ViewController found:", viewController);
// Use Key-Value Coding (KVC) to access the UILabel that holds the flag text.
// The property name "flag" was discovered through static analysis of the binary.
// Specifically, we used the command:
// `axt 0x1001563a0`
// which showed that the string "Arrr, find yerr hidden flag here!" was referenced inside the function:
// `sub.Captain_Nohook.ViewController.whereIsflag.allocator...ySo8UIButtonCF`
// From there, we reverse engineered the relationship between the ViewController and the flag UILabel.
const flagLabel = viewController.valueForKey_("flag");
// Print the current UILabel state
console.log("🏴☠️ UILabel da flag:");
console.log(" ▪️ Text:", flagLabel.text().toString());
console.log(" ▪️ Hidden?:", flagLabel.isHidden());
// Intercept all calls to UILabel.setText to observe when any UILabel text is changed at runtime.
Interceptor.attach(ObjC.classes.UILabel["- setText:"].implementation, {
onEnter: function (args) {
// `args[2]` contains the new NSString being set on the UILabel.
const newText = new ObjC.Object(args[2]).toString();
// Log the modification
console.log("📢 UILabel modified:");
console.log(" ▪️ Class: UILabel");
console.log(" ▪️ New text:", newText);
}
});
});
🧪 Real-Time Output After Button Press
Once the user interacts with the app, we observed the following real output:
📢 UILabel modified:
▪️ Class: UILabel
▪️ New text: Noncompliant device detected!
📢 UILabel modified:
▪️ Class: UILabel
▪️ New text: Yerr hook won't work!
📢 UILabel modified:
▪️ Class: UILabel
▪️ New text: MHL{H00k_1n_Y0ur_D3bUgg3r}
The final line contains the hidden flag, dynamically generated and not statically embedded.
✅ Final Thoughts
This challenge tested binary analysis, anti-debug bypass, and live instrumentation — all essential skills for mobile offensive security professionals.
FridaGadget made it possible to bypass runtime checks and extract the hidden flag, even in a protected app.
📫 About the Author
[Júnior Carreiro]
🔐 Mobile AppSec | iOS Security | Reverse Engineering
📍 Let's connect: [GitHub] · [Linkedin]
🏷️ Tags: #ios
#reverseengineering
#frida
#infosec
#mobile