Skip to content

Instantly share code, notes, and snippets.

@hinchley
Created September 12, 2016 09:39
Show Gist options
  • Save hinchley/8e60753b3a76fea7a954468a159bc44f to your computer and use it in GitHub Desktop.
Save hinchley/8e60753b3a76fea7a954468a159bc44f to your computer and use it in GitHub Desktop.
A PowerShell script for generating AppLocker policies. Usage is discussed in the following article: http://hinchley.net/2016/06/13/an-approach-for-managing-microsoft-applocker-policies/
<# examples:
# generate a baseline policy.
powershell.exe -file applocker.ps1 -baseline -output c:\policies\baseline.xml
# generate an application-specific policy.
powershell.exe -file applocker.ps1 -application -in c:\policies\baseline.xml -out c:\policies\application.xml
# generate an adhoc policy.
powershell.exe -file applocker.ps1 -adhoc -in c:\path -filter *.* -out c:\policies\adhoc.xml
# merge policies.
powershell.exe -file applocker.ps1 -merge -in c:\policies -out c:\policies\merged.xml
#>
<# expected usage:
1. run the script to generate a baseline. e.g.
powershell.exe -file applocker.ps1 -baseline -out c:\policies\baseline.xml
2. install an application.
3. rerun the script to generate an application-specific policy. e.g.
powershell.exe -file applocker.ps1 -application -in c:\policies\baseline.xml -out c:\policies\someapp.xml
4. rerun the script to merge applocker policies under c:\policies. e.g.
powershell.exe -file applocker.ps1 -merge -in c:\policies -out c:\policies\soe.xml
#>
param(
[switch]$baseline,
[switch]$application,
[switch]$adhoc,
[switch]$merge,
[string]$in,
[string]$filter,
[string]$out
)
function log($message) { write-host -foregroundcolor green $message }
function error($message) { write-host -foregroundcolor magenta $message; exit }
# check baseline switches.
if ($baseline) {
if ($out -eq $null) { error 'Usage: powershell.exe -file applocker.ps1 -baseline -out c:\policies\baseline.xml' }
if (! (test-path (split-path $out -parent))) { error 'The output folder does not exist.' }
}
# check application switches.
if ($application) {
if ($in -eq $null -or $out -eq $null) { error 'powershell.exe -file applocker.ps1 -application -in c:\policies\baseline.xml -out c:\policies\application.xml' }
if (! (test-path $in)) { error 'The baseline policy does not exist.' }
if (! (test-path (split-path $out -parent))) { error 'The output folder does not exist.' }
}
# check adhoc switches.
if ($adhoc) {
if ($in -eq $null -or $filter -eq $null -or $out -eq $null) { error 'powershell.exe -file applocker.ps1 -adhoc -in c:\path -filter *.* -out c:\policies\adhoc.xml' }
if (! (test-path $in)) { error 'The input folder does not exist.' }
if (! (test-path (split-path $out -parent))) { error 'The output folder does not exist.' }
}
# check merge switches.
if ($merge) {
if ($in -eq $null -or $out -eq $null) { error 'powershell.exe -file applocker.ps1 -merge -in c:\policies -out c:\policies\merged.xml' }
if (! (test-path $in)) { error 'The input folder does not exist.' }
if (! (test-path (split-path $out -parent))) { error 'The output folder does not exist.' }
}
# we will trust all products published by the following vendors.
$vendors = "O=MICROSOFT CORPORATION, L=REDMOND, S=WASHINGTON, C=US", "O=CITRIX SYSTEMS, INC., L=SANTA CLARA, S=CALIFORNIA, C=US"
# directories to scan.
$paths = @('C:\Program Files', 'C:\Program Files (x86)')
# rule collection types.
$types = 'Appx', 'Dll', 'Exe', 'Msi', 'Script'
# empty applocker policy.
$xml = [xml]@'
<AppLockerPolicy Version="1">
<RuleCollection Type="Appx" EnforcementMode="Enabled" />
<RuleCollection Type="Dll" EnforcementMode="Enabled" />
<RuleCollection Type="Exe" EnforcementMode="Enabled" />
<RuleCollection Type="Msi" EnforcementMode="Enabled" />
<RuleCollection Type="Script" EnforcementMode="Enabled" />
</AppLockerPolicy>
'@
# empty publisher rule.
$publisher = [xml]@'
<FilePublisherRule Id="" Name="" Description="" UserOrGroupSid="S-1-1-0" Action="Allow">
<Conditions>
<FilePublisherCondition PublisherName="" ProductName="*" BinaryName="*">
<BinaryVersionRange LowSection="*" HighSection="*" />
</FilePublisherCondition>
</Conditions>
</FilePublisherRule>
'@
# empty file hash rule.
$hash = [xml]@'
<FileHashRule Id="" Name="Windows 10 Hash Rules" Description="" UserOrGroupSid="S-1-1-0" Action="Allow">
<Conditions>
<FileHashCondition />
</Conditions>
</FileHashRule>
'@
# return the "real" extension of a file.
function get-filetype($path) {
$extension = [io.path]::getextension($path)
$extensions = @('.dll', '.exe', '.js', '.msi', '.ps1', '.vbs')
# extensions of file types that are actually dlls.
$imposterdlls = @('.api', '.8bx')
if ($extensions -contains $extension) { return $extension }
# a hack for files which are dll's without a pe header.
if ($imposterdlls -contains $extension) { return '.dll' }
$bytes = get-content $path -readcount 0 -encoding byte
$offset = $bytes[0x3c]
$signature = [char[]] $bytes[$offset..($offset + 3)]
if ([string]::join('', $signature) -eq "PE`0`0") {
$header = $offset + 4
$data = [bitconverter]::toint32($bytes, $header + 18)
if ($data -band 0x2000) { return '.dll' } else { return '.exe' }
}
return $extension
}
# insert a rule based on $template, with a rule type of $class, under $parent, into $policy.
function insert-rule($policy, $parent, $template, $class) {
$rule = $template.clone()
$rule.$class.id = [string]([guid]::newguid().guid)
return $parent.appendchild($policy.importnode($rule.$class, $true))
}
# insert $hashes into $policy under $parent.
function insert-hashes($policy, $hashes, $parent) {
$hashes | %{ [void]$parent.conditions.firstchild.appendchild($policy.importnode($_, $true)) }
}
# insert $publishers into $policy under $parent.
function insert-publishers($policy, $publishers, $parent) {
$publishers | %{
$_.conditions.filepublishercondition.binaryname = '*'
$_.conditions.filepublishercondition.binaryversionrange.lowsection = '*'
$_.conditions.filepublishercondition.binaryversionrange.highsection = '*'
[void]$parent.appendchild($policy.importnode($_, $true))
}
}
# insert $paths into $policy under $parent.
function insert-paths($policy, $paths, $parent) {
$paths | %{ [void]$parent.appendchild($policy.importnode($_, $true)) }
}
# insert conditions ($hashes and $publishers) into $policy under $parent.
function insert-conditions($policy, $parent, $hashes, $publishers) {
if ($hashes.count) {
$node = insert-rule $policy $parent $hash 'filehashrule'
insert-hashes $policy $hashes $node
}
if ($publishers.count) { insert-publishers $policy $publishers $parent }
}
# merge policy $one with $two and return union.
function merge-policies($one, $two) {
$target = $xml.clone()
foreach ($type in $types) {
$hashes = @($one.applockerpolicy.selectnodes("//RuleCollection[@Type='$type']//FileHash") +
$two.applockerpolicy.selectnodes("//RuleCollection[@Type='$type']//FileHash") | sort-object -property data -unique)
$publishers = @($one.applockerpolicy.selectnodes("//RuleCollection[@Type='$type']/FilePublisherRule") +
$two.applockerpolicy.selectnodes("//RuleCollection[@Type='$type']/FilePublisherRule") | sort-object -property name -unique)
# this script will never generate path rules, but we need to cater for merging with a policy that does.
$paths = @($one.applockerpolicy.selectnodes("//RuleCollection[@Type='$type']/FilePathRule") +
$two.applockerpolicy.selectnodes("//RuleCollection[@Type='$type']/FilePathRule") | sort-object -property name -unique)
$parent = $target.applockerpolicy.selectsinglenode("//RuleCollection[@Type='$type']")
insert-conditions $target $parent $hashes $publishers
insert-paths $target $paths $parent
}
return $target
}
# remove duplicate conditions between $one and $two and return result.
function remove-duplicates($one, $two) {
$target = $xml.clone()
foreach ($type in $types) {
$h1 = @($one.applockerpolicy.selectnodes("//RuleCollection[@Type='$type']//FileHash") | sort-object -property data)
$h2 = @($two.applockerpolicy.selectnodes("//RuleCollection[@Type='$type']//FileHash") | sort-object -property data)
$hashes = @(); compare-object -referenceobject $h1 -differenceobject $h2 -property data -passthru | ? { $_.sideindicator -eq '<=' } | %{ $hashes += $_ }
$p1 = @($one.applockerpolicy.selectnodes("//RuleCollection[@Type='$type']/FilePublisherRule") | sort-object -property name)
$p2 = @($two.applockerpolicy.selectnodes("//RuleCollection[@Type='$type']/FilePublisherRule") | sort-object -property name)
$publishers = @(); compare-object -referenceobject $p1 -differenceobject $p2 -property name -passthru | ? { $_.sideindicator -eq '<=' } | %{ $publishers += $_ }
$parent = $target.applockerpolicy.selectsinglenode("//RuleCollection[@Type='$type']")
insert-conditions $target $parent $hashes $publishers
}
return $target
}
# applocker cannot handle more than 1,500 or so hashes in a single rule. let's break into blocks of 1000.
function split-hashes($applocker) {
foreach ($type in $types) {
$collection = $applocker.applockerpolicy.selectsinglenode("//RuleCollection[@Type='$type']")
$hashes = @($applocker.applockerpolicy.selectnodes("//RuleCollection[@Type='$type']//FileHash") | sort-object -property data)
if ([math]::floor($hashes.count) -gt 0) {
[void]$collection.removechild($collection.filehashrule)
}
$min = 0; while ($min -lt $hashes.count) {
$max = $min + 999
insert-conditions $applocker $collection $hashes[$min..$max] $null
$min = $max + 1
}
}
}
# initialise policy.
$applocker = $xml.clone()
# initialise rules array.
$rules = @()
# if merging policies.
if ($merge) {
$applocker = $xml.clone()
get-childitem $in\*.xml -exclude (split-path $out -leaf) | %{
log "Merging AppLocker policy $($_.name)."
$applocker = merge-policies $applocker ([xml](get-content $_.fullname -encoding utf8))
}
log "Chunking hashes into blocks of 1,000."
split-hashes $applocker
log "Saving the merged policy."
$applocker.save($out)
return
}
# if generating an adhoc policy.
if ($adhoc) {
log "Creating adhoc AppLocker rules for $filter in folder $in."
$rules += get-childitem -path $in -filter $filter -recurse | select -expandproperty fullname | get-applockerfileinformation -ea silentlycontinue
$rules | %{
$_.path = $_.path -replace '%OSDRIVE%', 'C:'
$extension = [io.path]::getextension($_.path)
$_.path = $_.path -replace $extension, (get-filetype $_.path)
}
$policy = [xml]($rules | new-applockerpolicy -ruletype publisher, hash -user everyone -xml -ignoremissingfileinformation)
} else {
log "Creating AppLocker rules for the requested paths."
$paths | %{ $rules += get-applockerfileinformation -directory $_ -recurse -ea silentlycontinue }
$policy = [xml]($rules | new-applockerpolicy -ruletype publisher, hash -user everyone -xml -ignoremissingfileinformation)
}
log "Optimising rules."
foreach ($type in $types) {
$parent = $applocker.applockerpolicy.selectsinglenode("//RuleCollection[@Type='$type']")
$hashes = @($policy.applockerpolicy.selectnodes("//RuleCollection[@Type='$type']//FileHash") | sort-object -property data -unique)
$publishers = @($policy.applockerpolicy.selectnodes("//RuleCollection[@Type='$type']/FilePublisherRule") | %{
$_.name = "{0} - {1}" -f $_.conditions.filepublishercondition.publishername, $_.conditions.filepublishercondition.productname; $_
} | sort-object -property name -unique)
insert-conditions $applocker $parent $hashes $publishers
}
log "Consolidating file publisher conditions for trusted vendors."
foreach ($vendor in $vendors) {
$node = $publisher.clone()
$node.filepublisherrule.name = [string]"$vendor - *"
$node.filepublisherrule.conditions.filepublishercondition.publishername = [string]$vendor
foreach ($type in $types) {
$parent = $false
$applocker.applockerpolicy.selectnodes("//RuleCollection[@Type='$type']/FilePublisherRule[contains(@Name, '$vendor')]") | %{
$parent = $_.parentnode
[void]($parent.removechild($_))
}
if ($parent -ne $false) {
$node.filepublisherrule.id = [string]([guid]::newguid().guid)
[void]$parent.appendchild($applocker.importnode($node.filepublisherrule, $true))
}
}
}
if ($application) {
log "Comparing the new policy with the baseline and removing duplicate conditions."
$applocker = remove-duplicates $applocker ([xml](get-content $in -encoding utf8))
}
log "Removing publisher rule wildcards."
foreach ($type in $types) {
$applocker.applockerpolicy.selectnodes("//RuleCollection[@Type='$type']/FilePublisherRule/Conditions/FilePublisherCondition/BinaryVersionRange") | %{
$_.LowSection = "*"
$_.HighSection = "*"
}
}
log "Saving the policy."
$applocker.save($out)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment