diff --git a/WDACConfig/ArgumentCompleters.ps1 b/WDACConfig/ArgumentCompleters.ps1 deleted file mode 100644 index 8706200c2..000000000 --- a/WDACConfig/ArgumentCompleters.ps1 +++ /dev/null @@ -1,185 +0,0 @@ -<# -# argument tab auto-completion for CertPath param to show only .cer files in current directory and 2 sub-directories recursively -[scriptblock]$ArgumentCompleterCertPath = { - # Note the use of -Depth 1 - # Enclosing the $results = ... assignment in (...) also passes the value through. - ($results = Get-ChildItem -Depth 2 -Filter *.cer | ForEach-Object { "`"$_`"" }) - if (-not $results) { - # No results? - $null # Dummy response that prevents fallback to the default file-name completion. - } -} -#> - -# argument tab auto-completion for Policy Paths to show only .xml files and only suggest files that haven't been already selected by user -# https://stackoverflow.com/questions/76141864/how-to-make-a-powershell-argument-completer-that-only-suggests-files-not-already/76142865 -[scriptblock]$ArgumentCompleterPolicyPaths = { - # Get the current command and the already bound parameters - param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) - - # Find all string constants in the AST that end in ".xml" - $existing = $commandAst.FindAll({ - $args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] -and - $args[0].Value -like '*.xml' - }, - $false - ).Value - - # Get the xml files in the current directory - Get-ChildItem -Filter *.xml | ForEach-Object { - # Check if the file is already selected - if ($_.FullName -notin $existing) { - # Return the file name with quotes - "`"$_`"" - } - } -} - -# argument tab auto-completion for Certificate common name -[scriptblock]$ArgumentCompleterCertificateCN = { - $certs = foreach ($cert in (Get-ChildItem 'Cert:\CurrentUser\my')) { - (($cert.Subject -split ',' | Select-Object -First 1) -replace 'CN=', '').Trim() - } - $certs | ForEach-Object { return "`"$_`"" } -} - -# Argument tab auto-completion for installed Appx package names -[scriptblock]$ArgumentCompleterAppxPackageNames = { - # Get the current command and the already bound parameters - param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) - # Get the app package names that match the word to complete - Get-AppxPackage -Name *$wordToComplete* | ForEach-Object { - "`"$($_.Name)`"" - } -} - -# argument tab auto-completion for Base Policy Paths to show only .xml files and only suggest files that haven't been already selected by user -[scriptblock]$ArgumentCompleterPolicyPathsBasePoliciesOnly = { - # Get the current command and the already bound parameters - param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) - - # Find all string constants in the AST that end in ".xml" - $existing = $commandAst.FindAll({ - $args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] -and - $args[0].Value -like '*.xml' - }, - $false - ).Value - - # Get the xml files in the current directory - Get-ChildItem | Where-Object { $_.extension -like '*.xml' } | ForEach-Object { - - $xmlitem = [xml](Get-Content $_) - $PolicyType = $xmlitem.SiPolicy.PolicyType - - if ($PolicyType -eq 'Base Policy') { - - # Check if the file is already selected - if ($_.FullName -notin $existing) { - # Return the file name with quotes - "`"$_`"" - } - } - } -} - -# argument tab auto-completion for Supplemental Policy Paths to show only .xml files and only suggest files that haven't been already selected by user -[scriptblock]$ArgumentCompleterPolicyPathsSupplementalPoliciesOnly = { - # Get the current command and the already bound parameters - param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) - - # Find all string constants in the AST that end in ".xml" - $existing = $commandAst.FindAll({ - $args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] -and - $args[0].Value -like '*.xml' - }, - $false - ).Value - - # Get the xml files in the current directory - Get-ChildItem | Where-Object { $_.extension -like '*.xml' } | ForEach-Object { - - $xmlitem = [xml](Get-Content $_) - $PolicyType = $xmlitem.SiPolicy.PolicyType - - if ($PolicyType -eq 'Supplemental Policy') { - - # Check if the file is already selected - if ($_.FullName -notin $existing) { - # Return the file name with quotes - "`"$_`"" - } - } - } -} - -# Opens Folder picker GUI so that user can select folders to be processed -[scriptblock]$ArgumentCompleterFolderPathsPicker = { - # Load the System.Windows.Forms assembly - Add-Type -AssemblyName System.Windows.Forms - # non-top-most, works better with window focus - $browser = New-Object System.Windows.Forms.FolderBrowserDialog - $null = $browser.ShowDialog() - # Add quotes around the selected path - return "`"$($browser.SelectedPath)`"" -} - -# Opens File picker GUI so that user can select an .exe file - for SignTool.exe -[scriptblock]$ArgumentCompleterExeFilePathsPicker = { - # Load the System.Windows.Forms assembly - Add-Type -AssemblyName System.Windows.Forms - # Create a new OpenFileDialog object - $dialog = New-Object System.Windows.Forms.OpenFileDialog - # Set the filter to show only executable files - $dialog.Filter = 'Executable files (*.exe)|*.exe' - # Show the dialog and get the result - $result = $dialog.ShowDialog() - # If the user clicked OK, return the selected file path - if ($result -eq 'OK') { - return "`"$($dialog.FileName)`"" - } -} - -# Opens File picker GUI so that user can select a .cer file -[scriptblock]$ArgumentCompleterCerFilePathsPicker = { - # Load the System.Windows.Forms assembly - Add-Type -AssemblyName System.Windows.Forms - # Create a new OpenFileDialog object - $dialog = New-Object System.Windows.Forms.OpenFileDialog - # Set the filter to show only certificate files - $dialog.Filter = 'Certificate files (*.cer)|*.cer' - # Show the dialog and get the result - $result = $dialog.ShowDialog() - # If the user clicked OK, return the selected file path - if ($result -eq 'OK') { - return "`"$($dialog.FileName)`"" - } -} - -# Opens File picker GUI so that user can select a .xml file -[scriptblock]$ArgumentCompleterXmlFilePathsPicker = { - # Load the System.Windows.Forms assembly - Add-Type -AssemblyName System.Windows.Forms - # Create a new OpenFileDialog object - $dialog = New-Object System.Windows.Forms.OpenFileDialog - # Set the filter to show only XML files - $dialog.Filter = 'XML files (*.xml)|*.xml' - # Show the dialog and get the result - $result = $dialog.ShowDialog() - # If the user clicked OK, return the selected file path - if ($result -eq 'OK') { - return "`"$($dialog.FileName)`"" - } -} - -# Opens Folder picker GUI so that user can select folders to be processed -# WildCard file paths -[scriptblock]$ArgumentCompleterFolderPathsPickerWildCards = { - # Load the System.Windows.Forms assembly - Add-Type -AssemblyName System.Windows.Forms - # non-top-most, works better with window focus - $browser = New-Object System.Windows.Forms.FolderBrowserDialog - $null = $browser.ShowDialog() - # Add quotes around the selected path - return "`"$($browser.SelectedPath)\*`"" -} \ No newline at end of file diff --git a/WDACConfig/Confirm-WDACConfig.psm1 b/WDACConfig/Confirm-WDACConfig.psm1 deleted file mode 100644 index 640ea06a2..000000000 --- a/WDACConfig/Confirm-WDACConfig.psm1 +++ /dev/null @@ -1,172 +0,0 @@ -#Requires -RunAsAdministrator -function Confirm-WDACConfig { - [CmdletBinding(DefaultParameterSetName = 'List Active Policies')] - Param( - [Alias('L')] - [Parameter(Mandatory = $false, ParameterSetName = 'List Active Policies')][Switch]$ListActivePolicies, - [Alias('V')] - [Parameter(Mandatory = $false, ParameterSetName = 'Verify WDAC Status')][Switch]$VerifyWDACStatus, - [Alias('S')] - [Parameter(Mandatory = $false, ParameterSetName = 'Check SmartAppControl Status')][Switch]$CheckSmartAppControlStatus, - - [Parameter(Mandatory = $false, DontShow = $true)][Switch]$DummyParameter # To hide common parameters - ) - - DynamicParam { - - # Add the dynamic parameters to the param dictionary - $ParamDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() - - if ($PSBoundParameters['ListActivePolicies']) { - - # Create a dynamic parameter for -OnlyBasePolicies - $OnlyBasePoliciesDynamicParameter = [System.Management.Automation.ParameterAttribute]@{ - Mandatory = $false - ParameterSetName = 'List Active Policies' - HelpMessage = 'Only List Base Policies' - } - - $ParamDictionary.Add('OnlyBasePolicies', [System.Management.Automation.RuntimeDefinedParameter]::new( - 'OnlyBasePolicies', - [switch], - [System.Management.Automation.ParameterAttribute[]]@($OnlyBasePoliciesDynamicParameter) - )) - - - # Create a dynamic parameter for -OnlySupplementalPolicies - $OnlySupplementalPoliciesDynamicParameter = [System.Management.Automation.ParameterAttribute]@{ - Mandatory = $false - ParameterSetName = 'List Active Policies' - HelpMessage = 'Only List Supplemental Policies' - } - - $ParamDictionary.Add('OnlySupplementalPolicies', [System.Management.Automation.RuntimeDefinedParameter]::new( - 'OnlySupplementalPolicies', - [switch], - [System.Management.Automation.ParameterAttribute[]]@($OnlySupplementalPoliciesDynamicParameter) - )) - } - - # Create a dynamic parameter for -SkipVersionCheck, Adding this parameter as dynamic will make it appear at the end of the parameters - $SkipVersionCheckDynamicParameter = [System.Management.Automation.ParameterAttribute]@{ - Mandatory = $false - # To make this parameter available for all parameter sets - ParameterSetName = '__AllParameterSets' - HelpMessage = 'Skip Version Check' - } - - $ParamDictionary.Add('SkipVersionCheck', [System.Management.Automation.RuntimeDefinedParameter]::new( - 'SkipVersionCheck', - [switch], - [System.Management.Automation.ParameterAttribute[]]@($SkipVersionCheckDynamicParameter) - )) - - return $ParamDictionary - } - - begin { - # Importing resources such as functions by dot-sourcing so that they will run in the same scope and their variables will be usable - . "$psscriptroot\Resources.ps1" - - - # Regular parameters are automatically bound to variables in the function scope - # Dynamic parameters however, are only available in the parameter dictionary, which is why we have to access them using $PSBoundParameters - # or assign them manually to another variable in the function's scope - $OnlyBasePolicies = $($PSBoundParameters['OnlyBasePolicies']) - $OnlySupplementalPolicies = $($PSBoundParameters['OnlySupplementalPolicies']) - $SkipVersionCheck = $($PSBoundParameters['SkipVersionCheck']) - - - # Stop operation as soon as there is an error anywhere, unless explicitly specified otherwise - $ErrorActionPreference = 'Stop' - if (-NOT $SkipVersionCheck) { . Update-self } - - # Script block to show only non-system Base policies - [scriptblock]$OnlyBasePoliciesBLOCK = { - $BasePolicies = (CiTool -lp -json | ConvertFrom-Json).Policies | Where-Object { $_.IsSystemPolicy -ne 'True' } | Where-Object { $_.PolicyID -eq $_.BasePolicyID } - &$WriteLavender "`nThere are currently $(($BasePolicies.count)) Non-system Base policies deployed" - $BasePolicies - } - # Script block to show only non-system Supplemental policies - [scriptblock]$OnlySupplementalPoliciesBLOCK = { - $SupplementalPolicies = (CiTool -lp -json | ConvertFrom-Json).Policies | Where-Object { $_.IsSystemPolicy -ne 'True' } | Where-Object { $_.PolicyID -ne $_.BasePolicyID } - &$WriteLavender "`nThere are currently $(($SupplementalPolicies.count)) Non-system Supplemental policies deployed`n" - $SupplementalPolicies - } - - # If no main parameter was passed, run all of them - if (!$ListActivePolicies -and !$VerifyWDACStatus -and !$CheckSmartAppControlStatus) { - $ListActivePolicies = $true - $VerifyWDACStatus = $true - $CheckSmartAppControlStatus = $true - } - } - - process { - if ($ListActivePolicies) { - if ($OnlyBasePolicies) { &$OnlyBasePoliciesBLOCK } - if ($OnlySupplementalPolicies) { &$OnlySupplementalPoliciesBLOCK } - if (!$OnlyBasePolicies -and !$OnlySupplementalPolicies) { &$OnlyBasePoliciesBLOCK; &$OnlySupplementalPoliciesBLOCK } - } - - if ($VerifyWDACStatus) { - Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard | Select-Object -Property *codeintegrity* | Format-List - &$WriteLavender "2 -> Enforced`n1 -> Audit mode`n0 -> Disabled/Not running`n" - } - - if ($CheckSmartAppControlStatus) { - Get-MpComputerStatus | Select-Object -Property SmartAppControlExpiration, SmartAppControlState - if ((Get-MpComputerStatus).SmartAppControlState -eq 'Eval') { - &$WritePink "`nSmart App Control is in Evaluation mode." - } - elseif ((Get-MpComputerStatus).SmartAppControlState -eq 'On') { - &$WritePink "`nSmart App Control is turned on." - } - elseif ((Get-MpComputerStatus).SmartAppControlState -eq 'Off') { - &$WritePink "`nSmart App Control is turned off." - } - } - } - - <# -.SYNOPSIS -Show the status of WDAC on the system and lists the current deployed policies and shows details about each of them - -.LINK -https://github.com/HotCakeX/Harden-Windows-Security/wiki/Confirm-WDACConfig - -.DESCRIPTION -Using official Microsoft methods, Show the status of WDAC (Windows Defender Application Control) on the system, list the current deployed policies and show details about each of them. - -.COMPONENT -Windows Defender Application Control, ConfigCI PowerShell module - -.FUNCTIONALITY -Using official Microsoft methods, Show the status of WDAC (Windows Defender Application Control) on the system, list the current deployed policies and show details about each of them. - -.PARAMETER ListActivePolicies -Lists the currently deployed policies and shows details about each of them - -.PARAMETER VerifyWDACStatus -Shows the status of WDAC (Windows Defender Application Control) on the system - -.PARAMETER CheckSmartAppControlStatus -Checks the status of Smart App Control and reports the results on the console - -.PARAMETER SkipVersionCheck -Can be used with any parameter to bypass the online version check - only to be used in rare cases - -.EXAMPLE -Confirm-WDACConfig -ListActivePolicies -OnlyBasePolicies - -.EXAMPLE -Confirm-WDACConfig -ListActivePolicies -OnlySupplementalPolicies - -.EXAMPLE -Confirm-WDACConfig -ListActivePolicies - -#> -} - -# Set PSReadline tab completion to complete menu for easier access to available parameters - Only for the current session -Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete diff --git a/WDACConfig/Deploy-SignedWDACConfig.psm1 b/WDACConfig/Deploy-SignedWDACConfig.psm1 deleted file mode 100644 index f5432ee3a..000000000 --- a/WDACConfig/Deploy-SignedWDACConfig.psm1 +++ /dev/null @@ -1,264 +0,0 @@ -#Requires -RunAsAdministrator -function Deploy-SignedWDACConfig { - [CmdletBinding( - SupportsShouldProcess = $true, - PositionalBinding = $false, - ConfirmImpact = 'High' - )] - Param( - [ValidatePattern('\.xml$')] - [ValidateScript({ Test-Path $_ -PathType 'Leaf' }, ErrorMessage = 'The path you selected is not a file path.')] - [parameter(Mandatory = $true)][System.String[]]$PolicyPaths, - - [Parameter(Mandatory = $false)][Switch]$Deploy, - - [ValidatePattern('\.cer$')] - [ValidateScript({ Test-Path $_ -PathType 'Leaf' }, ErrorMessage = 'The path you selected is not a file path.')] - [parameter(Mandatory = $false)][System.String]$CertPath, - - [ValidateScript({ - $certs = foreach ($cert in (Get-ChildItem 'Cert:\CurrentUser\my')) { - (($cert.Subject -split ',' | Select-Object -First 1) -replace 'CN=', '').Trim() - } - $certs -contains $_ - }, ErrorMessage = "A certificate with the provided common name doesn't exist in the personal store of the user certificates." )] - [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)][System.String]$CertCN, - - [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] - [System.String]$SignToolPath, - - [Parameter(Mandatory = $false)][Switch]$SkipVersionCheck - ) - - begin { - # Importing resources such as functions by dot-sourcing so that they will run in the same scope and their variables will be usable - . "$psscriptroot\Resources.ps1" - - # Stop operation as soon as there is an error anywhere, unless explicitly specified otherwise - $ErrorActionPreference = 'Stop' - if (-NOT $SkipVersionCheck) { . Update-self } - - # Detecting if Debug switch is used, will do debugging actions based on that - $Debug = $PSBoundParameters.Debug.IsPresent - - # Fetch User account directory path - [string]$global:UserAccountDirectoryPath = (Get-CimInstance Win32_UserProfile -Filter "SID = '$([System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value)'").LocalPath - - #region User-Configurations-Processing-Validation - # If any of these parameters, that are mandatory for all of the position 0 parameters, isn't supplied by user - if (!$SignToolPath -or !$CertPath -or !$CertCN) { - # Read User configuration file if it exists - $UserConfig = Get-Content -Path "$global:UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" -ErrorAction SilentlyContinue - if ($UserConfig) { - # Validate the Json file and read its content to make sure it's not corrupted - try { $UserConfig = $UserConfig | ConvertFrom-Json } - catch { - Write-Error 'User Configuration Json file is corrupted, deleting it...' -ErrorAction Continue - # Calling this function with this parameter automatically does its job and breaks/stops the operation - Set-CommonWDACConfig -DeleteUserConfig - } - } - } - - # Get SignToolPath from user parameter or user config file or auto-detect it - if ($SignToolPath) { - $SignToolPathFinal = Get-SignTool -SignToolExePath $SignToolPath - } # If it is null, then Get-SignTool will behave the same as if it was called without any arguments. - else { - $SignToolPathFinal = Get-SignTool -SignToolExePath ($UserConfig.SignToolCustomPath ?? $null) - } - - # If CertPath parameter wasn't provided by user - if (!$CertPath) { - if ($UserConfig.CertificatePath) { - # validate user config values for Certificate Path - if (Test-Path $($UserConfig.CertificatePath)) { - # If the user config values are correct then use them - $CertPath = $UserConfig.CertificatePath - } - else { - throw 'The currently saved value for CertPath in user configurations is invalid.' - } - } - else { - throw "CertPath parameter can't be empty and no valid configuration was found for it." - } - } - - # If CertCN was not provided by user - if (!$CertCN) { - if ($UserConfig.CertificateCommonName) { - # Check if the value in the User configuration file exists and is valid - if (Confirm-CertCN $($UserConfig.CertificateCommonName)) { - # if it's valid then use it - $CertCN = $UserConfig.CertificateCommonName - } - else { - throw 'The currently saved value for CertCN in user configurations is invalid.' - } - } - else { - throw "CertCN parameter can't be empty and no valid configuration was found for it." - } - } - #endregion User-Configurations-Processing-Validation - } - - process { - foreach ($PolicyPath in $PolicyPaths) { - - # Gather policy details - $xml = [xml](Get-Content $PolicyPath) - [System.String]$PolicyType = $xml.SiPolicy.PolicyType - [System.String]$PolicyID = $xml.SiPolicy.PolicyID - [System.String]$PolicyName = ($xml.SiPolicy.Settings.Setting | Where-Object { $_.provider -eq 'PolicyInfo' -and $_.valuename -eq 'Name' -and $_.key -eq 'Information' }).value.string - [System.String[]]$PolicyRuleOptions = $xml.SiPolicy.Rules.Rule.Option - - # Remove the .CIP file of the same policy being signed and deployed if any in the current working directory - Remove-Item -Path ".\$PolicyID.cip" -ErrorAction SilentlyContinue - - # Ensure -Supplemental is not used when the policy type is supplemental - if ($PolicyType -eq 'Supplemental Policy') { - # Make sure -User is not added if the UMCI policy rule option doesn't exist in the policy, typically for Strict kernel mode policies - if ('Enabled:UMCI' -in $PolicyRuleOptions) { - Add-SignerRule -FilePath $PolicyPath -CertificatePath $CertPath -Update -User -Kernel - } - else { - Add-SignerRule -FilePath $PolicyPath -CertificatePath $CertPath -Update -Kernel - } - } - else { - # Make sure -User is not added if the UMCI policy rule option doesn't exist in the policy, typically for Strict kernel mode policies - if ('Enabled:UMCI' -in $PolicyRuleOptions) { - Add-SignerRule -FilePath $PolicyPath -CertificatePath $CertPath -Update -User -Kernel -Supplemental - } - else { - Add-SignerRule -FilePath $PolicyPath -CertificatePath $CertPath -Update -Kernel -Supplemental - } - } - Set-HVCIOptions -Strict -FilePath $PolicyPath - Set-RuleOption -FilePath $PolicyPath -Option 6 -Delete - ConvertFrom-CIPolicy $PolicyPath "$PolicyID.cip" | Out-Null - - # Configure the parameter splat - $ProcessParams = @{ - 'ArgumentList' = 'sign', '/v' , '/n', "`"$CertCN`"", '/p7', '.', '/p7co', '1.3.6.1.4.1.311.79.1', '/fd', 'certHash', ".\$PolicyID.cip" - 'FilePath' = $SignToolPathFinal - 'NoNewWindow' = $true - 'Wait' = $true - 'ErrorAction' = 'Stop' - } - # Hide the SignTool.exe's normal output unless -Debug parameter was used - if (!$Debug) { $ProcessParams['RedirectStandardOutput'] = 'NUL' } - # Sign the files with the specified cert - Start-Process @ProcessParams - - Remove-Item ".\$PolicyID.cip" -Force - Rename-Item "$PolicyID.cip.p7" -NewName "$PolicyID.cip" -Force - - if ($Deploy) { - - CiTool --update-policy ".\$PolicyID.cip" -json | Out-Null - Write-Host "`npolicy with the following details has been Signed and Deployed in Enforced Mode:" -ForegroundColor Green - Write-Output "PolicyName = $PolicyName" - Write-Output "PolicyGUID = $PolicyID`n" - Remove-Item -Path ".\$PolicyID.cip" -Force - - #region Detecting Strict Kernel mode policy and removing it from User Configs - if ('Enabled:UMCI' -notin $PolicyRuleOptions) { - - [System.String]$StrictKernelPolicyGUID = Get-CommonWDACConfig -StrictKernelPolicyGUID - [System.String]$StrictKernelNoFlightRootsPolicyGUID = Get-CommonWDACConfig -StrictKernelNoFlightRootsPolicyGUID - - if (($PolicyName -like '*Strict Kernel mode policy Enforced*')) { - if ($StrictKernelPolicyGUID) { - if ($($PolicyID.TrimStart('{').TrimEnd('}')) -eq $StrictKernelPolicyGUID) { - Remove-CommonWDACConfig -StrictKernelPolicyGUID | Out-Null - } - } - } - - elseif (($PolicyName -like '*Strict Kernel No Flights mode policy Enforced*')) { - if ($StrictKernelNoFlightRootsPolicyGUID) { - if ($($PolicyID.TrimStart('{').TrimEnd('}')) -eq $StrictKernelNoFlightRootsPolicyGUID) { - Remove-CommonWDACConfig -StrictKernelNoFlightRootsPolicyGUID | Out-Null - } - } - } - } - #endregion Detecting Strict Kernel mode policy and removing it from User Configs - - # Show the question only for base policies. Don't show it for Strict kernel mode policies - if (($PolicyType -ne 'Supplemental Policy') -and ($PolicyName -notlike '*Strict Kernel*')) { - - # Ask user question about whether or not to add the Signed policy xml file to the User Config Json for easier usage later - $userInput = '' - while ($userInput -notin 1, 2) { - $userInput = $(Write-Host 'Add the Signed policy xml file path just created to the User Configurations? Please enter 1 to Confirm or 2 to Skip.' -ForegroundColor Cyan ; Read-Host) - if ($userInput -eq 1) { - Set-CommonWDACConfig -SignedPolicyPath $PolicyPath - &$WriteHotPink "Added $PolicyPath to the User Configuration file." - } - elseif ($userInput -eq 2) { - &$WritePink 'Skipping...' - } - else { - Write-Warning 'Invalid input. Please enter 1 or 2 only.' - } - } - } - } - - else { - Write-Host "`npolicy with the following details has been Signed and is ready for deployment:" -ForegroundColor Green - Write-Output "PolicyName = $PolicyName" - Write-Output "PolicyGUID = $PolicyID`n" - } - } - } - - <# -.SYNOPSIS -Signs and Deploys WDAC policies, accepts signed or unsigned policies and deploys them - -.LINK -https://github.com/HotCakeX/Harden-Windows-Security/wiki/Deploy-SignedWDACConfig - -.DESCRIPTION -Using official Microsoft methods, Signs and Deploys WDAC policies, accepts signed or unsigned policies and deploys them (Windows Defender Application Control) - -.COMPONENT -Windows Defender Application Control, ConfigCI PowerShell module - -.FUNCTIONALITY -Using official Microsoft methods, Signs and Deploys WDAC policies, accepts signed or unsigned policies and deploys them (Windows Defender Application Control) - -.PARAMETER CertPath -Path to the certificate .cer file - -.PARAMETER PolicyPaths -Path to the policy xml files that are going to be signed - -.PARAMETER CertCN -Certificate common name - -.PARAMETER SignToolPath -Path to the SignTool.exe - optional parameter - -.PARAMETER Deploy -Indicates that the cmdlet will deploy the signed policy on the current system - -.PARAMETER SkipVersionCheck -Can be used with any parameter to bypass the online version check - only to be used in rare cases - -#> -} - -# Importing argument completer ScriptBlocks -. "$psscriptroot\ArgumentCompleters.ps1" -# Set PSReadline tab completion to complete menu for easier access to available parameters - Only for the current session -Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete -Register-ArgumentCompleter -CommandName 'Deploy-SignedWDACConfig' -ParameterName 'CertCN' -ScriptBlock $ArgumentCompleterCertificateCN -Register-ArgumentCompleter -CommandName 'Deploy-SignedWDACConfig' -ParameterName 'PolicyPaths' -ScriptBlock $ArgumentCompleterPolicyPaths -Register-ArgumentCompleter -CommandName 'Deploy-SignedWDACConfig' -ParameterName 'CertPath' -ScriptBlock $ArgumentCompleterCerFilePathsPicker -Register-ArgumentCompleter -CommandName 'Deploy-SignedWDACConfig' -ParameterName 'SignToolPath' -ScriptBlock $ArgumentCompleterExeFilePathsPicker diff --git a/WDACConfig/Edit-SignedWDACConfig.psm1 b/WDACConfig/Edit-SignedWDACConfig.psm1 deleted file mode 100644 index f922fce72..000000000 --- a/WDACConfig/Edit-SignedWDACConfig.psm1 +++ /dev/null @@ -1,1035 +0,0 @@ -#Requires -RunAsAdministrator -function Edit-SignedWDACConfig { - [CmdletBinding( - DefaultParameterSetName = 'Allow New Apps Audit Events', - SupportsShouldProcess = $true, - PositionalBinding = $false, - ConfirmImpact = 'High' - )] - Param( - [Alias('E')] - [Parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')][Switch]$AllowNewAppsAuditEvents, - [Alias('A')] - [Parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')][Switch]$AllowNewApps, - [Alias('M')] - [Parameter(Mandatory = $false, ParameterSetName = 'Merge Supplemental Policies')][Switch]$MergeSupplementalPolicies, - [Alias('U')] - [Parameter(Mandatory = $false, ParameterSetName = 'Update Base Policy')][Switch]$UpdateBasePolicy, - - [ValidatePattern('^[a-zA-Z0-9 ]+$', ErrorMessage = 'The Supplemental Policy Name can only contain alphanumeric and space characters.')] - [Parameter(Mandatory = $true, ParameterSetName = 'Allow New Apps Audit Events', ValueFromPipelineByPropertyName = $true)] - [Parameter(Mandatory = $true, ParameterSetName = 'Allow New Apps', ValueFromPipelineByPropertyName = $true)] - [Parameter(Mandatory = $true, ParameterSetName = 'Merge Supplemental Policies', ValueFromPipelineByPropertyName = $true)] - [System.String]$SuppPolicyName, - - [ValidatePattern('\.xml$')] - [ValidateScript({ Test-Path $_ -PathType 'Leaf' }, ErrorMessage = 'The path you selected is not a file path.')] - [Parameter(Mandatory = $true, ParameterSetName = 'Merge Supplemental Policies', ValueFromPipelineByPropertyName = $true)] - [System.String[]]$SuppPolicyPaths, - - [Parameter(Mandatory = $false, ParameterSetName = 'Merge Supplemental Policies')] - [switch]$KeepOldSupplementalPolicies, - - [ValidateSet([BasePolicyNamez])] - [Parameter(Mandatory = $true, ParameterSetName = 'Update Base Policy')] - [System.String[]]$CurrentBasePolicyName, - - [ValidateSet('AllowMicrosoft_Plus_Block_Rules', 'Lightly_Managed_system_Policy', 'DefaultWindows_WithBlockRules')] - [Parameter(Mandatory = $true, ParameterSetName = 'Update Base Policy')] - [System.String]$NewBasePolicyType, - - [ValidatePattern('\.cer$')] # Used by the entire Cmdlet - [ValidateScript({ Test-Path $_ -PathType 'Leaf' }, ErrorMessage = 'The path you selected is not a file path.')] - [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] - [System.String]$CertPath, - - [ValidatePattern('\.xml$')] - [ValidateScript({ - # Validate each Policy file in PolicyPaths parameter to make sure the user isn't accidentally trying to - # Edit an Unsigned policy using Edit-SignedWDACConfig cmdlet which is only made for Signed policies - $_ | ForEach-Object { - $xmlTest = [xml](Get-Content $_) - $RedFlag1 = $xmlTest.SiPolicy.SupplementalPolicySigners.SupplementalPolicySigner.SignerId - $RedFlag2 = $xmlTest.SiPolicy.UpdatePolicySigners.UpdatePolicySigner.SignerId - $RedFlag3 = $xmlTest.SiPolicy.PolicyID - $CurrentPolicyIDs = ((CiTool -lp -json | ConvertFrom-Json).Policies | Where-Object { $_.IsSystemPolicy -ne 'True' }).policyID | ForEach-Object { "{$_}" } - if ($RedFlag1 -or $RedFlag2) { - # Ensure the selected base policy xml file is deployed - if ($CurrentPolicyIDs -contains $RedFlag3) { - return $True - } - else { throw "The currently selected policy xml file isn't deployed." } - } - # This throw is shown only when User added a Signed policy xml file for Unsigned policy file path property in user configuration file - # Without this, the error shown would be vague: The variable cannot be validated because the value System.String[] is not a valid value for the PolicyPaths variable. - else { throw 'The policy xml file in User Configurations for SignedPolicyPath is Unsigned policy.' } - } - }, ErrorMessage = 'The selected policy xml file is Unsigned. Please use Edit-WDACConfig cmdlet to edit Unsigned policies.')] - [Parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events', ValueFromPipelineByPropertyName = $true)] - [Parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps', ValueFromPipelineByPropertyName = $true)] - [Parameter(Mandatory = $false, ParameterSetName = 'Merge Supplemental Policies', ValueFromPipelineByPropertyName = $true)] - [System.String[]]$PolicyPaths, - - [ValidateScript({ - $certs = foreach ($cert in (Get-ChildItem 'Cert:\CurrentUser\my')) { - (($cert.Subject -split ',' | Select-Object -First 1) -replace 'CN=', '').Trim() - } - $certs -contains $_ - }, ErrorMessage = "A certificate with the provided common name doesn't exist in the personal store of the user certificates." )] - [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] # Used by the entire Cmdlet - [System.String]$CertCN, - - # Setting the maxim range to the maximum allowed log size by Windows Event viewer - [ValidateRange(1024KB, 18014398509481983KB)][Parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] - [System.Int64]$LogSize, - - [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] - [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')] - [Switch]$NoScript, - - [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] - [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')] - [Switch]$NoUserPEs, - - [ValidateSet('OriginalFileName', 'InternalName', 'FileDescription', 'ProductName', 'PackageFamilyName', 'FilePath')] - [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] - [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')] - [System.String]$SpecificFileNameLevel, - - [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')][Switch]$IncludeDeletedFiles, - - [ValidateSet([Levelz])] - [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] - [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')] - [System.String]$Level = 'FilePublisher', # Setting the default value for the Level parameter - - [ValidateSet([Fallbackz])] - [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] - [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')] - [System.String[]]$Fallbacks = 'Hash', # Setting the default value for the Fallbacks parameter - - [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] # Used by the entire Cmdlet - [System.String]$SignToolPath, - - [Parameter(Mandatory = $false, ParameterSetName = 'Update Base Policy')] - [Switch]$RequireEVSigners, - - [Parameter(Mandatory = $false)] # Used by the entire Cmdlet - [Switch]$SkipVersionCheck - ) - - begin { - # Importing resources such as functions by dot-sourcing so that they will run in the same scope and their variables will be usable - . "$psscriptroot\Resources.ps1" - - # Stop operation as soon as there is an error anywhere, unless explicitly specified otherwise - $ErrorActionPreference = 'Stop' - if (-NOT $SkipVersionCheck) { . Update-self } - - # Fetching Temp Directory - [string]$global:UserTempDirectoryPath = [System.IO.Path]::GetTempPath() - - # Fetch User account directory path - [string]$global:UserAccountDirectoryPath = (Get-CimInstance Win32_UserProfile -Filter "SID = '$([System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value)'").LocalPath - - #region User-Configurations-Processing-Validation - # If any of these parameters, that are mandatory for all of the position 0 parameters, isn't supplied by user - if (!$PolicyPaths -or !$SignToolPath -or !$CertPath -or !$CertCN) { - # Read User configuration file if it exists - $UserConfig = Get-Content -Path "$global:UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" -ErrorAction SilentlyContinue - if ($UserConfig) { - # Validate the Json file and read its content to make sure it's not corrupted - try { $UserConfig = $UserConfig | ConvertFrom-Json } - catch { - Write-Error 'User Configurations Json file is corrupted, deleting it...' -ErrorAction Continue - # Calling this function with this parameter automatically does its job and breaks/stops the operation - Set-CommonWDACConfig -DeleteUserConfig - } - } - } - - # Get SignToolPath from user parameter or user config file or auto-detect it - if ($SignToolPath) { - $SignToolPathFinal = Get-SignTool -SignToolExePath $SignToolPath - } # If it is null, then Get-SignTool will behave the same as if it was called without any arguments. - else { - $SignToolPathFinal = Get-SignTool -SignToolExePath ($UserConfig.SignToolCustomPath ?? $null) - } - - # If CertPath parameter wasn't provided by user - if (!$CertPath) { - if ($UserConfig.CertificatePath) { - # validate user config values for Certificate Path - if (Test-Path $($UserConfig.CertificatePath)) { - # If the user config values are correct then use them - $CertPath = $UserConfig.CertificatePath - } - else { - throw 'The currently saved value for CertPath in user configurations is invalid.' - } - } - else { - throw "CertPath parameter can't be empty and no valid configuration was found for it." - } - } - - # If CertCN was not provided by user - if (!$CertCN) { - if ($UserConfig.CertificateCommonName) { - # Check if the value in the User configuration file exists and is valid - if (Confirm-CertCN $($UserConfig.CertificateCommonName)) { - # if it's valid then use it - $CertCN = $UserConfig.CertificateCommonName - } - else { - throw 'The currently saved value for CertCN in user configurations is invalid.' - } - } - else { - throw "CertCN parameter can't be empty and no valid configuration was found for it." - } - } - - # If PolicyPaths has no values - if (!$PolicyPaths) { - # make sure the ParameterSet being used has PolicyPaths parameter - Then enforces "mandatory" attribute for the parameter - if ($PSCmdlet.ParameterSetName -in 'Allow New Apps Audit Events', 'Allow New Apps', 'Merge Supplemental Policies') { - if ($UserConfig.SignedPolicyPath) { - # validate each policyPath read from user config file - if (Test-Path $($UserConfig.SignedPolicyPath)) { - $PolicyPaths = $UserConfig.SignedPolicyPath - } - else { - throw 'The currently saved value for SignedPolicyPath in user configurations is invalid.' - } - } - else { - throw "PolicyPaths parameter can't be empty and no valid configuration was found for SignedPolicyPath." - } - } - } - #endregion User-Configurations-Processing-Validation - - # Detecting if Debug switch is used, will do debugging actions based on that - $Debug = $PSBoundParameters.Debug.IsPresent - - # argument tab auto-completion and ValidateSet for Policy names - Class BasePolicyNamez : System.Management.Automation.IValidateSetValuesGenerator { - [System.String[]] GetValidValues() { - $BasePolicyNamez = ((CiTool -lp -json | ConvertFrom-Json).Policies | Where-Object { $_.IsSystemPolicy -ne 'True' } | Where-Object { $_.PolicyID -eq $_.BasePolicyID }).Friendlyname - return [System.String[]]$BasePolicyNamez - } - } - - # argument tab auto-completion and ValidateSet for Fallbacks - Class Fallbackz : System.Management.Automation.IValidateSetValuesGenerator { - [System.String[]] GetValidValues() { - $Fallbackz = ('Hash', 'FileName', 'SignedVersion', 'Publisher', 'FilePublisher', 'LeafCertificate', 'PcaCertificate', 'RootCertificate', 'WHQL', 'WHQLPublisher', 'WHQLFilePublisher', 'PFN', 'FilePath', 'None') - return [System.String[]]$Fallbackz - } - } - - # argument tab auto-completion and ValidateSet for level - Class Levelz : System.Management.Automation.IValidateSetValuesGenerator { - [System.String[]] GetValidValues() { - $Levelz = ('Hash', 'FileName', 'SignedVersion', 'Publisher', 'FilePublisher', 'LeafCertificate', 'PcaCertificate', 'RootCertificate', 'WHQL', 'WHQLPublisher', 'WHQLFilePublisher', 'PFN', 'FilePath', 'None') - return [System.String[]]$Levelz - } - } - - #Re-Deploy Basepolicy in Enforced mode - function Update-BasePolicyToEnforced { - # Deploy Enforced mode CIP - CiTool --update-policy ".\$PolicyID.cip" -json | Out-Null - &$WriteTeaGreen "`nThe Base policy with the following details has been Re-Signed and Re-Deployed in Enforced Mode:" - Write-Output "PolicyName = $PolicyName" - Write-Output "PolicyGUID = $PolicyID" - # Remove Enforced Mode CIP - Remove-Item ".\$PolicyID.cip" -Force - } - $DriveLettersGlobalRootFix = Invoke-Command -ScriptBlock $DriveLettersGlobalRootFixScriptBlock - } - - process { - - if ($AllowNewAppsAuditEvents) { - - # Change Code Integrity event logs size - if ($AllowNewAppsAuditEvents -and $LogSize) { Set-LogSize -LogSize $LogSize } - # Make sure there is no leftover from previous runs - Remove-Item -Path '.\ProgramDir_ScanResults*.xml' -Force -ErrorAction SilentlyContinue - Remove-Item -Path ".\SupplementalPolicy $SuppPolicyName.xml" -Force -ErrorAction SilentlyContinue - # Get the current date so that instead of the entire event viewer logs, only audit logs created after running this module will be captured - # The notice about variable being assigned and never used should be ignored - it's being dot-sourced from Resources file - [datetime]$Date = Get-Date - # An empty array that holds the Policy XML files - This array will eventually be used to create the final Supplemental policy - [System.Object[]]$PolicyXMLFilesArray = @() - - ################################### Initiate Live Audit Mode ################################### - - foreach ($PolicyPath in $PolicyPaths) { - # Creating a copy of the original policy in Temp folder so that the original one will be unaffected - $PolicyFileName = Split-Path $PolicyPath -Leaf - Remove-Item -Path "$global:UserTempDirectoryPath\$PolicyFileName" -Force -ErrorAction SilentlyContinue # make sure no file with the same name already exists in Temp folder - Copy-Item -Path $PolicyPath -Destination $global:UserTempDirectoryPath -Force - $PolicyPath = "$global:UserTempDirectoryPath\$PolicyFileName" - - # Defining Base policy - $xml = [xml](Get-Content $PolicyPath) - [System.String]$PolicyID = $xml.SiPolicy.PolicyID - [System.String]$PolicyName = ($xml.SiPolicy.Settings.Setting | Where-Object { $_.provider -eq 'PolicyInfo' -and $_.valuename -eq 'Name' -and $_.key -eq 'Information' }).value.string - - # Remove any cip file if there is any - Remove-Item -Path '.\*.cip' -Force -ErrorAction SilentlyContinue - - # Create CIP for Audit Mode - Set-RuleOption -FilePath $PolicyPath -Option 6 -Delete # Remove Unsigned policy rule option - Set-RuleOption -FilePath $PolicyPath -Option 3 # Add Audit mode policy rule option - ConvertFrom-CIPolicy $PolicyPath '.\AuditModeTemp.cip' | Out-Null - - # Create CIP for Enforced Mode - Set-RuleOption -FilePath $PolicyPath -Option 6 -Delete # Remove Unsigned policy rule option - Set-RuleOption -FilePath $PolicyPath -Option 3 -Delete # Remove Audit mode policy rule option - ConvertFrom-CIPolicy $PolicyPath '.\EnforcedModeTemp.cip' | Out-Null - - # Sign both CIPs - '.\AuditModeTemp.cip', '.\EnforcedModeTemp.cip' | ForEach-Object { - # Configure the parameter splat - $ProcessParams = @{ - 'ArgumentList' = 'sign', '/v' , '/n', "`"$CertCN`"", '/p7', '.', '/p7co', '1.3.6.1.4.1.311.79.1', '/fd', 'certHash', "`"$_`"" - 'FilePath' = $SignToolPathFinal - 'NoNewWindow' = $true - 'Wait' = $true - 'ErrorAction' = 'Stop' - } # Only show the output of SignTool if Debug switch is used - if (!$Debug) { $ProcessParams['RedirectStandardOutput'] = 'NUL' } - # Sign the files with the specified cert - Start-Process @ProcessParams - - # After creating signed .p7 files for each CIP, remove the old Unsigned ones - Remove-Item -Path $_ -Force - } - Rename-Item '.\EnforcedModeTemp.cip.p7' -NewName '.\EnforcedMode.cip' -Force - Rename-Item '.\AuditModeTemp.cip.p7' -NewName '.\AuditMode.cip' -Force - - ################# Snap back guarantee ################# - Write-Debug -Message 'Creating Enforced Mode SnapBack guarantee' - - $registryPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' - $command = @" -CiTool --update-policy "$((Get-Location).Path)\$PolicyID.cip" -json; Remove-Item -Path "$((Get-Location).Path)\$PolicyID.cip" -Force -"@ - $command | Out-File 'C:\EnforcedModeSnapBack.ps1' - New-ItemProperty -Path $registryPath -Name '*CIPolicySnapBack' -Value "powershell.exe -WindowStyle `"Hidden`" -ExecutionPolicy `"Bypass`" -Command `"& {&`"C:\EnforcedModeSnapBack.ps1`";Remove-Item -Path 'C:\EnforcedModeSnapBack.ps1' -Force}`"" -PropertyType String -Force | Out-Null - - # Deploy Audit mode CIP - Write-Debug -Message 'Deploying Audit mode CIP' - Rename-Item '.\AuditMode.cip' -NewName ".\$PolicyID.cip" -Force - CiTool --update-policy ".\$PolicyID.cip" -json | Out-Null - &$WriteTeaGreen "`nThe Base policy with the following details has been Re-Signed and Re-Deployed in Audit Mode:" - Write-Output "PolicyName = $PolicyName" - Write-Output "PolicyGUID = $PolicyID" - # Remove Audit Mode CIP - Remove-Item ".\$PolicyID.cip" -Force - # Prepare Enforced Mode CIP for Deployment - waiting to be Re-deployed at the right time - Rename-Item '.\EnforcedMode.cip' -NewName ".\$PolicyID.cip" -Force - - # A Try-Catch-Finally block so that if any errors occur, the Base policy will be Re-deployed in enforced mode - Try { - ################################### User Interaction #################################### - &$WritePink "`nAudit mode deployed, start installing your programs now" - &$WriteHotPink "When you've finished installing programs, Press Enter to start selecting program directories to scan`n" - Pause - - # Store the program paths that user browses for in an array - [System.Object[]]$ProgramsPaths = @() - Write-Host "`nSelect program directories to scan" -ForegroundColor Cyan - # Showing folder picker GUI to the user for folder path selection - do { - [System.Reflection.Assembly]::LoadWithPartialName('System.windows.forms') | Out-Null - $OBJ = New-Object System.Windows.Forms.FolderBrowserDialog - $OBJ.InitialDirectory = "$env:SystemDrive" - $OBJ.Description = $Description - $Spawn = New-Object System.Windows.Forms.Form -Property @{TopMost = $true } - $Show = $OBJ.ShowDialog($Spawn) - If ($Show -eq 'OK') { $ProgramsPaths += $OBJ.SelectedPath } - Else { break } - } - while ($true) - - # Make sure User browsed for at least 1 directory - # Exit the operation if user didn't select any folder paths - if ($ProgramsPaths.count -eq 0) { - Write-Host "`nNo program folder was selected, reverting the changes and quitting...`n" -ForegroundColor Red - # Causing break here to stop operation. Finally block will be triggered to Re-Deploy Base policy in Enforced mode - break - } - - Write-Host 'Here are the paths you selected:' -ForegroundColor Yellow - $ProgramsPaths | ForEach-Object { $_ } - - ################################### EventCapturing ################################ - - Write-Host 'Scanning Windows Event logs and creating a policy file, please wait...' -ForegroundColor Cyan - - # Extracting the array content from Get-AuditEventLogsProcessing function - $AuditEventLogsProcessingResults = Get-AuditEventLogsProcessing -Date $Date - - # Only create policy for files that are available on the disk based on Event viewer logs but weren't in user-selected program path(s), if there are any - if ($AuditEventLogsProcessingResults.AvailableFilesPaths) { - - # Using the function to find out which files are not in the user-selected path(s), if any, to only scan those - # this prevents duplicate rule creation and double file copying - $TestFilePathResults = (Test-FilePath -FilePath $AuditEventLogsProcessingResults.AvailableFilesPaths -DirectoryPath $ProgramsPaths).path | Select-Object -Unique - - Write-Debug -Message "$($TestFilePathResults.count) file(s) have been found in event viewer logs that don't exist in any of the folder paths you selected." - - # Another check to make sure there were indeed files found in Event viewer logs but weren't in any of the user-selected path(s) - if ($TestFilePathResults) { - # Create a folder in Temp directory to copy the files that are not included in user-selected program path(s) - # but detected in Event viewer audit logs, scan that folder, and in the end delete it - New-Item -Path "$global:UserTempDirectoryPath\TemporaryScanFolderForEventViewerFiles" -ItemType Directory | Out-Null - - Write-Debug -Message "The following file(s) are being copied to the TEMP directory for scanning because they were found in event logs but didn't exist in any of the user-selected paths:" - $TestFilePathResults | ForEach-Object { - Write-Debug -Message "$_" - Copy-Item -Path $_ -Destination "$global:UserTempDirectoryPath\TemporaryScanFolderForEventViewerFiles\" -ErrorAction SilentlyContinue - } - - # Create a policy XML file for available files on the disk - - # Creating a hash table to dynamically add parameters based on user input and pass them to New-Cipolicy cmdlet - [System.Collections.Hashtable]$AvailableFilesOnDiskPolicyMakerHashTable = @{ - FilePath = '.\RulesForFilesNotInUserSelectedPaths.xml' - ScanPath = "$global:UserTempDirectoryPath\TemporaryScanFolderForEventViewerFiles\" - Level = $Level - Fallback = $Fallbacks - MultiplePolicyFormat = $true - UserWriteablePaths = $true - AllowFileNameFallbacks = $true - } - # Assess user input parameters and add the required parameters to the hash table - if ($SpecificFileNameLevel) { $AvailableFilesOnDiskPolicyMakerHashTable['SpecificFileNameLevel'] = $SpecificFileNameLevel } - if ($NoScript) { $AvailableFilesOnDiskPolicyMakerHashTable['NoScript'] = $true } - if (!$NoUserPEs) { $AvailableFilesOnDiskPolicyMakerHashTable['UserPEs'] = $true } - - # Create the supplemental policy via parameter splatting - New-CIPolicy @AvailableFilesOnDiskPolicyMakerHashTable - - # Add the policy XML file to the array that holds policy XML files - $PolicyXMLFilesArray += '.\RulesForFilesNotInUserSelectedPaths.xml' - # Delete the Temporary folder in the TEMP folder - Remove-Item -Recurse -Path "$global:UserTempDirectoryPath\TemporaryScanFolderForEventViewerFiles\" -Force - } - } - - # Only create policy for files that are on longer available on the disk if there are any and - # if user chose to include deleted files in the final supplemental policy - if ($AuditEventLogsProcessingResults.DeletedFileHashes -and $IncludeDeletedFiles) { - - Write-Debug -Message "$($AuditEventLogsProcessingResults.DeletedFileHashes.count) file(s) have been found in event viewer logs that were run during Audit phase but are no longer on the disk, they are as follows:" - $AuditEventLogsProcessingResults.DeletedFileHashes | ForEach-Object { - Write-Debug -Message "$($_.'File Name')" - } - - # Save the File Rules and File Rule Refs in the FileRulesAndFileRefs.txt in the current working directory for debugging purposes - (Get-FileRules -HashesArray $AuditEventLogsProcessingResults.DeletedFileHashes) + (Get-RuleRefs -HashesArray $AuditEventLogsProcessingResults.DeletedFileHashes) | Out-File FileRulesAndFileRefs.txt - - # Put the Rules and RulesRefs in an empty policy file - New-EmptyPolicy -RulesContent (Get-FileRules -HashesArray $AuditEventLogsProcessingResults.DeletedFileHashes) -RuleRefsContent (Get-RuleRefs -HashesArray $AuditEventLogsProcessingResults.DeletedFileHashes) | Out-File .\DeletedFileHashesEventsPolicy.xml - - # adding the policy file that consists of rules from audit even logs, to the array - $PolicyXMLFilesArray += '.\DeletedFileHashesEventsPolicy.xml' - } - - ######################## Process Program Folders From User input ##################### - for ($i = 0; $i -lt $ProgramsPaths.Count; $i++) { - - # Creating a hash table to dynamically add parameters based on user input and pass them to New-Cipolicy cmdlet - [System.Collections.Hashtable]$UserInputProgramFoldersPolicyMakerHashTable = @{ - FilePath = ".\ProgramDir_ScanResults$($i).xml" - ScanPath = $ProgramsPaths[$i] - Level = $Level - Fallback = $Fallbacks - MultiplePolicyFormat = $true - UserWriteablePaths = $true - AllowFileNameFallbacks = $true - } - # Assess user input parameters and add the required parameters to the hash table - if ($SpecificFileNameLevel) { $UserInputProgramFoldersPolicyMakerHashTable['SpecificFileNameLevel'] = $SpecificFileNameLevel } - if ($NoScript) { $UserInputProgramFoldersPolicyMakerHashTable['NoScript'] = $true } - if (!$NoUserPEs) { $UserInputProgramFoldersPolicyMakerHashTable['UserPEs'] = $true } - - # Create the supplemental policy via parameter splatting - New-CIPolicy @UserInputProgramFoldersPolicyMakerHashTable - } - - # merge-cipolicy accept arrays - collecting all the policy files created by scanning user specified folders - $ProgramDir_ScanResults = Get-ChildItem '.\' | Where-Object { $_.Name -like 'ProgramDir_ScanResults*.xml' } - foreach ($file in $ProgramDir_ScanResults) { - $PolicyXMLFilesArray += $file.FullName - } - - #region Kernel-protected-files-automatic-detection-and-allow-rule-creation - # This part takes care of Kernel protected files such as the main executable of the games installed through Xbox app - # For these files, only Kernel can get their hashes, it passes them to event viewer and we take them from event viewer logs - # Any other attempts such as "Get-FileHash" or "Get-AuthenticodeSignature" fail and ConfigCI Module cmdlets totally ignore these files and do not create allow rules for them - - # Finding the file(s) first and storing them in an array - [System.Object[]]$ExesWithNoHash = @() - # looping through each user-selected path(s) - foreach ($ProgramsPath in $ProgramsPaths) { - # Making sure the currently processing path has any .exe in it - $AnyAvailableExes = (Get-ChildItem -Recurse -Path $ProgramsPath -Filter '*.exe').FullName - # if any .exe was found then continue testing them - if ($AnyAvailableExes) { - $AnyAvailableExes | ForEach-Object { - $CurrentExeWithNoHash = $_ - try { - # Testing each executable to find the protected ones - Get-FileHash -Path $CurrentExeWithNoHash -ErrorAction Stop | Out-Null - } - # Making sure only the right file is captured by narrowing down the error type. - # E.g., when get-filehash can't get a file's hash because its open by another program, the exception is different: System.IO.IOException - catch [System.UnauthorizedAccessException] { - $ExesWithNoHash += $CurrentExeWithNoHash - } - } - } - } - # Only proceed if any kernel protected file(s) were found in any of the user-selected directory path(s) - if ($ExesWithNoHash) { - - Write-Debug -Message "The following Kernel protected files detected, creating allow rules for them:`n" - if ($Debug) { $ExesWithNoHash | ForEach-Object { Write-Debug -Message "$_" } } - - [scriptblock]$KernelProtectedHashesBlock = { - foreach ($event in Get-WinEvent -FilterHashtable @{LogName = 'Microsoft-Windows-CodeIntegrity/Operational'; ID = 3076 } -ErrorAction SilentlyContinue | Where-Object { $_.TimeCreated -ge $Date } ) { - $xml = [xml]$event.toxml() - $xml.event.eventdata.data | - ForEach-Object { $hash = @{} } { $hash[$_.name] = $_.'#text' } { [pscustomobject]$hash } | - ForEach-Object { - if ($_.'File Name' -match ($pattern = '\\Device\\HarddiskVolume(\d+)\\(.*)$')) { - $hardDiskVolumeNumber = $Matches[1] - $remainingPath = $Matches[2] - $getletter = $DriveLettersGlobalRootFix | Where-Object { $_.devicepath -eq "\Device\HarddiskVolume$hardDiskVolumeNumber" } - $usablePath = "$($getletter.DriveLetter)$remainingPath" - $_.'File Name' = $_.'File Name' -replace $pattern, $usablePath - } # Check if file is currently on the disk - if (Test-Path $_.'File Name') { - # Check if the file exits in the $ExesWithNoHash array - if ($ExesWithNoHash -contains $_.'File Name') { - $_ | Select-Object FileVersion, 'File Name', PolicyGUID, 'SHA256 Hash', 'SHA256 Flat Hash', 'SHA1 Hash', 'SHA1 Flat Hash' - } - } - } - } - } - $KernelProtectedHashesBlockResults = Invoke-Command -ScriptBlock $KernelProtectedHashesBlock - - # Only proceed further if any hashes belonging to the detected kernel protected files were found in Event viewer - # If none is found then skip this part, because user didn't run those files/programs when audit mode was turned on in base policy, so no hash was found in audit logs - if ($KernelProtectedHashesBlockResults) { - - # Save the File Rules and File Rule Refs in the FileRulesAndFileRefs.txt in the current working directory for debugging purposes - (Get-FileRules -HashesArray $KernelProtectedHashesBlockResults) + (Get-RuleRefs -HashesArray $KernelProtectedHashesBlockResults) | Out-File KernelProtectedFiles.txt - - # Put the Rules and RulesRefs in an empty policy file - New-EmptyPolicy -RulesContent (Get-FileRules -HashesArray $KernelProtectedHashesBlockResults) -RuleRefsContent (Get-RuleRefs -HashesArray $KernelProtectedHashesBlockResults) | Out-File .\KernelProtectedFiles.xml - - # adding the policy file to the array of xml files - $PolicyXMLFilesArray += '.\KernelProtectedFiles.xml' - } - else { - Write-Warning -Message "The following Kernel protected files detected, but no hash was found for them in Event viewer logs.`nThis means you didn't run those files/programs when Audit mode was turned on.`n" - $ExesWithNoHash | ForEach-Object { Write-Warning -Message "$_" } - } - } - #endregion Kernel-protected-files-automatic-detection-and-allow-rule-creation - - Write-Debug -Message 'The following policy xml files are going to be merged into the final Supplemental policy and be deployed on the system:' - if ($Debug) { $PolicyXMLFilesArray | ForEach-Object { Write-Debug -Message "$_" } } - - # Merge all of the policy XML files in the array into the final Supplemental policy - Merge-CIPolicy -PolicyPaths $PolicyXMLFilesArray -OutputFilePath ".\SupplementalPolicy $SuppPolicyName.xml" | Out-Null - - # Delete these extra files unless user uses -Debug parameter - if (-NOT $Debug) { - Remove-Item -Path '.\FileRulesAndFileRefs.txt', '.\DeletedFileHashesEventsPolicy.xml' -Force -ErrorAction SilentlyContinue - Remove-Item -Path '.\ProgramDir_ScanResults*.xml', '.\RulesForFilesNotInUserSelectedPaths.xml' -Force -ErrorAction SilentlyContinue - Remove-Item -Path '.\KernelProtectedFiles.txt', '.\KernelProtectedFiles.xml' -Force -ErrorAction SilentlyContinue - } - } - # Unlike AllowNewApps parameter, AllowNewAppsAuditEvents parameter performs Event viewer scanning and kernel protected files detection - # So the base policy enforced mode snap back can't happen any sooner than this point - catch { - $_ - $_.CategoryInfo - $_.ErrorDetails - $_.Exception - $_.FullyQualifiedErrorId - $_.InvocationInfo - $_.PipelineIterationInfo - $_.PSMessageDetails - $_.ScriptStackTrace - $_.TargetObject - } - finally { - # Deploy Enforced mode CIP - Write-Debug -Message 'Finally Block Running' - Update-BasePolicyToEnforced - # Enforced Mode Snapback removal after base policy has already been successfully re-enforced - Write-Debug -Message 'Removing the SnapBack guarantee because the base policy has been successfully re-enforced' - Remove-Item -Path 'C:\EnforcedModeSnapBack.ps1' -Force - Remove-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' -Name '*CIPolicySnapBack' -Force - } - - #################### Supplemental-policy-processing-and-deployment ############################ - - $SuppPolicyPath = ".\SupplementalPolicy $SuppPolicyName.xml" - $SuppPolicyID = Set-CIPolicyIdInfo -FilePath $SuppPolicyPath -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" -ResetPolicyID -BasePolicyToSupplementPath $PolicyPath - $SuppPolicyID = $SuppPolicyID.Substring(11) - Add-SignerRule -FilePath $SuppPolicyPath -CertificatePath $CertPath -Update -User -Kernel - - # Make sure policy rule options that don't belong to a Supplemental policy don't exit - @(0, 1, 2, 3, 4, 6, 8, 9, 10, 11, 12, 15, 16, 17, 19, 20) | ForEach-Object { Set-RuleOption -FilePath $SuppPolicyPath -Option $_ -Delete } - - Set-HVCIOptions -Strict -FilePath $SuppPolicyPath - Set-CIPolicyVersion -FilePath $SuppPolicyPath -Version '1.0.0.0' - - ConvertFrom-CIPolicy $SuppPolicyPath "$SuppPolicyID.cip" | Out-Null - - # Configure the parameter splat - $ProcessParams = @{ - 'ArgumentList' = 'sign', '/v' , '/n', "`"$CertCN`"", '/p7', '.', '/p7co', '1.3.6.1.4.1.311.79.1', '/fd', 'certHash', ".\$SuppPolicyID.cip" - 'FilePath' = $SignToolPathFinal - 'NoNewWindow' = $true - 'Wait' = $true - 'ErrorAction' = 'Stop' - } # Only show the output of SignTool if Debug switch is used - if (!$Debug) { $ProcessParams['RedirectStandardOutput'] = 'NUL' } - # Sign the files with the specified cert - Start-Process @ProcessParams - - Remove-Item ".\$SuppPolicyID.cip" -Force - Rename-Item "$SuppPolicyID.cip.p7" -NewName "$SuppPolicyID.cip" -Force - CiTool --update-policy ".\$SuppPolicyID.cip" -json | Out-Null - &$WriteTeaGreen "`nSupplemental policy with the following details has been Signed and Deployed in Enforced Mode:" - Write-Output "SupplementalPolicyName = $SuppPolicyName" - Write-Output "SupplementalPolicyGUID = $SuppPolicyID" - Remove-Item ".\$SuppPolicyID.cip" -Force - Remove-Item -Path $PolicyPath -Force # Remove the policy xml file in Temp folder we created earlier - } - } - - if ($AllowNewApps) { - - # remove any possible files from previous runs - Remove-Item -Path '.\ProgramDir_ScanResults*.xml' -Force -ErrorAction SilentlyContinue - Remove-Item -Path ".\SupplementalPolicy $SuppPolicyName.xml" -Force -ErrorAction SilentlyContinue - # An empty array that holds the Policy XML files - This array will eventually be used to create the final Supplemental policy - [System.Object[]]$PolicyXMLFilesArray = @() - - #Initiate Live Audit Mode - - foreach ($PolicyPath in $PolicyPaths) { - # Creating a copy of the original policy in Temp folder so that the original one will be unaffected - $PolicyFileName = Split-Path $PolicyPath -Leaf - Remove-Item -Path "$global:UserTempDirectoryPath\$PolicyFileName" -Force -ErrorAction SilentlyContinue # make sure no file with the same name already exists in Temp folder - Copy-Item -Path $PolicyPath -Destination $global:UserTempDirectoryPath -Force - $PolicyPath = "$global:UserTempDirectoryPath\$PolicyFileName" - - # Defining Base policy - $xml = [xml](Get-Content $PolicyPath) - [System.String]$PolicyID = $xml.SiPolicy.PolicyID - [System.String]$PolicyName = ($xml.SiPolicy.Settings.Setting | Where-Object { $_.provider -eq 'PolicyInfo' -and $_.valuename -eq 'Name' -and $_.key -eq 'Information' }).value.string - - # Remove any cip file if there is any - Remove-Item -Path '.\*.cip' -Force -ErrorAction SilentlyContinue - - # Create CIP for Audit Mode - Set-RuleOption -FilePath $PolicyPath -Option 6 -Delete # Remove Unsigned policy rule option - Set-RuleOption -FilePath $PolicyPath -Option 3 # Add Audit mode policy rule option - ConvertFrom-CIPolicy $PolicyPath '.\AuditModeTemp.cip' | Out-Null - - # Create CIP for Enforced Mode - Set-RuleOption -FilePath $PolicyPath -Option 6 -Delete # Remove Unsigned policy rule option - Set-RuleOption -FilePath $PolicyPath -Option 3 -Delete # Remove Audit mode policy rule option - ConvertFrom-CIPolicy $PolicyPath '.\EnforcedModeTemp.cip' | Out-Null - - # Sign both CIPs - '.\AuditModeTemp.cip', '.\EnforcedModeTemp.cip' | ForEach-Object { - # Configure the parameter splat - $ProcessParams = @{ - 'ArgumentList' = 'sign', '/v' , '/n', "`"$CertCN`"", '/p7', '.', '/p7co', '1.3.6.1.4.1.311.79.1', '/fd', 'certHash', "`"$_`"" - 'FilePath' = $SignToolPathFinal - 'NoNewWindow' = $true - 'Wait' = $true - 'ErrorAction' = 'Stop' - } # Only show the output of SignTool if Debug switch is used - if (!$Debug) { $ProcessParams['RedirectStandardOutput'] = 'NUL' } - # Sign the files with the specified cert - Start-Process @ProcessParams - - # After creating signed .p7 files for each CIP, remove the old Unsigned ones - Remove-Item -Path $_ -Force - } - Rename-Item '.\EnforcedModeTemp.cip.p7' -NewName '.\EnforcedMode.cip' -Force - Rename-Item '.\AuditModeTemp.cip.p7' -NewName '.\AuditMode.cip' -Force - - ################# Snap back guarantee ################# - Write-Debug -Message 'Creating Enforced Mode SnapBack guarantee' - - $registryPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' - $command = @" -CiTool --update-policy "$((Get-Location).Path)\$PolicyID.cip" -json; Remove-Item -Path "$((Get-Location).Path)\$PolicyID.cip" -Force -"@ - $command | Out-File 'C:\EnforcedModeSnapBack.ps1' - New-ItemProperty -Path $registryPath -Name '*CIPolicySnapBack' -Value "powershell.exe -WindowStyle `"Hidden`" -ExecutionPolicy `"Bypass`" -Command `"& {&`"C:\EnforcedModeSnapBack.ps1`";Remove-Item -Path 'C:\EnforcedModeSnapBack.ps1' -Force}`"" -PropertyType String -Force | Out-Null - - # Deploy Audit mode CIP - Write-Debug -Message 'Deploying Audit mode CIP' - Rename-Item '.\AuditMode.cip' -NewName ".\$PolicyID.cip" -Force - CiTool --update-policy ".\$PolicyID.cip" -json | Out-Null - &$WriteTeaGreen "`nThe Base policy with the following details has been Re-Signed and Re-Deployed in Audit Mode:" - Write-Output "PolicyName = $PolicyName" - Write-Output "PolicyGUID = $PolicyID" - # Remove Audit Mode CIP - Remove-Item ".\$PolicyID.cip" -Force - # Prepare Enforced Mode CIP for Deployment - waiting to be Re-deployed at the right time - Rename-Item '.\EnforcedMode.cip' -NewName ".\$PolicyID.cip" -Force - - # A Try-Catch-Finally block so that if any errors occur, the Base policy will be Re-deployed in enforced mode - Try { - ################################### User Interaction #################################### - &$WritePink "`nAudit mode deployed, start installing your programs now" - &$WriteHotPink "When you've finished installing programs, Press Enter to start selecting program directories to scan`n" - Pause - - # Store the program paths that user browses for in an array - [System.Object[]]$ProgramsPaths = @() - Write-Host "`nSelect program directories to scan`n" -ForegroundColor Cyan - # Showing folder picker GUI to the user for folder path selection - do { - [System.Reflection.Assembly]::LoadWithPartialName('System.windows.forms') | Out-Null - $OBJ = New-Object System.Windows.Forms.FolderBrowserDialog - $OBJ.InitialDirectory = "$env:SystemDrive" - $OBJ.Description = $Description - $Spawn = New-Object System.Windows.Forms.Form -Property @{TopMost = $true } - $Show = $OBJ.ShowDialog($Spawn) - If ($Show -eq 'OK') { $ProgramsPaths += $OBJ.SelectedPath } - Else { break } - } - while ($true) - - # Make sure User browsed for at least 1 directory - # Exit the operation if user didn't select any folder paths - if ($ProgramsPaths.count -eq 0) { - Write-Host "`nNo program folder was selected, reverting the changes and quitting...`n" -ForegroundColor Red - # Causing break here to stop operation. Finally block will be triggered to Re-Deploy Base policy in Enforced mode - break - } - } - catch { - # Show any extra info about any possible error that might've occurred - $_ - $_.CategoryInfo - $_.ErrorDetails - $_.Exception - $_.FullyQualifiedErrorId - $_.InvocationInfo - $_.PipelineIterationInfo - $_.PSMessageDetails - $_.ScriptStackTrace - $_.TargetObject - } - finally { - # Deploy Enforced mode CIP - Write-Debug -Message 'Finally Block Running' - Update-BasePolicyToEnforced - # Enforced Mode Snapback removal after base policy has already been successfully re-enforced - Write-Debug -Message 'Removing the SnapBack guarantee because the base policy has been successfully re-enforced' - Remove-Item -Path 'C:\EnforcedModeSnapBack.ps1' -Force - Remove-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' -Name '*CIPolicySnapBack' -Force - } - - Write-Host "`nHere are the paths you selected:" -ForegroundColor Yellow - $ProgramsPaths | ForEach-Object { $_ } - - #Process Program Folders From User input - - # Scan each of the folder paths that user selected - for ($i = 0; $i -lt $ProgramsPaths.Count; $i++) { - - # Creating a hash table to dynamically add parameters based on user input and pass them to New-Cipolicy cmdlet - [System.Collections.Hashtable]$UserInputProgramFoldersPolicyMakerHashTable = @{ - FilePath = ".\ProgramDir_ScanResults$($i).xml" - ScanPath = $ProgramsPaths[$i] - Level = $Level - Fallback = $Fallbacks - MultiplePolicyFormat = $true - UserWriteablePaths = $true - AllowFileNameFallbacks = $true - } - # Assess user input parameters and add the required parameters to the hash table - if ($SpecificFileNameLevel) { $UserInputProgramFoldersPolicyMakerHashTable['SpecificFileNameLevel'] = $SpecificFileNameLevel } - if ($NoScript) { $UserInputProgramFoldersPolicyMakerHashTable['NoScript'] = $true } - if (!$NoUserPEs) { $UserInputProgramFoldersPolicyMakerHashTable['UserPEs'] = $true } - - # Create the supplemental policy via parameter splatting - New-CIPolicy @UserInputProgramFoldersPolicyMakerHashTable - } - - # merge-cipolicy accept arrays - collecting all the policy files created by scanning user specified folders - $ProgramDir_ScanResults = Get-ChildItem '.\' | Where-Object { $_.Name -like 'ProgramDir_ScanResults*.xml' } - foreach ($file in $ProgramDir_ScanResults) { - $PolicyXMLFilesArray += $file.FullName - } - - Write-Debug -Message 'The following policy xml files are going to be merged into the final Supplemental policy and be deployed on the system:' - if ($Debug) { $PolicyXMLFilesArray | ForEach-Object { Write-Debug -Message "$_" } } - - # Merge all of the policy XML files in the array into the final Supplemental policy - Merge-CIPolicy -PolicyPaths $PolicyXMLFilesArray -OutputFilePath ".\SupplementalPolicy $SuppPolicyName.xml" | Out-Null - - Remove-Item -Path '.\ProgramDir_ScanResults*.xml' -Force - - #################### Supplemental-policy-processing-and-deployment ############################ - - $SuppPolicyPath = ".\SupplementalPolicy $SuppPolicyName.xml" - $SuppPolicyID = Set-CIPolicyIdInfo -FilePath $SuppPolicyPath -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" -ResetPolicyID -BasePolicyToSupplementPath $PolicyPath - $SuppPolicyID = $SuppPolicyID.Substring(11) - Add-SignerRule -FilePath $SuppPolicyPath -CertificatePath $CertPath -Update -User -Kernel - - # Make sure policy rule options that don't belong to a Supplemental policy don't exit - @(0, 1, 2, 3, 4, 6, 8, 9, 10, 11, 12, 15, 16, 17, 19, 20) | ForEach-Object { Set-RuleOption -FilePath $SuppPolicyPath -Option $_ -Delete } - - Set-HVCIOptions -Strict -FilePath $SuppPolicyPath - Set-CIPolicyVersion -FilePath $SuppPolicyPath -Version '1.0.0.0' - - ConvertFrom-CIPolicy $SuppPolicyPath "$SuppPolicyID.cip" | Out-Null - - # Configure the parameter splat - $ProcessParams = @{ - 'ArgumentList' = 'sign', '/v' , '/n', "`"$CertCN`"", '/p7', '.', '/p7co', '1.3.6.1.4.1.311.79.1', '/fd', 'certHash', ".\$SuppPolicyID.cip" - 'FilePath' = $SignToolPathFinal - 'NoNewWindow' = $true - 'Wait' = $true - 'ErrorAction' = 'Stop' - } # Only show the output of SignTool if Debug switch is used - if (!$Debug) { $ProcessParams['RedirectStandardOutput'] = 'NUL' } - # Sign the files with the specified cert - Start-Process @ProcessParams - - Remove-Item ".\$SuppPolicyID.cip" -Force - Rename-Item "$SuppPolicyID.cip.p7" -NewName "$SuppPolicyID.cip" -Force - CiTool --update-policy ".\$SuppPolicyID.cip" -json | Out-Null - &$WriteTeaGreen "`nSupplemental policy with the following details has been Signed and Deployed in Enforced Mode:" - Write-Output "SupplementalPolicyName = $SuppPolicyName" - Write-Output "SupplementalPolicyGUID = $SuppPolicyID" - Remove-Item ".\$SuppPolicyID.cip" -Force - Remove-Item -Path $PolicyPath -Force # Remove the policy xml file in Temp folder we created earlier - } - } - - if ($MergeSupplementalPolicies) { - foreach ($PolicyPath in $PolicyPaths) { - ############ Input policy verification prior to doing anything ############ - foreach ($SuppPolicyPath in $SuppPolicyPaths) { - $Supplementalxml = [xml](Get-Content $SuppPolicyPath) - $SupplementalPolicyID = $Supplementalxml.SiPolicy.PolicyID - $SupplementalPolicyType = $Supplementalxml.SiPolicy.PolicyType - $DeployedPoliciesIDs = (CiTool -lp -json | ConvertFrom-Json).Policies.PolicyID | ForEach-Object { return "{$_}" } - # Check the type of the user selected Supplemental policy XML files to make sure they are indeed Supplemental policies - if ($SupplementalPolicyType -ne 'Supplemental Policy') { - Write-Error -Message "The Selected XML file with GUID $SupplementalPolicyID isn't a Supplemental Policy." - } - # Check to make sure the user selected Supplemental policy XML files are deployed on the system - if ($DeployedPoliciesIDs -notcontains $SupplementalPolicyID) { - Write-Error -Message "The Selected Supplemental XML file with GUID $SupplementalPolicyID isn't deployed on the system." - } - } - # Perform the merge - Merge-CIPolicy -PolicyPaths $SuppPolicyPaths -OutputFilePath "$SuppPolicyName.xml" | Out-Null - # Delete the deployed Supplemental policies that user selected from the system because we're going to deploy the new merged policy that contains all of them - foreach ($SuppPolicyPath in $SuppPolicyPaths) { - $Supplementalxml = [xml](Get-Content $SuppPolicyPath) - $SupplementalPolicyID = $Supplementalxml.SiPolicy.PolicyID - Citool --remove-policy $SupplementalPolicyID -json | Out-Null - # remove the old policy files unless user chose to keep them - if (!$KeepOldSupplementalPolicies) { Remove-Item -Path $SuppPolicyPath -Force } - } - # Prepare the final merged Supplemental policy for deployment - $SuppPolicyID = Set-CIPolicyIdInfo -FilePath "$SuppPolicyName.xml" -ResetPolicyID -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" -BasePolicyToSupplementPath $PolicyPath - $SuppPolicyID = $SuppPolicyID.Substring(11) - Add-SignerRule -FilePath "$SuppPolicyName.xml" -CertificatePath $CertPath -Update -User -Kernel - Set-HVCIOptions -Strict -FilePath "$SuppPolicyName.xml" - Set-RuleOption -FilePath "$SuppPolicyName.xml" -Option 6 -Delete - ConvertFrom-CIPolicy "$SuppPolicyName.xml" "$SuppPolicyID.cip" | Out-Null - - # Configure the parameter splat - $ProcessParams = @{ - 'ArgumentList' = 'sign', '/v' , '/n', "`"$CertCN`"", '/p7', '.', '/p7co', '1.3.6.1.4.1.311.79.1', '/fd', 'certHash', ".\$SuppPolicyID.cip" - 'FilePath' = $SignToolPathFinal - 'NoNewWindow' = $true - 'Wait' = $true - 'ErrorAction' = 'Stop' - } # Only show the output of SignTool if Debug switch is used - if (!$Debug) { $ProcessParams['RedirectStandardOutput'] = 'NUL' } - # Sign the files with the specified cert - Start-Process @ProcessParams - - Remove-Item ".\$SuppPolicyID.cip" -Force - Rename-Item "$SuppPolicyID.cip.p7" -NewName "$SuppPolicyID.cip" -Force - CiTool --update-policy "$SuppPolicyID.cip" -json | Out-Null - &$WriteTeaGreen "`nThe Signed Supplemental policy $SuppPolicyName has been deployed on the system, replacing the old ones.`nSystem Restart Not immediately needed but eventually required to finish the removal of previous individual Supplemental policies." - Remove-Item -Path "$SuppPolicyID.cip" -Force - } - } - - if ($UpdateBasePolicy) { - # First get the Microsoft recommended driver block rules - Invoke-Command -ScriptBlock $GetBlockRulesSCRIPTBLOCK | Out-Null - - switch ($NewBasePolicyType) { - 'AllowMicrosoft_Plus_Block_Rules' { - Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowMicrosoft.xml' -Destination '.\AllowMicrosoft.xml' - Merge-CIPolicy -PolicyPaths .\AllowMicrosoft.xml, '.\Microsoft recommended block rules.xml' -OutputFilePath .\BasePolicy.xml | Out-Null - Set-CIPolicyIdInfo -FilePath .\BasePolicy.xml -PolicyName "Allow Microsoft Plus Block Rules refreshed On $(Get-Date -Format 'MM-dd-yyyy')" - @(0, 2, 5, 11, 12, 16, 17, 19, 20) | ForEach-Object { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ } - @(3, 4, 6, 9, 10, 13, 18) | ForEach-Object { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ -Delete } - } - 'Lightly_Managed_system_Policy' { - Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowMicrosoft.xml' -Destination '.\AllowMicrosoft.xml' - Merge-CIPolicy -PolicyPaths .\AllowMicrosoft.xml, '.\Microsoft recommended block rules.xml' -OutputFilePath .\BasePolicy.xml | Out-Null - Set-CIPolicyIdInfo -FilePath .\BasePolicy.xml -PolicyName "Signed And Reputable policy refreshed on $(Get-Date -Format 'MM-dd-yyyy')" - @(0, 2, 5, 11, 12, 14, 15, 16, 17, 19, 20) | ForEach-Object { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ } - @(3, 4, 6, 9, 10, 13, 18) | ForEach-Object { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ -Delete } - # Configure required services for ISG authorization - Start-Process -FilePath 'C:\Windows\System32\appidtel.exe' -ArgumentList 'start' -Wait -NoNewWindow - Start-Process -FilePath 'C:\Windows\System32\sc.exe' -ArgumentList 'config', 'appidsvc', 'start= auto' -Wait -NoNewWindow - } - 'DefaultWindows_WithBlockRules' { - Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\DefaultWindows_Enforced.xml' -Destination '.\DefaultWindows_Enforced.xml' - - # Allowing SignTool to be able to run after Default Windows base policy is deployed - &$WriteTeaGreen "`nCreating allow rules for SignTool.exe in the DefaultWindows base policy so you can continue using it after deploying the DefaultWindows base policy." - New-Item -Path "$global:UserTempDirectoryPath\TemporarySignToolFile" -ItemType Directory -Force | Out-Null - Copy-Item -Path $SignToolPathFinal -Destination "$global:UserTempDirectoryPath\TemporarySignToolFile" -Force - New-CIPolicy -ScanPath "$global:UserTempDirectoryPath\TemporarySignToolFile" -Level FilePublisher -Fallback Hash -UserPEs -UserWriteablePaths -MultiplePolicyFormat -AllowFileNameFallbacks -FilePath .\SignTool.xml - # Delete the Temporary folder in the TEMP folder - if (!$Debug) { Remove-Item -Recurse -Path "$global:UserTempDirectoryPath\TemporarySignToolFile" -Force } - - # Scan PowerShell core directory and add them to the Default Windows base policy so that the module can be used after it's been deployed - if (Test-Path 'C:\Program Files\PowerShell') { - &$WriteHotPink "`nCreating allow rules for PowerShell in the DefaultWindows base policy so you can continue using this module after deploying it." - New-CIPolicy -ScanPath 'C:\Program Files\PowerShell' -Level FilePublisher -NoScript -Fallback Hash -UserPEs -UserWriteablePaths -MultiplePolicyFormat -AllowFileNameFallbacks -FilePath .\AllowPowerShell.xml - Merge-CIPolicy -PolicyPaths .\DefaultWindows_Enforced.xml, .\AllowPowerShell.xml, .\SignTool.xml, '.\Microsoft recommended block rules.xml' -OutputFilePath .\BasePolicy.xml | Out-Null - } - else { - Merge-CIPolicy -PolicyPaths .\DefaultWindows_Enforced.xml, .\SignTool.xml, '.\Microsoft recommended block rules.xml' -OutputFilePath .\BasePolicy.xml | Out-Null - } - Set-CIPolicyIdInfo -FilePath .\BasePolicy.xml -PolicyName "Default Windows Plus Block Rules refreshed On $(Get-Date -Format 'MM-dd-yyyy')" - @(0, 2, 5, 11, 12, 16, 17, 19, 20) | ForEach-Object { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ } - @(3, 4, 6, 9, 10, 13, 18) | ForEach-Object { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ -Delete } - } - } - - if ($UpdateBasePolicy -and $RequireEVSigners) { Set-RuleOption -FilePath .\BasePolicy.xml -Option 8 } - - # Remove the extra files create during module operation that are no longer necessary - if (!$Debug) { - Remove-Item '.\AllowPowerShell.xml', '.\SignTool.xml', '.\AllowMicrosoft.xml', '.\DefaultWindows_Enforced.xml' -Force -ErrorAction SilentlyContinue - Remove-Item '.\Microsoft recommended block rules.xml' -Force - } - - # Get the policy ID of the currently deployed base policy based on the policy name that user selected - $CurrentID = ((CiTool -lp -json | ConvertFrom-Json).Policies | Where-Object { $_.IsSystemPolicy -ne 'True' } | Where-Object { $_.Friendlyname -eq $CurrentBasePolicyName }).BasePolicyID - $CurrentID = "{$CurrentID}" - Remove-Item ".\$CurrentID.cip" -Force -ErrorAction SilentlyContinue - - [xml]$xml = Get-Content '.\BasePolicy.xml' - $xml.SiPolicy.PolicyID = $CurrentID - $xml.SiPolicy.BasePolicyID = $CurrentID - $xml.Save('.\BasePolicy.xml') - - Add-SignerRule -FilePath .\BasePolicy.xml -CertificatePath $CertPath -Update -User -Kernel -Supplemental - - Set-CIPolicyVersion -FilePath .\BasePolicy.xml -Version '1.0.0.1' - Set-HVCIOptions -Strict -FilePath .\BasePolicy.xml - - ConvertFrom-CIPolicy '.\BasePolicy.xml' "$CurrentID.cip" | Out-Null - - # Configure the parameter splat - $ProcessParams = @{ - 'ArgumentList' = 'sign', '/v' , '/n', "`"$CertCN`"", '/p7', '.', '/p7co', '1.3.6.1.4.1.311.79.1', '/fd', 'certHash', ".\$CurrentID.cip" - 'FilePath' = $SignToolPathFinal - 'NoNewWindow' = $true - 'Wait' = $true - 'ErrorAction' = 'Stop' - } # Only show the output of SignTool if Debug switch is used - if (!$Debug) { $ProcessParams['RedirectStandardOutput'] = 'NUL' } - # Sign the files with the specified cert - Start-Process @ProcessParams - - Remove-Item ".\$CurrentID.cip" -Force - Rename-Item "$CurrentID.cip.p7" -NewName "$CurrentID.cip" -Force - # Deploy the new base policy with the same GUID on the system - CiTool --update-policy "$CurrentID.cip" -json | Out-Null - # Keep the new base policy XML file that was just deployed, in the current directory, so user can keep it for later - $PolicyFiles = @{ - 'AllowMicrosoft_Plus_Block_Rules' = 'AllowMicrosoftPlusBlockRules.xml' - 'Lightly_Managed_system_Policy' = 'SignedAndReputable.xml' - 'DefaultWindows_WithBlockRules' = 'DefaultWindowsPlusBlockRules.xml' - } - Remove-Item ".\$CurrentID.cip" -Force - Remove-Item $PolicyFiles[$NewBasePolicyType] -Force -ErrorAction SilentlyContinue - Rename-Item -Path '.\BasePolicy.xml' -NewName $PolicyFiles[$NewBasePolicyType] - &$WritePink "Base Policy has been successfully updated to $NewBasePolicyType" - &$WriteLavender 'Keep in mind that your previous policy path saved in User Configurations is no longer valid as you just changed your Base policy.' - } - } - - <# -.SYNOPSIS -Edits Signed WDAC policies deployed on the system (Windows Defender Application Control) - -.LINK -https://github.com/HotCakeX/Harden-Windows-Security/wiki/Edit-SignedWDACConfig - -.DESCRIPTION -Using official Microsoft methods, Edits Signed WDAC policies deployed on the system (Windows Defender Application Control) - -.COMPONENT -Windows Defender Application Control, ConfigCI PowerShell module - -.FUNCTIONALITY -Using official Microsoft methods, Edits Signed WDAC policies deployed on the system (Windows Defender Application Control) - -.PARAMETER AllowNewAppsAuditEvents -Rebootlessly install new apps/programs when Signed policy is already deployed, use audit events to capture installation files, scan their directories for new Supplemental policy, Sign and deploy thew Supplemental policy. - -.PARAMETER AllowNewApps -Rebootlessly install new apps/programs when Signed policy is already deployed, scan their directories for new Supplemental policy, Sign and deploy thew Supplemental policy. - -.PARAMETER MergeSupplementalPolicies -Merges multiple Signed deployed supplemental policies into 1 single supplemental policy, removes the old ones, deploys the new one. System restart needed to take effect. - -.PARAMETER UpdateBasePolicy -It can rebootlessly change the type of the deployed signed base policy. It can update the recommended block rules and/or change policy rule options in the deployed base policy. - -.PARAMETER SkipVersionCheck -Can be used with any parameter to bypass the online version check - only to be used in rare cases - -#> -} - -# Importing argument completer ScriptBlocks -. "$psscriptroot\ArgumentCompleters.ps1" -# Set PSReadline tab completion to complete menu for easier access to available parameters - Only for the current session -Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete -Register-ArgumentCompleter -CommandName 'Edit-SignedWDACConfig' -ParameterName 'CertCN' -ScriptBlock $ArgumentCompleterCertificateCN -Register-ArgumentCompleter -CommandName 'Edit-SignedWDACConfig' -ParameterName 'CertPath' -ScriptBlock $ArgumentCompleterCerFilePathsPicker -Register-ArgumentCompleter -CommandName 'Edit-SignedWDACConfig' -ParameterName 'SignToolPath' -ScriptBlock $ArgumentCompleterExeFilePathsPicker -Register-ArgumentCompleter -CommandName 'Edit-SignedWDACConfig' -ParameterName 'PolicyPaths' -ScriptBlock $ArgumentCompleterPolicyPathsBasePoliciesOnly -Register-ArgumentCompleter -CommandName 'Edit-SignedWDACConfig' -ParameterName 'SuppPolicyPaths' -ScriptBlock $ArgumentCompleterPolicyPathsSupplementalPoliciesOnly diff --git a/WDACConfig/Edit-WDACConfig.psm1 b/WDACConfig/Edit-WDACConfig.psm1 deleted file mode 100644 index bcd1740ef..000000000 --- a/WDACConfig/Edit-WDACConfig.psm1 +++ /dev/null @@ -1,876 +0,0 @@ -#Requires -RunAsAdministrator -function Edit-WDACConfig { - [CmdletBinding( - DefaultParameterSetName = 'Allow New Apps Audit Events', - SupportsShouldProcess = $true, - PositionalBinding = $false, - ConfirmImpact = 'High' - )] - Param( - [Alias('E')] - [Parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')][Switch]$AllowNewAppsAuditEvents, - [Alias('A')] - [Parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')][Switch]$AllowNewApps, - [Alias('M')] - [Parameter(Mandatory = $false, ParameterSetName = 'Merge Supplemental Policies')][Switch]$MergeSupplementalPolicies, - [Alias('U')] - [Parameter(Mandatory = $false, ParameterSetName = 'Update Base Policy')][Switch]$UpdateBasePolicy, - - [ValidatePattern('^[a-zA-Z0-9 ]+$', ErrorMessage = 'The Supplemental Policy Name can only contain alphanumeric and space characters.')] - [Parameter(Mandatory = $true, ParameterSetName = 'Allow New Apps Audit Events', ValueFromPipelineByPropertyName = $true)] - [Parameter(Mandatory = $true, ParameterSetName = 'Allow New Apps', ValueFromPipelineByPropertyName = $true)] - [Parameter(Mandatory = $true, ParameterSetName = 'Merge Supplemental Policies', ValueFromPipelineByPropertyName = $true)] - [System.String]$SuppPolicyName, - - [ValidatePattern('\.xml$')] - [ValidateScript({ - # Validate each Policy file in PolicyPaths parameter to make sure the user isn't accidentally trying to - # Edit a Signed policy using Edit-WDACConfig cmdlet which is only made for Unsigned policies - $_ | ForEach-Object { - $xmlTest = [xml](Get-Content $_) - $RedFlag1 = $xmlTest.SiPolicy.SupplementalPolicySigners.SupplementalPolicySigner.SignerId - $RedFlag2 = $xmlTest.SiPolicy.UpdatePolicySigners.UpdatePolicySigner.SignerId - $RedFlag3 = $xmlTest.SiPolicy.PolicyID - $CurrentPolicyIDs = ((CiTool -lp -json | ConvertFrom-Json).Policies | Where-Object { $_.IsSystemPolicy -ne 'True' }).policyID | ForEach-Object { "{$_}" } - if (!$RedFlag1 -and !$RedFlag2) { - # Ensure the selected base policy xml file is deployed - if ($CurrentPolicyIDs -contains $RedFlag3) { - return $True - } - else { throw "The currently selected policy xml file isn't deployed." } - } - # This throw is shown only when User added a Signed policy xml file for Unsigned policy file path property in user configuration file - # Without this, the error shown would be vague: The variable cannot be validated because the value System.String[] is not a valid value for the PolicyPaths variable. - else { throw 'The policy xml file in User Configurations for UnsignedPolicyPath is a Signed policy.' } - } - }, ErrorMessage = 'The selected policy xml file is Signed. Please use Edit-SignedWDACConfig cmdlet to edit Signed policies.')] - [Parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events', ValueFromPipelineByPropertyName = $true)] - [Parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps', ValueFromPipelineByPropertyName = $true)] - [Parameter(Mandatory = $false, ParameterSetName = 'Merge Supplemental Policies', ValueFromPipelineByPropertyName = $true)] - [System.String[]]$PolicyPaths, - - [ValidatePattern('\.xml$')] - [ValidateScript({ Test-Path $_ -PathType 'Leaf' }, ErrorMessage = 'The path you selected is not a file path.')] - [Parameter(Mandatory = $true, ParameterSetName = 'Merge Supplemental Policies', ValueFromPipelineByPropertyName = $true)] - [System.String[]]$SuppPolicyPaths, - - [Parameter(Mandatory = $false, ParameterSetName = 'Merge Supplemental Policies')] - [switch]$KeepOldSupplementalPolicies, - - [ValidateSet([Levelz])] - [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] - [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')] - [System.String]$Level = 'FilePublisher', # Setting the default value for the Level parameter - - [ValidateSet([Fallbackz])] - [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] - [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')] - [System.String[]]$Fallbacks = 'Hash', # Setting the default value for the Fallbacks parameter - - [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] - [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')] - [Switch]$NoScript, - - [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] - [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')] - [Switch]$NoUserPEs, - - [ValidateSet('OriginalFileName', 'InternalName', 'FileDescription', 'ProductName', 'PackageFamilyName', 'FilePath')] - [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] - [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')] - [System.String]$SpecificFileNameLevel, - - # Setting the maxim range to the maximum allowed log size by Windows Event viewer - [ValidateRange(1024KB, 18014398509481983KB)] - [Parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] - [System.Int64]$LogSize, - - [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')][Switch]$IncludeDeletedFiles, - - [ValidateSet([BasePolicyNamez])] - [Parameter(Mandatory = $true, ParameterSetName = 'Update Base Policy')][System.String[]]$CurrentBasePolicyName, - - [ValidateSet('AllowMicrosoft_Plus_Block_Rules', 'Lightly_Managed_system_Policy', 'DefaultWindows_WithBlockRules')] - [Parameter(Mandatory = $true, ParameterSetName = 'Update Base Policy')][System.String]$NewBasePolicyType, - - [Parameter(Mandatory = $false, ParameterSetName = 'Update Base Policy')][Switch]$RequireEVSigners, - - [Parameter(Mandatory = $false)][Switch]$SkipVersionCheck - ) - - begin { - # Importing resources such as functions by dot-sourcing so that they will run in the same scope and their variables will be usable - . "$psscriptroot\Resources.ps1" - - # Stop operation as soon as there is an error anywhere, unless explicitly specified otherwise - $ErrorActionPreference = 'Stop' - if (-NOT $SkipVersionCheck) { . Update-self } - - # Fetching Temp Directory - [string]$global:UserTempDirectoryPath = [System.IO.Path]::GetTempPath() - - # Fetch User account directory path - [string]$global:UserAccountDirectoryPath = (Get-CimInstance Win32_UserProfile -Filter "SID = '$([System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value)'").LocalPath - - # Detecting if Debug switch is used, will do debugging actions based on that - $Debug = $PSBoundParameters.Debug.IsPresent - - #region User-Configurations-Processing-Validation - # make sure the ParameterSet being used has PolicyPaths parameter - Then enforces "mandatory" attribute for the parameter - if ($PSCmdlet.ParameterSetName -in 'Allow New Apps Audit Events', 'Allow New Apps', 'Merge Supplemental Policies') { - # If any of these parameters, that are mandatory for all of the position 0 parameters, isn't supplied by user - if (!$PolicyPaths) { - # Read User configuration file if it exists - $UserConfig = Get-Content -Path "$global:UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" -ErrorAction SilentlyContinue - if ($UserConfig) { - # Validate the Json file and read its content to make sure it's not corrupted - try { $UserConfig = $UserConfig | ConvertFrom-Json } - catch { - Write-Error 'User Configuration Json file is corrupted, deleting it...' -ErrorAction Continue - # Calling this function with this parameter automatically does its job and breaks/stops the operation - Set-CommonWDACConfig -DeleteUserConfig - } - } - } - # If PolicyPaths has no values - if (!$PolicyPaths) { - if ($UserConfig.UnsignedPolicyPath) { - # validate each policyPath read from user config file - if (Test-Path $($UserConfig.UnsignedPolicyPath)) { - $PolicyPaths = $UserConfig.UnsignedPolicyPath - } - else { - throw 'The currently saved value for UnsignedPolicyPath in user configurations is invalid.' - } - } - else { - throw "PolicyPaths parameter can't be empty and no valid configuration was found for UnsignedPolicyPath." - } - } - } - #endregion User-Configurations-Processing-Validation - - # argument tab auto-completion and ValidateSet for Policy names - Class BasePolicyNamez : System.Management.Automation.IValidateSetValuesGenerator { - [System.String[]] GetValidValues() { - $BasePolicyNamez = ((CiTool -lp -json | ConvertFrom-Json).Policies | Where-Object { $_.IsSystemPolicy -ne 'True' } | Where-Object { $_.PolicyID -eq $_.BasePolicyID }).Friendlyname - - return [System.String[]]$BasePolicyNamez - } - } - - # argument tab auto-completion and ValidateSet for Fallbacks - Class Fallbackz : System.Management.Automation.IValidateSetValuesGenerator { - [System.String[]] GetValidValues() { - $Fallbackz = ('Hash', 'FileName', 'SignedVersion', 'Publisher', 'FilePublisher', 'LeafCertificate', 'PcaCertificate', 'RootCertificate', 'WHQL', 'WHQLPublisher', 'WHQLFilePublisher', 'PFN', 'FilePath', 'None') - return [System.String[]]$Fallbackz - } - } - - # argument tab auto-completion and ValidateSet for level - Class Levelz : System.Management.Automation.IValidateSetValuesGenerator { - [System.String[]] GetValidValues() { - $Levelz = ('Hash', 'FileName', 'SignedVersion', 'Publisher', 'FilePublisher', 'LeafCertificate', 'PcaCertificate', 'RootCertificate', 'WHQL', 'WHQLPublisher', 'WHQLFilePublisher', 'PFN', 'FilePath', 'None') - return [System.String[]]$Levelz - } - } - - # Redeploy the base policy in Enforced mode - function Update-BasePolicyToEnforced { - # Deploy Enforced mode CIP - CiTool --update-policy ".\$PolicyID.cip" -json | Out-Null - &$WriteTeaGreen "`nThe Base policy with the following details has been Re-Deployed in Enforced Mode:" - Write-Output "PolicyName = $PolicyName" - Write-Output "PolicyGUID = $PolicyID" - # Remove Enforced Mode CIP - Remove-Item ".\$PolicyID.cip" -Force - } - - $DriveLettersGlobalRootFix = Invoke-Command -ScriptBlock $DriveLettersGlobalRootFixScriptBlock - } - - process { - - if ($AllowNewApps) { - # remove any possible files from previous runs - Remove-Item -Path '.\ProgramDir_ScanResults*.xml' -Force -ErrorAction SilentlyContinue - Remove-Item -Path ".\SupplementalPolicy $SuppPolicyName.xml" -Force -ErrorAction SilentlyContinue - # An empty array that holds the Policy XML files - This array will eventually be used to create the final Supplemental policy - [System.Object[]]$PolicyXMLFilesArray = @() - - #Initiate Live Audit Mode - - foreach ($PolicyPath in $PolicyPaths) { - # Creating a copy of the original policy in Temp folder so that the original one will be unaffected - $PolicyFileName = Split-Path $PolicyPath -Leaf - Remove-Item -Path "$global:UserTempDirectoryPath\$PolicyFileName" -Force -ErrorAction SilentlyContinue # make sure no file with the same name already exists in Temp folder - Copy-Item -Path $PolicyPath -Destination $global:UserTempDirectoryPath -Force - $PolicyPath = "$global:UserTempDirectoryPath\$PolicyFileName" - - # Defining Base policy - $xml = [xml](Get-Content $PolicyPath) - [System.String]$PolicyID = $xml.SiPolicy.PolicyID - [System.String]$PolicyName = ($xml.SiPolicy.Settings.Setting | Where-Object { $_.provider -eq 'PolicyInfo' -and $_.valuename -eq 'Name' -and $_.key -eq 'Information' }).value.string - - # Remove any cip file if there is any - Remove-Item -Path '.\*.cip' -Force -ErrorAction SilentlyContinue - - # Create CIP for Audit Mode - Set-RuleOption -FilePath $PolicyPath -Option 3 # Add Audit mode policy rule option - ConvertFrom-CIPolicy $PolicyPath '.\AuditMode.cip' | Out-Null - - # Create CIP for Enforced Mode - Set-RuleOption -FilePath $PolicyPath -Option 3 -Delete # Remove Audit mode policy rule option - ConvertFrom-CIPolicy $PolicyPath '.\EnforcedMode.cip' | Out-Null - - ################# Snap back guarantee ################# - Write-Debug -Message 'Creating Enforced Mode SnapBack guarantee' - - <# - # CMD and Scheduled Task Method - $taskAction = New-ScheduledTaskAction -Execute 'cmd.exe' -Argument '/c c:\EnforcedModeSnapBack.cmd' - $taskTrigger = New-ScheduledTaskTrigger -AtLogOn - $principal = New-ScheduledTaskPrincipal -GroupId 'BUILTIN\Administrators' -RunLevel Highest - $TaskSettings = New-ScheduledTaskSettingsSet -Hidden -Compatibility Win8 -DontStopIfGoingOnBatteries -Priority 0 -AllowStartIfOnBatteries - Register-ScheduledTask -TaskName 'EnforcedModeSnapBack' -Action $taskAction -Trigger $taskTrigger -Principal $principal -Settings $TaskSettings -Force | Out-Null - - Set-Content -Force "c:\EnforcedModeSnapBack.cmd" -Value @" -REM Deploying the Enforced Mode SnapBack CI Policy -CiTool --update-policy "$((Get-Location).Path)\$PolicyID.cip" -json -REM Deleting the Scheduled task responsible for running this CMD file -schtasks /Delete /TN EnforcedModeSnapBack /F -REM Deleting the CI Policy file -del /f /q "$((Get-Location).Path)\$PolicyID.cip" -REM Deleting this CMD file itself -del "%~f0" -"@ -#> - # PowerShell and RunOnce Method - - $registryPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' - $command = @" -CiTool --update-policy "$((Get-Location).Path)\$PolicyID.cip" -json; Remove-Item -Path "$((Get-Location).Path)\$PolicyID.cip" -Force -"@ - $command | Out-File 'C:\EnforcedModeSnapBack.ps1' - New-ItemProperty -Path $registryPath -Name '*CIPolicySnapBack' -Value "powershell.exe -WindowStyle `"Hidden`" -ExecutionPolicy `"Bypass`" -Command `"& {&`"C:\EnforcedModeSnapBack.ps1`";Remove-Item -Path 'C:\EnforcedModeSnapBack.ps1' -Force}`"" -PropertyType String -Force | Out-Null - - # Deploy Audit mode CIP - Write-Debug -Message 'Deploying Audit mode CIP' - Rename-Item '.\AuditMode.cip' -NewName ".\$PolicyID.cip" -Force - CiTool --update-policy ".\$PolicyID.cip" -json | Out-Null - &$WriteTeaGreen "`nThe Base policy with the following details has been Re-Deployed in Audit Mode:" - Write-Output "PolicyName = $PolicyName" - Write-Output "PolicyGUID = $PolicyID" - # Remove Audit Mode CIP - Remove-Item ".\$PolicyID.cip" -Force - # Prepare Enforced Mode CIP for Deployment - waiting to be Re-deployed at the right time - Rename-Item '.\EnforcedMode.cip' -NewName ".\$PolicyID.cip" -Force - - # A Try-Catch-Finally block so that if any errors occur, the Base policy will be Re-deployed in enforced mode - Try { - ################################### User Interaction #################################### - &$WritePink "`nAudit mode deployed, start installing your programs now" - &$WriteHotPink "When you've finished installing programs, Press Enter to start selecting program directories to scan`n" - Pause - - # Store the program paths that user browses for in an array - [System.Object[]]$ProgramsPaths = @() - Write-Host "`nSelect program directories to scan" -ForegroundColor Cyan - # Showing folder picker GUI to the user for folder path selection - do { - [System.Reflection.Assembly]::LoadWithPartialName('System.windows.forms') | Out-Null - $OBJ = New-Object System.Windows.Forms.FolderBrowserDialog - $OBJ.InitialDirectory = "$env:SystemDrive" - $OBJ.Description = $Description - $Spawn = New-Object System.Windows.Forms.Form -Property @{TopMost = $true } - $Show = $OBJ.ShowDialog($Spawn) - If ($Show -eq 'OK') { $ProgramsPaths += $OBJ.SelectedPath } - Else { break } - } - while ($true) - - # Make sure User browsed for at least 1 directory - # Exit the operation if user didn't select any folder paths - if ($ProgramsPaths.count -eq 0) { - Write-Host "`nNo program folder was selected, reverting the changes and quitting...`n" -ForegroundColor Red - # Causing break here to stop operation. Finally block will be triggered to Re-Deploy Base policy in Enforced mode - break - } - } - catch { - # Show any extra info about any possible error that might've occurred - $_ - $_.CategoryInfo - $_.ErrorDetails - $_.Exception - $_.FullyQualifiedErrorId - $_.InvocationInfo - $_.PipelineIterationInfo - $_.PSMessageDetails - $_.ScriptStackTrace - $_.TargetObject - } - finally { - # Deploy Enforced mode CIP - Write-Debug -Message 'Finally Block Running' - Update-BasePolicyToEnforced - - # Enforced Mode Snapback removal after base policy has already been successfully re-enforced - Write-Debug -Message 'Removing the SnapBack guarantee because the base policy has been successfully re-enforced' - - # For PowerShell Method - Remove-Item -Path 'C:\EnforcedModeSnapBack.ps1' -Force - Remove-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' -Name '*CIPolicySnapBack' -Force - - # For CMD Method - # Unregister-ScheduledTask -TaskName 'EnforcedModeSnapBack' -Confirm:$false - # Remove-Item -Path 'c:\EnforcedModeSnapBack.cmd' -Force - } - - Write-Host "`nHere are the paths you selected:" -ForegroundColor Yellow - $ProgramsPaths | ForEach-Object { $_ } - - #Process Program Folders From User input - - # Scan each of the folder paths that user selected - for ($i = 0; $i -lt $ProgramsPaths.Count; $i++) { - - # Creating a hash table to dynamically add parameters based on user input and pass them to New-Cipolicy cmdlet - [System.Collections.Hashtable]$UserInputProgramFoldersPolicyMakerHashTable = @{ - FilePath = ".\ProgramDir_ScanResults$($i).xml" - ScanPath = $ProgramsPaths[$i] - Level = $Level - Fallback = $Fallbacks - MultiplePolicyFormat = $true - UserWriteablePaths = $true - AllowFileNameFallbacks = $true - } - # Assess user input parameters and add the required parameters to the hash table - if ($SpecificFileNameLevel) { $UserInputProgramFoldersPolicyMakerHashTable['SpecificFileNameLevel'] = $SpecificFileNameLevel } - if ($NoScript) { $UserInputProgramFoldersPolicyMakerHashTable['NoScript'] = $true } - if (!$NoUserPEs) { $UserInputProgramFoldersPolicyMakerHashTable['UserPEs'] = $true } - - # Create the supplemental policy via parameter splatting - New-CIPolicy @UserInputProgramFoldersPolicyMakerHashTable - } - - # merge-cipolicy accept arrays - collecting all the policy files created by scanning user specified folders - $ProgramDir_ScanResults = Get-ChildItem '.\' | Where-Object { $_.Name -like 'ProgramDir_ScanResults*.xml' } - foreach ($file in $ProgramDir_ScanResults) { - $PolicyXMLFilesArray += $file.FullName - } - - Write-Debug -Message 'The following policy xml files are going to be merged into the final Supplemental policy and be deployed on the system:' - if ($Debug) { $PolicyXMLFilesArray | ForEach-Object { Write-Debug -Message "$_" } } - - # Merge all of the policy XML files in the array into the final Supplemental policy - Merge-CIPolicy -PolicyPaths $PolicyXMLFilesArray -OutputFilePath ".\SupplementalPolicy $SuppPolicyName.xml" | Out-Null - - Remove-Item -Path '.\ProgramDir_ScanResults*.xml' -Force - - #################### Supplemental-policy-processing-and-deployment ############################ - - $SuppPolicyPath = ".\SupplementalPolicy $SuppPolicyName.xml" - $SuppPolicyID = Set-CIPolicyIdInfo -FilePath $SuppPolicyPath -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" -ResetPolicyID -BasePolicyToSupplementPath $PolicyPath - $SuppPolicyID = $SuppPolicyID.Substring(11) - - # Make sure policy rule options that don't belong to a Supplemental policy don't exit - @(0, 1, 2, 3, 4, 8, 9, 10, 11, 12, 15, 16, 17, 19, 20) | ForEach-Object { Set-RuleOption -FilePath $SuppPolicyPath -Option $_ -Delete } - - Set-HVCIOptions -Strict -FilePath $SuppPolicyPath - Set-CIPolicyVersion -FilePath $SuppPolicyPath -Version '1.0.0.0' - - ConvertFrom-CIPolicy $SuppPolicyPath "$SuppPolicyID.cip" | Out-Null - CiTool --update-policy ".\$SuppPolicyID.cip" -json | Out-Null - &$WriteTeaGreen "`nSupplemental policy with the following details has been Deployed in Enforced Mode:" - Write-Output "SupplementalPolicyName = $SuppPolicyName" - Write-Output "SupplementalPolicyGUID = $SuppPolicyID" - Remove-Item ".\$SuppPolicyID.cip" -Force - Remove-Item -Path $PolicyPath -Force # Remove the policy xml file in Temp folder we created earlier - } - } - - if ($AllowNewAppsAuditEvents) { - # Change Code Integrity event logs size - if ($AllowNewAppsAuditEvents -and $LogSize) { Set-LogSize -LogSize $LogSize } - # Make sure there is no leftover from previous runs - Remove-Item -Path '.\ProgramDir_ScanResults*.xml' -Force -ErrorAction SilentlyContinue - Remove-Item -Path ".\SupplementalPolicy $SuppPolicyName.xml" -Force -ErrorAction SilentlyContinue - # Get the current date so that instead of the entire event viewer logs, only audit logs created after running this module will be captured - # The notice about variable being assigned and never used should be ignored - it's being dot-sourced from Resources file - [datetime]$Date = Get-Date - # An empty array that holds the Policy XML files - This array will eventually be used to create the final Supplemental policy - [System.Object[]]$PolicyXMLFilesArray = @() - - ################################### Initiate Live Audit Mode ################################### - - foreach ($PolicyPath in $PolicyPaths) { - # Creating a copy of the original policy in Temp folder so that the original one will be unaffected - $PolicyFileName = Split-Path $PolicyPath -Leaf - Remove-Item -Path "$global:UserTempDirectoryPath\$PolicyFileName" -Force -ErrorAction SilentlyContinue # make sure no file with the same name already exists in Temp folder - Copy-Item -Path $PolicyPath -Destination $global:UserTempDirectoryPath -Force - $PolicyPath = "$global:UserTempDirectoryPath\$PolicyFileName" - - # Defining Base policy - $xml = [xml](Get-Content $PolicyPath) - [System.String]$PolicyID = $xml.SiPolicy.PolicyID - [System.String]$PolicyName = ($xml.SiPolicy.Settings.Setting | Where-Object { $_.provider -eq 'PolicyInfo' -and $_.valuename -eq 'Name' -and $_.key -eq 'Information' }).value.string - - # Remove any cip file if there is any - Remove-Item -Path '.\*.cip' -Force -ErrorAction SilentlyContinue - - # Create CIP for Audit Mode - Set-RuleOption -FilePath $PolicyPath -Option 3 # Add Audit mode policy rule option - ConvertFrom-CIPolicy $PolicyPath '.\AuditMode.cip' | Out-Null - - # Create CIP for Enforced Mode - Set-RuleOption -FilePath $PolicyPath -Option 3 -Delete # Remove Audit mode policy rule option - ConvertFrom-CIPolicy $PolicyPath '.\EnforcedMode.cip' | Out-Null - - ################# Snap back guarantee ################# - Write-Debug -Message 'Creating Enforced Mode SnapBack guarantee' - - $registryPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' - $command = @" -CiTool --update-policy "$((Get-Location).Path)\$PolicyID.cip" -json; Remove-Item -Path "$((Get-Location).Path)\$PolicyID.cip" -Force -"@ - $command | Out-File 'C:\EnforcedModeSnapBack.ps1' - New-ItemProperty -Path $registryPath -Name '*CIPolicySnapBack' -Value "powershell.exe -WindowStyle `"Hidden`" -ExecutionPolicy `"Bypass`" -Command `"& {&`"C:\EnforcedModeSnapBack.ps1`";Remove-Item -Path 'C:\EnforcedModeSnapBack.ps1' -Force}`"" -PropertyType String -Force | Out-Null - - # Deploy Audit mode CIP - Write-Debug -Message 'Deploying Audit mode CIP' - Rename-Item '.\AuditMode.cip' -NewName ".\$PolicyID.cip" -Force - CiTool --update-policy ".\$PolicyID.cip" -json | Out-Null - &$WriteTeaGreen "`nThe Base policy with the following details has been Re-Deployed in Audit Mode:" - Write-Output "PolicyName = $PolicyName" - Write-Output "PolicyGUID = $PolicyID" - # Remove Audit Mode CIP - Remove-Item ".\$PolicyID.cip" -Force - # Prepare Enforced Mode CIP for Deployment - waiting to be Re-deployed at the right time - Rename-Item '.\EnforcedMode.cip' -NewName ".\$PolicyID.cip" -Force - - # A Try-Catch-Finally block so that if any errors occur, the Base policy will be Re-deployed in enforced mode - Try { - ################################### User Interaction #################################### - &$WritePink "`nAudit mode deployed, start installing your programs now" - &$WriteHotPink "When you've finished installing programs, Press Enter to start selecting program directories to scan`n" - Pause - - # Store the program paths that user browses for in an array - [System.Object[]]$ProgramsPaths = @() - Write-Host "`nSelect program directories to scan`n" -ForegroundColor Cyan - # Showing folder picker GUI to the user for folder path selection - do { - [System.Reflection.Assembly]::LoadWithPartialName('System.windows.forms') | Out-Null - $OBJ = New-Object System.Windows.Forms.FolderBrowserDialog - $OBJ.InitialDirectory = "$env:SystemDrive" - $OBJ.Description = $Description - $Spawn = New-Object System.Windows.Forms.Form -Property @{TopMost = $true } - $Show = $OBJ.ShowDialog($Spawn) - If ($Show -eq 'OK') { $ProgramsPaths += $OBJ.SelectedPath } - Else { break } - } - while ($true) - - # Make sure User browsed for at least 1 directory - # Exit the operation if user didn't select any folder paths - if ($ProgramsPaths.count -eq 0) { - Write-Host "`nNo program folder was selected, reverting the changes and quitting...`n" -ForegroundColor Red - # Causing break here to stop operation. Finally block will be triggered to Re-Deploy Base policy in Enforced mode - break - } - - Write-Host 'Here are the paths you selected:' -ForegroundColor Yellow - $ProgramsPaths | ForEach-Object { $_ } - - ################################### EventCapturing ################################ - - Write-Host 'Scanning Windows Event logs and creating a policy file, please wait...' -ForegroundColor Cyan - - # Extracting the array content from Get-AuditEventLogsProcessing function - $AuditEventLogsProcessingResults = Get-AuditEventLogsProcessing -Date $Date - - # Only create policy for files that are available on the disk based on Event viewer logs but weren't in user-selected program path(s), if there are any - if ($AuditEventLogsProcessingResults.AvailableFilesPaths) { - - # Using the function to find out which files are not in the user-selected path(s), if any, to only scan those - # this prevents duplicate rule creation and double file copying - $TestFilePathResults = (Test-FilePath -FilePath $AuditEventLogsProcessingResults.AvailableFilesPaths -DirectoryPath $ProgramsPaths).path | Select-Object -Unique - - Write-Debug -Message "$($TestFilePathResults.count) file(s) have been found in event viewer logs that don't exist in any of the folder paths you selected." - - # Another check to make sure there were indeed files found in Event viewer logs but weren't in any of the user-selected path(s) - if ($TestFilePathResults) { - # Create a folder in Temp directory to copy the files that are not included in user-selected program path(s) - # but detected in Event viewer audit logs, scan that folder, and in the end delete it - New-Item -Path "$global:UserTempDirectoryPath\TemporaryScanFolderForEventViewerFiles" -ItemType Directory | Out-Null - - Write-Debug -Message "The following file(s) are being copied to the TEMP directory for scanning because they were found in event logs but didn't exist in any of the user-selected paths:" - $TestFilePathResults | ForEach-Object { - Write-Debug -Message "$_" - Copy-Item -Path $_ -Destination "$global:UserTempDirectoryPath\TemporaryScanFolderForEventViewerFiles\" -ErrorAction SilentlyContinue - } - - # Create a policy XML file for available files on the disk - - # Creating a hash table to dynamically add parameters based on user input and pass them to New-Cipolicy cmdlet - [System.Collections.Hashtable]$AvailableFilesOnDiskPolicyMakerHashTable = @{ - FilePath = '.\RulesForFilesNotInUserSelectedPaths.xml' - ScanPath = "$global:UserTempDirectoryPath\TemporaryScanFolderForEventViewerFiles\" - Level = $Level - Fallback = $Fallbacks - MultiplePolicyFormat = $true - UserWriteablePaths = $true - AllowFileNameFallbacks = $true - } - # Assess user input parameters and add the required parameters to the hash table - if ($SpecificFileNameLevel) { $AvailableFilesOnDiskPolicyMakerHashTable['SpecificFileNameLevel'] = $SpecificFileNameLevel } - if ($NoScript) { $AvailableFilesOnDiskPolicyMakerHashTable['NoScript'] = $true } - if (!$NoUserPEs) { $AvailableFilesOnDiskPolicyMakerHashTable['UserPEs'] = $true } - - # Create the supplemental policy via parameter splatting - New-CIPolicy @AvailableFilesOnDiskPolicyMakerHashTable - - # Add the policy XML file to the array that holds policy XML files - $PolicyXMLFilesArray += '.\RulesForFilesNotInUserSelectedPaths.xml' - # Delete the Temporary folder in the TEMP folder - Remove-Item -Recurse -Path "$global:UserTempDirectoryPath\TemporaryScanFolderForEventViewerFiles\" -Force - } - } - - # Only create policy for files that are on longer available on the disk if there are any and - # if user chose to include deleted files in the final supplemental policy - if ($AuditEventLogsProcessingResults.DeletedFileHashes -and $IncludeDeletedFiles) { - - Write-Debug -Message "$($AuditEventLogsProcessingResults.DeletedFileHashes.count) file(s) have been found in event viewer logs that were run during Audit phase but are no longer on the disk, they are as follows:" - $AuditEventLogsProcessingResults.DeletedFileHashes | ForEach-Object { - Write-Debug -Message "$($_.'File Name')" - } - - # Save the File Rules and File Rule Refs in the FileRulesAndFileRefs.txt in the current working directory for debugging purposes - ((Get-FileRules -HashesArray $AuditEventLogsProcessingResults.DeletedFileHashes) + (Get-RuleRefs -HashesArray $AuditEventLogsProcessingResults.DeletedFileHashes)).Trim() | Out-File FileRulesAndFileRefs.txt - - # Put the Rules and RulesRefs in an empty policy file - New-EmptyPolicy -RulesContent (Get-FileRules -HashesArray $AuditEventLogsProcessingResults.DeletedFileHashes) -RuleRefsContent (Get-RuleRefs -HashesArray $AuditEventLogsProcessingResults.DeletedFileHashes) | Out-File .\DeletedFileHashesEventsPolicy.xml - - # adding the policy file that consists of rules from audit even logs, to the array - $PolicyXMLFilesArray += '.\DeletedFileHashesEventsPolicy.xml' - } - - ######################## Process Program Folders From User input ##################### - for ($i = 0; $i -lt $ProgramsPaths.Count; $i++) { - - # Creating a hash table to dynamically add parameters based on user input and pass them to New-Cipolicy cmdlet - [System.Collections.Hashtable]$UserInputProgramFoldersPolicyMakerHashTable = @{ - FilePath = ".\ProgramDir_ScanResults$($i).xml" - ScanPath = $ProgramsPaths[$i] - Level = $Level - Fallback = $Fallbacks - MultiplePolicyFormat = $true - UserWriteablePaths = $true - AllowFileNameFallbacks = $true - } - # Assess user input parameters and add the required parameters to the hash table - if ($SpecificFileNameLevel) { $UserInputProgramFoldersPolicyMakerHashTable['SpecificFileNameLevel'] = $SpecificFileNameLevel } - if ($NoScript) { $UserInputProgramFoldersPolicyMakerHashTable['NoScript'] = $true } - if (!$NoUserPEs) { $UserInputProgramFoldersPolicyMakerHashTable['UserPEs'] = $true } - - # Create the supplemental policy via parameter splatting - New-CIPolicy @UserInputProgramFoldersPolicyMakerHashTable - } - - # Merge-cipolicy accept arrays - collecting all the policy files created by scanning user specified folders - $ProgramDir_ScanResults = Get-ChildItem '.\' | Where-Object { $_.Name -like 'ProgramDir_ScanResults*.xml' } - foreach ($file in $ProgramDir_ScanResults) { - $PolicyXMLFilesArray += $file.FullName - } - - #region Kernel-protected-files-automatic-detection-and-allow-rule-creation - # This part takes care of Kernel protected files such as the main executable of the games installed through Xbox app - # For these files, only Kernel can get their hashes, it passes them to event viewer and we take them from event viewer logs - # Any other attempts such as "Get-FileHash" or "Get-AuthenticodeSignature" fail and ConfigCI Module cmdlets totally ignore these files and do not create allow rules for them - - # Finding the file(s) first and storing them in an array - [System.Object[]]$ExesWithNoHash = @() - # looping through each user-selected path(s) - foreach ($ProgramsPath in $ProgramsPaths) { - # Making sure the currently processing path has any .exe in it - $AnyAvailableExes = (Get-ChildItem -Recurse -Path $ProgramsPath -Filter '*.exe').FullName - # if any .exe was found then continue testing them - if ($AnyAvailableExes) { - $AnyAvailableExes | ForEach-Object { - $CurrentExeWithNoHash = $_ - try { - # Testing each executable to find the protected ones - Get-FileHash -Path $CurrentExeWithNoHash -ErrorAction Stop | Out-Null - } - # Making sure only the right file is captured by narrowing down the error type. - # E.g., when get-filehash can't get a file's hash because its open by another program, the exception is different: System.IO.IOException - catch [System.UnauthorizedAccessException] { - $ExesWithNoHash += $CurrentExeWithNoHash - } - } - } - } - # Only proceed if any kernel protected file(s) were found in any of the user-selected directory path(s) - if ($ExesWithNoHash) { - - Write-Debug -Message "The following Kernel protected files detected, creating allow rules for them:`n" - if ($Debug) { $ExesWithNoHash | ForEach-Object { Write-Debug -Message "$_" } } - - [scriptblock]$KernelProtectedHashesBlock = { - foreach ($event in Get-WinEvent -FilterHashtable @{LogName = 'Microsoft-Windows-CodeIntegrity/Operational'; ID = 3076 } -ErrorAction SilentlyContinue | Where-Object { $_.TimeCreated -ge $Date } ) { - $xml = [xml]$event.toxml() - $xml.event.eventdata.data | - ForEach-Object { $hash = @{} } { $hash[$_.name] = $_.'#text' } { [pscustomobject]$hash } | - ForEach-Object { - if ($_.'File Name' -match ($pattern = '\\Device\\HarddiskVolume(\d+)\\(.*)$')) { - $hardDiskVolumeNumber = $Matches[1] - $remainingPath = $Matches[2] - $getletter = $DriveLettersGlobalRootFix | Where-Object { $_.devicepath -eq "\Device\HarddiskVolume$hardDiskVolumeNumber" } - $usablePath = "$($getletter.DriveLetter)$remainingPath" - $_.'File Name' = $_.'File Name' -replace $pattern, $usablePath - } # Check if file is currently on the disk - if (Test-Path $_.'File Name') { - # Check if the file exits in the $ExesWithNoHash array - if ($ExesWithNoHash -contains $_.'File Name') { - $_ | Select-Object FileVersion, 'File Name', PolicyGUID, 'SHA256 Hash', 'SHA256 Flat Hash', 'SHA1 Hash', 'SHA1 Flat Hash' - } - } - } - } - } - $KernelProtectedHashesBlockResults = Invoke-Command -ScriptBlock $KernelProtectedHashesBlock - - # Only proceed further if any hashes belonging to the detected kernel protected files were found in Event viewer - # If none is found then skip this part, because user didn't run those files/programs when audit mode was turned on in base policy, so no hash was found in audit logs - if ($KernelProtectedHashesBlockResults) { - - # Save the File Rules and File Rule Refs in the FileRulesAndFileRefs.txt in the current working directory for debugging purposes - (Get-FileRules -HashesArray $KernelProtectedHashesBlockResults) + (Get-RuleRefs -HashesArray $KernelProtectedHashesBlockResults) | Out-File KernelProtectedFiles.txt - - # Put the Rules and RulesRefs in an empty policy file - New-EmptyPolicy -RulesContent (Get-FileRules -HashesArray $KernelProtectedHashesBlockResults) -RuleRefsContent (Get-RuleRefs -HashesArray $KernelProtectedHashesBlockResults) | Out-File .\KernelProtectedFiles.xml - - # adding the policy file to the array of xml files - $PolicyXMLFilesArray += '.\KernelProtectedFiles.xml' - } - else { - Write-Warning -Message "The following Kernel protected files detected, but no hash was found for them in Event viewer logs.`nThis means you didn't run those files/programs when Audit mode was turned on.`n" - $ExesWithNoHash | ForEach-Object { Write-Warning -Message "$_" } - } - } - #endregion Kernel-protected-files-automatic-detection-and-allow-rule-creation - - Write-Debug -Message 'The following policy xml files are going to be merged into the final Supplemental policy and be deployed on the system:' - if ($Debug) { $PolicyXMLFilesArray | ForEach-Object { Write-Debug -Message "$_" } } - - # Merge all of the policy XML files in the array into the final Supplemental policy - Merge-CIPolicy -PolicyPaths $PolicyXMLFilesArray -OutputFilePath ".\SupplementalPolicy $SuppPolicyName.xml" | Out-Null - - # Delete these extra files unless user uses -Debug parameter - if (-NOT $Debug) { - Remove-Item -Path '.\RulesForFilesNotInUserSelectedPaths.xml', '.\ProgramDir_ScanResults*.xml' -Force -ErrorAction SilentlyContinue - Remove-Item -Path '.\KernelProtectedFiles.xml', '.\DeletedFileHashesEventsPolicy.xml' -Force -ErrorAction SilentlyContinue - Remove-Item -Path '.\KernelProtectedFiles.txt', '.\FileRulesAndFileRefs.txt' -Force -ErrorAction SilentlyContinue - } - } - # Unlike AllowNewApps parameter, AllowNewAppsAuditEvents parameter performs Event viewer scanning and kernel protected files detection - # So the base policy enforced mode snap back can't happen any sooner than this point - catch { - $_ - $_.CategoryInfo - $_.ErrorDetails - $_.Exception - $_.FullyQualifiedErrorId - $_.InvocationInfo - $_.PipelineIterationInfo - $_.PSMessageDetails - $_.ScriptStackTrace - $_.TargetObject - } - finally { - # Deploy Enforced mode CIP - Write-Debug -Message 'Finally Block Running' - Update-BasePolicyToEnforced - # Enforced Mode Snapback removal after base policy has already been successfully re-enforced - Write-Debug -Message 'Removing the SnapBack guarantee because the base policy has been successfully re-enforced' - Remove-Item -Path 'C:\EnforcedModeSnapBack.ps1' -Force - Remove-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' -Name '*CIPolicySnapBack' -Force - } - - #################### Supplemental-policy-processing-and-deployment ############################ - - $SuppPolicyPath = ".\SupplementalPolicy $SuppPolicyName.xml" - $SuppPolicyID = Set-CIPolicyIdInfo -FilePath $SuppPolicyPath -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" -ResetPolicyID -BasePolicyToSupplementPath $PolicyPath - $SuppPolicyID = $SuppPolicyID.Substring(11) - - # Make sure policy rule options that don't belong to a Supplemental policy don't exit - @(0, 1, 2, 3, 4, 8, 9, 10, 11, 12, 15, 16, 17, 19, 20) | ForEach-Object { Set-RuleOption -FilePath $SuppPolicyPath -Option $_ -Delete } - - Set-HVCIOptions -Strict -FilePath $SuppPolicyPath - Set-CIPolicyVersion -FilePath $SuppPolicyPath -Version '1.0.0.0' - - ConvertFrom-CIPolicy $SuppPolicyPath "$SuppPolicyID.cip" | Out-Null - CiTool --update-policy ".\$SuppPolicyID.cip" -json | Out-Null - &$WriteTeaGreen "`nSupplemental policy with the following details has been Deployed in Enforced Mode:" - Write-Output "SupplementalPolicyName = $SuppPolicyName" - Write-Output "SupplementalPolicyGUID = $SuppPolicyID" - Remove-Item ".\$SuppPolicyID.cip" -Force - Remove-Item -Path $PolicyPath -Force # Remove the policy xml file in Temp folder we created earlier - } - } - - if ($MergeSupplementalPolicies) { - foreach ($PolicyPath in $PolicyPaths) { - ############ Input policy verification prior to doing anything ############ - foreach ($SuppPolicyPath in $SuppPolicyPaths) { - $Supplementalxml = [xml](Get-Content $SuppPolicyPath) - $SupplementalPolicyID = $Supplementalxml.SiPolicy.PolicyID - $SupplementalPolicyType = $Supplementalxml.SiPolicy.PolicyType - $DeployedPoliciesIDs = (CiTool -lp -json | ConvertFrom-Json).Policies.PolicyID | ForEach-Object { return "{$_}" } - # Check the type of the user selected Supplemental policy XML files to make sure they are indeed Supplemental policies - if ($SupplementalPolicyType -ne 'Supplemental Policy') { - Write-Error -Message "The Selected XML file with GUID $SupplementalPolicyID isn't a Supplemental Policy." - } - # Check to make sure the user selected Supplemental policy XML files are deployed on the system - if ($DeployedPoliciesIDs -notcontains $SupplementalPolicyID) { - Write-Error -Message "The Selected Supplemental XML file with GUID $SupplementalPolicyID isn't deployed on the system." - } - } - # Perform the merge - Merge-CIPolicy -PolicyPaths $SuppPolicyPaths -OutputFilePath "$SuppPolicyName.xml" | Out-Null - # Delete the deployed Supplemental policies that user selected from the system because we're going to deploy the new merged policy that contains all of them - foreach ($SuppPolicyPath in $SuppPolicyPaths) { - $Supplementalxml = [xml](Get-Content $SuppPolicyPath) - $SupplementalPolicyID = $Supplementalxml.SiPolicy.PolicyID - Citool --remove-policy $SupplementalPolicyID -json | Out-Null - # remove the old policy files unless user chose to keep them - if (!$KeepOldSupplementalPolicies) { Remove-Item -Path $SuppPolicyPath -Force } - } - # Prepare the final merged Supplemental policy for deployment - $SuppPolicyID = Set-CIPolicyIdInfo -FilePath "$SuppPolicyName.xml" -ResetPolicyID -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" -BasePolicyToSupplementPath $PolicyPath - $SuppPolicyID = $SuppPolicyID.Substring(11) - Set-HVCIOptions -Strict -FilePath "$SuppPolicyName.xml" - ConvertFrom-CIPolicy "$SuppPolicyName.xml" "$SuppPolicyID.cip" | Out-Null - CiTool --update-policy "$SuppPolicyID.cip" -json | Out-Null - &$WriteTeaGreen "`nThe Supplemental policy $SuppPolicyName has been deployed on the system, replacing the old ones.`nSystem Restart Not immediately needed but eventually required to finish the removal of previous individual Supplemental policies." - Remove-Item -Path "$SuppPolicyID.cip" -Force - } - } - - if ($UpdateBasePolicy) { - # First get the Microsoft recommended driver block rules - Invoke-Command -ScriptBlock $GetBlockRulesSCRIPTBLOCK | Out-Null - - switch ($NewBasePolicyType) { - 'AllowMicrosoft_Plus_Block_Rules' { - Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowMicrosoft.xml' -Destination '.\AllowMicrosoft.xml' - Merge-CIPolicy -PolicyPaths .\AllowMicrosoft.xml, '.\Microsoft recommended block rules.xml' -OutputFilePath .\BasePolicy.xml | Out-Null - Set-CIPolicyIdInfo -FilePath .\BasePolicy.xml -PolicyName "Allow Microsoft Plus Block Rules refreshed On $(Get-Date -Format 'MM-dd-yyyy')" - @(0, 2, 5, 6, 11, 12, 16, 17, 19, 20) | ForEach-Object { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ } - @(3, 4, 9, 10, 13, 18) | ForEach-Object { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ -Delete } - } - 'Lightly_Managed_system_Policy' { - Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowMicrosoft.xml' -Destination '.\AllowMicrosoft.xml' - Merge-CIPolicy -PolicyPaths .\AllowMicrosoft.xml, '.\Microsoft recommended block rules.xml' -OutputFilePath .\BasePolicy.xml | Out-Null - Set-CIPolicyIdInfo -FilePath .\BasePolicy.xml -PolicyName "Signed And Reputable policy refreshed on $(Get-Date -Format 'MM-dd-yyyy')" - @(0, 2, 5, 6, 11, 12, 14, 15, 16, 17, 19, 20) | ForEach-Object { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ } - @(3, 4, 9, 10, 13, 18) | ForEach-Object { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ -Delete } - # Configure required services for ISG authorization - Start-Process -FilePath 'C:\Windows\System32\appidtel.exe' -ArgumentList 'start' -Wait -NoNewWindow - Start-Process -FilePath 'C:\Windows\System32\sc.exe' -ArgumentList 'config', 'appidsvc', 'start= auto' -Wait -NoNewWindow - } - 'DefaultWindows_WithBlockRules' { - Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\DefaultWindows_Enforced.xml' -Destination '.\DefaultWindows_Enforced.xml' - # Scan PowerShell core directory and add them to the Default Windows base policy so that the module can be used after it's been deployed - if (Test-Path 'C:\Program Files\PowerShell') { - Write-Host 'Creating allow rules for PowerShell in the DefaultWindows base policy so you can continue using this module after deploying it.' -ForegroundColor Blue - New-CIPolicy -ScanPath 'C:\Program Files\PowerShell' -Level FilePublisher -NoScript -Fallback Hash -UserPEs -UserWriteablePaths -MultiplePolicyFormat -FilePath .\AllowPowerShell.xml - Merge-CIPolicy -PolicyPaths .\DefaultWindows_Enforced.xml, .\AllowPowerShell.xml, '.\Microsoft recommended block rules.xml' -OutputFilePath .\BasePolicy.xml | Out-Null - } - else { - Merge-CIPolicy -PolicyPaths .\DefaultWindows_Enforced.xml, '.\Microsoft recommended block rules.xml' -OutputFilePath .\BasePolicy.xml | Out-Null - } - Set-CIPolicyIdInfo -FilePath .\BasePolicy.xml -PolicyName "Default Windows Plus Block Rules refreshed On $(Get-Date -Format 'MM-dd-yyyy')" - @(0, 2, 5, 6, 11, 12, 16, 17, 19, 20) | ForEach-Object { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ } - @(3, 4, 9, 10, 13, 18) | ForEach-Object { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ -Delete } - } - } - - if ($UpdateBasePolicy -and $RequireEVSigners) { Set-RuleOption -FilePath .\BasePolicy.xml -Option 8 } - - Set-CIPolicyVersion -FilePath .\BasePolicy.xml -Version '1.0.0.1' - Set-HVCIOptions -Strict -FilePath .\BasePolicy.xml - - # Remove the extra files create during module operation that are no longer necessary - Remove-Item '.\AllowPowerShell.xml', '.\DefaultWindows_Enforced.xml', '.\AllowMicrosoft.xml' -Force -ErrorAction SilentlyContinue - Remove-Item '.\Microsoft recommended block rules.xml' -Force - - # Get the policy ID of the currently deployed base policy based on the policy name that user selected - $CurrentID = ((CiTool -lp -json | ConvertFrom-Json).Policies | Where-Object { $_.IsSystemPolicy -ne 'True' } | Where-Object { $_.Friendlyname -eq $CurrentBasePolicyName }).BasePolicyID - $CurrentID = "{$CurrentID}" - Write-Debug -Message "This is the current ID of deployed base policy that is going to be used in the new base policy: $CurrentID" - [xml]$xml = Get-Content '.\BasePolicy.xml' - $xml.SiPolicy.PolicyID = $CurrentID - $xml.SiPolicy.BasePolicyID = $CurrentID - $xml.Save('.\BasePolicy.xml') - ConvertFrom-CIPolicy '.\BasePolicy.xml' "$CurrentID.cip" | Out-Null - # Deploy the new base policy with the same GUID on the system - CiTool --update-policy "$CurrentID.cip" -json | Out-Null - # Remove the policy binary after it's been deployed - Remove-Item "$CurrentID.cip" -Force - - # Keep the new base policy XML file that was just deployed, in the current directory, so user can keep it for later - $PolicyFiles = @{ - 'AllowMicrosoft_Plus_Block_Rules' = 'AllowMicrosoftPlusBlockRules.xml' - 'Lightly_Managed_system_Policy' = 'SignedAndReputable.xml' - 'DefaultWindows_WithBlockRules' = 'DefaultWindowsPlusBlockRules.xml' - } - Remove-Item $PolicyFiles[$NewBasePolicyType] -Force -ErrorAction SilentlyContinue - Rename-Item -Path '.\BasePolicy.xml' -NewName $PolicyFiles[$NewBasePolicyType] -Force - &$WritePink "Base Policy has been successfully updated to $NewBasePolicyType" - &$WriteLavender 'Keep in mind that your previous policy path saved in User Configurations is no longer valid as you just changed your Base policy.' - } - } - - <# -.SYNOPSIS -Edits Unsigned WDAC policies deployed on the system - -.LINK -https://github.com/HotCakeX/Harden-Windows-Security/wiki/Edit-WDACConfig - -.DESCRIPTION -Using official Microsoft methods, Edits non-signed WDAC policies deployed on the system - -.COMPONENT -Windows Defender Application Control, ConfigCI PowerShell module - -.FUNCTIONALITY -Using official Microsoft methods, Edits non-signed WDAC policies deployed on the system - -.PARAMETER AllowNewApps -While an unsigned WDAC policy is already deployed on the system, rebootlessly turn on Audit mode in it, which will allow you to install a new app that was otherwise getting blocked. - -.PARAMETER AllowNewAppsAuditEvents -While an unsigned WDAC policy is already deployed on the system, rebootlessly turn on Audit mode in it, which will allow you to install a new app that was otherwise getting blocked. - -.PARAMETER MergeSupplementalPolicies -Merges multiple deployed supplemental policies into 1 single supplemental policy, removes the old ones, deploys the new one. System restart needed to take effect. - -.PARAMETER UpdateBasePolicy -It can rebootlessly change the type of the deployed base policy. It can update the recommended block rules and/or change policy rule options in the deployed base policy. - -.PARAMETER SkipVersionCheck -Can be used with any parameter to bypass the online version check - only to be used in rare cases - -#> -} - -# Importing argument completer ScriptBlocks -. "$psscriptroot\ArgumentCompleters.ps1" -# Set PSReadline tab completion to complete menu for easier access to available parameters - Only for the current session -Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete -Register-ArgumentCompleter -CommandName 'Edit-WDACConfig' -ParameterName 'PolicyPaths' -ScriptBlock $ArgumentCompleterPolicyPathsBasePoliciesOnly -Register-ArgumentCompleter -CommandName 'Edit-WDACConfig' -ParameterName 'SuppPolicyPaths' -ScriptBlock $ArgumentCompleterPolicyPathsSupplementalPoliciesOnly diff --git a/WDACConfig/Get-CommonWDACConfig.psm1 b/WDACConfig/Get-CommonWDACConfig.psm1 deleted file mode 100644 index 7946ac589..000000000 --- a/WDACConfig/Get-CommonWDACConfig.psm1 +++ /dev/null @@ -1,127 +0,0 @@ -#Requires -RunAsAdministrator -function Get-CommonWDACConfig { - [CmdletBinding()] - Param( - [parameter(Mandatory = $false)][switch]$CertCN, - [parameter(Mandatory = $false)][switch]$CertPath, - [parameter(Mandatory = $false)][switch]$SignToolPath, - [parameter(Mandatory = $false)][switch]$SignedPolicyPath, - [parameter(Mandatory = $false)][switch]$UnsignedPolicyPath, - [parameter(Mandatory = $false, DontShow = $true)][switch]$StrictKernelPolicyGUID, # DontShow prevents common parameters from being displayed too - [parameter(Mandatory = $false, DontShow = $true)][switch]$StrictKernelNoFlightRootsPolicyGUID, - [parameter(Mandatory = $false)][switch]$Open, - [parameter(Mandatory = $false, DontShow = $true)][switch]$LastUpdateCheck - ) - begin { - # Importing resources such as functions by dot-sourcing so that they will run in the same scope and their variables will be usable - . "$psscriptroot\Resources.ps1" - - # Stop operation as soon as there is an error anywhere, unless explicitly specified otherwise - $ErrorActionPreference = 'Stop' - - # Fetch User account directory path - [string]$global:UserAccountDirectoryPath = (Get-CimInstance Win32_UserProfile -Filter "SID = '$([System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value)'").LocalPath - - # Create User configuration folder if it doesn't already exist - if (-NOT (Test-Path -Path "$global:UserAccountDirectoryPath\.WDACConfig\")) { - New-Item -ItemType Directory -Path "$global:UserAccountDirectoryPath\.WDACConfig\" -Force -ErrorAction Stop | Out-Null - Write-Debug -Message "The .WDACConfig folder in current user's folder has been created because it didn't exist." - } - - # Create User configuration file if it doesn't already exist - if (-NOT (Test-Path -Path "$global:UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json")) { - New-Item -ItemType File -Path "$global:UserAccountDirectoryPath\.WDACConfig\" -Name 'UserConfigurations.json' -Force -ErrorAction Stop | Out-Null - Write-Debug -Message "The UserConfigurations.json file in \.WDACConfig\ folder has been created because it didn't exist." - } - - if ($Open) { - . "$global:UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" - break - } - - if ($PSBoundParameters.Count -eq 0) { - # Display this message if User Configuration file is empty - if ($null -eq (Get-Content -Path "$global:UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json")) { - &$WritePink "`nYour current WDAC User Configurations is empty." - } - # Display this message if User Configuration file has content - else { - &$WritePink "`nThis is your current WDAC User Configurations: " - Get-Content -Path "$global:UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" | ConvertFrom-Json | Format-List * - } - break - } - - # Read the current user configurations - [PSCustomObject]$CurrentUserConfigurations = Get-Content -Path "$global:UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" - # If the file exists but is corrupted and has bad values, rewrite it - try { - $CurrentUserConfigurations = $CurrentUserConfigurations | ConvertFrom-Json - } - catch { - Write-Warning 'The UserConfigurations.json was corrupted, clearing it.' - Set-Content -Path "$global:UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" -Value '' - } - } - - process {} - - end { - # Use a switch statement to check which parameter is present and output the corresponding value from the json file - switch ($true) { - $SignedPolicyPath.IsPresent { Write-Output $CurrentUserConfigurations.SignedPolicyPath } - $UnsignedPolicyPath.IsPresent { Write-Output $CurrentUserConfigurations.UnsignedPolicyPath } - $SignToolPath.IsPresent { Write-Output $CurrentUserConfigurations.SignToolCustomPath } - $CertCN.IsPresent { Write-Output $CurrentUserConfigurations.CertificateCommonName } - $StrictKernelPolicyGUID.IsPresent { Write-Output $CurrentUserConfigurations.StrictKernelPolicyGUID } - $StrictKernelNoFlightRootsPolicyGUID.IsPresent { Write-Output $CurrentUserConfigurations.StrictKernelNoFlightRootsPolicyGUID } - $CertPath.IsPresent { Write-Output $CurrentUserConfigurations.CertificatePath } - $LastUpdateCheck.IsPresent { Write-Output $CurrentUserConfigurations.LastUpdateCheck } - } - } -} - -<# -.SYNOPSIS -Query and Read common values for parameters used by WDACConfig module - -.LINK -https://github.com/HotCakeX/Harden-Windows-Security/wiki/Get-CommonWDACConfig - -.DESCRIPTION -Reads and gets the values from the User Config Json file, used by the module internally and also to display the values on the console for the user - -.COMPONENT -Windows Defender Application Control, ConfigCI PowerShell module, WDACConfig module - -.FUNCTIONALITY -Reads and gets the values from the User Config Json file, used by the module internally and also to display the values on the console for the user - -.PARAMETER SignedPolicyPath -Shows the path to a Signed WDAC xml policy - -.PARAMETER UnsignedPolicyPath -Shows the path to an Unsigned WDAC xml policy - -.PARAMETER CertCN -Shows the certificate common name - -.PARAMETER SignToolPath -Shows the path to the SignTool.exe - -.PARAMETER CertPath -Shows the path to a .cer certificate file - -.PARAMETER Open -Opens the User Configuration file with the default app assigned to open Json files - -.PARAMETER StrictKernelPolicyGUID -Shows the GUID of the Strict Kernel mode policy - -.PARAMETER StrictKernelNoFlightRootsPolicyGUID -Shows the GUID of the Strict Kernel no Flights root mode policy - -#> - -# Set PSReadline tab completion to complete menu for easier access to available parameters - Only for the current session -Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete diff --git a/WDACConfig/Invoke-WDACSimulation.psm1 b/WDACConfig/Invoke-WDACSimulation.psm1 deleted file mode 100644 index 5be864ef3..000000000 --- a/WDACConfig/Invoke-WDACSimulation.psm1 +++ /dev/null @@ -1,263 +0,0 @@ -#Requires -RunAsAdministrator -function Invoke-WDACSimulation { - [CmdletBinding( - PositionalBinding = $false, - SupportsShouldProcess = $true - )] - Param( - [ValidateScript({ Test-Path $_ -PathType 'Container' }, ErrorMessage = 'The path you selected is not a folder path.')] - [Parameter(Mandatory = $true)][System.String]$FolderPath, - - [ValidateScript({ Test-Path $_ -PathType 'Leaf' }, ErrorMessage = 'The path you selected is not a file path.')] - [Parameter(Mandatory = $true)][System.String]$XmlFilePath, - - [Parameter(Mandatory = $false)][Switch]$SkipVersionCheck # Used by the entire Cmdlet - ) - - begin { - - # Importing resources such as functions by dot-sourcing so that they will run in the same scope and their variables will be usable - . "$psscriptroot\Resources2.ps1" - . "$psscriptroot\Resources.ps1" - - # Detecting if Debug switch is used, will do debugging actions based on that - $Debug = $PSBoundParameters.Debug.IsPresent - - # Stop operation as soon as there is an error anywhere, unless explicitly specified otherwise - $ErrorActionPreference = 'Stop' - if (-NOT $SkipVersionCheck) { . Update-self } - } - - process { - # For Testing purposes - # $FolderPath = '' - # $XmlFilePath = '' - - if ($FolderPath) { - # Store the processed results of the valid Signed files - [System.Object[]]$SignedResult = @() - - # File paths of the files allowed by Signer/certificate - [System.Object[]]$AllowedSignedFilePaths = @() - - # File paths of the files allowed by Hash - [System.Object[]]$AllowedUnsignedFilePaths = @() - - # Stores the final object of all of the results - [System.Object[]]$MegaOutputObject = @() - - # File paths of the Signed files with HashMismatch Status - [System.Object[]]$SignedHashMismatchFilePaths = @() - - # File paths of the Signed files with a status that doesn't fall into any other category - [System.Object[]]$SignedButUnknownFilePaths = @() - - # Hash Sha256 values of all the file rules based on hash in the supplied xml policy file - [System.Object[]]$SHA256HashesFromXML = (Get-FileRuleOutput -xmlPath $XmlFilePath).hashvalue - - # Get all of the files that WDAC supports from the user provided directory - [System.Object[]]$CollectedFiles = (Get-ChildItem -Recurse -Path $FolderPath -File -Include '*.sys', '*.exe', '*.com', '*.dll', '*.ocx', '*.msp', '*.mst', '*.msi', '*.js', '*.vbs', '*.ps1', '*.appx').FullName - - # Loop through each file - $CollectedFiles | ForEach-Object { - - $CurrentFilePath = $_ - - # Check see if the file's hash exists in the XML file regardless of whether it's signed or not - # This is because WDAC policies sometimes have hash rules for signed files too - try { - $CurrentFilePathHash = (Get-AppLockerFileInformation -Path $CurrentFilePath -ErrorAction Stop).hash -replace 'SHA256 0x', '' - } - catch { - Write-Debug -Message "Get-AppLockerFileInformation failed for the file at $CurrentFilePath, using New-CIPolicyRule cmdlet..." - - $CurrentHashOutput = New-CIPolicyRule -Level hash -Fallback none -AllowFileNameFallbacks -UserWriteablePaths -DriverFilePath $CurrentFilePath - - $CurrentFilePathHash = ($CurrentHashOutput | Where-Object { $_.name -like '*Hash Sha256*' }).attributes.hash - } - - # if the file's hash exists in the XML file - if ($CurrentFilePathHash -in $SHA256HashesFromXML) { - $AllowedUnsignedFilePaths += $CurrentFilePath - } - else { - - switch ((Get-AuthenticodeSignature -FilePath $CurrentFilePath).Status) { - # If the file is signed and valid - 'valid' { - # If debug is used show extra info on the console - if ($Debug) { - Write-Host "Currently processing signed file: `n$CurrentFilePath" -ForegroundColor Yellow - } - # Use the function in Resources2.ps1 file to process it - $SignedResult += Compare-SignerAndCertificate -XmlFilePath $XmlFilePath -SignedFilePath $CurrentFilePath | Where-Object { ($_.CertRootMatch -eq $true) -and ($_.CertNameMatch -eq $true) -and ($_.CertPublisherMatch -eq $true) } - break - } - 'HashMismatch' { - $SignedHashMismatchFilePaths += $CurrentFilePath - break - } - default { $SignedButUnknownFilePaths += $CurrentFilePath; break } - } - } - } - - # File paths of the files allowed by Signer/certificate, Unique - [System.Object[]]$AllowedSignedFilePaths = $SignedResult.FilePath | Get-Unique - - - if ($AllowedUnsignedFilePaths) { - # Loop through the first array and create output objects with the file path and source - foreach ($Path in $AllowedUnsignedFilePaths) { - # Create a hash table with the file path and source - [System.Collections.Hashtable]$Object = @{ - FilePath = $Path - Source = 'Hash' - Permission = 'Allowed' - } - # Convert the hash table to a PSObject and add it to the output array - $MegaOutputObject += New-Object -TypeName PSObject -Property $Object - } - } - - # For valid Signed files - if ($AllowedSignedFilePaths) { - # Loop through the second array and create output objects with the file path and source - foreach ($Path in $AllowedSignedFilePaths) { - # Create a hash table with the file path and source properties - [System.Collections.Hashtable]$Object = @{ - FilePath = $Path - Source = 'Signer' - Permission = 'Allowed' - } - # Convert the hash table to a PSObject and add it to the output array - $MegaOutputObject += New-Object -TypeName PSObject -Property $Object - } - } - - # For Signed files with mismatch signature status - if ($SignedHashMismatchFilePaths) { - # Loop through the second array and create output objects with the file path and source - foreach ($Path in $SignedHashMismatchFilePaths) { - # Create a hash table with the file path and source properties - [System.Collections.Hashtable]$Object = @{ - FilePath = $Path - Source = 'Signer' - Permission = 'Not Allowed - Hash Mismatch' - } - # Convert the hash table to a PSObject and add it to the output array - $MegaOutputObject += New-Object -TypeName PSObject -Property $Object - } - } - - # For Signed files with Unknown signature status - if ($SignedButUnknownFilePaths) { - # Loop through the second array and create output objects with the file path and source - foreach ($Path in $SignedButUnknownFilePaths) { - # Create a hash table with the file path and source properties - [System.Collections.Hashtable]$Object = @{ - FilePath = $Path - Source = 'Signer' - Permission = 'Not Allowed - Expired or unknown' - } - # Convert the hash table to a PSObject and add it to the output array - $MegaOutputObject += New-Object -TypeName PSObject -Property $Object - } - } - - # Unique number of files allowed by hash - used for counting only - $UniqueFilesAllowedByHash = $MegaOutputObject | Select-Object -Property FilePath, source, Permission -Unique | Where-Object { $_.source -eq 'hash' } - - # To detect files that are not allowed - - # Check if any supported files were found in the user provided directory and any of them were allowed - if ($($MegaOutputObject.Filepath) -and $CollectedFiles) { - # Compare the paths of all the supported files that were found in user provided directory with the array of files that were allowed by Signer or hash in the policy - # Then save the output to a different array - [System.Object[]]$FinalComparisonForFilesNotAllowed = Compare-Object -ReferenceObject $($MegaOutputObject.Filepath) -DifferenceObject $CollectedFiles -PassThru | Where-Object { $_.SideIndicator -eq '=>' } - } - - # If there is any files in the user selected directory that is not allowed by the policy - if ($FinalComparisonForFilesNotAllowed) { - - foreach ($Path in $FinalComparisonForFilesNotAllowed) { - # Create a hash table with the file path and source properties - [System.Collections.Hashtable]$Object = @{ - FilePath = $Path - Source = 'N/A' - Permission = 'Not Allowed' - } - # Convert the hash table to a PSObject and add it to the output array - $MegaOutputObject += New-Object -TypeName PSObject -Property $Object - } - } - - # Change the color of the Table header - $PSStyle.Formatting.TableHeader = "$($PSStyle.Foreground.FromRGB(255,165,0))" - - # Display the final main output array as a table - allowed files - $MegaOutputObject | Select-Object -Property FilePath, - - @{ - Label = 'Source' - Expression = - { switch ($_.source) { - { $_ -eq 'Signer' } { $color = "$($PSStyle.Foreground.FromRGB(152,255,152))" } # Use PSStyle to set the color - { $_ -eq 'Hash' } { $color = "$($PSStyle.Foreground.FromRGB(255,255,49))" } # Use PSStyle to set the color - { $_ -eq 'N/A' } { $color = "$($PSStyle.Foreground.FromRGB(255,20,147))" } # Use PSStyle to set the color - } - "$color$($_.source)$($PSStyle.Reset)" # Use PSStyle to reset the color - } - }, Permission -Unique | Sort-Object -Property Permission | Format-Table -Property FilePath, Source, Permission - - # Showing Signature based allowed file details - &$WriteLavender "`n$($AllowedSignedFilePaths.count) File(s) Inside the Selected Folder Are Allowed by Signatures by Your Policy." - - # Showing Hash based allowed file details - &$WriteLavender "$($UniqueFilesAllowedByHash.count) File(s) Inside the Selected Folder Are Allowed by Hashes by Your Policy.`n" - - # Export the output as CSV - $MegaOutputObject | Select-Object -Property FilePath, source, Permission -Unique | Sort-Object -Property Permission | Export-Csv -Path .\WDACSimulationOutput.csv -Force - - if ($Debug) { - Write-Host 'Files that were UNSIGNED' -ForegroundColor Blue - $AllowedUnsignedFilePaths - } - - } - } - - <# -.SYNOPSIS -Simulates the deployment of the WDAC policy - -.LINK -https://github.com/HotCakeX/Harden-Windows-Security/wiki/Invoke-WDACSimulation - -.DESCRIPTION -Simulates the deployment of the WDAC policy by analyzing a folder and checking which of the files in the folder are allowed by a user selected policy xml file - -.COMPONENT -Windows Defender Application Control, ConfigCI PowerShell module - -.FUNCTIONALITY -Simulates the deployment of the WDAC policy - -.PARAMETER FolderPath -Provide path to a folder where you want WDAC simulation to take place - -.PARAMETER XmlFilePath -Provide path to a policy xml file that you want the cmdlet to simulate its deployment and running files against it - -.PARAMETER SkipVersionCheck -Can be used with any parameter to bypass the online version check - only to be used in rare cases - -#> -} - -# Importing argument completer ScriptBlocks -. "$psscriptroot\ArgumentCompleters.ps1" -# Set PSReadline tab completion to complete menu for easier access to available parameters - Only for the current session -Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete -Register-ArgumentCompleter -CommandName 'Invoke-WDACSimulation' -ParameterName 'FolderPath' -ScriptBlock $ArgumentCompleterFolderPathsPicker -Register-ArgumentCompleter -CommandName 'Invoke-WDACSimulation' -ParameterName 'XmlFilePath' -ScriptBlock $ArgumentCompleterXmlFilePathsPicker diff --git a/WDACConfig/New-WDACConfig.psm1 b/WDACConfig/New-WDACConfig.psm1 deleted file mode 100644 index 9950213c5..000000000 --- a/WDACConfig/New-WDACConfig.psm1 +++ /dev/null @@ -1,670 +0,0 @@ -#Requires -RunAsAdministrator -function New-WDACConfig { - [CmdletBinding( - DefaultParameterSetName = 'Get Block Rules', - SupportsShouldProcess = $true, - PositionalBinding = $false, - ConfirmImpact = 'High' - )] - Param( - # 9 Main parameters - should be used for position 0 - [Parameter(Mandatory = $false, ParameterSetName = 'Get Block Rules')][Switch]$GetBlockRules, - [Parameter(Mandatory = $false, ParameterSetName = 'Get Driver Block Rules')][Switch]$GetDriverBlockRules, - [Parameter(Mandatory = $false, ParameterSetName = 'Make AllowMSFT With Block Rules')][Switch]$MakeAllowMSFTWithBlockRules, - [Parameter(Mandatory = $false, ParameterSetName = 'Set Auto Update Driver Block Rules')][Switch]$SetAutoUpdateDriverBlockRules, - [Parameter(Mandatory = $false, ParameterSetName = 'Prep MSFT Only Audit')][Switch]$PrepMSFTOnlyAudit, - [Parameter(Mandatory = $false, ParameterSetName = 'Prep Default Windows Audit')][Switch]$PrepDefaultWindowsAudit, - [Parameter(Mandatory = $false, ParameterSetName = 'Make Policy From Audit Logs')][Switch]$MakePolicyFromAuditLogs, - [Parameter(Mandatory = $false, ParameterSetName = 'Make Light Policy')][Switch]$MakeLightPolicy, - [Parameter(Mandatory = $false, ParameterSetName = 'Make DefaultWindows With Block Rules')][Switch]$MakeDefaultWindowsWithBlockRules, - - [ValidateSet('Allow Microsoft Base', 'Default Windows Base')] - [Parameter(Mandatory = $true, ParameterSetName = 'Make Policy From Audit Logs')] - [System.String]$BasePolicyType, - - [Parameter(Mandatory = $false, ParameterSetName = 'Make AllowMSFT With Block Rules')] - [Parameter(Mandatory = $false, ParameterSetName = 'Make Policy From Audit Logs')] - [Parameter(Mandatory = $false, ParameterSetName = 'Make Light Policy')] - [Parameter(Mandatory = $false, ParameterSetName = 'Make DefaultWindows With Block Rules')] - [Parameter(Mandatory = $false, ParameterSetName = 'Prep MSFT Only Audit')] - [Parameter(Mandatory = $false, ParameterSetName = 'Prep Default Windows Audit')] - [Parameter(Mandatory = $false, ParameterSetName = 'Get Block Rules')] - [Parameter(Mandatory = $false, ParameterSetName = 'Get Driver Block Rules')] - [Switch]$Deploy, - - [Parameter(Mandatory = $false, ParameterSetName = 'Make DefaultWindows With Block Rules')] - [switch]$IncludeSignTool, - - [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Make DefaultWindows With Block Rules')] - [System.String]$SignToolPath, - - [Parameter(Mandatory = $false, ParameterSetName = 'Make Light Policy')] - [Parameter(Mandatory = $false, ParameterSetName = 'Make Policy From Audit Logs')] - [Parameter(Mandatory = $false, ParameterSetName = 'Make AllowMSFT With Block Rules')] - [Parameter(Mandatory = $false, ParameterSetName = 'Make DefaultWindows With Block Rules')] - [Switch]$TestMode, - - [Parameter(Mandatory = $false, ParameterSetName = 'Make AllowMSFT With Block Rules')] - [Parameter(Mandatory = $false, ParameterSetName = 'Make Policy From Audit Logs')] - [Parameter(Mandatory = $false, ParameterSetName = 'Make Light Policy')] - [Parameter(Mandatory = $false, ParameterSetName = 'Make DefaultWindows With Block Rules')] - [Switch]$RequireEVSigners, - - [ValidateSet('OriginalFileName', 'InternalName', 'FileDescription', 'ProductName', 'PackageFamilyName', 'FilePath')] - [Parameter(Mandatory = $false, ParameterSetName = 'Make Policy From Audit Logs')] - [System.String]$SpecificFileNameLevel, - - [Parameter(Mandatory = $false, ParameterSetName = 'Make Policy From Audit Logs')] - [Switch]$NoDeletedFiles, - - [Parameter(Mandatory = $false, ParameterSetName = 'Make Policy From Audit Logs')] - [Switch]$NoUserPEs, - - [Parameter(Mandatory = $false, ParameterSetName = 'Make Policy From Audit Logs')] - [Switch]$NoScript, - - [ValidateSet([Levelz])] - [parameter(Mandatory = $false, ParameterSetName = 'Make Policy From Audit Logs')] - [System.String]$Level = 'FilePublisher', # Setting the default value for the Level parameter - - [ValidateSet([Fallbackz])] - [parameter(Mandatory = $false, ParameterSetName = 'Make Policy From Audit Logs')] - [System.String[]]$Fallbacks = 'Hash', # Setting the default value for the Fallbacks parameter - - # Setting the maxim range to the maximum allowed log size by Windows Event viewer - [ValidateRange(1024KB, 18014398509481983KB)] - [Parameter(Mandatory = $false, ParameterSetName = 'Prep MSFT Only Audit')] - [Parameter(Mandatory = $false, ParameterSetName = 'Prep Default Windows Audit')] - [Parameter(Mandatory = $false, ParameterSetName = 'Make Policy From Audit Logs')] - [System.Int64]$LogSize, - - [Parameter(Mandatory = $false)][Switch]$SkipVersionCheck - ) - - begin { - # Importing resources such as functions by dot-sourcing so that they will run in the same scope and their variables will be usable - . "$psscriptroot\Resources.ps1" - - # Stop operation as soon as there is an error anywhere, unless explicitly specified otherwise - $ErrorActionPreference = 'Stop' - - # Fetching Temp Directory - [string]$global:UserTempDirectoryPath = [System.IO.Path]::GetTempPath() - - # Fetch User account directory path - [string]$global:UserAccountDirectoryPath = (Get-CimInstance Win32_UserProfile -Filter "SID = '$([System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value)'").LocalPath - - #region User-Configurations-Processing-Validation - # If User is creating Default Windows policy and including SignTool path - if ($IncludeSignTool -and $MakeDefaultWindowsWithBlockRules) { - # Read User configuration file if it exists - $UserConfig = Get-Content -Path "$global:UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" -ErrorAction SilentlyContinue - if ($UserConfig) { - # Validate the Json file and read its content to make sure it's not corrupted - try { $UserConfig = $UserConfig | ConvertFrom-Json } - catch { - Write-Error 'User Configurations Json file is corrupted, deleting it...' -ErrorAction Continue - # Calling this function with this parameter automatically does its job and breaks/stops the operation - Set-CommonWDACConfig -DeleteUserConfig - } - } - } - - # Get SignToolPath from user parameter or user config file or auto-detect it - if ($SignToolPath) { - $SignToolPathFinal = Get-SignTool -SignToolExePath $SignToolPath - } # If it is null, then Get-SignTool will behave the same as if it was called without any arguments. - elseif ($IncludeSignTool -and $MakeDefaultWindowsWithBlockRules) { - $SignToolPathFinal = Get-SignTool -SignToolExePath ($UserConfig.SignToolCustomPath ?? $null) - } - #endregion User-Configurations-Processing-Validation - - # Detecting if Debug switch is used, will do debugging actions based on that - $Debug = $PSBoundParameters.Debug.IsPresent - - # argument tab auto-completion and ValidateSet for Fallbacks - Class Fallbackz : System.Management.Automation.IValidateSetValuesGenerator { - [System.String[]] GetValidValues() { - $Fallbackz = ('Hash', 'FileName', 'SignedVersion', 'Publisher', 'FilePublisher', 'LeafCertificate', 'PcaCertificate', 'RootCertificate', 'WHQL', 'WHQLPublisher', 'WHQLFilePublisher', 'PFN', 'FilePath', 'None') - return [System.String[]]$Fallbackz - } - } - - # argument tab auto-completion and ValidateSet for level - Class Levelz : System.Management.Automation.IValidateSetValuesGenerator { - [System.String[]] GetValidValues() { - $Levelz = ('Hash', 'FileName', 'SignedVersion', 'Publisher', 'FilePublisher', 'LeafCertificate', 'PcaCertificate', 'RootCertificate', 'WHQL', 'WHQLPublisher', 'WHQLFilePublisher', 'PFN', 'FilePath', 'None') - return [System.String[]]$Levelz - } - } - - [scriptblock]$GetDriverBlockRulesSCRIPTBLOCK = { - [System.String]$DriverRules = (Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/MicrosoftDocs/windows-itpro-docs/public/windows/security/application-security/application-control/windows-defender-application-control/design/microsoft-recommended-driver-block-rules.md').Content -replace "(?s).*``````xml(.*)``````.*", '$1' - # Remove the unnecessary rules and elements - not using this one because then during the merge there will be error - The reason is that "" is the only FileruleRef in the xml and after removing it, the element will be empty - $DriverRules = $DriverRules -replace '', '' - $DriverRules = $DriverRules -replace '', '' - $DriverRules = $DriverRules -replace '', '' - $DriverRules | Out-File 'Microsoft recommended driver block rules TEMP.xml' - # Remove empty lines from the policy file - Get-Content 'Microsoft recommended driver block rules TEMP.xml' | Where-Object { $_.trim() -ne '' } | Out-File 'Microsoft recommended driver block rules.xml' - Remove-Item 'Microsoft recommended driver block rules TEMP.xml' -Force - Set-RuleOption -FilePath 'Microsoft recommended driver block rules.xml' -Option 3 -Delete - Set-HVCIOptions -Strict -FilePath 'Microsoft recommended driver block rules.xml' - # Display extra info about the Microsoft Drivers block list - Invoke-Command -ScriptBlock $DriversBlockListInfoGatheringSCRIPTBLOCK - # Display the result as object - [PSCustomObject]@{ - PolicyFile = 'Microsoft recommended driver block rules.xml' - } - } - - [scriptblock]$MakeAllowMSFTWithBlockRulesSCRIPTBLOCK = { - - param([System.Boolean]$NoCIP) - # Get the latest Microsoft recommended block rules - Invoke-Command -ScriptBlock $GetBlockRulesSCRIPTBLOCK | Out-Null - Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowMicrosoft.xml' -Destination 'AllowMicrosoft.xml' - Merge-CIPolicy -PolicyPaths .\AllowMicrosoft.xml, 'Microsoft recommended block rules.xml' -OutputFilePath .\AllowMicrosoftPlusBlockRules.xml | Out-Null - [System.String]$PolicyID = Set-CIPolicyIdInfo -FilePath .\AllowMicrosoftPlusBlockRules.xml -PolicyName "Allow Microsoft Plus Block Rules - $(Get-Date -Format 'MM-dd-yyyy')" -ResetPolicyID - [System.String]$PolicyID = $PolicyID.Substring(11) - Set-CIPolicyVersion -FilePath .\AllowMicrosoftPlusBlockRules.xml -Version '1.0.0.0' - @(0, 2, 5, 6, 11, 12, 16, 17, 19, 20) | ForEach-Object { Set-RuleOption -FilePath .\AllowMicrosoftPlusBlockRules.xml -Option $_ } - @(3, 4, 9, 10, 13, 18) | ForEach-Object { Set-RuleOption -FilePath .\AllowMicrosoftPlusBlockRules.xml -Option $_ -Delete } - if ($TestMode -and $MakeAllowMSFTWithBlockRules) { - 9..10 | ForEach-Object { Set-RuleOption -FilePath .\AllowMicrosoftPlusBlockRules.xml -Option $_ } - } - if ($RequireEVSigners -and $MakeAllowMSFTWithBlockRules) { - Set-RuleOption -FilePath .\AllowMicrosoftPlusBlockRules.xml -Option 8 - } - Set-HVCIOptions -Strict -FilePath .\AllowMicrosoftPlusBlockRules.xml - ConvertFrom-CIPolicy .\AllowMicrosoftPlusBlockRules.xml "$PolicyID.cip" | Out-Null - # Remove the extra files that were created during module operation and are no longer needed - Remove-Item '.\AllowMicrosoft.xml', 'Microsoft recommended block rules.xml' -Force - [PSCustomObject]@{ - PolicyFile = 'AllowMicrosoftPlusBlockRules.xml' - BinaryFile = "$PolicyID.cip" - } - if ($Deploy -and $MakeAllowMSFTWithBlockRules) { - CiTool --update-policy "$PolicyID.cip" -json | Out-Null - Write-Host "`n" - Remove-Item -Path "$PolicyID.cip" -Force - } - if ($NoCIP) - { Remove-Item -Path "$PolicyID.cip" -Force } - } - - [scriptblock]$MakeDefaultWindowsWithBlockRulesSCRIPTBLOCK = { - param([System.Boolean]$NoCIP) - Invoke-Command -ScriptBlock $GetBlockRulesSCRIPTBLOCK | Out-Null - Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\DefaultWindows_Enforced.xml' -Destination 'DefaultWindows_Enforced.xml' - - [System.Boolean]$global:MergeSignToolPolicy = $false - - if ($SignToolPathFinal) { - # Allowing SignTool to be able to run after Default Windows base policy is deployed in Signed scenario - &$WriteTeaGreen "`nCreating allow rules for SignTool.exe in the DefaultWindows base policy so you can continue using it after deploying the DefaultWindows base policy." - New-Item -Path "$global:UserTempDirectoryPath\TemporarySignToolFile" -ItemType Directory -Force | Out-Null - Copy-Item -Path $SignToolPathFinal -Destination "$global:UserTempDirectoryPath\TemporarySignToolFile" -Force - New-CIPolicy -ScanPath "$global:UserTempDirectoryPath\TemporarySignToolFile" -Level FilePublisher -Fallback Hash -UserPEs -UserWriteablePaths -MultiplePolicyFormat -AllowFileNameFallbacks -FilePath .\SignTool.xml - # Delete the Temporary folder in the TEMP folder - if (!$Debug) { Remove-Item -Recurse -Path "$global:UserTempDirectoryPath\TemporarySignToolFile" -Force } - - [System.Boolean]$global:MergeSignToolPolicy = $true - } - - # Scan PowerShell core directory and allow its files in the Default Windows base policy so that module can still be used once it's been deployed - if (Test-Path 'C:\Program Files\PowerShell') { - &$WriteLavender 'Creating allow rules for PowerShell in the DefaultWindows base policy so you can continue using this module after deploying it.' - New-CIPolicy -ScanPath 'C:\Program Files\PowerShell' -Level FilePublisher -NoScript -Fallback Hash -UserPEs -UserWriteablePaths -MultiplePolicyFormat -FilePath .\AllowPowerShell.xml - - if ($global:MergeSignToolPolicy) { - Merge-CIPolicy -PolicyPaths .\DefaultWindows_Enforced.xml, .\AllowPowerShell.xml, 'Microsoft recommended block rules.xml', .\SignTool.xml -OutputFilePath .\DefaultWindowsPlusBlockRules.xml | Out-Null - } - else { - Merge-CIPolicy -PolicyPaths .\DefaultWindows_Enforced.xml, .\AllowPowerShell.xml, 'Microsoft recommended block rules.xml' -OutputFilePath .\DefaultWindowsPlusBlockRules.xml | Out-Null - } - } - else { - if ($global:MergeSignToolPolicy) { - Merge-CIPolicy -PolicyPaths .\DefaultWindows_Enforced.xml, 'Microsoft recommended block rules.xml', .\SignTool.xml -OutputFilePath .\DefaultWindowsPlusBlockRules.xml | Out-Null - } - else { - Merge-CIPolicy -PolicyPaths .\DefaultWindows_Enforced.xml, 'Microsoft recommended block rules.xml' -OutputFilePath .\DefaultWindowsPlusBlockRules.xml | Out-Null - } - } - - [System.String]$PolicyID = Set-CIPolicyIdInfo -FilePath .\DefaultWindowsPlusBlockRules.xml -PolicyName "Default Windows Plus Block Rules - $(Get-Date -Format 'MM-dd-yyyy')" -ResetPolicyID - [System.String]$PolicyID = $PolicyID.Substring(11) - Set-CIPolicyVersion -FilePath .\DefaultWindowsPlusBlockRules.xml -Version '1.0.0.0' - @(0, 2, 5, 6, 11, 12, 16, 17, 19, 20) | ForEach-Object { Set-RuleOption -FilePath .\DefaultWindowsPlusBlockRules.xml -Option $_ } - @(3, 4, 9, 10, 13, 18) | ForEach-Object { Set-RuleOption -FilePath .\DefaultWindowsPlusBlockRules.xml -Option $_ -Delete } - if ($TestMode -and $MakeDefaultWindowsWithBlockRules) { - 9..10 | ForEach-Object { Set-RuleOption -FilePath .\DefaultWindowsPlusBlockRules.xml -Option $_ } - } - if ($RequireEVSigners -and $MakeDefaultWindowsWithBlockRules) { - Set-RuleOption -FilePath .\DefaultWindowsPlusBlockRules.xml -Option 8 - } - Set-HVCIOptions -Strict -FilePath .\DefaultWindowsPlusBlockRules.xml - ConvertFrom-CIPolicy .\DefaultWindowsPlusBlockRules.xml "$PolicyID.cip" | Out-Null - - Remove-Item .\AllowPowerShell.xml -Force -ErrorAction SilentlyContinue - Remove-Item '.\DefaultWindows_Enforced.xml', 'Microsoft recommended block rules.xml' -Force - if ($global:MergeSignToolPolicy -and !$Debug) { Remove-Item -Path .\SignTool.xml -Force } - - [PSCustomObject]@{ - PolicyFile = 'DefaultWindowsPlusBlockRules.xml' - BinaryFile = "$PolicyID.cip" - } - - if ($Deploy -and $MakeDefaultWindowsWithBlockRules) { - CiTool --update-policy "$PolicyID.cip" -json | Out-Null - Write-Host "`n" - Remove-Item -Path "$PolicyID.cip" -Force - } - if ($NoCIP) { Remove-Item -Path "$PolicyID.cip" -Force } - } - - [scriptblock]$DeployLatestDriverBlockRulesSCRIPTBLOCK = { - Invoke-WebRequest -Uri 'https://aka.ms/VulnerableDriverBlockList' -OutFile VulnerableDriverBlockList.zip - Expand-Archive .\VulnerableDriverBlockList.zip -DestinationPath 'VulnerableDriverBlockList' -Force - Rename-Item .\VulnerableDriverBlockList\SiPolicy_Enforced.p7b -NewName 'SiPolicy.p7b' -Force - Copy-Item .\VulnerableDriverBlockList\SiPolicy.p7b -Destination 'C:\Windows\System32\CodeIntegrity' - citool --refresh -json | Out-Null - &$WritePink 'SiPolicy.p7b has been deployed and policies refreshed.' - Remove-Item .\VulnerableDriverBlockList* -Recurse -Force - Invoke-Command -ScriptBlock $DriversBlockListInfoGatheringSCRIPTBLOCK - } - - [scriptblock]$DeployLatestBlockRulesSCRIPTBLOCK = { - (Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/MicrosoftDocs/windows-itpro-docs/public/windows/security/application-security/application-control/windows-defender-application-control/design/applications-that-can-bypass-wdac.md').Content -replace "(?s).*``````xml(.*)``````.*", '$1' | Out-File '.\Microsoft recommended block rules TEMP.xml' - # Remove empty lines from the policy file - Get-Content '.\Microsoft recommended block rules TEMP.xml' | Where-Object { $_.trim() -ne '' } | Out-File '.\Microsoft recommended block rules.xml' - Set-RuleOption -FilePath '.\Microsoft recommended block rules.xml' -Option 3 -Delete - @(0, 2, 6, 11, 12, 16, 19, 20) | ForEach-Object { Set-RuleOption -FilePath '.\Microsoft recommended block rules.xml' -Option $_ } - Set-HVCIOptions -Strict -FilePath '.\Microsoft recommended block rules.xml' - Remove-Item -Path '.\Microsoft recommended block rules TEMP.xml' -Force - [System.String]$PolicyID = (Set-CIPolicyIdInfo -FilePath '.\Microsoft recommended block rules.xml' -ResetPolicyID).Substring(11) - Set-CIPolicyIdInfo -PolicyName "Microsoft Windows User Mode Policy - Enforced - $(Get-Date -Format 'MM-dd-yyyy')" -FilePath '.\Microsoft recommended block rules.xml' - ConvertFrom-CIPolicy '.\Microsoft recommended block rules.xml' "$PolicyID.cip" | Out-Null - CiTool --update-policy "$PolicyID.cip" -json | Out-Null - &$WriteLavender 'The Microsoft recommended block rules policy has been deployed in enforced mode.' - Remove-Item "$PolicyID.cip" -Force - } - - [scriptblock]$SetAutoUpdateDriverBlockRulesSCRIPTBLOCK = { - # create a scheduled task that runs every 7 days - if (-NOT (Get-ScheduledTask -TaskName 'MSFT Driver Block list update' -ErrorAction SilentlyContinue)) { - $action = New-ScheduledTaskAction -Execute 'Powershell.exe' ` - -Argument '-NoProfile -WindowStyle Hidden -command "& {try {Invoke-WebRequest -Uri "https://aka.ms/VulnerableDriverBlockList" -OutFile VulnerableDriverBlockList.zip -ErrorAction Stop}catch{exit};Expand-Archive .\VulnerableDriverBlockList.zip -DestinationPath "VulnerableDriverBlockList" -Force;Rename-Item .\VulnerableDriverBlockList\SiPolicy_Enforced.p7b -NewName "SiPolicy.p7b" -Force;Copy-item .\VulnerableDriverBlockList\SiPolicy.p7b -Destination "C:\Windows\System32\CodeIntegrity";citool --refresh -json;Remove-Item .\VulnerableDriverBlockList -Recurse -Force;Remove-Item .\VulnerableDriverBlockList.zip -Force;}"' - $TaskPrincipal = New-ScheduledTaskPrincipal -LogonType S4U -UserId $env:USERNAME -RunLevel Highest - # trigger - $Time = New-ScheduledTaskTrigger -Once -At (Get-Date).AddHours(1) -RepetitionInterval (New-TimeSpan -Days 7) - # register the task - Register-ScheduledTask -Action $action -Trigger $Time -Principal $TaskPrincipal -TaskPath 'MSFT Driver Block list update' -TaskName 'MSFT Driver Block list update' -Description 'Microsoft Recommended Driver Block List update' - # define advanced settings for the task - $TaskSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -Compatibility Win8 -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 3) - # add advanced settings we defined to the task - Set-ScheduledTask -TaskPath 'MSFT Driver Block list update' -TaskName 'MSFT Driver Block list update' -Settings $TaskSettings - } - Invoke-Command -ScriptBlock $DriversBlockListInfoGatheringSCRIPTBLOCK - } - - [scriptblock]$PrepMSFTOnlyAuditSCRIPTBLOCK = { - if ($PrepMSFTOnlyAudit -and $LogSize) { Set-LogSize -LogSize $LogSize } - Copy-Item -Path C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowMicrosoft.xml -Destination .\AllowMicrosoft.xml - Set-RuleOption -FilePath .\AllowMicrosoft.xml -Option 3 - [System.String]$PolicyID = Set-CIPolicyIdInfo -FilePath .\AllowMicrosoft.xml -ResetPolicyID - [System.String]$PolicyID = $PolicyID.Substring(11) - Set-CIPolicyIdInfo -PolicyName 'PrepMSFTOnlyAudit' -FilePath .\AllowMicrosoft.xml - ConvertFrom-CIPolicy .\AllowMicrosoft.xml "$PolicyID.cip" | Out-Null - if ($Deploy) { - CiTool --update-policy "$PolicyID.cip" -json | Out-Null - &$WriteHotPink 'The default AllowMicrosoft policy has been deployed in Audit mode. No reboot required.' - Remove-Item 'AllowMicrosoft.xml', "$PolicyID.cip" -Force - } - else { - &$WriteHotPink 'The default AllowMicrosoft policy has been created in Audit mode and is ready for deployment.' - } - } - - [scriptblock]$PrepDefaultWindowsAuditSCRIPTBLOCK = { - if ($PrepDefaultWindowsAudit -and $LogSize) { Set-LogSize -LogSize $LogSize } - Copy-Item -Path C:\Windows\schemas\CodeIntegrity\ExamplePolicies\DefaultWindows_Audit.xml -Destination .\DefaultWindows_Audit.xml -Force - - # Making Sure neither PowerShell core nor WDACConfig module files are added to the Supplemental policy created by -MakePolicyFromAuditLogs parameter - # by adding them first to the deployed Default Windows policy in Audit mode. Because WDACConfig module files don't need to be allowed to run since they are *.ps1 and .*psm1 files - # And PowerShell core files will be added to the DefaultWindows Base policy anyway - if (Test-Path 'C:\Program Files\PowerShell') { - New-CIPolicy -ScanPath 'C:\Program Files\PowerShell' -Level FilePublisher -NoScript -Fallback Hash -UserPEs -UserWriteablePaths -MultiplePolicyFormat -FilePath .\AllowPowerShell.xml - New-CIPolicy -ScanPath "$psscriptroot" -Level hash -UserPEs -UserWriteablePaths -MultiplePolicyFormat -FilePath .\WDACConfigModule.xml - Merge-CIPolicy -PolicyPaths .\DefaultWindows_Audit.xml, .\AllowPowerShell.xml, .\WDACConfigModule.xml -OutputFilePath .\DefaultWindows_Audit_temp.xml | Out-Null - - Remove-Item DefaultWindows_Audit.xml -Force - Rename-Item -Path .\DefaultWindows_Audit_temp.xml -NewName 'DefaultWindows_Audit.xml' -Force - Remove-Item 'WDACConfigModule.xml', 'AllowPowerShell.xml' -Force - } - - Set-RuleOption -FilePath .\DefaultWindows_Audit.xml -Option 3 - [System.String]$PolicyID = Set-CIPolicyIdInfo -FilePath .\DefaultWindows_Audit.xml -ResetPolicyID - [System.String]$PolicyID = $PolicyID.Substring(11) - Set-CIPolicyIdInfo -PolicyName 'PrepDefaultWindows' -FilePath .\DefaultWindows_Audit.xml - ConvertFrom-CIPolicy .\DefaultWindows_Audit.xml "$PolicyID.cip" | Out-Null - if ($Deploy) { - CiTool --update-policy "$PolicyID.cip" -json | Out-Null - &$WriteLavender 'The defaultWindows policy has been deployed in Audit mode. No reboot required.' - Remove-Item 'DefaultWindows_Audit.xml', "$PolicyID.cip" -Force - } - else { - &$WriteLavender 'The defaultWindows policy has been created in Audit mode and is ready for deployment.' - } - } - - [scriptblock]$MakePolicyFromAuditLogsSCRIPTBLOCK = { - if ($MakePolicyFromAuditLogs -and $LogSize) { Set-LogSize -LogSize $LogSize } - # Make sure there is no leftover files from previous operations of this same command - Remove-Item -Path "$home\WDAC\*" -Recurse -Force -ErrorAction SilentlyContinue - # Create a working directory in user's folder - New-Item -Type Directory -Path "$home\WDAC" -Force | Out-Null - Set-Location "$home\WDAC" - - ############################### Base Policy Processing ############################### - - switch ($BasePolicyType) { - 'Allow Microsoft Base' { - Invoke-Command -ScriptBlock $MakeAllowMSFTWithBlockRulesSCRIPTBLOCK | Out-Null - $xml = [xml](Get-Content .\AllowMicrosoftPlusBlockRules.xml) - $BasePolicyID = $xml.SiPolicy.PolicyID - # define the location of the base policy - $BasePolicy = 'AllowMicrosoftPlusBlockRules.xml' - } - 'Default Windows Base' { - Invoke-Command -ScriptBlock $MakeDefaultWindowsWithBlockRulesSCRIPTBLOCK | Out-Null - $xml = [xml](Get-Content .\DefaultWindowsPlusBlockRules.xml) - $BasePolicyID = $xml.SiPolicy.PolicyID - # define the location of the base policy - $BasePolicy = 'DefaultWindowsPlusBlockRules.xml' - } - } - if ($TestMode -and $MakePolicyFromAuditLogs) { - 9..10 | ForEach-Object { Set-RuleOption -FilePath $BasePolicy -Option $_ } - } - if ($RequireEVSigners -and $MakePolicyFromAuditLogs) { - Set-RuleOption -FilePath $BasePolicy -Option 8 - } - - ############################### Supplemental Processing ############################### - - # Produce a policy xml file from event viewer logs - &$WriteLavender 'Scanning Windows Event logs and creating a policy file, please wait...' - - # Creating a hash table to dynamically add parameters based on user input and pass them to New-Cipolicy cmdlet - [System.Collections.Hashtable]$PolicyMakerHashTable = @{ - FilePath = 'AuditLogsPolicy_NoDeletedFiles.xml' - Audit = $true - Level = $Level - Fallback = $Fallbacks - MultiplePolicyFormat = $true - UserWriteablePaths = $true - WarningAction = 'SilentlyContinue' - AllowFileNameFallbacks = $true - } - # Assess user input parameters and add the required parameters to the hash table - if ($SpecificFileNameLevel) { $PolicyMakerHashTable['SpecificFileNameLevel'] = $SpecificFileNameLevel } - if ($NoScript) { $PolicyMakerHashTable['NoScript'] = $true } - if (!$NoUserPEs) { $PolicyMakerHashTable['UserPEs'] = $true } - - &$WriteHotPink "`nGenerating Supplemental policy with the following specifications:" - $PolicyMakerHashTable - Write-Host "`n" - # Create the supplemental policy via parameter splatting for files in event viewer that are currently on the disk - New-CIPolicy @PolicyMakerHashTable - - if (!$NoDeletedFiles) { - # Get Event viewer logs for code integrity - check the file path of all of the files in the log, resolve them using the command above - show files that are no longer available on the disk - [scriptblock]$AuditEventLogsDeletedFilesScriptBlock = { - foreach ($event in Get-WinEvent -FilterHashtable @{LogName = 'Microsoft-Windows-CodeIntegrity/Operational'; ID = 3076 }) { - $xml = [xml]$event.toxml() - $xml.event.eventdata.data | - ForEach-Object { $hash = @{} } { $hash[$_.name] = $_.'#text' } { [pscustomobject]$hash } | - ForEach-Object { - if ($_.'File Name' -match ($pattern = '\\Device\\HarddiskVolume(\d+)\\(.*)$')) { - $hardDiskVolumeNumber = $Matches[1] - $remainingPath = $Matches[2] - $getletter = $DriveLettersGlobalRootFix | Where-Object { $_.devicepath -eq "\Device\HarddiskVolume$hardDiskVolumeNumber" } - $usablePath = "$($getletter.DriveLetter)$remainingPath" - $_.'File Name' = $_.'File Name' -replace $pattern, $usablePath - } - if (-NOT (Test-Path $_.'File Name')) { - $_ | Select-Object FileVersion, 'File Name', PolicyGUID, 'SHA256 Hash', 'SHA256 Flat Hash', 'SHA1 Hash', 'SHA1 Flat Hash' - } - } - } - } - # storing the output from the scriptblock above in a variable - $DeletedFileHashesArray = Invoke-Command -ScriptBlock $AuditEventLogsDeletedFilesScriptBlock - } - # run the following only if there are any event logs for files no longer on the disk and if -NoDeletedFiles switch parameter wasn't used - if ($DeletedFileHashesArray -and !$NoDeletedFiles) { - - # Save the the File Rules and File Rule Refs to the Out-File FileRulesAndFileRefs.txt in the current working directory - (Get-FileRules -HashesArray $DeletedFileHashesArray) + (Get-RuleRefs -HashesArray $DeletedFileHashesArray) | Out-File FileRulesAndFileRefs.txt - - # Put the Rules and RulesRefs in an empty policy file - New-EmptyPolicy -RulesContent (Get-FileRules -HashesArray $DeletedFileHashesArray) -RuleRefsContent (Get-RuleRefs -HashesArray $DeletedFileHashesArray) | Out-File .\DeletedFilesHashes.xml - - # Merge the policy file we created at first using Event Viewer logs, with the policy file we created for Hash of the files no longer available on the disk - Merge-CIPolicy -PolicyPaths 'AuditLogsPolicy_NoDeletedFiles.xml', .\DeletedFilesHashes.xml -OutputFilePath .\SupplementalPolicy.xml | Out-Null - } - # do this only if there are no event logs detected with files no longer on the disk, so we use the policy file created earlier using Audit even logs - else { - Rename-Item 'AuditLogsPolicy_NoDeletedFiles.xml' -NewName 'SupplementalPolicy.xml' -Force - } - # Convert the SupplementalPolicy.xml policy file from base policy to supplemental policy of our base policy - Set-CIPolicyVersion -FilePath 'SupplementalPolicy.xml' -Version '1.0.0.0' - [System.String]$PolicyID = Set-CIPolicyIdInfo -FilePath 'SupplementalPolicy.xml' -PolicyName "Supplemental Policy made from Audit Event Logs on $(Get-Date -Format 'MM-dd-yyyy')" -ResetPolicyID -BasePolicyToSupplementPath $BasePolicy - [System.String]$PolicyID = $PolicyID.Substring(11) - # Make sure policy rule options that don't belong to a Supplemental policy don't exit - @(0, 1, 2, 3, 4, 8, 9, 10, 11, 12, 15, 16, 17, 19, 20) | ForEach-Object { Set-RuleOption -FilePath 'SupplementalPolicy.xml' -Option $_ -Delete } - - # Set the hypervisor Code Integrity option for Supplemental policy to Strict - Set-HVCIOptions -Strict -FilePath 'SupplementalPolicy.xml' - # convert the Supplemental Policy file to .cip binary file - ConvertFrom-CIPolicy 'SupplementalPolicy.xml' "$policyID.cip" | Out-Null - - [PSCustomObject]@{ - BasePolicyFile = $BasePolicy - BasePolicyGUID = $BasePolicyID - } - [PSCustomObject]@{ - SupplementalPolicyFile = 'SupplementalPolicy.xml' - SupplementalPolicyGUID = $PolicyID - } - - if (-NOT $Debug) { - Remove-Item -Path 'AuditLogsPolicy_NoDeletedFiles.xml', 'FileRulesAndFileRefs.txt', 'DeletedFilesHashes.xml' -Force -ErrorAction SilentlyContinue - } - - if ($Deploy -and $MakePolicyFromAuditLogs) { - CiTool --update-policy "$BasePolicyID.cip" -json | Out-Null - CiTool --update-policy "$policyID.cip" -json | Out-Null - &$WritePink "`nBase policy and Supplemental Policies deployed and activated.`n" - # Get the correct Prep mode Audit policy ID to remove from the system - switch ($BasePolicyType) { - 'Allow Microsoft Base' { - $IDToRemove = ((CiTool -lp -json | ConvertFrom-Json).Policies | Where-Object { $_.FriendlyName -eq 'PrepMSFTOnlyAudit' }).PolicyID - } - 'Default Windows Base' { - $IDToRemove = ((CiTool -lp -json | ConvertFrom-Json).Policies | Where-Object { $_.FriendlyName -eq 'PrepDefaultWindows' }).PolicyID - } - } - CiTool --remove-policy "{$IDToRemove}" -json | Out-Null - &$WriteLavender "`nSystem restart required to finish removing the Audit mode Prep policy" - } - } - - [scriptblock]$MakeLightPolicySCRIPTBLOCK = { - # Delete the any policy with the same name in the current working directory - Remove-Item -Path 'SignedAndReputable.xml' -Force -ErrorAction SilentlyContinue - Invoke-Command $MakeAllowMSFTWithBlockRulesSCRIPTBLOCK -ArgumentList $true | Out-Null - Rename-Item -Path 'AllowMicrosoftPlusBlockRules.xml' -NewName 'SignedAndReputable.xml' -Force - @(14, 15) | ForEach-Object { Set-RuleOption -FilePath .\SignedAndReputable.xml -Option $_ } - if ($TestMode -and $MakeLightPolicy) { - 9..10 | ForEach-Object { Set-RuleOption -FilePath .\SignedAndReputable.xml -Option $_ } - } - if ($RequireEVSigners -and $MakeLightPolicy) { - Set-RuleOption -FilePath .\SignedAndReputable.xml -Option 8 - } - $BasePolicyID = Set-CIPolicyIdInfo -FilePath .\SignedAndReputable.xml -ResetPolicyID -PolicyName "Signed And Reputable policy - $(Get-Date -Format 'MM-dd-yyyy')" - $BasePolicyID = $BasePolicyID.Substring(11) - Set-CIPolicyVersion -FilePath .\SignedAndReputable.xml -Version '1.0.0.0' - Set-HVCIOptions -Strict -FilePath .\SignedAndReputable.xml - ConvertFrom-CIPolicy .\SignedAndReputable.xml "$BasePolicyID.cip" | Out-Null - # Configure required services for ISG authorization - Start-Process -FilePath 'C:\Windows\System32\appidtel.exe' -ArgumentList 'start' -Wait -NoNewWindow - Start-Process -FilePath 'C:\Windows\System32\sc.exe' -ArgumentList 'config', 'appidsvc', 'start= auto' -Wait -NoNewWindow - if ($Deploy -and $MakeLightPolicy) { - CiTool --update-policy "$BasePolicyID.cip" -json | Out-Null - } - [PSCustomObject]@{ - BasePolicyFile = 'SignedAndReputable.xml' - BasePolicyGUID = $BasePolicyID - } - } - - # Script block that is used to supply extra information regarding Microsoft recommended driver block rules in commands that use them - [scriptblock]$DriversBlockListInfoGatheringSCRIPTBLOCK = { - [System.String]$owner = 'MicrosoftDocs' - [System.String]$repo = 'windows-itpro-docs' - [System.String]$path = 'windows/security/application-security/application-control/windows-defender-application-control/design/microsoft-recommended-driver-block-rules.md' - - [System.String]$ApiUrl = "https://api.github.com/repos/$owner/$repo/commits?path=$path" - [System.Object[]]$Response = Invoke-RestMethod $ApiUrl - [datetime]$Date = $Response[0].commit.author.date - - &$WriteLavender "The document containing the drivers block list on GitHub was last updated on $Date" - [System.String]$MicrosoftRecommendeDriverBlockRules = (Invoke-WebRequest 'https://raw.githubusercontent.com/MicrosoftDocs/windows-itpro-docs/public/windows/security/application-security/application-control/windows-defender-application-control/design/microsoft-recommended-driver-block-rules.md').Content - $MicrosoftRecommendeDriverBlockRules -match '(.*)' | Out-Null - &$WritePink "The current version of Microsoft recommended drivers block list is $($Matches[1])" - } - - if (-NOT $SkipVersionCheck) { . Update-self } - - $DriveLettersGlobalRootFix = Invoke-Command -ScriptBlock $DriveLettersGlobalRootFixScriptBlock - } - - process { - - switch ($true) { - # Deploy the latest block rules - { $GetBlockRules -and $Deploy } { & $DeployLatestBlockRulesSCRIPTBLOCK; break } - # Get the latest block rules - $GetBlockRules { & $GetBlockRulesSCRIPTBLOCK; break } - # Deploy the latest driver block rules - { $GetDriverBlockRules -and $Deploy } { & $DeployLatestDriverBlockRulesSCRIPTBLOCK; break } - # Get the latest driver block rules - { $GetDriverBlockRules } { & $GetDriverBlockRulesSCRIPTBLOCK; break } - - $SetAutoUpdateDriverBlockRules { & $SetAutoUpdateDriverBlockRulesSCRIPTBLOCK; break } - $MakeAllowMSFTWithBlockRules { & $MakeAllowMSFTWithBlockRulesSCRIPTBLOCK; break } - $MakePolicyFromAuditLogs { & $MakePolicyFromAuditLogsSCRIPTBLOCK; break } - $PrepMSFTOnlyAudit { & $PrepMSFTOnlyAuditSCRIPTBLOCK; break } - $MakeLightPolicy { & $MakeLightPolicySCRIPTBLOCK; break } - $MakeDefaultWindowsWithBlockRules { & $MakeDefaultWindowsWithBlockRulesSCRIPTBLOCK; break } - $PrepDefaultWindowsAudit { & $PrepDefaultWindowsAuditSCRIPTBLOCK; break } - default { Write-Warning 'None of the main parameters were selected.'; break } - } - } - - <# -.SYNOPSIS -Automate a lot of tasks related to WDAC (Windows Defender Application Control) - -.LINK -https://github.com/HotCakeX/Harden-Windows-Security/wiki/New-WDACConfig - -.DESCRIPTION -Using official Microsoft methods, configure and use Windows Defender Application Control - -.COMPONENT -Windows Defender Application Control, ConfigCI PowerShell module - -.FUNCTIONALITY -Automate various tasks related to Windows Defender Application Control (WDAC) - -.PARAMETER GetBlockRules -Create Microsoft recommended block rules xml policy and remove the allow rules - -.PARAMETER GetDriverBlockRules -Create Microsoft recommended driver block rules xml policy and remove the allow rules - -.PARAMETER MakeAllowMSFTWithBlockRules -Make WDAC policy by merging AllowMicrosoft policy with the recommended block rules - -.PARAMETER SetAutoUpdateDriverBlockRules -Make a Scheduled Task that automatically runs every 7 days to download the newest Microsoft Recommended driver block rules - -.PARAMETER PrepMSFTOnlyAudit -Prepare the system for Audit mode using AllowMicrosoft default policy - -.PARAMETER PrepDefaultWindowsAudit -Prepare the system for Audit mode using DefaultWindows policy - -.PARAMETER MakePolicyFromAuditLogs -Make a WDAC Policy from Audit event logs that also covers files no longer on disk - -.PARAMETER MakeLightPolicy -Make a WDAC Policy with ISG for Lightly Managed system - -.PARAMETER MakeDefaultWindowsWithBlockRules -Make a WDAC policy by merging DefaultWindows policy with the recommended block rules - -.PARAMETER BasePolicyType -Select the Base Policy Type - -.PARAMETER Deploy -Deploys the policy that is being created - -.PARAMETER IncludeSignTool -Indicates that the Default Windows policy that is being created must include Allow rules for SignTool.exe - This parameter must be used when you intend to Sign and Deploy the Default Windows policy. - -.PARAMETER SignToolPath -Path to the SignTool.exe file - Optional - -.PARAMETER TestMode -Indicates that the created/deployed policy will have Enabled:Boot Audit on Failure and Enabled:Advanced Boot Options Menu policy rule options - -.PARAMETER RequireEVSigners -Indicates that the created/deployed policy will have Require EV Signers policy rule option. - -.PARAMETER NoDeletedFiles -Indicates that files that were run during program installations but then were deleted and are no longer on the disk, won't be added to the supplemental policy. This can mean the programs you installed will be allowed to run but installation/reinstallation might not be allowed once the policies are deployed. - -.PARAMETER SpecificFileNameLevel -You can choose one of the following options: "OriginalFileName", "InternalName", "FileDescription", "ProductName", "PackageFamilyName", "FilePath". More info available on Microsoft Learn - -.PARAMETER NoUserPEs -By default, the module includes user PEs in the scan. When you use this switch parameter, they won't be included. - -.PARAMETER NoScript -Won't scan script files - -.PARAMETER Level -Offers the same official Levels for scanning of event logs. If no level is specified the default, which is set to FilePublisher in this module, will be used. - -.PARAMETER Fallbacks -Offers the same official Fallbacks for scanning of event logs. If no fallbacks are specified the default, which is set to Hash in this module, will be used. - -.PARAMETER LogSize -Specifies the log size for Microsoft-Windows-CodeIntegrity/Operational events. The values must be in the form of . e.g., 2MB, 10MB, 1GB, 1TB. The minimum accepted value is 1MB which is the default. - -.PARAMETER SkipVersionCheck -Can be used with any parameter to bypass the online version check - only to be used in rare cases - -#> -} - -# Importing argument completer ScriptBlocks -. "$psscriptroot\ArgumentCompleters.ps1" -# Set PSReadline tab completion to complete menu for easier access to available parameters - Only for the current session -Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete -Register-ArgumentCompleter -CommandName 'New-WDACConfig' -ParameterName 'SignToolPath' -ScriptBlock $ArgumentCompleterExeFilePathsPicker diff --git a/WDACConfig/Remove-CommonWDACConfig.psm1 b/WDACConfig/Remove-CommonWDACConfig.psm1 deleted file mode 100644 index d3158b34e..000000000 --- a/WDACConfig/Remove-CommonWDACConfig.psm1 +++ /dev/null @@ -1,168 +0,0 @@ -#Requires -RunAsAdministrator -function Remove-CommonWDACConfig { - [CmdletBinding()] - Param( - [parameter(Mandatory = $false)][switch]$CertCN, - [parameter(Mandatory = $false)][switch]$CertPath, - [parameter(Mandatory = $false)][switch]$SignToolPath, - [parameter(Mandatory = $false)][switch]$UnsignedPolicyPath, - [parameter(Mandatory = $false)][switch]$SignedPolicyPath, - [parameter(Mandatory = $false)][switch]$StrictKernelPolicyGUID, - [parameter(Mandatory = $false)][switch]$StrictKernelNoFlightRootsPolicyGUID, - [parameter(Mandatory = $false, DontShow = $true)][switch]$LastUpdateCheck # DontShow prevents common parameters from being displayed too - ) - begin { - # Importing resources such as functions by dot-sourcing so that they will run in the same scope and their variables will be usable - . "$psscriptroot\Resources.ps1" - - # Stop operation as soon as there is an error anywhere, unless explicitly specified otherwise - $ErrorActionPreference = 'Stop' - - # Fetch User account directory path - [string]$global:UserAccountDirectoryPath = (Get-CimInstance Win32_UserProfile -Filter "SID = '$([System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value)'").LocalPath - - # Create User configuration folder if it doesn't already exist - if (-NOT (Test-Path -Path "$global:UserAccountDirectoryPath\.WDACConfig\")) { - New-Item -ItemType Directory -Path "$global:UserAccountDirectoryPath\.WDACConfig\" -Force -ErrorAction Stop | Out-Null - Write-Debug -Message "The .WDACConfig folder in current user's folder has been created because it didn't exist." - } - - # Create User configuration file if it doesn't already exist - if (-NOT (Test-Path -Path "$global:UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json")) { - New-Item -ItemType File -Path "$global:UserAccountDirectoryPath\.WDACConfig\" -Name 'UserConfigurations.json' -Force -ErrorAction Stop | Out-Null - Write-Debug -Message "The UserConfigurations.json file in \.WDACConfig\ folder has been created because it didn't exist." - } - - # Delete the entire User Configs if a more specific parameter wasn't used - if ($PSBoundParameters.Count -eq 0) { - Remove-Item -Path "$global:UserAccountDirectoryPath\.WDACConfig\" -Recurse -Force - &$WritePink 'User Configurations for WDACConfig module have been deleted.' - break - } - - # Read the current user configurations - $CurrentUserConfigurations = Get-Content -Path "$global:UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" - # If the file exists but is corrupted and has bad values, rewrite it - try { - $CurrentUserConfigurations = $CurrentUserConfigurations | ConvertFrom-Json - } - catch { - Set-Content -Path "$global:UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" -Value '' - } - - # An object to hold the User configurations - $UserConfigurationsObject = [PSCustomObject]@{ - SignedPolicyPath = '' - UnsignedPolicyPath = '' - SignToolCustomPath = '' - CertificateCommonName = '' - CertificatePath = '' - StrictKernelPolicyGUID = '' - StrictKernelNoFlightRootsPolicyGUID = '' - LastUpdateCheck = '' - } - } - process { - if ($SignedPolicyPath) { - $UserConfigurationsObject.SignedPolicyPath = '' - } - else { - $UserConfigurationsObject.SignedPolicyPath = $CurrentUserConfigurations.SignedPolicyPath - } - - if ($UnsignedPolicyPath) { - $UserConfigurationsObject.UnsignedPolicyPath = '' - } - else { - $UserConfigurationsObject.UnsignedPolicyPath = $CurrentUserConfigurations.UnsignedPolicyPath - } - - if ($SignToolPath) { - $UserConfigurationsObject.SignToolCustomPath = '' - } - else { - $UserConfigurationsObject.SignToolCustomPath = $CurrentUserConfigurations.SignToolCustomPath - } - - if ($CertPath) { - $UserConfigurationsObject.CertificatePath = '' - } - else { - $UserConfigurationsObject.CertificatePath = $CurrentUserConfigurations.CertificatePath - } - - if ($CertCN) { - $UserConfigurationsObject.CertificateCommonName = '' - } - else { - $UserConfigurationsObject.CertificateCommonName = $CurrentUserConfigurations.CertificateCommonName - } - - if ($StrictKernelPolicyGUID) { - $UserConfigurationsObject.StrictKernelPolicyGUID = '' - } - else { - $UserConfigurationsObject.StrictKernelPolicyGUID = $CurrentUserConfigurations.StrictKernelPolicyGUID - } - - if ($StrictKernelNoFlightRootsPolicyGUID) { - $UserConfigurationsObject.StrictKernelNoFlightRootsPolicyGUID = '' - } - else { - $UserConfigurationsObject.StrictKernelNoFlightRootsPolicyGUID = $CurrentUserConfigurations.StrictKernelNoFlightRootsPolicyGUID - } - - if ($LastUpdateCheck) { - $UserConfigurationsObject.LastUpdateCheck = '' - } - else { - $UserConfigurationsObject.LastUpdateCheck = $CurrentUserConfigurations.LastUpdateCheck - } - } - end { - # Update the User Configurations file - $UserConfigurationsObject | ConvertTo-Json | Set-Content "$global:UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" - &$WritePink "`nThis is your new WDAC User Configurations: " - Get-Content -Path "$global:UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" | ConvertFrom-Json | Format-List * - } -} -<# -.SYNOPSIS -Removes common values for parameters used by WDACConfig module - -.LINK -https://github.com/HotCakeX/Harden-Windows-Security/wiki/Remove-CommonWDACConfig - -.DESCRIPTION -Removes common values for parameters used by WDACConfig module from the User Configurations JSON file. If you don't use it with any parameters, then all User Configs will be deleted. - -.COMPONENT -Windows Defender Application Control, ConfigCI PowerShell module, WDACConfig module - -.FUNCTIONALITY -Removes common values for parameters used by WDACConfig module from the User Configurations JSON file. If you don't use it with any parameters, then all User Configs will be deleted. - -.PARAMETER SignedPolicyPath -Removes the SignedPolicyPath from User Configs - -.PARAMETER UnsignedPolicyPath -Removes the UnsignedPolicyPath from User Configs - -.PARAMETER CertCN -Removes the CertCN from User Configs - -.PARAMETER SignToolPath -Removes the SignToolPath from User Configs - -.PARAMETER CertPath -Removes the CertPath from User Configs - -.PARAMETER StrictKernelPolicyGUID -Removes the StrictKernelPolicyGUID from User Configs - -.PARAMETER StrictKernelNoFlightRootsPolicyGUID -Removes the StrictKernelNoFlightRootsPolicyGUID from User Configs - -#> -# Set PSReadline tab completion to complete menu for easier access to available parameters - Only for the current session -Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete diff --git a/WDACConfig/Remove-WDACConfig.psm1 b/WDACConfig/Remove-WDACConfig.psm1 deleted file mode 100644 index 75393573d..000000000 --- a/WDACConfig/Remove-WDACConfig.psm1 +++ /dev/null @@ -1,357 +0,0 @@ -#Requires -RunAsAdministrator -function Remove-WDACConfig { - [CmdletBinding( - DefaultParameterSetName = 'Signed Base', - SupportsShouldProcess = $true, - PositionalBinding = $false, - ConfirmImpact = 'High' - )] - Param( - [Alias('S')] - [Parameter(Mandatory = $false, ParameterSetName = 'Signed Base')][Switch]$SignedBase, - [Alias('U')] - [Parameter(Mandatory = $false, ParameterSetName = 'Unsigned Or Supplemental')][Switch]$UnsignedOrSupplemental, - - [ValidatePattern('\.xml$')] - [ValidateScript({ - # Validate each Policy file in PolicyPaths parameter to make sure the user isn't accidentally trying to remove an Unsigned policy - $_ | ForEach-Object { - $xmlTest = [xml](Get-Content $_) - $RedFlag1 = $xmlTest.SiPolicy.SupplementalPolicySigners.SupplementalPolicySigner.SignerId - $RedFlag2 = $xmlTest.SiPolicy.UpdatePolicySigners.UpdatePolicySigner.SignerId - if ($RedFlag1 -or $RedFlag2) { return $True } - } - }, ErrorMessage = 'The policy XML file(s) you chose are Unsigned policies. Please use Remove-WDACConfig cmdlet with -UnsignedOrSupplemental parameter instead.')] - [parameter(Mandatory = $true, ParameterSetName = 'Signed Base', ValueFromPipelineByPropertyName = $true)] - [System.String[]]$PolicyPaths, - - [ValidateScript({ - $certs = foreach ($cert in (Get-ChildItem 'Cert:\CurrentUser\my')) { - (($cert.Subject -split ',' | Select-Object -First 1) -replace 'CN=', '').Trim() - } - $certs -contains $_ - }, ErrorMessage = "A certificate with the provided common name doesn't exist in the personal store of the user certificates." )] - [parameter(Mandatory = $false, ParameterSetName = 'Signed Base', ValueFromPipelineByPropertyName = $true)] - [System.String]$CertCN, - - # https://stackoverflow.com/questions/76143006/how-to-prevent-powershell-validateset-argument-completer-from-suggesting-the-sam/76143269 - # https://stackoverflow.com/questions/76267235/powershell-how-to-cross-reference-parameters-between-2-argument-completers - [ArgumentCompleter({ - # Define the parameters that this script block will accept. - param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) - - # Get a list of policies using the CiTool, excluding system policies and policies that aren't on disk. - # by adding "| Where-Object { $_.FriendlyName }" we make sure the auto completion works when at least one of the policies doesn't have a friendly name - $policies = (CiTool -lp -json | ConvertFrom-Json).Policies | Where-Object { $_.IsOnDisk -eq 'True' } | Where-Object { $_.IsSystemPolicy -ne 'True' } | Where-Object { $_.FriendlyName } - - # Create a hashtable mapping policy names to policy IDs. This will be used later to check if a policy ID already exists. - $NameIDMap = @{} - foreach ($policy in $policies) { - $NameIDMap[$policy.Friendlyname] = $policy.policyID - } - - # Get the IDs of existing policies that are already being used in the current command. - $existingIDs = $fakeBoundParameters['PolicyIDs'] - - # Get the policy names that are currently being used in the command. This is done by looking at the abstract syntax tree (AST) - # of the command and finding all string literals, which are assumed to be policy names. - $existing = $commandAst.FindAll({ - $args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] - }, $false).Value - - # Filter out the policy names that are already being used or whose corresponding policy IDs are already being used. - # The resulting list of policy names is what will be shown as autocomplete suggestions. - $candidates = $policies.Friendlyname | Where-Object { $_ -notin $existing -and $NameIDMap[$_] -notin $existingIDs } - - # Additionally, if the policy name contains spaces, it's enclosed in single quotes to ensure it's treated as a single argument. - # This is achieved using the Compare-Object cmdlet to compare the existing and candidate values, and outputting the resulting matches. - # For each resulting match, it checks if the match contains a space, if so, it's enclosed in single quotes, if not, it's returned as is. - (Compare-Object -PassThru $candidates $existing | Where-Object SideIndicator -EQ '<='). - ForEach({ if ($_ -match ' ') { "'{0}'" -f $_ } else { $_ } }) - })] - [ValidateScript({ - if ($_ -notin [PolicyNamezx]::new().GetValidValues()) { throw "Invalid policy name: $_" } - $true - })] - [Parameter(Mandatory = $false, ParameterSetName = 'Unsigned Or Supplemental')] - [System.String[]]$PolicyNames, - - # https://stackoverflow.com/questions/76143006/how-to-prevent-powershell-validateset-argument-completer-from-suggesting-the-sam/76143269 - # https://stackoverflow.com/questions/76267235/powershell-how-to-cross-reference-parameters-between-2-argument-completers - [ArgumentCompleter({ - param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) - - # Get a list of policies using the CiTool, excluding system policies and policies that aren't on disk. - $policies = (CiTool -lp -json | ConvertFrom-Json).Policies | Where-Object { $_.IsOnDisk -eq 'True' } | Where-Object { $_.IsSystemPolicy -ne 'True' } - # Create a hashtable mapping policy IDs to policy names. This will be used later to check if a policy name already exists. - $IDNameMap = @{} - foreach ($policy in $policies) { - $IDNameMap[$policy.policyID] = $policy.Friendlyname - } - # Get the names of existing policies that are already being used in the current command. - $existingNames = $fakeBoundParameters['PolicyNames'] - # Get the policy IDs that are currently being used in the command. This is done by looking at the abstract syntax tree (AST) - # of the command and finding all string literals, which are assumed to be policy IDs. - $existing = $commandAst.FindAll({ - $args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] - }, $false).Value - # Filter out the policy IDs that are already being used or whose corresponding policy names are already being used. - # The resulting list of policy IDs is what will be shown as autocomplete suggestions. - $candidates = $policies.policyID | Where-Object { $_ -notin $existing -and $IDNameMap[$_] -notin $existingNames } - # Return the candidates. - return $candidates - })] - [ValidateScript({ - if ($_ -notin [PolicyIDzx]::new().GetValidValues()) { throw "Invalid policy ID: $_" } - $true - })] - [Parameter(Mandatory = $false, ParameterSetName = 'Unsigned Or Supplemental')] - [System.String[]]$PolicyIDs, - - [parameter(Mandatory = $false, ParameterSetName = 'Signed Base', ValueFromPipelineByPropertyName = $true)] - [System.String]$SignToolPath, - - [Parameter(Mandatory = $false)][Switch]$SkipVersionCheck - ) - - begin { - # Importing resources such as functions by dot-sourcing so that they will run in the same scope and their variables will be usable - . "$psscriptroot\Resources.ps1" - - # Stop operation as soon as there is an error anywhere, unless explicitly specified otherwise - $ErrorActionPreference = 'Stop' - if (-NOT $SkipVersionCheck) { . Update-self } - # Detecting if Debug switch is used, will do debugging actions based on that - $Debug = $PSBoundParameters.Debug.IsPresent - - # Fetch User account directory path - [string]$global:UserAccountDirectoryPath = (Get-CimInstance Win32_UserProfile -Filter "SID = '$([System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value)'").LocalPath - - #region User-Configurations-Processing-Validation - if ($PSCmdlet.ParameterSetName -eq 'Signed Base') { - # If any of these parameters, that are mandatory for all of the position 0 parameters, isn't supplied by user - if (!$SignToolPath -or !$CertCN) { - # Read User configuration file if it exists - $UserConfig = Get-Content -Path "$global:UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" -ErrorAction SilentlyContinue - if ($UserConfig) { - # Validate the Json file and read its content to make sure it's not corrupted - try { $UserConfig = $UserConfig | ConvertFrom-Json } - catch { - Write-Error 'User Configuration Json file is corrupted, deleting it...' -ErrorAction Continue - # Calling this function with this parameter automatically does its job and breaks/stops the operation - Set-CommonWDACConfig -DeleteUserConfig - } - } - } - - # Get SignToolPath from user parameter or user config file or auto-detect it - if ($SignToolPath) { - $SignToolPathFinal = Get-SignTool -SignToolExePath $SignToolPath - } # If it is null, then Get-SignTool will behave the same as if it was called without any arguments. - else { - $SignToolPathFinal = Get-SignTool -SignToolExePath ($UserConfig.SignToolCustomPath ?? $null) - } - - # If CertCN was not provided by user - if (!$CertCN) { - if ($UserConfig.CertificateCommonName) { - # Check if the value in the User configuration file exists and is valid - if (Confirm-CertCN $($UserConfig.CertificateCommonName)) { - # if it's valid then use it - $CertCN = $UserConfig.CertificateCommonName - } - else { - throw 'The currently saved value for CertCN in user configurations is invalid.' - } - } - else { - throw "CertCN parameter can't be empty and no valid configuration was found for it." - } - } - } - #endregion User-Configurations-Processing-Validation - - # ValidateSet for Policy names - Class PolicyNamezx : System.Management.Automation.IValidateSetValuesGenerator { - [System.String[]] GetValidValues() { - $PolicyNamezx = ((CiTool -lp -json | ConvertFrom-Json).Policies | Where-Object { $_.IsOnDisk -eq 'True' } | Where-Object { $_.IsSystemPolicy -ne 'True' }).Friendlyname | Select-Object -Unique - return [System.String[]]$PolicyNamezx - } - } - - # ValidateSet for Policy IDs - Class PolicyIDzx : System.Management.Automation.IValidateSetValuesGenerator { - [System.String[]] GetValidValues() { - $PolicyIDzx = ((CiTool -lp -json | ConvertFrom-Json).Policies | Where-Object { $_.IsOnDisk -eq 'True' } | Where-Object { $_.IsSystemPolicy -ne 'True' }).policyID - - return [System.String[]]$PolicyIDzx - } - } - - - # argument tab auto-completion and ValidateSet for Policy names - # Defines the PolicyNamez class that implements the IValidateSetValuesGenerator interface. This class is responsible for generating a list of valid values for the policy names. - Class PolicyNamez : System.Management.Automation.IValidateSetValuesGenerator { - # Creates a static hashtable to store a mapping of policy IDs to their respective friendly names. - static [Hashtable] $IDNameMap = @{} - - # Defines a method to get valid policy names from the policies on disk that aren't system policies. - [System.String[]] GetValidValues() { - $policies = (CiTool -lp -json | ConvertFrom-Json).Policies | Where-Object { $_.IsOnDisk -eq 'True' } | Where-Object { $_.IsSystemPolicy -ne 'True' } - self::$IDNameMap = @{} - foreach ($policy in $policies) { - self::$IDNameMap[$policy.policyID] = $policy.Friendlyname - } - # Returns an array of unique policy names. - return [System.String[]]($policies.Friendlyname | Select-Object -Unique) - } - - # Defines a static method to get a policy name by its ID. This method will be used to check if a policy ID is already in use. - static [System.String] GetPolicyNameByID($ID) { - return self::$IDNameMap[$ID] - } - } - - # Defines the PolicyIDz class that also implements the IValidateSetValuesGenerator interface. This class is responsible for generating a list of valid values for the policy IDs. - Class PolicyIDz : System.Management.Automation.IValidateSetValuesGenerator { - # Creates a static hashtable to store a mapping of policy friendly names to their respective IDs. - static [Hashtable] $NameIDMap = @{} - - # Defines a method to get valid policy IDs from the policies on disk that aren't system policies. - [System.String[]] GetValidValues() { - $policies = (CiTool -lp -json | ConvertFrom-Json).Policies | Where-Object { $_.IsOnDisk -eq 'True' } | Where-Object { $_.IsSystemPolicy -ne 'True' } - self::$NameIDMap = @{} - foreach ($policy in $policies) { - self::$NameIDMap[$policy.Friendlyname] = $policy.policyID - } - # Returns an array of unique policy IDs. - return [System.String[]]($policies.policyID | Select-Object -Unique) - } - - # Defines a static method to get a policy ID by its name. This method will be used to check if a policy name is already in use. - static [System.String] GetPolicyIDByName($Name) { - return self::$NameIDMap[$Name] - } - } - } - - process { - - if ($SignedBase) { - foreach ($PolicyPath in $PolicyPaths) { - $xml = [xml](Get-Content $PolicyPath) - [System.String]$PolicyID = $xml.SiPolicy.PolicyID - # Prevent users from accidentally attempting to remove policies that aren't even deployed on the system - $CurrentPolicyIDs = ((CiTool -lp -json | ConvertFrom-Json).Policies | Where-Object { $_.IsSystemPolicy -ne 'True' }).policyID | ForEach-Object { "{$_}" } - Write-Debug -Message "The policy ID of the currently processing xml file is $PolicyID" - if ($CurrentPolicyIDs -notcontains $PolicyID) { - Write-Error -Message "The selected policy file isn't deployed on the system." -ErrorAction Stop - } - - ######################## Sanitize the policy file by removing SupplementalPolicySigners ######################## - $SuppSingerIDs = $xml.SiPolicy.SupplementalPolicySigners.SupplementalPolicySigner.SignerId - $PolicyName = ($xml.SiPolicy.Settings.Setting | Where-Object { $_.provider -eq 'PolicyInfo' -and $_.valuename -eq 'Name' -and $_.key -eq 'Information' }).value.string - if ($SuppSingerIDs) { - Write-Debug -Message "`n$($SuppSingerIDs.count) SupplementalPolicySigners have been found in $PolicyName policy, removing them now..." - $SuppSingerIDs | ForEach-Object { - $PolContent = Get-Content -Raw -Path $PolicyPath - $PolContent -match "" | Out-Null - $PolContent = $PolContent -replace $Matches[0], '' - Set-Content -Value $PolContent -Path $PolicyPath - } - $PolContent -match '[\S\s]*' | Out-Null - $PolContent = $PolContent -replace $Matches[0], '' - Set-Content -Value $PolContent -Path $PolicyPath - - # remove empty lines from the entire policy file - (Get-Content -Path $PolicyPath) | Where-Object { $_.trim() -ne '' } | Set-Content -Path $PolicyPath -Force - Write-Debug -Message 'Policy successfully sanitized and all SupplementalPolicySigners have been removed.' - } - else { - Write-Debug -Message "`nNo sanitization required because no SupplementalPolicySigners have been found in $PolicyName policy." - } - - Set-RuleOption -FilePath $PolicyPath -Option 6 - ConvertFrom-CIPolicy $PolicyPath "$PolicyID.cip" | Out-Null - - # Configure the parameter splat - $ProcessParams = @{ - 'ArgumentList' = 'sign', '/v' , '/n', "`"$CertCN`"", '/p7', '.', '/p7co', '1.3.6.1.4.1.311.79.1', '/fd', 'certHash', ".\$PolicyID.cip" - 'FilePath' = $SignToolPathFinal - 'NoNewWindow' = $true - 'Wait' = $true - 'ErrorAction' = 'Stop' - } - if (!$Debug) { $ProcessParams['RedirectStandardOutput'] = 'NUL' } - # Sign the files with the specified cert - Start-Process @ProcessParams - - Remove-Item ".\$PolicyID.cip" -Force - Rename-Item "$PolicyID.cip.p7" -NewName "$PolicyID.cip" -Force - CiTool --update-policy ".\$PolicyID.cip" -json | Out-Null - Write-Host "`nPolicy with the following details has been Re-signed and Re-deployed in Unsigned mode.`nPlease restart your system." -ForegroundColor Green - Write-Output "PolicyName = $PolicyName" - Write-Output "PolicyGUID = $PolicyID`n" - } - } - - if ($UnsignedOrSupplemental) { - - # If IDs were supplied by user - foreach ($ID in $PolicyIDs ) { - citool --remove-policy "{$ID}" -json | Out-Null - Write-Host "Policy with the ID $ID has been successfully removed." -ForegroundColor Green - } - - # If names were supplied by user - # Empty array to store Policy IDs based on the input name, this will take care of the situations where multiple policies with the same name are deployed - [System.Object[]]$NameID = @() - foreach ($PolicyName in $PolicyNames) { - $NameID += ((CiTool -lp -json | ConvertFrom-Json).Policies | Where-Object { $_.IsOnDisk -eq 'True' } | Where-Object { $_.FriendlyName -eq $PolicyName }).PolicyID - } - - Write-Debug -Message 'The Following policy IDs have been gathered from the supplied policy names and are going to be removed from the system' - if ($Debug) { $NameID | Select-Object -Unique | ForEach-Object { Write-Debug -Message "$_" } } - - $NameID | Select-Object -Unique | ForEach-Object { - citool --remove-policy "{$_}" -json | Out-Null - Write-Host "Policy with the ID $_ has been successfully removed." -ForegroundColor Green - } - } - } - - <# -.SYNOPSIS -Removes Signed and unsigned deployed WDAC policies (Windows Defender Application Control) - -.LINK -https://github.com/HotCakeX/Harden-Windows-Security/wiki/Remove-WDACConfig - -.DESCRIPTION -Using official Microsoft methods, Removes Signed and unsigned deployed WDAC policies (Windows Defender Application Control) - -.COMPONENT -Windows Defender Application Control, ConfigCI PowerShell module - -.FUNCTIONALITY -Using official Microsoft methods, Removes Signed and unsigned deployed WDAC policies (Windows Defender Application Control) - -.PARAMETER SignedBase -Remove Signed Base WDAC Policies - -.PARAMETER UnsignedOrSupplemental -Remove Unsigned deployed WDAC policies as well as Signed deployed Supplemental WDAC policies - -.PARAMETER SkipVersionCheck -Can be used with any parameter to bypass the online version check - only to be used in rare cases - -#> -} - -# Importing argument completer ScriptBlocks -. "$psscriptroot\ArgumentCompleters.ps1" -# Set PSReadline tab completion to complete menu for easier access to available parameters - Only for the current session -Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete -Register-ArgumentCompleter -CommandName 'Remove-WDACConfig' -ParameterName 'CertCN' -ScriptBlock $ArgumentCompleterCertificateCN -Register-ArgumentCompleter -CommandName 'Remove-WDACConfig' -ParameterName 'PolicyPaths' -ScriptBlock $ArgumentCompleterPolicyPathsBasePoliciesOnly -Register-ArgumentCompleter -CommandName 'Remove-WDACConfig' -ParameterName 'SignToolPath' -ScriptBlock $ArgumentCompleterExeFilePathsPicker diff --git a/WDACConfig/Resources.ps1 b/WDACConfig/Resources.ps1 deleted file mode 100644 index e7445e8a5..000000000 --- a/WDACConfig/Resources.ps1 +++ /dev/null @@ -1,478 +0,0 @@ -# Stop operation as soon as there is an error anywhere, unless explicitly specified otherwise -$ErrorActionPreference = 'Stop' -if (-NOT ([System.Environment]::OSVersion.Version -ge '10.0.22621')) { Write-Error -Message "You're not using Windows 11 22H2, exiting..." } - -# Get the path to SignTool -function Get-SignTool { - param( - [parameter(Mandatory = $false)][System.String]$SignToolExePath - ) - # If Sign tool path wasn't provided by parameter, try to detect it automatically, if fails, stop the operation - if (!$SignToolExePath) { - if ($Env:PROCESSOR_ARCHITECTURE -eq 'AMD64') { - if ( Test-Path -Path 'C:\Program Files (x86)\Windows Kits\*\bin\*\x64\signtool.exe') { - $SignToolExePath = 'C:\Program Files (x86)\Windows Kits\*\bin\*\x64\signtool.exe' - } - else { - Write-Error -Message "signtool.exe couldn't be found" - } - } - elseif ($Env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { - if (Test-Path -Path 'C:\Program Files (x86)\Windows Kits\*\bin\*\arm64\signtool.exe') { - $SignToolExePath = 'C:\Program Files (x86)\Windows Kits\*\bin\*\arm64\signtool.exe' - } - else { - Write-Error -Message "signtool.exe couldn't be found" - } - } - } - try { - # Validate the SignTool executable - [System.Version]$WindowsSdkVersion = '10.0.22621.755' # Setting the minimum version of SignTool that is allowed to be executed - [System.Boolean]$GreenFlag1 = (((Get-Item -Path $SignToolExePath).VersionInfo).ProductVersionRaw -ge $WindowsSdkVersion) - [System.Boolean]$GreenFlag2 = (((Get-Item -Path $SignToolExePath).VersionInfo).FileVersionRaw -ge $WindowsSdkVersion) - [System.Boolean]$GreenFlag3 = ((Get-Item -Path $SignToolExePath).VersionInfo).CompanyName -eq 'Microsoft Corporation' - [System.Boolean]$GreenFlag4 = ((Get-AuthenticodeSignature -FilePath $SignToolExePath).Status -eq 'Valid') - [System.Boolean]$GreenFlag5 = ((Get-AuthenticodeSignature -FilePath $SignToolExePath).StatusMessage -eq 'Signature verified.') - } - catch { - Write-Error "SignTool executable couldn't be verified." - } - # If any of the 5 checks above fails, the operation stops - if (!$GreenFlag1 -or !$GreenFlag2 -or !$GreenFlag3 -or !$GreenFlag4 -or !$GreenFlag5) { - Write-Error -Message "The SignTool executable was found but couldn't be verified. Please download the latest Windows SDK to get the newest SignTool executable. Official download link: http://aka.ms/WinSDK" - } - else { - return $SignToolExePath - } -} - - -# Make sure the latest version of the module is installed and if not, automatically update it, clean up any old versions -function Update-self { - - try { - # Get the last update check time - [Datetime]$UserConfigDate = Get-CommonWDACConfig -LastUpdateCheck - } - catch { - # If the User Config file doesn't exist then set this flag to perform online update check - [bool]$PerformOnlineUpdateCheck = $true - } - - # Ensure these are run only if the User Config file exists and contains a date for last update check - if (!$PerformOnlineUpdateCheck) { - # Get the current time - [Datetime]$CurrentDateTime = Get-Date - # Calculate the minutes elapsed since the last online update check - [int]$TimeDiff = ($CurrentDateTime - $UserConfigDate).TotalMinutes - } - - # Only check for updates if the last attempt occured more than 10 minutes ago or the User Config file for last update check doesn't exist - # This prevents the module from constantly doing an update check by fetching the version file from GitHub - if (($TimeDiff -gt 10) -or $PerformOnlineUpdateCheck) { - - $CurrentVersion = (Test-ModuleManifest "$psscriptroot\WDACConfig.psd1").Version.ToString() - try { - # First try the GitHub source - $LatestVersion = Invoke-RestMethod -Uri 'https://raw.githubusercontent.com/HotCakeX/Harden-Windows-Security/main/WDACConfig/version.txt' - } - catch { - try { - # If GitHub source is unavailable, use the Azure DevOps source - $LatestVersion = Invoke-RestMethod -Uri 'https://dev.azure.com/SpyNetGirl/011c178a-7b92-462b-bd23-2c014528a67e/_apis/git/repositories/5304fef0-07c0-4821-a613-79c01fb75657/items?path=/WDACConfig/version.txt' - } - catch { - Write-Error -Message "Couldn't verify if the latest version of the module is installed, please check your Internet connection. You can optionally bypass the online check by using -SkipVersionCheck parameter." - } - } - if ($CurrentVersion -lt $LatestVersion) { - &$WritePink "The currently installed module's version is $CurrentVersion while the latest version is $LatestVersion - Auto Updating the module... 💓" - Remove-Module -Name 'WDACConfig' -Force - # Do this if the module was installed properly using Install-module cmdlet - try { - Uninstall-Module -Name 'WDACConfig' -AllVersions -Force -ErrorAction Stop - Install-Module -Name 'WDACConfig' -RequiredVersion $LatestVersion -Force - Import-Module -Name 'WDACConfig' -RequiredVersion $LatestVersion -Force -Global - } - # Do this if module files/folder was just copied to Documents folder and not properly installed - Should rarely happen - catch { - Install-Module -Name 'WDACConfig' -RequiredVersion $LatestVersion -Force - Import-Module -Name 'WDACConfig' -RequiredVersion $LatestVersion -Force -Global - } - # Make sure the old version isn't run after update - Write-Output "$($PSStyle.Foreground.FromRGB(152,255,152))Update successful, please run the cmdlet again.$($PSStyle.Reset)" - break - return - } - - # Reset the last update timer to the current time - Set-CommonWDACConfig -LastUpdateCheck $(Get-Date ) | Out-Null - } -} - - -# Increase Code Integrity Operational Event Logs size from the default 1MB to user defined size -function Set-LogSize { - [CmdletBinding()] - param ([System.Int64]$LogSize) - $logName = 'Microsoft-Windows-CodeIntegrity/Operational' - $log = New-Object System.Diagnostics.Eventing.Reader.EventLogConfiguration $logName - $log.MaximumSizeInBytes = $LogSize - $log.IsEnabled = $true - $log.SaveChanges() -} - - -# function that takes 2 arrays, one contains file paths and the other contains folder paths. It checks them and shows file paths -# that are not in any of the folder paths. Performs this check recursively too so works if the filepath is in a sub-directory of a folder path -function Test-FilePath { - param ( - [Parameter(Mandatory = $true)] - [System.String[]]$FilePath, - [Parameter(Mandatory = $true)] - [System.String[]]$DirectoryPath - ) - - # Loop through each file path - foreach ($file in $FilePath) { - # Check if the file path is valid - if (Test-Path $file -PathType 'Leaf') { - # Get the full path of the file - $FileFullPath = Resolve-Path $file - - # Initialize a variable to store the result - $Result = $false - - # Loop through each directory path - foreach ($directory in $DirectoryPath) { - # Check if the directory path is valid - if (Test-Path $directory -PathType 'Container') { - # Get the full path of the directory - $DirectoryFullPath = Resolve-Path $directory - - # Check if the file path starts with the directory path - if ($FileFullPath -like "$DirectoryFullPath\*") { - # The file is inside the directory or its sub-directories - $Result = $true - break # Exit the inner loop - } - } - else { - # The directory path is not valid - Write-Warning "The directory path '$directory' is not valid." - } - } - - # Output the file path if it is not inside any of the directory paths - if (-not $Result) { - Write-Output $FileFullPath - } - } - else { - # The file path is not valid - Write-Warning "The file path '$file' is not valid." - } - } -} - - -# Script block that lists every \Device\Harddiskvolume - https://superuser.com/questions/1058217/list-every-device-harddiskvolume -# These are DriveLetter mappings -[scriptblock]$DriveLettersGlobalRootFixScriptBlock = { - $signature = @' -[DllImport("kernel32.dll", SetLastError=true)] -[return: MarshalAs(UnmanagedType.Bool)] -public static extern bool GetVolumePathNamesForVolumeNameW([MarshalAs(UnmanagedType.LPWStr)] string lpszVolumeName, -[MarshalAs(UnmanagedType.LPWStr)] [Out] StringBuilder lpszVolumeNamePaths, uint cchBuferLength, -ref UInt32 lpcchReturnLength); - -[DllImport("kernel32.dll", SetLastError = true)] -public static extern IntPtr FindFirstVolume([Out] StringBuilder lpszVolumeName, -uint cchBufferLength); - -[DllImport("kernel32.dll", SetLastError = true)] -public static extern bool FindNextVolume(IntPtr hFindVolume, [Out] StringBuilder lpszVolumeName, uint cchBufferLength); - -[DllImport("kernel32.dll", SetLastError = true)] -public static extern uint QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax); - -'@ - Add-Type -ErrorAction SilentlyContinue -MemberDefinition $signature -Name Win32Utils -Namespace PInvoke -Using PInvoke, System.Text - - [UInt32] $lpcchReturnLength = 0 - [UInt32] $Max = 65535 - $sbVolumeName = New-Object System.Text.StringBuilder($Max, $Max) - $sbPathName = New-Object System.Text.StringBuilder($Max, $Max) - $sbMountPoint = New-Object System.Text.StringBuilder($Max, $Max) - [IntPtr] $volumeHandle = [PInvoke.Win32Utils]::FindFirstVolume($sbVolumeName, $Max) - do { - $volume = $sbVolumeName.toString() - $unused = [PInvoke.Win32Utils]::GetVolumePathNamesForVolumeNameW($volume, $sbMountPoint, $Max, [Ref] $lpcchReturnLength) - $ReturnLength = [PInvoke.Win32Utils]::QueryDosDevice($volume.Substring(4, $volume.Length - 1 - 4), $sbPathName, [UInt32] $Max) - if ($ReturnLength) { - $DriveMapping = @{ - DriveLetter = $sbMountPoint.toString() - VolumeName = $volume - DevicePath = $sbPathName.ToString() - } - Write-Output (New-Object PSObject -Property $DriveMapping) - } - else { - Write-Output 'No mountpoint found for: ' + $volume - } - } while ([PInvoke.Win32Utils]::FindNextVolume([IntPtr] $volumeHandle, $sbVolumeName, $Max)) -} - - -### Function to separately capture FileHashes of deleted files and FilePaths of available files from Event Viewer Audit Logs #### -Function Get-AuditEventLogsProcessing { - param ($Date) - - $DriveLettersGlobalRootFix = Invoke-Command -ScriptBlock $DriveLettersGlobalRootFixScriptBlock - - # Defining a custom object to store and finally return it as results - $AuditEventLogsProcessingResults = [PSCustomObject]@{ - # Defining object properties as arrays - AvailableFilesPaths = @() - DeletedFileHashes = @() - } - - # Event Viewer Code Integrity logs scan - foreach ($event in Get-WinEvent -FilterHashtable @{LogName = 'Microsoft-Windows-CodeIntegrity/Operational'; ID = 3076 } -ErrorAction SilentlyContinue | Where-Object { $_.TimeCreated -ge $Date } ) { - $xml = [xml]$event.toxml() - $xml.event.eventdata.data | - ForEach-Object { $hash = @{} } { $hash[$_.name] = $_.'#text' } { [pscustomobject]$hash } | - ForEach-Object { - if ($_.'File Name' -match ($pattern = '\\Device\\HarddiskVolume(\d+)\\(.*)$')) { - $hardDiskVolumeNumber = $Matches[1] - $remainingPath = $Matches[2] - $getletter = $DriveLettersGlobalRootFix | Where-Object { $_.devicepath -eq "\Device\HarddiskVolume$hardDiskVolumeNumber" } - $usablePath = "$($getletter.DriveLetter)$remainingPath" - $_.'File Name' = $_.'File Name' -replace $pattern, $usablePath - } # Check if file is currently on the disk - if (Test-Path $_.'File Name') { - $AuditEventLogsProcessingResults.AvailableFilesPaths += $_.'File Name' - } # If file is not currently on the disk, extract its hashes from event log - elseif (-NOT (Test-Path $_.'File Name')) { - $AuditEventLogsProcessingResults.DeletedFileHashes += $_ | Select-Object FileVersion, 'File Name', PolicyGUID, 'SHA256 Hash', 'SHA256 Flat Hash', 'SHA1 Hash', 'SHA1 Flat Hash' - } - } - } - # return the results as an object - return $AuditEventLogsProcessingResults -} - - -# Creates a policy file and requires 2 parameters to supply the file rules and rule references -function New-EmptyPolicy { - param ( - $RulesContent, - $RuleRefsContent - ) - $EmptyPolicy = @" - - -10.0.0.0 -{2E07F7E4-194C-4D20-B7C9-6F44A6C5A234} - - - - - - - - - - - - - - - - - - -$RulesContent - - - - - - - - - - - -$RuleRefsContent - - - - - - -0 -{B163125F-E30A-43FC-ABEC-E30B4EE88FA8} -{B163125F-E30A-43FC-ABEC-E30B4EE88FA8} - -"@ - return $EmptyPolicy -} - - -# Gets the latest Microsoft Recommended block rules, removes its allow all rules and sets HVCI to strict -[scriptblock]$GetBlockRulesSCRIPTBLOCK = { - $Rules = (Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/MicrosoftDocs/windows-itpro-docs/public/windows/security/application-security/application-control/windows-defender-application-control/design/applications-that-can-bypass-wdac.md').Content -replace "(?s).*``````xml(.*)``````.*", '$1' -replace '|', '' - $Rules | Out-File '.\Microsoft recommended block rules TEMP.xml' - # Removing empty lines from policy file - Get-Content '.\Microsoft recommended block rules TEMP.xml' | Where-Object { $_.trim() -ne '' } | Out-File '.\Microsoft recommended block rules.xml' - Remove-Item '.\Microsoft recommended block rules TEMP.xml' -Force - Set-RuleOption -FilePath '.\Microsoft recommended block rules.xml' -Option 3 -Delete - Set-HVCIOptions -Strict -FilePath '.\Microsoft recommended block rules.xml' - [PSCustomObject]@{ - PolicyFile = 'Microsoft recommended block rules.xml' - } -} - - -# Function to check Certificate Common name - used mostly to validate values in UserConfigurations.json -function Confirm-CertCN ([string]$CN) { - $certs = foreach ($cert in (Get-ChildItem 'Cert:\CurrentUser\my')) { - (($cert.Subject -split ',' | Select-Object -First 1) -replace 'CN=', '').Trim() - } - $certs -contains $CN ? $true : $false -} - - -# script blocks for custom color writing -[scriptblock]$WriteHotPink = { Write-Output "$($PSStyle.Foreground.FromRGB(255,105,180))$($args[0])$($PSStyle.Reset)" } -[scriptblock]$WritePink = { Write-Output "$($PSStyle.Foreground.FromRGB(255,0,230))$($args[0])$($PSStyle.Reset)" } -[scriptblock]$WriteLavender = { Write-Output "$($PSStyle.Foreground.FromRgb(255,179,255))$($args[0])$($PSStyle.Reset)" } -[scriptblock]$WriteTeaGreen = { Write-Output "$($PSStyle.Foreground.FromRgb(133, 222, 119))$($args[0])$($PSStyle.Reset)" } - -# Create File Rules based on hash of the files no longer available on the disk and store them in the $Rules variable -function Get-FileRules { - param ($HashesArray) - $HashesArray | ForEach-Object -Begin { $i = 1 } -Process { - $Rules += Write-Output "`n" - $Rules += Write-Output "`n" - $Rules += Write-Output "`n" - $Rules += Write-Output "`n" - $i++ - } - return ($Rules.Trim()) -} - - -# Create File Rule Refs based on the ID of the File Rules above and store them in the $RulesRefs variable -function Get-RuleRefs { - param ($HashesArray) - $HashesArray | ForEach-Object -Begin { $i = 1 } -Process { - $RulesRefs += Write-Output "`n" - $RulesRefs += Write-Output "`n" - $RulesRefs += Write-Output "`n" - $RulesRefs += Write-Output "`n" - $i++ - } - return ($RulesRefs.Trim()) -} - - -# Can remove _0 from the ID and SignerId of all the elements in the policy xml file -Function Remove-ZerosFromIDs { - param( - [Parameter(Mandatory = $true)] - [ValidateScript({ Test-Path -Path $_ -PathType Leaf })] - [string]$FilePath - ) - # Load the xml file - [xml]$xml = Get-Content -Path $FilePath - - # Get all the elements with ID attribute - $Elements = $xml.SelectNodes('//*[@ID]') - - # Loop through the elements and replace _0 with empty string in the ID value and SignerId value - foreach ($Element in $Elements) { - $Element.ID = $Element.ID -replace '_0', '' - # Check if the element has child elements with SignerId attribute - if ($Element.HasChildNodes) { - # Get the child elements with SignerId attribute - $childElements = $Element.SelectNodes('.//*[@SignerId]') - # Loop through the child elements and replace _0 with empty string in the SignerId value - foreach ($childElement in $childElements) { - $childElement.SignerId = $childElement.SignerId -replace '_0', '' - } - } - } - - # Get the CiSigners element by name - $CiSigners = $xml.SiPolicy.CiSigners - - # Check if the CiSigners element has child elements with SignerId attribute - if ($CiSigners.HasChildNodes) { - # Get the child elements with SignerId attribute - $CiSignersChildren = $CiSigners.ChildNodes - # Loop through the child elements and replace _0 with empty string in the SignerId value - foreach ($CiSignerChild in $CiSignersChildren) { - $CiSignerChild.SignerId = $CiSignerChild.SignerId -replace '_0', '' - } - } - - # Save the modified xml file - $xml.Save($FilePath) -} - - -# Moves all User mode AllowedSigners in the User mode signing scenario to the Kernel mode signing scenario and then -# deletes the entire User mode signing scenario block -Function Move-UserModeToKernelMode { - param( - [Parameter(Mandatory = $true)] - [ValidateScript({ Test-Path -Path $_ -PathType Leaf })] - [string]$FilePath - ) - - # Load the XML file as an XmlDocument object - $xml = [xml](Get-Content -Path $FilePath) - - # Get the SigningScenario nodes as an array - $signingScenarios = $xml.SiPolicy.SigningScenarios.SigningScenario - - # Find the SigningScenario node with Value 131 and store it in a variable - $signingScenario131 = $signingScenarios | Where-Object { $_.Value -eq '131' } - - # Find the SigningScenario node with Value 12 and store it in a variable - $signingScenario12 = $signingScenarios | Where-Object { $_.Value -eq '12' } - - # Get the AllowedSigners node from the SigningScenario node with Value 12 - $AllowedSigners12 = $signingScenario12.ProductSigners.AllowedSigners - - # Check if the AllowedSigners node has any child nodes - if ($AllowedSigners12.HasChildNodes) { - # Loop through each AllowedSigner node from the SigningScenario node with Value 12 - foreach ($AllowedSigner in $AllowedSigners12.AllowedSigner) { - # Create a new AllowedSigner node and copy the SignerId attribute from the original node - # Use the namespace of the parent element when creating the new element - $NewAllowedSigner = $xml.CreateElement('AllowedSigner', $signingScenario131.NamespaceURI) - $NewAllowedSigner.SetAttribute('SignerId', $AllowedSigner.SignerId) - - # Append the new AllowedSigner node to the AllowedSigners node of the SigningScenario node with Value 131 - # out-null to prevent console display - $signingScenario131.ProductSigners.AllowedSigners.AppendChild($NewAllowedSigner) | Out-Null - } - - # Remove the SigningScenario node with Value 12 from the XML document - # out-null to prevent console display - $xml.SiPolicy.SigningScenarios.RemoveChild($signingScenario12) | Out-Null - } - - # Remove Signing Scenario 12 block only if it exists and has no allowed signers (i.e. is empty) - if ($signingScenario12 -and $AllowedSigners12.count -eq 0) { - # Remove the SigningScenario node with Value 12 from the XML document - $xml.SiPolicy.SigningScenarios.RemoveChild($signingScenario12) - } - - # Save the modified XML document to a new file - $xml.Save($FilePath) -} diff --git a/WDACConfig/Resources2.ps1 b/WDACConfig/Resources2.ps1 deleted file mode 100644 index 081338b8b..000000000 --- a/WDACConfig/Resources2.ps1 +++ /dev/null @@ -1,792 +0,0 @@ -# For testing purposes -#$FilePath = '' -#$SignedFilePath = '' -#$XmlFilePath = '' - - -# Defining a custom object to store the signer information -class Signer { - [string]$ID - [string]$Name - [string]$CertRoot - [string]$CertPublisher -} - -# Function that takes an XML file path as input and returns an array of Signer objects -function Get-SignerInfo { - param( - [Parameter(Mandatory = $true)][string]$XmlFilePath - ) - - # Load the XML file and select the Signer nodes - $xml = [xml](Get-Content $XmlFilePath) - $Signers = $xml.SiPolicy.Signers.Signer - - # Create an empty array to store the output - [System.Object[]]$output = @() - - # Loop through each Signer node and extract the information - foreach ($signer in $signers) { - # Create a new Signer object and assign the properties - $SignerObj = [Signer]::new() - $SignerObj.ID = $signer.ID - $SignerObj.Name = $signer.Name - $SignerObj.CertRoot = $signer.CertRoot.Value - $SignerObj.CertPublisher = $signer.CertPublisher.Value - - # Add the Signer object to the output array - $output += $SignerObj - } - - # Return the output array - return $output -} - -# Function to calculate the TBS of a certificate -function Get-TBSCertificate { - param ($Cert) - - # Get the raw data of the certificate - $RawData = $Cert.RawData - - # Create an ASN.1 reader to parse the certificate - $AsnReader = New-Object System.Formats.Asn1.AsnReader -ArgumentList $RawData, ([System.Formats.Asn1.AsnEncodingRules]::DER) - - # Read the certificate sequence - $Certificate = $AsnReader.ReadSequence() - - # Read the TBS (To be signed) value of the certificate - $TbsCertificate = $Certificate.ReadEncodedValue() - - # Read the signature algorithm sequence - $SignatureAlgorithm = $Certificate.ReadSequence() - - # Read the algorithm OID of the signature - $AlgorithmOid = $SignatureAlgorithm.ReadObjectIdentifier() - - # Define a hash function based on the algorithm OID - switch ($AlgorithmOid) { - '1.2.840.113549.1.1.4' { $HashFunction = [System.Security.Cryptography.MD5]::Create() } - '1.2.840.10040.4.3' { $HashFunction = [System.Security.Cryptography.SHA1]::Create() } - '2.16.840.1.101.3.4.3.2' { $HashFunction = [System.Security.Cryptography.SHA256]::Create() } - '2.16.840.1.101.3.4.3.3' { $HashFunction = [System.Security.Cryptography.SHA384]::Create() } - '2.16.840.1.101.3.4.3.4' { $HashFunction = [System.Security.Cryptography.SHA512]::Create() } - '1.2.840.10045.4.1' { $HashFunction = [System.Security.Cryptography.SHA1]::Create() } - '1.2.840.10045.4.3.2' { $HashFunction = [System.Security.Cryptography.SHA256]::Create() } - '1.2.840.10045.4.3.3' { $HashFunction = [System.Security.Cryptography.SHA384]::Create() } - '1.2.840.10045.4.3.4' { $HashFunction = [System.Security.Cryptography.SHA512]::Create() } - '1.2.840.113549.1.1.5' { $HashFunction = [System.Security.Cryptography.SHA1]::Create() } - '1.2.840.113549.1.1.11' { $HashFunction = [System.Security.Cryptography.SHA256]::Create() } - '1.2.840.113549.1.1.12' { $HashFunction = [System.Security.Cryptography.SHA384]::Create() } - '1.2.840.113549.1.1.13' { $HashFunction = [System.Security.Cryptography.SHA512]::Create() } - default { throw "No handler for algorithm $AlgorithmOid" } - } - - # Compute the hash of the TBS value using the hash function - $Hash = $HashFunction.ComputeHash($TbsCertificate.ToArray()) - - # Convert the hash to a hex string and return it - return [System.BitConverter]::ToString($hash) -replace '-', '' -} - -# Helps get the 2nd aka nested signer/signature of the dual signed files -# https://www.sysadmins.lv/blog-en/reading-multiple-signatures-from-signed-file-with-powershell.aspx -# https://www.sysadmins.lv/disclaimer.aspx -function Get-AuthenticodeSignatureEx { - [CmdletBinding()] - param( - [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] - [String[]]$FilePath # The path of the file(s) to get the signature of - ) - begin { - # Define the signature of the Crypt32.dll library functions to use - $signature = @' - [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] - public static extern bool CryptQueryObject( - int dwObjectType, - [MarshalAs(UnmanagedType.LPWStr)] - string pvObject, - int dwExpectedContentTypeFlags, - int dwExpectedFormatTypeFlags, - int dwFlags, - ref int pdwMsgAndCertEncodingType, - ref int pdwContentType, - ref int pdwFormatType, - ref IntPtr phCertStore, - ref IntPtr phMsg, - ref IntPtr ppvContext - ); - [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] - public static extern bool CryptMsgGetParam( - IntPtr hCryptMsg, - int dwParamType, - int dwIndex, - byte[] pvData, - ref int pcbData - ); - [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] - public static extern bool CryptMsgClose( - IntPtr hCryptMsg - ); - [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] - public static extern bool CertCloseStore( - IntPtr hCertStore, - int dwFlags - ); -'@ - # Load the System.Security assembly to use the SignedCms class - Add-Type -AssemblyName System.Security -ErrorAction SilentlyContinue - # Add the Crypt32.dll library functions as a type - Add-Type -MemberDefinition $signature -Namespace PKI -Name Crypt32 -ErrorAction SilentlyContinue - # Define some constants for the CryptQueryObject function parameters - $CERT_QUERY_OBJECT_FILE = 0x1 - $CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED = 0x400 - $CERT_QUERY_FORMAT_FLAG_BINARY = 0x2 - - # Define a helper function to get the timestamps of the countersigners - function getTimeStamps($SignerInfo) { - [System.Object[]]$retValue = @() - foreach ($CounterSignerInfos in $Infos.CounterSignerInfos) { - # Get the signing time attribute from the countersigner info object - $sTime = ($CounterSignerInfos.SignedAttributes | Where-Object { $_.Oid.Value -eq '1.2.840.113549.1.9.5' }).Values | ` - Where-Object { $null -ne $_.SigningTime } - # Create a custom object with the countersigner certificate and signing time properties - $tsObject = New-Object psobject -Property @{ - Certificate = $CounterSignerInfos.Certificate - SigningTime = $sTime.SigningTime.ToLocalTime() - } - # Add the custom object to the return value array - $retValue += $tsObject - } - # Return the array of custom objects with countersigner info - $retValue - - } - } - process { - # For each file path, get the authenticode signature using the built-in cmdlet - Get-AuthenticodeSignature $FilePath | ForEach-Object { - $Output = $_ # Store the output object in a variable - if ($null -ne $Output.SignerCertificate) { - # If the output object has a signer certificate property - # Initialize some variables to store the output parameters of the CryptQueryObject function - $pdwMsgAndCertEncodingType = 0 - $pdwContentType = 0 - $pdwFormatType = 0 - [IntPtr]$phCertStore = [IntPtr]::Zero - [IntPtr]$phMsg = [IntPtr]::Zero - [IntPtr]$ppvContext = [IntPtr]::Zero - # Call the CryptQueryObject function to get the handle of the PKCS #7 message from the file path - $return = [PKI.Crypt32]::CryptQueryObject( - $CERT_QUERY_OBJECT_FILE, - $Output.Path, - $CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, - $CERT_QUERY_FORMAT_FLAG_BINARY, - 0, - [ref]$pdwMsgAndCertEncodingType, - [ref]$pdwContentType, - [ref]$pdwFormatType, - [ref]$phCertStore, - [ref]$phMsg, - [ref]$ppvContext - ) - if (!$return) { return } # If the function fails, return nothing - $pcbData = 0 # Initialize a variable to store the size of the PKCS #7 message data - # Call the CryptMsgGetParam function to get the size of the PKCS #7 message data - $return = [PKI.Crypt32]::CryptMsgGetParam($phMsg, 29, 0, $null, [ref]$pcbData) - if (!$return) { return } # If the function fails, return nothing - $pvData = New-Object byte[] -ArgumentList $pcbData # Create a byte array to store the PKCS #7 message data - # Call the CryptMsgGetParam function again to get the PKCS #7 message data - $return = [PKI.Crypt32]::CryptMsgGetParam($phMsg, 29, 0, $pvData, [ref]$pcbData) - $SignedCms = New-Object Security.Cryptography.Pkcs.SignedCms # Create a SignedCms object to decode the PKCS #7 message data - $SignedCms.Decode($pvData) # Decode the PKCS #7 message data and populate the SignedCms object properties - $Infos = $SignedCms.SignerInfos[0] # Get the first signer info object from the SignedCms object - # Add some properties to the output object, such as TimeStamps, DigestAlgorithm and NestedSignature - $Output | Add-Member -MemberType NoteProperty -Name TimeStamps -Value $null - $Output | Add-Member -MemberType NoteProperty -Name DigestAlgorithm -Value $Infos.DigestAlgorithm.FriendlyName - # Call the helper function to get the timestamps of the countersigners and assign it to the TimeStamps property - $Output.TimeStamps = getTimeStamps $Infos - # Check if there is a nested signature attribute in the signer info object by looking for the OID 1.3.6.1.4.1.311.2.4.1 - $second = $Infos.UnsignedAttributes | Where-Object { $_.Oid.Value -eq '1.3.6.1.4.1.311.2.4.1' } - if ($second) { - # If there is a nested signature attribute - # Get the value of the nested signature attribute as a raw data byte array - $value = $second.Values | Where-Object { $_.Oid.Value -eq '1.3.6.1.4.1.311.2.4.1' } - $SignedCms2 = New-Object Security.Cryptography.Pkcs.SignedCms # Create another SignedCms object to decode the nested signature data - $SignedCms2.Decode($value.RawData) # Decode the nested signature data and populate the SignedCms object properties - $Output | Add-Member -MemberType NoteProperty -Name NestedSignature -Value $null - $Infos = $SignedCms2.SignerInfos[0] # Get the first signer info object from the nested signature SignedCms object - # Create a custom object with some properties of the nested signature, such as signer certificate, digest algorithm and timestamps - $nested = New-Object psobject -Property @{ - SignerCertificate = $Infos.Certificate - DigestAlgorithm = $Infos.DigestAlgorithm.FriendlyName - TimeStamps = getTimeStamps $Infos - } - # Assign the custom object to the NestedSignature property of the output object - $Output.NestedSignature = $nested - } - # Return the output object with the added properties - $Output - # Close the handles of the PKCS #7 message and the certificate store - [void][PKI.Crypt32]::CryptMsgClose($phMsg) - [void][PKI.Crypt32]::CertCloseStore($phCertStore, 0) - } - else { - # If the output object does not have a signer certificate property - # Return the output object as it is - $Output - } - } - } - end {} -} - - - -# A function to get all the certificates from a signed file or a certificate object and output a Collection -function Get-SignedFileCertificates { - param ( - # Define two sets of parameters, one for the FilePath and one for the CertObject - [Parameter()] - [string]$FilePath, - [Parameter(ValueFromPipeline = $true)] - [System.Security.Cryptography.X509Certificates.X509Certificate2]$X509Certificate2 - ) - - # Create an X509Certificate2Collection object - $CertCollection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection - - # Check which parameter set is used - if ($FilePath) { - # If the FilePath parameter is used, import all the certificates from the file - $CertCollection.Import($FilePath, $null, 'DefaultKeySet') - } - elseif ($X509Certificate2) { - # If the CertObject parameter is used, add the certificate object to the collection - $CertCollection.Add($X509Certificate2) - } - - # Return the collection - return $CertCollection -} - - -# A function to detect Root, Intermediate and Leaf certificates -function Get-CertificateDetails { - param ( - [Parameter(ParameterSetName = 'Based on File Path', Mandatory = $true)] - [System.String]$FilePath, - - [Parameter(ParameterSetName = 'Based on Certificate', Mandatory = $true)] - $X509Certificate2, - - [Parameter(ParameterSetName = 'Based on Certificate')] - [System.String]$LeafCNOfTheNestedCertificate, # This is used only for when -X509Certificate2 parameter is used, so that we can filter out the Leaf certificate and only get the Intermediate certificates at the end of this function - - [Parameter(ParameterSetName = 'Based on File Path')] - [Parameter(ParameterSetName = 'Based on Certificate')] - [switch]$IntermediateOnly, - - [Parameter(ParameterSetName = 'Based on File Path')] - [Parameter(ParameterSetName = 'Based on Certificate')] - [switch]$LeafCertificate - ) - - # An array to hold objects - [System.Object[]]$Obj = @() - - if ($FilePath) { - # Get all the certificates from the file path using the Get-SignedFileCertificates function - $CertCollection = Get-SignedFileCertificates -FilePath $FilePath | Where-Object { $_.EnhancedKeyUsageList.FriendlyName -ne 'Time Stamping' } - } - else { - # The "| Where-Object {$_ -ne 0}" part is used to filter the output coming from Get-AuthenticodeSignatureEx function that gets nested certificate - $CertCollection = Get-SignedFileCertificates -X509Certificate2 $X509Certificate2 | Where-Object { $_.EnhancedKeyUsageList.FriendlyName -ne 'Time Stamping' } | Where-Object { $_ -ne 0 } - } - - # Loop through each certificate in the collection and call this function recursively with the certificate object as an input - foreach ($Cert in $CertCollection) { - - # Build the certificate chain - $Chain = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Chain - - # Set the chain policy properties - $chain.ChainPolicy.RevocationMode = 'NoCheck' - $chain.ChainPolicy.RevocationFlag = 'EndCertificateOnly' - $chain.ChainPolicy.VerificationFlags = 'NoFlag' - - [void]$Chain.Build($Cert) - - # If AllCertificates is present, loop through all chain elements and display all certificates - foreach ($Element in $Chain.ChainElements) { - # Create a custom object with the certificate properties - - # Extract the data after CN= in the subject and issuer properties - # When a common name contains a comma ',' then it will automatically be wrapped around double quotes. E.g., "Skylum Software USA, Inc." - # The methods below are conditional regex. Different patterns are used based on the availability of at least one double quote in the CN field, indicating that it had comma in it so it had been enclosed with double quotes by system - - $Element.Certificate.Subject -match 'CN=(?.*?),.*' | Out-Null - $SubjectCN = $matches['InitialRegexTest2'] -like '*"*' ? ($Element.Certificate.Subject -split 'CN="(.+?)"')[1] : $matches['InitialRegexTest2'] - - $Element.Certificate.Issuer -match 'CN=(?.*?),.*' | Out-Null - $IssuerCN = $matches['InitialRegexTest3'] -like '*"*' ? ($Element.Certificate.Issuer -split 'CN="(.+?)"')[1] : $matches['InitialRegexTest3'] - - # Get the TBS value of the certificate using the Get-TBSCertificate function - $TbsValue = Get-TBSCertificate -cert $Element.Certificate - # Create a custom object with the extracted properties and the TBS value - $Obj += [pscustomobject]@{ - SubjectCN = $SubjectCN - IssuerCN = $IssuerCN - NotAfter = $element.Certificate.NotAfter - TBSValue = $TbsValue - } - } - } - - if ($FilePath) { - - # The reason the commented code below is not used is because some files such as C:\Windows\System32\xcopy.exe or d3dcompiler_47.dll that are signed by Microsoft report a different Leaf certificate common name when queried using Get-AuthenticodeSignature - # (Get-AuthenticodeSignature -FilePath $FilePath).SignerCertificate.Subject -match 'CN=(?.*?),.*' | Out-Null - - $CertificateUsingAlternativeMethod = [System.Security.Cryptography.X509Certificates.X509Certificate]::CreateFromSignedFile($FilePath) - $CertificateUsingAlternativeMethod.Subject -match 'CN=(?.*?),.*' | Out-Null - - - [string]$TestAgainst = $matches['InitialRegexTest4'] -like '*"*' ? ((Get-AuthenticodeSignature -FilePath $FilePath).SignerCertificate.Subject -split 'CN="(.+?)"')[1] : $matches['InitialRegexTest4'] - - - if ($IntermediateOnly) { - - $FinalObj = $Obj | - Where-Object { $_.SubjectCN -ne $_.IssuerCN } | # To omit Root certificate from the result - Where-Object { $_.SubjectCN -ne $TestAgainst } | # To omit the Leaf certificate - Group-Object -Property TBSValue | ForEach-Object { $_.Group[0] } # To make sure the output values are unique based on TBSValue property - - return $FinalObj - - } - elseif ($LeafCertificate) { - - $FinalObj = $Obj | - Where-Object { $_.SubjectCN -ne $_.IssuerCN } | # To omit Root certificate from the result - Where-Object { $_.SubjectCN -eq $TestAgainst } | # To get the Leaf certificate - Group-Object -Property TBSValue | ForEach-Object { $_.Group[0] } # To make sure the output values are unique based on TBSValue property - - return $FinalObj - } - - } - # If nested certificate is being processed and X509Certificate2 object is passed - elseif ($X509Certificate2) { - - if ($IntermediateOnly) { - - $FinalObj = $Obj | - Where-Object { $_.SubjectCN -ne $_.IssuerCN } | # To omit Root certificate from the result - Where-Object { $_.SubjectCN -ne $LeafCNOfTheNestedCertificate } | # To omit the Leaf certificate - Group-Object -Property TBSValue | ForEach-Object { $_.Group[0] } # To make sure the output values are unique based on TBSValue property - - return $FinalObj - - } - elseif ($LeafCertificate) { - - $FinalObj = $Obj | - Where-Object { $_.SubjectCN -ne $_.IssuerCN } | # To omit Root certificate from the result - Where-Object { $_.SubjectCN -eq $LeafCNOfTheNestedCertificate } | # To get the Leaf certificate - Group-Object -Property TBSValue | ForEach-Object { $_.Group[0] } # To make sure the output values are unique based on TBSValue property - - return $FinalObj - - } - } -} - - - -<# - -# Function that shows the details of certificates. E.g, All intermediate certs, Leaf cert or the entire chain, depending on optional switch parameters -function Get-CertificateDetails { - # Use the param keyword to define the parameters - param ( - # Make the FilePath parameter mandatory and validate that it is a valid file path - [Parameter()] - [ValidateScript({ Test-Path $_ -PathType Leaf })] - [string]$FilePath, - $X509Certificate2, - [switch]$IntermediateOnly, - [switch]$AllCertificates, - [switch]$LeafCertificate - ) - - if ($FilePath) { - # Get the certificate from the file path - $Cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $FilePath - } - # if file path isn't used and instead a X509Certificate2 is provided then assign it directly to the $Cert variable - elseif ($X509Certificate2) { - $Cert = $X509Certificate2 - } - - # Build the certificate chain - $Chain = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Chain - - # Set the chain policy properties - $chain.ChainPolicy.RevocationMode = 'NoCheck' - $chain.ChainPolicy.RevocationFlag = 'EndCertificateOnly' - $chain.ChainPolicy.VerificationFlags = 'NoFlag' - - [void]$Chain.Build($Cert) - - # Check the value of the switch parameters - if ($IntermediateOnly) { - # If IntermediateOnly is present, loop through the chain elements and display only the intermediate certificates - for ($i = 1; $i -lt $Chain.ChainElements.Count - 1; $i++) { - # Create a custom object with the certificate properties - $Element = $Chain.ChainElements[$i] - # Extract the data after CN= in the subject and issuer properties - $SubjectCN = ($Element.Certificate.Subject -split '(?:^|,)CN=|,')[1] - $IssuerCN = ($Element.Certificate.Issuer -split '(?:^|,)CN=|,')[1] - # Get the TBS value of the certificate using the Get-TBSCertificate function - $TbsValue = Get-TBSCertificate -cert $Element.Certificate - # Create a custom object with the extracted properties and the TBS value - $Obj = [pscustomobject]@{ - SubjectCN = $SubjectCN - IssuerCN = $issuerCN - NotAfter = $Element.Certificate.NotAfter - TBSValue = $TbsValue - } - # Display the object - Write-Output $Obj - } - } - elseif ($AllCertificates) { - # If AllCertificates is present, loop through all chain elements and display all certificates - foreach ($Element in $Chain.ChainElements) { - # Create a custom object with the certificate properties - # Extract the data after CN= in the subject and issuer properties - $SubjectCN = ($Element.Certificate.Subject -split '(?:^|,)CN=|,')[1] - $IssuerCN = ($Element.Certificate.Issuer -split '(?:^|,)CN=|,')[1] - # Get the TBS value of the certificate using the Get-TBSCertificate function - $TbsValue = Get-TBSCertificate -cert $Element.Certificate - # Create a custom object with the extracted properties and the TBS value - $Obj = [pscustomobject]@{ - SubjectCN = $SubjectCN - IssuerCN = $IssuerCN - NotAfter = $element.Certificate.NotAfter - TBSValue = $TbsValue - } - # Display the object - Write-Output $obj - } - } - elseif ($LeafCertificate) { - # If LeafCertificate is present, create a custom object with the leaf certificate properties - # Extract the data after CN= in the subject and issuer properties - $SubjectCN = ($Chain.ChainElements[0].Certificate.Subject -split '(?:^|,)CN=|,')[1] - $IssuerCN = ($Chain.ChainElements[0].Certificate.Issuer -split '(?:^|,)CN=|,')[1] - # Get the TBS value of the certificate using the Get-TBSCertificate function - $TbsValue = Get-TBSCertificate -cert $Chain.ChainElements[0].Certificate - # Create a custom object with the extracted properties and the TBS value - $Obj = [pscustomobject]@{ - SubjectCN = $SubjectCN - IssuerCN = $IssuerCN - NotAfter = $Chain.ChainElements[0].Certificate.NotAfter - TBSValue = $TbsValue - } - # Display the object - Write-Output 'Leaf Certificate:' - Write-Output $obj - } - else { - # If none of the switch parameters are present, display a message to inform the user of their options - Write-Output 'Please specify one of the following switch parameters to get certificate details: -IntermediateOnly, -AllCertificates, or -LeafCertificate.' - } -} - -#> - - -# a function that takes WDAC XML policy file path and a Signed file path as inputs and compares the output of the Get-SignerInfo and Get-CertificateDetails functions -function Compare-SignerAndCertificate { - param( - [Parameter(Mandatory = $true)][string]$XmlFilePath, - [Parameter(Mandatory = $true)] [string]$SignedFilePath - ) - - # Get the signer information from the XML file path using the Get-SignerInfo function - $SignerInfo = Get-SignerInfo -XmlFilePath $XmlFilePath - - # An array to store the details of the main certificate of the signed file - [System.Object[]]$CertificateDetails = @() - - # An array to store the details of the Nested certificate of the signed file - [System.Object[]]$NestedCertificateDetails = @() - - # An array to store the final comparison results of this function - [System.Object[]]$ComparisonResults = @() - - # Get the certificate details from the signed file path using the Get-CertificateDetails function with the -IntermediateOnly parameter - $CertificateDetails = Get-CertificateDetails -IntermediateOnly -FilePath $SignedFilePath - - # Get the Nested certificate of the signed file, if any - $ExtraCertificateDetails = Get-AuthenticodeSignatureEx -FilePath $SignedFilePath - - # Extract it from the nested property - $NestedCertificate = ($ExtraCertificateDetails).NestedSignature.SignerCertificate - - if ($null -ne $NestedCertificate) { - - # First get the CN of the leaf certificate of the nested Certificate - $NestedCertificate.Subject -match 'CN=(?.*?),.*' | Out-Null - $LeafCNOfTheNestedCertificate = $matches['InitialRegexTest1'] -like '*"*' ? ($NestedCertificate.Subject -split 'CN="(.+?)"')[1] : $matches['InitialRegexTest1'] - - # Send the nested certificate along with its Leaf certificate's CN to the Get-CertificateDetails function with -IntermediateOnly parameter in order to only get the intermediate certificates of the Nested certificate - $NestedCertificateDetails = Get-CertificateDetails -IntermediateOnly -X509Certificate2 $NestedCertificate -LeafCNOfTheNestedCertificate $LeafCNOfTheNestedCertificate - } - - - # Declare $LeafCertificateDetails as an array - [System.Object[]]$LeafCertificateDetails = @() - - # Declare $NestedLeafCertificateDetails as an array - [System.Object[]]$NestedLeafCertificateDetails = @() - - # Get the leaf certificate details of the Main Certificate from the signed file path - $LeafCertificateDetails = Get-CertificateDetails -LeafCertificate -FilePath $SignedFilePath - - # Get the leaf certificate details of the Nested Certificate from the signed file path, if it exists - if ($null -ne $NestedCertificate) { - # append an X509Certificate2 object to the array - $NestedLeafCertificateDetails = Get-CertificateDetails -LeafCertificate -X509Certificate2 $NestedCertificate -LeafCNOfTheNestedCertificate $LeafCNOfTheNestedCertificate - } - - - # Loop through each signer in the signer information array - foreach ($Signer in $SignerInfo) { - # Create a custom object to store the comparison result for this signer - $ComparisonResult = [pscustomobject]@{ - SignerID = $Signer.ID - SignerName = $Signer.Name - SignerCertRoot = $Signer.CertRoot - SignerCertPublisher = $Signer.CertPublisher - CertSubjectCN = $null - CertIssuerCN = $null - CertNotAfter = $null - CertTBSValue = $null - CertRootMatch = $false - CertNameMatch = $false - CertPublisherMatch = $false - FilePath = $SignedFilePath # Add the file path to the object - } - - # Loop through each certificate in the certificate details array of the Main Cert - foreach ($Certificate in $CertificateDetails) { - - # Check if the signer's CertRoot (referring to the TBS value in the xml file which belongs to an intermediate cert of the file)... - # ...matches the TBSValue of the file's certificate (TBS values of one of the intermediate certificates of the file since -IntermediateOnly parameter is used earlier and that's what FilePublisher level uses) - # So this checks to see if the Signer's TBS value in xml matches any of the TBS value(s) of the file's intermediate certificate(s), if it does, that means that file is allowed to run by the WDAC engine - if ($Signer.CertRoot -eq $Certificate.TBSValue) { - - # Assign the certificate properties to the comparison result object and set the CertRootMatch to true based on further conditions - $ComparisonResult.CertSubjectCN = $Certificate.SubjectCN - $ComparisonResult.CertIssuerCN = $Certificate.IssuerCN - $ComparisonResult.CertNotAfter = $Certificate.NotAfter - $ComparisonResult.CertTBSValue = $Certificate.TBSValue - - # if the signed file has nested certificate, only set a flag instead of setting the entire CertRootMatch property to true - if ($null -ne $NestedCertificate) { - $CertRootMatchPart1 = $true - } - else { - $ComparisonResult.CertRootMatch = $true # meaning one of the TBS values of the file's intermediate certs is in the xml file signers' TBS values - } - - # Check if the signer's name (Referring to the one in the XML file) matches the Intermediate certificate's SubjectCN - if ($Signer.Name -eq $Certificate.SubjectCN) { - # Set the CertNameMatch to true - $ComparisonResult.CertNameMatch = $true # this should naturally be always true like the CertRootMatch because this is the CN of the same cert that has its TBS value in the xml file in signers - } - - - # Check if the signer's CertPublisher (aka Leaf Certificate's CN used in the xml policy) matches the leaf certificate's SubjectCN (of the file) - if ($Signer.CertPublisher -eq $LeafCertificateDetails.SubjectCN) { - - # if the signed file has nested certificate, only set a flag instead of setting the entire CertPublisherMatch property to true - if ($null -ne $NestedCertificate) { - $CertPublisherMatchPart1 = $true - } - else { - $ComparisonResult.CertPublisherMatch = $true - } - } - - # Break out of the inner loop whether we found a match for this signer or not - break - } - } - - # Nested Certificate TBS processing, if it exists - if ($null -ne $NestedCertificate) { - - # Loop through each certificate in the NESTED certificate details array - foreach ($Certificate in $NestedCertificateDetails) { - - # Check if the signer's CertRoot (referring to the TBS value in the xml file which belongs to an intermediate cert of the file)... - # ...matches the TBSValue of the file's certificate (TBS values of one of the intermediate certificates of the file since -IntermediateOnly parameter is used earlier and that's what FilePublisher level uses) - # So this checks to see if the Signer's TBS value in xml matches any of the TBS value(s) of the file's intermediate certificate(s), if yes, that means that file is allowed to run by WDAC engine - if ($Signer.CertRoot -eq $Certificate.TBSValue) { - - # Assign the certificate properties to the comparison result object and set the CertRootMatch to true - $ComparisonResult.CertSubjectCN = $Certificate.SubjectCN - $ComparisonResult.CertIssuerCN = $Certificate.IssuerCN - $ComparisonResult.CertNotAfter = $Certificate.NotAfter - $ComparisonResult.CertTBSValue = $Certificate.TBSValue - - # When file has nested signature, only set a flag instead of setting the entire property to true - $CertRootMatchPart2 = $true - - # Check if the signer's Name matches the Intermediate certificate's SubjectCN - if ($Signer.Name -eq $Certificate.SubjectCN) { - # Set the CertNameMatch to true - $ComparisonResult.CertNameMatch = $true # this should naturally be always true like the CertRootMatch because this is the CN of the same cert that has its TBS value in the xml file in signers - } - - - # Check if the signer's CertPublisher (aka Leaf Certificate's CN used in the xml policy) matches the leaf certificate's SubjectCN (of the file) - if ($Signer.CertPublisher -eq $LeafCNOfTheNestedCertificate) { - # If yes, set the CertPublisherMatch to true for this comparison result object - $CertPublisherMatchPart2 = $true - } - - # Break out of the inner loop whether we found a match for this signer or not - break - } - } - } - - - # if the signed file has nested certificate - if ($null -ne $NestedCertificate) { - - # check if both of the file's certificates (Nested and Main) are available in the Signers in xml policy - if (($CertRootMatchPart1 -eq $true) -and ($CertRootMatchPart2 -eq $true)) { - $ComparisonResult.CertRootMatch = $true # meaning all of the TBS values of the double signed file's intermediate certificates exists in the xml file's signers' TBS values - } - else { - $ComparisonResult.CertRootMatch = $false - } - - # check if Lean certificate CN of both of the file's certificates (Nested and Main) are available in the Signers in xml policy - if (($CertPublisherMatchPart1 -eq $true) -and ($CertPublisherMatchPart2 -eq $true)) { - $ComparisonResult.CertPublisherMatch = $true - } - else { - $ComparisonResult.CertPublisherMatch = $false - } - - } - - # Add the comparison result object to the comparison results array - $ComparisonResults += $ComparisonResult - - } - - - # Return the comparison results array - return $ComparisonResults -} - - - - -# Define a function to load an xml file and create an output array of custom objects that contain the file rules that are based on file hashes -function Get-FileRuleOutput ($xmlPath) { - - # Load the xml file into a variable - $xml = [xml](Get-Content -Path $xmlPath) - - # Create an empty array to store the output - [System.Object[]]$OutPutHashInfoProcessing = @() - - # Loop through each file rule in the xml file - foreach ($filerule in $xml.SiPolicy.FileRules.Allow) { - - # Extract the hash value from the Hash attribute - $hashvalue = $filerule.Hash - - # Extract the hash type from the FriendlyName attribute using regex - $hashtype = $filerule.FriendlyName -replace '.* (Hash (Sha1|Sha256|Page Sha1|Page Sha256|Authenticode SIP Sha256))$', '$1' - - # Extract the file path from the FriendlyName attribute using regex - $FilePathForHash = $filerule.FriendlyName -replace ' (Hash (Sha1|Sha256|Page Sha1|Page Sha256|Authenticode SIP Sha256))$', '' - - # Create a custom object with the three properties - $object = [PSCustomObject]@{ - HashValue = $hashvalue - HashType = $hashtype - FilePathForHash = $FilePathForHash - } - - # Add the object to the output array if it is not a duplicate hash value - if ($OutPutHashInfoProcessing.HashValue -notcontains $hashvalue) { - $OutPutHashInfoProcessing += $object - } - } - - # Only show the Authenticode Hash SHA256 - $OutPutHashInfoProcessing = $OutPutHashInfoProcessing | Where-Object { $_.hashtype -eq 'Hash Sha256' } - - # Return the output array - return $OutPutHashInfoProcessing -} - - -<# NOT USED ANYMORE - -# Define a function to compare two xml files and return an array of objects with a custom property for the comparison result -function Compare-XmlFiles ($refXmlPath, $tarXmlPath) { - - # Load the reference xml file and create an output array using the Get-FileRuleOutput function - $refoutput = Get-FileRuleOutput -xmlPath $refXmlPath - - # Load the target xml file and create an output array using the Get-FileRuleOutput function - $taroutput = Get-FileRuleOutput -xmlPath $tarXmlPath - - # make sure they are not empty - if ($refoutput -and $taroutput) { - - # Compare the output arrays using the Compare-Object cmdlet with the -Property parameter - # Specify the HashValue property as the property to compare - # Use the -PassThru parameter to return the original input objects - # Use the -IncludeEqual parameter to include the objects that are equal in both arrays - $comparison = Compare-Object -ReferenceObject $refoutput -DifferenceObject $taroutput -Property HashValue -PassThru -IncludeEqual - - # Create an empty array to store the output objects - [System.Object[]]$OutPutHashComparison = @() - - # Loop through each object in the comparison array - foreach ($object in $comparison) { - - # Create a custom property called Comparison and assign it a value based on the SideIndicator property - switch ($object.SideIndicator) { - '<=' { $comparison = 'Only in reference' } - '=>' { $comparison = 'Only in target' } - '==' { $comparison = 'Both' } - } - - # Add the Comparison property to the object using the Add-Member cmdlet - $object | Add-Member -MemberType NoteProperty -Name Comparison -Value $comparison - - # Add the object to the output array - $OutPutHashComparison += $object - } - - # Return the output array - return $OutPutHashComparison - - } -} -#> - diff --git a/WDACConfig/Set-CommonWDACConfig.psm1 b/WDACConfig/Set-CommonWDACConfig.psm1 deleted file mode 100644 index 3df33a3b8..000000000 --- a/WDACConfig/Set-CommonWDACConfig.psm1 +++ /dev/null @@ -1,216 +0,0 @@ -#Requires -RunAsAdministrator -function Set-CommonWDACConfig { - [CmdletBinding()] - Param( - [ValidateScript({ - $certs = foreach ($cert in (Get-ChildItem 'Cert:\CurrentUser\my')) { - (($cert.Subject -split ',' | Select-Object -First 1) -replace 'CN=', '').Trim() - } - $certs -contains $_ - }, ErrorMessage = "A certificate with the provided common name doesn't exist in the personal store of the user certificates." )] - [parameter(Mandatory = $false)][System.String]$CertCN, - - [ValidatePattern('\.cer$')] - [ValidateScript({ Test-Path $_ -PathType 'Leaf' }, ErrorMessage = 'The path you selected is not a file path.')] - [parameter(Mandatory = $false)][System.String]$CertPath, - - [ValidatePattern('\.exe$')] - [ValidateScript({ Test-Path $_ -PathType 'Leaf' }, ErrorMessage = 'The path you selected is not a file path.')] - [parameter(Mandatory = $false)][System.String]$SignToolPath, - - [ValidatePattern('\.xml$')] - [ValidateScript({ - $_ | ForEach-Object { - $xmlTest = [xml](Get-Content $_) - $RedFlag1 = $xmlTest.SiPolicy.SupplementalPolicySigners.SupplementalPolicySigner.SignerId - $RedFlag2 = $xmlTest.SiPolicy.UpdatePolicySigners.UpdatePolicySigner.SignerId - if (!$RedFlag1 -and !$RedFlag2) { - return $True - } - else { throw 'The selected policy xml file is Signed, Please select an Unsigned policy.' } - } - }, ErrorMessage = 'The selected policy xml file is Signed, Please select an Unsigned policy.')] - [parameter(Mandatory = $false)][System.String]$UnsignedPolicyPath, - - [ValidatePattern('\.xml$')] - [ValidateScript({ - $_ | ForEach-Object { - $xmlTest = [xml](Get-Content $_) - $RedFlag1 = $xmlTest.SiPolicy.SupplementalPolicySigners.SupplementalPolicySigner.SignerId - $RedFlag2 = $xmlTest.SiPolicy.UpdatePolicySigners.UpdatePolicySigner.SignerId - if ($RedFlag1 -or $RedFlag2) { - return $True - } - else { throw 'The selected policy xml file is Unsigned, Please select a Signed policy.' } - } - }, ErrorMessage = 'The selected policy xml file is Unsigned, Please select a Signed policy.')] - [parameter(Mandatory = $false)][System.String]$SignedPolicyPath, - - [parameter(Mandatory = $false, DontShow = $true)][System.Guid]$StrictKernelPolicyGUID, # DontShow prevents common parameters from being displayed too - - [parameter(Mandatory = $false, DontShow = $true)][System.Guid]$StrictKernelNoFlightRootsPolicyGUID, - - [parameter(Mandatory = $false, DontShow = $true)][datetime]$LastUpdateCheck - ) - begin { - # Importing resources such as functions by dot-sourcing so that they will run in the same scope and their variables will be usable - . "$psscriptroot\Resources.ps1" - - # Stop operation as soon as there is an error anywhere, unless explicitly specified otherwise - $ErrorActionPreference = 'Stop' - - # Fetch User account directory path - [string]$global:UserAccountDirectoryPath = (Get-CimInstance Win32_UserProfile -Filter "SID = '$([System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value)'").LocalPath - - # Create User configuration folder if it doesn't already exist - if (-NOT (Test-Path -Path "$global:UserAccountDirectoryPath\.WDACConfig\")) { - New-Item -ItemType Directory -Path "$global:UserAccountDirectoryPath\.WDACConfig\" -Force -ErrorAction Stop | Out-Null - Write-Debug -Message "The .WDACConfig folder in current user's folder has been created because it didn't exist." - } - - # Create User configuration file if it doesn't already exist - if (-NOT (Test-Path -Path "$global:UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json")) { - New-Item -ItemType File -Path "$global:UserAccountDirectoryPath\.WDACConfig\" -Name 'UserConfigurations.json' -Force -ErrorAction Stop | Out-Null - Write-Debug -Message "The UserConfigurations.json file in \.WDACConfig\ folder has been created because it didn't exist." - } - - if ($PSBoundParameters.Count -eq 0) { - Write-Error 'No parameter was selected.' - break - } - - # Read the current user configurations - $CurrentUserConfigurations = Get-Content -Path "$global:UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" - # If the file exists but is corrupted and has bad values, rewrite it - try { - $CurrentUserConfigurations = $CurrentUserConfigurations | ConvertFrom-Json - } - catch { - Set-Content -Path "$global:UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" -Value '' - } - - # An object to hold the User configurations - $UserConfigurationsObject = [PSCustomObject]@{ - SignedPolicyPath = '' - UnsignedPolicyPath = '' - SignToolCustomPath = '' - CertificateCommonName = '' - CertificatePath = '' - StrictKernelPolicyGUID = '' - StrictKernelNoFlightRootsPolicyGUID = '' - LastUpdateCheck = '' - } - } - process { - - if ($SignedPolicyPath) { - $UserConfigurationsObject.SignedPolicyPath = $SignedPolicyPath - } - else { - $UserConfigurationsObject.SignedPolicyPath = $CurrentUserConfigurations.SignedPolicyPath - } - - if ($UnsignedPolicyPath) { - $UserConfigurationsObject.UnsignedPolicyPath = $UnsignedPolicyPath - } - else { - $UserConfigurationsObject.UnsignedPolicyPath = $CurrentUserConfigurations.UnsignedPolicyPath - } - - if ($SignToolPath) { - $UserConfigurationsObject.SignToolCustomPath = $SignToolPath - } - else { - $UserConfigurationsObject.SignToolCustomPath = $CurrentUserConfigurations.SignToolCustomPath - } - - if ($CertPath) { - $UserConfigurationsObject.CertificatePath = $CertPath - } - else { - $UserConfigurationsObject.CertificatePath = $CurrentUserConfigurations.CertificatePath - } - - if ($CertCN) { - $UserConfigurationsObject.CertificateCommonName = $CertCN - } - else { - $UserConfigurationsObject.CertificateCommonName = $CurrentUserConfigurations.CertificateCommonName - } - - if ($StrictKernelPolicyGUID) { - $UserConfigurationsObject.StrictKernelPolicyGUID = $StrictKernelPolicyGUID - } - else { - $UserConfigurationsObject.StrictKernelPolicyGUID = $CurrentUserConfigurations.StrictKernelPolicyGUID - } - - if ($StrictKernelNoFlightRootsPolicyGUID) { - $UserConfigurationsObject.StrictKernelNoFlightRootsPolicyGUID = $StrictKernelNoFlightRootsPolicyGUID - } - else { - $UserConfigurationsObject.StrictKernelNoFlightRootsPolicyGUID = $CurrentUserConfigurations.StrictKernelNoFlightRootsPolicyGUID - } - - if ($LastUpdateCheck) { - $UserConfigurationsObject.LastUpdateCheck = $LastUpdateCheck - } - else { - $UserConfigurationsObject.LastUpdateCheck = $CurrentUserConfigurations.LastUpdateCheck - } - } - end { - # Update the User Configurations file - $UserConfigurationsObject | ConvertTo-Json | Set-Content "$global:UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" - &$WritePink "`nThis is your new WDAC User Configurations: " - Get-Content -Path "$global:UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" | ConvertFrom-Json | Format-List * - } -} -<# -.SYNOPSIS -Add/Change common values for parameters used by WDACConfig module - -.LINK -https://github.com/HotCakeX/Harden-Windows-Security/wiki/Set-CommonWDACConfig - -.DESCRIPTION -Add/Change common values for parameters used by WDACConfig module so that you won't have to provide values for those repetitive parameters each time you need to use the WDACConfig module cmdlets. - -.COMPONENT -Windows Defender Application Control, ConfigCI PowerShell module, WDACConfig module - -.FUNCTIONALITY -Add/Change common values for parameters used by WDACConfig module so that you won't have to provide values for those repetitive parameters each time you need to use the WDACConfig module cmdlets. - -.PARAMETER SignedPolicyPath -Path to a Signed WDAC xml policy - -.PARAMETER UnsignedPolicyPath -Path to an Unsigned WDAC xml policy - -.PARAMETER CertCN -Certificate common name - -.PARAMETER SignToolPath -Path to the SignTool.exe - -.PARAMETER CertPath -Path to a .cer certificate file - -.PARAMETER StrictKernelPolicyGUID -GUID of the Strict Kernel mode policy - -.PARAMETER StrictKernelNoFlightRootsPolicyGUID -GUID of the Strict Kernel no Flights root mode policy - -#> - -# Importing argument completer ScriptBlocks -. "$psscriptroot\ArgumentCompleters.ps1" -# Set PSReadline tab completion to complete menu for easier access to available parameters - Only for the current session -Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete -Register-ArgumentCompleter -CommandName 'Set-CommonWDACConfig' -ParameterName 'CertCN' -ScriptBlock $ArgumentCompleterCertificateCN -Register-ArgumentCompleter -CommandName 'Set-CommonWDACConfig' -ParameterName 'CertPath' -ScriptBlock $ArgumentCompleterCerFilePathsPicker -Register-ArgumentCompleter -CommandName 'Set-CommonWDACConfig' -ParameterName 'SignToolPath' -ScriptBlock $ArgumentCompleterExeFilePathsPicker -Register-ArgumentCompleter -CommandName 'Set-CommonWDACConfig' -ParameterName 'SignedPolicyPath' -ScriptBlock $ArgumentCompleterPolicyPathsBasePoliciesOnly -Register-ArgumentCompleter -CommandName 'Set-CommonWDACConfig' -ParameterName 'UnsignedPolicyPath' -ScriptBlock $ArgumentCompleterPolicyPathsBasePoliciesOnly diff --git a/WDACConfig/Utilities/Functions no longer in use/Other functions.ps1 b/WDACConfig/Utilities/Functions no longer in use/Other functions.ps1 new file mode 100644 index 000000000..a373fc3f6 --- /dev/null +++ b/WDACConfig/Utilities/Functions no longer in use/Other functions.ps1 @@ -0,0 +1,151 @@ +<# + +The following file contains functions related to Resources2.ps1 file in the WDACConfig module +that were used mainly by the Invoke-WDACSimulation cmdlet + +#> + + +# Define a function to compare two xml files and return an array of objects with a custom property for the comparison result +function Compare-XmlFiles ($refXmlPath, $tarXmlPath) { + + # Load the reference xml file and create an output array using the Get-FileRuleOutput function + $refoutput = Get-FileRuleOutput -xmlPath $refXmlPath + + # Load the target xml file and create an output array using the Get-FileRuleOutput function + $taroutput = Get-FileRuleOutput -xmlPath $tarXmlPath + + # make sure they are not empty + if ($refoutput -and $taroutput) { + + # Compare the output arrays using the Compare-Object cmdlet with the -Property parameter + # Specify the HashValue property as the property to compare + # Use the -PassThru parameter to return the original input objects + # Use the -IncludeEqual parameter to include the objects that are equal in both arrays + $comparison = Compare-Object -ReferenceObject $refoutput -DifferenceObject $taroutput -Property HashValue -PassThru -IncludeEqual + + # Create an empty array to store the output objects + [System.Object[]]$OutputHashComparison = @() + + # Loop through each object in the comparison array + foreach ($Object in $comparison) { + + # Create a custom property called Comparison and assign it a value based on the SideIndicator property + switch ($Object.SideIndicator) { + '<=' { $comparison = 'Only in reference' } + '=>' { $comparison = 'Only in target' } + '==' { $comparison = 'Both' } + } + + # Add the Comparison property to the object using the Add-Member cmdlet + $Object | Add-Member -MemberType NoteProperty -Name Comparison -Value $comparison + + # Add the object to the output array + $OutputHashComparison += $Object + } + + # Return the output array + return $OutputHashComparison + + } +} + +# Function that shows the details of certificates. E.g, All intermediate certs, Leaf cert or the entire chain, depending on optional switch parameters +function Get-CertificateDetails { + # Use the param keyword to define the parameters + param ( + # Make the FilePath parameter mandatory and validate that it is a valid file path + [Parameter()] + [ValidateScript({ Test-Path -Path $_ -PathType Leaf })] + [System.String]$FilePath, + $X509Certificate2, + [System.Management.Automation.SwitchParameter]$IntermediateOnly, + [System.Management.Automation.SwitchParameter]$AllCertificates, + [System.Management.Automation.SwitchParameter]$LeafCertificate + ) + + if ($FilePath) { + # Get the certificate from the file path + $Cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $FilePath + } + # if file path isn't used and instead a X509Certificate2 is provided then assign it directly to the $Cert variable + elseif ($X509Certificate2) { + $Cert = $X509Certificate2 + } + + # Build the certificate chain + $Chain = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Chain + + # Set the chain policy properties + $chain.ChainPolicy.RevocationMode = 'NoCheck' + $chain.ChainPolicy.RevocationFlag = 'EndCertificateOnly' + $chain.ChainPolicy.VerificationFlags = 'NoFlag' + + [void]$Chain.Build($Cert) + + # Check the value of the switch parameters + if ($IntermediateOnly) { + # If IntermediateOnly is present, loop through the chain elements and display only the intermediate certificates + for ($i = 1; $i -lt $Chain.ChainElements.Count - 1; $i++) { + # Create a custom object with the certificate properties + $Element = $Chain.ChainElements[$i] + # Extract the data after CN= in the subject and issuer properties + $SubjectCN = ($Element.Certificate.Subject -split '(?:^|,)CN=|,')[1] + $IssuerCN = ($Element.Certificate.Issuer -split '(?:^|,)CN=|,')[1] + # Get the TBS value of the certificate using the Get-TBSCertificate function + $TbsValue = Get-TBSCertificate -cert $Element.Certificate + # Create a custom object with the extracted properties and the TBS value + $Obj = [pscustomobject]@{ + SubjectCN = $SubjectCN + IssuerCN = $issuerCN + NotAfter = $Element.Certificate.NotAfter + TBSValue = $TbsValue + } + # Display the object + Write-Output -InputObject $Obj + } + } + elseif ($AllCertificates) { + # If AllCertificates is present, loop through all chain elements and display all certificates + foreach ($Element in $Chain.ChainElements) { + # Create a custom object with the certificate properties + # Extract the data after CN= in the subject and issuer properties + $SubjectCN = ($Element.Certificate.Subject -split '(?:^|,)CN=|,')[1] + $IssuerCN = ($Element.Certificate.Issuer -split '(?:^|,)CN=|,')[1] + # Get the TBS value of the certificate using the Get-TBSCertificate function + $TbsValue = Get-TBSCertificate -cert $Element.Certificate + # Create a custom object with the extracted properties and the TBS value + $Obj = [pscustomobject]@{ + SubjectCN = $SubjectCN + IssuerCN = $IssuerCN + NotAfter = $element.Certificate.NotAfter + TBSValue = $TbsValue + } + # Display the object + Write-Output -InputObject $obj + } + } + elseif ($LeafCertificate) { + # If LeafCertificate is present, create a custom object with the leaf certificate properties + # Extract the data after CN= in the subject and issuer properties + $SubjectCN = ($Chain.ChainElements[0].Certificate.Subject -split '(?:^|,)CN=|,')[1] + $IssuerCN = ($Chain.ChainElements[0].Certificate.Issuer -split '(?:^|,)CN=|,')[1] + # Get the TBS value of the certificate using the Get-TBSCertificate function + $TbsValue = Get-TBSCertificate -cert $Chain.ChainElements[0].Certificate + # Create a custom object with the extracted properties and the TBS value + $Obj = [pscustomobject]@{ + SubjectCN = $SubjectCN + IssuerCN = $IssuerCN + NotAfter = $Chain.ChainElements[0].Certificate.NotAfter + TBSValue = $TbsValue + } + # Display the object + Write-Output -InputObject 'Leaf Certificate:' + Write-Output -InputObject $obj + } + else { + # If none of the switch parameters are present, display a message to inform the user of their options + Write-Output -InputObject 'Please specify one of the following switch parameters to get certificate details: -IntermediateOnly, -AllCertificates, or -LeafCertificate.' + } +} + diff --git a/WDACConfig/Utilities/Functions no longer in use/Remove-ZerosFromIDs.psm1 b/WDACConfig/Utilities/Functions no longer in use/Remove-ZerosFromIDs.psm1 new file mode 100644 index 000000000..d2301aa87 --- /dev/null +++ b/WDACConfig/Utilities/Functions no longer in use/Remove-ZerosFromIDs.psm1 @@ -0,0 +1,49 @@ +Function Remove-ZerosFromIDs { + <# + .SYNOPSIS + Can remove _0 from the ID and SignerId of all the elements in the policy xml file + #> + param( + [Parameter(Mandatory = $true)] + [ValidateScript({ Test-Path -Path $_ -PathType Leaf })] + [System.String]$FilePath + ) + # Load the xml file + [System.Xml.XmlDocument]$Xml = Get-Content -Path $FilePath + + # Get all the elements with ID attribute + $Elements = $Xml.SelectNodes('//*[@ID]') + + # Loop through the elements and replace _0 with empty string in the ID value and SignerId value + foreach ($Element in $Elements) { + $Element.ID = $Element.ID -replace '_0', '' + # Check if the element has child elements with SignerId attribute + if ($Element.HasChildNodes) { + # Get the child elements with SignerId attribute + $childElements = $Element.SelectNodes('.//*[@SignerId]') + # Loop through the child elements and replace _0 with empty string in the SignerId value + foreach ($childElement in $childElements) { + $childElement.SignerId = $childElement.SignerId -replace '_0', '' + } + } + } + + # Get the CiSigners element by name + $CiSigners = $Xml.SiPolicy.CiSigners + + # Check if the CiSigners element has child elements with SignerId attribute + if ($CiSigners.HasChildNodes) { + # Get the child elements with SignerId attribute + $CiSignersChildren = $CiSigners.ChildNodes + # Loop through the child elements and replace _0 with empty string in the SignerId value + foreach ($CiSignerChild in $CiSignersChildren) { + $CiSignerChild.SignerId = $CiSignerChild.SignerId -replace '_0', '' + } + } + + # Save the modified xml file + $Xml.Save($FilePath) +} + +# Export external facing functions only, prevent internal functions from getting exported +Export-ModuleMember -Function 'Remove-ZerosFromIDs' diff --git a/WDACConfig/Utilities/Invoke-WDACConfig.ps1 b/WDACConfig/Utilities/Invoke-WDACConfig.ps1 new file mode 100644 index 000000000..9a116a9fd --- /dev/null +++ b/WDACConfig/Utilities/Invoke-WDACConfig.ps1 @@ -0,0 +1,10 @@ +# This file is for launching WDACConfig module in VS Code so that it can attach its debugger to the process + +# Get the current folder of this script file +[System.String]$ScriptFilePath = ($MyInvocation.MyCommand.path | Split-Path -Parent) + +# Import the module into the current scope using the relative path of the module itself +Import-Module -FullyQualifiedName "$ScriptFilePath\..\WDACConfig Module Files\WDACConfig.psd1" -Force + +# Uncomment and replace with any cmdlet of the WDACConfig module that is going to be debugged +Confirm-WDACConfig diff --git a/WDACConfig/WDACConfig Module Files/Core/Confirm-WDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Confirm-WDACConfig.psm1 new file mode 100644 index 000000000..93426b3c5 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/Core/Confirm-WDACConfig.psm1 @@ -0,0 +1,170 @@ +Function Confirm-WDACConfig { + [CmdletBinding(DefaultParameterSetName = 'List Active Policies')] + Param( + [Alias('L')] + [Parameter(Mandatory = $false, ParameterSetName = 'List Active Policies')][System.Management.Automation.SwitchParameter]$ListActivePolicies, + [Alias('V')] + [Parameter(Mandatory = $false, ParameterSetName = 'Verify WDAC Status')][System.Management.Automation.SwitchParameter]$VerifyWDACStatus, + [Alias('S')] + [Parameter(Mandatory = $false, ParameterSetName = 'Check SmartAppControl Status')][System.Management.Automation.SwitchParameter]$CheckSmartAppControlStatus, + + [Parameter(Mandatory = $false, DontShow = $true)][System.Management.Automation.SwitchParameter]$DummyParameter + ) + + DynamicParam { + + # Add the dynamic parameters to the param dictionary + $ParamDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() + + if ($PSBoundParameters['ListActivePolicies']) { + + # Create a dynamic parameter for -OnlyBasePolicies + $OnlyBasePoliciesDynamicParameter = [System.Management.Automation.ParameterAttribute]@{ + Mandatory = $false + ParameterSetName = 'List Active Policies' + HelpMessage = 'Only List Base Policies' + } + + $ParamDictionary.Add('OnlyBasePolicies', [System.Management.Automation.RuntimeDefinedParameter]::new( + 'OnlyBasePolicies', + [System.Management.Automation.SwitchParameter], + [System.Management.Automation.ParameterAttribute[]]@($OnlyBasePoliciesDynamicParameter) + )) + + # Create a dynamic parameter for -OnlySupplementalPolicies + $OnlySupplementalPoliciesDynamicParameter = [System.Management.Automation.ParameterAttribute]@{ + Mandatory = $false + ParameterSetName = 'List Active Policies' + HelpMessage = 'Only List Supplemental Policies' + } + + $ParamDictionary.Add('OnlySupplementalPolicies', [System.Management.Automation.RuntimeDefinedParameter]::new( + 'OnlySupplementalPolicies', + [System.Management.Automation.SwitchParameter], + [System.Management.Automation.ParameterAttribute[]]@($OnlySupplementalPoliciesDynamicParameter) + )) + } + + # Create a dynamic parameter for -SkipVersionCheck, Adding this parameter as dynamic will make it appear at the end of the parameters + $SkipVersionCheckDynamicParameter = [System.Management.Automation.ParameterAttribute]@{ + Mandatory = $false + # To make this parameter available for all parameter sets + ParameterSetName = '__AllParameterSets' + HelpMessage = 'Skip Version Check' + } + + $ParamDictionary.Add('SkipVersionCheck', [System.Management.Automation.RuntimeDefinedParameter]::new( + 'SkipVersionCheck', + [System.Management.Automation.SwitchParameter], + [System.Management.Automation.ParameterAttribute[]]@($SkipVersionCheckDynamicParameter) + )) + + return $ParamDictionary + } + + begin { + # Detecting if Verbose switch is used + $PSBoundParameters.Verbose.IsPresent ? ([System.Boolean]$Verbose = $true) : ([System.Boolean]$Verbose = $false) | Out-Null + + # Importing the $PSDefaultParameterValues to the current session, prior to everything else + . "$ModuleRootPath\CoreExt\PSDefaultParameterValues.ps1" + + # Importing the required sub-modules + Write-Verbose -Message 'Importing the required sub-modules' + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Update-self.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Write-ColorfulText.psm1" -Force + + # Regular parameters are automatically bound to variables in the function scope + # Dynamic parameters however, are only available in the parameter dictionary, which is why we have to access them using $PSBoundParameters + # or assign them manually to another variable in the function's scope + [System.Management.Automation.SwitchParameter]$OnlyBasePolicies = $($PSBoundParameters['OnlyBasePolicies']) + [System.Management.Automation.SwitchParameter]$OnlySupplementalPolicies = $($PSBoundParameters['OnlySupplementalPolicies']) + [System.Management.Automation.SwitchParameter]$SkipVersionCheck = $($PSBoundParameters['SkipVersionCheck']) + + # if -SkipVersionCheck wasn't passed, run the updater + # Redirecting the Update-Self function's information Stream to $null because Write-Host + # Used by Write-ColorfulText outputs to both information stream and host console + if (-NOT $SkipVersionCheck) { Update-self 6> $null } + + # Script block to show only non-system Base policies + [System.Management.Automation.ScriptBlock]$OnlyBasePoliciesBLOCK = { + [System.Object[]]$BasePolicies = (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { $_.IsSystemPolicy -ne 'True' } | Where-Object -FilterScript { $_.PolicyID -eq $_.BasePolicyID } + Write-ColorfulText -Color Lavender -InputText "`nThere are currently $(($BasePolicies.count)) Non-system Base policies deployed" + $BasePolicies + } + # Script block to show only non-system Supplemental policies + [System.Management.Automation.ScriptBlock]$OnlySupplementalPoliciesBLOCK = { + [System.Object[]]$SupplementalPolicies = (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { $_.IsSystemPolicy -ne 'True' } | Where-Object -FilterScript { $_.PolicyID -ne $_.BasePolicyID } + Write-ColorfulText -Color Lavender -InputText "`nThere are currently $(($SupplementalPolicies.count)) Non-system Supplemental policies deployed`n" + $SupplementalPolicies + } + + # If no main parameter was passed, run all of them + if (!$ListActivePolicies -and !$VerifyWDACStatus -and !$CheckSmartAppControlStatus) { + $ListActivePolicies = $true + $VerifyWDACStatus = $true + $CheckSmartAppControlStatus = $true + } + } + + process { + if ($ListActivePolicies) { + if ($OnlyBasePolicies) { &$OnlyBasePoliciesBLOCK } + if ($OnlySupplementalPolicies) { &$OnlySupplementalPoliciesBLOCK } + if (!$OnlyBasePolicies -and !$OnlySupplementalPolicies) { &$OnlyBasePoliciesBLOCK; &$OnlySupplementalPoliciesBLOCK } + } + + if ($VerifyWDACStatus) { + Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard | Select-Object -Property *codeintegrity* | Format-List + Write-ColorfulText -Color Lavender -InputText "2 -> Enforced`n1 -> Audit mode`n0 -> Disabled/Not running`n" + } + + if ($CheckSmartAppControlStatus) { + Get-MpComputerStatus | Select-Object -Property SmartAppControlExpiration, SmartAppControlState + if ((Get-MpComputerStatus).SmartAppControlState -eq 'Eval') { + Write-ColorfulText -Color Pink -InputText "`nSmart App Control is in Evaluation mode." + } + elseif ((Get-MpComputerStatus).SmartAppControlState -eq 'On') { + Write-ColorfulText -Color Pink -InputText "`nSmart App Control is turned on." + } + elseif ((Get-MpComputerStatus).SmartAppControlState -eq 'Off') { + Write-ColorfulText -Color Pink -InputText "`nSmart App Control is turned off." + } + } + } + + <# +.SYNOPSIS + Shows the status of WDAC on the system, lists the currently deployed policies and shows the details about each of them. + It can also show the status of Smart App Control. +.LINK + https://github.com/HotCakeX/Harden-Windows-Security/wiki/Confirm-WDACConfig +.DESCRIPTION + Using official Microsoft methods, Show the status of WDAC (Windows Defender Application Control) on the system, list the current deployed policies and show details about each of them. +.COMPONENT + Windows Defender Application Control, ConfigCI PowerShell module +.FUNCTIONALITY + Using official Microsoft methods, Show the status of WDAC (Windows Defender Application Control) on the system, list the current deployed policies and show details about each of them. +.PARAMETER ListActivePolicies + Lists the currently deployed policies and shows details about each of them +.PARAMETER VerifyWDACStatus + Shows the status of WDAC (Windows Defender Application Control) on the system +.PARAMETER CheckSmartAppControlStatus + Checks the status of Smart App Control and reports the results on the console +.PARAMETER SkipVersionCheck + Can be used with any parameter to bypass the online version check - only to be used in rare cases +.PARAMETER DummyParameter + To hide the common parameters +.EXAMPLE + Confirm-WDACConfig -ListActivePolicies -OnlyBasePolicies +.EXAMPLE + Confirm-WDACConfig -ListActivePolicies -OnlySupplementalPolicies +.EXAMPLE + Confirm-WDACConfig -ListActivePolicies +.INPUTS + System.Management.Automation.SwitchParameter +.OUTPUTS + System.String + System.Object +#> +} diff --git a/WDACConfig/WDACConfig Module Files/Core/Deploy-SignedWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Deploy-SignedWDACConfig.psm1 new file mode 100644 index 000000000..bcd9cb3b3 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/Core/Deploy-SignedWDACConfig.psm1 @@ -0,0 +1,293 @@ +Function Deploy-SignedWDACConfig { + [CmdletBinding( + SupportsShouldProcess = $true, + PositionalBinding = $false, + ConfirmImpact = 'High' + )] + Param( + [ValidatePattern('\.xml$')] + [ValidateScript({ Test-Path -Path $_ -PathType 'Leaf' }, ErrorMessage = 'The path you selected is not a file path.')] + [parameter(Mandatory = $true)][System.String[]]$PolicyPaths, + + [Parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$Deploy, + + [ValidatePattern('\.cer$')] + [ValidateScript({ Test-Path -Path $_ -PathType 'Leaf' }, ErrorMessage = 'The path you selected is not a file path.')] + [parameter(Mandatory = $false)][System.String]$CertPath, + + [ValidateScript({ + [System.String[]]$Certificates = foreach ($Cert in (Get-ChildItem -Path 'Cert:\CurrentUser\my')) { + (($Cert.Subject -split ',' | Select-Object -First 1) -replace 'CN=', '').Trim() + } + $Certificates -contains $_ + }, ErrorMessage = "A certificate with the provided common name doesn't exist in the personal store of the user certificates." )] + [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)][System.String]$CertCN, + + [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] + [System.String]$SignToolPath, + + [Parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$SkipVersionCheck + ) + + begin { + # Detecting if Verbose switch is used + $PSBoundParameters.Verbose.IsPresent ? ([System.Boolean]$Verbose = $true) : ([System.Boolean]$Verbose = $false) | Out-Null + + # Importing the $PSDefaultParameterValues to the current session, prior to everything else + . "$ModuleRootPath\CoreExt\PSDefaultParameterValues.ps1" + + # Importing the required sub-modules + Write-Verbose -Message 'Importing the required sub-modules' + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Update-self.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Get-SignTool.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Confirm-CertCN.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Write-ColorfulText.psm1" -Force + + # if -SkipVersionCheck wasn't passed, run the updater + # Redirecting the Update-Self function's information Stream to $null because Write-Host + # Used by Write-ColorfulText outputs to both information stream and host console + if (-NOT $SkipVersionCheck) { Update-self 6> $null } + + #Region User-Configurations-Processing-Validation + # If any of these parameters, that are mandatory for all of the position 0 parameters, isn't supplied by user + if (!$SignToolPath -or !$CertPath -or !$CertCN) { + # Read User configuration file if it exists + $UserConfig = Get-Content -Path "$UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" -ErrorAction SilentlyContinue + if ($UserConfig) { + # Validate the Json file and read its content to make sure it's not corrupted + try { $UserConfig = $UserConfig | ConvertFrom-Json } + catch { + Write-Error -Message 'User Configuration Json file is corrupted, deleting it...' -ErrorAction Continue + Remove-CommonWDACConfig + } + } + } + + # Get SignToolPath from user parameter or user config file or auto-detect it + if ($SignToolPath) { + $SignToolPathFinal = Get-SignTool -SignToolExePath $SignToolPath + } # If it is null, then Get-SignTool will behave the same as if it was called without any arguments. + else { + $SignToolPathFinal = Get-SignTool -SignToolExePath ($UserConfig.SignToolCustomPath ?? $null) + } + + # If CertPath parameter wasn't provided by user + if (!$CertPath) { + if ($UserConfig.CertificatePath) { + # validate user config values for Certificate Path + if (Test-Path -Path $($UserConfig.CertificatePath)) { + # If the user config values are correct then use them + $CertPath = $UserConfig.CertificatePath + } + else { + throw 'The currently saved value for CertPath in user configurations is invalid.' + } + } + else { + throw 'CertPath parameter cannot be empty and no valid configuration was found for it.' + } + } + + # If CertCN was not provided by user + if (!$CertCN) { + if ($UserConfig.CertificateCommonName) { + # Check if the value in the User configuration file exists and is valid + if (Confirm-CertCN -CN $($UserConfig.CertificateCommonName)) { + # if it's valid then use it + $CertCN = $UserConfig.CertificateCommonName + } + else { + throw 'The currently saved value for CertCN in user configurations is invalid.' + } + } + else { + throw 'CertCN parameter cannot be empty and no valid configuration was found for it.' + } + } + #Endregion User-Configurations-Processing-Validation + } + + process { + foreach ($PolicyPath in $PolicyPaths) { + + Write-Verbose -Message "Gathering policy details from: $PolicyPath" + $Xml = [System.Xml.XmlDocument](Get-Content -Path $PolicyPath) + [System.String]$PolicyType = $Xml.SiPolicy.PolicyType + [System.String]$PolicyID = $Xml.SiPolicy.PolicyID + [System.String]$PolicyName = ($Xml.SiPolicy.Settings.Setting | Where-Object -FilterScript { $_.provider -eq 'PolicyInfo' -and $_.valuename -eq 'Name' -and $_.key -eq 'Information' }).value.string + [System.String[]]$PolicyRuleOptions = $Xml.SiPolicy.Rules.Rule.Option + + Write-Verbose -Message 'Removing any existing .CIP file of the same policy being signed and deployed if any in the current working directory' + Remove-Item -Path ".\$PolicyID.cip" -ErrorAction SilentlyContinue + + Write-Verbose -Message 'Checking if the policy type is Supplemental and if so, removing the -Supplemental parameter from the SignerRule command' + if ($PolicyType -eq 'Supplemental Policy') { + + Write-Verbose -Message 'Policy type is Supplemental' + + # Make sure -User is not added if the UMCI policy rule option doesn't exist in the policy, typically for Strict kernel mode policies + if ('Enabled:UMCI' -in $PolicyRuleOptions) { + Add-SignerRule -FilePath $PolicyPath -CertificatePath $CertPath -Update -User -Kernel + } + else { + Write-Verbose -Message 'UMCI policy rule option does not exist in the policy, typically for Strict kernel mode policies' + Add-SignerRule -FilePath $PolicyPath -CertificatePath $CertPath -Update -Kernel + } + } + else { + + Write-Verbose -Message 'Policy type is Base' + + # Make sure -User is not added if the UMCI policy rule option doesn't exist in the policy, typically for Strict kernel mode policies + if ('Enabled:UMCI' -in $PolicyRuleOptions) { + Add-SignerRule -FilePath $PolicyPath -CertificatePath $CertPath -Update -User -Kernel -Supplemental + } + else { + Write-Verbose -Message 'UMCI policy rule option does not exist in the policy, typically for Strict kernel mode policies' + Add-SignerRule -FilePath $PolicyPath -CertificatePath $CertPath -Update -Kernel -Supplemental + } + } + + Write-Verbose -Message 'Setting HVCI to Strict' + Set-HVCIOptions -Strict -FilePath $PolicyPath + + Write-Verbose -Message 'Removing the Unsigned mode option from the policy rules' + Set-RuleOption -FilePath $PolicyPath -Option 6 -Delete + + Write-Verbose -Message 'Converting the policy to .CIP file' + ConvertFrom-CIPolicy -XmlFilePath $PolicyPath -BinaryFilePath "$PolicyID.cip" | Out-Null + + # Configure the parameter splat + $ProcessParams = @{ + 'ArgumentList' = 'sign', '/v' , '/n', "`"$CertCN`"", '/p7', '.', '/p7co', '1.3.6.1.4.1.311.79.1', '/fd', 'certHash', ".\$PolicyID.cip" + 'FilePath' = $SignToolPathFinal + 'NoNewWindow' = $true + 'Wait' = $true + 'ErrorAction' = 'Stop' + } + # Hide the SignTool.exe's normal output unless -Verbose parameter was used + if (!$Verbose) { $ProcessParams['RedirectStandardOutput'] = 'NUL' } + + # Sign the files with the specified cert + Write-Verbose -Message 'Signing the policy with the specified certificate' + Start-Process @ProcessParams + + Write-Verbose -Message 'Making sure a .CIP file with the same name is not present in the current working directory' + Remove-Item -Path ".\$PolicyID.cip" -Force + + Write-Verbose -Message 'Renaming the .p7 file to .cip' + Rename-Item -Path "$PolicyID.cip.p7" -NewName "$PolicyID.cip" -Force + + if ($Deploy) { + + Write-Verbose -Message 'Deploying the policy' + &'C:\Windows\System32\CiTool.exe' --update-policy ".\$PolicyID.cip" -json | Out-Null + + Write-Host -Object 'policy with the following details has been Signed and Deployed in Enforced Mode:' -ForegroundColor Green + Write-ColorfulText -Color MintGreen -InputText "PolicyName = $PolicyName" + Write-ColorfulText -Color MintGreen -InputText "PolicyGUID = $PolicyID" + + Write-Verbose -Message 'Removing the .CIP file after deployment' + Remove-Item -Path ".\$PolicyID.cip" -Force + + #Region Detecting Strict Kernel mode policy and removing it from User Configs + if ('Enabled:UMCI' -notin $PolicyRuleOptions) { + + [System.String]$StrictKernelPolicyGUID = Get-CommonWDACConfig -StrictKernelPolicyGUID + [System.String]$StrictKernelNoFlightRootsPolicyGUID = Get-CommonWDACConfig -StrictKernelNoFlightRootsPolicyGUID + + if (($PolicyName -like '*Strict Kernel mode policy Enforced*')) { + + Write-Verbose -Message 'The deployed policy is Strict Kernel mode' + + if ($StrictKernelPolicyGUID) { + if ($($PolicyID.TrimStart('{').TrimEnd('}')) -eq $StrictKernelPolicyGUID) { + + Write-Verbose -Message 'Removing the GUID of the deployed Strict Kernel mode policy from the User Configs' + Remove-CommonWDACConfig -StrictKernelPolicyGUID | Out-Null + } + } + } + + elseif (($PolicyName -like '*Strict Kernel No Flights mode policy Enforced*')) { + + Write-Verbose -Message 'The deployed policy is Strict Kernel No Flights mode' + + if ($StrictKernelNoFlightRootsPolicyGUID) { + if ($($PolicyID.TrimStart('{').TrimEnd('}')) -eq $StrictKernelNoFlightRootsPolicyGUID) { + + Write-Verbose -Message 'Removing the GUID of the deployed Strict Kernel No Flights mode policy from the User Configs' + Remove-CommonWDACConfig -StrictKernelNoFlightRootsPolicyGUID | Out-Null + } + } + } + } + #Endregion Detecting Strict Kernel mode policy and removing it from User Configs + + # Show the question only for base policies. Don't show it for Strict kernel mode policies either + if (($PolicyType -ne 'Supplemental Policy') -and ($PolicyName -notlike '*Strict Kernel*')) { + + # Ask user question about whether or not to add the Signed policy xml file to the User Config Json for easier usage later + $UserInput = '' + while ($UserInput -notin 1, 2) { + $UserInput = $(Write-Host -Object 'Add the Signed policy xml file path just created to the User Configurations? Please enter 1 to Confirm or 2 to Skip.' -ForegroundColor Cyan ; Read-Host) + if ($UserInput -eq 1) { + Set-CommonWDACConfig -SignedPolicyPath $PolicyPath + Write-ColorfulText -Color HotPink -InputText "Added $PolicyPath to the User Configuration file." + } + elseif ($UserInput -eq 2) { + Write-ColorfulText -Color Pink -InputText 'Skipping...' + } + else { + Write-Warning -Message 'Invalid input. Please enter 1 or 2 only.' + } + } + } + } + + else { + Write-Host -Object 'policy with the following details has been Signed and is ready for deployment:' -ForegroundColor Green + Write-ColorfulText -Color MintGreen -InputText "PolicyName = $PolicyName" + Write-ColorfulText -Color MintGreen -InputText "PolicyGUID = $PolicyID`n" + } + } + } + + <# +.SYNOPSIS + Signs and Deploys WDAC policies, accepts signed or unsigned policies and deploys them +.LINK + https://github.com/HotCakeX/Harden-Windows-Security/wiki/Deploy-SignedWDACConfig +.DESCRIPTION + Using official Microsoft methods, Signs and Deploys WDAC policies, accepts signed or unsigned policies and deploys them (Windows Defender Application Control) +.COMPONENT + Windows Defender Application Control, ConfigCI PowerShell module +.FUNCTIONALITY + Using official Microsoft methods, Signs and Deploys WDAC policies, accepts signed or unsigned policies and deploys them (Windows Defender Application Control) +.PARAMETER CertPath + Path to the certificate .cer file +.PARAMETER PolicyPaths + Path to the policy xml files that are going to be signed +.PARAMETER CertCN + Certificate common name +.PARAMETER SignToolPath + Path to the SignTool.exe - optional parameter +.PARAMETER Deploy + Indicates that the cmdlet will deploy the signed policy on the current system +.PARAMETER SkipVersionCheck + Can be used with any parameter to bypass the online version check - only to be used in rare cases +.INPUTS + System.String + System.String[] + System.Management.Automation.SwitchParameter +.OUTPUTS + System.String +#> +} + +# Importing argument completer ScriptBlocks +. "$ModuleRootPath\Resources\ArgumentCompleters.ps1" +Register-ArgumentCompleter -CommandName 'Deploy-SignedWDACConfig' -ParameterName 'CertCN' -ScriptBlock $ArgumentCompleterCertificateCN +Register-ArgumentCompleter -CommandName 'Deploy-SignedWDACConfig' -ParameterName 'PolicyPaths' -ScriptBlock $ArgumentCompleterPolicyPaths +Register-ArgumentCompleter -CommandName 'Deploy-SignedWDACConfig' -ParameterName 'CertPath' -ScriptBlock $ArgumentCompleterCerFilePathsPicker +Register-ArgumentCompleter -CommandName 'Deploy-SignedWDACConfig' -ParameterName 'SignToolPath' -ScriptBlock $ArgumentCompleterExeFilePathsPicker diff --git a/WDACConfig/WDACConfig Module Files/Core/Edit-SignedWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Edit-SignedWDACConfig.psm1 new file mode 100644 index 000000000..e74616e87 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/Core/Edit-SignedWDACConfig.psm1 @@ -0,0 +1,1287 @@ +Function Edit-SignedWDACConfig { + [CmdletBinding( + DefaultParameterSetName = 'Allow New Apps Audit Events', + SupportsShouldProcess = $true, + PositionalBinding = $false, + ConfirmImpact = 'High' + )] + Param( + [Alias('E')] + [Parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')][System.Management.Automation.SwitchParameter]$AllowNewAppsAuditEvents, + [Alias('A')] + [Parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')][System.Management.Automation.SwitchParameter]$AllowNewApps, + [Alias('M')] + [Parameter(Mandatory = $false, ParameterSetName = 'Merge Supplemental Policies')][System.Management.Automation.SwitchParameter]$MergeSupplementalPolicies, + [Alias('U')] + [Parameter(Mandatory = $false, ParameterSetName = 'Update Base Policy')][System.Management.Automation.SwitchParameter]$UpdateBasePolicy, + + [ValidatePattern('^[a-zA-Z0-9 ]+$', ErrorMessage = 'The Supplemental Policy Name can only contain alphanumeric and space characters.')] + [Parameter(Mandatory = $true, ParameterSetName = 'Allow New Apps Audit Events', ValueFromPipelineByPropertyName = $true)] + [Parameter(Mandatory = $true, ParameterSetName = 'Allow New Apps', ValueFromPipelineByPropertyName = $true)] + [Parameter(Mandatory = $true, ParameterSetName = 'Merge Supplemental Policies', ValueFromPipelineByPropertyName = $true)] + [System.String]$SuppPolicyName, + + [ValidatePattern('\.xml$')] + [ValidateScript({ Test-Path -Path $_ -PathType 'Leaf' }, ErrorMessage = 'The path you selected is not a file path.')] + [Parameter(Mandatory = $true, ParameterSetName = 'Merge Supplemental Policies', ValueFromPipelineByPropertyName = $true)] + [System.String[]]$SuppPolicyPaths, + + [Parameter(Mandatory = $false, ParameterSetName = 'Merge Supplemental Policies')] + [System.Management.Automation.SwitchParameter]$KeepOldSupplementalPolicies, + + [ValidateSet([BasePolicyNamez])] + [Parameter(Mandatory = $true, ParameterSetName = 'Update Base Policy')] + [System.String[]]$CurrentBasePolicyName, + + [ValidateSet('AllowMicrosoft_Plus_Block_Rules', 'Lightly_Managed_system_Policy', 'DefaultWindows_WithBlockRules')] + [Parameter(Mandatory = $true, ParameterSetName = 'Update Base Policy')] + [System.String]$NewBasePolicyType, + + [ValidatePattern('\.cer$')] + [ValidateScript({ Test-Path -Path $_ -PathType 'Leaf' }, ErrorMessage = 'The path you selected is not a file path.')] + [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] + [System.String]$CertPath, + + [ValidatePattern('\.xml$')] + [ValidateScript({ + # Validate each Policy file in PolicyPaths parameter to make sure the user isn't accidentally trying to + # Edit an Unsigned policy using Edit-SignedWDACConfig cmdlet which is only made for Signed policies + $_ | ForEach-Object -Process { + $XmlTest = [System.Xml.XmlDocument](Get-Content -Path $_) + $RedFlag1 = $XmlTest.SiPolicy.SupplementalPolicySigners.SupplementalPolicySigner.SignerId + $RedFlag2 = $XmlTest.SiPolicy.UpdatePolicySigners.UpdatePolicySigner.SignerId + $RedFlag3 = $XmlTest.SiPolicy.PolicyID + $CurrentPolicyIDs = ((&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { $_.IsSystemPolicy -ne 'True' }).policyID | ForEach-Object -Process { "{$_}" } + if ($RedFlag1 -or $RedFlag2) { + # Ensure the selected base policy xml file is deployed + if ($CurrentPolicyIDs -contains $RedFlag3) { + return $True + } + else { throw "The currently selected policy xml file isn't deployed." } + } + # This throw is shown only when User added a Signed policy xml file for Unsigned policy file path property in user configuration file + # Without this, the error shown would be vague: The variable cannot be validated because the value System.String[] is not a valid value for the PolicyPaths variable. + else { throw 'The policy xml file in User Configurations for SignedPolicyPath is Unsigned policy.' } + } + }, ErrorMessage = 'The selected policy xml file is Unsigned. Please use Edit-WDACConfig cmdlet to edit Unsigned policies.')] + [Parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events', ValueFromPipelineByPropertyName = $true)] + [Parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps', ValueFromPipelineByPropertyName = $true)] + [Parameter(Mandatory = $false, ParameterSetName = 'Merge Supplemental Policies', ValueFromPipelineByPropertyName = $true)] + [System.String[]]$PolicyPaths, + + [ValidateScript({ + [System.String[]]$Certificates = foreach ($cert in (Get-ChildItem -Path 'Cert:\CurrentUser\my')) { + (($cert.Subject -split ',' | Select-Object -First 1) -replace 'CN=', '').Trim() + } + $Certificates -contains $_ + }, ErrorMessage = "A certificate with the provided common name doesn't exist in the personal store of the user certificates." )] + [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] + [System.String]$CertCN, + + [ValidateRange(1024KB, 18014398509481983KB)][Parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] + [System.Int64]$LogSize, + + [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] + [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')] + [System.Management.Automation.SwitchParameter]$NoScript, + + [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] + [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')] + [System.Management.Automation.SwitchParameter]$NoUserPEs, + + [ValidateSet('OriginalFileName', 'InternalName', 'FileDescription', 'ProductName', 'PackageFamilyName', 'FilePath')] + [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] + [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')] + [System.String]$SpecificFileNameLevel, + + [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')][System.Management.Automation.SwitchParameter]$IncludeDeletedFiles, + + [ValidateSet([Levelz])] + [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] + [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')] + [System.String]$Level = 'FilePublisher', + + [ValidateSet([Fallbackz])] + [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] + [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')] + [System.String[]]$Fallbacks = 'Hash', + + [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] + [System.String]$SignToolPath, + + [Parameter(Mandatory = $false, ParameterSetName = 'Update Base Policy')] + [System.Management.Automation.SwitchParameter]$RequireEVSigners, + + [Parameter(Mandatory = $false)] + [System.Management.Automation.SwitchParameter]$SkipVersionCheck + ) + + begin { + # Detecting if Verbose switch is used + $PSBoundParameters.Verbose.IsPresent ? ([System.Boolean]$Verbose = $true) : ([System.Boolean]$Verbose = $false) | Out-Null + + # Importing the $PSDefaultParameterValues to the current session, prior to everything else + . "$ModuleRootPath\CoreExt\PSDefaultParameterValues.ps1" + + # Importing the required sub-modules + Write-Verbose -Message 'Importing the required sub-modules' + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Update-self.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Get-SignTool.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Confirm-CertCN.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Get-GlobalRootDrives.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Write-ColorfulText.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Set-LogSize.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Test-FilePath.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Get-AuditEventLogsProcessing.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\New-EmptyPolicy.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Get-RuleRefs.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Get-FileRules.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Get-BlockRulesMeta.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\New-SnapBackGuarantee.psm1" -Force + + # if -SkipVersionCheck wasn't passed, run the updater + # Redirecting the Update-Self function's information Stream to $null because Write-Host + # Used by Write-ColorfulText outputs to both information stream and host console + if (-NOT $SkipVersionCheck) { Update-self 6> $null } + + #Region User-Configurations-Processing-Validation + # If any of these parameters, that are mandatory for all of the position 0 parameters, isn't supplied by user + if (!$PolicyPaths -or !$SignToolPath -or !$CertPath -or !$CertCN) { + # Read User configuration file if it exists + $UserConfig = Get-Content -Path "$UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" -ErrorAction SilentlyContinue + if ($UserConfig) { + # Validate the Json file and read its content to make sure it's not corrupted + try { $UserConfig = $UserConfig | ConvertFrom-Json } + catch { + Write-Error -Message 'User Configurations Json file is corrupted, deleting it...' -ErrorAction Continue + Remove-CommonWDACConfig + } + } + } + + # Get SignToolPath from user parameter or user config file or auto-detect it + if ($SignToolPath) { + $SignToolPathFinal = Get-SignTool -SignToolExePath $SignToolPath + } # If it is null, then Get-SignTool will behave the same as if it was called without any arguments. + else { + $SignToolPathFinal = Get-SignTool -SignToolExePath ($UserConfig.SignToolCustomPath ?? $null) + } + + # If CertPath parameter wasn't provided by user + if (!$CertPath) { + if ($UserConfig.CertificatePath) { + # validate user config values for Certificate Path + if (Test-Path -Path $($UserConfig.CertificatePath)) { + # If the user config values are correct then use them + $CertPath = $UserConfig.CertificatePath + } + else { + throw 'The currently saved value for CertPath in user configurations is invalid.' + } + } + else { + throw 'CertPath parameter cannot be empty and no valid configuration was found for it.' + } + } + + # If CertCN was not provided by user + if (!$CertCN) { + if ($UserConfig.CertificateCommonName) { + # Check if the value in the User configuration file exists and is valid + if (Confirm-CertCN -CN $($UserConfig.CertificateCommonName)) { + # if it's valid then use it + $CertCN = $UserConfig.CertificateCommonName + } + else { + throw 'The currently saved value for CertCN in user configurations is invalid.' + } + } + else { + throw 'CertCN parameter cannot be empty and no valid configuration was found for it.' + } + } + + # If PolicyPaths has no values + if (!$PolicyPaths) { + # make sure the ParameterSet being used has PolicyPaths parameter - Then enforces "mandatory" attribute for the parameter + if ($PSCmdlet.ParameterSetName -in 'Allow New Apps Audit Events', 'Allow New Apps', 'Merge Supplemental Policies') { + if ($UserConfig.SignedPolicyPath) { + # validate each policyPath read from user config file + if (Test-Path -Path $($UserConfig.SignedPolicyPath)) { + $PolicyPaths = $UserConfig.SignedPolicyPath + } + else { + throw 'The currently saved value for SignedPolicyPath in user configurations is invalid.' + } + } + else { + throw 'PolicyPaths parameter cannot be empty and no valid configuration was found for SignedPolicyPath.' + } + } + } + #Endregion User-Configurations-Processing-Validation + + # Detecting if Debug switch is used, will do debugging actions based on that + $PSBoundParameters.Debug.IsPresent ? ([System.Boolean]$Debug = $true) : ([System.Boolean]$Debug = $false) | Out-Null + + # argument tab auto-completion and ValidateSet for Policy names + Class BasePolicyNamez : System.Management.Automation.IValidateSetValuesGenerator { + [System.String[]] GetValidValues() { + $BasePolicyNamez = ((&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { $_.IsSystemPolicy -ne 'True' } | Where-Object -FilterScript { $_.PolicyID -eq $_.BasePolicyID }).Friendlyname + return [System.String[]]$BasePolicyNamez + } + } + + # argument tab auto-completion and ValidateSet for Fallbacks + Class Fallbackz : System.Management.Automation.IValidateSetValuesGenerator { + [System.String[]] GetValidValues() { + $Fallbackz = ('Hash', 'FileName', 'SignedVersion', 'Publisher', 'FilePublisher', 'LeafCertificate', 'PcaCertificate', 'RootCertificate', 'WHQL', 'WHQLPublisher', 'WHQLFilePublisher', 'PFN', 'FilePath', 'None') + return [System.String[]]$Fallbackz + } + } + + # argument tab auto-completion and ValidateSet for level + Class Levelz : System.Management.Automation.IValidateSetValuesGenerator { + [System.String[]] GetValidValues() { + $Levelz = ('Hash', 'FileName', 'SignedVersion', 'Publisher', 'FilePublisher', 'LeafCertificate', 'PcaCertificate', 'RootCertificate', 'WHQL', 'WHQLPublisher', 'WHQLFilePublisher', 'PFN', 'FilePath', 'None') + return [System.String[]]$Levelz + } + } + + function Update-BasePolicyToEnforced { + <# + .SYNOPSIS + A helper function used to redeploy the base policy in Enforced mode + .INPUTS + None. This function uses the global variables $PolicyName and $PolicyID + .OUTPUTS + System.String + #> + [CmdletBinding()] + param() + + # Deploy Enforced mode CIP + &'C:\Windows\System32\CiTool.exe' --update-policy '.\EnforcedMode.cip' -json | Out-Null + Write-ColorfulText -Color Lavender -InputText 'The Base policy with the following details has been Re-Signed and Re-Deployed in Enforced Mode:' + Write-ColorfulText -Color MintGreen -InputText "PolicyName = $PolicyName" + Write-ColorfulText -Color MintGreen -InputText "PolicyGUID = $PolicyID" + # Remove Enforced Mode CIP + Remove-Item -Path '.\EnforcedMode.cip' -Force + } + } + + process { + + if ($AllowNewApps) { + # remove any possible files from previous runs + Write-Verbose -Message 'Removing any possible files from previous runs' + Remove-Item -Path '.\ProgramDir_ScanResults*.xml' -Force -ErrorAction SilentlyContinue + Remove-Item -Path ".\SupplementalPolicy $SuppPolicyName.xml" -Force -ErrorAction SilentlyContinue + + # An empty array that holds the Policy XML files - This array will eventually be used to create the final Supplemental policy + [System.Object[]]$PolicyXMLFilesArray = @() + + #Initiate Live Audit Mode + + foreach ($PolicyPath in $PolicyPaths) { + # Creating a copy of the original policy in Temp folder so that the original one will be unaffected + Write-Verbose -Message 'Creating a copy of the original policy in Temp folder so that the original one will be unaffected' + # Get the policy file name + [System.String]$PolicyFileName = Split-Path -Path $PolicyPath -Leaf + # make sure no file with the same name already exists in Temp folder + Remove-Item -Path "$UserTempDirectoryPath\$PolicyFileName" -Force -ErrorAction SilentlyContinue + Copy-Item -Path $PolicyPath -Destination $UserTempDirectoryPath -Force + [System.String]$PolicyPath = "$UserTempDirectoryPath\$PolicyFileName" + + Write-Verbose -Message 'Retrieving the Base policy name and ID' + $Xml = [System.Xml.XmlDocument](Get-Content -Path $PolicyPath) + [System.String]$PolicyID = $Xml.SiPolicy.PolicyID + [System.String]$PolicyName = ($Xml.SiPolicy.Settings.Setting | Where-Object -FilterScript { $_.provider -eq 'PolicyInfo' -and $_.valuename -eq 'Name' -and $_.key -eq 'Information' }).value.string + + # Remove any cip file if there is any + Write-Verbose -Message 'Removing any cip file if there is any in the current working directory' + Remove-Item -Path '.\*.cip' -Force -ErrorAction SilentlyContinue + + Write-Verbose -Message 'Creating Audit Mode CIP' + # Remove Unsigned policy rule option + Set-RuleOption -FilePath $PolicyPath -Option 6 -Delete + # Add Audit mode policy rule option + Set-RuleOption -FilePath $PolicyPath -Option 3 + # Create CIP for Audit Mode + ConvertFrom-CIPolicy -XmlFilePath $PolicyPath -BinaryFilePath '.\AuditMode.cip' | Out-Null + + Write-Verbose -Message 'Creating Enforced Mode CIP' + # Remove Unsigned policy rule option + Set-RuleOption -FilePath $PolicyPath -Option 6 -Delete + # Remove Audit mode policy rule option + Set-RuleOption -FilePath $PolicyPath -Option 3 -Delete + # Create CIP for Enforced Mode + ConvertFrom-CIPolicy -XmlFilePath $PolicyPath -BinaryFilePath '.\EnforcedMode.cip' | Out-Null + + # Sign both CIPs + '.\AuditMode.cip', '.\EnforcedMode.cip' | ForEach-Object -Process { + # Configure the parameter splat + $ProcessParams = @{ + 'ArgumentList' = 'sign', '/v' , '/n', "`"$CertCN`"", '/p7', '.', '/p7co', '1.3.6.1.4.1.311.79.1', '/fd', 'certHash', "`"$_`"" + 'FilePath' = $SignToolPathFinal + 'NoNewWindow' = $true + 'Wait' = $true + 'ErrorAction' = 'Stop' + } # Only show the output of SignTool if Verbose switch is used + if (!$Verbose) { $ProcessParams['RedirectStandardOutput'] = 'NUL' } + # Sign the files with the specified cert + Start-Process @ProcessParams + } + + Write-Verbose -Message 'Removing the unsigned CIPs' + Remove-Item -Path '.\EnforcedMode.cip' -Force + Remove-Item -Path '.\AuditMode.cip' -Force + + Write-Verbose -Message 'Renaming the signed CIPs to remove the .p7 extension' + Rename-Item -Path '.\EnforcedMode.cip.p7' -NewName '.\EnforcedMode.cip' -Force + Rename-Item -Path '.\AuditMode.cip.p7' -NewName '.\AuditMode.cip' -Force + + #Region Snap-Back-Guarantee + Write-Verbose -Message 'Creating Enforced Mode SnapBack guarantee' + New-SnapBackGuarantee -Location (Get-Location).Path + + # Deploy the Audit mode CIP + Write-Verbose -Message 'Deploying the Audit mode CIP' + &'C:\Windows\System32\CiTool.exe' --update-policy '.\AuditMode.cip' -json | Out-Null + + Write-ColorfulText -Color Lavender -InputText 'The Base policy with the following details has been Re-Signed and Re-Deployed in Audit Mode:' + Write-ColorfulText -Color MintGreen -InputText "PolicyName = $PolicyName" + Write-ColorfulText -Color MintGreen -InputText "PolicyGUID = $PolicyID" + + # Remove the Audit Mode CIP + Remove-Item -Path '.\AuditMode.cip' -Force + #Endregion Snap-Back-Guarantee + + # A Try-Catch-Finally block so that if any errors occur, the Base policy will be Re-deployed in enforced mode + Try { + #Region User-Interaction + Write-ColorfulText -Color Pink -InputText 'Audit mode deployed, start installing your programs now' + Write-ColorfulText -Color HotPink -InputText 'When you have finished installing programs, Press Enter to start selecting program directories to scan' + Pause + + # Store the program paths that user browses for in an array + [System.IO.DirectoryInfo[]]$ProgramsPaths = @() + Write-Host -Object 'Select program directories to scan' -ForegroundColor Cyan + + # Showing folder picker GUI to the user for folder path selection + do { + [System.Reflection.Assembly]::LoadWithPartialName('System.windows.forms') | Out-Null + [System.Windows.Forms.FolderBrowserDialog]$OBJ = New-Object -TypeName System.Windows.Forms.FolderBrowserDialog + $OBJ.InitialDirectory = "$env:SystemDrive" + $OBJ.Description = $Description + [System.Windows.Forms.Form]$Spawn = New-Object -TypeName System.Windows.Forms.Form -Property @{TopMost = $true } + [System.String]$Show = $OBJ.ShowDialog($Spawn) + If ($Show -eq 'OK') { $ProgramsPaths += $OBJ.SelectedPath } + Else { break } + } + while ($true) + #Endregion User-Interaction + + # Make sure User browsed for at least 1 directory + # Exit the operation if user didn't select any folder paths + if ($ProgramsPaths.count -eq 0) { + Write-Host -Object 'No program folder was selected, reverting the changes and quitting...' -ForegroundColor Red + # Causing break here to stop operation. Finally block will be triggered to Re-Deploy Base policy in Enforced mode + break + } + } + catch { + # Show any extra info about any possible error that might've occurred + Throw $_ + } + finally { + # Deploy Enforced mode CIP + Write-Verbose -Message 'Finally Block Running' + Update-BasePolicyToEnforced + + # Enforced Mode Snapback removal after base policy has already been successfully re-enforced + Write-Verbose -Message 'Removing the SnapBack guarantee because the base policy has been successfully re-enforced' + Unregister-ScheduledTask -TaskName 'EnforcedModeSnapBack' -Confirm:$false + Remove-Item -Path 'C:\EnforcedModeSnapBack.cmd' -Force + } + + Write-Host -Object 'Here are the paths you selected:' -ForegroundColor Yellow + $ProgramsPaths | ForEach-Object -Process { $_.FullName } + + # Scan each of the folder paths that user selected + Write-Verbose -Message 'Scanning each of the folder paths that user selected' + for ($i = 0; $i -lt $ProgramsPaths.Count; $i++) { + + # Creating a hash table to dynamically add parameters based on user input and pass them to New-Cipolicy cmdlet + [System.Collections.Hashtable]$UserInputProgramFoldersPolicyMakerHashTable = @{ + FilePath = ".\ProgramDir_ScanResults$($i).xml" + ScanPath = $ProgramsPaths[$i] + Level = $Level + Fallback = $Fallbacks + MultiplePolicyFormat = $true + UserWriteablePaths = $true + AllowFileNameFallbacks = $true + } + # Assess user input parameters and add the required parameters to the hash table + if ($SpecificFileNameLevel) { $UserInputProgramFoldersPolicyMakerHashTable['SpecificFileNameLevel'] = $SpecificFileNameLevel } + if ($NoScript) { $UserInputProgramFoldersPolicyMakerHashTable['NoScript'] = $true } + if (!$NoUserPEs) { $UserInputProgramFoldersPolicyMakerHashTable['UserPEs'] = $true } + + # Create the supplemental policy via parameter splatting + Write-Verbose -Message "Currently scanning: $($ProgramsPaths[$i])" + New-CIPolicy @UserInputProgramFoldersPolicyMakerHashTable + } + + # Merge-CiPolicy accepts arrays - collecting all the policy files created by scanning user specified folders + Write-Verbose -Message 'Collecting all the policy files created by scanning user specified folders' + + foreach ($file in (Get-ChildItem -File -Path '.\' -Filter 'ProgramDir_ScanResults*.xml')) { + $PolicyXMLFilesArray += $file.FullName + } + + Write-Verbose -Message 'The following policy xml files are going to be merged into the final Supplemental policy and be deployed on the system:' + $PolicyXMLFilesArray | ForEach-Object -Process { Write-Verbose -Message "$_" } + + # Merge all of the policy XML files in the array into the final Supplemental policy + Write-Verbose -Message 'Merging all of the policy XML files in the array into the final Supplemental policy' + Merge-CIPolicy -PolicyPaths $PolicyXMLFilesArray -OutputFilePath ".\SupplementalPolicy $SuppPolicyName.xml" | Out-Null + + Write-Verbose -Message 'Removing the ProgramDir_ScanResults* xml files' + Remove-Item -Path '.\ProgramDir_ScanResults*.xml' -Force + + #Region Supplemental-policy-processing-and-deployment + Write-Verbose -Message 'Supplemental policy processing and deployment' + + Write-Verbose -Message 'Getting the path of the Supplemental policy' + [System.String]$SuppPolicyPath = ".\SupplementalPolicy $SuppPolicyName.xml" + + Write-Verbose -Message 'Converting the policy to a Supplemental policy type and resetting its ID' + [System.String]$SuppPolicyID = Set-CIPolicyIdInfo -FilePath $SuppPolicyPath -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" -ResetPolicyID -BasePolicyToSupplementPath $PolicyPath + $SuppPolicyID = $SuppPolicyID.Substring(11) + + Write-Verbose -Message 'Adding signer rule to the Supplemental policy' + Add-SignerRule -FilePath $SuppPolicyPath -CertificatePath $CertPath -Update -User -Kernel + + # Make sure policy rule options that don't belong to a Supplemental policy don't exist + Write-Verbose -Message 'Making sure policy rule options that do not belong to a Supplemental policy do not exist' + @(0, 1, 2, 3, 4, 6, 8, 9, 10, 11, 12, 15, 16, 17, 19, 20) | ForEach-Object -Process { Set-RuleOption -FilePath $SuppPolicyPath -Option $_ -Delete } + + Write-Verbose -Message 'Setting HVCI to Strict' + Set-HVCIOptions -Strict -FilePath $SuppPolicyPath + + Write-Verbose -Message 'Setting the Supplemental policy version to 1.0.0.0' + Set-CIPolicyVersion -FilePath $SuppPolicyPath -Version '1.0.0.0' + + Write-Verbose -Message 'Converting the Supplemental policy to a CIP file' + ConvertFrom-CIPolicy -XmlFilePath $SuppPolicyPath -BinaryFilePath "$SuppPolicyID.cip" | Out-Null + + # Configure the parameter splat + $ProcessParams = @{ + 'ArgumentList' = 'sign', '/v' , '/n', "`"$CertCN`"", '/p7', '.', '/p7co', '1.3.6.1.4.1.311.79.1', '/fd', 'certHash', ".\$SuppPolicyID.cip" + 'FilePath' = $SignToolPathFinal + 'NoNewWindow' = $true + 'Wait' = $true + 'ErrorAction' = 'Stop' + } # Only show the output of SignTool if Verbose switch is used + if (!$Verbose) { $ProcessParams['RedirectStandardOutput'] = 'NUL' } + + # Sign the files with the specified cert + Write-Verbose -Message 'Signing the Supplemental policy with the specified cert' + Start-Process @ProcessParams + + Write-Verbose -Message 'Removing the unsigned Supplemental policy file' + Remove-Item -Path ".\$SuppPolicyID.cip" -Force + + Write-Verbose -Message 'Renaming the signed Supplemental policy file to remove the .p7 extension' + Rename-Item -Path "$SuppPolicyID.cip.p7" -NewName "$SuppPolicyID.cip" -Force + + Write-Verbose -Message 'Deploying the Supplemental policy' + &'C:\Windows\System32\CiTool.exe' --update-policy ".\$SuppPolicyID.cip" -json | Out-Null + + Write-ColorfulText -Color Lavender -InputText 'Supplemental policy with the following details has been Signed and Deployed in Enforced Mode:' + Write-ColorfulText -Color MintGreen -InputText "SupplementalPolicyName = $SuppPolicyName" + Write-ColorfulText -Color MintGreen -InputText "SupplementalPolicyGUID = $SuppPolicyID" + + Write-Verbose -Message 'Removing the signed Supplemental policy CIP file after deployment' + Remove-Item -Path ".\$SuppPolicyID.cip" -Force + + # Remove the policy xml file in Temp folder we created earlier + Remove-Item -Path $PolicyPath -Force + + #Endregion Supplemental-policy-processing-and-deployment + } + } + + if ($AllowNewAppsAuditEvents) { + # Change Code Integrity event logs size + if ($AllowNewAppsAuditEvents -and $LogSize) { + Write-Verbose -Message 'Changing Code Integrity event logs size' + Set-LogSize -LogSize $LogSize + } + + # Make sure there is no leftover from previous runs + Write-Verbose -Message 'Removing any possible files from previous runs' + Remove-Item -Path '.\ProgramDir_ScanResults*.xml' -Force -ErrorAction SilentlyContinue + Remove-Item -Path ".\SupplementalPolicy $SuppPolicyName.xml" -Force -ErrorAction SilentlyContinue + + # Get the current date so that instead of the entire event viewer logs, only audit logs created after running this module will be captured + Write-Verbose -Message 'Getting the current date' + [System.DateTime]$Date = Get-Date + + # An empty array that holds the Policy XML files - This array will eventually be used to create the final Supplemental policy + [System.Object[]]$PolicyXMLFilesArray = @() + + #Initiate Live Audit Mode + + foreach ($PolicyPath in $PolicyPaths) { + # Creating a copy of the original policy in Temp folder so that the original one will be unaffected + Write-Verbose -Message 'Creating a copy of the original policy in Temp folder so that the original one will be unaffected' + # Get the policy file name + [System.String]$PolicyFileName = Split-Path -Path $PolicyPath -Leaf + # make sure no file with the same name already exists in Temp folder + Remove-Item -Path "$UserTempDirectoryPath\$PolicyFileName" -Force -ErrorAction SilentlyContinue + Copy-Item -Path $PolicyPath -Destination $UserTempDirectoryPath -Force + [System.String]$PolicyPath = "$UserTempDirectoryPath\$PolicyFileName" + + Write-Verbose -Message 'Retrieving the Base policy name and ID' + $Xml = [System.Xml.XmlDocument](Get-Content -Path $PolicyPath) + [System.String]$PolicyID = $Xml.SiPolicy.PolicyID + [System.String]$PolicyName = ($Xml.SiPolicy.Settings.Setting | Where-Object -FilterScript { $_.provider -eq 'PolicyInfo' -and $_.valuename -eq 'Name' -and $_.key -eq 'Information' }).value.string + + # Remove any cip file if there is any + Write-Verbose -Message 'Removing any cip file if there is any in the current working directory' + Remove-Item -Path '.\*.cip' -Force -ErrorAction SilentlyContinue + + Write-Verbose -Message 'Creating Audit Mode CIP' + # Remove Unsigned policy rule option + Set-RuleOption -FilePath $PolicyPath -Option 6 -Delete + # Add Audit mode policy rule option + Set-RuleOption -FilePath $PolicyPath -Option 3 + # Create CIP for Audit Mode + ConvertFrom-CIPolicy -XmlFilePath $PolicyPath -BinaryFilePath '.\AuditMode.cip' | Out-Null + + Write-Verbose -Message 'Creating Enforced Mode CIP' + # Remove Unsigned policy rule option + Set-RuleOption -FilePath $PolicyPath -Option 6 -Delete + # Remove Audit mode policy rule option + Set-RuleOption -FilePath $PolicyPath -Option 3 -Delete + # Create CIP for Enforced Mode + ConvertFrom-CIPolicy -XmlFilePath $PolicyPath -BinaryFilePath '.\EnforcedMode.cip' | Out-Null + + # Sign both CIPs + '.\AuditMode.cip', '.\EnforcedMode.cip' | ForEach-Object -Process { + # Configure the parameter splat + $ProcessParams = @{ + 'ArgumentList' = 'sign', '/v' , '/n', "`"$CertCN`"", '/p7', '.', '/p7co', '1.3.6.1.4.1.311.79.1', '/fd', 'certHash', "`"$_`"" + 'FilePath' = $SignToolPathFinal + 'NoNewWindow' = $true + 'Wait' = $true + 'ErrorAction' = 'Stop' + } # Only show the output of SignTool if Verbose switch is used + if (!$Verbose) { $ProcessParams['RedirectStandardOutput'] = 'NUL' } + # Sign the files with the specified cert + Start-Process @ProcessParams + } + + Write-Verbose -Message 'Removing the unsigned CIPs' + Remove-Item -Path '.\EnforcedMode.cip' -Force + Remove-Item -Path '.\AuditMode.cip' -Force + + Write-Verbose -Message 'Renaming the signed CIPs to remove the .p7 extension' + Rename-Item -Path '.\EnforcedMode.cip.p7' -NewName '.\EnforcedMode.cip' -Force + Rename-Item -Path '.\AuditMode.cip.p7' -NewName '.\AuditMode.cip' -Force + + #Region Snap-Back-Guarantee + Write-Verbose -Message 'Creating Enforced Mode SnapBack guarantee' + New-SnapBackGuarantee -Location (Get-Location).Path + + # Deploy the Audit mode CIP + Write-Verbose -Message 'Deploying the Audit mode CIP' + &'C:\Windows\System32\CiTool.exe' --update-policy '.\AuditMode.cip' -json | Out-Null + + Write-ColorfulText -Color Lavender -InputText 'The Base policy with the following details has been Re-Signed and Re-Deployed in Audit Mode:' + Write-ColorfulText -Color MintGreen -InputText "PolicyName = $PolicyName" + Write-ColorfulText -Color MintGreen -InputText "PolicyGUID = $PolicyID" + + # Remove the Audit Mode CIP + Remove-Item -Path '.\AuditMode.cip' -Force + #Endregion Snap-Back-Guarantee + + # A Try-Catch-Finally block so that if any errors occur, the Base policy will be Re-deployed in enforced mode + Try { + #Region User-Interaction + Write-ColorfulText -Color Pink -InputText 'Audit mode deployed, start installing your programs now' + Write-ColorfulText -Color HotPink -InputText 'When you have finished installing programs, Press Enter to start selecting program directories to scan' + Pause + + # Store the program paths that user browses for in an array + [System.IO.DirectoryInfo[]]$ProgramsPaths = @() + Write-Host -Object 'Select program directories to scan' -ForegroundColor Cyan + + # Showing folder picker GUI to the user for folder path selection + do { + [System.Reflection.Assembly]::LoadWithPartialName('System.windows.forms') | Out-Null + [System.Windows.Forms.FolderBrowserDialog]$OBJ = New-Object -TypeName System.Windows.Forms.FolderBrowserDialog + $OBJ.InitialDirectory = "$env:SystemDrive" + $OBJ.Description = $Description + [System.Windows.Forms.Form]$Spawn = New-Object -TypeName System.Windows.Forms.Form -Property @{TopMost = $true } + [System.String]$Show = $OBJ.ShowDialog($Spawn) + If ($Show -eq 'OK') { $ProgramsPaths += $OBJ.SelectedPath } + Else { break } + } + while ($true) + #Endregion User-Interaction + + # Make sure User browsed for at least 1 directory + # Exit the operation if user didn't select any folder paths + if ($ProgramsPaths.count -eq 0) { + Write-Host -Object 'No program folder was selected, reverting the changes and quitting...' -ForegroundColor Red + # Causing break here to stop operation. Finally block will be triggered to Re-Deploy Base policy in Enforced mode + break + } + + Write-Host -Object 'Here are the paths you selected:' -ForegroundColor Yellow + $ProgramsPaths | ForEach-Object -Process { $_.FullName } + + #Region EventCapturing + + Write-Host -Object 'Scanning Windows Event logs and creating a policy file, please wait...' -ForegroundColor Cyan + + # Extracting the array content from Get-AuditEventLogsProcessing function + $AuditEventLogsProcessingResults = Get-AuditEventLogsProcessing -Date $Date + + # Only create policy for files that are available on the disk (based on Event viewer logs) + # but weren't in user-selected program path(s), if there are any + if ($AuditEventLogsProcessingResults.AvailableFilesPaths) { + + # Using the function to find out which files are not in the user-selected path(s), if any, to only scan those + # this prevents duplicate rule creation and double file copying + $TestFilePathResults = (Test-FilePath -FilePath $AuditEventLogsProcessingResults.AvailableFilesPaths -DirectoryPath $ProgramsPaths).path | Select-Object -Unique + + Write-Verbose -Message "$($TestFilePathResults.count) file(s) have been found in event viewer logs that don't exist in any of the folder paths you selected." + + # Another check to make sure there were indeed files found in Event viewer logs but weren't in any of the user-selected path(s) + if ($TestFilePathResults) { + + # Create a folder in Temp directory to copy the files that are not included in user-selected program path(s) + # but detected in Event viewer audit logs, scan that folder, and in the end delete it + New-Item -Path "$UserTempDirectoryPath\TemporaryScanFolderForEventViewerFiles" -ItemType Directory -Force | Out-Null + + Write-Verbose -Message 'The following file(s) are being copied to the TEMP directory for scanning because they were found in event logs but did not exist in any of the user-selected paths:' + $TestFilePathResults | ForEach-Object -Process { + Write-Verbose -Message "$_" + Copy-Item -Path $_ -Destination "$UserTempDirectoryPath\TemporaryScanFolderForEventViewerFiles\" -Force -ErrorAction SilentlyContinue + } + + # Create a policy XML file for available files on the disk + + # Creating a hash table to dynamically add parameters based on user input and pass them to New-Cipolicy cmdlet + [System.Collections.Hashtable]$AvailableFilesOnDiskPolicyMakerHashTable = @{ + FilePath = '.\RulesForFilesNotInUserSelectedPaths.xml' + ScanPath = "$UserTempDirectoryPath\TemporaryScanFolderForEventViewerFiles\" + Level = $Level + Fallback = $Fallbacks + MultiplePolicyFormat = $true + UserWriteablePaths = $true + AllowFileNameFallbacks = $true + } + # Assess user input parameters and add the required parameters to the hash table + if ($SpecificFileNameLevel) { $AvailableFilesOnDiskPolicyMakerHashTable['SpecificFileNameLevel'] = $SpecificFileNameLevel } + if ($NoScript) { $AvailableFilesOnDiskPolicyMakerHashTable['NoScript'] = $true } + if (!$NoUserPEs) { $AvailableFilesOnDiskPolicyMakerHashTable['UserPEs'] = $true } + + # Create the supplemental policy via parameter splatting + Write-Verbose -Message 'Creating a policy file for files that are available on the disk but were not in user-selected program path(s)' + New-CIPolicy @AvailableFilesOnDiskPolicyMakerHashTable + + # Add the policy XML file to the array that holds policy XML files + $PolicyXMLFilesArray += '.\RulesForFilesNotInUserSelectedPaths.xml' + + # Delete the Temporary folder in the TEMP folder + Write-Verbose -Message 'Deleting the Temporary folder in the TEMP folder' + Remove-Item -Recurse -Path "$UserTempDirectoryPath\TemporaryScanFolderForEventViewerFiles\" -Force + } + } + + # Only create policy for files that are on longer available on the disk if there are any and + # if user chose to include deleted files in the final supplemental policy + if ($AuditEventLogsProcessingResults.DeletedFileHashes -and $IncludeDeletedFiles) { + + Write-Verbose -Message 'Attempting to create a policy for files that are no longer available on the disk but were detected in event viewer logs' + + # Displaying the unique values and count. Even though the DeletedFileHashesEventsPolicy.xml will have many duplicates, the final supplemental policy that will be deployed on the system won't have any duplicates + # Because Merge-CiPolicy will automatically take care of removing them + Write-Verbose -Message "$(($AuditEventLogsProcessingResults.DeletedFileHashes.'File Name' | Select-Object -Unique).count) file(s) have been found in event viewer logs that were run during Audit phase but are no longer on the disk, they are as follows:" + $AuditEventLogsProcessingResults.DeletedFileHashes.'File Name' | Select-Object -Unique | ForEach-Object -Process { + Write-Verbose -Message "$_" + } + + Write-Verbose -Message 'Creating FileRules and RuleRefs for files that are no longer available on the disk but were detected in event viewer logs' + [System.String]$FileRulesHashesResults = Get-FileRules -HashesArray $AuditEventLogsProcessingResults.DeletedFileHashes + [System.String]$RuleRefsHashesResults = (Get-RuleRefs -HashesArray $AuditEventLogsProcessingResults.DeletedFileHashes).Trim() + + # Save the File Rules and File Rule Refs in the FileRulesAndFileRefs.txt in the current working directory for debugging purposes + Write-Verbose -Message 'Saving the File Rules and File Rule Refs in the FileRulesAndFileRefs.txt in the current working directory for debugging purposes' + $FileRulesHashesResults + $RuleRefsHashesResults | Out-File -FilePath FileRulesAndFileRefs.txt -Force + + # Put the Rules and RulesRefs in an empty policy file + Write-Verbose -Message 'Putting the Rules and RulesRefs in an empty policy file' + New-EmptyPolicy -RulesContent $FileRulesHashesResults -RuleRefsContent $RuleRefsHashesResults | Out-File -FilePath .\DeletedFileHashesEventsPolicy.xml -Force + + # adding the policy file that consists of rules from audit even logs, to the array + Write-Verbose -Message 'Adding the policy file (DeletedFileHashesEventsPolicy.xml) that consists of rules from audit even logs, to the array of XML files' + $PolicyXMLFilesArray += '.\DeletedFileHashesEventsPolicy.xml' + } + #Endregion EventCapturing + + #Region Process-Program-Folders-From-User-input + Write-Verbose -Message 'Scanning each of the folder paths that user selected' + + for ($i = 0; $i -lt $ProgramsPaths.Count; $i++) { + + # Creating a hash table to dynamically add parameters based on user input and pass them to New-Cipolicy cmdlet + [System.Collections.Hashtable]$UserInputProgramFoldersPolicyMakerHashTable = @{ + FilePath = ".\ProgramDir_ScanResults$($i).xml" + ScanPath = $ProgramsPaths[$i] + Level = $Level + Fallback = $Fallbacks + MultiplePolicyFormat = $true + UserWriteablePaths = $true + AllowFileNameFallbacks = $true + } + # Assess user input parameters and add the required parameters to the hash table + if ($SpecificFileNameLevel) { $UserInputProgramFoldersPolicyMakerHashTable['SpecificFileNameLevel'] = $SpecificFileNameLevel } + if ($NoScript) { $UserInputProgramFoldersPolicyMakerHashTable['NoScript'] = $true } + if (!$NoUserPEs) { $UserInputProgramFoldersPolicyMakerHashTable['UserPEs'] = $true } + + # Create the supplemental policy via parameter splatting + Write-Verbose -Message "Currently scanning: $($ProgramsPaths[$i])" + New-CIPolicy @UserInputProgramFoldersPolicyMakerHashTable + } + + # Merge-CiPolicy accepts arrays - collecting all the policy files created by scanning user specified folders + Write-Verbose -Message 'Collecting all the policy files created by scanning user specified folders' + + foreach ($file in (Get-ChildItem -File -Path '.\' -Filter 'ProgramDir_ScanResults*.xml')) { + $PolicyXMLFilesArray += $file.FullName + } + #Endregion Process-Program-Folders-From-User-input + + #Region Kernel-protected-files-automatic-detection-and-allow-rule-creation + # This part takes care of Kernel protected files such as the main executable of the games installed through Xbox app + # For these files, only Kernel can get their hashes, it passes them to event viewer and we take them from event viewer logs + # Any other attempts such as "Get-FileHash" or "Get-AuthenticodeSignature" fail and ConfigCI Module cmdlets totally ignore these files and do not create allow rules for them + + Write-Verbose -Message 'Checking for Kernel protected files' + + # Finding the file(s) first and storing them in an array + [System.String[]]$ExesWithNoHash = @() + + # looping through each user-selected path(s) + foreach ($ProgramsPath in $ProgramsPaths) { + + # Making sure the currently processing path has any .exe in it + [System.String[]]$AnyAvailableExes = (Get-ChildItem -File -Recurse -Path $ProgramsPath -Filter '*.exe').FullName + + # if any .exe was found then continue testing them + if ($AnyAvailableExes) { + foreach ($Exe in $AnyAvailableExes) { + try { + # Testing each executable to find the protected ones + Get-FileHash -Path $Exe -ErrorAction Stop | Out-Null + } + # If the executable is protected, it will throw an exception and the script will continue to the next one + # Making sure only the right file is captured by narrowing down the error type. + # E.g., when get-filehash can't get a file's hash because its open by another program, the exception is different: System.IO.IOException + catch [System.UnauthorizedAccessException] { + $ExesWithNoHash += $Exe + } + } + } + } + + # Only proceed if any kernel protected file(s) were found in any of the user-selected directory path(s) + if ($ExesWithNoHash) { + + Write-Verbose -Message 'The following Kernel protected files detected, creating allow rules for them:' + $ExesWithNoHash | ForEach-Object -Process { Write-Verbose -Message "$_" } + + [System.Management.Automation.ScriptBlock]$KernelProtectedHashesBlock = { + foreach ($event in Get-WinEvent -FilterHashtable @{LogName = 'Microsoft-Windows-CodeIntegrity/Operational'; ID = 3076 } -ErrorAction SilentlyContinue | Where-Object -FilterScript { $_.TimeCreated -ge $Date } ) { + $Xml = [System.Xml.XmlDocument]$event.toxml() + $Xml.event.eventdata.data | + ForEach-Object -Begin { $Hash = @{} } -Process { $hash[$_.name] = $_.'#text' } -End { [pscustomobject]$hash } | + ForEach-Object -Process { + if ($_.'File Name' -match ($pattern = '\\Device\\HarddiskVolume(\d+)\\(.*)$')) { + $hardDiskVolumeNumber = $Matches[1] + $remainingPath = $Matches[2] + $getletter = Get-GlobalRootDrives | Where-Object -FilterScript { $_.devicepath -eq "\Device\HarddiskVolume$hardDiskVolumeNumber" } + $usablePath = "$($getletter.DriveLetter)$remainingPath" + $_.'File Name' = $_.'File Name' -replace $pattern, $usablePath + } # Check if file is currently on the disk + if (Test-Path -Path $_.'File Name') { + # Check if the file exits in the $ExesWithNoHash array + if ($ExesWithNoHash -contains $_.'File Name') { + $_ | Select-Object -Property FileVersion, 'File Name', PolicyGUID, 'SHA256 Hash', 'SHA256 Flat Hash', 'SHA1 Hash', 'SHA1 Flat Hash' + } + } + } + } + } + + $KernelProtectedHashesBlockResults = Invoke-Command -ScriptBlock $KernelProtectedHashesBlock + + # Only proceed further if any hashes belonging to the detected kernel protected files were found in Event viewer + # If none is found then skip this part, because user didn't run those files/programs when audit mode was turned on in base policy, so no hash was found in audit logs + if ($KernelProtectedHashesBlockResults) { + + # Save the File Rules and File Rule Refs in the FileRulesAndFileRefs.txt in the current working directory for debugging purposes + (Get-FileRules -HashesArray $KernelProtectedHashesBlockResults) + (Get-RuleRefs -HashesArray $KernelProtectedHashesBlockResults) | Out-File -FilePath KernelProtectedFiles.txt -Force + + # Put the Rules and RulesRefs in an empty policy file + New-EmptyPolicy -RulesContent (Get-FileRules -HashesArray $KernelProtectedHashesBlockResults) -RuleRefsContent (Get-RuleRefs -HashesArray $KernelProtectedHashesBlockResults) | Out-File -FilePath .\KernelProtectedFiles.xml -Force + + # adding the policy file to the array of xml files + $PolicyXMLFilesArray += '.\KernelProtectedFiles.xml' + } + else { + Write-Warning -Message "The following Kernel protected files detected, but no hash was found for them in Event viewer logs.`nThis means you didn't run those files/programs when Audit mode was turned on." + $ExesWithNoHash | ForEach-Object -Process { Write-Warning -Message "$_" } + } + } + else { + Write-Verbose -Message 'No Kernel protected files in the user selected paths were detected' + } + #Endregion Kernel-protected-files-automatic-detection-and-allow-rule-creation + + Write-Verbose -Message 'The following policy xml files are going to be merged into the final Supplemental policy and be deployed on the system:' + $PolicyXMLFilesArray | ForEach-Object -Process { Write-Verbose -Message "$_" } + + # Merge all of the policy XML files in the array into the final Supplemental policy + Merge-CIPolicy -PolicyPaths $PolicyXMLFilesArray -OutputFilePath ".\SupplementalPolicy $SuppPolicyName.xml" | Out-Null + + # Delete these extra files unless user uses -Debug parameter + if (!$Debug) { + Remove-Item -Path '.\RulesForFilesNotInUserSelectedPaths.xml', '.\ProgramDir_ScanResults*.xml' -Force -ErrorAction SilentlyContinue + Remove-Item -Path '.\KernelProtectedFiles.xml', '.\DeletedFileHashesEventsPolicy.xml' -Force -ErrorAction SilentlyContinue + Remove-Item -Path '.\KernelProtectedFiles.txt', '.\FileRulesAndFileRefs.txt' -Force -ErrorAction SilentlyContinue + } + } + # Unlike AllowNewApps parameter, AllowNewAppsAuditEvents parameter performs Event viewer scanning and kernel protected files detection + # So the base policy enforced mode snap back can't happen any sooner than this point + catch { + Throw $_ + } + finally { + # Deploy Enforced mode CIP + Write-Verbose -Message 'Finally Block Running' + Update-BasePolicyToEnforced + + # Enforced Mode Snapback removal after base policy has already been successfully re-enforced + Write-Verbose -Message 'Removing the SnapBack guarantee because the base policy has been successfully re-enforced' + Unregister-ScheduledTask -TaskName 'EnforcedModeSnapBack' -Confirm:$false + Remove-Item -Path 'C:\EnforcedModeSnapBack.cmd' -Force + } + + #Region Supplemental-policy-processing-and-deployment + + Write-Verbose -Message 'Supplemental policy processing and deployment' + [System.String]$SuppPolicyPath = ".\SupplementalPolicy $SuppPolicyName.xml" + + Write-Verbose -Message 'Converting the policy to a Supplemental policy type and resetting its ID' + [System.String]$SuppPolicyID = Set-CIPolicyIdInfo -FilePath $SuppPolicyPath -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" -ResetPolicyID -BasePolicyToSupplementPath $PolicyPath + $SuppPolicyID = $SuppPolicyID.Substring(11) + + Write-Verbose -Message 'Adding signer rule to the Supplemental policy' + Add-SignerRule -FilePath $SuppPolicyPath -CertificatePath $CertPath -Update -User -Kernel + + # Make sure policy rule options that don't belong to a Supplemental policy don't exist + Write-Verbose -Message 'Making sure policy rule options that do not belong to a Supplemental policy do not exist' + @(0, 1, 2, 3, 4, 6, 8, 9, 10, 11, 12, 15, 16, 17, 19, 20) | ForEach-Object -Process { Set-RuleOption -FilePath $SuppPolicyPath -Option $_ -Delete } + + Write-Verbose -Message 'Setting HVCI to Strict' + Set-HVCIOptions -Strict -FilePath $SuppPolicyPath + + Write-Verbose -Message 'Setting the Supplemental policy version to 1.0.0.0' + Set-CIPolicyVersion -FilePath $SuppPolicyPath -Version '1.0.0.0' + + Write-Verbose -Message 'Converting the Supplemental policy to a CIP file' + ConvertFrom-CIPolicy -XmlFilePath $SuppPolicyPath -BinaryFilePath "$SuppPolicyID.cip" | Out-Null + + # Configure the parameter splat + $ProcessParams = @{ + 'ArgumentList' = 'sign', '/v' , '/n', "`"$CertCN`"", '/p7', '.', '/p7co', '1.3.6.1.4.1.311.79.1', '/fd', 'certHash', ".\$SuppPolicyID.cip" + 'FilePath' = $SignToolPathFinal + 'NoNewWindow' = $true + 'Wait' = $true + 'ErrorAction' = 'Stop' + } + # Only show the output of SignTool if Verbose switch is used + if (!$Verbose) { $ProcessParams['RedirectStandardOutput'] = 'NUL' } + + # Sign the files with the specified cert + Write-Verbose -Message 'Signing the Supplemental policy with the specified cert' + Start-Process @ProcessParams + + Write-Verbose -Message 'Removing the unsigned Supplemental policy file' + Remove-Item -Path ".\$SuppPolicyID.cip" -Force + + Write-Verbose -Message 'Renaming the signed Supplemental policy file to remove the .p7 extension' + Rename-Item -Path "$SuppPolicyID.cip.p7" -NewName "$SuppPolicyID.cip" -Force + + Write-Verbose -Message 'Deploying the Supplemental policy' + &'C:\Windows\System32\CiTool.exe' --update-policy ".\$SuppPolicyID.cip" -json | Out-Null + + Write-ColorfulText -Color Lavender -InputText 'Supplemental policy with the following details has been Signed and Deployed in Enforced Mode:' + Write-ColorfulText -Color MintGreen -InputText "SupplementalPolicyName = $SuppPolicyName" + Write-ColorfulText -Color MintGreen -InputText "SupplementalPolicyGUID = $SuppPolicyID" + + Write-Verbose -Message 'Removing the signed Supplemental policy CIP file after deployment' + Remove-Item -Path ".\$SuppPolicyID.cip" -Force + + # Remove the policy xml file in Temp folder we created earlier + Remove-Item -Path $PolicyPath -Force + + #Endregion Supplemental-policy-processing-and-deployment + } + } + + if ($MergeSupplementalPolicies) { + foreach ($PolicyPath in $PolicyPaths) { + + #Region Input-policy-verification + Write-Verbose -Message 'Verifying the input policy files' + foreach ($SuppPolicyPath in $SuppPolicyPaths) { + + Write-Verbose -Message "Getting policy ID and type of: $SuppPolicyPath" + $Supplementalxml = [System.Xml.XmlDocument](Get-Content -Path $SuppPolicyPath) + [System.String]$SupplementalPolicyID = $Supplementalxml.SiPolicy.PolicyID + [System.String]$SupplementalPolicyType = $Supplementalxml.SiPolicy.PolicyType + + Write-Verbose -Message 'Getting the IDs of the currently deployed policies on the system' + [System.String[]]$DeployedPoliciesIDs = (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies.PolicyID | ForEach-Object -Process { return "{$_}" } + + # Check the type of the user selected Supplemental policy XML files to make sure they are indeed Supplemental policies + Write-Verbose -Message 'Checking the type of the policy' + if ($SupplementalPolicyType -ne 'Supplemental Policy') { + Throw "The Selected XML file with GUID $SupplementalPolicyID isn't a Supplemental Policy." + } + + # Check to make sure the user selected Supplemental policy XML files are deployed on the system + Write-Verbose -Message 'Checking the deployment status of the policy' + if ($DeployedPoliciesIDs -notcontains $SupplementalPolicyID) { + Throw "The Selected Supplemental XML file with GUID $SupplementalPolicyID isn't deployed on the system." + } + } + #Endregion Input-policy-verification + + Write-Verbose -Message 'Merging the Supplemental policies into a single policy file' + Merge-CIPolicy -PolicyPaths $SuppPolicyPaths -OutputFilePath "$SuppPolicyName.xml" | Out-Null + + # Remove the deployed Supplemental policies that user selected from the system, because we're going to deploy the new merged policy that contains all of them + Write-Verbose -Message 'Removing the deployed Supplemental policies that user selected from the system' + foreach ($SuppPolicyPath in $SuppPolicyPaths) { + + # Get the policy ID of the currently selected Supplemental policy + $Supplementalxml = [System.Xml.XmlDocument](Get-Content -Path $SuppPolicyPath) + [System.String]$SupplementalPolicyID = $Supplementalxml.SiPolicy.PolicyID + + Write-Verbose -Message "Removing policy with ID: $SupplementalPolicyID" + &'C:\Windows\System32\CiTool.exe' --remove-policy $SupplementalPolicyID -json | Out-Null + + # remove the old policy files unless user chose to keep them + if (!$KeepOldSupplementalPolicies) { + Write-Verbose -Message "Removing the old policy file: $SuppPolicyPath" + Remove-Item -Path $SuppPolicyPath -Force + } + } + + Write-Verbose -Message 'Preparing the final merged Supplemental policy for deployment' + Write-Verbose -Message 'Converting the policy to a Supplemental policy type and resetting its ID' + $SuppPolicyID = Set-CIPolicyIdInfo -FilePath "$SuppPolicyName.xml" -ResetPolicyID -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" -BasePolicyToSupplementPath $PolicyPath + $SuppPolicyID = $SuppPolicyID.Substring(11) + + Write-Verbose -Message 'Adding signer rules to the Supplemental policy' + Add-SignerRule -FilePath "$SuppPolicyName.xml" -CertificatePath $CertPath -Update -User -Kernel + + Write-Verbose -Message 'Setting HVCI to Strict' + Set-HVCIOptions -Strict -FilePath "$SuppPolicyName.xml" + + Write-Verbose -Message 'Removing the Unsigned mode policy rule option' + Set-RuleOption -FilePath "$SuppPolicyName.xml" -Option 6 -Delete + + Write-Verbose -Message 'Converting the Supplemental policy to a CIP file' + ConvertFrom-CIPolicy -XmlFilePath "$SuppPolicyName.xml" -BinaryFilePath "$SuppPolicyID.cip" | Out-Null + + # Configure the parameter splat + $ProcessParams = @{ + 'ArgumentList' = 'sign', '/v' , '/n', "`"$CertCN`"", '/p7', '.', '/p7co', '1.3.6.1.4.1.311.79.1', '/fd', 'certHash', ".\$SuppPolicyID.cip" + 'FilePath' = $SignToolPathFinal + 'NoNewWindow' = $true + 'Wait' = $true + 'ErrorAction' = 'Stop' + } # Only show the output of SignTool if Verbose switch is used + if (!$Verbose) { $ProcessParams['RedirectStandardOutput'] = 'NUL' } + + # Sign the files with the specified cert + Write-Verbose -Message 'Signing the Supplemental policy with the specified cert' + Start-Process @ProcessParams + + Write-Verbose -Message 'Removing the unsigned Supplemental policy file' + Remove-Item -Path ".\$SuppPolicyID.cip" -Force + + Write-Verbose -Message 'Renaming the signed Supplemental policy file to remove the .p7 extension' + Rename-Item -Path "$SuppPolicyID.cip.p7" -NewName "$SuppPolicyID.cip" -Force + + Write-Verbose -Message 'Deploying the Supplemental policy' + &'C:\Windows\System32\CiTool.exe' --update-policy "$SuppPolicyID.cip" -json | Out-Null + + Write-ColorfulText -Color TeaGreen -InputText "The Signed Supplemental policy $SuppPolicyName has been deployed on the system, replacing the old ones.`nSystem Restart is not immediately needed but eventually required to finish the removal of the previous individual Supplemental policies." + + Write-Verbose -Message 'Removing the signed Supplemental policy CIP file after deployment' + Remove-Item -Path "$SuppPolicyID.cip" -Force + } + } + + if ($UpdateBasePolicy) { + + Write-Verbose -Message 'Getting the Microsoft recommended block rules by calling the Get-BlockRulesMeta function' + Get-BlockRulesMeta 6> $null + + Write-Verbose -Message 'Determining the type of the new base policy' + switch ($NewBasePolicyType) { + + 'AllowMicrosoft_Plus_Block_Rules' { + Write-Verbose -Message 'The new base policy type is AllowMicrosoft_Plus_Block_Rules' + + Write-Verbose -Message 'Copying the AllowMicrosoft.xml template policy file to the current working directory' + Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowMicrosoft.xml' -Destination '.\AllowMicrosoft.xml' -Force + + Write-Verbose -Message 'Merging the AllowMicrosoft.xml and Microsoft recommended block rules into a single policy file' + Merge-CIPolicy -PolicyPaths .\AllowMicrosoft.xml, '.\Microsoft recommended block rules.xml' -OutputFilePath .\BasePolicy.xml | Out-Null + + Write-Verbose -Message 'Setting the policy name' + Set-CIPolicyIdInfo -FilePath .\BasePolicy.xml -PolicyName "Allow Microsoft Plus Block Rules refreshed On $(Get-Date -Format 'MM-dd-yyyy')" + + Write-Verbose -Message 'Setting the policy rule options' + @(0, 2, 5, 11, 12, 16, 17, 19, 20) | ForEach-Object -Process { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ } + + Write-Verbose -Message 'Removing the unnecessary policy rule options' + @(3, 4, 6, 9, 10, 13, 18) | ForEach-Object -Process { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ -Delete } + } + + 'Lightly_Managed_system_Policy' { + Write-Verbose -Message 'The new base policy type is Lightly_Managed_system_Policy' + + Write-Verbose -Message 'Copying the AllowMicrosoft.xml template policy file to the current working directory' + Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowMicrosoft.xml' -Destination '.\AllowMicrosoft.xml' -Force + + Write-Verbose -Message 'Merging the AllowMicrosoft.xml and Microsoft recommended block rules into a single policy file' + Merge-CIPolicy -PolicyPaths .\AllowMicrosoft.xml, '.\Microsoft recommended block rules.xml' -OutputFilePath .\BasePolicy.xml | Out-Null + + Write-Verbose -Message 'Setting the policy name' + Set-CIPolicyIdInfo -FilePath .\BasePolicy.xml -PolicyName "Signed And Reputable policy refreshed on $(Get-Date -Format 'MM-dd-yyyy')" + + Write-Verbose -Message 'Setting the policy rule options' + @(0, 2, 5, 11, 12, 14, 15, 16, 17, 19, 20) | ForEach-Object -Process { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ } + + Write-Verbose -Message 'Removing the unnecessary policy rule options' + @(3, 4, 6, 9, 10, 13, 18) | ForEach-Object -Process { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ -Delete } + + # Configure required services for ISG authorization + Write-Verbose -Message 'Configuring required services for ISG authorization' + Start-Process -FilePath 'C:\Windows\System32\appidtel.exe' -ArgumentList 'start' -Wait -NoNewWindow + Start-Process -FilePath 'C:\Windows\System32\sc.exe' -ArgumentList 'config', 'appidsvc', 'start= auto' -Wait -NoNewWindow + } + + 'DefaultWindows_WithBlockRules' { + Write-Verbose -Message 'The new base policy type is DefaultWindows_WithBlockRules' + + Write-Verbose -Message 'Copying the DefaultWindows.xml template policy file to the current working directory' + Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\DefaultWindows_Enforced.xml' -Destination '.\DefaultWindows_Enforced.xml' -Force + + # Allowing SignTool to be able to run after Default Windows base policy is deployed + Write-ColorfulText -Color TeaGreen -InputText 'Creating allow rules for SignTool.exe in the DefaultWindows base policy so you can continue using it after deploying the DefaultWindows base policy.' + + Write-Verbose -Message 'Creating a new folder in the TEMP directory to copy SignTool.exe to it' + New-Item -Path "$UserTempDirectoryPath\TemporarySignToolFile" -ItemType Directory -Force | Out-Null + + Write-Verbose -Message 'Copying SignTool.exe to the folder in the TEMP directory' + Copy-Item -Path $SignToolPathFinal -Destination "$UserTempDirectoryPath\TemporarySignToolFile" -Force + + Write-Verbose -Message 'Scanning the folder in the TEMP directory to create a policy for SignTool.exe' + New-CIPolicy -ScanPath "$UserTempDirectoryPath\TemporarySignToolFile" -Level FilePublisher -Fallback Hash -UserPEs -UserWriteablePaths -MultiplePolicyFormat -AllowFileNameFallbacks -FilePath .\SignTool.xml + + # Delete the Temporary folder in the TEMP folder + if (!$Debug) { + Write-Verbose -Message 'Deleting the Temporary folder in the TEMP directory' + Remove-Item -Recurse -Path "$UserTempDirectoryPath\TemporarySignToolFile" -Force + } + + if (Test-Path -Path 'C:\Program Files\PowerShell') { + Write-Verbose -Message 'Scanning the PowerShell core directory ' + + Write-ColorfulText -Color HotPink -InputText 'Creating allow rules for PowerShell in the DefaultWindows base policy so you can continue using this module after deploying it.' + New-CIPolicy -ScanPath 'C:\Program Files\PowerShell' -Level FilePublisher -NoScript -Fallback Hash -UserPEs -UserWriteablePaths -MultiplePolicyFormat -AllowFileNameFallbacks -FilePath .\AllowPowerShell.xml + + Write-Verbose -Message 'Merging the DefaultWindows.xml, AllowPowerShell.xml, SignTool.xml and Microsoft recommended block rules into a single policy file' + Merge-CIPolicy -PolicyPaths .\DefaultWindows_Enforced.xml, .\AllowPowerShell.xml, .\SignTool.xml, '.\Microsoft recommended block rules.xml' -OutputFilePath .\BasePolicy.xml | Out-Null + } + else { + Write-Verbose -Message 'Not including the PowerShell core directory in the policy' + Write-Verbose -Message 'Merging the DefaultWindows.xml, SignTool.xml and Microsoft recommended block rules into a single policy file' + Merge-CIPolicy -PolicyPaths .\DefaultWindows_Enforced.xml, .\SignTool.xml, '.\Microsoft recommended block rules.xml' -OutputFilePath .\BasePolicy.xml | Out-Null + } + + Write-Verbose -Message 'Setting the policy name' + Set-CIPolicyIdInfo -FilePath .\BasePolicy.xml -PolicyName "Default Windows Plus Block Rules refreshed On $(Get-Date -Format 'MM-dd-yyyy')" + + Write-Verbose -Message 'Setting the policy rule options' + @(0, 2, 5, 11, 12, 16, 17, 19, 20) | ForEach-Object -Process { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ } + + Write-Verbose -Message 'Removing the unnecessary policy rule options' + @(3, 4, 6, 9, 10, 13, 18) | ForEach-Object -Process { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ -Delete } + } + } + + if ($UpdateBasePolicy -and $RequireEVSigners) { + Write-Verbose -Message 'Adding the EV Signers rule option to the base policy' + Set-RuleOption -FilePath .\BasePolicy.xml -Option 8 + } + + # Remove the extra files create during module operation that are no longer necessary + if (!$Debug) { + Remove-Item -Path '.\AllowPowerShell.xml', '.\SignTool.xml', '.\AllowMicrosoft.xml', '.\DefaultWindows_Enforced.xml' -Force -ErrorAction SilentlyContinue + Remove-Item -Path '.\Microsoft recommended block rules.xml' -Force + } + + Write-Verbose -Message 'Getting the policy ID of the currently deployed base policy based on the policy name that user selected' + [System.String]$CurrentID = ((&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { $_.IsSystemPolicy -ne 'True' } | Where-Object -FilterScript { $_.Friendlyname -eq $CurrentBasePolicyName }).BasePolicyID + $CurrentID = "{$CurrentID}" + + Write-Verbose -Message 'Making sure there is not a .CIP file with the same name as the current base policy ID in the current working directory' + Remove-Item -Path ".\$CurrentID.cip" -Force -ErrorAction SilentlyContinue + + Write-Verbose -Message 'Reading the current base policy XML file' + [System.Xml.XmlDocument]$Xml = Get-Content -Path '.\BasePolicy.xml' + + Write-Verbose -Message 'Setting the policy ID and Base policy ID to the current base policy ID in the generated XML file' + $Xml.SiPolicy.PolicyID = $CurrentID + $Xml.SiPolicy.BasePolicyID = $CurrentID + + Write-Verbose -Message 'Saving the updated XML file' + $Xml.Save('.\BasePolicy.xml') + + Write-Verbose -Message 'Adding signer rules to the base policy' + Add-SignerRule -FilePath .\BasePolicy.xml -CertificatePath $CertPath -Update -User -Kernel -Supplemental + + Write-Verbose -Message 'Setting the policy version to 1.0.0.1' + Set-CIPolicyVersion -FilePath .\BasePolicy.xml -Version '1.0.0.1' + + Write-Verbose -Message 'Setting HVCI to Strict' + Set-HVCIOptions -Strict -FilePath .\BasePolicy.xml + + Write-Verbose -Message 'Converting the base policy to a CIP file' + ConvertFrom-CIPolicy -XmlFilePath '.\BasePolicy.xml' -BinaryFilePath "$CurrentID.cip" | Out-Null + + # Configure the parameter splat + $ProcessParams = @{ + 'ArgumentList' = 'sign', '/v' , '/n', "`"$CertCN`"", '/p7', '.', '/p7co', '1.3.6.1.4.1.311.79.1', '/fd', 'certHash', ".\$CurrentID.cip" + 'FilePath' = $SignToolPathFinal + 'NoNewWindow' = $true + 'Wait' = $true + 'ErrorAction' = 'Stop' + } # Only show the output of SignTool if Verbose switch is used + if (!$Verbose) { $ProcessParams['RedirectStandardOutput'] = 'NUL' } + + # Sign the files with the specified cert + Write-Verbose -Message 'Signing the base policy with the specified cert' + Start-Process @ProcessParams + + Write-Verbose -Message 'Removing the unsigned base policy file' + Remove-Item -Path ".\$CurrentID.cip" -Force + + Write-Verbose -Message 'Renaming the signed base policy file to remove the .p7 extension' + Rename-Item -Path "$CurrentID.cip.p7" -NewName "$CurrentID.cip" -Force + + Write-Verbose -Message 'Deploying the new base policy with the same GUID on the system' + &'C:\Windows\System32\CiTool.exe' --update-policy "$CurrentID.cip" -json | Out-Null + + # Keep the new base policy XML file that was just deployed, in the current directory, so user can keep it for later + # Defining a hashtable that contains the policy names and their corresponding XML file names + [System.Collections.Hashtable]$PolicyFiles = @{ + 'AllowMicrosoft_Plus_Block_Rules' = 'AllowMicrosoftPlusBlockRules.xml' + 'Lightly_Managed_system_Policy' = 'SignedAndReputable.xml' + 'DefaultWindows_WithBlockRules' = 'DefaultWindowsPlusBlockRules.xml' + } + + Write-Verbose -Message 'Removing the signed base policy CIP file after deployment' + Remove-Item -Path ".\$CurrentID.cip" -Force + + Write-Verbose -Message 'Making sure a policy file with the same name as the current base policy does not exist in the current working directory' + Remove-Item -Path $PolicyFiles[$NewBasePolicyType] -Force -ErrorAction SilentlyContinue + + Write-Verbose -Message 'Renaming the base policy XML file to match the new base policy type' + Rename-Item -Path '.\BasePolicy.xml' -NewName $PolicyFiles[$NewBasePolicyType] -Force + + Write-ColorfulText -Color Pink -InputText "Base Policy has been successfully updated to $NewBasePolicyType" + + if (Get-CommonWDACConfig -SignedPolicyPath) { + Write-Verbose -Message 'Replacing the old signed policy path in User Configurations with the new one' + Set-CommonWDACConfig -SignedPolicyPath (Get-ChildItem -Path $PolicyFiles[$NewBasePolicyType]).FullName | Out-Null + } + } + } + + <# +.SYNOPSIS + Edits Signed WDAC policies deployed on the system (Windows Defender Application Control) +.LINK + https://github.com/HotCakeX/Harden-Windows-Security/wiki/Edit-SignedWDACConfig +.DESCRIPTION + Using official Microsoft methods, Edits Signed WDAC policies deployed on the system (Windows Defender Application Control) +.COMPONENT + Windows Defender Application Control, ConfigCI PowerShell module +.FUNCTIONALITY + Using official Microsoft methods, Edits Signed WDAC policies deployed on the system (Windows Defender Application Control) +.PARAMETER AllowNewAppsAuditEvents + Rebootlessly install new apps/programs when Signed policy is already deployed, use audit events to capture installation files, scan their directories for new Supplemental policy, Sign and deploy thew Supplemental policy. +.PARAMETER AllowNewApps + Rebootlessly install new apps/programs when Signed policy is already deployed, scan their directories for new Supplemental policy, Sign and deploy thew Supplemental policy. +.PARAMETER MergeSupplementalPolicies + Merges multiple Signed deployed supplemental policies into 1 single supplemental policy, removes the old ones, deploys the new one. System restart needed to take effect. +.PARAMETER UpdateBasePolicy + It can rebootlessly change the type of the deployed signed base policy. It can update the recommended block rules and/or change policy rule options in the deployed base policy. +.PARAMETER SkipVersionCheck + Can be used with any parameter to bypass the online version check - only to be used in rare cases + It is used by the entire Cmdlet. +.PARAMETER LogSize + The log size to set for Code Integrity/Operational event logs + The accepted values are between 1024 KB and 18014398509481983 KB + The max range is the maximum allowed log size by Windows Event viewer +.PARAMETER CertCN + Common name of the certificate used to sign the deployed Signed WDAC policy + It is Used by the entire Cmdlet +.PARAMETER Level + The level that determines how the selected folder will be scanned. + The default value for it is FilePublisher. +.PARAMETER Fallbacks + The fallback level(s) that determine how the selected folder will be scanned. + The default value for it is Hash. +.INPUTS + System.Int64 + System.String + System.String[] + System.Management.Automation.SwitchParameter +.OUTPUTS + System.String +#> +} + +# Importing argument completer ScriptBlocks +. "$ModuleRootPath\Resources\ArgumentCompleters.ps1" +Register-ArgumentCompleter -CommandName 'Edit-SignedWDACConfig' -ParameterName 'CertCN' -ScriptBlock $ArgumentCompleterCertificateCN +Register-ArgumentCompleter -CommandName 'Edit-SignedWDACConfig' -ParameterName 'CertPath' -ScriptBlock $ArgumentCompleterCerFilePathsPicker +Register-ArgumentCompleter -CommandName 'Edit-SignedWDACConfig' -ParameterName 'SignToolPath' -ScriptBlock $ArgumentCompleterExeFilePathsPicker +Register-ArgumentCompleter -CommandName 'Edit-SignedWDACConfig' -ParameterName 'PolicyPaths' -ScriptBlock $ArgumentCompleterPolicyPathsBasePoliciesOnly +Register-ArgumentCompleter -CommandName 'Edit-SignedWDACConfig' -ParameterName 'SuppPolicyPaths' -ScriptBlock $ArgumentCompleterPolicyPathsSupplementalPoliciesOnly diff --git a/WDACConfig/WDACConfig Module Files/Core/Edit-WDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Edit-WDACConfig.psm1 new file mode 100644 index 000000000..ab6236142 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/Core/Edit-WDACConfig.psm1 @@ -0,0 +1,1049 @@ +Function Edit-WDACConfig { + [CmdletBinding( + DefaultParameterSetName = 'Allow New Apps Audit Events', + SupportsShouldProcess = $true, + PositionalBinding = $false, + ConfirmImpact = 'High' + )] + Param( + [Alias('E')] + [Parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')][System.Management.Automation.SwitchParameter]$AllowNewAppsAuditEvents, + [Alias('A')] + [Parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')][System.Management.Automation.SwitchParameter]$AllowNewApps, + [Alias('M')] + [Parameter(Mandatory = $false, ParameterSetName = 'Merge Supplemental Policies')][System.Management.Automation.SwitchParameter]$MergeSupplementalPolicies, + [Alias('U')] + [Parameter(Mandatory = $false, ParameterSetName = 'Update Base Policy')][System.Management.Automation.SwitchParameter]$UpdateBasePolicy, + + [ValidatePattern('^[a-zA-Z0-9 ]+$', ErrorMessage = 'The Supplemental Policy Name can only contain alphanumeric and space characters.')] + [Parameter(Mandatory = $true, ParameterSetName = 'Allow New Apps Audit Events', ValueFromPipelineByPropertyName = $true)] + [Parameter(Mandatory = $true, ParameterSetName = 'Allow New Apps', ValueFromPipelineByPropertyName = $true)] + [Parameter(Mandatory = $true, ParameterSetName = 'Merge Supplemental Policies', ValueFromPipelineByPropertyName = $true)] + [System.String]$SuppPolicyName, + + [ValidatePattern('\.xml$')] + [ValidateScript({ + # Validate each Policy file in PolicyPaths parameter to make sure the user isn't accidentally trying to + # Edit a Signed policy using Edit-WDACConfig cmdlet which is only made for Unsigned policies + $_ | ForEach-Object -Process { + $XmlTest = [System.Xml.XmlDocument](Get-Content -Path $_) + $RedFlag1 = $XmlTest.SiPolicy.SupplementalPolicySigners.SupplementalPolicySigner.SignerId + $RedFlag2 = $XmlTest.SiPolicy.UpdatePolicySigners.UpdatePolicySigner.SignerId + $RedFlag3 = $XmlTest.SiPolicy.PolicyID + $CurrentPolicyIDs = ((&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { $_.IsSystemPolicy -ne 'True' }).policyID | ForEach-Object -Process { "{$_}" } + if (!$RedFlag1 -and !$RedFlag2) { + # Ensure the selected base policy xml file is deployed + if ($CurrentPolicyIDs -contains $RedFlag3) { + return $True + } + else { throw "The currently selected policy xml file isn't deployed." } + } + # This throw is shown only when User added a Signed policy xml file for Unsigned policy file path property in user configuration file + # Without this, the error shown would be vague: The variable cannot be validated because the value System.String[] is not a valid value for the PolicyPaths variable. + else { throw 'The policy xml file in User Configurations for UnsignedPolicyPath is a Signed policy.' } + } + }, ErrorMessage = 'The selected policy xml file is Signed. Please use Edit-SignedWDACConfig cmdlet to edit Signed policies.')] + [Parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events', ValueFromPipelineByPropertyName = $true)] + [Parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps', ValueFromPipelineByPropertyName = $true)] + [Parameter(Mandatory = $false, ParameterSetName = 'Merge Supplemental Policies', ValueFromPipelineByPropertyName = $true)] + [System.String[]]$PolicyPaths, + + [ValidatePattern('\.xml$')] + [ValidateScript({ Test-Path -Path $_ -PathType 'Leaf' }, ErrorMessage = 'The path you selected is not a file path.')] + [Parameter(Mandatory = $true, ParameterSetName = 'Merge Supplemental Policies', ValueFromPipelineByPropertyName = $true)] + [System.String[]]$SuppPolicyPaths, + + [Parameter(Mandatory = $false, ParameterSetName = 'Merge Supplemental Policies')] + [System.Management.Automation.SwitchParameter]$KeepOldSupplementalPolicies, + + [ValidateSet([Levelz])] + [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] + [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')] + [System.String]$Level = 'FilePublisher', + + [ValidateSet([Fallbackz])] + [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] + [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')] + [System.String[]]$Fallbacks = 'Hash', + + [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] + [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')] + [System.Management.Automation.SwitchParameter]$NoScript, + + [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] + [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')] + [System.Management.Automation.SwitchParameter]$NoUserPEs, + + [ValidateSet('OriginalFileName', 'InternalName', 'FileDescription', 'ProductName', 'PackageFamilyName', 'FilePath')] + [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] + [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps')] + [System.String]$SpecificFileNameLevel, + + [ValidateRange(1024KB, 18014398509481983KB)] + [Parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')] + [System.Int64]$LogSize, + + [parameter(Mandatory = $false, ParameterSetName = 'Allow New Apps Audit Events')][System.Management.Automation.SwitchParameter]$IncludeDeletedFiles, + + [ValidateSet([BasePolicyNamez])] + [Parameter(Mandatory = $true, ParameterSetName = 'Update Base Policy')][System.String[]]$CurrentBasePolicyName, + + [ValidateSet('AllowMicrosoft_Plus_Block_Rules', 'Lightly_Managed_system_Policy', 'DefaultWindows_WithBlockRules')] + [Parameter(Mandatory = $true, ParameterSetName = 'Update Base Policy')][System.String]$NewBasePolicyType, + + [Parameter(Mandatory = $false, ParameterSetName = 'Update Base Policy')][System.Management.Automation.SwitchParameter]$RequireEVSigners, + + [Parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$SkipVersionCheck + ) + + begin { + # Detecting if Verbose switch is used + $PSBoundParameters.Verbose.IsPresent ? ([System.Boolean]$Verbose = $true) : ([System.Boolean]$Verbose = $false) | Out-Null + + # Importing the $PSDefaultParameterValues to the current session, prior to everything else + . "$ModuleRootPath\CoreExt\PSDefaultParameterValues.ps1" + + # Importing the required sub-modules + Write-Verbose -Message 'Importing the required sub-modules' + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Update-self.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Get-GlobalRootDrives.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Write-ColorfulText.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Set-LogSize.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Test-FilePath.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Get-AuditEventLogsProcessing.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\New-EmptyPolicy.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Get-RuleRefs.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Get-FileRules.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Get-BlockRulesMeta.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\New-SnapBackGuarantee.psm1" -Force + + # if -SkipVersionCheck wasn't passed, run the updater + # Redirecting the Update-Self function's information Stream to $null because Write-Host + # Used by Write-ColorfulText outputs to both information stream and host console + if (-NOT $SkipVersionCheck) { Update-self 6> $null } + + # Detecting if Debug switch is used, will do debugging actions based on that + $PSBoundParameters.Debug.IsPresent ? ([System.Boolean]$Debug = $true) : ([System.Boolean]$Debug = $false) | Out-Null + + #Region User-Configurations-Processing-Validation + # make sure the ParameterSet being used has PolicyPaths parameter - Then enforces "mandatory" attribute for the parameter + if ($PSCmdlet.ParameterSetName -in 'Allow New Apps Audit Events', 'Allow New Apps', 'Merge Supplemental Policies') { + # If any of these parameters, that are mandatory for all of the position 0 parameters, isn't supplied by user + if (!$PolicyPaths) { + # Read User configuration file if it exists + $UserConfig = Get-Content -Path "$UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" -ErrorAction SilentlyContinue + if ($UserConfig) { + # Validate the Json file and read its content to make sure it's not corrupted + try { $UserConfig = $UserConfig | ConvertFrom-Json } + catch { + Write-Error -Message 'User Configuration Json file is corrupted, deleting it...' -ErrorAction Continue + Remove-CommonWDACConfig + } + } + } + # If PolicyPaths has no values + if (!$PolicyPaths) { + if ($UserConfig.UnsignedPolicyPath) { + # validate each policyPath read from user config file + if (Test-Path -Path $($UserConfig.UnsignedPolicyPath)) { + $PolicyPaths = $UserConfig.UnsignedPolicyPath + } + else { + throw 'The currently saved value for UnsignedPolicyPath in user configurations is invalid.' + } + } + else { + throw 'PolicyPaths parameter cannot be empty and no valid configuration was found for UnsignedPolicyPath.' + } + } + } + #Endregion User-Configurations-Processing-Validation + + # argument tab auto-completion and ValidateSet for Policy names + Class BasePolicyNamez : System.Management.Automation.IValidateSetValuesGenerator { + [System.String[]] GetValidValues() { + $BasePolicyNamez = ((&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { $_.IsSystemPolicy -ne 'True' } | Where-Object -FilterScript { $_.PolicyID -eq $_.BasePolicyID }).Friendlyname + + return [System.String[]]$BasePolicyNamez + } + } + + # argument tab auto-completion and ValidateSet for Fallbacks + Class Fallbackz : System.Management.Automation.IValidateSetValuesGenerator { + [System.String[]] GetValidValues() { + $Fallbackz = ('Hash', 'FileName', 'SignedVersion', 'Publisher', 'FilePublisher', 'LeafCertificate', 'PcaCertificate', 'RootCertificate', 'WHQL', 'WHQLPublisher', 'WHQLFilePublisher', 'PFN', 'FilePath', 'None') + return [System.String[]]$Fallbackz + } + } + + # argument tab auto-completion and ValidateSet for level + Class Levelz : System.Management.Automation.IValidateSetValuesGenerator { + [System.String[]] GetValidValues() { + $Levelz = ('Hash', 'FileName', 'SignedVersion', 'Publisher', 'FilePublisher', 'LeafCertificate', 'PcaCertificate', 'RootCertificate', 'WHQL', 'WHQLPublisher', 'WHQLFilePublisher', 'PFN', 'FilePath', 'None') + return [System.String[]]$Levelz + } + } + + function Update-BasePolicyToEnforced { + <# + .SYNOPSIS + A helper function used to redeploy the base policy in Enforced mode + .INPUTS + None. This function uses the global variables $PolicyName and $PolicyID + .OUTPUTS + System.String + #> + [CmdletBinding()] + param() + + # Deploy Enforced mode CIP + &'C:\Windows\System32\CiTool.exe' --update-policy '.\EnforcedMode.cip' -json | Out-Null + Write-ColorfulText -Color Lavender -InputText 'The Base policy with the following details has been Re-Deployed in Enforced Mode:' + Write-ColorfulText -Color MintGreen -InputText "PolicyName = $PolicyName" + Write-ColorfulText -Color MintGreen -InputText "PolicyGUID = $PolicyID" + # Remove Enforced Mode CIP + Remove-Item -Path '.\EnforcedMode.cip' -Force + } + } + + process { + + if ($AllowNewApps) { + # remove any possible files from previous runs + Write-Verbose -Message 'Removing any possible files from previous runs' + Remove-Item -Path '.\ProgramDir_ScanResults*.xml' -Force -ErrorAction SilentlyContinue + Remove-Item -Path ".\SupplementalPolicy $SuppPolicyName.xml" -Force -ErrorAction SilentlyContinue + + # An empty array that holds the Policy XML files - This array will eventually be used to create the final Supplemental policy + [System.Object[]]$PolicyXMLFilesArray = @() + + #Initiate Live Audit Mode + + foreach ($PolicyPath in $PolicyPaths) { + # Creating a copy of the original policy in Temp folder so that the original one will be unaffected + Write-Verbose -Message 'Creating a copy of the original policy in Temp folder so that the original one will be unaffected' + # Get the policy file name + [System.String]$PolicyFileName = Split-Path -Path $PolicyPath -Leaf + # make sure no file with the same name already exists in Temp folder + Remove-Item -Path "$UserTempDirectoryPath\$PolicyFileName" -Force -ErrorAction SilentlyContinue + Copy-Item -Path $PolicyPath -Destination $UserTempDirectoryPath -Force + [System.String]$PolicyPath = "$UserTempDirectoryPath\$PolicyFileName" + + Write-Verbose -Message 'Retrieving the Base policy name and ID' + $Xml = [System.Xml.XmlDocument](Get-Content -Path $PolicyPath) + [System.String]$PolicyID = $Xml.SiPolicy.PolicyID + [System.String]$PolicyName = ($Xml.SiPolicy.Settings.Setting | Where-Object -FilterScript { $_.provider -eq 'PolicyInfo' -and $_.valuename -eq 'Name' -and $_.key -eq 'Information' }).value.string + + # Remove any cip file if there is any + Write-Verbose -Message 'Removing any cip file if there is any in the current working directory' + Remove-Item -Path '.\*.cip' -Force -ErrorAction SilentlyContinue + + Write-Verbose -Message 'Creating Audit Mode CIP' + # Add Audit mode policy rule option + Set-RuleOption -FilePath $PolicyPath -Option 3 + # Create CIP for Audit Mode + ConvertFrom-CIPolicy -XmlFilePath $PolicyPath -BinaryFilePath '.\AuditMode.cip' | Out-Null + + Write-Verbose -Message 'Creating Enforced Mode CIP' + # Remove Audit mode policy rule option + Set-RuleOption -FilePath $PolicyPath -Option 3 -Delete + # Create CIP for Enforced Mode + ConvertFrom-CIPolicy -XmlFilePath $PolicyPath -BinaryFilePath '.\EnforcedMode.cip' | Out-Null + + #Region Snap-Back-Guarantee + Write-Verbose -Message 'Creating Enforced Mode SnapBack guarantee' + New-SnapBackGuarantee -Location (Get-Location).Path + + # Deploy the Audit mode CIP + Write-Verbose -Message 'Deploying the Audit mode CIP' + &'C:\Windows\System32\CiTool.exe' --update-policy '.\AuditMode.cip' -json | Out-Null + + Write-ColorfulText -Color Lavender -InputText 'The Base policy with the following details has been Re-Deployed in Audit Mode:' + Write-ColorfulText -Color MintGreen -InputText "PolicyName = $PolicyName" + Write-ColorfulText -Color MintGreen -InputText "PolicyGUID = $PolicyID" + + # Remove Audit Mode CIP + Remove-Item -Path '.\AuditMode.cip' -Force + #Endregion Snap-Back-Guarantee + + # A Try-Catch-Finally block so that if any errors occur, the Base policy will be Re-deployed in enforced mode + Try { + #Region User-Interaction + Write-ColorfulText -Color Pink -InputText 'Audit mode deployed, start installing your programs now' + Write-ColorfulText -Color HotPink -InputText 'When you have finished installing programs, Press Enter to start selecting program directories to scan' + Pause + + # Store the program paths that user browses for in an array + [System.IO.DirectoryInfo[]]$ProgramsPaths = @() + Write-Host -Object 'Select program directories to scan' -ForegroundColor Cyan + + # Showing folder picker GUI to the user for folder path selection + do { + [System.Reflection.Assembly]::LoadWithPartialName('System.windows.forms') | Out-Null + [System.Windows.Forms.FolderBrowserDialog]$OBJ = New-Object -TypeName System.Windows.Forms.FolderBrowserDialog + $OBJ.InitialDirectory = "$env:SystemDrive" + $OBJ.Description = $Description + [System.Windows.Forms.Form]$Spawn = New-Object -TypeName System.Windows.Forms.Form -Property @{TopMost = $true } + [System.String]$Show = $OBJ.ShowDialog($Spawn) + If ($Show -eq 'OK') { $ProgramsPaths += $OBJ.SelectedPath } + Else { break } + } + while ($true) + #Endregion User-Interaction + + # Make sure User browsed for at least 1 directory + # Exit the operation if user didn't select any folder paths + if ($ProgramsPaths.count -eq 0) { + Write-Host -Object 'No program folder was selected, reverting the changes and quitting...' -ForegroundColor Red + # Causing break here to stop operation. Finally block will be triggered to Re-Deploy Base policy in Enforced mode + break + } + } + catch { + # Show any extra info about any possible error that might've occurred + Throw $_ + } + finally { + # Deploy Enforced mode CIP + Write-Verbose -Message 'Finally Block Running' + Update-BasePolicyToEnforced + + # Enforced Mode Snapback removal after base policy has already been successfully re-enforced + Write-Verbose -Message 'Removing the SnapBack guarantee because the base policy has been successfully re-enforced' + + # For CMD Method + Unregister-ScheduledTask -TaskName 'EnforcedModeSnapBack' -Confirm:$false + Remove-Item -Path 'C:\EnforcedModeSnapBack.cmd' -Force + } + + Write-Host -Object 'Here are the paths you selected:' -ForegroundColor Yellow + $ProgramsPaths | ForEach-Object -Process { $_.FullName } + + # Scan each of the folder paths that user selected + Write-Verbose -Message 'Scanning each of the folder paths that user selected' + for ($i = 0; $i -lt $ProgramsPaths.Count; $i++) { + + # Creating a hash table to dynamically add parameters based on user input and pass them to New-Cipolicy cmdlet + [System.Collections.Hashtable]$UserInputProgramFoldersPolicyMakerHashTable = @{ + FilePath = ".\ProgramDir_ScanResults$($i).xml" + ScanPath = $ProgramsPaths[$i] + Level = $Level + Fallback = $Fallbacks + MultiplePolicyFormat = $true + UserWriteablePaths = $true + AllowFileNameFallbacks = $true + } + # Assess user input parameters and add the required parameters to the hash table + if ($SpecificFileNameLevel) { $UserInputProgramFoldersPolicyMakerHashTable['SpecificFileNameLevel'] = $SpecificFileNameLevel } + if ($NoScript) { $UserInputProgramFoldersPolicyMakerHashTable['NoScript'] = $true } + if (!$NoUserPEs) { $UserInputProgramFoldersPolicyMakerHashTable['UserPEs'] = $true } + + # Create the supplemental policy via parameter splatting + Write-Verbose -Message "Currently scanning: $($ProgramsPaths[$i])" + New-CIPolicy @UserInputProgramFoldersPolicyMakerHashTable + } + + # Merge-CiPolicy accepts arrays - collecting all the policy files created by scanning user specified folders + Write-Verbose -Message 'Collecting all the policy files created by scanning user specified folders' + + foreach ($file in (Get-ChildItem -File -Path '.\' -Filter 'ProgramDir_ScanResults*.xml')) { + $PolicyXMLFilesArray += $file.FullName + } + + Write-Verbose -Message 'The following policy xml files are going to be merged into the final Supplemental policy and be deployed on the system:' + $PolicyXMLFilesArray | ForEach-Object -Process { Write-Verbose -Message "$_" } + + # Merge all of the policy XML files in the array into the final Supplemental policy + Write-Verbose -Message 'Merging all of the policy XML files in the array into the final Supplemental policy' + Merge-CIPolicy -PolicyPaths $PolicyXMLFilesArray -OutputFilePath ".\SupplementalPolicy $SuppPolicyName.xml" | Out-Null + + Write-Verbose -Message 'Removing the ProgramDir_ScanResults* xml files' + Remove-Item -Path '.\ProgramDir_ScanResults*.xml' -Force + + #Region Supplemental-policy-processing-and-deployment + Write-Verbose -Message 'Supplemental policy processing and deployment' + + Write-Verbose -Message 'Getting the path of the Supplemental policy' + [System.String]$SuppPolicyPath = ".\SupplementalPolicy $SuppPolicyName.xml" + + Write-Verbose -Message 'Converting the policy to a Supplemental policy type and resetting its ID' + [System.String]$SuppPolicyID = Set-CIPolicyIdInfo -FilePath $SuppPolicyPath -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" -ResetPolicyID -BasePolicyToSupplementPath $PolicyPath + $SuppPolicyID = $SuppPolicyID.Substring(11) + + # Make sure policy rule options that don't belong to a Supplemental policy don't exist + Write-Verbose -Message 'Making sure policy rule options that do not belong to a Supplemental policy do not exist' + @(0, 1, 2, 3, 4, 8, 9, 10, 11, 12, 15, 16, 17, 19, 20) | ForEach-Object -Process { Set-RuleOption -FilePath $SuppPolicyPath -Option $_ -Delete } + + Write-Verbose -Message 'Setting HVCI to Strict' + Set-HVCIOptions -Strict -FilePath $SuppPolicyPath + + Write-Verbose -Message 'Setting the Supplemental policy version to 1.0.0.0' + Set-CIPolicyVersion -FilePath $SuppPolicyPath -Version '1.0.0.0' + + Write-Verbose -Message 'Convert the Supplemental policy to a CIP file' + ConvertFrom-CIPolicy -XmlFilePath $SuppPolicyPath -BinaryFilePath "$SuppPolicyID.cip" | Out-Null + + Write-Verbose -Message 'Deploying the Supplemental policy' + &'C:\Windows\System32\CiTool.exe' --update-policy ".\$SuppPolicyID.cip" -json | Out-Null + + Write-ColorfulText -Color Lavender -InputText 'Supplemental policy with the following details has been deployed in Enforced Mode:' + Write-ColorfulText -Color MintGreen -InputText "SupplementalPolicyName = $SuppPolicyName" + Write-ColorfulText -Color MintGreen -InputText "SupplementalPolicyGUID = $SuppPolicyID" + + Write-Verbose -Message 'Removing the Supplemental policy CIP file after deployment' + Remove-Item -Path ".\$SuppPolicyID.cip" -Force + + # Remove the policy xml file in Temp folder we created earlier + Write-Verbose -Message 'Removing the policy xml file in Temp folder we created earlier' + Remove-Item -Path $PolicyPath -Force + + #Endregion Supplemental-policy-processing-and-deployment + } + } + + if ($AllowNewAppsAuditEvents) { + # Change Code Integrity event logs size + if ($AllowNewAppsAuditEvents -and $LogSize) { + Write-Verbose -Message 'Changing Code Integrity event logs size' + Set-LogSize -LogSize $LogSize + } + + # Make sure there is no leftover from previous runs + Write-Verbose -Message 'Removing any possible files from previous runs' + Remove-Item -Path '.\ProgramDir_ScanResults*.xml' -Force -ErrorAction SilentlyContinue + Remove-Item -Path ".\SupplementalPolicy $SuppPolicyName.xml" -Force -ErrorAction SilentlyContinue + + # Get the current date so that instead of the entire event viewer logs, only audit logs created after running this module will be captured + Write-Verbose -Message 'Getting the current date' + [System.DateTime]$Date = Get-Date + + # An empty array that holds the Policy XML files - This array will eventually be used to create the final Supplemental policy + [System.Object[]]$PolicyXMLFilesArray = @() + + #Initiate Live Audit Mode + + foreach ($PolicyPath in $PolicyPaths) { + # Creating a copy of the original policy in Temp folder so that the original one will be unaffected + Write-Verbose -Message 'Creating a copy of the original policy in Temp folder so that the original one will be unaffected' + # Get the policy file name + [System.String]$PolicyFileName = Split-Path -Path $PolicyPath -Leaf + # make sure no file with the same name already exists in Temp folder + Remove-Item -Path "$UserTempDirectoryPath\$PolicyFileName" -Force -ErrorAction SilentlyContinue + Copy-Item -Path $PolicyPath -Destination $UserTempDirectoryPath -Force + [System.String]$PolicyPath = "$UserTempDirectoryPath\$PolicyFileName" + + Write-Verbose -Message 'Retrieving the Base policy name and ID' + $Xml = [System.Xml.XmlDocument](Get-Content -Path $PolicyPath) + [System.String]$PolicyID = $Xml.SiPolicy.PolicyID + [System.String]$PolicyName = ($Xml.SiPolicy.Settings.Setting | Where-Object -FilterScript { $_.provider -eq 'PolicyInfo' -and $_.valuename -eq 'Name' -and $_.key -eq 'Information' }).value.string + + # Remove any cip file if there is any + Write-Verbose -Message 'Removing any cip file if there is any in the current working directory' + Remove-Item -Path '.\*.cip' -Force -ErrorAction SilentlyContinue + + Write-Verbose -Message 'Creating Audit Mode CIP' + # Add Audit mode policy rule option + Set-RuleOption -FilePath $PolicyPath -Option 3 + # Create CIP for Audit Mode + ConvertFrom-CIPolicy -XmlFilePath $PolicyPath -BinaryFilePath '.\AuditMode.cip' | Out-Null + + Write-Verbose -Message 'Creating Enforced Mode CIP' + # Remove Audit mode policy rule option + Set-RuleOption -FilePath $PolicyPath -Option 3 -Delete + # Create CIP for Enforced Mode + ConvertFrom-CIPolicy -XmlFilePath $PolicyPath -BinaryFilePath '.\EnforcedMode.cip' | Out-Null + + #Region Snap-Back-Guarantee + Write-Verbose -Message 'Creating Enforced Mode SnapBack guarantee' + New-SnapBackGuarantee -Location (Get-Location).Path + + # Deploy the Audit mode CIP + Write-Verbose -Message 'Deploying the Audit mode CIP' + &'C:\Windows\System32\CiTool.exe' --update-policy '.\AuditMode.cip' -json | Out-Null + + Write-ColorfulText -Color Lavender -InputText 'The Base policy with the following details has been Re-Deployed in Audit Mode:' + Write-ColorfulText -Color MintGreen -InputText "PolicyName = $PolicyName" + Write-ColorfulText -Color MintGreen -InputText "PolicyGUID = $PolicyID" + + # Remove Audit Mode CIP + Remove-Item -Path '.\AuditMode.cip' -Force + #Endregion Snap-Back-Guarantee + + # A Try-Catch-Finally block so that if any errors occur, the Base policy will be Re-deployed in enforced mode + Try { + #Region User-Interaction + Write-ColorfulText -Color Pink -InputText 'Audit mode deployed, start installing your programs now' + Write-ColorfulText -Color HotPink -InputText 'When you have finished installing programs, Press Enter to start selecting program directories to scan' + Pause + + # Store the program paths that user browses for in an array + [System.IO.DirectoryInfo[]]$ProgramsPaths = @() + Write-Host -Object 'Select program directories to scan' -ForegroundColor Cyan + + # Showing folder picker GUI to the user for folder path selection + do { + [System.Reflection.Assembly]::LoadWithPartialName('System.windows.forms') | Out-Null + [System.Windows.Forms.FolderBrowserDialog]$OBJ = New-Object -TypeName System.Windows.Forms.FolderBrowserDialog + $OBJ.InitialDirectory = "$env:SystemDrive" + $OBJ.Description = $Description + [System.Windows.Forms.Form]$Spawn = New-Object -TypeName System.Windows.Forms.Form -Property @{TopMost = $true } + [System.String]$Show = $OBJ.ShowDialog($Spawn) + If ($Show -eq 'OK') { $ProgramsPaths += $OBJ.SelectedPath } + Else { break } + } + while ($true) + #Endregion User-Interaction + + # Make sure User browsed for at least 1 directory + # Exit the operation if user didn't select any folder paths + if ($ProgramsPaths.count -eq 0) { + Write-Host -Object 'No program folder was selected, reverting the changes and quitting...' -ForegroundColor Red + # Causing break here to stop operation. Finally block will be triggered to Re-Deploy Base policy in Enforced mode + break + } + + Write-Host -Object 'Here are the paths you selected:' -ForegroundColor Yellow + $ProgramsPaths | ForEach-Object -Process { $_.FullName } + + #Region EventCapturing + + Write-Host -Object 'Scanning Windows Event logs and creating a policy file, please wait...' -ForegroundColor Cyan + + # Extracting the array content from Get-AuditEventLogsProcessing function + $AuditEventLogsProcessingResults = Get-AuditEventLogsProcessing -Date $Date + + # Only create policy for files that are available on the disk (based on Event viewer logs) + # but weren't in user-selected program path(s), if there are any + if ($AuditEventLogsProcessingResults.AvailableFilesPaths) { + + # Using the function to find out which files are not in the user-selected path(s), if any, to only scan those by first copying them to another directory + # this prevents duplicate rule creation and double file copying + $TestFilePathResults = (Test-FilePath -FilePath $AuditEventLogsProcessingResults.AvailableFilesPaths -DirectoryPath $ProgramsPaths).path | Select-Object -Unique + + Write-Verbose -Message "$($TestFilePathResults.count) file(s) have been found in event viewer logs that don't exist in any of the folder paths you selected." + + # Another check to make sure there were indeed files found in Event viewer logs but weren't in any of the user-selected path(s) + if ($TestFilePathResults) { + + # Create a folder in Temp directory to copy the files that are not included in user-selected program path(s) + # but detected in Event viewer audit logs, scan that folder, and in the end delete it + New-Item -Path "$UserTempDirectoryPath\TemporaryScanFolderForEventViewerFiles" -ItemType Directory -Force | Out-Null + + Write-Verbose -Message 'The following file(s) are being copied to the TEMP directory for scanning because they were found in event logs but did not exist in any of the user-selected paths:' + $TestFilePathResults | ForEach-Object -Process { + Write-Verbose -Message "$_" + Copy-Item -Path $_ -Destination "$UserTempDirectoryPath\TemporaryScanFolderForEventViewerFiles\" -Force -ErrorAction SilentlyContinue + } + + # Create a policy XML file for available files on the disk + + # Creating a hash table to dynamically add parameters based on user input and pass them to New-Cipolicy cmdlet + [System.Collections.Hashtable]$AvailableFilesOnDiskPolicyMakerHashTable = @{ + FilePath = '.\RulesForFilesNotInUserSelectedPaths.xml' + ScanPath = "$UserTempDirectoryPath\TemporaryScanFolderForEventViewerFiles\" + Level = $Level + Fallback = $Fallbacks + MultiplePolicyFormat = $true + UserWriteablePaths = $true + AllowFileNameFallbacks = $true + } + # Assess user input parameters and add the required parameters to the hash table + if ($SpecificFileNameLevel) { $AvailableFilesOnDiskPolicyMakerHashTable['SpecificFileNameLevel'] = $SpecificFileNameLevel } + if ($NoScript) { $AvailableFilesOnDiskPolicyMakerHashTable['NoScript'] = $true } + if (!$NoUserPEs) { $AvailableFilesOnDiskPolicyMakerHashTable['UserPEs'] = $true } + + # Create the supplemental policy via parameter splatting + Write-Verbose -Message 'Creating a policy file for files that are available on the disk but were not in user-selected program path(s)' + New-CIPolicy @AvailableFilesOnDiskPolicyMakerHashTable + + # Add the policy XML file to the array that holds policy XML files + $PolicyXMLFilesArray += '.\RulesForFilesNotInUserSelectedPaths.xml' + + # Delete the Temporary folder in the TEMP folder + Write-Verbose -Message 'Deleting the Temporary folder in the TEMP folder' + Remove-Item -Recurse -Path "$UserTempDirectoryPath\TemporaryScanFolderForEventViewerFiles\" -Force + } + } + + # Only create policy for files that are on longer available on the disk if there are any and + # if user chose to include deleted files in the final supplemental policy + if ($AuditEventLogsProcessingResults.DeletedFileHashes -and $IncludeDeletedFiles) { + + Write-Verbose -Message 'Attempting to create a policy for files that are no longer available on the disk but were detected in event viewer logs' + + # Displaying the unique values and count. Even though the DeletedFileHashesEventsPolicy.xml will have many duplicates, the final supplemental policy that will be deployed on the system won't have any duplicates + # Because Merge-CiPolicy will automatically take care of removing them + Write-Verbose -Message "$(($AuditEventLogsProcessingResults.DeletedFileHashes.'File Name' | Select-Object -Unique).count) file(s) have been found in event viewer logs that were run during Audit phase but are no longer on the disk, they are as follows:" + $AuditEventLogsProcessingResults.DeletedFileHashes.'File Name' | Select-Object -Unique | ForEach-Object -Process { + Write-Verbose -Message "$_" + } + + Write-Verbose -Message 'Creating FileRules and RuleRefs for files that are no longer available on the disk but were detected in event viewer logs' + [System.String]$FileRulesHashesResults = Get-FileRules -HashesArray $AuditEventLogsProcessingResults.DeletedFileHashes + [System.String]$RuleRefsHashesResults = (Get-RuleRefs -HashesArray $AuditEventLogsProcessingResults.DeletedFileHashes).Trim() + + # Save the File Rules and File Rule Refs in the FileRulesAndFileRefs.txt in the current working directory for debugging purposes + Write-Verbose -Message 'Saving the File Rules and File Rule Refs in the FileRulesAndFileRefs.txt in the current working directory for debugging purposes' + $FileRulesHashesResults + $RuleRefsHashesResults | Out-File -FilePath FileRulesAndFileRefs.txt -Force + + # Put the Rules and RulesRefs in an empty policy file + Write-Verbose -Message 'Putting the Rules and RulesRefs in an empty policy file' + New-EmptyPolicy -RulesContent $FileRulesHashesResults -RuleRefsContent $RuleRefsHashesResults | Out-File -FilePath .\DeletedFileHashesEventsPolicy.xml -Force + + # adding the policy file that consists of rules from audit even logs, to the array + Write-Verbose -Message 'Adding the policy file (DeletedFileHashesEventsPolicy.xml) that consists of rules from audit even logs, to the array of XML files' + $PolicyXMLFilesArray += '.\DeletedFileHashesEventsPolicy.xml' + } + #Endregion EventCapturing + + #Region Process-Program-Folders-From-User-input + Write-Verbose -Message 'Scanning each of the folder paths that user selected' + + for ($i = 0; $i -lt $ProgramsPaths.Count; $i++) { + + # Creating a hash table to dynamically add parameters based on user input and pass them to New-Cipolicy cmdlet + [System.Collections.Hashtable]$UserInputProgramFoldersPolicyMakerHashTable = @{ + FilePath = ".\ProgramDir_ScanResults$($i).xml" + ScanPath = $ProgramsPaths[$i] + Level = $Level + Fallback = $Fallbacks + MultiplePolicyFormat = $true + UserWriteablePaths = $true + AllowFileNameFallbacks = $true + } + # Assess user input parameters and add the required parameters to the hash table + if ($SpecificFileNameLevel) { $UserInputProgramFoldersPolicyMakerHashTable['SpecificFileNameLevel'] = $SpecificFileNameLevel } + if ($NoScript) { $UserInputProgramFoldersPolicyMakerHashTable['NoScript'] = $true } + if (!$NoUserPEs) { $UserInputProgramFoldersPolicyMakerHashTable['UserPEs'] = $true } + + # Create the supplemental policy via parameter splatting + Write-Verbose -Message "Currently scanning: $($ProgramsPaths[$i])" + New-CIPolicy @UserInputProgramFoldersPolicyMakerHashTable + } + + # Merge-CiPolicy accepts arrays - collecting all the policy files created by scanning user specified folders + Write-Verbose -Message 'Collecting all the policy files created by scanning user specified folders' + + foreach ($file in (Get-ChildItem -File -Path '.\' -Filter 'ProgramDir_ScanResults*.xml')) { + $PolicyXMLFilesArray += $file.FullName + } + #Endregion Process-Program-Folders-From-User-input + + #Region Kernel-protected-files-automatic-detection-and-allow-rule-creation + # This part takes care of Kernel protected files such as the main executable of the games installed through Xbox app + # For these files, only Kernel can get their hashes, it passes them to event viewer and we take them from event viewer logs + # Any other attempts such as "Get-FileHash" or "Get-AuthenticodeSignature" fail and ConfigCI Module cmdlets totally ignore these files and do not create allow rules for them + + Write-Verbose -Message 'Checking for Kernel protected files' + + # Finding the file(s) first and storing them in an array + [System.String[]]$ExesWithNoHash = @() + + # looping through each user-selected path(s) + foreach ($ProgramsPath in $ProgramsPaths) { + + # Making sure the currently processing path has any .exe in it + [System.String[]]$AnyAvailableExes = (Get-ChildItem -File -Recurse -Path $ProgramsPath -Filter '*.exe').FullName + + # if any .exe was found then continue testing them + if ($AnyAvailableExes) { + foreach ($Exe in $AnyAvailableExes) { + try { + # Testing each executable to find the protected ones + Get-FileHash -Path $Exe -ErrorAction Stop | Out-Null + } + # If the executable is protected, it will throw an exception and the script will continue to the next one + # Making sure only the right file is captured by narrowing down the error type. + # E.g., when get-filehash can't get a file's hash because its open by another program, the exception is different: System.IO.IOException + catch [System.UnauthorizedAccessException] { + $ExesWithNoHash += $Exe + } + } + } + } + + # Only proceed if any kernel protected file(s) were found in any of the user-selected directory path(s) + if ($ExesWithNoHash) { + + Write-Verbose -Message 'The following Kernel protected files detected, creating allow rules for them:' + $ExesWithNoHash | ForEach-Object -Process { Write-Verbose -Message "$_" } + + [System.Management.Automation.ScriptBlock]$KernelProtectedHashesBlock = { + foreach ($event in Get-WinEvent -FilterHashtable @{LogName = 'Microsoft-Windows-CodeIntegrity/Operational'; ID = 3076 } -ErrorAction SilentlyContinue | Where-Object -FilterScript { $_.TimeCreated -ge $Date } ) { + $Xml = [System.Xml.XmlDocument]$event.toxml() + $Xml.event.eventdata.data | + ForEach-Object -Begin { $Hash = @{} } -Process { $hash[$_.name] = $_.'#text' } -End { [pscustomobject]$hash } | + ForEach-Object -Process { + if ($_.'File Name' -match ($pattern = '\\Device\\HarddiskVolume(\d+)\\(.*)$')) { + $hardDiskVolumeNumber = $Matches[1] + $remainingPath = $Matches[2] + $getletter = Get-GlobalRootDrives | Where-Object -FilterScript { $_.devicepath -eq "\Device\HarddiskVolume$hardDiskVolumeNumber" } + $usablePath = "$($getletter.DriveLetter)$remainingPath" + $_.'File Name' = $_.'File Name' -replace $pattern, $usablePath + } # Check if file is currently on the disk + if (Test-Path -Path $_.'File Name') { + # Check if the file exits in the $ExesWithNoHash array + if ($ExesWithNoHash -contains $_.'File Name') { + $_ | Select-Object -Property FileVersion, 'File Name', PolicyGUID, 'SHA256 Hash', 'SHA256 Flat Hash', 'SHA1 Hash', 'SHA1 Flat Hash' + } + } + } + } + } + + $KernelProtectedHashesBlockResults = Invoke-Command -ScriptBlock $KernelProtectedHashesBlock + + # Only proceed further if any hashes belonging to the detected kernel protected files were found in Event viewer + # If none is found then skip this part, because user didn't run those files/programs when audit mode was turned on in base policy, so no hash was found in audit logs + if ($KernelProtectedHashesBlockResults) { + + # Save the File Rules and File Rule Refs in the FileRulesAndFileRefs.txt in the current working directory for debugging purposes + (Get-FileRules -HashesArray $KernelProtectedHashesBlockResults) + (Get-RuleRefs -HashesArray $KernelProtectedHashesBlockResults) | Out-File -FilePath KernelProtectedFiles.txt -Force + + # Put the Rules and RulesRefs in an empty policy file + New-EmptyPolicy -RulesContent (Get-FileRules -HashesArray $KernelProtectedHashesBlockResults) -RuleRefsContent (Get-RuleRefs -HashesArray $KernelProtectedHashesBlockResults) | Out-File -FilePath .\KernelProtectedFiles.xml -Force + + # adding the policy file to the array of xml files + $PolicyXMLFilesArray += '.\KernelProtectedFiles.xml' + } + else { + Write-Warning -Message "The following Kernel protected files detected, but no hash was found for them in Event viewer logs.`nThis means you didn't run those files/programs when Audit mode was turned on." + $ExesWithNoHash | ForEach-Object -Process { Write-Warning -Message "$_" } + } + } + else { + Write-Verbose -Message 'No Kernel protected files in the user selected paths were detected' + } + #Endregion Kernel-protected-files-automatic-detection-and-allow-rule-creation + + Write-Verbose -Message 'The following policy xml files are going to be merged into the final Supplemental policy and be deployed on the system:' + $PolicyXMLFilesArray | ForEach-Object -Process { Write-Verbose -Message "$_" } + + # Merge all of the policy XML files in the array into the final Supplemental policy + Merge-CIPolicy -PolicyPaths $PolicyXMLFilesArray -OutputFilePath ".\SupplementalPolicy $SuppPolicyName.xml" | Out-Null + + # Delete these extra files unless user uses -Debug parameter + if (!$Debug) { + Remove-Item -Path '.\RulesForFilesNotInUserSelectedPaths.xml', '.\ProgramDir_ScanResults*.xml' -Force -ErrorAction SilentlyContinue + Remove-Item -Path '.\KernelProtectedFiles.xml', '.\DeletedFileHashesEventsPolicy.xml' -Force -ErrorAction SilentlyContinue + Remove-Item -Path '.\KernelProtectedFiles.txt', '.\FileRulesAndFileRefs.txt' -Force -ErrorAction SilentlyContinue + } + } + # Unlike AllowNewApps parameter, AllowNewAppsAuditEvents parameter performs Event viewer scanning and kernel protected files detection + # So the base policy enforced mode snap back can't happen any sooner than this point + catch { + Throw $_ + } + finally { + # Deploy Enforced mode CIP + Write-Verbose -Message 'Finally Block Running' + Update-BasePolicyToEnforced + + # Enforced Mode Snapback removal after base policy has already been successfully re-enforced + Write-Verbose -Message 'Removing the SnapBack guarantee because the base policy has been successfully re-enforced' + Unregister-ScheduledTask -TaskName 'EnforcedModeSnapBack' -Confirm:$false + Remove-Item -Path 'C:\EnforcedModeSnapBack.cmd' -Force + } + + #Region Supplemental-policy-processing-and-deployment + + Write-Verbose -Message 'Supplemental policy processing and deployment' + [System.String]$SuppPolicyPath = ".\SupplementalPolicy $SuppPolicyName.xml" + + Write-Verbose -Message 'Converting the policy to a Supplemental policy type and resetting its ID' + [System.String]$SuppPolicyID = Set-CIPolicyIdInfo -FilePath $SuppPolicyPath -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" -ResetPolicyID -BasePolicyToSupplementPath $PolicyPath + $SuppPolicyID = $SuppPolicyID.Substring(11) + + # Make sure policy rule options that don't belong to a Supplemental policy don't exist + Write-Verbose -Message 'Making sure policy rule options that do not belong to a Supplemental policy do not exist' + @(0, 1, 2, 3, 4, 8, 9, 10, 11, 12, 15, 16, 17, 19, 20) | ForEach-Object -Process { Set-RuleOption -FilePath $SuppPolicyPath -Option $_ -Delete } + + Write-Verbose -Message 'Setting HVCI to Strict' + Set-HVCIOptions -Strict -FilePath $SuppPolicyPath + + Write-Verbose -Message 'Setting the Supplemental policy version to 1.0.0.0' + Set-CIPolicyVersion -FilePath $SuppPolicyPath -Version '1.0.0.0' + + Write-Verbose -Message 'Convert the Supplemental policy to a CIP file' + ConvertFrom-CIPolicy -XmlFilePath $SuppPolicyPath -BinaryFilePath "$SuppPolicyID.cip" | Out-Null + + Write-Verbose -Message 'Deploying the Supplemental policy' + &'C:\Windows\System32\CiTool.exe' --update-policy ".\$SuppPolicyID.cip" -json | Out-Null + + Write-ColorfulText -Color Lavender -InputText 'Supplemental policy with the following details has been deployed in Enforced Mode:' + Write-ColorfulText -Color MintGreen -InputText "SupplementalPolicyName = $SuppPolicyName" + Write-ColorfulText -Color MintGreen -InputText "SupplementalPolicyGUID = $SuppPolicyID" + + Write-Verbose -Message 'Removing the Supplemental policy CIP file after deployment' + Remove-Item -Path ".\$SuppPolicyID.cip" -Force + + # Remove the policy xml file in Temp folder we created earlier + Remove-Item -Path $PolicyPath -Force + + #Endregion Supplemental-policy-processing-and-deployment + } + } + + if ($MergeSupplementalPolicies) { + foreach ($PolicyPath in $PolicyPaths) { + + #Region Input-policy-verification + Write-Verbose -Message 'Verifying the input policy files' + foreach ($SuppPolicyPath in $SuppPolicyPaths) { + + Write-Verbose -Message "Getting policy ID and type of: $SuppPolicyPath" + $Supplementalxml = [System.Xml.XmlDocument](Get-Content -Path $SuppPolicyPath) + [System.String]$SupplementalPolicyID = $Supplementalxml.SiPolicy.PolicyID + [System.String]$SupplementalPolicyType = $Supplementalxml.SiPolicy.PolicyType + + Write-Verbose -Message 'Getting the IDs of the currently deployed policies on the system' + [System.String[]]$DeployedPoliciesIDs = (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies.PolicyID | ForEach-Object -Process { return "{$_}" } + + # Check the type of the user selected Supplemental policy XML files to make sure they are indeed Supplemental policies + Write-Verbose -Message 'Checking the type of the policy' + if ($SupplementalPolicyType -ne 'Supplemental Policy') { + Throw "The Selected XML file with GUID $SupplementalPolicyID isn't a Supplemental Policy." + } + + # Check to make sure the user selected Supplemental policy XML files are deployed on the system + Write-Verbose -Message 'Checking the deployment status of the policy' + if ($DeployedPoliciesIDs -notcontains $SupplementalPolicyID) { + Throw "The Selected Supplemental XML file with GUID $SupplementalPolicyID isn't deployed on the system." + } + } + #Endregion Input-policy-verification + + Write-Verbose -Message 'Merging the Supplemental policies into a single policy file' + Merge-CIPolicy -PolicyPaths $SuppPolicyPaths -OutputFilePath "$SuppPolicyName.xml" | Out-Null + + # Remove the deployed Supplemental policies that user selected from the system, because we're going to deploy the new merged policy that contains all of them + Write-Verbose -Message 'Removing the deployed Supplemental policies that user selected from the system' + foreach ($SuppPolicyPath in $SuppPolicyPaths) { + + # Get the policy ID of the currently selected Supplemental policy + $Supplementalxml = [System.Xml.XmlDocument](Get-Content -Path $SuppPolicyPath) + [System.String]$SupplementalPolicyID = $Supplementalxml.SiPolicy.PolicyID + + Write-Verbose -Message "Removing policy with ID: $SupplementalPolicyID" + &'C:\Windows\System32\CiTool.exe' --remove-policy $SupplementalPolicyID -json | Out-Null + + # remove the old policy files unless user chose to keep them + if (!$KeepOldSupplementalPolicies) { + Write-Verbose -Message "Removing the old policy file: $SuppPolicyPath" + Remove-Item -Path $SuppPolicyPath -Force + } + } + + Write-Verbose -Message 'Preparing the final merged Supplemental policy for deployment' + Write-Verbose -Message 'Converting the policy to a Supplemental policy type and resetting its ID' + $SuppPolicyID = Set-CIPolicyIdInfo -FilePath "$SuppPolicyName.xml" -ResetPolicyID -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" -BasePolicyToSupplementPath $PolicyPath + $SuppPolicyID = $SuppPolicyID.Substring(11) + + Write-Verbose -Message 'Setting HVCI to Strict' + Set-HVCIOptions -Strict -FilePath "$SuppPolicyName.xml" + + Write-Verbose -Message 'Converting the Supplemental policy to a CIP file' + ConvertFrom-CIPolicy -XmlFilePath "$SuppPolicyName.xml" -BinaryFilePath "$SuppPolicyID.cip" | Out-Null + + Write-Verbose -Message 'Deploying the Supplemental policy' + &'C:\Windows\System32\CiTool.exe' --update-policy "$SuppPolicyID.cip" -json | Out-Null + + Write-ColorfulText -Color TeaGreen -InputText "The Supplemental policy $SuppPolicyName has been deployed on the system, replacing the old ones.`nSystem Restart is not immediately needed but eventually required to finish the removal of the previous individual Supplemental policies." + + Write-Verbose -Message 'Removing the Supplemental policy CIP file after deployment' + Remove-Item -Path "$SuppPolicyID.cip" -Force + } + } + + if ($UpdateBasePolicy) { + + Write-Verbose -Message 'Getting the Microsoft recommended block rules by calling the Get-BlockRulesMeta function' + Get-BlockRulesMeta 6> $null + + Write-Verbose -Message 'Determining the type of the new base policy' + switch ($NewBasePolicyType) { + 'AllowMicrosoft_Plus_Block_Rules' { + Write-Verbose -Message 'The new base policy type is AllowMicrosoft_Plus_Block_Rules' + + Write-Verbose -Message 'Copying the AllowMicrosoft.xml template policy file to the current working directory' + Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowMicrosoft.xml' -Destination '.\AllowMicrosoft.xml' -Force + + Write-Verbose -Message 'Merging the AllowMicrosoft.xml and Microsoft recommended block rules into a single policy file' + Merge-CIPolicy -PolicyPaths .\AllowMicrosoft.xml, '.\Microsoft recommended block rules.xml' -OutputFilePath .\BasePolicy.xml | Out-Null + + Write-Verbose -Message 'Setting the policy name' + Set-CIPolicyIdInfo -FilePath .\BasePolicy.xml -PolicyName "Allow Microsoft Plus Block Rules refreshed On $(Get-Date -Format 'MM-dd-yyyy')" + + Write-Verbose -Message 'Setting the policy rule options' + @(0, 2, 5, 6, 11, 12, 16, 17, 19, 20) | ForEach-Object -Process { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ } + + Write-Verbose -Message 'Removing the unnecessary policy rule options' + @(3, 4, 9, 10, 13, 18) | ForEach-Object -Process { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ -Delete } + } + 'Lightly_Managed_system_Policy' { + Write-Verbose -Message 'The new base policy type is Lightly_Managed_system_Policy' + + Write-Verbose -Message 'Copying the AllowMicrosoft.xml template policy file to the current working directory' + Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowMicrosoft.xml' -Destination '.\AllowMicrosoft.xml' -Force + + Write-Verbose -Message 'Merging the AllowMicrosoft.xml and Microsoft recommended block rules into a single policy file' + Merge-CIPolicy -PolicyPaths .\AllowMicrosoft.xml, '.\Microsoft recommended block rules.xml' -OutputFilePath .\BasePolicy.xml | Out-Null + + Write-Verbose -Message 'Setting the policy name' + Set-CIPolicyIdInfo -FilePath .\BasePolicy.xml -PolicyName "Signed And Reputable policy refreshed on $(Get-Date -Format 'MM-dd-yyyy')" + + Write-Verbose -Message 'Setting the policy rule options' + @(0, 2, 5, 6, 11, 12, 14, 15, 16, 17, 19, 20) | ForEach-Object -Process { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ } + + Write-Verbose -Message 'Removing the unnecessary policy rule options' + @(3, 4, 9, 10, 13, 18) | ForEach-Object -Process { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ -Delete } + + # Configure required services for ISG authorization + Write-Verbose -Message 'Configuring required services for ISG authorization' + Start-Process -FilePath 'C:\Windows\System32\appidtel.exe' -ArgumentList 'start' -Wait -NoNewWindow + Start-Process -FilePath 'C:\Windows\System32\sc.exe' -ArgumentList 'config', 'appidsvc', 'start= auto' -Wait -NoNewWindow + } + 'DefaultWindows_WithBlockRules' { + Write-Verbose -Message 'The new base policy type is DefaultWindows_WithBlockRules' + + Write-Verbose -Message 'Copying the DefaultWindows.xml template policy file to the current working directory' + Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\DefaultWindows_Enforced.xml' -Destination '.\DefaultWindows_Enforced.xml' -Force + + if (Test-Path -Path 'C:\Program Files\PowerShell') { + Write-Verbose -Message 'Scanning the PowerShell core directory ' + + Write-ColorfulText -Color HotPink -InputText 'Creating allow rules for PowerShell in the DefaultWindows base policy so you can continue using this module after deploying it.' + + New-CIPolicy -ScanPath 'C:\Program Files\PowerShell' -Level FilePublisher -NoScript -Fallback Hash -UserPEs -UserWriteablePaths -MultiplePolicyFormat -AllowFileNameFallbacks -FilePath .\AllowPowerShell.xml + + Write-Verbose -Message 'Merging the DefaultWindows.xml, AllowPowerShell.xml, SignTool.xml and Microsoft recommended block rules into a single policy file' + Merge-CIPolicy -PolicyPaths .\DefaultWindows_Enforced.xml, .\AllowPowerShell.xml, '.\Microsoft recommended block rules.xml' -OutputFilePath .\BasePolicy.xml | Out-Null + } + else { + Write-Verbose -Message 'Not including the PowerShell core directory in the policy' + Write-Verbose -Message 'Merging the DefaultWindows.xml, SignTool.xml and Microsoft recommended block rules into a single policy file' + Merge-CIPolicy -PolicyPaths .\DefaultWindows_Enforced.xml, '.\Microsoft recommended block rules.xml' -OutputFilePath .\BasePolicy.xml | Out-Null + } + + Write-Verbose -Message 'Setting the policy name' + Set-CIPolicyIdInfo -FilePath .\BasePolicy.xml -PolicyName "Default Windows Plus Block Rules refreshed On $(Get-Date -Format 'MM-dd-yyyy')" + + Write-Verbose -Message 'Setting the policy rule options' + @(0, 2, 5, 6, 11, 12, 16, 17, 19, 20) | ForEach-Object -Process { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ } + + Write-Verbose -Message 'Removing the unnecessary policy rule options' + @(3, 4, 9, 10, 13, 18) | ForEach-Object -Process { Set-RuleOption -FilePath .\BasePolicy.xml -Option $_ -Delete } + } + } + + if ($UpdateBasePolicy -and $RequireEVSigners) { + Write-Verbose -Message 'Adding the EV Signers rule option to the base policy' + Set-RuleOption -FilePath .\BasePolicy.xml -Option 8 + } + + # Remove the extra files create during module operation that are no longer necessary + if (!$Debug) { + Remove-Item -Path '.\AllowPowerShell.xml', '.\DefaultWindows_Enforced.xml', '.\AllowMicrosoft.xml' -Force -ErrorAction SilentlyContinue + Remove-Item -Path '.\Microsoft recommended block rules.xml' -Force + } + + # Get the policy ID of the currently deployed base policy based on the policy name that user selected + Write-Verbose -Message 'Getting the policy ID of the currently deployed base policy based on the policy name that user selected' + [System.String]$CurrentID = ((&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { $_.IsSystemPolicy -ne 'True' } | Where-Object -FilterScript { $_.Friendlyname -eq $CurrentBasePolicyName }).BasePolicyID + $CurrentID = "{$CurrentID}" + + Write-Verbose -Message "This is the current ID of deployed base policy that is going to be used in the new base policy: $CurrentID" + Write-Verbose -Message 'Reading the current base policy XML file' + [System.Xml.XmlDocument]$Xml = Get-Content -Path '.\BasePolicy.xml' + + Write-Verbose -Message 'Setting the policy ID and Base policy ID to the current base policy ID in the generated XML file' + $Xml.SiPolicy.PolicyID = $CurrentID + $Xml.SiPolicy.BasePolicyID = $CurrentID + + Write-Verbose -Message 'Saving the updated XML file' + $Xml.Save('.\BasePolicy.xml') + + Write-Verbose -Message 'Setting the policy version to 1.0.0.1' + Set-CIPolicyVersion -FilePath .\BasePolicy.xml -Version '1.0.0.1' + + Write-Verbose -Message 'Setting HVCI to Strict' + Set-HVCIOptions -Strict -FilePath .\BasePolicy.xml + + Write-Verbose -Message 'Converting the base policy to a CIP file' + ConvertFrom-CIPolicy -XmlFilePath '.\BasePolicy.xml' -BinaryFilePath "$CurrentID.cip" | Out-Null + + Write-Verbose -Message 'Deploying the new base policy with the same GUID on the system' + &'C:\Windows\System32\CiTool.exe' --update-policy "$CurrentID.cip" -json | Out-Null + + Write-Verbose -Message 'Removing the base policy CIP file after deployment' + Remove-Item -Path "$CurrentID.cip" -Force + + # Keep the new base policy XML file that was just deployed, in the current directory, so user can keep it for later + # Defining a hashtable that contains the policy names and their corresponding XML file names + [System.Collections.Hashtable]$PolicyFiles = @{ + 'AllowMicrosoft_Plus_Block_Rules' = 'AllowMicrosoftPlusBlockRules.xml' + 'Lightly_Managed_system_Policy' = 'SignedAndReputable.xml' + 'DefaultWindows_WithBlockRules' = 'DefaultWindowsPlusBlockRules.xml' + } + + Write-Verbose -Message 'Making sure a policy file with the same name as the current base policy does not exist in the current working directory' + Remove-Item -Path $PolicyFiles[$NewBasePolicyType] -Force -ErrorAction SilentlyContinue + + Write-Verbose -Message 'Renaming the base policy XML file to match the new base policy type' + Rename-Item -Path '.\BasePolicy.xml' -NewName $PolicyFiles[$NewBasePolicyType] -Force + + Write-ColorfulText -Color Pink -InputText "Base Policy has been successfully updated to $NewBasePolicyType" + + if (Get-CommonWDACConfig -UnsignedPolicyPath) { + Write-Verbose -Message 'Replacing the old unsigned policy path in User Configurations with the new one' + Set-CommonWDACConfig -UnsignedPolicyPath (Get-ChildItem -Path $PolicyFiles[$NewBasePolicyType]).FullName | Out-Null + } + } + } + + <# +.SYNOPSIS + Edits Unsigned WDAC policies deployed on the system +.LINK + https://github.com/HotCakeX/Harden-Windows-Security/wiki/Edit-WDACConfig +.DESCRIPTION + Using official Microsoft methods, Edits non-signed WDAC policies deployed on the system +.COMPONENT + Windows Defender Application Control, ConfigCI PowerShell module +.FUNCTIONALITY + Using official Microsoft methods, Edits non-signed WDAC policies deployed on the system +.PARAMETER AllowNewApps + While an unsigned WDAC policy is already deployed on the system, rebootlessly turn on Audit mode in it, which will allow you to install a new app that was otherwise getting blocked. +.PARAMETER AllowNewAppsAuditEvents + While an unsigned WDAC policy is already deployed on the system, rebootlessly turn on Audit mode in it, which will allow you to install a new app that was otherwise getting blocked. +.PARAMETER MergeSupplementalPolicies + Merges multiple deployed supplemental policies into 1 single supplemental policy, removes the old ones, deploys the new one. System restart needed to take effect. +.PARAMETER UpdateBasePolicy + It can rebootlessly change the type of the deployed base policy. It can update the recommended block rules and/or change policy rule options in the deployed base policy. +.PARAMETER SkipVersionCheck + Can be used with any parameter to bypass the online version check - only to be used in rare cases + It is used by the entire Cmdlet. +.PARAMETER Level + The level that determines how the selected folder will be scanned. + The default value for it is FilePublisher. +.PARAMETER Fallbacks + The fallback level(s) that determine how the selected folder will be scanned. + The default value for it is Hash. +.PARAMETER LogSize + The log size to set for Code Integrity/Operational event logs + The accepted values are between 1024 KB and 18014398509481983 KB + The max range is the maximum allowed log size by Windows Event viewer +.INPUTS + System.Int64 + System.String[] + System.String + System.Management.Automation.SwitchParameter +.OUTPUTS + System.String +#> +} + +# Importing argument completer ScriptBlocks +. "$ModuleRootPath\Resources\ArgumentCompleters.ps1" +Register-ArgumentCompleter -CommandName 'Edit-WDACConfig' -ParameterName 'PolicyPaths' -ScriptBlock $ArgumentCompleterPolicyPathsBasePoliciesOnly +Register-ArgumentCompleter -CommandName 'Edit-WDACConfig' -ParameterName 'SuppPolicyPaths' -ScriptBlock $ArgumentCompleterPolicyPathsSupplementalPoliciesOnly diff --git a/WDACConfig/WDACConfig Module Files/Core/Get-CommonWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Get-CommonWDACConfig.psm1 new file mode 100644 index 000000000..66fd80281 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/Core/Get-CommonWDACConfig.psm1 @@ -0,0 +1,118 @@ +Function Get-CommonWDACConfig { + [CmdletBinding()] + Param( + [parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$CertCN, + [parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$CertPath, + [parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$SignToolPath, + [parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$SignedPolicyPath, + [parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$UnsignedPolicyPath, + [parameter(Mandatory = $false, DontShow = $true)][System.Management.Automation.SwitchParameter]$StrictKernelPolicyGUID, + [parameter(Mandatory = $false, DontShow = $true)][System.Management.Automation.SwitchParameter]$StrictKernelNoFlightRootsPolicyGUID, + [parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$Open, + [parameter(Mandatory = $false, DontShow = $true)][System.Management.Automation.SwitchParameter]$LastUpdateCheck + ) + begin { + # Importing the $PSDefaultParameterValues to the current session, prior to everything else + . "$ModuleRootPath\CoreExt\PSDefaultParameterValues.ps1" + # Importing the required sub-modules + Write-Verbose -Message 'Importing the required sub-modules' + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Write-ColorfulText.psm1" -Force + + # Create User configuration folder if it doesn't already exist + if (-NOT (Test-Path -Path "$UserAccountDirectoryPath\.WDACConfig\")) { + New-Item -ItemType Directory -Path "$UserAccountDirectoryPath\.WDACConfig\" -Force -ErrorAction Stop | Out-Null + Write-Verbose -Message 'The .WDACConfig folder in the current user folder has been created because it did not exist.' + } + + # Create User configuration file if it doesn't already exist + if (-NOT (Test-Path -Path "$UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json")) { + New-Item -ItemType File -Path "$UserAccountDirectoryPath\.WDACConfig\" -Name 'UserConfigurations.json' -Force -ErrorAction Stop | Out-Null + Write-Verbose -Message 'The UserConfigurations.json file in \.WDACConfig\ folder has been created because it did not exist.' + } + + if ($Open) { + . "$UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" + break + } + + # Display this message if User Configuration file is empty + if ($null -eq (Get-Content -Path "$UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json")) { + Write-Verbose -Message 'Your current WDAC User Configurations is empty.' + # set a boolean value that returns from the Process and End blocks as well + [System.Boolean]$ReturnAndDone = $true + + Return + } + + Write-Verbose -Message 'Reading the current user configurations' + [PSCustomObject]$CurrentUserConfigurations = Get-Content -Path "$UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" + + # If the file exists but is corrupted and has bad values, rewrite it + try { + $CurrentUserConfigurations = $CurrentUserConfigurations | ConvertFrom-Json + } + catch { + Write-Warning -Message 'The UserConfigurations.json was corrupted, clearing it.' + Set-Content -Path "$UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" -Value '' + } + } + + process {} + + end { + + if ($true -eq $ReturnAndDone) { return } + + # Use a switch statement to check which parameter is present and output the corresponding value from the json file + switch ($true) { + $SignedPolicyPath.IsPresent { Write-Output -InputObject $CurrentUserConfigurations.SignedPolicyPath } + $UnsignedPolicyPath.IsPresent { Write-Output -InputObject $CurrentUserConfigurations.UnsignedPolicyPath } + $SignToolPath.IsPresent { Write-Output -InputObject $CurrentUserConfigurations.SignToolCustomPath } + $CertCN.IsPresent { Write-Output -InputObject $CurrentUserConfigurations.CertificateCommonName } + $StrictKernelPolicyGUID.IsPresent { Write-Output -InputObject $CurrentUserConfigurations.StrictKernelPolicyGUID } + $StrictKernelNoFlightRootsPolicyGUID.IsPresent { Write-Output -InputObject $CurrentUserConfigurations.StrictKernelNoFlightRootsPolicyGUID } + $CertPath.IsPresent { Write-Output -InputObject $CurrentUserConfigurations.CertificatePath } + $LastUpdateCheck.IsPresent { Write-Output -InputObject $CurrentUserConfigurations.LastUpdateCheck } + Default { + # If no parameter is present, display all the values + Write-ColorfulText -Color Pink -InputText "`nThis is your current WDAC User Configurations: " + Write-Output -InputObject $CurrentUserConfigurations + } + } + } + <# +.SYNOPSIS + Query and Read common values for parameters used by WDACConfig module +.LINK + https://github.com/HotCakeX/Harden-Windows-Security/wiki/Get-CommonWDACConfig +.DESCRIPTION + Reads and gets the values from the User Config Json file, used by the module internally and also to display the values on the console for the user +.COMPONENT + Windows Defender Application Control, ConfigCI PowerShell module, WDACConfig module +.FUNCTIONALITY + Reads and gets the values from the User Config Json file, used by the module internally and also to display the values on the console for the user +.PARAMETER SignedPolicyPath + Shows the path to a Signed WDAC xml policy +.PARAMETER UnsignedPolicyPath + Shows the path to an Unsigned WDAC xml policy +.PARAMETER CertCN + Shows the certificate common name +.PARAMETER SignToolPath + Shows the path to the SignTool.exe +.PARAMETER CertPath + Shows the path to a .cer certificate file +.PARAMETER Open + Opens the User Configuration file with the default app assigned to open Json files +.PARAMETER StrictKernelPolicyGUID + Shows the GUID of the Strict Kernel mode policy +.PARAMETER StrictKernelNoFlightRootsPolicyGUID + Shows the GUID of the Strict Kernel no Flights root mode policy +.INPUTS + System.Management.Automation.SwitchParameter +.OUTPUTS + System.Object[] + System.DateTime + System.String + System.Guid +#> +} diff --git a/WDACConfig/WDACConfig Module Files/Core/Invoke-WDACSimulation.psm1 b/WDACConfig/WDACConfig Module Files/Core/Invoke-WDACSimulation.psm1 new file mode 100644 index 000000000..bb69678e5 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/Core/Invoke-WDACSimulation.psm1 @@ -0,0 +1,313 @@ +Function Invoke-WDACSimulation { + [CmdletBinding( + PositionalBinding = $false, + SupportsShouldProcess = $true + )] + Param( + [ValidateScript({ Test-Path -Path $_ -PathType 'Container' }, ErrorMessage = 'The path you selected is not a folder path.')] + [Parameter(Mandatory = $true)][System.IO.DirectoryInfo]$FolderPath, + + [ValidateScript({ Test-Path -Path $_ -PathType 'Leaf' }, ErrorMessage = 'The path you selected is not a file path.')] + [Parameter(Mandatory = $true)][System.IO.FileInfo]$XmlFilePath, + + [Parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$SkipVersionCheck + ) + + begin { + # Detecting if Verbose switch is used + $PSBoundParameters.Verbose.IsPresent ? ([System.Boolean]$Verbose = $true) : ([System.Boolean]$Verbose = $false) | Out-Null + + # Importing the $PSDefaultParameterValues to the current session, prior to everything else + . "$ModuleRootPath\CoreExt\PSDefaultParameterValues.ps1" + + # Importing resources such as functions by dot-sourcing so that they will run in the same scope and their variables will be usable + . "$ModuleRootPath\Resources\Resources2.ps1" + + # Importing the required sub-modules + Write-Verbose -Message 'Importing the required sub-modules' + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Update-self.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Write-ColorfulText.psm1" -Force + + # if -SkipVersionCheck wasn't passed, run the updater + # Redirecting the Update-Self function's information Stream to $null because Write-Host + # Used by Write-ColorfulText outputs to both information stream and host console + if (-NOT $SkipVersionCheck) { Update-self 6> $null } + + # The total number of the main steps for the progress bar to render + [System.Int16]$TotalSteps = 4 + [System.Int16]$CurrentStep = 0 + } + + process { + # Store the processed results of the valid Signed files + [System.Object[]]$SignedResult = @() + + # File paths of the files allowed by Signer/certificate + [System.IO.FileInfo[]]$AllowedSignedFilePaths = @() + + # File paths of the files allowed by Hash + [System.IO.FileInfo[]]$AllowedUnsignedFilePaths = @() + + # Stores the final object of all of the results + [System.Object[]]$MegaOutputObject = @() + + # File paths of the Signed files with HashMismatch Status + [System.IO.FileInfo[]]$SignedHashMismatchFilePaths = @() + + # File paths of the Signed files with a status that doesn't fall into any other category + [System.IO.FileInfo[]]$SignedButUnknownFilePaths = @() + + # 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) + + [System.String[]]$SHA256HashesFromXML = (Get-FileRuleOutput -xmlPath $XmlFilePath).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-ChildItem -Recurse -Path $FolderPath -File -Include '*.sys', '*.exe', '*.com', '*.dll', '*.ocx', '*.msp', '*.mst', '*.msi', '*.js', '*.vbs', '*.ps1', '*.appx').FullName + + # 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.' } + + try { + + # Loop through each file + Write-Verbose -Message 'Looping through each supported file' + + $CurrentStep++ + Write-Progress -Id 0 -Activity 'Looping through each supported file' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) + + # The total number of the sub steps for the progress bar to render + [System.Int64]$TotalSubSteps = $CollectedFiles.Count + [System.Int64]$CurrentSubStep = 0 + + foreach ($CurrentFilePath in $CollectedFiles) { + + Write-Verbose -Message "Processing file: $CurrentFilePath" + + $CurrentSubStep++ + Write-Progress -Id 1 -ParentId 0 -Activity "Processing file $CurrentSubStep/$TotalSubSteps" -Status "$CurrentFilePath" -PercentComplete ($CurrentSubStep / $TotalSubSteps * 100) + + # Check see if the file's hash exists in the XML file regardless of whether it's signed or not + # This is because WDAC policies sometimes have hash rules for signed files too + # So here we prioritize being authorized by file hash over being authorized by Signature + try { + Write-Verbose -Message 'Using Get-AppLockerFileInformation to retrieve the hashes of the file' + [System.String]$CurrentFilePathHash = (Get-AppLockerFileInformation -Path $CurrentFilePath -ErrorAction Stop).hash -replace 'SHA256 0x', '' + } + catch { + Write-Verbose -Message 'Get-AppLockerFileInformation failed, using New-CIPolicyRule cmdlet...' + [System.Collections.ArrayList]$CurrentHashOutput = New-CIPolicyRule -Level hash -Fallback none -AllowFileNameFallbacks -UserWriteablePaths -DriverFilePath $CurrentFilePath + [System.String]$CurrentFilePathHash = ($CurrentHashOutput | Where-Object -FilterScript { $_.name -like '*Hash Sha256*' }).attributes.hash + } + + # if the file's hash exists in the XML file then add the file's path to the allowed files and do not check anymore that whether the file is signed or not + if ($CurrentFilePathHash -in $SHA256HashesFromXML) { + Write-Verbose -Message 'Hash of the file exists in the supplied XML file' + $AllowedUnsignedFilePaths += $CurrentFilePath + } + # If the file's hash does not exist in the supplied XML file, then check its signature + else { + # Get the status of file's signature + switch ((Get-AuthenticodeSignature -FilePath $CurrentFilePath).Status) { + # If the file is signed and valid + 'valid' { + # Use the function in Resources2.ps1 file to process it + Write-Verbose -Message 'The file is signed and has valid signature' + $SignedResult += Compare-SignerAndCertificate -XmlFilePath $XmlFilePath -SignedFilePath $CurrentFilePath | Where-Object -FilterScript { ($_.CertRootMatch -eq $true) -and ($_.CertNameMatch -eq $true) -and ($_.CertPublisherMatch -eq $true) } + break + } + # If the file is signed but is tampered + 'HashMismatch' { + Write-Warning -Message 'The file has hash mismatch, it is most likely tampered.' + $SignedHashMismatchFilePaths += $CurrentFilePath + break + } + # If the file is signed but has unknown signature status + default { + Write-Verbose -Message 'The file has unknown signature status' + $SignedButUnknownFilePaths += $CurrentFilePath + break + } + } + } + } + } + catch { + # Complete the main progress bar because there was an error + Write-Progress -Id 0 -Activity 'WDAC Simulation interrupted.' -Completed + # Throw whatever error that was encountered + throw $_ + } + finally { + # Complete the nested progress bar whether there was an error or not + Write-Progress -Id 1 -Activity 'All of the files have been processed.' -Completed + } + + $CurrentStep++ + Write-Progress -Id 0 -Activity 'Preparing the output' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) + + # File paths of the files allowed by Signer/certificate, Unique + [System.Object[]]$AllowedSignedFilePaths = $SignedResult.FilePath | Get-Unique + + if ($AllowedUnsignedFilePaths) { + # Loop through the first array and create output objects with the file path and source + Write-Verbose -Message 'Looping through the array of files allowed by hash' + foreach ($Path in $AllowedUnsignedFilePaths) { + # Create a hash table with the file path and source + [System.Collections.Hashtable]$Object = @{ + FilePath = $Path + Source = 'Hash' + Permission = 'Allowed' + } + # Convert the hash table to a PSObject and add it to the output array + $MegaOutputObject += New-Object -TypeName PSObject -Property $Object + } + } + + # For valid Signed files + if ($AllowedSignedFilePaths) { + # Loop through the second array and create output objects with the file path and source + Write-Verbose -Message 'Looping through the array of files allowed by valid signature' + foreach ($Path in $AllowedSignedFilePaths) { + # Create a hash table with the file path and source properties + [System.Collections.Hashtable]$Object = @{ + FilePath = $Path + Source = 'Signer' + Permission = 'Allowed' + } + # Convert the hash table to a PSObject and add it to the output array + $MegaOutputObject += New-Object -TypeName PSObject -Property $Object + } + } + + # For Signed files with mismatch signature status + if ($SignedHashMismatchFilePaths) { + Write-Verbose -Message 'Looping through the array of signed files with hash mismatch' + # Loop through the second array and create output objects with the file path and source + foreach ($Path in $SignedHashMismatchFilePaths) { + # Create a hash table with the file path and source properties + [System.Collections.Hashtable]$Object = @{ + FilePath = $Path + Source = 'Signer' + Permission = 'Not Allowed - Hash Mismatch' + } + # Convert the hash table to a PSObject and add it to the output array + $MegaOutputObject += New-Object -TypeName PSObject -Property $Object + } + } + + # For Signed files with Unknown signature status + if ($SignedButUnknownFilePaths) { + Write-Verbose -Message 'Looping through the array of files with unknown signature status' + # Loop through the second array and create output objects with the file path and source + foreach ($Path in $SignedButUnknownFilePaths) { + # Create a hash table with the file path and source properties + [System.Collections.Hashtable]$Object = @{ + FilePath = $Path + Source = 'Signer' + Permission = 'Not Allowed - Expired or unknown' + } + # Convert the hash table to a PSObject and add it to the output array + $MegaOutputObject += New-Object -TypeName PSObject -Property $Object + } + } + + # Unique number of files allowed by hash - used for counting only + $UniqueFilesAllowedByHash = $MegaOutputObject | Select-Object -Property FilePath, source, Permission -Unique | Where-Object -FilterScript { $_.source -eq 'hash' } + + # To detect files that are not allowed + + # Check if any supported files were found in the user provided directory and any of them was processed by signer or was allowed by hash + if ($($MegaOutputObject.Filepath) -and $CollectedFiles) { + # Compare the paths of all of the supported files found in user provided directory with the array of files that were processed by Signer or allowed by hash in the policy + # Then save the output to a different array + [System.Object[]]$FinalComparisonForFilesNotAllowed = Compare-Object -ReferenceObject $($MegaOutputObject.Filepath) -DifferenceObject $CollectedFiles -PassThru | Where-Object -FilterScript { $_.SideIndicator -eq '=>' } + } + + # If there are any files in the user selected directory that is not allowed by the policy + if ($FinalComparisonForFilesNotAllowed) { + Write-Verbose -Message 'Looping through the array of files not allowed by the policy' + foreach ($Path in $FinalComparisonForFilesNotAllowed) { + # Create a hash table with the file path and source properties + [System.Collections.Hashtable]$Object = @{ + FilePath = $Path + Source = 'N/A' + Permission = 'Not Allowed' + } + # Convert the hash table to a PSObject and add it to the output array + $MegaOutputObject += New-Object -TypeName PSObject -Property $Object + } + } + } + + end { + # Change the color of the Table header + $PSStyle.Formatting.TableHeader = "$($PSStyle.Foreground.FromRGB(255,165,0))" + + # Display the final main output array as a table - allowed files + $MegaOutputObject | Select-Object -Property FilePath, + + @{ + Label = 'Source' + Expression = + { switch ($_.source) { + { $_ -eq 'Signer' } { $color = "$($PSStyle.Foreground.FromRGB(152,255,152))" } # Use PSStyle to set the color + { $_ -eq 'Hash' } { $color = "$($PSStyle.Foreground.FromRGB(255,255,49))" } # Use PSStyle to set the color + { $_ -eq 'N/A' } { $color = "$($PSStyle.Foreground.FromRGB(255,20,147))" } # Use PSStyle to set the color + } + "$color$($_.source)$($PSStyle.Reset)" # Use PSStyle to reset the color + } + }, Permission -Unique | Sort-Object -Property Permission | Format-Table -Property FilePath, Source, Permission + + # Showing Signature based allowed file details + Write-ColorfulText -Color Lavender -InputText "`n$($AllowedSignedFilePaths.count) File(s) Inside the Selected Folder Are Allowed by Signatures by Your Policy." + + # Showing Hash based allowed file details + Write-ColorfulText -Color Lavender -InputText "$($UniqueFilesAllowedByHash.count) File(s) Inside the Selected Folder Are Allowed by Hashes by Your Policy.`n" + + # Export the output as CSV + $MegaOutputObject | Select-Object -Property FilePath, source, Permission -Unique | Sort-Object -Property Permission | Export-Csv -Path .\WDACSimulationOutput.csv -Force + + Write-Progress -Id 0 -Activity 'WDAC Simulation completed.' -Completed + } + + <# +.SYNOPSIS + Simulates the deployment of the WDAC policy. +.LINK + https://github.com/HotCakeX/Harden-Windows-Security/wiki/Invoke-WDACSimulation +.DESCRIPTION + Simulates the deployment of the WDAC policy by analyzing a folder and checking which of the files in the folder are allowed by a user selected policy xml file +.COMPONENT + Windows Defender Application Control, ConfigCI PowerShell module +.FUNCTIONALITY + Simulates the deployment of the WDAC policy +.PARAMETER FolderPath + Provide path to a folder where you want WDAC simulation to take place +.PARAMETER XmlFilePath + Provide path to a policy xml file that you want the cmdlet to simulate its deployment and running files against it +.PARAMETER SkipVersionCheck + Can be used with any parameter to bypass the online version check - only to be used in rare cases + It is used by the entire Cmdlet. +.INPUTS + System.IO.FileInfo + System.IO.DirectoryInfo + System.Management.Automation.SwitchParameter +.OUTPUTS + System.Object[] + System.String +#> +} + +# Importing argument completer ScriptBlocks +. "$ModuleRootPath\Resources\ArgumentCompleters.ps1" +Register-ArgumentCompleter -CommandName 'Invoke-WDACSimulation' -ParameterName 'FolderPath' -ScriptBlock $ArgumentCompleterFolderPathsPicker +Register-ArgumentCompleter -CommandName 'Invoke-WDACSimulation' -ParameterName 'XmlFilePath' -ScriptBlock $ArgumentCompleterXmlFilePathsPicker diff --git a/WDACConfig/New-DenyWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/New-DenyWDACConfig.psm1 similarity index 51% rename from WDACConfig/New-DenyWDACConfig.psm1 rename to WDACConfig/WDACConfig Module Files/Core/New-DenyWDACConfig.psm1 index 4b09a996c..9cefed301 100644 --- a/WDACConfig/New-DenyWDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/New-DenyWDACConfig.psm1 @@ -1,6 +1,5 @@ -#Requires -RunAsAdministrator -function New-DenyWDACConfig { - [CmdletBinding( +Function New-DenyWDACConfig { + [CmdletBinding( DefaultParameterSetName = 'Drivers', PositionalBinding = $false, SupportsShouldProcess = $true, @@ -9,20 +8,20 @@ function New-DenyWDACConfig { Param( # Main parameters for position 0 [Alias('N')] - [Parameter(Mandatory = $false, ParameterSetName = 'Normal')][Switch]$Normal, + [Parameter(Mandatory = $false, ParameterSetName = 'Normal')][System.Management.Automation.SwitchParameter]$Normal, [Alias('D')] - [Parameter(Mandatory = $false, ParameterSetName = 'Drivers')][Switch]$Drivers, + [Parameter(Mandatory = $false, ParameterSetName = 'Drivers')][System.Management.Automation.SwitchParameter]$Drivers, [Alias('P')] - [parameter(mandatory = $false, ParameterSetName = 'Installed AppXPackages')][switch]$InstalledAppXPackages, + [parameter(mandatory = $false, ParameterSetName = 'Installed AppXPackages')][System.Management.Automation.SwitchParameter]$InstalledAppXPackages, [parameter(Mandatory = $true, ParameterSetName = 'Installed AppXPackages', ValueFromPipelineByPropertyName = $true)] [System.String]$PackageName, [ValidatePattern('^[a-zA-Z0-9 ]+$', ErrorMessage = 'The Supplemental Policy Name can only contain alphanumeric characters and spaces.')] - [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] # Used by the entire Cmdlet - [System.String]$PolicyName, - - [ValidateScript({ Test-Path $_ -PathType 'Container' }, ErrorMessage = 'The path you selected is not a folder path.')] + [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] + [System.String]$PolicyName, + + [ValidateScript({ Test-Path -Path $_ -PathType 'Container' }, ErrorMessage = 'The path you selected is not a folder path.')] [Parameter(Mandatory = $false, ParameterSetName = 'Normal')] [Parameter(Mandatory = $false, ParameterSetName = 'Drivers')] [System.String[]]$ScanLocations, @@ -30,69 +29,80 @@ function New-DenyWDACConfig { [ValidateSet([Levelz])] [Parameter(Mandatory = $false, ParameterSetName = 'Normal')] [Parameter(Mandatory = $false, ParameterSetName = 'Drivers')] - [System.String]$Level = 'FilePublisher', # Setting the default value for the Level parameter + [System.String]$Level = 'FilePublisher', [ValidateSet([Fallbackz])] [Parameter(Mandatory = $false, ParameterSetName = 'Normal')] [Parameter(Mandatory = $false, ParameterSetName = 'Drivers')] - [System.String[]]$Fallbacks = 'Hash', # Setting the default value for the Fallbacks parameter - + [System.String[]]$Fallbacks = 'Hash', + [ValidateSet('OriginalFileName', 'InternalName', 'FileDescription', 'ProductName', 'PackageFamilyName', 'FilePath')] [Parameter(Mandatory = $false, ParameterSetName = 'Normal')] [System.String]$SpecificFileNameLevel, [Parameter(Mandatory = $false, ParameterSetName = 'Normal')] - [Switch]$NoUserPEs, + [System.Management.Automation.SwitchParameter]$NoUserPEs, [Parameter(Mandatory = $false, ParameterSetName = 'Normal')] - [Switch]$NoScript, + [System.Management.Automation.SwitchParameter]$NoScript, - [Parameter(Mandatory = $false)] # Used by the entire Cmdlet - [Switch]$Deploy, - - [Parameter(Mandatory = $false)][Switch]$SkipVersionCheck # Used by the entire Cmdlet + [Parameter(Mandatory = $false)] + [System.Management.Automation.SwitchParameter]$Deploy, + + [Parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$SkipVersionCheck ) - begin { + begin { + # Detecting if Verbose switch is used + $PSBoundParameters.Verbose.IsPresent ? ([System.Boolean]$Verbose = $true) : ([System.Boolean]$Verbose = $false) | Out-Null + + # Importing the $PSDefaultParameterValues to the current session, prior to everything else + . "$ModuleRootPath\CoreExt\PSDefaultParameterValues.ps1" - # Importing resources such as functions by dot-sourcing so that they will run in the same scope and their variables will be usable - . "$psscriptroot\Resources.ps1" + # Importing the required sub-modules + Write-Verbose -Message 'Importing the required sub-modules' + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Update-self.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Write-ColorfulText.psm1" -Force # Detecting if Debug switch is used, will do debugging actions based on that - $Debug = $PSBoundParameters.Debug.IsPresent + $PSBoundParameters.Debug.IsPresent ? ([System.Boolean]$Debug = $true) : ([System.Boolean]$Debug = $false) | Out-Null # argument tab auto-completion and ValidateSet for Fallbacks Class Fallbackz : System.Management.Automation.IValidateSetValuesGenerator { [System.String[]] GetValidValues() { $Fallbackz = ('Hash', 'FileName', 'SignedVersion', 'Publisher', 'FilePublisher', 'LeafCertificate', 'PcaCertificate', 'RootCertificate', 'WHQL', 'WHQLPublisher', 'WHQLFilePublisher', 'PFN', 'FilePath', 'None') - + return [System.String[]]$Fallbackz } } - + # argument tab auto-completion and ValidateSet for level Class Levelz : System.Management.Automation.IValidateSetValuesGenerator { [System.String[]] GetValidValues() { $Levelz = ('Hash', 'FileName', 'SignedVersion', 'Publisher', 'FilePublisher', 'LeafCertificate', 'PcaCertificate', 'RootCertificate', 'WHQL', 'WHQLPublisher', 'WHQLFilePublisher', 'PFN', 'FilePath', 'None') - + return [System.String[]]$Levelz } } - # Stop operation as soon as there is an error anywhere, unless explicitly specified otherwise - $ErrorActionPreference = 'Stop' - if (-NOT $SkipVersionCheck) { . Update-self } + # if -SkipVersionCheck wasn't passed, run the updater + # Redirecting the Update-Self function's information Stream to $null because Write-Host + # Used by Write-ColorfulText outputs to both information stream and host console + if (-NOT $SkipVersionCheck) { Update-self 6> $null } } - + process { + # Create deny supplemental policy for general files, apps etc. if ($Normal) { - # remove any possible files from previous runs + + Write-Verbose -Message 'Removing any possible files from previous runs' Remove-Item -Path '.\ProgramDir_ScanResults*.xml' -Force -ErrorAction SilentlyContinue + # An array to hold the temporary xml files of each user-selected folders [System.Object[]]$PolicyXMLFilesArray = @() - ######################## Process Program Folders From User input ##################### + Write-Verbose -Message 'Processing Program Folders From User input' for ($i = 0; $i -lt $ScanLocations.Count; $i++) { # Creating a hash table to dynamically add parameters based on user input and pass them to New-Cipolicy cmdlet @@ -106,69 +116,80 @@ function New-DenyWDACConfig { Deny = $true AllowFileNameFallbacks = $true } - # Assess user input parameters and add the required parameters to the hash table + # Assess user input parameters and add the required parameters to the hash table if ($SpecificFileNameLevel) { $UserInputProgramFoldersPolicyMakerHashTable['SpecificFileNameLevel'] = $SpecificFileNameLevel } - if ($NoScript) { $UserInputProgramFoldersPolicyMakerHashTable['NoScript'] = $true } - if (!$NoUserPEs) { $UserInputProgramFoldersPolicyMakerHashTable['UserPEs'] = $true } + if ($NoScript) { $UserInputProgramFoldersPolicyMakerHashTable['NoScript'] = $true } + if (!$NoUserPEs) { $UserInputProgramFoldersPolicyMakerHashTable['UserPEs'] = $true } # Create the supplemental policy via parameter splatting + Write-Verbose -Message "Currently scanning and creating a deny policy for the folder: $($ScanLocations[$i])" New-CIPolicy @UserInputProgramFoldersPolicyMakerHashTable - } - - Write-Debug -Message 'The Deny policy with the following configuration is being created' - if ($Debug) { $UserInputProgramFoldersPolicyMakerHashTable } - - # Merge-cipolicy accept arrays - collecting all the policy files created by scanning user specified folders - $ProgramDir_ScanResults = Get-ChildItem '.\' | Where-Object { $_.Name -like 'ProgramDir_ScanResults*.xml' } - foreach ($file in $ProgramDir_ScanResults) { + } + + Write-ColorfulText -Color Pink -InputText 'The Deny policy with the following configuration is being created' + $UserInputProgramFoldersPolicyMakerHashTable + + # Merge-CiPolicy accepts arrays - collecting all the policy files created by scanning user specified folders + Write-Verbose -Message 'Collecting all the policy files created by scanning user specified folders' + foreach ($file in (Get-ChildItem -File -Path '.\' -Filter 'ProgramDir_ScanResults*.xml')) { $PolicyXMLFilesArray += $file.FullName - } - # Adding the AllowAll default policy path to the array of policy paths + Write-Verbose -Message 'Adding the AllowAll default template policy path to the array of policy paths to merge' $PolicyXMLFilesArray += 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowAll.xml' - # creating the final Deny base policy from the xml files in the paths array + + Write-Verbose -Message 'Creating the final Deny base policy from the xml files in the paths array' Merge-CIPolicy -PolicyPaths $PolicyXMLFilesArray -OutputFilePath ".\DenyPolicy $PolicyName.xml" | Out-Null - - [System.String]$policyID = Set-CIPolicyIdInfo -FilePath "DenyPolicy $PolicyName.xml" -ResetPolicyID -PolicyName "$PolicyName" - [System.String]$policyID = $policyID.Substring(11) + + Write-Verbose -Message 'Assigning a name and resetting the policy ID' + [System.String]$PolicyID = Set-CIPolicyIdInfo -FilePath "DenyPolicy $PolicyName.xml" -ResetPolicyID -PolicyName "$PolicyName" + [System.String]$PolicyID = $PolicyID.Substring(11) + + Write-Verbose -Message 'Setting the policy version to 1.0.0.0' Set-CIPolicyVersion -FilePath "DenyPolicy $PolicyName.xml" -Version '1.0.0.0' - - @(0, 2, 5, 6, 11, 12, 16, 17, 19, 20) | ForEach-Object { + + Write-Verbose -Message 'Setting the policy rule options' + @(0, 2, 5, 6, 11, 12, 16, 17, 19, 20) | ForEach-Object -Process { Set-RuleOption -FilePath "DenyPolicy $PolicyName.xml" -Option $_ } - - @(3, 4, 9, 10, 13, 18) | ForEach-Object { - Set-RuleOption -FilePath "DenyPolicy $PolicyName.xml" -Option $_ -Delete } - - Set-HVCIOptions -Strict -FilePath "DenyPolicy $PolicyName.xml" - ConvertFrom-CIPolicy "DenyPolicy $PolicyName.xml" "$policyID.cip" | Out-Null - [PSCustomObject]@{ - DenyPolicyFile = "DenyPolicy $PolicyName.xml" - DenyPolicyGUID = $PolicyID - } - + + Write-Verbose -Message 'Deleting the unnecessary policy rule options' + @(3, 4, 9, 10, 13, 18) | ForEach-Object -Process { + Set-RuleOption -FilePath "DenyPolicy $PolicyName.xml" -Option $_ -Delete } + + Write-Verbose -Message 'Setting the HVCI to Strict' + Set-HVCIOptions -Strict -FilePath "DenyPolicy $PolicyName.xml" + + Write-Verbose -Message 'Converting the policy XML to .CIP' + ConvertFrom-CIPolicy -XmlFilePath "DenyPolicy $PolicyName.xml" -BinaryFilePath "$PolicyID.cip" | Out-Null + + Write-ColorfulText -Color MintGreen -InputText "DenyPolicyFile = DenyPolicy $PolicyName.xml" + Write-ColorfulText -Color MintGreen -InputText "DenyPolicyGUID = $PolicyID" + if (!$Debug) { Remove-Item -Path '.\ProgramDir_ScanResults*.xml' -Force } - - if ($Deploy) { - CiTool --update-policy "$policyID.cip" -json | Out-Null - Write-Host -NoNewline "`n$policyID.cip for " -ForegroundColor Green - Write-Host -NoNewline "$PolicyName" -ForegroundColor Magenta - Write-Host ' has been deployed.' -ForegroundColor Green - Remove-Item -Path "$policyID.cip" -Force + + if ($Deploy) { + Write-Verbose -Message 'Deploying the policy' + &'C:\Windows\System32\CiTool.exe' --update-policy "$PolicyID.cip" -json | Out-Null + + Write-ColorfulText -Color Pink -InputText "A Deny Base policy with the name $PolicyName has been deployed." + + Write-Verbose -Message 'Removing the .CIP file after deployment' + Remove-Item -Path "$PolicyID.cip" -Force } } + # Create Deny base policy for Driver files - elseif ($Drivers) { + if ($Drivers) { - powershell.exe { + powershell.exe -Command { [System.Object[]]$DriverFilesObject = @() # loop through each user-selected folder paths foreach ($ScanLocation in $args[0]) { # DriverFile object holds the full details of all of the scanned drivers - This scan is greedy, meaning it stores as much information as it can find - # about each driver file, any available info about digital signature, hash, FileName, Internal Name etc. of each driver is saved and nothing is left out - $DriverFilesObject += Get-SystemDriver -ScanPath $ScanLocation -UserPEs + # about each driver file, any available info about digital signature, hash, FileName, Internal Name etc. of each driver is saved and nothing is left out + $DriverFilesObject += Get-SystemDriver -ScanPath $ScanLocation -UserPEs } [System.Collections.Hashtable]$PolicyMakerHashTable = @{ @@ -182,59 +203,60 @@ function New-DenyWDACConfig { } # Creating a base policy using the DriverFile object and specifying which detail about each driver should be used in the policy file New-CIPolicy @PolicyMakerHashTable - + } -args $ScanLocations, $Level, $Fallbacks - + # Merging AllowAll default policy with our Deny temp policy Merge-CIPolicy -PolicyPaths 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowAll.xml', '.\DenyPolicy Temp.xml' -OutputFilePath ".\DenyPolicy $PolicyName.xml" | Out-Null Remove-Item -Path '.\DenyPolicy Temp.xml' -Force - [System.String]$policyID = Set-CIPolicyIdInfo -FilePath "DenyPolicy $PolicyName.xml" -ResetPolicyID -PolicyName "$PolicyName" - [System.String]$policyID = $policyID.Substring(11) + [System.String]$PolicyID = Set-CIPolicyIdInfo -FilePath "DenyPolicy $PolicyName.xml" -ResetPolicyID -PolicyName "$PolicyName" + [System.String]$PolicyID = $PolicyID.Substring(11) Set-CIPolicyVersion -FilePath "DenyPolicy $PolicyName.xml" -Version '1.0.0.0' - - @(0, 2, 5, 6, 11, 12, 16, 17, 19, 20) | ForEach-Object { + + @(0, 2, 5, 6, 11, 12, 16, 17, 19, 20) | ForEach-Object -Process { Set-RuleOption -FilePath "DenyPolicy $PolicyName.xml" -Option $_ } - - @(3, 4, 9, 10, 13, 18) | ForEach-Object { + + @(3, 4, 9, 10, 13, 18) | ForEach-Object -Process { Set-RuleOption -FilePath "DenyPolicy $PolicyName.xml" -Option $_ -Delete } - - Set-HVCIOptions -Strict -FilePath "DenyPolicy $PolicyName.xml" - ConvertFrom-CIPolicy "DenyPolicy $PolicyName.xml" "$policyID.cip" | Out-Null - - [PSCustomObject]@{ - DenyPolicyFile = "DenyPolicy $PolicyName.xml" - DenyPolicyGUID = $PolicyID - } - if ($Deploy) { - CiTool --update-policy "$policyID.cip" -json | Out-Null - Write-Host -NoNewline "`n$policyID.cip for " -ForegroundColor Green - Write-Host -NoNewline "$PolicyName" -ForegroundColor Magenta - Write-Host ' has been deployed.' -ForegroundColor Green - Remove-Item -Path "$policyID.cip" -Force - } + + Set-HVCIOptions -Strict -FilePath "DenyPolicy $PolicyName.xml" + ConvertFrom-CIPolicy -XmlFilePath "DenyPolicy $PolicyName.xml" -BinaryFilePath "$PolicyID.cip" | Out-Null + + Write-ColorfulText -Color MintGreen -InputText "DenyPolicyFile = DenyPolicy $PolicyName.xml" + Write-ColorfulText -Color MintGreen -InputText "DenyPolicyGUID = $PolicyID" + + if ($Deploy) { + Write-Verbose -Message 'Deploying the policy' + &'C:\Windows\System32\CiTool.exe' --update-policy "$PolicyID.cip" -json | Out-Null + + Write-ColorfulText -Color Pink -InputText "A Deny Base policy with the name $PolicyName has been deployed." + + Write-Verbose -Message 'Removing the .CIP file after deployment' + Remove-Item -Path "$PolicyID.cip" -Force + } } # Creating Deny rule for Appx Packages if ($InstalledAppXPackages) { do { Get-AppxPackage -Name $PackageName - Write-Debug -Message "This is the Selected package name $PackageName" - $Question = Read-Host "`nIs this the intended results based on your Installed Appx packages? Enter 1 to continue, Enter 2 to exit`n" + Write-Verbose -Message "This is the Selected package name $PackageName" + $Question = Read-Host -Prompt "`nIs this the intended results based on your Installed Appx packages? Enter 1 to continue, Enter 2 to exit`n" } until ( (($Question -eq 1) -or ($Question -eq 2)) ) if ($Question -eq 2) { break } - powershell.exe { + powershell.exe -Command { # Get all the packages based on the supplied name - $Package = Get-AppxPackage -Name $args[0] + $Package = Get-AppxPackage -Name $args[0] # Create rules for each package - foreach ($item in $Package) { - $Rules += New-CIPolicyRule -Deny -Package $item + foreach ($Item in $Package) { + $Rules += New-CIPolicyRule -Deny -Package $Item } - + # Generate the supplemental policy xml file New-CIPolicy -MultiplePolicyFormat -FilePath '.\AppxDenyPolicyTemp.xml' -Rules $Rules } -args $PackageName @@ -244,66 +266,74 @@ function New-DenyWDACConfig { # Removing the temp deny policy Remove-Item -Path '.\AppxDenyPolicyTemp.xml' -Force - [System.String]$policyID = Set-CIPolicyIdInfo -FilePath ".\AppxDenyPolicy $PolicyName.xml" -ResetPolicyID -PolicyName "$PolicyName" - [System.String]$policyID = $policyID.Substring(11) + [System.String]$PolicyID = Set-CIPolicyIdInfo -FilePath ".\AppxDenyPolicy $PolicyName.xml" -ResetPolicyID -PolicyName "$PolicyName" + [System.String]$PolicyID = $PolicyID.Substring(11) Set-CIPolicyVersion -FilePath ".\AppxDenyPolicy $PolicyName.xml" -Version '1.0.0.0' - - @(0, 2, 6, 11, 12, 16, 17, 19, 20) | ForEach-Object { + + @(0, 2, 6, 11, 12, 16, 17, 19, 20) | ForEach-Object -Process { Set-RuleOption -FilePath ".\AppxDenyPolicy $PolicyName.xml" -Option $_ } - - @(3, 4, 8, 9, 10, 13, 14, 15, 18) | ForEach-Object { + + @(3, 4, 8, 9, 10, 13, 14, 15, 18) | ForEach-Object -Process { Set-RuleOption -FilePath ".\AppxDenyPolicy $PolicyName.xml" -Option $_ -Delete } - Set-HVCIOptions -Strict -FilePath ".\AppxDenyPolicy $PolicyName.xml" - ConvertFrom-CIPolicy ".\AppxDenyPolicy $PolicyName.xml" "$policyID.cip" | Out-Null - - [PSCustomObject]@{ - DenyPolicyFile = ".\AppxDenyPolicy $PolicyName.xml" - DenyPolicyGUID = $PolicyID + Set-HVCIOptions -Strict -FilePath ".\AppxDenyPolicy $PolicyName.xml" + ConvertFrom-CIPolicy -XmlFilePath ".\AppxDenyPolicy $PolicyName.xml" -BinaryFilePath "$PolicyID.cip" | Out-Null + + Write-ColorfulText -Color MintGreen -InputText "DenyPolicyFile = AppxDenyPolicy $PolicyName.xml" + Write-ColorfulText -Color MintGreen -InputText "DenyPolicyGUID = $PolicyID" + + if ($Deploy) { + Write-Verbose -Message 'Deploying the policy' + &'C:\Windows\System32\CiTool.exe' --update-policy "$PolicyID.cip" -json | Out-Null + + Write-ColorfulText -Color Pink -InputText "A Deny Base policy with the name $PolicyName has been deployed." + + Write-Verbose -Message 'Removing the .CIP file after deployment' + Remove-Item -Path "$PolicyID.cip" -Force } - - if ($Deploy) { - CiTool --update-policy "$policyID.cip" -json | Out-Null - &$WritePink "A Deny Base policy with the name $PolicyName has been deployed." - Remove-Item -Path "$policyID.cip" -Force - } } - } - + } + <# .SYNOPSIS -Creates Deny base policies (Windows Defender Application Control) - + Creates Deny base policies (Windows Defender Application Control) .LINK -https://github.com/HotCakeX/Harden-Windows-Security/wiki/New-DenyWDACConfig - + https://github.com/HotCakeX/Harden-Windows-Security/wiki/New-DenyWDACConfig .DESCRIPTION -Using official Microsoft methods to create Deny base policies (Windows Defender Application Control) - + Using official Microsoft methods to create Deny base policies (Windows Defender Application Control) .COMPONENT -Windows Defender Application Control, ConfigCI PowerShell module - + Windows Defender Application Control, ConfigCI PowerShell module .FUNCTIONALITY -Using official Microsoft methods, Removes Signed and unsigned deployed WDAC policies (Windows Defender Application Control) - + Using official Microsoft methods, Removes Signed and unsigned deployed WDAC policies (Windows Defender Application Control) +.PARAMETER PolicyName + It's used by the entire Cmdlet. It is the name of the base policy that will be created. .PARAMETER Normal -Creates a Deny standalone base policy by scanning a directory for files. The base policy created by this parameter can be deployed side by side any other base/supplemental policy. - + Creates a Deny standalone base policy by scanning a directory for files. The base policy created by this parameter can be deployed side by side any other base/supplemental policy. +.PARAMETER Level + The level that determines how the selected folder will be scanned. + The default value for it is FilePublisher. +.PARAMETER Fallbacks + The fallback level(s) that determine how the selected folder will be scanned. + The default value for it is Hash. +.PARAMETER Deploy + It's used by the entire Cmdlet. Indicates that the created Base deny policy will be deployed on the system. .PARAMETER Drivers -Creates a Deny standalone base policy for drivers only by scanning a directory for driver files. The base policy created by this parameter can be deployed side by side any other base/supplemental policy. - + Creates a Deny standalone base policy for drivers only by scanning a directory for driver files. The base policy created by this parameter can be deployed side by side any other base/supplemental policy. .PARAMETER InstalledAppXPackages -Creates a Deny standalone base policy for an installed App based on Appx package family names - + Creates a Deny standalone base policy for an installed App based on Appx package family names .PARAMETER SkipVersionCheck -Can be used with any parameter to bypass the online version check - only to be used in rare cases - + Can be used with any parameter to bypass the online version check - only to be used in rare cases + It's used by the entire Cmdlet. +.INPUTS + System.String[] + System.String + System.Management.Automation.SwitchParameter +.OUTPUTS + System.String #> } # Importing argument completer ScriptBlocks -. "$psscriptroot\ArgumentCompleters.ps1" -# Set PSReadline tab completion to complete menu for easier access to available parameters - Only for the current session -Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete +. "$ModuleRootPath\Resources\ArgumentCompleters.ps1" Register-ArgumentCompleter -CommandName 'New-DenyWDACConfig' -ParameterName 'ScanLocations' -ScriptBlock $ArgumentCompleterFolderPathsPicker Register-ArgumentCompleter -CommandName 'New-DenyWDACConfig' -ParameterName 'PackageName' -ScriptBlock $ArgumentCompleterAppxPackageNames diff --git a/WDACConfig/New-KernelModeWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/New-KernelModeWDACConfig.psm1 similarity index 58% rename from WDACConfig/New-KernelModeWDACConfig.psm1 rename to WDACConfig/WDACConfig Module Files/Core/New-KernelModeWDACConfig.psm1 index ac1e1ab13..cb9511400 100644 --- a/WDACConfig/New-KernelModeWDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/New-KernelModeWDACConfig.psm1 @@ -1,44 +1,52 @@ -#Requires -RunAsAdministrator -function New-KernelModeWDACConfig { +Function New-KernelModeWDACConfig { [CmdletBinding( SupportsShouldProcess = $true, PositionalBinding = $false, ConfirmImpact = 'High' )] - Param( - [Parameter(Mandatory = $false, ParameterSetName = 'Default Strict Kernel')][switch]$Default, - [Parameter(Mandatory = $false, ParameterSetName = 'No Flight Roots')][switch]$NoFlightRoots, + Param( + [Parameter(Mandatory = $false, ParameterSetName = 'Default Strict Kernel')][System.Management.Automation.SwitchParameter]$Default, + [Parameter(Mandatory = $false, ParameterSetName = 'No Flight Roots')][System.Management.Automation.SwitchParameter]$NoFlightRoots, [Parameter(Mandatory = $false, ParameterSetName = 'Default Strict Kernel')] [Parameter(Mandatory = $false, ParameterSetName = 'No Flight Roots')] - [switch]$PrepMode, + [System.Management.Automation.SwitchParameter]$PrepMode, [Parameter(Mandatory = $false, ParameterSetName = 'Default Strict Kernel')] [Parameter(Mandatory = $false, ParameterSetName = 'No Flight Roots')] - [switch]$AuditAndEnforce, + [System.Management.Automation.SwitchParameter]$AuditAndEnforce, [Parameter(Mandatory = $false, ParameterSetName = 'Default Strict Kernel')] [Parameter(Mandatory = $false, ParameterSetName = 'No Flight Roots')] - [Switch]$Deploy, + [System.Management.Automation.SwitchParameter]$Deploy, [Parameter(Mandatory = $false, ParameterSetName = 'Default Strict Kernel')] [Parameter(Mandatory = $false, ParameterSetName = 'No Flight Roots')] - [switch]$EVSigners, - - [Parameter(Mandatory = $false)][Switch]$SkipVersionCheck + [System.Management.Automation.SwitchParameter]$EVSigners, + + [Parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$SkipVersionCheck ) begin { - # Importing resources such as functions by dot-sourcing so that they will run in the same scope and their variables will be usable - . "$psscriptroot\Resources.ps1" + # Detecting if Verbose switch is used + $PSBoundParameters.Verbose.IsPresent ? ([System.Boolean]$Verbose = $true) : ([System.Boolean]$Verbose = $false) | Out-Null + + # Importing the $PSDefaultParameterValues to the current session, prior to everything else + . "$ModuleRootPath\CoreExt\PSDefaultParameterValues.ps1" - # Stop operation as soon as there is an error anywhere, unless explicitly specified otherwise - $ErrorActionPreference = 'Stop' + # Importing the required sub-modules + Write-Verbose -Message 'Importing the required sub-modules' + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Update-self.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Write-ColorfulText.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Move-UserModeToKernelMode.psm1" -Force # Detecting if Debug switch is used, will do debugging actions based on that - $Debug = $PSBoundParameters.Debug.IsPresent + $PSBoundParameters.Debug.IsPresent ? ([System.Boolean]$Debug = $true) : ([System.Boolean]$Debug = $false) | Out-Null - if (-NOT $SkipVersionCheck) { . Update-self } + # if -SkipVersionCheck wasn't passed, run the updater + # Redirecting the Update-Self function's information Stream to $null because Write-Host + # Used by Write-ColorfulText outputs to both information stream and host console + if (-NOT $SkipVersionCheck) { Update-self 6> $null } # Check if the PrepMode and AuditAndEnforce parameters are used together and ensure one of them is used if (-not ($PSBoundParameters.ContainsKey('PrepMode') -xor $PSBoundParameters.ContainsKey('AuditAndEnforce'))) { @@ -46,67 +54,74 @@ function New-KernelModeWDACConfig { Write-Error -Message 'You must specify either -PrepMode or -AuditAndEnforce, but not both.' -Category InvalidArgument } - # Function to swap GUIDs in a WDAC policy XML file - function Edit-GUIDs { + Function Edit-GUIDs { + <# + .SYNOPSIS + Function to swap GUIDs in a WDAC policy XML file + #> + [CmdletBinding()] param( [System.String]$PolicyIDInput, [System.String]$PolicyFilePathInput ) - + [System.String]$PolicyID = "{$PolicyIDInput}" # Read the xml file as an xml object - [xml]$xml = Get-Content -Path $PolicyFilePathInput + [System.Xml.XmlDocument]$Xml = Get-Content -Path $PolicyFilePathInput # Define the new values for PolicyID and BasePolicyID [System.String]$newPolicyID = $PolicyID [System.String]$newBasePolicyID = $PolicyID # Replace the old values with the new ones - $xml.SiPolicy.PolicyID = $newPolicyID - $xml.SiPolicy.BasePolicyID = $newBasePolicyID + $Xml.SiPolicy.PolicyID = $newPolicyID + $Xml.SiPolicy.BasePolicyID = $newBasePolicyID # Save the modified xml file - $xml.Save($PolicyFilePathInput) + $Xml.Save($PolicyFilePathInput) } - # Function to build Audit mode policy only - function Build-PrepModeStrictKernelPolicy { + Function Build-PrepModeStrictKernelPolicy { + <# + .SYNOPSIS + Function to build Audit mode policy only + #> [CmdletBinding()] param ( - [Parameter(Mandatory = $false)][switch]$DefaultWindowsKernel, - [Parameter(Mandatory = $false)][switch]$DefaultWindowsKernelNoFlights + [Parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$DefaultWindowsKernel, + [Parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$DefaultWindowsKernelNoFlights ) begin { if ($DefaultWindowsKernel) { - $PolicyPath = "$psscriptroot\WDAC Policies\DefaultWindows_Enforced_Kernel.xml" + $PolicyPath = "$ModuleRootPath\Resources\WDAC Policies\DefaultWindows_Enforced_Kernel.xml" $PolicyFileName = '.\DefaultWindows_Enforced_Kernel.xml' $PolicyName = 'Strict Kernel mode policy Audit' # Check if there is a pending Audit mode Kernel mode WDAC policy already available in User Config file [System.String]$CurrentStrictKernelPolicyGUID = Get-CommonWDACConfig -StrictKernelPolicyGUID - If ($CurrentStrictKernelPolicyGUID) { + If ($CurrentStrictKernelPolicyGUID) { # Check if the pending Audit mode Kernel mode WDAC policy is deployed on the system - [System.String]$CurrentStrictKernelPolicyGUIDConfirmation = ((CiTool -lp -json | ConvertFrom-Json).Policies | Where-Object { $_.PolicyID -eq $CurrentStrictKernelPolicyGUID }).policyID + [System.String]$CurrentStrictKernelPolicyGUIDConfirmation = ((&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { $_.PolicyID -eq $CurrentStrictKernelPolicyGUID }).policyID } } - if ($DefaultWindowsKernelNoFlights) { - $PolicyPath = "$psscriptroot\WDAC Policies\DefaultWindows_Enforced_Kernel_NoFlights.xml" + if ($DefaultWindowsKernelNoFlights) { + $PolicyPath = "$ModuleRootPath\Resources\WDAC Policies\DefaultWindows_Enforced_Kernel_NoFlights.xml" $PolicyFileName = '.\DefaultWindows_Enforced_Kernel_NoFlights.xml' $PolicyName = 'Strict Kernel No Flights mode policy Audit' # Check if there is a pending Audit mode Kernel mode WDAC No Flight Roots policy already available in User Config file [System.String]$CurrentStrictKernelNoFlightRootsPolicyGUID = Get-CommonWDACConfig -StrictKernelNoFlightRootsPolicyGUID - If ($CurrentStrictKernelNoFlightRootsPolicyGUID) { + If ($CurrentStrictKernelNoFlightRootsPolicyGUID) { # Check if the pending Audit mode Kernel mode WDAC No Flight Roots policy is deployed on the system - [System.String]$CurrentStrictKernelPolicyGUIDConfirmation = ((CiTool -lp -json | ConvertFrom-Json).Policies | Where-Object { $_.PolicyID -eq $CurrentStrictKernelNoFlightRootsPolicyGUID }).policyID + [System.String]$CurrentStrictKernelPolicyGUIDConfirmation = ((&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { $_.PolicyID -eq $CurrentStrictKernelNoFlightRootsPolicyGUID }).policyID } - } + } } @@ -117,20 +132,20 @@ function New-KernelModeWDACConfig { $Global:PolicyID = $PolicyID.Substring(11) Set-CIPolicyVersion -FilePath "$PolicyFileName" -Version '1.0.0.0' # Setting policy rule options for the audit mode policy - @(2, 3, 6, 16, 17, 20) | ForEach-Object { Set-RuleOption -FilePath "$PolicyFileName" -Option $_ } - @(0, 4, 8, 9, 10, 11, 12, 13, 14, 15, 18, 19) | ForEach-Object { Set-RuleOption -FilePath "$PolicyFileName" -Option $_ -Delete } + @(2, 3, 6, 16, 17, 20) | ForEach-Object -Process { Set-RuleOption -FilePath "$PolicyFileName" -Option $_ } + @(0, 4, 8, 9, 10, 11, 12, 13, 14, 15, 18, 19) | ForEach-Object -Process { Set-RuleOption -FilePath "$PolicyFileName" -Option $_ -Delete } # If user chooses to add EVSigners, add it to the policy if ($EVSigners) { Set-RuleOption -FilePath "$PolicyFileName" -Option 8 } # If user chooses to go with no flight root certs then block flight/insider builds in policy rule options if ($DefaultWindowsKernelNoFlights) { Set-RuleOption -FilePath "$PolicyFileName" -Option 4 } - + # Set the already available and deployed GUID as the new PolicyID to prevent deploying duplicate Audit mode policies if ($CurrentStrictKernelPolicyGUIDConfirmation) { Edit-GUIDs -PolicyIDInput $CurrentStrictKernelPolicyGUIDConfirmation -PolicyFilePathInput "$PolicyFileName" $Global:PolicyID = $CurrentStrictKernelPolicyGUIDConfirmation - } - - Set-HVCIOptions -Strict -FilePath "$PolicyFileName" + } + + Set-HVCIOptions -Strict -FilePath "$PolicyFileName" } } } @@ -143,56 +158,56 @@ function New-KernelModeWDACConfig { # Build the Audit mode policy Build-PrepModeStrictKernelPolicy -DefaultWindowsKernel # Convert the xml to CIP binary - ConvertFrom-CIPolicy .\DefaultWindows_Enforced_Kernel.xml "$PolicyID.cip" | Out-Null - + ConvertFrom-CIPolicy -XmlFilePath .\DefaultWindows_Enforced_Kernel.xml -BinaryFilePath "$PolicyID.cip" | Out-Null + # Deploy the policy if Deploy parameter is used and perform additional tasks on the system - if ($Deploy) { - + if ($Deploy) { + # Set the GUID of the Audit mode policy in the User Configuration file Set-CommonWDACConfig -StrictKernelPolicyGUID $PolicyID | Out-Null - CiTool.exe --update-policy "$PolicyID.cip" -json | Out-Null - &$WriteHotPink 'Strict Kernel mode policy has been deployed in Audit mode, please restart your system.' - + &'C:\Windows\System32\CiTool.exe' --update-policy "$PolicyID.cip" -json | Out-Null + Write-ColorfulText -Color HotPink -InputText 'Strict Kernel mode policy has been deployed in Audit mode, please restart your system.' + # Clear Code Integrity operational before system restart so that after boot it will only have the correct and new logs - wevtutil cl 'Microsoft-Windows-CodeIntegrity/Operational' - wevtutil cl 'Microsoft-Windows-AppLocker/MSI and Script' + &'C:\Windows\System32\wevtutil.exe' cl 'Microsoft-Windows-CodeIntegrity/Operational' + &'C:\Windows\System32\wevtutil.exe' cl 'Microsoft-Windows-AppLocker/MSI and Script' if (!$Debug) { Remove-Item -Path '.\DefaultWindows_Enforced_Kernel.xml', ".\$PolicyID.cip" -Force -ErrorAction SilentlyContinue } } else { - &$WriteHotPink 'Strict Kernel mode Audit policy has been created in the current working directory.' - } + Write-ColorfulText -Color HotPink -InputText 'Strict Kernel mode Audit policy has been created in the current working directory.' + } } if ($AuditAndEnforce) { # Get the Strict Kernel Audit mode policy's GUID to use for the Enforced mode policy - # This will eliminate the need for an extra reboot + # This will eliminate the need for an extra reboot [System.String]$PolicyID = Get-CommonWDACConfig -StrictKernelPolicyGUID # Verify the Policy ID in the User Config exists and is valid $ObjectGuid = [System.Guid]::Empty if ([System.Guid]::TryParse($PolicyID, [ref]$ObjectGuid)) { - Write-Debug 'Valid GUID found in User Configs for Audit mode policy' + Write-Verbose -Message 'Valid GUID found in User Configs for Audit mode policy' } else { - Write-Error 'Invalid or nonexistent GUID in User Configs for Audit mode policy, Use the -PrepMode parameter first.' + Write-Error -Message 'Invalid or nonexistent GUID in User Configs for Audit mode policy, Use the -PrepMode parameter first.' } - - powershell.exe { + + powershell.exe -Command { # Scan Event viewer logs for drivers $DriverFilesObj = Get-SystemDriver -Audit # Create a policy xml file from the driver files - New-CIPolicy -MultiplePolicyFormat -Level FilePublisher -Fallback None -FilePath '.\DriverFilesScanPolicy.xml' -DriverFiles $DriverFilesObj - } - - # Build the same policy again after restart, do not trust the policy xml file made before restart - Copy-Item -Path "$psscriptroot\WDAC Policies\DefaultWindows_Enforced_Kernel.xml" -Destination .\DefaultWindows_Enforced_Kernel.xml -Force - + New-CIPolicy -MultiplePolicyFormat -Level FilePublisher -Fallback None -FilePath '.\DriverFilesScanPolicy.xml' -DriverFiles $DriverFilesObj + } + + # Build the same policy again after restart, do not trust the policy xml file made before restart + Copy-Item -Path "$ModuleRootPath\Resources\WDAC Policies\DefaultWindows_Enforced_Kernel.xml" -Destination .\DefaultWindows_Enforced_Kernel.xml -Force + # Merge the base policy with the policy made from driver files to deploy it as one Merge-CIPolicy -PolicyPaths '.\DefaultWindows_Enforced_Kernel.xml', '.\DriverFilesScanPolicy.xml' -OutputFilePath '.\Final_DefaultWindows_Enforced_Kernel.xml' | Out-Null - + # Remove the old policy again because we used it in merge and don't need it anymore Remove-Item -Path '.\DefaultWindows_Enforced_Kernel.xml' -Force @@ -206,18 +221,18 @@ function New-KernelModeWDACConfig { Set-CIPolicyVersion -FilePath '.\Final_DefaultWindows_Enforced_Kernel.xml' -Version '1.0.0.0' # Setting policy rule options for the final Enforced mode policy - @(2, 6, 16, 17, 20) | ForEach-Object { Set-RuleOption -FilePath '.\Final_DefaultWindows_Enforced_Kernel.xml' -Option $_ } - @(0, 3, 4, 8, 9, 10, 11, 12, 13, 14, 15, 18, 19) | ForEach-Object { Set-RuleOption -FilePath '.\Final_DefaultWindows_Enforced_Kernel.xml' -Option $_ -Delete } + @(2, 6, 16, 17, 20) | ForEach-Object -Process { Set-RuleOption -FilePath '.\Final_DefaultWindows_Enforced_Kernel.xml' -Option $_ } + @(0, 3, 4, 8, 9, 10, 11, 12, 13, 14, 15, 18, 19) | ForEach-Object -Process { Set-RuleOption -FilePath '.\Final_DefaultWindows_Enforced_Kernel.xml' -Option $_ -Delete } if ($EVSigners) { Set-RuleOption -FilePath '.\Final_DefaultWindows_Enforced_Kernel.xml' -Option 8 } - + Set-HVCIOptions -Strict -FilePath '.\Final_DefaultWindows_Enforced_Kernel.xml' - + # Deploy the policy if Deploy parameter is used - if ($Deploy) { - ConvertFrom-CIPolicy '.\Final_DefaultWindows_Enforced_Kernel.xml' "$PolicyID.cip" | Out-Null - CiTool.exe --update-policy "$PolicyID.cip" -json | Out-Null - &$WritePink 'Strict Kernel mode policy has been deployed in Enforced mode, no restart required.' + if ($Deploy) { + ConvertFrom-CIPolicy -XmlFilePath '.\Final_DefaultWindows_Enforced_Kernel.xml' -BinaryFilePath "$PolicyID.cip" | Out-Null + &'C:\Windows\System32\CiTool.exe' --update-policy "$PolicyID.cip" -json | Out-Null + Write-ColorfulText -Color Pink -InputText 'Strict Kernel mode policy has been deployed in Enforced mode, no restart required.' # Delete its GUID from User Configurations Remove-CommonWDACConfig -StrictKernelPolicyGUID | Out-Null @@ -226,15 +241,15 @@ function New-KernelModeWDACConfig { # Remove the Audit mode policy from the system # This step is necessary if user didn't use the -Deploy parameter # And instead wants to first Sign and then deploy it using the Deploy-SignedWDACConfig cmdlet - CiTool.exe --remove-policy "{$PolicyID}" -json | Out-Null - &$WritePink 'Strict Kernel mode Enforced policy has been created in the current working directory.' + &'C:\Windows\System32\CiTool.exe' --remove-policy "{$PolicyID}" -json | Out-Null + Write-ColorfulText -Color Pink -InputText 'Strict Kernel mode Enforced policy has been created in the current working directory.' } if (!$Debug) { Remove-Item -Path ".\$PolicyID.cip", '.\DriverFilesScanPolicy.xml' -Force -ErrorAction SilentlyContinue - } + } } } - + # For Strict Kernel mode WDAC policy without allowing Flight root certs (i.e. not allowing insider builds) if ($PSCmdlet.ParameterSetName -eq 'No Flight Roots' -and $PSBoundParameters.ContainsKey('NoFlightRoots')) { @@ -242,61 +257,61 @@ function New-KernelModeWDACConfig { # Creating the audit mode policy Build-PrepModeStrictKernelPolicy -DefaultWindowsKernelNoFlights # Convert the xml to CIP binary - ConvertFrom-CIPolicy .\DefaultWindows_Enforced_Kernel_NoFlights.xml "$PolicyID.cip" | Out-Null + ConvertFrom-CIPolicy -XmlFilePath .\DefaultWindows_Enforced_Kernel_NoFlights.xml -BinaryFilePath "$PolicyID.cip" | Out-Null # Deploy the policy if Deploy parameter is used and perform additional tasks on the system - if ($Deploy) { + if ($Deploy) { # Set the GUID of the Audit mode policy in the User Configuration file Set-CommonWDACConfig -StrictKernelNoFlightRootsPolicyGUID $PolicyID | Out-Null - CiTool.exe --update-policy "$PolicyID.cip" -json | Out-Null - &$WriteHotPink 'Strict Kernel mode policy with no flighting root certs has been deployed in Audit mode, please restart your system.' - + &'C:\Windows\System32\CiTool.exe' --update-policy "$PolicyID.cip" -json | Out-Null + Write-ColorfulText -Color HotPink -InputText 'Strict Kernel mode policy with no flighting root certs has been deployed in Audit mode, please restart your system.' + # Clear Code Integrity operational before system restart so that after boot it will only have the correct and new logs - wevtutil cl 'Microsoft-Windows-CodeIntegrity/Operational' - wevtutil cl 'Microsoft-Windows-AppLocker/MSI and Script' + &'C:\Windows\System32\wevtutil.exe' cl 'Microsoft-Windows-CodeIntegrity/Operational' + &'C:\Windows\System32\wevtutil.exe' cl 'Microsoft-Windows-AppLocker/MSI and Script' if (!$Debug) { Remove-Item -Path '.\DefaultWindows_Enforced_Kernel_NoFlights.xml', ".\$PolicyID.cip" -Force -ErrorAction SilentlyContinue - } + } } else { - &$WriteHotPink 'Strict Kernel mode Audit policy with no flighting root certs has been created in the current working directory.' - } + Write-ColorfulText -Color HotPink -InputText 'Strict Kernel mode Audit policy with no flighting root certs has been created in the current working directory.' + } } - if ($AuditAndEnforce) { - + if ($AuditAndEnforce) { + # Get the Strict Kernel Audit mode policy's GUID to use for the Enforced mode policy - # This will eliminate the need for an extra reboot + # This will eliminate the need for an extra reboot [System.String]$PolicyID = Get-CommonWDACConfig -StrictKernelNoFlightRootsPolicyGUID # Verify the Policy ID in the User Config exists and is valid $ObjectGuid = [System.Guid]::Empty if ([System.Guid]::TryParse($PolicyID, [ref]$ObjectGuid)) { - Write-Debug 'Valid GUID found in User Configs for Audit mode policy' + Write-Verbose -Message 'Valid GUID found in User Configs for Audit mode policy' } else { - Write-Error 'Invalid or nonexistent GUID in User Configs for Audit mode policy, Use the -PrepMode parameter first.' - } + Write-Error -Message 'Invalid or nonexistent GUID in User Configs for Audit mode policy, Use the -PrepMode parameter first.' + } powershell.exe { # Scan Event viewer logs for drivers $DriverFilesObj = Get-SystemDriver -Audit # Create a policy xml file from the driver files - New-CIPolicy -MultiplePolicyFormat -Level FilePublisher -Fallback None -FilePath '.\DriverFilesScanPolicy.xml' -DriverFiles $DriverFilesObj - } - - # Build the same policy again after restart, do not trust the policy xml file made before restart - Copy-Item -Path "$psscriptroot\WDAC Policies\DefaultWindows_Enforced_Kernel_NoFlights.xml" -Destination '.\DefaultWindows_Enforced_Kernel_NoFlights.xml' -Force - + New-CIPolicy -MultiplePolicyFormat -Level FilePublisher -Fallback None -FilePath '.\DriverFilesScanPolicy.xml' -DriverFiles $DriverFilesObj + } + + # Build the same policy again after restart, do not trust the policy xml file made before restart + Copy-Item -Path "$ModuleRootPath\Resources\WDAC Policies\DefaultWindows_Enforced_Kernel_NoFlights.xml" -Destination '.\DefaultWindows_Enforced_Kernel_NoFlights.xml' -Force + # Merge the base policy with the policy made from driver files to deploy it as one Merge-CIPolicy -PolicyPaths '.\DefaultWindows_Enforced_Kernel_NoFlights.xml', '.\DriverFilesScanPolicy.xml' -OutputFilePath '.\Final_DefaultWindows_Enforced_Kernel.xml' | Out-Null - + # Remove the old policy again because we used it in merge and don't need it anymore Remove-Item -Path '.\DefaultWindows_Enforced_Kernel_NoFlights.xml' -Force # Move all AllowedSigners from Usermode to Kernel mode signing scenario Move-UserModeToKernelMode -FilePath '.\Final_DefaultWindows_Enforced_Kernel.xml' | Out-Null - + # Set the GUIDs for the XML policy file Edit-GUIDs -PolicyIDInput $PolicyID -PolicyFilePathInput '.\Final_DefaultWindows_Enforced_Kernel.xml' @@ -304,19 +319,19 @@ function New-KernelModeWDACConfig { Set-CIPolicyVersion -FilePath '.\Final_DefaultWindows_Enforced_Kernel.xml' -Version '1.0.0.0' # Setting policy rule options for the final Enforced mode policy - @(2, 4, 6, 16, 17, 20) | ForEach-Object { Set-RuleOption -FilePath '.\Final_DefaultWindows_Enforced_Kernel.xml' -Option $_ } - @(0, 3, 8, 9, 10, 11, 12, 13, 14, 15, 18, 19) | ForEach-Object { Set-RuleOption -FilePath '.\Final_DefaultWindows_Enforced_Kernel.xml' -Option $_ -Delete } + @(2, 4, 6, 16, 17, 20) | ForEach-Object -Process { Set-RuleOption -FilePath '.\Final_DefaultWindows_Enforced_Kernel.xml' -Option $_ } + @(0, 3, 8, 9, 10, 11, 12, 13, 14, 15, 18, 19) | ForEach-Object -Process { Set-RuleOption -FilePath '.\Final_DefaultWindows_Enforced_Kernel.xml' -Option $_ -Delete } if ($EVSigners) { Set-RuleOption -FilePath '.\Final_DefaultWindows_Enforced_Kernel.xml' -Option 8 } - + Set-HVCIOptions -Strict -FilePath '.\Final_DefaultWindows_Enforced_Kernel.xml' - + # Deploy the policy if Deploy parameter is used - if ($Deploy) { - ConvertFrom-CIPolicy '.\Final_DefaultWindows_Enforced_Kernel.xml' "$PolicyID.cip" | Out-Null - CiTool.exe --update-policy "$PolicyID.cip" -json | Out-Null - &$WritePink 'Strict Kernel mode policy with no flighting root certs has been deployed in Enforced mode, no restart required.' - + if ($Deploy) { + ConvertFrom-CIPolicy -XmlFilePath '.\Final_DefaultWindows_Enforced_Kernel.xml' -BinaryFilePath "$PolicyID.cip" | Out-Null + &'C:\Windows\System32\CiTool.exe' --update-policy "$PolicyID.cip" -json | Out-Null + Write-ColorfulText -Color Pink -InputText 'Strict Kernel mode policy with no flighting root certs has been deployed in Enforced mode, no restart required.' + # Delete its GUID from User Configurations Remove-CommonWDACConfig -StrictKernelNoFlightRootsPolicyGUID | Out-Null } @@ -324,54 +339,44 @@ function New-KernelModeWDACConfig { # Remove the Audit mode policy from the system # This step is necessary if user didn't use the -Deploy parameter # And instead wants to first Sign and then deploy it using the Deploy-SignedWDACConfig cmdlet - CiTool.exe --remove-policy "{$PolicyID}" -json | Out-Null - &$WritePink 'Strict Kernel mode Enforced policy with no flighting root certs has been created in the current working directory.' - } + &'C:\Windows\System32\CiTool.exe' --remove-policy "{$PolicyID}" -json | Out-Null + Write-ColorfulText -Color Pink -InputText 'Strict Kernel mode Enforced policy with no flighting root certs has been created in the current working directory.' + } if (!$Debug) { Remove-Item -Path ".\$PolicyID.cip", '.\DriverFilesScanPolicy.xml' -Force -ErrorAction SilentlyContinue - } + } } } - } - + } + <# .SYNOPSIS -Creates Kernel only mode WDAC policy capable of protecting against BYOVD attacks category - + Creates Kernel only mode WDAC policy capable of protecting against BYOVD attacks category .LINK -https://github.com/HotCakeX/Harden-Windows-Security/wiki/New%E2%80%90KernelModeWDACConfig - + https://github.com/HotCakeX/Harden-Windows-Security/wiki/New%E2%80%90KernelModeWDACConfig .DESCRIPTION -Using official Microsoft methods, configure and use Windows Defender Application Control - + Using official Microsoft methods, configure and use Windows Defender Application Control .COMPONENT -Windows Defender Application Control, ConfigCI PowerShell module - + Windows Defender Application Control, ConfigCI PowerShell module .FUNCTIONALITY -Creates Kernel only mode WDAC policy capable of protecting against BYOVD attacks category - + Creates Kernel only mode WDAC policy capable of protecting against BYOVD attacks category .PARAMETER Default -Creates the strict Kernel mode WDAC policy based off of the default Windows WDAC example policy. - + Creates the strict Kernel mode WDAC policy based off of the default Windows WDAC example policy. .PARAMETER NoFlightRoots -Creates the strict Kernel mode WDAC policy based off of the default Windows WDAC example policy, doesn't allow flighting/insider builds. - + Creates the strict Kernel mode WDAC policy based off of the default Windows WDAC example policy, doesn't allow flighting/insider builds. .PARAMETER PrepMode -Deploys the Kernel mode WDAC policy in Audit mode so that you can restart your system and start capturing any blocked drivers to be automatically allowed. - + Deploys the Kernel mode WDAC policy in Audit mode so that you can restart your system and start capturing any blocked drivers to be automatically allowed. .PARAMETER AuditAndEnforce -Deploys the final Kernel mode WDAC policy in Enforced mode - + Deploys the final Kernel mode WDAC policy in Enforced mode .PARAMETER EVSigners -Adds EVSigners policy rule option to the deployed policy. Applicable for both Audit and Enforced modes. Drivers not EV (Extended Validation) signed cannot run nor can they be allowed in a Supplemental policy. - + Adds EVSigners policy rule option to the deployed policy. Applicable for both Audit and Enforced modes. Drivers not EV (Extended Validation) signed cannot run nor can they be allowed in a Supplemental policy. .PARAMETER Deploy -Deploys the selected policy type instead of just creating it - + Deploys the selected policy type instead of just creating it .PARAMETER SkipVersionCheck -Can be used with any parameter to bypass the online version check - only to be used in rare cases - + Can be used with any parameter to bypass the online version check - only to be used in rare cases +.INPUTS + System.Management.Automation.SwitchParameter +.OUTPUTS + System.String #> } -# Set PSReadline tab completion to complete menu for easier access to available parameters - Only for the current session -Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete diff --git a/WDACConfig/New-SupplementalWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/New-SupplementalWDACConfig.psm1 similarity index 50% rename from WDACConfig/New-SupplementalWDACConfig.psm1 rename to WDACConfig/WDACConfig Module Files/Core/New-SupplementalWDACConfig.psm1 index ed68c3681..e7e40005c 100644 --- a/WDACConfig/New-SupplementalWDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/New-SupplementalWDACConfig.psm1 @@ -1,5 +1,4 @@ -#Requires -RunAsAdministrator -function New-SupplementalWDACConfig { +Function New-SupplementalWDACConfig { [CmdletBinding( DefaultParameterSetName = 'Normal', SupportsShouldProcess = $true, @@ -9,129 +8,134 @@ function New-SupplementalWDACConfig { Param( # Main parameters for position 0 [Alias('N')] - [Parameter(Mandatory = $false, ParameterSetName = 'Normal')][Switch]$Normal, + [Parameter(Mandatory = $false, ParameterSetName = 'Normal')][System.Management.Automation.SwitchParameter]$Normal, [Alias('W')] - [Parameter(Mandatory = $false, ParameterSetName = 'Folder Path With WildCards')][Switch]$PathWildCards, + [Parameter(Mandatory = $false, ParameterSetName = 'Folder Path With WildCards')][System.Management.Automation.SwitchParameter]$PathWildCards, [Alias('P')] - [parameter(mandatory = $false, ParameterSetName = 'Installed AppXPackages')][switch]$InstalledAppXPackages, - + [parameter(mandatory = $false, ParameterSetName = 'Installed AppXPackages')][System.Management.Automation.SwitchParameter]$InstalledAppXPackages, + [parameter(Mandatory = $true, ParameterSetName = 'Installed AppXPackages', ValueFromPipelineByPropertyName = $true)] [System.String]$PackageName, - [ValidateScript({ Test-Path $_ -PathType 'Container' }, ErrorMessage = 'The path you selected is not a folder path.')] - [parameter(Mandatory = $true, ParameterSetName = 'Normal', ValueFromPipelineByPropertyName = $true)] + [ValidateScript({ Test-Path -Path $_ -PathType 'Container' }, ErrorMessage = 'The path you selected is not a folder path.')] + [parameter(Mandatory = $true, ParameterSetName = 'Normal', ValueFromPipelineByPropertyName = $true)] [System.String]$ScanLocation, - [ValidatePattern('\*', ErrorMessage = "You didn't supply a path that contains wildcard character '*' .")] + [ValidatePattern('\*', ErrorMessage = 'You did not supply a path that contains wildcard character (*) .')] [parameter(Mandatory = $true, ParameterSetName = 'Folder Path With WildCards', ValueFromPipelineByPropertyName = $true)] [System.String]$FolderPath, [ValidatePattern('^[a-zA-Z0-9 ]+$', ErrorMessage = 'The Supplemental Policy Name can only contain alphanumeric and space characters.')] - [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] # Used by the entire Cmdlet + [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [System.String]$SuppPolicyName, - + [ValidatePattern('\.xml$')] - [ValidateScript({ Test-Path $_ -PathType 'Leaf' }, ErrorMessage = 'The path you selected is not a file path.')] - [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] # Used by the entire Cmdlet + [ValidateScript({ Test-Path -Path $_ -PathType 'Leaf' }, ErrorMessage = 'The path you selected is not a file path.')] + [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [System.String]$PolicyPath, - [parameter(Mandatory = $false)] # Used by the entire Cmdlet - [Switch]$Deploy, - + [parameter(Mandatory = $false)] + [System.Management.Automation.SwitchParameter]$Deploy, + [ValidateSet('OriginalFileName', 'InternalName', 'FileDescription', 'ProductName', 'PackageFamilyName', 'FilePath')] [Parameter(Mandatory = $false, ParameterSetName = 'Normal')] [System.String]$SpecificFileNameLevel, [Parameter(Mandatory = $false, ParameterSetName = 'Normal')] - [Switch]$NoUserPEs, + [System.Management.Automation.SwitchParameter]$NoUserPEs, [Parameter(Mandatory = $false, ParameterSetName = 'Normal')] - [Switch]$NoScript, + [System.Management.Automation.SwitchParameter]$NoScript, [ValidateSet([Levelz])] [parameter(Mandatory = $false, ParameterSetName = 'Normal')] - [System.String]$Level = 'FilePublisher', # Setting the default value for the Level parameter + [System.String]$Level = 'FilePublisher', [ValidateSet([Fallbackz])] [parameter(Mandatory = $false, ParameterSetName = 'Normal')] - [System.String[]]$Fallbacks = 'Hash', # Setting the default value for the Fallbacks parameter - - [Parameter(Mandatory = $false)][Switch]$SkipVersionCheck + [System.String[]]$Fallbacks = 'Hash', + + [Parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$SkipVersionCheck ) begin { - # Importing resources such as functions by dot-sourcing so that they will run in the same scope and their variables will be usable - . "$psscriptroot\Resources.ps1" + # Detecting if Verbose switch is used + $PSBoundParameters.Verbose.IsPresent ? ([System.Boolean]$Verbose = $true) : ([System.Boolean]$Verbose = $false) | Out-Null + + # Importing the $PSDefaultParameterValues to the current session, prior to everything else + . "$ModuleRootPath\CoreExt\PSDefaultParameterValues.ps1" + + # Importing the required sub-modules + Write-Verbose -Message 'Importing the required sub-modules' + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Update-self.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Write-ColorfulText.psm1" -Force # argument tab auto-completion and ValidateSet for Fallbacks Class Fallbackz : System.Management.Automation.IValidateSetValuesGenerator { [System.String[]] GetValidValues() { - $Fallbackz = ('Hash', 'FileName', 'SignedVersion', 'Publisher', 'FilePublisher', 'LeafCertificate', 'PcaCertificate', 'RootCertificate', 'WHQL', 'WHQLPublisher', 'WHQLFilePublisher', 'PFN', 'FilePath', 'None') + $Fallbackz = ('Hash', 'FileName', 'SignedVersion', 'Publisher', 'FilePublisher', 'LeafCertificate', 'PcaCertificate', 'RootCertificate', 'WHQL', 'WHQLPublisher', 'WHQLFilePublisher', 'PFN', 'FilePath', 'None') return [System.String[]]$Fallbackz } } # argument tab auto-completion and ValidateSet for level Class Levelz : System.Management.Automation.IValidateSetValuesGenerator { [System.String[]] GetValidValues() { - $Levelz = ('Hash', 'FileName', 'SignedVersion', 'Publisher', 'FilePublisher', 'LeafCertificate', 'PcaCertificate', 'RootCertificate', 'WHQL', 'WHQLPublisher', 'WHQLFilePublisher', 'PFN', 'FilePath', 'None') + $Levelz = ('Hash', 'FileName', 'SignedVersion', 'Publisher', 'FilePublisher', 'LeafCertificate', 'PcaCertificate', 'RootCertificate', 'WHQL', 'WHQLPublisher', 'WHQLFilePublisher', 'PFN', 'FilePath', 'None') return [System.String[]]$Levelz } } - - # Stop operation as soon as there is an error anywhere, unless explicitly specified otherwise - $ErrorActionPreference = 'Stop' - if (-NOT $SkipVersionCheck) { . Update-self } - - # Fetch User account directory path - [string]$global:UserAccountDirectoryPath = (Get-CimInstance Win32_UserProfile -Filter "SID = '$([System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value)'").LocalPath - - #region User-Configurations-Processing-Validation + + # if -SkipVersionCheck wasn't passed, run the updater + # Redirecting the Update-Self function's information Stream to $null because Write-Host + # Used by Write-ColorfulText outputs to both information stream and host console + if (-NOT $SkipVersionCheck) { Update-self 6> $null } + + #Region User-Configurations-Processing-Validation # If any of these parameters, that are mandatory for all of the position 0 parameters, isn't supplied by user if (!$PolicyPath) { # Read User configuration file if it exists - $UserConfig = Get-Content -Path "$global:UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" -ErrorAction SilentlyContinue + $UserConfig = Get-Content -Path "$UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" -ErrorAction SilentlyContinue if ($UserConfig) { # Validate the Json file and read its content to make sure it's not corrupted try { $UserConfig = $UserConfig | ConvertFrom-Json } - catch { - Write-Error 'User Configuration Json file is corrupted, deleting it...' -ErrorAction Continue - # Calling this function with this parameter automatically does its job and breaks/stops the operation - Set-CommonWDACConfig -DeleteUserConfig - } + catch { + Write-Error -Message 'User Configuration Json file is corrupted, deleting it...' -ErrorAction Continue + Remove-CommonWDACConfig + } } } # If PolicyPaths has no values - if (!$PolicyPath) { + if (!$PolicyPath) { if ($UserConfig.UnsignedPolicyPath) { # validate each policyPath read from user config file - if (Test-Path $($UserConfig.UnsignedPolicyPath)) { + if (Test-Path -Path $($UserConfig.UnsignedPolicyPath)) { $PolicyPath = $UserConfig.UnsignedPolicyPath } else { throw 'The currently saved value for UnsignedPolicyPath in user configurations is invalid.' - } + } } else { - throw "PolicyPath parameter can't be empty and no valid configuration was found for UnsignedPolicyPath." + throw 'PolicyPath parameter cannot be empty and no valid configuration was found for UnsignedPolicyPath.' } - } - #endregion User-Configurations-Processing-Validation + } + #Endregion User-Configurations-Processing-Validation # Ensure when user selects the -Deploy parameter, the base policy is not signed if ($Deploy) { - $xmlTest = [xml](Get-Content $PolicyPath) - $RedFlag1 = $xmlTest.SiPolicy.SupplementalPolicySigners.SupplementalPolicySigner.SignerId - $RedFlag2 = $xmlTest.SiPolicy.UpdatePolicySigners.UpdatePolicySigner.SignerId - if ($RedFlag1 -or $RedFlag2) { + $XmlTest = [System.Xml.XmlDocument](Get-Content -Path $PolicyPath) + $RedFlag1 = $XmlTest.SiPolicy.SupplementalPolicySigners.SupplementalPolicySigner.SignerId + $RedFlag2 = $XmlTest.SiPolicy.UpdatePolicySigners.UpdatePolicySigner.SignerId + if ($RedFlag1 -or $RedFlag2) { Write-Error -Message 'You are using -Deploy parameter and the selected base policy is Signed. Please use Deploy-SignedWDACConfig to deploy it.' - } + } } } process { - + if ($Normal) { - + # Creating a hash table to dynamically add parameters based on user input and pass them to New-Cipolicy cmdlet [System.Collections.Hashtable]$PolicyMakerHashTable = @{ FilePath = "SupplementalPolicy $SuppPolicyName.xml" @@ -143,195 +147,182 @@ function New-SupplementalWDACConfig { AllowFileNameFallbacks = $true } # Assess user input parameters and add the required parameters to the hash table - if ($SpecificFileNameLevel) { $PolicyMakerHashTable['SpecificFileNameLevel'] = $SpecificFileNameLevel } - if ($NoScript) { $PolicyMakerHashTable['NoScript'] = $true } - if (!$NoUserPEs) { $PolicyMakerHashTable['UserPEs'] = $true } + if ($SpecificFileNameLevel) { $PolicyMakerHashTable['SpecificFileNameLevel'] = $SpecificFileNameLevel } + if ($NoScript) { $PolicyMakerHashTable['NoScript'] = $true } + if (!$NoUserPEs) { $PolicyMakerHashTable['UserPEs'] = $true } - &$WriteHotPink "`nGenerating Supplemental policy with the following specifications:" + Write-ColorfulText -Color HotPink -InputText 'Generating Supplemental policy with the following specifications:' $PolicyMakerHashTable - Write-Host "`n" + Write-Host -Object '' # Create the supplemental policy via parameter splatting - New-CIPolicy @PolicyMakerHashTable - - [System.String]$policyID = Set-CIPolicyIdInfo -FilePath "SupplementalPolicy $SuppPolicyName.xml" -ResetPolicyID -BasePolicyToSupplementPath $PolicyPath -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" - [System.String]$policyID = $policyID.Substring(11) + New-CIPolicy @PolicyMakerHashTable + + [System.String]$PolicyID = Set-CIPolicyIdInfo -FilePath "SupplementalPolicy $SuppPolicyName.xml" -ResetPolicyID -BasePolicyToSupplementPath $PolicyPath -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" + [System.String]$PolicyID = $PolicyID.Substring(11) Set-CIPolicyVersion -FilePath "SupplementalPolicy $SuppPolicyName.xml" -Version '1.0.0.0' - # Make sure policy rule options that don't belong to a Supplemental policy don't exit - @(0, 1, 2, 3, 4, 9, 10, 11, 12, 15, 16, 17, 19, 20) | ForEach-Object { - Set-RuleOption -FilePath "SupplementalPolicy $SuppPolicyName.xml" -Option $_ -Delete } - Set-HVCIOptions -Strict -FilePath "SupplementalPolicy $SuppPolicyName.xml" - ConvertFrom-CIPolicy "SupplementalPolicy $SuppPolicyName.xml" "$policyID.cip" | Out-Null - [PSCustomObject]@{ - SupplementalPolicyFile = "SupplementalPolicy $SuppPolicyName.xml" - SupplementalPolicyGUID = $PolicyID - } - if ($Deploy) { - CiTool --update-policy "$policyID.cip" -json | Out-Null - &$WritePink "A Supplemental policy with the name $SuppPolicyName has been deployed." - Remove-Item -Path "$policyID.cip" -Force + # Make sure policy rule options that don't belong to a Supplemental policy don't exist + @(0, 1, 2, 3, 4, 9, 10, 11, 12, 15, 16, 17, 19, 20) | ForEach-Object -Process { + Set-RuleOption -FilePath "SupplementalPolicy $SuppPolicyName.xml" -Option $_ -Delete } + Set-HVCIOptions -Strict -FilePath "SupplementalPolicy $SuppPolicyName.xml" + ConvertFrom-CIPolicy -XmlFilePath "SupplementalPolicy $SuppPolicyName.xml" -BinaryFilePath "$PolicyID.cip" | Out-Null + + Write-ColorfulText -Color MintGreen -InputText "SupplementalPolicyFile = SupplementalPolicy $SuppPolicyName.xml" + Write-ColorfulText -Color MintGreen -InputText "SupplementalPolicyGUID = $PolicyID" + + if ($Deploy) { + &'C:\Windows\System32\CiTool.exe' --update-policy "$PolicyID.cip" -json | Out-Null + Write-ColorfulText -Color Pink -InputText "A Supplemental policy with the name $SuppPolicyName has been deployed." + Remove-Item -Path "$PolicyID.cip" -Force } } - + if ($PathWildCards) { - + # Using Windows PowerShell to handle serialized data since PowerShell core throws an error # Creating the Supplemental policy file - powershell.exe { + powershell.exe -Command { $RulesWildCards = New-CIPolicyRule -FilePathRule $args[0] New-CIPolicy -MultiplePolicyFormat -FilePath ".\SupplementalPolicy $($args[1]).xml" -Rules $RulesWildCards } -args $FolderPath, $SuppPolicyName # Giving the Supplemental policy the correct properties - [System.String]$policyID = Set-CIPolicyIdInfo -FilePath ".\SupplementalPolicy $SuppPolicyName.xml" -ResetPolicyID -BasePolicyToSupplementPath $PolicyPath -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" - [System.String]$policyID = $policyID.Substring(11) + [System.String]$PolicyID = Set-CIPolicyIdInfo -FilePath ".\SupplementalPolicy $SuppPolicyName.xml" -ResetPolicyID -BasePolicyToSupplementPath $PolicyPath -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" + [System.String]$PolicyID = $PolicyID.Substring(11) Set-CIPolicyVersion -FilePath ".\SupplementalPolicy $SuppPolicyName.xml" -Version '1.0.0.0' - - # Make sure policy rule options that don't belong to a Supplemental policy don't exit - @(0, 1, 2, 3, 4, 9, 10, 11, 12, 15, 16, 17, 19, 20) | ForEach-Object { + + # Make sure policy rule options that don't belong to a Supplemental policy don't exist + @(0, 1, 2, 3, 4, 9, 10, 11, 12, 15, 16, 17, 19, 20) | ForEach-Object -Process { Set-RuleOption -FilePath ".\SupplementalPolicy $SuppPolicyName.xml" -Option $_ -Delete } - + # Adding policy rule option 18 Disabled:Runtime FilePath Rule Protection Set-RuleOption -FilePath ".\SupplementalPolicy $SuppPolicyName.xml" -Option 18 - - Set-HVCIOptions -Strict -FilePath ".\SupplementalPolicy $SuppPolicyName.xml" - ConvertFrom-CIPolicy ".\SupplementalPolicy $SuppPolicyName.xml" "$policyID.cip" | Out-Null - [PSCustomObject]@{ - SupplementalPolicyFile = ".\SupplementalPolicy $SuppPolicyName.xml" - SupplementalPolicyGUID = $PolicyID - } - - if ($Deploy) { - CiTool --update-policy "$policyID.cip" -json | Out-Null - &$WritePink "A Supplemental policy with the name $SuppPolicyName has been deployed." - Remove-Item -Path "$policyID.cip" -Force + + Set-HVCIOptions -Strict -FilePath ".\SupplementalPolicy $SuppPolicyName.xml" + ConvertFrom-CIPolicy -XmlFilePath ".\SupplementalPolicy $SuppPolicyName.xml" -BinaryFilePath "$PolicyID.cip" | Out-Null + + Write-ColorfulText -Color MintGreen -InputText "SupplementalPolicyFile = SupplementalPolicy $SuppPolicyName.xml" + Write-ColorfulText -Color MintGreen -InputText "SupplementalPolicyGUID = $PolicyID" + + if ($Deploy) { + &'C:\Windows\System32\CiTool.exe' --update-policy "$PolicyID.cip" -json | Out-Null + Write-ColorfulText -Color Pink -InputText "A Supplemental policy with the name $SuppPolicyName has been deployed." + Remove-Item -Path "$PolicyID.cip" -Force } } if ($InstalledAppXPackages) { do { Get-AppxPackage -Name $PackageName - Write-Debug -Message "This is the Selected package name $PackageName" - $Question = Read-Host "`nIs this the intended results based on your Installed Appx packages? Enter 1 to continue, Enter 2 to exit" + Write-Verbose -Message "This is the Selected package name $PackageName" + $Question = Read-Host -Prompt "`nIs this the intended results based on your Installed Appx packages? Enter 1 to continue, Enter 2 to exit" } until ( (($Question -eq 1) -or ($Question -eq 2)) ) if ($Question -eq 2) { break } - powershell.exe { + powershell.exe -Command { # Get all the packages based on the supplied name $Package = Get-AppxPackage -Name $args[0] # Get package dependencies if any $PackageDependencies = $Package.Dependencies # Create rules for each package - foreach ($item in $Package) { - $Rules += New-CIPolicyRule -Package $item + foreach ($Item in $Package) { + $Rules += New-CIPolicyRule -Package $Item } - - # Create rules for each pacakge dependency, if any + + # Create rules for each package dependency, if any if ($PackageDependencies) { - foreach ($item in $PackageDependencies) { - $Rules += New-CIPolicyRule -Package $item + foreach ($Item in $PackageDependencies) { + $Rules += New-CIPolicyRule -Package $Item } } - + # Generate the supplemental policy xml file New-CIPolicy -MultiplePolicyFormat -FilePath ".\SupplementalPolicy $($args[1]).xml" -Rules $Rules } -args $PackageName, $SuppPolicyName # Giving the Supplemental policy the correct properties - [System.String]$policyID = Set-CIPolicyIdInfo -FilePath ".\SupplementalPolicy $SuppPolicyName.xml" -ResetPolicyID -BasePolicyToSupplementPath $PolicyPath -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" - [System.String]$policyID = $policyID.Substring(11) + [System.String]$PolicyID = Set-CIPolicyIdInfo -FilePath ".\SupplementalPolicy $SuppPolicyName.xml" -ResetPolicyID -BasePolicyToSupplementPath $PolicyPath -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" + [System.String]$PolicyID = $PolicyID.Substring(11) Set-CIPolicyVersion -FilePath ".\SupplementalPolicy $SuppPolicyName.xml" -Version '1.0.0.0' - - # Make sure policy rule options that don't belong to a Supplemental policy don't exit - @(0, 1, 2, 3, 4, 9, 10, 11, 12, 15, 16, 17, 18, 19, 20) | ForEach-Object { - Set-RuleOption -FilePath ".\SupplementalPolicy $SuppPolicyName.xml" -Option $_ -Delete } - - Set-HVCIOptions -Strict -FilePath ".\SupplementalPolicy $SuppPolicyName.xml" - ConvertFrom-CIPolicy ".\SupplementalPolicy $SuppPolicyName.xml" "$policyID.cip" | Out-Null - [PSCustomObject]@{ - SupplementalPolicyFile = ".\SupplementalPolicy $SuppPolicyName.xml" - SupplementalPolicyGUID = $PolicyID - } - if ($Deploy) { - CiTool --update-policy "$policyID.cip" -json | Out-Null - &$WritePink "A Supplemental policy with the name $SuppPolicyName has been deployed." - Remove-Item -Path "$policyID.cip" -Force + # Make sure policy rule options that don't belong to a Supplemental policy don't exist + @(0, 1, 2, 3, 4, 9, 10, 11, 12, 15, 16, 17, 18, 19, 20) | ForEach-Object -Process { + Set-RuleOption -FilePath ".\SupplementalPolicy $SuppPolicyName.xml" -Option $_ -Delete } + + Set-HVCIOptions -Strict -FilePath ".\SupplementalPolicy $SuppPolicyName.xml" + ConvertFrom-CIPolicy -XmlFilePath ".\SupplementalPolicy $SuppPolicyName.xml" -BinaryFilePath "$PolicyID.cip" | Out-Null + + Write-ColorfulText -Color MintGreen -InputText "SupplementalPolicyFile = SupplementalPolicy $SuppPolicyName.xml" + Write-ColorfulText -Color MintGreen -InputText "SupplementalPolicyGUID = $PolicyID" + + if ($Deploy) { + &'C:\Windows\System32\CiTool.exe' --update-policy "$PolicyID.cip" -json | Out-Null + Write-ColorfulText -Color Pink -InputText "A Supplemental policy with the name $SuppPolicyName has been deployed." + Remove-Item -Path "$PolicyID.cip" -Force } } - } - + } + <# .SYNOPSIS -Automate a lot of tasks related to WDAC (Windows Defender Application Control) - + Automate a lot of tasks related to WDAC (Windows Defender Application Control) .LINK -https://github.com/HotCakeX/Harden-Windows-Security/wiki/New-SupplementalWDACConfig - + https://github.com/HotCakeX/Harden-Windows-Security/wiki/New-SupplementalWDACConfig .DESCRIPTION -Using official Microsoft methods, configure and use Windows Defender Application Control - + Using official Microsoft methods, configure and use Windows Defender Application Control .COMPONENT -Windows Defender Application Control, ConfigCI PowerShell module - + Windows Defender Application Control, ConfigCI PowerShell module .FUNCTIONALITY -Automate various tasks related to Windows Defender Application Control (WDAC) - -.PARAMETER Normal -Make a Supplemental policy by scanning a directory, you can optionally use other parameters too to fine tune the scan process - + Automate various tasks related to Windows Defender Application Control (WDAC) +.PARAMETER Normal + Make a Supplemental policy by scanning a directory, you can optionally use other parameters too to fine tune the scan process .PARAMETER PathWildCards -Make a Supplemental policy by scanning a directory and creating a wildcard FilePath rules for all of the files inside that directory, recursively - + Make a Supplemental policy by scanning a directory and creating a wildcard FilePath rules for all of the files inside that directory, recursively .PARAMETER InstalledAppXPackages -Make a Supplemental policy based on the Package Family Name of an installed Windows app (Appx) - + Make a Supplemental policy based on the Package Family Name of an installed Windows app (Appx) .PARAMETER PackageName -Enter the package name of an installed app. Supports wildcard * character. e.g., *Edge* or "*Microsoft*". - + Enter the package name of an installed app. Supports wildcard * character. e.g., *Edge* or "*Microsoft*". .PARAMETER ScanLocation -The directory or drive that you want to scan for files that will be allowed to run by the Supplemental policy. - + The directory or drive that you want to scan for files that will be allowed to run by the Supplemental policy. .PARAMETER FolderPath -Path of a folder that you want to allow using wildcards *. - + Path of a folder that you want to allow using wildcards *. .PARAMETER SuppPolicyName -Add a descriptive name for the Supplemental policy. Accepts only alphanumeric and space characters. - + Add a descriptive name for the Supplemental policy. Accepts only alphanumeric and space characters. + It is used by the entire Cmdlet. .PARAMETER PolicyPath -Browse for the xml file of the Base policy this Supplemental policy is going to expand. Supports tab completion by showing only .xml files with Base Policy Type. - + Browse for the xml file of the Base policy this Supplemental policy is going to expand. Supports tab completion by showing only .xml files with Base Policy Type. + It is used by the entire Cmdlet. .PARAMETER Deploy -Indicates that the module will automatically deploy the Supplemental policy after creation. - + Indicates that the module will automatically deploy the Supplemental policy after creation. + It is used by the entire Cmdlet. .PARAMETER SpecificFileNameLevel -You can choose one of the following options: "OriginalFileName", "InternalName", "FileDescription", "ProductName", "PackageFamilyName", "FilePath" - + You can choose one of the following options: "OriginalFileName", "InternalName", "FileDescription", "ProductName", "PackageFamilyName", "FilePath" .PARAMETER NoUserPEs -By default the module includes user PEs in the scan, but when you use this switch parameter, they won't be included. - + By default the module includes user PEs in the scan, but when you use this switch parameter, they won't be included. .PARAMETER NoScript -https://learn.microsoft.com/en-us/powershell/module/configci/new-cipolicy#-noscript - + Refer to this page for more info: https://learn.microsoft.com/en-us/powershell/module/configci/new-cipolicy#-noscript .PARAMETER Level -Offers the same official Levels for scanning of the specified directory path. If no level is specified the default, which is set to FilePublisher in this module, will be used. - + The level that determines how the selected folder will be scanned. + The default value for it is FilePublisher. .PARAMETER Fallbacks -Offers the same official Fallbacks for scanning of the specified directory path. If no fallbacks is specified the default, which is set to Hash in this module, will be used. - + The fallback level(s) that determine how the selected folder will be scanned. + The default value for it is Hash. .PARAMETER SkipVersionCheck -Can be used with any parameter to bypass the online version check - only to be used in rare cases - + Can be used with any parameter to bypass the online version check - only to be used in rare cases +.INPUTS + System.String[] + System.String + System.Management.Automation.SwitchParameter +.OUTPUTS + System.String #> } # Importing argument completer ScriptBlocks -. "$psscriptroot\ArgumentCompleters.ps1" -# Set PSReadline tab completion to complete menu for easier access to available parameters - Only for the current session -Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete +. "$ModuleRootPath\Resources\ArgumentCompleters.ps1" Register-ArgumentCompleter -CommandName 'New-SupplementalWDACConfig' -ParameterName 'PolicyPath' -ScriptBlock $ArgumentCompleterPolicyPathsBasePoliciesOnly Register-ArgumentCompleter -CommandName 'New-SupplementalWDACConfig' -ParameterName 'PackageName' -ScriptBlock $ArgumentCompleterAppxPackageNames Register-ArgumentCompleter -CommandName 'New-SupplementalWDACConfig' -ParameterName 'ScanLocation' -ScriptBlock $ArgumentCompleterFolderPathsPicker diff --git a/WDACConfig/WDACConfig Module Files/Core/New-WDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/New-WDACConfig.psm1 new file mode 100644 index 000000000..e70fc9887 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/Core/New-WDACConfig.psm1 @@ -0,0 +1,979 @@ +Function New-WDACConfig { + [CmdletBinding( + DefaultParameterSetName = 'Get Block Rules', + SupportsShouldProcess = $true, + PositionalBinding = $false, + ConfirmImpact = 'High' + )] + Param( + # 9 Main parameters - should be used for position 0 + [Parameter(Mandatory = $false, ParameterSetName = 'Get Block Rules')][System.Management.Automation.SwitchParameter]$GetBlockRules, + [Parameter(Mandatory = $false, ParameterSetName = 'Get Driver Block Rules')][System.Management.Automation.SwitchParameter]$GetDriverBlockRules, + [Parameter(Mandatory = $false, ParameterSetName = 'Make AllowMSFT With Block Rules')][System.Management.Automation.SwitchParameter]$MakeAllowMSFTWithBlockRules, + [Parameter(Mandatory = $false, ParameterSetName = 'Set Auto Update Driver Block Rules')][System.Management.Automation.SwitchParameter]$SetAutoUpdateDriverBlockRules, + [Parameter(Mandatory = $false, ParameterSetName = 'Prep MSFT Only Audit')][System.Management.Automation.SwitchParameter]$PrepMSFTOnlyAudit, + [Parameter(Mandatory = $false, ParameterSetName = 'Prep Default Windows Audit')][System.Management.Automation.SwitchParameter]$PrepDefaultWindowsAudit, + [Parameter(Mandatory = $false, ParameterSetName = 'Make Policy From Audit Logs')][System.Management.Automation.SwitchParameter]$MakePolicyFromAuditLogs, + [Parameter(Mandatory = $false, ParameterSetName = 'Make Light Policy')][System.Management.Automation.SwitchParameter]$MakeLightPolicy, + [Parameter(Mandatory = $false, ParameterSetName = 'Make DefaultWindows With Block Rules')][System.Management.Automation.SwitchParameter]$MakeDefaultWindowsWithBlockRules, + + [ValidateSet('Allow Microsoft Base', 'Default Windows Base')] + [Parameter(Mandatory = $true, ParameterSetName = 'Make Policy From Audit Logs')] + [System.String]$BasePolicyType, + + [Parameter(Mandatory = $false, ParameterSetName = 'Make AllowMSFT With Block Rules')] + [Parameter(Mandatory = $false, ParameterSetName = 'Make Policy From Audit Logs')] + [Parameter(Mandatory = $false, ParameterSetName = 'Make Light Policy')] + [Parameter(Mandatory = $false, ParameterSetName = 'Make DefaultWindows With Block Rules')] + [Parameter(Mandatory = $false, ParameterSetName = 'Prep MSFT Only Audit')] + [Parameter(Mandatory = $false, ParameterSetName = 'Prep Default Windows Audit')] + [Parameter(Mandatory = $false, ParameterSetName = 'Get Block Rules')] + [Parameter(Mandatory = $false, ParameterSetName = 'Get Driver Block Rules')] + [System.Management.Automation.SwitchParameter]$Deploy, + + [Parameter(Mandatory = $false, ParameterSetName = 'Make DefaultWindows With Block Rules')] + [System.Management.Automation.SwitchParameter]$IncludeSignTool, + + [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Make DefaultWindows With Block Rules')] + [System.String]$SignToolPath, + + [Parameter(Mandatory = $false, ParameterSetName = 'Make Light Policy')] + [Parameter(Mandatory = $false, ParameterSetName = 'Make Policy From Audit Logs')] + [Parameter(Mandatory = $false, ParameterSetName = 'Make AllowMSFT With Block Rules')] + [Parameter(Mandatory = $false, ParameterSetName = 'Make DefaultWindows With Block Rules')] + [System.Management.Automation.SwitchParameter]$TestMode, + + [Parameter(Mandatory = $false, ParameterSetName = 'Make AllowMSFT With Block Rules')] + [Parameter(Mandatory = $false, ParameterSetName = 'Make Policy From Audit Logs')] + [Parameter(Mandatory = $false, ParameterSetName = 'Make Light Policy')] + [Parameter(Mandatory = $false, ParameterSetName = 'Make DefaultWindows With Block Rules')] + [System.Management.Automation.SwitchParameter]$RequireEVSigners, + + [ValidateSet('OriginalFileName', 'InternalName', 'FileDescription', 'ProductName', 'PackageFamilyName', 'FilePath')] + [Parameter(Mandatory = $false, ParameterSetName = 'Make Policy From Audit Logs')] + [System.String]$SpecificFileNameLevel, + + [Parameter(Mandatory = $false, ParameterSetName = 'Make Policy From Audit Logs')] + [System.Management.Automation.SwitchParameter]$NoDeletedFiles, + + [Parameter(Mandatory = $false, ParameterSetName = 'Make Policy From Audit Logs')] + [System.Management.Automation.SwitchParameter]$NoUserPEs, + + [Parameter(Mandatory = $false, ParameterSetName = 'Make Policy From Audit Logs')] + [System.Management.Automation.SwitchParameter]$NoScript, + + [ValidateSet([Levelz])] + [parameter(Mandatory = $false, ParameterSetName = 'Make Policy From Audit Logs')] + [System.String]$Level = 'FilePublisher', + + [ValidateSet([Fallbackz])] + [parameter(Mandatory = $false, ParameterSetName = 'Make Policy From Audit Logs')] + [System.String[]]$Fallbacks = 'Hash', + + [ValidateRange(1024KB, 18014398509481983KB)] + [Parameter(Mandatory = $false, ParameterSetName = 'Prep MSFT Only Audit')] + [Parameter(Mandatory = $false, ParameterSetName = 'Prep Default Windows Audit')] + [Parameter(Mandatory = $false, ParameterSetName = 'Make Policy From Audit Logs')] + [System.Int64]$LogSize, + + [Parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$SkipVersionCheck + ) + + begin { + # Detecting if Verbose switch is used + $PSBoundParameters.Verbose.IsPresent ? ([System.Boolean]$Verbose = $true) : ([System.Boolean]$Verbose = $false) | Out-Null + + # Importing the $PSDefaultParameterValues to the current session, prior to everything else + . "$ModuleRootPath\CoreExt\PSDefaultParameterValues.ps1" + + # Importing the required sub-modules + Write-Verbose -Message 'Importing the required sub-modules' + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Update-self.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Get-SignTool.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Get-GlobalRootDrives.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Write-ColorfulText.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Set-LogSize.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\New-EmptyPolicy.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Get-RuleRefs.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Get-FileRules.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Get-BlockRulesMeta.psm1" -Force + + #Region User-Configurations-Processing-Validation + # If User is creating Default Windows policy and including SignTool path + if ($IncludeSignTool -and $MakeDefaultWindowsWithBlockRules) { + # Read User configuration file if it exists + $UserConfig = Get-Content -Path "$UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" -ErrorAction SilentlyContinue + if ($UserConfig) { + # Validate the Json file and read its content to make sure it's not corrupted + try { $UserConfig = $UserConfig | ConvertFrom-Json } + catch { + Write-Error -Message 'User Configurations Json file is corrupted, deleting it...' -ErrorAction Continue + Remove-CommonWDACConfig + } + } + } + + # Get SignToolPath from user parameter or user config file or auto-detect it + if ($SignToolPath) { + $SignToolPathFinal = Get-SignTool -SignToolExePath $SignToolPath + } # If it is null, then Get-SignTool will behave the same as if it was called without any arguments. + elseif ($IncludeSignTool -and $MakeDefaultWindowsWithBlockRules) { + $SignToolPathFinal = Get-SignTool -SignToolExePath ($UserConfig.SignToolCustomPath ?? $null) + } + #Endregion User-Configurations-Processing-Validation + + # Detecting if Debug switch is used, will do debugging actions based on that + $PSBoundParameters.Debug.IsPresent ? ([System.Boolean]$Debug = $true) : ([System.Boolean]$Debug = $false) | Out-Null + + # argument tab auto-completion and ValidateSet for Fallbacks + Class Fallbackz : System.Management.Automation.IValidateSetValuesGenerator { + [System.String[]] GetValidValues() { + $Fallbackz = ('Hash', 'FileName', 'SignedVersion', 'Publisher', 'FilePublisher', 'LeafCertificate', 'PcaCertificate', 'RootCertificate', 'WHQL', 'WHQLPublisher', 'WHQLFilePublisher', 'PFN', 'FilePath', 'None') + return [System.String[]]$Fallbackz + } + } + + # argument tab auto-completion and ValidateSet for level + Class Levelz : System.Management.Automation.IValidateSetValuesGenerator { + [System.String[]] GetValidValues() { + $Levelz = ('Hash', 'FileName', 'SignedVersion', 'Publisher', 'FilePublisher', 'LeafCertificate', 'PcaCertificate', 'RootCertificate', 'WHQL', 'WHQLPublisher', 'WHQLFilePublisher', 'PFN', 'FilePath', 'None') + return [System.String[]]$Levelz + } + } + Function Get-DriverBlockRules { + <# + .SYNOPSIS + Gets the latest Microsoft Recommended Driver Block rules and processes them + Can optionally deploy them + .INPUTS + System.Management.Automation.SwitchParameter + .OUTPUTS + System.String + .PARAMETER Deploy + Indicates that the function will deploy the latest Microsoft recommended drivers block list + #> + [CmdletBinding()] + param ( + [System.Management.Automation.SwitchParameter]$Deploy + ) + + if ($Deploy) { + Write-Verbose -Message 'Downloading the Microsoft Recommended Driver Block List archive' + Invoke-WebRequest -Uri 'https://aka.ms/VulnerableDriverBlockList' -OutFile VulnerableDriverBlockList.zip -ProgressAction SilentlyContinue + + Write-Verbose -Message 'Expanding the Block list archive' + Expand-Archive -Path .\VulnerableDriverBlockList.zip -DestinationPath 'VulnerableDriverBlockList' -Force + + Write-Verbose -Message 'Renaming the block list file to SiPolicy.p7b' + Rename-Item -Path .\VulnerableDriverBlockList\SiPolicy_Enforced.p7b -NewName 'SiPolicy.p7b' -Force + + Write-Verbose -Message 'Copying the new block list to the CodeIntegrity folder, replacing any old ones' + Copy-Item -Path .\VulnerableDriverBlockList\SiPolicy.p7b -Destination 'C:\Windows\System32\CodeIntegrity' -Force + + Write-Verbose -Message 'Refreshing the system WDAC policies using CiTool.exe' + &'C:\Windows\System32\CiTool.exe' --refresh -json | Out-Null + + Write-ColorfulText -Color Pink -InputText 'SiPolicy.p7b has been deployed and policies refreshed.' + + Write-Verbose -Message 'Cleaning up' + Remove-Item -Path .\VulnerableDriverBlockList* -Recurse -Force + + Write-Verbose -Message 'Displaying extra info about the Microsoft recommended Drivers block list' + Invoke-Command -ScriptBlock $DriversBlockListInfoGatheringSCRIPTBLOCK + } + else { + # Downloading the latest Microsoft Recommended Driver Block Rules from the official source + Write-Verbose -Message 'Downloading the latest Microsoft Recommended Driver Block Rules from the official source' + [System.String]$DriverRules = (Invoke-WebRequest -Uri $MSFTRecommendedDriverBlockRulesURL -ProgressAction SilentlyContinue).Content -replace "(?s).*``````xml(.*)``````.*", '$1' + + # Remove the unnecessary rules and elements - not using this one because then during the merge there will be error - The reason is that "" is the only FileruleRef in the xml and after removing it, the element will be empty + Write-Verbose -Message 'Removing the allow all rules and rule refs from the policy' + $DriverRules = $DriverRules -replace '', '' + $DriverRules = $DriverRules -replace '', '' + $DriverRules = $DriverRules -replace '', '' + + # Output the XML content to a file + Write-Verbose -Message 'Creating XML policy file' + $DriverRules | Out-File -FilePath 'Microsoft recommended driver block rules TEMP.xml' -Force + + # Remove empty lines from the policy file + Write-Verbose -Message 'Removing the empty lines from the policy XML file' + Get-Content -Path 'Microsoft recommended driver block rules TEMP.xml' | Where-Object -FilterScript { $_.trim() -ne '' } | Out-File -FilePath 'Microsoft recommended driver block rules.xml' -Force + + Write-Verbose -Message 'Removing the temp XML file' + Remove-Item -Path 'Microsoft recommended driver block rules TEMP.xml' -Force + + Write-Verbose -Message 'Removing the Audit mode policy rule option' + Set-RuleOption -FilePath 'Microsoft recommended driver block rules.xml' -Option 3 -Delete + + Write-Verbose -Message 'Setting the HVCI option to strict' + Set-HVCIOptions -Strict -FilePath 'Microsoft recommended driver block rules.xml' + + # Display extra info about the Microsoft recommended Drivers block list + Write-Verbose -Message 'Displaying extra info about the Microsoft recommended Drivers block list' + Invoke-Command -ScriptBlock $DriversBlockListInfoGatheringSCRIPTBLOCK + + # Display the result + Write-ColorfulText -Color MintGreen -InputText 'PolicyFile = Microsoft recommended driver block rules.xml' + } + } + + Function Build-AllowMSFTWithBlockRules { + <# + .SYNOPSIS + A helper function that downloads the latest Microsoft recommended block rules + and merges them with the Allow Microsoft template policy. + It can also deploy the policy on the system. + .PARAMETER NoCIP + Indicates that the created .CIP binary file must be deleted at the end. + It's usually used when calling this function from other functions that don't need the .CIP output of this function. + .INPUTS + System.Management.Automation.SwitchParameter + .OUTPUTS + System.String + #> + [CmdletBinding()] + param( + [System.Management.Automation.SwitchParameter]$NoCIP + ) + # Get the latest Microsoft recommended block rules + Write-Verbose -Message 'Getting the latest Microsoft recommended block rules' + Get-BlockRulesMeta 6> $null + + Write-Verbose -Message 'Copying the AllowMicrosoft.xml from Windows directory to the current working directory' + Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowMicrosoft.xml' -Destination 'AllowMicrosoft.xml' -Force + + Write-Verbose -Message 'Merging the AllowMicrosoft.xml with Microsoft Recommended Block rules.xml' + Merge-CIPolicy -PolicyPaths .\AllowMicrosoft.xml, 'Microsoft recommended block rules.xml' -OutputFilePath .\AllowMicrosoftPlusBlockRules.xml | Out-Null + + Write-Verbose -Message 'Resetting the policy ID and setting a name for AllowMicrosoftPlusBlockRules.xml' + [System.String]$PolicyID = Set-CIPolicyIdInfo -FilePath .\AllowMicrosoftPlusBlockRules.xml -PolicyName "Allow Microsoft Plus Block Rules - $(Get-Date -Format 'MM-dd-yyyy')" -ResetPolicyID + [System.String]$PolicyID = $PolicyID.Substring(11) + + Write-Verbose -Message 'Setting AllowMicrosoftPlusBlockRules.xml policy version to 1.0.0.0' + Set-CIPolicyVersion -FilePath .\AllowMicrosoftPlusBlockRules.xml -Version '1.0.0.0' + + Write-Verbose -Message 'Configuring the policy rule options' + @(0, 2, 5, 6, 11, 12, 16, 17, 19, 20) | ForEach-Object -Process { Set-RuleOption -FilePath .\AllowMicrosoftPlusBlockRules.xml -Option $_ } + @(3, 4, 9, 10, 13, 18) | ForEach-Object -Process { Set-RuleOption -FilePath .\AllowMicrosoftPlusBlockRules.xml -Option $_ -Delete } + + if ($TestMode -and $MakeAllowMSFTWithBlockRules) { + Write-Verbose -Message 'Setting "Boot Audit on Failure" and "Advanced Boot Options Menu" policy rule options for the AllowMicrosoftPlusBlockRules.xml policy because TestMode parameter was used' + 9..10 | ForEach-Object -Process { Set-RuleOption -FilePath .\AllowMicrosoftPlusBlockRules.xml -Option $_ } + } + if ($RequireEVSigners -and $MakeAllowMSFTWithBlockRules) { + Write-Verbose -Message 'Setting "Required:EV Signers" policy rule option for the AllowMicrosoftPlusBlockRules.xml policy because RequireEVSigners parameter was used' + Set-RuleOption -FilePath .\AllowMicrosoftPlusBlockRules.xml -Option 8 + } + + Write-Verbose -Message 'Setting HVCI to Strict' + Set-HVCIOptions -Strict -FilePath .\AllowMicrosoftPlusBlockRules.xml + + Write-Verbose -Message 'Converting the AllowMicrosoftPlusBlockRules.xml policy file to .CIP binary' + ConvertFrom-CIPolicy -XmlFilePath .\AllowMicrosoftPlusBlockRules.xml -BinaryFilePath "$PolicyID.cip" | Out-Null + + # Remove the extra files that were created during module operation and are no longer needed + Write-Verbose -Message 'Removing the extra files that were created during module operation and are no longer needed' + Remove-Item -Path '.\AllowMicrosoft.xml', 'Microsoft recommended block rules.xml' -Force + + Write-Verbose -Message 'Displaying the outout' + Write-ColorfulText -Color MintGreen -InputText 'PolicyFile = AllowMicrosoftPlusBlockRules.xml' + Write-ColorfulText -Color MintGreen -InputText "BinaryFile = $PolicyID.cip" + + if ($Deploy -and $MakeAllowMSFTWithBlockRules) { + Write-Verbose -Message 'Deploying the AllowMicrosoftPlusBlockRules.xml policy' + &'C:\Windows\System32\CiTool.exe' --update-policy "$PolicyID.cip" -json | Out-Null + + Write-Verbose -Message 'Removing the generated .CIP binary file after deploying it' + Remove-Item -Path "$PolicyID.cip" -Force + } + + if ($NoCIP) { + Write-Verbose -Message 'Removing the generated .CIP binary file because -NoCIP parameter was used' + Remove-Item -Path "$PolicyID.cip" -Force + } + } + + Function Build-DefaultWindowsWithBlockRules { + <# + .SYNOPSIS + A helper function that downloads the latest Microsoft recommended block rules + and merges them with the DefaultWindows_Enforced template policy. + It can also deploy the policy on the system. + .INPUTS + None. You cannot pipe objects to this function. + .OUTPUTS + System.String + #> + [CmdletBinding()] + param() + + Write-Verbose -Message 'Getting the latest Microsoft recommended block rules' + Get-BlockRulesMeta 6> $null + + Write-Verbose -Message 'Copying the DefaultWindows_Enforced.xml from Windows directory to the current working directory' + Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\DefaultWindows_Enforced.xml' -Destination 'DefaultWindows_Enforced.xml' -Force + + # Setting a flag for Scanning the SignTool.exe and merging it with the final base policy + [System.Boolean]$MergeSignToolPolicy = $false + + if ($SignToolPathFinal) { + # Allowing SignTool to be able to run after Default Windows base policy is deployed in Signed scenario + Write-ColorfulText -Color TeaGreen -InputText "`nCreating allow rules for SignTool.exe in the DefaultWindows base policy so you can continue using it after deploying the DefaultWindows base policy." + + Write-Verbose -Message 'Creating a new temporary directory in the temp directory' + New-Item -Path "$UserTempDirectoryPath\TemporarySignToolFile" -ItemType Directory -Force | Out-Null + + Write-Verbose -Message 'Copying the SignTool.exe to the newly created directory in the temp directory' + Copy-Item -Path $SignToolPathFinal -Destination "$UserTempDirectoryPath\TemporarySignToolFile" -Force + + Write-Verbose -Message 'Scanning the SignTool.exe in the temp directory and generating the SignTool.xml policy' + New-CIPolicy -ScanPath "$UserTempDirectoryPath\TemporarySignToolFile" -Level FilePublisher -Fallback Hash -UserPEs -UserWriteablePaths -MultiplePolicyFormat -AllowFileNameFallbacks -FilePath .\SignTool.xml + + # Delete the Temporary folder in the TEMP folder + if (!$Debug) { + Write-Verbose -Message 'Debug parameter was not used, removing the files created in the temp directory' + Remove-Item -Recurse -Path "$UserTempDirectoryPath\TemporarySignToolFile" -Force + } + + # Setting the flag to true so that the SignTool.xml file will be merged with the final policy + $MergeSignToolPolicy = $true + } + + # Scan PowerShell core directory and allow its files in the Default Windows base policy so that module can still be used once it's been deployed + if (Test-Path -Path 'C:\Program Files\PowerShell') { + + Write-ColorfulText -Color Lavender -InputText 'Creating allow rules for PowerShell in the DefaultWindows base policy so you can continue using this module after deploying it.' + New-CIPolicy -ScanPath 'C:\Program Files\PowerShell' -Level FilePublisher -NoScript -Fallback Hash -UserPEs -UserWriteablePaths -MultiplePolicyFormat -FilePath .\AllowPowerShell.xml + + if ($MergeSignToolPolicy) { + Write-Verbose -Message 'Merging the policy files, including SignTool.xml, to create the final DefaultWindowsPlusBlockRules.xml policy' + Merge-CIPolicy -PolicyPaths .\DefaultWindows_Enforced.xml, .\AllowPowerShell.xml, 'Microsoft recommended block rules.xml', .\SignTool.xml -OutputFilePath .\DefaultWindowsPlusBlockRules.xml | Out-Null + } + else { + Write-Verbose -Message 'Merging the policy files to create the final DefaultWindowsPlusBlockRules.xml policy' + Merge-CIPolicy -PolicyPaths .\DefaultWindows_Enforced.xml, .\AllowPowerShell.xml, 'Microsoft recommended block rules.xml' -OutputFilePath .\DefaultWindowsPlusBlockRules.xml | Out-Null + } + } + else { + if ($MergeSignToolPolicy) { + Write-Verbose -Message 'Merging the policy files, including SignTool.xml, to create the final DefaultWindowsPlusBlockRules.xml policy' + Merge-CIPolicy -PolicyPaths .\DefaultWindows_Enforced.xml, 'Microsoft recommended block rules.xml', .\SignTool.xml -OutputFilePath .\DefaultWindowsPlusBlockRules.xml | Out-Null + } + else { + Write-Verbose -Message 'Merging the policy files to create the final DefaultWindowsPlusBlockRules.xml policy' + Merge-CIPolicy -PolicyPaths .\DefaultWindows_Enforced.xml, 'Microsoft recommended block rules.xml' -OutputFilePath .\DefaultWindowsPlusBlockRules.xml | Out-Null + } + } + + Write-Verbose -Message 'Resetting the policy ID and setting a name for DefaultWindowsPlusBlockRules.xml' + [System.String]$PolicyID = Set-CIPolicyIdInfo -FilePath .\DefaultWindowsPlusBlockRules.xml -PolicyName "Default Windows Plus Block Rules - $(Get-Date -Format 'MM-dd-yyyy')" -ResetPolicyID + [System.String]$PolicyID = $PolicyID.Substring(11) + + Write-Verbose -Message 'Setting the version of DefaultWindowsPlusBlockRules.xml policy to 1.0.0.0' + Set-CIPolicyVersion -FilePath .\DefaultWindowsPlusBlockRules.xml -Version '1.0.0.0' + + Write-Verbose -Message 'Configuring the policy rule options' + @(0, 2, 5, 6, 11, 12, 16, 17, 19, 20) | ForEach-Object -Process { Set-RuleOption -FilePath .\DefaultWindowsPlusBlockRules.xml -Option $_ } + @(3, 4, 9, 10, 13, 18) | ForEach-Object -Process { Set-RuleOption -FilePath .\DefaultWindowsPlusBlockRules.xml -Option $_ -Delete } + + if ($TestMode -and $MakeDefaultWindowsWithBlockRules) { + Write-Verbose -Message 'Setting "Boot Audit on Failure" and "Advanced Boot Options Menu" policy rule options for the DefaultWindowsPlusBlockRules.xml policy because TestMode parameter was used' + 9..10 | ForEach-Object -Process { Set-RuleOption -FilePath .\DefaultWindowsPlusBlockRules.xml -Option $_ } + } + + if ($RequireEVSigners -and $MakeDefaultWindowsWithBlockRules) { + Write-Verbose -Message 'Setting "Required:EV Signers" policy rule option for the DefaultWindowsPlusBlockRules.xml policy because RequireEVSigners parameter was used' + Set-RuleOption -FilePath .\DefaultWindowsPlusBlockRules.xml -Option 8 + } + + Write-Verbose -Message 'Setting HVCI to Strict' + Set-HVCIOptions -Strict -FilePath .\DefaultWindowsPlusBlockRules.xml + + Write-Verbose -Message 'Converting the DefaultWindowsPlusBlockRules.xml policy file to .CIP binary' + ConvertFrom-CIPolicy -XmlFilePath .\DefaultWindowsPlusBlockRules.xml -BinaryFilePath "$PolicyID.cip" | Out-Null + + Write-Verbose -Message 'Removing the extra files that were created during module operation and are no longer needed' + Remove-Item -Path .\AllowPowerShell.xml -Force -ErrorAction SilentlyContinue + Remove-Item -Path '.\DefaultWindows_Enforced.xml', 'Microsoft recommended block rules.xml' -Force + + if ($MergeSignToolPolicy -and !$Debug) { + Write-Verbose -Message 'Deleting SignTool.xml' + Remove-Item -Path .\SignTool.xml -Force + } + + Write-Verbose -Message 'Displaying the output' + Write-ColorfulText -Color MintGreen -InputText 'PolicyFile = DefaultWindowsPlusBlockRules.xml' + Write-ColorfulText -Color MintGreen -InputText "BinaryFile = $PolicyID.cip" + + if ($Deploy -and $MakeDefaultWindowsWithBlockRules) { + Write-Verbose -Message 'Deploying the DefaultWindowsPlusBlockRules.xml policy' + &'C:\Windows\System32\CiTool.exe' --update-policy "$PolicyID.cip" -json | Out-Null + + Write-Verbose -Message 'Removing the generated .CIP binary file after deploying it' + Remove-Item -Path "$PolicyID.cip" -Force + } + } + + Function Deploy-LatestBlockRules { + <# + .SYNOPSIS + A helper function that downloads the latest Microsoft recommended block rules + .INPUTS + None. You cannot pipe objects to this function. + .OUTPUTS + System.String + #> + [CmdletBinding()] + param() + + Write-Verbose -Message 'Downloading the latest Microsoft recommended block rules and creating Microsoft recommended block rules TEMP.xml' + (Invoke-WebRequest -Uri $MSFTRecommendedBlockRulesURL -ProgressAction SilentlyContinue).Content -replace "(?s).*``````xml(.*)``````.*", '$1' | Out-File -FilePath '.\Microsoft recommended block rules TEMP.xml' -Force + + # Remove empty lines from the policy file + Write-Verbose -Message 'Removing any empty lines from the Temp policy file and generating the Microsoft recommended block rules.xml' + Get-Content -Path '.\Microsoft recommended block rules TEMP.xml' | Where-Object -FilterScript { $_.trim() -ne '' } | Out-File -FilePath '.\Microsoft recommended block rules.xml' -Force + + Set-RuleOption -FilePath '.\Microsoft recommended block rules.xml' -Option 3 -Delete + @(0, 2, 6, 11, 12, 16, 19, 20) | ForEach-Object -Process { Set-RuleOption -FilePath '.\Microsoft recommended block rules.xml' -Option $_ } + Set-HVCIOptions -Strict -FilePath '.\Microsoft recommended block rules.xml' + Remove-Item -Path '.\Microsoft recommended block rules TEMP.xml' -Force + [System.String]$PolicyID = (Set-CIPolicyIdInfo -FilePath '.\Microsoft recommended block rules.xml' -ResetPolicyID).Substring(11) + Set-CIPolicyIdInfo -PolicyName "Microsoft Windows User Mode Policy - Enforced - $(Get-Date -Format 'MM-dd-yyyy')" -FilePath '.\Microsoft recommended block rules.xml' + ConvertFrom-CIPolicy -XmlFilePath '.\Microsoft recommended block rules.xml' -BinaryFilePath "$PolicyID.cip" | Out-Null + &'C:\Windows\System32\CiTool.exe' --update-policy "$PolicyID.cip" -json | Out-Null + Write-ColorfulText -Color Lavender -InputText 'The Microsoft recommended block rules policy has been deployed in enforced mode.' + Remove-Item -Path "$PolicyID.cip" -Force + } + + Function Set-AutoUpdateDriverBlockRules { + <# + .SYNOPSIS + A helper function that creates a scheduled task to keep the Microsoft Recommended Driver Block rules + In Windows up to date quickly ahead of its official release schedule. It does this by downloading and applying + The latest block list every 7 days on the system. + .INPUTS + None. You cannot pipe objects to this function. + .OUTPUTS + System.Void + #> + [CmdletBinding()] + param() + + # Get the state of fast weekly Microsoft recommended driver block list update scheduled task + Write-Verbose -Message 'Getting the state of MSFT Driver Block list update Scheduled task' + [System.String]$BlockListScheduledTaskState = (Get-ScheduledTask -TaskName 'MSFT Driver Block list update' -TaskPath '\MSFT Driver Block list update\' -ErrorAction SilentlyContinue).State + + # Create scheduled task for fast weekly Microsoft recommended driver block list update if it doesn't exist or exists but is not Ready/Running + if (-NOT (($BlockListScheduledTaskState -eq 'Ready' -or $BlockListScheduledTaskState -eq 'Running'))) { + + Write-Verbose -Message "Creating the MSFT Driver Block list update task because its state is neither Running nor Ready, it's $BlockListScheduledTaskState" + # Get the SID of the SYSTEM account. It is a well-known SID, but still querying it, going to use it to create the scheduled task + [System.Security.Principal.SecurityIdentifier]$SYSTEMSID = New-Object -TypeName System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::LocalSystemSid, $null) + + # Create a scheduled task action, this defines how to download and install the latest Microsoft Recommended Driver Block Rules + [Microsoft.Management.Infrastructure.CimInstance]$Action = New-ScheduledTaskAction -Execute 'Powershell.exe' ` + -Argument '-NoProfile -WindowStyle Hidden -command "& {try {Invoke-WebRequest -Uri "https://aka.ms/VulnerableDriverBlockList" -OutFile VulnerableDriverBlockList.zip -ErrorAction Stop}catch{exit};Expand-Archive .\VulnerableDriverBlockList.zip -DestinationPath "VulnerableDriverBlockList" -Force;Rename-Item .\VulnerableDriverBlockList\SiPolicy_Enforced.p7b -NewName "SiPolicy.p7b" -Force;Copy-Item .\VulnerableDriverBlockList\SiPolicy.p7b -Destination "C:\Windows\System32\CodeIntegrity";citool --refresh -json;Remove-Item .\VulnerableDriverBlockList -Recurse -Force;Remove-Item .\VulnerableDriverBlockList.zip -Force;}"' + + # Create a scheduled task principal and assign the SYSTEM account's SID to it so that the task will run under its context + [Microsoft.Management.Infrastructure.CimInstance]$TaskPrincipal = New-ScheduledTaskPrincipal -LogonType S4U -UserId $($SYSTEMSID.Value) -RunLevel Highest + + # Create a trigger for the scheduled task. The task will first run one hour after its creation and from then on will run every 7 days, indefinitely + [Microsoft.Management.Infrastructure.CimInstance]$Time = New-ScheduledTaskTrigger -Once -At (Get-Date).AddHours(1) -RepetitionInterval (New-TimeSpan -Days 7) + + # Register the scheduled task. If the task's state is disabled, it will be overwritten with a new task that is enabled + Register-ScheduledTask -Action $Action -Trigger $Time -Principal $TaskPrincipal -TaskPath 'MSFT Driver Block list update' -TaskName 'MSFT Driver Block list update' -Description 'Microsoft Recommended Driver Block List update' -Force + + # Define advanced settings for the scheduled task + [Microsoft.Management.Infrastructure.CimInstance]$TaskSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -Compatibility 'Win8' -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 3) -RestartCount 4 -RestartInterval (New-TimeSpan -Hours 6) -RunOnlyIfNetworkAvailable + + # Add the advanced settings we defined above to the scheduled task + Set-ScheduledTask -TaskName 'MSFT Driver Block list update' -TaskPath 'MSFT Driver Block list update' -Settings $TaskSettings + } + + Write-Verbose -Message 'Displaying extra info about the Microsoft recommended Drivers block list' + Invoke-Command -ScriptBlock $DriversBlockListInfoGatheringSCRIPTBLOCK + } + + Function Build-MSFTOnlyAudit { + <# + .SYNOPSIS + A helper function that creates a WDAC policy based on AllowMicrosoft template policy. + It has audit policy rule option. + It can also call the Set-LogSize function to modify the size of Code Integrity Operational event log + It uses the $LogSize variable available in the New-WDACConfig's scope to do that. + .INPUTS + None. You cannot pipe objects to this function. + .OUTPUTS + System.Void + #> + [CmdletBinding()] + param() + + if ($PrepMSFTOnlyAudit -and $LogSize) { + Write-Verbose -Message 'Changing the Log size of Code Integrity Operational event log' + Set-LogSize -LogSize $LogSize + } + + Write-Verbose -Message 'Copying AllowMicrosoft.xml from Windows directory to the current working directory' + Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowMicrosoft.xml' -Destination .\AllowMicrosoft.xml -Force + + Write-Verbose -Message 'Enabling Audit mode' + Set-RuleOption -FilePath .\AllowMicrosoft.xml -Option 3 + + Write-Verbose -Message 'Resetting the Policy ID' + [System.String]$PolicyID = Set-CIPolicyIdInfo -FilePath .\AllowMicrosoft.xml -ResetPolicyID + [System.String]$PolicyID = $PolicyID.Substring(11) + + Write-Verbose -Message 'Assigning "PrepMSFTOnlyAudit" as the policy name' + Set-CIPolicyIdInfo -PolicyName 'PrepMSFTOnlyAudit' -FilePath .\AllowMicrosoft.xml + + Write-Verbose -Message 'Converting AllowMicrosoft.xml to .CIP Binary' + ConvertFrom-CIPolicy -XmlFilePath .\AllowMicrosoft.xml -BinaryFilePath "$PolicyID.cip" | Out-Null + + if ($Deploy) { + Write-Verbose -Message 'Deploying the AllowMicrosoft.xml policy on the system' + &'C:\Windows\System32\CiTool.exe' --update-policy "$PolicyID.cip" -json | Out-Null + Write-ColorfulText -Color HotPink -InputText 'The default AllowMicrosoft policy has been deployed in Audit mode. No reboot required.' + Remove-Item -Path 'AllowMicrosoft.xml', "$PolicyID.cip" -Force + } + else { + Write-ColorfulText -Color HotPink -InputText 'The default AllowMicrosoft policy has been created in Audit mode and is ready for deployment.' + } + } + + Function Build-DefaultWindowsAudit { + <# + .SYNOPSIS + A helper function that creates a WDAC policy based on DefaultWindows template policy. + It has audit policy rule option. + It can also call the Set-LogSize function to modify the size of Code Integrity Operational event log + It uses the $LogSize variable available in the New-WDACConfig's scope to do that. + .INPUTS + None. You cannot pipe objects to this function. + .OUTPUTS + System.Void + #> + [CmdletBinding()] + param() + + if ($PrepDefaultWindowsAudit -and $LogSize) { + Write-Verbose -Message 'Changing the Log size of Code Integrity Operational event log' + Set-LogSize -LogSize $LogSize + } + + Write-Verbose -Message 'Copying DefaultWindows_Audit.xml from Windows directory to the current working directory' + Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\DefaultWindows_Audit.xml' -Destination .\DefaultWindows_Audit.xml -Force + + # Making Sure neither PowerShell core nor WDACConfig module files are added to the Supplemental policy created by -MakePolicyFromAuditLogs parameter + # by adding them first to the deployed Default Windows policy in Audit mode. Because WDACConfig module files don't need to be allowed to run since they are *.ps1 and .*psm1 files + # And PowerShell core files will be added to the DefaultWindows Base policy anyway + if (Test-Path -Path 'C:\Program Files\PowerShell') { + Write-Verbose -Message 'Scanning PowerShell core directory and creating a policy file' + New-CIPolicy -ScanPath 'C:\Program Files\PowerShell' -Level FilePublisher -NoScript -Fallback Hash -UserPEs -UserWriteablePaths -MultiplePolicyFormat -FilePath .\AllowPowerShell.xml + + Write-Verbose -Message 'Scanning WDACConfig module directory and creating a policy file' + New-CIPolicy -ScanPath "$ModuleRootPath" -Level hash -UserPEs -UserWriteablePaths -MultiplePolicyFormat -FilePath .\WDACConfigModule.xml + + Write-Verbose -Message 'Merging the policy files for PowerShell core and WDACConfig module with the DefaultWindows_Audit.xml policy file' + Merge-CIPolicy -PolicyPaths .\DefaultWindows_Audit.xml, .\AllowPowerShell.xml, .\WDACConfigModule.xml -OutputFilePath .\DefaultWindows_Audit_temp.xml | Out-Null + + Write-Verbose -Message 'removing DefaultWindows_Audit.xml policy' + Remove-Item -Path DefaultWindows_Audit.xml -Force + + Write-Verbose -Message 'Renaming DefaultWindows_Audit_temp.xml to DefaultWindows_Audit.xml' + Rename-Item -Path .\DefaultWindows_Audit_temp.xml -NewName 'DefaultWindows_Audit.xml' -Force + + Write-Verbose -Message 'Removing AllowPowerShell.xml and WDACConfigModule.xml policies' + Remove-Item -Path 'WDACConfigModule.xml', 'AllowPowerShell.xml' -Force + } + + Write-Verbose -Message 'Enabling Audit mode' + Set-RuleOption -FilePath .\DefaultWindows_Audit.xml -Option 3 + + Write-Verbose -Message 'Resetting the Policy ID' + [System.String]$PolicyID = Set-CIPolicyIdInfo -FilePath .\DefaultWindows_Audit.xml -ResetPolicyID + [System.String]$PolicyID = $PolicyID.Substring(11) + + Write-Verbose -Message 'Assigning "PrepDefaultWindowsAudit" as the policy name' + Set-CIPolicyIdInfo -PolicyName 'PrepDefaultWindows' -FilePath .\DefaultWindows_Audit.xml + + Write-Verbose -Message 'Converting DefaultWindows_Audit.xml to .CIP Binary' + ConvertFrom-CIPolicy -XmlFilePath .\DefaultWindows_Audit.xml -BinaryFilePath "$PolicyID.cip" | Out-Null + + if ($Deploy) { + Write-Verbose -Message 'Deploying the DefaultWindows_Audit.xml policy on the system' + &'C:\Windows\System32\CiTool.exe' --update-policy "$PolicyID.cip" -json | Out-Null + + Write-ColorfulText -Color Lavender -InputText 'The defaultWindows policy has been deployed in Audit mode. No reboot required.' + + Write-Verbose -Message 'Removing the generated .CIP files' + Remove-Item -Path 'DefaultWindows_Audit.xml', "$PolicyID.cip" -Force + } + else { + Write-ColorfulText -Color Lavender -InputText 'The defaultWindows policy has been created in Audit mode and is ready for deployment.' + } + } + + Function Build-PolicyFromAuditLogs { + <# + .SYNOPSIS + A helper function that creates 2 WDAC policies. A bas policy from one of the standard templates + and a Supplemental policy based on the Code Integrity Operational audit logs + .INPUTS + None. You cannot pipe objects to this function. + .OUTPUTS + System.String + #> + [CmdletBinding()] + param() + + if ($MakePolicyFromAuditLogs -and $LogSize) { + Write-Verbose -Message 'Changing the Log size of Code Integrity Operational event log' + Set-LogSize -LogSize $LogSize + } + + # Make sure there is no leftover files from previous operations of this same command + Write-Verbose -Message 'Make sure there is no leftover files from previous operations of this same command' + Remove-Item -Path "$home\WDAC\*" -Recurse -Force -ErrorAction SilentlyContinue + + # Create a working directory in user's folder + Write-Verbose -Message 'Create a working directory in user folder' + New-Item -Type Directory -Path "$home\WDAC" -Force | Out-Null + Set-Location "$home\WDAC" + + #Region Base-Policy-Processing + switch ($BasePolicyType) { + 'Allow Microsoft Base' { + Write-Verbose -Message 'Creating Allow Microsoft Base policy' + Build-AllowMSFTWithBlockRules | Out-Null + $Xml = [System.Xml.XmlDocument](Get-Content -Path .\AllowMicrosoftPlusBlockRules.xml) + $BasePolicyID = $Xml.SiPolicy.PolicyID + # define the location of the base policy + $BasePolicy = 'AllowMicrosoftPlusBlockRules.xml' + } + 'Default Windows Base' { + Write-Verbose -Message 'Creating Default Windows Base policy' + Build-DefaultWindowsWithBlockRules | Out-Null + $Xml = [System.Xml.XmlDocument](Get-Content -Path .\DefaultWindowsPlusBlockRules.xml) + $BasePolicyID = $Xml.SiPolicy.PolicyID + # define the location of the base policy + $BasePolicy = 'DefaultWindowsPlusBlockRules.xml' + } + } + + if ($TestMode -and $MakePolicyFromAuditLogs) { + Write-Verbose -Message 'Setting "Boot Audit on Failure" and "Advanced Boot Options Menu" policy rule options because TestMode parameter was used' + 9..10 | ForEach-Object -Process { Set-RuleOption -FilePath $BasePolicy -Option $_ } + } + + if ($RequireEVSigners -and $MakePolicyFromAuditLogs) { + Write-Verbose -Message 'Setting "Required:EV Signers" policy rule option because RequireEVSigners parameter was used' + Set-RuleOption -FilePath $BasePolicy -Option 8 + } + #Endregion Base-Policy-Processing + + #Region Supplemental-Policy-Processing + # Produce a policy xml file from event viewer logs + Write-ColorfulText -Color Lavender -InputText 'Scanning Windows Event logs and creating a policy file, please wait...' + + # Creating a hash table to dynamically add parameters based on user input and pass them to New-Cipolicy cmdlet + [System.Collections.Hashtable]$PolicyMakerHashTable = @{ + FilePath = 'AuditLogsPolicy_NoDeletedFiles.xml' + Audit = $true + Level = $Level + Fallback = $Fallbacks + MultiplePolicyFormat = $true + UserWriteablePaths = $true + WarningAction = 'SilentlyContinue' + AllowFileNameFallbacks = $true + } + # Assess user input parameters and add the required parameters to the hash table + if ($SpecificFileNameLevel) { $PolicyMakerHashTable['SpecificFileNameLevel'] = $SpecificFileNameLevel } + if ($NoScript) { $PolicyMakerHashTable['NoScript'] = $true } + if (!$NoUserPEs) { $PolicyMakerHashTable['UserPEs'] = $true } + + Write-ColorfulText -Color HotPink -InputText 'Generating Supplemental policy with the following specifications:' + $PolicyMakerHashTable + Write-Host -Object '' + + # Create the supplemental policy via parameter splatting for files in event viewer that are currently on the disk + New-CIPolicy @PolicyMakerHashTable + + if (!$NoDeletedFiles) { + # Get Event viewer logs for code integrity - check the file path of all of the files in the log, resolve them using the command above - show files that are no longer available on the disk + [System.Management.Automation.ScriptBlock]$AuditEventLogsDeletedFilesScriptBlock = { + foreach ($event in Get-WinEvent -FilterHashtable @{LogName = 'Microsoft-Windows-CodeIntegrity/Operational'; ID = 3076 }) { + $Xml = [System.Xml.XmlDocument]$event.toxml() + $Xml.event.eventdata.data | + ForEach-Object -Begin { $Hash = @{} } -Process { $hash[$_.name] = $_.'#text' } -End { [pscustomobject]$hash } | + ForEach-Object -Process { + if ($_.'File Name' -match ($pattern = '\\Device\\HarddiskVolume(\d+)\\(.*)$')) { + $hardDiskVolumeNumber = $Matches[1] + $remainingPath = $Matches[2] + $getletter = Get-GlobalRootDrives | Where-Object -FilterScript { $_.devicepath -eq "\Device\HarddiskVolume$hardDiskVolumeNumber" } + $usablePath = "$($getletter.DriveLetter)$remainingPath" + $_.'File Name' = $_.'File Name' -replace $pattern, $usablePath + } + if (-NOT (Test-Path -Path $_.'File Name')) { + $_ | Select-Object -Property FileVersion, 'File Name', PolicyGUID, 'SHA256 Hash', 'SHA256 Flat Hash', 'SHA1 Hash', 'SHA1 Flat Hash' + } + } + } + } + # storing the output from the scriptblock above in a variable + $DeletedFileHashesArray = Invoke-Command -ScriptBlock $AuditEventLogsDeletedFilesScriptBlock + } + # run the following only if there are any event logs for files no longer on the disk and if -NoDeletedFiles switch parameter wasn't used + if ($DeletedFileHashesArray -and !$NoDeletedFiles) { + + # Save the the File Rules and File Rule Refs to the Out-File FileRulesAndFileRefs.txt in the current working directory + (Get-FileRules -HashesArray $DeletedFileHashesArray) + (Get-RuleRefs -HashesArray $DeletedFileHashesArray) | Out-File -FilePath FileRulesAndFileRefs.txt -Force + + # Put the Rules and RulesRefs in an empty policy file + New-EmptyPolicy -RulesContent (Get-FileRules -HashesArray $DeletedFileHashesArray) -RuleRefsContent (Get-RuleRefs -HashesArray $DeletedFileHashesArray) | Out-File -FilePath .\DeletedFilesHashes.xml -Force + + # Merge the policy file we created at first using Event Viewer logs, with the policy file we created for Hash of the files no longer available on the disk + Merge-CIPolicy -PolicyPaths 'AuditLogsPolicy_NoDeletedFiles.xml', .\DeletedFilesHashes.xml -OutputFilePath .\SupplementalPolicy.xml | Out-Null + } + # do this only if there are no event logs detected with files no longer on the disk, so we use the policy file created earlier using Audit even logs + else { + Rename-Item -Path 'AuditLogsPolicy_NoDeletedFiles.xml' -NewName 'SupplementalPolicy.xml' -Force + } + + Write-Verbose -Message 'Setting the version for SupplementalPolicy.xml policy to 1.0.0.0' + Set-CIPolicyVersion -FilePath 'SupplementalPolicy.xml' -Version '1.0.0.0' + + # Convert the SupplementalPolicy.xml policy file from base policy to supplemental policy of our base policy + Write-Verbose -Message 'Convert the SupplementalPolicy.xml policy file from base policy to supplemental policy of our base policy' + [System.String]$PolicyID = Set-CIPolicyIdInfo -FilePath 'SupplementalPolicy.xml' -PolicyName "Supplemental Policy made from Audit Event Logs on $(Get-Date -Format 'MM-dd-yyyy')" -ResetPolicyID -BasePolicyToSupplementPath $BasePolicy + [System.String]$PolicyID = $PolicyID.Substring(11) + + # Make sure policy rule options that don't belong to a Supplemental policy don't exist + Write-Verbose -Message 'Setting the policy rule options for the Supplemental policy by making sure policy rule options that do not belong to a Supplemental policy do not exist' + @(0, 1, 2, 3, 4, 8, 9, 10, 11, 12, 15, 16, 17, 19, 20) | ForEach-Object -Process { Set-RuleOption -FilePath 'SupplementalPolicy.xml' -Option $_ -Delete } + + # Set the hypervisor Code Integrity option for Supplemental policy to Strict + Write-Verbose -Message 'Setting HVCI to strict for SupplementalPolicy.xml' + Set-HVCIOptions -Strict -FilePath 'SupplementalPolicy.xml' + + # convert the Supplemental Policy file to .cip binary file + Write-Verbose -Message 'Converting SupplementalPolicy.xml policy to .CIP binary' + ConvertFrom-CIPolicy -XmlFilePath 'SupplementalPolicy.xml' -BinaryFilePath "$PolicyID.cip" | Out-Null + + #Endregion Supplemental-Policy-Processing + + Write-ColorfulText -Color MintGreen -InputText "BasePolicyFile = $BasePolicy" + Write-ColorfulText -Color MintGreen -InputText "BasePolicyGUID = $BasePolicyID" + + Write-ColorfulText -Color MintGreen -InputText 'SupplementalPolicyFile = SupplementalPolicy.xml' + Write-ColorfulText -Color MintGreen -InputText "SupplementalPolicyGUID = $PolicyID" + + if (-NOT $Debug) { + Remove-Item -Path 'AuditLogsPolicy_NoDeletedFiles.xml', 'FileRulesAndFileRefs.txt', 'DeletedFilesHashes.xml' -Force -ErrorAction SilentlyContinue + } + + if ($Deploy -and $MakePolicyFromAuditLogs) { + + Write-Verbose -Message 'Deploying the Base policy and Supplemental policy' + &'C:\Windows\System32\CiTool.exe' --update-policy "$BasePolicyID.cip" -json | Out-Null + &'C:\Windows\System32\CiTool.exe' --update-policy "$PolicyID.cip" -json | Out-Null + + Write-ColorfulText -Color Pink -InputText "`nBase policy and Supplemental Policies deployed and activated.`n" + + # Get the correct Prep mode Audit policy ID to remove from the system + Write-Verbose -Message 'Getting the correct Prep mode Audit policy ID to remove from the system' + switch ($BasePolicyType) { + 'Allow Microsoft Base' { + Write-Verbose -Message 'Going to remove the AllowMicrosoft policy from the system because Allow Microsoft Base was used' + $IDToRemove = ((&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { $_.FriendlyName -eq 'PrepMSFTOnlyAudit' }).PolicyID + } + 'Default Windows Base' { + Write-Verbose -Message 'Going to remove the DefaultWindows policy from the system because Default Windows Base was used' + $IDToRemove = ((&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { $_.FriendlyName -eq 'PrepDefaultWindows' }).PolicyID + } + } + + &'C:\Windows\System32\CiTool.exe' --remove-policy "{$IDToRemove}" -json | Out-Null + Write-ColorfulText -Color Lavender -InputText "`nSystem restart required to finish removing the Audit mode Prep policy" + } + } + + Function Build-LightPolicy { + <# + .SYNOPSIS + A helper function that created SignedAndReputable WDAC policy + which is based on AllowMicrosoft template policy. + It includes Microsoft Recommended Block rules. + It uses ISG to authorize files with good reputation. + .INPUTS + None. You cannot pipe objects to this function. + .OUTPUTS + System.String + #> + [CmdletBinding()] + param() + + # Delete any policy with the same name in the current working directory + Remove-Item -Path 'SignedAndReputable.xml' -Force -ErrorAction SilentlyContinue + + Write-Verbose -Message 'Calling Build-AllowMSFTWithBlockRules function to create AllowMicrosoftPlusBlockRules.xml policy' + Build-AllowMSFTWithBlockRules -NoCIP | Out-Null + + Write-Verbose -Message 'Renaming AllowMicrosoftPlusBlockRules.xml to SignedAndReputable.xml' + Rename-Item -Path 'AllowMicrosoftPlusBlockRules.xml' -NewName 'SignedAndReputable.xml' -Force + + Write-Verbose -Message 'Setting the policy rule options for the SignedAndReputable.xml policy' + @(14, 15) | ForEach-Object -Process { Set-RuleOption -FilePath .\SignedAndReputable.xml -Option $_ } + + if ($TestMode -and $MakeLightPolicy) { + Write-Verbose -Message 'Setting "Boot Audit on Failure" and "Advanced Boot Options Menu" policy rule options because TestMode parameter was used' + 9..10 | ForEach-Object -Process { Set-RuleOption -FilePath .\SignedAndReputable.xml -Option $_ } + } + if ($RequireEVSigners -and $MakeLightPolicy) { + Write-Verbose -Message 'Setting "Required:EV Signers" policy rule option because RequireEVSigners parameter was used' + Set-RuleOption -FilePath .\SignedAndReputable.xml -Option 8 + } + + Write-Verbose -Message 'Resetting the policy ID and setting a name for SignedAndReputable.xml' + $BasePolicyID = Set-CIPolicyIdInfo -FilePath .\SignedAndReputable.xml -ResetPolicyID -PolicyName "Signed And Reputable policy - $(Get-Date -Format 'MM-dd-yyyy')" + $BasePolicyID = $BasePolicyID.Substring(11) + + Write-Verbose -Message 'Setting the version of SignedAndReputable.xml policy to 1.0.0.0' + Set-CIPolicyVersion -FilePath .\SignedAndReputable.xml -Version '1.0.0.0' + + Write-Verbose -Message 'Setting HVCI to Strict' + Set-HVCIOptions -Strict -FilePath .\SignedAndReputable.xml + + Write-Verbose -Message 'Converting SignedAndReputable.xml policy to .CIP binary' + ConvertFrom-CIPolicy -XmlFilePath .\SignedAndReputable.xml -BinaryFilePath "$BasePolicyID.cip" | Out-Null + + # Configure required services for ISG authorization + Write-Verbose -Message 'Configuring required services for ISG authorization' + Start-Process -FilePath 'C:\Windows\System32\appidtel.exe' -ArgumentList 'start' -Wait -NoNewWindow + Start-Process -FilePath 'C:\Windows\System32\sc.exe' -ArgumentList 'config', 'appidsvc', 'start= auto' -Wait -NoNewWindow + + if ($Deploy -and $MakeLightPolicy) { + Write-Verbose -Message 'Deploying the SignedAndReputable.xml policy' + &'C:\Windows\System32\CiTool.exe' --update-policy "$BasePolicyID.cip" -json | Out-Null + } + + Write-Verbose -Message 'Displaying the output' + Write-ColorfulText -Color MintGreen -InputText 'BasePolicyFile = SignedAndReputable.xml' + Write-ColorfulText -Color MintGreen -InputText "BasePolicyGUID = $BasePolicyID" + } + + # Script block that is used to supply extra information regarding Microsoft recommended driver block rules in commands that use them + [System.Management.Automation.ScriptBlock]$DriversBlockListInfoGatheringSCRIPTBLOCK = { + [System.String]$owner = 'MicrosoftDocs' + [System.String]$repo = 'windows-itpro-docs' + [System.String]$path = 'windows/security/application-security/application-control/windows-defender-application-control/design/microsoft-recommended-driver-block-rules.md' + + [System.String]$ApiUrl = "https://api.github.com/repos/$owner/$repo/commits?path=$path" + [System.Object[]]$Response = Invoke-RestMethod -Uri $ApiUrl -ProgressAction SilentlyContinue + [System.DateTime]$Date = $Response[0].commit.author.date + + Write-ColorfulText -Color Lavender -InputText "The document containing the drivers block list on GitHub was last updated on $Date" + [System.String]$MicrosoftRecommendeDriverBlockRules = (Invoke-WebRequest -Uri $MSFTRecommendedDriverBlockRulesURL -ProgressAction SilentlyContinue).Content + $MicrosoftRecommendeDriverBlockRules -match '(.*)' | Out-Null + Write-ColorfulText -Color Pink -InputText "The current version of Microsoft recommended drivers block list is $($Matches[1])" + } + + # if -SkipVersionCheck wasn't passed, run the updater + # Redirecting the Update-Self function's information Stream to $null because Write-Host + # Used by Write-ColorfulText outputs to both information stream and host console + if (-NOT $SkipVersionCheck) { Update-self 6> $null } + } + + process { + + switch ($true) { + # Deploy the latest block rules + { $GetBlockRules -and $Deploy } { Deploy-LatestBlockRules ; break } + # Get the latest block rules + $GetBlockRules { Get-BlockRulesMeta ; break } + # Get the latest driver block rules and Deploy them if New-WDACConfig -GetDriverBlockRules was called with -Deploy parameter + { $GetDriverBlockRules } { Get-DriverBlockRules -Deploy:$Deploy ; break } + + $SetAutoUpdateDriverBlockRules { Set-AutoUpdateDriverBlockRules ; break } + $MakeAllowMSFTWithBlockRules { Build-AllowMSFTWithBlockRules ; break } + $MakePolicyFromAuditLogs { Build-PolicyFromAuditLogs ; break } + $PrepMSFTOnlyAudit { Build-MSFTOnlyAudit ; break } + $MakeLightPolicy { Build-LightPolicy ; break } + $MakeDefaultWindowsWithBlockRules { Build-DefaultWindowsWithBlockRules ; break } + $PrepDefaultWindowsAudit { Build-DefaultWindowsAudit ; break } + default { Write-Warning -Message 'None of the main parameters were selected.'; break } + } + } + + <# +.SYNOPSIS + Automate a lot of tasks related to WDAC (Windows Defender Application Control) +.LINK + https://github.com/HotCakeX/Harden-Windows-Security/wiki/New-WDACConfig +.DESCRIPTION + Using official Microsoft methods, configure and use Windows Defender Application Control +.COMPONENT + Windows Defender Application Control, ConfigCI PowerShell module +.FUNCTIONALITY + Automate various tasks related to Windows Defender Application Control (WDAC) +.PARAMETER GetBlockRules + Create Microsoft recommended block rules xml policy and remove the allow rules +.PARAMETER GetDriverBlockRules + Create Microsoft recommended driver block rules xml policy and remove the allow rules +.PARAMETER MakeAllowMSFTWithBlockRules + Make WDAC policy by merging AllowMicrosoft policy with the recommended block rules +.PARAMETER SetAutoUpdateDriverBlockRules + Make a Scheduled Task that automatically runs every 7 days to download the newest Microsoft Recommended driver block rules +.PARAMETER PrepMSFTOnlyAudit + Prepare the system for Audit mode using AllowMicrosoft default policy +.PARAMETER PrepDefaultWindowsAudit + Prepare the system for Audit mode using DefaultWindows policy +.PARAMETER MakePolicyFromAuditLogs + Make a WDAC Policy from Audit event logs that also covers files no longer on disk +.PARAMETER MakeLightPolicy + Make a WDAC Policy with ISG for Lightly Managed system +.PARAMETER MakeDefaultWindowsWithBlockRules + Make a WDAC policy by merging DefaultWindows policy with the recommended block rules +.PARAMETER BasePolicyType + Select the Base Policy Type +.PARAMETER Deploy + Deploys the policy that is being created +.PARAMETER IncludeSignTool + Indicates that the Default Windows policy that is being created must include Allow rules for SignTool.exe - This parameter must be used when you intend to Sign and Deploy the Default Windows policy. +.PARAMETER SignToolPath + Path to the SignTool.exe file - Optional +.PARAMETER TestMode + Indicates that the created/deployed policy will have Enabled:Boot Audit on Failure and Enabled:Advanced Boot Options Menu policy rule options +.PARAMETER RequireEVSigners + Indicates that the created/deployed policy will have Require EV Signers policy rule option. +.PARAMETER NoDeletedFiles + Indicates that files that were run during program installations but then were deleted and are no longer on the disk, won't be added to the supplemental policy. This can mean the programs you installed will be allowed to run but installation/reinstallation might not be allowed once the policies are deployed. +.PARAMETER SpecificFileNameLevel + You can choose one of the following options: "OriginalFileName", "InternalName", "FileDescription", "ProductName", "PackageFamilyName", "FilePath". More info available on Microsoft Learn +.PARAMETER NoUserPEs + By default, the module includes user PEs in the scan. When you use this switch parameter, they won't be included. +.PARAMETER NoScript + Won't scan script files +.PARAMETER Level + Offers the same official Levels for scanning of event logs. If no level is specified the default, which is set to FilePublisher in this module, will be used. +.PARAMETER Fallbacks + Offers the same official Fallbacks for scanning of event logs. If no fallbacks are specified the default, which is set to Hash in this module, will be used. +.PARAMETER LogSize + Specifies the log size for Microsoft-Windows-CodeIntegrity/Operational events. The values must be in the form of . e.g., 2MB, 10MB, 1GB, 1TB. The minimum accepted value is 1MB which is the default. + The maximum range is the maximum allowed log size by Windows Event viewer +.PARAMETER SkipVersionCheck + Can be used with any parameter to bypass the online version check - only to be used in rare cases +.INPUTS + System.Int64 + System.String[] + System.String + System.Management.Automation.SwitchParameter +.OUTPUTS + System.String +#> +} + +# Importing argument completer ScriptBlocks +. "$ModuleRootPath\Resources\ArgumentCompleters.ps1" +Register-ArgumentCompleter -CommandName 'New-WDACConfig' -ParameterName 'SignToolPath' -ScriptBlock $ArgumentCompleterExeFilePathsPicker diff --git a/WDACConfig/WDACConfig Module Files/Core/Remove-CommonWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Remove-CommonWDACConfig.psm1 new file mode 100644 index 000000000..a0bad647a --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/Core/Remove-CommonWDACConfig.psm1 @@ -0,0 +1,172 @@ +Function Remove-CommonWDACConfig { + [CmdletBinding()] + Param( + [parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$CertCN, + [parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$CertPath, + [parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$SignToolPath, + [parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$UnsignedPolicyPath, + [parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$SignedPolicyPath, + [parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$StrictKernelPolicyGUID, + [parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$StrictKernelNoFlightRootsPolicyGUID, + [parameter(Mandatory = $false, DontShow = $true)][System.Management.Automation.SwitchParameter]$LastUpdateCheck + ) + begin { + # Importing the $PSDefaultParameterValues to the current session, prior to everything else + . "$ModuleRootPath\CoreExt\PSDefaultParameterValues.ps1" + + # Create User configuration folder if it doesn't already exist + if (-NOT (Test-Path -Path "$UserAccountDirectoryPath\.WDACConfig\")) { + New-Item -ItemType Directory -Path "$UserAccountDirectoryPath\.WDACConfig\" -Force -ErrorAction Stop | Out-Null + Write-Verbose -Message 'The .WDACConfig folder in the current user folder has been created because it did not exist.' + } + + # Create User configuration file if it doesn't already exist + if (-NOT (Test-Path -Path "$UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json")) { + New-Item -ItemType File -Path "$UserAccountDirectoryPath\.WDACConfig\" -Name 'UserConfigurations.json' -Force -ErrorAction Stop | Out-Null + Write-Verbose -Message 'The UserConfigurations.json file in \.WDACConfig\ folder has been created because it did not exist.' + } + + # Delete the entire User Configs if a more specific parameter wasn't used + # This method is better than $PSBoundParameters since it also contains common parameters + if (!$CertCN -And !$CertPath -And !$SignToolPath -And !$UnsignedPolicyPath -And !$SignedPolicyPath -And !$StrictKernelPolicyGUID -And !$StrictKernelNoFlightRootsPolicyGUID -And !$LastUpdateCheck) { + Remove-Item -Path "$UserAccountDirectoryPath\.WDACConfig\" -Recurse -Force + Write-Verbose -Message 'User Configurations for WDACConfig module have been deleted.' + + # set a boolean value that returns from the Process and End blocks as well + [System.Boolean]$ReturnAndDone = $true + + Return + } + + # Read the current user configurations + [System.Object[]]$CurrentUserConfigurations = Get-Content -Path "$UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" + + # If the file exists but is corrupted and has bad values, rewrite it + try { + $CurrentUserConfigurations = $CurrentUserConfigurations | ConvertFrom-Json + } + catch { + Set-Content -Path "$UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" -Value '' + } + + # An object to hold the User configurations + $UserConfigurationsObject = [PSCustomObject]@{ + SignedPolicyPath = '' + UnsignedPolicyPath = '' + SignToolCustomPath = '' + CertificateCommonName = '' + CertificatePath = '' + StrictKernelPolicyGUID = '' + StrictKernelNoFlightRootsPolicyGUID = '' + LastUpdateCheck = '' + } + } + process { + + if ($true -eq $ReturnAndDone) { return } + + if ($SignedPolicyPath) { + Write-Verbose -Message 'Removing the SignedPolicyPath' + $UserConfigurationsObject.SignedPolicyPath = '' + } + else { + $UserConfigurationsObject.SignedPolicyPath = $CurrentUserConfigurations.SignedPolicyPath + } + + if ($UnsignedPolicyPath) { + Write-Verbose -Message 'Removing the UnsignedPolicyPath' + $UserConfigurationsObject.UnsignedPolicyPath = '' + } + else { + $UserConfigurationsObject.UnsignedPolicyPath = $CurrentUserConfigurations.UnsignedPolicyPath + } + + if ($SignToolPath) { + Write-Verbose -Message 'Removing the SignToolPath' + $UserConfigurationsObject.SignToolCustomPath = '' + } + else { + $UserConfigurationsObject.SignToolCustomPath = $CurrentUserConfigurations.SignToolCustomPath + } + + if ($CertPath) { + Write-Verbose -Message 'Removing the CertPath' + $UserConfigurationsObject.CertificatePath = '' + } + else { + $UserConfigurationsObject.CertificatePath = $CurrentUserConfigurations.CertificatePath + } + + if ($CertCN) { + Write-Verbose -Message 'Removing the CertCN' + $UserConfigurationsObject.CertificateCommonName = '' + } + else { + $UserConfigurationsObject.CertificateCommonName = $CurrentUserConfigurations.CertificateCommonName + } + + if ($StrictKernelPolicyGUID) { + Write-Verbose -Message 'Removing the StrictKernelPolicyGUID' + $UserConfigurationsObject.StrictKernelPolicyGUID = '' + } + else { + $UserConfigurationsObject.StrictKernelPolicyGUID = $CurrentUserConfigurations.StrictKernelPolicyGUID + } + + if ($StrictKernelNoFlightRootsPolicyGUID) { + Write-Verbose -Message 'Removing the StrictKernelNoFlightRootsPolicyGUID' + $UserConfigurationsObject.StrictKernelNoFlightRootsPolicyGUID = '' + } + else { + $UserConfigurationsObject.StrictKernelNoFlightRootsPolicyGUID = $CurrentUserConfigurations.StrictKernelNoFlightRootsPolicyGUID + } + + if ($LastUpdateCheck) { + Write-Verbose -Message 'Removing the LastUpdateCheck' + $UserConfigurationsObject.LastUpdateCheck = '' + } + else { + $UserConfigurationsObject.LastUpdateCheck = $CurrentUserConfigurations.LastUpdateCheck + } + } + end { + + if ($true -eq $ReturnAndDone) { return } + + # Update the User Configurations file + Write-Verbose -Message 'Saving the changes' + $UserConfigurationsObject | ConvertTo-Json | Set-Content -Path "$UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" + } + <# +.SYNOPSIS + Removes common values for parameters used by WDACConfig module +.LINK + https://github.com/HotCakeX/Harden-Windows-Security/wiki/Remove-CommonWDACConfig +.DESCRIPTION + Removes common values for parameters used by WDACConfig module from the User Configurations JSON file. If you don't use it with any parameters, then all User Configs will be deleted. +.COMPONENT + Windows Defender Application Control, ConfigCI PowerShell module, WDACConfig module +.FUNCTIONALITY + Removes common values for parameters used by WDACConfig module from the User Configurations JSON file. If you don't use it with any parameters, then all User Configs will be deleted. +.PARAMETER SignedPolicyPath + Removes the SignedPolicyPath from User Configs +.PARAMETER UnsignedPolicyPath + Removes the UnsignedPolicyPath from User Configs +.PARAMETER CertCN + Removes the CertCN from User Configs +.PARAMETER SignToolPath + Removes the SignToolPath from User Configs +.PARAMETER CertPath + Removes the CertPath from User Configs +.PARAMETER StrictKernelPolicyGUID + Removes the StrictKernelPolicyGUID from User Configs +.PARAMETER StrictKernelNoFlightRootsPolicyGUID + Removes the StrictKernelNoFlightRootsPolicyGUID from User Configs +.PARAMETER LastUpdateCheck + Using DontShow for this parameter which prevents common parameters from being displayed too +.INPUTS + System.Management.Automation.SwitchParameter +.OUTPUTS + System.String +#> +} diff --git a/WDACConfig/WDACConfig Module Files/Core/Remove-WDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Remove-WDACConfig.psm1 new file mode 100644 index 000000000..dfc5ca9cc --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/Core/Remove-WDACConfig.psm1 @@ -0,0 +1,400 @@ +Function Remove-WDACConfig { + [CmdletBinding( + DefaultParameterSetName = 'Signed Base', + SupportsShouldProcess = $true, + PositionalBinding = $false, + ConfirmImpact = 'High' + )] + Param( + [Alias('S')] + [Parameter(Mandatory = $false, ParameterSetName = 'Signed Base')][System.Management.Automation.SwitchParameter]$SignedBase, + [Alias('U')] + [Parameter(Mandatory = $false, ParameterSetName = 'Unsigned Or Supplemental')][System.Management.Automation.SwitchParameter]$UnsignedOrSupplemental, + + [ValidatePattern('\.xml$')] + [ValidateScript({ + # Validate each Policy file in PolicyPaths parameter to make sure the user isn't accidentally trying to remove an Unsigned policy + $_ | ForEach-Object -Process { + $XmlTest = [System.Xml.XmlDocument](Get-Content -Path $_) + $RedFlag1 = $XmlTest.SiPolicy.SupplementalPolicySigners.SupplementalPolicySigner.SignerId + $RedFlag2 = $XmlTest.SiPolicy.UpdatePolicySigners.UpdatePolicySigner.SignerId + if ($RedFlag1 -or $RedFlag2) { return $True } + } + }, ErrorMessage = 'The policy XML file(s) you chose are Unsigned policies. Please use Remove-WDACConfig cmdlet with -UnsignedOrSupplemental parameter instead.')] + [parameter(Mandatory = $true, ParameterSetName = 'Signed Base', ValueFromPipelineByPropertyName = $true)] + [System.String[]]$PolicyPaths, + + [ValidateScript({ + [System.String[]]$Certificates = foreach ($Cert in (Get-ChildItem -Path 'Cert:\CurrentUser\my')) { + (($Cert.Subject -split ',' | Select-Object -First 1) -replace 'CN=', '').Trim() + } + $Certificates -contains $_ + }, ErrorMessage = "A certificate with the provided common name doesn't exist in the personal store of the user certificates." )] + [parameter(Mandatory = $false, ParameterSetName = 'Signed Base', ValueFromPipelineByPropertyName = $true)] + [System.String]$CertCN, + + [ArgumentCompleter({ + # Define the parameters that this script block will accept. + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) + + # Get a list of policies using the CiTool, excluding system policies and policies that aren't on disk. + # by adding "| Where-Object -FilterScript { $_.FriendlyName }" we make sure the auto completion works when at least one of the policies doesn't have a friendly name + $Policies = (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { $_.IsOnDisk -eq 'True' } | Where-Object -FilterScript { $_.IsSystemPolicy -ne 'True' } | Where-Object -FilterScript { $_.FriendlyName } + + # Create a hashtable mapping policy names to policy IDs. This will be used later to check if a policy ID already exists. + $NameIDMap = @{} + foreach ($Policy in $Policies) { + $NameIDMap[$Policy.Friendlyname] = $Policy.policyID + } + + # Get the IDs of existing policies that are already being used in the current command. + $ExistingIDs = $fakeBoundParameters['PolicyIDs'] + + # Get the policy names that are currently being used in the command. This is done by looking at the abstract syntax tree (AST) + # of the command and finding all string literals, which are assumed to be policy names. + $Existing = $commandAst.FindAll({ + $args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] + }, $false).Value + + # Filter out the policy names that are already being used or whose corresponding policy IDs are already being used. + # The resulting list of policy names is what will be shown as autocomplete suggestions. + $Candidates = $Policies.Friendlyname | Where-Object -FilterScript { $_ -notin $Existing -and $NameIDMap[$_] -notin $ExistingIDs } + + # Additionally, if the policy name contains spaces, it's enclosed in single quotes to ensure it's treated as a single argument. + # This is achieved using the Compare-Object cmdlet to compare the existing and candidate values, and outputting the resulting matches. + # For each resulting match, it checks if the match contains a space, if so, it's enclosed in single quotes, if not, it's returned as is. + (Compare-Object -ReferenceObject $Candidates -DifferenceObject $Existing -PassThru | Where-Object -Property SideIndicator -EQ '<=' ). + ForEach({ if ($_ -match ' ') { "'{0}'" -f $_ } else { $_ } }) + })] + [ValidateScript({ + if ($_ -notin [PolicyNamezx]::new().GetValidValues()) { throw "Invalid policy name: $_" } + $true + })] + [Parameter(Mandatory = $false, ParameterSetName = 'Unsigned Or Supplemental')] + [System.String[]]$PolicyNames, + + [ArgumentCompleter({ + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) + + # Get a list of policies using the CiTool, excluding system policies and policies that aren't on disk. + $Policies = (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { $_.IsOnDisk -eq 'True' } | Where-Object -FilterScript { $_.IsSystemPolicy -ne 'True' } + # Create a hashtable mapping policy IDs to policy names. This will be used later to check if a policy name already exists. + $IDNameMap = @{} + foreach ($Policy in $Policies) { + $IDNameMap[$Policy.policyID] = $Policy.Friendlyname + } + # Get the names of existing policies that are already being used in the current command. + $ExistingNames = $fakeBoundParameters['PolicyNames'] + # Get the policy IDs that are currently being used in the command. This is done by looking at the abstract syntax tree (AST) + # of the command and finding all string literals, which are assumed to be policy IDs. + $Existing = $commandAst.FindAll({ + $args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] + }, $false).Value + # Filter out the policy IDs that are already being used or whose corresponding policy names are already being used. + # The resulting list of policy IDs is what will be shown as autocomplete suggestions. + $Candidates = $Policies.policyID | Where-Object -FilterScript { $_ -notin $Existing -and $IDNameMap[$_] -notin $ExistingNames } + # Return the candidates. + return $Candidates + })] + [ValidateScript({ + if ($_ -notin [PolicyIDzx]::new().GetValidValues()) { throw "Invalid policy ID: $_" } + $true + })] + [Parameter(Mandatory = $false, ParameterSetName = 'Unsigned Or Supplemental')] + [System.String[]]$PolicyIDs, + + [parameter(Mandatory = $false, ParameterSetName = 'Signed Base', ValueFromPipelineByPropertyName = $true)] + [System.String]$SignToolPath, + + [Parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$SkipVersionCheck + ) + + begin { + # Detecting if Verbose switch is used + $PSBoundParameters.Verbose.IsPresent ? ([System.Boolean]$Verbose = $true) : ([System.Boolean]$Verbose = $false) | Out-Null + + # Importing the $PSDefaultParameterValues to the current session, prior to everything else + . "$ModuleRootPath\CoreExt\PSDefaultParameterValues.ps1" + + # Importing the required sub-modules + Write-Verbose -Message 'Importing the required sub-modules' + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Update-self.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Get-SignTool.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Confirm-CertCN.psm1" -Force + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Write-ColorfulText.psm1" -Force + + # if -SkipVersionCheck wasn't passed, run the updater + # Redirecting the Update-Self function's information Stream to $null because Write-Host + # Used by Write-ColorfulText outputs to both information stream and host console + if (-NOT $SkipVersionCheck) { Update-self 6> $null } + + #Region User-Configurations-Processing-Validation + + Write-Verbose -Message 'Validating and processing user configurations' + + if ($PSCmdlet.ParameterSetName -eq 'Signed Base') { + # If any of these parameters, that are mandatory for all of the position 0 parameters, isn't supplied by user + if (!$SignToolPath -or !$CertCN) { + # Read User configuration file if it exists + $UserConfig = Get-Content -Path "$UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" -ErrorAction SilentlyContinue + if ($UserConfig) { + # Validate the Json file and read its content to make sure it's not corrupted + try { $UserConfig = $UserConfig | ConvertFrom-Json } + catch { + Write-Error -Message 'User Configuration Json file is corrupted, deleting it...' -ErrorAction Continue + Remove-CommonWDACConfig + } + } + } + + # Get SignToolPath from user parameter or user config file or auto-detect it + if ($SignToolPath) { + $SignToolPathFinal = Get-SignTool -SignToolExePath $SignToolPath + } # If it is null, then Get-SignTool will behave the same as if it was called without any arguments. + else { + $SignToolPathFinal = Get-SignTool -SignToolExePath ($UserConfig.SignToolCustomPath ?? $null) + } + + # If CertCN was not provided by user + if (!$CertCN) { + if ($UserConfig.CertificateCommonName) { + # Check if the value in the User configuration file exists and is valid + if (Confirm-CertCN -CN $($UserConfig.CertificateCommonName)) { + # if it's valid then use it + $CertCN = $UserConfig.CertificateCommonName + } + else { + throw 'The currently saved value for CertCN in user configurations is invalid.' + } + } + else { + throw 'CertCN parameter cannot be empty and no valid configuration was found for it.' + } + } + } + #Endregion User-Configurations-Processing-Validation + + # ValidateSet for Policy names + Class PolicyNamezx : System.Management.Automation.IValidateSetValuesGenerator { + [System.String[]] GetValidValues() { + $PolicyNamezx = ((&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { $_.IsOnDisk -eq 'True' } | Where-Object -FilterScript { $_.IsSystemPolicy -ne 'True' }).Friendlyname | Select-Object -Unique + return [System.String[]]$PolicyNamezx + } + } + + # ValidateSet for Policy IDs + Class PolicyIDzx : System.Management.Automation.IValidateSetValuesGenerator { + [System.String[]] GetValidValues() { + $PolicyIDzx = ((&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { $_.IsOnDisk -eq 'True' } | Where-Object -FilterScript { $_.IsSystemPolicy -ne 'True' }).policyID + + return [System.String[]]$PolicyIDzx + } + } + + + # argument tab auto-completion and ValidateSet for Policy names + # Defines the PolicyNamez class that implements the IValidateSetValuesGenerator interface. This class is responsible for generating a list of valid values for the policy names. + Class PolicyNamez : System.Management.Automation.IValidateSetValuesGenerator { + # Creates a static hashtable to store a mapping of policy IDs to their respective friendly names. + static [System.Collections.Hashtable] $IDNameMap = @{} + + # Defines a method to get valid policy names from the policies on disk that aren't system policies. + [System.String[]] GetValidValues() { + $Policies = (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { $_.IsOnDisk -eq 'True' } | Where-Object -FilterScript { $_.IsSystemPolicy -ne 'True' } + self::$IDNameMap = @{} + foreach ($Policy in $Policies) { + self::$IDNameMap[$Policy.policyID] = $Policy.Friendlyname + } + # Returns an array of unique policy names. + return [System.String[]]($Policies.Friendlyname | Select-Object -Unique) + } + + # Defines a static method to get a policy name by its ID. This method will be used to check if a policy ID is already in use. + static [System.String] GetPolicyNameByID($ID) { + return self::$IDNameMap[$ID] + } + } + + # Defines the PolicyIDz class that also implements the IValidateSetValuesGenerator interface. This class is responsible for generating a list of valid values for the policy IDs. + Class PolicyIDz : System.Management.Automation.IValidateSetValuesGenerator { + # Creates a static hashtable to store a mapping of policy friendly names to their respective IDs. + static [System.Collections.Hashtable] $NameIDMap = @{} + + # Defines a method to get valid policy IDs from the policies on disk that aren't system policies. + [System.String[]] GetValidValues() { + $Policies = (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { $_.IsOnDisk -eq 'True' } | Where-Object -FilterScript { $_.IsSystemPolicy -ne 'True' } + self::$NameIDMap = @{} + foreach ($Policy in $Policies) { + self::$NameIDMap[$Policy.Friendlyname] = $Policy.policyID + } + # Returns an array of unique policy IDs. + return [System.String[]]($Policies.policyID | Select-Object -Unique) + } + + # Defines a static method to get a policy ID by its name. This method will be used to check if a policy name is already in use. + static [System.String] GetPolicyIDByName($Name) { + return self::$NameIDMap[$Name] + } + } + } + + process { + # If a signed policy is being removed + if ($SignedBase) { + + Write-Verbose -Message 'Looping over each selected policy XML file' + foreach ($PolicyPath in $PolicyPaths) { + + # Convert the XML file into an XML object + Write-Verbose -Message 'Converting the XML file to an XML object' + $Xml = [System.Xml.XmlDocument](Get-Content -Path $PolicyPath) + + # Extract the Policy ID from the XML object + Write-Verbose -Message 'Extracting the Policy ID from the XML object' + [System.String]$PolicyID = $Xml.SiPolicy.PolicyID + Write-Verbose -Message "The policy ID of the currently processing xml file is $PolicyID" + + # Prevent users from accidentally attempting to remove policies that aren't even deployed on the system + Write-Verbose -Message 'Making sure the selected XML policy is deployed on the system' + $CurrentPolicyIDs = ((&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { $_.IsSystemPolicy -ne 'True' }).policyID | ForEach-Object -Process { "{$_}" } + if ($CurrentPolicyIDs -notcontains $PolicyID) { + Throw 'The selected policy file is not deployed on the system.' + } + + # Sanitize the policy file by removing SupplementalPolicySigners from it + Write-Verbose -Message 'Sanitizing the XML policy file by removing SupplementalPolicySigners from it' + + # Extracting the SupplementalPolicySigner ID from the selected XML policy file, if any + $SuppSingerIDs = $Xml.SiPolicy.SupplementalPolicySigners.SupplementalPolicySigner.SignerId + # Extracting the policy name from the selected XML policy file + $PolicyName = ($Xml.SiPolicy.Settings.Setting | Where-Object -FilterScript { $_.provider -eq 'PolicyInfo' -and $_.valuename -eq 'Name' -and $_.key -eq 'Information' }).value.string + + if ($SuppSingerIDs) { + Write-Verbose -Message "`n$($SuppSingerIDs.count) SupplementalPolicySigners have been found in $PolicyName policy, removing them now..." + + # Looping over each SupplementalPolicySigner and removing it + $SuppSingerIDs | ForEach-Object -Process { + $PolContent = Get-Content -Raw -Path $PolicyPath + $PolContent -match "" | Out-Null + $PolContent = $PolContent -replace $Matches[0], '' + Set-Content -Value $PolContent -Path $PolicyPath + } + + # Removing the Supplemental policy signers block from the XML file + $PolContent -match '[\S\s]*' | Out-Null + $PolContent = $PolContent -replace $Matches[0], '' + Set-Content -Value $PolContent -Path $PolicyPath + + # Remove empty lines from the entire policy file + (Get-Content -Path $PolicyPath) | Where-Object -FilterScript { $_.trim() -ne '' } | Set-Content -Path $PolicyPath -Force + Write-Verbose -Message 'Policy successfully sanitized and all SupplementalPolicySigners have been removed.' + } + else { + Write-Verbose -Message "`nNo sanitization required because no SupplementalPolicySigners have been found in $PolicyName policy." + } + + # Adding policy rule option "Unsigned System Integrity Policy" to the selected XML policy file + Set-RuleOption -FilePath $PolicyPath -Option 6 + # Converting the Policy XML file to CIP binary file + ConvertFrom-CIPolicy -XmlFilePath $PolicyPath -BinaryFilePath "$PolicyID.cip" | Out-Null + + # Configure the parameter splat + $ProcessParams = @{ + 'ArgumentList' = 'sign', '/v' , '/n', "`"$CertCN`"", '/p7', '.', '/p7co', '1.3.6.1.4.1.311.79.1', '/fd', 'certHash', ".\$PolicyID.cip" + 'FilePath' = $SignToolPathFinal + 'NoNewWindow' = $true + 'Wait' = $true + 'ErrorAction' = 'Stop' + } + if (!$Verbose) { $ProcessParams['RedirectStandardOutput'] = 'NUL' } + + # Sign the files with the specified cert + Write-Verbose -Message 'Signing the new CIP binary' + Start-Process @ProcessParams + + # Removing the unsigned CIP file + Remove-Item -Path ".\$PolicyID.cip" -Force + # Fixing the extension name of the newly signed CIP file + Rename-Item -Path "$PolicyID.cip.p7" -NewName "$PolicyID.cip" -Force + + # Deploying the newly signed CIP file + Write-Verbose -Message 'Deploying the newly signed CIP file' + &'C:\Windows\System32\CiTool.exe' --update-policy ".\$PolicyID.cip" -json | Out-Null + + Write-ColorfulText -Color Lavender -InputText "Policy with the following details has been Re-signed and Re-deployed in Unsigned mode.`nPlease restart your system." + Write-ColorfulText -Color MintGreen -InputText "PolicyName = $PolicyName" + Write-ColorfulText -Color MintGreen -InputText "PolicyGUID = $PolicyID" + } + } + + # If an unsigned policy is being removed + if ($UnsignedOrSupplemental) { + + # If IDs were supplied by user + foreach ($ID in $PolicyIDs ) { + &'C:\Windows\System32\CiTool.exe' --remove-policy "{$ID}" -json | Out-Null + Write-Host -Object "Policy with the ID $ID has been successfully removed." -ForegroundColor Green + } + + # If names were supplied by user + # Empty array to store Policy IDs based on the input name, this will take care of the situations where multiple policies with the same name are deployed + [System.Object[]]$NameID = @() + foreach ($PolicyName in $PolicyNames) { + $NameID += ((&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { $_.IsOnDisk -eq 'True' } | Where-Object -FilterScript { $_.FriendlyName -eq $PolicyName }).PolicyID + } + + Write-Verbose -Message 'The Following policy IDs have been gathered from the supplied policy names and are going to be removed from the system' + $NameID | Select-Object -Unique | ForEach-Object -Process { Write-Verbose -Message "$_" } + + $NameID | Select-Object -Unique | ForEach-Object -Process { + &'C:\Windows\System32\CiTool.exe' --remove-policy "{$_}" -json | Out-Null + Write-Host -Object "Policy with the ID $_ has been successfully removed." -ForegroundColor Green + } + } + } + + <# +.SYNOPSIS + Removes Signed and unsigned deployed WDAC policies (Windows Defender Application Control) +.LINK + https://github.com/HotCakeX/Harden-Windows-Security/wiki/Remove-WDACConfig +.DESCRIPTION + Using official Microsoft methods, Removes Signed and unsigned deployed WDAC policies (Windows Defender Application Control) +.COMPONENT + Windows Defender Application Control, ConfigCI PowerShell module +.FUNCTIONALITY + Using official Microsoft methods, Removes Signed and unsigned deployed WDAC policies (Windows Defender Application Control) +.PARAMETER PolicyNames + Names of the deployed policies to be removed + https://stackoverflow.com/questions/76143006/how-to-prevent-powershell-validateset-argument-completer-from-suggesting-the-sam/76143269 + https://stackoverflow.com/questions/76267235/powershell-how-to-cross-reference-parameters-between-2-argument-completers +.PARAMETER PolicyIDs + IDs of the deployed policies to be removed + https://stackoverflow.com/questions/76143006/how-to-prevent-powershell-validateset-argument-completer-from-suggesting-the-sam/76143269 + https://stackoverflow.com/questions/76267235/powershell-how-to-cross-reference-parameters-between-2-argument-completers +.PARAMETER SignedBase + Remove Signed Base WDAC Policies +.PARAMETER PolicyPaths + Path to the XML policy file(s) of the deployed policies to be removed +.PARAMETER CertCN + Certificate common name to be used to sign the policy file(s) that are going to be removed in unsigned mode +.PARAMETER SignToolPath + Path to the SignTool.exe +.PARAMETER UnsignedOrSupplemental + Remove Unsigned deployed WDAC policies as well as Signed deployed Supplemental WDAC policies +.PARAMETER SkipVersionCheck + Can be used with any parameter to bypass the online version check - only to be used in rare cases +.INPUTS + System.String + System.String[] + System.Management.Automation.SwitchParameter +.OUTPUTS + System.String +#> +} + +# Importing argument completer ScriptBlocks +. "$ModuleRootPath\Resources\ArgumentCompleters.ps1" +Register-ArgumentCompleter -CommandName 'Remove-WDACConfig' -ParameterName 'CertCN' -ScriptBlock $ArgumentCompleterCertificateCN +Register-ArgumentCompleter -CommandName 'Remove-WDACConfig' -ParameterName 'PolicyPaths' -ScriptBlock $ArgumentCompleterPolicyPathsBasePoliciesOnly +Register-ArgumentCompleter -CommandName 'Remove-WDACConfig' -ParameterName 'SignToolPath' -ScriptBlock $ArgumentCompleterExeFilePathsPicker diff --git a/WDACConfig/WDACConfig Module Files/Core/Set-CommonWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Set-CommonWDACConfig.psm1 new file mode 100644 index 000000000..0f70cd233 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/Core/Set-CommonWDACConfig.psm1 @@ -0,0 +1,220 @@ +Function Set-CommonWDACConfig { + [CmdletBinding()] + Param( + [ValidateScript({ + [System.String[]]$Certificates = foreach ($cert in (Get-ChildItem -Path 'Cert:\CurrentUser\my')) { + (($cert.Subject -split ',' | Select-Object -First 1) -replace 'CN=', '').Trim() + } + $Certificates -contains $_ + }, ErrorMessage = "A certificate with the provided common name doesn't exist in the personal store of the user certificates." )] + [parameter(Mandatory = $false)][System.String]$CertCN, + + [ValidatePattern('\.cer$')] + [ValidateScript({ Test-Path -Path $_ -PathType 'Leaf' }, ErrorMessage = 'The path you selected is not a file path.')] + [parameter(Mandatory = $false)][System.String]$CertPath, + + [ValidatePattern('\.exe$')] + [ValidateScript({ Test-Path -Path $_ -PathType 'Leaf' }, ErrorMessage = 'The path you selected is not a file path.')] + [parameter(Mandatory = $false)][System.String]$SignToolPath, + + [ValidatePattern('\.xml$')] + [ValidateScript({ + $_ | ForEach-Object -Process { + $XmlTest = [System.Xml.XmlDocument](Get-Content -Path $_) + $RedFlag1 = $XmlTest.SiPolicy.SupplementalPolicySigners.SupplementalPolicySigner.SignerId + $RedFlag2 = $XmlTest.SiPolicy.UpdatePolicySigners.UpdatePolicySigner.SignerId + if (!$RedFlag1 -and !$RedFlag2) { + return $True + } + else { throw 'The selected policy xml file is Signed, Please select an Unsigned policy.' } + } + }, ErrorMessage = 'The selected policy xml file is Signed, Please select an Unsigned policy.')] + [parameter(Mandatory = $false)][System.String]$UnsignedPolicyPath, + + [ValidatePattern('\.xml$')] + [ValidateScript({ + $_ | ForEach-Object -Process { + $XmlTest = [System.Xml.XmlDocument](Get-Content -Path $_) + $RedFlag1 = $XmlTest.SiPolicy.SupplementalPolicySigners.SupplementalPolicySigner.SignerId + $RedFlag2 = $XmlTest.SiPolicy.UpdatePolicySigners.UpdatePolicySigner.SignerId + if ($RedFlag1 -or $RedFlag2) { + return $True + } + else { throw 'The selected policy xml file is Unsigned, Please select a Signed policy.' } + } + }, ErrorMessage = 'The selected policy xml file is Unsigned, Please select a Signed policy.')] + [parameter(Mandatory = $false)][System.String]$SignedPolicyPath, + + [parameter(Mandatory = $false, DontShow = $true)][System.Guid]$StrictKernelPolicyGUID, + [parameter(Mandatory = $false, DontShow = $true)][System.Guid]$StrictKernelNoFlightRootsPolicyGUID, + [parameter(Mandatory = $false, DontShow = $true)][System.DateTime]$LastUpdateCheck + ) + begin { + # Importing the $PSDefaultParameterValues to the current session, prior to everything else + . "$ModuleRootPath\CoreExt\PSDefaultParameterValues.ps1" + + # Create User configuration folder if it doesn't already exist + if (-NOT (Test-Path -Path "$UserAccountDirectoryPath\.WDACConfig\")) { + New-Item -ItemType Directory -Path "$UserAccountDirectoryPath\.WDACConfig\" -Force -ErrorAction Stop | Out-Null + Write-Verbose -Message 'The .WDACConfig folder in the current user folder has been created because it did not exist.' + } + + # Create User configuration file if it doesn't already exist + if (-NOT (Test-Path -Path "$UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json")) { + New-Item -ItemType File -Path "$UserAccountDirectoryPath\.WDACConfig\" -Name 'UserConfigurations.json' -Force -ErrorAction Stop | Out-Null + Write-Verbose -Message 'The UserConfigurations.json file in \.WDACConfig\ folder has been created because it did not exist.' + } + + if (!$CertCN -And !$CertPath -And !$SignToolPath -And !$UnsignedPolicyPath -And !$SignedPolicyPath -And !$StrictKernelPolicyGUID -And !$StrictKernelNoFlightRootsPolicyGUID -And !$LastUpdateCheck) { + Throw 'No parameter was selected.' + } + + # Trying to read the current user configurations + Write-Verbose -Message 'Trying to read the current user configurations' + [System.Object[]]$CurrentUserConfigurations = Get-Content -Path "$UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" + + # If the file exists but is corrupted and has bad values, rewrite it + try { + $CurrentUserConfigurations = $CurrentUserConfigurations | ConvertFrom-Json + } + catch { + Write-Verbose -Message 'The user configurations file exists but is corrupted and has bad values, rewriting it' + Set-Content -Path "$UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" -Value '' + } + + # An object to hold the User configurations + $UserConfigurationsObject = [PSCustomObject]@{ + SignedPolicyPath = '' + UnsignedPolicyPath = '' + SignToolCustomPath = '' + CertificateCommonName = '' + CertificatePath = '' + StrictKernelPolicyGUID = '' + StrictKernelNoFlightRootsPolicyGUID = '' + LastUpdateCheck = '' + } + } + process { + + Write-Verbose -Message 'Processing each user configuration property' + + if ($SignedPolicyPath) { + Write-Verbose -Message 'Saving the supplied Signed Policy path in user configurations.' + $UserConfigurationsObject.SignedPolicyPath = $SignedPolicyPath + } + else { + Write-Verbose -Message 'No changes to the Signed Policy path property was detected.' + $UserConfigurationsObject.SignedPolicyPath = $CurrentUserConfigurations.SignedPolicyPath + } + + if ($UnsignedPolicyPath) { + Write-Verbose -Message 'Saving the supplied Unsigned Policy path in user configurations.' + $UserConfigurationsObject.UnsignedPolicyPath = $UnsignedPolicyPath + } + else { + Write-Verbose -Message 'No changes to the Unsigned Policy path property was detected.' + $UserConfigurationsObject.UnsignedPolicyPath = $CurrentUserConfigurations.UnsignedPolicyPath + } + + if ($SignToolPath) { + Write-Verbose -Message 'Saving the supplied SignTool path in user configurations.' + $UserConfigurationsObject.SignToolCustomPath = $SignToolPath + } + else { + Write-Verbose -Message 'No changes to the Signtool path property was detected.' + $UserConfigurationsObject.SignToolCustomPath = $CurrentUserConfigurations.SignToolCustomPath + } + + if ($CertPath) { + Write-Verbose -Message 'Saving the supplied Certificate path in user configurations.' + $UserConfigurationsObject.CertificatePath = $CertPath + } + else { + Write-Verbose -Message 'No changes to the Certificate path property was detected.' + $UserConfigurationsObject.CertificatePath = $CurrentUserConfigurations.CertificatePath + } + + if ($CertCN) { + Write-Verbose -Message 'Saving the supplied Certificate common name in user configurations.' + $UserConfigurationsObject.CertificateCommonName = $CertCN + } + else { + Write-Verbose -Message 'No changes to the Certificate common name property was detected.' + $UserConfigurationsObject.CertificateCommonName = $CurrentUserConfigurations.CertificateCommonName + } + + if ($StrictKernelPolicyGUID) { + Write-Verbose -Message 'Saving the supplied Strict Kernel policy GUID in user configurations.' + $UserConfigurationsObject.StrictKernelPolicyGUID = $StrictKernelPolicyGUID + } + else { + Write-Verbose -Message 'No changes to the Strict Kernel policy GUID property was detected.' + $UserConfigurationsObject.StrictKernelPolicyGUID = $CurrentUserConfigurations.StrictKernelPolicyGUID + } + + if ($StrictKernelNoFlightRootsPolicyGUID) { + Write-Verbose -Message 'Saving the supplied Strict Kernel NoFlightRoot policy GUID in user configurations.' + $UserConfigurationsObject.StrictKernelNoFlightRootsPolicyGUID = $StrictKernelNoFlightRootsPolicyGUID + } + else { + Write-Verbose -Message 'No changes to the Strict Kernel NoFlightRoot policy GUID property was detected.' + $UserConfigurationsObject.StrictKernelNoFlightRootsPolicyGUID = $CurrentUserConfigurations.StrictKernelNoFlightRootsPolicyGUID + } + + if ($LastUpdateCheck) { + Write-Verbose -Message 'Saving the supplied Last Update Check in user configurations.' + $UserConfigurationsObject.LastUpdateCheck = $LastUpdateCheck + } + else { + Write-Verbose -Message 'No changes to the Last Update Check property was detected.' + $UserConfigurationsObject.LastUpdateCheck = $CurrentUserConfigurations.LastUpdateCheck + } + } + end { + # Update the User Configurations file + Write-Verbose -Message 'Saving the changes' + $UserConfigurationsObject | ConvertTo-Json | Set-Content -Path "$UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" + + Get-Content -Path "$UserAccountDirectoryPath\.WDACConfig\UserConfigurations.json" | ConvertFrom-Json | Format-List -Property * + } + <# +.SYNOPSIS + Add/Change common values for parameters used by WDACConfig module +.LINK + https://github.com/HotCakeX/Harden-Windows-Security/wiki/Set-CommonWDACConfig +.DESCRIPTION + Add/Change common values for parameters used by WDACConfig module so that you won't have to provide values for those repetitive parameters each time you need to use the WDACConfig module cmdlets. +.COMPONENT + Windows Defender Application Control, ConfigCI PowerShell module, WDACConfig module +.FUNCTIONALITY + Add/Change common values for parameters used by WDACConfig module so that you won't have to provide values for those repetitive parameters each time you need to use the WDACConfig module cmdlets. +.PARAMETER SignedPolicyPath + Path to a Signed WDAC xml policy +.PARAMETER UnsignedPolicyPath + Path to an Unsigned WDAC xml policy +.PARAMETER CertCN + Certificate common name +.PARAMETER SignToolPath + Path to the SignTool.exe +.PARAMETER CertPath + Path to a .cer certificate file +.PARAMETER StrictKernelPolicyGUID + GUID of the Strict Kernel mode policy +.PARAMETER StrictKernelNoFlightRootsPolicyGUID + GUID of the Strict Kernel no Flights root mode policy +.INPUTS + System.DateTime + System.Guid + System.String +.OUTPUTS + System.Object[] +#> +} + +# Importing argument completer ScriptBlocks +. "$ModuleRootPath\Resources\ArgumentCompleters.ps1" +Register-ArgumentCompleter -CommandName 'Set-CommonWDACConfig' -ParameterName 'CertCN' -ScriptBlock $ArgumentCompleterCertificateCN +Register-ArgumentCompleter -CommandName 'Set-CommonWDACConfig' -ParameterName 'CertPath' -ScriptBlock $ArgumentCompleterCerFilePathsPicker +Register-ArgumentCompleter -CommandName 'Set-CommonWDACConfig' -ParameterName 'SignToolPath' -ScriptBlock $ArgumentCompleterExeFilePathsPicker +Register-ArgumentCompleter -CommandName 'Set-CommonWDACConfig' -ParameterName 'SignedPolicyPath' -ScriptBlock $ArgumentCompleterPolicyPathsBasePoliciesOnly +Register-ArgumentCompleter -CommandName 'Set-CommonWDACConfig' -ParameterName 'UnsignedPolicyPath' -ScriptBlock $ArgumentCompleterPolicyPathsBasePoliciesOnly diff --git a/WDACConfig/WDACConfig Module Files/CoreExt/PSDefaultParameterValues.ps1 b/WDACConfig/WDACConfig Module Files/CoreExt/PSDefaultParameterValues.ps1 new file mode 100644 index 000000000..60e32d340 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/CoreExt/PSDefaultParameterValues.ps1 @@ -0,0 +1,26 @@ +# $PSDefaultParameterValues only get read from scope where invocation occurs +# This is why this file is dot-sourced in every other component of the WDACConfig module at the beginning +$PSDefaultParameterValues = @{ + 'Invoke-WebRequest:HttpVersion' = '3.0' + 'Invoke-WebRequest:SslProtocol' = 'Tls12,Tls13' + 'Invoke-RestMethod:HttpVersion' = '3.0' + 'Invoke-RestMethod:SslProtocol' = 'Tls12,Tls13' + 'Import-Module:Verbose' = $false + 'Export-ModuleMember:Verbose' = $false + 'Add-Type:Verbose' = $false + 'Get-WinEvent:Verbose' = $false + 'Confirm-CertCN:Verbose' = $Verbose + 'Get-AuditEventLogsProcessing:Verbose' = $Verbose + 'Get-FileRules:Verbose' = $Verbose + 'Get-BlockRulesMeta:Verbose' = $Verbose + 'Get-GlobalRootDrives:Verbose' = $Verbose + 'Get-RuleRefs:Verbose' = $Verbose + 'Get-SignTool:Verbose' = $Verbose + 'Move-UserModeToKernelMode:Verbose' = $Verbose + 'New-EmptyPolicy:Verbose' = $Verbose + 'Set-LogSize:Verbose' = $Verbose + 'Test-FilePath:Verbose' = $Verbose + 'Update-self:Verbose' = $Verbose + 'Write-ColorfulText:Verbose' = $Verbose + 'New-SnapBackGuarantee:Verbose' = $Verbose +} diff --git a/WDACConfig/WDACConfig Module Files/Preloader.ps1 b/WDACConfig/WDACConfig Module Files/Preloader.ps1 new file mode 100644 index 000000000..9873f6c99 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/Preloader.ps1 @@ -0,0 +1,40 @@ +if (!$IsWindows) { + Throw 'The WDACConfig module only runs on Windows operation systems.' +} + +# Specifies that the WDACConfig module requires Administrator privileges +#Requires -RunAsAdministrator + +# Create tamper resistant global variables (if they don't already exist) +try { + if ((Test-Path -Path 'Variable:\MSFTRecommendedBlockRulesURL') -eq $false) { New-Variable -Name 'MSFTRecommendedBlockRulesURL' -Value 'https://raw.githubusercontent.com/MicrosoftDocs/windows-itpro-docs/public/windows/security/application-security/application-control/windows-defender-application-control/design/applications-that-can-bypass-wdac.md' -Option 'Constant' -Scope 'Global' -Description 'User Mode block rules' -Force } + if ((Test-Path -Path 'Variable:\MSFTRecommendedDriverBlockRulesURL') -eq $false) { New-Variable -Name 'MSFTRecommendedDriverBlockRulesURL' -Value 'https://raw.githubusercontent.com/MicrosoftDocs/windows-itpro-docs/public/windows/security/application-security/application-control/windows-defender-application-control/design/microsoft-recommended-driver-block-rules.md' -Option 'Constant' -Scope 'Global' -Description 'Kernel Mode block rules' -Force } + if ((Test-Path -Path 'Variable:\UserTempDirectoryPath') -eq $false) { New-Variable -Name 'UserTempDirectoryPath' -Value ([System.IO.Path]::GetTempPath()) -Option 'Constant' -Scope 'Global' -Description 'Properly and securely retrieved Temp Directory' -Force } + if ((Test-Path -Path 'Variable:\UserAccountDirectoryPath') -eq $false) { New-Variable -Name 'UserAccountDirectoryPath' -Value ((Get-CimInstance Win32_UserProfile -Filter "SID = '$([System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value)'").LocalPath) -Option 'Constant' -Scope 'Global' -Description 'Securely retrieved User profile directory' -Force } + if ((Test-Path -Path 'Variable:\Requiredbuild') -eq $false) { New-Variable -Name 'Requiredbuild' -Value '22621.2428' -Option 'Constant' -Scope 'Script' -Description 'Minimum required OS build number' -Force } + if ((Test-Path -Path 'Variable:\OSBuild') -eq $false) { New-Variable -Name 'OSBuild' -Value ([System.Environment]::OSVersion.Version.Build) -Option 'Constant' -Scope 'Script' -Description 'Current OS build version' -Force } + if ((Test-Path -Path 'Variable:\UBR') -eq $false) { New-Variable -Name 'UBR' -Value (Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'UBR') -Option 'Constant' -Scope 'Script' -Description 'Update Build Revision (UBR) number' -Force } + if ((Test-Path -Path 'Variable:\FullOSBuild') -eq $false) { New-Variable -Name 'FullOSBuild' -Value "$OSBuild.$UBR" -Option 'Constant' -Scope 'Script' -Description 'Create full OS build number as seen in Windows Settings' -Force } +} +catch { + Throw 'Could not set the required global variables.' +} + +# A constant variable that is automatically imported in the caller's environment and used to detect the main module's root directory +# Create it only if it's not already present, helps when user tries to import the module over and over again without closing the PowerShell session +try { + Get-Variable -Name 'ModuleRootPath' -ErrorAction Stop | Out-Null +} +catch { + try { + New-Variable -Name 'ModuleRootPath' -Value ($PSScriptRoot) -Option 'Constant' -Scope 'Global' -Description 'Storing the value of $PSScriptRoot in a global constant variable to allow the internal functions to use it when navigating the module structure' -Force + } + catch { + Throw 'Could not set the ModuleRootPath required global variable.' + } +} + +# Make sure the current OS build is equal or greater than the required build number +if (-NOT ([System.Decimal]$FullOSBuild -ge [System.Decimal]$Requiredbuild)) { + Throw [System.PlatformNotSupportedException] "You are not using the latest build of the Windows OS. A minimum build of $Requiredbuild is required but your OS build is $FullOSBuild`nPlease go to Windows Update to install the updates and then try again." +} diff --git a/WDACConfig/WDACConfig Module Files/Resources/ArgumentCompleters.ps1 b/WDACConfig/WDACConfig Module Files/Resources/ArgumentCompleters.ps1 new file mode 100644 index 000000000..fe12fc34e --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/Resources/ArgumentCompleters.ps1 @@ -0,0 +1,188 @@ +<# +# argument tab auto-completion for CertPath param to show only .cer files in current directory and 2 sub-directories recursively +[System.Management.Automation.ScriptBlock]$ArgumentCompleterCertPath = { + # Note the use of -Depth 1 + # Enclosing the $Results = ... assignment in (...) also passes the value through. + ($Results = Get-ChildItem -Depth 2 -Filter *.cer | ForEach-Object -Process { "`"$_`"" }) + if (-not $Results) { + # No results? + $null # Dummy response that prevents fallback to the default file-name completion. + } +} +#> + +# Importing the $PSDefaultParameterValues to the current session, prior to everything else +. "$ModuleRootPath\CoreExt\PSDefaultParameterValues.ps1" + +# argument tab auto-completion for Policy Paths to show only .xml files and only suggest files that haven't been already selected by user +# https://stackoverflow.com/questions/76141864/how-to-make-a-powershell-argument-completer-that-only-suggests-files-not-already/76142865 +[System.Management.Automation.ScriptBlock]$ArgumentCompleterPolicyPaths = { + # Get the current command and the already bound parameters + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) + + # Find all string constants in the AST that end in ".xml" + $Existing = $commandAst.FindAll({ + $args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] -and + $args[0].Value -like '*.xml' + }, + $false + ).Value + + # Get the xml files in the current directory + Get-ChildItem -File -Filter *.xml | ForEach-Object -Process { + # Check if the file is already selected + if ($_.FullName -notin $Existing) { + # Return the file name with quotes + "`"$_`"" + } + } +} + +# argument tab auto-completion for Certificate common name +[System.Management.Automation.ScriptBlock]$ArgumentCompleterCertificateCN = { + [System.String[]]$Certificates = foreach ($Cert in (Get-ChildItem -Path 'Cert:\CurrentUser\my')) { + (($Cert.Subject -split ',' | Select-Object -First 1) -replace 'CN=', '').Trim() + } + $Certificates | ForEach-Object -Process { return "`"$_`"" } +} + +# Argument tab auto-completion for installed Appx package names +[System.Management.Automation.ScriptBlock]$ArgumentCompleterAppxPackageNames = { + # Get the current command and the already bound parameters + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) + # Get the app package names that match the word to complete + Get-AppxPackage -Name *$wordToComplete* | ForEach-Object -Process { + "`"$($_.Name)`"" + } +} + +# argument tab auto-completion for Base Policy Paths to show only .xml files and only suggest files that haven't been already selected by user +[System.Management.Automation.ScriptBlock]$ArgumentCompleterPolicyPathsBasePoliciesOnly = { + # Get the current command and the already bound parameters + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) + + # Find all string constants in the AST that end in ".xml" + $Existing = $commandAst.FindAll({ + $args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] -and + $args[0].Value -like '*.xml' + }, + $false + ).Value + + # Get the xml files in the current directory + Get-ChildItem -File | Where-Object -FilterScript { $_.extension -like '*.xml' } | ForEach-Object -Process { + + $XmlItem = [System.Xml.XmlDocument](Get-Content -Path $_) + $PolicyType = $XmlItem.SiPolicy.PolicyType + + if ($PolicyType -eq 'Base Policy') { + + # Check if the file is already selected + if ($_.FullName -notin $Existing) { + # Return the file name with quotes + "`"$_`"" + } + } + } +} + +# argument tab auto-completion for Supplemental Policy Paths to show only .xml files and only suggest files that haven't been already selected by user +[System.Management.Automation.ScriptBlock]$ArgumentCompleterPolicyPathsSupplementalPoliciesOnly = { + # Get the current command and the already bound parameters + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) + + # Find all string constants in the AST that end in ".xml" + $Existing = $commandAst.FindAll({ + $args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] -and + $args[0].Value -like '*.xml' + }, + $false + ).Value + + # Get the xml files in the current directory + Get-ChildItem -File | Where-Object -FilterScript { $_.extension -like '*.xml' } | ForEach-Object -Process { + + $XmlItem = [System.Xml.XmlDocument](Get-Content -Path $_) + $PolicyType = $XmlItem.SiPolicy.PolicyType + + if ($PolicyType -eq 'Supplemental Policy') { + + # Check if the file is already selected + if ($_.FullName -notin $Existing) { + # Return the file name with quotes + "`"$_`"" + } + } + } +} + +# Opens Folder picker GUI so that user can select folders to be processed +[System.Management.Automation.ScriptBlock]$ArgumentCompleterFolderPathsPicker = { + # Load the System.Windows.Forms assembly + Add-Type -AssemblyName 'System.Windows.Forms' + # non-top-most, works better with window focus + [System.Windows.Forms.FolderBrowserDialog]$Browser = New-Object -TypeName 'System.Windows.Forms.FolderBrowserDialog' + $null = $Browser.ShowDialog() + # Add quotes around the selected path + return "`"$($Browser.SelectedPath)`"" +} + +# Opens File picker GUI so that user can select an .exe file - for SignTool.exe +[System.Management.Automation.ScriptBlock]$ArgumentCompleterExeFilePathsPicker = { + # Load the System.Windows.Forms assembly + Add-Type -AssemblyName 'System.Windows.Forms' + # Create a new OpenFileDialog object + [System.Windows.Forms.OpenFileDialog]$Dialog = New-Object -TypeName 'System.Windows.Forms.OpenFileDialog' + # Set the filter to show only executable files + $Dialog.Filter = 'Executable files (*.exe)|*.exe' + # Show the dialog and get the result + [System.String]$Result = $Dialog.ShowDialog() + # If the user clicked OK, return the selected file path + if ($Result -eq 'OK') { + return "`"$($Dialog.FileName)`"" + } +} + +# Opens File picker GUI so that user can select a .cer file +[System.Management.Automation.ScriptBlock]$ArgumentCompleterCerFilePathsPicker = { + # Load the System.Windows.Forms assembly + Add-Type -AssemblyName 'System.Windows.Forms' + # Create a new OpenFileDialog object + [System.Windows.Forms.OpenFileDialog]$Dialog = New-Object -TypeName 'System.Windows.Forms.OpenFileDialog' + # Set the filter to show only certificate files + $Dialog.Filter = 'Certificate files (*.cer)|*.cer' + # Show the dialog and get the result + [System.String]$Result = $Dialog.ShowDialog() + # If the user clicked OK, return the selected file path + if ($Result -eq 'OK') { + return "`"$($Dialog.FileName)`"" + } +} + +# Opens File picker GUI so that user can select a .xml file +[System.Management.Automation.ScriptBlock]$ArgumentCompleterXmlFilePathsPicker = { + # Load the System.Windows.Forms assembly + Add-Type -AssemblyName 'System.Windows.Forms' + # Create a new OpenFileDialog object + [System.Windows.Forms.OpenFileDialog]$Dialog = New-Object -TypeName 'System.Windows.Forms.OpenFileDialog' + # Set the filter to show only XML files + $Dialog.Filter = 'XML files (*.xml)|*.xml' + # Show the dialog and get the result + [System.String]$Result = $Dialog.ShowDialog() + # If the user clicked OK, return the selected file path + if ($Result -eq 'OK') { + return "`"$($Dialog.FileName)`"" + } +} + +# Opens Folder picker GUI so that user can select folders to be processed +# WildCard file paths +[System.Management.Automation.ScriptBlock]$ArgumentCompleterFolderPathsPickerWildCards = { + # Load the System.Windows.Forms assembly + Add-Type -AssemblyName 'System.Windows.Forms' + # non-top-most, works better with window focus + [System.Windows.Forms.FolderBrowserDialog]$Browser = New-Object -TypeName 'System.Windows.Forms.FolderBrowserDialog' + $null = $Browser.ShowDialog() + # Add quotes around the selected path and a wildcard character at the end + return "`"$($Browser.SelectedPath)\*`"" +} \ No newline at end of file diff --git a/WDACConfig/WDACConfig Module Files/Resources/Resources2.ps1 b/WDACConfig/WDACConfig Module Files/Resources/Resources2.ps1 new file mode 100644 index 000000000..1a6a683d6 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/Resources/Resources2.ps1 @@ -0,0 +1,751 @@ +# Importing the $PSDefaultParameterValues to the current session, prior to everything else +. "$ModuleRootPath\CoreExt\PSDefaultParameterValues.ps1" + +# Defining a custom object to store the signer information +class Signer { + [System.String]$ID + [System.String]$Name + [System.String]$CertRoot + [System.String]$CertPublisher +} + +Function Get-SignerInfo { + <# + .SYNOPSIS + Function that takes an XML file path as input and returns an array of Signer objects + .INPUTS + System.IO.FileInfo + .OUTPUTS + Signer[] + .PARAMETER XmlFilePath + The XML file path that the user selected for WDAC simulation. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)][System.IO.FileInfo]$XmlFilePath + ) + begin { + # Load the XML file + $Xml = [System.Xml.XmlDocument](Get-Content -Path $XmlFilePath) + } + process { + # Select the Signer nodes + [System.Object[]]$Signers = $Xml.SiPolicy.Signers.Signer + + # Create an empty array to store the output + [Signer[]]$Output = @() + + # Loop through each Signer node and extract the information + foreach ($Signer in $Signers) { + # Create a new Signer object and assign the properties + [Signer]$SignerObj = [Signer]::new() + $SignerObj.ID = $Signer.ID + $SignerObj.Name = $Signer.Name + $SignerObj.CertRoot = $Signer.CertRoot.Value + $SignerObj.CertPublisher = $Signer.CertPublisher.Value + + # Add the Signer object to the output array + $Output += $SignerObj + } + } + end { + # Return the output array + return $Output + } +} + +Function Get-TBSCertificate { + <# + .SYNOPSIS + Function to calculate the TBS value of a certificate + .INPUTS + System.Security.Cryptography.X509Certificates.X509Certificate2 + .OUTPUTS + System.String + .PARAMETER Cert + The certificate that is going to be used to retrieve its TBS value + #> + [CmdletBinding()] + param ( + [System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert + ) + + # Get the raw data of the certificate + [System.Byte[]]$RawData = $Cert.RawData + + # Create an ASN.1 reader to parse the certificate + [System.Formats.Asn1.AsnReader]$AsnReader = New-Object -TypeName System.Formats.Asn1.AsnReader -ArgumentList $RawData, ([System.Formats.Asn1.AsnEncodingRules]::DER) + + # Read the certificate sequence + [System.Formats.Asn1.AsnReader]$Certificate = $AsnReader.ReadSequence() + + # Read the TBS (To be signed) value of the certificate + $TbsCertificate = $Certificate.ReadEncodedValue() + + # Read the signature algorithm sequence + [System.Formats.Asn1.AsnReader]$SignatureAlgorithm = $Certificate.ReadSequence() + + # Read the algorithm OID of the signature + [System.String]$AlgorithmOid = $SignatureAlgorithm.ReadObjectIdentifier() + + # Define a hash function based on the algorithm OID + switch ($AlgorithmOid) { + '1.2.840.113549.1.1.4' { $HashFunction = [System.Security.Cryptography.MD5]::Create() ; break } + '1.2.840.10040.4.3' { $HashFunction = [System.Security.Cryptography.SHA1]::Create() ; break } + '2.16.840.1.101.3.4.3.2' { $HashFunction = [System.Security.Cryptography.SHA256]::Create() ; break } + '2.16.840.1.101.3.4.3.3' { $HashFunction = [System.Security.Cryptography.SHA384]::Create() ; break } + '2.16.840.1.101.3.4.3.4' { $HashFunction = [System.Security.Cryptography.SHA512]::Create() ; break } + '1.2.840.10045.4.1' { $HashFunction = [System.Security.Cryptography.SHA1]::Create() ; break } + '1.2.840.10045.4.3.2' { $HashFunction = [System.Security.Cryptography.SHA256]::Create() ; break } + '1.2.840.10045.4.3.3' { $HashFunction = [System.Security.Cryptography.SHA384]::Create() ; break } + '1.2.840.10045.4.3.4' { $HashFunction = [System.Security.Cryptography.SHA512]::Create() ; break } + '1.2.840.113549.1.1.5' { $HashFunction = [System.Security.Cryptography.SHA1]::Create() ; break } + '1.2.840.113549.1.1.11' { $HashFunction = [System.Security.Cryptography.SHA256]::Create() ; break } + '1.2.840.113549.1.1.12' { $HashFunction = [System.Security.Cryptography.SHA384]::Create() ; break } + '1.2.840.113549.1.1.13' { $HashFunction = [System.Security.Cryptography.SHA512]::Create() ; break } + # sha-1WithRSAEncryption + '1.3.14.3.2.29' { $HashFunction = [System.Security.Cryptography.SHA1]::Create() ; break } + default { throw "No handler for algorithm $AlgorithmOid" } + } + + # Compute the hash of the TBS value using the hash function + [System.Byte[]]$Hash = $HashFunction.ComputeHash($TbsCertificate.ToArray()) + + # Convert the hash to a hex string + [System.String]$HexStringOutput = [System.BitConverter]::ToString($Hash) -replace '-', '' + + # Return the output + return $HexStringOutput +} + +Function Get-AuthenticodeSignatureEx { + <# + .SYNOPSIS + Helps to get the 2nd aka nested signer/signature of the dual signed files + .NOTES + This function is used in a very minimum capacity by the WDACConfig module and it's modified to meet the WDACConfig's requirements + .LINK + https://www.sysadmins.lv/blog-en/reading-multiple-signatures-from-signed-file-with-powershell.aspx + https://www.sysadmins.lv/disclaimer.aspx + .PARAMETER FilePath + The path of the file(s) to get the signature of + .INPUTS + System.String[] + .OUTPUTS + System.Management.Automation.Signature + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] + [System.String[]]$FilePath + ) + + begin { + + # Define the signature of the Crypt32.dll library functions to use + [System.String]$Signature = @' + [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool CryptQueryObject( + int dwObjectType, + [MarshalAs(UnmanagedType.LPWStr)] + string pvObject, + int dwExpectedContentTypeFlags, + int dwExpectedFormatTypeFlags, + int dwFlags, + ref int pdwMsgAndCertEncodingType, + ref int pdwContentType, + ref int pdwFormatType, + ref IntPtr phCertStore, + ref IntPtr phMsg, + ref IntPtr ppvContext + ); + [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool CryptMsgGetParam( + IntPtr hCryptMsg, + int dwParamType, + int dwIndex, + byte[] pvData, + ref int pcbData + ); + [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool CryptMsgClose( + IntPtr hCryptMsg + ); + [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool CertCloseStore( + IntPtr hCertStore, + int dwFlags + ); +'@ + + # Load the System.Security assembly to use the SignedCms class + Add-Type -AssemblyName 'System.Security' -ErrorAction SilentlyContinue + # Add the Crypt32.dll library functions as a type + Add-Type -MemberDefinition $Signature -Namespace 'PKI' -Name 'Crypt32' -ErrorAction SilentlyContinue + + # Define some constants for the CryptQueryObject function parameters + [System.Int16]$CERT_QUERY_OBJECT_FILE = 0x1 + [System.Int32]$CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED = 0x400 + [System.Int16]$CERT_QUERY_FORMAT_FLAG_BINARY = 0x2 + + # Define a helper function to get the timestamps of the countersigners + function Get-TimeStamps { + param ( + $SignerInfo + ) + + [System.Object[]]$RetValue = @() + + foreach ($CounterSignerInfos in $Infos.CounterSignerInfos) { + # Get the signing time attribute from the countersigner info object + $STime = ($CounterSignerInfos.SignedAttributes | Where-Object -FilterScript { $_.Oid.Value -eq '1.2.840.113549.1.9.5' }).Values | ` + Where-Object -FilterScript { $null -ne $_.SigningTime } + # Create a custom object with the countersigner certificate and signing time properties + $TsObject = New-Object psobject -Property @{ + Certificate = $CounterSignerInfos.Certificate + SigningTime = $STime.SigningTime.ToLocalTime() + } + # Add the custom object to the return value array + $RetValue += $TsObject + } + # Return the array of custom objects with countersigner info + $RetValue + + } + } + process { + # For each file path, get the authenticode signature using the built-in cmdlet + foreach ($Output in Get-AuthenticodeSignature $FilePath) { + + # Initialize some variables to store the output parameters of the CryptQueryObject function + [System.Int64]$PdwMsgAndCertEncodingType = 0 + [System.Int64]$PdwContentType = 0 + [System.Int64]$PdwFormatType = 0 + [System.IntPtr]$PhCertStore = [System.IntPtr]::Zero + [System.IntPtr]$PhMsg = [System.IntPtr]::Zero + [System.IntPtr]$PpvContext = [System.IntPtr]::Zero + + # Call the CryptQueryObject function to get the handle of the PKCS #7 message from the file path + $Return = [PKI.Crypt32]::CryptQueryObject( + $CERT_QUERY_OBJECT_FILE, + $Output.Path, + $CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, + $CERT_QUERY_FORMAT_FLAG_BINARY, + 0, + [ref]$pdwMsgAndCertEncodingType, + [ref]$pdwContentType, + [ref]$pdwFormatType, + [ref]$phCertStore, + [ref]$phMsg, + [ref]$ppvContext + ) + # If the function fails, return nothing + if (!$Return) { return } + + # Initialize a variable to store the size of the PKCS #7 message data + [System.Int64]$PcbData = 0 + + # Call the CryptMsgGetParam function to get the size of the PKCS #7 message data + $Return = [PKI.Crypt32]::CryptMsgGetParam($phMsg, 29, 0, $null, [ref]$PcbData) + + # If the function fails, return nothing + if (!$Return) { return } + + # Create a byte array to store the PKCS #7 message data + $PvData = New-Object -TypeName 'System.Byte[]' -ArgumentList $PcbData + + # Call the CryptMsgGetParam function again to get the PKCS #7 message data + $Return = [PKI.Crypt32]::CryptMsgGetParam($PhMsg, 29, 0, $PvData, [System.Management.Automation.PSReference]$PcbData) + + # Create a SignedCms object to decode the PKCS #7 message data + [System.Security.Cryptography.Pkcs.SignedCms]$SignedCms = New-Object -TypeName 'Security.Cryptography.Pkcs.SignedCms' + + # Decode the PKCS #7 message data and populate the SignedCms object properties + $SignedCms.Decode($PvData) + + # Get the first signer info object from the SignedCms object + $Infos = $SignedCms.SignerInfos[0] + + # Add some properties to the output object, such as TimeStamps, DigestAlgorithm and NestedSignature + $Output | Add-Member -MemberType NoteProperty -Name TimeStamps -Value $null + $Output | Add-Member -MemberType NoteProperty -Name DigestAlgorithm -Value $Infos.DigestAlgorithm.FriendlyName + + # Call the helper function to get the timestamps of the countersigners and assign it to the TimeStamps property + $Output.TimeStamps = Get-TimeStamps -SignerInfo $Infos + + # Check if there is a nested signature attribute in the signer info object by looking for the OID 1.3.6.1.4.1.311.2.4.1 + $second = $Infos.UnsignedAttributes | Where-Object -FilterScript { $_.Oid.Value -eq '1.3.6.1.4.1.311.2.4.1' } + + if ($Second) { + + # If there is a nested signature attribute + # Get the value of the nested signature attribute as a raw data byte array + $value = $Second.Values | Where-Object -FilterScript { $_.Oid.Value -eq '1.3.6.1.4.1.311.2.4.1' } + + # Create another SignedCms object to decode the nested signature data + [System.Security.Cryptography.Pkcs.SignedCms]$SignedCms2 = New-Object -TypeName 'Security.Cryptography.Pkcs.SignedCms' + + # Decode the nested signature data and populate the SignedCms object properties + $SignedCms2.Decode($value.RawData) + $Output | Add-Member -MemberType NoteProperty -Name NestedSignature -Value $null + + # Get the first signer info object from the nested signature SignedCms object + $Infos = $SignedCms2.SignerInfos[0] + + # Create a custom object with some properties of the nested signature, such as signer certificate, digest algorithm and timestamps + $Nested = New-Object -TypeName 'psobject' -Property @{ + SignerCertificate = $Infos.Certificate + DigestAlgorithm = $Infos.DigestAlgorithm.FriendlyName + TimeStamps = Get-TimeStamps -SignerInfo $Infos + } + # Assign the custom object to the NestedSignature property of the output object + $Output.NestedSignature = $Nested + } + # Return the output object with the added properties + $Output + + # Close the handles of the PKCS #7 message and the certificate store + [void][PKI.Crypt32]::CryptMsgClose($PhMsg) + [void][PKI.Crypt32]::CertCloseStore($PhCertStore, 0) + } + } + end {} +} + +Function Get-SignedFileCertificates { + <# + .SYNOPSIS + A function to get all the certificates from a signed file or a certificate object and output a Collection + .PARAMETER FilePath + Optional parameter, the function will get all the certificates from this file if this parameter is used + .PARAMETER X509Certificate2 + Optional parameter, the function will get all the certificates from this certificate object if this parameter is used + .INPUTS + System.String + System.Security.Cryptography.X509Certificates.X509Certificate2 + .OUTPUTS + System.Security.Cryptography.X509Certificates.X509Certificate2Collection + #> + [CmdletBinding()] + param ( + [Parameter()] + [System.String]$FilePath, + [Parameter(ValueFromPipeline = $true)] + [System.Security.Cryptography.X509Certificates.X509Certificate2]$X509Certificate2 + ) + + begin { + # Create an X509Certificate2Collection object + [System.Security.Cryptography.X509Certificates.X509Certificate2Collection]$CertCollection = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2Collection + } + + process { + # Check which parameter set is used + if ($FilePath) { + # If the FilePath parameter is used, import all the certificates from the file + $CertCollection.Import($FilePath, $null, 'DefaultKeySet') + } + elseif ($X509Certificate2) { + # If the CertObject parameter is used, add the certificate object to the collection + $CertCollection.Add($X509Certificate2) + } + } + + end { + # Return the collection + return $CertCollection + } +} + +Function Get-CertificateDetails { + <# + .SYNOPSIS + A function to detect Root, Intermediate and Leaf certificates + .INPUTS + System.String + System.Management.Automation.SwitchParameter + .OUTPUTS + System.Object[] + .PARAMETER FilePath + Path to a signed file + .PARAMETER X509Certificate2 + An X509Certificate2 object + .PARAMETER IntermediateOnly + Indicates that the function will only return the Intermediate certificate details + .PARAMETER LeafCertificate + Indicates that the function will only return the Leaf certificate details + .PARAMETER LeafCNOfTheNestedCertificate + This is used only for when -X509Certificate2 parameter is used, so that we can filter out the Leaf certificate and only get the Intermediate certificates at the end of this function + #> + [CmdletBinding()] + param ( + [Parameter(ParameterSetName = 'Based on File Path', Mandatory = $true)] + [System.String]$FilePath, + + [Parameter(ParameterSetName = 'Based on Certificate', Mandatory = $true)] + $X509Certificate2, + + [Parameter(ParameterSetName = 'Based on Certificate')] + [System.String]$LeafCNOfTheNestedCertificate, + + [Parameter(ParameterSetName = 'Based on File Path')] + [Parameter(ParameterSetName = 'Based on Certificate')] + [System.Management.Automation.SwitchParameter]$IntermediateOnly, + + [Parameter(ParameterSetName = 'Based on File Path')] + [Parameter(ParameterSetName = 'Based on Certificate')] + [System.Management.Automation.SwitchParameter]$LeafCertificate + ) + + # An array to hold objects + [System.Object[]]$Obj = @() + + if ($FilePath) { + # Get all the certificates from the file path using the Get-SignedFileCertificates function + $CertCollection = Get-SignedFileCertificates -FilePath $FilePath | Where-Object -FilterScript { $_.EnhancedKeyUsageList.FriendlyName -ne 'Time Stamping' } + } + else { + # The "| Where-Object -FilterScript {$_ -ne 0}" part is used to filter the output coming from Get-AuthenticodeSignatureEx function that gets nested certificate + $CertCollection = Get-SignedFileCertificates -X509Certificate2 $X509Certificate2 | Where-Object -FilterScript { $_.EnhancedKeyUsageList.FriendlyName -ne 'Time Stamping' } | Where-Object -FilterScript { $_ -ne 0 } + } + + # Loop through each certificate in the collection and call this function recursively with the certificate object as an input + foreach ($Cert in $CertCollection) { + + # Build the certificate chain + [System.Security.Cryptography.X509Certificates.X509Chain]$Chain = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Chain + + # Set the chain policy properties + $chain.ChainPolicy.RevocationMode = 'NoCheck' + $chain.ChainPolicy.RevocationFlag = 'EndCertificateOnly' + $chain.ChainPolicy.VerificationFlags = 'NoFlag' + + [void]$Chain.Build($Cert) + + # If AllCertificates is present, loop through all chain elements and display all certificates + foreach ($Element in $Chain.ChainElements) { + # Create a custom object with the certificate properties + + # Extract the data after CN= in the subject and issuer properties + # When a common name contains a comma ',' then it will automatically be wrapped around double quotes. E.g., "Skylum Software USA, Inc." + # The methods below are conditional regex. Different patterns are used based on the availability of at least one double quote in the CN field, indicating that it had comma in it so it had been enclosed with double quotes by system + + $Element.Certificate.Subject -match 'CN=(?.*?),.*' | Out-Null + [System.String]$SubjectCN = $matches['InitialRegexTest2'] -like '*"*' ? ($Element.Certificate.Subject -split 'CN="(.+?)"')[1] : $matches['InitialRegexTest2'] + + $Element.Certificate.Issuer -match 'CN=(?.*?),.*' | Out-Null + [System.String]$IssuerCN = $matches['InitialRegexTest3'] -like '*"*' ? ($Element.Certificate.Issuer -split 'CN="(.+?)"')[1] : $matches['InitialRegexTest3'] + + # Get the TBS value of the certificate using the Get-TBSCertificate function + [System.String]$TbsValue = Get-TBSCertificate -cert $Element.Certificate + # Create a custom object with the extracted properties and the TBS value + $Obj += [pscustomobject]@{ + SubjectCN = $SubjectCN + IssuerCN = $IssuerCN + NotAfter = $element.Certificate.NotAfter + TBSValue = $TbsValue + } + } + } + + if ($FilePath) { + + # The reason the commented code below is not used is because some files such as C:\Windows\System32\xcopy.exe or d3dcompiler_47.dll that are signed by Microsoft report a different Leaf certificate common name when queried using Get-AuthenticodeSignature + # (Get-AuthenticodeSignature -FilePath $FilePath).SignerCertificate.Subject -match 'CN=(?.*?),.*' | Out-Null + + [System.Security.Cryptography.X509Certificates.X509Certificate]$CertificateUsingAlternativeMethod = [System.Security.Cryptography.X509Certificates.X509Certificate]::CreateFromSignedFile($FilePath) + $CertificateUsingAlternativeMethod.Subject -match 'CN=(?.*?),.*' | Out-Null + + [System.String]$TestAgainst = $matches['InitialRegexTest4'] -like '*"*' ? ((Get-AuthenticodeSignature -FilePath $FilePath).SignerCertificate.Subject -split 'CN="(.+?)"')[1] : $matches['InitialRegexTest4'] + + if ($IntermediateOnly) { + + $FinalObj = $Obj | + Where-Object -FilterScript { $_.SubjectCN -ne $_.IssuerCN } | # To omit Root certificate from the result + Where-Object -FilterScript { $_.SubjectCN -ne $TestAgainst } | # To omit the Leaf certificate + Group-Object -Property TBSValue | ForEach-Object -Process { $_.Group[0] } # To make sure the output values are unique based on TBSValue property + + return [System.Object[]]$FinalObj + + } + elseif ($LeafCertificate) { + + $FinalObj = $Obj | + Where-Object -FilterScript { $_.SubjectCN -ne $_.IssuerCN } | # To omit Root certificate from the result + Where-Object -FilterScript { $_.SubjectCN -eq $TestAgainst } | # To get the Leaf certificate + Group-Object -Property TBSValue | ForEach-Object -Process { $_.Group[0] } # To make sure the output values are unique based on TBSValue property + + return [System.Object[]]$FinalObj + } + + } + # If nested certificate is being processed and X509Certificate2 object is passed + elseif ($X509Certificate2) { + + if ($IntermediateOnly) { + + $FinalObj = $Obj | + Where-Object -FilterScript { $_.SubjectCN -ne $_.IssuerCN } | # To omit Root certificate from the result + Where-Object -FilterScript { $_.SubjectCN -ne $LeafCNOfTheNestedCertificate } | # To omit the Leaf certificate + Group-Object -Property TBSValue | ForEach-Object -Process { $_.Group[0] } # To make sure the output values are unique based on TBSValue property + + return [System.Object[]]$FinalObj + + } + elseif ($LeafCertificate) { + + $FinalObj = $Obj | + Where-Object -FilterScript { $_.SubjectCN -ne $_.IssuerCN } | # To omit Root certificate from the result + Where-Object -FilterScript { $_.SubjectCN -eq $LeafCNOfTheNestedCertificate } | # To get the Leaf certificate + Group-Object -Property TBSValue | ForEach-Object -Process { $_.Group[0] } # To make sure the output values are unique based on TBSValue property + + return [System.Object[]]$FinalObj + } + } +} + +Function Compare-SignerAndCertificate { + <# + .SYNOPSIS + a function that takes WDAC XML policy file path and a Signed file path as inputs and compares the output of the Get-SignerInfo and Get-CertificateDetails functions + .INPUTS + System.String + .OUTPUTS + System.Object[] + .PARAMETER XmlFilePath + Path to a WDAC XML file + .PARAMETER SignedFilePath + Path to a signed file + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)][System.String]$XmlFilePath, + [Parameter(Mandatory = $true)][System.String]$SignedFilePath + ) + + # Get the signer information from the XML file path using the Get-SignerInfo function + [Signer[]]$SignerInfo = Get-SignerInfo -XmlFilePath $XmlFilePath + + # An array to store the details of the Primary certificate's Intermediate certificate(s) of the signed file + [System.Object[]]$PrimaryCertificateIntermediateDetails = @() + + # An array to store the details of the Nested certificate of the signed file + [System.Object[]]$NestedCertificateDetails = @() + + # An array to store the final comparison results of this function + [System.Object[]]$ComparisonResults = @() + + # Get the intermediate certificate(s) details of the Primary certficiate from the signed file using the Get-CertificateDetails function + [System.Object[]]$PrimaryCertificateIntermediateDetails = Get-CertificateDetails -IntermediateOnly -FilePath $SignedFilePath + + # Get the Nested (Secondary) certificate of the signed file, if any + [System.Management.Automation.Signature]$ExtraCertificateDetails = Get-AuthenticodeSignatureEx -FilePath $SignedFilePath + + # Extract the Nested (Secondary) certificate from the nested property, if any + $NestedCertificate = ($ExtraCertificateDetails).NestedSignature.SignerCertificate + + if ($null -ne [System.Security.Cryptography.X509Certificates.X509Certificate2]$NestedCertificate) { + # First get the CN of the leaf certificate of the nested Certificate + $NestedCertificate.Subject -match 'CN=(?.*?),.*' | Out-Null + $LeafCNOfTheNestedCertificate = $matches['InitialRegexTest1'] -like '*"*' ? ($NestedCertificate.Subject -split 'CN="(.+?)"')[1] : $matches['InitialRegexTest1'] + + # Send the nested certificate along with its Leaf certificate's CN to the Get-CertificateDetails function with -IntermediateOnly parameter in order to only get the intermediate certificates of the Nested certificate + $NestedCertificateDetails = Get-CertificateDetails -IntermediateOnly -X509Certificate2 $NestedCertificate -LeafCNOfTheNestedCertificate $LeafCNOfTheNestedCertificate + } + + # Get the leaf certificate details of the Main Certificate from the signed file path + [System.Object]$LeafCertificateDetails = Get-CertificateDetails -LeafCertificate -FilePath $SignedFilePath + + # Get the leaf certificate details of the Nested Certificate from the signed file path, if it exists + if ($null -ne $NestedCertificate) { + # append an X509Certificate2 object to the array + $NestedLeafCertificateDetails = Get-CertificateDetails -LeafCertificate -X509Certificate2 $NestedCertificate -LeafCNOfTheNestedCertificate $LeafCNOfTheNestedCertificate + } + + # Loop through each signer in the signer information array + foreach ($Signer in $SignerInfo) { + # Create a custom object to store the comparison result for this signer + $ComparisonResult = [pscustomobject]@{ + SignerID = $Signer.ID + SignerName = $Signer.Name + SignerCertRoot = $Signer.CertRoot + SignerCertPublisher = $Signer.CertPublisher + CertSubjectCN = $null + CertIssuerCN = $null + CertNotAfter = $null + CertTBSValue = $null + CertRootMatch = $false + CertNameMatch = $false + CertPublisherMatch = $false + FilePath = $SignedFilePath # Add the file path to the object + } + + # Loop through each certificate in the certificate details array of the Main Cert + foreach ($Certificate in $PrimaryCertificateIntermediateDetails) { + + # Check if the signer's CertRoot (referring to the TBS value in the xml file which belongs to an intermediate cert of the file)... + # ...matches the TBSValue of the file's certificate (TBS values of one of the intermediate certificates of the file since -IntermediateOnly parameter is used earlier and that's what FilePublisher level uses) + # So this checks to see if the Signer's TBS value in xml matches any of the TBS value(s) of the file's intermediate certificate(s), if it does, that means that file is allowed to run by the WDAC engine + + # Or if the Signer's CertRoot matches the TBS value of the file's primary certificate's Leaf Certificate + # This can happen with other rules than FilePublisher etc. + if (($Signer.CertRoot -eq $Certificate.TBSValue) -or ($Signer.CertRoot -eq $LeafCertificateDetails.TBSValue)) { + + # Assign the certificate properties to the comparison result object and set the CertRootMatch to true based on further conditions + $ComparisonResult.CertSubjectCN = $Certificate.SubjectCN + $ComparisonResult.CertIssuerCN = $Certificate.IssuerCN + $ComparisonResult.CertNotAfter = $Certificate.NotAfter + $ComparisonResult.CertTBSValue = $Certificate.TBSValue + + # if the signed file has nested certificate, only set a flag instead of setting the entire CertRootMatch property to true + if ($null -ne $NestedCertificate) { + $CertRootMatchPart1 = $true + } + else { + # meaning one of the TBS values of the file's intermediate certs or File's Primary Leaf Certificate's TBS value is in the xml file signers' TBS values + $ComparisonResult.CertRootMatch = $true + } + + # Check if the signer's name (Referring to the one in the XML file) matches the Intermediate certificate's SubjectCN or Leaf Certificate's SubjectCN + if (($Signer.Name -eq $Certificate.SubjectCN) -or ($Signer.Name -eq $LeafCertificateDetails.SubjectCN)) { + # Set the CertNameMatch to true + $ComparisonResult.CertNameMatch = $true # this should naturally be always true like the CertRootMatch because this is the CN of the same cert that has its TBS value in the xml file in signers + } + + # Check if the signer's CertPublisher (aka Leaf Certificate's CN used in the xml policy) matches the leaf certificate's SubjectCN (of the file) + if ($Signer.CertPublisher -eq $LeafCertificateDetails.SubjectCN) { + + # if the signed file has nested certificate, only set a flag instead of setting the entire CertPublisherMatch property to true + if ($null -ne $NestedCertificate) { + $CertPublisherMatchPart1 = $true + } + else { + $ComparisonResult.CertPublisherMatch = $true + } + } + + # Break out of the inner loop whether we found a match for this signer or not + break + } + } + + # Nested Certificate TBS processing, if it exists + if ($null -ne $NestedCertificate) { + + # Loop through each certificate in the NESTED certificate details array + foreach ($Certificate in $NestedCertificateDetails) { + + # Check if the signer's CertRoot (referring to the TBS value in the xml file which belongs to an intermediate cert of the file)... + # ...matches the TBSValue of the file's certificate (TBS values of one of the intermediate certificates of the file since -IntermediateOnly parameter is used earlier and that's what FilePublisher level uses) + # So this checks to see if the Signer's TBS value in xml matches any of the TBS value(s) of the file's intermediate certificate(s), if yes, that means that file is allowed to run by WDAC engine + if ($Signer.CertRoot -eq $Certificate.TBSValue) { + + # Assign the certificate properties to the comparison result object and set the CertRootMatch to true + $ComparisonResult.CertSubjectCN = $Certificate.SubjectCN + $ComparisonResult.CertIssuerCN = $Certificate.IssuerCN + $ComparisonResult.CertNotAfter = $Certificate.NotAfter + $ComparisonResult.CertTBSValue = $Certificate.TBSValue + + # When file has nested signature, only set a flag instead of setting the entire property to true + $CertRootMatchPart2 = $true + + # Check if the signer's Name matches the Intermediate certificate's SubjectCN + if ($Signer.Name -eq $Certificate.SubjectCN) { + # Set the CertNameMatch to true + $ComparisonResult.CertNameMatch = $true # this should naturally be always true like the CertRootMatch because this is the CN of the same cert that has its TBS value in the xml file in signers + } + + # Check if the signer's CertPublisher (aka Leaf Certificate's CN used in the xml policy) matches the leaf certificate's SubjectCN (of the file) + if ($Signer.CertPublisher -eq $LeafCNOfTheNestedCertificate) { + # If yes, set the CertPublisherMatch to true for this comparison result object + $CertPublisherMatchPart2 = $true + } + + # Break out of the inner loop whether we found a match for this signer or not + break + } + } + } + + # if the signed file has nested certificate + if ($null -ne $NestedCertificate) { + + # check if both of the file's certificates (Nested and Main) are available in the Signers in xml policy + if (($CertRootMatchPart1 -eq $true) -and ($CertRootMatchPart2 -eq $true)) { + $ComparisonResult.CertRootMatch = $true # meaning all of the TBS values of the double signed file's intermediate certificates exists in the xml file's signers' TBS values + } + else { + $ComparisonResult.CertRootMatch = $false + } + + # check if Lean certificate CN of both of the file's certificates (Nested and Main) are available in the Signers in xml policy + if (($CertPublisherMatchPart1 -eq $true) -and ($CertPublisherMatchPart2 -eq $true)) { + $ComparisonResult.CertPublisherMatch = $true + } + else { + $ComparisonResult.CertPublisherMatch = $false + } + } + + # Add the comparison result object to the comparison results array + [System.Object[]]$ComparisonResults += $ComparisonResult + } + + # Return the comparison results array + return $ComparisonResults +} + +Function Get-FileRuleOutput { + <# + .SYNOPSIS + a function to load an xml file and create an output array of custom objects that contain the file rules that are based on file hashes + .PARAMETER XmlPath + Path to the XML file that user selected for WDAC simulation + .INPUTS + System.IO.FileInfo + .OUTPUTS + System.Object[] + #> + [CmdletBinding()] + param( + [parameter(Mandatory = $true)] + [System.IO.FileInfo]$XmlPath + ) + + # Load the xml file into a variable + $Xml = [System.Xml.XmlDocument](Get-Content -Path $XmlPath) + + # Create an empty array to store the output + [System.Object[]]$OutputHashInfoProcessing = @() + + # Loop through each file rule in the xml file + foreach ($FileRule in $Xml.SiPolicy.FileRules.Allow) { + + # Extract the hash value from the Hash attribute + [System.String]$Hashvalue = $FileRule.Hash + + # Extract the hash type from the FriendlyName attribute using regex + [System.String]$HashType = $FileRule.FriendlyName -replace '.* (Hash (Sha1|Sha256|Page Sha1|Page Sha256|Authenticode SIP Sha256))$', '$1' + + # Extract the file path from the FriendlyName attribute using regex + [System.IO.FileInfo]$FilePathForHash = $FileRule.FriendlyName -replace ' (Hash (Sha1|Sha256|Page Sha1|Page Sha256|Authenticode SIP Sha256))$', '' + + # Create a custom object with the three properties + $Object = [PSCustomObject]@{ + HashValue = $Hashvalue + HashType = $HashType + FilePathForHash = $FilePathForHash + } + + # Add the object to the output array if it is not a duplicate hash value + if ($OutputHashInfoProcessing.HashValue -notcontains $Hashvalue) { + $OutputHashInfoProcessing += $Object + } + } + + # Only show the Authenticode Hash SHA256 + [System.Object[]]$OutputHashInfoProcessing = $OutputHashInfoProcessing | Where-Object -FilterScript { $_.hashtype -eq 'Hash Sha256' } + + # Return the output array + return $OutputHashInfoProcessing +} diff --git a/WDACConfig/WDAC Policies/DefaultWindows_Enforced_Kernel.xml b/WDACConfig/WDACConfig Module Files/Resources/WDAC Policies/DefaultWindows_Enforced_Kernel.xml similarity index 99% rename from WDACConfig/WDAC Policies/DefaultWindows_Enforced_Kernel.xml rename to WDACConfig/WDACConfig Module Files/Resources/WDAC Policies/DefaultWindows_Enforced_Kernel.xml index b8838c2e3..757a47255 100644 --- a/WDACConfig/WDAC Policies/DefaultWindows_Enforced_Kernel.xml +++ b/WDACConfig/WDACConfig Module Files/Resources/WDAC Policies/DefaultWindows_Enforced_Kernel.xml @@ -122,11 +122,11 @@ - + + + +$RulesContent + + + + + + + + + + + +$RuleRefsContent + + + + + + +0 +{B163125F-E30A-43FC-ABEC-E30B4EE88FA8} +{B163125F-E30A-43FC-ABEC-E30B4EE88FA8} + +"@ + return $EmptyPolicy +} + +# Export external facing functions only, prevent internal functions from getting exported +Export-ModuleMember -Function 'New-EmptyPolicy' diff --git a/WDACConfig/WDACConfig Module Files/Shared/New-SnapBackGuarantee.psm1 b/WDACConfig/WDACConfig Module Files/Shared/New-SnapBackGuarantee.psm1 new file mode 100644 index 000000000..139cd8932 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/Shared/New-SnapBackGuarantee.psm1 @@ -0,0 +1,75 @@ +Function New-SnapBackGuarantee { + <# + .SYNOPSIS + A function that arms the system with a snapback guarantee in case of a reboot during the base policy enforcement process. + This will help prevent the system from being stuck in audit mode in case of a power outage or a reboot during the base policy enforcement process. + .PARAMETER Location + The directory location of the base policy file that will be enforced. + .INPUTS + System.IO.DirectoryInfo + .OUTPUTS + System.Void + #> + [CmdletBinding()] + Param( + [parameter(Mandatory = $true)] + [System.IO.DirectoryInfo]$Location + ) + + # Using CMD and Scheduled Task Method + + Write-Verbose -Message 'Creating the scheduled task for Snap Back Guarantee' + + # Creating the scheduled task action + [Microsoft.Management.Infrastructure.CimInstance]$TaskAction = New-ScheduledTaskAction -Execute 'cmd.exe' -Argument '/c C:\EnforcedModeSnapBack.cmd' + # Creating the scheduled task trigger + [Microsoft.Management.Infrastructure.CimInstance]$TaskTrigger = New-ScheduledTaskTrigger -AtLogOn + # Creating the scheduled task principal, will run the task under the system account using its well-known SID + [Microsoft.Management.Infrastructure.CimInstance]$Principal = New-ScheduledTaskPrincipal -UserId 'S-1-5-18' -RunLevel Highest + # Setting the task to run with the highest priority. This is to ensure that the task runs as soon as possible after the reboot. It runs even on logon screen before user logs on too. + [Microsoft.Management.Infrastructure.CimInstance]$TaskSettings = New-ScheduledTaskSettingsSet -Hidden -Compatibility Win8 -DontStopIfGoingOnBatteries -Priority 0 -AllowStartIfOnBatteries + # Register the scheduled task + Register-ScheduledTask -TaskName 'EnforcedModeSnapBack' -Action $TaskAction -Trigger $TaskTrigger -Principal $Principal -Settings $TaskSettings -Force | Out-Null + + # Saving the EnforcedModeSnapBack.cmd file to the root of C drive + # It contains the instructions to revert the base policy to enforced mode + Set-Content -Force 'C:\EnforcedModeSnapBack.cmd' -Value @" +REM Deploying the Enforced Mode SnapBack CI Policy +CiTool --update-policy "$Location\EnforcedMode.cip" -json +REM Deleting the Scheduled task responsible for running this CMD file +schtasks /Delete /TN EnforcedModeSnapBack /F +REM Deleting the CI Policy file +del /f /q "$Location\EnforcedMode.cip" +REM Deleting this CMD file itself +del "%~f0" +"@ + +} + +# Export external facing functions only, prevent internal functions from getting exported +Export-ModuleMember -Function 'New-SnapBackGuarantee' + +# An alternative way to do this which is less reliable because RunOnce key can be deleted by 3rd party programs during installation etc. +<# + # Using PowerShell and RunOnce Method + + # Defining the registry path for RunOnce key + [System.String]$RegistryPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' + # Defining the command that will be executed by the RunOnce key in case of a reboot + [System.String]$Command = @" +CiTool --update-policy "$((Get-Location).Path)\EnforcedMode.cip" -json; Remove-Item -Path "$((Get-Location).Path)\EnforcedMode.cip" -Force +"@ + # Saving the command to a file that will be executed by the RunOnce key in case of a reboot + $Command | Out-File -FilePath 'C:\EnforcedModeSnapBack.ps1' -Force + # Saving the command that runs the EnforcedModeSnapBack.ps1 file in the next reboot to the RunOnce key + New-ItemProperty -Path $RegistryPath -Name '*CIPolicySnapBack' -Value "powershell.exe -WindowStyle `"Hidden`" -ExecutionPolicy `"Bypass`" -Command `"& {&`"C:\EnforcedModeSnapBack.ps1`";Remove-Item -Path 'C:\EnforcedModeSnapBack.ps1' -Force}`"" -PropertyType String -Force | Out-Null +#> + +# If the alternative way is used, this should be added to the Finally block under the: +# Enforced Mode Snapback removal after base policy has already been successfully re-enforced + +<# +# For PowerShell Method +# Remove-Item -Path 'C:\EnforcedModeSnapBack.ps1' -Force +# Remove-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' -Name '*CIPolicySnapBack' -Force +#> \ No newline at end of file diff --git a/WDACConfig/WDACConfig Module Files/Shared/Set-LogSize.psm1 b/WDACConfig/WDACConfig Module Files/Shared/Set-LogSize.psm1 new file mode 100644 index 000000000..7e1325c33 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/Shared/Set-LogSize.psm1 @@ -0,0 +1,28 @@ +Function Set-LogSize { + <# + .SYNOPSIS + Increase Code Integrity Operational Event Logs size from the default 1MB to user defined size + .INPUTS + System.Int64 + .OUTPUTS + System.Void + .PARAMETER LogSize + Size of the Code Integrity Operational Event Log + #> + [CmdletBinding()] + param ( + [System.Int64]$LogSize + ) + # Importing the $PSDefaultParameterValues to the current session, prior to everything else + . "$ModuleRootPath\CoreExt\PSDefaultParameterValues.ps1" + + Write-Verbose -Message "Setting 'Microsoft-Windows-CodeIntegrity/Operational' log size to $LogSize" + [System.String]$LogName = 'Microsoft-Windows-CodeIntegrity/Operational' + [System.Diagnostics.Eventing.Reader.EventLogConfiguration]$Log = New-Object -TypeName System.Diagnostics.Eventing.Reader.EventLogConfiguration -ArgumentList $LogName + $Log.MaximumSizeInBytes = $LogSize + $Log.IsEnabled = $true + $Log.SaveChanges() +} + +# Export external facing functions only, prevent internal functions from getting exported +Export-ModuleMember -Function 'Set-LogSize' diff --git a/WDACConfig/WDACConfig Module Files/Shared/Test-FilePath.psm1 b/WDACConfig/WDACConfig Module Files/Shared/Test-FilePath.psm1 new file mode 100644 index 000000000..6382d2a5d --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/Shared/Test-FilePath.psm1 @@ -0,0 +1,64 @@ +Function Test-FilePath { + <# + .SYNOPSIS + function that takes 2 arrays, one contains file paths and the other contains folder paths. It checks them and shows file paths + that are not in any of the folder paths. Performs this check recursively too so works if the filepath is in a sub-directory of a folder path + .INPUTS + System.String[] + .OUTPUTS + System.String[] + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [System.String[]]$FilePath, + [Parameter(Mandatory = $true)] + [System.String[]]$DirectoryPath + ) + # Importing the $PSDefaultParameterValues to the current session, prior to everything else + . "$ModuleRootPath\CoreExt\PSDefaultParameterValues.ps1" + + # Loop through each file path + foreach ($file in $FilePath) { + # Check if the file path is valid + if (Test-Path -Path $file -PathType 'Leaf') { + # Get the full path of the file + $FileFullPath = Resolve-Path -Path $file + + # Initialize a variable to store the result + [System.Boolean]$Result = $false + + # Loop through each directory path + foreach ($Directory in $DirectoryPath) { + # Check if the directory path is valid + if (Test-Path -Path $Directory -PathType 'Container') { + # Get the full path of the directory + $DirectoryFullPath = Resolve-Path -Path $Directory + + # Check if the file path starts with the directory path + if ($FileFullPath -like "$DirectoryFullPath\*") { + # The file is inside the directory or its sub-directories + $Result = $true + break # Exit the inner loop + } + } + else { + # The directory path is not valid + Write-Warning -Message "The directory path '$Directory' is not valid." + } + } + + # Output the file path if it is not inside any of the directory paths + if (-not $Result) { + Write-Output -InputObject $FileFullPath + } + } + else { + # The file path is not valid + Write-Warning -Message "The file path '$file' is not valid." + } + } +} + +# Export external facing functions only, prevent internal functions from getting exported +Export-ModuleMember -Function 'Test-FilePath' diff --git a/WDACConfig/WDACConfig Module Files/Shared/Update-self.psm1 b/WDACConfig/WDACConfig Module Files/Shared/Update-self.psm1 new file mode 100644 index 000000000..9e5234dfb --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/Shared/Update-self.psm1 @@ -0,0 +1,86 @@ +Function Update-self { + <# + .SYNOPSIS + Make sure the latest version of the module is installed and if not, automatically update it, clean up any old versions + .INPUTS + None. You cannot pipe objects to this function. + .OUTPUTS + System.Void + #> + [CmdletBinding()] + param() + # Importing the $PSDefaultParameterValues to the current session, prior to everything else + . "$ModuleRootPath\CoreExt\PSDefaultParameterValues.ps1" + # Importing the required sub-modules + Import-Module -FullyQualifiedName "$ModuleRootPath\Shared\Write-ColorfulText.psm1" -Force + + try { + # Get the last update check time + Write-Verbose -Message 'Getting the last update check time' + [System.DateTime]$UserConfigDate = Get-CommonWDACConfig -LastUpdateCheck + } + catch { + # If the User Config file doesn't exist then set this flag to perform online update check + Write-Verbose -Message 'No LastUpdateCheck was found in the user configurations, will perform online update check' + [System.Boolean]$PerformOnlineUpdateCheck = $true + } + + # Ensure these are run only if the User Config file exists and contains a date for last update check + if (!$PerformOnlineUpdateCheck) { + # Get the current time + [System.DateTime]$CurrentDateTime = Get-Date + # Calculate the minutes elapsed since the last online update check + [System.Int64]$TimeDiff = ($CurrentDateTime - $UserConfigDate).TotalMinutes + } + + # Only check for updates if the last attempt occured more than 10 minutes ago or the User Config file for last update check doesn't exist + # This prevents the module from constantly doing an update check by fetching the version file from GitHub + if (($TimeDiff -gt 10) -or $PerformOnlineUpdateCheck) { + + Write-Verbose -Message 'Performing online update check' + + [System.Version]$CurrentVersion = (Test-ModuleManifest -Path "$ModuleRootPath\WDACConfig.psd1").Version.ToString() + try { + # First try the GitHub source + [System.Version]$LatestVersion = Invoke-RestMethod -Uri 'https://raw.githubusercontent.com/HotCakeX/Harden-Windows-Security/main/WDACConfig/version.txt' -ProgressAction SilentlyContinue + } + catch { + try { + # If GitHub source is unavailable, use the Azure DevOps source + [System.Version]$LatestVersion = Invoke-RestMethod -Uri 'https://dev.azure.com/SpyNetGirl/011c178a-7b92-462b-bd23-2c014528a67e/_apis/git/repositories/5304fef0-07c0-4821-a613-79c01fb75657/items?path=/WDACConfig/version.txt' -ProgressAction SilentlyContinue + } + catch { + Throw [System.Security.VerificationException] 'Could not verify if the latest version of the module is installed, please check your Internet connection. You can optionally bypass the online check by using -SkipVersionCheck parameter.' + } + } + if ($CurrentVersion -lt $LatestVersion) { + Write-ColorfulText -Color Pink -InputText "The currently installed module's version is $CurrentVersion while the latest version is $LatestVersion - Auto Updating the module... 💓" + Remove-Module -Name 'WDACConfig' -Force + # Do this if the module was installed properly using Install-module cmdlet + try { + Uninstall-Module -Name 'WDACConfig' -AllVersions -Force -ErrorAction Stop + Install-Module -Name 'WDACConfig' -RequiredVersion $LatestVersion -Force + Import-Module -Name 'WDACConfig' -RequiredVersion $LatestVersion -Force -Global + } + # Do this if module files/folder was just copied to Documents folder and not properly installed - Should rarely happen + catch { + Install-Module -Name 'WDACConfig' -RequiredVersion $LatestVersion -Force + Import-Module -Name 'WDACConfig' -RequiredVersion $LatestVersion -Force -Global + } + # Make sure the old version isn't run after update + Write-Output -InputObject "$($PSStyle.Foreground.FromRGB(152,255,152))Update successful, please run the cmdlet again.$($PSStyle.Reset)" + break + return + } + + # Reset the last update timer to the current time + Write-Verbose -Message 'Resetting the last update timer to the current time' + Set-CommonWDACConfig -LastUpdateCheck $(Get-Date) | Out-Null + } + else { + Write-Verbose -Message "Skipping online update check because the last update check was performed $TimeDiff minutes ago" + } +} + +# Export external facing functions only, prevent internal functions from getting exported +Export-ModuleMember -Function 'Update-self' diff --git a/WDACConfig/WDACConfig Module Files/Shared/Write-ColorfulText.psm1 b/WDACConfig/WDACConfig Module Files/Shared/Write-ColorfulText.psm1 new file mode 100644 index 000000000..908b03d66 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/Shared/Write-ColorfulText.psm1 @@ -0,0 +1,69 @@ +Function Write-ColorfulText { + <# + .SYNOPSIS + Function to write modern colorful text + .INPUTS + System.String + .OUTPUTS + System.String + #> + [CmdletBinding()] + [Alias('WCT')] + + param ( + [Parameter(Mandatory = $True)] + [Alias('C')] + [ValidateSet('Fuchsia', 'Orange', 'NeonGreen', 'MintGreen', 'PinkBoldBlink', 'PinkBold', 'Rainbow' , 'Gold', 'TeaGreen', 'Lavender', 'PinkNoNewLine', 'VioletNoNewLine', 'Violet', 'Pink', 'HotPink')] + [System.String]$Color, + + [parameter(Mandatory = $True)] + [Alias('I')] + [System.String]$InputText + ) + # Importing the $PSDefaultParameterValues to the current session, prior to everything else + . "$ModuleRootPath\CoreExt\PSDefaultParameterValues.ps1" + + switch ($Color) { + 'Fuchsia' { Write-Host "$($PSStyle.Foreground.FromRGB(236,68,155))$InputText$($PSStyle.Reset)"; break } + 'Orange' { Write-Host "$($PSStyle.Foreground.FromRGB(255,165,0))$InputText$($PSStyle.Reset)"; break } + 'NeonGreen' { Write-Host "$($PSStyle.Foreground.FromRGB(153,244,67))$InputText$($PSStyle.Reset)"; break } + 'MintGreen' { Write-Host "$($PSStyle.Foreground.FromRGB(152,255,152))$InputText$($PSStyle.Reset)"; break } + 'PinkBoldBlink' { Write-Host "$($PSStyle.Foreground.FromRgb(255,192,203))$($PSStyle.Bold)$($PSStyle.Blink)$InputText$($PSStyle.Reset)"; break } + 'PinkBold' { Write-Host "$($PSStyle.Foreground.FromRgb(255,192,203))$($PSStyle.Bold)$($PSStyle.Reverse)$InputText$($PSStyle.Reset)"; break } + 'Gold' { Write-Host "$($PSStyle.Foreground.FromRgb(255,215,0))$InputText$($PSStyle.Reset)"; break } + 'VioletNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRGB(153,0,255))$InputText$($PSStyle.Reset)" -NoNewline; break } + 'PinkNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRGB(255,0,230))$InputText$($PSStyle.Reset)" -NoNewline; break } + 'Violet' { Write-Host "$($PSStyle.Foreground.FromRGB(153,0,255))$InputText$($PSStyle.Reset)"; break } + 'Pink' { Write-Host "$($PSStyle.Foreground.FromRGB(255,0,230))$InputText$($PSStyle.Reset)"; break } + 'Lavender' { Write-Host "$($PSStyle.Foreground.FromRgb(255,179,255))$InputText$($PSStyle.Reset)"; break } + 'TeaGreen' { Write-Host "$($PSStyle.Foreground.FromRgb(133, 222, 119))$InputText$($PSStyle.Reset)"; break } + 'HotPink' { Write-Host "$($PSStyle.Foreground.FromRGB(255,105,180))$InputText$($PSStyle.Reset)"; break } + 'Rainbow' { + [System.Object[]]$Colors = @( + [System.Drawing.Color]::Pink, + [System.Drawing.Color]::HotPink, + [System.Drawing.Color]::SkyBlue, + [System.Drawing.Color]::HotPink, + [System.Drawing.Color]::SkyBlue, + [System.Drawing.Color]::LightSkyBlue, + [System.Drawing.Color]::LightGreen, + [System.Drawing.Color]::Coral, + [System.Drawing.Color]::Plum, + [System.Drawing.Color]::Gold + ) + + [System.String]$Output = '' + for ($I = 0; $I -lt $InputText.Length; $I++) { + $Color = $Colors[$I % $Colors.Length] + $Output += "$($PSStyle.Foreground.FromRGB($Color.R, $Color.G, $Color.B))$($PSStyle.Blink)$($InputText[$I])$($PSStyle.BlinkOff)$($PSStyle.Reset)" + } + Write-Output $Output + break + } + + Default { Throw 'Unspecified Color' } + } +} + +# Export external facing functions only, prevent internal functions from getting exported +Export-ModuleMember -Function 'Write-ColorfulText' diff --git a/WDACConfig/WDACConfig.psd1 b/WDACConfig/WDACConfig Module Files/WDACConfig.psd1 similarity index 77% rename from WDACConfig/WDACConfig.psd1 rename to WDACConfig/WDACConfig Module Files/WDACConfig.psd1 index 77365bf97..ce73f0e0c 100644 --- a/WDACConfig/WDACConfig.psd1 +++ b/WDACConfig/WDACConfig Module Files/WDACConfig.psd1 @@ -1,14 +1,10 @@ -# Module manifest for module 'WDACConfig' -# Generated by: HotCakeX -# Generated on: 4/2/2023 - @{ # Script module or binary module file associated with this manifest. - # RootModule = "" + RootModule = 'WDACConfig.psm1' # Version number of this module. - ModuleVersion = '0.2.6' + ModuleVersion = '0.2.7' # Supported PSEditions CompatiblePSEditions = @('Core') @@ -80,7 +76,7 @@ To get help and syntax on PowerShell console, type: '@ # Minimum version of the PowerShell engine required by this module - PowerShellVersion = '7.3.6' + PowerShellVersion = '7.4.0' # Name of the PowerShell host required by this module # PowerShellHostName = '' @@ -104,7 +100,7 @@ To get help and syntax on PowerShell console, type: # RequiredAssemblies = @() # Script files (.ps1) that are run in the caller's environment prior to importing this module. - # ScriptsToProcess = @() + ScriptsToProcess = @('Preloader.ps1') # Type files (.ps1xml) to be loaded when importing this module # TypesToProcess = @() @@ -113,19 +109,19 @@ To get help and syntax on PowerShell console, type: # FormatsToProcess = @() # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess - NestedModules = @('New-WDACConfig.psm1', - 'Remove-WDACConfig.psm1', - 'Deploy-SignedWDACConfig.psm1', - 'Confirm-WDACConfig.psm1', - 'Edit-WDACConfig.psm1', - 'Edit-SignedWDACConfig.psm1', - 'New-SupplementalWDACConfig.psm1', - 'New-DenyWDACConfig.psm1', - 'Set-CommonWDACConfig.psm1', - 'New-KernelModeWDACConfig.psm1', - 'Invoke-WDACSimulation.psm1', - 'Get-CommonWDACConfig.psm1', - 'Remove-CommonWDACConfig.psm1') + NestedModules = @('Core\New-WDACConfig.psm1', + 'Core\Remove-WDACConfig.psm1', + 'Core\Deploy-SignedWDACConfig.psm1', + 'Core\Confirm-WDACConfig.psm1', + 'Core\Edit-WDACConfig.psm1', + 'Core\Edit-SignedWDACConfig.psm1', + 'Core\New-SupplementalWDACConfig.psm1', + 'Core\New-DenyWDACConfig.psm1', + 'Core\Set-CommonWDACConfig.psm1', + 'Core\New-KernelModeWDACConfig.psm1', + 'Core\Invoke-WDACSimulation.psm1', + 'Core\Get-CommonWDACConfig.psm1', + 'Core\Remove-CommonWDACConfig.psm1') # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = @('New-WDACConfig', @@ -170,25 +166,43 @@ To get help and syntax on PowerShell console, type: # ModuleList = @() # List of all files packaged with this module - FileList = @('WDACConfig.psd1', - 'New-WDACConfig.psm1', - 'Deploy-SignedWDACConfig.psm1', - 'Remove-WDACConfig.psm1', - 'Confirm-WDACConfig.psm1', - 'Edit-WDACConfig.psm1', - 'Edit-SignedWDACConfig.psm1', - 'New-SupplementalWDACConfig.psm1', - 'Resources.ps1', - 'ArgumentCompleters.ps1', - 'New-DenyWDACConfig.psm1', - 'Set-CommonWDACConfig.psm1', - 'New-KernelModeWDACConfig.psm1', - 'WDAC Policies\DefaultWindows_Enforced_Kernel.xml', - 'WDAC Policies\DefaultWindows_Enforced_Kernel_NoFlights.xml', - 'Invoke-WDACSimulation.psm1', - 'Resources2.ps1', - 'Get-CommonWDACConfig.psm1', - 'Remove-CommonWDACConfig.psm1') + FileList = @( + 'WDACConfig.psd1', + 'WDACConfig.psm1', + 'Preloader.ps1', + 'Core\New-WDACConfig.psm1', + 'Core\Deploy-SignedWDACConfig.psm1', + 'Core\Remove-WDACConfig.psm1', + 'Core\Confirm-WDACConfig.psm1', + 'Core\Edit-WDACConfig.psm1', + 'Core\Edit-SignedWDACConfig.psm1', + 'Core\New-SupplementalWDACConfig.psm1', + 'Core\New-DenyWDACConfig.psm1', + 'Core\Set-CommonWDACConfig.psm1', + 'Core\New-KernelModeWDACConfig.psm1', + 'Core\Invoke-WDACSimulation.psm1', + 'Core\Get-CommonWDACConfig.psm1', + 'Core\Remove-CommonWDACConfig.psm1', + 'CoreExt\PSDefaultParameterValues.ps1', + 'Resources\Resources2.ps1', + 'Resources\ArgumentCompleters.ps1' + 'Resources\WDAC Policies\DefaultWindows_Enforced_Kernel.xml', + 'Resources\WDAC Policies\DefaultWindows_Enforced_Kernel_NoFlights.xml', + 'Shared\Confirm-CertCN.psm1', + 'Shared\Get-AuditEventLogsProcessing.psm1', + 'Shared\Get-BlockRulesMeta.psm1', + 'Shared\Get-FileRules.psm1', + 'Shared\Get-GlobalRootDrives.psm1', + 'Shared\Get-RuleRefs.psm1', + 'Shared\Get-SignTool.psm1', + 'Shared\Move-UserModeToKernelMode.psm1', + 'Shared\New-EmptyPolicy.psm1', + 'Shared\Set-LogSize.psm1', + 'Shared\Test-FilePath.psm1', + 'Shared\Update-self.psm1', + 'Shared\Write-ColorfulText.psm1', + 'Shared\New-SnapBackGuarantee.psm1' + ) # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. PrivateData = @{ diff --git a/WDACConfig/WDACConfig Module Files/WDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/WDACConfig.psm1 new file mode 100644 index 000000000..8b7f651ae --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/WDACConfig.psm1 @@ -0,0 +1,5 @@ +# Stopping the module process if any error occurs +$global:ErrorActionPreference = 'Stop' + +# Set PSReadline tab completion to complete menu for easier access to available parameters - Only for the current session +Set-PSReadLineKeyHandler -Key 'Tab' -Function 'MenuComplete' diff --git a/WDACConfig/WDACConfig.code-workspace b/WDACConfig/WDACConfig.code-workspace new file mode 100644 index 000000000..eb9780719 --- /dev/null +++ b/WDACConfig/WDACConfig.code-workspace @@ -0,0 +1,31 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + "powershell.codeFormatting.autoCorrectAliases": true, + "powershell.codeFormatting.avoidSemicolonsAsLineTerminators": true, + "powershell.codeFormatting.trimWhitespaceAroundPipe": true, + "powershell.codeFormatting.useConstantStrings": true, + "powershell.codeFormatting.useCorrectCasing": true, + "powershell.codeFormatting.whitespaceBetweenParameters": true + }, + "extensions": { + "recommendations": [ + "ms-vscode.powershell" + ] + }, + "launch": { + "version": "0.2.0", + "configurations": [ + { + "name": "Debug", + "request": "launch", + "type": "PowerShell", + "script": "${workspaceFolder}/Utilities/Invoke-WDACConfig.ps1" + } + ] + } +} \ No newline at end of file diff --git a/WDACConfig/version.txt b/WDACConfig/version.txt index a53741c09..967b33ffb 100644 --- a/WDACConfig/version.txt +++ b/WDACConfig/version.txt @@ -1 +1 @@ -0.2.6 \ No newline at end of file +0.2.7 \ No newline at end of file