Back to all posts

Prefetch is one of those artifacts that gets called "proof of execution" so often that people forget to ask what it actually proves. It proves that Windows started a process running an executable with a given name and path hash. That is enough to close a lot of cases. It is not enough to close all of them, and the gap is where I have watched analysts make claims they could not defend.

This post is the version of "what does the Prefetch tell us" I would write for somebody who is about to walk into a contested investigation.

What Prefetch is, in plain terms

Windows watches the first ten seconds of every newly-started process. It records which files the process touches, which DLLs it loads, and how memory pages were faulted in. That trace gets compressed and stored as a .pf file in C:\Windows\Prefetch\. The next time the same executable starts, Windows reads the .pf file to pre-load the pages it expects to need, which is where the name comes from.

That is the operational purpose. The forensic value is incidental: because Windows needs to know what to pre-load, it has to remember the executable ran. So Prefetch keeps a per-binary record of the last eight executions, the total number of executions, the binary's resolved path, and a list of files the binary loaded.

The filename format is <EXECNAME>-<HASH>.pf, where the hash is computed from the full path (and on Win10+, sometimes other context) so that the same binary run from two different locations gets two different .pf files. This matters: an attacker who copies psexec.exe to C:\Users\victim\AppData\Local\Temp\ produces a different Prefetch entry from a legitimate psexec.exe in C:\Program Files\. You can tell the two apart at a glance.

What it actually proves

A .pf file proves that an executable with the embedded name was started — meaning a process was created and ran far enough to be observed by the Prefetch service. That is a strong claim. Stronger than Shimcache, stronger than the AmCache, and stronger than the USN journal on its own.

What it does not prove:

  • It does not prove the file at the path still on disk is the file that ran. Prefetch records the executable's path, not its hash. If evil.exe was deleted and a benign evil.exe later dropped in its place, the .pf references "the executable at that path" — which is now a different file.
  • It does not prove a human launched it. Windows starts processes for all sorts of automated reasons. A .pf file for cmd.exe means cmd started; it does not mean a user typed anything into it.
  • It does not prove what the binary did once it ran. You get the load list, not the command line. For arguments you need Sysmon EID 1 or Security 4688.
  • It does not prove execution in a specific user context. Prefetch is per-machine. The "who" question needs a different artifact.

If you take only one thing away: Prefetch is execution evidence at the binary level, not at the user, command, or behavior level.

The Win10/11 quirks that bite people

For years the line was "Windows keeps the last eight execution times". On Win10 1709+ that became the last eight as a maximum, but routinely fewer — Windows started aggressively expiring older entries during maintenance windows. If you read a .pf and only find three execution timestamps, do not conclude the binary only ran three times. It ran at least three times.

On Win11 22H2 and newer, the Prefetch on SSDs is partially disabled by the OS under some conditions. Microsoft calls this an optimization. Forensically, it is a hole. On SSD-backed Win11 hosts I have seen Prefetch coverage drop to maybe 60% of executions. Always check HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters\EnablePrefetcher and EnableSuperfetch on the host before assuming Prefetch is complete. The default value 3 means both are on; 0 means off.

Servers are worse. Prefetch is disabled by default on Windows Server. Many estates have a GPO that turns it on, many do not. If you are doing DFIR on a server and there is no Prefetch directory or the directory is empty, that may simply be the default — not adversary tampering.

The MAM format (Win10+ compressed Prefetch) is incompatible with older offline parsers. Eric Zimmerman's PECmd handles it correctly; many of the older Python parsers do not, and silently produce wrong results. I have seen reports built on prefetchparser.py output from a Win10 host that were structurally wrong because the parser silently skipped MAM-compressed files.

Reading the timestamps without getting fooled

Each .pf file carries up to eight execution times and one "last run" time. The format is FILETIME (100-nanosecond intervals since 1601 UTC), and the times are the times Windows started monitoring the process, not the times the binary was on disk. That distinction matters in one specific case.

Last run minus Created (of the .pf file itself, from NTFS) is roughly the lifetime of the entry. If Last run is recent but the .pf file was created two years ago, the binary has been running on this host for two years. That alone can rule out the "first time we have ever seen this tool" hypothesis. Pair it with the MFT for the binary's own dates.

Where I see analysts get burned: comparing a .pf "Last run" to a 4688 "Process create" elsewhere on the host and treating them as the same time. They are close but not identical, and the offset varies across builds and CPU loads. Within a few seconds is normal. If you see a one-minute gap, you are looking at the same execution. If you see an hour, you are looking at two different runs.

The file load list, and what it is good for

Beyond the execution counter, every .pf carries a list of files the executable opened in its first ten seconds. Up to ~1000 entries on a normal binary, all stored as paths Windows resolved at the time. This is the part most people skim past.

The load list is where I look when I need to answer "what did this process touch". Three patterns I look for routinely:

  • DLL search-order hijacking evidence. A trusted binary loading a DLL from \AppData\Local\Temp\ instead of \System32\ shows up as a path in the load list. Sort by path prefix.
  • Configuration files for malware. A lot of commodity malware loads its config from a fixed filename next to the binary. \AppData\Roaming\<tool>\config.bin and similar paths are dead giveaways.
  • Document opens. If winword.exe has \Users\bob\Documents\offer-letter.docx in its load list and you are investigating a phishing claim, that is corroboration that the document was opened on this host. Pair with the LNK files and jump lists for the user-level context.

The load list does not include files written, only files read (in the broad sense that Windows traces page faults on them). To see writes you need Sysmon EID 11.

Anti-forensics and what holds up

Attackers can and do delete .pf files. Three things to know:

  1. Deleting the .pf file does not erase the trace from the MFT. A Prefetch deletion leaves a FileDelete | Close USN record and an MFT entry that lingers until reused. If the directory looks suspiciously bare, parse the USN journal.
  2. Disabling Prefetcher mid-investigation is easy and detectable. A registry value change in PrefetchParameters updates the SYSTEM hive's LastWrite. Compare against VSC snapshots.
  3. The .pf files of binaries the attacker brought with them are often left behind. This is the gift that keeps giving. Attackers will scrub the obvious places — the binary itself, the staging directory — and forget Prefetch. I have closed cases where the only surviving evidence of a renamed mimikatz was the MIMI-xxxxxx.pf file that was never cleaned up.

A workflow I actually run

Order of operations on a host where Prefetch matters:

  1. Acquire the whole C:\Windows\Prefetch\ directory. Preserve file system timestamps; do not copy with anything that resets them.
  2. List by NTFS Created time, descending. Anything created during the suspected window is on the short list.
  3. Parse to a flat table: filename, executable name, executable path, run count, last 8 run times.
  4. Sort by run count ascending. Single-run binaries with paths in user-writable locations are interesting. Many-run binaries living in \System32\ are normal.
  5. For each interesting row, pull the load list and look for the patterns above.
  6. Corroborate with Sysmon / Security EVTX by matching the executable path and the run timestamps. This is where the Prefetch claim becomes a defensible execution claim.

Step 4 is what I find myself coaching analysts on the most. Counter-intuitively, the binaries that ran once are usually more interesting than the ones that ran a hundred times.

Where Prefetch fits in the bigger artifact picture

It is one tool of several, and the strongest claims combine it with others. Prefetch puts a name on the list. AmCache puts a hash next to it. Shimcache says Windows looked at the file at some point. The MFT places the file in time and space. The USN journal narrates what happened to it. Sysmon and Security tell you who, when, and with what arguments.

Treat Prefetch as the linchpin for "the binary ran", and you will use it correctly. Treat it as the answer to "what happened on this host", and you will be wrong.

Reading .pf files in the browser

The parser on this site decodes Win10/11 MAM-compressed Prefetch as well as the legacy SCCA format, in your browser, with no upload. Drop a .pf (or the whole directory) in and you get the executable name, run count, the eight execution times and the file load list in a sortable table. Useful when the host's Prefetch contents are sensitive enough that you would rather not hand them to a third-party service.

Further reading

If the Prefetch service was disabled, none of the above will save you. Check first.