Skip to content

Commit

Permalink
Moved some code to C#
Browse files Browse the repository at this point in the history
For improved performance moved some PowerShell code to native C#, this reduces overhead and makes things faster in general
  • Loading branch information
HotCakeX committed Jun 19, 2024
1 parent 36b967d commit c3e4e58
Show file tree
Hide file tree
Showing 19 changed files with 358 additions and 675 deletions.
95 changes: 0 additions & 95 deletions WDACConfig/Utilities/Hashes.csv

This file was deleted.

171 changes: 171 additions & 0 deletions WDACConfig/WDACConfig Module Files/C#/CertificateHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Formats.Asn1; // to use the AsnReader and AsnWriter classes

namespace WDACConfig
{
// a class to throw a custom exception when the certificate collection cannot be obtained during WDAC Simulation
public class ExceptionFailedToGetCertificateCollection : Exception
{
public ExceptionFailedToGetCertificateCollection(string message, string functionName)
: base($"{functionName}: {message}")
{
}
}

public class CertificateHelper
{
public static string GetTBSCertificate(X509Certificate2 cert)
// Calculates the TBS value of a certificate
{
// Get the raw data of the certificate
byte[] rawData = cert.RawData;

// Create an ASN.1 reader to parse the certificate
AsnReader asnReader = new AsnReader(rawData, AsnEncodingRules.DER);

// Read the certificate sequence
AsnReader certificate = asnReader.ReadSequence();

// Read the TBS (To be signed) value of the certificate
ReadOnlyMemory<byte> tbsCertificate = certificate.ReadEncodedValue();

// Read the signature algorithm sequence
AsnReader signatureAlgorithm = certificate.ReadSequence();

// Read the algorithm OID of the signature
string algorithmOid = signatureAlgorithm.ReadObjectIdentifier();

// Define a hash function based on the algorithm OID
HashAlgorithm hashFunction;
switch (algorithmOid)
{
case "1.2.840.113549.1.1.4":
hashFunction = MD5.Create();
break;
case "1.2.840.113549.1.1.5":
case "1.3.14.3.2.29": //sha-1WithRSAEncryption
hashFunction = SHA1.Create();
break;
case "1.2.840.113549.1.1.11":
hashFunction = SHA256.Create();
break;
case "1.2.840.113549.1.1.12":
hashFunction = SHA384.Create();
break;
case "1.2.840.113549.1.1.13":
hashFunction = SHA512.Create();
break;
// These are less likely to be used since ConfigCI doesn't support their OIDs
case "1.2.840.10040.4.3":
hashFunction = SHA1.Create();
break;
case "2.16.840.1.101.3.4.3.2":
hashFunction = SHA256.Create();
break;
case "2.16.840.1.101.3.4.3.3":
hashFunction = SHA384.Create();
break;
case "2.16.840.1.101.3.4.3.4":
hashFunction = SHA512.Create();
break;
case "1.2.840.10045.4.1":
hashFunction = SHA1.Create();
break;
case "1.2.840.10045.4.3.2":
hashFunction = SHA256.Create();
break;
case "1.2.840.10045.4.3.3":
hashFunction = SHA384.Create();
break;
case "1.2.840.10045.4.3.4":
hashFunction = SHA512.Create();
break;
default:
throw new Exception($"No handler for algorithm {algorithmOid}");
}

// Compute the hash of the TBS value using the hash function
byte[] hash = hashFunction.ComputeHash(tbsCertificate.ToArray());

// Convert the hash to a hex string
string hexStringOutput = BitConverter.ToString(hash).Replace("-", "");

return hexStringOutput;
}

public static string ConvertHexToOID(string hex)
// Converts a hexadecimal string to an OID
// Used for converting hexadecimal values found in the EKU sections of the WDAC policies to their respective OIDs.

{
if (string.IsNullOrEmpty(hex))
{
throw new ArgumentException("Hex string cannot be null or empty", nameof(hex));
}

// Convert the hexadecimal string to a byte array by looping through the string in pairs of two characters
// and converting each pair to a byte using the base 16 (hexadecimal) system
byte[] numArray = new byte[hex.Length / 2];
for (int i = 0; i < hex.Length; i += 2)
{
numArray[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
}

// Change the first byte from 1 to 6 because the hexadecimal string is missing the tag and length bytes
// that are required for the ASN.1 encoding of an OID
// The tag byte indicates the type of the data, and for an OID it is 6
// The length byte indicates the number of bytes that follow the tag byte
// and for this example it is 10 (0A in hexadecimal)
numArray[0] = 6;

// Create an AsnReader object with the default encoding rules
// This is a class that can read the ASN.1 BER, CER, and DER data formats
// BER (Basic Encoding Rules) is the most flexible and widely used encoding rule
// CER (Canonical Encoding Rules) is a subset of BER that ensures a unique encoding
// DER (Distinguished Encoding Rules) is a subset of CER that ensures a deterministic encoding
// The AsnReader object takes the byte array as input and the encoding rule as an argument
AsnReader asnReader = new AsnReader(numArray, AsnEncodingRules.BER);

// Read the OID as an ObjectIdentifier
// This is a method of the AsnReader class that returns the OID as a string
// The first two numbers are derived from the first byte of the encoded data
// The rest of the numbers are derived from the subsequent bytes using a base 128 (variable-length) system
string oid = asnReader.ReadObjectIdentifier();

// Return the OID value as string
return oid;
}

public static X509Certificate2Collection GetSignedFileCertificates(string filePath, X509Certificate2 x509Certificate2 = null)
// gets all the certificates from a signed file or a certificate object and output a Collection
{
// Create an X509Certificate2Collection object
X509Certificate2Collection certCollection = new X509Certificate2Collection();

// If the FilePath parameter is used, import all the certificates from the files
if (!string.IsNullOrEmpty(filePath))
{
try
{
// If the FilePath parameter is used, import all the certificates from the file
certCollection.Import(filePath);
}
catch (Exception)
{
// Throw a custom exception that will be caught by Invoke-WDACPolicySimulation cmdlets
throw new ExceptionFailedToGetCertificateCollection("Could not get the certificate collection of the file due to lack of necessary permissions.", "GetSignedFileCertificates");
}
}
// If the CertObject parameter is used, add the certificate object to the collection
else if (x509Certificate2 != null)
{
certCollection.Add(x509Certificate2);
}

return certCollection;
}
}
}
19 changes: 19 additions & 0 deletions WDACConfig/WDACConfig Module Files/C#/SimulationInput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Used by WDAC Simulations
namespace WDACConfig
{
public class SimulationInput
{
// Adding public getters and setters for the properties
public System.IO.FileInfo FilePath { get; set; }
public System.Management.Automation.Signature GetAuthenticodeResults { get; set; }
public System.Xml.XmlDocument XMLContent { get; set; }

// Adding a constructor to initialize the properties
public SimulationInput(System.IO.FileInfo filepath, System.Management.Automation.Signature getauthenticoderesults, System.Xml.XmlDocument xmlcontent)
{
FilePath = filepath;
GetAuthenticodeResults = getauthenticoderesults;
XMLContent = xmlcontent;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ Function Edit-WDACConfig {
}

if ($HasSelectedLogs) {
$null = Wait-Job -Job $ECCSignedAuditLogsJob
$null = Wait-Job -Job $ECCSignedAuditLogsJob
# Redirecting Verbose and Debug output streams because they are automatically displayed already on the console using StreamingHost parameter
Receive-Job -Job $ECCSignedAuditLogsJob 4>$null 5>$null
Remove-Job -Job $ECCSignedAuditLogsJob -Force
Expand Down
38 changes: 23 additions & 15 deletions WDACConfig/WDACConfig Module Files/Core/Invoke-WDACSimulation.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,10 @@ Function Invoke-WDACSimulation {
# Check if the supplied XML file contains Allow all rule
[System.Boolean]$ShouldExit = $false

if ((Get-Content -LiteralPath $XmlFilePath -Raw) -match '<Allow ID="ID_ALLOW_.*" FriendlyName=".*" FileName="\*".*/>') {
# Get the content of the XML file
[System.String]$XMLContent = Get-Content -LiteralPath $XmlFilePath -Raw

if ($XMLContent -match '<Allow ID="ID_ALLOW_.*" FriendlyName=".*" FileName="\*".*/>') {
Write-Verbose -Message "The supplied XML file '$($XmlFilePath.Name)' contains a rule that allows all files."

# Set a flag to exit the subsequent blocks
Expand All @@ -106,11 +109,6 @@ Function Invoke-WDACSimulation {
# Exit the Begin block
Return
}
}

process {
# Exit the Process block
if ($ShouldExit) { Return }

# Store the PSCustomObjects that contain file paths and SpecificFileNameLevel options of valid Allowed Signed files - FilePublisher level
$SignedFile_FilePublisher_Objects = New-Object -TypeName System.Collections.Generic.List[System.Object]
Expand Down Expand Up @@ -157,21 +155,26 @@ Function Invoke-WDACSimulation {
# Make it case-insensitive
[System.StringComparer]::InvariantCultureIgnoreCase
)
}

process {
# Exit the Process block
if ($ShouldExit) { Return }

# Hash Sha256 values of all the file rules based on hash in the supplied xml policy file
Write-Verbose -Message 'Getting the Sha256 Hash values of all the file rules based on hash in the supplied xml policy file'

$CurrentStep++
Write-Progress -Id 0 -Activity 'Getting the Sha256 Hash values from the XML file' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100)

$SHA256HashesFromXML = [System.Collections.Generic.HashSet[System.String]]@((Get-FileRuleOutput -xmlPath $XmlFilePath).hashvalue)
$SHA256HashesFromXML = [System.Collections.Generic.HashSet[System.String]]@((Get-FileRuleOutput -Xml ([System.Xml.XmlDocument]$XMLContent)).HashValue)

# Get all of the file paths of the files that WDAC supports, from the user provided directory
Write-Verbose -Message 'Getting all of the file paths of the files that WDAC supports, from the user provided directory'

$CurrentStep++
Write-Progress -Id 0 -Activity "Getting the supported files' paths" -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100)
[System.IO.FileInfo[]]$CollectedFiles = Get-FilesFast -Directory $FolderPath -File $FilePath
$CollectedFiles = [System.Collections.Generic.HashSet[System.IO.FileInfo]]@(Get-FilesFast -Directory $FolderPath -File $FilePath)

# Make sure the selected directory contains files with the supported extensions
if (!$CollectedFiles) { Throw 'There are no files in the selected directory that are supported by the WDAC engine.' }
Expand Down Expand Up @@ -271,17 +274,22 @@ Function Invoke-WDACSimulation {
# If the file's hash does not exist in the supplied XML file, then check its signature
else {

$GetAuthenticodeSignatureResults = Get-AuthenticodeSignature -LiteralPath $CurrentFilePath

# Get the status of file's signature
:MainSwitchLabel switch (Get-AuthenticodeSignature -LiteralPath $CurrentFilePath) {
:MainSwitchLabel switch ($GetAuthenticodeSignatureResults.Status) {

# If the file is signed and valid
{ $_.Status -eq 'valid' } {

'valid' {
try {
# Use the Compare-SignerAndCertificate function to process it
$ComparisonResult = Compare-SignerAndCertificate -XmlFilePath $XmlFilePath -SignedFilePath $CurrentFilePath
$ComparisonResult = Compare-SignerAndCertificate -SimulationInput ([WDACConfig.SimulationInput]::New(
$CurrentFilePath,
$GetAuthenticodeSignatureResults,
[System.Xml.XmlDocument]$XMLContent
))
}
catch [ExceptionFailedToGetCertificateCollection] {
catch [WDACConfig.ExceptionFailedToGetCertificateCollection] {
# If the file's certificate collections could not be fetched due to lack of necessary permissions, place it in a different array of file path
[System.Void]$InAccessibleFilePaths.Add($CurrentFilePath)

Expand Down Expand Up @@ -354,7 +362,7 @@ Function Invoke-WDACSimulation {
}

# If the file is signed but is tampered
{ $_.Status -eq 'HashMismatch' } {
'HashMismatch' {
Write-Warning -Message "The file: $CurrentFilePath has hash mismatch, it is most likely tampered."

[System.Void]$SignedHashMismatchFilePaths.Add($CurrentFilePath)
Expand All @@ -363,7 +371,7 @@ Function Invoke-WDACSimulation {
}

# If the file is not signed
{ $_.Status -eq 'NotSigned' } {
'NotSigned' {
Write-Verbose -Message 'The file is not signed and is not allowed by hash'

[System.Void]$UnsignedNotAllowedFilePaths.Add($CurrentFilePath)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,6 @@ Function New-SupplementalWDACConfig {

if ($PSBoundParameters['Certificates']) {
Import-Module -Force -FullyQualifiedName @(
"$ModuleRootPath\WDACSimulation\Get-TBSCertificate.psm1",
"$ModuleRootPath\WDACSimulation\Get-SignedFileCertificates.psm1",
"$ModuleRootPath\WDACSimulation\Get-CertificateDetails.psm1",
"$ModuleRootPath\XMLOps\New-RootAndLeafCertificateLevelRules.psm1",
"$ModuleRootPath\XMLOps\Clear-CiPolicy_Semantic.psm1"
Expand Down Expand Up @@ -349,7 +347,7 @@ Function New-SupplementalWDACConfig {
foreach ($CertPath in $CertificatePaths) {

# All certificates have this value, which will create signer rules with TBS Hash only and result in RootCertificate Level
$MainCertificateDetails = Get-SignedFileCertificates -FilePath "$CertPath"
$MainCertificateDetails = [WDACConfig.CertificateHelper]::GetSignedFileCertificates($CertPath)

# Only non-root certificates have this value, which will create signer rules with subject name and TBSHash and result in LeafCertificate Level
$LeafCertificateDetails = (Get-CertificateDetails -FilePath "$CertPath").LeafCertificate
Expand All @@ -375,7 +373,8 @@ Function New-SupplementalWDACConfig {
Write-Verbose -Message "New-SupplementalWDACConfig: Root certificate signer is going to be created for the certificate located at $CertPath"

# Get the TBS value of the certificate
$CurrentRootAndLeafSignerSigner.TBS = Get-TBSCertificate -Cert $MainCertificateDetails
$CurrentRootAndLeafSignerSigner.TBS = [WDACConfig.CertificateHelper]::GetTBSCertificate($MainCertificateDetails)

$CurrentRootAndLeafSignerSigner.SiSigningScenario = $SigningScenarioTranslated
$CurrentRootAndLeafSignerSigner.SignerName = $MainCertificateDetails.Subject
$CurrentRootAndLeafSignerSigner.SignerType = 'Root'
Expand Down
11 changes: 0 additions & 11 deletions WDACConfig/WDACConfig Module Files/CoreExt/Classes.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,6 @@ Class CertCNz : System.Management.Automation.IValidateSetValuesGenerator {
}
}

# a class to throw a custom exception when the certificate collection cannot be obtained during WDAC Simulation
[NoRunspaceAffinity()]
class ExceptionFailedToGetCertificateCollection : System.Exception {
[System.String]$AdditionalData

ExceptionFailedToGetCertificateCollection([System.String]$Message, [System.String]$AdditionalData) : base($Message) {
$This.additionalData = $AdditionalData
}
}

# a class to define valid policy rule options
[NoRunspaceAffinity()]
Class RuleOptionsx : System.Management.Automation.IValidateSetValuesGenerator {
Expand Down Expand Up @@ -127,7 +117,6 @@ Class HashCreator {
[ScanLevelz]
[CertCNz]
[BasePolicyNamez]
[ExceptionFailedToGetCertificateCollection]
[RuleOptionsx]
[CertificateDetailsCreator]
[FilePublisherSignerCreator]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ $PSDefaultParameterValues = @{
'Compare-SecureStrings:Verbose' = $Verbose
'Get-KernelModeDriversAudit:Verbose' = $Verbose
'Copy-CiRules:Verbose' = $Verbose
'Get-TBSCertificate:Verbose' = $Verbose
'Get-SignerInfo:Verbose' = $Verbose
'Get-SignedFileCertificates:Verbose' = $Verbose
'Get-FileRuleOutput:Verbose' = $Verbose
'Get-CertificateDetails:Verbose' = $Verbose
'Get-NestedSignerSignature:Verbose' = $Verbose
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Function Move-UserModeToKernelMode {
[System.Void]$signingScenario131.ProductSigners.AllowedSigners.AppendChild($NewAllowedSigner)
}

# Remove the SigningScenario node with Value 12 from the XML document
# Remove the SigningScenario node with Value 12 from the XML document
[System.Void]$Xml.SiPolicy.SigningScenarios.RemoveChild($signingScenario12)
}

Expand Down
4 changes: 1 addition & 3 deletions WDACConfig/WDACConfig Module Files/WDACConfig.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -236,14 +236,11 @@ This is an advanced PowerShell module for WDAC (Windows Defender Application Con
'Shared\Invoke-CiSigning.psm1',
'Shared\Show-DirectoryPathPicker.psm1',
'Shared\Test-ECCSignedFiles.psm1',
'WDACSimulation\Get-TBSCertificate.psm1',
'WDACSimulation\Get-SignerInfo.psm1',
'WDACSimulation\Get-SignedFileCertificates.psm1',
'WDACSimulation\Get-FileRuleOutput.psm1',
'WDACSimulation\Get-CertificateDetails.psm1',
'WDACSimulation\Get-NestedSignerSignature.psm1',
'WDACSimulation\Compare-SignerAndCertificate.psm1',
'WDACSimulation\Convert-HexToOID.psm1',
'WDACSimulation\Get-ExtendedFileInfo.psm1',
'C#\Signer.cs',
'C#\Kernel32dll.cs',
Expand All @@ -254,6 +251,7 @@ This is an advanced PowerShell module for WDAC (Windows Defender Application Con
'C#\WldpQuerySecurityPolicy.cs',
'C#\ArgumentCompleters.cs',
'C#\PolicyHashObj.cs',
'C#\CertificateHelper.cs',
'Help\ConvertTo-WDACPolicy.xml',
'Help\ConvertTo-WDACPolicy.md',
'XMLOps\Build-SignerAndHashObjects.psm1',
Expand Down
Loading

0 comments on commit c3e4e58

Please sign in to comment.