Skip to content

12 · Timeline Analysis

Building a Super Timeline with Plaso

Build and filter a Windows super timeline with Plaso: log2timeline, psteal and psort flags, time zones, MACB, Timeline Explorer, EZ Tools CSVs and pivoting.

Published on Updated on 9 min read

A super timeline merges every timestamped artifact on a system (file-system metadata, registry keys, event logs, browser history, Prefetch, LNK files and more) into one chronological view. Instead of pivoting between a dozen tools, you read one ordered list and watch the incident unfold minute by minute. Plaso, the engine behind log2timeline.py, is the reference open-source tool for building one. This guide covers the commands and flags that matter, time zones, MACB, filtering, the Timeline Explorer workflow, lighter mini-timelines built from EZ Tools, and the mistakes that waste hours.

Why a timeline

Individual artifacts answer narrow questions. A timeline answers the big one: in what order did things happen? An attacker who logs on over RDP, drops a tool, runs it, creates a service and deletes the tool touches the Security log, the $MFT, the USN journal, Prefetch, the SYSTEM hive and the System log. Put side by side, those traces show causality and expose gaps, such as the ten minutes where the event log is empty because someone cleared it.

The Plaso toolchain

ToolRole
log2timeline.pyExtracts events from an image, device, directory or file into a .plaso storage file
psort.pyFilters, deduplicates and exports a storage file (CSV, JSON lines, XLSX, OpenSearch)
psteal.pyRuns extraction and export in one command
pinfo.pyShows what a storage file contains: parsers used, event counts, warnings
image_export.pyExtracts files from an image by path, extension or signature

The simplest install on Linux or macOS is the official Docker image (log2timeline/plaso). Pin the version you use and record it in your notes, because parser output and options change between releases.

Step 1: extract with log2timeline

# Full disk image, all partitions, current volume plus every shadow copy
log2timeline.py --storage-file HOST01.plaso \
  --partitions all --vss-stores all \
  /cases/HOST01/HOST01.E01

# A KAPE triage folder (much faster than a full image)
log2timeline.py --storage-file HOST01-triage.plaso /cases/HOST01/triage/C

# Same thing through Docker
docker run --rm -v /cases:/data log2timeline/plaso \
  log2timeline.py --storage-file /data/HOST01/HOST01-triage.plaso /data/HOST01/triage/C

Useful options:

  • --partitions all and --vss-stores all (or --no-vss) avoid interactive prompts on multi-partition images. Volume Shadow Copies often hold the older hives and logs an attacker cleaned, but they multiply processing time.
  • --parsers selects parsers or presets. Run log2timeline.py --info to list what your version supports. Long-standing presets include win7 (Windows 7 and later) and win_gen; do not assume a win10 preset exists unless --info shows it. You can mix presets, parsers and exclusions: --parsers "win7,!filestat".
  • --artifact-filters or -f/--file-filter restrict extraction to known paths (for example event logs and hives), which turns a full image into a triage-sized job.
  • --workers sets the number of worker processes; --hashers none skips file hashing when you do not need it.
  • --timezone tells Plaso how to interpret timestamps stored without a time zone indicator. Most Windows artifacts are already UTC, so only set it to the source system's zone when you know some sources record local time. log2timeline.py --timezone list prints valid names.

After extraction, check the result before trusting it:

pinfo.py HOST01.plaso

pinfo.py lists the parsers that produced events and any extraction warnings. A timeline with zero winevtx events from a Windows image means something went wrong, not that the logs were empty.

Step 2: filter and export with psort

# Export everything in the classic l2tcsv layout (Timeline Explorer reads it natively)
psort.py -o l2tcsv -w HOST01.csv HOST01.plaso

# The "dynamic" CSV lets you choose columns
psort.py -o dynamic --fields datetime,timestamp_desc,source,source_long,message,parser,display_name,tag \
  -w HOST01-dynamic.csv HOST01.plaso

# Only a time window (filter syntax as in recent Plaso releases)
psort.py -o l2tcsv -w window.csv HOST01.plaso \
  "date > DATETIME('2026-06-20T00:00:00') AND date < DATETIME('2026-06-22T00:00:00')"

