8KSec — ClearRoute
My hands-on iOS solution
8KSec — ClearRoute
1. Objective
ClearRoute is an iOS network security challenge where the application sends a supposedly protected HTTPS POST request containing a hidden flag. The objective is to recover this flag by instrumenting the app, analyzing its runtime behavior, and extracting the request data before TLS encryption. Traditional MITM proxying or SSL pinning bypass tools do not work because the app does not rely on standard trust evaluation APIs.
2. Static Analysis
We inspect the application's Info.plist to verify whether ATS exceptions exist. Below is the complete Info.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>24D70</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>ClearRoute</string>
<key>CFBundleIcons</key>
<dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>AppIcon60x60</string>
</array>
<key>CFBundleIconName</key>
<string>AppIcon</string>
</dict>
</dict>
<key>CFBundleIdentifier</key>
<string>com.8ksec.ClearRoute</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>ClearRoute</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>iPhoneOS</string>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>22E235</string>
<key>DTPlatformName</key>
<string>iphoneos</string>
<key>DTPlatformVersion</key>
<string>18.4</string>
<key>DTSDKBuild</key>
<string>22E235</string>
<key>DTSDKName</key>
<string>iphoneos18.4</string>
<key>DTXcode</key>
<string>1630</string>
<key>DTXcodeBuild</key>
<string>16E140</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>MinimumOSVersion</key>
<string>16.0</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict/>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UILaunchScreen</key>
<dict>
<key>UILaunchScreen</key>
<dict/>
</dict>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~iphone</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
3. Binary Analysis
![]()
This view shows the initial endpoint scanning phase where I searched the Mach-O for any hints of URLs, hosts, or hardcoded networking logic. Nothing meaningful appeared.
4. Class Discovery
![]()
Here I inspected the loaded Objective-C classes and found the FLEX components hidden inside the app. Seeing FLEXNetworkObserver and related classes confirmed that the app was intercepting its own requests internally before TLS.
5. Certificate Check
During the certificate inspection phase, I verified that the app did not include any custom certificates, trust anchors, or pinning material. This confirmed that the challenge was not about bypassing certificate validation but something happening entirely in process.
6. Runtime Instrumentation
We enable FLEX and hook request-building APIs to capture data before TLS encryption.
[FLEXManager.sharedManager showExplorer];
[FLEXNetworkObserver.sharedObserver setEnabled:YES];
7. Payload Extraction
![]()
This screenshot captures the moment the plaintext POST payload appears through the Frida hooks and FLEX logger.
Decoded body:
{"8ksec_intercepted":"CTF{no_proxies_allowed}","user":"john_doe"}
Frida Script
/*
* clearroute_flex_and_request_logger.js
* For the 8kSec ClearRoute training app.
*
* What it does:
* 1) Shows FLEX overlay and enables FLEXNetworkObserver (builtin logger).
* 2) Fallback: hooks common requestbuilding methods to print URL, headers, and body preTLS.
*/
'use strict';
function utf8FromNSData(nsdata) {
try {
const NSString = ObjC.classes.NSString;
const str = NSString.alloc().initWithData_encoding_(nsdata, 4);
const out = str.toString();
str.release();
return out;
} catch (_) { return ""; }
}
function logLine(s) { console.log("[*] " + s); }
function ok(s) { console.log("[+] " + s); }
function warn(s) { console.log("[-] " + s); }
function enableFLEX() {
try {
const FLEXManager = ObjC.classes.FLEXManager;
if (FLEXManager && FLEXManager["+ sharedManager"] && FLEXManager["- showExplorer"]) {
FLEXManager["+ sharedManager"]()["- showExplorer"]();
ok("FLEX overlay shown");
}
const FLEXNetworkObserver = ObjC.classes.FLEXNetworkObserver;
if (FLEXNetworkObserver) {
if (FLEXNetworkObserver["+ sharedObserver"] && FLEXNetworkObserver["- setEnabled:"]) {
const obs = FLEXNetworkObserver["+ sharedObserver"]();
obs;
ok("FLEXNetworkObserver enabled via singleton");
}
}
} catch (e) { warn("enableFLEX error: " + e); }
}
function hookRequestConstructionFallbacks() {
const NSMutableURLRequest = ObjC.classes.NSMutableURLRequest;
const NSURLRequest = ObjC.classes.NSURLRequest;
const NSURLSession = ObjC.classes.NSURLSession;
if (NSMutableURLRequest) {
if (NSMutableURLRequest["- setHTTPBody:"]) {
Interceptor.attach(NSMutableURLRequest["- setHTTPBody:"].implementation, {
onEnter(args) {
try {
const self = new ObjC.Object(args[0]);
const body = new ObjC.Object(args[2]);
const url = self.URL() ? self.URL().toString() : "";
logLine("setHTTPBody for " + url);
logLine("HTTP Body (UTF8 guess): " + utf8FromNSData(body));
} catch (e) { warn("setHTTPBody hook err: " + e); }
}
});
ok("Hooked NSMutableURLRequest setHTTPBody");
}
if (NSMutableURLRequest["- setValue:forHTTPHeaderField:"]) {
Interceptor.attach(NSMutableURLRequest["- setValue:forHTTPHeaderField:"].implementation, {
onEnter(args) {
try {
const self = new ObjC.Object(args[0]);
const value = new ObjC.Object(args[2]).toString();
const field = new ObjC.Object(args[3]).toString();
const url = self.URL() ? self.URL().toString() : "";
logLine("Header for " + url + " => " + field + ": " + value);
} catch (e) { warn("setValue hook err: " + e); }
}
});
ok("Hooked NSMutableURLRequest setValue header");
}
}
if (NSURLSession) {
const candidates = [
"- dataTaskWithRequest:",
"- dataTaskWithRequest:completionHandler:",
"- uploadTaskWithRequest:fromData:",
"- uploadTaskWithRequest:fromData:completionHandler:",
"- uploadTaskWithRequest:fromFile:",
"- uploadTaskWithRequest:fromFile:completionHandler:"
];
candidates.forEach(sel => {
if (NSURLSession[sel]) {
Interceptor.attach(NSURLSession[sel].implementation, {
onEnter(args) {
try {
const req = new ObjC.Object(args[2]);
const url = req && req.URL() ? req.URL().toString() : "";
logLine("NSURLSession call " + sel + " URL => " + url);
if (req && req.allHTTPHeaderFields) {
const headers = req.allHTTPHeaderFields();
logLine("Headers => " + headers.toString());
}
if (req && req.HTTPBody && req.HTTPBody()) {
logLine("HTTP Body => " + utf8FromNSData(req.HTTPBody()));
}
} catch (e) { warn("NSURLSession hook err: " + e); }
}
});
ok("Hooked NSURLSession " + sel);
}
});
}
const NSURLSessionTask = ObjC.classes.NSURLSessionTask;
if (NSURLSessionTask && NSURLSessionTask["- resume"]) {
Interceptor.attach(NSURLSessionTask["- resume"].implementation, {
onEnter(args) {
try {
const task = new ObjC.Object(args[0]);
const req = task && task.currentRequest ? task.currentRequest() : null;
if (req) {
const url = req.URL() ? req.URL().toString() : "";
logLine("Task resume URL => " + url);
if (req.allHTTPHeaderFields) {
logLine("Headers => " + req.allHTTPHeaderFields().toString());
}
if (req.HTTPBody && req.HTTPBody()) {
logLine("HTTP Body => " + utf8FromNSData(req.HTTPBody()));
}
}
} catch (e) { warn("NSURLSessionTask resume hook err: " + e); }
}
});
ok("Hooked NSURLSessionTask resume");
}
}
if (ObjC.available) {
ObjC.schedule(ObjC.mainQueue, function () {
ok("ObjectiveC runtime available. Initializing…");
enableFLEX();
hookRequestConstructionFallbacks();
ok("Ready. Trigger the POST in the app and watch this console or use FLEX Network.");
});
} else {
warn("ObjectiveC runtime not available.");
}