Scoring Applocker rules

To be able to audit Applocker rules created or hunt for human error, I wanted to have a simple way to score Applocker rules.
Are they good or evil? Are they tuned and specific enough to allow or block something?
Is there any rule that is more permissive or not well designed due to a human error?

  • Issue

My Google-fu didn’t find anything relevant to help me score Applocker rules 😦

  • Solution

How are rules scored?
I’ve tried to score various aspects of the rule, based on the type (Publisher, Path or Hash), the identity targeted, the action (Allow or Deny) and the conditions.
The code aims at finding and identifying some rules that are more permissive than others, so that it’s easier to spot them and pay attention to these rules.
All the functions scoring rules follow a simple principle. The higher score, the more dangerous/permissive the rule is actually.
There’s a limitation, the code doesn’t evaluate at all the Exceptions and worse, having conditions could have a negative impact on the score (lowering it).
The other limitation is that the code assumes that there isn’t an administrator introducing malicious rules.

How does it perform? Show me examples !

Import-Module -Name Applocker -SkipEditionCheck
Import-Module ~/Documents/ApplockerScore.psm1 -Verbose
Get-ApplockerPolicyRuleScore -Type Script -Rule '(Default Rule) All scripts'


You can see that there’s tab completion for the parameter. You just need to begin by the Type to be able to display the rule names on the 2nd parameter using tab completion.
The default rule that allows anything for an admin is score over 1000. It means it’s too permissive. It’s a Bypass rule for anything (a wildcard) for the members of the local Administrators group. It’s not the highest score you can get.

cd\
Import-Module -Name Applocker -SkipEditionCheck
Import-Module ~/Documents/ApplockerScore.psm1
Get-Help Get-ApplockerPolicyRuleScore -Full
$rule = Get-ApplockerPolicyRuleScore -Type Script -Rule '(Default Rule) All scripts' -Verbose
 Write-Verbose -Message "Final score for '$($rule.Name)' is $($rule.Score)" -Verbose
$rule.Object
$rule.Object | gm


It shows how the score is calculated and what was evaluated to score the rule.
There’s an embedded object property that is its XML definition converted back to its original object type.

Besides simple examples, can it be used against exported Applocker policies to XML files?

Yes, it can. You just need to use the internal private functions and load them.
Here’s a practical example on Ultimate AppLocker ByPass List from Oddvar Moe

Let’s say, you’ve downloaded his Github repo as a zip file, extracted it and loaded the internal functions.
You can now do the following:

Get-ChildItem -Path '~\Downloads\UltimateAppLockerByPassList-master\UltimateAppLockerByPassList-master\AppLocker-BlockPolicies\*.xml' |
ForEach-Object {
([xml](Get-Content -Path $_.FullName -ReadCount 0)).AppLockerPolicy.RuleCollection.ChildNodes |
Out-GridView -PassThru |
ForEach-Object {
$TypeScore = Get-ApplockerRuleTypeScore $_.OuterXML -Verbose
$ActionSore = Get-ApplockerRuleActionScore $_.OuterXML -Verbose
$IdScore = Get-ApplockerRuleUserOrGroupSidScore $_.OuterXML -Verbose
$ConditionScore = Get-ApplockerRuleConditionsScore $_.OuterXML -Verbose
[PSCustomObject]@{
Id = $_.Id
Name = $_.Name
Score = ($TypeScore+$IdScore+$ConditionScore)*$ActionSore
XML = $_.OuterXML
}
}
}

Here’s an example on the rule named ‘Signed by Skype’:

Here’s a 2nd example on the rule about MSHTA:

Where’s the code?

It’s stored in a psm1 file uploaded as gist on GitHub.com:

