From 9cd8300a0915785aec2b400861d52952057befbd Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 01:41:18 +0000 Subject: [PATCH] Windows: audit coverage true-up, Server profile and per-object audit rules - Turn on SAM, Other Policy Change, MPSSVC Rule-Level Policy Change, DPAPI Activity and Group Membership auditing. - Add audit-server.csv (Kerberos, Directory Service) applied automatically on Windows Server SKUs; -AuditProfile overrides. - Add sacls.psd1 and per-object audit rules (SACLs) on persistence and tampering registry keys and folders, so the File System and Registry categories actually produce events. Recorded in state and removed by -Revert; -SkipObjectAuditing opts out. WinLogs.Acl.ps1 holds the helpers (registry SACLs need SeSecurityPrivilege and a Win32 key open). - Size/enable more channels (NTLM, Security-Mitigations, PrintService, SmbClient, AppLocker, Diagnosis-Scripted, DriverFrameworks, PowerShellCore, and DC/DNS channels on Server). - Tests assert profile-specific audit settings, SACL add/remove, and that 4657 and 4663 events fire; CI adds a windows-11-arm client job. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/test.yml | 4 +- docs/what-gets-logged.md | 26 ++++++- tests/Test-WinLogs.ps1 | 37 ++++++++++ windows/Install-WinLogs.ps1 | 137 ++++++++++++++++++++++++++++++++++-- windows/WinLogs.Acl.ps1 | 62 ++++++++++++++++ windows/audit-server.csv | 5 ++ windows/audit.csv | 9 +-- windows/sacls.psd1 | 33 +++++++++ 8 files changed, 298 insertions(+), 15 deletions(-) create mode 100644 windows/WinLogs.Acl.ps1 create mode 100644 windows/audit-server.csv create mode 100644 windows/sacls.psd1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5ea5229..b535386 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,8 +9,10 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-2022, windows-2025] + # windows-11-arm is a client SKU on ARM64 (Windows 10/11 audit profile, Sysmon64a). + os: [windows-2022, windows-2025, windows-11-arm] runs-on: ${{ matrix.os }} + continue-on-error: ${{ matrix.os == 'windows-11-arm' }} steps: - uses: actions/checkout@v4 - name: Test with Windows PowerShell 5.1 diff --git a/docs/what-gets-logged.md b/docs/what-gets-logged.md index d849e07..f31b6b1 100644 --- a/docs/what-gets-logged.md +++ b/docs/what-gets-logged.md @@ -7,13 +7,33 @@ | Legacy override | `SCENoApplyLegacyAuditPolicy=1` so advanced audit policy wins | n/a | | PowerShell | Script Block (incl. invocation start/stop), Module (`*`), Transcription to `C:\ProgramData\WinLogs\transcripts`; set for Windows PowerShell and PowerShell 7 | Microsoft-Windows-PowerShell/Operational 4103/4104, transcript files | | Sysmon | sysmon-modular balanced config (process, network, DNS, image load, registry, WMI, pipes, ...) | Microsoft-Windows-Sysmon/Operational | -| Channels enabled / enlarged | Task Scheduler/Operational, DNS-Client/Operational (both usually off), Defender, WMI-Activity, BITS-Client, Firewall, TerminalServices, CodeIntegrity, System, Application | each channel | +| Channels enabled / enlarged | Task Scheduler, DNS-Client, DriverFrameworks-UserMode (usually off); Defender, WMI-Activity, BITS-Client, Firewall, TerminalServices, CodeIntegrity, NTLM, Security-Mitigations, PrintService, SmbClient Security, AppLocker (4 logs), Diagnosis-Scripted, PowerShellCore, System, Application | each channel | | Sizes | Security, Sysmon, PowerShell: `-LogSizeMB` (default 1024). Others: up to 256 MB. Logs are never shrunk. | n/a | -Changes from the original audit policy: added **PNP Activity** (USB / device insertion, 6416) and set **Other Object Access Events** to Success and Failure (scheduled task creation 4698/4702). +## Client vs Server + +`Install-WinLogs.ps1` picks an audit profile automatically (`-AuditProfile Auto|Workstation|Server`): + +| | Windows 10 / 11 | Windows Server (incl. domain controllers) | +|---|---|---| +| Base policy (`audit.csv`) | yes | yes | +| Kerberos Authentication Service and Service Ticket Operations (4768/4769/4771...), Directory Service Access, Directory Service Replication (`audit-server.csv`) | no (these only generate events on a DC) | yes | +| Extra channels | n/a | Directory Service, DNS Server, DNS Server Audit, DFS Replication (only if the role is installed) | + +## Audit policy changes from the original 2016 baseline +Added: PNP Activity (USB / device insertion, 6416), Group Membership (4627), and Other Object Access Events set to Success and Failure (scheduled task creation 4698/4702). +Turned on: SAM (4661 handle requests to the SAM), Other Policy Change Events, MPSSVC Rule-Level Policy Change (firewall rule changes, 4946-4948), DPAPI Activity (4692/4693). +Still off on purpose because they are very noisy: Handle Manipulation, Non Sensitive Privilege Use, Other Privilege Use, Filtering Platform Packet Drop, Detailed Directory Service Replication. + +## Per-object auditing (SACLs) +The File System and Registry audit categories only log access to objects that carry an audit rule. `windows/sacls.psd1` lists what gets one (success audit for Everyone) and is meant to be edited: +- Registry: Run / RunOnce (native and WOW6432Node), Policies\Explorer\Run, Active Setup, Winlogon, Windows (AppInit_DLLs), Services, Session Manager, Image File Execution Options, SilentProcessExit, Lsa, SecurityProviders (WDigest), and the Defender policy key. Events: 4657 (value change), 4656/4663. +- Files: the all-users StartUp folder, `System32\Tasks`, `System32\drivers`, and the hosts file. Events: 4663 (and 4656). +- Per-user hives (HKCU Run keys) are not covered, since each user's hive is only loaded while that user is signed in. +- Tamper-protected keys (for example Defender) may refuse the rule; that is reported as a warning and the run continues. +- `-SkipObjectAuditing` leaves these out; `-Revert` removes exactly the rules it added. Notes -- File System and Registry auditing only produce events for objects that have a SACL; the policy alone does not audit every file. - Filtering Platform Connection (5156/5158) is very noisy and overlaps Sysmon event 3. Remove that row from `audit.csv` if logs roll too quickly. - Transcripts are plain files that malware running as admin can delete. Fine for a lab, not evidence-grade. - Everything is recorded in `C:\ProgramData\WinLogs\state.json`; `-Revert` restores it. diff --git a/tests/Test-WinLogs.ps1 b/tests/Test-WinLogs.ps1 index 0d28042..80d7a4d 100644 --- a/tests/Test-WinLogs.ps1 +++ b/tests/Test-WinLogs.ps1 @@ -11,6 +11,7 @@ $repo = Split-Path $PSScriptRoot -Parent $install = Join-Path $repo 'windows\Install-WinLogs.ps1' $prep = Join-Path $repo 'windows\Prepare-Offline.ps1' $state = Join-Path $env:ProgramData 'WinLogs\state.json' +. (Join-Path $repo 'windows\WinLogs.Acl.ps1') $failed = @() # Surface problems as GitHub Actions annotations (readable without downloading the full log). @@ -44,6 +45,12 @@ Write-Host '== Baseline' $cmdKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Audit' $before = Reg $cmdKey 'ProcessCreationIncludeCmdLine_Enabled' $auditBefore = Audit 'Process Creation' +$runKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run' +$startup = Join-Path $env:ProgramData 'Microsoft\Windows\Start Menu\Programs\StartUp' +$runSaclBefore = Get-AuditRuleCount $runKey +$startupSaclBefore = Get-AuditRuleCount $startup +$isServer = (Get-CimInstance Win32_OperatingSystem).ProductType -ne 1 +Write-Host " (server profile expected: $isServer)" Write-Host '== -WhatIf changes nothing' & $install -OfflinePath $offline -Force -WhatIf | Out-Null @@ -62,6 +69,15 @@ Check 'Transcription policy' ((Reg "$psw\Transcription" 'EnableTranscript Check 'PowerShell 7 policy mirrored' ((Reg 'HKLM:\SOFTWARE\Policies\Microsoft\PowerShellCore\ScriptBlockLogging' 'EnableScriptBlockLogging') -eq 1) Check 'Process Creation audited (Success)' ((Audit 'Process Creation') -match 'Success') Check 'Security log >= 1 GB' ((Get-WinEvent -ListLog Security).MaximumSizeInBytes -ge 1GB) +Check 'Group Membership audited' ((Audit 'Group Membership') -match 'Success') +Check 'SAM audited (Success and Failure)' ((Audit 'SAM') -match 'Success and Failure') +if ($isServer) { + Check 'Kerberos Service Ticket audited (server overlay)' ((Audit 'Kerberos Service Ticket Operations') -match 'Success and Failure') +} else { + Check 'Kerberos not audited on client' ((Audit 'Kerberos Service Ticket Operations') -match 'No Auditing') +} +Check 'SACL on Run key' ((Get-AuditRuleCount $runKey) -gt $runSaclBefore) +Check 'SACL on StartUp folder' ((Get-AuditRuleCount $startup) -gt $startupSaclBefore) Check 'Task Scheduler log enabled' ((Get-WinEvent -ListLog 'Microsoft-Windows-TaskScheduler/Operational').IsEnabled) Write-Host '== Sysmon' @@ -85,6 +101,25 @@ if ($svc) { Check 'Sysmon event 1 recorded' ($sys1.Count -gt 0) } +Write-Host '== Object auditing produces events' +$valName = 'WinLogsTest' + [guid]::NewGuid().ToString('N').Substring(0, 8) +$fileName = Join-Path $startup "$valName.txt" +New-ItemProperty -Path $runKey -Name $valName -Value 'C:\Windows\System32\cmd.exe' -PropertyType String -Force | Out-Null +Set-Content -Path $fileName -Value 'test' +function Wait-Event($id, $pattern) { + for ($i = 0; $i -lt 10; $i++) { + $hit = @(Get-WinEvent -FilterHashtable @{ LogName = 'Security'; Id = $id } -MaxEvents 500 -ErrorAction SilentlyContinue | + Where-Object { $_.Message -match [regex]::Escape($pattern) }) + if ($hit.Count) { return $true } + Start-Sleep -Seconds 2 + } + return $false +} +Check '4657 registry value change on Run key' (Wait-Event 4657 $valName) +Check '4663 file write in StartUp folder' (Wait-Event 4663 "$valName.txt") +Remove-ItemProperty -Path $runKey -Name $valName -ErrorAction SilentlyContinue +Remove-Item $fileName -Force -ErrorAction SilentlyContinue + Write-Host '== Re-run is idempotent' & $install -OfflinePath $offline -Force Check 'second run succeeded' $true @@ -95,6 +130,8 @@ Check 'state.json removed' (-not (Test-Path $state)) Check 'cmdline auditing restored' ((Reg $cmdKey 'ProcessCreationIncludeCmdLine_Enabled') -eq $before) Check 'PowerShell policy removed' ($null -eq (Reg "$psw\ScriptBlockLogging" 'EnableScriptBlockLogging')) Check 'audit policy restored' ((Audit 'Process Creation') -eq $auditBefore) +Check 'Run key SACL removed' ((Get-AuditRuleCount $runKey) -eq $runSaclBefore) +Check 'StartUp SACL removed' ((Get-AuditRuleCount $startup) -eq $startupSaclBefore) Check 'Sysmon service removed' ($null -eq (Get-Service -Name 'Sysmon*' -ErrorAction SilentlyContinue)) if ($failed.Count) { Write-Host "`n$($failed.Count) failed: $($failed -join '; ')" -ForegroundColor Red; exit 1 } diff --git a/windows/Install-WinLogs.ps1 b/windows/Install-WinLogs.ps1 index 2ac53b9..6829885 100644 --- a/windows/Install-WinLogs.ps1 +++ b/windows/Install-WinLogs.ps1 @@ -7,7 +7,8 @@ Run from an elevated prompt. Intended for disposable lab machines only. What it does: - * Advanced audit policy (auditpol /restore from audit.csv) + * Advanced audit policy (auditpol /restore from audit.csv, plus audit-server.csv on Server) + * Per-object audit rules (SACLs) (persistence registry keys and folders, see sacls.psd1) * Command line in process events (4688) * PowerShell Script Block, Module and Transcription logging (Windows PowerShell + PowerShell 7) * Larger event logs, and enables useful channels that are off by default @@ -29,6 +30,14 @@ Optional SHA-256 the Sysmon.zip must match (downloaded or offline). The Authenticode signature of the extracted binary is always checked. +.PARAMETER AuditProfile + Auto (default), Workstation or Server. Server adds the domain-controller categories (Kerberos, + Directory Service) from audit-server.csv. Auto picks Server on any Windows Server SKU. + +.PARAMETER SkipObjectAuditing + Do not add per-object audit rules (SACLs, see sacls.psd1). Without those rules the File System and + Registry audit categories log almost nothing. + .PARAMETER LogSizeMB Maximum size for the big channels (Security, Sysmon, PowerShell). Default 1024. @@ -53,6 +62,8 @@ param( [switch]$SkipSysmon, [string]$SysmonConfigPath, [string]$SysmonSha256, + [ValidateSet('Auto', 'Workstation', 'Server')][string]$AuditProfile = 'Auto', + [switch]$SkipObjectAuditing, [ValidateRange(64, 65536)][int]$LogSizeMB = 1024, [switch]$Revert, [switch]$KeepSysmon, @@ -63,6 +74,7 @@ Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $Root = $PSScriptRoot +. (Join-Path $PSScriptRoot 'WinLogs.Acl.ps1') $DataDir = Join-Path $env:ProgramData 'WinLogs' $StateFile = Join-Path $DataDir 'state.json' $TranscriptDir = Join-Path $DataDir 'transcripts' @@ -85,6 +97,7 @@ function New-State { registry = @() # {path,name,existed,type,value} registryKeys = @() # keys we created logs = @() # {name,enabled,maxBytes} + sacls = @() # {kind,path,rights,inherit} sysmon = $null # {installedByUs,mode} } } @@ -146,6 +159,73 @@ function Confirm-Lab { if ($answer -cne 'YES') { throw 'Aborted.' } } +# --- audit helpers ---------------------------------------------------------------------------- + +function Resolve-AuditProfile { + if ($AuditProfile -ne 'Auto') { return $AuditProfile } + # ProductType: 1 = workstation (Windows 10/11), 2 = domain controller, 3 = server + if ((Get-CimInstance Win32_OperatingSystem).ProductType -eq 1) { return 'Workstation' } + return 'Server' +} + +function New-AuditRule([string]$Kind, [string]$Rights, [string]$Inherit) { + $everyone = New-Object Security.Principal.SecurityIdentifier('S-1-1-0') + $inh = [Security.AccessControl.InheritanceFlags]$Inherit + $prop = [Security.AccessControl.PropagationFlags]::None + $flag = [Security.AccessControl.AuditFlags]::Success + if ($Kind -eq 'Registry') { + return New-Object Security.AccessControl.RegistryAuditRule($everyone, [Security.AccessControl.RegistryRights]$Rights, $inh, $prop, $flag) + } + return New-Object Security.AccessControl.FileSystemAuditRule($everyone, [Security.AccessControl.FileSystemRights]$Rights, $inh, $prop, $flag) +} + +# Reads a key's / file's SACL, lets $Change modify it, writes it back (see WinLogs.Acl.ps1). +function Edit-ObjectAudit([string]$Kind, [string]$Path, [scriptblock]$Change) { + if ($Kind -eq 'Registry') { + $key = Open-AuditKey $Path + try { + $sec = $key.GetAccessControl([Security.AccessControl.AccessControlSections]::Audit) + & $Change $sec + $key.SetAccessControl($sec) + } finally { $key.Close() } + } else { + Enable-SecurityPrivilege + $acl = Get-Acl -LiteralPath $Path -Audit + & $Change $acl + Set-Acl -LiteralPath $Path -AclObject $acl + } +} + +# Adds a success-audit rule for Everyone (a SACL entry) and records it so -Revert can remove it. +function Add-ObjectAudit($State, [string]$Kind, [string]$Path, [string]$Rights, [string]$Inherit) { + $Path = [Environment]::ExpandEnvironmentVariables($Path) + if (-not (Test-Path -LiteralPath $Path)) { Write-Verbose "Audit target not present, skipping: $Path"; return } + if ($PSCmdlet.ShouldProcess($Path, "Add audit rule ($Rights)")) { + try { + $rule = New-AuditRule $Kind $Rights $Inherit + Edit-ObjectAudit $Kind $Path { param($sec) $sec.AddAuditRule($rule) } + if (-not (@($State.sacls) | Where-Object { $_.path -eq $Path -and $_.rights -eq $Rights })) { + $State.sacls += [ordered]@{ kind = $Kind; path = $Path; rights = $Rights; inherit = $Inherit } + } + } catch { Write-Warn "Could not set audit rule on '$Path': $($_.Exception.Message)" } + } +} + +function Remove-ObjectAudit($Entry) { + if (-not (Test-Path -LiteralPath $Entry.path)) { return } + if ($PSCmdlet.ShouldProcess($Entry.path, 'Remove audit rule')) { + try { + $rule = New-AuditRule $Entry.kind $Entry.rights $Entry.inherit + Edit-ObjectAudit $Entry.kind $Entry.path { param($sec) [void]$sec.RemoveAuditRuleSpecific($rule) } + } catch { Write-Warn "Could not remove audit rule from '$($Entry.path)': $($_.Exception.Message)" } + } +} + +function Invoke-AuditRestore([string]$Csv) { + & auditpol.exe /restore /file:"$Csv" | Out-Null + if ($LASTEXITCODE -ne 0) { throw "auditpol /restore failed for $Csv" } +} + # --- revert ----------------------------------------------------------------------------------- function Invoke-Revert { @@ -161,6 +241,11 @@ function Invoke-Revert { } } + if ($state.PSObject.Properties['sacls']) { + Write-Step 'Removing per-object audit rules' + foreach ($e in @($state.sacls)) { Remove-ObjectAudit $e } + } + Write-Step 'Restoring registry values' foreach ($e in @($state.registry)) { if (-not $PSCmdlet.ShouldProcess("$($e.path)\$($e.name)", 'Restore')) { continue } @@ -303,7 +388,7 @@ if ($Revert) { Invoke-Revert; return } $os = Get-CimInstance Win32_OperatingSystem if ([version]$os.Version -lt [version]'10.0') { throw "Unsupported OS: $($os.Caption). Windows 10 or later is required." } -Write-Host "WinLogs on $($os.Caption) (build $($os.BuildNumber))" +Write-Host "WinLogs on $($os.Caption) (build $($os.BuildNumber)), audit profile: $(Resolve-AuditProfile)" Confirm-Lab $state = $null @@ -314,6 +399,8 @@ if ($prior) { $state.registry = @($state.registry) $state.registryKeys = @($state.registryKeys) $state.logs = @($state.logs) + if (-not $state.PSObject.Properties['sacls']) { $state | Add-Member -NotePropertyName sacls -NotePropertyValue @() } + $state.sacls = @($state.sacls) } else { $state = New-State } # 1. Advanced audit policy @@ -328,8 +415,28 @@ if ($PSCmdlet.ShouldProcess('audit policy', "auditpol /restore $auditCsv")) { if ($LASTEXITCODE -ne 0) { throw 'auditpol /backup failed.' } $state.auditBackup = $backup } - & auditpol.exe /restore /file:"$auditCsv" | Out-Null - if ($LASTEXITCODE -ne 0) { throw 'auditpol /restore failed.' } + Invoke-AuditRestore $auditCsv +} +$profileName = Resolve-AuditProfile +if ($profileName -eq 'Server') { + Write-Step 'Adding Server / domain controller audit categories' + if ($PSCmdlet.ShouldProcess('audit policy', 'auditpol /restore audit-server.csv')) { + Invoke-AuditRestore (Join-Path $Root 'audit-server.csv') + } +} + +# 1b. Per-object audit rules (without these, File System / Registry auditing logs nearly nothing) +if ($SkipObjectAuditing) { + Write-Warn 'Skipping per-object audit rules (-SkipObjectAuditing).' +} else { + Write-Step 'Adding per-object audit rules (sacls.psd1)' + Enable-SecurityPrivilege + $sacl = Import-PowerShellDataFile -Path (Join-Path $Root 'sacls.psd1') + foreach ($r in $sacl.Registry) { Add-ObjectAudit $state 'Registry' $r.Path $r.Rights 'ContainerInherit' } + foreach ($f in $sacl.Files) { + $inh = if ($f.ContainsKey('Inherit')) { $f.Inherit } else { 'None' } + Add-ObjectAudit $state 'File' $f.Path $f.Rights $inh + } } # 2. Registry: command line auditing, legacy audit override, PowerShell logging @@ -362,7 +469,7 @@ if ($SkipSysmon) { # 4. Event logs: sizes, and channels that are off by default Write-Step 'Sizing and enabling event logs' -$big = 'Security', 'Microsoft-Windows-Sysmon/Operational', 'Microsoft-Windows-PowerShell/Operational', 'Windows PowerShell' +$big = 'Security', 'Microsoft-Windows-Sysmon/Operational', 'Microsoft-Windows-PowerShell/Operational', 'Windows PowerShell', 'PowerShellCore/Operational' foreach ($n in $big) { Set-TrackedLog $state $n $LogSizeMB $true } $medium = 'System', 'Application', 'Microsoft-Windows-Windows Defender/Operational', @@ -371,10 +478,26 @@ $medium = 'System', 'Application', 'Microsoft-Windows-Windows Firewall With Advanced Security/Firewall', 'Microsoft-Windows-TerminalServices-LocalSessionManager/Operational', 'Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational', - 'Microsoft-Windows-CodeIntegrity/Operational' + 'Microsoft-Windows-CodeIntegrity/Operational', + 'Microsoft-Windows-NTLM/Operational', + 'Microsoft-Windows-Security-Mitigations/KernelMode', + 'Microsoft-Windows-Security-Mitigations/UserMode', + 'Microsoft-Windows-PrintService/Admin', + 'Microsoft-Windows-PrintService/Operational', + 'Microsoft-Windows-SmbClient/Security', + 'Microsoft-Windows-AppLocker/EXE and DLL', + 'Microsoft-Windows-AppLocker/MSI and Script', + 'Microsoft-Windows-AppLocker/Packaged app-Deployment', + 'Microsoft-Windows-AppLocker/Packaged app-Execution', + 'Microsoft-Windows-Diagnosis-Scripted/Operational' +if ($profileName -eq 'Server') { + # Present only when the role is installed; Set-TrackedLog skips channels that do not exist. + $medium += 'Directory Service', 'DNS Server', 'Microsoft-Windows-DNSServer/Audit', 'DFS Replication' +} foreach ($n in $medium) { Set-TrackedLog $state $n ([math]::Min(256, $LogSizeMB)) $true } # Off by default on most builds; valuable for persistence and DNS visibility. -foreach ($n in 'Microsoft-Windows-TaskScheduler/Operational', 'Microsoft-Windows-DNS-Client/Operational') { +foreach ($n in 'Microsoft-Windows-TaskScheduler/Operational', 'Microsoft-Windows-DNS-Client/Operational', + 'Microsoft-Windows-DriverFrameworks-UserMode/Operational') { Set-TrackedLog $state $n ([math]::Min(256, $LogSizeMB)) $true } diff --git a/windows/WinLogs.Acl.ps1 b/windows/WinLogs.Acl.ps1 new file mode 100644 index 0000000..df1291a --- /dev/null +++ b/windows/WinLogs.Acl.ps1 @@ -0,0 +1,62 @@ +# Helpers for reading and writing SACLs (audit rules). Dot-sourced by Install-WinLogs.ps1 and the tests. +# +# Two Windows quirks drive this file: +# * a process must switch ON SeSecurityPrivilege (admins hold it, disabled by default) to touch a SACL; +# * .NET's RegistryKey.OpenSubKey refuses the ACCESS_SYSTEM_SECURITY right, and Get-Acl -Audit fails on +# registry keys, so the key is opened with the Win32 API and wrapped with RegistryKey.FromHandle. + +if (-not ('WinLogsNative' -as [type])) { + Add-Type -TypeDefinition @' +using System; +using System.Runtime.InteropServices; +using Microsoft.Win32; +using Microsoft.Win32.SafeHandles; + +public static class WinLogsNative { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct TokPriv1Luid { public int Count; public long Luid; public int Attr; } + + [DllImport("advapi32.dll", SetLastError = true)] static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr tok); + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] static extern bool LookupPrivilegeValue(string host, string name, ref long luid); + [DllImport("advapi32.dll", SetLastError = true)] static extern bool AdjustTokenPrivileges(IntPtr tok, bool disAll, ref TokPriv1Luid n, int len, IntPtr prev, IntPtr relen); + [DllImport("kernel32.dll")] static extern IntPtr GetCurrentProcess(); + [DllImport("advapi32.dll", CharSet = CharSet.Unicode)] static extern int RegOpenKeyEx(IntPtr hKey, string sub, int options, int sam, out IntPtr result); + + public static bool EnablePrivilege(string priv) { + IntPtr tok = IntPtr.Zero; + if (!OpenProcessToken(GetCurrentProcess(), 0x28, ref tok)) return false; + TokPriv1Luid tp; tp.Count = 1; tp.Luid = 0; tp.Attr = 2; + if (!LookupPrivilegeValue(null, priv, ref tp.Luid)) return false; + return AdjustTokenPrivileges(tok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero); + } + + // HKLM subkey opened with READ_CONTROL | ACCESS_SYSTEM_SECURITY, so its SACL can be read and written. + public static RegistryKey OpenHklmForAudit(string sub) { + IntPtr h; + int rc = RegOpenKeyEx(new IntPtr(unchecked((int)0x80000002)), sub, 0, 0x01020000, out h); + if (rc != 0) throw new System.ComponentModel.Win32Exception(rc); + return RegistryKey.FromHandle(new SafeRegistryHandle(h, true)); + } +} +'@ +} + +function Enable-SecurityPrivilege { [void][WinLogsNative]::EnablePrivilege('SeSecurityPrivilege') } + +function Open-AuditKey([string]$Path) { + Enable-SecurityPrivilege + [WinLogsNative]::OpenHklmForAudit(($Path -replace '^HKLM:\\', '')) +} + +# Number of audit rules on a registry key or file/folder. +function Get-AuditRuleCount([string]$Path) { + if ($Path -like 'HKLM:*') { + $key = Open-AuditKey $Path + try { + return @($key.GetAccessControl([Security.AccessControl.AccessControlSections]::Audit). + GetAuditRules($true, $false, [Security.Principal.SecurityIdentifier])).Count + } finally { $key.Close() } + } + Enable-SecurityPrivilege + return @((Get-Acl -LiteralPath $Path -Audit).Audit).Count +} diff --git a/windows/audit-server.csv b/windows/audit-server.csv new file mode 100644 index 0000000..d2b4bdd --- /dev/null +++ b/windows/audit-server.csv @@ -0,0 +1,5 @@ +Machine Name,Policy Target,Subcategory,Subcategory GUID,Inclusion Setting,Exclusion Setting,Setting Value +,System,Audit Kerberos Authentication Service,{0cce9242-69ae-11d9-bed3-505054503030},Success and Failure,,3 +,System,Audit Kerberos Service Ticket Operations,{0cce9240-69ae-11d9-bed3-505054503030},Success and Failure,,3 +,System,Audit Directory Service Access,{0cce923b-69ae-11d9-bed3-505054503030},Success and Failure,,3 +,System,Audit Directory Service Replication,{0cce923d-69ae-11d9-bed3-505054503030},Success,,1 \ No newline at end of file diff --git a/windows/audit.csv b/windows/audit.csv index 19bfa5e..1392495 100644 --- a/windows/audit.csv +++ b/windows/audit.csv @@ -9,7 +9,7 @@ Machine Name,Policy Target,Subcategory,Subcategory GUID,Inclusion Setting,Exclus ,System,Audit Other Account Management Events,{0cce923a-69ae-11d9-bed3-505054503030},Success and Failure,,3 ,System,Audit Security Group Management,{0cce9237-69ae-11d9-bed3-505054503030},Success and Failure,,3 ,System,Audit User Account Management,{0cce9235-69ae-11d9-bed3-505054503030},Success and Failure,,3 -,System,Audit DPAPI Activity,{0cce922d-69ae-11d9-bed3-505054503030},No Auditing,,0 +,System,Audit DPAPI Activity,{0cce922d-69ae-11d9-bed3-505054503030},Success and Failure,,3 ,System,Audit Process Creation,{0cce922b-69ae-11d9-bed3-505054503030},Success and Failure,,3 ,System,Audit Process Termination,{0cce922c-69ae-11d9-bed3-505054503030},Success and Failure,,3 ,System,Audit PNP Activity,{0cce9248-69ae-11d9-bed3-505054503030},Success,,1 @@ -25,6 +25,7 @@ Machine Name,Policy Target,Subcategory,Subcategory GUID,Inclusion Setting,Exclus ,System,Audit IPsec Quick Mode,{0cce9219-69ae-11d9-bed3-505054503030},No Auditing,,0 ,System,Audit Logoff,{0cce9216-69ae-11d9-bed3-505054503030},Success,,1 ,System,Audit Logon,{0cce9215-69ae-11d9-bed3-505054503030},Success and Failure,,3 +,System,Audit Group Membership,{0cce9249-69ae-11d9-bed3-505054503030},Success,,1 ,System,Audit Network Policy Server,{0cce9243-69ae-11d9-bed3-505054503030},Success and Failure,,3 ,System,Audit Other Logon/Logoff Events,{0cce921c-69ae-11d9-bed3-505054503030},Success and Failure,,3 ,System,Audit Special Logon,{0cce921b-69ae-11d9-bed3-505054503030},Success and Failure,,3 @@ -40,14 +41,14 @@ Machine Name,Policy Target,Subcategory,Subcategory GUID,Inclusion Setting,Exclus ,System,Audit Other Object Access Events,{0cce9227-69ae-11d9-bed3-505054503030},Success and Failure,,3 ,System,Audit Registry,{0cce921e-69ae-11d9-bed3-505054503030},Success,,1 ,System,Audit Removable Storage,{0cce9245-69ae-11d9-bed3-505054503030},Success and Failure,,3 -,System,Audit SAM,{0cce9220-69ae-11d9-bed3-505054503030},No Auditing,,0 +,System,Audit SAM,{0cce9220-69ae-11d9-bed3-505054503030},Success and Failure,,3 ,System,Audit Central Access Policy Staging,{0cce9246-69ae-11d9-bed3-505054503030},No Auditing,,0 ,System,Audit Audit Policy Change,{0cce922f-69ae-11d9-bed3-505054503030},Success and Failure,,3 ,System,Audit Authentication Policy Change,{0cce9230-69ae-11d9-bed3-505054503030},Success and Failure,,3 ,System,Audit Authorization Policy Change,{0cce9231-69ae-11d9-bed3-505054503030},Success and Failure,,3 ,System,Audit Filtering Platform Policy Change,{0cce9233-69ae-11d9-bed3-505054503030},Success,,1 -,System,Audit MPSSVC Rule-Level Policy Change,{0cce9232-69ae-11d9-bed3-505054503030},No Auditing,,0 -,System,Audit Other Policy Change Events,{0cce9234-69ae-11d9-bed3-505054503030},No Auditing,,0 +,System,Audit MPSSVC Rule-Level Policy Change,{0cce9232-69ae-11d9-bed3-505054503030},Success,,1 +,System,Audit Other Policy Change Events,{0cce9234-69ae-11d9-bed3-505054503030},Success and Failure,,3 ,System,Audit Non Sensitive Privilege Use,{0cce9229-69ae-11d9-bed3-505054503030},No Auditing,,0 ,System,Audit Other Privilege Use Events,{0cce922a-69ae-11d9-bed3-505054503030},No Auditing,,0 ,System,Audit Sensitive Privilege Use,{0cce9228-69ae-11d9-bed3-505054503030},Success and Failure,,3 diff --git a/windows/sacls.psd1 b/windows/sacls.psd1 new file mode 100644 index 0000000..bcd0d90 --- /dev/null +++ b/windows/sacls.psd1 @@ -0,0 +1,33 @@ +# Per-object audit rules (SACLs). The "Audit File System" and "Audit Registry" categories only log +# access to objects that have a SACL; these are the objects worth watching for persistence, defense +# evasion, credential access and tampering. Edit freely; Install-WinLogs.ps1 -Revert removes what it added. +# Each rule audits SUCCESS for Everyone. Paths may use %ENV% variables. +@{ + Registry = @( + # Autostart / persistence + @{ Path = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run'; Rights = 'SetValue, CreateSubKey, Delete, ChangePermissions, TakeOwnership' } + @{ Path = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce'; Rights = 'SetValue, CreateSubKey, Delete, ChangePermissions, TakeOwnership' } + @{ Path = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run'; Rights = 'SetValue, CreateSubKey, Delete, ChangePermissions, TakeOwnership' } + @{ Path = 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run'; Rights = 'SetValue, CreateSubKey, Delete, ChangePermissions, TakeOwnership' } + @{ Path = 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\RunOnce'; Rights = 'SetValue, CreateSubKey, Delete, ChangePermissions, TakeOwnership' } + @{ Path = 'HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components'; Rights = 'SetValue, CreateSubKey, Delete' } + @{ Path = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'; Rights = 'SetValue, CreateSubKey, Delete, ChangePermissions, TakeOwnership' } + @{ Path = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows'; Rights = 'SetValue, Delete' } # AppInit_DLLs, Load + @{ Path = 'HKLM:\SYSTEM\CurrentControlSet\Services'; Rights = 'SetValue, CreateSubKey, Delete' } + @{ Path = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'; Rights = 'SetValue, Delete' } # BootExecute, PendingFileRenameOperations + # Defense evasion / debugger hijack + @{ Path = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options'; Rights = 'SetValue, CreateSubKey, Delete' } + @{ Path = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SilentProcessExit'; Rights = 'SetValue, CreateSubKey, Delete' } + # Credential access / authentication + @{ Path = 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa'; Rights = 'SetValue, CreateSubKey, Delete, ChangePermissions, TakeOwnership' } + @{ Path = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders'; Rights = 'SetValue, CreateSubKey, Delete' } # WDigest UseLogonCredential + # Security tooling tampering (may be refused when Tamper Protection is on; that is only a warning) + @{ Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows Defender'; Rights = 'SetValue, CreateSubKey, Delete' } + ) + Files = @( + @{ Path = '%ProgramData%\Microsoft\Windows\Start Menu\Programs\StartUp'; Rights = 'CreateFiles, CreateDirectories, Delete, ChangePermissions, TakeOwnership'; Inherit = 'ContainerInherit, ObjectInherit' } + @{ Path = '%SystemRoot%\System32\Tasks'; Rights = 'CreateFiles, CreateDirectories, Delete, ChangePermissions, TakeOwnership'; Inherit = 'ContainerInherit, ObjectInherit' } + @{ Path = '%SystemRoot%\System32\drivers'; Rights = 'CreateFiles, Delete'; Inherit = 'ContainerInherit' } + @{ Path = '%SystemRoot%\System32\drivers\etc\hosts'; Rights = 'WriteData, AppendData, Delete, ChangePermissions'; Inherit = 'None' } + ) +}