Functions/Get-SysmonConfiguration.ps1
<#
.SYNOPSIS Parses a Sysmon driver configuration from the registry. Output is nearly identical to that of "sysmon.exe -c" but without the requirement to run sysmon.exe. .DESCRIPTION Get-SysmonConfiguration parses a Sysmon configuration from the registry without the need to run "sysmon.exe -c". This function is designed to enable Sysmon configuration auditing at scale as well as reconnaissance for red teamers. Get-SysmonConfiguration has been tested with the following Sysmon versions: 6.20 Due to the admin-only ACL set on the Sysmon driver registry key, Get-SysmonConfiguration will typically need to run in an elevated context. Because the user-mode service and driver names can be changed, Get-SysmonConfiguration will locate the service and driver regardless of their names. Author: Matthew Graeber (@mattifestation) License: BSD 3-Clause Required Dependencies: ConvertFrom-SysmonBinaryConfiguration .PARAMETER MatchExeOutput Mirrors the text output of "sysmon.exe -c". This parameter was implemented primarily to enable testing scenarios - i.e. to ensure that the output matches that of the version of Sysmon (or schema) being tested against. .EXAMPLE Get-SysmonConfiguration .EXAMPLE Get-SysmonConfiguration -MatchExeOutput .OUTPUTS Sysmon.Configuration Outputs a fully parsed Sysmon configuration including the hash of the registry rule blob for auditing purposes. System.String Outputs mirrored output from "sysmon.exe -c". .NOTES Get-SysmonConfiguration will have to be manually validated for each new Sysmon and configuration schema version. Please report all bugs and indiscrepencies with new versions by supplying the following information: 1) The Sysmon config XML that's generating the error (only schema versions 3.30 and later). 2) The version of Sysmon being used (only 6.20 and later). #> function Get-SysmonConfiguration { [OutputType('Sysmon.Configuration', ParameterSetName = 'PSOutput')] [OutputType([String], ParameterSetName = 'ExeOutput')] [CmdletBinding(DefaultParameterSetName = 'PSOutput')] param ( [Parameter(ParameterSetName = 'ExeOutput')] [Switch] $MatchExeOutput ) # Find the Sysmon driver based solely off the presence of the "Rules" value. # This is being done because the user can optionally specify a driver name other than the default: SysmonDrv $ServiceParameters = Get-ChildItem -Path HKLM:\SYSTEM\CurrentControlSet\Services -Recurse -Include 'Parameters' -ErrorAction SilentlyContinue $DriverParameters = $ServiceParameters | Where-Object { $_.Property -contains 'Rules' } if (-not $DriverParameters) { Write-Error 'Unable to locate a Sysmon driver. Either it is not installed or you do not have permissions to read the driver configuration in the registry.' return } $FoundSysmonMatch = $False $SysmonDriverName = $null $SysmonServiceName = $null $SysmonDriverParams = $null # Just in case there is more than one instance where there is a "Rules" value, correlate it with the user-mode service to confirm. $DriverParameters | ForEach-Object { $CandidateDriverName = $_.PSParentPath.Split('\')[-1] $CandidateDriverParams = $_ $CandidateUserModeServices = $ServiceParameters | Where-Object { $_.Property -contains 'DriverName' } if (-not $CandidateUserModeServices) { Write-Error 'Unable to locate a user-mode Sysmon service.' return } $CandidateUserModeServices | ForEach-Object { $CandidateServiceName = $_.PSParentPath.Split('\')[-1] $DriverName = ($_ | Get-ItemProperty).DriverName # We have a matching user-mode Sysmon service and Sysmon driver. if ($DriverName -eq $CandidateDriverName) { $FoundSysmonMatch = $True $SysmonDriverName = $CandidateDriverName $SysmonServiceName = $CandidateServiceName $SysmonDriverParams = $CandidateDriverParams | Get-ItemProperty } } } if ($FoundSysmonMatch) { # HKLM\SYSTEM\CurrentControlSet\Services\<SYSMON_DRIVER_NAME>\Parameters $RuleBytes = $SysmonDriverParams.Rules # REG_BINARY $Options = $SysmonDriverParams.Options # REG_DWORD $HashingAlgorithmValue = $SysmonDriverParams.HashingAlgorithm # REG_DWORD $ProcessAccessMasks = $SysmonDriverParams.ProcessAccessMasks # REG_BINARY - No larger than size: 0x28 (0x28 / 4 == 10: unique masks to interpret alongside ProcessAccessNames) $ProcessAccessNames = $SysmonDriverParams.ProcessAccessNames # REG_MULTI_SZ - Can have no more than 10 entries $CheckRevocation = $SysmonDriverParams.CheckRevocation # REG_BINARY of size: 1 byte # The high-order bit of HashingAlgorithm must be set to 1 (i.e. 0x80000000) $HashingAlgorithms = if ($HashingAlgorithmValue) { if ($HashingAlgorithmValue -band 1) { 'SHA1' } if ($HashingAlgorithmValue -band 2) { 'MD5' } if ($HashingAlgorithmValue -band 4) { 'SHA256' } if ($HashingAlgorithmValue -band 8) { 'IMPHASH' } } $NetworkConnection = $False if ($Options -band 1) { $NetworkConnection = $True } $ImageLoading = $False if ($Options -band 2) { $ImageLoading = $True } $CRLChecking = $False if (($CheckRevocation.Count -gt 0) -and ($CheckRevocation[0] -eq 1)) { $CRLChecking = $True } # Parse the binary rules blob. $Rules = ConvertFrom-SysmonBinaryConfiguration -RuleBytes $RuleBytes $ProcessAccess = $False if ($Rules.Events.EventType -contains 'ProcessAccess') { $ProcessAccess = $True } # Process ProcessAccessNames and ProcessAccessMasks. # The code path to actually use these appears to be a dead one now. # I'm only parsing this to mirror Sysmon 6.20 supporting parsing. $ProcessAccessList = New-Object -TypeName PSObject[]($ProcessAccessNames.Count) for ($i = 0; $i -lt $ProcessAccessNames.Count; $i++) { $ProcessAccessList[$i] = [PSCustomObject] @{ ProcessName = $ProcessAccessNames[$i] AccessMask = [BitConverter]::ToInt32($ProcessAccessMasks, $i * 4) } } $Properties = [Ordered] @{ PSTypeName = 'Sysmon.Configuration' ServiceName = $SysmonServiceName DriverName = $SysmonDriverName HashingAlgorithms = $HashingAlgorithms NetworkConnectionEnabled = $NetworkConnection ImageLoadingEnabled = $ImageLoading CRLCheckingEnabled = $CRLChecking ProcessAccessEnabled = $ProcessAccess ProcessAccess = $ProcessAccessList SchemaVersion = $Rules.SchemaVersion.ToString(2) ConfigBlobSHA256Hash = $Rules.ConfigBlobSHA256Hash Rules = $Rules.Events } # Don't print the ProcessAccess property if it's not populated. With Sysmon 6.20, this # should never be present anyway unless there's a stale artifact from an older version. if ($ProcessAccessList.Count -eq 0) { $Properties.Remove('ProcessAccess') } if ($MatchExeOutput) { $NetworkConnectionString = if ($NetworkConnection) { 'enabled' } else { 'disabled' } $ImageLoadingString = if ($ImageLoading) { 'enabled' } else { 'disabled' } $CRLCheckingString = if ($CRLChecking) { 'enabled' } else { 'disabled' } $ProcessAccessString = if ($ProcessAccess) { 'enabled' } else { 'disabled' } if ($ProcessAccessList) { $ProcessAccessString = ($ProcessAccessList | ForEach-Object { "`"$($_.ProcessName)`":0x$($_.AccessMask.ToString('x'))" }) -join ',' } $AllRuleText = $Rules.Events | ForEach-Object { # Dumb hacks to format output to the original "sysmon.exe -c" output $EventType = $_.EventType if ($EventType.StartsWith('RegistryEvent')) { $EventType = 'RegistryEvent' } if ($EventType.StartsWith('WmiEvent')) { $EventType = 'WmiEvent' } if ($EventType.StartsWith('PipeEvent')) { $EventType = 'PipeEvent' } $RuleText = $_.Rules | ForEach-Object { $FilterText = switch ($_.Filter) { 'Is' { 'is' } 'IsNot' { 'is not' } 'Contains' { 'contains' } 'Excludes' { 'excludes' } 'BeginWith' { 'begin with' } 'EndWith' { 'end with' } 'LessThan' { 'less than' } 'MoreThan' { 'more than' } 'Image' { 'image' } } "`t{0,-30} filter: {1,-12} value: '{2}'" -f $_.RuleType, $FilterText, $_.RuleText } $RuleSet = @" - {0,-34} onmatch: {1} {2} "@ -f $EventType, $_.OnMatch.ToLower(), ($RuleText | Out-String).TrimEnd("`r`n") $RuleSet.TrimEnd("`r`n") } $ConfigOutput = @" Current configuration: {0,-34}{1} {2,-34}{3} {4,-34}{5} {6,-34}{7} {8,-34}{9} {10,-34}{11} {12,-34}{13} Rule configuration (version {14}): {15} "@ -f ' - Service name:', $SysmonServiceName, ' - Driver name:', $SysmonDriverName, ' - HashingAlgorithms:', ($HashingAlgorithms -join ','), ' - Network connection:', $NetworkConnectionString, ' - Image loading:', $ImageLoadingString, ' - CRL checking:', $CRLCheckingString, ' - Process Access:', $ProcessAccessString, "$($Rules.SchemaVersion.Major).$($Rules.SchemaVersion.Minor.ToString().PadRight(2, '0'))", ($AllRuleText | Out-String).TrimEnd("`r`n") $ConfigOutput } else { [PSCustomObject] $Properties } } else { Write-Error 'Unable to locate a Sysmon driver and user-mode service.' } } |