Skip to content

Latest commit

 

History

26 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 

Repository files navigation

Virtualized SOC & Malware Analysis Home Lab

A two-stage security operations lab built on VMware, simulating real-world attack chains (brute force, credential dumping, lateral movement) against a deliberately vulnerable Windows Server, with both Splunk and Wazuh deployed as SIEMs over the same telemetry. The lab is the source of every detection, dashboard, and IR artifact in the writeup.

Goal: Build a realistic, isolated environment to practice log analysis, detection engineering, and incident response without burning a cloud account — and to validate the question "would I actually catch a real adversary on this stack?" against myself.


Table of contents


Architecture

The lab is dual-homed to balance two competing requirements: enough isolation to safely run live malware and brute-force traffic against the local home network, and enough internet access to pull updates, packages, and threat intel feeds when needed.

flowchart TB
    subgraph Internet["Internet"]
        direction LR
        Updates["Updates, threat feeds,<br/>Wazuh/Splunk downloads"]
    end

    subgraph Host["Host Machine (macOS)"]
        direction LR
        HostNet["Host network<br/>192.168.86.0/24"]
    end

    subgraph VBox["VMware Workstation"]
        direction TB
    end

    Host --> VBox

    subgraph LabNet["Isolated Lab Network — Host-Only (192.168.86.0/24)"]
        direction TB
        Kali["Kali Linux<br/>attacker<br/>192.168.86.130<br/>2 vCPU · 2 GB · 20 GB"]
        Windows["Windows Server 2025<br/>victim<br/>192.168.86.129<br/>1 vCPU · 2 GB · 64 GB<br/>Sysmon (SwiftOnSecurity →<br/>Olaf Hartong modular)<br/>Wazuh Agent"]
        Ubuntu["Ubuntu Server<br/>SIEM host<br/>192.168.86.131<br/>2 vCPU · 16 GB · 50 GB"]
    end

    VBox --> LabNet

    subgraph UbuntuBox["Ubuntu Server internals"]
        direction TB
        Docker["Docker Engine"]
        subgraph Stack["docker-compose stack"]
            Wazuh["Wazuh Manager<br/>+ Indexer<br/>+ Dashboard<br/>:55000 / :9443"]
        end
        Docker --> Stack
    end

    Ubuntu -. "Wazuh Agent<br/>TCP 1514" .-> Stack

    Kali -. "RDP 3389<br/>SMB 445<br/>WinRM 5985" .-> Windows
    Windows -. "Sysmon +<br/>Security/Application<br/>event logs" .-> Stack

    Internet -. "Outbound only<br/>(toggleable via script)" .-> Ubuntu
    Internet -. "Pull packages" .-> Kali

    style Kali fill:#2b1d1d,stroke:#ff2e63,color:#fff
    style Windows fill:#1d2b2b,stroke:#00ffd5,color:#fff
    style Wazuh fill:#0a0e14,stroke:#00ffd5,color:#fff
    style Docker fill:#0a0e14,stroke:#00ffd5,color:#fff
    style Internet fill:#0a0e14,stroke:#666,color:#fff
    style Host fill:#0a0e14,stroke:#666,color:#fff
Loading

Why dual-homed and not just one adapter?

A single NAT adapter on each VM still routes traffic through the host's home network. With Mimikatz, PsExec, and brute-force traffic in play, that's an unacceptable blast radius. The fix:

  • Adapter 1 — Host-Only (192.168.86.0/24): the only network the three lab VMs live on. Kali can reach Windows, Windows can reach the Ubuntu SIEM, nothing reaches the home LAN.
  • Adapter 2 — NAT: outbound internet only. Disabled by default on Kali via a small bash toggle (ifdown eth1 / ifup eth1) so the attacker VM only talks to the lab subnet unless I explicitly want it to pull a package.

A real SOC analyst will recognize this as the same logic that drives production network segmentation: the analyst workstation and the monitored endpoints live on different trust zones, and the SIEM tier sits in its own.


Environment