# Only event logs and Prefetch, by data type
psort.py -o l2tcsv -w focused.csv HOST01.plaso \
  "data_type is 'windows:evtx:record' or data_type is 'windows:prefetch:execution'"

Event filter syntax has changed across Plaso releases (older versions compared date against plain strings). If a filter fails, check psort.py -h and the event filters page of the Plaso documentation for your version. psort.py -o list prints the available output modules; json_line and opensearch are useful for loading into other platforms. For Timesketch, upload the .plaso file itself through the Timesketch UI or its importer: psort's old Timesketch output module was removed in recent releases.

Deduplication and tagging

psort removes duplicate events by default, which matters when the same file appears in the live volume and in several shadow copies. Pass -a (--include_all) when you need every copy, for example to prove when a file disappeared between two snapshots.

psort can also run analysis plugins while exporting. The tagging plugin applies rules from a tagging file shipped with Plaso (tag_windows.txt in many releases) and labels events such as application execution, service installation or event log clearing. The labels appear in the tag column and give you a quick way to jump to interesting rows:

psort.py --analysis tagging --tagging-file tag_windows.txt \
  -o l2tcsv -w HOST01-tagged.csv HOST01.plaso

The tagging file name and format have changed over time; list the files in your installation's Plaso data directory before relying on this.

psteal for quick jobs

psteal.py --source /cases/HOST01/triage/C -o l2tcsv -w HOST01-quick.csv

psteal.py keeps the .plaso file too, so you can re-sort it later with different filters.

Time zones and UTC normalisation

Most Windows timestamps are FILETIME values in UTC: $MFT, registry last write times, event records, Prefetch run times. A few sources are local time, such as some text logs and certain application databases. Plaso stores everything in UTC and exports in UTC unless you pass --output-time-zone to psort.

Rules that keep timelines honest:

  • Confirm the system's time zone from SYSTEM\ControlSet00x\Control\TimeZoneInformation before interpreting anything.
  • Keep the timeline in UTC. Convert to local time only in the final report, and state which zone you used.
  • When adding external sources (firewall, EDR, cloud logs), confirm each one's zone and clock drift.
  • Remember daylight saving changes when an investigation spans March or October.

The UTC and local time entry lists the common traps.

Reading MACB

File-system events carry the four NTFS MACB timestamps:

LetterNTFS timestampTypical cause
MModifiedFile content written
AAccessedFile read (updates may be disabled or delayed depending on version and configuration)
CChanged ($MFT entry)Metadata change: rename, permissions, move within a volume
BBornFile created on this volume

In the l2tcsv output, the MACB column shows which timestamps share a given time: MACB on one row means all four are equal, a classic sign of a freshly created file. The timestamp_desc field in the dynamic output names the timestamp explicitly (for example "Creation Time" or "Last Access Time").

Each file has two sets of these: $STANDARD_INFORMATION and $FILE_NAME. Timestomping tools usually rewrite only $STANDARD_INFORMATION, so a $SI birth time years before the $FN birth time is a red flag. See $SI versus $FN and the NTFS forensics guide. Copying a file creates a new birth time but keeps the original modification time, so "modified before born" usually means copied, not forged.

Working in Timeline Explorer

Eric Zimmerman's Timeline Explorer opens large CSVs quickly and recognises the Plaso l2tcsv layout.

  1. Open the CSV. Let it finish loading; the status bar shows the row count.
  2. Use the date range filter first to cut to your window.
  3. Filter the Source or Source Description columns to the artifact families you care about.
  4. Search for your indicators (file name, user, IP) with the global search, then clear the search and tag relevant rows so you keep context around them.
  5. Group by a column (drag the header to the group area) to spot bursts, such as 200 file creations in one second.
  6. Save the session so tags and filters persist, and export tagged rows for the report.

Freeze the timestamp column and hide the ones you do not need. A timeline you can read beats a complete timeline you cannot.

Mini-timelines versus the full super timeline

A full super timeline on a large image can hold tens of millions of rows. Often a mini-timeline built from a few sources answers the question faster.

ApproachWhen to use itCost
Mini-timeline from EZ Tools CSVsYou know roughly what and when; triageMinutes
Plaso with limited parsers or file filtersSeveral artifact families, one hostTens of minutes
Full super timeline with VSSUnknown scope, subtle activity, court-boundHours

