MapsSync_0.0.1 stores Apple Maps search and route history on iOS. Most of it tells you what a user looked up, not where the phone was. One field is different. When a user requests a route, MapsSync_0.0.1 can record the device's own position, a timestamp and an accuracy value inside a protobuf BLOB.
We tested that BLOB against a known timeline and against Cache.sqlite. The device location matched to the last decimal place.
Key findings
- The protobuf in
ZROUTEREQUESTSTORAGEholds a device latitude, longitude, timestamp and horizontal accuracy. All four matched a Cache.sqlite record exactly. - The same protobuf holds the destination coordinates. They matched the destination selected on the device.
- The protobuf timestamp is not the row's
ZCREATETIME. In our test it was 8 seconds earlier. Use the protobuf timestamp for the location. - A row with no BLOB is a search. It says nothing about where the device was.
- Nothing in this database shows the device drove the route or reached the destination.
What was already known
This test builds on published work. Cellebrite's 2025 location booklet lists three points about the ZHISTORYITEM table:
ZROUTEREQUESTSTORAGEis a protobuf BLOB with the start and end of a requested route.- The start is the device's real location when node
1/0/3/100holds a value of 1. - If
ZROUTEREQUESTSTORAGEis empty, treat the row as a search and not a visit.
What was missing was a node-by-node map of the protobuf and a check of its values against a second source. That's what this test adds.
One scope note: MapsSync_0.0.1 belongs to the native Apple Maps app. It holds nothing from Google Maps, Waze or other third-party apps.
Test setup
| Item | Detail |
|---|---|
| Device | Apple iPhone 16 Pro |
| iOS version | 18.3 |
| Database | /private/var/mobile/Containers/Shared/AppGroup/[GUID]/Maps/MapsSync_0.0.1 |
| Comparison source | /private/var/mobile/Library/Caches/com.apple.routined/Cache.sqlite, table ZRTCLLOCATIONMO |
| Test date and place | 13 Aug 2025, Columbia, SC, Eastern Daylight Time (UTC-4) |
| Extraction | Full File System, Cellebrite Inseyets UFED 10.6.0.377, SHA256 of the zip verified |
| Analysis tools | 7-Zip, DB Browser for SQLite, Mushy (protobuf viewer) |
Actions on the device
Times are local (UTC-4) and read off a clock by hand, so allow 1 to 2 seconds of error.
| Time | Action |
|---|---|
| 13:03:07 | Maps opened |
| 13:04:00 | Searched "Chick-fil-a" |
| 13:04:27 | Selected the Harbison location (294 Harbison Blvd, Columbia, SC 29212) |
| 13:04:48 | Started navigation |
| 13:05:03 | Ended navigation |
| 13:05:26 | Maps closed in the App Switcher |
| 13:05:46 | Maps opened again. Chick-fil-a was still selected. |
| 13:06:19 | Cleared the Chick-fil-a selection |
| 13:06:36 | Cleared the search bar |
| 13:07:01 | Searched 5623 Two Notch Rd and selected it from the list |
| 13:07:25 | Cleared the search without starting navigation |
| 13:07:30 | Maps closed in the App Switcher |
| 13:08:54 | Maps opened |
| 13:09:29 | Searched 1931 Pineview and selected it from the list |
| 13:09:47 | Cleared the selection |
| 13:10:10 | Started typing 1701 Main St. Never selected a result or pressed Search. |
| 13:10:42 | Maps closed in the App Switcher |
What MapsSync_0.0.1 recorded
Seventeen actions produced four rows in ZHISTORYITEM.
| Z_PK | ZCREATETIME | ZMODIFICATIONTIME | Other data | BLOB | Matches action |
|---|---|---|---|---|---|
| 1 | 13:04:04 | 13:04:04 | ZQUERY = Chick-fil-A ZLOCATIONDISPLAY = Columbia | No | 13:04:00 search |
| 2 | 13:04:27 | 13:04:47 | None | Yes | 13:04:27 selection, 13:04:48 navigation start |
| 3 | 13:07:00 | 13:07:00 | ZLATITUDE1 = 34.053193 ZLONGITUDE1 = -80.9806143 ZMAPITEMLASTREFRESHED = 13:07:00 | No | 13:07:01 Two Notch Rd search |
| 4 | 13:09:29 | 13:09:29 | None | Yes | 13:09:29 Pineview search and selection |
Timestamps
ZCREATETIME, ZMODIFICATIONTIME and ZMAPITEMLASTREFRESHED are Mac Absolute timestamps stored in UTC. Each one landed within a few seconds of the logged action.
Row 2 is the useful one. ZCREATETIME lines up with selecting the destination. ZMODIFICATIONTIME lines up with starting navigation, one second off the logged time. In row 4 the two values are identical, and that route was requested but never started. Two records is a thin basis for a rule, so treat "modification time equals navigation start" as a lead to test, not a finding.
This query gives you the same view with UTC output:
SELECT
Z_PK,
datetime(ZCREATETIME + 978307200, 'unixepoch') AS create_utc,
datetime(ZMODIFICATIONTIME + 978307200, 'unixepoch') AS modified_utc,
datetime(ZMAPITEMLASTREFRESHED + 978307200, 'unixepoch') AS mapitem_refreshed_utc,
ZLATITUDE1,
ZLONGITUDE1,
ZLOCATIONDISPLAY,
ZQUERY,
ZROUTEREQUESTSTORAGE
FROM ZHISTORYITEM
ORDER BY ZCREATETIME;
Inside the ZROUTEREQUESTSTORAGE protobuf
The BLOB from row 2 was copied out and opened in Mushy. The top level has four nodes. Everything of interest sits under node 1, which splits into two branches:
1, 0is the route start, which here was the device location.1, 1is the destination.
Device location nodes
| Node path | Content | Value in row 2 |
|---|---|---|
| 1, 0, 9, 1, 1 | Device latitude | 34.0587641220565 |
| 1, 0, 9, 1, 2 | Device longitude | -80.9487052735735 |
| 1, 0, 9, 3 | Timestamp (Mac Absolute, UTC) | 776797459.647788 2025-08-13 17:04:19 UTC |
| 1, 0, 9, 5 | Horizontal accuracy (meters) | 18.3073625129036 |
Some of these values repeat elsewhere in the protobuf. The paths above are the ones we tested.
Destination nodes
| Node path | Content | Value in row 2 |
|---|---|---|
| 1, 1, 9, 1 | Destination latitude | 34.0733068 |
| 1, 1, 9, 2 | Destination longitude | -81.1562814 |
The destination branch carries coordinates only. No timestamp, no accuracy. In both BLOBs the coordinates matched the destination selected during the test.
Validating against Cache.sqlite
Cache.sqlite, and its ZRTCLLOCATIONMO table in particular, is a well-tested source of iOS device location. We looked for a record with the same timestamp as the protobuf and found one.
| Source | Timestamp (UTC-4) | Latitude | Longitude | Horizontal accuracy |
|---|---|---|---|---|
| MapsSync_0.0.1 | 2025-08-13 13:04:19 | 34.0587641220565 | -80.9487052735735 | 18.3073625129036 |
| Cache.sqlite | 2025-08-13 13:04:19 | 34.0587641220565 | -80.9487052735735 | 18.3073625129036 |
Every digit matches. We repeated the check with row 4, the only other row with a BLOB. It matched its Cache.sqlite record exactly too.
A match to 13 decimal places isn't two sensors agreeing. It points to Maps writing the same location fix that routined cached. That matters for two reasons. The MapsSync value is as good as the Cache.sqlite value. And Cache.sqlite ages out quickly, so a MapsSync_0.0.1 record may still give you a validated device position after the Cache.sqlite record is gone. How long MapsSync rows persist was not part of this test.
The timestamp gap you need to know about
The protobuf timestamp did not match either row timestamp:
| Value | Time (UTC-4) | What it marks |
|---|---|---|
| Protobuf node 1, 0, 9, 3 | 13:04:19 | When the location fix was taken |
| ZCREATETIME | 13:04:27 | Destination selected, route requested |
| ZMODIFICATIONTIME | 13:04:47 | Navigation started |
The fix is 8 seconds older than the route request. Maps appears to use the most recent fix it already has and does not take a new one at the moment of the request. In this test the phone was stationary, so the gap changed nothing. On a moving device, or with a longer gap, the difference could matter.
Report the protobuf timestamp as the time of the location. Report ZCREATETIME as the time of the user action. Don't merge them.
What MapsSync_0.0.1 did not record
- Typing without selecting. The 1701 Main St entry was typed but never selected or submitted. It left no row. More testing is needed before you call that a rule.
- Travel. Navigation ran for 15 seconds and was ended. No record shows the device moved along the route or arrived. A route request is intent, not presence at the destination.
- A BLOB for every selected address. Rows 3 and 4 came from the same kind of action: search an address, select it, clear it. Row 4 got a BLOB. Row 3 did not. We don't know why. Row 3 stored only the searched place's coordinates in
ZLATITUDE1andZLONGITUDE1. - Search text for address searches.
ZQUERYheld "Chick-fil-A" for the business search. It was NULL for the address searches. It may hold business or place names only. That needs more testing.
How to use this in casework
- Query
ZHISTORYITEMand separate rows with aZROUTEREQUESTSTORAGEBLOB from rows without one. - Treat rows without a BLOB as searches.
ZLATITUDE1andZLONGITUDE1are the searched place, not the phone. - For rows with a BLOB, decode the protobuf. Pull the device location from
1, 0, 9and the destination from1, 1, 9. - Check node
1/0/3/100for a value of 1 before you call the start point a device location. A user can set a route start that isn't their current position. - If Cache.sqlite still covers the time frame, find the matching
ZRTCLLOCATIONMOrecord and document the match. - Report the location with its own timestamp and horizontal accuracy. Report the row timestamps as user activity.
Limits of this test
- One device, one iOS version, four database rows, two BLOBs.
- The device was stationary the whole time.
- Routes with a manually chosen start point were not tested, and the
1/0/3/100flag was not examined in this round. - Record retention and iCloud sync behavior were not tested.
Run the same check on your own test device and iOS version before you rely on these node paths in a report.
Bottom line
When ZROUTEREQUESTSTORAGE is populated, MapsSync_0.0.1 gives you a device position, a time and an accuracy value that matched Cache.sqlite exactly in this test, plus destination coordinates that matched the route the user requested. It tells you where the phone was when the user asked for directions. It does not tell you the user went there.
Testing and original report by Michael Edwards, Digital Forensics Examiner, Richland County Sheriff's Department. Prior reference: Cellebrite, Location Booklet (2025).