Component Specs Role
Kali Linux 2 vCPU, 2 GB RAM, 20 GB disk Attacker platform (Hydra, Nmap, Mimikatz, Impacket)
Windows Server 2025 1 vCPU, 2 GB RAM, 64 GB disk Victim — deliberately weakened (Defender off, SMBv1 on, weak local account, RDP without NLA)
Ubuntu Server 22.04 2 vCPU, 16 GB RAM, 50 GB disk SIEM host (Docker + Wazuh stack)
VMware Workstation Hypervisor; provides the dual NIC topology above
Sysmon SwiftOnSecurity config → Olaf Hartong modular (Research) Endpoint telemetry enrichment on Windows

The Windows VM is intentionally a worst-practice configuration so detection content has something to fire on. Do not deploy any of these settings outside a lab.


Stage 1 — Splunk deployment

Before Wazuh, the lab ran on Splunk. The motivation was the same: a proper SIEM, not a grep over Event Viewer. The Universal Forwarder on Windows was the first real lesson in agent-to-indexer plumbing.

The first detection end-to-end was a Hydra brute force on the local Administrator account, captured with this SPL:

index=* sourcetype="WinEventLog:Security"
    (EventCode=4625 OR EventCode=4624)
| eval Status=case(EventCode=4625, "Failed Attempt",
                   EventCode=4624, "Successful Login")
| timechart span=1m count by Status

The chart shows 8 failed logon attempts at 04:01 from a single source — the textbook signature of password spraying. The same investigation continues into the Event 4625 detail:

Field Value
Account Name Administrator
Workstation Name kali
Source Network Address 192.168.86.130
Source Port 0
Logon Process NtLmSsp
Authentication Package NTLM
Failure Reason Unknown user name or bad password
Status / Sub Status 0xC000006D / 0xC000006A

The source IP matching the Kali subnet is the smoking gun — it's why recording the attacker IP at the alert level (not just the count) was worth the extra SPL.

The first SOC dashboard was a single panel: a Threat Gauge of failed logon attempts against a pie chart of victim usernames, with a timeline of breach attempts. Basic, but a solid foundation for the mental model of "event source → indexer → search head → dashboard."

Verdict on Splunk: It works, the SPL is genuinely powerful, and the dashboards are first-class. For a learning lab, it's expensive and the free tier caps ingestion. The pivot to Wazuh was driven by cost and a real interest in seeing what an open-source XDR-grade SIEM gives you out of the box.


Stage 2 — Wazuh (containerized)

Wazuh is a heavier stack than Splunk: manager + indexer + dashboard, each with their own resource profile. The original plan to run it side-by-side with Splunk on a 4 GB VM failed in the obvious way (50 GB of log volume at boot, OOM on the manager service). The containerized approach was the right move but needed real work to get right.

Spin-up order that worked:

  1. Fresh Ubuntu Server 22.04 install.
  2. Install Docker + docker-compose.
  3. Pull the Wazuh single-node stack YAML.
  4. Set vm.max_map_count=262144 on the host (Elasticsearch requirement).
  5. Extend the LVM logical volume — the default Ubuntu Server install only allocated ~24 GB of the 50 GB virtual disk to the root LV, and Wazuh filled it inside an hour.
  6. Deploy the Wazuh agent on the Windows box using the manager's IP, explicitly set in ossec.conf — the agent silently fails to register without that.

Dashboard after first agent enrolls: the standard Wazuh overview shows the agent's endpoint posture in summary form — agent count, alert breakdown by severity, MITRE ATT&CK shortcuts, and IT-hygiene / compliance panels (PCI DSS, GDPR, HIPAA, CIS benchmarks) out of the box. The numbers worth noting at first ingest were:

Panel Value at first ingest
Critical alerts (24h) 0
High alerts (24h) 0
Medium alerts (24h) 316
Low alerts (24h) 647
Active agents 1
MITRE techniques observed 9 Impact, 1 Defense Evasion

