Decoding What iOS Location Services Send to Apple: An LLM Is an Unreliable Analyst but a Useful Toolmaker
Organizations: Trinity College Dublin, Ireland
Abstract
Reverse engineering the data formats used by mobile software is skilled and slow, and using AI to assist with it is attractive. We evaluate this for the protobuf messages that an iPhone sends to Apple's location services. Working from Apple's binaries, we use two frontier models (Claude Opus 4.8, Codex GPT Sol 5.6) to reconstruct the schemas of protobufs observed in network traffic. The great majority of the schemas contain substantial errors, and these are hard to detect because the schemas are internally consistent. Direct AI use is therefore unsuitable for forensic and privacy analysis. AI can, however, write reliable analysis tools when their output can be validated. We use Claude to write a Python tool, Extractor, that reliably recovers protobuf schemas from Apple binaries. We validate it against binaries compiled from schemas with known answers, previously published schemas, and network measurements. We demonstrate the use of Extractor to decode and analyse the location services network traffic of a jailbroken iPhone 8 (iOS 16.7.11).
Figures & tables
| Field type | Fields | Claude | Codex |
|---|---|---|---|
| Scalar | 4,151 | 3,575 (86.1%) | 3,283 (79.1%) |
| Enum: right enum | 993 | 807 (81.3%) | 602 (60.6%) |
| all values correct | 993 | 775 (78.0%) | 575 (57.9%) |
| Message: right message | 2,644 | 2,337 (88.4%) | 1,923 (72.7%) |
| whole subtree correct | 2,644 | 1,528 (57.8%) | 1,374 (52.0%) |
| Claude | Codex | |
| Descriptor does not load: it names a type it never defines e.g. Codex gives CoarseLocationMetadata.type an enum type Type that it never defines | 67 | 24 |
| Enum type never defined | 52 | 24 |
| Message type never defined | 13 | 0 |
| Type of a scalar field never defined | 2 | 0 |
| Value silently dropped, or left as raw bytes e.g. Claude types the latitude LatLngE7.latE7 ( sfixed32 ) as int32 , so a decoder skips it | 38 | 61 |
| Message left as bytes | 29 | 1 |
| Module | Responsibility |
|---|---|
| protocore.py | ivar type encoding to proto scalar/message type and label |
| writeto.py | writeTo: CFG walk, field number/name binding (Section IV-B1 ) |
| dispatch.py | readFrom: field-dispatch decoding, used for repeated-element typing (Section IV-B2 ) |
| repeated.py | repeated-field element type/ivar resolution (Section IV-B2 ) |
| enums.py | both enum-recovery mechanisms across dense/sparse/hybrid shapes (Section IV-B4 ) |
| write_helpers.py | classifies a writeTo: helper’s wire-type family from its tag-building instructions (Section IV-B3 ) |
| Module | Recovers | Statement coverage | Branch coverage |
|---|---|---|---|
| protocore.py | ivar type/label decoding | 89.2% (33/37) | 87.5% (21/24) |
| dispatch.py | readFrom: field-dispatch decoding for repeated-element typing (Section IV-B2 ) | 88.4% (274/310) | 78.5% (135/172) |
| writeto.py | writeTo: field number/name binding (Section IV-B1 ) | 84.0% (247/294) | 70.1% (143/204) |
| write_helpers.py | wire-type family classification (Section IV-B3 ) | 83.0% (44/53) | 73.3% (22/30) |
| enums.py | enum value-table recovery (Section IV-B4 ) | 80.2% (482/601) | 67.0% (233/348) |
| repeated.py | repeated-field element resolution (Section IV-B2 ) | 77.9% (127/163) | 67.6% (69/102) |
| Client | Request | Location sent |
|---|---|---|
| Weather widget | Reverse geocoding | City-level (one fixed point several kilometres from the handset; location-off experiment only) |
| analyticsd | Reverse geocoding | Coordinates rounded to 1 to 2 decimal places (about 1 to 10 km) |
| network- serviceproxy | Reverse geocoding | Reported accuracy about 3.7 km |
| kbd | Geocoding | Postal district, no coordinates |
| CoreLocation prompt plugins | Dataset status check | None (IP address only) |
| Wi-Fi service ( ThreeBars- XPCService ) | Wi-Fi quality lookup | MAC address of the connected access point, no coordinates |
| Endpoint | Data | Uploads observed |
|---|---|---|
| gsp10-ssl.apple.com/hcy/pbcwloc | LTE cell (MCC, MNC, TAC, cell ID, band, signal strength) and Wi-Fi (MAC address, channel, signal strength, scan time) observations, each with the handset location and motion state | 5 in two batches 12 hours apart, data up to 12 hours old |
| gsp10-ssl.apple.com/pds/pd | GPS locations with altitude, accuracy, speed, course and motion state, app identifier, tripId | 1, with 99 locations 13 hours old |
| gsp9-ssl.apple.com/hvr/v2/rtloc (sent by geod ) | As /pds/pd | 1, with 66 locations 42 s old |
| gsp10-ssl.apple.com/hvr/trc | Pressure, temperature, locations with GNSS satellite reports (satellite identifier, signal strength, azimuth, elevation), compass attitude, pedometer, motion state, app identifier | 2, with 317 and 864 events, up to 13 hours old |
| gsp10-ssl.apple.com/psr | Barometric pressure with the GPS fix at the time of the reading | 3, one with an empty body |
| Identifier | Lifetime observed | Documented |
|---|---|---|
| Daily record session IDs | At least 106 days | No; the 15-month label is not mentioned |
| Session ID in geod telemetry | 20 IDs across 11 processes. Maximum age per ID 3 to 29 days. Used over 16 to 17 days by geod , the Wi-Fi quality service and a prompt plugin | No |
| dispatcher.arpc sessionId | 10 IDs across the clients, none seen for more than 10 seconds | No |
| A/B guid | At least 1.8 days; a different value in the drive experiment | No |
| A/B bucketId | Assigned by server: 7428 from 17 February to 6 March, 12296 on 4 and 5 June | No |
| tripId | One trip | Yes, rotates when the trip ends |
| Apple statement | Assessment |
|---|---|
| 1. Wi-Fi and cell submissions are anonymous, encrypted and use a rotating identifier | Incomplete |
| 2. Routing and Traffic data is sent with an identifier that rotates at the end of a trip | Consistent for the identifier; incomplete for the data |
| 3. With Location Services off, apps cannot access location | Incomplete |
| 4. Device Analytics sharing can be disabled altogether | Not addressed |
| 5. Significant Locations are encrypted end to end; on-device processing means Apple does not learn the pattern of life | Not addressed (encryption); contradicted (pattern of life) |
| 6. Maps identifiers rotate or are longer-lived | Identifier lifetimes are given only for Maps: Not addressed |
| Message (class) | Field(s) | Published | Extractor | Binary |
| wloc request ( ALSLocationRequest ) | 3, 4 | sint32 | int32 | Int32Field , i |
| wloc request ( ALSLocationRequest ) | 25 | optional | repeated | array ivar, Submessage |
| LTE cell tower ( ALSLteCellTower ) | 1–4, 6, 7 | uint32 | int32 | Int32Field , i |
| wloc location ( ALSLocation ) | 3–7, 12 | int64 | int32 | Int32Field , i |
| wloc location ( ALSLocation ) | 11, 13 | int64 | uint32 | Uint32Field , I |
| pbcwloc Wi-Fi entry ( CLPWifiAPLocation ) | 9 | int32 | enum | i , has scanTypeAsString: |
| Message | Field no. | Published name | Binary name | Meaning | Observed values |
| wloc location ( ALSLocation ) | 4 | unknown_value4 | locationType | unnamed | 0 or 3, 2,001 locations (3 in 550) |
| 7 | speed | confidence | differs | not observed in responses | |
| 11 | motion_activity_type | score | differs | 1 to 948 (median 63), 2,001 locations | |
| 12 | motion_activity_confidence | reach | differs | 0 to 70,502 (median 435), 2,001 locations | |
| 13 | provider | infoMask | differs | not observed in responses | |
| pbcwloc motion activity ( CLPMotionActivity ) | 1 | confidence | type | differs (swapped) | 1, 2, 3 |
| Fields | rustpush | Extractor | n | Binary |
| enumerations, e.g. RequestOperation.Header 35, Subscription 2, ShareInfo 3, ResetRequest 1 | int32 or uint32 | enum | 30 | the class has a <field>AsString: method, or the Swift type is an enumeration |
| nested messages, e.g. SubscriptionNotification 1, DispositionType 1–5 | bytes | message | 18 | ivars of a message class ( @"CKDPSubscriptionNotificationAlert" ), or arrays written by the routine that writes message fields |
| Asset 4, 14, 18 | uint64 | int64 | 3 | ivar q (signed) |
| TlkShare 2, 3, 5, 10 | uint64 or uint32 | int64 | 4 | Swift type Int64 |
| EncryptedAttributes 3–10, 14, 15 | uint64, uint32 or int32 | int32, int64 or uint32 | 10 | ivars i , q or I , as recovered by Extractor (all 10) |
| AssetUploadData 4, AssetInfo 3, MMCSHeaders 2 | uint32, uint64 or int32 | int64 or uint64 | 3 | ivars q , q and Q |
| Binary class (rustpush message) | Field | rustpush name | Binary name | Type |
| NGMPBPublicDevicePrekey ( PreKeyData ) | 1 | key | prekey | bytes |
| 2 | signature | prekeySignature | bytes | |
| 3 | timestamp | timestamp | double | |
| NGMPBPublicDeviceIdentity ( NgmPublicIdentity ) | 1 | publicKey | signingKey | bytes |
| NGMPBInnerMessage ( InnerMessage ) | 1 | message | message | bytes |
| 2 | counter | counter | uint32 |
| Onboarding screen | Option selected |
|---|---|
| Language | "English", "Ireland" |
| Quick Start | "Set Up Manually" |
| Written and Spoken Languages | "Continue" |
| Choose a Wi-Fi Network | "Continue without wifi", "Continue" 24 24 24 An ”Unable to activate” screen is displayed. We click ”Other options”, ”Use Mac or PC”, then connect the iPhone to a MacBook using a USB cable. On the MacBook we open Apple Music, click the iPhone icon, then ”Open in Finder”. A popup says ”Activating” and, after a delay, the iPhone moves on to the next screen. We then disconnect the USB cable. |
| Data & Privacy | "Continue" |
| Touch ID | "Set up Touch ID later" |
| Endpoint | Purpose | Request content | Response content | Loc. sent | When loc. off? |
|---|---|---|---|---|---|
| configuration.ls.apple.com/config/defaults | Settings | URL params: OS version, hardware | XML plist: feature flags, tile-type switches, endpoint URLs, per-country feature maps | No | |
| gspe35-ssl.ls.apple.com/geo_manifest/dynamic/config | Settings | URL params: country code, OS version, hardware | Protobuf: geod resources, incl. map tiles, fonts, icons, textures available, endpoint URLs | Country code | |
| gspe1-ssl.ls.apple.com/pep/gcc | IP country lookup | Standard HTTP headers only; no body | Plain text: Country code | No | |
| gsp-ssl.ls.apple.com/ab.arpc | A/B experiments | ARPC protobuf: session GUID, locale, OS version, country code | Protobuf: bucket ID for namespaces (parsec, Siri, RAP, mapsAb) | Country code | |
| gsp64-ssl.ls.apple.com/hvr/v3/use | Telemetry | ARPC protobuf: batched per-request log records with app ID, HW/OS, connectivity type, carrier, country code, latency, HTTP status, server IP:port, tile-set ID | Empty ARPC acknowledgement (0-byte payload) | Carrier; country code | |
| gsp9-ssl.apple.com/hvr/v2/rtloc | Real-time traffic probe | ARPC protobuf: sequence of timestamped records with handset GPS location, accuracy, handset motion type | Empty ARPC acknowledgement (0-byte payload) | Precise (GPS, 5 m accuracy) |
| Endpoint | Purpose | Request content | Response content | Loc. sent | When loc. off? |
|---|---|---|---|---|---|
| gs-loc.apple.com /clls/wloc | Wi-Fi/cell location lookup | Request contains a cell tower identity (MCC, MNC, cell ID) or a Wi-Fi MAC address (in one request, the handset lat/lon). | Response is a list of cell towers or Wi-Fi MAC addresses and their lat/lon positions. | Precise (Wi-Fi/cell tower coverage area; unrounded lat/lon in one request) | |
| gsp10-ssl.apple.com/hcy/pbcwloc | Cell/Wi-Fi observation upload | Cell tower (MCC, MNC, TAC, cell ID, signal strength) or Wi-Fi (MAC address, channel, signal strength) records, each with the handset location and motion state | Acknowledgement only | Precise (10–400 m) | |
| gsp10-ssl.apple.com/pds/pd | GPS fix upload | GPS fixes (lat/lon, altitude, accuracy, speed, course, motion state), app bundle ID, trip ID | Acknowledgement only | Precise (GPS, 5 m) | |
| gsp10-ssl.apple.com/hvr/trc | Sensor trace upload | Pressure, temperature, locations with GNSS satellite data, compass attitude, pedometer, motion state, app bundle ID | Empty | Precise (GPS and network fixes) | |
| gsp10-ssl.apple.com/psr | Pressure upload | Barometric pressure reading and timestamp, with the GPS fix at the time of the reading (lat/lon, altitude, accuracy) | Empty | Precise (GPS, 5 m) | |
| cl2.apple.com/1/v1/ {lat0}/{lon0}/{lat}_{lon}.gz | Wi-Fi tile download | Tile SW corner in URL path 1 | A tile containing Wi-Fi MAC addresses and lat/lon positions | Coarse (tile size, 5 km) |
| Endpoint | Parameter | Example value | Description |
|---|---|---|---|
| configuration.ls.apple.com /config/defaults | os | ios | Operating system identifier |
| os_version | 16.7.11 | OS version string | |
| hardware | iPhone10,1 | Hardware model identifier | |
| gspe35-ssl.ls.apple.com /geo_manifest/dynamic/config | application | geod | Client application name |
| application_version | 1 | Application version | |
| country_code | IE | Country code |
| Header | Description / example value |
|---|---|
| maps-auth-token | HMAC of the tile version v (e.g. v=18965870 ); see Section VIII-B |
| x-tilekey | Integer tile key, e.g. 81607936 |
| maps-tile-x / -y / -z | XYZ tile grid coordinates (column, row, zoom level), e.g. x=3, y=2, z=3 |
| maps-tile-style | Tile parameters, e.g. style=20&size=2&scale=0&v=18965870 . The headers carry the same fields as the tile.vf query parameters of Table XVI ; styles 1, 11, 20, 30 and 53 and zoom levels 3 to 15 occur in them |
| x-msg-priority | Small integer, either 5 or 8 |
| x-os-version | iOS version and build string, 16.7.11.20H360 |
Software Reverse Engineering
Software reverse engineering recovers program structure, behavior, and intent from binaries when source code is missing, optimized, or obfuscated.
See all 35 papers