#Requires -Version 3.0
#region Score the identity
Function Get-ApplockerRuleUserOrGroupSidScore {
[CmdletBinding()]
[OutputType([int32])]
Param(
[Parameter(Mandatory)]
[Alias('UserOrGroupSid','SID')]
$InputObject
)
Begin {}
Process {
$InputObject |
ForEach-Object {
Switch -Regex ($_) {
'S-1-1-0' { # EveryOne
Write-Verbose -Message 'Applocker score: 50 ; Everyone'
50 ; break
}
'S-1-5-32-544' { # Administrators
Write-Verbose -Message 'Applocker score: 10 ; Administrators'
10 ; break
}
'S-1-5-32-545' { # Users
Write-Verbose -Message 'Applocker score: 10 ; Users'
10 ; break
}
# S-1-5-11 # Authenticated Users
# S-1-5-32-546 # Guests
# S-1-5-32-547 # Power Users
default {
Write-Verbose -Message 'Applocker score: Id is specific'
10
}
}
}
}
End {}
}
#endregion
#region Score the rule type
Function Get-ApplockerRuleTypeScore {
[CmdletBinding()]
[OutputType([int32])]
Param(
[Parameter(Mandatory)]
[string[]]$InputObject
)
Begin {}
Process {
$InputObject |
ForEach-Object {
Switch -Regex ($_) {
'^<FilePath(Rule|Condition)' { Write-Verbose -Message 'Applocker score: 100 ; Path type' ; 100 }
'^<FilePublisher(Rule|Condition)' { Write-Verbose -Message 'Applocker score: 50 ; Path publisher' ; 50}
'^<FileHash(Rule|Condition)' {Write-Verbose -Message 'Applocker score: 50 ; Path Hash' ; 50}
default {}
}
}
}
End {}
}
#endregion
#region Score the rule action
Function Get-ApplockerRuleActionScore {
[CmdletBinding()]
[OutputType([int32])]
Param(
[Parameter(Mandatory)]
[string[]]$InputObject
)
Begin {}
Process {
$InputObject |
ForEach-Object {
Switch -Regex ($_) {
'\sAction="Allow"' {
Write-Verbose -Message 'Applocker score: *1 ; Action is Allow'
1
}
'\sAction="Deny"' {
Write-Verbose -Message 'Applocker score: /10 ; Action is Deny'
0.1
}
default {}
}
}
}
End {}
}
#endregion
#region Score the rule conditions
Function Get-ApplockerRuleConditionsScore {
[CmdletBinding()]
[OutputType([int32])]
Param(
[Parameter(Mandatory)]
[string[]]$InputObject
)
Begin {}
Process {
$InputObject |
ForEach-Object {
($(
Switch -Regex ($_) {
'^<FilePath(Rule|Condition)' {
Write-Verbose -Message 'Applocker score: Path type'
# Depth: count the occurrence of '\'
$depth = 110 - (($_ -split '\\').Count)*10
Write-Verbose -Message "Depth score is $($depth)"
$depth
# Evaluate if there's a wildcard in the path
if ($_ -match '\\\*\\') {
50
}
Switch -Regex ($_) {
# Just a specific extension
# .exe, .com *.msi, msp mst ps1 cmd bat vbs js appx msix dll ocx
'\sPath="\*\.([eE][xX][eE]|[cC][oO][mM]|[mM][sS][iI]|[mM][sS][pP]|[mM][sS][tT]|[pP][sS]1|[cC][mM][dD]|[bB][aA][tT]|[vV][bB][sS]|[jJ][sS])"\s' {
Write-Verbose -Message "Path score: 500 (only a *.ext)"
500
}
# Path letter
'\sPath="(c|C):\\' {
Write-Verbose -Message "Path score: 100 (Path on C: drive)"
100
}
'\sPath="[abABd-zD-Z]:\\' {
Write-Verbose -Message "Path score: 100 (Path not on C: drive)"
50
}
# Extension: just a wildcard
'\sPath="\*\.\*"\s' { # *.*
Write-Verbose -Message "Path score: 1000 (only a *.*)"
1000 ; break
}
'\sPath="\*"\s' { # *
Write-Verbose -Message "Path score: 1000 (only a *)"
1000 ; break
}
default {}
}
}
'^<FilePublisher(Rule|Condition)' {
Write-Verbose -Message 'Applocker score: Path publisher'
Switch -Regex ($_) {
'PublisherName="\*"\sProductName="\*"\sBinaryName="\*"\>\<BinaryVersionRange\sLowSection="0\.0\.0\.0"\sHighSection="\*"' {
Write-Verbose -Message "Publisher score: 1000 (default Appx PublisherName=*)"
1000 ; break
}
'ProductName="\*"\sBinaryName="\*"\>\<BinaryVersionRange\sLowSection="\*"\sHighSection="\*"' {
Write-Verbose -Message "Publisher score: 500 (only a PublisherName)"
500 ; break
}
'\sBinaryName="\*"\>\<BinaryVersionRange\sLowSection="\*" HighSection="\*"' {
Write-Verbose -Message "Publisher score: 500 (PublisherName and ProductName)"
200 ; break
}
'BinaryVersionRange\sLowSection="\*"\sHighSection="\*"' {
Write-Verbose -Message "Publisher score: 100 (PublisherName and ProductName and FileName)"
100 ; break
}
default {
Write-Verbose -Message "Publisher score: 50 (default)"
50
}
}
}
'^<FileHash(Rule|Condition)' {
Write-Verbose -Message "Hash score: 50"
50
}
default {}
}
) | Measure-Object -Sum).Sum
}
}
End {}
}
#endregion
#region Convert to Object from Xml
Function Get-ApplockerRuleObjectFromXml {
[CmdletBinding()]
[OutputType([int32])]
Param(
[Parameter(Mandatory)]
[string[]]$InputObject
)
Begin {}
Process {
$InputObject |
ForEach-Object {
$o = $_
Switch -Regex ($_) {
'^<FilePathRule' {
try {
[Microsoft.Security.ApplicationId.PolicyManagement.PolicyModel.FilePathRule]::FromXml($_)
} catch {
Write-Warning -Message "Failed to translate $($o) to Applocker FilePathRule because $($_.Exception.Message)"
}
break
}
'^<FilePublisherRule' {
try {
[Microsoft.Security.ApplicationId.PolicyManagement.PolicyModel.FilePublisherRule]::FromXml($_)
} catch {
Write-Warning -Message "Failed to translate $($o) to Applocker FilePublisherRule because $($_.Exception.Message)"
}
break
}
'^<FileHashRule' {
try {
[Microsoft.Security.ApplicationId.PolicyManagement.PolicyModel.FileHashRule]::FromXml($_)
} catch {
Write-Warning -Message "Failed to translate $($o) to Applocker FilePublisherRule because $($_.Exception.Message)"
}
break
}
default {
Write-Warning -Message 'Failed to transtale input into an Applocker rule'
}
}
}
}
End {}
}
#endregion
Function Get-ApplockerPolicyRuleScore {
<#
.SYNOPSIS
Score an Applocker policy rule
.DESCRIPTION
Score an Applocker policy rule
.PARAMETER Type
Parameter that indicates the RuleCollectionType to find a rule
.PARAMETER Rule
Dynamic parameter that indicates the Name of the rule to score
.EXAMPLE
Get-ApplockerPolicyRuleScore -Type Script -Rule '(Default Rule) All scripts'
Id Name Score XML
-- ---- ----- ---
ed97d0cb-15ff-430f-b82c-8d7832957725 (Default Rule) All scripts 1210 <FilePathRule Id
.EXAMPLE
$rule = Get-ApplockerPolicyRuleScore -Type Script -Rule '(Default Rule) All scripts' -Verbose
Write-Verbose -Message "Final score for '$($rule.Name)' is $($rule.Score)" -Verbose
$rule.Object
VERBOSE: Dealing with Rule Collection type: Script
VERBOSE: Dealing with Rule Name: (Default Rule) All scripts
VERBOSE: Applocker score: 100 ; Path type
VERBOSE: Applocker score: *1 ; Action is Allow
VERBOSE: Applocker score: 10 ; Administrators
VERBOSE: Applocker score: Path type
VERBOSE: Depth score is 100
VERBOSE: Path score: 1000 (only a *)
VERBOSE: Final score for '(Default Rule) All scripts' is 1210
PathConditions : {*}
PathExceptions : {}
PublisherExceptions : {}
HashExceptions : {}
Id : ed97d0cb-15ff-430f-b82c-8d7832957725
Name : (Default Rule) All scripts
Description : Allows members of the local Administrators group to run all scripts.
UserOrGroupSid : S-1-5-32-544
Action : Allow
#>
[CmdletBinding()]
Param(
[ValidateSet('Exe','Script','Msi','Appx','Dll')]
[Parameter(Mandatory)]
[String]$Type
)
DynamicParam {
$Dictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary
#region helper function
Function New-ParameterAttributCollection {
[CmdletBinding(SupportsShouldProcess)]
Param(
[Switch]$Mandatory,
[Switch]$ValueFromPipeline,
[Switch]$ValueFromPipelineByPropertyName,
[String]$ParameterSetName,
[Parameter()]
[ValidateSet(
'Arguments','Count','Drive','EnumeratedArguments','Length','NotNull',
'NotNullOrEmpty','Pattern','Range','Script','Set','UserDrive'
)][string]$ValidateType,
[Parameter()]
$ValidationContent
)
Begin {}
Process {
if ($PSCmdlet.ShouldProcess('Create new Attribute')) {
$c = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$a = New-Object System.Management.Automation.ParameterAttribute
if ($Mandatory) {
$a.Mandatory = $true
}
if ($ValueFromPipeline) {
$a.ValueFromPipeline = $true
}
if ($ValueFromPipelineByPropertyName) {
$a.ValueFromPipelineByPropertyName=$true
}
if ($ParameterSetName) {
$a.ParameterSetName = $ParameterSetName
}
$c.Add($a)
if ($ValidateType -and $ValidationContent) {
try {
$c.Add((New-Object "System.Management.Automation.Validate$($ValidateType)Attribute"(
$ValidationContent
)))
} catch {
Throw $_
}
}
$c
}
}
End {}
}
#endregion
try {
$ApplockerPolicyXml = [xml](Get-AppLockerPolicy -Effective -Xml -ErrorAction Stop)
} catch {
Throw 'Failed to read the effective Applocker policy into XML'
}
#region param Rule
$Dictionary.Add(
'Rule',
(New-Object System.Management.Automation.RuntimeDefinedParameter(
'Rule',
[string],
(New-ParameterAttributCollection -Mandatory -ValidateType Set -ValidationContent (
$ApplockerPolicyXml.SelectNodes("/AppLockerPolicy/RuleCollection[@Type='$($PSBoundParameters['Type'])']").ChildNodes| ForEach-Object { $_.Name }
))
))
)
$Dictionary
}
Begin {}
Process {
Write-Verbose -Message "Dealing with Rule Collection type: $($PSBoundParameters['Type'])"
Write-Verbose -Message "Dealing with Rule Name: $($PSBoundParameters['Rule'])"
# Select node
$n = $ApplockerPolicyXml.SelectNodes("/AppLockerPolicy/RuleCollection[@Type='$($PSBoundParameters['Type'])']").ChildNodes |
Where-Object { $_.Name -eq "$($PSBoundParameters['Rule'])" }
try {
$TypeScore = Get-ApplockerRuleTypeScore -InputObject $n.OuterXml
$ActionSore = Get-ApplockerRuleActionScore -InputObject $n.OuterXml
$IdScore = Get-ApplockerRuleUserOrGroupSidScore -InputObject $n.OuterXml
$ConditionScore = Get-ApplockerRuleConditionsScore -InputObject $n.OuterXml
$r = [PSCustomObject]@{
Id = $n.Id
Name = $n.Name
Score = ($TypeScore+$IdScore+$ConditionScore)*$ActionSore
XML = $n.OuterXml
Object = Get-ApplockerRuleObjectFromXml -InputObject $n.OuterXml
}
Update-TypeData -TypeName 'Applocker.Rule.Score' -DefaultDisplayPropertySet 'Id','Name','Score','XML' -Force
$r.PSTypeNames.Insert(0,'Applocker.Rule.Score')
$r
} catch {
Throw "Something went wrong while scoring applocker rule: $($_.Exception.Message)"
}
}
End {}
} # endof Get-ApplockerPolicyRuleScore
Export-ModuleMember -Function 'Get-ApplockerPolicyRuleScore'