The vulnerability count without any attack traffic is the more interesting number. The Vulnerability Detection panel on a default Windows Server 2025 install against Wazuh's built-in vulnerability scanner produced:

Severity Count
Critical 12
High 574
Medium 264
Low 6

That's a useful baseline for any "is this VM production-ready" decision — and a strong argument for why even a freshly-installed Windows host benefits from continuous vulnerability scanning in a real environment.


Attack simulations & detection

Each scenario below follows the same shape: setup → execution → detection → result. Every detection quoted is a real Wazuh rule ID that fired in this lab, with the rule description verbatim from the alert.

1. Brute force (Hydra) → RDP/NTLM

Setup. Created a local account Administrator with a weak password. Enabled RDP without Network Level Authentication. Hydra runs from the Kali host against the Windows host on the lab subnet.

Execution.

hydra -l Administrator -P /usr/share/wordlists/rockyou.txt \
      -t 4 -V 192.168.86.129 rdp

Detection — Splunk stage (4625 + 4624 SPL above): 8 failed logon attempts in a single minute, single source IP. Splunk visualizes the pattern; the analyst has to do the rest.

Detection — Wazuh stage (rule 1014, level 10):

Rule 1014 (Level 10): Successful Remote Logon Detected — User: Administrator — NTLM authentication, possible pass-the-hash attack. Source IP: 192.168.86.130 (Kali).

Hydra finally landed a valid credential. The level-10 severity is because the rule chain is built to flag exactly the post-bruteforce "first successful NTLM logon from a host that just generated a 4625 storm" scenario. This is the kill shot alert. A SOC L1 seeing this rule fire on a production network would open a P1 ticket immediately.

Result. Credential compromise of the local Administrator account. The account was reset and the source IP added to the watchlist for the duration of the engagement.


2. Credential dumping (Mimikatz)

Setup. The Windows VM has SMBv1 enabled and a writable C$ share (the default for local admins). Mimikatz is staged on Kali and dropped to the victim over SMB.

Execution.

# On Kali — drop mimikatz to the victim's C: drive over SMB
smbclient //192.168.86.129/C$ -U Administrator
> put mimikatz.exe

# Pivot to Impacket PsExec for execution
impacket-psexec Administrator:'Admin!'@192.168.86.130
C:\> C:\mimikatz.exe "privilege::debug" "sekurlsa::logonpasswords" "exit"

Detection — file drop (rule 92218, level 6):

Rule 92218 (Level 6): Possible abuse of Windows admin shares by binary dropped in Windows root folder by system process.

This is the Wazuh rule that fires on a binary landing in C:\ via SMB, an unusual path for legitimate executables. Source IP again 192.168.86.130.

Detection — service install (rule 67027 / 92650, level 3 / 12):

Rule 92650 (Level 12): New Windows Service Created to start from win... Binary path: C:\mimikatz.exe. Event IDs 4688 (process creation) and 7045 (service install).

A level-12 alert on a brand-new service whose image path is a suspicious binary in C:\ is the kind of detection that doesn't require a known-bad signature. It fires on the shape of the behavior, which is what makes it useful against novel tooling.

Detection — command line (Sysmon + 4688 with audit policy):

Rule 92052 (Level 4): Windows command prompt started by an abnormal process.

The full command line is preserved in the alert:

C:\mimikatz.exe "privilege::debug" "sekurlsa::logonpasswords" "exit"

This is where the Advanced Audit Policy Configuration → Detailed Tracking → Include Command Line in Process Creation Events setting earns its keep. Without it, the alert would just say mimikatz.exe ran; with it, the full intent is captured.

Result — and an important failure mode. The sekurlsa::logonpasswords module did not produce cleartext credentials in the Wazuh alert. Reason: Credential Guard on Windows Server 2025 isolates the LSASS process from any non-TrustedInstaller process — including an admin-context Mimikatz. The token impersonation succeeded (NT AUTHORITY\SYSTEM), but the read of LSASS memory was blocked.

