Functions/ConvertTo-SysmonXMLConfiguration.ps1
<#
.SYNOPSIS Recovers a Sysmon XML configuration from a binary configuration. .DESCRIPTION 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. Author: Matthew Graeber (@mattifestation) License: BSD 3-Clause Required Dependencies: Get-SysmonConfiguration GeneratedCode.ps1 .PARAMETER Configuration Specifies the parsed Sysmon configuration output from Get-SysmonConfiguration. .EXAMPLE Get-SysmonConfiguration | ConvertTo-SysmonXMLConfiguration .EXAMPLE $Configuration = Get-SysmonConfiguration ConvertTo-SysmonXMLConfiguration -Configuration $Configuration .INPUTS Sysmon.Configuration 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. .OUTPUTS System.String Outputs a Sysmon XML configuration document. #> function ConvertTo-SysmonXMLConfiguration { [OutputType([String])] [CmdletBinding()] param ( [Parameter(Mandatory = $True, ValueFromPipeline = $True)] [PSTypeName('Sysmon.Configuration')] $Configuration ) $SchemaVersion = $Configuration.SchemaVersion # Get the parsing code for the respective schema. # Code injection note: an attacker would be able to influence the schema version used. That would only influence what # non-injectible source code was supplied to Add-Type, however. $ConfigurationSchemaSource variables should always be # constant variables with script (i.e. module) scope. $SchemaSource = Get-Variable -Name "SysmonConfigSchemaSource_$($SchemaVersion.Replace('.', '_'))" -Scope Script -ValueOnly # Compile the parsing code Add-Type -TypeDefinition $SchemaSource -ReferencedAssemblies 'System.Xml' -ErrorAction Stop $NamespaceName = "Sysmon_$($SchemaVersion.Replace('.', '_'))" # Create a base "Sysmon" object. This serves as the root node that will eventually be serialized to XML. $Sysmon = New-Object -TypeName "$NamespaceName.Sysmon" $Sysmon.schemaversion = $Configuration.SchemaVersion if ($Configuration.CRLCheckingEnabled) { $Sysmon.CheckRevocation = New-Object -TypeName "$NamespaceName.SysmonCheckRevocation" } # The hashing algorithms need to be lower case in the XML config. $Sysmon.HashAlgorithms = ($Configuration.HashingAlgorithms | ForEach-Object { $_.ToLower() }) -join ',' $ProcessAccessString = ($Configuration.ProcessAccess | ForEach-Object { "$($_.ProcessName):0x$($_.AccessMask.ToString('x'))" }) -join ',' if ($ProcessAccessString) { $Sysmon.ProcessAccessConfig = $ProcessAccessString } # Do not consider redundant event types. A well-formed binary Sysmon rule blob will have # identical RegistryEvent, PipeEvent, and WmiEvent rule entries as of config schema version 3.4[0] $EventTypesToExclude = @( 'RegistryEventSetValue', 'RegistryEventDeleteKey', 'PipeEventConnected', 'WmiEventConsumer', 'WmiEventConsumerToFilter' ) # Group rules by their respective event types - a requirement for # setting properties properly in the SysmonEventFiltering instance. $EventGrouping = $Configuration.Rules | Where-Object { -not ($EventTypesToExclude -contains $_.EventType) } | Group-Object -Property EventType # A configuration can technically not have any EventFiltering rules. if ($EventGrouping) { $Sysmon.EventFiltering = New-Object -TypeName "$NamespaceName.SysmonEventFiltering" foreach ($Event in $EventGrouping) { # The name of the event - e.g. ProcessCreate, FileCreate, etc. $EventName = $Event.Name # Normalize these event names. # Have a mentioned that I hate that these aren't unique names in Sysmon? switch ($EventName) { 'RegistryEventCreateKey' { $EventName = 'RegistryEvent' } 'PipeEventCreated' { $EventName = 'PipeEvent' } 'WmiEventFilter' { $EventName = 'WmiEvent' } } if ($Event.Count -gt 2) { Write-Error "There is more than two $EventName entries. This should not be possible." return } if (($Event.Count -eq 2) -and ($Event.Group[0].OnMatch -eq $Event.Group[1].OnMatch)) { Write-Error "The `"onmatch`" attribute values for the $EventName rules are not `"include`" and `"exclude`". This should not be possible." return } $Events = foreach ($RuleSet in $Event.Group) { # The dynamic typing that follows relies upon naming consistency in the schema serialization source code. $EventInstance = New-Object -TypeName "$NamespaceName.SysmonEventFiltering$EventName" -Property @{ onmatch = $RuleSet.OnMatch.ToLower() } $RuleDefs = @{} foreach ($Rule in $RuleSet.Rules) { $PropertyName = $Rule.RuleType # Since each property can be of a unique type, resolve it accordingly. $PropertyTypeName = ("$NamespaceName.SysmonEventFiltering$EventName" -as [Type]).GetProperty($PropertyName).PropertyType.FullName.TrimEnd('[]') if (-not $RuleDefs.ContainsKey($PropertyName)) { $RuleDefs[$PropertyName] = New-Object -TypeName "Collections.ObjectModel.Collection``1[$PropertyTypeName]" } $RuleInstance = New-Object -TypeName $PropertyTypeName # This needs to be lower case in the XML config. $RuleInstance.condition = $Rule.Filter.ToLower() # An exception is thrown here if the value has a space and it is being cast to an enum type. # Currently, "Protected Process" is the only instance. I'll need to refactor this if more instances arise. if ($Rule.RuleText -eq 'Protected Process') { $RuleInstance.Value = 'ProtectedProcess' } else { $RuleInstance.Value = $Rule.RuleText } $RuleDefs[$PropertyName].Add($RuleInstance) } # Set the collected rule properties accordingly. foreach ($PropertyName in $RuleDefs.Keys) { $EventInstance."$PropertyName" = $RuleDefs[$PropertyName] } $EventInstance } $EventPropertyName = $Events[0].GetType().Name.Substring('SysmonEventFiltering'.Length) $Sysmon.EventFiltering."$EventPropertyName" = $Events } } $XmlWriter = $null try { $XmlWriterSetting = New-Object -TypeName Xml.XmlWriterSettings # A Sysmon XML config is not expected to have an XML declaration line. $XmlWriterSetting.OmitXmlDeclaration = $True $XmlWriterSetting.Indent = $True # Use two spaces in place of a tab character. $XmlWriterSetting.IndentChars = ' ' # Normalize newlines to CRLF. $XmlWriterSetting.NewLineHandling = [Xml.NewLineHandling]::Replace $XMlStringBuilder = New-Object -TypeName Text.StringBuilder $XmlWriter = [Xml.XmlWriter]::Create($XMlStringBuilder, $XmlWriterSetting) $XmlSerializer = New-Object -TypeName Xml.Serialization.XmlSerializer -ArgumentList ("$NamespaceName.Sysmon" -as [Type]), '' # This will strip any additional "xmlns" attributes from the root Sysmon element. $EmptyNamespaces = New-Object -TypeName Xml.Serialization.XmlSerializerNamespaces $EmptyNamespaces.Add('', '') $XmlSerializer.Serialize($XmlWriter, $Sysmon, $EmptyNamespaces) } catch { Write-Error $_ } finally { if ($XmlWriter) { $XmlWriter.Close() } } $XMlStringBuilder.ToString() } |