Research / LAB-0006

Linking records across files

One event on a phone usually leaves records in several places: a database row, an entry in an XML file, a line in a log. A finding is stronger when those records can be linked and weaker when only one store speaks. This article says what a link needs, lets you build one with synthetic records, and shows what happens to a finding when a store is lost.

  • Daniel Addai, SectraTEK Lab
  • Version 1.0
  • 2026-11-06
  • CC BY 4.0
  • Early research

Cite as

Addai, D., SectraTEK Lab (2026). Linking records across files. LAB-0006, version 1.0. https://lab.sectratek.com/research/lab-0006/

Citation guide

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].

One camera use, three different recordsA synthetic event, an app using the camera in the minute that starts at 10:12:00, with arrows to three stores of the App Ops service. The history database holds a row with the first event of the window, 10:12:05, and a count of 4. The last-access file holds the last access, 10:12:41. The bug report dump holds a printed time of 10:12:41 with an offset from the dump instant. All three share the package and operation as their key, and none repeats another value.Event (synthetic example)com.example.notesuses the camera in theminute from 10:12:00key: package + operationHistory database, 1-minute windowsapp_op_history_short_interval.dbrow: com.example.notes / camera / first event 10:12:05 / count 4Keeps the first event of a window and a count. Later times are not stored.Last-access fileappops_accesses.xmlentry: com.example.notes / camera / last access 10:12:41Keeps the last access only. Earlier accesses and how many are not stored.Bug report dumpApp Ops sectionline: com.example.notes / camera / printed 10:12:41 + offsetA snapshot at dump time. Not a stored history.
Show this figure as a table
The three records of one synthetic camera use
StoreRecord (synthetic)What the store keeps
History database, 1-minute windowscom.example.notes / camera / first event 10:12:05 / count 4The first event of a window and a count. Later times are not stored.
Last-access filecom.example.notes / camera / last access 10:12:41The last access only. Earlier accesses and how many are not stored.
Bug report dumpcom.example.notes / camera / printed 10:12:41 with an offset from the dump instantA snapshot at dump time. Not a stored history.
Figure 1One synthetic camera use and the three records it leaves. The key (package and operation) is shared, the times are not equal, and no record repeats another. Source: layout of the three stores as described in LAB-TR-0003 (synthetic example, invented values).

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.

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
Scenario 1, app first install. Event: com.example.notes first installed at about 09:14:03. Tolerance 300 s.
StoreRecord (synthetic)Shared keyClock relationshipResultExplanation
Package listcom.example.notes, first install, 09:14:02PassPass, 1 s from the event timeSupportedNot needed.
Package restrictionscom.example.notes, first install, 09:14:04PassPass, 1 s from the event timeSupportedNot needed.
Package restrictionscom.example.maps, first install, 09:14:05FailPass, 2 s from the event timeNot supportedDifferent package.
APK file timecom.example.notes, file time, Day 3, 11:40:12PassFail, 2 d 2 h from the event timeNot supportedExplained: the file time follows the last update, so it dates an update, not this install.
Scenario 2, camera use. Event: com.example.notes used the camera in the minute that starts at 10:12:00.
StoreRecord (synthetic)Shared keyClock relationshipResultExplanation
History databasecom.example.notes / camera, first event, count 4, 10:12:05PassPass, inside the windowSupportedNot needed.
History databasecom.example.notes / microphone, first event, count 1, 10:12:20FailPass, inside the windowNot supportedDifferent operation.
History databasecom.example.notes / camera, first event, count 2, 07:30:10PassFail, 2 h 41 min outside the windowNot supportedExplained: an earlier window with its own row.
Last-access filecom.example.notes / camera, last access, 10:12:41PassPass, inside the windowSupportedExplained: differs from the row's 10:12:05 but is inside its window, which counted 4 accesses.
Bug report dumpcom.example.notes / camera, printed time with offset, 10:12:41PassNot establishedPartialOpen: dump times were not shown to match other forms. Indicator only.
What the finding becomes when stores are taken out, if every supported record is linked
ScenarioStores heldFinding
InstallAll threePresent. 2 stores, one writer (package manager). One store can be lost, no writer.
InstallWithout package list, or without package restrictionsPresent. Single witness.
InstallWithout APK file timePresent. Unchanged: that store was not a witness.
InstallWithout package list and package restrictionsReads as absent if APK file time is trusted. Its detection rate for first installs was 28 percent.
CameraAll threePresent. 2 stores, 2 writers. The dump line is an indicator only.
CameraWithout the history database, or without the last-access filePresent. Single witness.
CameraWithout the database and the fileIndicator only, from the dump line if it was linked. Otherwise unobservable.
Figure 3A record linker with synthetic records. Link a record, read the three tests, then take a store out of the acquisition and read the finding again. Source: store layouts and tolerances from LAB-TR-0003 and LAB-TR-0032 (synthetic example, invented values).

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].

