Timestamp normalisation for Linux forensic analysis. The stat-offset-is-the-analyst’s-zone trap, identifying the host’s zone from a mounted image, UTC normalisation math, reporting conventions. Forensic timestamps are meaningless without an explicit zone.
Side: blue
The analyst-zone trap
ext4 stores UTC seconds in the inode. stat projects them through the analyst workstation’s TZ environment variable or /etc/localtime — not the image’s. The offset shown on a stat line tells you the analyst’s zone, not the host’s.
# WRONG: assume Modify offset reflects the image's host
stat /mnt/image/some/file
# Modify: 2026-04-15 17:07:07 -0400 <-- this is the ANALYST'S TZ
# RIGHT: take TZ out of the equation
TZ=UTC stat /mnt/image/some/file
# Modify: 2026-04-15 21:07:07 +0000 <-- now it's UTC, regardless of analyst zoneFor an entire investigation:
export TZ=UTC # set in the working shell
TZ=UTC ls -la --full-time /mnt/image/etc/ # one-shot per commandSet TZ=UTC in ~/.bashrc for the duration of a case to make this default behaviour.
Identify the host’s zone (from a mounted image)
ls -l /mnt/image/etc/localtime # symlink target reveals zone name (e.g. /usr/share/zoneinfo/Pacific/Honolulu)
cat /mnt/image/etc/timezone # plain text on Debian-familyUTC normalisation math
UTC = local - offset
local = UTC + offset
Offset is what the timestamp displays after the time portion: -0400 → offset = -4 hours; +1000 → offset = +10 hours.
Worked example. Image timestamp:
Modify: 2026-04-15 17:07:07 -0400
| Step | Operation |
|---|---|
| 1. Normalise to UTC | 17:07:07 - (-4 hr) = 17:07:07 + 4 hr = 21:07:07 UTC |
| 2. Find host’s zone | cat /mnt/image/etc/timezone → host’s zone identifier |
| 3. Project UTC into host’s zone | UTC + host_offset = host_local_time |
| 4. Report | 2026-04-15 21:07:07 UTC (host-local in parentheses if useful) |
Sanity check
Going UTC back to the analyst’s zone via stored UTC seconds should round-trip to the original stat output. If the projected 21:07:07 UTC does not reproduce 17:07:07 -0400 on the analyst’s -0400 workstation, an arithmetic error has been made.
Reporting conventions
| Where | Format |
|---|---|
| Timeline / cross-host narrative | UTC only, e.g. 2026-04-15 21:07:07 UTC |
| Raw evidence rows (literal tool output) | literal tool output, including offset: Modify: 2026-04-15 17:07:07 -0400 |
| Per-finding interpretation prose | UTC primary, host-local parenthesised when readability matters |
Never put bare local times in narrative without offset or zone identification. 17:07:07 alone is meaningless across hosts.
NTP and clock drift
Before relying on absolute timestamp correlation across hosts, check the writing host’s clock health.
journalctl -t ntpd | grep -iE 'step|adjust|sync' # large clock adjustments
journalctl --list-boots # detect long uptime / missing bootsIndicators of unreliable clock:
ntpdlog entries showing large step adjustments- Gaps or backward jumps in monotonic timestamp sequences
- Discrepancies between log line clock and adjacent file mtimes
Hosts that ran without NTP for the incident window have unreliable absolute timestamps. Correlate by relative ordering (event A before event B) within that host; anchor absolute time using other hosts.
Pitfalls
statoffset is the analyst’s zone, not the host’s. Always setTZ=UTCto neutralise.- Mixing local times across hosts in narrative: “alert fired at 14:30, exfil started at 03:15” with no zones is unreadable. UTC primary, always.
- Forgetting DST transitions: a host’s offset on 2 April differs from 2 May. The host’s tzdata applies the rule.
- FAT / exFAT store no offset. Interpret only with knowledge of the source host’s zone — never assume UTC.
- journalctl on an offline image projects timestamps via the analyst’s
TZ. SetTZ=UTCbeforejournalctl --root=/mnt/image.
links:
Field Manual | Timezones in Forensics | Linux Forensics | Filesystem Metadata | Log Triage