Combining EZ Tools output

The EZ Tools (run individually or through KAPE's !EZParser module) each produce CSVs with their own timestamp columns. For a quick file-system timeline, MFTECmd can write a bodyfile that The Sleuth Kit's mactime turns into a timeline:

# $MFT to CSV, and to a bodyfile for mactime
MFTECmd.exe -f D:\triage\C\$MFT --csv .\out --csvf mft.csv
MFTECmd.exe -f D:\triage\C\$MFT --body .\out --bodyf mft.body --bdl C --blf

# USN journal, resolving parent paths through the $MFT
MFTECmd.exe -f D:\triage\C\$Extend\$J -m D:\triage\C\$MFT --csv .\out --csvf usn.csv
mactime -b mft.body -z UTC -d > mft_timeline.csv

To merge several CSVs (MFT, EvtxECmd, PECmd timeline, LECmd), normalise them to three or four common columns (timestamp UTC, source, description, path) with a short PowerShell or Python script and concatenate. Timeline Explorer then sorts them together. Keep the original CSVs: the merged view is for navigation, the originals are your evidence.

Pivoting from a known-bad time

The most productive timeline work starts from an anchor: one event you know is malicious, with a reliable time.

  1. Pick the anchor: an EDR alert, a 4624 type 10 logon from an unknown IP, a service installation (7045), a known-bad file's $FN birth time.
  2. Slice around it. psort.py --slice 2026-06-21T14:07:00 --slice_size 30 exports only the events within 30 minutes of that time (check psort.py -h for the exact option names in your version).
  3. Read forward and backward. What happened just before (the delivery) and just after (execution, persistence, lateral movement)?
  4. Extract new indicators from what you find: file names, accounts, paths, IPs.
  5. Search the full timeline for each new indicator, which usually produces new anchors. Repeat.

The evidence of execution guide explains what each execution row means, and lateral movement forensics helps interpret the logon events that often connect hosts.

Performance tips

  • Process a triage collection rather than a full image when you can. It is often ten times faster or more.
  • Give Plaso fast local storage for both input and output; network shares are a common bottleneck.
  • Tune --workers to your CPU count and watch memory; each worker needs RAM.
  • Skip what you do not need: --hashers none, --no-vss for a first pass, excluded noisy parsers.
  • Run one storage file per host. Merging hosts at extraction time makes filtering harder.
  • Reuse the .plaso file: exporting different slices with psort is cheap compared with re-extracting.

Pitfalls

  • Wrong --timezone. Setting it to local time for sources that are already UTC shifts those events by the offset. Only use it for sources without zone information.
  • Trusting an unchecked run. Always run pinfo.py and compare event counts per parser with what you expect.
  • Drowning in rows. Exporting everything before you have an anchor leads to scrolling, not analysis.
  • Reading one timestamp as an action. A registry last write time says a key changed, not which value, and a ShimCache time is the file's modification time, not a run time.
  • Ignoring $SI and $FN differences and missing timestomping.
  • Forgetting shadow copies when the attacker cleaned logs or deleted files.
  • Old filter syntax copied from blog posts written for older Plaso versions.
  • Losing reproducibility. Record the Plaso version, full commands and filters in the case notes.

Workflow checklist

  • Confirm the source system's time zone and note it
  • Choose the approach: mini-timeline, limited Plaso, or full super timeline
  • Run log2timeline.py with explicit --partitions, --vss-stores and --parsers
  • Validate the storage file with pinfo.py
  • Export with psort.py (l2tcsv for Timeline Explorer), sliced around your anchor
  • Tag relevant rows, extract new indicators and pivot
  • Cross-check key events against the source artifacts and their cheat sheets
  • Record versions, commands, filters and time zone in the report

Timestamp semantics differ by source; check the cheat sheets for the $MFT, event logs, Prefetch, LNK files and ShimCache before drawing conclusions. If you are new to the field, start with getting started with Windows forensics.

Area 12

Timeline Analysis

The artifact cheat sheets behind this guide: location by Windows version, what each one proves and the tools to parse it.

Related articles