Share of events with a single witness, by event typeHorizontal bars of the median share of events that had only one witnessing store, for eight event types. Screen on in bug reports 98 percent, connected call on Android 94, screen on in Android extractions 92, app first install on Android 89, text message on iOS 88, text message on Android 59, call on iOS 43, app last update on Android 30. Android bars are blue, iOS bars are grey.0%25%50%75%100%Screen on, bug report7 phones98%Connected call7 phones94%Screen on, extraction8 phones92%App first install14 phones89%Text message22 phones88%Text message10 phones59%Call28 phones43%App last update14 phones30%AndroidiOS
Show this figure as a table
Median share of events with a single witness, by event type
Event typePlatformPhonesEvents with one witness (median phone)
Screen on, bug reportAndroid798%
Connected callAndroid794%
Screen on, extractionAndroid892%
App first installAndroid1489%
Text messageiOS2288%
Text messageAndroid1059%
CalliOS2843%
App last updateAndroid1430%
Figure 4Share of events with a single witness, median over phones with at least five events of the type. A high bar means that losing one store would remove most findings of that type. Source: LAB-TR-0032, Table 4 (research collection; restarts are left out because only three phones had enough events).

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":

  1. Name the key that the stores share.
  2. State the clock relationship you used (equality, a tolerance or a window) and its size.
  3. List the witnesses and their writers.
  4. For every store without a record, say why it can be empty, or say that it is unexplained.
  5. 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

  1. On a phone you control, install an app and open its camera feature once. Write down the minute.
  2. Take a full file system extraction and a bug report from the same phone.
  3. For each store in Figure 3, find the record and note its key, its time and what the store keeps.
  4. Apply the three tests. Write down the tolerance or window you used, and why any store has no record.
  5. 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.

Claims and numbers, with the paper and section that hold them
Claim or numberPaperSection
A store has a writer; findings rest on the stores that record the event; stores can be lost to a reader[1] LAB-TR-00321 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-00324.1; 7 External
Tolerance of 300 s for restarts and installs; restart stores and install stores[1] LAB-TR-00324.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-00325.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-00325.2, Table 4
26 of 39 screen-on events held by three stores and two writers[1] LAB-TR-00325.2
52.5 percent single witness for iOS calls; 2.4 percent for a five-call narrative[1] LAB-TR-00323.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-00325.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-00325.4, Table 6
Days with no record observed by one store only: 79, 91 and 97 percent[1] LAB-TR-00325.5
Existence findings are monotone in the stores read; absence findings are not; unobservable versus absent[1] LAB-TR-00323.2; 3.3, Proposition 2
Ambiguous links at most 3.6 percent at the base tolerance[1] LAB-TR-00325.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-00032.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-00035.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-00036.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-0038Abstract; Table 2 (RQ2)
7 of 13 Android phones with messages held some dated before the first boot[4] LAB-TR-0028Abstract
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-0035Abstract; 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-0039Abstract; Table 2
Media tombstone table covered a median 39 percent of issued identifiers with no live row[7] LAB-TR-0030Abstract

Papers cited

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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 history
VersionDateChange
1.02026-11-06First publication.

Corrections log

No corrections have been published for this article. Corrections to any article are listed on Corrections.