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'

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.