The alternative lsadump::sam module, which reads the SAM registry hive rather than LSASS, still worked and dumped NTLM hashes:

Administrator:500:
  aad3b435b51404ee:aad3b435b51404ee:
  580b16d486d8d2cafa00b314d41fa396:::

This is the part of the writeup that I think is most useful for a junior SOC analyst: the lab hit a real modern defense, and the detection chain (file drop → service install → abnormal cmd → command line capture) still fired end-to-end. A detection that fires even when the actual attack goal is partly blocked is worth more than one that only fires on a successful compromise.


3. Lateral movement (PsExec)

Setup. After the Mimikatz run, the same Kali box attempts to move laterally using Impacket's PsExec against the same victim (in this lab, the "lateral" target is a second victim on a separate subnet; for the simulation, the same host is used to keep the lab minimal).

Execution.

impacket-psexec Administrator:'Admin!'@192.168.174.130 \
  cmd.exe
# Impacket uploads KKtqVIHf.exe to ADMIN$, creates service RlJW,
# executes, returns nt authority\system

Detection — service-from-random-binary (rule 92307, level 3):

Rule 92307 (Level 3): Evidence of new service creation found in registry under HKLM\System\CurrentControlSet\Services\sJXO. Binary path: C:\LovMhziA.exe.

The interesting signal here is the binary name. Impacket generates random 8-character names per engagement (KKtqVIHf.exe, LovMhziA.exe, sJXO for the service). A service name of 8 random chars pointing at an C:\ binary that wasn't there yesterday is a high-fidelity PsExec signature. Wazuh's syscheck + sysmon integration plus the New-Windows-Service-Created rule cover exactly this case.

Result. Service-based lateral movement detected, attacker achieved nt authority\system on the target. Detection fired at file-creation time, before the service was started — fast enough to act on if a real SOC were watching.


Detection engineering: what failed & what fixed it

A lab isn't useful if everything works the first time. These are the real problems I hit, in order.

1. Nmap scans weren't generating alerts

Symptom. Ran nmap -sS 192.168.86.129 from Kali. Wazuh dashboard stayed quiet. pfirewall.log on Windows showed the connections; Wazuh didn't surface them.

Root cause. The default Wazuh agent config explicitly excludes Event IDs 5156 (Windows Firewall let a connection through) and 5157 (Windows Firewall blocked a connection) — they're noisy in production.

Fix. secpol.msc → Advanced Audit Policy Configuration → Object Access → enable Audit Filtering Platform Connection and Audit Filtering Platform Packet Drop, both Success and Failure. Wazuh then started showing connection events.

Lesson. Don't trust the default <rule> blocks in ossec.conf blindly. They optimize for production noise; a lab needs the opposite posture.

2. Sysmon config was filtering out the interesting events

Symptom. Sysmon was installed but the lab's alerts were thin relative to the actual activity.

Root cause. The SwiftOnSecurity config is production-tuned to ignore noise. Most of the attacks in this lab — particularly the remote-originated ones — fall into the "ignored" bucket.

Fix. Switched to Olaf Hartong's Sysmon-Modular (the Research variant). It logs more events by default and the modular structure makes it easy to add/remove include rules without rewriting the whole config.

Lesson. The right Sysmon config depends on what you're defending against. Production ≠ research.

3. LVM partition was 24 GB instead of 50 GB

Symptom. Wazuh service refused to start. No error in systemd, no error in journalctl. Just dead.

Root cause. Default Ubuntu Server installer only allocates ~24 GB of the 50 GB virtual disk to the root logical volume. Wazuh filled it on first run.

Fix. Extended the LV with lvextend and resized the filesystem with resize2fs. Permanent fix.

Lesson. Disk-full failures in containerized stacks often present as opaque service-start failures. Check df -h first, every time.

4. Wazuh agent not registering — silent

Symptom. Agent installed on Windows, service started, but the manager never showed it. No error message in the agent log either.

Root cause. ossec.conf on the Windows side didn't have the manager IP explicitly set. The agent tried to find it via the default broadcast and failed silently.

