TraceTheMap: Winning an iOS Location Challenge by Patching Distance Checks
An iOS dynamic instrumentation writeup for TraceTheMap, covering Core Location delegate discovery, live CLLocation tracing, distance calculation hooks, and forcing marker collection with Frida.
TraceTheMap is a Swift-based iOS location challenge. The application places five hidden map markers within a 1 km radius and awards 100 points for each marker collected.

The objective was to make the application display the win page by collecting all five markers:
You Win! You've collected all 5 flags.
The app only awards a marker when the player is within 50 meters of it. It also includes anti-spoofing and behavioral checks, so blindly spoofing GPS coordinates is not the cleanest path. Instead, I used Frida to understand and patch the app's runtime location-distance checks.
Objective
Score 500 points by making the application believe the current location is within range of all five hidden markers.
Final Result
You Win! You've collected all 5 flags.
There is no flag string for this challenge. The goal is to reach the win page.
Environment
The challenge was solved on a jailbroken iPhone with Frida. I first identified the target process with:
frida-ps -U -ai

The relevant output showed:
PID Name Identifier 1644 TraceTheMap com.8ksec.TraceTheMap
The running process was confirmed with:
frida-ps -U | grep -i Trace

Discovering Location-Related Methods
The first step was to identify how the application receives location updates. The discovery script enumerated Objective-C classes and methods containing selectors related to location handling.
for (const className in ObjC.classes) {
const cls = ObjC.classes[className];
try {
if (!cls.$ownMethods) continue;
cls.$ownMethods.forEach(m => {
if (m.includes('locationManager') || m.includes('didUpdateLocations')) {
console.log('[candidate]', className, m);
}
});
} catch (_) {}
}The script was executed against the running app:
frida -U -n TraceTheMap -l trace_location.js


The important app-specific candidate was:
[candidate] TraceTheMap.LocationManager - locationManager:didUpdateLocations:
This confirmed that GPS updates were received through a custom class named TraceTheMap.LocationManager.
Hooking the Location Manager
After identifying the delegate method, I hooked it and inspected the live CLLocation objects received by the application.
const targetClass = 'TraceTheMap.LocationManager';
const targetMethod = '- locationManager:didUpdateLocations:';
const cls = ObjC.classes[targetClass];
Interceptor.attach(cls[targetMethod].implementation, {
onEnter(args) {
const locations = new ObjC.Object(args[3]);
console.log('[locations]', locations.toString());
console.log('[count]', locations.count());
}
});The script was executed with:
frida -U -n TraceTheMap -l hook_location_manager.js

The hook confirmed that the app was consuming normal Core Location updates through standard CLLocation objects.
Enumerating the Custom Location Manager
The custom location manager class was then enumerated to see whether marker or score-related methods were exposed directly.
const targetClass = 'TraceTheMap.LocationManager';
const cls = ObjC.classes[targetClass];
console.log('[+] Found class:', targetClass);
cls.$ownMethods.forEach(m => {
console.log(m);
});frida -U -n TraceTheMap -l enum_location_manager.js

The class only exposed initialization and the location update callback:
- init - locationManager:didUpdateLocations: - .cxx_destruct
That meant the scoring logic was probably performed downstream after location updates were received. Instead of hunting every Swift score method, the cleaner target was the shared distance calculation used by the app.
Tracing Distance Calculations
The challenge rule was simple:
Collect a marker when distance <= 50 meters
On iOS, distance between two CLLocation objects is commonly calculated with -[CLLocation distanceFromLocation:]. I hooked this method and printed both locations involved in each distance check.
const CLLocation = ObjC.classes.CLLocation;
const method = CLLocation['- distanceFromLocation:'];
Interceptor.attach(method.implementation, {
onEnter(args) {
this.selfLoc = new ObjC.Object(args[0]);
this.otherLoc = new ObjC.Object(args[2]);
console.log('[self]', this.selfLoc.toString());
console.log('[other]', this.otherLoc.toString());
},
onLeave(retval) {
console.log('[raw retval]', retval);
}
});frida -U -n TraceTheMap -l trace_distance.js

The output showed repeated distance checks between the current location and five different marker locations, matching the challenge requirement to collect five markers. That made CLLocation -distanceFromLocation: the best patch point.
Patching distanceFromLocation:
The app's scoring condition was effectively:
if distance <= 50 meters:
collect marker
add 100 pointsRather than spoofing GPS coordinates, I replaced the method that returns the distance. -[CLLocation distanceFromLocation:] returns a CLLocationDistance, which is a double. The replacement returned 10.0, safely below the 50 meter threshold.
const CLLocation = ObjC.classes.CLLocation;
const method = CLLocation['- distanceFromLocation:'];
const replacement = new NativeCallback(function (self, _cmd, otherLocation) {
console.log('[+] distanceFromLocation called -> fake 10.0m');
return 10.0;
}, 'double', ['pointer', 'pointer', 'pointer']);
Interceptor.replace(method.implementation, replacement);frida -U -n TraceTheMap -l force_distance.js

Each call returned 10.0m, causing every marker check to pass as if the player were within collection range.
Win Page
After the patched distance checks completed, the application awarded all five markers and displayed the win page:
You Win! You've collected all 5 flags.
Demo Video
Scripts Used
trace_location.jsdiscovered app-specific location delegate methods.hook_location_manager.jsinspected liveCLLocationobjects received by the app.enum_location_manager.jsenumerated methods onTraceTheMap.LocationManager.trace_distance.jsidentified repeated calls toCLLocation -distanceFromLocation:.force_distance.jsreplaced the distance calculation and returned10.0meters.
Final Thoughts
This challenge was a good example of choosing a stable patch point instead of fighting the app's anti-spoofing behavior. By tracing the location pipeline and patching the shared distance calculation, every marker check passed without needing to simulate realistic movement across the map.