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. You need to explicitly enable it 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