This is Part 6 of the AD DFIR Lab series. We configure all the auditing before running the attacks — if we don’t do it now, the attacks won’t leave a trace.
Why audit before attacking
A DFIR lab without auditing is like investigating a crime without security cameras. The attacks happen, but when you come to investigate there’s nothing left. That’s why Phase 8 comes before Phase 9 (attacks): instrument first, attack second, and then the artifacts are there to analyze.
What we’ll capture:
Windows:
- Sysmon with olafhartong’s
sysmon-modular(2704 lines of rules) - Windows Advanced Audit Policy via
auditpol(not GPO) - PowerShell logging: Script Block (4104), Module (4103), Transcription
- Command line in 4688
- Operational logs: WinRM, WMI-Activity, TaskScheduler
Linux:
- auditd with 50 DFIR rules (exec, auth, sudo, ssh, sssd, systemd, cron, priv esc)
- persistent journald in
/var/log/journal/
Windows: Sysmon with sysmon-modular
The Sysmon that ships with GOAD is from 2021 (v13) and uses the SwiftOnSecurity config. We use the latest Sysmon (v15+) with olafhartong’s sysmon-modular, which has better coverage for modern MITRE ATT&CK techniques.
# On the Proxmox host
cd /root/lab/audit
wget https://download.sysinternals.com/files/Sysmon.zip
wget https://raw.githubusercontent.com/olafhartong/sysmon-modular/master/sysmonconfig.xml
# 2704 lines of rules with MITRE ATT&CK mapping
And the Ansible playbook installs Sysmon on all 6 Windows VMs:
- name: Copy Sysmon.zip to Windows VM
ansible.windows.win_copy:
src: /root/lab/audit/Sysmon.zip
dest: 'C:\sysmon\Sysmon.zip'
- name: Unzip Sysmon
community.windows.win_unzip:
src: 'C:\sysmon\Sysmon.zip'
dest: 'C:\sysmon'
- name: Copy sysmon-modular config
ansible.windows.win_copy:
src: /root/lab/audit/sysmonconfig.xml
dest: 'C:\sysmon\sysmonconfig.xml'
- name: Install Sysmon (first time)
ansible.windows.win_command: 'C:\sysmon\Sysmon64.exe -accepteula -i C:\sysmon\sysmonconfig.xml'
when: sysmon_svc.exists is not defined or not sysmon_svc.exists
- name: Update Sysmon config (if already installed)
ansible.windows.win_command: 'C:\sysmon\Sysmon64.exe -c C:\sysmon\sysmonconfig.xml'
when: sysmon_svc.exists is defined and sysmon_svc.exists
The logic is well thought out: on a fresh install use -i; if already installed use -c to update the config without reinstalling.
Key Sysmon events captured by the modular config
| ID | Event | Why |
|---|---|---|
| 1 | ProcessCreate | With command line — the foundation of all analysis |
| 3 | NetworkConnect | Outbound connections |
| 7 | ImageLoaded | Loaded DLLs (DLL sideloading detection) |
| 8 | CreateRemoteThread | Process injection |
| 10 | ProcessAccess | Opening handles to other processes (mimikatz → lsass.exe) |
| 11 | FileCreate | File creation in suspicious paths |
| 13 | RegistryValueSet | Registry modification (persistence) |
| 22 | DnsQuery | DNS resolution — gold for C2 detection |
After the attacks, we’ll have thousands of these events in Microsoft-Windows-Sysmon/Operational.evtx.
Windows Advanced Audit Policy
Sysmon is great but doesn’t catch everything. Security.evtx is where Kerberos events (4768, 4769), logons (4624, 4625), and DCSync (4662) live — DCSync being the attack where someone impersonates a domain controller to pull password hashes over the replication protocol. Windows groups these events into audit subcategories (finer-grained than the top-level categories), and each one has to be switched on explicitly with auditpol:
- name: Enable Account Logon auditing (4768-4776 Kerberos)
ansible.windows.win_shell: |
auditpol /set /subcategory:"Credential Validation" /success:enable /failure:enable
auditpol /set /subcategory:"Kerberos Authentication Service" /success:enable /failure:enable
auditpol /set /subcategory:"Kerberos Service Ticket Operations" /success:enable /failure:enable
- name: Enable DS Access (4662 — DCSync detection)
ansible.windows.win_shell: |
auditpol /set /subcategory:"Directory Service Access" /success:enable /failure:enable
auditpol /set /subcategory:"Directory Service Changes" /success:enable /failure:enable
auditpol /set /subcategory:"Directory Service Replication" /success:enable /failure:enable
Enabled categories (Success + Failure):
| Category | Key events |
|---|---|
| Account Logon | 4768 (TGT request), 4769 (TGS request), 4771, 4776 |
| Logon/Logoff | 4624 (success), 4625 (failure), 4634, 4647, 4648, 4672 (special) |
| Object Access | 5140/5145 (file share), 4656 (handle), 4663 (access) |
| DS Access | 4662 (DCSync detection) |
| Detailed Tracking | 4688 (process creation), 4689 (termination) |
| Privilege Use | 4673, 4674 |
| Account Management | 4720-4738 (user changes), 4726 (delete) |
| Policy Change | 4719, 4907 |
Command line in 4688
By default Event 4688 only records the executable name, not the arguments. For DFIR this is useless — you need to see what parameters the attacker used. Enabled via a registry key:
- name: Enable command line in 4688
ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Audit
name: ProcessCreationIncludeCmdLine_Enabled
data: 1
type: dword
Without this you’d see powershell.exe but not powershell.exe -enc SQBFAHgA.... With this, you capture the full payload.
PowerShell logging
Attackers use PowerShell for everything. Without PowerShell logging you lose half the artifacts of a modern incident. Three levels:
# Script Block Logging — Event 4104 (the most important)
- ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging
name: EnableScriptBlockLogging
data: 1
type: dword
# Module Logging — Event 4103 (all modules)
- ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging
name: EnableModuleLogging
data: 1
type: dword
- ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging\ModuleNames
name: '*'
data: '*'
type: string
# Transcription — text files with everything typed
- ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription
name: EnableTranscripting
data: 1
type: dword
- ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription
name: OutputDirectory
data: 'C:\PSTranscripts'
type: string
With this:
- Event 4104: any script block PowerShell executes (even Base64-obfuscated, it’s decoded before being logged)
- Event 4103: every individual command from every module
- Transcription: text files in
C:\PSTranscriptswith the entire session
Log sizing (important for limited disk)
The Hetzner server has 2×512 GB NVMe in RAID1, with ~310 GB free in the ZFS pool after creating all VMs. Logs can grow fast if we don’t cap them.
Conservative calculation (per-VM and total):
| Log | Per VM | Total 6 Windows |
|---|---|---|
| Security | 512 MB | 3 GB |
| Sysmon | 512 MB | 3 GB |
| PowerShell Operational | 256 MB | 1.5 GB |
| WinRM Operational | 128 MB | 768 MB |
| WMI-Activity | 128 MB | 768 MB |
| TaskScheduler | 128 MB | 768 MB |
| Windows subtotal | ~1.6 GB | ~10 GB |
| Linux auditd | - | 600 MB |
| journald | - | 500 MB |
| TOTAL | ~11 GB |
With 310 GB free in ZFS, the safety ratio is ~28x. Comfortable.
# Sizes applied with wevtutil
- ansible.windows.win_shell: wevtutil sl Security /ms:536870912
- ansible.windows.win_shell: wevtutil sl Microsoft-Windows-Sysmon/Operational /ms:536870912
- ansible.windows.win_shell: wevtutil sl Microsoft-Windows-PowerShell/Operational /ms:268435456
Gotcha: the initial sizes I set (1GB each) were excessive for a lab on a shared server. The first iteration could have reached ~24 GB of potential logs. Checking disk ratios before launching the attacks made me realize this and I reduced the values. Always calculate the worst case before enabling full auditing in a disk-limited environment.
Linux: auditd with DFIR rules
For Ubuntu (LNX01), we use auditd with DFIR-focused rules, not compliance:
## ======= AUTHENTICATION =======
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/sudoers -p wa -k identity
-w /etc/sudoers.d/ -p wa -k identity
## ======= PROCESS EXECUTION =======
-a always,exit -F arch=b64 -S execve -k exec
-a always,exit -F arch=b32 -S execve -k exec
## ======= SUDO (uid != euid = escalated) =======
-a always,exit -F arch=b64 -S execve -C uid!=euid -F euid=0 -k sudo_exec
## ======= SSH =======
-w /etc/ssh/sshd_config -p wa -k sshd
-w /root/.ssh/ -p wa -k root_ssh
## ======= PRIVILEGE ESCALATION =======
-w /bin/su -p x -k priv_esc
-w /usr/bin/sudo -p x -k priv_esc
-w /usr/bin/pkexec -p x -k priv_esc
## ======= KERNEL MODULES =======
-a always,exit -F arch=b64 -S init_module -S delete_module -k modules
## ======= SSSD / KERBEROS =======
-w /etc/sssd/ -p wa -k sssd
-w /etc/krb5.conf -p wa -k kerberos
-w /etc/krb5.keytab -p wa -k kerberos
## ======= CRON / SYSTEMD =======
-w /etc/cron.d/ -p wa -k cron
-w /etc/crontab -p wa -k cron
-w /var/spool/cron/ -p wa -k cron
-w /etc/systemd/ -p wa -k systemd
50 rules total, with keys for quick filtering:
# Search privileged executions
sudo ausearch -k sudo_exec --start recent
# Search /etc/passwd modifications
sudo ausearch -k identity
# Search SSSD authentication attempts
sudo ausearch -k sssd
auditd size
Configured for 600MB total (200MB × 3 files) with automatic rotation:
max_log_file = 200
num_logs = 3
max_log_file_action = ROTATE
And persistent journald (survives reboots), capped at 500 MB:
[Journal]
SystemMaxUse=500M
MaxRetentionSec=30day
Storage=persistent
Post-deployment verification
# Sysmon running on Windows
ansible -i goad/inventory dc01 -m win_shell -a '(Get-Service sysmon64).Status'
# Running
# Sysmon events already captured
ansible -i goad/inventory dc01 -m win_shell -a '(Get-WinEvent -ListLog Microsoft-Windows-Sysmon/Operational).RecordCount'
# 1660
# Audit policy active
ansible -i goad/inventory dc01 -m win_shell -a 'auditpol /get /category:* | Select-String "Success and Failure"'
# System Integrity Success and Failure
# Logon Success and Failure
# Process Creation Success and Failure
# ...
# auditd on Linux
ssh ubuntu@192.168.10.32 'sudo auditctl -l | wc -l'
# 50
All 7 domain machines are instrumented. When we launch the attacks in Phase 9, each technique will leave traces in the EVTX/logs that we can analyze later with tools like masstin.
Next: Part 7 — Fire and Blood: Attack Scenarios and Forensic Analysis (coming soon)
Previous: Part 5 — The Smallfolk: Users, Groups and Vulnerabilities
$ comments