// HackTheBox  ·  writeup

CafePoisoning

HackTheBox Medium tsharkregipystringspefileCapstoneEvtxECmdPECmd
DFIR

Scenario

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.

Executive Summary

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

Timestamp note: Q1 and Q2 need UTC with seconds. tshark -t ud prints local time, so pull frame.time_epoch and convert with date -u -d @<epoch>.

Initial Access

Q7 — Identify the Wi-Fi network name (SSID) and the authentication algorithm used by the compromised user’s connection.

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()]
Click flag to reveal Cuppa Ce:WPA2-Personal

Discovery

Q1 — The attacker performed a host discovery scan to identify devices on the network. Provide the start time of this activity in UTC.

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 ud prints local time (+11 on the analysis box), which shifts the answer by hours. Use the epoch and convert it yourself.

Click flag to reveal 2025-03-10 21:07:05

Credential Access

Q2 — The attacker launched an ARP poisoning attack. Provide the start time in UTC

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'
Click to reveal answer 2025-03-10 21:07:33
Q3 — What MAC address did the attacker use during the ARP poisoning attack?

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
Click flag to reveal 08:00:27:9b:8b:bd
Q4 — What is the gateway IP address of the network?

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
Click to reveal answer 192.168.1.90
Q5 — Which spoofed domain was accessed by the compromised user?

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
Click flag to reveal devx-corp.net
Q6 — Before the attack the victim accessed this domain's legitimate web server. What was its IP address?

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
Click to reveal answer 137.50.21.6

Execution

Q8 — Identify the download link used to fetch the malicious executable.

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
Click to reveal answer http://192.168.1.11/update.exe
Q11 — The malicious executable verifies privileges before execution to ensure it runs as administrator. Which Win32 API function is used for this check?

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.

Click flag to reveal CheckTokenMembership()

Command and Control

Q9 — Identify the IP address and port number of the Command-and-Control (C2) server.

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.

Click flag to reveal 192.168.1.11:5078
Q10 — The malicious executable is designed to check the C2 server before connecting. Provide the domain name of the C2 server.

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]
Click to reveal answer s1rx-update.xyz

Defense Evasion

Q12 — Which command was executed by the attacker to disable Windows Defender?

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 $MFT record. Event logs did not contain it.

Click to reveal answer Set-MpPreference -DisableRealtimeMonitoring $true -Verbose

Persistence

Q13 — A persistence mechanism was created by the attacker. Provide the registry key used for persistence.

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'})
Click flag to reveal HKCU\Control Panel\Desktop

Attack Summary

PhaseAction
DiscoveryARP sweep of the subnet from 08:00:27:9b:8b:bd
Credential AccessUnsolicited ARP replies poison the gateway 192.168.1.90
Credential AccessDNS spoofing points devx-corp[.]net at the attacker host
ExecutionVictim downloads update.exe over HTTP and runs it
Command and ControlBinary XOR-decodes 192.168.1.11:5078 and probes s1rx-update[.]xyz
Defense Evasionupdate.ps1 disables Defender real-time monitoring
PersistenceSCRNSAVE.EXE set under HKCU\Control Panel\Desktop

IOCs

TypeValue
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
URLhxxp[://]192[.]168[.]1[.]11/update[.]exe
C2192.168.1.11:5078
Domain (C2 check)s1rx-update[.]xyz
RegistryHKCU\Control Panel\Desktop (SCRNSAVE.EXE = C:\Windows\System32\Screensaver.scr)
Wi-FiCuppa Ce (WPA2-Personal)

MITRE ATT&CK

TechniqueIDDescription
Remote System DiscoveryT1018ARP sweep of the subnet to find live hosts
Adversary-in-the-Middle: ARP Cache PoisoningT1557.002Unsolicited ARP replies impersonating the gateway
Adversary-in-the-MiddleT1557DNS spoofing of the update domain
Ingress Tool TransferT1105update.exe served over HTTP from the attacker host
User Execution: Malicious FileT1204.002Victim ran the fake update
Application Layer ProtocolT1071C2 connection to the hardcoded host and port
Impair Defenses: Disable or Modify ToolsT1562.001Set-MpPreference turns off real-time monitoring
Event Triggered Execution: ScreensaverT1546.002SCRNSAVE.EXE set in the user’s desktop key

Defender Takeaways

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.

Conclusion

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 $MFT and a user registry value, so only a combined network and host view tells the whole story.