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/localtimenot 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 zone

For an entire investigation:

export TZ=UTC                                # set in the working shell
TZ=UTC ls -la --full-time /mnt/image/etc/    # one-shot per command

Set 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-family

UTC 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
StepOperation
1. Normalise to UTC17:07:07 - (-4 hr) = 17:07:07 + 4 hr = 21:07:07 UTC
2. Find host’s zonecat /mnt/image/etc/timezone → host’s zone identifier
3. Project UTC into host’s zoneUTC + host_offset = host_local_time
4. Report2026-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

WhereFormat
Timeline / cross-host narrativeUTC 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 proseUTC 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 boots

Indicators of unreliable clock:

  • ntpd log 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

  • stat offset is the analyst’s zone, not the host’s. Always set TZ=UTC to 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. Set TZ=UTC before journalctl --root=/mnt/image.

Field Manual | Timezones in Forensics | Linux Forensics | Filesystem Metadata | Log Triage