Skip to content

Instantly share code, notes, and snippets.

@mattifestation
Last active December 19, 2023 10:23
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mattifestation/0669a250e64b2578819908e2b84acabb to your computer and use it in GitHub Desktop.
Save mattifestation/0669a250e64b2578819908e2b84acabb to your computer and use it in GitHub Desktop.
Script I use to make sense of code integrity audit/enforcement events for primarily baselining purposes.
function Get-CodeIntegrityEvent {
<#
.SYNOPSIS
Returns code integrity event log audit/enforcement events in a more human-readable fashion.
.DESCRIPTION
Get-CodeIntegrityEvent retrieves and parses Microsoft-Windows-CodeIntegrity/Operational PE audit and enforcement events into a format that is more human-readable. This function is designed to facilitate regular code integrity policy baselining.
Author: Matthew Graeber
License: BSD 3-Clause
.PARAMETER User
Specifies that only user-mode events should be returned. If neither -User nor -Kernel is specified, user and kernel events are returned.
.PARAMETER Kernel
Specifies that only kernel-mode events should be returned. If neither -User nor -Kernel is specified, user and kernel events are returned.
.PARAMETER Audit
Specifies that only audit events (event ID 3076) should be returned. If neither -Audit nor -Enforce is specified, audit and enforcement events are returned.
.PARAMETER Enforce
Specifies that only enforcement events (event ID 3077) should be returned. If neither -Audit nor -Enforce is specified, audit and enforcement events are returned.
.PARAMETER SinceLastPolicyRefresh
Specifies that events should only be returned since the last time the code integrity policy was refreshed. This option is useful for baselining purposes.
.PARAMETER MaxEvents
Specifies the maximum number of events that Get-CodeIntegrityEvent returns. The default is to return all the events.
.EXAMPLE
Get-CodeIntegrityEvent -SinceLastPolicyRefresh
Return all code integrity events (user/kernel/audit/enforcement) since the last code intgrity policy refresh.
.EXAMPLE
Get-CodeIntegrityEvent -User -SinceLastPolicyRefresh
Return all user-mode code integrity events (audit/enforcement) since the last code intgrity policy refresh.
.EXAMPLE
Get-CodeIntegrityEvent -Kernel -MaxEvents 5
Return the most recent 5 kernel mode code integrity events.
.EXAMPLE
Get-CodeIntegrityEvent -Kernel -Enforce
Return all kernel mode enforcement events.
#>
[CmdletBinding()]
param (
[Switch]
$User,
[Switch]
$Kernel,
[Switch]
$Audit,
[Switch]
$Enforce,
[Switch]
$SinceLastPolicyRefresh,
[Switch]
$SkipSignerAndWhqlChecks,
[Int64]
$MaxEvents
)
# If neither -User nor -Kernel are supplied, do not filter based on signing scenario
# If -User and -Kernel are supplied, do not filter based on signing scenario
# Only filter in a mutually exclusive scenario.
$ScenarioFilter = ''
if ($User -and !$Kernel) {
# 1 == A user-mode rule triggered
$ScenarioFilter = " and EventData[Data[@Name='SI Signing Scenario'] = 1]"
} elseif ($Kernel -and !$User) {
# 2 == A kernel-mode rule triggered
$ScenarioFilter = " and EventData[Data[@Name='SI Signing Scenario'] = 0]"
}
# If neither -Audit nor -Enforce are supplied, do not filter based on event ID
# If -Audit and -Enforce are supplied, do not filter based on event ID
# Only filter in a mutually exclusive scenario.
$ModeFilter = '(EventID = 3076 or EventID = 3077)'
if ($Audit -and !$Enforce) {
# Event ID 3076 == an audit event
$ModeFilter = "EventID = 3076"
} elseif ($Enforce -and !$Audit) {
# Event ID 3077 == an enforcement event
$ModeFilter = "EventID = 3077"
}
$PolicyRefreshFilter = ''
if ($SinceLastPolicyRefresh) {
# Only consider failed audit events that occured after the last CI policy update (event ID 3099)
$LastPolicyUpdateEvent = Get-WinEvent -FilterHashtable @{ LogName = 'Microsoft-Windows-CodeIntegrity/Operational'; Id = 3099 } -MaxEvents 1 -ErrorAction Ignore
# Sometimes this event will not be present - e.g. if the log rolled since the last update.
if ($LastPolicyUpdateEvent) {
$DateTimeAfter = [Xml.XmlConvert]::ToString($LastPolicyUpdateEvent.TimeCreated.ToUniversalTime())
$PolicyRefreshFilter = " and TimeCreated[@SystemTime >= '$DateTimeAfter']"
} else {
Write-Verbose "No policy update event was present in the event log. Ignoring the -SinceLastPolicyRefresh switch."
}
}
$Filter = "*[System[$($ModeFilter)$($PolicyRefreshFilter)]$ScenarioFilter]"
Write-Verbose "XPath Filter: $Filter"
# File paths are often in the format of device path (e.g. \Device\HarddiskVolumeN\).
# Get-Partition is used to map the volume number to a partition so that file paths can be normalized.
$Partitions = Get-Partition
$PartitionMapping = @{}
foreach ($Partition in $Partitions) {
if ($Partition.DriveLetter) {
$PartitionMapping[$Partition.PartitionNumber.ToString()] = $Partition.DriveLetter
}
}
# This hashtable is used to resolve RequestedSigningLevel and ValidatedSigningLevel fields into human-readable properties
# For more context around signing levels, Alex Ionescu (@aionescu) has a great resource on them:
# http://www.alex-ionescu.com/?p=146
$SigningLevelMapping = @{
[Byte] 0x0 = 'Unchecked'
[Byte] 0x1 = 'Unsigned'
[Byte] 0x2 = 'Enterprise'
[Byte] 0x3 = 'Custom1'
[Byte] 0x4 = 'Authenticode'
[Byte] 0x5 = 'Custom2'
[Byte] 0x6 = 'Store'
[Byte] 0x7 = 'Antimalware'
[Byte] 0x8 = 'Microsoft'
[Byte] 0x9 = 'Custom4'
[Byte] 0xA = 'Custom5'
[Byte] 0xB = 'DynamicCodegen'
[Byte] 0xC = 'Windows'
[Byte] 0xD = 'WindowsProtectedProcessLight'
[Byte] 0xE = 'WindowsTcb'
[Byte] 0xF = 'Custom6'
}
$MaxEventArg = @{}
# Pass -MaxEvents through to Get-WinEvent
if ($MaxEvents) { $MaxEventArg = @{ MaxEvents = $MaxEvents } }
Get-WinEvent -LogName 'Microsoft-Windows-CodeIntegrity/Operational' -FilterXPath $Filter @MaxEventArg -ErrorAction Ignore | ForEach-Object {
if ($_.Version -lt 5) {
Write-Warning "Get-CodeIntegrityEvent does not support event version $($_.Version). Version 5+ is supported. Ensure you are running Windows 1903+."
}
switch ($_.Id) {
3076 { $EventType = 'Audit' }
3077 { $EventType = 'Enforce' }
default {
$EventType = $null
Write-Warning "Unsupported event type: $($_.Id)"
}
}
$WHQLFailed = $False
if (-not $SkipSignerAndWhqlChecks) {
# A correlated 3082 event indicates that WHQL verification failed
$WHQLEvent = Get-WinEvent -LogName 'Microsoft-Windows-CodeIntegrity/Operational' -FilterXPath "*[System[EventID = 3082 and Correlation[@ActivityID = '$($_.ActivityId.Guid)']]]" -ErrorAction Ignore
if ($WHQLEvent) { $WHQLFailed = $True }
}
$ResolvedSigners = $null
if (-not $SkipSignerAndWhqlChecks) {
# Retrieve correlated signer info (event ID 3089)
# Note: there may be more than one correlated signer event in the case of the file having multiple signers.
$SignerInfo = Get-WinEvent -LogName 'Microsoft-Windows-CodeIntegrity/Operational' -FilterXPath "*[System[EventID = 3089 and Correlation[@ActivityID = '$($_.ActivityId.Guid)']]]" -ErrorAction Ignore
$ResolvedSigners = $SignerInfo | ForEach-Object {
$SignatureTypeVal = $_.Properties[6].Value
# Note: these signature type mappings were determined based on inference.
switch ($SignatureTypeVal) {
0 { $SignatureType = 'Hash' }
1 { $SignatureType = 'Authenticode' }
4 { $SignatureType = 'Catalog' }
default {
$SignatureType = 'Unknown'
Write-Warning "Unknown signature type value: $SignatureTypeVal. Investigate what this might correspond to and update this function accordingly."
}
}
[PSCustomObject] @{
SignatureIndex = $_.Properties[1].Value
PageHash = $_.Properties[5].Value
SignatureType = $SignatureType
ValidatedSigningLevel = $SigningLevelMapping[$_.Properties[7].Value]
NotValidBefore = $_.Properties[11].Value
NotValidAfter = $_.Properties[12].Value
PublisherName = $_.Properties[14].Value
IssuerName = $_.Properties[16].Value
PublisherTBSHash = (($_.Properties[18].Value | ForEach-Object { '{0:X2}' -f $_ }) -join '')
IssuerTBSHash = (($_.Properties[20].Value | ForEach-Object { '{0:X2}' -f $_ }) -join '')
}
}
}
$SigningScenarioVal = $_.Properties[16].Value
switch ($SigningScenarioVal) {
0 { $Scenario = 'Driver' }
1 { $Scenario = 'UserMode' }
default {
$Scenario = 'Unknown'
Write-Warning "Unknown signing scenario value: $SigningScenarioVal. Investigate what this might correspond to and update this function accordingly."
}
}
$FilePath = $_.Properties[1].Value
$ResolvedFilePath = $null
# Make a best effort to resolve the device path to a normal path.
if ($FilePath -match '(?<Prefix>^\\Device\\HarddiskVolume(?<VolumeNumber>\d)\\)') {
$ResolvedFilePath = $FilePath.Replace($Matches['Prefix'], "$($PartitionMapping[$Matches['VolumeNumber']]):\")
} elseif ($FilePath.ToLower().StartsWith('system32')) {
$ResolvedFilePath = "$($Env:windir)$($FilePath.Substring(8))"
}
# If all else fails regarding path resolution, show a warning.
if ($ResolvedFilePath -and !(Test-Path -Path $ResolvedFilePath)) {
Write-Warning "The following file path was either not resolved properly or was not present on disk: $ResolvedFilePath"
}
$SHA1FileHash = $null
if ($_.Properties[11].Value -eq 20) {
$SHA1FileHash = ($_.Properties[12].Value | ForEach-Object { '{0:X2}' -f $_ }) -join ''
}
[PSCustomObject] @{
TimeCreated = $_.TimeCreated
EventType = $EventType
SigningScenario = $Scenario
FilePath = $FilePath
ResolvedFilePath = $ResolvedFilePath
SHA1FileHash = $SHA1FileHash
ProcessID = $_.ProcessId
ProcessName = $_.Properties[3].Value
RequestedSigningLevel = $SigningLevelMapping[$_.Properties[4].Value]
ValidatedSigningLevel = $SigningLevelMapping[$_.Properties[5].Value]
PolicyName = $_.Properties[18].Value
PolicyID = $_.Properties[20].Value
PolicyGUID = $_.Properties[32].Value.Guid.ToUpper()
OriginalFileName = $_.Properties[24].Value
InternalName = $_.Properties[26].Value
FileDescription = $_.Properties[28].Value
ProductName = $_.Properties[30].Value
FileVersion = $_.Properties[31].Value
FailedWHQL = $WHQLFailed
SignerInfo = $ResolvedSigners
}
}
}
@gd2009
Copy link

gd2009 commented Sep 9, 2021

how do i get this to run ? i tried running from powershell didnt work

@mattifestation
Copy link
Author

how do i get this to run ? i tried running from powershell didnt work

Hey, @gd2009. Can you supply the steps you went through to execute it? I suspect you may have just dot-sourced it without calling the Get-CodeIntegrityEvent function.

. .\WDACBaselining.ps1
Get-CodeIntegrityEvent

I recommend the version in the WDACTools module. This gist isn't maintained.

@mattifestation
Copy link
Author

It also won't return anything if you don't have any 3076 or 3077 events in the Microsoft-Windows-CodeIntegrity/Operational event log.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment