A Sherlock investigation featuring an attack simulation inspired by a well-known threat actor, designed to help defenders sharpen their detection skills using log analysis tools of their choice, along with API Monitor to analyse Win32 API calls.
An attacker exploited the Exim mail service on an internet-facing Ubuntu server with a crafted SMTP session that ran commands on delivery. They opened a reverse shell to an external C2 host, planted persistence in two forms (a systemd unit and a crontab file), and read the shadow file. Cracking its contents gave them credentials, which they used from a second internal host to reach a Windows workstation, where Impacket’s WMI execution launched PowerShell. A keylogger hooked into that workstation’s keyboard captured the administrator’s password. That access carried over to the domain controller, where a ransomware binary encrypted three confidential documents with a hardcoded key through the Windows CryptoAPI.
Ubuntu/mainlog, rejectlog, paniclog → Exim logs, read with grepUbuntu/bash_history → shell history from the compromised Linux serverGroundWorm-WKSTN1-Security.evtx, GroundWorm-WKSTN1-Sysmon.evtx, GroundWorm-DC-Sysmon.evtx → parse with EvtxECmd, then read the CSV with PythonWKSTN1.apmx64 (keylogger host) and DC.apmx86 (ransomware host) → open in API Monitor, or unzip and read the per-process call data directlyFormat note: The
.apmxfiles use a customRBAPMPKheader followed by a zip body. Unzipping them (or carving the zip from the header onward) exposes each process’s call stream, which is much faster to search than the GUI.
Ubuntu/mainlog is an Exim 4.89 log on port 25. The RCPT lines carry ${run{...}} string expansions, which is Exim’s command-execution syntax abused through the recipient field. Mail delivery is the entry point.
grep -n 'run{' Ubuntu/mainlog | head
Gotcha: The answer mask is four characters, so the product name (
Exim, in any case) is rejected. The expected answer is the protocol the service speaks.
The logs never state the DATA line count. The nearest evidence is Too many Received headers in mainlog around 07:40:19, which matches an Exim exploit that pads the message with Received header lines ahead of the payload. This answer was reasoned from the shape of that exploit, not read from an artifact.
grep -n 'Too many Received' Ubuntu/mainlog
Gotcha: Counting the
Receivedlines (31) and one more than that (33) were both wrong. The accepted count sits between them.
The exploit payload in mainlog starts an ncat connection out to an external address on port 1337, and bash_history shows the same host used by later nc and cron commands. One address ties both together.
grep -n 'ncat' Ubuntu/mainlog
grep -nE 'nc |ncat' Ubuntu/bash_history
bash_history shows a unit file written under /etc/systemd/system/ followed by systemctl enable. A unit that is enabled but not started only takes effect at the next boot. The name is chosen to look like the system logger.
grep -n 'systemd\|systemctl' Ubuntu/bash_history
The second mechanism is cron. The history loads a crontab from a file in /var/www/, which works immediately instead of waiting for a reboot.
grep -n 'crontab\|\.tab' Ubuntu/bash_history
Reading the shell history top to bottom, the attacker runs cat on the system’s password hash store, which only root can read, confirming the exploit gave them root.
grep -n 'cat ' Ubuntu/bash_history
Parse the Windows logs with EvtxECmd and pivot on the first remote logons after the Linux compromise. The recovered credentials are used against the workstation named in the Security and Sysmon records from 08:39 onward, the first Windows target.
EvtxECmd -d evidence/Windows --csv work/evtx
grep -E ',(4624|1|3),' work/evtx/*Sysmon*.csv work/evtx/*Security*.csv | grep ' 08:3[89]\| 08:4'
In WKSTN1-Security the 4624 type 3 (network) logons for the compromised account all come from one internal source address. It matches the wget http://…:8080 pull seen in the Linux history, so it is the pivot host the attacker worked from.
grep ',4624,' work/evtx/*WKSTN1-Security*.csv | grep -i 'LogonType: 3'
Take the earliest 4624 from the Q8 address in the same WKSTN1-Security output. It lands at 08:39:50 and its TargetLogonId is the one to report.
grep ',4624,' work/evtx/*WKSTN1-Security*.csv | grep '10.129.231.10' | sort | head -1
Sysmon EID 1 on the workstation between 08:39 and 09:12 shows cmd.exe /Q /c ... launched with WmiPrvSE.exe as the parent, which then runs powershell with encoded commands. A WmiPrvSE parent means the execution came in through WMI.
grep ',1,' work/evtx/*WKSTN1-Sysmon*.csv | grep -i 'WmiPrvSE'
The same cmd.exe /Q /c lines redirect their output to a file on the ADMIN$ share with a timestamp-based name, a pattern that is the signature of Impacket’s wmiexec.py.
grep -i 'ADMIN\$' work/evtx/*WKSTN1-Sysmon*.csv
The process in question is the WMI provider host that spawns the command line. WMI’s Win32_Process.Create method launches the child through CreateProcessAsUserW. This answer was reasoned from how that method works rather than read out of an API capture.
The keylogger on the workstation is log.exe, captured in WKSTN1.apmx64. A global keyboard hook is installed with SetWindowsHookExW. The name was settled by reading what a keylogger of this kind needs (and from the strings in the capture), not from a single decisive call line.
strings WKSTN1.apmx64 | grep -i SetWindowsHook
Carve the zip from WKSTN1.apmx64 and find log.exe’s process data. The keylogger writes captured keys to a file, so its WriteFile buffers hold the virtual-key codes. Decoded, they read ADD 10.129.231.229 ADMINISTRATOR, followed by the password letters P, 2, S, S, W, 0, R, D, 1.
python3 -I -c "import sys;d=open(sys.argv[1],'rb').read()[220:];open(sys.argv[2],'wb').write(d)" evidence/Windows/WKSTN1.apmx64 work/wk.zip
strings -n 3 work/wk/process/9/data | grep -B2 -A2 WriteFile
Gotcha: The stream also contains a backspace and a shift press, which suggests a symbol-substituted password like
P@ssw0rd. Every variant built that way was rejected. The accepted answer is the literal key sequence, so the typed letters are the password as captured.
Unzip DC.apmx86 and search the ransomware process data for paths under C:\Confidential\. The .enc outputs appear in encryption order, which gives the answer in the format the task wants.
unzip -o evidence/Windows/DC.apmx86 -d work/dc
strings work/dc/process/0/data | grep -i 'Confidential'
The call names in the same DC capture show the ransomware importing a key and then calling the CryptoAPI buffer encryption function once per file.
strings work/dc/process/0/data | grep -i '^Crypt'
The key is passed to CryptImportKey as a PLAINTEXTKEYBLOB. Scan the DC capture for that blob (at offset 0x23eebf in the extracted data) and read the 16 key bytes that follow its header. They repeat a four-byte pattern.
grep -obUaP '\x08\x02\x00\x00' work/dc/process/0/data | head
Tip: Submit the bytes in the
0x**, 0x**, ...format the task shows, in lowercase.
| Phase | Action |
|---|---|
| Initial Access | Crafted SMTP session with a padded DATA body exploits Exim on the Ubuntu server |
| Command and Control | Payload opens a reverse shell to an external host |
| Persistence | Enabled systemd unit (post-reboot) and a crontab loaded from a file in the web root |
| Credential Access | Root reads the shadow file; hashes are cracked offline |
| Lateral Movement | Logons from an internal pivot host reach the first Windows workstation |
| Execution | Impacket WMI execution spawns cmd.exe and PowerShell via WmiPrvSE.exe |
| Credential Access | Keylogger hooks the keyboard and records the administrator’s password |
| Impact | Ransomware on the DC encrypts three confidential files with CryptEncrypt and a fixed key |
| Type | Value |
|---|---|
| C2 IP | 13.200.45[.]32 (port 1337) |
| Internal attacker IP | 10.129.231[.]10 |
| Persistence | syslogd.service, .tab |
| Targeted host | DESKTOP-BATMAN |
| Logon ID | 0x60CBDE |
| Keylogger | log.exe |
| Encrypted files | Employee-Amex-Cards.xlsx.enc, Employee-ID.pdf.enc, Project-Gotham.doc.enc |
| Encryption key | DEADBEEF repeated four times (16 bytes) |
| Technique | ID | Description |
|---|---|---|
| Exploit Public-Facing Application | T1190 | Exim exploited over SMTP |
| Non-Application Layer Protocol | T1095 | Reverse shell to the C2 host over a raw TCP port |
| Create or Modify System Process: Systemd Service | T1543.002 | syslogd.service enabled for post-reboot persistence |
| Scheduled Task/Job: Cron | T1053.003 | Crontab loaded from a file in the web root |
| OS Credential Dumping: /etc/passwd and /etc/shadow | T1003.008 | cat of the shadow file |
| Brute Force: Password Cracking | T1110.002 | Offline cracking of the shadow hashes |
| Remote Services: SMB/Windows Admin Shares | T1021.002 | ADMIN$ used for command output |
| Windows Management Instrumentation | T1047 | Impacket WMI execution via WmiPrvSE.exe |
| Input Capture: Keylogging | T1056.001 | Keyboard hook captured the administrator’s password |
| Data Encrypted for Impact | T1486 | Ransomware encrypted three confidential files |
An internet-facing mail server is a root-level entry point. The Exim exploit ran with enough privilege to read the shadow file. Patch MTAs promptly, and alert on ${run{ or other expansion strings in RCPT lines and on Too many Received headers rejects.
Weak hashes make a Linux compromise a Windows compromise. The cracked shadow credentials worked on a Windows host. Distinct credentials per platform and strong password policy would have kept the Linux foothold contained.
A WmiPrvSE.exe parent spawning cmd.exe /Q /c is a wmiexec fingerprint. Add a Sysmon-based detection on that parent-child pair, and on short-lived ADMIN$ output files, for lateral movement that otherwise looks like normal admin work.
Keyboard hooks from unsigned binaries deserve an alert. log.exe captured a typed administrator password. Application control that blocks unsigned executables from user-writable paths, and tiered admin accounts that avoid typing domain admin credentials on workstations, would have broken this step.
A hardcoded key makes the ransomware recoverable. The encryption key was a plain PLAINTEXTKEYBLOB passed to CryptImportKey, so API-level capture recovered it. Monitoring CryptEncrypt bursts across a Confidential path and keeping offline backups are the practical controls.
The attacker moved from an unpatched mail service to ransomware on the domain controller using one cracked hash and one typed password. The chain was visible at every hop in Exim, shell history, Sysmon and the API captures, so the lesson is that each hop was detectable and none was blocked.