[ ← BACK TO INDEX ]

SOFTWARE // GNSS · iOS

iOS GPS LOCATION SIMULATOR

Driving a real iPhone's reported position from a browser map — and learning exactly how iOS exposes a simulated fix.

PYTHONFLASKpymobiledevice3RemoteXPCCoreLocationGNSS / L1iOS 17+

SUMMARY

A small Flask app with a Leaflet map front-end that injects a simulated GPS location onto a USB-connected iPhone and walks it along real streets at a set pace. Click a point to teleport, draw a multi-point circuit, or replay a GPX track; live position streams back to the map over SSE. Built to experiment with location-based apps on hardware I own — and mostly an excuse to reverse how iOS 17+ developer tunneling and CoreLocation source-tagging actually work.

ARCHITECTURE

browser map (Leaflet) ──HTTP / SSE──▶ Flask app │ ▼ movement engine ── walk waypoints · Gaussian GPS jitter · 2 Hz tick │ ▼ pymobiledevice3 LocationSimulation ──RemoteXPC tunnel──▶ iPhone

Great-circle math (haversine / bearing / step-along-bearing) moves the position between waypoints. The engine ticks twice a second so iOS sees a steady stream of fixes and derives speed and course from the cadence. A clean navigation path is tracked separately from the jittered position that's actually reported, so the underlying route stays exact while the reported fix wobbles like a real receiver.

WHAT I ACTUALLY MESSED WITH

iOS 17+ moved everything behind a tunnel. Modern iOS runs developer services over a RemoteXPC tunnel that needs a root tunneld process to bring up the network interface. Getting a stable DVT/instruments session over that tunnel was the bulk of the work.
The invisible Developer Mode toggle. Settings → Privacy & Security → Developer Mode simply does not exist until a developer tool pokes the device the right way — pymobiledevice3 amfi reveal-developer-mode is what makes the switch appear. A plain USB/lockdown connection isn't enough on current iOS.
A moving-target library. The whole DVT / location API went async across pymobiledevice3 releases, so I bridged the async device calls onto a background event loop to keep the engine's tick loop synchronous.
iOS knows it's fake — by design, and that's the interesting part. No amount of realistic pathing hides a software-simulated location, because iOS stamps every injected fix with CLLocationSourceInformation.isSimulatedBySoftware = true. The flag comes from the OS, not the coordinates — a clean, honest signal any app can read. The only ways to produce an unflagged fix are RF-level GNSS spoofing (transmitting L1 — regulated spectrum) or OS-level hooks. Good reminder that the robust anti-spoof check is the source flag plus a motion-sensor cross-check, not coordinate realism.

STACK

Built and tested strictly on hardware I own, for understanding how iOS surfaces a simulated location to apps and how to detect it. Not for defeating third-party location or anti-fraud controls.