8kSec - SwizzleMeTimbers¶
Description: SwizzleMeTimbers is a pirate-themed iOS app with a secret buried deep inside its view controller. A simple button reads “Unlock Treasure”, but it’s protected by a method that always returns false, unless you’re crafty enough to change its behavior at runtime.
Link: https://academy.8ksec.io/course/ios-application-exploitation-challenges
Install an IPA file can be difficult. So, for make it more easy, I made a YouTube video with the process using Sideloadly. LINK: https://www.youtube.com/watch?v=YPpo9owRKGE
Once you have the app installed, let's proceed with the challenge. unzip the .ipa file.
Recon¶
In this application, we can see a simple button that says "Unlock Treasure"
If we click it, we see a message that says "That ain't the pirate's path"
Inside of Payload/SwizzleMeTimbers.app/ we can see the SwizzleMeTimbers.debug.dylib.
Let's import this file into Ghidra for examine some code.
But before, let's explore some classes with Frida!
Here's the code:
// list classes and methods
if (!ObjC.available) throw new Error("ObjC not found");
for (const n in ObjC.classes) {
if (/ViewController|Treasure|Swizzle/i.test(n)) console.log("[C]", n);
}
const CANDIDATE = "NOT YET";
if (ObjC.classes[CANDIDATE]) {
console.log("[Methods of]", CANDIDATE, ObjC.classes[CANDIDATE].$methods);
}
Now run the command:
In the output we found a Interesting class namedSwizzleMeTimbers.Q9V0 Let's add in the const CANDIDATE in the script (replace NOT YET). And run again the script.
We can see now in the output a huge list of methods:
After a huge look, I found two methods:-
_9zB -
t4G0
So, let's go now to Ghidra and we will look for this suspicious classes and methods.
First, the _9zB method always will return false:
/* SwizzleMeTimbers.Q9V0._9zB() -> Swift.Bool */
bool __thiscall SwizzleMeTimbers::Q9V0::_9zB(Q9V0 *this)
{
return false;
}
_9zB() is the logical guard that controls whether the user can unlock the treasure. But if this is true will trigger the t4G0 method!
void $$SwizzleMeTimbers.Q9V0.(t4G0_in__8DFD43FAB028256CB03EE728FE8EB512)()_->_()(void)
{
_8DFD43FAB028256CB03EE728FE8EB512 *p_Var1;
_8DFD43FAB028256CB03EE728FE8EB512 *unaff_x20;
String SVar2;
String SVar3;
p_Var1 = unaff_x20;
_objc_msgSend();
if (((uint)p_Var1 & 1) == 0) {
SVar2 = Swift::String::init(s_Nah_0000cc36,10,0);
SVar3 = Swift::String::init(s_That_ain_t_the_pirate_s_path._0000cc50,0x21,0);
$$SwizzleMeTimbers.Q9V0.(_P0_in__8DFD43FAB028256CB03EE728FE8EB512)(Swift.String,Swift.String)_-> _()
(SVar2.str,SVar2.bridgeObject,SVar3.str);
_swift_bridgeObjectRelease(SVar3.bridgeObject);
_swift_bridgeObjectRelease(SVar2.bridgeObject);
}
else {
SVar2 = Swift::String::init(s_Ye_got_it_0000cc80,0x17,0);
SVar3 = SwizzleMeTimbers::Q9V0::_8DFD43FAB028256CB03EE728FE8EB512::get_g0(unaff_x20);
$$SwizzleMeTimbers.Q9V0.(_P0_in__8DFD43FAB028256CB03EE728FE8EB512)(Swift.String,Swift.String)_-> _()
(SVar2.str,SVar2.bridgeObject,SVar3.str);
_swift_bridgeObjectRelease(SVar3.bridgeObject);
_swift_bridgeObjectRelease(SVar2.bridgeObject);
}
return;
}
-
When you press Unlock Treasure,
t4G0is called. -
This method executes
_9zB()as validation. -
If
true, the flag is obtained viag0()and displayed in aUIAlertController.
Unlocking the Treasure¶
Since _9zB() was exposed as an ObjC method, we dynamically overrode it with Frida:
if(!ObjC.available) throw "ObjC off";
const VC = ObjC.classes["SwizzleMeTimbers.Q9V0"];
VC["- _9zB"].implementation = ObjC.implement(VC["- _9zB"], () => 1); // force bool
let shown = false;
const AC = ObjC.classes.UIAlertController;
const S = "- setMessage:";
const o = AC[S].implementation;
AC[S].implementation = ObjC.implement(AC[S], function (self, _cmd, msg) {
try {
const s = new ObjC.Object(msg).toString();
if (!shown && /CTF\{.*\}/.test(s)) {
shown = true;
console.log("---hook--- " + s);
}
} catch (_) {}
return o(self, _cmd, msg);
});
// trigger IBAction
ObjC.schedule(ObjC.mainQueue, () => {
ObjC.choose(VC, {
onMatch(i) { if (i["- t4G0:"]) i["- t4G0:"](ptr(0)); },
onComplete() {}
});
});
And now run the Frida command:
Press the Unlock Treasure button and now you will get the flag!!Glossary¶
| API / Function | Description |
|---|---|
ObjC.implement() | Defines a new implementation for a class method |
UIAlertController | Native component to display alerts |
- setMessage: | Message setter within the UIAlertController |
- t4G0: | IBAction selector when pressing the button |
- _9zB | Swift method that acts as a guard |
ObjC.choose() | Finds live instances of a class in memory |
ObjC.schedule(ObjC.mainQueue, fn) | Executes code on the main thread (needed for UI) |
Some considerations
-
The
_9zB()method wasn't visible in the UI, but it was swizzleable because it was generated as an ObjC selector. -
The
g0()method that returns the flag didn't need to be hooked, as the UIAlert exposed it directly. -
Using UIAlertController allowed the flag to be sniffed without the need for further reversing.
Flag: CTF{{Swizzle_mbers}}
I hope you found it useful (:

