digital forensics

Analyzing Windows Prefetch with Python

Parse Windows Prefetch files using the windowsprefetch Python library to reconstruct application execution history, detect renamed or masquerading binaries, and identify suspicious program execution patterns.

digital-forensicsexecution-historyincident-responsemalware-analysisprefetchwindows
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

Windows Prefetch files (.pf) record application execution data including executable names, run counts, timestamps, loaded DLLs, and accessed directories. This skill covers parsing Prefetch files using the windowsprefetch Python library to reconstruct execution timelines, detect renamed or masquerading binaries by comparing executable names with loaded resources, and identifying suspicious programs that may indicate malware execution or lateral movement.

When to Use

  • When investigating security incidents that require analyzing windows prefetch with python
  • When building detection rules or threat hunting queries for this domain
  • When SOC analysts need structured procedures for this analysis type
  • When validating security monitoring coverage for related attack techniques

Prerequisites

  • Python 3.9+ with windowsprefetch library (pip install windowsprefetch)
  • Windows Prefetch files from C:\Windows\Prefetch\ (versions 17-30 supported)
  • Understanding of Windows Prefetch file naming conventions (EXECUTABLE-HASH.pf)

Steps

Step 1: Collect Prefetch Files

Gather .pf files from target system's C:\Windows\Prefetch\ directory.

Step 2: Parse Execution History

Extract executable name, run count, last execution timestamps, and volume information.

Step 3: Detect Suspicious Execution

Flag known attack tools (mimikatz, psexec, etc.), renamed binaries, and unusual execution patterns.

Step 4: Build Execution Timeline

Reconstruct chronological execution timeline from all Prefetch files.

Expected Output

JSON report with execution history, suspicious executables, renamed binary indicators, and timeline reconstruction.

Example Output

$ python3 prefetch_analyzer.py --dir /evidence/Windows/Prefetch --output /analysis/prefetch_report
 
Windows Prefetch Analyzer v2.1
================================
Source: /evidence/Windows/Prefetch/
Prefetch Format: Windows 10 (MAM compressed, version 30)
Files Found: 234
 
--- Execution Timeline (Incident Window: 2024-01-15 to 2024-01-18) ---
Last Executed (UTC)     | Run Count | Filename                    | Hash     | Path
------------------------|-----------|-----------------------------|----------|------------------------------------------
2024-01-15 14:33:15     | 1         | Q4_REPORT.XLSM-2A1B3C4D.pf | 2A1B3C4D | C:\Users\jsmith\Downloads\Q4_Report.xlsm
2024-01-15 14:35:44     | 1         | POWERSHELL.EXE-A2B3C4D5.pf  | A2B3C4D5 | C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
2024-01-15 14:36:30     | 3         | UPDATE_CLIENT.EXE-B3C4D5E6.pf| B3C4D5E6| C:\ProgramData\Updates\update_client.exe
2024-01-15 15:10:22     | 1         | NETSCAN.EXE-C4D5E6F7.pf     | C4D5E6F7 | C:\Users\jsmith\Downloads\netscan.exe
2024-01-16 02:28:00     | 1         | PROCDUMP64.EXE-D5E6F7A8.pf  | D5E6F7A8 | C:\Windows\Temp\procdump64.exe
2024-01-16 02:30:15     | 2         | MIMIKATZ.EXE-E6F7A8B9.pf    | E6F7A8B9 | C:\Windows\Temp\mimikatz.exe
2024-01-16 02:40:00     | 4         | PSEXEC.EXE-F7A8B9C0.pf      | F7A8B9C0 | C:\Users\jsmith\AppData\Local\Temp\psexec.exe
2024-01-17 02:45:00     | 1         | SDELETE64.EXE-A8B9C0D1.pf   | A8B9C0D1 | C:\Windows\Temp\sdelete64.exe
2024-01-18 03:00:45     | 1         | WEVTUTIL.EXE-B9C0D1E2.pf    | B9C0D1E2 | C:\Windows\System32\wevtutil.exe
 
--- Renamed Binary Detection ---
ALERT: UPDATE_CLIENT.EXE loaded DLLs consistent with Cobalt Strike beacon:
  Referenced DLLs: wininet.dll, ws2_32.dll, advapi32.dll, dnsapi.dll, netapi32.dll
  Volume: \VOLUME{01d94f2a3b5c7d8e-A4E73F21} (C:)
  Directories referenced:
    C:\ProgramData\Updates\
    C:\Windows\System32\
 
