The challenge blends network forensics, malware analysis, and host-based forensics into one cohesive case, requiring investigators to pivot between Wireshark captures, registry hives, browser artifacts, cache data, event logs, and reverse engineering tools.
By the end, players will have fully reconstructed the attacker’s campaign — from first packet to persistence — and uncovered how a simple coffee-shop update became a full system compromise.
An attacker on a coffee-shop Wi-Fi network swept the subnet with ARP requests to find live hosts, then ARP-poisoned the gateway so the victim’s traffic passed through their machine. A spoofed DNS answer sent the victim’s browser to the attacker’s web server, and the victim downloaded and ran a fake update executable. The binary checked its privileges, hid its C2 details behind single-byte XOR, and probed a domain before connecting. A PowerShell script then disabled Defender real-time monitoring, and a registry value in the user’s hive made the payload run again. The network side came from the pcap, the host side from the registry, Chrome cache, $MFT and prefetch.
Artifacts/CafePoisoning.pcapng → tshark or Wireshark. Victim traffic with the attacker’s ARP, DNS and HTTP activityC\ tree holding the $MFT, registry hives (SOFTWARE, NTUSER.DAT), Chrome cache, event logs and prefetch → regipy, EvtxECmd, PECmd, stringsstrings, pefile, Capstone, objdump)Timestamp note: Q1 and Q2 need UTC with seconds.
tshark -t udprints local time, so pullframe.time_epochand convert withdate -u -d @<epoch>.
The victim joined the coffee-shop network, and the host remembers it. Wireless profiles live in the SOFTWARE hive under Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles, where ProfileName holds the SSID. The Wi-Fi artifacts on the same host show the network used WPA2 with a pre-shared key, which gives the authentication half of the answer. Join the two with a colon to match the format mask.
from regipy.registry import RegistryHive; [print(s.name, {v.name: v.value for v in s.iter_values()}) for s in RegistryHive('SOFTWARE').get_key('Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles').iter_subkeys()]
Dump every ARP frame with its epoch and sender, drop the link-local 169.254.x.x noise from the victim, and look for one MAC asking “who has” across consecutive addresses in the subnet. The first request of that sweep is the start of the scan. Convert the epoch to UTC with date -u.
tshark -r CafePoisoning.pcapng -Y arp -T fields -e frame.number -e frame.time_epoch -e arp.opcode -e arp.src.hw_mac -e arp.src.proto_ipv4 -e arp.dst.proto_ipv4 > arp.txt
date -u -d @1741640825.06 '+%Y-%m-%d %H:%M:%S'
Gotcha:
tshark -t udprints local time (+11 on the analysis box), which shifts the answer by hours. Use the epoch and convert it yourself.
The sweep MAC from Q1 keeps appearing in arp.txt, and shortly after the scan it starts sending ARP replies nobody asked for. Unsolicited opcode-2 replies at a steady cadence are the poisoning, and the first one marks the start.
awk '$3==2 && $4=="08:00:27:9b:8b:bd"' arp.txt | head
date -u -d @1741640853.825 '+%Y-%m-%d %H:%M:%S'
The source MAC on the unsolicited replies from Q2 is the attacker’s. It is also the sender of the Q1 sweep, which ties the scan and the poisoning to one machine.
awk '$3==2 {print $4}' arp.txt | sort | uniq -c | sort -rn
A poisoning reply claims someone else’s IP with the attacker’s MAC. The address the attacker impersonates in those replies is the gateway.
awk '$3==2 && $4=="08:00:27:9b:8b:bd" {print $5}' arp.txt | sort | uniq -c | sort -rn
With the victim’s traffic routed through the attacker, check the DNS responses the victim received. One name resolves to the attacker’s own address, the same host that ran the Q1 sweep, and the victim then sends an HTTP GET to it.
tshark -r CafePoisoning.pcapng -Y "dns.flags.response==1" -T fields -e frame.time_epoch -e dns.qry.name -e dns.a
The pcap only holds the spoofed answer, so the genuine visit has to come from the host. Search the extracted triage tree for the Q5 domain; the hit is Chrome’s cache data_1, which kept entries from before the poisoning. The strings around the match show the address of the real server.
grep -rla devx ex/C
strings -a data_1 | grep -i -B3 -A3 devx
List the victim’s HTTP requests. After the spoofed lookup in Q5 the GET goes to the attacker’s address and asks for an executable. The full URL is the host plus the request URI.
tshark -r CafePoisoning.pcapng -Y http.request -T fields -e frame.time_epoch -e http.request.full_uri
tshark --export-objects saved only about 1 KB of the download, so reassemble the TCP stream directly and write the raw bytes out as the sample. The strings and import table of the rebuilt PE show the API used to test for administrator membership.
tshark -r CafePoisoning.pcapng -q -z follow,tcp,raw,43 > stream.hex
strings -a mal.exe | grep -iE 'Token|Membership|Admin'
Gotcha: The object export looks like a complete download but only holds the start of the file. Follow the TCP stream instead.
The pcap has a busy external conversation on port 4000 that looks like a RAT beacon, but it predates the malware and is not its traffic. The real value is in the binary. Disassemble the code around getaddrinfo: a short host list is decoded with a single-byte XOR (key 0x58), and a separate getaddrinfo argument holds the port string. The decoded list has two IPs and the domain from Q10. The public IP is also in the list but is not the one that pairs with the port. The answer is the LAN address, which is the attacker host from Q1. This came from disassembly and decoded constants, not from packets.
objdump -d -M intel mal.exe | less
strings -a mal.exe | grep -E '^[0-9]{4,5}$'
Gotcha: The busiest external TCP conversation in the pcap is not the malware’s, and the second hardcoded public IP is also wrong. Read which host the code actually connects to.
A second encoded string in the same binary is a domain probed before the connection. Brute-forcing single-byte XOR keys across the file and searching for a TLD decodes it with key 0x5f.
d=open('mal.exe','rb').read(); [print(k, s) for k in range(1,256) for s in [bytes(b^k for b in d).decode('latin1')] if '.xyz' in s]
The PowerShell event logs (parsed with EvtxECmd) and a search of the pcap did not contain the command. A search of the extracted tree for update.ps1 turns up the script’s file record, and its content is small enough to be resident in the $MFT entry, so a grep -a for the Defender cmdlet reads it directly.
grep -rla update.ps1 ex
grep -a MpPreference 'ex/C/$MFT'
Tip: The command survives only in the resident
$MFTrecord. Event logs did not contain it.
The Run keys in SOFTWARE and NTUSER.DAT are clean, so list keys by last-write time in the window after the download. PECmd shows reg.exe running in that window, and NTUSER.DAT has SCRNSAVE.EXE under the desktop settings key pointing at a .scr in System32, with the key written at 21:15:47. That is screensaver persistence. This was reasoned from the write time and the reg.exe prefetch, not read from a command line; the key path is the answer.
PECmd -d pf --csv work/pf
from regipy.registry import RegistryHive; k=RegistryHive('NTUSER.DAT').get_key('Control Panel\\Desktop'); print(k.header.last_modified, {v.name: v.value for v in k.iter_values() if v.name=='SCRNSAVE.EXE'})
| Phase | Action |
|---|---|
| Discovery | ARP sweep of the subnet from 08:00:27:9b:8b:bd |
| Credential Access | Unsolicited ARP replies poison the gateway 192.168.1.90 |
| Credential Access | DNS spoofing points devx-corp[.]net at the attacker host |
| Execution | Victim downloads update.exe over HTTP and runs it |
| Command and Control | Binary XOR-decodes 192.168.1.11:5078 and probes s1rx-update[.]xyz |
| Defense Evasion | update.ps1 disables Defender real-time monitoring |
| Persistence | SCRNSAVE.EXE set under HKCU\Control Panel\Desktop |
| Type | Value |
|---|---|
| MAC (attacker) | 08:00:27:9b:8b:bd |
| IP (attacker / C2) | 192.168.1.11 |
| IP (gateway) | 192.168.1.90 |
| Domain (spoofed) | devx-corp[.]net |
| IP (legitimate devx-corp) | 137.50.21[.]6 |
| URL | hxxp[://]192[.]168[.]1[.]11/update[.]exe |
| C2 | 192.168.1.11:5078 |
| Domain (C2 check) | s1rx-update[.]xyz |
| Registry | HKCU\Control Panel\Desktop (SCRNSAVE.EXE = C:\Windows\System32\Screensaver.scr) |
| Wi-Fi | Cuppa Ce (WPA2-Personal) |
| Technique | ID | Description |
|---|---|---|
| Remote System Discovery | T1018 | ARP sweep of the subnet to find live hosts |
| Adversary-in-the-Middle: ARP Cache Poisoning | T1557.002 | Unsolicited ARP replies impersonating the gateway |
| Adversary-in-the-Middle | T1557 | DNS spoofing of the update domain |
| Ingress Tool Transfer | T1105 | update.exe served over HTTP from the attacker host |
| User Execution: Malicious File | T1204.002 | Victim ran the fake update |
| Application Layer Protocol | T1071 | C2 connection to the hardcoded host and port |
| Impair Defenses: Disable or Modify Tools | T1562.001 | Set-MpPreference turns off real-time monitoring |
| Event Triggered Execution: Screensaver | T1546.002 | SCRNSAVE.EXE set in the user’s desktop key |
Open Wi-Fi without client isolation lets any guest poison the segment. Enabling client isolation and dynamic ARP inspection would have blocked the unsolicited replies that started the attack. A host that sends many ARP requests across the subnet followed by unsolicited replies is a clean detection pattern.
A DNS answer that points an internet domain at a private address is a red flag. The victim’s resolver chain returned the attacker’s LAN address for a public domain. Alert on public names resolving to RFC1918 addresses, and prefer encrypted DNS on untrusted networks.
Executable downloads over plain HTTP from a local address should never be silent. The fake update arrived with no TLS and no signature check. Application allow-listing or SmartScreen-style reputation would have stopped an unsigned binary served from a LAN host.
Script content can survive only in the $MFT. The Defender-disable command never appeared in the event logs; it was resident in the file record for the script. Enable PowerShell script block logging and Defender tamper protection so the change is both blocked and logged.
Screensaver values are a quiet persistence spot. Nothing was in the Run keys. Monitor writes to SCRNSAVE.EXE and reg.exe executions, and compare against the usual autostart sweep.
The attacker turned a shared coffee-shop network into a full compromise with ARP poisoning, a spoofed domain and a fake update. Every step left evidence, but it was split across the pcap, the browser cache, the
$MFTand a user registry value, so only a combined network and host view tells the whole story.