๐Ÿง  iOS Reverse Engineering: Defeating Anti-Debug

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@<iphone-ip>:/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.


#appsec #mobile #offsec #ios

Komentar

Postingan populer dari blog ini

๐Ÿ›ก️ Talk to the World of Cybersecurity

Enhancing Threat Detection with Wazuh: Managing False Positives, False Negatives, and AI Integration