--- Execution Frequency Analysis ---
Most Executed (Top 5):
  1. SVCHOST.EXE          (267 runs)
  2. CHROME.EXE           (189 runs)
  3. EXPLORER.EXE         (156 runs)
  4. RUNTIMEBROKER.EXE    (134 runs)
  5. OUTLOOK.EXE          (98 runs)
 
First-Time Executions (Never seen before incident window):
  6 executables first run between 2024-01-15 and 2024-01-18
 
Summary:
  Total prefetch files:         234
  Suspicious executables:       6
  Renamed binary indicators:    1 (update_client.exe)
  Anti-forensics tools:         2 (sdelete64.exe, wevtutil.exe)
  JSON report: /analysis/prefetch_report/prefetch_timeline.json
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 1

api-reference.md1.7 KB

API Reference: Analyzing Windows Prefetch with Python

windowsprefetch Library

import windowsprefetch
 
pf = windowsprefetch.Prefetch("CMD.EXE-1234ABCD.pf")
print(pf.executableName)  # CMD.EXE
print(pf.runCount)        # 42
print(pf.lastRunTime)     # 2025-01-15 10:30:22
print(pf.timestamps)      # List of up to 8 execution times
print(pf.resources)       # List of loaded files/DLLs
print(pf.volumes)         # Volume info (name, serial, creation)

Install: pip install windowsprefetch

Prefetch File Versions

Version Windows Max Timestamps
17 XP/2003 1
23 Vista/7 1
26 8/8.1 8
30 10/11 8 (compressed)

File Naming Convention

Format: EXECUTABLE-XXXXXXXX.pf

  • EXECUTABLE: uppercase executable name
  • XXXXXXXX: hash of file path (allows multiple entries per executable)

Suspicious Executables to Flag

Category Examples
Credential tools mimikatz, rubeus, lazagne, secretsdump
Lateral movement psexec, psexesvc, wmiexec
C2 agents beacon, meterpreter, covenant, empire
LOLBins certutil, mshta, regsvr32, rundll32, bitsadmin
Recon sharphound, bloodhound, nmap

Prefetch Directory Location

C:\Windows\Prefetch\

Requires admin privileges to read. Enable via:

reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters"

References

Scripts 1

agent.py7.0 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for analyzing Windows Prefetch files with Python.

