digital forensics

Building Super Timelines with Plaso

Generate log2timeline and Plaso super-timelines and triage them in Timesketch.

dfirdigital-forensicsincident-responselog2timelineplasosuper-timelinetimeline-analysistimesketch
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Authorized Use Only: Build timelines only from evidence you are authorized to analyze. Work from forensic images/copies and preserve chain of custody.

Overview

Plaso (Plaso Langar Að Safna Öllu) is the open-source engine behind log2timeline, the standard for building forensic super timelines — a single chronological, normalized view fusing hundreds of artifact types (file-system MACB times, registry, EVTX, browser history, prefetch, LNK, $UsnJrnl, syslog, and more) into one timeline. Plaso has three core CLI tools:

  • log2timeline.py — extracts events from a source (disk image, mount point, directory, or device) into a .plaso storage file using its large parser/plugin set.
  • pinfo.py — reports on the contents and processing metadata of a .plaso file.
  • psort.py — post-processes, filters, deduplicates, time-zones, and exports the storage file to an output format (CSV, JSON-line, Elasticsearch, Timesketch, etc.).
  • psteal.py — convenience wrapper that runs extraction + export in one step.

The resulting timeline is enormous, so analysts triage it in Timesketch — a collaborative, web-based timeline analysis platform that ingests .plaso files (or CSV/JSONL) and supports filtering, tagging, starring, saved searches, and automated analyzers.

When to Use

  • Reconstructing the full sequence of events on a compromised host during incident response.
  • Correlating activity across many artifact sources on a single normalized timeline.
  • Investigating anti-forensic behavior such as timestomping or log clearing (which stands out against MACB and journal evidence).
  • Feeding a curated timeline into Timesketch for team triage.

