Skip to content

File Rule Level: Hash

Matt Graeber edited this page Oct 14, 2021 · 1 revision

Microsoft’s Description

Specifies individual hash values for each discovered binary. This is the most specific level and requires additional effort to maintain the current product versions’ hash values. Each time a binary is updated, the hash value changes, therefore requiring a policy update.

Conditions for Considering the "Hash" File Rule Level

  • You need to allow the execution of a binary or script that is not signed. The binary or script originates from a 3rd party (i.e., is not developed internally). Internally developed applications/scripts would ideally be signed with a controlled company code signing certificate.
  • You want to permit execution of software that rarely changes in the most secure fashion possible. In this scenario, there is a minimal maintenance burden since it is assumed that the software will rarely change.
  • You need to deny the execution of a specific, vulnerable version of a binary or script that would otherwise be permitted to execute based on a more permissive allow rule.

It remains very common for software packages to have mixed signing scenarios, where some components are signed properly but other components are not. For example, it is common for software packaged with 3rd party dependencies to not be signed by software vendors.

Example Scenarios

Allow

I have an unsigned executable and a PowerShell script module distributed by a 3rd party that I need to execute in my environment, HelloWDAC.exe and HelloWDACPowerShell.psm1. Because they are not signed by the vendor, my safest option to permit execution is by applying the Hash file rule level. To develop allow rules for them, I place them into a directory named Unsigned and run the following commands:

$FileInfo = Get-SystemDriver -ScanPath .\Unsigned -UserPEs -NoShadowCopy

New-CIPolicy -FilePath UnsignedAllowRules.xml -DriverFiles $FileInfo -Level Hash

This results in the creation of the following rules:

<Allow ID="ID_ALLOW_A_1" FriendlyName="HelloWDAC.exe Hash Sha1" Hash="53DCA1E5D6DB7BE6461D0DE8131AE7E2A5B09069" />
<Allow ID="ID_ALLOW_A_2" FriendlyName="HelloWDAC.exe Hash Sha256" Hash="7B7141A5FC840C2D6E3C398863B4E7EB37D30C97EFE06F3CBC450D576E8BB7A0" />
<Allow ID="ID_ALLOW_A_3" FriendlyName="HelloWDAC.exe Hash Page Sha1" Hash="A0EE0671ABCADF3DB49A8CBF60C0D0739BFB1A70" />
<Allow ID="ID_ALLOW_A_4" FriendlyName="HelloWDAC.exe Hash Page Sha256" Hash="891829B2BA8B1E17E626D0BCA8962648F144EF9526CEAE8A5036E585F3D221DC" />
<Allow ID="ID_ALLOW_A_5" FriendlyName="HelloWDACPowerShell.psm1 Hash Sha1" Hash="BBA4B37C6AF99DEDD73023CFD5BA97CCDEA85554" />
<Allow ID="ID_ALLOW_A_6" FriendlyName="HelloWDACPowerShell.psm1 Hash Sha256" Hash="F4B7EBF9C254944AAEB613D1FC4F53AEE19234C4C423CB4A2D26B0C2B5E1B44F" />
<Allow ID="ID_ALLOW_A_7" FriendlyName="HelloWDACPowerShell.psm1 Hash Authenticode SIP Sha256" Hash="54B3F03815224AF0A95AE6B244050EF7F612360E9F9416E44B4EAF5EA86CEED0" />

Note: the above rules would need to be merged into a broader policy.

Deny

You allow Windows-signed code to execute but you need to block vulnerable versions of pubprn.vbs from executing. To develop a deny rule, I placed pubprn.vbs into a directory named ScriptToBlock and ran the following commands:

mkdir ScriptToBlock

Copy-Item -Path C:\Windows\System32\Printing_Admin_Scripts\en-US\pubprn.vbs -Destination .\ScriptToBlock

$FileInfo = Get-SystemDriver -ScanPath .\ScriptToBlock -UserPEs -NoShadowCopy

New-CIPolicy -FilePath BlockedScript.xml -DriverFiles $FileInfo -Level Hash -Deny

This results in the creation of the following rules:

<Deny ID="ID_DENY_D_1" FriendlyName="pubprn.vbs Hash Sha1" Hash="B26EE8DD2B6448F94AFF465A76D5F245D8C98CF9" />
<Deny ID="ID_DENY_D_2" FriendlyName="pubprn.vbs Hash Sha256" Hash="44D2E86C1C7D0592133BC7BC0464B32FF97C3BFB74DC47B9BBD3B4FE0451CE80" />
<Deny ID="ID_DENY_D_3" FriendlyName="pubprn.vbs Hash Authenticode SIP Sha256" Hash="6D7E4BDA2C15C5D370818CFF6EE9BD869898EED49600193BE618FAD41FDDF661" />

Note: the above rules would need to be merged into a broader policy.

Hash Types

There are five unique hash types that can be generated when the Hash file rule level is selected:

1. Hash Sha1

This generated hash type is a common source of confusion as it is a different kind of hash depending on whether the file is a Portable Executable (PE) or script code. When the file is a PE, this hash is the SHA1 Authenticode hash of the file. For script code, it represents the SHA1 file hash of the file. Depending upon whether or not you are generating an allow or deny rule, this will result in different outcomes:

  • An allow rule for a PE: There is no way to permit a PE file to execute based on a file hash. Allowing a PE to run based on Authenticode hash means that that specific version of the code is allowed to run independent of its signature or arbitrary data appended outside of the fields of the PE header. In my opinion, this doesn’t increase risk. There are tools that can append arbitrary code or data to PE files without invaliding the signature but it would still require that something else execute it. I think it is a common misconception that if you can change the file hash of a PE without invalidating the signature that it represents an application control bypass. It is not because it would require an application control bypass to subvert the hash rule in this case.
  • An allow rule for a script: Only the script based on the file hash may execute. No exceptions.
  • A deny rule for a PE: The PE will be blocked based on its Authenticode hash which is vastly more secure than blocking based on file hash. There exists no safe way to block execution based on file hash. By blocking based on Authenticode hash, that specific version of the software will be blocked independent of its file hash. Great care must be taken though to mitigate downgrade attacks where an adversary might find another version of the same vulnerable code to drop and execute. So while blocking based on Authenticode hash is far safer than blocking based on file hash, it should not be considered a robust enforcement strategy.
  • A deny rule for a script: The script will be blocked based on its file hash. This strategy is subject to trivial evasion since an adversary could have multiple methods of bypassing the block logic as this blog post highlights in detail. Fortunately, Microsoft recently started enforcing Authenticode signatures in scripts and is represented as the "Authenticode SIP" hash, described below.

2. Hash Sha256

Identical to Hash Sha1 but a SHA256 hash.

It is assumed that both Sha1 and Sha256 hashes are supported for backwards compatibility.

3. Hash Page Sha1

Signed PE files can have page hashes representing hashes of each section of the PE embedded within their signatures (or stored in a catalog file). Under certain conditions, those pashes can be validated upon loading the image or upon being paged back into memory. Page hashes appear to only be relevant and validated when the PE is built with the IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY flag.

The ConfigCI PowerShell module generates a page hash values in a WDAC policy by calling the ComputeFirstPageHash function in wintrust.dll, an undocumented function. Even though a PE can have many page hashes, WDAC only considers the first page hash, the hash of the PE header.

According to this informative Twitter thread, page hashes in WDAC policies are not relevant for PEs if its signature does not include page hashes which will be the case in most PEs. Unfortunately, the Get-SystemDriver in the ConfigCI module does not check for the presence of page hashes in PEs and it generates page hash rules automatically, resulting in potentially unnecessary clutter in a policy consisting of many hash rules.

Ultimately, it’s not clear to me what distinct purpose page hash rules serve over file and Authenticode hash rules. For those interested in learning more about page hashing, there are few references but here are some that were marginally useful:

4. Hash Page Sha256

Signed PEs can also have SHA256 page hashes embedded within them.

5. Authenticode SIP

Non-PE code can be signed as well. The calculation of a robust Authenticode hash depends on the file type. For example, the way a PowerShell script is signed and validated differs from how VBScript code is signed and validated. Each implementation of the respective Authenticode hashing algorithm is contained within a Subject Interface Package (SIP) DLL for the corresponding file type. As of this writing, the following non-PE file types can be signed and enforced by WDAC accordingly:

  • MSI
  • Windows Script Host (WSH) code which includes the following file extensions: .vbs (VBScript), .js (JScript), .vbe (Encoded VBScript), .wsf (Windows Script File), .jse (Encoded JScript)
  • PowerShell code which includes the following file extensions: .ps1, .ps1xml, .psc1, .psd1, .psm1, .cdxml, .mof
  • AppX files which includes the following file extensions: .appx, .p7x, .appxbundle, .eappx

For some additional information on how SIP DLLs generate Authenticode hashes for script types, this Tweet offers some information.

Interpreting File Hashes in the Event Log

When WDAC is placed into audit or enforcement mode, multiple events can surface based on the file type, all of which surface hash information for the PEs or scripts. This section will show example events and highlight how to interpret the relevant fields that are understandably subject to misinterpretation. WDAC logs the following events when code is audited or blocked:

  • PE files: Microsoft-Windows-CodeIntegrity/Operational event ID 3077 (Error: blocked, enforced), 3076 (Information: audited, not enforced)
  • Non-PE script files: Microsoft-Windows-AppLocker/MSI and Script event ID 8029 (Error: blocked, enforced) and 8028 (Warning: audited, not enforced), 8037 (Information: allowed to run)

Example: Microsoft-Windows-CodeIntegrity/Operational 3077 Event

FileNameLength: 59
File Name: \Device\HarddiskVolume4\WDACTest\EXE_Unsigned\HelloWDAC.exe
ProcessNameLength: 78
Process Name: \Device\HarddiskVolume4\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
Requested Signing Level: 2
Validated Signing Level: 1
Statu: 0xc0e90002
SHA1 Hash Size: 20
SHA1 Hash: 53DCA1E5D6DB7BE6461D0DE8131AE7E2A5B09069
SHA256 Hash Size: 32
SHA256 Hash: 7B7141A5FC840C2D6E3C398863B4E7EB37D30C97EFE06F3CBC450D576E8BB7A0
SHA1 Flat Hash Size: 20
SHA1 Flat Hash: FA29F72DF51F2522320F7077C4B4C8774A1414BA
SHA256 Flat Hash Size: 32
SHA256 Flat Hash: EC4CF649BBFC0B065946197FF33884084091C7AEFE1F140CE8F688D8C3643E86
USN: 3713517104
SI Signing Scenario: 1
PolicyNameLength: 24
PolicyName: DefaultMicrosoftEnforced
PolicyIDLength: 6
PolicyID: 041317
PolicyHashSize: 32
PolicyHash: DE06D273D6AA11DEDB46E84F54ED8E79638C6024B3350C70F3FFC6B147283FC1
OriginalFileNameLength: 13
OriginalFileName: HelloWDAC.exe
InternalNameLength: 13
InternalName: HelloWDAC.exe
FileDescriptionLength: 1
FileDescription:
ProductNameLength: 0
ProductName:
FileVersion: 1.2.3.4
PolicyGUID: {a244370e-44c9-4c06-b551-f6016e563076}
UserWriteable: false
PackageFamilyNameLength: 0
PackageFamilyName:

Relevant Hash Fields:

Event Log Field Name Description
SHA1 Flat Hash The SHA1 flat file hash
SHA256 Flat Hash The SHA256 flat file hash
SHA1 Hash The SHA1 Authenticode hash of the file
SHA256 Hash The SHA256 Authenticode hash of the file

Example: Microsoft-Windows-AppLocker/MSI and Script 8029 Event

FilePathLength: 41
FilePath: C:\WDACTest\pubprnModifiedButValidSig.vbs
Sha1Hash: 3CD5945C3293BBF2C33AC65F8D98164E6A92DF35
Sha256Hash: 6D7E4BDA2C15C5D370818CFF6EE9BD869898EED49600193BE618FAD41FDDF661
Result: -790036478
USN: 3720935112
Sha1CatalogHash: 3CD5945C3293BBF2C33AC65F8D98164E6A92DF35
Sha256CatalogHash: DBCBC2EB3CA98A421D33367318E987CBA5108689561D1AF1FAC973A1960776C0
UserWriteable: true