Parses Prefetch (.pf) files to reconstruct execution history,
detect renamed/masquerading binaries, and identify suspicious
tool execution using the windowsprefetch library.
"""

import argparse
import hashlib
import json
import os
from datetime import datetime
from pathlib import Path

try:
    import windowsprefetch
except ImportError:
    windowsprefetch = None

SUSPICIOUS_EXECUTABLES = {
    "mimikatz", "psexec", "psexesvc", "procdump", "lazagne",
    "rubeus", "sharphound", "bloodhound", "cobalt", "beacon",
    "meterpreter", "powersploit", "empire", "covenant",
    "secretsdump", "wce", "fgdump", "pwdump", "gsecdump",
    "certutil", "bitsadmin", "mshta", "regsvr32", "rundll32",
    "wscript", "cscript", "msiexec", "installutil",
}

LOLBINS = {
    "certutil.exe", "bitsadmin.exe", "mshta.exe", "regsvr32.exe",
    "rundll32.exe", "wscript.exe", "cscript.exe", "msiexec.exe",
    "installutil.exe", "regasm.exe", "regsvcs.exe", "msconfig.exe",
    "esentutl.exe", "expand.exe", "extrac32.exe", "findstr.exe",
    "hh.exe", "ie4uinit.exe", "makecab.exe", "replace.exe",
}


class PrefetchAnalyzer:
    """Analyzes Windows Prefetch files for forensic investigation."""

    def __init__(self, output_dir="./prefetch_analysis"):
        self.output_dir = Path(output_dir)
        self.output_dir.mkdir(parents=True, exist_ok=True)
        self.findings = []
        self.executions = []

    def parse_prefetch_file(self, pf_path):
        """Parse a single Prefetch file and extract execution data."""
        if windowsprefetch is None:
            raise RuntimeError("windowsprefetch not installed: pip install windowsprefetch")
        try:
            pf = windowsprefetch.Prefetch(pf_path)
        except Exception:
            return None

        timestamps = []
        if hasattr(pf, "lastRunTime"):
            timestamps.append(str(pf.lastRunTime))
        if hasattr(pf, "timestamps"):
            timestamps.extend([str(t) for t in pf.timestamps])

        resources = []
        if hasattr(pf, "resources"):
            resources = pf.resources if isinstance(pf.resources, list) else []
        elif hasattr(pf, "filenames"):
            resources = pf.filenames if isinstance(pf.filenames, list) else []

        volumes = []
        if hasattr(pf, "volumes"):
            for v in pf.volumes:
                volumes.append({
                    "name": getattr(v, "name", str(v)),
                    "serial": getattr(v, "serialNumber", ""),
                })

        entry = {
            "file": str(pf_path),
            "executable": pf.executableName if hasattr(pf, "executableName") else Path(pf_path).stem,
            "run_count": pf.runCount if hasattr(pf, "runCount") else 0,
            "last_run_time": timestamps[0] if timestamps else "",
            "all_timestamps": timestamps,
            "pf_hash": Path(pf_path).stem.split("-")[-1] if "-" in Path(pf_path).stem else "",
            "resources_count": len(resources),
            "volumes": volumes,
            "file_size": os.path.getsize(pf_path),
            "file_sha256": self._hash_file(pf_path),
        }
        self.executions.append(entry)
        return entry

    def _hash_file(self, path):
        h = hashlib.sha256()
        with open(path, "rb") as f:
            for chunk in iter(lambda: f.read(8192), b""):
                h.update(chunk)
        return h.hexdigest()

    def parse_directory(self, prefetch_dir):
        """Parse all .pf files in a directory."""
        pf_dir = Path(prefetch_dir)
        pf_files = sorted(pf_dir.glob("*.pf"), key=lambda p: p.stat().st_mtime, reverse=True)
        for pf_file in pf_files:
            self.parse_prefetch_file(str(pf_file))
        return len(pf_files)

    def detect_suspicious(self):
        """Flag known attack tools and LOLBins."""
        for entry in self.executions:
            exe = entry["executable"].lower()
            exe_base = exe.replace(".exe", "")
            if exe_base in SUSPICIOUS_EXECUTABLES:
                self.findings.append({
                    "severity": "critical", "type": "Attack Tool Executed",
                    "detail": f"{entry['executable']} run {entry['run_count']} times, "
                              f"last: {entry['last_run_time']}",
                })
            elif exe in LOLBINS:
                if entry["run_count"] > 10:
                    self.findings.append({
                        "severity": "medium", "type": "LOLBin High Usage",
                        "detail": f"{entry['executable']} run {entry['run_count']} times",
                    })

    def detect_renamed_binaries(self):
        """Detect potential binary renaming/masquerading."""
        for entry in self.executions:
            exe = entry["executable"].upper()
            pf_name = Path(entry["file"]).stem.upper()
            expected_prefix = exe.replace(".EXE", "")
            if not pf_name.startswith(expected_prefix):
                self.findings.append({
                    "severity": "high", "type": "Possible Renamed Binary",
                    "detail": f"PF name '{pf_name}' does not match executable '{exe}'",
                })

    def build_timeline(self):
        """Build chronological execution timeline."""
        timeline = []
        for entry in self.executions:
            for ts in entry["all_timestamps"]:
                if ts:
                    timeline.append({
                        "timestamp": ts,
                        "executable": entry["executable"],
                        "run_count": entry["run_count"],
                    })
        timeline.sort(key=lambda x: x["timestamp"], reverse=True)
        return timeline[:100]

    def generate_report(self, prefetch_dir):
        count = self.parse_directory(prefetch_dir)
        self.detect_suspicious()
        self.detect_renamed_binaries()
        timeline = self.build_timeline()

        report = {
            "report_date": datetime.utcnow().isoformat(),
            "prefetch_dir": str(prefetch_dir),
            "total_prefetch_files": count,
            "total_unique_executables": len(self.executions),
            "execution_history": self.executions,
            "execution_timeline": timeline[:50],
            "findings": self.findings,
            "total_findings": len(self.findings),
        }
        out = self.output_dir / "prefetch_analysis_report.json"
        with open(out, "w") as f:
            json.dump(report, f, indent=2, default=str)
        print(json.dumps(report, indent=2, default=str))
        return report


def main():
    parser = argparse.ArgumentParser(
        description="Analyze Windows Prefetch files for execution forensics"
    )
    parser.add_argument("prefetch_dir", help="Path to directory containing .pf files")
    parser.add_argument("--output-dir", default="./prefetch_analysis")
    args = parser.parse_args()

    os.makedirs(args.output_dir, exist_ok=True)
    analyzer = PrefetchAnalyzer(output_dir=args.output_dir)
    analyzer.generate_report(args.prefetch_dir)


if __name__ == "__main__":
    main()
Keep exploring