├── LICENSE ├── PSSysmonTools ├── Code │ ├── ConfigurationMerger.ps1 │ ├── GeneratedCode.ps1 │ ├── SysmonRuleParser.ps1 │ └── SysmonSchemaValidator.ps1 ├── PSSysmonTools.psd1 ├── PSSysmonTools.psm1 ├── Schemas │ ├── SysmonConfigurationSchema_3_40.xsd │ ├── SysmonConfigurationSchema_4_00.xsd │ └── SysmonConfigurationSchema_4_10.xsd └── Tests │ ├── Module.Tests.ps1 │ ├── PSSysmonTools.Tests.ps1 │ ├── SampleConfigs │ ├── README.txt │ ├── Sysmon_3_40_Autogenerated.xml │ ├── Sysmon_3_40_Empty.xml │ ├── Sysmon_4_0_Autogenerated.xml │ └── Sysmon_4_0_Empty.xml │ └── SupportedSysmonBinaries │ ├── README.txt │ ├── Sysmon_6_20.exe │ ├── Sysmon_7_00.exe │ ├── Sysmon_7_01.exe │ └── Sysmon_8_00.exe ├── README.md └── SysmonRegFormat.pdf /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Matt Graeber 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /PSSysmonTools/Code/ConfigurationMerger.ps1: -------------------------------------------------------------------------------- 1 | function Merge-SysmonXMLConfiguration { 2 | <# 3 | .SYNOPSIS 4 | 5 | Merges one or more Sysmon XML configurations. 6 | 7 | .DESCRIPTION 8 | 9 | Merge-SysmonXMLConfiguration merges one or more Sysmon XML configurations into a reference policy. Having to merge Sysmon configurations allows you to maintain sets of smaller configs and then selectively merge them based on the specific environment/goals. 10 | 11 | Author: Matthew Graeber (@mattifestation) 12 | License: BSD 3-Clause 13 | 14 | Required Dependencies: Test-SysmonConfiguration 15 | GeneratedCode.ps1 16 | 17 | .PARAMETER ReferencePolicyPath 18 | 19 | Specifies a Sysmon XML configuration into which all other policies will be merged. 20 | 21 | .PARAMETER PolicyToMergePath 22 | 23 | Specifies one or more Sysmon XML configurations to merge into the reference policy. 24 | 25 | .PARAMETER ExcludeMergeComments 26 | 27 | Specifies that merge comments should be excluded from the resulting XML. 28 | 29 | .EXAMPLE 30 | 31 | Merge-SysmonXMLConfiguration -ReferencePolicyPath MasterPolicy.xml -PolicyToMergePath ('policy1.xml', 'policy2.xml') 32 | 33 | .EXAMPLE 34 | 35 | $PoliciesToMerge = ls .\sysmon_configs\*.xml 36 | Merge-SysmonXMLConfiguration -ReferencePolicyPath MasterPolicy.xml -PolicyToMergePath $PoliciesToMerge 37 | 38 | .EXAMPLE 39 | 40 | ls .\sysmon_configs\*.xml | Merge-SysmonXMLConfiguration -ReferencePolicyPath MasterPolicy.xml 41 | 42 | .INPUTS 43 | 44 | System.IO.FileInfo, System.String 45 | 46 | Accepts one or more Sysmon confguration XML files over the pipeline. 47 | 48 | .OUTPUTS 49 | 50 | System.String 51 | 52 | Outputs a String consisting of the merged policy. 53 | #> 54 | 55 | [OutputType([System.String])] 56 | [CmdletBinding()] 57 | param ( 58 | [Parameter(Mandatory = $True)] 59 | [String] 60 | $ReferencePolicyPath, 61 | 62 | [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] 63 | [Alias('FullName')] 64 | [String[]] 65 | $PolicyToMergePath, 66 | 67 | [Switch] 68 | $ExcludeMergeComments 69 | ) 70 | 71 | BEGIN { 72 | $ReferencePolicyFullPath = Resolve-Path -Path $ReferencePolicyPath 73 | 74 | Write-Verbose "Validating the reference policy XML against the Sysmon rule schema: $ReferencePolicyFullPath" 75 | $ReferencePolicyValidationResult = Test-SysmonConfiguration -Path $ReferencePolicyFullPath -ErrorAction Stop 76 | 77 | if (-not ($Script:SupportedSchemaVersions -contains $ReferencePolicyValidationResult.SchemaVersion)) { 78 | Write-Error "The reference policy XML ($ReferencePolicyFullPath) has an unsupported schema version: $($ReferencePolicyValidationResult.SchemaVersion). Supported schema versions are: $($Script:SupportedSchemaVersions -join ', ')" 79 | return 80 | } 81 | 82 | # Get the parsing code for the respective schema. 83 | # Code injection note: an attacker would be able to influence the schema version used. That would only influence what 84 | # non-injectible source code was supplied to Add-Type, however. $ConfigurationSchemaSource variables should always be 85 | # constant variables with script (i.e. module) scope. 86 | $SchemaSource = Get-Variable -Name "SysmonConfigSchemaSource_$($ReferencePolicyValidationResult.SchemaVersion.Replace('.', '_'))" -Scope Script -ValueOnly 87 | 88 | # Compile the parsing code 89 | Add-Type -TypeDefinition $SchemaSource -ReferencedAssemblies 'System.Xml' -ErrorAction Stop 90 | 91 | $NamespaceName = "Sysmon_$($ReferencePolicyValidationResult.SchemaVersion.Replace('.', '_'))" 92 | Write-Verbose $ReferencePolicyValidationResult.SchemaVersion 93 | Write-Verbose $NamespaceName 94 | # This will be used to deserialize all of the XML configs and to serialize the merged config. 95 | $XmlSerializer = New-Object -TypeName Xml.Serialization.XmlSerializer -ArgumentList ("$NamespaceName.Sysmon" -as [Type]), '' 96 | 97 | $XMLReader = New-Object -TypeName Xml.XmlTextReader -ArgumentList $ReferencePolicyFullPath 98 | 99 | $ReferenceSysmon = $XmlSerializer.Deserialize($XMLReader) -as "$NamespaceName.Sysmon" 100 | 101 | $XMLReader.Close() 102 | 103 | # Collect each property name implemented by the SysmonEventFiltering type - e.g. ProcessCreate, RegistryEvent, etc. 104 | $EventFilteringProperties = ("$NamespaceName.SysmonEventFiltering" -as [Type]).GetProperties().Name 105 | 106 | # Sysmon objects will be deserialized for each XML policy. 107 | $SysmonList = New-Object -TypeName "Collections.ObjectModel.Collection``1[$NamespaceName.Sysmon]" 108 | 109 | # It's possible that there is no EnvetFiltering instance in the reference policy - e.g. if merging with a blank policy. 110 | if ($null -eq $ReferenceSysmon.EventFiltering) { $ReferenceSysmon.EventFiltering = New-Object -TypeName "$NamespaceName.SysmonEventFiltering" } 111 | 112 | $SysmonList.Add($ReferenceSysmon) 113 | 114 | $CommentList = New-Object -TypeName "Collections.ObjectModel.Collection``1[System.String]" 115 | $LongestPathLength = $ReferencePolicyFullPath.Path.Length 116 | } 117 | 118 | PROCESS { 119 | # Deserialize each XML policy supplied via the -PolicyToMergePath parameter. 120 | foreach ($Policy in $PolicyToMergePath) { 121 | $PolicyFullPath = Resolve-Path $Policy 122 | 123 | if ($PolicyFullPath.Path.Length -gt $LongestPathLength) { $LongestPathLength = $PolicyFullPath.Path.Length } 124 | $CommentList.Add(" * $PolicyFullPath") 125 | 126 | # Each policy must pass XSD validation. 127 | Write-Verbose "Validating the following policy XML against the Sysmon rule schema: $PolicyFullPath" 128 | $ValidationResult = Test-SysmonConfiguration -Path $PolicyFullPath -ErrorAction Stop 129 | 130 | if ($ValidationResult.SchemaVersion -ne $ReferencePolicyValidationResult.SchemaVersion) { 131 | Write-Error "The schema version of $PolicyFullPath ($($ValidationResult.SchemaVersion)) does not match that of the reference configuration: $ReferencePolicyFullPath ($($ReferencePolicyValidationResult.SchemaVersion))" 132 | return 133 | } 134 | 135 | if ($ValidationResult.Validated) { 136 | $XMLReader = New-Object -TypeName Xml.XmlTextReader -ArgumentList $PolicyFullPath 137 | 138 | $SysmonList.Add(($XmlSerializer.Deserialize($XMLReader) -as "$NamespaceName.Sysmon")) 139 | 140 | $XMLReader.Close() 141 | } 142 | } 143 | } 144 | 145 | END { 146 | # Iterate over each event type - e.g. ProcessCreate, RegistryEvent, etc. 147 | foreach ($EventFilteringProperty in $EventFilteringProperties) { 148 | Write-Verbose "Iterating over $EventFilteringProperty events." 149 | 150 | # Group the "include" and "exclude" events of similar types together 151 | $EventGrouping = $SysmonList.EventFiltering."$EventFilteringProperty" | Group-Object -Property onmatch 152 | 153 | # Collect each property name implemented by each respective event type - e.g. UtcTime, Image, TargetObject, etc. 154 | $RulePropertyNames = ("$NamespaceName.SysmonEventFiltering$EventFilteringProperty" -as [Type]).GetProperties().Name | 155 | Where-Object { $_ -ne 'onmatch' } 156 | 157 | $Events = foreach ($Event in $EventGrouping) { 158 | # For example, imagine we are just going over ProcessCreate "include" rules here. 159 | # Here, we will need to collect all the rules for each property of the ProcessCreate event type. 160 | 161 | # i.e. "include" or "exclude" 162 | $OnMatchVal = $Event.Name 163 | 164 | # e.g. create a new instance of a ProcessCreate object. 165 | # This is where we will add the collected rules 166 | $EventInstance = New-Object -TypeName "$NamespaceName.SysmonEventFiltering$EventFilteringProperty" 167 | $EventInstance.onmatch = $OnMatchVal 168 | 169 | foreach ($RulePropertyName in $RulePropertyNames) { 170 | # Rules will be collected here. Their uniqueness will be determined 171 | # (i.e. de-duped) by using the result of GetHashCode() as the key. 172 | $UniqueRuleTable = @{} 173 | 174 | # This will be an array of rules of the same type corresponding to the current event type. 175 | $Event.Group."$RulePropertyName" | Where-Object { $null -ne $_ } | ForEach-Object { 176 | $RuleHashCode = "$($_.condition)$($_.Value)".GetHashCode() 177 | 178 | if (-not $UniqueRuleTable.ContainsKey($RuleHashCode)) { $UniqueRuleTable[$RuleHashCode] = $_ } 179 | } 180 | 181 | $UniqueRules = foreach ($Key in $UniqueRuleTable.Keys) { $UniqueRuleTable[$Key] } 182 | 183 | # So now we have a set of unique rules for a given rule type 184 | # e.g. this is the set of all unique TargetObject rules for a RegistryEvent "include" instance. 185 | $EventInstance."$RulePropertyName" = $UniqueRules 186 | } 187 | 188 | $EventInstance 189 | } 190 | 191 | # Ensure the event groups are typed properly. 192 | $Events = $Events -as "$NamespaceName.SysmonEventFiltering$EventFilteringProperty[]" 193 | 194 | $ReferenceSysmon.EventFiltering."$EventFilteringProperty" = $Events 195 | } 196 | 197 | $XmlWriter = $null 198 | 199 | try { 200 | $XmlWriterSetting = New-Object -TypeName Xml.XmlWriterSettings 201 | # A Sysmon XML config is not expected to have an XML declaration line. 202 | $XmlWriterSetting.OmitXmlDeclaration = $True 203 | $XmlWriterSetting.Indent = $True 204 | # Use two spaces in place of a tab character. 205 | $XmlWriterSetting.IndentChars = ' ' 206 | # Normalize newlines to CRLF. 207 | $XmlWriterSetting.NewLineHandling = [Xml.NewLineHandling]::Replace 208 | 209 | $XMlStringBuilder = New-Object -TypeName Text.StringBuilder 210 | 211 | $XmlWriter = [Xml.XmlWriter]::Create($XMlStringBuilder, $XmlWriterSetting) 212 | 213 | if (-not $ExcludeMergeComments) { 214 | $AdditionalPadLen = 6 215 | $PaddedFormatString = " {0,-$($LongestPathLength + $AdditionalPadLen)} " 216 | $XmlWriter.WriteComment($PaddedFormatString -f 'Merged Sysmon policy') 217 | $XmlWriter.WriteComment($PaddedFormatString -f ('=' * ($LongestPathLength + $AdditionalPadLen))) 218 | $XmlWriter.WriteComment($PaddedFormatString -f ' Reference policy:') 219 | $XmlWriter.WriteComment($PaddedFormatString -f " * $ReferencePolicyFullPath") 220 | $XmlWriter.WriteComment($PaddedFormatString -f ' Merged policies:') 221 | foreach ($String in $CommentList) { $XmlWriter.WriteComment($PaddedFormatString -f $String) } 222 | $XmlWriter.WriteComment($PaddedFormatString -f ('=' * ($LongestPathLength + $AdditionalPadLen))) 223 | } 224 | 225 | # This will strip any additional "xmlns" attributes from the root Sysmon element. 226 | $EmptyNamespaces = New-Object -TypeName Xml.Serialization.XmlSerializerNamespaces 227 | $EmptyNamespaces.Add('', '') 228 | 229 | $XmlSerializer.Serialize($XmlWriter, $ReferenceSysmon, $EmptyNamespaces) 230 | } catch { 231 | Write-Error $_ 232 | } finally { 233 | if ($XmlWriter) { $XmlWriter.Close() } 234 | } 235 | 236 | $XMlStringBuilder.ToString() 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /PSSysmonTools/Code/SysmonRuleParser.ps1: -------------------------------------------------------------------------------- 1 | function ConvertFrom-SysmonBinaryConfiguration { 2 | <# 3 | .SYNOPSIS 4 | 5 | Parses a binary Sysmon configuration. 6 | 7 | .DESCRIPTION 8 | 9 | ConvertFrom-SysmonBinaryConfiguration parses a binary Sysmon configuration. The configuration is typically stored in the registry at the following path: HKLM\SYSTEM\CurrentControlSet\Services\SysmonDrv\Parameters\Rules 10 | 11 | ConvertFrom-SysmonBinaryConfiguration currently only supports the following schema versions: 3.30, 3.40 12 | 13 | Author: Matthew Graeber (@mattifestation) 14 | License: BSD 3-Clause 15 | 16 | .PARAMETER RuleBytes 17 | 18 | Specifies the raw bytes of a Sysmon configuration from the registry. 19 | 20 | .EXAMPLE 21 | 22 | [Byte[]] $RuleBytes = Get-ItemPropertyValue -Path HKLM:\SYSTEM\CurrentControlSet\Services\SysmonDrv\Parameters -Name Rules 23 | ConvertFrom-SysmonBinaryConfiguration -RuleBytes $RuleBytes 24 | 25 | .OUTPUTS 26 | 27 | Sysmon.EventCollection 28 | 29 | Output a fully-parsed rule object including the hash of the rules blob. 30 | 31 | .NOTES 32 | 33 | ConvertFrom-SysmonBinaryConfiguration is designed to serve as a helper function for Get-SysmonConfiguration. 34 | #> 35 | 36 | [OutputType('Sysmon.EventCollection')] 37 | [CmdletBinding()] 38 | param ( 39 | [Parameter(Mandatory = $True)] 40 | [Byte[]] 41 | [ValidateNotNullOrEmpty()] 42 | $RuleBytes 43 | ) 44 | 45 | #region Define byte to string mappings. This may change across verions. 46 | $SupportedSchemaVersions = @( 47 | [Version] '3.30.0.0', 48 | [Version] '3.40.0.0', 49 | [Version] '4.00.0.0', 50 | [Version] '4.10.0.0' 51 | ) 52 | 53 | $EventConditionMapping = @{ 54 | 0 = 'Is' 55 | 1 = 'IsNot' 56 | 2 = 'Contains' 57 | 3 = 'Excludes' 58 | 4 = 'BeginWith' 59 | 5 = 'EndWith' 60 | 6 = 'LessThan' 61 | 7 = 'MoreThan' 62 | 8 = 'Image' 63 | } 64 | 65 | # The following value to string mappings were all pulled from 66 | # IDA and will require manual validation with with each new 67 | # Sysmon and schema version. Here's hoping they don't change often! 68 | $ProcessCreateMapping = @{ 69 | 0 = 'UtcTime' 70 | 1 = 'ProcessGuid' 71 | 2 = 'ProcessId' 72 | 3 = 'Image' 73 | 4 = 'CommandLine' 74 | 5 = 'CurrentDirectory' 75 | 6 = 'User' 76 | 7 = 'LogonGuid' 77 | 8 = 'LogonId' 78 | 9 = 'TerminalSessionId' 79 | 10 = 'IntegrityLevel' 80 | 11 = 'Hashes' 81 | 12 = 'ParentProcessGuid' 82 | 13 = 'ParentProcessId' 83 | 14 = 'ParentImage' 84 | 15 = 'ParentCommandLine' 85 | } 86 | 87 | $ProcessCreateMapping_4_00 = @{ 88 | 0 = 'UtcTime' 89 | 1 = 'ProcessGuid' 90 | 2 = 'ProcessId' 91 | 3 = 'Image' 92 | 4 = 'FileVersion' 93 | 5 = 'Description' 94 | 6 = 'Product' 95 | 7 = 'Company' 96 | 8 = 'CommandLine' 97 | 9 = 'CurrentDirectory' 98 | 10 = 'User' 99 | 11 = 'LogonGuid' 100 | 12 = 'LogonId' 101 | 13 = 'TerminalSessionId' 102 | 14 = 'IntegrityLevel' 103 | 15 = 'Hashes' 104 | 16 = 'ParentProcessGuid' 105 | 17 = 'ParentProcessId' 106 | 18 = 'ParentImage' 107 | 19 = 'ParentCommandLine' 108 | } 109 | 110 | $FileCreateTimeMapping = @{ 111 | 0 = 'UtcTime' 112 | 1 = 'ProcessGuid' 113 | 2 = 'ProcessId' 114 | 3 = 'Image' 115 | 4 = 'TargetFilename' 116 | 5 = 'CreationUtcTime' 117 | 6 = 'PreviousCreationUtcTime' 118 | } 119 | 120 | $NetworkConnectMapping = @{ 121 | 0 = 'UtcTime' 122 | 1 = 'ProcessGuid' 123 | 2 = 'ProcessId' 124 | 3 = 'Image' 125 | 4 = 'User' 126 | 5 = 'Protocol' 127 | 6 = 'Initiated' 128 | 7 = 'SourceIsIpv6' 129 | 8 = 'SourceIp' 130 | 9 = 'SourceHostname' 131 | 10 = 'SourcePort' 132 | 11 = 'SourcePortName' 133 | 12 = 'DestinationIsIpv6' 134 | 13 = 'DestinationIp' 135 | 14 = 'DestinationHostname' 136 | 15 = 'DestinationPort' 137 | 16 = 'DestinationPortName' 138 | } 139 | 140 | $SysmonServiceStateChangeMapping = @{ 141 | 0 = 'UtcTime' 142 | 1 = 'State' 143 | 2 = 'Version' 144 | 3 = 'SchemaVersion' 145 | } 146 | 147 | $ProcessTerminateMapping = @{ 148 | 0 = 'UtcTime' 149 | 1 = 'ProcessGuid' 150 | 2 = 'ProcessId' 151 | 3 = 'Image' 152 | } 153 | 154 | $DriverLoadMapping = @{ 155 | 0 = 'UtcTime' 156 | 1 = 'ImageLoaded' 157 | 2 = 'Hashes' 158 | 3 = 'Signed' 159 | 4 = 'Signature' 160 | 5 = 'SignatureStatus' 161 | } 162 | 163 | $ImageLoadMapping = @{ 164 | 0 = 'UtcTime' 165 | 1 = 'ProcessGuid' 166 | 2 = 'ProcessId' 167 | 3 = 'Image' 168 | 4 = 'ImageLoaded' 169 | 5 = 'Hashes' 170 | 6 = 'Signed' 171 | 7 = 'Signature' 172 | 8 = 'SignatureStatus' 173 | } 174 | 175 | $ImageLoadMapping_4_00 = @{ 176 | 0 = 'UtcTime' 177 | 1 = 'ProcessGuid' 178 | 2 = 'ProcessId' 179 | 3 = 'Image' 180 | 4 = 'ImageLoaded' 181 | 5 = 'FileVersion' 182 | 6 = 'Description' 183 | 7 = 'Product' 184 | 8 = 'Company' 185 | 9 = 'Hashes' 186 | 10 = 'Signed' 187 | 11 = 'Signature' 188 | 12 = 'SignatureStatus' 189 | } 190 | 191 | $CreateRemoteThreadMapping = @{ 192 | 0 = 'UtcTime' 193 | 1 = 'SourceProcessGuid' 194 | 2 = 'SourceProcessId' 195 | 3 = 'SourceImage' 196 | 4 = 'TargetProcessGuid' 197 | 5 = 'TargetProcessId' 198 | 6 = 'TargetImage' 199 | 7 = 'NewThreadId' 200 | 8 = 'StartAddress' 201 | 9 = 'StartModule' 202 | 10 = 'StartFunction' 203 | } 204 | 205 | $RawAccessReadMapping = @{ 206 | 0 = 'UtcTime' 207 | 1 = 'ProcessGuid' 208 | 2 = 'ProcessId' 209 | 3 = 'Image' 210 | 4 = 'Device' 211 | } 212 | 213 | $ProcessAccessMapping = @{ 214 | 0 = 'UtcTime' 215 | 1 = 'SourceProcessGUID' 216 | 2 = 'SourceProcessId' 217 | 3 = 'SourceThreadId' 218 | 4 = 'SourceImage' 219 | 5 = 'TargetProcessGUID' 220 | 6 = 'TargetProcessId' 221 | 7 = 'TargetImage' 222 | 8 = 'GrantedAccess' 223 | 9 = 'CallTrace' 224 | } 225 | 226 | $FileCreateMapping = @{ 227 | 0 = 'UtcTime' 228 | 1 = 'ProcessGuid' 229 | 2 = 'ProcessId' 230 | 3 = 'Image' 231 | 4 = 'TargetFilename' 232 | 5 = 'CreationUtcTime' 233 | } 234 | 235 | $RegistryEventCreateKeyMapping = @{ 236 | 0 = 'EventType' 237 | 1 = 'UtcTime' 238 | 2 = 'ProcessGuid' 239 | 3 = 'ProcessId' 240 | 4 = 'Image' 241 | 5 = 'TargetObject' 242 | } 243 | 244 | $RegistryEventSetValueMapping = @{ 245 | 0 = 'EventType' 246 | 1 = 'UtcTime' 247 | 2 = 'ProcessGuid' 248 | 3 = 'ProcessId' 249 | 4 = 'Image' 250 | 5 = 'TargetObject' 251 | 6 = 'Details' 252 | } 253 | 254 | $RegistryEventDeleteKeyMapping = @{ 255 | 0 = 'EventType' 256 | 1 = 'UtcTime' 257 | 2 = 'ProcessGuid' 258 | 3 = 'ProcessId' 259 | 4 = 'Image' 260 | 5 = 'TargetObject' 261 | 6 = 'NewName' 262 | } 263 | 264 | $FileCreateStreamHashMapping = @{ 265 | 0 = 'UtcTime' 266 | 1 = 'ProcessGuid' 267 | 2 = 'ProcessId' 268 | 3 = 'Image' 269 | 4 = 'TargetFilename' 270 | 5 = 'CreationUtcTime' 271 | 6 = 'Hash' 272 | } 273 | 274 | $SysmonConfigurationChangeMapping = @{ 275 | 0 = 'UtcTime' 276 | 1 = 'Configuration' 277 | 2 = 'ConfigurationFileHash' 278 | } 279 | 280 | $PipeEventCreatedMapping = @{ 281 | 0 = 'UtcTime' 282 | 1 = 'ProcessGuid' 283 | 2 = 'ProcessId' 284 | 3 = 'PipeName' 285 | 4 = 'Image' 286 | } 287 | 288 | $PipeEventConnectedMapping = @{ 289 | 0 = 'UtcTime' 290 | 1 = 'ProcessGuid' 291 | 2 = 'ProcessId' 292 | 3 = 'PipeName' 293 | 4 = 'Image' 294 | } 295 | 296 | $WmiEventFilterMapping = @{ 297 | 0 = 'EventType' 298 | 1 = 'UtcTime' 299 | 2 = 'Operation' 300 | 3 = 'User' 301 | 4 = 'EventNamespace' 302 | 5 = 'Name' 303 | 6 = 'Query' 304 | } 305 | 306 | $WmiEventConsumerMapping = @{ 307 | 0 = 'EventType' 308 | 1 = 'UtcTime' 309 | 2 = 'Operation' 310 | 3 = 'User' 311 | 4 = 'Name' 312 | 5 = 'Type' 313 | 6 = 'Destination' 314 | } 315 | 316 | $WmiEventConsumerToFilterMapping = @{ 317 | 0 = 'EventType' 318 | 1 = 'UtcTime' 319 | 2 = 'Operation' 320 | 3 = 'User' 321 | 4 = 'Consumer' 322 | 5 = 'Filter' 323 | } 324 | 325 | $EventTypeMapping = @{ 326 | 1 = @('ProcessCreate', $ProcessCreateMapping) 327 | 2 = @('FileCreateTime', $FileCreateTimeMapping) 328 | 3 = @('NetworkConnect', $NetworkConnectMapping) 329 | # SysmonServiceStateChange is not actually present in the schema. It is here for the sake of completeness. 330 | 4 = @('SysmonServiceStateChange', $SysmonServiceStateChangeMapping) 331 | 5 = @('ProcessTerminate', $ProcessTerminateMapping) 332 | 6 = @('DriverLoad', $DriverLoadMapping) 333 | 7 = @('ImageLoad', $ImageLoadMapping) 334 | 8 = @('CreateRemoteThread', $CreateRemoteThreadMapping) 335 | 9 = @('RawAccessRead', $RawAccessReadMapping) 336 | 10 = @('ProcessAccess', $ProcessAccessMapping) 337 | 11 = @('FileCreate', $FileCreateMapping) 338 | 12 = @('RegistryEventCreateKey', $RegistryEventCreateKeyMapping) 339 | 13 = @('RegistryEventSetValue', $RegistryEventSetValueMapping) 340 | 14 = @('RegistryEventDeleteKey', $RegistryEventDeleteKeyMapping) 341 | 15 = @('FileCreateStreamHash', $FileCreateStreamHashMapping) 342 | # SysmonConfigurationChange is not actually present in the schema. It is here for the sake of completeness. 343 | 16 = @('SysmonConfigurationChange', $SysmonConfigurationChangeMapping) 344 | 17 = @('PipeEventCreated', $PipeEventCreatedMapping) 345 | 18 = @('PipeEventConnected', $PipeEventConnectedMapping) 346 | 19 = @('WmiEventFilter', $WmiEventFilterMapping) 347 | 20 = @('WmiEventConsumer', $WmiEventConsumerMapping) 348 | 21 = @('WmiEventConsumerToFilter', $WmiEventConsumerToFilterMapping) 349 | } 350 | #endregion 351 | 352 | $RuleMemoryStream = New-Object -TypeName System.IO.MemoryStream -ArgumentList @(,$RuleBytes) 353 | 354 | $RuleReader = New-Object -TypeName System.IO.BinaryReader -ArgumentList $RuleMemoryStream 355 | 356 | # I'm noting here for the record that parsing could be slightly more robust to account for malformed 357 | # rule blobs. I'm writing this in my spare time so I likely won't put too much work into increased 358 | # parsing robustness. 359 | 360 | if ($RuleBytes.Count -lt 16) { 361 | $RuleReader.Dispose() 362 | $RuleMemoryStream.Dispose() 363 | throw 'Insufficient length to contain a Sysmon rule header.' 364 | } 365 | 366 | # This value should be either 0 or 1. 1 should be expected for a current Sysmon config. 367 | # A value of 1 indicates that offset 8 will contain the file offset to the first rule grouping. 368 | # A value of 0 should indicate that offset 8 will be the start of the first rule grouping. 369 | # Currently, I am just going to check that the value is 1 and throw an exception if it's not. 370 | $HeaderValue0 = $RuleReader.ReadUInt16() 371 | 372 | if ($HeaderValue0 -ne 1) { 373 | $RuleReader.Dispose() 374 | $RuleMemoryStream.Dispose() 375 | throw "Incorrect header value at offset 0x00. Expected: 1. Actual: $HeaderValue0" 376 | } 377 | 378 | # This value is expected to be 1. Any other value will indicate the presence of a "registry rule version" 379 | # that is incompatible with the current Sysmon schema version. A value other than 1 likely indicates the 380 | # presence of an old version of Sysmon. Any value besides 1 will not be supported in this script. 381 | $HeaderValue1 = $RuleReader.ReadUInt16() 382 | 383 | if ($HeaderValue1 -ne 1) { 384 | $RuleReader.Dispose() 385 | $RuleMemoryStream.Dispose() 386 | throw "Incorrect header value at offset 0x02. Expected: 1. Actual: $HeaderValue1" 387 | } 388 | 389 | $RuleGroupCount = $RuleReader.ReadUInt32() 390 | $RuleGroupBeginOffset = $RuleReader.ReadUInt32() 391 | 392 | $SchemaVersionMinor = $RuleReader.ReadUInt16() 393 | $SchemaVersionMajor = $RuleReader.ReadUInt16() 394 | 395 | $SchemaVersion = New-Object -TypeName System.Version -ArgumentList $SchemaVersionMajor, $SchemaVersionMinor, 0, 0 396 | 397 | Write-Verbose "Obtained the following schema version: $($SchemaVersion.ToString(2))" 398 | 399 | if (-not ($SupportedSchemaVersions -contains $SchemaVersion)) { 400 | $RuleReader.Dispose() 401 | $RuleMemoryStream.Dispose() 402 | throw "Unsupported schema version: $($SchemaVersion.ToString(2)). Schema version must be at least $($MinimumSupportedSchemaVersion.ToString(2))" 403 | } 404 | 405 | #region Perform offset updates depending upon the schema version here 406 | # This logic should be the first candidate for refactoring should the schema change drastically in the future. 407 | switch ($SchemaVersion.ToString(2)) { 408 | '4.0' { 409 | Write-Verbose 'Using schema version 4.00 updated offsets.' 410 | # ProcessCreate and ImageLoad values changed 411 | $EventTypeMapping[1][1] = $ProcessCreateMapping_4_00 412 | $EventTypeMapping[7][1] = $ImageLoadMapping_4_00 413 | } 414 | } 415 | #endregion 416 | 417 | $null = $RuleReader.BaseStream.Seek($RuleGroupBeginOffset, 'Begin') 418 | 419 | $EventCollection = for ($i = 0; $i -lt $RuleGroupCount; $i++) { 420 | $EventTypeValue = $RuleReader.ReadInt32() 421 | $EventType = $EventTypeMapping[$EventTypeValue][0] 422 | $EventTypeRuleTypes = $EventTypeMapping[$EventTypeValue][1] 423 | $OnMatchValue = $RuleReader.ReadInt32() 424 | 425 | $OnMatch = $null 426 | 427 | switch ($OnMatchValue) { 428 | 0 { $OnMatch = 'Exclude' } 429 | 1 { $OnMatch = 'Include' } 430 | default { $OnMatch = '?' } 431 | } 432 | 433 | $NextEventTypeOffset = $RuleReader.ReadInt32() 434 | $RuleCount = $RuleReader.ReadInt32() 435 | [PSObject[]] $Rules = New-Object -TypeName PSObject[]($RuleCount) 436 | 437 | # Parse individual rules here 438 | for ($j = 0; $j -lt $RuleCount; $j++) { 439 | $RuleType = $EventTypeRuleTypes[$RuleReader.ReadInt32()] 440 | $Filter = $EventConditionMapping[$RuleReader.ReadInt32()] 441 | $NextRuleOffset = $RuleReader.ReadInt32() 442 | $RuleTextLength = $RuleReader.ReadInt32() 443 | $RuleTextBytes = $RuleReader.ReadBytes($RuleTextLength) 444 | $RuleText = [Text.Encoding]::Unicode.GetString($RuleTextBytes).TrimEnd("`0") 445 | 446 | $Rules[$j] = [PSCustomObject] @{ 447 | PSTypeName = 'Sysmon.Rule' 448 | RuleType = $RuleType 449 | Filter = $Filter 450 | RuleText = $RuleText 451 | } 452 | 453 | $null = $RuleReader.BaseStream.Seek($NextRuleOffset, 'Begin') 454 | } 455 | 456 | [PSCustomObject] @{ 457 | PSTypeName = 'Sysmon.EventGroup' 458 | EventType = $EventType 459 | OnMatch = $OnMatch 460 | Rules = $Rules 461 | } 462 | 463 | $null = $RuleReader.BaseStream.Seek($NextEventTypeOffset, 'Begin') 464 | } 465 | 466 | $RuleReader.Dispose() 467 | $RuleMemoryStream.Dispose() 468 | 469 | # Calculate the hash of the binary rule blob 470 | $SHA256Hasher = New-Object -TypeName System.Security.Cryptography.SHA256CryptoServiceProvider 471 | $ConfigBlobSHA256Hash = ($SHA256Hasher.ComputeHash($RuleBytes) | ForEach-Object { $_.ToString('X2') }) -join '' 472 | 473 | [PSCustomObject] @{ 474 | PSTypeName = 'Sysmon.EventCollection' 475 | SchemaVersion = $SchemaVersion 476 | ConfigBlobSHA256Hash = $ConfigBlobSHA256Hash 477 | Events = $EventCollection 478 | } 479 | } 480 | 481 | function Get-SysmonConfiguration { 482 | <# 483 | .SYNOPSIS 484 | 485 | 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. 486 | 487 | .DESCRIPTION 488 | 489 | 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. 490 | 491 | Get-SysmonConfiguration has been tested with the following Sysmon versions: 6.20 492 | 493 | 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. 494 | 495 | Author: Matthew Graeber (@mattifestation) 496 | License: BSD 3-Clause 497 | 498 | Required Dependencies: ConvertFrom-SysmonBinaryConfiguration 499 | 500 | .PARAMETER MatchExeOutput 501 | 502 | 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. 503 | 504 | .EXAMPLE 505 | 506 | Get-SysmonConfiguration 507 | 508 | .EXAMPLE 509 | 510 | Get-SysmonConfiguration -MatchExeOutput 511 | 512 | .OUTPUTS 513 | 514 | Sysmon.Configuration 515 | 516 | Outputs a fully parsed Sysmon configuration including the hash of the registry rule blob for auditing purposes. 517 | 518 | System.String 519 | 520 | Outputs mirrored output from "sysmon.exe -c". 521 | 522 | .NOTES 523 | 524 | 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: 525 | 526 | 1) The Sysmon config XML that's generating the error (only schema versions 3.30 and later). 527 | 2) The version of Sysmon being used (only 6.20 and later). 528 | #> 529 | 530 | [OutputType('Sysmon.Configuration', ParameterSetName = 'PSOutput')] 531 | [OutputType([String], ParameterSetName = 'ExeOutput')] 532 | [CmdletBinding(DefaultParameterSetName = 'PSOutput')] 533 | param ( 534 | [Parameter(ParameterSetName = 'ExeOutput')] 535 | [Switch] 536 | $MatchExeOutput 537 | ) 538 | 539 | # Find the Sysmon driver based solely off the presence of the "Rules" value. 540 | # This is being done because the user can optionally specify a driver name other than the default: SysmonDrv 541 | $ServiceParameters = Get-ChildItem -Path HKLM:\SYSTEM\CurrentControlSet\Services -Recurse -Include 'Parameters' -ErrorAction SilentlyContinue 542 | $DriverParameters = $ServiceParameters | Where-Object { $_.Property -contains 'Rules' } 543 | 544 | if (-not $DriverParameters) { 545 | 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.' 546 | return 547 | } 548 | 549 | $FoundSysmonMatch = $False 550 | $SysmonDriverName = $null 551 | $SysmonServiceName = $null 552 | $SysmonDriverParams = $null 553 | 554 | # Just in case there is more than one instance where there is a "Rules" value, correlate it with the user-mode service to confirm. 555 | $DriverParameters | ForEach-Object { 556 | $CandidateDriverName = $_.PSParentPath.Split('\')[-1] 557 | $CandidateDriverParams = $_ 558 | 559 | $CandidateUserModeServices = $ServiceParameters | Where-Object { $_.Property -contains 'DriverName' } 560 | 561 | if (-not $CandidateUserModeServices) { 562 | Write-Error 'Unable to locate a user-mode Sysmon service.' 563 | return 564 | } 565 | 566 | $CandidateUserModeServices | ForEach-Object { 567 | $CandidateServiceName = $_.PSParentPath.Split('\')[-1] 568 | $DriverName = ($_ | Get-ItemProperty).DriverName 569 | 570 | # We have a matching user-mode Sysmon service and Sysmon driver. 571 | if ($DriverName -eq $CandidateDriverName) { 572 | $FoundSysmonMatch = $True 573 | $SysmonDriverName = $CandidateDriverName 574 | $SysmonServiceName = $CandidateServiceName 575 | $SysmonDriverParams = $CandidateDriverParams | Get-ItemProperty 576 | } 577 | } 578 | } 579 | 580 | if ($FoundSysmonMatch) { 581 | # HKLM\SYSTEM\CurrentControlSet\Services\\Parameters 582 | $RuleBytes = $SysmonDriverParams.Rules # REG_BINARY 583 | $Options = $SysmonDriverParams.Options # REG_DWORD 584 | $HashingAlgorithmValue = $SysmonDriverParams.HashingAlgorithm # REG_DWORD 585 | $ProcessAccessMasks = $SysmonDriverParams.ProcessAccessMasks # REG_BINARY - No larger than size: 0x28 (0x28 / 4 == 10: unique masks to interpret alongside ProcessAccessNames) 586 | $ProcessAccessNames = $SysmonDriverParams.ProcessAccessNames # REG_MULTI_SZ - Can have no more than 10 entries 587 | $CheckRevocation = $SysmonDriverParams.CheckRevocation # REG_BINARY of size: 1 byte 588 | 589 | # The high-order bit of HashingAlgorithm must be set to 1 (i.e. 0x80000000) 590 | $HashingAlgorithms = if ($HashingAlgorithmValue) { 591 | if ($HashingAlgorithmValue -band 1) { 'SHA1' } 592 | if ($HashingAlgorithmValue -band 2) { 'MD5' } 593 | if ($HashingAlgorithmValue -band 4) { 'SHA256' } 594 | if ($HashingAlgorithmValue -band 8) { 'IMPHASH' } 595 | } 596 | 597 | $NetworkConnection = $False 598 | if ($Options -band 1) { $NetworkConnection = $True } 599 | 600 | $ImageLoading = $False 601 | if ($Options -band 2) { $ImageLoading = $True } 602 | 603 | $CRLChecking = $False 604 | if (($CheckRevocation.Count -gt 0) -and ($CheckRevocation[0] -eq 1)) { $CRLChecking = $True } 605 | 606 | # Parse the binary rules blob. 607 | $Rules = ConvertFrom-SysmonBinaryConfiguration -RuleBytes $RuleBytes 608 | 609 | $ProcessAccess = $False 610 | if ($Rules.Events.EventType -contains 'ProcessAccess') { $ProcessAccess = $True } 611 | 612 | # Process ProcessAccessNames and ProcessAccessMasks. 613 | # The code path to actually use these appears to be a dead one now. 614 | # I'm only parsing this to mirror Sysmon 6.20 supporting parsing. 615 | $ProcessAccessList = New-Object -TypeName PSObject[]($ProcessAccessNames.Count) 616 | for ($i = 0; $i -lt $ProcessAccessNames.Count; $i++) { 617 | $ProcessAccessList[$i] = [PSCustomObject] @{ 618 | ProcessName = $ProcessAccessNames[$i] 619 | AccessMask = [BitConverter]::ToInt32($ProcessAccessMasks, $i * 4) 620 | } 621 | } 622 | 623 | $Properties = [Ordered] @{ 624 | PSTypeName = 'Sysmon.Configuration' 625 | ServiceName = $SysmonServiceName 626 | DriverName = $SysmonDriverName 627 | HashingAlgorithms = $HashingAlgorithms 628 | NetworkConnectionEnabled = $NetworkConnection 629 | ImageLoadingEnabled = $ImageLoading 630 | CRLCheckingEnabled = $CRLChecking 631 | ProcessAccessEnabled = $ProcessAccess 632 | ProcessAccess = $ProcessAccessList 633 | SchemaVersion = $Rules.SchemaVersion.ToString(2) 634 | ConfigBlobSHA256Hash = $Rules.ConfigBlobSHA256Hash 635 | Rules = $Rules.Events 636 | } 637 | 638 | # Don't print the ProcessAccess property if it's not populated. With Sysmon 6.20, this 639 | # should never be present anyway unless there's a stale artifact from an older version. 640 | if ($ProcessAccessList.Count -eq 0) { $Properties.Remove('ProcessAccess') } 641 | 642 | if ($MatchExeOutput) { 643 | 644 | $NetworkConnectionString = if ($NetworkConnection) { 'enabled' } else { 'disabled' } 645 | $ImageLoadingString = if ($ImageLoading) { 'enabled' } else { 'disabled' } 646 | $CRLCheckingString = if ($CRLChecking) { 'enabled' } else { 'disabled' } 647 | $ProcessAccessString = if ($ProcessAccess) { 'enabled' } else { 'disabled' } 648 | if ($ProcessAccessList) { 649 | $ProcessAccessString = ($ProcessAccessList | ForEach-Object { "`"$($_.ProcessName)`":0x$($_.AccessMask.ToString('x'))" }) -join ',' 650 | } 651 | 652 | $AllRuleText = $Rules.Events | ForEach-Object { 653 | # Dumb hacks to format output to the original "sysmon.exe -c" output 654 | $EventType = $_.EventType 655 | if ($EventType.StartsWith('RegistryEvent')) { $EventType = 'RegistryEvent' } 656 | if ($EventType.StartsWith('WmiEvent')) { $EventType = 'WmiEvent' } 657 | if ($EventType.StartsWith('PipeEvent')) { $EventType = 'PipeEvent' } 658 | 659 | $RuleText = $_.Rules | ForEach-Object { 660 | $FilterText = switch ($_.Filter) { 661 | 'Is' { 'is' } 662 | 'IsNot' { 'is not' } 663 | 'Contains' { 'contains' } 664 | 'Excludes' { 'excludes' } 665 | 'BeginWith' { 'begin with' } 666 | 'EndWith' { 'end with' } 667 | 'LessThan' { 'less than' } 668 | 'MoreThan' { 'more than' } 669 | 'Image' { 'image' } 670 | } 671 | 672 | "`t{0,-30} filter: {1,-12} value: '{2}'" -f $_.RuleType, $FilterText, $_.RuleText 673 | } 674 | 675 | $RuleSet = @" 676 | - {0,-34} onmatch: {1} 677 | {2} 678 | "@ -f $EventType, 679 | $_.OnMatch.ToLower(), 680 | ($RuleText | Out-String).TrimEnd("`r`n") 681 | 682 | $RuleSet.TrimEnd("`r`n") 683 | } 684 | 685 | 686 | $ConfigOutput = @" 687 | Current configuration: 688 | {0,-34}{1} 689 | {2,-34}{3} 690 | {4,-34}{5} 691 | {6,-34}{7} 692 | {8,-34}{9} 693 | {10,-34}{11} 694 | {12,-34}{13} 695 | 696 | Rule configuration (version {14}): 697 | {15} 698 | "@ -f ' - Service name:', 699 | $SysmonServiceName, 700 | ' - Driver name:', 701 | $SysmonDriverName, 702 | ' - HashingAlgorithms:', 703 | ($HashingAlgorithms -join ','), 704 | ' - Network connection:', 705 | $NetworkConnectionString, 706 | ' - Image loading:', 707 | $ImageLoadingString, 708 | ' - CRL checking:', 709 | $CRLCheckingString, 710 | ' - Process Access:', 711 | $ProcessAccessString, 712 | "$($Rules.SchemaVersion.Major).$($Rules.SchemaVersion.Minor.ToString().PadRight(2, '0'))", 713 | ($AllRuleText | Out-String).TrimEnd("`r`n") 714 | 715 | $ConfigOutput 716 | } else { 717 | [PSCustomObject] $Properties 718 | } 719 | } else { 720 | Write-Error 'Unable to locate a Sysmon driver and user-mode service.' 721 | } 722 | } 723 | 724 | function ConvertTo-SysmonXMLConfiguration { 725 | <# 726 | .SYNOPSIS 727 | 728 | Recovers a Sysmon XML configuration from a binary configuration. 729 | 730 | .DESCRIPTION 731 | 732 | ConvertTo-SysmonXMLConfiguration takes the parsed output from Get-SysmonConfiguration and converts it to an XML configuration. This function is useful for recovering lost Sysmon configurations or for performing reconnaisance. 733 | 734 | Author: Matthew Graeber (@mattifestation) 735 | License: BSD 3-Clause 736 | 737 | Required Dependencies: Get-SysmonConfiguration 738 | GeneratedCode.ps1 739 | 740 | .PARAMETER Configuration 741 | 742 | Specifies the parsed Sysmon configuration output from Get-SysmonConfiguration. 743 | 744 | .EXAMPLE 745 | 746 | Get-SysmonConfiguration | ConvertTo-SysmonXMLConfiguration 747 | 748 | .EXAMPLE 749 | 750 | $Configuration = Get-SysmonConfiguration 751 | ConvertTo-SysmonXMLConfiguration -Configuration $Configuration 752 | 753 | .INPUTS 754 | 755 | Sysmon.Configuration 756 | 757 | ConvertTo-SysmonXMLConfiguration accepts a single result from Get-SysmonConfiguration over the pipeline. Note: it will not accept input from Get-SysmonConfiguration when "-MatchExeOutput" is specified. 758 | 759 | .OUTPUTS 760 | 761 | System.String 762 | 763 | Outputs a Sysmon XML configuration document. 764 | #> 765 | 766 | [OutputType([String])] 767 | [CmdletBinding()] 768 | param ( 769 | [Parameter(Mandatory = $True, ValueFromPipeline = $True)] 770 | [PSTypeName('Sysmon.Configuration')] 771 | $Configuration 772 | ) 773 | 774 | $SchemaVersion = $Configuration.SchemaVersion 775 | 776 | # Get the parsing code for the respective schema. 777 | # Code injection note: an attacker would be able to influence the schema version used. That would only influence what 778 | # non-injectible source code was supplied to Add-Type, however. $ConfigurationSchemaSource variables should always be 779 | # constant variables with script (i.e. module) scope. 780 | $SchemaSource = Get-Variable -Name "SysmonConfigSchemaSource_$($SchemaVersion.Replace('.', '_'))" -Scope Script -ValueOnly 781 | 782 | # Compile the parsing code 783 | Add-Type -TypeDefinition $SchemaSource -ReferencedAssemblies 'System.Xml' -ErrorAction Stop 784 | 785 | $NamespaceName = "Sysmon_$($SchemaVersion.Replace('.', '_'))" 786 | 787 | # Create a base "Sysmon" object. This serves as the root node that will eventually be serialized to XML. 788 | $Sysmon = New-Object -TypeName "$NamespaceName.Sysmon" 789 | 790 | $Sysmon.schemaversion = $Configuration.SchemaVersion 791 | 792 | if ($Configuration.CRLCheckingEnabled) { $Sysmon.CheckRevocation = New-Object -TypeName "$NamespaceName.SysmonCheckRevocation" } 793 | 794 | # The hashing algorithms need to be lower case in the XML config. 795 | $Sysmon.HashAlgorithms = ($Configuration.HashingAlgorithms | ForEach-Object { $_.ToLower() }) -join ',' 796 | 797 | $ProcessAccessString = ($Configuration.ProcessAccess | ForEach-Object { "$($_.ProcessName):0x$($_.AccessMask.ToString('x'))" }) -join ',' 798 | if ($ProcessAccessString) { $Sysmon.ProcessAccessConfig = $ProcessAccessString } 799 | 800 | # Do not consider redundant event types. A well-formed binary Sysmon rule blob will have 801 | # identical RegistryEvent, PipeEvent, and WmiEvent rule entries as of config schema version 3.4[0] 802 | $EventTypesToExclude = @( 803 | 'RegistryEventSetValue', 804 | 'RegistryEventDeleteKey', 805 | 'PipeEventConnected', 806 | 'WmiEventConsumer', 807 | 'WmiEventConsumerToFilter' 808 | ) 809 | 810 | # Group rules by their respective event types - a requirement for 811 | # setting properties properly in the SysmonEventFiltering instance. 812 | $EventGrouping = $Configuration.Rules | 813 | Where-Object { -not ($EventTypesToExclude -contains $_.EventType) } | 814 | Group-Object -Property EventType 815 | 816 | # A configuration can technically not have any EventFiltering rules. 817 | if ($EventGrouping) { 818 | $Sysmon.EventFiltering = New-Object -TypeName "$NamespaceName.SysmonEventFiltering" 819 | 820 | foreach ($Event in $EventGrouping) { 821 | # The name of the event - e.g. ProcessCreate, FileCreate, etc. 822 | $EventName = $Event.Name 823 | 824 | # Normalize these event names. 825 | # Have a mentioned that I hate that these aren't unique names in Sysmon? 826 | switch ($EventName) { 827 | 'RegistryEventCreateKey' { $EventName = 'RegistryEvent' } 828 | 'PipeEventCreated' { $EventName = 'PipeEvent' } 829 | 'WmiEventFilter' { $EventName = 'WmiEvent' } 830 | } 831 | 832 | if ($Event.Count -gt 2) { 833 | Write-Error "There is more than two $EventName entries. This should not be possible." 834 | return 835 | } 836 | 837 | if (($Event.Count -eq 2) -and ($Event.Group[0].OnMatch -eq $Event.Group[1].OnMatch)) { 838 | Write-Error "The `"onmatch`" attribute values for the $EventName rules are not `"include`" and `"exclude`". This should not be possible." 839 | return 840 | } 841 | 842 | $Events = foreach ($RuleSet in $Event.Group) { 843 | # The dynamic typing that follows relies upon naming consistency in the schema serialization source code. 844 | $EventInstance = New-Object -TypeName "$NamespaceName.SysmonEventFiltering$EventName" -Property @{ 845 | onmatch = $RuleSet.OnMatch.ToLower() 846 | } 847 | 848 | $RuleDefs = @{} 849 | 850 | foreach ($Rule in $RuleSet.Rules) { 851 | $PropertyName = $Rule.RuleType 852 | # Since each property can be of a unique type, resolve it accordingly. 853 | $PropertyTypeName = ("$NamespaceName.SysmonEventFiltering$EventName" -as [Type]).GetProperty($PropertyName).PropertyType.FullName.TrimEnd('[]') 854 | 855 | if (-not $RuleDefs.ContainsKey($PropertyName)) { 856 | $RuleDefs[$PropertyName] = New-Object -TypeName "Collections.ObjectModel.Collection``1[$PropertyTypeName]" 857 | } 858 | 859 | $RuleInstance = New-Object -TypeName $PropertyTypeName 860 | # This needs to be lower case in the XML config. 861 | $RuleInstance.condition = $Rule.Filter.ToLower() 862 | # An exception is thrown here if the value has a space and it is being cast to an enum type. 863 | # Currently, "Protected Process" is the only instance. I'll need to refactor this if more instances arise. 864 | if ($Rule.RuleText -eq 'Protected Process') { $RuleInstance.Value = 'ProtectedProcess' } else { $RuleInstance.Value = $Rule.RuleText } 865 | 866 | $RuleDefs[$PropertyName].Add($RuleInstance) 867 | } 868 | 869 | # Set the collected rule properties accordingly. 870 | foreach ($PropertyName in $RuleDefs.Keys) { 871 | $EventInstance."$PropertyName" = $RuleDefs[$PropertyName] 872 | } 873 | 874 | $EventInstance 875 | } 876 | 877 | $EventPropertyName = $Events[0].GetType().Name.Substring('SysmonEventFiltering'.Length) 878 | $Sysmon.EventFiltering."$EventPropertyName" = $Events 879 | } 880 | } 881 | 882 | $XmlWriter = $null 883 | 884 | try { 885 | $XmlWriterSetting = New-Object -TypeName Xml.XmlWriterSettings 886 | # A Sysmon XML config is not expected to have an XML declaration line. 887 | $XmlWriterSetting.OmitXmlDeclaration = $True 888 | $XmlWriterSetting.Indent = $True 889 | # Use two spaces in place of a tab character. 890 | $XmlWriterSetting.IndentChars = ' ' 891 | # Normalize newlines to CRLF. 892 | $XmlWriterSetting.NewLineHandling = [Xml.NewLineHandling]::Replace 893 | 894 | $XMlStringBuilder = New-Object -TypeName Text.StringBuilder 895 | 896 | $XmlWriter = [Xml.XmlWriter]::Create($XMlStringBuilder, $XmlWriterSetting) 897 | 898 | $XmlSerializer = New-Object -TypeName Xml.Serialization.XmlSerializer -ArgumentList ("$NamespaceName.Sysmon" -as [Type]), '' 899 | # This will strip any additional "xmlns" attributes from the root Sysmon element. 900 | $EmptyNamespaces = New-Object -TypeName Xml.Serialization.XmlSerializerNamespaces 901 | $EmptyNamespaces.Add('', '') 902 | 903 | $XmlSerializer.Serialize($XmlWriter, $Sysmon, $EmptyNamespaces) 904 | } catch { 905 | Write-Error $_ 906 | } finally { 907 | if ($XmlWriter) { $XmlWriter.Close() } 908 | } 909 | 910 | $XMlStringBuilder.ToString() 911 | } 912 | -------------------------------------------------------------------------------- /PSSysmonTools/Code/SysmonSchemaValidator.ps1: -------------------------------------------------------------------------------- 1 | filter Test-SysmonConfiguration { 2 | <# 3 | .SYNOPSIS 4 | 5 | Validates a Sysmon configuration. 6 | 7 | .DESCRIPTION 8 | 9 | Test-SysmonConfiguration validates a Sysmon configuration XML document against its respective XML schema (present in the "Schemas" directory). 10 | 11 | The XML schemas and Test-SysmonConfiguration are designed to validate configurations without the need of Sysmon. Additionally, as of Sysmon 6.20, Sysmon does not expose a configuration schema publicly. There is a DTD schema embedded in the binary but the schema itself doesn't validate due to repeating RegistryEvent and WmiEvent elements. DTD schemas are also not nearly expressive enough nor do they permit code generation with xsd.exe. 12 | 13 | Author: Matthew Graeber (@mattifestation) 14 | License: BSD 3-Clause 15 | 16 | Required Dependencies: the XSDs in the "Schemas" directory. 17 | 18 | .PARAMETER Path 19 | 20 | Specifies the path to a Sysmon configuration XML. 21 | 22 | .EXAMPLE 23 | 24 | Test-SysmonConfiguration -Path sysmonconfig.xml 25 | 26 | .OUTPUTS 27 | 28 | Sysmon.XMLValidationResult 29 | 30 | Outputs an object consisting of the results of the schema validation. 31 | #> 32 | 33 | [OutputType('Sysmon.XMLValidationResult')] 34 | [CmdletBinding()] 35 | param ( 36 | [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] 37 | [String] 38 | [Alias('FullName')] 39 | [ValidateNotNullOrEmpty()] 40 | $Path 41 | ) 42 | 43 | $FullPath = Resolve-Path $Path 44 | 45 | $FileContents = Get-Content -Path $FullPath 46 | 47 | Write-Verbose "Attempting to parse the following file: $FullPath" 48 | 49 | if ($FileContents) { 50 | try { 51 | # First, attempt to extract the schemaversion attribute so that 52 | # the XML can be validated against the correct Sysmon XSD. 53 | $XMLContent = [Xml] $FileContents 54 | $SchemaVersion = $XMLContent.Sysmon.schemaversion 55 | 56 | if (-not $SchemaVersion) { 57 | Write-Error 'A "schemaversion" attribute value was not present in the specified XML.' 58 | return 59 | } 60 | 61 | switch ($SchemaVersion) { 62 | # Oddly, Sysmon interprets ".4" as ".40". It also ignores leading numbers after the second digit. 63 | '3.40' { 64 | $XSDPath = "$Script:ModuleBase\Schemas\SysmonConfigurationSchema_3_40.xsd" 65 | } 66 | 67 | '3.4' { 68 | $XSDPath = "$Script:ModuleBase\Schemas\SysmonConfigurationSchema_3_40.xsd" 69 | # Since Sysmon normalizes the schema version to 3.40, we'll do it here as well. 70 | $SchemaVersion = '3.40' 71 | } 72 | 73 | '4.00' { 74 | $XSDPath = "$Script:ModuleBase\Schemas\SysmonConfigurationSchema_4_00.xsd" 75 | } 76 | 77 | '4.0' { 78 | $XSDPath = "$Script:ModuleBase\Schemas\SysmonConfigurationSchema_4_00.xsd" 79 | $SchemaVersion = '4.00' 80 | } 81 | 82 | '4.1' { 83 | $XSDPath = "$Script:ModuleBase\Schemas\SysmonConfigurationSchema_4_10.xsd" 84 | $SchemaVersion = '4.10' 85 | } 86 | 87 | '4.10' { 88 | $XSDPath = "$Script:ModuleBase\Schemas\SysmonConfigurationSchema_4_10.xsd" 89 | } 90 | 91 | default { 92 | Write-Error "Schema version $SchemaVersion is not supported." 93 | return 94 | } 95 | } 96 | 97 | $SchemaVersion = ([Version] $SchemaVersion).ToString(2) 98 | 99 | Write-Verbose "Using the following schema version: $SchemaVersion" 100 | Write-Verbose "Using the following XSD: $XSDPath" 101 | 102 | # At this point, the XML can be validated against its respective schema. 103 | 104 | $SysmonConfigNamespace = 'urn:schemas-specterops.io:SysmonConfiguration' 105 | 106 | $XMLSettings = New-Object -TypeName Xml.XmlReaderSettings 107 | $XMLSettings.CheckCharacters = $True 108 | $XMLSettings.CloseInput = $True 109 | $XMLSettings.IgnoreWhitespace = $True 110 | $XMLSettings.NameTable = New-Object -TypeName Xml.NameTable 111 | $XMLNamespaceManager = New-Object -TypeName Xml.XmlNamespaceManager -ArgumentList $XMLSettings.NameTable 112 | 113 | # Since the namespace is not supplied in a Sysmon config, it needs to be specified here. 114 | $XMLNamespaceManager.AddNamespace([String]::Empty, $SysmonConfigNamespace) 115 | $XMLParserContext = New-Object -TypeName Xml.XmlParserContext -ArgumentList $XMLSettings.NameTable, $XMLNamespaceManager, $null, ([Xml.XmlSpace]::Default) 116 | 117 | $XMLSettings.ValidationType = [Xml.ValidationType]::Schema 118 | $null = $XMLSettings.Schemas.Add($SysmonConfigNamespace, $XSDPath) 119 | $XMLSettings.ValidationFlags = $XMLSettings.ValidationFlags -bor [Xml.Schema.XmlSchemaValidationFlags]::ReportValidationWarnings 120 | 121 | # Raise a "XMLValidationError" event consisting of error/warning context in the event of a failed validation 122 | $null = Register-ObjectEvent -InputObject $XMLSettings -EventName ValidationEventHandler -SourceIdentifier XMLValidator -Action { 123 | param($sender, [Xml.Schema.ValidationEventArgs] $e) 124 | 125 | New-Event -SourceIdentifier XMLValidationError -MessageData $e 126 | } 127 | 128 | $XMLReader = [Xml.XmlReader]::Create($FullPath.Path, $XMLSettings, $XMLParserContext) 129 | 130 | # Recursively read each element of the supplied XML. An validation errors will trigger the scriptblock above. 131 | while ($XMLReader.Read()) { } 132 | 133 | $XMLReader.Close() 134 | Unregister-Event -SourceIdentifier XMLValidator 135 | 136 | $ValidationSucceeded = $True 137 | 138 | # If there were any errors or warnings, surface them accordingly. 139 | Get-Event -SourceIdentifier XMLValidationError -ErrorAction SilentlyContinue | ForEach-Object { 140 | $ValidationEvent = $_ 141 | 142 | # Surfacing the validation error as a PowerShell error will 143 | # allow the user to decide how to handle the error via -ErrorAction 144 | switch ($ValidationEvent.MessageData.Severity) { 145 | 'Warning' { Write-Warning $ValidationEvent.MessageData.Message; $ValidationSucceeded = $False } 146 | 'Error' { Write-Error $ValidationEvent.MessageData.Message; $ValidationSucceeded = $False } 147 | } 148 | 149 | # Remove the event upon processing it. 150 | $ValidationEvent | Remove-Event 151 | } 152 | 153 | [PSCustomObject] @{ 154 | PSTypeName = 'Sysmon.XMLValidationResult' 155 | Validated = $ValidationSucceeded 156 | SchemaVersion = $SchemaVersion 157 | Path = $FullPath.Path 158 | } 159 | } catch { 160 | Write-Error $_ 161 | return 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /PSSysmonTools/PSSysmonTools.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | RootModule = 'PSSysmonTools.psm1' 3 | 4 | ModuleVersion = '0.2.2.0' 5 | 6 | GUID = '0f91cb8e-56b8-44c2-821c-bab1ad7c369a' 7 | 8 | Author = 'Matthew Graeber' 9 | 10 | Copyright = 'BSD 3-Clause' 11 | 12 | Description = 'A module for working with Sysinternals Sysmon.' 13 | 14 | PowerShellVersion = '3.0' 15 | 16 | # Functions to export from this module 17 | FunctionsToExport = @( 18 | 'Get-SysmonConfiguration', 19 | 'ConvertFrom-SysmonBinaryConfiguration', 20 | 'Test-SysmonConfiguration', 21 | 'ConvertTo-SysmonXMLConfiguration', 22 | 'Merge-SysmonXMLConfiguration' 23 | ) 24 | 25 | PrivateData = @{ 26 | 27 | PSData = @{ 28 | Tags = @('security', 'DFIR', 'defense') 29 | 30 | LicenseUri = 'https://github.com/mattifestation/PSSysmonTools/blob/master/LICENSE' 31 | 32 | ProjectUri = 'https://github.com/mattifestation/PSSysmonTools' 33 | 34 | ReleaseNotes = @' 35 | 36 | 0.2.4 37 | ----- 38 | Enhancements: 39 | * Added schemaversion 4.1 by Olaf Hartong 40 | 41 | 0.2.3 42 | ----- 43 | Bug fixes: 44 | * Uppercase hash algorithms are supported. Thanks, Carlos Perez! 45 | 46 | 0.2.2 47 | ----- 48 | Enhancements: 49 | * Added Pester tests to test across all supported versions of Sysmon 50 | 51 | Bug fixes: 52 | * Fixed version display inconsistency in Get-SysmonConfiguration -MatchExeOutput 53 | 54 | 0.2.1 55 | ----- 56 | Enhancements: 57 | * Updated to support Sysmon v7 and schema version 4.0 58 | 59 | 0.2.0 60 | ----- 61 | Enhancements: 62 | * Added ConvertTo-SysmonXMLConfiguration 63 | * Test-SysmonConfiguration now supports input from the pipeline. 64 | * Added Merge-SysmonXMLConfiguration 65 | 66 | 0.1.0 67 | ----- 68 | Enhancements: 69 | * PSSysmonTools is now a proper PowerShell module. 70 | * Added Get-SysmonConfiguration, ConvertFrom-SysmonBinaryConfiguration, Test-SysmonConfiguration 71 | * Added a Sysmon 3.4 configuration schema in the Schemas directory 72 | * Added a mostly autogenerated 3.4 schema instance to Tests\SampleConfigs\Sysmon_3_40_Autogenerated.xml 73 | '@ 74 | } 75 | 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /PSSysmonTools/PSSysmonTools.psm1: -------------------------------------------------------------------------------- 1 | Get-ChildItem "$PSScriptRoot\Code\*" -Include '*.ps1' | ForEach-Object { . $_.FullName } 2 | 3 | Set-Variable -Name ModuleBase -Option Constant -Scope Script -Value $PSScriptRoot 4 | Set-Variable -Name SupportedSchemaVersions -Option Constant -Scope Script -Value @( 5 | '3.40', 6 | '4.0', 7 | '4.1', 8 | '4.10' 9 | ) 10 | -------------------------------------------------------------------------------- /PSSysmonTools/Schemas/SysmonConfigurationSchema_3_40.xsd: -------------------------------------------------------------------------------- 1 |  2 | 7 | 8 | 9 | 10 | Represents the set of supported onmatch attribute values. 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Represents the set of supported condition attribute values. 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | Represents the set of supported condition attribute values in the case where it only makes sense to support "is" and "is not". 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | Represents the set of supported process integrity levels. 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | Represents the set of supported WMI events. 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | Represents the set of supported WMI operations. 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | Represents the set of supported digital signature validation results. 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | Represents a well-formatted GUID. 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | Represents a well-formatted hexadecimal string. Note that the hex digits must be upper case. 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | Represents the set of supported hash algorithms. 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | Represents a well-formatted major.minor version number where both number can be represented with up to two digits. 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | This type is applied to a Sysmon rule element where there are no constraints on the value. 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | This type is applied to a Sysmon rule element where the value is expected to be a GUID. 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | This type is applied to a Sysmon rule element where the value is expected to be a hexadecimal value. 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | This type is applied to a Sysmon rule element where the value is expected to be a specific process integrity level. 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | This type is applied to a Sysmon rule element where the value is expected to be a specific WMI operation. 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | This type is applied to a Sysmon rule element where the value is expected to be a specific WMI event. 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | This type is applied to a Sysmon rule element where the value is expected to be either "true" or "false". 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | This type is applied to a Sysmon rule element where the value is expected to be an unsigned integer. 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | This type is applied to a Sysmon rule element where the value is expected to be an unsigned short. 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | This type is applied to a Sysmon rule element where the value is expected to be a specific signature validation status. 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | Sysmon 3.4[0] schema 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | Specifies a set of events to trigger on. 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | Event ID: 1 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | Event ID: 2 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | Event ID: 3 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | Event ID: 5 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | Event ID: 6 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | Event ID: 7 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | Event ID: 8 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | Event ID: 9 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | Event ID: 10 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | Event ID: 11 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | Event ID: 12, 13, 14 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | Event ID: 15 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | Event ID: 17, 18 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | Event ID: 19, 20, 21 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | Specifies the hashes that will be calculated upon an event firing. 605 | 606 | 607 | 608 | 609 | 610 | 611 | Specifies the name of the Sysmon driver to be used upon installation of the Sysmon service. This is an alternative to the sysmon.exe "-d" switch. 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | The purpose of ProcessAccessConfig is unknown now that ProcessAccess event rules are supported. Perhaps this offers legacy schema support. 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | The purpose of PipeMonitoringConfig is unknown now that PipeEvent event rules are supported. Perhaps this offers legacy schema support. 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | CheckRevocation is an empty element where when present, indicates that certificate revocation checking should be performed. This is an alternative to the sysmon.exe "-r" switch. 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | Specifies the sysmon schema version number. 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | -------------------------------------------------------------------------------- /PSSysmonTools/Schemas/SysmonConfigurationSchema_4_00.xsd: -------------------------------------------------------------------------------- 1 |  2 | 7 | 8 | 9 | 10 | Represents the set of supported onmatch attribute values. 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Represents the set of supported condition attribute values. 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | Represents the set of supported condition attribute values in the case where it only makes sense to support "is" and "is not". 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | Represents the set of supported process integrity levels. 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | Represents the set of supported WMI events. 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | Represents the set of supported WMI operations. 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | Represents the set of supported digital signature validation results. 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | Represents a well-formatted GUID. 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | Represents a well-formatted hexadecimal string. Note that the hex digits must be upper case. 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | Represents the set of supported hash algorithms. 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | Represents a well-formatted major.minor version number where both number can be represented with up to two digits. 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | This type is applied to a Sysmon rule element where there are no constraints on the value. 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | This type is applied to a Sysmon rule element where the value is expected to be a GUID. 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | This type is applied to a Sysmon rule element where the value is expected to be a hexadecimal value. 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | This type is applied to a Sysmon rule element where the value is expected to be a specific process integrity level. 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | This type is applied to a Sysmon rule element where the value is expected to be a specific WMI operation. 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | This type is applied to a Sysmon rule element where the value is expected to be a specific WMI event. 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | This type is applied to a Sysmon rule element where the value is expected to be either "true" or "false". 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | This type is applied to a Sysmon rule element where the value is expected to be an unsigned integer. 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | This type is applied to a Sysmon rule element where the value is expected to be an unsigned short. 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | This type is applied to a Sysmon rule element where the value is expected to be a specific signature validation status. 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | Sysmon 4.0[0] schema 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | Specifies a set of events to trigger on. 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | Event ID: 1 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | Event ID: 2 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | Event ID: 3 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | Event ID: 5 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | Event ID: 6 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | Event ID: 7 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | Event ID: 8 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | Event ID: 9 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | Event ID: 10 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | Event ID: 11 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | Event ID: 12, 13, 14 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | Event ID: 15 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | Event ID: 17, 18 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | Event ID: 19, 20, 21 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | Specifies the hashes that will be calculated upon an event firing. 613 | 614 | 615 | 616 | 617 | 618 | 619 | Specifies the name of the Sysmon driver to be used upon installation of the Sysmon service. This is an alternative to the sysmon.exe "-d" switch. 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | The purpose of ProcessAccessConfig is unknown now that ProcessAccess event rules are supported. Perhaps this offers legacy schema support. 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | The purpose of PipeMonitoringConfig is unknown now that PipeEvent event rules are supported. Perhaps this offers legacy schema support. 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | CheckRevocation is an empty element where when present, indicates that certificate revocation checking should be performed. This is an alternative to the sysmon.exe "-r" switch. 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | Specifies the sysmon schema version number. 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | -------------------------------------------------------------------------------- /PSSysmonTools/Schemas/SysmonConfigurationSchema_4_10.xsd: -------------------------------------------------------------------------------- 1 |  2 | 7 | 8 | 9 | 10 | Represents the set of supported onmatch attribute values. 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Represents the set of supported condition attribute values. 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | Represents the set of supported condition attribute values in the case where it only makes sense to support "is" and "is not". 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | Represents the RuleName field values. 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | Represents the set of supported process integrity levels. 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | Represents the set of supported WMI events. 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | Represents the set of supported WMI operations. 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | Represents the set of supported digital signature validation results. 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | Represents a well-formatted GUID. 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | Represents a well-formatted hexadecimal string. Note that the hex digits must be upper case. 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | Represents the set of supported hash algorithms. 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | Represents a well-formatted major.minor version number where both number can be represented with up to two digits. 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | This type is applied to a Sysmon rule element where there are no constraints on the value. 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | This type is applied to a Sysmon rule element where the value is expected to be a GUID. 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | This type is applied to a Sysmon rule element where the value is expected to be a hexadecimal value. 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | This type is applied to a Sysmon rule element where the value is expected to be a specific process integrity level. 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | This type is applied to a Sysmon rule element where the value is expected to be a specific WMI operation. 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | This type is applied to a Sysmon rule element where the value is expected to be a specific WMI event. 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | This type is applied to a Sysmon rule element where the value is expected to be either "true" or "false". 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | This type is applied to a Sysmon rule element where the value is expected to be an unsigned integer. 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | This type is applied to a Sysmon rule element where the value is expected to be an unsigned short. 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | This type is applied to a Sysmon rule element where the value is expected to be a specific signature validation status. 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | Sysmon 4.1[0] schema 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | Specifies a set of events to trigger on. 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | Event ID: 1 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | Event ID: 2 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | Event ID: 3 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | Event ID: 5 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | Event ID: 6 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | Event ID: 7 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | Event ID: 8 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | Event ID: 9 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | Event ID: 10 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | Event ID: 11 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | Event ID: 12, 13, 14 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | Event ID: 15 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | Event ID: 17, 18 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | Event ID: 19, 20, 21 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | Specifies the hashes that will be calculated upon an event firing. 634 | 635 | 636 | 637 | 638 | 639 | 640 | Specifies the name of the Sysmon driver to be used upon installation of the Sysmon service. This is an alternative to the sysmon.exe "-d" switch. 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | The purpose of ProcessAccessConfig is unknown now that ProcessAccess event rules are supported. Perhaps this offers legacy schema support. 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | The purpose of PipeMonitoringConfig is unknown now that PipeEvent event rules are supported. Perhaps this offers legacy schema support. 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | CheckRevocation is an empty element where when present, indicates that certificate revocation checking should be performed. This is an alternative to the sysmon.exe "-r" switch. 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | Specifies the sysmon schema version number. 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | -------------------------------------------------------------------------------- /PSSysmonTools/Tests/Module.Tests.ps1: -------------------------------------------------------------------------------- 1 | Set-StrictMode -Version Latest 2 | 3 | $TestScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent 4 | $ModuleRoot = Resolve-Path "$TestScriptRoot\.." 5 | $ModuleManifest = "$ModuleRoot\PSSysmonTools.psd1" 6 | 7 | Remove-Module [P]SSysmonTools 8 | $Module = Import-Module $ModuleManifest -Force -ErrorAction Stop -PassThru 9 | 10 | Describe 'Module-wide tests' -Tags 'Module' { 11 | $FunctionsList = $Module.ExportedCommands.Keys 12 | 13 | foreach ($Function in $FunctionsList) 14 | { 15 | # Retrieve the Help of the function 16 | $Help = Get-Help -Name $Function -Full 17 | 18 | # Parse the function using AST 19 | $AST = [Management.Automation.Language.Parser]::ParseInput((Get-Content function:$Function), [ref]$null, [ref]$null) 20 | 21 | Context "Exported command parameters for $Function" { 22 | It 'should have have an OutputType attribute' { 23 | $OutputTypeAttribute = $AST.ParamBlock.Attributes | Where-Object { $_.TypeName.Name -eq 'OutputType' } | Select -First 1 24 | $OutputTypeAttribute | Should Not BeNullOrEmpty 25 | $OutputTypeAttribute.TypeName.Name | Should BeExactly 'OutputType' 26 | $OutputTypeAttribute.PositionalArguments | Should Not BeNullOrEmpty 27 | } 28 | } 29 | 30 | Context "Required exported command naming scheme for $Function" { 31 | It 'should have a "Sysmon" noun prefix' { 32 | $Module.ExportedCommands[$Function].Noun.Substring(0, 6) | Should BeExactly 'Sysmon' 33 | } 34 | } 35 | 36 | # Comment-based help tests derived from: 37 | # http://www.lazywinadmin.com/2016/05/using-pester-to-test-your-comment-based.html 38 | Context "Comment-based help for: $Function"{ 39 | It 'should contain a .SYNOPSIS block' { 40 | $Help.Synopsis | Should Not BeNullOrEmpty 41 | } 42 | 43 | It 'should contain a .DESCRIPTION block' { 44 | $Help.Description | Should Not BeNullOrEmpty 45 | } 46 | 47 | It 'should have an author listed' { 48 | $Help.Description.Text.Contains('Author:') | Should Be $True 49 | } 50 | 51 | It 'should contain a BSD license in the synopsis' { 52 | $Help.Description.Text.Contains('License: BSD 3-Clause') | Should Be $True 53 | } 54 | 55 | # Get the parameters declared in the Comment Based Help 56 | $HelpParameters = @($Help.Parameters.Parameter) 57 | 58 | # Get the parameters declared in the AST PARAM() Block 59 | $ASTParameters = @($AST.ParamBlock.Parameters.Name.Variablepath.Userpath) 60 | 61 | It 'should contain a matching number of .PARAMETER blocks for all defined parameters' { 62 | $NamedArgs = try { $AST.ParamBlock.Attributes.NamedArguments } catch { $null } 63 | 64 | if ($NamedArgs -and $NamedArgs.ArgumentName -contains 'SupportsShouldProcess') { 65 | $Count = $ASTParameters.Count + 2 # Accounting for -WhatIf and -Confirm 66 | } else { 67 | $Count = $ASTParameters.Count 68 | } 69 | 70 | $HelpParameters.Count | Should Be $Count 71 | } 72 | 73 | # Parameter Description 74 | $Help.Parameters.Parameter | ForEach-Object { 75 | if ($ASTParameters -contains $_.Name) { 76 | It "should contain a .PARAMETER block for the following parameter: $($_.Name)"{ 77 | $_.Description | Should Not BeNullOrEmpty 78 | } 79 | } 80 | } 81 | 82 | # Examples 83 | It 'should contain at least one .EXAMPLE block' { 84 | @($Help.Examples.Example.Code).Count | Should BeGreaterThan 0 85 | } 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /PSSysmonTools/Tests/PSSysmonTools.Tests.ps1: -------------------------------------------------------------------------------- 1 | $TestScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent 2 | $ModuleRoot = Resolve-Path "$TestScriptRoot\.." 3 | $ModuleManifest = "$ModuleRoot\PSSysmonTools.psd1" 4 | 5 | Remove-Module [P]SSysmonTools 6 | Import-Module $ModuleManifest -Force -ErrorAction Stop 7 | 8 | # Insert check to ensure that these tests are running elevated 9 | $IsRunningElevated = (New-Object -TypeName System.Security.Principal.WindowsPrincipal -ArgumentList ([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) 10 | 11 | if (-not $IsRunningElevated) { 12 | throw 'PSSysmonTools tests must run from an elevated PowerShell prompt.' 13 | } 14 | 15 | $BinToSchemaVerMapping = @{ 16 | '6.20' = '3.40' 17 | '7.00' = '4.0' 18 | '7.01' = '4.0' 19 | } 20 | 21 | $SysmonBinPath = Join-Path -Path $ModuleRoot -ChildPath 'Tests\SupportedSysmonBinaries' 22 | $SysmonBins = Get-ChildItem "$SysmonBinPath\Sysmon_*.exe" 23 | 24 | $SampleConfigPath = Join-Path -Path $ModuleRoot -ChildPath 'Tests\SampleConfigs' 25 | $SampleConfigs = Get-ChildItem "$SampleConfigPath\*.xml" 26 | 27 | $StdoutPath = Join-Path -Path $env:TEMP -ChildPath 'stdout.txt' 28 | 29 | # Delete any existing stdout.txt files. 30 | Remove-Item -Path $StdoutPath -Force -ErrorAction SilentlyContinue 31 | 32 | function Test-SysmonInstalled { 33 | # Find the Sysmon driver based solely off the presence of the "Rules" value. 34 | # This is being done because the user can optionally specify a driver name other than the default: SysmonDrv 35 | $ServiceParameters = Get-ChildItem -Path HKLM:\SYSTEM\CurrentControlSet\Services -Recurse -Include 'Parameters' -ErrorAction SilentlyContinue 36 | $DriverParameters = $ServiceParameters | Where-Object { $_.Property -contains 'Rules' } 37 | 38 | if (-not $DriverParameters) { 39 | 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.' 40 | return 41 | } 42 | 43 | $FoundSysmonMatch = $False 44 | $SysmonDriverName = $null 45 | $SysmonServiceName = $null 46 | $SysmonDriverParams = $null 47 | 48 | # Just in case there is more than one instance where there is a "Rules" value, correlate it with the user-mode service to confirm. 49 | $DriverParameters | ForEach-Object { 50 | $CandidateDriverName = $_.PSParentPath.Split('\')[-1] 51 | $CandidateDriverParams = $_ 52 | 53 | $CandidateUserModeServices = $ServiceParameters | Where-Object { $_.Property -contains 'DriverName' } 54 | 55 | $CandidateUserModeServices | ForEach-Object { 56 | $CandidateServiceName = $_.PSParentPath.Split('\')[-1] 57 | $DriverName = ($_ | Get-ItemProperty).DriverName 58 | 59 | # We have a matching user-mode Sysmon service and Sysmon driver. 60 | if ($DriverName -eq $CandidateDriverName) { 61 | $FoundSysmonMatch = $True 62 | $SysmonDriverName = $CandidateDriverName 63 | $SysmonServiceName = $CandidateServiceName 64 | $SysmonDriverParams = $CandidateDriverParams | Get-ItemProperty 65 | } 66 | } 67 | } 68 | 69 | [PSCustomObject] @{ 70 | SysmonInstalled = $FoundSysmonMatch 71 | ServiceName = $SysmonServiceName 72 | DriverName = $SysmonDriverName 73 | } 74 | } 75 | 76 | $SysmonInstallStatus = Test-SysmonInstalled 77 | 78 | # Before running any tests, ensure that Sysmon is not installed 79 | if ($SysmonInstallStatus.SysmonInstalled) { 80 | throw @" 81 | A Sysmon service and driver was found. You must manually uninstall the Sysmon service and driver before proceeding with these tests. 82 | Service name: $($SysmonInstallStatus.ServiceName) 83 | Driver name: $($SysmonInstallStatus.DriverName) 84 | "@ 85 | } 86 | 87 | Describe 'Test-SysmonConfiguration' { 88 | Context 'parameter validation' { 89 | It 'should accept -Path' { 90 | { Test-SysmonConfiguration -Path $SampleConfigs[0].FullName } | Should Not Throw 91 | 92 | $ValidationResult = Test-SysmonConfiguration -Path $SampleConfigs[0].FullName 93 | 94 | $ValidationResult | Should Not BeNullOrEmpty 95 | $ValidationResult.PSObject.TypeNames[0] | Should BeExactly 'Sysmon.XMLValidationResult' 96 | } 97 | 98 | It 'should accept pipeline input' { 99 | { $SampleConfigs | Test-SysmonConfiguration } | Should Not Throw 100 | } 101 | } 102 | 103 | Context 'expected behavior' { 104 | foreach ($SampleConfig in $SampleConfigs) { 105 | It "should have expected output for the following config: $($SampleConfig.FullName)" { 106 | $ValidationResult = Test-SysmonConfiguration -Path $SampleConfig.FullName 107 | 108 | $ExpectedVersion = ($SampleConfig.FullName.Split('_')[1..2]) -join '.' 109 | 110 | $ValidationResult.Validated | Should Be $True 111 | $ValidationResult.SchemaVersion | Should BeExactly $ExpectedVersion 112 | $ValidationResult.Path | Should BeExactly $SampleConfig.FullName 113 | } 114 | } 115 | 116 | It 'should throw an exception upon attempting to parse non XML' { 117 | { Get-ChildItem "$SampleConfigPath\README.txt" | Test-SysmonConfiguration -ErrorAction Stop } | Should Throw 118 | } 119 | } 120 | } 121 | 122 | # First validate if an existing instance of Sysmon is installed 123 | $SysmonBins | ForEach-Object { 124 | $CurrentSysmonVersion = $_.VersionInfo.FileVersion 125 | $SysmonPath = $_.FullName 126 | 127 | $SchemaVersion = $BinToSchemaVerMapping[$CurrentSysmonVersion] 128 | $CurrentConfig = $SampleConfigs | ? { $_.Name -eq "Sysmon_$($SchemaVersion.Replace('.', '_'))_Autogenerated.xml" } 129 | 130 | Describe "Sysmon $CurrentSysmonVersion Tests" { 131 | Context 'service installation' { 132 | It 'should not have an already installed service/driver' { 133 | # This would indicate that the last service installation failed to uninstall properly. 134 | # throw an exception in this case as testing cannot continue under this condition. 135 | $SysmonInstallStatus = Test-SysmonInstalled 136 | 137 | $SysmonInstallStatus.SysmonInstalled | Should Be $False 138 | } 139 | 140 | It "should install the service with an autogenerated test schema ($($CurrentConfig.Name))" { 141 | Start-Process -FilePath $SysmonPath -ArgumentList ('-NoLogo', '-i', $CurrentConfig.FullName) -Wait -RedirectStandardOutput $StdoutPath -NoNewWindow 142 | 143 | $StdoutResult = Get-Content $StdoutPath 144 | 145 | Remove-Item -Path $StdoutPath -Force -ErrorAction SilentlyContinue 146 | 147 | $StdoutResult[2] -match 'is already registered' | Should Be $False 148 | $StdoutResult[2] -match 'installed.' | Should Be $True 149 | $StdoutResult[3] -match 'installed.' | Should Be $True 150 | } 151 | } 152 | 153 | Describe 'Get-SysmonConfiguration' { 154 | Context 'parameter validation' { 155 | It 'should accept no arguments' { 156 | { Get-SysmonConfiguration } | Should Not Throw 157 | 158 | $Config = Get-SysmonConfiguration 159 | 160 | $Config | Should Not BeNullOrEmpty 161 | $Config.PSObject.TypeNames[0] | Should BeExactly 'Sysmon.Configuration' 162 | } 163 | 164 | It 'should accept -MatchExeOutput' { 165 | { Get-SysmonConfiguration } | Should Not Throw 166 | 167 | $Config = Get-SysmonConfiguration -MatchExeOutput 168 | 169 | $Config | Should Not BeNullOrEmpty 170 | $Config -is [String] | Should Be $True 171 | } 172 | } 173 | 174 | Context 'expected behavior' { 175 | It 'match XML config values' { 176 | $Config = Get-SysmonConfiguration 177 | 178 | $Config.ServiceName | Should Not BeNullOrEmpty 179 | $Config.DriverName | Should Not BeNullOrEmpty 180 | $Config.HashingAlgorithms | Should Not BeNullOrEmpty 181 | $Config.NetworkConnectionEnabled | Should Be $True 182 | $Config.ImageLoadingEnabled | Should Be $True 183 | $Config.CRLCheckingEnabled | Should Be $True 184 | $Config.ProcessAccessEnabled | Should Be $True 185 | $Config.ProcessAccess | Should Not BeNullOrEmpty 186 | $Config.SchemaVersion | Should BeExactly $SchemaVersion 187 | $Config.ConfigBlobSHA256Hash | Should Not BeNullOrEmpty 188 | $Config.Rules | Should Not BeNullOrEmpty 189 | } 190 | 191 | # This is the primary way in which all populated properties of Get-SysmonConfiguration are validated against sysmon.exe 192 | # This test is a little time-consuming because "sysmon.exe -c" takes a while to complete. 193 | It 'should match the output of "sysmon.exe -c" when -MatchExeOutput is supplied' { 194 | Start-Process -FilePath $SysmonPath -ArgumentList ('-NoLogo', '-c') -Wait -RedirectStandardOutput $StdoutPath -NoNewWindow 195 | 196 | $StdoutResult = Get-Content -Path $StdoutPath -Raw 197 | 198 | Remove-Item -Path $StdoutPath -Force -ErrorAction SilentlyContinue 199 | 200 | $OutputPath = Join-Path -Path $env:TEMP -ChildPath Result.txt 201 | 202 | # Save the output to a file and then read the contents back. This will make string comparison easier. 203 | Get-SysmonConfiguration -MatchExeOutput | Out-File -FilePath $OutputPath -Encoding ascii 204 | $ConfigText = Get-Content -Path $OutputPath -Raw 205 | 206 | Remove-Item -Path $OutputPath -Force -ErrorAction SilentlyContinue 207 | 208 | $StdoutResult | Should Not BeNullOrEmpty 209 | $ConfigText | Should Not BeNullOrEmpty 210 | $ConfigText | Should BeExactly $StdoutResult 211 | } 212 | } 213 | } 214 | 215 | Describe 'ConvertTo-SysmonXMLConfiguration' { 216 | Context 'parameter validation' { 217 | It 'should accept -Configuration' { 218 | { Get-SysmonConfiguration | ConvertTo-SysmonXMLConfiguration } | Should Not Throw 219 | 220 | $XMLConfig = Get-SysmonConfiguration | ConvertTo-SysmonXMLConfiguration 221 | 222 | $XMLConfig | Should Not BeNullOrEmpty 223 | $XMLConfig -is [String] | Should Be $True 224 | } 225 | } 226 | 227 | Context 'expected behavior' { 228 | It 'should output valid, parsable XML' { 229 | $XMLConfig = Get-SysmonConfiguration | ConvertTo-SysmonXMLConfiguration 230 | 231 | ([XML] $XMLConfig) -is [System.Xml.XmlDocument] | Should Be $True 232 | } 233 | 234 | It 'should validate against its respective configuration schema' { 235 | $OutputPath = Join-Path -Path $env:TEMP -ChildPath Result.xml 236 | 237 | Get-SysmonConfiguration | ConvertTo-SysmonXMLConfiguration | Out-File -FilePath $OutputPath -Encoding ascii 238 | 239 | $ValidationResult = Test-SysmonConfiguration -Path $OutputPath 240 | 241 | Remove-Item -Path $OutputPath -Force -ErrorAction SilentlyContinue 242 | 243 | $ValidationResult.Validated | Should Be $True 244 | $ValidationResult.SchemaVersion | Should BeExactly $SchemaVersion 245 | } 246 | 247 | # The autogenerated test was originally the output of ConvertTo-SysmonXMLConfiguration 248 | # I should validate the the recovered XML is identical to the original XML 249 | It 'should be identical to the original XML configuration' { 250 | $OutputPath = Join-Path -Path $env:TEMP -ChildPath Result.txt 251 | 252 | Get-SysmonConfiguration | ConvertTo-SysmonXMLConfiguration | Out-File -FilePath $OutputPath -Encoding ascii 253 | 254 | $RecoveredXMLText = Get-Content -Path $OutputPath -Raw 255 | 256 | Remove-Item -Path $OutputPath -Force -ErrorAction SilentlyContinue 257 | 258 | $OriginalXMlText = Get-Content -Path $CurrentConfig -Raw 259 | 260 | $RecoveredXMLText | Should BeExactly $OriginalXMlText 261 | } 262 | } 263 | } 264 | 265 | Describe 'Merge-SysmonXMLConfiguration' { 266 | Context 'parameter validation' { 267 | It 'should accept -Configuration' { 268 | { Merge-SysmonXMLConfiguration -ReferencePolicyPath $CurrentConfig.FullName -PolicyToMergePath $CurrentConfig.FullName } | Should Not Throw 269 | 270 | $MergedXMLConfig = Merge-SysmonXMLConfiguration -ReferencePolicyPath $CurrentConfig.FullName -PolicyToMergePath $CurrentConfig.FullName 271 | 272 | $MergedXMLConfig | Should Not BeNullOrEmpty 273 | $MergedXMLConfig -is [String] | Should Be $True 274 | $MergedXMLConfig.Substring(0,25) | Should BeExactly ' 2 | 3 | md5,sha1 4 | -------------------------------------------------------------------------------- /PSSysmonTools/Tests/SampleConfigs/Sysmon_4_0_Empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | md5,SHA1 4 | -------------------------------------------------------------------------------- /PSSysmonTools/Tests/SupportedSysmonBinaries/README.txt: -------------------------------------------------------------------------------- 1 | The Sysmon binaries present in this directory are used for compatibility testing purposes. Each binary present in this directory implies that PSSysmonTools supports the schema versions they support as well as the binary format stored in the registry. -------------------------------------------------------------------------------- /PSSysmonTools/Tests/SupportedSysmonBinaries/Sysmon_6_20.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattifestation/PSSysmonTools/4d2549b7d0b90e042a97285698557b046629c12d/PSSysmonTools/Tests/SupportedSysmonBinaries/Sysmon_6_20.exe -------------------------------------------------------------------------------- /PSSysmonTools/Tests/SupportedSysmonBinaries/Sysmon_7_00.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattifestation/PSSysmonTools/4d2549b7d0b90e042a97285698557b046629c12d/PSSysmonTools/Tests/SupportedSysmonBinaries/Sysmon_7_00.exe -------------------------------------------------------------------------------- /PSSysmonTools/Tests/SupportedSysmonBinaries/Sysmon_7_01.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattifestation/PSSysmonTools/4d2549b7d0b90e042a97285698557b046629c12d/PSSysmonTools/Tests/SupportedSysmonBinaries/Sysmon_7_01.exe -------------------------------------------------------------------------------- /PSSysmonTools/Tests/SupportedSysmonBinaries/Sysmon_8_00.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattifestation/PSSysmonTools/4d2549b7d0b90e042a97285698557b046629c12d/PSSysmonTools/Tests/SupportedSysmonBinaries/Sysmon_8_00.exe -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PSSysmonTools 2 | Sysmon Tools for PowerShell 3 | 4 | ## Implemented functions 5 | ### Get-SysmonConfiguration 6 | 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. 7 | ### ConvertFrom-SysmonBinaryConfiguration 8 | Parses a binary Sysmon configuration. ConvertFrom-SysmonBinaryConfiguration is designed to serve as a helper function for Get-SysmonConfiguration. 9 | ### Test-SysmonConfiguration 10 | Validates a Sysmon configuration. 11 | ### ConvertTo-SysmonXMLConfiguration 12 | Recovers a Sysmon XML configuration from a binary configuration. 13 | ### Merge-SysmonXMLConfiguration 14 | Merges one or more Sysmon XML configurations. 15 | 16 | Please refer to built-in help for each function for more information. 17 | 18 | ## Notes 19 | These PowerShell functions will need 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: 20 | 21 | 1) The Sysmon config XML that's generating the error (only schema versions 3.40 and later). 22 | 2) The version of Sysmon being used (only 6.20 and later). 23 | 24 | Also, please file feature requests in the form of GitHub issues! Thank you! 25 | -------------------------------------------------------------------------------- /SysmonRegFormat.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattifestation/PSSysmonTools/4d2549b7d0b90e042a97285698557b046629c12d/SysmonRegFormat.pdf --------------------------------------------------------------------------------