Relevant Hash Fields:

Event Log Field Name Description
Sha1Hash, Sha1CatalogHash The SHA1 flat file hash
Sha256CatalogHash The SHA256 flat file hash
Sha256Hash The SHA256 Authenticode hash of the file

Extracting File Hashes

For those wanting to validate hashes independent of WDAC, there are various utilities that can calculate and display the hashes described above.

File Hashes

Get-FileHash can be used to calculate and display file hashes.

Get-FileHash -Path C:\WDACTest\EXE\HelloWDAC.exe -Algorithm SHA1
Get-FileHash -Path C:\WDACTest\EXE\HelloWDAC.exe -Algorithm SHA256

Certutil can be used to calculate and display file hashes.

certutil -hashfile C:\WDACTest\EXE\HelloWDAC.exe SHA1
certutil -hashfile C:\WDACTest\EXE\HelloWDAC.exe SHA256

Authenticode Hashes

Sigcheck can be used to calculate and display the SHA1 and SHA256 Authenticode hashes of a PE file. They are presented in the PESHA1 and PE256 fields.

sigcheck -h C:\WDACTest\EXE\HelloWDAC.exe

Get-SystemDriver can be used to calculate and display only the header page hash for a PE file. If page hashes are present in the PE file, it does not attempt to parse and display them. Authenticode hashes are stored in the Hash (SHA1) and Hash256 fields.

Get-SystemDriver -ScanPath C:\WDACTest\EXE -NoScript -NoShadowCopy -UserPEs

Page Hashes

Signtool can be used to display all embedded page hashes in a PE file that has page hashes present.

signtool verify /ph /v C:\Windows\System32\sppsvc.exe

Get-SystemDriver can be used to calculate and display only the header page hash for a PE file. If page hashes are present in the PE file, it does not attempt to parse and display them. Page hashes are stored in the PageHash (a SHA1 hash) and PageHash256 fields.

Get-SystemDriver -ScanPath C:\WDACTest\EXE -NoScript -NoShadowCopy -UserPEs

Appendix

For anyone wanting to reproduce similar results in their testing, the files that I will use commonly in these blog posts can be downloaded here. They just print "Hello, WDAC!" to the console. Here are their respective VirusTotal links as well:

HelloWDAC.exe was generated with the following PowerShell code:

Add-Type -TypeDefinition @'
using System.Reflection;
using System.Runtime.CompilerServices;
[assembly: AssemblyVersionAttribute("1.2.3.4")]
public class WDACTest {
    public static void Main() {
        System.Console.WriteLine("Hello, WDAC!");
    }
}
'@ -OutputAssembly .\HelloWDAC.exe

HelloWDACPowerShell.ps1 consists of the following script code:

Add-Type -TypeDefinition @'
public class WDACTest {
    public static void Main() {
        System.Console.WriteLine("Hello, WDAC!");
    }
}
'@

[WDACTest]::Main()

HelloWDACSigned.exe and HelloWDACPowerShellSigned.psm1 were signed using the following PowerShell code:

$Arguments = @{
    Subject = 'CN=My Self-signed Code Signing'
    Type = 'CodeSigningCert'
    KeySpec = 'Signature'
    KeyUsage = 'DigitalSignature'
    FriendlyName = 'My Self-signed Code Signing'
    NotAfter = ((Get-Date).AddYears(3))
    CertStoreLocation = 'Cert:\CurrentUser\My'
}

$TestCodeSigningCert = New-SelfSignedCertificate @Arguments

Copy-Item .\HelloWDAC.exe .\HelloWDACSigned.exe
Copy-Item .\HelloWDACPowerShell.psm1 .\HelloWDACPowerShellSigned.psm1

Set-AuthenticodeSignature -Certificate $TestCodeSigningCert -TimestampServer 'http://timestamp.digicert.com' -FilePath .\HelloWDACSigned.exe
Set-AuthenticodeSignature -Certificate $TestCodeSigningCert -TimestampServer 'http://timestamp.digicert.com' -FilePath .\HelloWDACPowerShellSigned.psm1