Cite as
Addai, D., SectraTEK Lab (2026). Linking records across files. LAB-0006, version 1.0. https://lab.sectratek.com/research/lab-0006/
Methodology note
Drafted with AI assistance and reviewed by the author before publication.
Summary
- A phone seldom says anything once. The same event can leave a row in a database, an entry in an XML file and a line in a log, each written by a different component for a different purpose.
- Three tests make a link: a shared key such as a package name, a clock relationship that fits what each store records, and an explanation for any store that should hold a record and does not.
- One store is often the only witness. In the Lab's research collection the median share of events with a single witness ranged from 30 to 98 percent, depending on the event type.
- Adding a store can only add support to a finding that something happened. Losing one can turn that finding into a finding that nothing happened.
- A report should name each finding's witnesses and writers, and write "not observable", not "absent", for times that no store you hold covers.
One event, many records
A finding about a phone is usually written as if the phone said it once: the app was installed at this time, the camera was used in this minute, the phone restarted on this day. In practice a phone says most things in as many places as keep a record of them. We call each place a store. A store can be a database, an XML file, a log, or a snapshot printed into a bug report. Each has its own writer (the component that creates its records), its own purpose and its own retention [1].
An examiner rarely holds every store. An acquisition class may leave one out, a tool may not read it, or a retention limit may have erased it. Two readers of the same phone can then reach different verdicts without either making a mistake. This article is about the step in between: deciding that records in different stores describe one event, and what the finding is worth once that is decided.
Figure 1 shows the idea with a synthetic example. One use of the camera by an app leaves a record in three stores of Android's App Ops service [2].
Show this figure as a table
| Store | Record (synthetic) | What the store keeps |
|---|---|---|
| History database, 1-minute windows | com.example.notes / camera / first event 10:12:05 / count 4 | The first event of a window and a count. Later times are not stored. |
| Last-access file | com.example.notes / camera / last access 10:12:41 | The last access only. Earlier accesses and how many are not stored. |
| Bug report dump | com.example.notes / camera / printed 10:12:41 with an offset from the dump instant | A snapshot at dump time. Not a stored history. |
No record repeats another. The database row keeps the first event of a one-minute window and a count of accesses. The XML file keeps the last access and nothing earlier. The dump in a bug report is a snapshot, printed with a time and an offset from the moment it was taken [2]. A reader who expects three copies of one value will think the stores disagree. A reader who knows what each store keeps will see that they fit together.
What makes a link
Before two records are called one event, we ask for three things. A link that lacks one of them is a guess.
A shared key
The key is whatever both stores use to name the subject: a package name, a package name with an operation, a run code. LAB-0001 shows a two-letter run code that links two Samsung recovery files. Time alone makes a weak link. In Figure 3, a record for a different package lies two seconds from an install time and still does not link. Some events have no key at all. A restart has no subject, so restart records can only be linked by time, with a tolerance of 300 seconds in the Lab's measurement [1].
A clock relationship
Records of one event rarely carry the same time, and a link should not require it. The App Ops history row holds the time of the first access in its window, while the XML file holds the last access. On five Android 16 images, 54.0 percent of 3,180 XML times equaled a database time to the millisecond. Of the unequal times that fell inside a window, 99.8 percent (1,182 of 1,184) belonged to a row that counted more than one access, which is what the platform source predicts [2]. The right test is that the time falls inside the window, not that it is equal.
For other pairs of stores the Lab measured how far apart linked records lie (Figure 2). In the median the difference was under 7 seconds for every event type except restarts, where a dropbox entry and a usage event differed by 18 seconds and other pairs by 180 and 364 seconds. The platform sources give a reason: the dropbox entry is written when the boot-completed broadcast is received, and the usage event records the start of the runtime [1].
| Event (class) | Store pair | Linked pairs | Median difference (s) | 95th percentile (s) |
|---|---|---|---|---|
| Restart (Android) | usage event and dropbox entry | 20 | 18 | 49 |
| Restart (Android) | usage event and bootstat | 11 | 180 | 1,435 |
| Restart (Android) | bootstat and dropbox entry | 10 | 364 | 1,526 |
| Screen on (Android) | usage store and vendor battery log | 2,037 | 0.62 | 3.85 |
| Screen on (bug report) | usage dump and event log | 29 | 0.00 | 0.02 |
| Connected call (Android) | battery log and call log | 258 | 0.23 | 76 |
| Call (iOS) | interaction store and call history | 23,241 | 6.89 | 31 |
| Text message (Android) | telephony provider and messaging app | 147,908 | 0.00 | 28 |
| App first install (Android) | package restrictions and APK file time | 182 | 1.64 | 24 |
| App last update (Android) | package list and APK file time | 597 | 5.99 | 94 |
Two more limits apply to time. Readings of different clocks can be compared only with a stated bound on how far the clocks may disagree. Readings of one clock share an offset, and can be ordered once they differ by more than the sum of their resolutions [3]. On 300 synthetic phones, comparing point timestamps put 3.1 percent of near-simultaneous pairs from different stores in the wrong order [3]. And a time on a record is not always a time the event happened on this phone: of 13 Android phones with messages, 7 held some dated before the phone's first boot, which is consistent with records carried over from an earlier handset [4].
An explanation for what is missing
A link also needs a story for a store that should hold a record and does not. Some reasons are not errors: the store never records this kind of event, its retention has passed, a newer value replaced the old one, or the acquisition did not include it. A package's file time dates its first install only while the package has never been updated; after an update it dates the update. In the Lab's research collection that store held 28 percent of the first installs that another store also held, against 98 percent for package restrictions [1]. A store can also explain its own gaps: in Android media stores, the tombstone table covered a median 39 percent of the issued identifiers that had no live row [7].
A missing record with an explanation is neutral. A missing record without one is a question.
Link the records yourself
Figure 3 is a record linker with invented values. Pick a scenario, then select a record to link it to the event. The figure tests the link for a shared key, a clock relationship and an explanation, and says whether it counts as a witness. Untick "In my acquisition" on a store to see what the finding becomes when that store is lost. The table under the figure holds the same content.
Select a record to link or unlink it. Lines run from a linked record to the event: solid for supported, dashed for partial, dotted for not supported. All values are synthetic examples.
Show this figure as a table
| Store | Record (synthetic) | Shared key | Clock relationship | Result | Explanation |
|---|---|---|---|---|---|
| Package list | com.example.notes, first install, 09:14:02 | Pass | Pass, 1 s from the event time | Supported | Not needed. |
| Package restrictions | com.example.notes, first install, 09:14:04 | Pass | Pass, 1 s from the event time | Supported | Not needed. |
| Package restrictions | com.example.maps, first install, 09:14:05 | Fail | Pass, 2 s from the event time | Not supported | Different package. |
| APK file time | com.example.notes, file time, Day 3, 11:40:12 | Pass | Fail, 2 d 2 h from the event time | Not supported | Explained: the file time follows the last update, so it dates an update, not this install. |
| Store | Record (synthetic) | Shared key | Clock relationship | Result | Explanation |
|---|---|---|---|---|---|
| History database | com.example.notes / camera, first event, count 4, 10:12:05 | Pass | Pass, inside the window | Supported | Not needed. |
| History database | com.example.notes / microphone, first event, count 1, 10:12:20 | Fail | Pass, inside the window | Not supported | Different operation. |
| History database | com.example.notes / camera, first event, count 2, 07:30:10 | Pass | Fail, 2 h 41 min outside the window | Not supported | Explained: an earlier window with its own row. |
| Last-access file | com.example.notes / camera, last access, 10:12:41 | Pass | Pass, inside the window | Supported | Explained: differs from the row's 10:12:05 but is inside its window, which counted 4 accesses. |
| Bug report dump | com.example.notes / camera, printed time with offset, 10:12:41 | Pass | Not established | Partial | Open: dump times were not shown to match other forms. Indicator only. |
| Scenario | Stores held | Finding |
|---|---|---|
| Install | All three | Present. 2 stores, one writer (package manager). One store can be lost, no writer. |
| Install | Without package list, or without package restrictions | Present. Single witness. |
| Install | Without APK file time | Present. Unchanged: that store was not a witness. |
| Install | Without package list and package restrictions | Reads as absent if APK file time is trusted. Its detection rate for first installs was 28 percent. |
| Camera | All three | Present. 2 stores, 2 writers. The dump line is an indicator only. |
| Camera | Without the history database, or without the last-access file | Present. Single witness. |
| Camera | Without the database and the file | Indicator only, from the dump line if it was linked. Otherwise unobservable. |
What to look for. In the install scenario the package list and the package restrictions agree to within two seconds, but both are written by the package manager, so they count as one writer. The APK file time looks related, yet it follows the last update, so it is not a witness of the install. Take out the two package-manager stores and the APK store is left. It covers the time and holds no linked record, so the finding would read as absent, and that absence rests on a store with a 28 percent detection rate.
In the camera scenario the XML time does not equal the database time, yet the link is supported, because the time falls inside the window of a row that counted four accesses. The dump line shares the key and the time, but the Lab did not show that dump times match other forms, so it stays an indicator. Take out the database and the file, and nothing you hold covers the minute: the finding is unobservable, which is different from absent.
How many stores stand behind a finding
In the Lab's research collection we linked records with the rules above and counted the stores behind each event. Figure 4 gives, for each event type, the share of events with a single witness in the median phone that had at least five events of the type. For app first installs it was 89 percent, for screen-on events in Android extractions 92 percent and for connected calls 94 percent. App last updates are the exception at 30 percent, because two stores record them [1].
Show this figure as a table
| Event type | Platform | Phones | Events with one witness (median phone) |
|---|---|---|---|
| Screen on, bug report | Android | 7 | 98% |
| Connected call | Android | 7 | 94% |
| Screen on, extraction | Android | 8 | 92% |
| App first install | Android | 14 | 89% |
| Text message | iOS | 22 | 88% |
| Text message | Android | 10 | 59% |
| Call | iOS | 28 | 43% |
| App last update | Android | 14 | 30% |
Counting stores can also overstate protection. In bug reports the event log and the system log both record screen wake-ups from the same power service. Of the 39 screen-on events with two or more stores, 26 were held by three stores but only two writers [1]. A second store written by the same component adds no protection against a fault in that component.
A report is a chain of such findings, and a chain is as strong as its weakest link. If findings are independent and 52.5 percent of calls have a single witness, as in the iOS call data, the chance that all five calls of a five-call timeline have a second witness is 2.4 percent [1].
When a store is missing
Existence and absence behave differently. Adding a store can only add support to a finding that something happened. Removing a store can turn it into a finding that nothing happened: if the lost store was the only witness and another store covers the time, the remaining store is silent, and a careless reader writes "no record" for an event that occurred [1].
How often is that exposure real? In the research collection, after the only witness was lost, another store still covered the time for 36 percent of single-witness iOS calls and 29 percent of single-witness Android screen-on events. For 96 percent of single-witness iOS messages nothing else covered the time, so the finding became unobservable, not absent. Whether a covering store supports absence depends on how well it records such events: for iOS calls, the interaction store held 75 percent of the calls that the call-history database also held [1]. The same asymmetry applies to a day with no records. Of the days with no record, 79 percent (iOS calls), 91 percent (iOS messages) and 97 percent (Android screen-on) were observed by one store only [1].
Removed apps show the effect at a larger scale. We counted package names that a store held for an app the phone no longer lists as installed. Of 770 such names on Android, 98 percent were held by exactly one store, and reading the app store's local state alone found 91 percent of them; any other single store found at most 5.5 percent. On iOS, 11 of 118 logged uninstalls (9 percent) left a name in any store we read [5]. A missing trace in one store is weak evidence that an app was never installed.
Writing the finding
A finding that states a time should state its witnesses, whether they share a writer, and, for any absence, which stores observed the time and how often they hold events of that kind [1]. "No restart was recorded in the usage store, which covers the day and held 73 percent of the restarts that another store also held" is a different claim from "the phone did not restart". Without the usage store, the first claim becomes "not observable".
A finding should also be traceable. In a replay of a reference reconstruction on 53 phones, 70 percent of findings had a complete chain from the finding to a file, a digest and a value. None of the findings of absence, which were 22 percent of all findings, recorded what had been searched [6].
Before you write "the phone did X at time T":
- Name the key that the stores share.
- State the clock relationship you used (equality, a tolerance or a window) and its size.
- List the witnesses and their writers.
- For every store without a record, say why it can be empty, or say that it is unexplained.
- For a store that is not in your acquisition, write "not observable" for the times only that store covers.
Try it on a test phone
- On a phone you control, install an app and open its camera feature once. Write down the minute.
- Take a full file system extraction and a bug report from the same phone.
- For each store in Figure 3, find the record and note its key, its time and what the store keeps.
- Apply the three tests. Write down the tolerance or window you used, and why any store has no record.
- Take one store away and write the finding again.
Publish counts and store names, never values from a phone that holds personal data.
What this does not prove
Read these before you report a finding
- A link is an indicator that two records describe one event. It does not prove it. Some links were ambiguous: at most 3.6 percent of links at the base tolerance [1].
- More witnesses do not prove that the event happened as described. Stores that share a writer count once, and different writers do not show that stores fail independently.
- A missing record does not prove that the event did not happen. An absence is supported only by a store that covers the time and records such events reliably.
- None of the App Ops records says who held the phone [2].
- The linker is a teaching model with invented values. The shares and times are medians from a small, Samsung-heavy research collection, and may differ on other phones and releases.
Sources
Every number and claim above comes from a Lab Technical Report. No public documentation was used for a stated fact.
| Claim or number | Paper | Section |
|---|---|---|
| A store has a writer; findings rest on the stores that record the event; stores can be lost to a reader | [1] LAB-TR-0032 | 1 Introduction; 3.1 |
| 14 Android extractions (12 Samsung), 7 bug reports, 33 iOS extractions; 9 Android 16 and 5 Android 10 to 14; 20 iOS 26 and 12 iOS 15 to 18 | [1] LAB-TR-0032 | 4.1; 7 External |
| Tolerance of 300 s for restarts and installs; restart stores and install stores | [1] LAB-TR-0032 | 4.1, Table 2 |
| Time differences in Figure 2: pair counts, medians, 95th percentiles; under 7 s except restarts; 18, 180 and 364 s; dropbox and usage event reason | [1] LAB-TR-0032 | 5.3, Table 5; abstract |
| Single-witness medians in Figure 4 (98, 94, 92, 89, 88, 59, 43, 30 percent) and phone counts; the range 30 to 98 in the Summary | [1] LAB-TR-0032 | 5.2, Table 4 |
| 26 of 39 screen-on events held by three stores and two writers | [1] LAB-TR-0032 | 5.2 |
| 52.5 percent single witness for iOS calls; 2.4 percent for a five-call narrative | [1] LAB-TR-0032 | 3.3, Proposition 4; Table 4 |
| Detection rates: APK file time 28 percent and package restrictions 98 percent for first installs; interaction store 75 percent for iOS calls; usage store 73 percent for restarts | [1] LAB-TR-0032 | 5.4, Table 7 |
| After losing the only witness: 36 percent of iOS calls and 29 percent of Android screen-on events still covered; 96 percent of iOS messages unobservable | [1] LAB-TR-0032 | 5.4, Table 6 |
| Days with no record observed by one store only: 79, 91 and 97 percent | [1] LAB-TR-0032 | 5.5 |
| Existence findings are monotone in the stores read; absence findings are not; unobservable versus absent | [1] LAB-TR-0032 | 3.2; 3.3, Proposition 2 |
| Ambiguous links at most 3.6 percent at the base tolerance | [1] LAB-TR-0032 | 5.6, Table 9 |
| History database keeps the first event of a window and a count; 1-minute windows; XML keeps the last access; dump is a snapshot with a printed time and an offset | [2] LAB-TR-0003 | 2.1; 2.2; 2.4; 3, Table 1 |
| 3,180 in-span XML times; 54.0 percent equal to a database time; 1,182 of 1,184 (99.8 percent) of unequal times in a window belong to a row with count above 1; five Android 16 images | [2] LAB-TR-0003 | 5.3; 5.4, Table 4 |
| Dump times not shown to match another form; none of the forms says who held the device | [2] LAB-TR-0003 | 6.1 |
| Same-clock readings ordered once they differ by more than the sum of resolutions; 3.1 percent wrong with point timestamps on 300 synthetic phones | [3] LAB-TR-0038 | Abstract; Table 2 (RQ2) |
| 7 of 13 Android phones with messages held some dated before the first boot | [4] LAB-TR-0028 | Abstract |
| 770 Android names of removed apps; 98 percent in exactly one store; 91 percent found by the app store state alone; at most 5.5 percent by any other store; 11 of 118 iOS uninstalls (9 percent) | [5] LAB-TR-0035 | Abstract; Table 1 (RQ1, RQ2) |
| 70 percent of findings with a complete chain on 53 phones; no finding of absence recorded a probe; absence findings are 22 percent of all findings | [6] LAB-TR-0039 | Abstract; Table 2 |
| Media tombstone table covered a median 39 percent of issued identifiers with no live row | [7] LAB-TR-0030 | Abstract |
Papers cited
- Addai, D., Egyir, K., Tetteh, S. and Adjabeng, S. Resilience of Mobile Forensic Findings to the Loss of a Single Data Store. Lab Technical Report LAB-TR-0032. Available on this site (no DOI yet). Page
- Addai, D. Where Android Records Who Used What: App Ops History in Databases, XML Files and Bug Reports. Lab Technical Report LAB-TR-0003, Zenodo, DOI 10.5281/zenodo.23717924. Page
- Lab Technical Report LAB-TR-0038. A Calculus of Temporal Uncertainty for Ordering Phone Timestamps over Clock Domains. Available on this site (no DOI yet). Page
- Lab Technical Report LAB-TR-0028. Device-Life Boundary Indicators and the Classification of Restored Versus Native Records on Android. Available on this site (no DOI yet). Page
- Lab Technical Report LAB-TR-0035. Where Removed Apps Leave Traces: A Per-Store Measurement on Android and iOS Phones. Available on this site (no DOI yet). Page
- Lab Technical Report LAB-TR-0039. Can a Finding Be Traced to Its Bytes? Lineage Coverage of a Phone-Forensic Reconstruction. Available on this site (no DOI yet). Page
- Lab Technical Report LAB-TR-0030. Upper Bounds on Record Loss from Identifier Counters in Android Media and Message Stores. Available on this site (no DOI yet). Page
Version history and corrections
| Version | Date | Change |
|---|---|---|
| 1.0 | 2026-11-06 | First publication. |
Corrections log
No corrections have been published for this article. Corrections to any article are listed on Corrections.