How to whitelist your AutoRuns artifacts

Have you ever wondered how can I create a whitelist of my known harmless AutoRuns artifacts?

There are various ways to filter and ignore known artifacts. It can be done either for hunting purposes and to spot more quickly what’s unknown that requires a review and (probably) further actions.

You can filter Autoruns artifacts by using the authenticode certificate, the SHA256 hash,…

All these methods will work but there are some known artifacts that are still difficult to filter out.

I’ll show here a way to deal with these artifacts and create a baseline or whitelist of known artifacts.

I’ve introduced a new function in the module to create a whitelist of artifacts stored in a .ps1 file.

You can directly send the output of the main Get-PSAutorun function to the new New-AutoRunsBaseLine so that a .ps1 file is created. This powershell script stores an array of artifcats stored as PSCustomobject.

You may wonder why a .ps1 file and not a json file. The reason is that you can use a code signing certificate to sign the created ps1 file using the built-in Set-AuthenticodeSignature cmdlet. It brings integrity. It’s a signed whitelist. Signing may also be required if you use the PowerShell constrained language mode.

You can for example store all the artifacts with the maximum properties available.

Get-PSAutorun -VerifyDigitalSignature -ShowFileHash|
New-AutoRunsBaseLine -Verbose

Later on, to see if there are any changes, you can use the second function I’ve introduced: Compare-AutoRunsBaseLine