Prerequisites

  • Install Plaso (Docker is the supported, reproducible method):
    docker pull log2timeline/plaso
    # Run a tool, mounting your evidence/output directory
    docker run -v /cases:/data log2timeline/plaso log2timeline.py --version
    Alternatively on Ubuntu via the GIFT PPA:
    sudo add-apt-repository ppa:gift/stable
    sudo apt-get update && sudo apt-get install -y plaso-tools
  • A Timesketch instance (docker-compose deployment from https://github.com/google/timesketch) for triage.
  • A forensic image (E01/raw) or mounted file system.

Objectives

  • Extract events from an image into a .plaso storage file.
  • Inspect the storage file with pinfo.
  • Filter and export a focused super timeline with psort.
  • Import the timeline into Timesketch and triage it.

MITRE ATT&CK Mapping

ID Official Technique Name Relevance to this skill
T1070 Indicator Removal Super timelines reveal indicator-removal behavior (log clearing, file deletion, timestomping) by exposing inconsistencies between MACB timestamps, the USN journal, and event logs.

Plaso is a defensive forensics engine; the mapping reflects the anti-forensic adversary behavior super timelines are well suited to detect.

Workflow

1. Extract events into a storage file

log2timeline.py writes a .plaso file from a source. --storage-file names the output; the source can be an .E01, raw image, mount point, or directory.

log2timeline.py --storage-file timeline.plaso /cases/greendale/image.E01

Scope parsers for speed/relevance with --parsers (presets like win7, webhist, or explicit parser names):

log2timeline.py --parsers "win7,!filestat" --storage-file timeline.plaso /cases/image.E01

2. Inspect the storage file

pinfo.py reports source, parsers used, event counts, and any warnings.

pinfo.py timeline.plaso

3. Export a filtered super timeline (CSV)

psort.py selects an output module with -o, writes with -w, normalizes the timezone with --output-time-zone, and accepts an event filter expression to scope a date range.

psort.py --output-time-zone 'UTC' \
  -o l2tcsv \
  -w supertimeline.csv \
  timeline.plaso \
  "date > datetime('2026-01-01T00:00:00') AND date < datetime('2026-01-27T00:00:00')"

For Timesketch-friendly JSON lines, use the json_line output module:

psort.py --output-time-zone 'UTC' -o json_line -w supertimeline.jsonl timeline.plaso

4. One-step extraction + export with psteal

psteal.py runs extraction and CSV export together for quick triage.

psteal.py --source /cases/greendale/image.E01 -o l2tcsv -w supertimeline.csv

5. Import into Timesketch

Use the official timesketch_importer CLI to upload the .plaso (or CSV/JSONL) into a sketch. Timesketch chunks/reassembles and indexes the file.

timesketch_importer \
  --host http://127.0.0.1:5000 \
  --username admin \
  --timeline_name "greendale-host01" \
  --sketch_id 1 \
  timeline.plaso

6. Triage in Timesketch

In the sketch UI:

  • Filter to a suspicious window or data_type (e.g. windows:evtx:record, fs:stat).
  • Star/tag events of interest and add comments for collaboration.
  • Save searches and run analyzers (e.g. browser timeframe, similarity, sigma) over the timeline.
  • Build a narrative from corroborating events across artifact sources.

7. Hunt for anti-forensics

Look for MACB timestamps that disagree with $UsnJrnl entries (timestomping), gaps or EventLog cleared (1102) records, and deleted-then-recreated files — all visible on the unified timeline.

Tools and Resources

Resource Purpose Link
Plaso (log2timeline) Timeline engine + tools https://github.com/log2timeline/plaso
Plaso documentation Tool usage and parsers https://plaso.readthedocs.io/
Timesketch Timeline analysis platform https://github.com/google/timesketch
Timesketch docs Deployment, importer, analyzers https://timesketch.org/
Plaso Docker image Reproducible runtime https://hub.docker.com/r/log2timeline/plaso

Key Commands

Command Purpose
log2timeline.py --storage-file out.plaso <source> Extract events
log2timeline.py --parsers <preset> ... Scope parsers
pinfo.py out.plaso Inspect storage file
psort.py -o l2tcsv -w out.csv out.plaso "<filter>" Filter + export CSV
psort.py -o json_line -w out.jsonl out.plaso Export JSONL
psteal.py --source <img> -o l2tcsv -w out.csv Extract + export in one step
timesketch_importer --host ... <file> Import into Timesketch

Validation Criteria

  • .plaso storage file produced from the source image
  • pinfo confirms expected parsers ran and event counts are non-zero
  • Super timeline exported with UTC normalization and a scoped filter
  • Timeline imported into a Timesketch sketch and indexed
  • Suspicious window triaged with tags/stars/saved searches
  • Anti-forensic indicators (timestomping, log clearing) checked
  • Findings documented with corroborating cross-source events
Source materials

References and resources

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

References 2

api-reference.md2.2 KB

Plaso / log2timeline Command Reference

Plaso ships four CLI tools. Run them directly or via the Docker image (log2timeline/plaso).

log2timeline.py (extraction)

Flag Purpose
--storage-file <file> Output .plaso storage file
<source> Source: .E01, raw image, mount point, directory, device
--parsers <list> Restrict parsers (presets win7, webhist, etc.; !name excludes)
--partitions <spec> Select partitions (e.g. all)
--vss-stores <spec> Process Volume Shadow Copies
--hashers <list> Compute file hashes (e.g. sha256)
-z <tz> Source timezone
--workers <n> Number of extraction workers
log2timeline.py --storage-file timeline.plaso /cases/image.E01
log2timeline.py --parsers "win7,!filestat" --storage-file timeline.plaso /cases/image.E01

pinfo.py (inspect)

pinfo.py timeline.plaso          # summary
pinfo.py -v timeline.plaso       # verbose

psort.py (post-process / export)

Flag Purpose
-o <module> Output module: l2tcsv, json_line, dynamic, elastic, timesketch
-w <file> Write output to file
--output-time-zone <tz> Normalize output timezone (e.g. UTC)
<storage> The .plaso file
"<filter>" Event filter expression (trailing argument)
psort.py --output-time-zone 'UTC' -o l2tcsv -w supertimeline.csv timeline.plaso \
  "date > datetime('2026-01-01T00:00:00') AND date < datetime('2026-01-27T00:00:00')"
psort.py --output-time-zone 'UTC' -o json_line -w supertimeline.jsonl timeline.plaso

psteal.py (extract + export wrapper)

psteal.py --source /cases/image.E01 -o l2tcsv -w supertimeline.csv

Common event filter fields

Field Example
date date > datetime('2026-01-01T00:00:00')
data_type data_type == 'windows:evtx:record'
parser parser contains 'winreg'
timestamp_desc timestamp_desc contains 'Creation'

Timesketch import

timesketch_importer \
  --host http://127.0.0.1:5000 \
  --username admin \
  --timeline_name "host01" \
  --sketch_id 1 \
  timeline.plaso

timesketch_importer accepts .plaso, .csv, and .jsonl inputs.

standards.md1.0 KB

Standards and Framework Mapping — Building Super Timelines with Plaso

NIST Cybersecurity Framework 2.0

ID Name Rationale
RS.AN-03 Analysis is performed to establish what has taken place during an incident and the root cause of the incident Plaso super timelines fuse all host artifacts into one chronological view, the primary method for reconstructing the sequence and root cause of an incident.

MITRE ATT&CK

ID Name Rationale
T1070 Indicator Removal Unified timelines expose anti-forensic actions (log clearing, file deletion, timestomping) via contradictions between MACB times, the USN journal, and event logs.

Supporting References

Scripts 1

agent.py4.5 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
Plaso super-timeline helper.

Builds and runs validated log2timeline.py / psort.py / psteal.py commands and
optionally imports the result into Timesketch with timesketch_importer. Supports
running the tools natively or through the official Docker image.

Reference: https://plaso.readthedocs.io/
"""
import argparse
import shutil
import subprocess
import sys
from pathlib import Path


def base_cmd(tool, use_docker, data_dir):
    """Return the argv prefix to invoke a plaso tool, native or via Docker."""
    if use_docker:
        # Mount data_dir at /data inside the container
        return ["docker", "run", "--rm", "-v", f"{data_dir}:/data",
                "log2timeline/plaso", tool]
    found = shutil.which(tool)
    if not found:
        print(f"[!] {tool} not on PATH; consider --docker", file=sys.stderr)
    return [tool]


def map_path(p, use_docker):
    """When using Docker, rewrite host paths under data_dir to /data/..."""
    # The caller is expected to pass paths relative to data_dir for Docker.
    return p


def extract(args):
    cmd = base_cmd("log2timeline.py", args.docker, args.data_dir)
    cmd += ["--storage-file", args.storage]
    if args.parsers:
        cmd += ["--parsers", args.parsers]
    if args.vss:
        cmd += ["--vss-stores", "all"]
    if args.hashers:
        cmd += ["--hashers", args.hashers]
    cmd.append(args.source)
    return run(cmd, args.dry_run)


def info(args):
    cmd = base_cmd("pinfo.py", args.docker, args.data_dir)
    cmd.append(args.storage)
    return run(cmd, args.dry_run)


def export(args):
    cmd = base_cmd("psort.py", args.docker, args.data_dir)
    cmd += ["--output-time-zone", args.tz, "-o", args.output_module,
            "-w", args.output, args.storage]
    if args.filter:
        cmd.append(args.filter)
    return run(cmd, args.dry_run)


def psteal(args):
    cmd = base_cmd("psteal.py", args.docker, args.data_dir)
    cmd += ["--source", args.source, "-o", args.output_module, "-w", args.output]
    return run(cmd, args.dry_run)


def tsimport(args):
    tool = shutil.which("timesketch_importer") or "timesketch_importer"
    cmd = [tool, "--host", args.host, "--username", args.username,
           "--timeline_name", args.timeline_name,
           "--sketch_id", str(args.sketch_id), args.file]
    return run(cmd, args.dry_run)


def run(cmd, dry_run):
    print("[*] " + " ".join(f'"{c}"' if " " in c else c for c in cmd))
    if dry_run:
        return 0
    try:
        return subprocess.run(cmd, check=False).returncode
    except FileNotFoundError:
        print(f"[!] command not found: {cmd[0]}", file=sys.stderr)
        return 127


def main():
    p = argparse.ArgumentParser(description="Plaso super-timeline helper")
    p.add_argument("--docker", action="store_true",
                   help="Run plaso tools via the log2timeline/plaso image")
    p.add_argument("--data-dir", default="/cases",
                   help="Host dir mounted at /data when using --docker")
    p.add_argument("--dry-run", action="store_true")
    sub = p.add_subparsers(dest="cmd", required=True)

    e = sub.add_parser("extract", help="log2timeline.py")
    e.add_argument("--source", required=True)
    e.add_argument("--storage", required=True)
    e.add_argument("--parsers")
    e.add_argument("--vss", action="store_true")
    e.add_argument("--hashers")
    e.set_defaults(func=extract)

    i = sub.add_parser("info", help="pinfo.py")
    i.add_argument("--storage", required=True)
    i.set_defaults(func=info)

    x = sub.add_parser("export", help="psort.py")
    x.add_argument("--storage", required=True)
    x.add_argument("--output", required=True)
    x.add_argument("--output-module", default="l2tcsv")
    x.add_argument("--tz", default="UTC")
    x.add_argument("--filter")
    x.set_defaults(func=export)

    s = sub.add_parser("psteal", help="psteal.py (extract+export)")
    s.add_argument("--source", required=True)
    s.add_argument("--output", required=True)
    s.add_argument("--output-module", default="l2tcsv")
    s.set_defaults(func=psteal)

    t = sub.add_parser("import", help="timesketch_importer")
    t.add_argument("--host", required=True)
    t.add_argument("--username", default="admin")
    t.add_argument("--timeline-name", dest="timeline_name", required=True)
    t.add_argument("--sketch-id", dest="sketch_id", type=int, required=True)
    t.add_argument("--file", required=True)
    t.set_defaults(func=tsimport)

    args = p.parse_args()
    return args.func(args)


if __name__ == "__main__":
    sys.exit(main())
Keep exploring