Blog / Security

  • forensics
  • timestamps
  • linux
  • filesystems
  • timeline-analysis
  • security

Why a Forensic Timeline Lies When File Timestamps Get Copied

A file that says it was modified in March 2019 can have arrived on the disk last Tuesday. If you build a timeline from the modified time alone, you will put an event in the past that never happened there. Copying preserves some timestamps and cannot preserve others, and the mismatch is both the trap and the clue.

The four timestamps people lump together

Forensic tools talk about MACB. On Linux with ext4 those map to four different things:

  • Modified: when the file's contents last changed (mtime).
  • Accessed: when it was last read (atime), subject to mount options.
  • Changed: when the inode's metadata last changed (ctime). Not "created", despite what the name suggests.
  • Birth: when this inode came into existence on this filesystem (btime, also called crtime).

Only two of these can be set from userspace: atime and mtime, via utimensat(). Anyone can do that to a file they own, which is what touch -d does. Ctime and birth time belong to the kernel and filesystem.

Watch it happen

Make a file, give it an old mtime, then copy it with and without -p:

echo hello > a
touch -d '2019-03-04 10:00:00' a
sleep 2
cp a plain
cp -p a preserved
stat -c '%n  mtime=%y  ctime=%z  birth=%w' a plain preserved

Roughly what you get (the dates in the second and third columns will be today's for you):

a          mtime=2019-03-04 10:00:00  ctime=2026-10-06 09:14:01  birth=2026-10-06 09:14:01
plain      mtime=2026-10-06 09:14:03  ctime=2026-10-06 09:14:03  birth=2026-10-06 09:14:03
preserved  mtime=2019-03-04 10:00:00  ctime=2026-10-06 09:14:03  birth=2026-10-06 09:14:03

Look at preserved. Its mtime says 2019. Its ctime and birth say a few seconds ago. Even the original a has a fresh ctime, because the touch that set the old mtime was itself a metadata change.

Why the plain copy is the honest one

Plain cp gives a new file whose mtime is the time you wrote the bytes. That is arguably correct: the contents of this inode were last modified now. The timeline is dull but not misleading.

The preserving tools are the problem:

  • cp -p and cp -a
  • rsync -t (included in -a)
  • tar x, which restores mtime from the archive by default
  • most unzip tools, and anything restoring a backup

They copy mtime and usually atime from the source, so the copy claims a history it did not have on this disk.

What that does to a timeline

Tools like Plaso or the Sleuth Kit's mactime sort events by timestamp and treat each as "something happened then". A restored backup of 40,000 files will scatter 40,000 modification events across years, with a single cluster of ctime and birth events at restore time.

An investigator who filters on "files modified in the week of the incident" will miss anything restored with preserved times. Worse, the reverse also bites: a malicious file extracted from an archive carries whatever mtime the attacker baked into it, and sits comfortably among legitimate old files.

The tell: ctime and birth disagree with mtime

Quick detour, because this is the neat bit. Under normal operation, a file's mtime cannot be later than its ctime, since changing contents also updates ctime. And a file's mtime is rarely earlier than its birth time, because you cannot modify a file before it exists.

So mtime earlier than birth time is the signature of a copy or extraction. Same for a modified time that predates a tidy cluster of identical ctimes across a directory. Check it directly:

find . -type f -printf '%B@ %T@ %p\n' | awk '$2 < $1 {print $3}'

Here %B@ is birth time and %T@ is mtime, both as epoch seconds. Any path printed was "modified" before it existed on this filesystem. If birth time is unavailable, GNU find prints a blank or zero there, so check your filesystem supports it first.

Caveats that will catch you out

  • Not proof of tampering. Downloads, package installs, backups and ordinary mv across filesystems all produce this pattern. It tells you the file was placed here, not why.
  • Ctime can be moved by the clock. If someone sets the system clock, ctime follows it. That needs root, and it leaves traces elsewhere (logs, journal gaps), but it is not impossible.
  • Renames touch ctime. A mv within one filesystem keeps mtime and birth but bumps ctime on Linux, so a recent ctime alone does not mean new content.
  • Atime is unreliable. With the default relatime, reads often do not update it, and noatime stops it entirely.
  • Precision differs. FAT stores mtime at two-second resolution, so copying onto a USB stick quietly rounds your timestamps. Cross-filesystem comparisons need a tolerance.

What to do about it

  • Record all four times for evidence files as soon as you can, with stat or statx, before anything touches them.
  • Work from a hashed image and mount it read-only with noatime, so your own examination does not create new timestamps.
  • Treat mtime as a claim and ctime as the harder-to-fake witness. When the two disagree, say so in the timeline rather than picking one.
  • Look for clusters. Thousands of files sharing a ctime to the second is a bulk operation, not thousands of events.

A timeline is only as honest as the least trustworthy timestamp in it. The useful habit is to ask of each one: who was able to set this, and who wasn't?