Fix. Set <server><address>192.168.86.131</address></server> explicitly in ossec.conf and restarted the agent service.

Lesson. Read the actual log files, not just systemctl status. Wazuh's agent-side logs are verbose; the answer is in there.


MITRE ATT&CK coverage

Tactic Technique Sub-technique Lab evidence
Credential Access T1110 Brute Force T1110.001 Password Guessing Hydra → 8 × Event 4625 (Splunk)
Credential Access T1003 OS Credential Dumping T1003.002 Security Account Manager lsadump::sam output (Mimikatz)
Credential Access T1003 OS Credential Dumping T1003.001 LSASS Memory (blocked by Credential Guard) sekurlsa::logonpasswords attempt (Mimikatz)
Lateral Movement T1021 Remote Services T1021.002 SMB/Windows Admin Shares smbclient drop, ADMIN$ abuse (rule 92218)
Lateral Movement T1569 System Services T1569.002 Service Execution Impacket PsExec service install (rules 67027, 92307, 92650)
Execution T1059 Command and Scripting Interpreter T1059.001 PowerShell, T1059.003 Windows Command Shell cmd.exe chain, abnormal-cmd rule 92052
Defense Evasion T1027 Obfuscated Files or Information Random-named PsExec service binaries
Defense Evasion T1562 Impair Defenses T1562.001 Disable or Modify Tools Defender disabled on victim (intentional)
Persistence T1543 Create or Modify System Process T1543.003 Windows Service Mimikatz / PsExec service install (rule 92650)
Impact (observed) Wazuh MITRE panel — 9 Impact, 1 Defense Evasion on first ingest (vulnerability scanner noise)

Lessons & what I'd rebuild

What I learned the hard way:

  1. Default SIEM configs optimize for production noise, not lab signal. The Wazuh defaults that exclude 5156/5157 firewall events are the right call in a 10k-endpoint deployment and the wrong call for a 1-VM lab. Read every <rule> block.
  2. The audit policy is the detection floor. A SIEM is useless if the OS isn't generating the events the SIEM matches against. Command-line auditing (4688 with Include Command Line) and object-access auditing (5156/5157) are the two settings that turned this lab from "pretty dashboards" into "actual detections."
  3. Credential Guard is real, and it's the default on modern Windows Server. A junior analyst reading older Mimikatz tutorials will get confused when sekurlsa::logonpasswords silently fails. Knowing that SAM dumping still works is the practical takeaway.
  4. Sysmon config is a project, not a setting. The SwiftOnSecurity vs Olaf Hartong choice represents a real trade-off between production signal-to-noise and lab/research coverage. For detection engineering in a SOC, you'd probably run a hybrid — Hartong's base, with production noise-rules layered on top.
  5. The most important SPL/Sigma rule in a SOC isn't the one that fires on a known-bad hash. It's the one that fires on "binary with random 8-character name dropped to C:\ and registered as a Windows service" — because that pattern covers a family of attacks, not a single sample.

What I'd rebuild:

  • Add Suricata or Zeek on a SPAN port between the lab switch and the victim. The Wazuh stack is endpoint-centric; adding network telemetry (DNS logs, TLS SNI, HTTP requests) would catch the lateral movement earlier in the kill chain.
  • Centralize logs into the home SIEM, not just the lab one. A Wazuh manager on the host machine that ingests lab agent logs would let me run Sigma rules against the live data.
  • Time-bound the lab with an automated attack script. A cron that fires a random MITRE technique every hour, with alerting on a separate channel, would let me validate detection coverage continuously instead of one-off.
  • Wire a real threat intel feed. AbuseIPDB or EmergingThreats Suricata rules would turn a portion of the Wazuh alerts into "known-bad source" classifications, which is closer to how a real SOC triages.

References

About

A virtualized SOC lab utilizing Dockerized Wazuh to detect simulated attacks (Hydra, Mimikatz) within a secured, dual-homed network environment.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors