A bit about timestomping

A bit about timestomping
heh, bit, you'll get it if you make it to the bottom.

I have a question for everyone: which of the following two files, test1.txt or test2.txt, has been timestomped? One of them has, and one of them hasn’t — I’m not trying to play that kind of trick.

I mean, it's pretty obvious right?

If you guessed test1.txt, you’re wrong. Here’s how I prepared these:

Compressing and decompressing a file isn't out of the ordinary

In the case of test1.zip, the Change and Birth times are when it was decompressed. The Accessed and Modified times are the original times, rounded to the nearest second. The A and M times get rounded because the original ZIP specification used the MS-DOS time format, which can only record even-numbered seconds — it stores seconds in 2-second increments rather than exact values. If you’re thinking that doesn’t quite add up since the seconds field is an odd number here, there’s more on that at the bottom — but I’m trying not to get sidetracked.

The reason I bring this up is that there’s a long-held belief among some in forensics that a rounded seconds field is a clear indication of timestomping. The reasoning I’ve heard is that tools cannot write to the milliseconds field. That can be the case — older variants of the touch command only write with second-level precision. That clearly isn’t the case for all methods though, and natural actions may lead to legitimate files lacking sub-second precision.

The older 'touch' method. The format here is YYYYMMDDhhmm.ss

Now, you might have noticed that none of these have modified the Change or Birth times. Alright, here’s the next one: which of these two has been timestomped? Again, one of them has, one hasn’t.

Everything looks normal here

I mean, flip a coin — you really can’t tell, not based on the information here. In this case it’s test1.txt that has been timestomped, all four fields including Birth. It was created with the command: timedatectl set-ntp 0 && sudo date -s "2025-10-26 03:54:56.547553" && sleep 1 && > test1.txt && sudo timedatectl set-ntp 1 , (turn NTP off, set a time, create a file, and turn it back on). The sleep just introduces a bit of jitter. You might think it would be really noticeable that the entire system time changed, but it’s very brief, and many tools and alerts are only looking for the more well-known touch-based method.

One more — no quiz this time.

evil_bash kind of gives it away

Here we can once again see some of the timestamp fields rounded to the nearest second; there’s no sub-second precision in the Modified field. Both have Record Change and Birth times after the Modify and Access times. There isn’t an immediate giveaway.

In this case, the following command was used to copy the timestamps from a legitimate file onto an existing one: cp --attributes-only --preserve=mode,ownership,timestamps /bin/bash /bin/evil_bash (Check the file size to note that it didn’t copy the file data.) I noticed something interesting when I created this example — many system files have a Modified date rounded to the nearest second. My guess is that part of the packaging process involves compressing them, which creates rounded seconds when they’re unpacked during installation or updates. If anyone knows for sure, leave a comment.

This is far from every method someone could use to modify timestamps, and there are plenty of articles out there on additional methods and how to detect them. The point I want to drive home here is that perfectly natural events in a file’s lifecycle can make it look timestomped — and there are timestomping methods that avoid the “key indicators” people often rely on. It isn’t a straightforward exercise, and there are no hard-and-fast rules. I hate timestamps more than any other forensic artifact because they’re so inconsistent and so many things can change or modify a given field.

Extras - $FN > $SI

Richard Davis of 13Cubed reminded me of another timestomping myth I should dispel. The "indicator" goes that if the $FN timestamps on a Windows file are greater than the $SI timestamps, it's an indication that the file has been timestomped. Here's what it looks like if I use (Get-Item $file).CreationTimeUtc to set the Creation date on a file. I also changed LastWriteTimeUtc and LastAccessTimeUtc.

That sub-second thing is really not paying off

Note that our sub-second precision indicator has once again gone right out the window. I've highlighted the stomped $SI times in red (2020), compared to the actual $FN timestamps in green that weren't changed. Well that's pretty obvious, in order to disguise our malware we'll have to get really creative to hide that delta. Something complicated, like renaming the file...

Well that was too easy

That's it. I literally just renamed the file from original.txt to new.txt, and I did it via the command line to avoid updating the Accessed date (which is a trash timestamp anyway). When you rename or move a file, Windows updates the $FN timestamps based on the (falsified) $SI records. So yea, don't count on that one either.

Extras - Zip Oddities

Regarding the odd (odd number, that is — not strange) timestamp from the ZIP file: I admit, I was surprised by this myself, only because I expected the seconds field to be truncated down and to read 02:41:24.000000000. It turns out modern versions of zip store one-second precision in a “UT extra” field. You can see this with zipinfo:

This is a different file than the top example, but the output shows the same behavior.

This output is even more interesting, because the DOS seconds should be floored — not rounded. So the original value, 02:52:17.006579221, for the DOS timestamp should have been stored as 02:52:16 once compressed. A minor difference, sure, but not what I was expecting. I can only assume that modern versions of the Linux zip utilities are rounding instead of truncating.

Here’s the expected behavior on Windows. The top left has the original file with sub-second precision (yellow). The top right is Windows’ built-in ZIP utility results (Right-click → Send to → Compressed (zipped) folder). We can see that the red highlighted times weren't rounded up to 34 or down to 33 — they were floored to the next lowest even-second value, 32.

The bottom left shows 7-Zip’s Add to archive → zip behavior, which does track sub-seconds, as do .7z archives in the bottom right. A side effect of this is that it causes one of the $SI fields to occur before the associated $FN field — which is another “indicator” of timestomping dispelled above. It's pretty close here, but imagine if something had been archived months or years ago.

The top two are what I was expecting

Let me emphasize again the inconsistencies with timestamps: two different utilities, both ostensibly using the same “ZIP” standard, produce different results. Comparing 7-Zip’s ZIP utility and Windows’ built-in ZIP utility, the biggest difference I noticed is that the “version of encoding software” has been bumped from 2.0 to 6.3.

I'm not sure if this is purely a 7z thing, or if anything using v 6.3 would have sub-seconds

Extras - MS-DOS Timestamps

Ok, I went down a rabbit hole to learn about the 2 second accuracy, and by God if I'm going to waste a Sunday afternoon, you are going to learn from it. Here's a directory table from MS-DOS 6.22. If you can't quite tell some of the colors apart, don't worry, the important ones are the bright colors. The fields are, in order with size (bytes): Name (8), Extension (3), Attributes (1), Reserved (10), Time (2), Date (2), startCluster (2), fileSize (4). Time is the one in yellow, date in the light blue.

Unfortunately the ability to display seconds wasn't available in MS-DOS

These bytes are in little-endian order, so 06 83 -> 83 06 -> 10000011 00000110

We've gone all the way down, lets go back up. Bits 0-4 are the seconds, 5-10 are the minutes, and 11-15 are the hours. Remember to read right to left.

10000  011000 00110
│      │      │
│      │      └──── seconds
│      └───── minutes
└───────── hours

Let's focus on the seconds. Binary math is easy. Again, you go from right to left. If the value is a 1, then you take 2 to the power of that position. If it's a 0, it's 0; below I show the powers position regardless of the bit value just so you can see how it counts up, don't let that confuse you. Then you add them all up.

00110
││││└─── 2^0 = 0 <- If the bit is 0, we just assign a 0 to that position
│││└──── 2^1 = 2
││└───── 2^2 = 4
│└────── 2^3 = 0
└─────── 2^4 = 0

That's 6, so 6 seconds right? Not so fast. You'll notice that the maximum value you can have here is 31. If all bits are flipped to 1 then you would have:
24 + 23 +22 + 21 + 20 = 16 + 8 + 4 + 2 + 1 = 31
You're only halfway to the 60 seconds in a minute. The value here isn't the number of seconds, not directly, it's the number of 2 second ticks. So you multiply that number by 2 to get to the seconds, or in this case, 12 seconds past the minute.

Going back to our timestamps from earlier, lets use the Windows Zip times for clarity, we went from 10:02:33.772682800 to 10:02:32.000000000. So to store the time value, we go floor(33 / 2) = 16 -> 16 gets stored in the seconds field. When we read the time back we take 16 * 2 to get the seconds, or 32. That's the reason it's always "rounded down" to the nearest even number. You'll always end up even when multiplying by 2.

For completeness, the minutes (011000) would be 16+8 or 24. The hours (10000) would be 16, or 4 PM. Those times you can check on the screenshot. Unfortunately, the ability to view seconds doesn't come until later versions of Microsoft operating systems. Note that the hours field goes to a maximum of 31, like the seconds, those aren't all valid, but it's enough to cover 24 hours and have some left over. The minutes have an extra bit and go to 25, or 63 total, again more than enough to cover the individual minutes.

If you want extra credit, in the date field bits 0-4 are the day, 5-8 are the month, and 9-15 are years since 1980.

Resources