Here’s an example where I duplicated an existing file and modified one of its properties: the ending digit of the version.

For hunting purposes, you may want to store a minimalist whitelist and use it as a filter to view what’s relevant.

Get-PSAutorun -VerifyDigitalSignature|
Where { -not($_.isOSbinary)}|
New-AutoRunsBaseLine

If you want to view what has been exported in the whitelist, you just need to run the script created.

~\Documents\PSAutoRunsBaseLine-20201102214715.ps1 |
Out-GridView -PassThru

Here’s an example where I selected only one difficult artifact to filter out from the grid output:

Here’s another example I use on my personal computer:

Get-PSAutorun -VerifyDigitalSignature -ShowFileHash |
Where {-not($_.Version)} | Where { -not($_.isOSbinary)} |
New-AutoRunsBaseLine -Verbose

If you want to see what that file contains, I’ve uploaded a version here.

If you think that these new functions are useful, please vote for them 😀

The above new functions are currently only part of the experimental branch of Autoruns repository on Github. It’s not signed digitally and the Autoruns.cat file has not been modified to reflect the changes of the main Autoruns files (.psd1 and psm1). It’s also not been published yet to the PowerShell Gallery. It all depends on your votes 😀 . If you think it’s useful, please let me know. If you think, it’s not, please let also me know. If you find issues with the code, please introduce it directly on Github using this link.

Enjoy and happy hunting 😎