Timestomping

What it is

Timestomping rewrites a file’s timestamps so it looks older, newer, or contemporary with other files. It defeats timeline analysis, which is the first thing an examiner reaches for.

Unlike named-stream and slack-space hiding, it stores nothing. It overwrites an existing field with a chosen value.

# Linux (ext4): set mtime and atime
touch -d '2019-01-01T00:00:00Z' report.txt

# macOS
SetFile -m 01/01/2019 00:00:00 report.txt   # from the Xcode command-line tools

# Windows (PowerShell)
(Get-Item report.txt).LastWriteTime = '2019-01-01'

The MACB timestamps

An examiner rebuilds “what happened when” from four timestamps, remembered as MACB:

Letter Meaning NTFS ext4 APFS
M content last modified $SI and $FN MtimeMs i_mtime mod_time
A content last accessed $SI and $FN AtimeMs i_atime access_time
C metadata last changed $SI and $FN CtimeMs (MFT change) i_ctime change_time (ctime)
B born, or created $SI and $FN CrtimeMs i_crtime create_time

Each filesystem stores these differently, at different precision, in different structures. Some fields cannot be set through any normal API. That mismatch is what makes timestomping detectable.

Detection by a third party

Timestomping is one of the more detectable techniques, because the same information exists in more than one place.

Why nemo’s detect reports nothing for timestomp

timestompTechnique.Detect (internal/technique/technique.go) confirms the entry implements the capability, then returns nil, nil.

A current timestamp cannot show whether it was altered. Nemo has no historical baseline and no timestamp anomaly detector. It does not cross-check independent records, such as NTFS $SI against $FN. So it cannot report a finding from the current value alone.

hide does not read the original timestamp and save it to a manifest. So nemo clear --technique timestomp needs the original timestamp passed in, and it rejects a zero value. The shared reader would make an automatic backup possible, but the technique and manifest workflow do not do it.

Implementation Notes

On-disk mechanics

NTFS: the $SI and $FN pair

Every MFT record carries the four timestamps twice.

All eight values are Windows FILETIME: a 64-bit count of 100-nanosecond intervals since 1601-01-01 UTC.1

What an examiner checks:

ext4: split second and nanosecond fields

The 128-byte “good old” inode holds i_atime, i_mtime, and i_ctime as 32-bit signed seconds since 1970. Larger inodes, typically 256 bytes, add two things in the extra area past i_extra_isize:

internal/filesystem/ext4/timestomp.go implements exactly this.

Offsets from the code:

i_atime            8
i_mtime           16
i_extra_isize    128   (16-bit; xattr magic 0xEA020000 may follow)
i_mtime_extra    136
i_atime_extra    140
i_crtime         144
i_crtime_extra   148

What an examiner checks: no userland API can set i_ctime. The kernel stamps it with the current time on every inode change, including the write that stomps i_mtime. So an mtime far in the past with a ctime of a few seconds ago is the ext4 timestomp signature.

Nemo can write i_ctime, because it edits the inode bytes directly in an image. A live attacker on a mounted filesystem cannot.

APFS: nanosecond fields in the inode record

APFS stores the four timestamps in the j_inode_val_t inode record in the filesystem B-tree. Each is a 64-bit count of nanoseconds since 1970-01-01 UTC (create_time, mod_time, change_time, access_time).2 The precision is higher than NTFS or ext4, so a whole-second value is an even stronger sign that a human typed it.

APFS also keeps copies of the dates that a timestomp does not reach:

In nemo

filesystem.TimestompCapable exposes the current value and field support, as well as the write:

type TimestompCapable interface {
    Timestamp(field TimeField) (time.Time, error)
    SupportsTimestamp(field TimeField) (bool, error)
    SetTimestamp(field TimeField, t time.Time) error
}

timestompTechnique (internal/technique/technique.go) works like this:

The changed field

changed (filesystem.TimeChanged) maps to i_ctime on ext4 and change_time on APFS. The kernel sets this MACB field on its own, and no live userland API lets a process write it.

Editing the image directly bypasses that. So hide --field changed sets exactly what a live attacker cannot. It closes the “old mtime, fresh ctime” gap that normally gives a stomp away.

Per filesystem

A timestomp still leaves independent copies of the old dates behind. On APFS, the directory entry’s own date_added (j_drec_val_t), Spotlight metadata, and local snapshots all keep the value from before the stomp.

macOS live mode

The live path is in internal/filesystem/apfs/live_darwin.go.

See APFS live mode.

Further reading

References

  1. Microsoft, [MS-FSCC]: File System Control Codes, §2.4.7 and §2.4.4. ↩

  2. Apple, Apple File System Reference, j_inode_val_t. ↩