From d7d7a53def77f087c6b53ffbf1b178331bdc45f7 Mon Sep 17 00:00:00 2001 From: Violet Hansen Date: Thu, 19 Sep 2024 15:51:23 +0300 Subject: [PATCH 1/5] WinUI3 GUI initial WinUI3 GUI initial --- CONTRIBUTING.md => .github/CONTRIBUTING.md | 0 .gitignore | 7 +- Harden-Windows-Security Module/.editorconfig | 7 +- ...den-Windows-Security Module.code-workspace | 8 +- .../ICON-FULLSIZE.png | Bin 0 -> 105815 bytes .../ICON-SVG-ADVANCED.svg | 77 ++++ .../ICON-SVG-SIMPLIFIED.svg | 1 + .../MSFTDefender_ScheduledTask.cs | 46 +- WDACConfig/.editorconfig | 149 ++++++- WDACConfig/ICON-SVG-ADVANCED.svg | 23 + WDACConfig/ICON-SVG-Simplified.svg | 1 + WDACConfig/Icon smaller.png | Bin 2212 -> 1099 bytes WDACConfig/IconFullSize.png | Bin 0 -> 66358 bytes WDACConfig/Program.cs | 22 +- WDACConfig/Utilities/Hashes.csv | 119 ------ .../ArgumentCompleterAttribute.cs | 39 +- .../C#/ArgumentCompleters/BasePolicyNamez.cs | 45 +- .../C#/ArgumentCompleters/CertCNz.cs | 4 +- .../C#/ArgumentCompleters/RuleOptionsx.cs | 19 +- .../C#/ArgumentCompleters/ScanLevelz.cs | 6 +- .../C#/Custom Types/AuthenticodePageHashes.cs | 20 - .../Custom Types/CertificateDetailsCreator.cs | 21 - .../Custom Types/CertificateSignerCreator.cs | 17 - .../C#/Custom Types/ChainElement.cs | 35 -- .../C#/Custom Types/ChainPackage.cs | 27 -- .../C#/Custom Types/FileBasedInfoPackage.cs | 21 - .../C#/Custom Types/HashCreator.cs | 21 - .../C#/Custom Types/OpusSigner.cs | 17 - .../C#/Custom Types/Signer.cs | 42 -- .../C#/Custom Types/SimulationInput.cs | 24 -- .../C#/Custom Types/SimulationOutput.cs | 46 -- .../C#/Functions/AuthenticodeHashCalc.cs | 127 ------ .../C#/Functions/CodeIntegritySigner.cs | 62 --- .../C#/Functions/DebugLogger.cs | 35 -- .../C#/Functions/EventLogUtility.cs | 62 --- .../VerboseLogger.cs => Logging/Logger.cs} | 14 +- .../LoggerInitializer.cs | 16 +- .../Main Cmdlets/AssertWDACConfigIntegrity.cs | 222 ++++++++++ .../C#/Main Cmdlets/GetCIPolicySetting.cs | 81 ++++ .../C#/Main Cmdlets/GetCiFileHashes.cs | 89 ++++ .../TestCiPolicy.cs | 18 +- .../AllCertificatesGrabber.cs | 99 ++--- .../CIPolicyVersion.cs | 2 +- .../CertificateHelper.cs | 76 +--- .../CiPolicyUtility.cs | 26 +- .../C#/Other Functions/CodeIntegritySigner.cs | 68 +++ .../Crypt32CertCN.cs | 6 +- .../DirectorySelector.cs | 48 +-- .../DriveLetterMapper.cs | 10 +- .../DriversBlockRulesFetcher.cs | 12 +- .../EditGUIDs.cs | 4 +- .../C#/Other Functions/EventLogUtility.cs | 60 +++ .../FileDirectoryPathComparer.cs | 4 +- .../GetExtendedFileAttrib.cs | 0 .../GetFilesFast.cs | 12 +- .../GetOpusData.cs | 4 +- .../Initializer.cs | 2 +- .../MeowOpener.cs | 49 +-- .../MoveUserModeToKernelMode.cs | 10 +- .../PageHashCalc.cs | 16 +- .../RemoveSupplementalSigners.cs | 109 +++++ .../SecureStringComparer.cs | 0 .../C#/Other Functions/SnapBackGuarantee.cs | 178 ++++++++ .../StagingArea.cs | 0 .../VersionIncrementer.cs | 5 +- .../WldpQuerySecurityPolicy.cs | 0 .../XmlFilePathExtractor.cs | 8 +- .../AuthenticodePageHashes.cs | 12 + .../CertificateDetailsCreator.cs | 13 + .../CertificateSignerCreator.cs | 11 + .../C#/Types And Definitions/ChainElement.cs | 25 ++ .../C#/Types And Definitions/ChainPackage.cs | 19 + .../FileBasedInfoPackage.cs | 14 + .../FilePublisherSignerCreator.cs | 2 +- .../C#/Types And Definitions/HashCreator.cs | 13 + .../C#/Types And Definitions/OpusSigner.cs | 11 + .../PolicyHashObj.cs | 16 +- .../PublisherSignerCreator.cs | 2 +- .../C#/Types And Definitions/Signer.cs | 26 ++ .../Types And Definitions/SimulationInput.cs | 15 + .../Types And Definitions/SimulationOutput.cs | 74 ++++ .../C#/Types And Definitions/WinTrust.cs | 130 ++++++ .../C#/Variables/CILogIntel.cs | 6 +- .../{GlobalVariables.cs => GlobalVars.cs} | 9 +- .../WDAC Simulation/GetFileRuleOutput.cs | 4 +- .../C#/XMLOps/SignerAndHashBuilder.cs | 71 ++-- .../Core/Assert-WDACConfigIntegrity.psm1 | 108 +---- .../Core/Build-WDACCertificate.psm1 | 31 +- .../Core/Confirm-WDACConfig.psm1 | 12 +- .../Core/ConvertTo-WDACPolicy.psm1 | 137 +++--- .../Core/Deploy-SignedWDACConfig.psm1 | 59 ++- .../Core/Edit-SignedWDACConfig.psm1 | 213 +++++----- .../Core/Edit-WDACConfig.psm1 | 194 ++++----- .../Core/Get-CIPolicySetting.psm1 | 62 +-- .../Core/Get-CiFileHashes.psm1 | 18 +- .../Core/Get-CommonWDACConfig.psm1 | 10 +- .../Core/Invoke-WDACSimulation.psm1 | 43 +- .../Core/New-DenyWDACConfig.psm1 | 56 ++- .../Core/New-KernelModeWDACConfig.psm1 | 105 +++-- .../Core/New-SupplementalWDACConfig.psm1 | 57 ++- .../Core/New-WDACConfig.psm1 | 71 ++-- .../Core/Remove-CommonWDACConfig.psm1 | 31 +- .../Core/Remove-WDACConfig.psm1 | 33 +- .../Core/Set-CiRuleOptions.psm1 | 8 +- .../Core/Set-CommonWDACConfig.psm1 | 53 ++- .../CoreExt/PSDefaultParameterValues.ps1 | 59 --- .../Shared/Get-KernelModeDrivers.psm1 | 10 +- .../Shared/Get-KernelModeDriversAudit.psm1 | 17 +- .../Shared/Get-SignTool.psm1 | 22 +- .../Shared/New-SnapBackGuarantee.psm1 | 75 ---- .../Shared/Receive-CodeIntegrityLogs.psm1 | 49 +-- .../Shared/Remove-SupplementalSigners.psm1 | 102 ----- .../Shared/Set-LogPropertiesVisibility.psm1 | 2 - .../Shared/Test-ECCSignedFiles.psm1 | 12 +- .../Shared/Test-KernelProtectedFiles.psm1 | 4 +- .../Shared/Update-self.psm1 | 103 ----- .../WDACConfig Module Files/WDACConfig.psd1 | 6 +- .../WDACConfig Module Files/WDACConfig.psm1 | 114 ++++- .../Compare-SignerAndCertificate.psm1 | 21 +- .../Get-CertificateDetails.psm1 | 3 - .../WDACSimulation/Get-SignerInfo.psm1 | 2 - .../XMLOps/Checkpoint-Macros.psm1 | 2 +- .../XMLOps/Clear-CiPolicy_Semantic.psm1 | 2 - .../XMLOps/Close-EmptyXmlNodes_Semantic.psm1 | 1 - .../XMLOps/Compare-CorrelatedData.psm1 | 15 +- .../XMLOps/Merge-Signers_Semantic.psm1 | 14 +- .../XMLOps/New-CertificateSignerRules.psm1 | 2 - .../XMLOps/New-FilePublisherLevelRules.psm1 | 4 +- .../XMLOps/New-HashLevelRules.psm1 | 4 +- .../XMLOps/New-Macros.psm1 | 2 +- .../XMLOps/New-PFNLevelRules.psm1 | 2 - .../XMLOps/New-PublisherLevelRules.psm1 | 4 +- .../XMLOps/Optimize-MDECSVData.psm1 | 9 +- .../XMLOps/Remove-AllowElements_Semantic.psm1 | 2 - .../Remove-DuplicateFileAttrib_Semantic.psm1 | 4 +- .../Remove-UnreferencedFileRuleRefs.psm1 | 2 - WDACConfig/WDACConfig.code-workspace | 8 +- WDACConfig/WDACConfig.csproj | 14 +- WDACConfig/WinUI3/.editorconfig | 274 ++++++++++++ WDACConfig/WinUI3/App.xaml | 16 + WDACConfig/WinUI3/App.xaml.cs | 38 ++ .../WinUI3/Assets/BadgeLogo.scale-100.png | Bin 0 -> 233 bytes .../WinUI3/Assets/BadgeLogo.scale-125.png | Bin 0 -> 253 bytes .../WinUI3/Assets/BadgeLogo.scale-150.png | Bin 0 -> 293 bytes .../WinUI3/Assets/BadgeLogo.scale-200.png | Bin 0 -> 383 bytes .../WinUI3/Assets/BadgeLogo.scale-400.png | Bin 0 -> 771 bytes .../WinUI3/Assets/LargeTile.scale-100.png | Bin 0 -> 5482 bytes .../WinUI3/Assets/LargeTile.scale-125.png | Bin 0 -> 6810 bytes .../WinUI3/Assets/LargeTile.scale-150.png | Bin 0 -> 8538 bytes .../WinUI3/Assets/LargeTile.scale-200.png | Bin 0 -> 12012 bytes .../WinUI3/Assets/LargeTile.scale-400.png | Bin 0 -> 27812 bytes .../Assets/LockScreenLogo.scale-200.png | Bin 0 -> 432 bytes .../WinUI3/Assets/SmallTile.scale-100.png | Bin 0 -> 1788 bytes .../WinUI3/Assets/SmallTile.scale-125.png | Bin 0 -> 2267 bytes .../WinUI3/Assets/SmallTile.scale-150.png | Bin 0 -> 2571 bytes .../WinUI3/Assets/SmallTile.scale-200.png | Bin 0 -> 3310 bytes .../WinUI3/Assets/SmallTile.scale-400.png | Bin 0 -> 6811 bytes .../WinUI3/Assets/SplashScreen.scale-100.png | Bin 0 -> 5714 bytes .../WinUI3/Assets/SplashScreen.scale-125.png | Bin 0 -> 7212 bytes .../WinUI3/Assets/SplashScreen.scale-150.png | Bin 0 -> 9164 bytes .../WinUI3/Assets/SplashScreen.scale-200.png | Bin 0 -> 13305 bytes .../WinUI3/Assets/SplashScreen.scale-400.png | Bin 0 -> 32940 bytes .../Assets/Square150x150Logo.scale-100.png | Bin 0 -> 2641 bytes .../Assets/Square150x150Logo.scale-125.png | Bin 0 -> 3237 bytes .../Assets/Square150x150Logo.scale-150.png | Bin 0 -> 3930 bytes .../Assets/Square150x150Logo.scale-200.png | Bin 0 -> 5160 bytes .../Assets/Square150x150Logo.scale-400.png | Bin 0 -> 11606 bytes ...go.altform-lightunplated_targetsize-16.png | Bin 0 -> 718 bytes ...go.altform-lightunplated_targetsize-24.png | Bin 0 -> 1129 bytes ...o.altform-lightunplated_targetsize-256.png | Bin 0 -> 10620 bytes ...go.altform-lightunplated_targetsize-32.png | Bin 0 -> 1415 bytes ...go.altform-lightunplated_targetsize-48.png | Bin 0 -> 2105 bytes ...x44Logo.altform-unplated_targetsize-16.png | Bin 0 -> 718 bytes ...44Logo.altform-unplated_targetsize-256.png | Bin 0 -> 10620 bytes ...x44Logo.altform-unplated_targetsize-32.png | Bin 0 -> 1415 bytes ...x44Logo.altform-unplated_targetsize-48.png | Bin 0 -> 2105 bytes .../Assets/Square44x44Logo.scale-100.png | Bin 0 -> 1466 bytes .../Assets/Square44x44Logo.scale-125.png | Bin 0 -> 1888 bytes .../Assets/Square44x44Logo.scale-150.png | Bin 0 -> 2214 bytes .../Assets/Square44x44Logo.scale-200.png | Bin 0 -> 2970 bytes .../Assets/Square44x44Logo.scale-400.png | Bin 0 -> 5731 bytes .../Assets/Square44x44Logo.targetsize-16.png | Bin 0 -> 540 bytes .../Assets/Square44x44Logo.targetsize-24.png | Bin 0 -> 864 bytes ...x44Logo.targetsize-24_altform-unplated.png | Bin 0 -> 1129 bytes .../Assets/Square44x44Logo.targetsize-256.png | Bin 0 -> 8300 bytes .../Assets/Square44x44Logo.targetsize-32.png | Bin 0 -> 1153 bytes .../Assets/Square44x44Logo.targetsize-48.png | Bin 0 -> 1686 bytes WDACConfig/WinUI3/Assets/StoreLogo.backup.png | Bin 0 -> 456 bytes .../WinUI3/Assets/StoreLogo.scale-100.png | Bin 0 -> 2067 bytes .../WinUI3/Assets/StoreLogo.scale-125.png | Bin 0 -> 2669 bytes .../WinUI3/Assets/StoreLogo.scale-150.png | Bin 0 -> 3092 bytes .../WinUI3/Assets/StoreLogo.scale-200.png | Bin 0 -> 3949 bytes .../WinUI3/Assets/StoreLogo.scale-400.png | Bin 0 -> 7936 bytes .../Assets/Wide310x150Logo.scale-100.png | Bin 0 -> 2788 bytes .../Assets/Wide310x150Logo.scale-125.png | Bin 0 -> 3514 bytes .../Assets/Wide310x150Logo.scale-150.png | Bin 0 -> 4290 bytes .../Assets/Wide310x150Logo.scale-200.png | Bin 0 -> 5714 bytes .../Assets/Wide310x150Logo.scale-400.png | Bin 0 -> 13305 bytes .../WinUI3/Invoke-TacticalMSIXDeployment.ps1 | 193 +++++++++ WDACConfig/WinUI3/MainWindow.xaml | 50 +++ WDACConfig/WinUI3/MainWindow.xaml.cs | 59 +++ WDACConfig/WinUI3/Package.appxmanifest | 53 +++ WDACConfig/WinUI3/Pages/BlockRules.xaml | 14 + WDACConfig/WinUI3/Pages/BlockRules.xaml.cs | 15 + WDACConfig/WinUI3/Pages/CreatePolicy.xaml | 14 + WDACConfig/WinUI3/Pages/CreatePolicy.xaml.cs | 15 + WDACConfig/WinUI3/Pages/GetCIHashes.xaml | 33 ++ WDACConfig/WinUI3/Pages/GetCIHashes.xaml.cs | 62 +++ .../WinUI3/Pages/GetSecurePolicySettings.xaml | 45 ++ .../Pages/GetSecurePolicySettings.xaml.cs | 61 +++ .../WinUI3/Pages/GitHubDocumentation.xaml | 64 +++ .../WinUI3/Pages/GitHubDocumentation.xaml.cs | 77 ++++ WDACConfig/WinUI3/Pages/Home.xaml | 14 + WDACConfig/WinUI3/Pages/Home.xaml.cs | 18 + .../WinUI3/Pages/MicrosoftDocumentation.xaml | 64 +++ .../Pages/MicrosoftDocumentation.xaml.cs | 74 ++++ WDACConfig/WinUI3/Pages/Settings.xaml | 14 + WDACConfig/WinUI3/Pages/Settings.xaml.cs | 15 + .../PublishProfiles/win-arm64.pubxml | 13 + .../Properties/PublishProfiles/win-x64.pubxml | 13 + .../WinUI3/Properties/launchSettings.json | 10 + .../WinUI3/Shared Logics/Logging/Logger.cs | 27 ++ .../Logging/LoggerInitializer.cs | 43 ++ .../Main Cmdlets/AssertWDACConfigIntegrity.cs | 222 ++++++++++ .../Main Cmdlets/GetCIPolicySetting.cs | 81 ++++ .../Main Cmdlets/GetCiFileHashes.cs | 89 ++++ .../Main Cmdlets/TestCiPolicy.cs | 112 +++++ .../Other Functions/AllCertificatesGrabber.cs | 335 +++++++++++++++ .../Other Functions/CIPolicyVersion.cs | 46 ++ .../Other Functions/CertificateHelper.cs | 111 +++++ .../Other Functions/CiPolicyUtility.cs | 66 +++ .../Other Functions/CodeIntegritySigner.cs | 68 +++ .../Other Functions/Crypt32CertCN.cs | 68 +++ .../Other Functions/DriveLetterMapper.cs | 122 ++++++ .../DriversBlockRulesFetcher.cs | 70 +++ .../Other Functions/EditGUIDs.cs | 43 ++ .../Other Functions/EventLogUtility.cs | 60 +++ .../FileDirectoryPathComparer.cs | 51 +++ .../Other Functions/GetExtendedFileAttrib.cs | 163 +++++++ .../Other Functions/GetFilesFast.cs | 91 ++++ .../Other Functions/GetOpusData.cs | 249 +++++++++++ .../Other Functions/Initializer.cs | 55 +++ .../Other Functions/MeowOpener.cs | 102 +++++ .../MoveUserModeToKernelMode.cs | 121 ++++++ .../Other Functions/PageHashCalc.cs | 74 ++++ .../RemoveSupplementalSigners.cs | 109 +++++ .../Other Functions/SecureStringComparer.cs | 62 +++ .../Other Functions/SnapBackGuarantee.cs | 178 ++++++++ .../Other Functions/StagingArea.cs | 34 ++ .../Other Functions/VersionIncrementer.cs | 36 ++ .../WldpQuerySecurityPolicy.cs | 45 ++ .../Other Functions/XmlFilePathExtractor.cs | 43 ++ .../AuthenticodePageHashes.cs | 12 + .../CertificateDetailsCreator.cs | 13 + .../CertificateSignerCreator.cs | 11 + .../Types And Definitions/ChainElement.cs | 25 ++ .../Types And Definitions/ChainPackage.cs | 19 + .../FileBasedInfoPackage.cs | 14 + .../FilePublisherSignerCreator.cs | 54 +++ .../Types And Definitions/HashCreator.cs | 13 + .../Types And Definitions/OpusSigner.cs | 11 + .../Types And Definitions/PolicyHashObj.cs | 35 ++ .../PublisherSignerCreator.cs | 30 ++ .../Types And Definitions/Signer.cs | 26 ++ .../Types And Definitions/SimulationInput.cs | 15 + .../Types And Definitions/SimulationOutput.cs | 74 ++++ .../Types And Definitions/WinTrust.cs | 130 ++++++ .../Shared Logics/Variables/CILogIntel.cs | 74 ++++ .../Shared Logics/Variables/GlobalVars.cs | 55 +++ .../WDAC Simulation/GetFileRuleOutput.cs | 69 +++ .../XMLOps/SignerAndHashBuilder.cs | 402 ++++++++++++++++++ WDACConfig/WinUI3/WDACConfig.csproj | 148 +++++++ WDACConfig/WinUI3/WDACConfig.csproj.user | 47 ++ WDACConfig/WinUI3/WDACConfig.sln | 34 ++ WDACConfig/WinUI3/app.manifest | 19 + WDACConfig/WinUI3/exclusion.dic | 0 WDACConfig/exclusion.dic | 1 + WDACConfig/icon.png | Bin 4804 -> 2111 bytes WDACConfig/version.txt | 2 +- 279 files changed, 8089 insertions(+), 2402 deletions(-) rename CONTRIBUTING.md => .github/CONTRIBUTING.md (100%) create mode 100644 Harden-Windows-Security Module/ICON-FULLSIZE.png create mode 100644 Harden-Windows-Security Module/ICON-SVG-ADVANCED.svg create mode 100644 Harden-Windows-Security Module/ICON-SVG-SIMPLIFIED.svg create mode 100644 WDACConfig/ICON-SVG-ADVANCED.svg create mode 100644 WDACConfig/ICON-SVG-Simplified.svg create mode 100644 WDACConfig/IconFullSize.png delete mode 100644 WDACConfig/WDACConfig Module Files/C#/Custom Types/AuthenticodePageHashes.cs delete mode 100644 WDACConfig/WDACConfig Module Files/C#/Custom Types/CertificateDetailsCreator.cs delete mode 100644 WDACConfig/WDACConfig Module Files/C#/Custom Types/CertificateSignerCreator.cs delete mode 100644 WDACConfig/WDACConfig Module Files/C#/Custom Types/ChainElement.cs delete mode 100644 WDACConfig/WDACConfig Module Files/C#/Custom Types/ChainPackage.cs delete mode 100644 WDACConfig/WDACConfig Module Files/C#/Custom Types/FileBasedInfoPackage.cs delete mode 100644 WDACConfig/WDACConfig Module Files/C#/Custom Types/HashCreator.cs delete mode 100644 WDACConfig/WDACConfig Module Files/C#/Custom Types/OpusSigner.cs delete mode 100644 WDACConfig/WDACConfig Module Files/C#/Custom Types/Signer.cs delete mode 100644 WDACConfig/WDACConfig Module Files/C#/Custom Types/SimulationInput.cs delete mode 100644 WDACConfig/WDACConfig Module Files/C#/Custom Types/SimulationOutput.cs delete mode 100644 WDACConfig/WDACConfig Module Files/C#/Functions/AuthenticodeHashCalc.cs delete mode 100644 WDACConfig/WDACConfig Module Files/C#/Functions/CodeIntegritySigner.cs delete mode 100644 WDACConfig/WDACConfig Module Files/C#/Functions/DebugLogger.cs delete mode 100644 WDACConfig/WDACConfig Module Files/C#/Functions/EventLogUtility.cs rename WDACConfig/WDACConfig Module Files/C#/{Functions/VerboseLogger.cs => Logging/Logger.cs} (52%) rename WDACConfig/WDACConfig Module Files/C#/{Functions => Logging}/LoggerInitializer.cs (55%) create mode 100644 WDACConfig/WDACConfig Module Files/C#/Main Cmdlets/AssertWDACConfigIntegrity.cs create mode 100644 WDACConfig/WDACConfig Module Files/C#/Main Cmdlets/GetCIPolicySetting.cs create mode 100644 WDACConfig/WDACConfig Module Files/C#/Main Cmdlets/GetCiFileHashes.cs rename WDACConfig/WDACConfig Module Files/C#/{Functions => Main Cmdlets}/TestCiPolicy.cs (86%) rename WDACConfig/WDACConfig Module Files/C#/{Functions => Other Functions}/AllCertificatesGrabber.cs (78%) rename WDACConfig/WDACConfig Module Files/C#/{Functions => Other Functions}/CIPolicyVersion.cs (94%) rename WDACConfig/WDACConfig Module Files/C#/{Functions => Other Functions}/CertificateHelper.cs (61%) rename WDACConfig/WDACConfig Module Files/C#/{Functions => Other Functions}/CiPolicyUtility.cs (73%) create mode 100644 WDACConfig/WDACConfig Module Files/C#/Other Functions/CodeIntegritySigner.cs rename WDACConfig/WDACConfig Module Files/C#/{Functions => Other Functions}/Crypt32CertCN.cs (91%) rename WDACConfig/WDACConfig Module Files/C#/{Functions => Other Functions}/DirectorySelector.cs (61%) rename WDACConfig/WDACConfig Module Files/C#/{Functions => Other Functions}/DriveLetterMapper.cs (91%) rename WDACConfig/WDACConfig Module Files/C#/{Functions => Other Functions}/DriversBlockRulesFetcher.cs (91%) rename WDACConfig/WDACConfig Module Files/C#/{Functions => Other Functions}/EditGUIDs.cs (90%) create mode 100644 WDACConfig/WDACConfig Module Files/C#/Other Functions/EventLogUtility.cs rename WDACConfig/WDACConfig Module Files/C#/{Functions => Other Functions}/FileDirectoryPathComparer.cs (94%) rename WDACConfig/WDACConfig Module Files/C#/{Functions => Other Functions}/GetExtendedFileAttrib.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{Functions => Other Functions}/GetFilesFast.cs (89%) rename WDACConfig/WDACConfig Module Files/C#/{Functions => Other Functions}/GetOpusData.cs (98%) rename WDACConfig/WDACConfig Module Files/C#/{Functions => Other Functions}/Initializer.cs (96%) rename WDACConfig/WDACConfig Module Files/C#/{Functions => Other Functions}/MeowOpener.cs (63%) rename WDACConfig/WDACConfig Module Files/C#/{Functions => Other Functions}/MoveUserModeToKernelMode.cs (93%) rename WDACConfig/WDACConfig Module Files/C#/{Functions => Other Functions}/PageHashCalc.cs (68%) create mode 100644 WDACConfig/WDACConfig Module Files/C#/Other Functions/RemoveSupplementalSigners.cs rename WDACConfig/WDACConfig Module Files/C#/{Functions => Other Functions}/SecureStringComparer.cs (100%) create mode 100644 WDACConfig/WDACConfig Module Files/C#/Other Functions/SnapBackGuarantee.cs rename WDACConfig/WDACConfig Module Files/C#/{Functions => Other Functions}/StagingArea.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{Functions => Other Functions}/VersionIncrementer.cs (89%) rename WDACConfig/WDACConfig Module Files/C#/{Functions => Other Functions}/WldpQuerySecurityPolicy.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{Functions => Other Functions}/XmlFilePathExtractor.cs (78%) create mode 100644 WDACConfig/WDACConfig Module Files/C#/Types And Definitions/AuthenticodePageHashes.cs create mode 100644 WDACConfig/WDACConfig Module Files/C#/Types And Definitions/CertificateDetailsCreator.cs create mode 100644 WDACConfig/WDACConfig Module Files/C#/Types And Definitions/CertificateSignerCreator.cs create mode 100644 WDACConfig/WDACConfig Module Files/C#/Types And Definitions/ChainElement.cs create mode 100644 WDACConfig/WDACConfig Module Files/C#/Types And Definitions/ChainPackage.cs create mode 100644 WDACConfig/WDACConfig Module Files/C#/Types And Definitions/FileBasedInfoPackage.cs rename WDACConfig/WDACConfig Module Files/C#/{Custom Types => Types And Definitions}/FilePublisherSignerCreator.cs (96%) create mode 100644 WDACConfig/WDACConfig Module Files/C#/Types And Definitions/HashCreator.cs create mode 100644 WDACConfig/WDACConfig Module Files/C#/Types And Definitions/OpusSigner.cs rename WDACConfig/WDACConfig Module Files/C#/{Custom Types => Types And Definitions}/PolicyHashObj.cs (65%) rename WDACConfig/WDACConfig Module Files/C#/{Custom Types => Types And Definitions}/PublisherSignerCreator.cs (93%) create mode 100644 WDACConfig/WDACConfig Module Files/C#/Types And Definitions/Signer.cs create mode 100644 WDACConfig/WDACConfig Module Files/C#/Types And Definitions/SimulationInput.cs create mode 100644 WDACConfig/WDACConfig Module Files/C#/Types And Definitions/SimulationOutput.cs create mode 100644 WDACConfig/WDACConfig Module Files/C#/Types And Definitions/WinTrust.cs rename WDACConfig/WDACConfig Module Files/C#/Variables/{GlobalVariables.cs => GlobalVars.cs} (90%) rename WDACConfig/WDACConfig Module Files/C#/{Functions => }/WDAC Simulation/GetFileRuleOutput.cs (92%) delete mode 100644 WDACConfig/WDACConfig Module Files/CoreExt/PSDefaultParameterValues.ps1 delete mode 100644 WDACConfig/WDACConfig Module Files/Shared/New-SnapBackGuarantee.psm1 delete mode 100644 WDACConfig/WDACConfig Module Files/Shared/Remove-SupplementalSigners.psm1 delete mode 100644 WDACConfig/WDACConfig Module Files/Shared/Update-self.psm1 create mode 100644 WDACConfig/WinUI3/.editorconfig create mode 100644 WDACConfig/WinUI3/App.xaml create mode 100644 WDACConfig/WinUI3/App.xaml.cs create mode 100644 WDACConfig/WinUI3/Assets/BadgeLogo.scale-100.png create mode 100644 WDACConfig/WinUI3/Assets/BadgeLogo.scale-125.png create mode 100644 WDACConfig/WinUI3/Assets/BadgeLogo.scale-150.png create mode 100644 WDACConfig/WinUI3/Assets/BadgeLogo.scale-200.png create mode 100644 WDACConfig/WinUI3/Assets/BadgeLogo.scale-400.png create mode 100644 WDACConfig/WinUI3/Assets/LargeTile.scale-100.png create mode 100644 WDACConfig/WinUI3/Assets/LargeTile.scale-125.png create mode 100644 WDACConfig/WinUI3/Assets/LargeTile.scale-150.png create mode 100644 WDACConfig/WinUI3/Assets/LargeTile.scale-200.png create mode 100644 WDACConfig/WinUI3/Assets/LargeTile.scale-400.png create mode 100644 WDACConfig/WinUI3/Assets/LockScreenLogo.scale-200.png create mode 100644 WDACConfig/WinUI3/Assets/SmallTile.scale-100.png create mode 100644 WDACConfig/WinUI3/Assets/SmallTile.scale-125.png create mode 100644 WDACConfig/WinUI3/Assets/SmallTile.scale-150.png create mode 100644 WDACConfig/WinUI3/Assets/SmallTile.scale-200.png create mode 100644 WDACConfig/WinUI3/Assets/SmallTile.scale-400.png create mode 100644 WDACConfig/WinUI3/Assets/SplashScreen.scale-100.png create mode 100644 WDACConfig/WinUI3/Assets/SplashScreen.scale-125.png create mode 100644 WDACConfig/WinUI3/Assets/SplashScreen.scale-150.png create mode 100644 WDACConfig/WinUI3/Assets/SplashScreen.scale-200.png create mode 100644 WDACConfig/WinUI3/Assets/SplashScreen.scale-400.png create mode 100644 WDACConfig/WinUI3/Assets/Square150x150Logo.scale-100.png create mode 100644 WDACConfig/WinUI3/Assets/Square150x150Logo.scale-125.png create mode 100644 WDACConfig/WinUI3/Assets/Square150x150Logo.scale-150.png create mode 100644 WDACConfig/WinUI3/Assets/Square150x150Logo.scale-200.png create mode 100644 WDACConfig/WinUI3/Assets/Square150x150Logo.scale-400.png create mode 100644 WDACConfig/WinUI3/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png create mode 100644 WDACConfig/WinUI3/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png create mode 100644 WDACConfig/WinUI3/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png create mode 100644 WDACConfig/WinUI3/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png create mode 100644 WDACConfig/WinUI3/Assets/Square44x44Logo.altform-lightunplated_targetsize-48.png create mode 100644 WDACConfig/WinUI3/Assets/Square44x44Logo.altform-unplated_targetsize-16.png create mode 100644 WDACConfig/WinUI3/Assets/Square44x44Logo.altform-unplated_targetsize-256.png create mode 100644 WDACConfig/WinUI3/Assets/Square44x44Logo.altform-unplated_targetsize-32.png create mode 100644 WDACConfig/WinUI3/Assets/Square44x44Logo.altform-unplated_targetsize-48.png create mode 100644 WDACConfig/WinUI3/Assets/Square44x44Logo.scale-100.png create mode 100644 WDACConfig/WinUI3/Assets/Square44x44Logo.scale-125.png create mode 100644 WDACConfig/WinUI3/Assets/Square44x44Logo.scale-150.png create mode 100644 WDACConfig/WinUI3/Assets/Square44x44Logo.scale-200.png create mode 100644 WDACConfig/WinUI3/Assets/Square44x44Logo.scale-400.png create mode 100644 WDACConfig/WinUI3/Assets/Square44x44Logo.targetsize-16.png create mode 100644 WDACConfig/WinUI3/Assets/Square44x44Logo.targetsize-24.png create mode 100644 WDACConfig/WinUI3/Assets/Square44x44Logo.targetsize-24_altform-unplated.png create mode 100644 WDACConfig/WinUI3/Assets/Square44x44Logo.targetsize-256.png create mode 100644 WDACConfig/WinUI3/Assets/Square44x44Logo.targetsize-32.png create mode 100644 WDACConfig/WinUI3/Assets/Square44x44Logo.targetsize-48.png create mode 100644 WDACConfig/WinUI3/Assets/StoreLogo.backup.png create mode 100644 WDACConfig/WinUI3/Assets/StoreLogo.scale-100.png create mode 100644 WDACConfig/WinUI3/Assets/StoreLogo.scale-125.png create mode 100644 WDACConfig/WinUI3/Assets/StoreLogo.scale-150.png create mode 100644 WDACConfig/WinUI3/Assets/StoreLogo.scale-200.png create mode 100644 WDACConfig/WinUI3/Assets/StoreLogo.scale-400.png create mode 100644 WDACConfig/WinUI3/Assets/Wide310x150Logo.scale-100.png create mode 100644 WDACConfig/WinUI3/Assets/Wide310x150Logo.scale-125.png create mode 100644 WDACConfig/WinUI3/Assets/Wide310x150Logo.scale-150.png create mode 100644 WDACConfig/WinUI3/Assets/Wide310x150Logo.scale-200.png create mode 100644 WDACConfig/WinUI3/Assets/Wide310x150Logo.scale-400.png create mode 100644 WDACConfig/WinUI3/Invoke-TacticalMSIXDeployment.ps1 create mode 100644 WDACConfig/WinUI3/MainWindow.xaml create mode 100644 WDACConfig/WinUI3/MainWindow.xaml.cs create mode 100644 WDACConfig/WinUI3/Package.appxmanifest create mode 100644 WDACConfig/WinUI3/Pages/BlockRules.xaml create mode 100644 WDACConfig/WinUI3/Pages/BlockRules.xaml.cs create mode 100644 WDACConfig/WinUI3/Pages/CreatePolicy.xaml create mode 100644 WDACConfig/WinUI3/Pages/CreatePolicy.xaml.cs create mode 100644 WDACConfig/WinUI3/Pages/GetCIHashes.xaml create mode 100644 WDACConfig/WinUI3/Pages/GetCIHashes.xaml.cs create mode 100644 WDACConfig/WinUI3/Pages/GetSecurePolicySettings.xaml create mode 100644 WDACConfig/WinUI3/Pages/GetSecurePolicySettings.xaml.cs create mode 100644 WDACConfig/WinUI3/Pages/GitHubDocumentation.xaml create mode 100644 WDACConfig/WinUI3/Pages/GitHubDocumentation.xaml.cs create mode 100644 WDACConfig/WinUI3/Pages/Home.xaml create mode 100644 WDACConfig/WinUI3/Pages/Home.xaml.cs create mode 100644 WDACConfig/WinUI3/Pages/MicrosoftDocumentation.xaml create mode 100644 WDACConfig/WinUI3/Pages/MicrosoftDocumentation.xaml.cs create mode 100644 WDACConfig/WinUI3/Pages/Settings.xaml create mode 100644 WDACConfig/WinUI3/Pages/Settings.xaml.cs create mode 100644 WDACConfig/WinUI3/Properties/PublishProfiles/win-arm64.pubxml create mode 100644 WDACConfig/WinUI3/Properties/PublishProfiles/win-x64.pubxml create mode 100644 WDACConfig/WinUI3/Properties/launchSettings.json create mode 100644 WDACConfig/WinUI3/Shared Logics/Logging/Logger.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Logging/LoggerInitializer.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Main Cmdlets/AssertWDACConfigIntegrity.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Main Cmdlets/GetCIPolicySetting.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Main Cmdlets/GetCiFileHashes.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Main Cmdlets/TestCiPolicy.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/AllCertificatesGrabber.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/CIPolicyVersion.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/CertificateHelper.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/CiPolicyUtility.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/CodeIntegritySigner.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/Crypt32CertCN.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/DriveLetterMapper.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/DriversBlockRulesFetcher.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/EditGUIDs.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/EventLogUtility.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/FileDirectoryPathComparer.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/GetExtendedFileAttrib.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/GetFilesFast.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/GetOpusData.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/Initializer.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/MeowOpener.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/MoveUserModeToKernelMode.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/PageHashCalc.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/RemoveSupplementalSigners.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/SecureStringComparer.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/SnapBackGuarantee.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/StagingArea.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/VersionIncrementer.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/WldpQuerySecurityPolicy.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/XmlFilePathExtractor.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Types And Definitions/AuthenticodePageHashes.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Types And Definitions/CertificateDetailsCreator.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Types And Definitions/CertificateSignerCreator.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Types And Definitions/ChainElement.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Types And Definitions/ChainPackage.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Types And Definitions/FileBasedInfoPackage.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Types And Definitions/FilePublisherSignerCreator.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Types And Definitions/HashCreator.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Types And Definitions/OpusSigner.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Types And Definitions/PolicyHashObj.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Types And Definitions/PublisherSignerCreator.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Types And Definitions/Signer.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Types And Definitions/SimulationInput.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Types And Definitions/SimulationOutput.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Types And Definitions/WinTrust.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Variables/CILogIntel.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/Variables/GlobalVars.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/WDAC Simulation/GetFileRuleOutput.cs create mode 100644 WDACConfig/WinUI3/Shared Logics/XMLOps/SignerAndHashBuilder.cs create mode 100644 WDACConfig/WinUI3/WDACConfig.csproj create mode 100644 WDACConfig/WinUI3/WDACConfig.csproj.user create mode 100644 WDACConfig/WinUI3/WDACConfig.sln create mode 100644 WDACConfig/WinUI3/app.manifest create mode 100644 WDACConfig/WinUI3/exclusion.dic create mode 100644 WDACConfig/exclusion.dic diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md diff --git a/.gitignore b/.gitignore index aa5babb32..0a11dec3e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,9 @@ Harden-Windows-Security Module/bin/ WDACConfig/obj/ WDACConfig/.vs/ WDACConfig/bin/ -WDACConfig/App/WDACConfig/.vs/ \ No newline at end of file +WDACConfig/WinUI3/.vs/ +WDACConfig/WinUI3/bin/ +WDACConfig/WinUI3/obj/ +WDACConfig/WinUI3/Generated Files/ +WDACConfig/WinUI3/signtool.exe +WDACConfig/WinUI3/WDACConfig_1.0.0.0_x64.msix diff --git a/Harden-Windows-Security Module/.editorconfig b/Harden-Windows-Security Module/.editorconfig index b9cb04823..b72f1f784 100644 --- a/Harden-Windows-Security Module/.editorconfig +++ b/Harden-Windows-Security Module/.editorconfig @@ -22,7 +22,7 @@ dotnet_diagnostic.CA1310.severity = error dotnet_diagnostic.CA1401.severity = error # CA1303: Do not pass literals as localized parameters -dotnet_diagnostic.CA1303.severity = error +dotnet_diagnostic.CA1303.severity = silent # CA1309: Use ordinal string comparison dotnet_diagnostic.CA1309.severity = error @@ -196,7 +196,7 @@ dotnet_diagnostic.CA1805.severity = error dotnet_diagnostic.CA1806.severity = error # CA1819: Properties should not return arrays -dotnet_diagnostic.CA1819.severity = error +dotnet_diagnostic.CA1819.severity = silent # CA1834: Consider using 'StringBuilder.Append(char)' when applicable dotnet_diagnostic.CA1834.severity = error @@ -269,3 +269,6 @@ dotnet_diagnostic.IDE0120.severity = error # IDE0110: Remove unnecessary discard dotnet_diagnostic.IDE0110.severity = error + +# CA2101: Specify marshaling for P/Invoke string arguments +dotnet_diagnostic.CA2101.severity = error diff --git a/Harden-Windows-Security Module/Harden-Windows-Security Module.code-workspace b/Harden-Windows-Security Module/Harden-Windows-Security Module.code-workspace index f780546f8..ede087b24 100644 --- a/Harden-Windows-Security Module/Harden-Windows-Security Module.code-workspace +++ b/Harden-Windows-Security Module/Harden-Windows-Security Module.code-workspace @@ -227,7 +227,13 @@ "XDRs", "Zune" ], - "dotnetAcquisitionExtension.enablePreviewFeatures": true + "dotnetAcquisitionExtension.enablePreviewFeatures": true, + "files.exclude": { + "**/.vs": true, + "**/bin": true, + "**/obj": true, + "**/Properties": true + } }, "extensions": { "recommendations": [ diff --git a/Harden-Windows-Security Module/ICON-FULLSIZE.png b/Harden-Windows-Security Module/ICON-FULLSIZE.png new file mode 100644 index 0000000000000000000000000000000000000000..3597f3b3bf6dbc99aade84b20fe2e02939855f81 GIT binary patch literal 105815 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%0A?rp4aGpZaO{tl7^A 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%0D1+~tN)oMX zsW6&o1^JVARuwh*Ot9a*^hYCEzm<9Aws)nbAP7??_lSJ4*!1Lc7p5>R1L+Yz@Nz-) zba#QmzJ5u_%SvXG<{@T%{x!i zwjf3@r7h>&!V65td|gCzNN7WR!)YK{y?iH|%+!AHHFU!6ZpO#z<;`H7t~=xh29y(~ zr2KDa*tIbgf}Y*=8LNvslXE%00;}8pEUEv;)^$KNoiuU1oZ;>a zAg3pxMgvh^PAa?!{%tzWIFBR;&HWim=^J8#e@kTZG9S2yJo7Q8f zVY~6(C)x`XpF{74kZ+%!>G5_k)FgPjV{`*=FE>GuYjIfv#(X&Twoj9k#W@QRDBctI zzyUDJ-{fNgcGQ=~>?h7%^d8RPNd33_#Qyb4MVIU&1#7Ku1>%vvg01+Yn9=F-fHAEWnY92}_-cu$GGLr@tb z*~Xuhf6g#!MvC#Ie7cO!mn89AkWIUss{Z2eZ`d-her1vC1gRz2j6|w(9?jSx(Ll(K z5Kfnd$I)3SyLP>`CtkvDCK2Vpy-3j!CV7uv$bALh8i0>l}a_D^; zFA~J!W1{(1sss{+4zNDZUU|MXrhTg9t)@y+g;BAiblvKA-Q8PinS2SJ?%w@=Z_P+q z9)Ywcjcjfi0YY-G$r@3uH+b}my{@YyWf^6PkFRjS=ziZl-fu(8bXj}a2P4Saryn?J zKYAXXCH-<8JWJ-Sk>^`UX

#v~#d*6#$7 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

dPlCcIP6C!xoxpOce0>vl=Si;50O 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 literal 0 HcmV?d00001 diff --git a/Harden-Windows-Security Module/ICON-SVG-ADVANCED.svg b/Harden-Windows-Security Module/ICON-SVG-ADVANCED.svg new file mode 100644 index 000000000..6da4aa2e8 --- /dev/null +++ b/Harden-Windows-Security Module/ICON-SVG-ADVANCED.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Harden-Windows-Security Module/ICON-SVG-SIMPLIFIED.svg b/Harden-Windows-Security Module/ICON-SVG-SIMPLIFIED.svg new file mode 100644 index 000000000..b99564520 --- /dev/null +++ b/Harden-Windows-Security Module/ICON-SVG-SIMPLIFIED.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Harden-Windows-Security Module/Main files/C#/Protect Methods/MSFTDefender_ScheduledTask.cs b/Harden-Windows-Security Module/Main files/C#/Protect Methods/MSFTDefender_ScheduledTask.cs index e5755ccdb..491ab9d5f 100644 --- a/Harden-Windows-Security Module/Main files/C#/Protect Methods/MSFTDefender_ScheduledTask.cs +++ b/Harden-Windows-Security Module/Main files/C#/Protect Methods/MSFTDefender_ScheduledTask.cs @@ -1,8 +1,8 @@ -#nullable enable - using System; using System.Management; +#nullable enable + namespace HardenWindowsSecurity { public partial class MicrosoftDefender @@ -11,18 +11,18 @@ public static void MSFTDefender_ScheduledTask() { HardenWindowsSecurity.Logger.LogMessage("Creating scheduled task for fast weekly Microsoft recommended driver block list update", LogTypeIntel.Information); - // Initialize ManagementScope to interact with Task Scheduler's WMI namespace var scope = new ManagementScope(@"root\Microsoft\Windows\TaskScheduler"); - scope.Connect(); // Establish connection to the WMI namespace + // Establish connection to the WMI namespace + scope.Connect(); #region Action // Create a scheduled task action, this defines how to download and install the latest Microsoft Recommended Driver Block Rules - using var actionClass = new ManagementClass(scope, new ManagementPath("PS_ScheduledTask"), null); + using ManagementClass actionClass = new(scope, new ManagementPath("PS_ScheduledTask"), null); // Prepare method parameters for creating the task action - var actionInParams = actionClass.GetMethodParameters("NewActionByExec"); + ManagementBaseObject actionInParams = actionClass.GetMethodParameters("NewActionByExec"); actionInParams["Execute"] = "PowerShell.exe"; // The PowerShell command to run, downloading and deploying the drivers block list actionInParams["Argument"] = """ @@ -30,7 +30,7 @@ public static void MSFTDefender_ScheduledTask() """; // Execute the WMI method to create the action - var actionResult = actionClass.InvokeMethod("NewActionByExec", actionInParams, null); + ManagementBaseObject actionResult = actionClass.InvokeMethod("NewActionByExec", actionInParams, null); // Check if the action was created successfully if ((uint)actionResult["ReturnValue"] != 0) @@ -39,23 +39,23 @@ public static void MSFTDefender_ScheduledTask() } // Extract CIM instance for further use in task registration - var actionCimInstance = (ManagementBaseObject)actionResult["cmdletOutput"]; + ManagementBaseObject actionCimInstance = (ManagementBaseObject)actionResult["cmdletOutput"]; #endregion #region Principal // Create a scheduled task principal and assign the SYSTEM account's SID to it so that the task will run under its context - using var principalClass = new ManagementClass(scope, new ManagementPath("PS_ScheduledTask"), null); + using ManagementClass principalClass = new(scope, new ManagementPath("PS_ScheduledTask"), null); // Prepare method parameters to set up the principal (user context) - var principalInParams = principalClass.GetMethodParameters("NewPrincipalByUser"); + ManagementBaseObject principalInParams = principalClass.GetMethodParameters("NewPrincipalByUser"); principalInParams["UserId"] = "S-1-5-18"; // SYSTEM SID (runs with the highest system privileges) principalInParams["LogonType"] = 2; // S4U logon type, allows the task to run without storing credentials principalInParams["RunLevel"] = 1; // Highest run level, ensuring the task runs with elevated privileges // Execute the WMI method to create the principal - var principalResult = principalClass.InvokeMethod("NewPrincipalByUser", principalInParams, null); + ManagementBaseObject principalResult = principalClass.InvokeMethod("NewPrincipalByUser", principalInParams, null); // Check if the principal was created successfully if ((uint)principalResult["ReturnValue"] != 0) @@ -64,26 +64,26 @@ public static void MSFTDefender_ScheduledTask() } // Extract CIM instance for further use in task registration - var principalCimInstance = (ManagementBaseObject)principalResult["cmdletOutput"]; + ManagementBaseObject principalCimInstance = (ManagementBaseObject)principalResult["cmdletOutput"]; #endregion #region Trigger // 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 - using var triggerClass = new ManagementClass(scope, new ManagementPath("PS_ScheduledTask"), null); + using ManagementClass triggerClass = new(scope, new ManagementPath("PS_ScheduledTask"), null); // Prepare method parameters for setting the task trigger // DateTime and TimeSpan are .NET constructs that are not directly compatible with WMI methods, which require the use of DMTF DateTime and TimeInterval formats. // The conversion ensures that time-related parameters (e.g., DateTime.Now.AddHours(1) or TimeSpan.FromDays(7)) are formatted in a way that the WMI provider can interpret them correctly. // The ManagementDateTimeConverter class provides methods like ToDmtfDateTime and ToDmtfTimeInterval that perform these necessary conversions. - var triggerInParams = triggerClass.GetMethodParameters("NewTriggerByOnce"); + ManagementBaseObject triggerInParams = triggerClass.GetMethodParameters("NewTriggerByOnce"); triggerInParams["Once"] = true; // This switch indicates the task should run once triggerInParams["At"] = ManagementDateTimeConverter.ToDmtfDateTime(DateTime.Now.AddHours(1)); // Convert the current time +1 hour to DMTF format triggerInParams["RepetitionInterval"] = ManagementDateTimeConverter.ToDmtfTimeInterval(TimeSpan.FromDays(7)); // Convert 7-day interval to DMTF format // Execute the WMI method to create the trigger - var triggerResult = triggerClass.InvokeMethod("NewTriggerByOnce", triggerInParams, null); + ManagementBaseObject triggerResult = triggerClass.InvokeMethod("NewTriggerByOnce", triggerInParams, null); // Check if the trigger was created successfully if ((uint)triggerResult["ReturnValue"] != 0) @@ -92,16 +92,16 @@ public static void MSFTDefender_ScheduledTask() } // Extract CIM instance for further use in task registration - var triggerCimInstance = (ManagementBaseObject)triggerResult["cmdletOutput"]; + ManagementBaseObject triggerCimInstance = (ManagementBaseObject)triggerResult["cmdletOutput"]; #endregion #region Settings // Define advanced settings for the scheduled task - using var settingsClass = new ManagementClass(scope, new ManagementPath("PS_ScheduledTask"), null); + using ManagementClass settingsClass = new(scope, new ManagementPath("PS_ScheduledTask"), null); // Prepare method parameters to define advanced settings for the task - var settingsInParams = settingsClass.GetMethodParameters("NewSettings"); + ManagementBaseObject settingsInParams = settingsClass.GetMethodParameters("NewSettings"); settingsInParams["AllowStartIfOnBatteries"] = true; // Allow the task to start if the system is on battery settingsInParams["DontStopIfGoingOnBatteries"] = true; // Ensure the task isn't stopped if the system switches to battery power settingsInParams["Compatibility"] = 4; @@ -112,23 +112,23 @@ public static void MSFTDefender_ScheduledTask() settingsInParams["RestartInterval"] = ManagementDateTimeConverter.ToDmtfTimeInterval(TimeSpan.FromHours(6)); // Wait 6 hours between restarts (converted to DMTF format) // Execute the WMI method to set the task's advanced settings - var settingsResult = settingsClass.InvokeMethod("NewSettings", settingsInParams, null); + ManagementBaseObject settingsResult = settingsClass.InvokeMethod("NewSettings", settingsInParams, null); if ((uint)settingsResult["ReturnValue"] != 0) { throw new InvalidOperationException($"Failed to define task settings: {((uint)settingsResult["ReturnValue"])}"); } // Extract CIM instance for further use in task registration - var settingsCimInstance = (ManagementBaseObject)settingsResult["cmdletOutput"]; + ManagementBaseObject settingsCimInstance = (ManagementBaseObject)settingsResult["cmdletOutput"]; #endregion #region Register Task // Register the scheduled task. If the task's state is disabled, it will be overwritten with a new task that is enabled - using var registerClass = new ManagementClass(scope, new ManagementPath("PS_ScheduledTask"), null); + using ManagementClass registerClass = new(scope, new ManagementPath("PS_ScheduledTask"), null); // Prepare method parameters to register the task - var registerInParams = registerClass.GetMethodParameters("RegisterByPrincipal"); + ManagementBaseObject registerInParams = registerClass.GetMethodParameters("RegisterByPrincipal"); registerInParams["Force"] = true; // Overwrite any existing task with the same name registerInParams["Principal"] = principalCimInstance; registerInParams["Action"] = new ManagementBaseObject[] { actionCimInstance }; @@ -139,7 +139,7 @@ public static void MSFTDefender_ScheduledTask() registerInParams["Description"] = "Microsoft Recommended Driver Block List update"; // Execute the WMI method to register the task - var registerResult = registerClass.InvokeMethod("RegisterByPrincipal", registerInParams, null); + ManagementBaseObject registerResult = registerClass.InvokeMethod("RegisterByPrincipal", registerInParams, null); // Check if the task was registered successfully if ((uint)registerResult["ReturnValue"] != 0) diff --git a/WDACConfig/.editorconfig b/WDACConfig/.editorconfig index 4d28a622d..b72f1f784 100644 --- a/WDACConfig/.editorconfig +++ b/WDACConfig/.editorconfig @@ -1,5 +1,11 @@ [*.cs] +# Spell Checker dictionary +spelling_exclusion_path = .\exclusion.dic + +# Miscellaneous settings +trim_trailing_whitespace = true + # CA1304: Specify CultureInfo dotnet_diagnostic.CA1304.severity = error @@ -16,7 +22,7 @@ dotnet_diagnostic.CA1310.severity = error dotnet_diagnostic.CA1401.severity = error # CA1303: Do not pass literals as localized parameters -dotnet_diagnostic.CA1303.severity = error +dotnet_diagnostic.CA1303.severity = silent # CA1309: Use ordinal string comparison dotnet_diagnostic.CA1309.severity = error @@ -125,3 +131,144 @@ dotnet_diagnostic.CA1822.severity = error # CA1050: Declare types in namespaces dotnet_diagnostic.CA1050.severity = error + +# IDE0290: Use primary constructor | https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0290 +dotnet_diagnostic.IDE0290.severity = error + +# IDE0220: Add explicit cast in foreach loop | https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0220 +dotnet_diagnostic.IDE0220.severity = error + +# CA1868: Unnecessary call to 'Contains(item)' +dotnet_diagnostic.CA1868.severity = error + +# CA1869: Cache and reuse 'JsonSerializerOptions' instances +dotnet_diagnostic.CA1869.severity = error + +# IDE0090: Simplify new expression | https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0090 +dotnet_diagnostic.IDE0090.severity = error + +# IDE0059: Remove unnecessary value assignment | https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0059 +dotnet_diagnostic.IDE0059.severity = error + +# CA1829: Use Length/Count property instead of Count() when available +dotnet_diagnostic.CA1829.severity = error + +# CA1860: Avoid using 'Enumerable.Any()' extension method +dotnet_diagnostic.CA1860.severity = error + +# CA1854: Prefer the 'IDictionary.TryGetValue(TKey, out TValue)' method +dotnet_diagnostic.CA1854.severity = error + +# CA2208: Instantiate argument exceptions correctly +dotnet_diagnostic.CA2208.severity = error + +# IDE0100: Remove redundant equality +dotnet_diagnostic.IDE0100.severity = error + +# IDE0041: Use 'is null' check +dotnet_diagnostic.IDE0041.severity = error + +# IDE0028: Simplify collection initialization +dotnet_diagnostic.IDE0028.severity = error + +# CA1510: Use ArgumentNullException throw helper +dotnet_diagnostic.CA1510.severity = error + +# CA1509: Invalid entry in code metrics rule specification file +dotnet_diagnostic.CA1509.severity = error + +# CA1512: Use ArgumentOutOfRangeException throw helper +dotnet_diagnostic.CA1512.severity = error + +# CA1511: Use ArgumentException throw helper +dotnet_diagnostic.CA1511.severity = error + +# CA1514: Avoid redundant length argument +dotnet_diagnostic.CA1514.severity = error + +# CA1802: Use literals where appropriate +dotnet_diagnostic.CA1802.severity = error + +# CA1805: Do not initialize unnecessarily +dotnet_diagnostic.CA1805.severity = error + +# CA1806: Do not ignore method results +dotnet_diagnostic.CA1806.severity = error + +# CA1819: Properties should not return arrays +dotnet_diagnostic.CA1819.severity = silent + +# CA1834: Consider using 'StringBuilder.Append(char)' when applicable +dotnet_diagnostic.CA1834.severity = error + +# CA1832: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +dotnet_diagnostic.CA1832.severity = error + +# CA1846: Prefer 'AsSpan' over 'Substring' +dotnet_diagnostic.CA1846.severity = error + +# CA1862: Use the 'StringComparison' method overloads to perform case-insensitive string comparisons +dotnet_diagnostic.CA1862.severity = error + +# CA1863: Use 'CompositeFormat' +dotnet_diagnostic.CA1863.severity = error + +# CA1838: Avoid 'StringBuilder' parameters for P/Invokes +dotnet_diagnostic.CA1838.severity = error + +# CA1871: Do not pass a nullable struct to 'ArgumentNullException.ThrowIfNull' +dotnet_diagnostic.CA1871.severity = error + +# CA1872: Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' +dotnet_diagnostic.CA1872.severity = error + +# CA1864: Prefer the 'IDictionary.TryAdd(TKey, TValue)' method +dotnet_diagnostic.CA1864.severity = error + +# CA1861: Avoid constant arrays as arguments +dotnet_diagnostic.CA1861.severity = error + +# CA1858: Use 'StartsWith' instead of 'IndexOf' +dotnet_diagnostic.CA1858.severity = error + +# CA1853: Unnecessary call to 'Dictionary.ContainsKey(key)' +dotnet_diagnostic.CA1853.severity = error + +# CA1826: Do not use Enumerable methods on indexable collections +dotnet_diagnostic.CA1826.severity = error + +# CA1827: Do not use Count() or LongCount() when Any() can be used +dotnet_diagnostic.CA1827.severity = error + +# CA1828: Do not use CountAsync() or LongCountAsync() when AnyAsync() can be used +dotnet_diagnostic.CA1828.severity = error + +# IDE0019: Use pattern matching to avoid 'as' followed by a 'null' check +dotnet_diagnostic.IDE0019.severity = error + +# IDE0038: Use pattern matching to avoid 'is' check followed by a cast +dotnet_diagnostic.IDE0038.severity = error + +# IDE0058: Expression value is never used +dotnet_diagnostic.IDE0058.severity = error + +# CA2201: Do not raise reserved exception types +dotnet_diagnostic.CA2201.severity = error + +# IDE0240: Remove redundant nullable directive +dotnet_diagnostic.IDE0240.severity = silent + +# IDE0040: Add accessibility modifiers +dotnet_diagnostic.IDE0040.severity = error + +# IDE0010: Add missing cases +dotnet_diagnostic.IDE0010.severity = error + +# IDE0120: Simplify LINQ expression +dotnet_diagnostic.IDE0120.severity = error + +# IDE0110: Remove unnecessary discard +dotnet_diagnostic.IDE0110.severity = error + +# CA2101: Specify marshaling for P/Invoke string arguments +dotnet_diagnostic.CA2101.severity = error diff --git a/WDACConfig/ICON-SVG-ADVANCED.svg b/WDACConfig/ICON-SVG-ADVANCED.svg new file mode 100644 index 000000000..40bab4e3b --- /dev/null +++ b/WDACConfig/ICON-SVG-ADVANCED.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WDACConfig/ICON-SVG-Simplified.svg b/WDACConfig/ICON-SVG-Simplified.svg new file mode 100644 index 000000000..b63077690 --- /dev/null +++ b/WDACConfig/ICON-SVG-Simplified.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/WDACConfig/Icon smaller.png b/WDACConfig/Icon smaller.png index ae63bef399cc9e2c245589476f24c762826cb2d5..5aaa2b2ccbc1fe362c5c5cfe021ac1e7141a7c86 100644 GIT binary patch delta 1058 zcmV+-1l{|j5z7dWIDZ5ENkl-EZZBlk_dNniYu`+T0?)ASH3rIb>_3xBA~s=C#uDN8d+Tk9t+ zeKKZLRjG-*`Nz&3!Q??G7jhoWlHKe|chFFV=B{jVttsrgv z0A=Y@bK$cJ%t+jhn__BH$7>HPDtwitZzpYSBpbM=K)NwQ8jTq(I~Nq1I+a>pWos{z zmfqt9?nfYgM}NeWteDyS0+o`Mhq@Y@d*5H5=us;vTW2X--$Pp3x1^;__yNyHAU=B| zf5%PfNZge6RG2MWHyFDs%MZ7#FYkDJMPBfvrC-bkGAx0(R}FF0w?J6%p$eE1lmw{K z4M_Ll3Kp9`NH6Tx@Kv|l9fjMiF8E1v4 zLvOgRkRe{nN8m3nCl|b+*C4mC?H(tnoppc9AG}^5*|H zaL~8F8ij4d6%Mi$d#4UgUmThTb9CSbSlh}0F0-@eWfsaCABW?8_Pq0h7k-CL&GkU= zG!O!lvr&QB;a{M&9>Zn!MplXaENn<_hyJm%^M9a@51xc*>$VvQnS}@6<)HJY6Z6gu z|M>@0iVeQu37)xGv1>Dq@(C`pN8E0~kAwsN-EVf`@B#x-p8h&7__RQst3X;}WAYgo zxO6V-Iyo(LZHRQs8f%_FL4wJgolC;)Cc>CJF|+&Kf#9bF_H!gIvr~mi1cfJfW{Thw z)qkjv;4+u;6v!bF7+!F8pZNq8n&{q!mAK3f2az~4em*cfAu#76uhgzWVPti3TQw$g z*ZoL1FZRzD2B+&h2b#P+o{?kWx)UHEIC99KM1Fo>+gg}q46_|#_<{K%c cl>0UR0mtj~SC)IDjsO4v07*qoM6N<$g1B4$^Z)<= delta 2179 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 z zAEfsO!GuNmUr=?1B?s0p3ZmcXNS3t^jQ{4-(fx-0-ftNKOH?A4Z7=D29Bjei7Qky`j3wsr{ zAqXEFAGtDn@fv#mXLI}hy${>XA-X0T)PTDSzq>Mrcbt)#uqOky-)_It3UtNAvhh6O zd9s{uL68c{c8aFK&Q*gO4!aL)*b83czL$Ex_A_`B#)XFMHHTm7yfm-^wpa{b5Vfyu zZ4_wCF`{>`6Yvt&47+AOB{)M`CCES*S=(1L5O`uB7AUp`XE&f`k?(^HaFZPM4`RiI zR+l>YkDs3c&tWE1p7-T>$CJAfo3%AePjH9+jo$De;88yb$iumVb7f8(T>Jl#0eu)) zjQiGU9XzZ1r-zAwY|fD8z=dX#2((#|X(|Ih@NehZhlO2wvra&!!%T-)=2#H6qG8FO z7>WrcwWpR{4eujs9CRt@$tsKTX!4CEF#ON0{^wRp^!9&l1sYucLo40^6U6_}3WUV} z$CDroJcdIc*XRLf-mmfqi7}|_mj2 ziG)mR&f^v<8qMuku&Y)$aVKK8?=GZZBm99UMHFB&Zy!BB*bv_BA2mm@4YD=XHlcnS;-K#WwK|;RP?;GLDbGxZy&}u=!$Q z9J6(XIn|R^R7A8b>ag;TsM;0hGf5q5UnKWt&jtxJ!IAO=IJ;e(CNe-wZ5z9tFM_`l zhLk@YhcLu%fKC7DTYO_{DLZ`(_|<7&5zgH_iqHsX78;4%hK8L+Z3rS987KuGK(n!r z*aI+8e5-?e5t}$+L5Ja6P+Dsfl1RIIph`**4&- zYYfMFZ$nRX1*9WZcH4&D7_f0oXXWqV8<=7B8t|QVn{!trKK&fg#nHbu2*|+*+qq8L z%t4HY>GpucC2Zt`S@8U~D%5GxVEzDcjbQb0h`7Lm_!<{>w zdX~kBJBj>&s=mFdSOR3)!%u$J_GZqFK`Tzv=?mCwt<=xD=6u5Z=jsqFp51K&7vv*9 z1<5_^-^Qyt!}HVq7P#BL!-SJ+XR|s84w)9cC`jCG<)?rNWBkClroUdg4+GcxcgWz!Z#b?EqMP~dI?Xd4 zX4ttBKJuN6%rNPYpY{Gfc={ha{STg2k>~$6@bo`O`X6KZA7lEz!I;us6fIs<_IAnl zn7`c9Q2Q*A+M3h5KwUVYZ7|m;PWjFGYzP#bf* zAt_jE(9?rtZ`Xp!J$I`wPz-YqZ?JuvWKOM%}d*MpZeM$VYcmfoPv z8j_;73V+F?Q&)(x;(q2nX!*@ifaO*wN-0i^Zw&3Q41seCzSX|9FN*V+u`a}~@wfsP zLCLKfd>^l49Ra+4;_OZp`0HPv#y6h-L%o}(1gM=c;n(b#^C)uySgyCQ-~QNRQDv^h zq*o_o$u^pq+NJEd!w`S}FWQYi$6r2?S@y)}ZixU?76D=%wtF2REM+zY%WEQ!OQ>uU z^3hL8rE}Q>sy}nWotm4U+sE-BCHTfpPE2SX(Hx`E#xV%MvBX+ei0vcng-sTB}^s3-_r$IaLnL_rPdj8vfH zCb#SgYP^)gmBVEZQF{CJS{G(Gz~1}a_bf`w|FELPa9qX;`Tb>q1S+M%=`Tb}P9oDy z*QO@b(PiuWnv2T6K&VH=TwoiSsZ1_33U(fR>0|3-3zdnCFed!Z=VoO2unU~~7&4!f zbe$u?u^uXPp;FXfeUZT_#Dl!&Z0z!cH`nd#Tv>6I_pWmcxKiL^VG%2}@-K^*`+xcj z9~*w4o2Qj>Zba$jg`A#*t9>(DlOJ%$oPOd}kCllQKIL_Nm%WV$VvCudoZ{J1C z9v=&((o{EY8Ouudj}m|i*8i$A!wkvkKki@ci$qB%c$Acy^oPpG6+B7M4av{Zy~P-T z&hzGPAFGl18#1=VhIRGhmVjgYorJG6mkGCvV4+@@O`(skYu0bvyeo*9Y$Z!C5V%hN z+y`J*W;Ej0OrkUu*pG`UyD;7t4AlvoGJGV{87_1@jFBSe0vO723*nb)>Fp=PRfUel z%mhg&37UEkH=lp&XqTvVAH9*n9wLx1`kR2Nbw)Cs_Ze1lkJk&5kUJwzpCkw-twiB3 zE0FRsyy^o~fv$nU-?|eM{zu9K@m`{bH{K(ReP~im`khx9I2&X8$|?m!uSam-<`OF4 zc)nM)dr`)v_Q>zrAJ}zAI++NiW268T^-~0wn zR0r8JFZxWW(FVGOo(+1_ijRdc=7Tv;mgoGk%8pa))6h_HiTPUgrGR}ep7LWw4#b*oJFdQCiE82`*rxmD57?NU7q}B>PffilGN9RvoQhpN!Be^*=KU<-vATO!kXod+yR-}~#lH{^n zSO2mPgD2R%iV8>Vts$OXtZco)wJFr_(HF*9uf@V4aa2M2{M#Z*`_+aGd{K-O`pyO4 zv4+2MsAux0HOUhP6-$$eMbkCOHC}xzI+2vR#?fSI8Iqc$;Lwy|6LaD-ID;gvk!GfTTX9d4`evR&8H8k7DOlN^IoMqSjs zs4IW^#qXTN*w;j_qpvJj0+T%7#66mHru)ukS%pVHKuPbe>Uv*K-KjTBH=uX(~)kxGmG|}Zb`r_P}g2BcuBL$%A1qhGv{&j}H zqD8u~@7c{jFdYvF^`f5Dx#L9&j(z=Y)cx%ikrtdO(!dq!6#hq}R8<0!x2=#iy2o5) z@`2=EWfw^byELc92u-RHD{1DJ{Uz}L!^+TYr~Td)<`mLX`=hvZeXj)OI{7$CISUZ~ z)sLmr!Fu|qo}RDqQK^r3KYlxs@G%IWWJ3g;zN`?JUv=aXhI~oFmeA0nVCd)HbyI7x zD02O@STcJ^vK z6jR+YH^QSh;J1>`eq~I}5_q4+H)tr^4n?u{-yJe7;B=K7<~Vj^_k90+88lMFZ(W=9 z^v-=fzNDx7?xTI`Qhv~>;1?^}@B6JGyAMH|BC4vL#=?O8_i>7Vn z^n3wZ$I&e|XWF4nkoQs1P`b6PX;R%t*ZP0lF7W8t_4R5*{OR}Z+dCpXfDng&mYjhL z&Vt9>lS1Z+KSxb02WOBc#9^$ zt0-1NXM+FH^nATRN6UvG2c$MIJp|o2aITk{Q`R002U3rlOwAGF)BV5Ft@s~BLaiIN zuNOHq`6$&ZUg7(_Mx7hcm~WM@+I%j(9eQ&1!lU?hK5j2p0cU|e$gA(E|9pNmX4P%T zoivve5+Yb*V|VUI?q~Y>!KKPimlF&e@w%$)VwhSa;AuV|OWDu3{w56pj&mP1;IZ*d z;;h0eMxWqP_Tw(gt@>Yu!GK~Isb#M;NWd}`-ZvrKwScWlz*>x={5-wHQXQW-_cxNN z7jKL#-6Tl=++gMux*H~D3Jzd#_tlB86S5^VmD>XMS8rW@0aI}-Kr$)V8wphjWf`jL z)!{hlWsdf%>j(cM$kT?N*Wcf@+IS!{Z}!hA`$q0r9te2ROzTdqA0Wk}zh z53}=rZrdMPWh`WX%Lu7oT%iczPk(@XqHl{?*|E<>o2ba3?S&S2^PK_654FLYJGOp7$ zSHKDW0nv-3&m6rP$-Bxom^LICs05AX4s|>ZSZPQ6ug6q<>yl_;PZ#Zf|_VoiaxAg3uSMkAeIp=bW?CH079RIkVS!#fetHZd+dp4t# zG;%U^{9d)$pK(mYUMVv`v?C8e7ihKY>yY|6^I4E=VJ-WBI+%k~Ji;$V!8KK8w1uJ@kC!*&N3|zS-#)mx+w~{dm~jMz-5Y$r|DQJh-7e@-dmm zV@$_6#bDHko}f+F)gJ-M9|Vb%@y{HG(`bA~FR3+hp^1~c`-pIR%y7VyUKb>8TU zK0K6(P;q0g{~l`N>3J{_>mohQ`zqKQDMFdI{7S_C=RNlGC;>5y?-6^VT+zA3shQiy z@|mnGYD(v$NUH?rY1 z?DtVvD@ryQGgbs^c0WT*#Kx#Z65f`swK;xOaK=&Qnl?8kJxc=*{bBk=N$J zZGFCthHykziX<`oDBR)ER&-aRLHOuOe*DGLg~3rk%veJ_izc>j(43N~X~F{%^mrW! zV%P>hz8R!3Up(ovEs=WovNSv5t{D*mjLuU;MWXna0J>S>IOUiKoQJ6 zoB;}q$_{9s|G3ijzOD$m=A*a0Y2qY}$PT$p^K9-YYf!%x;!@$ULvbFa*Az*MTqm7HroEK!vbkv6OD}?dUI!s-{SY zU$L)uHucVD6xf;0Kt)W{QTPGH(cFQKa=gZ36~or7^N&~F0&Fd-Itq_l--nK3)_Ma< z3J&x}7u;MfFsFd140!irXK=tJ>^BW#My5jmUlKm_o{E?<@O{eUzP8j(*t!mo z?9fV;AK-n+ux&+i-zvZ+28$0+>UnadMv?SB9-t||eloI=*$xEvneOCSnxl-<`;Cj- zXzv&*Ps>x7y|#nVIe>oZ>o#-SsdTIbY<3z%6URaw8<@jwd#mW^4n@soI!XQ|$v;Tw zECpIP0wFUEg)JHn0LMI`<$TITo8C`*tokzt|FE^|*geSoMBF>y+v-C35mMhG4(2^@ zW`pM3+m?g^1k_&s^?tqtXg{5f$s*cwTBDJ;7i_V&kR`0+?FX6WWiGv*C$R@;96JP9 zz`gGebSXXnCN?bMXr}S7n}vozy!QVV?u7%hW*d402vGe9#Pr`Uk3!PmP(4YkWS9~xc{OJNn_kln_=ZcpTjZ&t_)#qiuVC5uJkgXm`OL30k#_>bkM1|(2!O#ba zT2}|Ab$e1uMvu@Fdd52U1ka^*3=@w|f961QL(XOj&Wgg!&hMSIp0$SDg>798=NJ3R z9|fX^4d>L_8o3w4rG<+EyL@Rht}FO&#{QfSf|};ES>Z;x3qJe2C|L`*()58f)L z!U_VDUpI|_NBgcD%fq)9 z6GheRY_TD=uW+pORcmDv9{6eCxvEe(Yl}>Z-MldVM;_oLzcWaPWd;Sr58G{@SyPMI zqeQA*jXhDv7e+AV$#I_7y)L1~z51Z)bPVFd_O9c&u3NS~u$)>O@|T4Has^u~RlY&F zQu!9b`;c8rYUL@A46MFCHxgX@={Z?|>c;D;l^kfk3*s0q6^{-(a=N)GMW(t*^*j5k z7SAEvW-kWpsXLEjsWlA9_^nVqL*H7g20w7aF?)Z;vdf)(N zaSPtR_U7ty%=`VmVMof}yem321elqG*?FJ2@tlP>3l9FGSS`vEB>Qoq+1NMl|%jOIt=P(p#s#keD>{rVNmd6YG`KBRipn4fl!`Bf#B9) zt_B|{p!asRzMokI*g|+L(r~XGr^3pUQ39UC&k4G7`7@iu{I6AoULW%b$wTF!?Bh(3 z>_ykQ#omj;>dkr|4t4>p|LeuwPBbW^MKQh(m7yJ=ztDHXGm`X_4SCMMXXq+@(&xfK zVMcZ}aEH76J`;G18h3m!M-0L}+>zY%w0~5UdMH5|$sX(7P_qd-UGnTH+R1gAyw8f> z7b#%mw(FEWzQ1xrG>fZ#{yQ@F=(oNbp)q-hfB0T-Hg3@09kUBkDu03PeB#gBen7(S zgJ1}MRTT;fX4*TNUEF14jQwD-Hho)-&!mXACcE{17ixsT+Re+RCBZ7Em>b(=hM^omP%V%^!9K}rEdZ10h8qL`U$PDFcR7r~c*?&b;Y1q@>>>~y%yWSw@f z&rfg7{DkQ>m(z~E8{=)>VTiW^Za{-5~eDb`!7=x6G& zdF@v-1T!9BzYdt+7q(Bzr)@(9&fLMB7}y94HZ${Sjaae82`6LXNtt}xT;4)*+ts<}9oJre zYkT+hg5$^fqUvelqcQ5NoXyG`kwX3F+|+|dzh9voniF?`8hOlz0YlrQK^B^GN$nwg z72qLC0&^cZpoDSEPE+lKMCj~f_sSm76O;G2Ug{|t8T&(&`}n=-jswYxej3GRW#VXKy?-FF{pw{&#t+w6E=Wu)qQw;)3 zgg7lK3d-#=^x%KRIEFyU33|f8a4; z1!ujFa6HIjf^+K%>%NG|8Q+sOju8e(Xwi-zKswdc(i4#w=Hf#8g1i(A*#SX2&{oli z4!wIWfBF_88rQDVzOy&*mQ(a4U~XeP+}r7P}04R+IaEcZ2|C1srhEa zMo7!AnzKi))31Ky!>0G9K4A&i^4%k~_?Am^lWHCK?nPYWE+#&DX$rl?vb=oG29vHZ zOi4d?WtHt~AQ0;0t_x#^;7x>L+7RjHG(a*v5sh|_4&M0SS`b;F2Z~a#&1HP_D$w>3 zJE*n57XvnEnj>~Wtg|wdG360>^)x_u7I}v*&7q-(#6u@*(boV7zFFufV>>&MxO?c6 zr$#BA---jy)7Cs1p$1E{IhA!(rqR zi|8qEpXp{Xj21=)Nbd-c{->qe07`9j1suTmX2>V&g&zYqoS<}$J>I90YR9}+S))louYkSfpam;hW!nKZX6EKe351s=_V{dv32<9_iO`h@N4KR0-Es@ zJ^7I@-9KLQdY<|wW6nOa{rqvxMzO{h!~B|^LhS%hhX3?=_b@Nk9=#nIcttb?0?REs zVu-SQ%S#09!+4;CQWxQqDO^He?jMaWU-1X|Xv&4YK>&^c@CZzgut)DlN&sF6&2612 z`@%6ue~TAHda5hfYuDWqcclESIs5kpJ+Q)_#-wX2yI%vl2j+#^qg9dA)8iXqC(PV@ zS|2WJfPk$EojoaWpXp18uKE?_{eDj%wD$!hywEXY}`Lu>gJ)6Rtm$iwex%O|XP2OKHb7b~jODgq#4 z*PAOg^L>8XRKZtBYOj-?z60-T_7^+>w`{Hg)I3=eMNUDHE$Bbiu$X|?a5f%zQ%ALh zeCuu(*}8`NG_xge=X>DgL$-447>u|tGwlLp?Nb)jQ=p>jbjE86R zV4%FY>&^G9$rynp?F+cXjgX#>>l$7!+EVo+tsAFa$}adGF62k*Nt^sXe0_O5)cY6r zm%&gON@Z{KYtx3J$TlOl)NN6>q{UV$iYy_rjJ327_o^ge&`lv)?Q29?M+=oQmSjry z(HO>f&Sw_YeV*6z_ssXRobx{Cyw5W8t-h}FpRiJ1=(iOA$gTa+p1?nO!W=E38|;O0 z2>d+fDp9=A!a?1HQ)S&_0I|x{H?|r=fzaQ$QLp1jLpeLl=O86+;GS}wU{#E0%WNjy zMbV>L#c|?uwL#+*%>{qmTHwAAonQZ+y}F<_a&r-O0l^k6cxz3Wce zR06Q#aXa7{#Tylqk+=Sn?f&_x!*h9Jr1d8R;smQ|DOL$@~;4i=@SC#UulkLMV@y)IMybUdMS;_^3h<1?p5!->TO z`U4fN+3N${T%WqT_>FdYC!~oD`FYes1@!h+UI<&p=XLF6DXIz|3@A_e+fZ8L#b-ZL zhaYRbjCwwAfo8zKfTeceM9#OHg`~X!UwS7ppul){664n6iFb`p)6+7xklfZf<#0L;`}XG^95RSh^?!6m2l6!Ro=L1-07je9Lxei`B*|lR!^PeKZ3K~ z%wFi@P5eyWN-*^5sgwaSKo!~i#i3uWK++n_TqBm~l=ac7FK_bGTT?t?@4ZJ;hivcf zEc8m7Q|bD4^d@dbw8?oHzQej8;{-#;FoRmfnrS3LT?kCt_~&x+YN5f2 zt`n?=7HeOrlH}J0ITXj=_T;;m*fQ9DxFfC;Cv?Xu`2bK}x zYH!&X0kEV~ImpTJNF_GI71d1fYc)tVoUD;_^T5fr72a)>o{6h!O{Flpd~t*9)PqDe zPe=Uh2S<4F1wsBQ*pbf=Q$1q)_+W-U~IGeoykUmc<*b1woOqem;ig%2e7B=h$= z)njBZei$WAYtfw0D&$>=RfIWiWVK`t_9svGNnK=16@?EI=}9P|Gv(Cncui88e1L+i zA~a>UEI=caa`yOg0vy{x_qW)D8}Es^h+BDFmvaGa2)^k{A_IR0Y-7ti7$RDahKPz4 z3?x0tT4=vN5Y_^_$Z0A!62@+U2UI%lLpg3^x99=Siyx!H{NEX)fh^$j-}574ehnjJ zU?d6emLW|l#?jB5VB!iv9E%yyn0}uE(>*Nv?`mZtBIlqaCefZwg5@06g8kf;J<33h`YGkss-KnMYZ$Iz9(gEDm2O7%j5=gY*EY zX(JkuoP|UO?=K3c7E~01`o`P)AE^ACe`1kJ&wFGfq@NOT>-LXmz zo|*}%Nuecb%9F!YcTwg>q4B=BhoRS1FHr2vU49L|WT+qbRg*NCVnNpxKx*;4d9^Dr zpuynX#}(~V6E#wuUVT&OMwzQ|kj6!g(cS`TUb%q?{cRxNu3ocN_pzn=qN^P+I=PGD zNkFJDu*a|7AUjD*tose_x4X)|SN;nQ-tpq!`KPo+6@zEt^}oOf*kgo;Nkzzl)JeBE zgDyIJu9IpcVDPL^=L{_xHzd<%@|#&jXW~}UPmjx?2miNdM4KgbI7~}L z9YBs>AdW;~jyhP~_+5G+IyWS`zs}hCmjc8O9tWcc6;%+Kg`eRC6BnUq77b1FnBzN% zAMu92+0zO5@rQdnKnb3rv}O)&8hZvm7T-moE=BOm=BZ1D3hq%#ECY?}2jjX>B1Jt0 zS!cp(pV>ujR4t%G`xm3H=T;G}Gsv9Ki6GRBnPx+3GoeMqLmfPpdwDPx0dcuiznqf8=53I8b?iBRmKprS;6rgJP>yUbFA%vcVlaD@Zh@KcmO!kO?-- zQ~S*;96~9F4T?M>Yu4uWJ?BmN4SU%6(4P$B?xT(F!$#YG-{{eq_QCgi%m8FsYMWiy zpVDSnhOLAj+@$uz+@GuV|CI5WRdW#h&f4SumH+(&e!LRr9tfXW4hy{0wt{o22FhuGTse9q_w=E}aJbjh#S>*K%@6Fy43Lf7?6AESmtY&j$X3 zD%(uqu^sRjypQYG1n81S`(UlPUlwmKk2c-O#U_{3&f%mCv~>5}&o6mDY(MJdU}=mg!_Oy^a*nf)L}P` zI5q60P}jjYZAVtejA)_{jrCzA@)<+rcfsne4wR+WRtIi{Kwy$t*vHx@sS#$WqRwKP z9U>aRD2%@*m_hIPug!!MVkgn#w-~fcMbH0-g*oc1s2PVXARbjPaO`GQ`+Z>dhY#bT z!sb+Z)fMv$VHkKD@dAnmx++kAC_Mz_+;UVt{L^t^UME?JFpKW#;*nia;QM$OheHsN zUi0al2{UwZcJfo|wuvKw|&)Wnut z=*{mvi=PP&e7Yi}M_YrhT4q2r6ea5{%5|AIcKBS`CEO64VeKbqhC;!n!YNz!JZRYW zNKKmwC$tD^K(htaKo!Oha8Nf+etBy-pqJ4!Y|ku+ z`}W$lP zqAe-d*<^khF9`R^6IxeUj3OCqP!+sHRs`jS?it!K{CQVn-Tc=?Gi$GXqbR+Zth-VN zISqIGNKLB=r-I|N?unm0f1dYRt9d5DATaaV0bwi1Rw+0bc2L8h)E3;Jj|!Zc;!#+% z7)dR0X5pfR3Zbe(1gN9-xRaDFjwcyc-Q&HfW7HK z&FCIC?T6{XF~kydk+2NWGf+D7!dDGMG_c`CZ`Ch2IV)3yE41hi4vjd)v&Yyca{I5U zQ@tR1wW!9h2omFi{ZB2)>-8X)IRiF<;kj=`h-7?_m||S~hLz*NZSPEaprja6*PFf1 zHa~y-jV1KxzF)c0?zLaaDsX!aw3AghVlMhoM;;Xq7C|O%Sdk@z%UOc2qrNV{6a$!wgL~9=Rc1u$>@WR5!STrB`Ny zZ5%P|O6ZxQfzQuPA%5c(cRS+8r?}OB4>*1qwIt>bynZD%O9e*Ty&2?<_n6!ZgCK9?Cp1Vq-+Lcsju_}i zv>bkJdwIJJ+z-YLX{O*aPV?lmARmjnA*U}&PKl^%0Y-=pe5YQ+d!gH)bkmuCgjRH&sRM(O*u}M{Uo)ek z>gu7Q_y86Y2a}5Hhk2}?_W|;1rCzQ5j2@qM+d=)zF{hZVf%uxJKqHYyf{E5M^ENp^ zBA6)N2qw@8Z5kDys>D5nNZtv{LD;bq5EA z#sG+x!O-6=4iKVvsKrUH=P3?PvY3$BXohXM1(la=KiA0lVH;%ib_!1;{a6y{G5;$B z`O?jCp5C;N^j#{yH+`l~|CRa`sw38>?1i55iadgFXZ3(GJY1&Vg2Pn8{$`Ktj1*kB z`Qtm7TdTb0z_g$k_S{_kHWa_$66Fql!EE?pLg~&p9d9UceOT!^v)jy{dIA-7?@q~Y zhf1Z7SWx8wXsJ2~Pq1UY^Xzz6i|cq7!@;+G4RS^YkG*OIJKL#|VgQDg72*ws zritHIe5~_8Q~m<1-EQyOuJE3mfHmVC0H?2n%>0r@(a%PQ#x=;O9>(-Aa@&RMZDmX| zVcb_s81hW+K*)2mb0xkVIFU zbe4JZ3~HF!2bLzF8Pj3N^{mAQKz9^W>?_sRZ^S`E8xnzAjBr1oY6diKk>M;4ILGmU zyd~r81$QHn%2vlzfy%I&&4B46JV0IVgcN64!+fx`rKofNPWb!)i?q+SB~WBo-n)b} zu1MTJ7b37z>_vKc3h_npj~S>PL9-Za5Fvd2UUsiBs$m4f)}3vb;L@k`fRAz+cz=8R z5(642=nP%_g;x^Wjp5OYU4d+x#;1 zZS}zT?Ujr5E0R9>SUl}4z4Bz(&$p(wW$#K#!0o3_<(KGs1zfL}=#cQ!x|QoQ6QUa6 z<>Qx+zJD`Lm)oTdO}#{1K=@q;Ca7w>BqJq>6}%)>%suMcH={q!67H zld9#5u%lIvUdorKkBiXXnY<*dGFVxHoqfF{aKU7q4D%d9-EoOh)E_tv7RuMMA|dnL zgEKn%)i`K4rtK_chR2%UpSo^mnpYA0$IR$@n9|d3DcUfePh`aR>6X^_KKPiS@0IOa zhj$bB58{5Fx^FHgHGi6Vb8$9(3JX#>u>L7UV|*bT;~oLduhQZza=P>Ro~A}Fc@gr= zJG4W2ohPDD)mOX9EUnAorPP6eAm*VULsn^$$|Ofd z=lqOuHPkQTS)E_i96D{ym}w z-FT=63-+0bI>&%;`rhHY5YI}vmrE4ZB6`Kbwtn46<5MZ`cXK*32?R#A5 z|MZF2K~t$tuEm>4r_%xdUu(!v)kk9$cKkD@AkY_Q$Hqx55TRn%&9h4X#|Z|+1Osq; z^A;P2asMHmweZ}GJ-LM9=D>E-W%x$@olrpz8a8FD@ae(uHQsj5&wIH>HS}Vb^h5vN znUp%!uod;lgjMJgFCDl3n|!4AhDhTLX4_+F(zRvU(H0W^f!+v7rwu zA!R3Sw1j%E9!(XWI9!uT;GgA@|H;X;u=yd%gvDbEndfanpK_iyW*c+Oll}>&`V3pau?~R;V_-Lg?oAfckEyI0E8j_;ysMuA02Uz zoU2xxKU|?(V@i{{s@8vE!*${gl>DPx44Lpn(N4JapuaoB3{#IF8cdusw=sUoEgvMq z4^vMNoVWI@+h2V4NB-2mx5cu|NChVpJL&>!seaq%ijg)({X8 zZ&ja~cOOR3Oh`-?3eqx!*K6#5#{%>YXMOeULSVwD7c4X&s^c(^Iio= z?~6$z`;OGzi?#6f@6diE=P5zl>>af&pR=D=*nvfw*t9)^^)S6d?I~N341jHrOWSO_ zkTaj)4g>sl>-=YYE?n}0^wA@2mUu!r06u?jZPat9mGZP4Vq2~Wqk+NV_^M{^Nly(t z<`{R(fi!Tjc9~h}YD}KsPCd6rZJXq?1?~-cIZdK}NQG#mh`A?id&s#fREwmMI34|6 z--0uCeYutio}W*H-FOBa+mo6*qg31!&98Xa(BNl2Ek@>2qRfkPDvLFq4yW!EN`X1o zAKZ(|SQc(&Rdbt4LbV8#rywG4?a1AA0oSKT+bi|1@)_NFMnD?qZVY*Wwwyqwu@C+O4vl@W&f|HZ!`b zQ~&$4nOl3{OfXUVRq<9+Y0q2_%i`DSiNAWqB41Tw{O^9OnuAI!!*z;Y_g>SjUW3Z< zx&7axZ$Wm-s&3k#g4va+tj*^xdFCoEiT~h1ZE7Ehus_6JZ~<;!3A!tJG2AtQa(kX# zrM1WNhOIU{=)C6C0@RM`v^|0o-W`)_J11ynh+cH{^5}DOZbz0r<=*Vs*LchKz^DxC z5#V^2b2$jII@ijoU28U$S^R@|IjiYvB*$yI0 zt~PD6Oc-9KiZH{Q8fkfWxk=FR-65Wy68kAS*5HX~>mW=B(D#85ao>;eGJA}jwyDPj z3lc+&^pMY(-FGUq+vYtut2&>i@@Mpw`k>mVrAYS$=ciBmfbR+~H?(5d#RtwOnUNhL zVUQ~7(RlD9LzhC|bH@TVcGF(4HgXHlJb|&!?=9O?)m*$@-xY@~ekQlf_VkSwyCt%f zH(b8A-#TM+?q4yHo13Km8WX!l+<}~hr!+7#zN4{|BB=O$lUlSgb!0t8-nc5sYN~ut z3;wRfOvSO+PYo1x;HuiWop$Ud*Z*t^{qlDOtWEI3(xA%y>o@ow_fb3iooY%@HwiM_ z6*6~ydAAt};R+>W0Ybjsr@*GU~uQ-m^#9Z^lcd(BH3m)`={UjE0|C*Dd>d*natc_G2^#KP6sh zXaRYmn7yGxtSO`yQ=u^c+ z9-+qQI_<7u`!)aB&GA{VsX2$86oEFBdJHso;912C__GL4vXtmi)cA2aey7{)6PRgm z)=#6+=_tD--D^BIAp@l105`ke5sZ#0*z8qb@7KR-qxAo~SxxXf``(5t4R7IcBlf@t z^bMbAp$gemHJEZSDLAXzm&VcC{~x8@^lYa|H=f<>nI(a}Kwh0FEu#rye2>H^{b#g` zHECisX+Z|mcd1T4t#F6_gCi9MGG;v|u%PYx*XL7~8kWIkUc$}XKRKsxe4hOGX8nMW zmkdK`ELzIkuqQ7`T4oK5?ICG|&H01<@Xdd||D>aAPhi&#hfQc3$mR06U80Ul#PD<6 z%C^cv(O2!5&9ZoX_RyC9%Ay+7e6gVk!{Qz~pPbAYY;S?Y3*>NI-Q+6K>%UmK2_Veb z+~bG^)#)7_&vRInq-3NPv~Q!E+HCwSo`V0X@_P6+hDb+2@mK4fuc|kA)pCKCkLMK- zUo`mU0U4P9s9>NgU4SbMIsW%(uR%>tJt-sF82JwHo5|H3k3~ap7dNyo=E>VIJM>fn zhf&{gh>|}(W}OtVPYw;M3Vl&8xD%t*`Xn57v}WOJ&9kqDUyicE@g=~V0E%ljNu_|> zAMUbGDqeJOtDW6Xg`F}$3Y$ne^Q_7IN<$-tDLw|tynW^%4&w9_i1Vx!>w_^QCFIzj zu0#UR`0#0bl(_K;5#a)9!v&O>mma{)v@M`HGmD_nj!e zXOPWkN>Q)Ls%&{9vdjb~)k1EcP4M9HtZ9d{3&r*ymm7UXHMzSyoh2~!pzfGn%^5Oh z`GTAq1cDT~dTH7(YHdLj@za8YSP^W-VfN>7`*V#mBngZAgJO74f0dU7KaB*YEp+=@ zj(EZ+x4K5No?}?hX&=t;z0sr@GCqRkLwL)F{QR~X(51Gf+Vqr)wHVn11^z;woYo3q z2dP_r>h!Ju(0g#i`O|jKopAC9V!UsYc10-fPklnLbID?mHmF(tj0fuYL{I@~UP26< z9o!bfb|wAue^cSnd_llFj3S7wr!lO4wfm6G;LX8Z0yGDEmWc6tV#0VE zrmvOLubVou1|uhP7#kW5JYfeM&t~8+OOVazA3{2Vi9ung2&s+2CypyGA}vUWf+HP& z|Az6jBRC5*ym+3*g5mQ$xpMDHzU05vPP5rJSz+Y8xyPuij9~>pLro^3BbW4ssC=y{ zUjd`K5#szivS%jEgIm#fUL;{TSRIGHTGlo7o%4nYsB&Gg|Ch%IQ$ z*}vTZ@%Q#JZ`J)1%bL!@nHH@gIWR8_$_fOBSI-g(u3&~WA}Y5(gU>aaboN{TYs=Oy zYYE(i-4AOg!-3G+Yo^zx3D#!mY32NAW~j`yl_voC5Bm@9)idR

$fr6TbQzQ=E_Pb)i-!rnBSo7Stk|Yv`cFJ%k7_b?p0f%wDCgp(e$dzW|P5}9Ot)8 z=4X6QQ7;&ibeI=AdSdq6Kw09h3qh4ZWGgjt;?|`efesrxC2oZGC$_Q<$z-=|pa*g) zV(yK6x#dTq?+MWLlL!xxICMQXN!fQwlv>@nqIAueTd$9VlAF|DB=65VWXEr_V%50c z=S4);%A{sAS-_iW$a|ExNW8Z_*H(-%@_nWnVil6C$&OT0X&*VUgQ>Z(Y8oUvXDdn= zk)_gzURzT3xAKkF_+mX~zc{(E-pbEpt_9n=-0V=bHZ5pdbgZ!l+UAb8@HqwR$Sm}3 zgPWhozSSq_9&PuMy)`jCro#hn#?X85FbVzUAYBvcI$sZ|&S6|tRks;qcjM43^tvIH zkw)8V?=^3H#2mxoR={BCe*RU4FElmBt1)-he`R&> zEjg}aA+tmr3!i6Ect(k*kS@MLo(jLLWB0-XmVwtpWt_A-bCy#0UOMQ`2U!a?K1Alp z6$h$FteTm^sbZ;(^e6puD!cK&s=QZeu&W2tv9_8=vhzP9k{rB1r+tU4V`Q^cNFFEX zp*y=NXtqEJnFYb`Pj_re_Fj$YrRR5ST2}4lerV8LAS(6L`?@BK_$ph$bB8_G6`TZ= zINIZ0suiE=zjS|D{3pz@C~t+018<{5{zg?cf(K?($^^G?I;9ri#W0$Q?OUv*VZ1zR z1Xmt@fDbDBh@3XDWLG6`feOQ$-ZIkRtxD8l>D%~!Y9B9)gFPbeHqcMEuuuRQ>_kRUz=tR$YC4Uo8{x46y7Tg24C)*^YnGZx_BPY_x$%5yzb7x3JCe#3N{%Peo$+Vizz4CD%Aur# zByZ2x_jSYDO6`u^^am7#mbkOC9CsuMX90w`V^|3_a9kJDx4%gzVEy4mk{( zt_Dd->y=>Fhq_4k=iBaKc9~z!%O|KV=ssd2GT z2*N9=y0+V$e$UaabHO}q_VxtYan@6u$T(X*%$K?g#68>ps&M%>^0;3zOC7s$;cm^$ zuH2zRIHj?42`d0=W{*Gs;Z040naD(Cr0Z5Y7{J=h%~taQPhh%1u(pN?wYa%UaP>tT zHG#8S+C&~tq#4Y`w!iUAC@mX1#Q_`;Puj;(q^wLOb+M<{|9|E`r@9Qysvy^c3h$qsPxJYJ;ZX|8NGLOiqcznVysI9_{B zKJFvNNbBgdPfZ`YD)JG_5&_2@OhLwhWFAKB6hy_v%3EBGdWWA^pHEDJGDqGD$~*q~ zuY5q~kNLmOzb^Xfd#|OsnC~gyvG0+)Ej}KemJLbxPoWrC2824nW-|%lI)3 zQxO47#T==1{jI0rZP(}9;-Tr2q8}K;-yriY+F<2>QB!=1x}7fT>i~V7i`kkD7kfLY z)8gs34l&FxaAdNQ#^x4VJ(Fzo$?KI zPZr#|0B()RMgvVsYudwmq2HsXFWu*!(+XjCFp~kDAmuV39kFWIli**!wR`QBDMMzW z7m!952ex7~CHI8V>YoSP z+s4tlrL>dxwf1E23ZIg_7h{gs^4h%q7Mw93ow0qmBA!@vpeqwO(Th4-xrS4>Xa;kz zCp)dW1CI$Yg*nb~r6}x`PDi9fXYc~RNz-(|45R(I+uA=|03}OYo0v#hcQ~D+)vV-% zcFsJ6+Zso4VoXY6A5NND%^O%Zee19<^)k405kw(_BuYAm?W0QuXlLYn4Ml^RFa7BA zFA*&2Em*Xp!6yT5Ovc%tvdgI}SXdaO<9QNzK#{)so^CFkwo}Vjr0hoB8@3JKktrPFoj0j? z?G+_6zK(WCFYbOebCwOJwR79HWmOjLF+!xD{ld5hBHOP^8L3b^DT_!VH8|Cpa+^5# z1CM2f<~3+)Q1TCvoF7c1ac)4j{as@*i>pgl$Lt~v_WnEY~$Y#VLd)9@`N0HQ&_P>lu zN~50`6b%}Ex-e^4S?C#G#I}hJEtL!cxF&S$<0-CrsxRt$ark=0RCz4(@h{lghQz>_ z2cVf1S;ar`BQU0(Bzss^>SB7Q`Shw&0W&KB-C$5Y=dJh$h|ML{FcTy+u^ZI5Zy~Bx zu3fnjIuBeBg{8hOVjou1nFC<}6t_ZYzs-If5JK2wbJK8w@2_(#<%5mPW zthjr4FYl}SK9yxF7GY}CJ=v|JUhWnjU4_O{GQL_Cz7E?zY;5Om@8qOrq?PM1(V|?8 zsXfTkXcI)q-ZliqgD>NW=Sf}JpnkBKlHl8Q6lnM2F#oTOb_w%Z*u~Ru9OPY$u@VBA zd1@N}``yEfiXCc~v?e-wiUyqr^h)rdcw-CFfus1Z-@PAvG`eIxvjK-0K3m>0@NVwg z=Y7+Qzu+%E^VbKS1~ac5eGt&h5W}S9-m;_kY^AIcF(UG!e2$i{k`dp>_$X}&PiCoN z?OMmPiq$S@pO*9;M9)1Q;YrbT@?plWy7>W z0_HZ?qvG<;E9o4$>9ybS*Pij~_iZ72n0R*}R@0bwZuz%tmI6OkSE5)ArT%TXk^8*B zp1()r@W4rnkf?E%^@R@Mz9V{u3$sS1qh0j1V)4!$vef%Ukkype@tOJ)(9w2RaIXn= zAoJ@Ne#qzZ-Idr0g9A)k?o z2PfXW;aUHhb-Rw2I=P7k&Hu39c95C3qJR498YV&g;Ix=JzI>;Zr?h2&Zgs~Yyv-F0 zT6ZaIrneWWoJB(}LF)2mE-oN~#!oAJxmVhClEPA%y3LVu{J&8hmC!4K_phho#ajjEZZ> z^X+ioeT{xZXbJnK=3me?38I0?KG6QaKei957gRUyKj>q{D3-t!V)8zC38jbFJxFg> z-1+T1M)!J-@;3U5*J1Bq9x?K^gyk~Dga zVfOQRS#M+`R3{c8p4{GOqezNO;%Qoz8l1n@h~6%Ssm`-tw}!|h@fcp7n8+X)p?L-0 zsJ7>Uf&*7FHE>vF>gz09&Ul_izcyK!3ch$V%0V#@F19X+f2GAr>W?RKRy77DG)ZCl z8tIplWQ=|FQC`wM2YI2U*Nl6-fD|*)7nLMkT#aFJZZ)F@>o<9E&*Gkl90Uz*N3i$p zuMgz-rG91~GK<$R_WthhqtnCe<&r~YFaK?A?t#GHrs&4AHfodf;$uJZ^NkLf>*|Y; z&HwOnKQ&nXnukuH6evZB5Tqzqbax%slqs&juoxXH-|bfObQP)6G!RWHvSFHb8^^Is z{R_X`p#KRHT;h#_BfY7SJ29H}YYXo8c6*`3gAz}(XH(`1U>)FSci5XTDUL}lP9M${ ztl7m|(_MqKG3qd%IRk@Vd~E!X#4?QW@au}e(^fq!38Dc?`;casVYQH#-r(n@40v_8 zTCsuRZCg2CZxKxN^0&^nRSsQ{NYh?OKos_j`1Q+XKrM<_Fxx~iowAREDKV)lN|%h$ zzVa@zO_o}L*mZ?~$B~o}=Kd+)5G7Nb3&SgG_VoBgHdZ-6g7FXq6~_&+#Exn$KBQ@> zz-|mo2Ufv*{{FBSN-H6kpK8}|s)(uZxaMF~X34C17&SO0;4)pMg)JgbrZZCJ zLCE89=YKBxYQlk~;cw3f5XT(v-mRJ1GZ@TMSa5W#nq?B{CgrPkOSj=uNZd-mqEuo$$ss$XL>(X%dcbgC+;Um2YaRcgMB*`*? zB&R@jNsNHv112>~GI70RA zKxen2fZx_~y1^^$eFB0F0qs~aYYtSqQ&vowm*aN`Q6mlXQDT4jRM#2&I1VEmSK%U0|Ka^~y&&Q(Hm)fZBd$hM5N~IY{L8;sQX~rz$<3dSGR>l)I zZ(nqW#{+;mc6%;o8jE6{d#!o~wD>A)M#Zc$C@7TI@I#klyPoovn(uwd-m@`k#m@Vh zJ}39iV~G*9g(m4U()G@vvFKraJD)@+I6}$7yRrY%kEgt6x>8mIUK0rW5fV1{yTzbL ztzdP#)sT^VEur~IVl=#Ezwz?)%Q6zgwZg^6>Scfu&WU`9e6c>Ay!M5QZ_Twr^BG3r z7-lqkfS#|=^4vsVMaPhsIqhc$*G#W|PFiLAk2_}>s$v);4k~tY{VRnU)H?3U?V>9$ zX{76a@EppwCq0%=GWb*Dz3+E=|Nh>gc%pQCK)XfI=IC)6(B zK;jQv3QxIDN`u!HYUVq06_Z&6EC|2JYv`oFkOFvX4}AfPo{q`c9F+K&)vdgu@Gtz9bu%dk^HP(nFK zM2dPMX(3|+ilHxB=P}+;)W-32ISte zBa6l!bn_#ej@wmln@|JmP4=D%#LXap&h~J({&-v<4wUNCd?13#927#u%oVCJjdeCRmmqPxO-c7T zJor#3Y?%_oPsb6>Xky&u(Tvqljgd%Z&A@`(-^sQ-Co1s+eh&)HI#+7J8BH%;?K@!~ z%`M3n#~2Zgd1JSsCn0IPPXIw+r=HhqWkTb5ogM%}Q<;2@)uXyru;%CYytq&9YV@z?`!U`IZfy6jIZc-u6WQ&iSNWWQ2C0bpV*_(~jM3YV;Q9{k z?XkoYmka#O@G+DfgsmU`65iC{#!toWpDar%5ygD%cs!pZ2z3qU0&eV%`Y76>c>dCk z`*2fECr_RUmIMz7%{ z%o21!Q_V9U86 zMAw6KASL&|%*Uv?JC$u7EXf*f6lgUKr8FSP8;U#ll5{|}7Plj|5lDmqhKbFyWS<^% z7s4f+mu>3=>q0QyF9cXdDR#go7?jn%EmqA{C=wL?A>klvr||t7QAz!^t}-mS;B<}K z5HqjARoZyxPpIge*9F>XH!TeAaF=aN`Huskc&uzu<-{3EAzvEI6uvY4M3 zaqI;#^fEF`?)PhdH|*l8TlIw6^4EbIJO-{PqJ!+-KA@=qMRdEt!$OI)iIuxjRC*&J zGKmpC3V6;UB|SYTg{h!J&8YMmN)pgTT@)f9CXICnSwvlO?NIgs!qfekA2*I`epOA;^z;qcj=O5-I3UX zGM)fe>UPqtSq*24%E9BzJN{$9|F~5TUJ#$q=3xs?L!$O`_j>8utj6nK;l0wBopegU zSW|oGwDj00`vMJ4M<^L(^~aM{5I~+nk8DNyL(GF`aq<5YZc*{ai z%0~RzkqS3QJ${-?(Ajky{sRLmQ+V#h?Jcj~BH&7y421n>9YtR?r>g&NaqT|+HD{C; zDslwb&1T3xALS|lD63W}X;h))lkZ21oxqQJejZ*_^35NWbhD=K)ivDJ<&ABQ7GonDW z;o2y7+n}+>meLBsl*1Jf+B z8l)>M(Mq6>vxR9z2C7U|gVo8t;1!Q7o~MOGMGNi(mBDK0@FmWK3VMf`kpAaNyfUxr zd+%EScvho+ois?2n&oL8D7I;(ujx!g!Sdc_q*6=bj3+}mgoX~(kXmS}aQeMxKHBOlK*+S+Spn=lV@d+ag3twa1qp8J@ zgKfxXD*3V>zT$~tItyxq9LqE6p$KGeK=husMI{LfSYZ-2l+4dOIS&bFF7q)eJ{U7u zXEE%`vAnT&cUN$$FlP*?m@ggTj?x*ndnRxFYE6EcKzj}O$E3rm)rJl27)BG-_Tom| ztr_?>IX2xfbEI`wH))VjpHG-@O*%u<=3~(`p>oM%fuD#u*M>smD8wYv(Mr{oe!CM67s|-QJ^8Exu2pgEJ zGhhIYuib{O=B;p9OHo2S56D*+a=NQ6ZFpcD6h82gJf8fzq^D?)Gy%g_&$qR`C1>&6 zsLC7%m3Y)B2`lFKd`6m}rN;830K)?`(N=iu>_mQYX*Leik{BvU&|K%QREl;nd=o_6 zybP{g!PmPm0b>C#L7FgFU+Kkd{bl_^j-DsHVvORZ%^O0Xk;igL6m6!wr@*;Djo10- zm2cEWea%SR02zk2}_}XsbY;tKV4tsC~$VT(v3Dm6B9h^-gptMHX~Oc6(60_6L%Wwq}#d#EGs-}`@~EzCN5FMGh5TBq-D zJ9}vWdrdP!$aFPY1z~p}ZlyfenlV5J_5l+mm!*?X3z`gN$$i9g(8X%~eHWN4F<>#4|nBh0>~AN zA`^M#z6XzhH@WhwjoGaj)QXsHL(6w31h)O$rgmecJmGQQuyJJUGv=| zUc+GEWpb$l4x_2LCj=gXws<61F}jzANx@wxdN`GUOZr)#{Zm;_&$0pjREOn+VM^** z!!1h2yjH&i;WhbMREkrSkxTaIcH82e6`XBJ8^*_plr~0^@#N6%q;=iH_B#-2BN8{8 zqaBp?Z^C`eFb&L>z^M1mUyg#texNdm*N=sEr01p>4br{X)_&fa?|pz}=F%Zgkjw)9?4O#`!&V z+87uJ4+N~QwBQ>2RO6{vpKGJ+k$0HxF4$d@zFq3gD0E_I!tCsmU7nw=#O2jYz5J;} z>6;X``&RcgI26U#q(1}Outtcu(zX1&VecmEP=ZW() zI*hk6g6=kGUV(nm&}-DYhdR_2xu@Om&x5~T<`fREtC_YfxpWx_Z6RU@9j{S$-I1C~ ziYS>f*i0OIV`g@Wxh;@ua6g|jmj3%W3lP)V&lrHZuxGuS&qm%91>d(azASkW*V4D3 zxOY*Y_Q{x}v(@p$oJ!<8wao1w?xO#Wf%8yuNuxE7)fQEcddX19IbC# zv}uoDAiSW|J8&p*w_E_j)>YAHWLZhZwOFFe?+TNa9&7Tzu%$=u)UXuDk!W&2q98!( z(d4?=NSC7S(9}CV?d`Z!T@HKT$`>1V6DLhlO79t^$p{M1ZmGd)7SV;&`X_|p`|~*;pyw+-j&)v`+a40RAOVl_1I=xmIx;4a;JeM zbT;EdJr4-pgh?&-2!-37N~H8QMzxK}yDArtR0JB0Gt#EI%SmxwtN@m=&rA0SBkwE5 zY_@rnOTp-uCL*_@&}?eRoMP`e8W`B;SZz*WIes}N!Y#yEQSz`g{z}HYoU<${5rDtZ z-J+;Lcpdt-=zR&~-_O|8O3WrqPR@rC(?>`v;On2J4S4aGxS6RY$tp;}SeoCdajTfyb)CTlkp{-y!gW!RM_)RK~5NWBVkh)>kcV ztZj6C^V?=6MZm_}6vi*F#IVeIx1opb7My1gR;_r;|8~HKc4`s`ecHMCZ<1Uy1zEFElch7S#O15x;O|fb+NtTAa z+vNWj-bUEqxqR)F+AF40fdp$GtC386VASHVQg>Cfpct{8&*RH((>dy|{r)kz&PKo+ z_`_?+`n)+BvOs>9c41p|cEMcstE4*LLDmMss90 zsouTQe{!7X2@I8>3@9Sa%nS~Fe5_y@leH7WB$oo`Q4p|{W;dzRpBmcb#jOp8O8l+; zoXZr1wJojWT+$;;-bv_?hIY0@2zZr~W4mvxntdTB@61p+X-!U>s?w)fJdxQ9>I%Y4XwT=n1fi)WNpJR$6Jx3;^t8qogP9Uty|x2 ze&l}t{U|SPv5dT$w1RN1uM(939CEL=Ce9!CTLOM%A55MpTC(M*59(ajs#aaX$#Z$+ z8ZaieUt&LwH;UB&ZCh478tw{A62pS3E<-s*sG=_Pj7eZ`h4>a+m2kwbd}cJe)-*5* zzTA8vqjQ;X1A8Ck&jktPfexv&<%x3K`*6JTmKD6w;S!zbt6!tebGecjcIxrM^kLob z1?fD5F#f<9l}Mhbd0t(yjJai6QEj@UM^h9=;UR`*6wq~j2dm=2JQ$4oA9FwNJ7fnX z#_LTllId(oPEQtiIYsGuUf}--6rXz%&2xuy+hDi*Pfz6KZAiZyNHE&ZtK`t`Nfh|b zenlmDb&D?MXbC1dn;!)aU97fngm zDW~vlKi;(zkkYH+`RIG<@3qp@Rhv0S--WcB)n}# ze$FvhKb^rwE7d+7yC>7FpF~|!fBfnm?{i!VoHzlJ(V3<)mb~HtGbK1cIzam~Eq=l3 zBBsyhH3f<`yg*2O3p9*%o@;}im}mLi{QQD!zCbN-;Tp;ti&cjQ&zWuG7QOv*=itvG zUL=$vnAIBtiJ-i!R@t}DHNzJ?;6LYuld-;JIClNTkWGZu1nunSV`i7>gL=(t43!q} zxBOR{^DTMcNOI{cQH;!j(c&ieYg3K1Mc4UBvR;k&%ZeV_xvGj-r6@zAzBCb>;1gQe zN#(Jw$FU8R?TqYT(5JF;cwrg@gLs;$@GC(xW$n|~@P6pwt-NK*<@gDwx#JT=3}ZH& zsF<1ahXv*GP}3N{?QrUZ8Yfza6-d1PG5ImH?CMv=*Y030?g^D^fcI zD}N*p9u_tj+tsD(u6?cbZvsV}zx1v1Jgw^&N#IidhSoKW+=`6a$uuwt2L+nrsE=@& zu4Al3qNjFH{0`mEpP{> z$sdvY1kMTS2K$p?xXkO|UU}lr<+sA}`NHL4aI%Zo@UhUoN*>&xW8B?>L8p}TvAv9k z!W)%|Unp;4@MfvcO5aX;SL8i$Ah|SG1jC%>_BG~i_Hr+rR^SdtHO@Psa-t*uSv&FZ z`?2>MN*n=LwVNg`8>V0K+ZbiTub+`|n{|0^L_|8w1+|D>+YF{MY4q0^6n`(W`Zz!G z8e|lRkx@J<-YzZMWzQIDLLI_jH~8JfdQAuUi4-ZQjc8lXqdN&BJm9S&6%7c`$mYs= zH_&iB*G2*uPE4N0`6}}+{&>!qbaeZoTYP-qaD!G#VKcfvBQLKZuxjiChezl{NFgr% zI;h2{#MlA;+atL&9IhRsrIIMy!p>F}P6TOvl?ag$76qfgnBKU36SG|pB@fN4cmJoo zFaL*njUIoahAyHFQRr4G3XzB~QK?j}R%I<&Ze=S=*=Cf=rQ%jX5)&c`WywBNsD!x* z5k@3qCzE~op68jFKA(T#`+a@y5BHZj&U2pgoPBvW$;wr1b>__(QH8_W-f+(~ONYwB zN>r$gWLVST(hH(xtlTdg=^WhZFRhP7p+rW$+z7Sy;{paW!ipVGLlZO8Bu33*yDqfG zrFlfOnb=&D?cqFY2~2j88ucH($jaEv9@_+kiHuus%G4oQDEoakI^SvT-5h;TD`~v> zXT7>1rG_6o+UY0Nv5R04;Z|%pC+!$o&H7fM!%?1$^2$)PsgFN8`?_g#Yv70I#h%%i z6Gytde$$d+B`U?fnUCKs!`a3EUZZ6+>1z$7gAjf=CP(?{(4omO^&36-$%HLOa2N)B z_GJDjeLcWnCIL&XhxDEnKRzMw`{6DLB=E&Q;u@yi0^I>;Ts-%(22BYV;&?mdRs1^| zM_R%4Qr9(IU7f=$7fnuqrZd#sVys<$zXy|8pf|v61*}HiOv!-a0z5bg&5h%~U%1VP zaVr$f%8&&PK(G6ZJ`|7^1Fj2hF>^PEYsWdMLx-b0+l)5|^@$u+1Qpmd1Q90#b;4m% zN768-=4L_NV#CInlkCy5m88D}l5w4Nd=a}%;4wzpe-0|(56!DR181#(0#`Hjy7@Yfwdu7y3Ig6} zi2GV_Bl%lsDWc$x+Sl%Lkis$?Yo|hl<5O}gc)%dD-;8$Q*M|Q7p(kU`tw-uO$)ior8K}_lR^EDD-H9Cr@s_#pYtoW2g!+9+)#Fu zc0-e6SiqT&pPNR`T>NDK`-TBU1whOsO|yGDc}8${tBw1X?bsl@K;_=D3-j@B7#`Wf z;%$=T5({+a;ifVplhngt8Upha{!AN*Rn?P*=}Sm4(gR2$t{fj^^HX#>7hf+OeEM9V zEH~^%QA@^V)RZil)PV8ShU>GfK#lm$Ba-P`d3cieR>#w@r=du@PQcIXN5)}8yzVLa zwHQ}SpQ7rV`yibKl&n|`g(#5>k{PlkEiUqG-tLiwKm5A?At2y2ugN#xl%GOit<&(6 z6kXKRve|~JQG(n5&H~quzQNN-16yH{r`LBca9&F-0y2m7oUR>Ykb5}KctA84VKAFm z`;MYoB5i}1;GQ195a6!!ncb)i-7%_OjOa@R3P>X1m^)CPk&p>N(D?5W1>y8o;v5wR zTrnh(r`M0*YrxDh1aw4&gW-`beM#>OuDCi>)@Ke zFKZNk2blfPrAq%p_d9naK~lc_jO|NQ$0U8CVI+JoH~W0}KQt@k7^f>5DEE`=ffc2b z{oaE}2(3>UIUXp>=EhsX;_)v{L1b8eLBUt-(=!a?NJYzFZ#~=?#NUCaTf^uhNvrm7 zy^^hkKeQPWY5EY?!beMMj}8~1cC&Z$Z!U{`B-z99i7?tm>J}Tn1IIGR@Kj%Ttlsh} zfOOM`Bk^snVC#{?A7I3G0i;%Nlr~eQVIqLBI+o*BQP#S33n*E?qdY{t59!SjB492h zBA2;_t7wIz<{g*pfHG{yj)Yq^(C!ZEFWf1De`4vTwjcpJ? zZg~kqXf7icJYT3R2gprJ`@c?1=YF6&@*Z=yKxu^@=M{!*P-`w)MBVg6BiYXp?7#UzG-`zDlm$9d zbD@dGEfGeZ7oZuD@%_CU$Weaoo`X5*qB9dR!2Si=Xw1vhvXIFmm4D4R8BY?GXrsVI z@E~ft6rTZV*$gzKU+yv7z$3whD#aP$ia?pi)Q{2g(6jAw0$Bdw>mhEOm=m{Fp@KjY zG{!WPvwu|RINZ z{y|9SewRJq?X%0h;q66H7oL7#ld|3iBVV}CvD1bYlKK!Y;MVdGX%FiZXG+?(aANp@ zVXP&_XLA&FM|lMPesuwyG8~`0Bt{S=532f9idE8n3W~(-#%(%(2tKcJhS}q*uChy- zTl>oJm0g?dd)hyt34d;tczlt2$1^@F0coK|)SJw`yv_3GBCwILTRb}0ybPiVUg0NO z(?)CS3gptXZbJ{gI*s$E|zOc0S5=v07oTSg|*Wer1 zSM86{U!C&PgjXsnRo-npV4KI@fcoa{M zv!0Z`KJ1Gg#^&@vrq`?mw2{v7_$VXK892x|sDEiz%)ugOXdZlZf`Cm{R{lzLur|Nj zx<(AnW|qyz2sui&-R0RW7*I$d@5k$k3XkC%ysO)4+Q@GeM^`udFSckh9{29?>&O=> zdBrY0K4Jd9u3kX$hb`oB|B=;xBaOxm`U>m{bGXx`1mrNXco+IjX1UyU$_z@xLV zID$N;Y49Egiez{_FC!RsLG4CU7_qhh(-sNSPic5zM!FPvy>U_T)wzqH*{S6XiAG*g zV~F2d$Ry)5U;-ltO&0R-Epq29!)RIHae~WXy87&{V?dl)_N^%iNdSi=0k{9!j76ma zZj~UHBbz@h1)JPZg7C#yv+aAPVPf64nJ1LcSvWTho=^5{+uh6vyRN|5hmbzO;(OZ9 z%=VOAZtHTgl{(xo4x?)9B50iJtG+7ywI5#@s#V`IHFtXqr!hJ>woe&Ck~lEQ0mWM( zmvpYFuQjA7L%2wNVVssXG;A6^SIilEWoHKrasQ@N`qJ0tW#%TMi;xi-KvQ37!PbB1 z8gRz;_}_{<3jvJ^@L>acmxoAQ4JjfB=Ux~boiO2A9j3Huj&w7OdA5R9Yp$5=>sk%{ zccKWzf7qFY;Yg(f%8|Pdf$bqzNbeDYik6@urF9-4vw@IW3u*`4F*Y%9w450-vpLxD zFVaM9J`P6BOiIIOuA<&b??^7nAh%{M=(s^2p#r`Lk+#2Pa7NGq3CT&_nJs-^uJJ*b+k1db{z~a~4Q`NB{&!)>6yb(*Xv}yukz{z^@q!&jQT#*C` zL6h8M8{djF-YmdBJ=0=bqCT_mD6B|y!uidJ)8uMJ|r5@`rzU!y^A-ILrmO5n47 z2F@>T6|S1pP-4Ykg@tf^*baHPZpzOBE`j|(;3gb9YQev^_fiL^6I5XUwN=B@C*%6G z3XW%h(ilUOCCxeq^6qDkMou_qN5*)!q@{=Swy)+6g4L4@LL+b#kXi(3ARd9>fNF31 zm2a7}>*y@qFqAtCPi$UOu%AJ8fq7gnm)U`p(Y_@}l74x14#42-xCVC?-dv#6Xg_vP z$B=Pty-@z?bq5FZeIrbZJTnN5!bd{jltu~OMfkb8kXhptRrMzMbgN?%B*dz%_xn+|bpuR-LA8zo;6)30dc3h42MedOd#9}bY?T-Vl z!Zy^08_=8}9G@mD7ptd)6c~m+;xTf2fiHZi2lf;C((*3MUZ&|M0f&f+?$0winJ7Ky z1UNJqAzjYiTE^utzY7=hoLU7DiL9VoQ^Bx)=MNP;qqO8&e+H?s;Rp4Hi1 zY5-{S5or);&2yUIU&s(pWK=n8#;%l6#hI(O?6ms@6adh@4)uS-=?ZM29w;5K90bbo zj<0wEDDO9hLmWfy4S-P>1f@)u!En5!w+Q;u3i}x!cT03AwlH6Rd4hi1o3#MP{>Ywf z2d&YQ^cKf(Nj$hVbGcV8R@Jk9tQzEQ$nr_`lG`<4TUtan80pvKe^k!g)c)iEN)yb& zwQz2n$0@unqey#8A&l;H9!5x(2KSM^Iv7%5iX2MM?H^j!K}ENm(UFX_^eYU@p(&pd z?YSQn>9HtSGNhy;NEiVMdDgB-k!SD0cedx=IpZKi{vY~|9zJ|n#(;7kgPuqA>?4w1 zH)QuAm2Nr5{hCF23;G^VE6)F?K_H2&ov3JncHh`$80#@ER@EQ+mw)|J)tNKFh>Zq7 z>V2N*F7Z~yH{$U~>T9%|op;B5;~d@1>@LL4xaTlpbgQR5!Z7p+NKk+?%nag>dUQ;_ z7|B!0d9-5E7V2jN!(Rr4VWIIiX0ZFlQ>(iHmLJbV2>Kpu@nHL7Ux2b8|KMYu_F;<9FOzZoe%RZk~L`Tlcqlkkc941 z`x1+ww>3O)TD{Zt z-1vRoUo?ebJ1@$`>IvV3F1bt97k_Aa7e9#FFUBqrnI25id(?BRJAi=-+63XS_U!1i z`{$)&H>`3MbyWrWqxdRaDqYlVNVDsvlV1wvcV^c*rvo50Agh@9b+P7zB`qRtpV&vT zj(y3`uRp(vL@BeDK#`!^bKBS&CHpn3bI7 z#2X2QfsnfD19hA3jh9pf;}i4F$Du37-OioLY#|d`keT@v|?l2HF0~JEPHwOpTLdfW*y7d*U}>`BE9pzDgl_y zPP*`+6=FsAj(XdW=E+NRyk4Z9aYsGu(Vmn(8`czL@&q-my|bk#x0>$6w<%Cn;Ele3 z#76^?y6Ut#RFYON8BzCXu|it4ea?;jst`(NHP@}G5s&%97?rk(f>*bL+n+sr;i6}F zG;%9cxw^rkjn2XP*;EOG$bfG#0gTEMiGubvN8f~wjkh9IE;06#OxW;9v?!TF*8NWY zCF-sU>S3~DMUd+dRQ07QlA1d`hQsD(iN6*RX%I0}5MKJR#I*{_pv=8LJ~g8$Sfb>_ zm#iZevJGWPhAQVV@At6QW`4T5wHmk;8~rdg|8baKy3p0(pbbG?uEP1$TJ&=a~plh-J86zFx!;*O%$knEY3w6)_lnbBV^1LZVHGmt0q%cpQ1(6lD1je`Bd^DN-a_WviZMw%r z49NIl&CFiq98CYC=nLlsUeEvb`mc{rWPlH_DPF)(jD@ythc}iV7*55cWqFaVsCXhp zkc$f{c4gV2pWz)`CG)c`Lujv) zf6=q6%vl*>Bki{s+z_uoXKVk zMYL#~D{X`Pky}Bb60P^8oU`uwf&74t(0RJkk59Sp=ic(46goxP4rT9&RJOe9_^`uX zk19J_jU77lm|638LHHQa>%|u0r%8qU#^S4jq!O3Ip61hk)Xd#c-bkpveVo`T(~&_bt;jf(91^oWUCjorRhmCzi_&VW5vuwU&LjM!XAzQDBKhQYNE!&Ox(?S=^qC9S zq_6V4h_RjKnwO2G^H5)^WOB2p%3#8*0+#vy`<&gDYA>(*=w2i3AU#u=eF|Aevayqe;-};d1FtakOkxq> zBb`Fj+gxz|;b0IOi|~n1f6cVsPkyg|2Ggc@Mb}+82sbC zuT@^x1^sF}^J>3XtaiwXen3T}20LCJdzAZLE{A))ze^8U+#-wJQJivq^`LRk44E;T zI;Wno93v06SDcp?@@94BN{`AW#CBI(dL_og>I|$#)ec&YPb>ac*`p%=gC{Qk0Kya>*!Z|pX0kcX7{cN6hFPxA#T30W1`?WJ@8d6nb{8-7fnthd7#!c3|dOnP0(_yV}*;t zj(N)yi-LuA9djZb^sHpRt8R>u)H#U&r8bxrIkD8&m1O zmAwr~AW>>j&(vI`F>%8{59(CsGlqn zj@Q;-syVS>N1*&^ar=_TR%V-}AkG-P-~Fxn_2*q2dW9To54k9$g`@{sIVJSGhKXhPFKN-H zet#p2uF&gN?iiCb>zVpOy<&aSCz{)iAc(lmoKc?^u!w7b(^*cWjo+^=-t;g)M@6N! zpN3XS;BHYeAct#sECm}}U5b1bA>df}-daXuf(~33 zcZ%c-xwK=ajJ^ERmnzQD(U4sC;yB;6c(N!0db(O*@(ff;>Kw;Fr_68piXBw2^K`#( zs;6_I1i~iuC->2m*o!T0Ig8B`UoA(T_b`C{ZNL|XUDDqmpc2Is%t5Dr9<&S4B)^^A z;sfeJop-tOXw>YloQdu#E+#&=yK*pi`GO(rrg0MZDMYT{I{xlaB$xw6gE_)ubIU2$ zuf}B8f#IhNV>b0rA2g{9mnUTPKw-X z%-kEbQrp8x*zYnFg&?-DnYJuyfaOmUn&kJhxg59wk%U<0R?V4mM#EtQ6z3%IL^-%; z`Y-S_1}Y!wTt~^opTvp8wvJ0+`v)JkTy|qsu1AoJ(=zwH9B`rFMB4RqBxmX=6x^6%lM4A1`LevDbi$84H!+^U$-GP*I%EEp{D61w&e;pZY zl&PE0G*GFob7kXW|G~<9M!Cw+S`M2czE@A_6!<(Q%&phdwT&U5ZF9%1>?j^(&A&Qm z=^+SHiTHABMgOfSBQDph9zQGX2g^WQkd@P>Tso~hbB)ok0|D9W+4ag=Ushwn$3W#% zoejR`wuU4<1(yd+izg?Mzaef?la=kkZm(e*Sc)FQglA6H8%6n1UVe};upUG()J2)u z%--DxW=k31Ahhr!RsRVS@uLFj5jTpf5!uA*jm|#>Of9!dc;}@xF$MU7i0>j-Lf0vL zIRB!y`{uRc91P|D*nbeJ)PsK%*&Od5Kve&1XHM?Xe*w1m+a2s5vZ~Co9;(2`_D5CL#Sw~p9rgxN-lj!^;gFdW ziJ#8863M8%lwJPKD9YdW>onWsT5&5vL8NM!T+Ncd2WqtYm>#In&}aOBsy{JHl?OzM z9v**03P!7u`-*qgu>J=B%{J7^9~hRq67)^fcAkjgOxAm`bsx_E_eu}{ zdT@2xh*F0FOiS!4T1?H7|4S-zUrL|Lx~`-&S5s`xF1gtvRgP}=0Th4O8mRQt1&_4} z-aVS*+N0n57^OOd>-C(CEvn1w%9w#xO-g)0ZQr+;_}%Niku&otJKc2$#-pWaYkHTt zSYtwL-lD5=28*bS{T!jR z(MqvZqOMHfN>HhY?a7Ri<6WPws`(_FKN)oA{`oFbx+HuIdW&=LU7F}Tzw zYg>;0>v!J#a{n{KITtKrTI%=(dZ+(m7*o~~fB!x7^D4OQKDJJ;1@kzZp6aAbWo+yS!!y%u zt?SPQuXc;km(&)Ls#{E*C#;X$!+DUyAB;IejON(hEQ{~RCTfdFl`W>G`i=j4q&ouU zm-??@ccl6UGOrNGACWYHp^(1c^L@Kdt=2Pc9ham7Y{PW=yoqRm`lRMIR3*L z?XBp@*OEq|$d1yC`@|U!{R^!#oxQKF&tij0Fb)l0a5Mb;-CAU&goy0|^0C#^d+6IY z>t|}R=kPO8ceJhU<`2t>2z`htb4~MI)3QXqf4bgi^uPDfX$4LCGUznwNc-w;tAM)O zZEuV?M$hb&-kkv%d7AzOz00+qB`7#tA&y-OO_&mzY`QMj9kCAb-q~tQYihCinDyd@ z%S$xM?hgM!S!Wxm#8yfmJ-wsmbNccA2v1Z?vc>{P)yPKmA9zedLE)>1XolDLQgrS_ zXFi*b!O|e4h+eXxtie!F#3@~3p78Uugb%W;``tP-9012#{jaJlq)|ZoK6c^CEm_$H zf+EkKtNG-ZKQYRw9<9Ie$QIM2^<1>7H5R^H@rL&1`qpDZakTL8*1Ao#F|!Bp?;;=1 zy?CrJH^!hDMl1S$VWfCxm?sJv)WD_;{P!^Xz|4$Hyf($lLbAf}hL}^bMBu_r$xs%s zrx#4zx0IPq#BfrMZg=Uh2VP((C{9!I9T(5JqgIflqwnX$x{MtoHGGSN=0pG_uPe2K z#Hp!nL#BzL2k$kWPGX+uQo+(t@>kOrO68MW<=f>RvlcA!>m~BfR=^PH#l2B8-$omw z4$!(5;=}59W*sTqa5mT^aN*iQ{&T;qyfjRosB`ag^Rc8=NZog_8w=OfKi>UaM=tbI z3pT-oyLQa*RiAj6F0MeepD+XBLfSVY??PZ$xkJ*QhFIqb{nLhb>Dp-_fIx9O--Y^b z6_!gbs;6sy#77VP*`C<$WO}$+PCH}hkJJe7mZOtZ&f&7EoS$8hF-D3K1nAFR3S!0a%iv^-zhx)R+S*lOvxW` zQw2ucK2IF74~?CyuE41y9s5lp;|k7epJ+k9dHCN2^nTSteT1$d7CvT8l!6w!` zeK`n{{8)@*TZkHCeZmiZ6u|~IbQ>}?tW#21>9}c4f5MjTF#rZqo7z#E~t+mh3FDq+mWrv5q4~nA| z=HipzGw Certificates = WDACConfig.AllCertificatesGrabber.WinTrust.GetAllFileSigners(@""); - - + + +namespace WDACConfig +{ + public class Program + { + public static void Main() + { + // Some test + // List Certificates = WDACConfig.AllCertificatesGrabber.WinTrust.GetAllFileSigners(@""); + } + + } +} diff --git a/WDACConfig/Utilities/Hashes.csv b/WDACConfig/Utilities/Hashes.csv index 46fca7540..e69de29bb 100644 --- a/WDACConfig/Utilities/Hashes.csv +++ b/WDACConfig/Utilities/Hashes.csv @@ -1,119 +0,0 @@ -"RelativePath","FileName","FileHash","FileHashSHA3_512" -".NETAssembliesToLoad.txt",".NETAssembliesToLoad.txt","D9D84A303C7F0E17A7AA69EC8969A9C124BB8839BCAA207CADDCA1FBE1DCB0AAFB9B10DFB78258AA26D21F2E528A12F59727EA1566C84CBE7E7EF72CB3287942","3F0627EF4A43BECE88A1D3D2024EFD862B61B07F0F27FFBFA82C406DE0BC5570AA3BFFB58AD0C3CCAFAE8700E02B4845B42C9889958317281E47EB70C4A7C2B0" -"WDACConfig.psd1","WDACConfig.psd1","F4E06797DF6AE5A6CD4DD14F62974032B64D0F28F76A5C248B8E85BE312866A3916C0304356A2BE7D7C14AF87E7AE4DF16BC9486B4EBD46563C5990159829BB6","45E6383D19A7753852B57ED33FE96B83AA6D76FC123D2FB87BDEF48A1C1804D71F1E078CF0D14D2A3036C6585180915B6589F2C29C672991A325AE10DDFC7BF7" -"WDACConfig.psm1","WDACConfig.psm1","27744F7FD36C25AC55A15F85D11185D87AABE5D4ACCB4BB23CED8BF5C7C0F5D3A6B98439528DF634D46645FA6A115FC36D45E95462FDC72C5098FF5BB3ADA1D0","39F241C87A68E7B7000B63DA236843C4BF1B13EA0822E51E6600CD78691718CD70AD975D8B5EF3C851BE4A6E3690412203BE9AD7A6DE4855A1F13485F2ACDE4D" -"Core\Assert-WDACConfigIntegrity.psm1","Assert-WDACConfigIntegrity.psm1","BF97FDDB723EDE8BBC8616C72F9ABFA8394D4C3BA528F54963FC01BB35990DE90DC82A9687D95292AE0996F8692C9C43B5E2F32D94009CAFC74D13CC5EDF2489","D5C8CD62EC9D3E64EA3B2269DB900B5DAF7171F429810D6166CEC6F8AF18D75477E5E0C5B991284604A3B8981D61C59F6B80A1B38B9820A247FDC60DC9694147" -"Core\Build-WDACCertificate.psm1","Build-WDACCertificate.psm1","A2E15EFC9852B25597D84A801D220E048F6383AB3067A86CDB441C814BE5D770C4A329DEC27A728A243A82392A1FB92A50D2FCC9185E6A8A461D73CB8A5D1225","6243B57E4393D2AC31988AEB82CF6644402FBB9E17CFC1FDA416798304FB57880E0BDBF9C9A9FF068EB91E05B71D8987B2A7F7AB5EB3FAB5258374176F3D8632" -"Core\Confirm-WDACConfig.psm1","Confirm-WDACConfig.psm1","1E0F0098A17815BFB817F0F8C6C8E70F0AB6A5F286F81135FF629BC82A2FD4C5F9875D9D1AAFB89C2A1143CAD3B4CBD376BADA4D457BE413167F8A35E1C0B670","7DBDCC43CDDA1E44E19EBB863FD01BE1D06A2F7E4D05E27F2C572B8E928999EBC3D023E4F9AF807A3ECBB712C14088DE8082BC8828300C8AB8DB7DD90861CA94" -"Core\ConvertTo-WDACPolicy.psm1","ConvertTo-WDACPolicy.psm1","0F9C1548A496057BEF8B26F0DD350618BB1D3A624005C488C1E1C082DACAA49B88DD2D31C456FAF15F2744172C7C5FA5E3EEC939A5E852054E349926FC461F90","1998BDC952F94B7DCDC7D0B2B5737C21FF5D502DBE4BA9FC2DA3D00A9CB9D64ACDBD2AC565A4690D1716EAA0FE7A9171BF7CB40E4F7752525FD9E09CE636E4E5" -"Core\Deploy-SignedWDACConfig.psm1","Deploy-SignedWDACConfig.psm1","06B1B37B0AE1FA3C04E403FDCED22510BF1A38811EBA71B43B2C879811698F13BB141A9B22E81BEDA685E6ACBD05D168FB1E08F42A333ABC15B1EDE66220108D","7EF761249F4C77A88E48F4C0B47022C0AD63AFEB469B58D7A4C9C2B1B55B7B87C4EBCE18C728E597CD3E43BD879073B6111468BF72C69A3D754C444FFA178414" -"Core\Edit-SignedWDACConfig.psm1","Edit-SignedWDACConfig.psm1","504808CE4A141E5FF8AC1DF443B8B6BCECD847B9B648323C83431185FB91A924103AE8EBE7D3B01A5AA32287F96F0D7B8FDA75039F5C45F6361537B2B3B81F77","7274A7097F269E846B8B7B22E9A409ADF9213BD7F1A949F5CBA3960A796AAD5A008B6D1C72CBBDC9743DFC169C817D58A8403A0A4480F3940F6EC72894B53867" -"Core\Edit-WDACConfig.psm1","Edit-WDACConfig.psm1","F061699C5A8CAACA9225FB5F2D0A0550D2B2FCEF602AAB96BBDBE271E9F8F890B967922EB7A7763624A4B84FD30120758BFBFC3DC8B27DBBBF2F82DF9F8F57C3","EC4E9D62E0C44F0B1E5634A16C2B03631A5DB396E556ED3AA7866FAC7E38E68FEE1C6B09FB7E95933D48FFFDD5E2113F00ADB64239FB417712E35BD9FE8A51FB" -"Core\Get-CiFileHashes.psm1","Get-CiFileHashes.psm1","EE5952F33747EA151D9E260587A7446FF5DC2CAB0F38CE024349090BFDED121D96C85CF02BAD24B69316C9AB049823B45A45A281AD9E8C6944FDCEE8ADB4CD9D","D8E6B1F77B178ABC2D54AB01CD32F2B8CC264B9C15A0830AF79E485DF6A9C8C04434116BCB89BA5681583B4160303F67DB68EE167E48F7DC94D496D9A75E106D" -"Core\Get-CIPolicySetting.psm1","Get-CIPolicySetting.psm1","6FF6CC75377C37209A144954B5547FAFD7B1A0E513545E862C49443718CE0645813B3744EE4710204C6C83BB105470B10B6694A95243E75B187C28EE8D9102B8","E730D932B91F4C1BF02600564D9FF69C9936A3B0D311F028EEC04D9BA95E6AED1275B48A1A0141E142BF5E9959A54D41B4156A3C3509646DA295C12A0FC7FEF2" -"Core\Get-CommonWDACConfig.psm1","Get-CommonWDACConfig.psm1","04D07FF7F780BF7160C0E98F0FD51C5C0A53FBA8FED84C37AC2B22716C5F788404B3EB459CAD3D03B7B9B7C7CF99F941C8AF6E557AE5739444ACB1D02BEF6C82","F0F4CCE9F2857CD3C7738211E1A8854A9A1C01A82350B51667C7F8E11B50D268342D5B798B05F69522F83C4CB580F497CBD338E7375D182D4FE05AD520FDECAA" -"Core\Invoke-WDACSimulation.psm1","Invoke-WDACSimulation.psm1","6424A7CD6CE96F0854BA7D9F9889E932574CBA0858AB246AB5D82DEA06161B9D217A3195EABADA32DB5B3BB2064A62E915C03D3A00E882737175EABFA96F0EB5","F03184A5B9E9EFCD6C41CF1E7C036DE655052E2B7207C1BED2FD8000D8F697E852747DA943F5A4EC4021688A4D0C89864E1D12A73C6DE8DD6AD77ABD84C57F4B" -"Core\New-DenyWDACConfig.psm1","New-DenyWDACConfig.psm1","5518BAF5E11728D802B7D14F69430F93E2BF94523CFDF87202BFF9926B3B90369E00F192F69ECD3399E4293A4BAE9D22DC2EBC5645703A952CA654FCEB795BEA","BB020AAAC19536C8F413B8C361E3E91972569D059850CF3BBC0302CCC6DD132AFF01CB8868837EFE56E66B806E8E7FA40B0C6B1941F30B71410C7C5C0BE4B1F0" -"Core\New-KernelModeWDACConfig.psm1","New-KernelModeWDACConfig.psm1","148AF2F1359B044C542A6230053BC51303E6D224C35BF676C5D6E0C81AE42AC0110155600F82F6BA9BDFC758C307B6E5F0BA5FCB89762656897E2C544189B1EC","473B6DB90D3603AD269AB12D759E6669B45FE0D3DE705B9E3C70B977F09FC683AB3751FED2019A45CF44FBD19AED208DF848EE5637AA455F73AF0E091ABBAD12" -"Core\New-SupplementalWDACConfig.psm1","New-SupplementalWDACConfig.psm1","63F6BBC1AF6B0E879D03ACD51D11FE0B4B58B40F747216FF6DFD6AFE03DEC2AD656C4653824BB7FC0015D0E42CB54EF4BA2C9BCFE05050157B2D505A23CE0B18","E09C46923364ED84C416B5A22AC665B530C08BD0CE8424A4C2A529E95F2876BF9932BA554BEFBED73950122F9642FE7D26436AB34C02DB9E6098C682A5C44A6F" -"Core\New-WDACConfig.psm1","New-WDACConfig.psm1","3B2350686EF24C52C79FE879BB51BB575333B773EAD0472BCDFF460A8CF973A257CADC5E06D9B0298EC5AEEBEC3042CA8F275B61D656B9610C4F3D2831574F70","72287A2266730E6BBEAAC364F0971100D76370ACD9BC4298CD64AABFF03C8DA8B797E19C4FB5F662224C861F47C1486B9742146D5626C166457A848A0CEA3039" -"Core\Remove-CommonWDACConfig.psm1","Remove-CommonWDACConfig.psm1","12EE53DB609E78423BBCF0A39761552025DC34BA67921D17A8CF9939551E9D69DE1664FB2990327E9C9E6D896824B056454C8CC8F389FBF908AF625269F971AC","F1E4628EA93A4AB883E98BA87512D3DBC53DFF979D18E3B071FBA24E4858FE320B237EC979BA2EF367EA71B957C93363FE35F8D9F69C9A124DCBDB7353C8FA56" -"Core\Remove-WDACConfig.psm1","Remove-WDACConfig.psm1","F0D33A8010F9141C545F347AA6E55BB8F555CCC379289450F4BF1EE931FCC1B53C16E2B4059C5BE45C1EC68074CF5362D07E92B4882224EBDC208D3898DC655D","90BF363AB9CBC3EDB59E19DFBE477FC5A756834F5DA644CDD5C589C0E1C4325E8353C924414F02A647F6B2F2B94B907BDA96D14AE58D427B0F73D2F725ED9A47" -"Core\Set-CiRuleOptions.psm1","Set-CiRuleOptions.psm1","36FBBDDD30520E088E27C4AA746D780DB8132620CE8BE4B70A818444FF841EF8C9CB885DF1EDB9D39A3C63713EAB05A14BB78058665BBD79AA20137D64D19955","28FCCF0E1C130300E6735B74C60A8F9F1BCEF6FCA53D9FF188AF6EA66069A7CE061F9AFD068C58071E4CB352824ACA703BC13B24E18925003EA650E4E3E93DB8" -"Core\Set-CommonWDACConfig.psm1","Set-CommonWDACConfig.psm1","C2FC01D7469D336EB6DB8378ED1A02C342092F87E44D1029649007FEFC22C667CA6575FC6F0DA0467E524C3DFE7343CC317EDEA99B1410A80F026223F6164D70","2FEC541BEF4D9D8156F27115F7977BA62E63B9287E27DAA8202BE18F6EF79DEE3E5793A7A4E6A23BC982DA8D437DB927BBC18D2929D0C2244772282D382BFD59" -"Core\Test-CiPolicy.psm1","Test-CiPolicy.psm1","DBE26C5F7AB78ED28D771BEA862C524C16EAF7B5DB02EEB36D749DB9C59EC333EA14F34C8453B7E0A5DC761CD0D46DC81E934E3A7E4E070559DF22C89EDB2671","C572D0139DDF62068DBE2EE263A2ED40BED63283B610A7DEA3E8DC67AE2437C93A0B86DBD9A238BB43A7887704B503B72F5EBD1F85A749C34B645128EFFB45ED" -"CoreExt\PSDefaultParameterValues.ps1","PSDefaultParameterValues.ps1","57BCFB78106074AA05DD817C7DC8570AF6A8F77E6D87DE227BFAD4978D373A42D4BBCAA4D22BFCEAC8125D1E21CBF693ACB7FDBB326AD28AD884F0CE483F0926","324296E454C53FA304148E4B92CE34731A5F3CC00F7353D9152AED3ECB9313FB242DA731A1C1874FF00706C1965A2508E042F6D5B46D48FBAA3C19171015F159" -"Help\ConvertTo-WDACPolicy.md","ConvertTo-WDACPolicy.md","C9A3374EC33A1A1EE6B85E993312B57443E958682536FD648982D7B0D79868A800ACDB40BD114A0FAAD774A35625745E66D8495CE1C7C6BA61677D76C3953158","69E35F64CF048A7840E9A936A5AF8D0C8D857E181A8B8DFC9B70957E70E715417478F2B4AF77E876F1E511D65CDFC73A498206FCB58E1448E5ACB69EE371DCD5" -"Help\ConvertTo-WDACPolicy.xml","ConvertTo-WDACPolicy.xml","A94A65BEA4492A68E9775112BF94174CB08500AF7259E18A9033BB22DBE8E94D0DFE5499048BDB4DC0AF5BDC633FFB4D7DC322C290B6404A6DEBBDB9D9CDD616","EC97142D79661E04F4B70B8190655B02DB573557B9667BD774F2714DCDEACD758C75FE8CD16C4F1BBDA162EA45DC6B4EEBD4341CD087DE408DE28378BAD92D0A" -"Public\MockConfigCIBootstrap.psm1","MockConfigCIBootstrap.psm1","7B07C00354D4EA6D80B2F2871EC9C1E039C75BC5E5E2356F66F15635831BF18E8B20D51214DE1913BE2C74ACC9BB22E6EFFFD8944A8FB824CBC8EC275D32E8EF","CC97429BAB23AE5EA068D2D68BD3002EC3CE08FD04DFFC0010DB9884E7BEADFB443F027EE73D09F6A56174A97CEECA440A4B37D2B4E1599B068C68D46CDC579E" -"Public\PSCustomObjectArrayToHashtableArray.psm1","PSCustomObjectArrayToHashtableArray.psm1","DE2B991E67278273C8431E428A49D517003CB786E9F15FADF0264462D234BDD45C19B80171139647791D1C8A68B8CAE9F8CE998C69154EE281D77786BA278794","E7DA5F31B198CF5545D30BF47DA2283E5CB61F0C6B6AFCA3DE59B6331142ADA28DFE1C7B24390FDFC86501DCABD1FFEB98ED3F083308C986DCE11350B66B5920" -"Public\Write-ColorfulText.psm1","Write-ColorfulText.psm1","BE30FE6D269A68CAB377A3E016DE774D670AA32F0213B0EC1CCAFA55E3B90DC904C64AE8D36B67392C92F5B2E4A4199BAAC8499C262D98344532CC771E4D00B5","FB9418374E5201E7FEBEFB77EDE2198FCE5F130AF200AE999A649FFDB7CB41A5D47BA5C2CEE548F4107594064D0D3E0DC58DF7903A1F8E84EF803946FB30BBEE" -"Public\Write-FinalOutput.psm1","Write-FinalOutput.psm1","A42114346920BDC4D567D8923F362D145659B6919B5F50CA4A88DEF961A6528A34AB89A076862F1DF8BBD46E5835BDE156E2EF8819A0DBB7637DC71F2666CEEB","64433D0C34588B1240B27270C24D446BCD91B5742B739560764BDE33EBE18B5CDC659D57BCCD8061FBDB5AC780D7E8DB7600A23A670B7A31182CEA191383CE29" -"Resources\PolicyRuleOptions.Json","PolicyRuleOptions.Json","1519B7A7A031923471C33E2D16E7FF05F3E40F06C3728B0E04D9B5669F893E7907809EDAD55C84B24565EA7D5E669A2C3CEE2EB6DF42D5628901010C41D03635","A97AAF4AFC1194B65CF92E7D97EBD99652F2809037AB823F238362C16CF0E8CB3BDDFA72D17B81FA534FEA9BD0EEBD19681DB954254A9B9E7C376C43C9508FA6" -"Shared\Get-KernelModeDrivers.psm1","Get-KernelModeDrivers.psm1","0723DFAE9165FCA835310065140FC88F5B48D317D939F1FD2BCDD087077F2E629A763DBC547DC60777AC6AF35256EBDF6EDC0A02BA96853AC58F315DA7EE030B","BEDF4E522719A6844A7964D703FD93B20EDB222E62FF8C10D4052E2F9C909401E0370902CE72828F75BD75729BDFED6AB879C157373F4736C45AEB644E483E30" -"Shared\Get-KernelModeDriversAudit.psm1","Get-KernelModeDriversAudit.psm1","7AA423D444CDEE42239E6E3732C1BCE15840096F7621D33300FD423EF16146810190976ABA0112637CAD4A56490370F23C865FF3B730EEE1FEDB1B28B865633F","8E879981403D4A51660106E45A51D0A89356F5868ED8487D05794E19CC6523F7E59B5DF6C5B204DCE7456FF5BED9D6171DC3E913B635761D687CF82C11748CA5" -"Shared\Get-SignTool.psm1","Get-SignTool.psm1","BBE212D809B149FE46BC51021036B14517DCD743748E9510C5B6F9CF1C8805CB14771F5A76FAF6DDCDC08C668C87FE234A19D3CD21B3CCB476F9355207D469C2","0B162B55710056E4B206EDE00C6216E1B1BD9D8B2EC7FD39F55061A5F398430FB1C6DEF78B1B0BE6E6D5EAAE93F00B03668C2B2097345430F9FDA6AA05931304" -"Shared\New-SnapBackGuarantee.psm1","New-SnapBackGuarantee.psm1","EF4B4A52A197E7429283D9577DA58D3C719886DDE1D1E66CF9E17E415428D156CF6DDDE71E41160036A7CEEE53B19B61533695C1329DC71108FF6147E7F88B50","D3688B80DB3D9873FC3086CE2F0C0C2E6AD6432AE86B1B156ACE1113822980CB6451E217D0ACF016BE9EF1E6F883511AA161BF1DC02CA79B91C2FCD78375EC55" -"Shared\Receive-CodeIntegrityLogs.psm1","Receive-CodeIntegrityLogs.psm1","29B5A2CB298ABA068F64C1F814A1FBF9053401FA6FCF35D059E97AD6A40BC8E58614DD16DEBFAFAF7BF5532BD545D361E5B3F9C01AF11D8FC6278C2A86A0E9FE","4DE96C61B5E5ACA9F616B2017FE5E91B29E70421CA45E9235E7A000EE85FC35B680746A48638441AE6FF5E9907AA75CB4A10A3F3A93E9EDC1844AEE35C2238E9" -"Shared\Remove-SupplementalSigners.psm1","Remove-SupplementalSigners.psm1","CE367BA1084E60E7D1789B7831FFB0EF9B4FC051223967356DFAAF307A2DA1B87D8CF0555A54E86F3E381433979D6EBE382D7AE71E493F7432D8E6BE1E24D494","A8214A0DF80B870CF5557BA54EDBC790D0147D42498BDA11F2F5586360060BB4B2FDF446512AC85A0DE017AE8BB197F079B35060E0B0E560C81F957AEBF22242" -"Shared\Select-LogProperties.psm1","Select-LogProperties.psm1","466379089AB118F509A4664A0325347F6D31E5E141089463E1B7C0AE6E5F002B7788222A63E12E478F141A2A5D6E0AA760E90AE7AA44A3844698E2432490D7A6","8A326BF412DE2C12876D9CD064D6F1201C1F337B248AEBC9FFAB6ED9CFA7B432FD2F9B420CA6855828AF6A3BBB2807411A2C9409B1D777C284EEFDF0F772AA8B" -"Shared\Set-LogPropertiesVisibility.psm1","Set-LogPropertiesVisibility.psm1","D0B4A97CF9F66BDDFCBB8BB38A932D2C3FAA9701391E62604A3F3406EC5B340184395A0D2015628A67808499A60FC5FA57CA5EF89F29AF2D1329093A063F0817","728146FD039BA5191FDC1A563D314C01BF35E7602F4C153B8DF8BA6BC42F4FE8590BACF8C5D7FDB9DA83EF1D87C158E6A937AC646411962342DF6C8F3602F4C5" -"Shared\Test-ECCSignedFiles.psm1","Test-ECCSignedFiles.psm1","8CFD933F97D9E183BBDBD0000AAF8C3F29ECDBB4010CB306A87034611ED7547C0422D1BD1115426E682BDAEAE0BC94D45A43D7F53F33FB19DADE7CC728756998","5A02D8FCEC5A750DB70388F880BF3A4526152A4100460E0B26E414B2413283FC6FA6F51667EB55B01B867930428D0700B95D14969CA46CE473448074BED53E20" -"Shared\Test-KernelProtectedFiles.psm1","Test-KernelProtectedFiles.psm1","D855644AF7B281FCAC841D54FF40B2405623BD95C8FE0180DA5D96E7EB466C8D18C816DA18A3CBCF8755C9A4FA4EFD1C1FD63C2574F513897C1662C6B745941A","56E87F18B3DA39CD1A5375200F681509950E8728D5561D595676AD3B3FF6F96ECA0168AEDC3C8A469D481DB5D51C9B22E470F7388CC79C2866E60F95D05349FF" -"Shared\Update-self.psm1","Update-self.psm1","DCB02A24ED8F422AA3A0A43C4BE9BD82D5441C1D097C7F5BF27968F69D40AA7CBF680B1AEDC1FB3094BD382F23D4A4ED9D09EA443F94C4024C276CD507EA20DE","8D24269DF0B169E1CC0527F86871D5334AA79B6653D1F1C66BEE95EBB7E6D2F12FEF57F0E377211C684846E32EDCD97DE157D8BFCA5109E06D4E80C4E9EB757F" -"WDACSimulation\Compare-SignerAndCertificate.psm1","Compare-SignerAndCertificate.psm1","8E75B2A6C2CFCC0CBAA36C54381E767B18DCD804200D2174DD160AD7B24C79BE302D2B9E8FB550217F4D695348D3F3D71BE53490DD83BCDE9B9650FE938CB256","80B62CE14425BEB72006336106ED37F303FDBAACC919A18EFE600D92756F7545B1DB41F385C84D6EF8FA2E1AA9B7C8856F873815BBFFCAE508A5E58D7BC65A97" -"WDACSimulation\Get-CertificateDetails.psm1","Get-CertificateDetails.psm1","4444456A2412C729206DC323E00F8529D0256C1BA5853194044056320056B9A4427D2F26147A84330FE4897CE08224921FACC2A9C3AE51E27F56066E6BD7AA46","6688BB4A7B205446CD4B2F53059C2838C1259CF7454CF67350E67A55784EA5A0A473BB3C92E0456C733039A26AD67F7AB62378D3DE0921A1F8024F91D5EDC8BE" -"WDACSimulation\Get-SignerInfo.psm1","Get-SignerInfo.psm1","DD9B40FF3F0DE059662712938C76C73314EF444333916504CCDA79EC7ACF5040DA0A1BC1977B5A06FE9742FA05BD56E536C1CEF0F8B410FDC56D4F46083276E7","16B5A96AE906210D96135A83E8F720598958EC3AE7C02336411894D9E59FEB0A53DF5DCAFD84C47D6B26CBC4707714C768A5523942241230F88C3976ACA17204" -"XMLOps\Checkpoint-Macros.psm1","Checkpoint-Macros.psm1","9BC68A54812B71866913587EA32B28120C7CF2B483586C0A2185A50137B5960CDB9A04CA7A6ED44D04012C828F7E07499343E12283DD530D1ED5B8669F6D57EA","CC2E6AAEB3D374D87C36D676025CD9CAEA29711859E20B832C234BC2F72688573AF04F1ED5B017BA68ECD1A5C79AF7247881D56752E961371E9187AA3B3EBC70" -"XMLOps\Clear-CiPolicy_Semantic.psm1","Clear-CiPolicy_Semantic.psm1","9259DD1D52B082DD116E74EF60C2A2C5A0218247D2C058CBAC1297F3FA6DBE0B35D9FF4D4C32BB16820FC991C4A1669F6935B09CD9CD90BA25A024ADF265ECE8","AC50272E68B7AD59A2A3301648B424527EB33DBE1C52BF571F01189C4004E40C1A3B4CB58058AE1A40B756E95D7CC4775A41A731AD5925195CB61745F29D0F02" -"XMLOps\Close-EmptyXmlNodes_Semantic.psm1","Close-EmptyXmlNodes_Semantic.psm1","524913AB70404D4FA172AAB86DFF66DD70A1614D9D672750552AF107083040B7E3C51C89C1702D97729065686AC7D04DD63B79D7CDD5347887CA93FE2F29D383","F98605A9DE93A064F131F7B33FFBFA535117B54EF012DCFDCDF7FBF1BFADDDC828C7319634793D4FA2D43852C3323631D9BDB99F11D8A8B021BC5E97ED5E8B4A" -"XMLOps\Compare-CorrelatedData.psm1","Compare-CorrelatedData.psm1","D2FC744CD11836FB64FB87F2C2938914039ED59C5A2AF6B31AD28BB01BEA697525DC672913D5FB9B8F29FC714FA090EE595704287346B4D7C39666CE7D33AF88","C15AF2635F87D213AFD237FA655A47DE1AC552ECF7E56AF1924D0B90A3B11771FE3339B63F9B33866A22D72873E471E0BD4BD8DBA4CA128090634C3E3FC20EE1" -"XMLOps\Merge-Signers_Semantic.psm1","Merge-Signers_Semantic.psm1","767E7003BAD63334C02FEF19B59A08A7A051CAC5FDAA87D9315250BCEBC5F22D741814F9AC5D1B4B7F35F426B04863C88F2AF57B36DEAAC8D5FFD763A70FC64E","BF0900AB81AF34F57F91A84FA5DAC4A0957FCBBA07DC044ACE9983A27136DC40A56B39740A1CCDE40200D5B379CCAACFAC25EB6E9AA3A697AC16A7E5963D0C45" -"XMLOps\New-CertificateSignerRules.psm1","New-CertificateSignerRules.psm1","F8D7974BCA562DE8031C5DE63271A2B8F2850C04F28DEA7F2EDC0352E22CE4A6D2B0ED955B987A6D5B0BF25ED3616046C8B70BDA9899ED39130ED79B7BC2132F","0EB19EB2D9BB4284602D78EF9A15CD5EA590064594374DDA4F78DE26CD6360D7396060759EBC4FB944A3A342F1373E8D3DD4F658CA5F72414EA7C3894A46C03A" -"XMLOps\New-FilePublisherLevelRules.psm1","New-FilePublisherLevelRules.psm1","C393CBD460200657B0DFEB06486AC1C697D4A9EC8B53E66A52AC2A8F6026B248D40162146D371B32EF0C1F9469A15F69C82D54D2057A5F7F886A82172C33EA3C","6EA2EF3E61BAA922201D8F039676074046088870D4377A2925A59A6194E50D6C6C3E872C9838CDDBEE47C524CFE37F037C7A16DE83754DF81A4B9410F7E399CE" -"XMLOps\New-HashLevelRules.psm1","New-HashLevelRules.psm1","36A5260D3D7C4BD515ABD2971F5A1D78D0B45F482A87996DC4B8E0F716703E78543C70DE8F9B0A5150F6CF3F5625B06B981AED5ED4B2023D322C1B527284A814","96316C3F72EFEA860CBFC0A6364D9A6AB1F7508B09723A5558A05F9C41CEDA257D0C47967F6F97E557C34AC7687A31969EE0F9A3F11702793605664B07CFEDF4" -"XMLOps\New-Macros.psm1","New-Macros.psm1","73935F5E005A5B0F72E36D3E8843767ECFD48CC8C4914CC468CECA98977C4196E6C586C5049FAA6DEAC8926DF75C6FE8191E59999130B9E0C4FA21D0D819C94A","82AADCE1D84A6A2C76E430828A700FFB9B078D5868F86286FAE5489721655426D93DEA3041BC7396ED0F3C975000137799C145D3A947196D1A5EE66809ED8CE5" -"XMLOps\New-PFNLevelRules.psm1","New-PFNLevelRules.psm1","33DC5FB33DFF1AAAC018092802879C4FCCA839C528CA85F36C9A6A19A3A9E924BAB48C1AC0952DE94140C7103F944CB58C3B8CB5B7C7F1D5912102291AEE68B0","0E57FE1B1F5F24FAD45E9FA697210A3AE9EE7B75559834891BBC286EAA89BCBDB97111ECD39E5780079B1452DF98246B7D7D1F5FBF2D4A7B07B6E16503DFC67E" -"XMLOps\New-PublisherLevelRules.psm1","New-PublisherLevelRules.psm1","C9F69D567D09D2DE6E3BA463466D61FE20818C80D53EFA7B3C5E82CBC60984B33CBE686038F1F78EDAF7AC9D45270AB67C4F1C45C459A67F54722FC9C817CB4B","A9AD58A7F87BF24A4DF011861813981FDD748E68E64F33F6A58C6171A1C9C6D63B2DCC1A8ACC0B8A09C3E6B8FCB3F272E61BB020A361B99D5A223554856B4C02" -"XMLOps\Optimize-MDECSVData.psm1","Optimize-MDECSVData.psm1","51D45985CEAF8245FD9DD2CFCCD4B2EF0795DE7E815CEA9D06E3C0B433575502DD51A23815A40B8558E77E03B27E8F7C545D4C22A368302774C333AE3E32CF58","A503F91DEB26EAFA0F55BB54EFEE23BC8AE1B0E27FFEC67E9BDEAFEFF2146BA1EC78EE1CDE0021B826E4844D2591D75DCEDEADC914A0D7239EE78C3F805B863D" -"XMLOps\Remove-AllowElements_Semantic.psm1","Remove-AllowElements_Semantic.psm1","10BAD8B9424A778FB4366FA3B896A1E86272D20AD5507EEFDF517E00D2C3CD9EDE53ADAF93B7DB0D4B0E7657BA6C90C5C2914621E3E78955C7EAEFA1F2B09357","280183E7C5AE6153A0A59BCBFA07B5101F9FC321DF740A1989CDF76565192EAA9A7BEA8FA56D478472BCA480711316391E5B4AF3B559DCA4E2BFE277AC018938" -"XMLOps\Remove-DuplicateFileAttrib_Semantic.psm1","Remove-DuplicateFileAttrib_Semantic.psm1","D1F28FCD92BF969370EAED81C13E6FAAED3ADBC331853D84CE4B432164DB8F8B014CC6C151441796C321B097745D526EA43185C77A2C0B46C7B94AAB45545B37","D122B8B056A18B97CFD758EC80F436FC5A8950FBD66D915E4231B3315190A96FD6E5B53B080C20466E4FECA6CABF5484937BAA0D1BEBC0004BEBEDBA7D1DD74A" -"XMLOps\Remove-UnreferencedFileRuleRefs.psm1","Remove-UnreferencedFileRuleRefs.psm1","7667E6EDF15F31DA0237C52595BF67CEFAB711915AFEF244DF8EDE70764FC325135E49EBDD57B49E87C313712D38D236B24B53FA790675EA50BCB067D3CF582A","83CF1233E02F456F0B7AD80A25739F8B4DC33F5CA0F019EA62BBC73D695F388807A57D8DA9D0E0DA038CFDA72AA1F1F8F7CBE175C8E03E415610CEB57F5E2A4A" -"C#\ArgumentCompleters\ArgumentCompleterAttribute.cs","ArgumentCompleterAttribute.cs","ACC939DD95D1B48C8FE329C857C61824F7533E13D78DFABE87437A4E22EA809770BD6ECEC4638610AE71DB0C2C406CEC8EF4EB07984A40E0837B7A96FF3ACD54","9A311FAB035BB7902A8F1F601E8C919F078DBEFE07A1409E32DB34B3B806152DB8C0FE62E516733E45F9DF7539DA5F4155D6AE1A1BB8376D472BD91AC4604E50" -"C#\ArgumentCompleters\BasePolicyNamez.cs","BasePolicyNamez.cs","BD005B4C253B36A820890232797E70A6BC4B98FB628918F15A07697CB26072C4FBBC7FE1E4EFF4022C577DE29527820DD943FF2F1F9959FC41EE26CFA1A89EB7","F2A3CBB9C819BBFB2095E027740988350649A784096E23BA8248A4D4F0A0AE7CA8975A80B7EDDC3005616C74DB886D4CB33E94CC3EB9F55BB1DF45C581794848" -"C#\ArgumentCompleters\CertCNz.cs","CertCNz.cs","662B57764E6570A3E006C8A5C874AFAF731B06E3B1C5BB40C0CC12EB3F70FBBEBF41B5ED4B317E8DEEF415A3143AE2E03CBE9C4B5FA0527DC2DF3CC07B7A3DA5","EF6F19FF103E4BD4A4A60EA6B0DD202372200E66FA3BB7FF6798A22E9D0E30430A9E3C487EEEAAEBB52C7A5824E67E2F6E5DB55D4CCA7E60540C744ACAAEC3C5" -"C#\ArgumentCompleters\RuleOptionsx.cs","RuleOptionsx.cs","4C4438EF5A23A4F99BDBD6809029CA38978631CB8A9470E22FB7946B856E619B4983055CB8DB34DF45B512C352B318E60F88CBD7B8EE163CCE29CC0A307C7543","E337CC14704B2BE62A392FE5D71BC6F40A208D52E03BD625D7A1A252366E8A97941CE83A53C34CDFC6C182C3CD11C06BCE294FCAE8073DFE07F59B948894A904" -"C#\ArgumentCompleters\ScanLevelz.cs","ScanLevelz.cs","AD425BFDC252CCBFE60544866DEDF2A7C04EFDE19681C78BCF72DBB9ACF5B6C8DE37A6E63CDB2403440A866BD8207ECE6F788E0A5B2922378359B6D029E9FF0B","248BAA1F40A683855FB69DD214150F4C0D65DAA133B0920DED33C548DFD7589628E99E10C685D04A9B611CEA6C571A5E8E830906807FA072A06F8ED3EF62928E" -"C#\Custom Types\AuthenticodePageHashes.cs","AuthenticodePageHashes.cs","395AF9E2B96FD07EA1C2A19938E5A7568341B82F681C68FDB9C1044A0F2B754268A56084FCDC47E64C74A925340B356304EECC05EFC33E63B058C9602CE201B1","D1D9F40EEC10531CE34913CF8CD197D9E4F738100EB0061908A53BE5670810BAB20B0DC225147940ED43921A1888F3FE32D6E3654C6F561F262115E0FA8016BA" -"C#\Custom Types\CertificateDetailsCreator.cs","CertificateDetailsCreator.cs","166F34DA527BAD64CA1D1487B835D8B8C48FDB048864A47DC259B46F3B9FF8F6EAE6EB65F489C090547EC6C4A092D849F0839E2E00149F11D4E268880711F912","78A4B606AE030709C5FEB2EDE0CC7C86B7D0874ABB984419CA25A67843DF4F17F92967BED52CB51926B38C76884ECE2911AB77A3C310F6899DF15C197086E18B" -"C#\Custom Types\CertificateSignerCreator.cs","CertificateSignerCreator.cs","5FC0539B08CB00C1D22B81DD47D63291FF0981AB9D58A40B1A55EF7DB12465EAF8A02C05F6482A27095318016351173D76428F23FFF50A6799FDE6D67F46C889","639029EE28D75E82AF0043C24DC4F0A1AE42B488832F9B6ACC54C80DD1036776A7419E35BAA471FC5E4B49CAD1C40B91AC03F68509B338A6595B46B961FE5A19" -"C#\Custom Types\ChainElement.cs","ChainElement.cs","493A86B0365C36771E47FD83CB26A8E3017B42698AE11E6321EE1E6219DD31980E1521EDB932173E9F4BA0E298491BCA40793BE542C09C07ABCF3CFB4226041A","D209D4BEBD7032FF9EEECC5C41BEA39F8DE739C01B4854F398D17E59D8CA64FF1E160BB158E9ABE09ED8370291E0CDE3DD52842E629D488D61D24827DFA15D7C" -"C#\Custom Types\ChainPackage.cs","ChainPackage.cs","6E977795BF2278AEC6CF70CCFC46C052134354C79E335338B9F2238500352BF4DDD1AE6B2734D39F2B117387C405E462B50753C313199046973345F2804B6DB2","92FDAE4C0DB140DF8C816A53DD10437324167A41C0C0856EB82FC681CC6BAE66426D970E19E927EC333198308F51886E85EEB6E4D949765F75D4F3412C9286BC" -"C#\Custom Types\FileBasedInfoPackage.cs","FileBasedInfoPackage.cs","F9039FE001F28591B24B7C8B9F86EF5CA346CB4DBADDD6614CD4EC5C9C8D3851C2539DB1DBE5D8695D07D7E7143B6D9E2C4C60FD9A7693BDF2EF7FAD61286470","F878FCD9C1525C4F767592C184A6A233F4E9A6E2BC2F4A07F0951E1FE65152A25C8F05F85D18659638C190AC155CE1DAB68DB2B4672E3A8616C8186D3A3774C2" -"C#\Custom Types\FilePublisherSignerCreator.cs","FilePublisherSignerCreator.cs","7485ABBAC20B65E0C704A5491EF96C104BEF153F327432467C4558F5CDF0586AF8BEEBD718C641D3E81E7ADEA52A33B30B04174C54355A02E6CC945B4156CB3A","127987DC2632BB9760B95B960415452B73BC3FDEC888F141975CD867112A1B262CA260851B2052BBD980D0FB53D1DFC32A6F8D9D0B7BFFD021AD2A0E728169A8" -"C#\Custom Types\HashCreator.cs","HashCreator.cs","5AB8EF0AE9E8E6588DC11C5728DD0655118F6B0A891AF04111168986FCECA7675AB5C87C742ABCB9F1149E6665FC7C5A523AE9E49EC49424CD3036D7BCF7CC63","659961200DC9F86CF2001EC3F337AFA26F115F3553EC569C53003619B0C31A319BB909C6F4A6C0AF76BD5B39AECB87623C0868A9173438F7E3492D8D15A3790C" -"C#\Custom Types\OpusSigner.cs","OpusSigner.cs","DC67240B94C864C347FA9207D969499BA71C054E6C977B63315AEC93FE026D07F5981503488332AAEE3092502BB16F917AD33FD334AC9F106529E54AA98FC88C","871B8F275BB0B744D0DB204AE68346C7A438920543B0C9F088AAC7BF243B7B9FBF536548719ABFAAC3CFBD6270B7EE687D29171330999579D49557A3076F14C1" -"C#\Custom Types\PolicyHashObj.cs","PolicyHashObj.cs","20962490FD2BFF5273B204B4457F0AD9D4F004C5700628EE54ADE16D0BA022D3DCEA2F9C6DD485F6BD36F266CBB560A230368534C01F6999CA936AEBF25FA426","0FAE99AD3EF3EEC33F9154923E75481C77174C46E3464A94369C928EABF620470B7BD4D1BB8692DAE46B9CF0806FC0E447F8FBFBC918880536513A6731DC4704" -"C#\Custom Types\PublisherSignerCreator.cs","PublisherSignerCreator.cs","BE66EFB58A635509C6C4292F85298B59494E565DBF2F49B9F6C4FFA7F5DA2B5E0CDA63691587AD772467990770284500FCC29C319F04CEEEE2A6CD3B9BF5FB88","41A8C71C9ADC0D1120F1CC427950AAF0797C4F0A658D0E9D336BDC7BFC880A2D2E9F42F14015F27F634910A3BF3C30CC98318F69B9174257F1B3C53636A245A9" -"C#\Custom Types\Signer.cs","Signer.cs","8549CF9A7F8E66FF709BB0A01B83A527924D1FDA89D07A545EA1C72FA6EFAF918883EE342B0AEBDD4D0CFBCCDB03E131B238A3973B42EE8FC016FE26BEF2986E","7D47B90BAC7E738057149488C0678F64A355F0E6E619349448E4E19D6DEBADF1C49B7FC140BC4AE45884FA799A94F1487B2E7D19B5669889515615CD41CF3813" -"C#\Custom Types\SimulationInput.cs","SimulationInput.cs","17BC223C676B9CD2B33294D567CB6A699CE610E73AB599E5C7EECA22716E5C40222F98EF8F3386533E8BBF04A562E817A8F24F4CA5CFC79A42B573DECEEB0F12","B122033CC2870809CAA076E25672A4F64C87A2C2FB5932D6CB017577B48541F5AA8B8A77BFA535FE466B8C962B1BCB8214C747B08EA75E0C10AB83AF62D5513B" -"C#\Custom Types\SimulationOutput.cs","SimulationOutput.cs","78B686EB62D678EAF86CFDE7E172960226DA07C2D8FE584EA87BB0617E9EB92798E7C8158B663158C9F3D3AA92D6EB1A8FE582038E0B2628749CCAF9BA9F90BC","AAD6295931775F1ADC8EED37BC6BE8EDA225F28CC174587C37021ACEB2A24261DB60BB7C7743258F7F2ECB83A23AB7E0ABA8EB0141CC274E63B8FBAB373B4183" -"C#\Functions\AllCertificatesGrabber.cs","AllCertificatesGrabber.cs","9A046DF6003690BF825591F9A8742BF873C436300EA11B154E3F23CDD0CE1B81000C8888452AD43E0214F8599A9AF0E16FC2F3501690C5154FF43EABEA1DE4B2","C2A406FE47A091F088922D76AEFBEE3FE3839ECF6684606C6319BD51E6771347C3165AB34D047A8B28412CBDE123C55379F36EB69B4DBCA5C3FED1221058A909" -"C#\Functions\AuthenticodeHashCalc.cs","AuthenticodeHashCalc.cs","C012AAB2DCB630FCFA39512CE1007264EF7E49316FA4348E7A009E4C2434BDAAFF3AC07079F3B81F5F2BE1F6DF34A7C7644C7AF5DD40A448800F2B53BF5F1D52","C3D2630BC5976961A751DA0ABA74491563D714071000AEE698B0A9AC3E3184EB320093A9C4657A75201ECF73F1063ACFF88B49CE6FE28D5B04FFC1AFF5C39728" -"C#\Functions\CertificateHelper.cs","CertificateHelper.cs","FDBAD2634D282FF00CBF57ED646C7BA44910ACA488FA4D1619761373EACE7B7F1E783F6586B9EA76B2E83E33A72E5432113141DB186C6BF872BD712C747A35F8","89D7464ABF0C20260F28C88EC9CC434EDD695763DC73C532016F3B8C29AF64EC48AD90A18A4F62784733559EDE9D3B11A039ECC3D11CA3CC987B544E87BB6658" -"C#\Functions\CiPolicyUtility.cs","CiPolicyUtility.cs","5B4D73F6B16D6DF6A0FA5E2212B6BFBF9B965540C039DDE3DD4D04515B7ADC31F541BFB62DAE30180B10B114E3EEEE6E6ADD9EACDA634230C10B5F099C76D880","C9EDF348C249E54103114C5D234BB21C6D5CB0A594A0FD658D9AC7CDD4907698326FFF553A8ECA7C9287ECBBC7EF418E890BEE8B74BED0E49D896AA53CA947D3" -"C#\Functions\CIPolicyVersion.cs","CIPolicyVersion.cs","9F2ABC9E84B6682FA45C790DD823713EF6A37B86E2DE7FE8D6DFF9E868078D638C83AE08F40195560F52C5A54E4C97E62C1485CEDD95AB455F4FD9F71ED3AFC1","1D38F312F9E718C9450F7D75A73474D2699ACB53B3B5469993623DE0FE68EC3AD2466B23B36A4D5CB452853EA36FD4BA23EBE708B33BF0D1FDC2558C0BE9933D" -"C#\Functions\CodeIntegritySigner.cs","CodeIntegritySigner.cs","C5F6E3941997355E842C9AC803DEBC228662C46A72ACFD61969B080E29724074A7703303DD9A24DCA14C66CA0076E6118698A6996C0033364368B5E6339AC57D","DEEE13876A1FBA8B6CE653C0BE80D5A1F1975DFE4EF14C15F7AD56D6212F48F3FEBE62BB756A7150B4A43E01869246C42546639D576E005B2ECDD1A8A8BDBB5E" -"C#\Functions\Crypt32CertCN.cs","Crypt32CertCN.cs","2B7F770BF72333291D3BC343F14BB1304FB3DD4622DD90FC1AB80EAA1D2B3E061101A279E1AD64E229BA3DA82A7F34F6034986254B93EDE7CC7DB9E4762291BD","904863B7C5A84AF860E51A494A7A5549E6208556678B3D630155A7F9DC1C41B9A9DCFEA2CD37C168C6AF63BEAD6C7B60398D90A783C3854AD51B64E1F1752642" -"C#\Functions\DebugLogger.cs","DebugLogger.cs","97C5AE3B01342EDA3B3B7E0DDEFE5364A2E57A5DFE94B2E2ED515CE95C23DD020C59DFCD7309FAC3DB10115963EFF7FBEB22A827579BFC3B08C8B2B662998DD3","66989F9934497A2F4B2FD326A404233BCB6AAC59466CA0CC03B7921BFC4C5A8DF18351F8F36EF1E6F51902DC0C22457B99683E39EB7659AC242266EE3BB23302" -"C#\Functions\DirectorySelector.cs","DirectorySelector.cs","BA10DAF2F7E671236EA677E55C2BAE273C6364F2855EEDC2FA34DFAEE55C91A8D0803C5058C8EA6137D9A1D2B89498181E805114C290F1E295907739BF3D339F","6B5B95B8C81D596C90A30924B15A1CED72F657EE3C8129038578253090D55B5BFF7F2C728EB9D7B83F48BDEBECA207ABEC5B2209A597B08DEAE0EF9DDFE00A0D" -"C#\Functions\DriveLetterMapper.cs","DriveLetterMapper.cs","34D221FA85ABF10582C8EA13FECFEA481B37FD0AF4C91828B5DAEC67FC53AE2CCD842C13225047E4850D45669C96A630455C9A51ABF08E97BED900D1EB71E76A","74C4882D11EDAB6BF729E76C807F2E2D5FEF411DDF3096EA96E1B35FD087FE818AF31CC61121362B3693BFD5EC82AE8BBF4D8B60AC034849C086552997B4A2E8" -"C#\Functions\DriversBlockRulesFetcher.cs","DriversBlockRulesFetcher.cs","5ACC4EF2617E0B33D0891D9D3E4A3B1892B932C2AFF03A0E53863F912CC2A7723FE1247C1D6976A426CFB4B5C329F2BA4F31DD508B17745F6E19F7F856ED3183","FDA51A2F67D436080C928A0AFEB82704AE8477F35877A20800D6C1A885F9DC3A6E7DB4F59833A0E163B2E2B9179D616A01444002C1ED7096438BE51631305B4C" -"C#\Functions\EditGUIDs.cs","EditGUIDs.cs","43DCD5B52E221D453C4EA56B7412E7099539C23C664610F3091A0A498B50EC81D52094A928ACF6FCA38064931C7A18ADD06DFF2581C75D0F61B231A0CA2DF034","FD284259F44990D624EADD686065AF6908ED54885B0B0553B89DFA1E7355A60B2BADDB58562B2A8F3473970E873907238AFB45DB13610657DAD7A56E67D2C56F" -"C#\Functions\EventLogUtility.cs","EventLogUtility.cs","79111BAF7862D9B39F89AB582DE2D93B6C5CB2F0FC226F0CB62A57A4CFB790A38DEDA58F99DF5F2EB822D01D59CE6FC30769CCE42016AC8F2B6FAEBEB678C7F7","E50DA280AD2A5ADE6BFCEC7F2C7A17047C1986AFD250013C96DEBCC09D543A2F8A45B7B105F0FC0358C50A8F1030439292287EDE6A7D2EC29971E2C4D1E16FAF" -"C#\Functions\FileDirectoryPathComparer.cs","FileDirectoryPathComparer.cs","471BA20F58226265093BED70D0D77453EDD8B370B6C37D0E5B025A18E1EBAB2B4C5975B680D9DD9191D2785CDA618BD431353BDFC5660428EDDA83F1A603E6B0","59CDC51C708B2EAA0C3005FED964997B8A5B73599CF7E068DB0E7E2F32889FBCDDE27EC8C50757510EBE7F570F128BC49545FB97AA3D0D769A194872013C2CD5" -"C#\Functions\GetExtendedFileAttrib.cs","GetExtendedFileAttrib.cs","A2CEAC8D194F677F74A71985E913919961ADE8DFAB931EF90AC7859212BCA055CBFF2F30E824BEECBC789188D045FBA6940CBFBF21FE23B859E25908CFCD9E73","0E366B43C945BB405268F5BBB931ADCDDD197E68AAB5F19BD43CC9D5D3004236F2B0CEBC4C88E9AB08210DC93583AF1699D816F6EA7F41208055C29E482CBB39" -"C#\Functions\GetFilesFast.cs","GetFilesFast.cs","64A9EBC06386EC699310FBAE3172E4D7C9F08E9FA276A864E452B14D09D854B64EB5A1A8F5CBA9A162057BAA4C106504B38EEA783D1A49919DF075EBB48C60A7","394032982FB4C8824B1AC18787035416151A5EA4D4212DC08A292711C24287B4EE4A02A8505AAC4D19983C3E861A30D77CC63D859EFE7B659F3A05BF7F76A329" -"C#\Functions\GetOpusData.cs","GetOpusData.cs","145CA1FD2374700BE2C7201D7F63562AB35AB6965A147F6ACE81351F33F810F0E89F4DF92448B8D65FE6B8322185E986E5C5297502520831BF11223AB7BB2624","06606688E242C41AD0DE1F252F1B3DD2E3B3363678FD498676EF261E9EDB872042C4CD2FB41711F537D21997B5AE8F045A0593BDE9350560828110953586D8B5" -"C#\Functions\Initializer.cs","Initializer.cs","E866652E2CF83D184ACACF6D5A662E3255D4387A2DB3BA0AFA7F3F2A22BEAF8CFC483AF996855FB945F08EAB2A140BD496289AD6B9CD19C275F1DBB66BD686F0","BDF07C125CBF598BF0ED9216830107456857AA8ABC5A90EFACCC07902E8A048F70134B090A16B082554892A9FFD86EFD595024F3EA13AEB6BB44F16C92B84C3E" -"C#\Functions\LoggerInitializer.cs","LoggerInitializer.cs","255445B30B648557C5903F12EA1E37B30A9AE69A7940C32B7918526D9F16D5A92D6672A2EEB4F05D3C007A45FE20E66B28D005442AA561C8056D7FE0432BBFC2","B534CC88614995CD999F53FFD529B45AC212E2CBFF8405FA5CF2E91ED118C45881CAC766B1DE8C623C5FEEBFF6DF329D8EFD17A41A6E6C676EDB1081B99D400E" -"C#\Functions\MeowOpener.cs","MeowOpener.cs","A51A1AB7C9227AD6E1C4F395733A7151AEFE7A32AF67B0AF34F0DB113C77F53D9656D8D22DD78E0BFCEF85C505985572EEEFE9C0489EB25B3B49152E3E48ACB5","345050C7BA4B6D0B024641A1D03ECC32E2A90F7031914EA214072061A8D2891484F0F4D12292289AE0240C83078BB1B826633E2F325A28818705EECC80EEEAAF" -"C#\Functions\MoveUserModeToKernelMode.cs","MoveUserModeToKernelMode.cs","A4A386CBB3ABF507BDE9D0A9EDAEACB1F67C65DF7D1A6766456F9988958ACDA3D7D693F3A39ADECF8D82E79A46F6EC5C3F868B615387E65E0982128737D3064F","F61044BDC270FF512AF73775FA7A6636C2796CF29D24412760FFA86C60CE3D53F11E13AB21AD22B005559EA1C444D5F20D0BCC22490AD6BE3A9F728A83BA2C59" -"C#\Functions\PageHashCalc.cs","PageHashCalc.cs","90370F579A9A17AD04C0CEAAB3B15BA7A8B0101520178CD73B7C4DDBAF471FE31191A6DC468AADDDC8A10AC2395A2F3D722228C9259246E882431085DD68DAE2","C701C7A53D93A7B601D2BFFBCD0B18C2FB359D5CF6D0E9581B1C287AF2FB29D2F2F8332FE820BEF4995AA1861C62AFEBAC6A06DE9A7C94365A8C292E6E405597" -"C#\Functions\SecureStringComparer.cs","SecureStringComparer.cs","3FE4DD0A2B3067B0DF49D68D8D96F2694EB7C125F19CD3B884B0D3FA32A6BEBB7E7BE99E689F643F4BD5793CD4F78AAE920FEA0D37E0B22A4ED873BDF6391FA2","54E63D84A02150CCE561CC22CA283F833282D83CDCE01FAD10D0024A0306DB8AF15928E4AB80F7553DC61640B705ED1411A8B7FDA3C2820499308439008856A1" -"C#\Functions\StagingArea.cs","StagingArea.cs","D74738BBB6EA15A5637F5610AAED16747E309C438A6BCC840681CFB029794A9B127EF481E58200B3BC3B1AE8F95BBFBA4C7DBCB13A7344DF22A28ACC9989B6D0","32260B2D60E5860D9D2E65F68E1F4F725814DED1F2EA54AB03D564DCFBCDD1C4E17862E46C2381112C2741A8789BF30221B806299F45A80E0BA5C31C1FB6A13B" -"C#\Functions\TestCiPolicy.cs","TestCiPolicy.cs","D84649D5A154F7E8ADFB67A465EC9CACA053C1F76C665EDA1687D5ED5E48F5F99FC9FA8A53F266AC45D3BD68B847AD5F1241E2780AB9504C67BACBCE053E6D75","7F844331077CA51F1D0491EE4C00D0156D6E9889E4EA50B601F7FEEFC5FA4285F73E6A0FE3202DD5465A9DF1CBCCCA7C33958009969EB5B1EE4CE47240B9C49F" -"C#\Functions\VerboseLogger.cs","VerboseLogger.cs","6B15A63B0BDEB67FF7C722B4528945B2FE1A05949AE9CB4C142CECA56A2E71355B601FF0C41A5A90188EC6892496C39780FF067B896FBDA3CC6CB0FE0D304CEF","9552A29A7F29A79C1D506269FA28A8B6070DC5FDFF0628533CB7E75332A54F574E51909AEBCC9D14A0472F589B1C98F1E1340CC22ABCC5647D2603B53D4DD34E" -"C#\Functions\VersionIncrementer.cs","VersionIncrementer.cs","B25F56A32878CFCF929ACF284B727D40F79B6EC4E3FCC8886D0985A6B6B4BBBEFDA58811A3BE0E639423DB2185178184102323099B9849E18D56D47EBF969412","C3EC31668709003F0742F81A4EF0114029773E26C8F50FD15C15D91AC84799A9D4D5248E5BDD77444FF37A1A576A64C51B031CC1136E0E57D3022BA7C0BDBB16" -"C#\Functions\WldpQuerySecurityPolicy.cs","WldpQuerySecurityPolicy.cs","EBB5AA457E1C37D1C77D1502892E1E9392C513BBF726273C45EA4527780F9CC255843301E5C46B9D95AAE3B1B350830C9AAC9CA0D3CA7EDB9C3F89BAD1D557B3","FDAB8254D6F1B758CB2A698934D34920BA54F93439446D65D47DDF73EAA8258292937521A966453E7A0691C6F9578AF5907C24B69FBDFF8CD9CBC02E1133E75D" -"C#\Functions\XmlFilePathExtractor.cs","XmlFilePathExtractor.cs","12E8F8E858B0D7BEB9D3C573AAE9C04C49B9317E8EFFF50164847A059ECA0EBE1C19BFE80508EBD27BE90047D82076FE4A1F9DCF116A123DDDE84542A1881D2F","F4A94385995CAB3EA8BCA763D0F6BB3CA214017B2033DC1AF3958F888710F297F8643250AAB9460FE101B56501D756253B461251E1A67634915BE0A13451FE69" -"C#\Variables\CILogIntel.cs","CILogIntel.cs","FE3A9BF2148FAD268062C4F34F146C0CD84ACE5FDBC01933C93BAA2012B2EFDCD4106DE080DC81C973917091359F11583A38DADDC33D50313A66F295CC59A9B2","B417E747877FB0257454C8721B8A66725488D209B38578AD36FB16E5BBA375D9AD27CC5BDAE940C9B3F3422E1CDAD78DC63833E0FD52E160CD4549DFB8225CF5" -"C#\Variables\GlobalVariables.cs","GlobalVariables.cs","F585CF38EC82C083C5C525C936E13638C70331B3E993B9AB5BA2379F2EB19D55CE6DAC7AC26AAE37BF19C1684972B0A1FF4C05B3D1784917AD76069162DE8279","D5DBAD92A86E8B9CA8B0BACC4A2F9AC3EB39148CD9BBD514D49A0468245BE4FE13BE020C467FB2F653A33BA87D5A6DA12EE067C1E8A001B437FF94B164ED924D" -"C#\XMLOps\SignerAndHashBuilder.cs","SignerAndHashBuilder.cs","048CECDE1BF9F392882ECF59B25FE92D2E0E891161E017316098A899B5ABAB4D4C3333DBF0485065346BAF4E9A96058C858D970E6B2C85D0E06AD36B33BA9F1C","B3BAB231F8B2A7B3E16E0E53438677B3980C74B2D258443DCBD9BB051C49DE74DB3DF52F8D6709280320E9703B2C23E35EB8834852BE3281452F73ACDA18D75E" -"Resources\User Configurations\Schema.json","Schema.json","9A20EF0148D298178B35C1AAB961C46AF62BBCC0BB0DCCBE63F2FE08E0A764406267449CDD686A01F85650622DA6E690D12FBB88BB3A7E070BA58C1AF8FBC813","D9D8391DAA994B8AE33433070A9438B4EAE5EFF4E00652E26A7C19870211B092991F6C4A8577D31255016369F4C5956A34A0404B78CB091AABC1043833C5CF79" -"Resources\WDAC Policies\DefaultWindows_Enforced_Kernel.xml","DefaultWindows_Enforced_Kernel.xml","846663A7B0CAD90A2305F3C3322D6C2CFA6277B7E4B083CB478FF409DB29A7D0D71318845B884518B8D2F87B66A5EA327D4EB2D39A9707D1EE41B0237812FFD6","4D1EFC2EC8AE373A7FDA3BC8666BF41A2D603FE150FA6F755439A6B1EF7352EAC687EED61888AB4478123D8A5BD7B3CD22BB48EB9192ACF3E75F4F917C8A1A64" -"Resources\WDAC Policies\DefaultWindows_Enforced_Kernel_NoFlights.xml","DefaultWindows_Enforced_Kernel_NoFlights.xml","7E4BC35A3F0840C8F3921FB260CE84660DC3CAACB7850A1AEF13AFC48B0E069D27562C5632444926BF60B44A0E0FF522D0215F1F7DD5E1A7E51A45E86AB7F44C","A50C9102ABF310CB5A0AE062E29EC45390C0DF192AA62E0F428804A0C9F0BBA704D22BFC41956886AFCA4342A64B91B30A6F2EA4DA0FAC2CF0367B3B46290706" -"Resources\WDAC Policies-Archived\DefaultWindows_Enforced_Kernel.xml","DefaultWindows_Enforced_Kernel.xml","BDC7B623386570F383B4A113BF06C7FF6A5A4271AFE572B5D68EEBC161CD650B62E70636527DFBEF09A8F95E66899CEEC424AA22CD00BBEF6D7888759D812F8D","6AD8A2005DC250814353E9006A9B03F2F6E7633877C8590130A24985965C1C7F58AB1A40C00B0A9F7D80BAFBFFB1FC5091931FA1FF732DFB3AF321CEB7DAAD08" -"Resources\WDAC Policies-Archived\DefaultWindows_Enforced_Kernel_NoFlights.xml","DefaultWindows_Enforced_Kernel_NoFlights.xml","D02BCCFA3C35E179A634AFCDE04259C43F8FBD619A4D0D2F7BAC1A8A9FBC58D3EBC7EE89B1B2EC6B3C17BD6EC38ADB501B271AEA3037B980D10EAB9AFA3B8308","046CF9A95CCF5288AF1D2BCFAB91E1CB3FB767A9E5B4131CEA4F4CBD4CD21AD36332AC304AA2E06D4509E77BD07309AB5EDC9978E84F51FFA4169D1CABBAB319" -"Resources\WDAC Policies-Archived\Readme.md","Readme.md","66F9B622C333505E782F1AF1509BFBDABF1AD5167042064593FEEA5245D6CFAFE60DBDA5231D600D4157BC424E49F77D33302CE77B79D1D30CA8E29ECFDB31F1","80E8A8638027931E613409A3F80847EAF0D929D382A3A8FD996E6152FA25591CAE9D06AA7E5E3D7B4CCD38E912CEA0B22A1AAE8BA34DD00D202852ADE673493F" -"C#\Functions\WDAC Simulation\GetFileRuleOutput.cs","GetFileRuleOutput.cs","BF3CF4DEC35E4AE008CE1FA4EFDFE05988B6B4D05F26C14E3752A5F65083EB08BD1C244D57CD4EAC8078510E8A49045E8E4C300B162E18E182CFC5021B0FAF76","1DF5BE320326A38ED02ECD625F74509B2FBDE2EB3654F46996691123B7CD3FEF6943AF3D948EEE9A9450888828003F5F28719D1B2EAE937CD329F0F908894B0F" diff --git a/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/ArgumentCompleterAttribute.cs b/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/ArgumentCompleterAttribute.cs index a848b8691..812c0ddf6 100644 --- a/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/ArgumentCompleterAttribute.cs +++ b/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/ArgumentCompleterAttribute.cs @@ -6,6 +6,9 @@ using System.Management.Automation.Language; using System.Windows.Forms; +// The returned collections for the argument completers need to be mutable so they should stay List instead of the immutable Enumerable +#pragma warning disable IDE0028 + #nullable enable namespace WDACConfig.ArgCompleter @@ -25,7 +28,7 @@ public IEnumerable CompleteArgument( IDictionary fakeBoundParameters) { // Initialize the file open dialog - using (OpenFileDialog dialog = new OpenFileDialog()) + using (OpenFileDialog dialog = new()) { // Show the dialog and get the result DialogResult result = dialog.ShowDialog(); @@ -38,7 +41,7 @@ public IEnumerable CompleteArgument( // Return the file path wrapped in quotes as a completion result return new List { - new CompletionResult($"\"{selectedFilePath}\"") + new($"\"{selectedFilePath}\"") }; } } @@ -79,7 +82,7 @@ public IEnumerable CompleteArgument( IDictionary fakeBoundParameters) { // Initialize the file open dialog - using (OpenFileDialog dialog = new OpenFileDialog()) + using (OpenFileDialog dialog = new()) { // Set the dialog filter to XML files dialog.Filter = "XML files (*.xml)|*.xml"; @@ -99,7 +102,7 @@ public IEnumerable CompleteArgument( // Return the file path wrapped in quotes as a completion result return new List { - new CompletionResult($"\"{selectedFilePath}\"") + new($"\"{selectedFilePath}\"") }; } } @@ -137,7 +140,7 @@ public IEnumerable CompleteArgument( IDictionary fakeBoundParameters) { // Initialize the folder browser dialog - using (FolderBrowserDialog dialog = new FolderBrowserDialog()) + using (FolderBrowserDialog dialog = new()) { // Show the dialog and get the result DialogResult result = dialog.ShowDialog(); @@ -150,7 +153,7 @@ public IEnumerable CompleteArgument( // Return the folder path wrapped in quotes as a completion result return new List { - new CompletionResult($"\"{selectedFolderPath}\"") + new($"\"{selectedFolderPath}\"") }; } } @@ -191,7 +194,7 @@ public IEnumerable CompleteArgument( IDictionary fakeBoundParameters) { // Initialize the file open dialog - using (OpenFileDialog dialog = new OpenFileDialog()) + using (OpenFileDialog dialog = new()) { // Set the dialog filter to XML files dialog.Filter = "XML files (*.xml)|*.xml"; @@ -213,7 +216,7 @@ public IEnumerable CompleteArgument( // Return the file paths wrapped in quotes as a completion result return new List { - new CompletionResult($"\"{selectedFilePaths}\"") + new($"\"{selectedFilePaths}\"") }; } } @@ -251,7 +254,7 @@ public IEnumerable CompleteArgument( IDictionary fakeBoundParameters) { // Initialize the file open dialog - using (OpenFileDialog dialog = new OpenFileDialog()) + using (OpenFileDialog dialog = new()) { // Enable multi-select dialog.Multiselect = true; @@ -269,7 +272,7 @@ public IEnumerable CompleteArgument( // Return the file paths wrapped in quotes as a completion result return new List { - new CompletionResult($"\"{selectedFilePaths}\"") + new($"\"{selectedFilePaths}\"") }; } } @@ -310,7 +313,7 @@ public IEnumerable CompleteArgument( IDictionary fakeBoundParameters) { // Initialize the file open dialog - using (OpenFileDialog dialog = new OpenFileDialog()) + using (OpenFileDialog dialog = new()) { // Set the dialog filter to executable files dialog.Filter = "Executable files (*.exe)|*.exe"; @@ -330,7 +333,7 @@ public IEnumerable CompleteArgument( // Return the file path wrapped in quotes as a completion result return new List { - new CompletionResult($"\"{selectedFilePath}\"") + new($"\"{selectedFilePath}\"") }; } } @@ -371,7 +374,7 @@ public IEnumerable CompleteArgument( IDictionary fakeBoundParameters) { // Initialize the file open dialog - using (OpenFileDialog dialog = new OpenFileDialog()) + using (OpenFileDialog dialog = new()) { // Set the dialog filter to certificate files dialog.Filter = "Certificate files (*.cer)|*.cer"; @@ -393,7 +396,7 @@ public IEnumerable CompleteArgument( // Return the file path wrapped in quotes as a completion result return new List { - new CompletionResult($"\"{selectedFilePath}\"") + new($"\"{selectedFilePath}\"") }; } } @@ -434,7 +437,7 @@ public IEnumerable CompleteArgument( IDictionary fakeBoundParameters) { // Initialize the file open dialog - using (OpenFileDialog dialog = new OpenFileDialog()) + using (OpenFileDialog dialog = new()) { // Set the dialog filter to certificate files dialog.Filter = "Certificate files (*.cer)|*.cer"; @@ -456,7 +459,7 @@ public IEnumerable CompleteArgument( // Return the file paths wrapped in quotes as a completion result return new List { - new CompletionResult($"\"{selectedFilePaths}\"") + new($"\"{selectedFilePaths}\"") }; } } @@ -494,7 +497,7 @@ public IEnumerable CompleteArgument( IDictionary fakeBoundParameters) { // Initialize the folder browser dialog - using (FolderBrowserDialog dialog = new FolderBrowserDialog()) + using (FolderBrowserDialog dialog = new()) { // Show the dialog and get the result DialogResult result = dialog.ShowDialog(); @@ -507,7 +510,7 @@ public IEnumerable CompleteArgument( // Return the folder path wrapped in quotes with a wildcard character as a completion result return new List { - new CompletionResult($"\"{selectedFolderPath}\\*\"") + new($"\"{selectedFolderPath}\\*\"") }; } } diff --git a/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/BasePolicyNamez.cs b/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/BasePolicyNamez.cs index 677e23385..105b68691 100644 --- a/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/BasePolicyNamez.cs +++ b/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/BasePolicyNamez.cs @@ -13,7 +13,7 @@ public class BasePolicyNamez : IValidateSetValuesGenerator public string[] GetValidValues() { // Run CiTool.exe and capture the output - ProcessStartInfo startInfo = new ProcessStartInfo + ProcessStartInfo startInfo = new() { FileName = @"C:\Windows\System32\CiTool.exe", Arguments = "-lp -json", @@ -22,35 +22,34 @@ public string[] GetValidValues() CreateNoWindow = true }; - using (Process process = new Process { StartInfo = startInfo }) - { - process.Start(); + using Process process = new() + { StartInfo = startInfo }; + _ = process.Start(); + + string jsonOutput = process.StandardOutput.ReadToEnd(); + process.WaitForExit(); - string jsonOutput = process.StandardOutput.ReadToEnd(); - process.WaitForExit(); + // Parse the JSON output + JsonDocument jsonDoc = JsonDocument.Parse(jsonOutput); + JsonElement policiesElement = jsonDoc.RootElement.GetProperty("Policies"); - // Parse the JSON output - JsonDocument jsonDoc = JsonDocument.Parse(jsonOutput); - JsonElement policiesElement = jsonDoc.RootElement.GetProperty("Policies"); + List validValues = []; - List validValues = new List(); + foreach (JsonElement policyElement in policiesElement.EnumerateArray()) + { + bool isSystemPolicy = policyElement.GetProperty("IsSystemPolicy").GetBoolean(); + string? policyId = policyElement.GetProperty("PolicyID").GetString(); + string? basePolicyId = policyElement.GetProperty("BasePolicyID").GetString(); + string? friendlyName = policyElement.GetProperty("FriendlyName").GetString(); - foreach (JsonElement policyElement in policiesElement.EnumerateArray()) + // Use ordinal, case-insensitive comparison for the policy IDs + if (!isSystemPolicy && string.Equals(policyId, basePolicyId, StringComparison.OrdinalIgnoreCase) && friendlyName != null) { - bool isSystemPolicy = policyElement.GetProperty("IsSystemPolicy").GetBoolean(); - string? policyId = policyElement.GetProperty("PolicyID").GetString(); - string? basePolicyId = policyElement.GetProperty("BasePolicyID").GetString(); - string? friendlyName = policyElement.GetProperty("FriendlyName").GetString(); - - // Use ordinal, case-insensitive comparison for the policy IDs - if (!isSystemPolicy && string.Equals(policyId, basePolicyId, StringComparison.OrdinalIgnoreCase) && friendlyName != null) - { - validValues.Add(friendlyName); - } + validValues.Add(friendlyName); } - - return validValues.ToArray(); } + + return validValues.ToArray(); } } } diff --git a/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/CertCNz.cs b/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/CertCNz.cs index 8976146d1..89cb5429e 100644 --- a/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/CertCNz.cs +++ b/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/CertCNz.cs @@ -14,10 +14,10 @@ public string[] GetValidValues() { // The ValidateSet attribute expects a unique set of values, and it will throw an error if there are duplicates // This HashSet will take care of that requirement - HashSet output = new HashSet(); + HashSet output = []; // Open the current user's personal store - using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) + using (X509Store store = new(StoreName.My, StoreLocation.CurrentUser)) { store.Open(OpenFlags.ReadOnly); diff --git a/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/RuleOptionsx.cs b/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/RuleOptionsx.cs index 86b8b7cb6..82ef4db92 100644 --- a/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/RuleOptionsx.cs +++ b/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/RuleOptionsx.cs @@ -23,29 +23,29 @@ public string[] GetValidValues() schemaData.Load(Path.Combine(WDACConfig.GlobalVars.CISchemaPath)); // Create a namespace manager to handle namespaces - XmlNamespaceManager nsManager = new XmlNamespaceManager(schemaData.NameTable); + XmlNamespaceManager nsManager = new(schemaData.NameTable); nsManager.AddNamespace("xs", "http://www.w3.org/2001/XMLSchema"); // Define the XPath query to fetch enumeration values string xpathQuery = "//xs:simpleType[@name='OptionType']/xs:restriction/xs:enumeration/@value"; // Create a new HashSet to store the valid policy rule options - HashSet validOptions = new HashSet(StringComparer.OrdinalIgnoreCase); + HashSet validOptions = new(StringComparer.OrdinalIgnoreCase); // Fetch enumeration values from the schema - XmlNodeList? optionNodes = schemaData.SelectNodes(xpathQuery, nsManager) ?? throw new Exception("No valid options found in the Code Integrity Schema."); + XmlNodeList? optionNodes = schemaData.SelectNodes(xpathQuery, nsManager) ?? throw new InvalidOperationException("No valid options found in the Code Integrity Schema."); foreach (XmlNode node in optionNodes) { if (node.Value != null) { - validOptions.Add(node.Value); + _ = validOptions.Add(node.Value); } } if (WDACConfig.GlobalVars.ModuleRootPath == null) { - throw new Exception("ModuleRootPath is null!"); + throw new InvalidOperationException("ModuleRootPath is null!"); } // Construct the full path to PolicyRuleOptions.Json @@ -55,19 +55,14 @@ public string[] GetValidValues() string jsonContent = File.ReadAllText(jsonFilePath); // Deserialize the JSON content - Dictionary? intel = System.Text.Json.JsonSerializer.Deserialize>(jsonContent); - - if (intel == null) - { - throw new Exception("The PolicyRuleOptions.Json file did not have valid JSON content to be deserialized."); - } + Dictionary? intel = System.Text.Json.JsonSerializer.Deserialize>(jsonContent) ?? throw new InvalidOperationException("The PolicyRuleOptions.Json file did not have valid JSON content to be deserialized."); // Perform validation foreach (string key in intel.Values) { if (!validOptions.Contains(key)) { - throw new Exception($"Invalid Policy Rule Option detected that is not part of the Code Integrity Schema: {key}"); + throw new InvalidOperationException($"Invalid Policy Rule Option detected that is not part of the Code Integrity Schema: {key}"); } } diff --git a/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/ScanLevelz.cs b/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/ScanLevelz.cs index ddb0bc904..06d2fef09 100644 --- a/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/ScanLevelz.cs +++ b/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/ScanLevelz.cs @@ -7,12 +7,12 @@ public class ScanLevelz : IValidateSetValuesGenerator { public string[] GetValidValues() { - string[] scanLevelz = new string[] - { + string[] scanLevelz = + [ "Hash", "FileName", "SignedVersion", "Publisher", "FilePublisher", "LeafCertificate", "PcaCertificate", "RootCertificate", "WHQL", "WHQLPublisher", "WHQLFilePublisher", "PFN", "FilePath", "None" - }; + ]; return scanLevelz; } } diff --git a/WDACConfig/WDACConfig Module Files/C#/Custom Types/AuthenticodePageHashes.cs b/WDACConfig/WDACConfig Module Files/C#/Custom Types/AuthenticodePageHashes.cs deleted file mode 100644 index 709e93884..000000000 --- a/WDACConfig/WDACConfig Module Files/C#/Custom Types/AuthenticodePageHashes.cs +++ /dev/null @@ -1,20 +0,0 @@ -#nullable enable - -namespace WDACConfig -{ - public class AuthenticodePageHashes - { - public string? SHA1Page { get; set; } - public string? SHA256Page { get; set; } - public string? SHa1Authenticode { get; set; } - public string? SHA256Authenticode { get; set; } - - public AuthenticodePageHashes(string? sha1Page, string? sha256Page, string? sha1Authenticode, string? sha256Authenticode) - { - SHA1Page = sha1Page; - SHA256Page = sha256Page; - SHa1Authenticode = sha1Authenticode; - SHA256Authenticode = sha256Authenticode; - } - } -} diff --git a/WDACConfig/WDACConfig Module Files/C#/Custom Types/CertificateDetailsCreator.cs b/WDACConfig/WDACConfig Module Files/C#/Custom Types/CertificateDetailsCreator.cs deleted file mode 100644 index 6c0f5b88d..000000000 --- a/WDACConfig/WDACConfig Module Files/C#/Custom Types/CertificateDetailsCreator.cs +++ /dev/null @@ -1,21 +0,0 @@ - -#nullable enable - -namespace WDACConfig -{ - public class CertificateDetailsCreator - { - public string IntermediateCertTBS { get; set; } - public string IntermediateCertName { get; set; } - public string LeafCertTBS { get; set; } - public string LeafCertName { get; set; } - - public CertificateDetailsCreator(string intermediateCertTBS, string intermediateCertName, string leafCertTBS, string leafCertName) - { - IntermediateCertTBS = intermediateCertTBS; - IntermediateCertName = intermediateCertName; - LeafCertTBS = leafCertTBS; - LeafCertName = leafCertName; - } - } -} \ No newline at end of file diff --git a/WDACConfig/WDACConfig Module Files/C#/Custom Types/CertificateSignerCreator.cs b/WDACConfig/WDACConfig Module Files/C#/Custom Types/CertificateSignerCreator.cs deleted file mode 100644 index f529662a1..000000000 --- a/WDACConfig/WDACConfig Module Files/C#/Custom Types/CertificateSignerCreator.cs +++ /dev/null @@ -1,17 +0,0 @@ -#nullable enable - -namespace WDACConfig -{ - public class CertificateSignerCreator - { - public string TBS { get; set; } - public string SignerName { get; set; } - public int SiSigningScenario { get; set; } - public CertificateSignerCreator(string tbs, string signerName, int siSigningScenario) - { - TBS = tbs; - SignerName = signerName; - SiSigningScenario = siSigningScenario; - } - } -} diff --git a/WDACConfig/WDACConfig Module Files/C#/Custom Types/ChainElement.cs b/WDACConfig/WDACConfig Module Files/C#/Custom Types/ChainElement.cs deleted file mode 100644 index 9d952f012..000000000 --- a/WDACConfig/WDACConfig Module Files/C#/Custom Types/ChainElement.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Security.Cryptography.X509Certificates; - -#nullable enable - -namespace WDACConfig -{ - // the enum for CertificateType - public enum CertificateType - { - Root = 0, - Intermediate = 1, - Leaf = 2 - } - - public class ChainElement - { - public string SubjectCN { get; set; } - public string IssuerCN { get; set; } - public DateTime NotAfter { get; set; } - public string TBSValue { get; set; } - public X509Certificate2 Certificate { get; set; } - public CertificateType Type { get; set; } - - public ChainElement(string subjectcn, string issuercn, DateTime notafter, string tbsvalue, X509Certificate2 certificate, CertificateType type) - { - SubjectCN = subjectcn; - IssuerCN = issuercn; - NotAfter = notafter; - TBSValue = tbsvalue; - Certificate = certificate; - Type = type; - } - } -} diff --git a/WDACConfig/WDACConfig Module Files/C#/Custom Types/ChainPackage.cs b/WDACConfig/WDACConfig Module Files/C#/Custom Types/ChainPackage.cs deleted file mode 100644 index 03725a6eb..000000000 --- a/WDACConfig/WDACConfig Module Files/C#/Custom Types/ChainPackage.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Security.Cryptography.Pkcs; -using System.Security.Cryptography.X509Certificates; - -#nullable enable - -namespace WDACConfig -{ - public class ChainPackage - { - public X509Chain CertificateChain { get; set; } - public SignedCms SignedCms { get; set; } - public WDACConfig.ChainElement RootCertificate { get; set; } - public WDACConfig.ChainElement[] IntermediateCertificates { get; set; } - public WDACConfig.ChainElement LeafCertificate { get; set; } - public ChainPackage(X509Chain certificatechain, SignedCms signedcms, WDACConfig.ChainElement rootcertificate, - WDACConfig.ChainElement[] intermediatecertificates, - WDACConfig.ChainElement leafcertificate) - { - CertificateChain = certificatechain; - SignedCms = signedcms; - RootCertificate = rootcertificate; - IntermediateCertificates = intermediatecertificates; - LeafCertificate = leafcertificate; - } - } -} - diff --git a/WDACConfig/WDACConfig Module Files/C#/Custom Types/FileBasedInfoPackage.cs b/WDACConfig/WDACConfig Module Files/C#/Custom Types/FileBasedInfoPackage.cs deleted file mode 100644 index 5baeefdc7..000000000 --- a/WDACConfig/WDACConfig Module Files/C#/Custom Types/FileBasedInfoPackage.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Collections.Generic; - -#nullable enable - -namespace WDACConfig -{ - // Used by the BuildSignerAndHashObjects method to store and return the output - public class FileBasedInfoPackage - { - public List FilePublisherSigners { get; set; } - public List PublisherSigners { get; set; } - public List CompleteHashes { get; set; } - - public FileBasedInfoPackage(List filepublishersigners, List publishersigners, List completehashes) - { - FilePublisherSigners = filepublishersigners; - PublisherSigners = publishersigners; - CompleteHashes = completehashes; - } - } -} diff --git a/WDACConfig/WDACConfig Module Files/C#/Custom Types/HashCreator.cs b/WDACConfig/WDACConfig Module Files/C#/Custom Types/HashCreator.cs deleted file mode 100644 index 93c7f0710..000000000 --- a/WDACConfig/WDACConfig Module Files/C#/Custom Types/HashCreator.cs +++ /dev/null @@ -1,21 +0,0 @@ - -#nullable enable - -namespace WDACConfig -{ - public class HashCreator - { - public string AuthenticodeSHA256 { get; set; } - public string AuthenticodeSHA1 { get; set; } - public string FileName { get; set; } - public int SiSigningScenario { get; set; } - - public HashCreator(string authenticodeSHA256, string authenticodeSHA1, string fileName, int siSigningScenario) - { - AuthenticodeSHA256 = authenticodeSHA256; - AuthenticodeSHA1 = authenticodeSHA1; - FileName = fileName; - SiSigningScenario = siSigningScenario; - } - } -} \ No newline at end of file diff --git a/WDACConfig/WDACConfig Module Files/C#/Custom Types/OpusSigner.cs b/WDACConfig/WDACConfig Module Files/C#/Custom Types/OpusSigner.cs deleted file mode 100644 index fc709d1ec..000000000 --- a/WDACConfig/WDACConfig Module Files/C#/Custom Types/OpusSigner.cs +++ /dev/null @@ -1,17 +0,0 @@ - -#nullable enable - -namespace WDACConfig -{ - public class OpusSigner - { - public string TBSHash { get; set; } - public string SubjectCN { get; set; } - - public OpusSigner(string tbsHash, string subjectCN) - { - TBSHash = tbsHash; - SubjectCN = subjectCN; - } - } -} diff --git a/WDACConfig/WDACConfig Module Files/C#/Custom Types/Signer.cs b/WDACConfig/WDACConfig Module Files/C#/Custom Types/Signer.cs deleted file mode 100644 index e8bdd38fe..000000000 --- a/WDACConfig/WDACConfig Module Files/C#/Custom Types/Signer.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Collections.Generic; - -#nullable enable - -namespace WDACConfig -{ - public class Signer - { - public string ID { get; set; } - public string Name { get; set; } - public string CertRoot { get; set; } - public string CertPublisher { get; set; } - public string CertIssuer { get; set; } - public string[] CertEKU { get; set; } - public string CertOemID { get; set; } - public string[] FileAttribRef { get; set; } - public Dictionary> FileAttrib { get; set; } - public string SignerScope { get; set; } - public bool IsWHQL { get; set; } - public bool IsAllowed { get; set; } - public bool HasEKU { get; set; } - public Signer(string id, string name, string certRoot, string certPublisher, string certIssuer, - string[] certEKU, string certOemID, string[] fileAttribRef, - Dictionary> fileAttrib, - string signerScope, bool isWHQL, bool isAllowed, bool hasEKU) - { - ID = id; - Name = name; - CertRoot = certRoot; - CertPublisher = certPublisher; - CertIssuer = certIssuer; - CertEKU = certEKU; - CertOemID = certOemID; - FileAttribRef = fileAttribRef; - FileAttrib = fileAttrib; - SignerScope = signerScope; - IsWHQL = isWHQL; - IsAllowed = isAllowed; - HasEKU = hasEKU; - } - } -} diff --git a/WDACConfig/WDACConfig Module Files/C#/Custom Types/SimulationInput.cs b/WDACConfig/WDACConfig Module Files/C#/Custom Types/SimulationInput.cs deleted file mode 100644 index fdc82aba6..000000000 --- a/WDACConfig/WDACConfig Module Files/C#/Custom Types/SimulationInput.cs +++ /dev/null @@ -1,24 +0,0 @@ - -#nullable enable - -// Used by WDAC Simulations -namespace WDACConfig -{ - public class SimulationInput - { - // Adding public getters and setters for the properties - public System.IO.FileInfo FilePath { get; set; } - public WDACConfig.ChainPackage[] AllFileSigners { get; set; } - public WDACConfig.Signer[] SignerInfo { get; set; } - public string[] EKUOIDs { get; set; } - - // Adding a constructor to initialize the properties - public SimulationInput(System.IO.FileInfo filepath, WDACConfig.ChainPackage[] allfilesigners, WDACConfig.Signer[] signerinfo, string[] ekuoids) - { - FilePath = filepath; - AllFileSigners = allfilesigners; - SignerInfo = signerinfo; - EKUOIDs = ekuoids; - } - } -} diff --git a/WDACConfig/WDACConfig Module Files/C#/Custom Types/SimulationOutput.cs b/WDACConfig/WDACConfig Module Files/C#/Custom Types/SimulationOutput.cs deleted file mode 100644 index 15391c0aa..000000000 --- a/WDACConfig/WDACConfig Module Files/C#/Custom Types/SimulationOutput.cs +++ /dev/null @@ -1,46 +0,0 @@ - -#nullable enable - -// Used by WDAC Simulations, the output of the comparer function/method -namespace WDACConfig -{ - // This class holds the details of the current file in the WDAC Simulation comparer - public class SimulationOutput - { - public string Path { get; set; } // the name of the file, which is truncated version of its path - public string Source { get; set; } // Source from the Comparer function is always 'Signer' - public bool IsAuthorized { get; set; } // Whether the file is authorized or not - public string SignerID { get; set; } // Gathered from the Get-SignerInfo function - public string SignerName { get; set; } // Gathered from the Get-SignerInfo function - public string SignerCertRoot { get; set; } // Gathered from the Get-SignerInfo function - public string SignerCertPublisher { get; set; } // Gathered from the Get-SignerInfo function - public string SignerScope { get; set; } // Gathered from the Get-SignerInfo function - public string[] SignerFileAttributeIDs { get; set; } // Gathered from the Get-SignerInfo function - public string MatchCriteria { get; set; } // The main level based on which the file is authorized - public string SpecificFileNameLevelMatchCriteria { get; set; } // Only those eligible for FilePublisher, WHQLFilePublisher or SignedVersion levels assign this value, otherwise it stays null - public string CertSubjectCN { get; set; } // Subject CN of the signer that allows the file - public string CertIssuerCN { get; set; } // Issuer CN of the signer that allows the file - public string CertNotAfter { get; set; } // NotAfter date of the signer that allows the file - public string CertTBSValue { get; set; } // TBS value of the signer that allows the file - public string FilePath { get; set; } // Full path of the file - public SimulationOutput(string path, string source, bool isauthorized, string signerID, string signerName, string signerCertRoot, string signerCertPublisher, string signerScope, string[] signerFileAttributeIDs, string matchCriteria, string specificFileNameLevelMatchCriteria, string certSubjectCN, string certIssuerCN, string certNotAfter, string certTBSValue, string filePath) - { - Path = path; - Source = source; - IsAuthorized = isauthorized; - SignerID = signerID; - SignerName = signerName; - SignerCertRoot = signerCertRoot; - SignerCertPublisher = signerCertPublisher; - SignerScope = signerScope; - SignerFileAttributeIDs = signerFileAttributeIDs; - MatchCriteria = matchCriteria; - SpecificFileNameLevelMatchCriteria = specificFileNameLevelMatchCriteria; - CertSubjectCN = certSubjectCN; - CertIssuerCN = certIssuerCN; - CertNotAfter = certNotAfter; - CertTBSValue = certTBSValue; - FilePath = filePath; - } - } -} diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/AuthenticodeHashCalc.cs b/WDACConfig/WDACConfig Module Files/C#/Functions/AuthenticodeHashCalc.cs deleted file mode 100644 index b2ba4d89b..000000000 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/AuthenticodeHashCalc.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System; -using System.Globalization; -using System.IO; -using System.Runtime.InteropServices; -using System.Text; - -#nullable enable - -namespace WDACConfig -{ - // necessary logics for Authenticode and First Page hash calculation - internal class WinTrust - { - // a constant field that defines a flag value for the native function - // This causes/helps the GetCiFileHashes method to return the flat file hashes whenever a non-conformant file is encountered - internal const uint CryptcatadminCalchashFlagNonconformantFilesFallbackFlat = 1; - - // a method to acquire a handle to a catalog administrator context using a native function from WinTrust.dll - [DllImport("WinTrust.dll", CharSet = CharSet.Unicode)] - internal static extern bool CryptCATAdminAcquireContext2( - ref IntPtr hCatAdmin, // the first parameter: a reference to a pointer to store the handle - IntPtr pgSubsystem, // the second parameter: a pointer to a GUID that identifies the subsystem - string pwszHashAlgorithm, // the third parameter: a string that specifies the hash algorithm to use - IntPtr pStrongHashPolicy, // the fourth parameter: a pointer to a structure that specifies the strong hash policy - uint dwFlags // the fifth parameter: a flag value that controls the behavior of the function - ); - - // a method to release a handle to a catalog administrator context using a native function from WinTrust.dll - [DllImport("WinTrust.dll", CharSet = CharSet.Unicode)] - internal static extern bool CryptCATAdminReleaseContext( - IntPtr hCatAdmin, // the first parameter: a pointer to the handle to release - uint dwFlags // the second parameter: a flag value that controls the behavior of the function - ); - - // a method to calculate the hash of a file using a native function from WinTrust.dll - [DllImport("WinTrust.dll", CharSet = CharSet.Unicode)] - internal static extern bool CryptCATAdminCalcHashFromFileHandle3( - IntPtr hCatAdmin, // the first parameter: a pointer to the handle of the catalog administrator context - IntPtr hFile, // the second parameter: a pointer to the handle of the file to hash - ref int pcbHash, // the third parameter: a reference to an integer that specifies the size of the hash buffer - IntPtr pbHash, // the fourth parameter: a pointer to a buffer to store the hash value - uint dwFlags // the fifth parameter: a flag value that controls the behavior of the function - ); - } - - public static class AuthPageHash - { - ///

- /// Method that outputs all 4 kinds of hashes - /// - /// The path to the file that is going to be hashed - /// WDACConfig.AuthenticodePageHashes object that contains all 4 kinds of hashes - public static WDACConfig.AuthenticodePageHashes GetCiFileHashes(string filePath) - { - return new WDACConfig.AuthenticodePageHashes( - WDACConfig.PageHashCalculator.GetPageHash("SHA1", filePath), - WDACConfig.PageHashCalculator.GetPageHash("SHA256", filePath), - GetAuthenticodeHash(filePath, "SHA1"), - GetAuthenticodeHash(filePath, "SHA256") - ); - } - - private static string? GetAuthenticodeHash(string filePath, string hashAlgorithm) - { - // A StringBuilder object to store the hash value as a hexadecimal string - StringBuilder hashString = new StringBuilder(64); - IntPtr contextHandle = IntPtr.Zero; - IntPtr fileStreamHandle = IntPtr.Zero; - IntPtr hashValue = IntPtr.Zero; - - try - { - using (FileStream fileStream = File.OpenRead(filePath)) - { - // DangerousGetHandle returns the handle to the file stream - fileStreamHandle = fileStream.SafeFileHandle.DangerousGetHandle(); - - if (fileStreamHandle == IntPtr.Zero) - { - return null; - } - - if (!WDACConfig.WinTrust.CryptCATAdminAcquireContext2(ref contextHandle, IntPtr.Zero, hashAlgorithm, IntPtr.Zero, 0)) - { - throw new Exception($"Could not acquire context for {hashAlgorithm}"); - } - - int hashSize = 0; - - if (!WDACConfig.WinTrust.CryptCATAdminCalcHashFromFileHandle3(contextHandle, fileStreamHandle, ref hashSize, IntPtr.Zero, WDACConfig.WinTrust.CryptcatadminCalchashFlagNonconformantFilesFallbackFlat)) - { - throw new Exception($"Could not hash {filePath} using {hashAlgorithm}"); - } - - hashValue = Marshal.AllocHGlobal(hashSize); - - if (!WDACConfig.WinTrust.CryptCATAdminCalcHashFromFileHandle3(contextHandle, fileStreamHandle, ref hashSize, hashValue, WDACConfig.WinTrust.CryptcatadminCalchashFlagNonconformantFilesFallbackFlat)) - { - throw new Exception($"Could not hash {filePath} using {hashAlgorithm}"); - } - - for (int offset = 0; offset < hashSize; offset++) - { - // Marshal.ReadByte returns a byte from the hashValue buffer at the specified offset - byte b = Marshal.ReadByte(hashValue, offset); - // Append the byte to the hashString as a hexadecimal string - hashString.Append(b.ToString("X2", CultureInfo.InvariantCulture)); - } - } - } - finally - { - if (hashValue != IntPtr.Zero) - { - Marshal.FreeHGlobal(hashValue); - } - - if (contextHandle != IntPtr.Zero) - { - WDACConfig.WinTrust.CryptCATAdminReleaseContext(contextHandle, 0); - } - } - - return hashString.ToString(); - } - } -} diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/CodeIntegritySigner.cs b/WDACConfig/WDACConfig Module Files/C#/Functions/CodeIntegritySigner.cs deleted file mode 100644 index 712029681..000000000 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/CodeIntegritySigner.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; - -#nullable enable - -namespace WDACConfig -{ - public static class CodeIntegritySigner - { - public static void InvokeCiSigning(FileInfo ciPath, FileInfo signToolPathFinal, string certCN) - { - // Validate inputs - if (ciPath == null) throw new ArgumentNullException(nameof(ciPath)); - if (signToolPathFinal == null) throw new ArgumentNullException(nameof(signToolPathFinal)); - if (string.IsNullOrEmpty(certCN)) throw new ArgumentException("Certificate Common Name cannot be null or empty.", nameof(certCN)); - - // Build the arguments for the process - string arguments = $"sign /v /n \"{certCN}\" /p7 . /p7co 1.3.6.1.4.1.311.79.1 /fd certHash \"{ciPath.Name}\""; - - // Set up the process start info - ProcessStartInfo startInfo = new ProcessStartInfo - { - FileName = signToolPathFinal.FullName, - Arguments = arguments, - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true, - WorkingDirectory = ciPath.DirectoryName // Set the working directory so that SignTool.exe will know where the .cip file is and where to save the output - }; - - // Start the process - using (Process process = new Process { StartInfo = startInfo }) - { - process.Start(); - - // Wait for the process to exit - process.WaitForExit(); - - // Read the output and error streams - string output = process.StandardOutput.ReadToEnd(); - string error = process.StandardError.ReadToEnd(); - - // Log the output and error - WDACConfig.VerboseLogger.Write(output); - - // Check if there is any error and throw an exception if there is - if (!string.IsNullOrEmpty(error)) - { - throw new InvalidOperationException($"SignTool failed with exit code {process.ExitCode}. Error: {error}"); - } - - // Check the exit code - if (process.ExitCode != 0) - { - throw new InvalidOperationException($"SignTool failed with exit code {process.ExitCode}. Error: {error}"); - } - } - } - } -} diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/DebugLogger.cs b/WDACConfig/WDACConfig Module Files/C#/Functions/DebugLogger.cs deleted file mode 100644 index 3ba9d52d9..000000000 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/DebugLogger.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; - -#nullable enable - -namespace WDACConfig -{ - public static class DebugLogger - { - /// - /// Write a Debug message to the console - /// The Debug messages are not redirectable in PowerShell - /// https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.host.pshostuserinterface - /// - /// - public static void Write(string message) - { - try - { - if (string.Equals(WDACConfig.GlobalVars.DebugPreference, "Continue", StringComparison.OrdinalIgnoreCase) || - string.Equals(WDACConfig.GlobalVars.DebugPreference, "Inquire", StringComparison.OrdinalIgnoreCase)) - { - if (WDACConfig.GlobalVars.Host != null) - { - WDACConfig.GlobalVars.Host.UI.WriteDebugLine(message); - } - } - - } - // Do not do anything if errors occur - // Since many methods write to the console asynchronously this can throw errors - // implement better ways such as using log file or in the near future a GUI for writing Debug messages when -Debug is used - catch { } - } - } -} diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/EventLogUtility.cs b/WDACConfig/WDACConfig Module Files/C#/Functions/EventLogUtility.cs deleted file mode 100644 index 9155881c5..000000000 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/EventLogUtility.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Diagnostics.Eventing.Reader; -using System.IO; - -#nullable enable - -namespace WDACConfig -{ - public static class EventLogUtility - { - /// - /// Increase Code Integrity Operational Event Logs size from the default 1MB to user-defined size. - /// Also automatically increases the log size by 1MB if the current free space is less than 1MB and the current maximum log size is less than or equal to 10MB. - /// This is to prevent infinitely expanding the max log size automatically. - /// - /// Size of the Code Integrity Operational Event Log - public static void SetLogSize(ulong logSize = 0) - { - WDACConfig.VerboseLogger.Write("Set-SetLogSize method started..."); - - string logName = "Microsoft-Windows-CodeIntegrity/Operational"; - - using (var logConfig = new EventLogConfiguration(logName)) - { - string logFilePath = Environment.ExpandEnvironmentVariables(logConfig.LogFilePath); - FileInfo logFileInfo = new FileInfo(logFilePath); - long currentLogFileSize = logFileInfo.Length; - long currentLogMaxSize = logConfig.MaximumSizeInBytes; - - if (logSize == 0) - { - if ((currentLogMaxSize - currentLogFileSize) < 1 * 1024 * 1024) - { - if (currentLogMaxSize <= 10 * 1024 * 1024) - { - WDACConfig.VerboseLogger.Write("Increasing the Code Integrity log size by 1MB because its current free space is less than 1MB."); - logConfig.MaximumSizeInBytes = currentLogMaxSize + 1 * 1024 * 1024; - logConfig.IsEnabled = true; - logConfig.SaveChanges(); - } - } - } - else - { - // Check if the provided log size is greater than 1100 KB - // To prevent from disabling the log or setting it to a very small size that is lower than its default size - if (logSize > 1100 * 1024) - { - WDACConfig.VerboseLogger.Write($"Setting Code Integrity log size to {logSize}."); - logConfig.MaximumSizeInBytes = (long)logSize; - logConfig.IsEnabled = true; - logConfig.SaveChanges(); - } - else - { - WDACConfig.VerboseLogger.Write("Provided log size is less than or equal to 1100 KB. No changes made."); - } - } - } - } - } -} diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/VerboseLogger.cs b/WDACConfig/WDACConfig Module Files/C#/Logging/Logger.cs similarity index 52% rename from WDACConfig/WDACConfig Module Files/C#/Functions/VerboseLogger.cs rename to WDACConfig/WDACConfig Module Files/C#/Logging/Logger.cs index 032fdd0a5..324ca9ed9 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/VerboseLogger.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Logging/Logger.cs @@ -1,10 +1,8 @@ -using System; - #nullable enable namespace WDACConfig { - public static class VerboseLogger + public static class Logger { /// /// Write a verbose message to the console @@ -16,19 +14,13 @@ public static void Write(string message) { try { - if (string.Equals(WDACConfig.GlobalVars.VerbosePreference, "Continue", StringComparison.OrdinalIgnoreCase) || - string.Equals(WDACConfig.GlobalVars.VerbosePreference, "Inquire", StringComparison.OrdinalIgnoreCase)) + if (GlobalVars.VerbosePreference) { - if (WDACConfig.GlobalVars.Host != null) - { - WDACConfig.GlobalVars.Host.UI.WriteVerboseLine(message); - } + GlobalVars.Host?.UI.WriteVerboseLine(message); } - } // Do not do anything if errors occur // Since many methods write to the console asynchronously this can throw errors - // implement better ways such as using log file or in the near future a GUI for writing verbose messages when -Verbose is used catch { } } } diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/LoggerInitializer.cs b/WDACConfig/WDACConfig Module Files/C#/Logging/LoggerInitializer.cs similarity index 55% rename from WDACConfig/WDACConfig Module Files/C#/Functions/LoggerInitializer.cs rename to WDACConfig/WDACConfig Module Files/C#/Logging/LoggerInitializer.cs index 7642e30c0..d40f28a62 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/LoggerInitializer.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Logging/LoggerInitializer.cs @@ -1,3 +1,4 @@ +using System; using System.Management.Automation.Host; #nullable enable @@ -14,19 +15,28 @@ public class LoggerInitializer /// public static void Initialize(string verbosePreference, string debugPreference, PSHost host) { + if (!string.IsNullOrWhiteSpace(verbosePreference)) { - WDACConfig.GlobalVars.VerbosePreference = verbosePreference; + if (string.Equals(verbosePreference, "Continue", StringComparison.OrdinalIgnoreCase) || + string.Equals(verbosePreference, "Inquire", StringComparison.OrdinalIgnoreCase)) + { + GlobalVars.VerbosePreference = true; + } } if (!string.IsNullOrWhiteSpace(debugPreference)) { - WDACConfig.GlobalVars.DebugPreference = debugPreference; + if (string.Equals(debugPreference, "Continue", StringComparison.OrdinalIgnoreCase) || + string.Equals(debugPreference, "Inquire", StringComparison.OrdinalIgnoreCase)) + { + GlobalVars.DebugPreference = true; + } } if (host != null) { - WDACConfig.GlobalVars.Host = host; + GlobalVars.Host = host; } } } diff --git a/WDACConfig/WDACConfig Module Files/C#/Main Cmdlets/AssertWDACConfigIntegrity.cs b/WDACConfig/WDACConfig Module Files/C#/Main Cmdlets/AssertWDACConfigIntegrity.cs new file mode 100644 index 000000000..04dd0b649 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/C#/Main Cmdlets/AssertWDACConfigIntegrity.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Security.Cryptography; +using System.Text; + +#nullable enable + +namespace WDACConfig +{ + // This class defines Hash entries for each file in the WDACConfig PowerShell module based on the cloud CSV + public class WDACConfigHashEntry(string? relativePath, string? fileName, string? fileHash, string? fileHashSHA3_512) + { + public string? RelativePath { get; set; } = relativePath; + public string? FileName { get; set; } = fileName; + public string? FileHash { get; set; } = fileHash; + public string? FileHashSHA3_512 { get; set; } = fileHashSHA3_512; + } + + + public class AssertWDACConfigIntegrity + { + /// + /// Hashes all of the files in the WDACConfig, download the cloud hashes, compares them with each other and report back hash mismatches + /// + /// + /// Location where new hash results will be saved + public static List? Invoke(bool SaveLocally, string? path) + { + + // Defining the output file name and the URL of the cloud CSV file + string OutputFileName = "Hashes.csv"; + string url = "https://raw.githubusercontent.com/HotCakeX/Harden-Windows-Security/main/WDACConfig/Utilities/Hashes.csv"; + + // Parse the CSV content + List ParsedCSVList = []; + + // Hash details of the current PowerShell files + List CurrentFileHashes = []; + + using HttpClient client = new(); + + // Download CSV content synchronously + string csvData = client.GetStringAsync(url).Result; + + // Parse the CSV content + ParsedCSVList = ParseCSV(csvData); + + // Get all of the files in the PowerShell module directory + List files = WDACConfig.FileUtility.GetFilesFast([new DirectoryInfo(WDACConfig.GlobalVars.ModuleRootPath!)], null, ["*"]); + + // Loop over each file + foreach (FileInfo file in files) + { + + // Making sure the PowerShell Gallery file in the WDACConfig module's folder is skipped + if (file.Name == "PSGetModuleInfo.xml") + { + continue; + } + + // Read the file as a byte array - This way we can get hashes of a file in use by another process where Get-FileHash would fail + byte[] Bytes = File.ReadAllBytes(file.FullName); + + // Compute the hash of the byte array + Byte[] HashBytes = SHA512.HashData(Bytes); + + // Convert the hash bytes to a hexadecimal string to make it look like the output of the Get-FileHash which produces hexadecimals (0-9 and A-F) + // If [System.Convert]::ToBase64String was used, it'd return the hash in base64 format, which uses 64 symbols (A-Z, a-z, 0-9, + and /) to represent each byte + String HashString = BitConverter.ToString(HashBytes); + + // Remove the dashes from the hexadecimal string + HashString = HashString.Replace("-", "", StringComparison.OrdinalIgnoreCase); + + // Add the file details to the list + CurrentFileHashes.Add(new WDACConfigHashEntry( + Path.GetRelativePath(WDACConfig.GlobalVars.ModuleRootPath!, file.FullName), + file.Name, + HashString, + null)); + } + + // Save the current files' hashes to a CSV in the user defined directory path + if (SaveLocally) + { + ExportToCsv(Path.Combine(path!, OutputFileName), CurrentFileHashes); + } + + // A list to store mismatches + List MismatchedEntries = []; + + // Compare the two lists + foreach (WDACConfigHashEntry currentFileHash in CurrentFileHashes) + { + // Find the corresponding entry in ParsedCSVList + WDACConfigHashEntry? matchingParsedEntry = ParsedCSVList.FirstOrDefault( + p => RemoveQuotes(p.RelativePath) == RemoveQuotes(currentFileHash.RelativePath) && + RemoveQuotes(p.FileName) == RemoveQuotes(currentFileHash.FileName) && + RemoveQuotes(p.FileHash) == RemoveQuotes(currentFileHash.FileHash) + ); + + // If there's no matching entry or the hashes are different, add to the mismatch list + if (matchingParsedEntry is null) + { + MismatchedEntries.Add(currentFileHash); + } + } + + if (MismatchedEntries.Count > 0) + { + Console.WriteLine("The following files are different from the ones in the cloud:"); + return MismatchedEntries; + } + else + { + Console.WriteLine("All of your local WDACConfig files are genuine."); + return null; + } + } + + /// + /// Parses the CSV content and returns a list of WDACConfigHashEntry objects + /// + /// + /// + private static List ParseCSV(string csvData) + { + var entries = new List(); + + using (StringReader reader = new(csvData)) + { + string? line; + bool isHeader = true; + + while ((line = reader.ReadLine()) != null) + { + // Skip the header + if (isHeader) + { + isHeader = false; + continue; + } + + // Split the CSV line by commas + var fields = line.Split(','); + + if (fields.Length == 4) + { + entries.Add(new WDACConfigHashEntry + ( + fields[0], + fields[1], + fields[2], + fields[3] + )); + } + } + } + + return entries; + } + + + /// + /// Exports the list of WDACConfigHashEntry objects to a CSV file + /// + /// + /// + private static void ExportToCsv(string outputPath, List entries) + { + // Ensure we create a new file or overwrite an existing one + using (StreamWriter writer = new(outputPath, false, Encoding.UTF8)) + { + // Write the CSV header + writer.WriteLine(""" +"RelativePath","FileName","FileHash","FileHashSHA3_512" +"""); + + // Write each entry in the list + foreach (var entry in entries) + { + string relativePath = EscapeCsv(entry.RelativePath); + string fileName = EscapeCsv(entry.FileName); + string fileHash = EscapeCsv(entry.FileHash); + string fileHashSHA3_512 = EscapeCsv(entry.FileHashSHA3_512); + + // Write the CSV row + writer.WriteLine($"{relativePath},{fileName},{fileHash},{fileHashSHA3_512}"); + } + } + } + + + /// + /// Escapes the content of a field for CSV (quotes any value with commas). + /// + /// The string to escape. + /// Escaped CSV string + private static string EscapeCsv(string? field) + { + // If the field is null or empty, return an empty string + if (string.IsNullOrWhiteSpace(field)) return ""; + + // Add quotes around each field regardless of whether they already include comma, quotes etc. + return $"\"{field.Replace("\"", "\"\"", StringComparison.OrdinalIgnoreCase)}\""; + + } + + + /// + /// Helper function to remove double quotes from the strings + /// + /// + /// + private static string? RemoveQuotes(string? input) + { + return input?.Trim('"'); + } + } +} diff --git a/WDACConfig/WDACConfig Module Files/C#/Main Cmdlets/GetCIPolicySetting.cs b/WDACConfig/WDACConfig Module Files/C#/Main Cmdlets/GetCIPolicySetting.cs new file mode 100644 index 000000000..0ec5372f4 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/C#/Main Cmdlets/GetCIPolicySetting.cs @@ -0,0 +1,81 @@ +using System.Runtime.InteropServices; + +#nullable enable + +namespace WDACConfig +{ + + public class SecurePolicySetting(object? Value, WDACConfig.WLDP_SECURE_SETTING_VALUE_TYPE ValueType, uint ValueSize, bool Status, int StatusCode) + { + public object? Value { get; set; } = Value; + public WDACConfig.WLDP_SECURE_SETTING_VALUE_TYPE ValueType { get; set; } = ValueType; + public uint ValueSize { get; set; } = ValueSize; + public bool Status { get; set; } = Status; + public int StatusCode { get; set; } = StatusCode; + } + + public class GetCIPolicySetting + { + + public static SecurePolicySetting Invoke(string provider, string key, string valueName) + { + + // Create UNICODE_STRING structures + var ProviderUS = WDACConfig.WldpQuerySecurityPolicyWrapper.InitUnicodeString(provider); + var KeyUS = WDACConfig.WldpQuerySecurityPolicyWrapper.InitUnicodeString(key); + var ValueNameUS = WDACConfig.WldpQuerySecurityPolicyWrapper.InitUnicodeString(valueName); + + // Prepare output variables + uint ValueSize = 1024; // Changed to uint to match the P/Invoke declaration + var Value = Marshal.AllocHGlobal((int)ValueSize); + + var result = WldpQuerySecurityPolicyWrapper.WldpQuerySecurityPolicy( + ref ProviderUS, + ref KeyUS, + ref ValueNameUS, + out WDACConfig.WLDP_SECURE_SETTING_VALUE_TYPE ValueType, + Value, + ref ValueSize + ); + + object? decodedValue = null; + + if (result == 0) + { + switch (ValueType) + { + case WDACConfig.WLDP_SECURE_SETTING_VALUE_TYPE.WldpBoolean: + decodedValue = Marshal.ReadByte(Value) != 0; + break; + case WDACConfig.WLDP_SECURE_SETTING_VALUE_TYPE.WldpString: + decodedValue = Marshal.PtrToStringUni(Value); + break; + case WDACConfig.WLDP_SECURE_SETTING_VALUE_TYPE.WldpInteger: + decodedValue = Marshal.ReadInt32(Value); + break; + case WDACConfig.WLDP_SECURE_SETTING_VALUE_TYPE.WldpNone: + break; + case WDACConfig.WLDP_SECURE_SETTING_VALUE_TYPE.WldpFlag: + break; + default: + break; + } + } + + // Free up the resources + Marshal.FreeHGlobal(ProviderUS.Buffer); + Marshal.FreeHGlobal(KeyUS.Buffer); + Marshal.FreeHGlobal(ValueNameUS.Buffer); + Marshal.FreeHGlobal(Value); + + return new SecurePolicySetting( + decodedValue, + ValueType, + ValueSize, + result == 0, + result + ); + } + + } +} diff --git a/WDACConfig/WDACConfig Module Files/C#/Main Cmdlets/GetCiFileHashes.cs b/WDACConfig/WDACConfig Module Files/C#/Main Cmdlets/GetCiFileHashes.cs new file mode 100644 index 000000000..d3abc2bd4 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/C#/Main Cmdlets/GetCiFileHashes.cs @@ -0,0 +1,89 @@ +using System; +using System.Globalization; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +#nullable enable + +namespace WDACConfig +{ + public static class CiFileHash + { + /// + /// Method that outputs all 4 kinds of hashes + /// + /// The path to the file that is going to be hashed + /// WDACConfig.CodeIntegrityHashes object that contains all 4 kinds of hashes + public static CodeIntegrityHashes GetCiFileHashes(string filePath) + { + return new CodeIntegrityHashes( + PageHashCalculator.GetPageHash("SHA1", filePath), + PageHashCalculator.GetPageHash("SHA256", filePath), + GetAuthenticodeHash(filePath, "SHA1"), + GetAuthenticodeHash(filePath, "SHA256") + ); + } + + private static string? GetAuthenticodeHash(string filePath, string hashAlgorithm) + { + // A StringBuilder object to store the hash value as a hexadecimal string + StringBuilder hashString = new(64); + IntPtr contextHandle = IntPtr.Zero; + IntPtr hashValue = IntPtr.Zero; + + try + { + using FileStream fileStream = File.OpenRead(filePath); + // DangerousGetHandle returns the handle to the file stream + nint fileStreamHandle = fileStream.SafeFileHandle.DangerousGetHandle(); + + if (fileStreamHandle == IntPtr.Zero) + { + return null; + } + + if (!WinTrust.CryptCATAdminAcquireContext2(ref contextHandle, IntPtr.Zero, hashAlgorithm, IntPtr.Zero, 0)) + { + throw new InvalidOperationException($"Could not acquire context for {hashAlgorithm}"); + } + + int hashSize = 0; + + if (!WinTrust.CryptCATAdminCalcHashFromFileHandle3(contextHandle, fileStreamHandle, ref hashSize, IntPtr.Zero, WinTrust.CryptcatadminCalchashFlagNonconformantFilesFallbackFlat)) + { + throw new InvalidOperationException($"Could not hash {filePath} using {hashAlgorithm}"); + } + + hashValue = Marshal.AllocHGlobal(hashSize); + + if (!WinTrust.CryptCATAdminCalcHashFromFileHandle3(contextHandle, fileStreamHandle, ref hashSize, hashValue, WinTrust.CryptcatadminCalchashFlagNonconformantFilesFallbackFlat)) + { + throw new InvalidOperationException($"Could not hash {filePath} using {hashAlgorithm}"); + } + + for (int offset = 0; offset < hashSize; offset++) + { + // Marshal.ReadByte returns a byte from the hashValue buffer at the specified offset + byte b = Marshal.ReadByte(hashValue, offset); + // Append the byte to the hashString as a hexadecimal string + _ = hashString.Append(b.ToString("X2", CultureInfo.InvariantCulture)); + } + } + finally + { + if (hashValue != IntPtr.Zero) + { + Marshal.FreeHGlobal(hashValue); + } + + if (contextHandle != IntPtr.Zero) + { + _ = WinTrust.CryptCATAdminReleaseContext(contextHandle, 0); + } + } + + return hashString.ToString(); + } + } +} diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/TestCiPolicy.cs b/WDACConfig/WDACConfig Module Files/C#/Main Cmdlets/TestCiPolicy.cs similarity index 86% rename from WDACConfig/WDACConfig Module Files/C#/Functions/TestCiPolicy.cs rename to WDACConfig/WDACConfig Module Files/C#/Main Cmdlets/TestCiPolicy.cs index 31db65a74..06922ae4d 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/TestCiPolicy.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Main Cmdlets/TestCiPolicy.cs @@ -42,10 +42,10 @@ public static class CiPolicyTest try { // Create the XmlReaderSettings object - XmlReaderSettings settings = new XmlReaderSettings(); + XmlReaderSettings settings = new(); // Add schema to settings - settings.Schemas.Add(null, schemaPath); + _ = settings.Schemas.Add(null, schemaPath); // Set the validation settings settings.ValidationType = ValidationType.Schema; @@ -60,15 +60,13 @@ public static class CiPolicyTest }; // Create an XmlDocument object - XmlDocument xmlDoc = new XmlDocument(); + XmlDocument xmlDoc = new(); // Load the input XML document xmlDoc.Load(xmlFilePath); - using (XmlReader reader = XmlReader.Create(new StringReader(xmlDoc.OuterXml), settings)) - { - // Validate the XML document - while (reader.Read()) { } - } + using XmlReader reader = XmlReader.Create(new StringReader(xmlDoc.OuterXml), settings); + // Validate the XML document + while (reader.Read()) { } return true; } @@ -85,7 +83,7 @@ public static class CiPolicyTest try { // Create a new SignedCms object to store the signed message - SignedCms signedCms = new SignedCms(); + SignedCms signedCms = new(); // Decode the signed message from the file specified by cipFilePath // The file is read as a byte array because the SignedCms.Decode() method expects a byte array as input @@ -107,7 +105,7 @@ public static class CiPolicyTest } else { - throw new ArgumentNullException("Either xmlFilePath or cipFilePath must be provided."); + throw new InvalidOperationException("Either xmlFilePath or cipFilePath must be provided."); } } } diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/AllCertificatesGrabber.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/AllCertificatesGrabber.cs similarity index 78% rename from WDACConfig/WDACConfig Module Files/C#/Functions/AllCertificatesGrabber.cs rename to WDACConfig/WDACConfig Module Files/C#/Other Functions/AllCertificatesGrabber.cs index 9a659dadb..7acb4272b 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/AllCertificatesGrabber.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Other Functions/AllCertificatesGrabber.cs @@ -8,34 +8,22 @@ // The following functions and methods use the Windows APIs to grab all of the certificates from a signed file -namespace WDACConfig.AllCertificatesGrabber +namespace WDACConfig { // a class to throw a custom exception when the certificate has HashMismatch - public class ExceptionHashMismatchInCertificate : Exception + public class ExceptionHashMismatchInCertificate(string message, string functionName) : Exception($"{functionName}: {message}") { - public ExceptionHashMismatchInCertificate(string message, string functionName) - : base($"{functionName}: {message}") - { - } } // Represents a signed CMS and its certificate chain - public class AllFileSigners + public class AllFileSigners(SignedCms signerCertificate, X509Chain certificateChain) { - public SignedCms Signer { get; } // SignedCms object containing signer's certificate and message - public X509Chain Chain { get; } // X509Chain object representing the certificate chain - - // Constructor initializes with signer certificate and certificate chain - public AllFileSigners(SignedCms signerCertificate, X509Chain certificateChain) - { - Signer = signerCertificate; - Chain = certificateChain; - } + public SignedCms Signer { get; } = signerCertificate; + public X509Chain Chain { get; } = certificateChain; } - // Interop with wintrust.dll for Windows Trust Verification - public static class WinTrust + public static class AllCertificatesGrabber { // Structure defining signer information for cryptographic providers // https://learn.microsoft.com/en-us/windows/win32/api/wintrust/ns-wintrust-crypt_provider_sgnr @@ -79,22 +67,6 @@ public struct CryptProviderData public IntPtr pasSigners; // Pointer to signers } - // Enum defining WinVerifyTrust results - public enum WinVerifyTrustResult : uint - { - Success = 0, // It's Success - SubjectCertificateRevoked = 2148204812, // Subject's certificate was revoked. (CERT_E_REVOKED) - SubjectNotTrusted = 2148204548, // Subject failed the specified verification action - CertExpired = 2148204801, // This is checked for - Signer's certificate was expired. (CERT_E_EXPIRED) - UntrustedRootCert = 2148204809, // A certification chain processed correctly but terminated in a root certificate that is not trusted by the trust provider. (CERT_E_UNTRUSTEDROOT) - HashMismatch = 2148098064, // This is checked for (aka: SignatureOrFileCorrupt) - (TRUST_E_BAD_DIGEST) - ProviderUnknown = 2148204545, // Trust provider is not recognized on this system - ActionUnknown = 2148204546, // Trust provider does not support the specified action - SubjectFormUnknown = 2148204547, // Trust provider does not support the subject's form - FileNotSigned = 2148204800, // File is not signed. (TRUST_E_NOSIGNATURE) - SubjectExplicitlyDistrusted = 2148204817, // Signer's certificate is in the Untrusted Publishers store - } - // Structure defining signature settings for WinTrust [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public class WinTrustSignatureSettings @@ -159,7 +131,7 @@ public class WinTrustData public uint RevocationChecks; // Revocation checks public uint UnionChoice = 1; // Union choice for trust verification public IntPtr FileInfoPtr; // Pointer to file information - public uint StateAction = StateActionVerify; // State action for trust verification + public uint StateAction = WinTrust.StateActionVerify; // State action for trust verification public IntPtr StateData = IntPtr.Zero; // Pointer to state data [MarshalAs(UnmanagedType.LPTStr)] private string? URLReference; // URL reference for trust verification @@ -208,30 +180,11 @@ ref int pcbData ); } - // Constants related to WinTrust - internal const uint StateActionVerify = 1; - internal const uint StateActionClose = 2; - internal static readonly Guid GenericWinTrustVerifyActionGuid = new Guid("{00AAC56B-CD44-11d0-8CC2-00C04FC295EE}"); - - // External method declarations for WinVerifyTrust and WTHelperProvDataFromStateData - [DllImport("wintrust.dll", CharSet = CharSet.Unicode)] - - // https://learn.microsoft.com/en-us/windows/win32/api/wintrust/nf-wintrust-winverifytrust - // Set to return a WinVerifyTrustResult enum - internal static extern WinVerifyTrustResult WinVerifyTrust( - IntPtr hwnd, - [MarshalAs(UnmanagedType.LPStruct)] Guid pgActionID, - IntPtr pWVTData); - - // https://learn.microsoft.com/en-us/windows/win32/api/wintrust/nf-wintrust-wthelperprovdatafromstatedata - [DllImport("wintrust.dll", CharSet = CharSet.Unicode, SetLastError = true)] - internal static extern IntPtr WTHelperProvDataFromStateData(IntPtr hStateData); - // This is the main method used to retrieve all signers for a given file public static List GetAllFileSigners(string FilePath) { // List to hold all file signers - List AllFileSigners = new List(); + List AllFileSigners = []; uint maxSigners = uint.MaxValue; // Maximum number of signers to process, initially set to maximum possible value uint Index = 0; // Index of the current signer being processed @@ -250,9 +203,9 @@ public static List GetAllFileSigners(string FilePath) Marshal.StructureToPtr(TrustedData, winTrustDataPointer, false); // Call WinVerifyTrust to verify trust on the file - WinVerifyTrustResult verifyTrustResult = WinVerifyTrust( + WinTrust.WinVerifyTrustResult verifyTrustResult = WinTrust.WinVerifyTrust( IntPtr.Zero, - GenericWinTrustVerifyActionGuid, + WinTrust.GenericWinTrustVerifyActionGuid, winTrustDataPointer ); @@ -277,13 +230,13 @@ public static List GetAllFileSigners(string FilePath) } // If the certificate is expired, continue to the next iteration - if (verifyTrustResult == WinVerifyTrustResult.CertExpired) + if (verifyTrustResult == WinTrust.WinVerifyTrustResult.CertExpired) { continue; } // if there is a hash mismatch in the file, throw an exception - if (verifyTrustResult == WinVerifyTrustResult.HashMismatch) + if (verifyTrustResult == WinTrust.WinVerifyTrustResult.HashMismatch) { // Throw a custom exception that will be caught by Invoke-WDACPolicySimulation cmdlet throw new ExceptionHashMismatchInCertificate($"WinTrust return code: {verifyTrustResult}", "The file is tampered with and there is a Hash Mismatch."); @@ -293,7 +246,7 @@ public static List GetAllFileSigners(string FilePath) if (TrustedData.StateData != IntPtr.Zero) { // Get provider data from state data - CryptProviderData providerData = Marshal.PtrToStructure(WTHelperProvDataFromStateData(TrustedData.StateData)); + CryptProviderData providerData = Marshal.PtrToStructure(WinTrust.WTHelperProvDataFromStateData(TrustedData.StateData)); int pcbData = 0; // Size of data in bytes @@ -322,17 +275,18 @@ public static List GetAllFileSigners(string FilePath) ) { // Initialize SignedCms object and decode the encoded message - SignedCms signerCertificate = new SignedCms(); + SignedCms signerCertificate = new(); signerCertificate.Decode(numArray); // Initialize X509Chain object based on signer's certificate chain context - X509Chain certificateChain; - // Check if csSigners is less than or equal to 0 if (providerData.csSigners <= 0U) { // If csSigners is 0 or negative, create a new X509Chain without parameters - certificateChain = new X509Chain(); + using X509Chain certificateChain = new(); + + // Add signer's certificate and certificate chain to AllFileSigners list + AllFileSigners.Add(new AllFileSigners(signerCertificate, certificateChain)); } else { @@ -341,27 +295,32 @@ public static List GetAllFileSigners(string FilePath) CryptProviderSigner signer = Marshal.PtrToStructure(providerData.pasSigners); // Initialize X509Chain with the pChainContext from the signer structure - certificateChain = new X509Chain(signer.pChainContext); - } + using X509Chain certificateChain = new(signer.pChainContext); - // Add signer's certificate and certificate chain to AllFileSigners list - AllFileSigners.Add(new AllFileSigners(signerCertificate, certificateChain)); + // Add signer's certificate and certificate chain to AllFileSigners list + AllFileSigners.Add(new AllFileSigners(signerCertificate, certificateChain)); + } } } } } finally { + +#pragma warning disable CA1508 // Avoid dead conditional code + if (TrustedData != null) { // Set StateAction to close the WinTrustData structure - TrustedData.StateAction = StateActionClose; + TrustedData.StateAction = WinTrust.StateActionClose; // Convert TrustedData back to pointer and call WinVerifyTrust to close the structure Marshal.StructureToPtr(TrustedData, winTrustDataPointer, false); - WinVerifyTrust(IntPtr.Zero, GenericWinTrustVerifyActionGuid, winTrustDataPointer); + _ = WinTrust.WinVerifyTrust(IntPtr.Zero, WinTrust.GenericWinTrustVerifyActionGuid, winTrustDataPointer); } +#pragma warning restore CA1508 // Avoid dead conditional code + // Free memory allocated to winTrustDataPointer Marshal.FreeHGlobal(winTrustDataPointer); diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/CIPolicyVersion.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/CIPolicyVersion.cs similarity index 94% rename from WDACConfig/WDACConfig Module Files/C#/Functions/CIPolicyVersion.cs rename to WDACConfig/WDACConfig Module Files/C#/Other Functions/CIPolicyVersion.cs index f1f751322..b959bde57 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/CIPolicyVersion.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Other Functions/CIPolicyVersion.cs @@ -38,7 +38,7 @@ public static string Measure(string number) } catch (Exception ex) { - WDACConfig.VerboseLogger.Write($"Error converting number to version: {ex.Message}"); + WDACConfig.Logger.Write($"Error converting number to version: {ex.Message}"); return number; } } diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/CertificateHelper.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/CertificateHelper.cs similarity index 61% rename from WDACConfig/WDACConfig Module Files/C#/Functions/CertificateHelper.cs rename to WDACConfig/WDACConfig Module Files/C#/Other Functions/CertificateHelper.cs index 06e9795e2..e9ed356d1 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/CertificateHelper.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Other Functions/CertificateHelper.cs @@ -8,12 +8,8 @@ namespace WDACConfig { // A class to throw a custom exception when the certificate collection cannot be obtained during WDAC Simulation - public class ExceptionFailedToGetCertificateCollection : Exception + public class ExceptionFailedToGetCertificateCollection(string message, string functionName) : Exception($"{functionName}: {message}") { - public ExceptionFailedToGetCertificateCollection(string message, string functionName) - : base($"{functionName}: {message}") - { - } } public class CertificateHelper @@ -25,7 +21,7 @@ public static string GetTBSCertificate(X509Certificate2 cert) byte[] rawData = cert.RawData; // Create an ASN.1 reader to parse the certificate - AsnReader asnReader = new AsnReader(rawData, AsnEncodingRules.DER); + AsnReader asnReader = new(rawData, AsnEncodingRules.DER); // Read the certificate sequence AsnReader certificate = asnReader.ReadSequence(); @@ -40,61 +36,31 @@ public static string GetTBSCertificate(X509Certificate2 cert) string algorithmOid = signatureAlgorithm.ReadObjectIdentifier(); // Define a hash function based on the algorithm OID - HashAlgorithm hashFunction; - switch (algorithmOid) + HashAlgorithm hashFunction = algorithmOid switch { - case "1.2.840.113549.1.1.4": - hashFunction = MD5.Create(); - break; - case "1.2.840.113549.1.1.5": - case "1.3.14.3.2.29": // sha-1WithRSAEncryption - hashFunction = SHA1.Create(); - break; - case "1.2.840.113549.1.1.11": - hashFunction = SHA256.Create(); - break; - case "1.2.840.113549.1.1.12": - hashFunction = SHA384.Create(); - break; - case "1.2.840.113549.1.1.13": - hashFunction = SHA512.Create(); - break; - // These are less likely to be used since ConfigCI doesn't support their OIDs - case "1.2.840.10040.4.3": - hashFunction = SHA1.Create(); - break; - case "2.16.840.1.101.3.4.3.2": - hashFunction = SHA256.Create(); - break; - case "2.16.840.1.101.3.4.3.3": - hashFunction = SHA384.Create(); - break; - case "2.16.840.1.101.3.4.3.4": - hashFunction = SHA512.Create(); - break; - case "1.2.840.10045.4.1": - hashFunction = SHA1.Create(); - break; - case "1.2.840.10045.4.3.2": - hashFunction = SHA256.Create(); - break; - case "1.2.840.10045.4.3.3": - hashFunction = SHA384.Create(); - break; - case "1.2.840.10045.4.3.4": - hashFunction = SHA512.Create(); - break; - default: - throw new Exception($"No handler for algorithm {algorithmOid}"); - } - + "1.2.840.113549.1.1.4" => MD5.Create(), + "1.2.840.113549.1.1.5" or "1.3.14.3.2.29" => SHA1.Create(), // sha-1WithRSAEncryption + "1.2.840.113549.1.1.11" => SHA256.Create(), + "1.2.840.113549.1.1.12" => SHA384.Create(), + "1.2.840.113549.1.1.13" => SHA512.Create(), + // These are less likely to be used since Code Integrity doesn't support their OIDs + "1.2.840.10040.4.3" => SHA1.Create(), + "2.16.840.1.101.3.4.3.2" => SHA256.Create(), + "2.16.840.1.101.3.4.3.3" => SHA384.Create(), + "2.16.840.1.101.3.4.3.4" => SHA512.Create(), + "1.2.840.10045.4.1" => SHA1.Create(), + "1.2.840.10045.4.3.2" => SHA256.Create(), + "1.2.840.10045.4.3.3" => SHA384.Create(), + "1.2.840.10045.4.3.4" => SHA512.Create(), + _ => throw new InvalidOperationException($"No handler for algorithm {algorithmOid}"), + }; using (hashFunction) { // Compute the hash of the TBS value using the hash function byte[] hash = hashFunction.ComputeHash(tbsCertificate.ToArray()); // Convert the hash to a hex string - string hexStringOutput = BitConverter.ToString(hash).Replace("-", "", StringComparison.OrdinalIgnoreCase); + string hexStringOutput = Convert.ToHexString(hash); return hexStringOutput; } @@ -130,7 +96,7 @@ public static string ConvertHexToOID(string hex) // CER (Canonical Encoding Rules) is a subset of BER that ensures a unique encoding // DER (Distinguished Encoding Rules) is a subset of CER that ensures a deterministic encoding // The AsnReader object takes the byte array as input and the encoding rule as an argument - AsnReader asnReader = new AsnReader(numArray, AsnEncodingRules.BER); + AsnReader asnReader = new(numArray, AsnEncodingRules.BER); // Read the OID as an ObjectIdentifier // This is a method of the AsnReader class that returns the OID as a string diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/CiPolicyUtility.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/CiPolicyUtility.cs similarity index 73% rename from WDACConfig/WDACConfig Module Files/C#/Functions/CiPolicyUtility.cs rename to WDACConfig/WDACConfig Module Files/C#/Other Functions/CiPolicyUtility.cs index dd41ade49..7dede707a 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/CiPolicyUtility.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Other Functions/CiPolicyUtility.cs @@ -34,43 +34,31 @@ public static void CopyCiRules(string sourceFilePath, string destinationFilePath } // Load the XML files as XmlDocument objects - XmlDocument sourceXmlDoc = new XmlDocument(); + XmlDocument sourceXmlDoc = new(); sourceXmlDoc.Load(sourceFilePath); - XmlDocument destinationXmlDoc = new XmlDocument(); + XmlDocument destinationXmlDoc = new(); destinationXmlDoc.Load(destinationFilePath); // Create an XmlNamespaceManager to handle the default namespace - XmlNamespaceManager nsmgr = new XmlNamespaceManager(sourceXmlDoc.NameTable); + XmlNamespaceManager nsmgr = new(sourceXmlDoc.NameTable); nsmgr.AddNamespace("ns", "urn:schemas-microsoft-com:sipolicy"); // Select the Rules node in the source XML document - XmlNode? sourceRulesNode = sourceXmlDoc.SelectSingleNode("/ns:SiPolicy/ns:Rules", nsmgr); - if (sourceRulesNode == null) - { - throw new Exception("The node was not found in the source XML file."); - } + XmlNode? sourceRulesNode = sourceXmlDoc.SelectSingleNode("/ns:SiPolicy/ns:Rules", nsmgr) ?? throw new InvalidOperationException("The node was not found in the source XML file."); // Select the SiPolicy node in the destination XML document - XmlNode? destinationSiPolicyNode = destinationXmlDoc.SelectSingleNode("/ns:SiPolicy", nsmgr); - if (destinationSiPolicyNode == null) - { - throw new Exception("The node was not found in the destination XML file."); - } + XmlNode? destinationSiPolicyNode = destinationXmlDoc.SelectSingleNode("/ns:SiPolicy", nsmgr) ?? throw new InvalidOperationException("The node was not found in the destination XML file."); // Select the existing Rules node in the destination XML document - XmlNode? destinationRulesNode = destinationSiPolicyNode.SelectSingleNode("ns:Rules", nsmgr); - if (destinationRulesNode == null) - { - throw new Exception("The node was not found in the destination XML file."); - } + XmlNode? destinationRulesNode = destinationSiPolicyNode.SelectSingleNode("ns:Rules", nsmgr) ?? throw new InvalidOperationException("The node was not found in the destination XML file."); // Replace the rules block in destinationXmlDoc with the rules block in sourceXmlDoc // Use the ImportNode method to create a copy of the rules node from $SourceFileContent // The second parameter ($true) indicates a deep clone, meaning that the node and its descendants are copied // https://learn.microsoft.com/en-us/dotnet/api/system.xml.xmldocument.importnode XmlNode importedRulesNode = destinationXmlDoc.ImportNode(sourceRulesNode, true); - destinationSiPolicyNode.ReplaceChild(importedRulesNode, destinationRulesNode); + _ = destinationSiPolicyNode.ReplaceChild(importedRulesNode, destinationRulesNode); destinationXmlDoc.Save(destinationFilePath); } diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/CodeIntegritySigner.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/CodeIntegritySigner.cs new file mode 100644 index 000000000..9b2f914ee --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/C#/Other Functions/CodeIntegritySigner.cs @@ -0,0 +1,68 @@ +using System; +using System.Diagnostics; +using System.IO; + +#nullable enable + +namespace WDACConfig +{ + public static class CodeIntegritySigner + { + /// + /// Invokes SignTool.exe to sign a Code Integrity Policy file. + /// + /// + /// + /// + /// + /// + public static void InvokeCiSigning(FileInfo ciPath, FileInfo signToolPathFinal, string certCN) + { + // Validate inputs + ArgumentNullException.ThrowIfNull(ciPath); + ArgumentNullException.ThrowIfNull(signToolPathFinal); + if (string.IsNullOrEmpty(certCN)) throw new ArgumentException("Certificate Common Name cannot be null or empty.", nameof(certCN)); + + // Build the arguments for the process + string arguments = $"sign /v /n \"{certCN}\" /p7 . /p7co 1.3.6.1.4.1.311.79.1 /fd certHash \"{ciPath.Name}\""; + + // Set up the process start info + ProcessStartInfo startInfo = new() + { + FileName = signToolPathFinal.FullName, + Arguments = arguments, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + WorkingDirectory = ciPath.DirectoryName // Set the working directory so that SignTool.exe will know where the .cip file is and where to save the output + }; + + // Start the process + using Process process = new() { StartInfo = startInfo }; + _ = process.Start(); + + // Wait for the process to exit + process.WaitForExit(); + + // Read the output and error streams + string output = process.StandardOutput.ReadToEnd(); + string error = process.StandardError.ReadToEnd(); + + // Log the output and error + WDACConfig.Logger.Write(output); + + // Check if there is any error and throw an exception if there is + if (!string.IsNullOrEmpty(error)) + { + throw new InvalidOperationException($"SignTool failed with exit code {process.ExitCode}. Error: {error}"); + } + + // Check the exit code + if (process.ExitCode != 0) + { + throw new InvalidOperationException($"SignTool failed with exit code {process.ExitCode}. Error: {error}"); + } + } + } +} diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/Crypt32CertCN.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/Crypt32CertCN.cs similarity index 91% rename from WDACConfig/WDACConfig Module Files/C#/Functions/Crypt32CertCN.cs rename to WDACConfig/WDACConfig Module Files/C#/Other Functions/Crypt32CertCN.cs index b3ab9f49b..e90fb77ef 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/Crypt32CertCN.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Other Functions/Crypt32CertCN.cs @@ -4,13 +4,15 @@ #nullable enable +#pragma warning disable CA1838 // Avoid 'StringBuilder' parameters for P/Invoke methods + namespace WDACConfig { public class CryptoAPI { // Importing function from crypt32.dll to access certificate information // https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certgetnamestringa - [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [DllImport("crypt32.dll", CharSet = CharSet.Unicode, SetLastError = true)] internal static extern bool CertGetNameString( IntPtr pCertContext, // the handle property of the certificate object int dwType, @@ -30,7 +32,7 @@ public static string GetNameString(IntPtr pCertContext, int dwType, string? pvTy { // Allocate a buffer for the name string, setting it big to handle longer names if needed const int bufferSize = 1024; - StringBuilder nameString = new StringBuilder(bufferSize); + StringBuilder nameString = new(bufferSize); // Convert the pvTypePara to a pointer if needed IntPtr pvTypeParaPtr = IntPtr.Zero; diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/DirectorySelector.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/DirectorySelector.cs similarity index 61% rename from WDACConfig/WDACConfig Module Files/C#/Functions/DirectorySelector.cs rename to WDACConfig/WDACConfig Module Files/C#/Other Functions/DirectorySelector.cs index 3890cc297..3f2fb0013 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/DirectorySelector.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Other Functions/DirectorySelector.cs @@ -20,32 +20,30 @@ public static class DirectorySelector public static DirectoryInfo[]? SelectDirectories() { // HashSet to store unique selected directories - HashSet programsPaths = new HashSet(new DirectoryInfoComparer()); + HashSet programsPaths = new(new DirectoryInfoComparer()); do { - using (FolderBrowserDialog dialog = new FolderBrowserDialog()) - { - dialog.Description = "To stop selecting directories, press ESC or select Cancel."; - dialog.ShowNewFolderButton = false; - dialog.RootFolder = Environment.SpecialFolder.MyComputer; + using FolderBrowserDialog dialog = new(); + dialog.Description = "To stop selecting directories, press ESC or select Cancel."; + dialog.ShowNewFolderButton = false; + dialog.RootFolder = Environment.SpecialFolder.MyComputer; - // Use ShowDialog and set top most by using Win32 API - // This method is much better than the ShowDialog overload that takes a parent form - // This makes the opened File/Folder picker top most without the ability to go behind the window that initiated it - // Which is the experience that other native Windows applications have - // Also after picking a directory, the next time the picker GUI opens up will be in the same directory as the last time instead of opening at C drive or some other default location - IntPtr hwnd = GetForegroundWindow(); - DialogResult result = dialog.ShowDialog(new WindowWrapper(hwnd)); + // Use ShowDialog and set top most by using Win32 API + // This method is much better than the ShowDialog overload that takes a parent form + // This makes the opened File/Folder picker top most without the ability to go behind the window that initiated it + // Which is the experience that other native Windows applications have + // Also after picking a directory, the next time the picker GUI opens up will be in the same directory as the last time instead of opening at C drive or some other default location + IntPtr hwnd = GetForegroundWindow(); + DialogResult result = dialog.ShowDialog(new WindowWrapper(hwnd)); - if (result == DialogResult.OK) - { - programsPaths.Add(new DirectoryInfo(dialog.SelectedPath)); - } - else - { - break; - } + if (result == DialogResult.OK) + { + _ = programsPaths.Add(new DirectoryInfo(dialog.SelectedPath)); + } + else + { + break; } } while (true); @@ -89,13 +87,9 @@ public int GetHashCode(DirectoryInfo obj) private static extern IntPtr GetForegroundWindow(); // Wrapper class to satisfy IWin32Window interface - public class WindowWrapper : IWin32Window + public class WindowWrapper(IntPtr handle) : IWin32Window { - private IntPtr _hwnd; - public WindowWrapper(IntPtr handle) - { - _hwnd = handle; - } + private IntPtr _hwnd = handle; // Property to satisfy IWin32Window interface public IntPtr Handle => _hwnd; diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/DriveLetterMapper.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/DriveLetterMapper.cs similarity index 91% rename from WDACConfig/WDACConfig Module Files/C#/Functions/DriveLetterMapper.cs rename to WDACConfig/WDACConfig Module Files/C#/Other Functions/DriveLetterMapper.cs index 62e31bac1..8a8349140 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/DriveLetterMapper.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Other Functions/DriveLetterMapper.cs @@ -5,6 +5,8 @@ #nullable enable +#pragma warning disable CA1838 // Avoid 'StringBuilder' parameters for P/Invoke methods + namespace WDACConfig { public static class DriveLetterMapper @@ -19,20 +21,20 @@ private static extern bool GetVolumePathNamesForVolumeNameW( ref UInt32 lpcchReturnLength); // Importing the FindFirstVolume function from kernel32.dll - [DllImport("kernel32.dll", SetLastError = true)] + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern IntPtr FindFirstVolume( [Out] StringBuilder lpszVolumeName, uint cchBufferLength); // Importing the FindNextVolume function from kernel32.dll - [DllImport("kernel32.dll", SetLastError = true)] + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern bool FindNextVolume( IntPtr hFindVolume, [Out] StringBuilder lpszVolumeName, uint cchBufferLength); // Importing the QueryDosDevice function from kernel32.dll - [DllImport("kernel32.dll", SetLastError = true)] + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern uint QueryDosDevice( string lpDeviceName, StringBuilder lpTargetPath, @@ -85,7 +87,7 @@ public static List GetGlobalRootDrives() // Convert the volume name to a string string volume = sbVolumeName.ToString(); // Get the mount point for the volume - bool hasMountPoint = GetVolumePathNamesForVolumeNameW(volume, sbMountPoint, max, ref lpcchReturnLength); + _ = GetVolumePathNamesForVolumeNameW(volume, sbMountPoint, max, ref lpcchReturnLength); // Get the device path for the volume uint returnLength = QueryDosDevice(volume.Substring(4, volume.Length - 5), sbPathName, (int)max); diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/DriversBlockRulesFetcher.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/DriversBlockRulesFetcher.cs similarity index 91% rename from WDACConfig/WDACConfig Module Files/C#/Functions/DriversBlockRulesFetcher.cs rename to WDACConfig/WDACConfig Module Files/C#/Other Functions/DriversBlockRulesFetcher.cs index a887b23a3..cbe56fdcd 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/DriversBlockRulesFetcher.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Other Functions/DriversBlockRulesFetcher.cs @@ -1,7 +1,6 @@ -using System.Collections.Generic; -using System.Net.Http; +using System; using System.IO; -using System; +using System.Net.Http; #nullable enable @@ -30,8 +29,7 @@ public static void Fetch(string StagingArea) string? systemDrive = Environment.GetEnvironmentVariable("SystemDrive"); // Initialize the final destination of the SiPolicy file - string SiPolicyFinalDestination = string.Empty; - + string SiPolicyFinalDestination; if (systemDrive != null) { // Construct the final destination of the SiPolicy file @@ -39,11 +37,11 @@ public static void Fetch(string StagingArea) } else { - throw new Exception("SystemDrive environment variable is null"); + throw new InvalidOperationException("SystemDrive environment variable is null"); } // Download the zip file - using (HttpClient client = new HttpClient()) + using (HttpClient client = new()) { // Download the file synchronously byte[] fileBytes = client.GetByteArrayAsync(DriversBlockListZipDownloadLink).GetAwaiter().GetResult(); diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/EditGUIDs.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/EditGUIDs.cs similarity index 90% rename from WDACConfig/WDACConfig Module Files/C#/Functions/EditGUIDs.cs rename to WDACConfig/WDACConfig Module Files/C#/Other Functions/EditGUIDs.cs index 4c5c93a7f..0d1475c31 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/EditGUIDs.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Other Functions/EditGUIDs.cs @@ -14,7 +14,7 @@ public static void EditGuids(string policyIdInput, FileInfo policyFilePathInput) string policyId = "{" + policyIdInput + "}"; // Load the XML document - XmlDocument xmlDoc = new XmlDocument(); + XmlDocument xmlDoc = new(); xmlDoc.Load(policyFilePathInput.FullName); // Define the new values for PolicyID and BasePolicyID @@ -22,7 +22,7 @@ public static void EditGuids(string policyIdInput, FileInfo policyFilePathInput) string newBasePolicyId = policyId; // Select the nodes and update their values - XmlNamespaceManager nsMgr = new XmlNamespaceManager(xmlDoc.NameTable); + XmlNamespaceManager nsMgr = new(xmlDoc.NameTable); nsMgr.AddNamespace("ns", "urn:schemas-microsoft-com:sipolicy"); XmlNode? policyIdNode = xmlDoc.SelectSingleNode("/ns:SiPolicy/ns:PolicyID", nsMgr); diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/EventLogUtility.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/EventLogUtility.cs new file mode 100644 index 000000000..ea9d82081 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/C#/Other Functions/EventLogUtility.cs @@ -0,0 +1,60 @@ +using System; +using System.Diagnostics.Eventing.Reader; +using System.IO; + +#nullable enable + +namespace WDACConfig +{ + public static class EventLogUtility + { + /// + /// Increase Code Integrity Operational Event Logs size from the default 1MB to user-defined size. + /// Also automatically increases the log size by 1MB if the current free space is less than 1MB and the current maximum log size is less than or equal to 10MB. + /// This is to prevent infinitely expanding the max log size automatically. + /// + /// Size of the Code Integrity Operational Event Log + public static void SetLogSize(ulong logSize = 0) + { + WDACConfig.Logger.Write("Set-SetLogSize method started..."); + + string logName = "Microsoft-Windows-CodeIntegrity/Operational"; + + using var logConfig = new EventLogConfiguration(logName); + string logFilePath = Environment.ExpandEnvironmentVariables(logConfig.LogFilePath); + FileInfo logFileInfo = new(logFilePath); + long currentLogFileSize = logFileInfo.Length; + long currentLogMaxSize = logConfig.MaximumSizeInBytes; + + if (logSize == 0) + { + if ((currentLogMaxSize - currentLogFileSize) < 1 * 1024 * 1024) + { + if (currentLogMaxSize <= 10 * 1024 * 1024) + { + WDACConfig.Logger.Write("Increasing the Code Integrity log size by 1MB because its current free space is less than 1MB."); + logConfig.MaximumSizeInBytes = currentLogMaxSize + 1 * 1024 * 1024; + logConfig.IsEnabled = true; + logConfig.SaveChanges(); + } + } + } + else + { + // Check if the provided log size is greater than 1100 KB + // To prevent from disabling the log or setting it to a very small size that is lower than its default size + if (logSize > 1100 * 1024) + { + WDACConfig.Logger.Write($"Setting Code Integrity log size to {logSize}."); + logConfig.MaximumSizeInBytes = (long)logSize; + logConfig.IsEnabled = true; + logConfig.SaveChanges(); + } + else + { + WDACConfig.Logger.Write("Provided log size is less than or equal to 1100 KB. No changes made."); + } + } + } + } +} diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/FileDirectoryPathComparer.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/FileDirectoryPathComparer.cs similarity index 94% rename from WDACConfig/WDACConfig Module Files/C#/Functions/FileDirectoryPathComparer.cs rename to WDACConfig/WDACConfig Module Files/C#/Other Functions/FileDirectoryPathComparer.cs index 093ba4047..a3db9579d 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/FileDirectoryPathComparer.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Other Functions/FileDirectoryPathComparer.cs @@ -18,7 +18,7 @@ public class FileDirectoryPathComparer /// List of unique file paths that don't reside in any of the directory paths (or their sub-directory paths) public static List TestFilePath(string[] directoryPaths, string[] filePaths) { - HashSet output = new HashSet(StringComparer.OrdinalIgnoreCase); + HashSet output = new(StringComparer.OrdinalIgnoreCase); // Loop through each file path foreach (string file in filePaths) @@ -40,7 +40,7 @@ public static List TestFilePath(string[] directoryPaths, string[] filePa // Output the file path if it is not inside any of the directory paths if (!isInDirectory) { - output.Add(file); + _ = output.Add(file); } } diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/GetExtendedFileAttrib.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/GetExtendedFileAttrib.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Functions/GetExtendedFileAttrib.cs rename to WDACConfig/WDACConfig Module Files/C#/Other Functions/GetExtendedFileAttrib.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/GetFilesFast.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/GetFilesFast.cs similarity index 89% rename from WDACConfig/WDACConfig Module Files/C#/Functions/GetFilesFast.cs rename to WDACConfig/WDACConfig Module Files/C#/Other Functions/GetFilesFast.cs index 00a69089b..1fb43c0bb 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/GetFilesFast.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Other Functions/GetFilesFast.cs @@ -18,12 +18,12 @@ public class FileUtility /// List of FileInfo objects. public static List GetFilesFast( DirectoryInfo[] directories, - FileInfo[] files, + FileInfo[]? files, string[] extensionsToFilterBy) { // Use the Default WDAC supported extensions and make them case-insensitive - HashSet extensions = new HashSet(StringComparer.InvariantCultureIgnoreCase) + HashSet extensions = new(StringComparer.InvariantCultureIgnoreCase) { ".sys", ".exe", ".com", ".dll", ".rll", ".ocx", ".msp", ".mst", ".msi", ".js", ".vbs", ".ps1", ".appx", ".bin", ".bat", ".hxs", ".mui", ".lex", ".mof" @@ -36,9 +36,9 @@ public static List GetFilesFast( } // Define a HashSet to store the final output - HashSet output = new HashSet(); + HashSet output = []; - EnumerationOptions options = new EnumerationOptions + EnumerationOptions options = new() { IgnoreInaccessible = true, RecurseSubdirectories = true, @@ -65,7 +65,7 @@ public static List GetFilesFast( // Check if the file extension is in the Extensions HashSet or Wildcard was used if (extensions.Contains(enumerator.Current.Extension) || extensions.Contains("*")) { - output.Add(enumerator.Current); + _ = output.Add(enumerator.Current); } } catch { } @@ -80,7 +80,7 @@ public static List GetFilesFast( { if (extensions.Contains(file.Extension)) { - output.Add(file); + _ = output.Add(file); } } } diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/GetOpusData.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/GetOpusData.cs similarity index 98% rename from WDACConfig/WDACConfig Module Files/C#/Functions/GetOpusData.cs rename to WDACConfig/WDACConfig Module Files/C#/Other Functions/GetOpusData.cs index 9b21e49fd..00b0279e1 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/GetOpusData.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Other Functions/GetOpusData.cs @@ -48,7 +48,7 @@ public struct OpusInfoObj public static List GetOpusData(SignedCms signature) { // Initializing a new List of OpusInfoObj to store the output data to return - List OEMOpusData = new List(); + List OEMOpusData = []; // Iterating through each SignerInfo in the SignerInfos collection of the signature foreach (SignerInfo signerInfo in signature.SignerInfos) @@ -86,7 +86,7 @@ public struct OpusInfoObj else { // Converting the unmanaged memory block to OpusInfoObj structure - Opus.OpusInfoObj structure = (Opus.OpusInfoObj)Marshal.PtrToStructure(decodedDataPtr, typeof(Opus.OpusInfoObj))!; + Opus.OpusInfoObj structure = Marshal.PtrToStructure(decodedDataPtr)!; // Adding the structure to OEMOpusData list OEMOpusData.Add(structure); } diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/Initializer.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/Initializer.cs similarity index 96% rename from WDACConfig/WDACConfig Module Files/C#/Functions/Initializer.cs rename to WDACConfig/WDACConfig Module Files/C#/Other Functions/Initializer.cs index 93651fa8a..6671759fb 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/Initializer.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Other Functions/Initializer.cs @@ -12,7 +12,7 @@ public class Initializer /// These are the codes that were present in the GlobalVar class's default constructor but defining them as a separate method allows any errors thrown in them to be properly displayed in PowerShell instead of showing an error occurred in the default constructor of a class public static void Initialize() { - using (RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion") ?? throw new Exception("Could not get the current Windows version from the registry")) + using (RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion") ?? throw new InvalidOperationException("Could not get the current Windows version from the registry")) { object? ubrValue = key.GetValue("UBR"); if (ubrValue != null && int.TryParse(ubrValue.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out int ubr)) diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/MeowOpener.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/MeowOpener.cs similarity index 63% rename from WDACConfig/WDACConfig Module Files/C#/Functions/MeowOpener.cs rename to WDACConfig/WDACConfig Module Files/C#/Other Functions/MeowOpener.cs index f774a7331..3eb4052e5 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/MeowOpener.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Other Functions/MeowOpener.cs @@ -15,8 +15,8 @@ public static class MeowParser [DllImport("AdvApi32.dll", CharSet = CharSet.Auto, SetLastError = true)] internal static extern bool CryptAcquireContext( out IntPtr MainCryptProviderHandle, // Output parameter to receive the handle of the cryptographic service provider. - [MarshalAs(UnmanagedType.LPTStr)] string Container, // The name of the key container within the cryptographic service provider. - [MarshalAs(UnmanagedType.LPTStr)] string Provider, // The name of the cryptographic service provider. + [MarshalAs(UnmanagedType.LPWStr)] string Container, // The name of the key container within the cryptographic service provider. + [MarshalAs(UnmanagedType.LPWStr)] string Provider, // The name of the cryptographic service provider. uint ProviderType, // The type of provider to acquire. uint Flags); // Flags to control the function behavior. @@ -25,28 +25,6 @@ internal static extern bool CryptAcquireContext( [DllImport("AdvApi32.dll", CharSet = CharSet.Auto, SetLastError = true)] internal static extern bool CryptReleaseContext(IntPtr MainCryptProviderHandle, uint Flags); // Releases the handle acquired by 'CryptAcquireContext'. - // P/Invoke declaration to import the 'CryptCATOpen' function from 'WinTrust.dll'. - // https://learn.microsoft.com/en-us/windows/win32/api/mscat/nf-mscat-cryptcatopen - [DllImport("WinTrust.dll", CharSet = CharSet.Auto, SetLastError = true)] - internal static extern IntPtr CryptCATOpen( - [MarshalAs(UnmanagedType.LPWStr)] string FileName, // The name of the catalog file. - uint OpenFlags, // Flags to control the function behavior. - IntPtr MainCryptProviderHandle, // Handle to the cryptographic service provider. - uint PublicVersion, // The public version number. - uint EncodingType); // The encoding type. - - // P/Invoke declaration to import the 'CryptCATEnumerateMember' function from 'WinTrust.dll'. - // https://learn.microsoft.com/en-us/windows/win32/api/mscat/nf-mscat-cryptcatenumeratemember - [DllImport("WinTrust.dll", CharSet = CharSet.Auto, SetLastError = true)] - internal static extern IntPtr CryptCATEnumerateMember( - IntPtr MeowLogHandle, // Handle to the catalog context. - IntPtr PrevCatalogMember); // Pointer to the previous catalog member. - - // P/Invoke declaration to import the 'CryptCATClose' function from 'WinTrust.dll'. - // https://learn.microsoft.com/en-us/windows/win32/api/mscat/nf-mscat-cryptcatclose - [DllImport("WinTrust.dll", CharSet = CharSet.Auto, SetLastError = true)] - internal static extern IntPtr CryptCATClose(IntPtr MainCryptProviderHandle); // Closes the catalog context. - // Defines a structure with sequential layout to match the native structure. // https://learn.microsoft.com/en-us/windows/win32/api/mscat/ns-mscat-cryptcatmember [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] @@ -68,13 +46,16 @@ internal struct MeowMemberCrypt // A public static method that returns a HashSet of strings. public static HashSet GetHashes(string SecurityCatalogFilePath) { - HashSet OutputHashSet = new HashSet(); // Initializes a new HashSet to store the hashes. + HashSet OutputHashSet = []; // Initializes a new HashSet to store the hashes. + + // Creates a new XmlDocument instance. + XmlDocument PurrfectCatalogXMLDoc = new() + { + XmlResolver = null // Disables the XML resolver for security reasons. + }; - XmlDocument PurrfectCatalogXMLDoc = new XmlDocument(); // Creates a new XmlDocument instance. - PurrfectCatalogXMLDoc.XmlResolver = null; // Disables the XML resolver for security reasons. IntPtr MainCryptProviderHandle = IntPtr.Zero; // Initializes the handle to zero. IntPtr MeowLogHandle = IntPtr.Zero; // Initializes the catalog context handle to zero. - IntPtr CharmPointer = IntPtr.Zero; // Unused pointer, initialized to zero. IntPtr KittyPointer = IntPtr.Zero; // Pointer to iterate through catalog members, initialized to zero. try @@ -87,7 +68,7 @@ public static HashSet GetHashes(string SecurityCatalogFilePath) } // Opens the catalog file and gets a handle to the catalog context. - MeowLogHandle = CryptCATOpen(SecurityCatalogFilePath, 0, MainCryptProviderHandle, 0, 0); + MeowLogHandle = WinTrust.CryptCATOpen(SecurityCatalogFilePath, 0, MainCryptProviderHandle, 0, 0); if (MeowLogHandle == IntPtr.Zero) { // If the handle is not obtained, the error can be captured (commented out). @@ -96,24 +77,24 @@ public static HashSet GetHashes(string SecurityCatalogFilePath) // Creates an XML element to represent the catalog file. XmlElement catalogElement = PurrfectCatalogXMLDoc.CreateElement("MeowFile"); - PurrfectCatalogXMLDoc.AppendChild(catalogElement); // Appends the element to the XML document. + _ = PurrfectCatalogXMLDoc.AppendChild(catalogElement); // Appends the element to the XML document. // Iterates through the catalog members. - while ((KittyPointer = CryptCATEnumerateMember(MeowLogHandle, KittyPointer)) != IntPtr.Zero) + while ((KittyPointer = WinTrust.CryptCATEnumerateMember(MeowLogHandle, KittyPointer)) != IntPtr.Zero) { // Converts the pointer to a structure. MeowMemberCrypt member = Marshal.PtrToStructure(KittyPointer); - OutputHashSet.Add(member.Hashes); // Adds the hashes to the HashSet. + _ = OutputHashSet.Add(member.Hashes); // Adds the hashes to the HashSet. } } finally { // Releases the cryptographic context and closes the catalog context in the finally block to ensure resources are freed. if (MainCryptProviderHandle != IntPtr.Zero) - CryptReleaseContext(MainCryptProviderHandle, 0); + _ = CryptReleaseContext(MainCryptProviderHandle, 0); if (MeowLogHandle != IntPtr.Zero) - CryptCATClose(MeowLogHandle); + _ = WinTrust.CryptCATClose(MeowLogHandle); } return OutputHashSet; // Returns the HashSet containing the hashes. } diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/MoveUserModeToKernelMode.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/MoveUserModeToKernelMode.cs similarity index 93% rename from WDACConfig/WDACConfig Module Files/C#/Functions/MoveUserModeToKernelMode.cs rename to WDACConfig/WDACConfig Module Files/C#/Other Functions/MoveUserModeToKernelMode.cs index 76947e876..141a53ea9 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/MoveUserModeToKernelMode.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Other Functions/MoveUserModeToKernelMode.cs @@ -26,7 +26,7 @@ public static void Move(string filePath) xml.Load(filePath); // Create an XmlNameSpaceManager object - XmlNamespaceManager nsManager = new XmlNamespaceManager(xml.NameTable); + XmlNamespaceManager nsManager = new(xml.NameTable); // Define namespace nsManager.AddNamespace("sip", "urn:schemas-microsoft-com:sipolicy"); @@ -90,7 +90,7 @@ public static void Move(string filePath) newSignerIdAttr.Value = allowedSigner.Attributes["SignerId"]!.Value; // Append the new SignerId attribute to the new AllowedSigner node - newAllowedSigner.Attributes!.Append(newSignerIdAttr); + _ = newAllowedSigner.Attributes!.Append(newSignerIdAttr); // Find the AllowedSigners node in SigningScenario 131 XmlNode? allowedSigners131 = signingScenario131.SelectSingleNode("./sip:ProductSigners/sip:AllowedSigners", nsManager); @@ -99,13 +99,13 @@ public static void Move(string filePath) if (allowedSigners131 != null) { // Append the new AllowedSigner node to the AllowedSigners node in SigningScenario 131 - allowedSigners131.AppendChild(newAllowedSigner); + _ = allowedSigners131.AppendChild(newAllowedSigner); } } } // Remove SigningScenario with Value 12 completely after moving all of its AllowedSigners to SigningScenario with the value of 131 - signingScenario12.ParentNode?.RemoveChild(signingScenario12); + _ = (signingScenario12.ParentNode?.RemoveChild(signingScenario12)); } } @@ -114,7 +114,7 @@ public static void Move(string filePath) } catch (Exception ex) { - throw new Exception($"An error occurred: {ex.Message}", ex); + throw new InvalidOperationException($"An error occurred: {ex.Message}", ex); } } } diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/PageHashCalc.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/PageHashCalc.cs similarity index 68% rename from WDACConfig/WDACConfig Module Files/C#/Functions/PageHashCalc.cs rename to WDACConfig/WDACConfig Module Files/C#/Other Functions/PageHashCalc.cs index b86829419..5d5e6c1c6 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/PageHashCalc.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Other Functions/PageHashCalc.cs @@ -12,14 +12,6 @@ namespace WDACConfig /// public static class PageHashCalculator { - // a method to compute the hash of the first page of a file using a native function from Wintrust.dll - [DllImport("Wintrust.dll", CharSet = CharSet.Unicode)] // an attribute to specify the DLL name and the character set - internal static extern int ComputeFirstPageHash( // the method signature - string pszAlgId, // the first parameter: the name of the hash algorithm to use - string filename, // the second parameter: the name of the file to hash - IntPtr buffer, // the third parameter: a pointer to a buffer to store the hash value - int bufferSize // the fourth parameter: the size of the buffer in bytes - ); // a method to get the hash of the first page of a file as a hexadecimal string public static string? GetPageHash(string algName, string fileName) // the method signature @@ -31,12 +23,12 @@ internal static extern int ComputeFirstPageHash( // the method signature int bufferSize = 0; // create a string builder to append the hash value - StringBuilder stringBuilder = new StringBuilder(62); + StringBuilder stringBuilder = new(62); try { // call the native function with the given parameters and store the return value - int firstPageHash1 = ComputeFirstPageHash(algName, fileName, buffer, bufferSize); + int firstPageHash1 = WinTrust.ComputeFirstPageHash(algName, fileName, buffer, bufferSize); // if the return value is zero, it means the function failed if (firstPageHash1 == 0) @@ -49,7 +41,7 @@ internal static extern int ComputeFirstPageHash( // the method signature buffer = Marshal.AllocHGlobal(firstPageHash1); // call the native function again with the same parameters and the allocated buffer - int firstPageHash2 = ComputeFirstPageHash(algName, fileName, buffer, firstPageHash1); + int firstPageHash2 = WinTrust.ComputeFirstPageHash(algName, fileName, buffer, firstPageHash1); // if the return value is zero, it means the function failed if (firstPageHash2 == 0) @@ -62,7 +54,7 @@ internal static extern int ComputeFirstPageHash( // the method signature for (int ofs = 0; ofs < firstPageHash2; ++ofs) // read each byte, convert it to a hexadecimal string, and append it to the string builder - stringBuilder.Append(Marshal.ReadByte(buffer, ofs).ToString("X2", CultureInfo.InvariantCulture)); + _ = stringBuilder.Append(Marshal.ReadByte(buffer, ofs).ToString("X2", CultureInfo.InvariantCulture)); // return the final string return stringBuilder.ToString(); diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/RemoveSupplementalSigners.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/RemoveSupplementalSigners.cs new file mode 100644 index 000000000..d319c14d8 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/C#/Other Functions/RemoveSupplementalSigners.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Xml; + +#nullable enable + +namespace WDACConfig +{ + public class CiPolicyHandler + { + /// + /// Removes the entire SupplementalPolicySigners block + /// and any Signer in Signers node that have the same ID as the SignerIds of the SupplementalPolicySigner(s) in ... node + /// from a CI policy XML file + /// + /// It doesn't do anything if the input policy file has no SupplementalPolicySigners block. + /// It will also always check if the Signers node is not empty, like + /// + /// + /// if it is then it will close it: + /// The function can run infinite number of times on the same file. + /// + /// The path to the CI policy XML file + /// + public static void RemoveSupplementalSigners(string path) + { + + // Validate input XML file compliance with CI policy schema + if (WDACConfig.CiPolicyTest.TestCiPolicy(path, "") is not true) + { + throw new InvalidOperationException("The input XML file is not compliant with the CI policy schema"); + } + + // Load XML document + XmlDocument xmlDoc = new(); + xmlDoc.Load(path); + + // Create namespace manager and add the default namespace with a prefix + XmlNamespaceManager namespaceManager = new(xmlDoc.NameTable); + namespaceManager.AddNamespace("ns", "urn:schemas-microsoft-com:sipolicy"); + + // Get SiPolicy node + XmlNode? siPolicyNode = xmlDoc.SelectSingleNode("//ns:SiPolicy", namespaceManager) ?? throw new InvalidOperationException("Invalid XML structure, SiPolicy node not found"); + + // Check if SupplementalPolicySigners exists and has child nodes + XmlNodeList? supplementalPolicySignersNodes = siPolicyNode.SelectNodes("ns:SupplementalPolicySigners", namespaceManager); + + if (supplementalPolicySignersNodes is not null && supplementalPolicySignersNodes.Count > 0) + { + Console.WriteLine("Removing the SupplementalPolicySigners blocks and corresponding Signers"); + + // Store SignerIds to remove + var signerIds = new HashSet(); + + // Loop through each SupplementalPolicySigners node + foreach (XmlNode supplementalPolicySignersNode in supplementalPolicySignersNodes) + { + var supplementalPolicySigners = supplementalPolicySignersNode.SelectNodes("ns:SupplementalPolicySigner", namespaceManager); + + // Get unique SignerIds + foreach (XmlElement node in supplementalPolicySigners!) + { + _ = signerIds.Add(node.GetAttribute("SignerId")); + } + + // Remove the entire SupplementalPolicySigners node + _ = siPolicyNode.RemoveChild(supplementalPolicySignersNode); + } + + // Remove corresponding Signers + foreach (var signerId in signerIds) + { + XmlNodeList? signersToRemove = siPolicyNode.SelectNodes($"ns:Signers/ns:Signer[@ID='{signerId}']", namespaceManager); + if (signersToRemove != null) + { + foreach (XmlNode signerNode in signersToRemove) + { + _ = siPolicyNode.SelectSingleNode("ns:Signers", namespaceManager)?.RemoveChild(signerNode); + } + } + } + } + + + // Check if the Signers node is empty, if so, close it properly + XmlNode? signersNode = siPolicyNode.SelectSingleNode("ns:Signers", namespaceManager); + + if (signersNode is not null && !signersNode.HasChildNodes) + { + // Create a new self-closing element with the same name and attributes as the old one + XmlElement newSignersNode = xmlDoc.CreateElement("Signers", "urn:schemas-microsoft-com:sipolicy"); + + if (signersNode.Attributes is not null) + { + + foreach (XmlAttribute attribute in signersNode.Attributes) + { + newSignersNode.SetAttribute(attribute.Name, attribute.Value); + } + + _ = siPolicyNode.ReplaceChild(newSignersNode, signersNode); + } + } + + // Save the updated XML content back to the file + xmlDoc.Save(path); + } + } +} diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/SecureStringComparer.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/SecureStringComparer.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Functions/SecureStringComparer.cs rename to WDACConfig/WDACConfig Module Files/C#/Other Functions/SecureStringComparer.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/SnapBackGuarantee.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/SnapBackGuarantee.cs new file mode 100644 index 000000000..7ac12bba9 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/C#/Other Functions/SnapBackGuarantee.cs @@ -0,0 +1,178 @@ +using System; +using System.IO; +using System.Management; + +#nullable enable + +namespace WDACConfig +{ + public class SnapBackGuarantee + { + + /// + /// A method 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. + /// + /// The path to the EnforcedMode.cip file that will be used to revert the base policy to enforced mode in case of a reboot. + /// + public static void New(string path) + { + + Logger.Write("Creating the scheduled task for Snap Back Guarantee"); + + // Initialize ManagementScope to interact with Task Scheduler's WMI namespace + var scope = new ManagementScope(@"root\Microsoft\Windows\TaskScheduler"); + // Establish connection to the WMI namespace + scope.Connect(); + + #region Action + // Creating a scheduled task action + using ManagementClass actionClass = new(scope, new ManagementPath("PS_ScheduledTask"), null); + + // Prepare method parameters for creating the task action + var actionInParams = actionClass.GetMethodParameters("NewActionByExec"); + actionInParams["Execute"] = "cmd.exe"; + + // The PowerShell command to run, downloading and deploying the drivers block list + actionInParams["Argument"] = $"/c \"{GlobalVars.UserConfigDir}\\EnforcedModeSnapBack.cmd\""; + + // Execute the WMI method to create the action + ManagementBaseObject actionResult = actionClass.InvokeMethod("NewActionByExec", actionInParams, null); + + // Check if the action was created successfully + if ((uint)actionResult["ReturnValue"] != 0) + { + throw new InvalidOperationException($"Failed to create task action: {((uint)actionResult["ReturnValue"])}"); + } + + // Extract CIM instance for further use in task registration + ManagementBaseObject actionCimInstance = (ManagementBaseObject)actionResult["cmdletOutput"]; + + #endregion + + + #region Principal + // Create a scheduled task principal and assign the SYSTEM account's SID to it so that the task will run under its context + using ManagementClass principalClass = new(scope, new ManagementPath("PS_ScheduledTask"), null); + + // Prepare method parameters to set up the principal (user context) + ManagementBaseObject principalInParams = principalClass.GetMethodParameters("NewPrincipalByUser"); + principalInParams["UserId"] = "S-1-5-18"; // SYSTEM SID (runs with the highest system privileges) + principalInParams["LogonType"] = 2; // S4U logon type, allows the task to run without storing credentials + principalInParams["RunLevel"] = 1; // Highest run level, ensuring the task runs with elevated privileges + + // Execute the WMI method to create the principal + ManagementBaseObject principalResult = principalClass.InvokeMethod("NewPrincipalByUser", principalInParams, null); + + // Check if the principal was created successfully + if ((uint)principalResult["ReturnValue"] != 0) + { + throw new InvalidOperationException($"Failed to create task principal: {((uint)principalResult["ReturnValue"])}"); + } + + // Extract CIM instance for further use in task registration + ManagementBaseObject principalCimInstance = (ManagementBaseObject)principalResult["cmdletOutput"]; + #endregion + + + #region Trigger + // Create a trigger for the scheduled task + using ManagementClass triggerClass = new(scope, new ManagementPath("PS_ScheduledTask"), null); + + ManagementBaseObject triggerInParams = triggerClass.GetMethodParameters("NewTriggerByLogon"); + triggerInParams["AtLogOn"] = true; + + // Execute the WMI method to create the trigger + ManagementBaseObject triggerResult = triggerClass.InvokeMethod("NewTriggerByLogon", triggerInParams, null); + + // Check if the trigger was created successfully + if ((uint)triggerResult["ReturnValue"] != 0) + { + throw new InvalidOperationException($"Failed to create task trigger: {((uint)triggerResult["ReturnValue"])}"); + } + + // Extract CIM instance for further use in task registration + var triggerCimInstance = (ManagementBaseObject)triggerResult["cmdletOutput"]; + #endregion + + + #region Settings + // Define advanced settings for the scheduled task + using ManagementClass settingsClass = new(scope, new ManagementPath("PS_ScheduledTask"), null); + + // Prepare method parameters to define advanced settings for the task + ManagementBaseObject settingsInParams = settingsClass.GetMethodParameters("NewSettings"); + settingsInParams["AllowStartIfOnBatteries"] = true; // Allow the task to start if the system is on battery + settingsInParams["DontStopIfGoingOnBatteries"] = true; // Ensure the task isn't stopped if the system switches to battery power + settingsInParams["Compatibility"] = 4; + // 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. + settingsInParams["Priority"] = 0; + settingsInParams["Hidden"] = true; + settingsInParams["RestartCount"] = 2; // Number of allowed task restarts on failure + settingsInParams["RestartInterval"] = ManagementDateTimeConverter.ToDmtfTimeInterval(TimeSpan.FromMinutes(3)); // Wait 3 minutes between restarts (converted to DMTF format) + + // Execute the WMI method to set the task's advanced settings + ManagementBaseObject settingsResult = settingsClass.InvokeMethod("NewSettings", settingsInParams, null); + if ((uint)settingsResult["ReturnValue"] != 0) + { + throw new InvalidOperationException($"Failed to define task settings: {((uint)settingsResult["ReturnValue"])}"); + } + + // Extract CIM instance for further use in task registration + ManagementBaseObject settingsCimInstance = (ManagementBaseObject)settingsResult["cmdletOutput"]; + #endregion + + + #region Register Task + // Register the scheduled task. If the task's state is disabled, it will be overwritten with a new task that is enabled + using ManagementClass registerClass = new(scope, new ManagementPath("PS_ScheduledTask"), null); + + // Prepare method parameters to register the task + ManagementBaseObject registerInParams = registerClass.GetMethodParameters("RegisterByPrincipal"); + registerInParams["Force"] = true; // Overwrite any existing task with the same name + registerInParams["Principal"] = principalCimInstance; + registerInParams["Action"] = new ManagementBaseObject[] { actionCimInstance }; + registerInParams["Trigger"] = new ManagementBaseObject[] { triggerCimInstance }; + registerInParams["Settings"] = settingsCimInstance; + registerInParams["TaskName"] = "EnforcedModeSnapBack"; + + // Execute the WMI method to register the task + var registerResult = registerClass.InvokeMethod("RegisterByPrincipal", registerInParams, null); + + // Check if the task was registered successfully + if ((uint)registerResult["ReturnValue"] != 0) + { + throw new InvalidOperationException($"Failed to register the task: {((uint)registerResult["ReturnValue"])}"); + } + #endregion + + Logger.Write("Successfully created the Microsoft Recommended Driver Block Rules auto updater scheduled task."); + + + + // Saving the EnforcedModeSnapBack.cmd file to the UserConfig directory in Program Files + // It contains the instructions to revert the base policy to enforced mode + + string savePath = Path.Combine(GlobalVars.UserConfigDir, "EnforcedModeSnapBack.cmd"); + + string contentToBeSaved = $@" +REM Deploying the Enforced Mode SnapBack CI Policy +CiTool --update-policy ""{path}"" -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 ""{path}"" +REM Deleting this CMD file itself +del ""%~f0"" +"; + + + // Write to file (overwrite if exists) + File.WriteAllText(savePath, contentToBeSaved); + + + // An alternative way to do this which is less reliable because RunOnce key can be deleted by 3rd party programs during installation etc. + + } + } +} diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/StagingArea.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/StagingArea.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Functions/StagingArea.cs rename to WDACConfig/WDACConfig Module Files/C#/Other Functions/StagingArea.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/VersionIncrementer.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/VersionIncrementer.cs similarity index 89% rename from WDACConfig/WDACConfig Module Files/C#/Functions/VersionIncrementer.cs rename to WDACConfig/WDACConfig Module Files/C#/Other Functions/VersionIncrementer.cs index 73240fcfe..b4357e705 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/VersionIncrementer.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Other Functions/VersionIncrementer.cs @@ -9,10 +9,7 @@ public class VersionIncrementer public static Version AddVersion(Version version) // This can recursively increment an input version by one, and is aware of the max limit { - if (version == null) - { - throw new ArgumentNullException(nameof(version)); - } + ArgumentNullException.ThrowIfNull(version); if (version.Revision < int.MaxValue) { diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/WldpQuerySecurityPolicy.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/WldpQuerySecurityPolicy.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Functions/WldpQuerySecurityPolicy.cs rename to WDACConfig/WDACConfig Module Files/C#/Other Functions/WldpQuerySecurityPolicy.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/XmlFilePathExtractor.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/XmlFilePathExtractor.cs similarity index 78% rename from WDACConfig/WDACConfig Module Files/C#/Functions/XmlFilePathExtractor.cs rename to WDACConfig/WDACConfig Module Files/C#/Other Functions/XmlFilePathExtractor.cs index 8472c70f0..4ec8bb807 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/XmlFilePathExtractor.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Other Functions/XmlFilePathExtractor.cs @@ -11,13 +11,13 @@ public class XmlFilePathExtractor public static HashSet GetFilePaths(string xmlFilePath) { // Initialize HashSet with StringComparer.OrdinalIgnoreCase to ensure case-insensitive, ordinal comparison - HashSet filePaths = new HashSet(StringComparer.OrdinalIgnoreCase); + HashSet filePaths = new(StringComparer.OrdinalIgnoreCase); - XmlDocument doc = new XmlDocument(); + XmlDocument doc = new(); doc.Load(xmlFilePath); // Create and configure XmlNamespaceManager - XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable); + XmlNamespaceManager nsmgr = new(doc.NameTable); nsmgr.AddNamespace("ns", "urn:schemas-microsoft-com:sipolicy"); // Select all nodes with the "Allow" tag @@ -32,7 +32,7 @@ public static HashSet GetFilePaths(string xmlFilePath) if (node.Attributes != null && node.Attributes["FilePath"] != null) { // Add the file path to the HashSet - filePaths.Add(node.Attributes["FilePath"]!.Value); + _ = filePaths.Add(node.Attributes["FilePath"]!.Value); } } } diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/AuthenticodePageHashes.cs b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/AuthenticodePageHashes.cs new file mode 100644 index 000000000..7edf4b4a3 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/AuthenticodePageHashes.cs @@ -0,0 +1,12 @@ +#nullable enable + +namespace WDACConfig +{ + public class CodeIntegrityHashes(string? sha1Page, string? sha256Page, string? sha1Authenticode, string? sha256Authenticode) + { + public string? SHA1Page { get; set; } = sha1Page; + public string? SHA256Page { get; set; } = sha256Page; + public string? SHa1Authenticode { get; set; } = sha1Authenticode; + public string? SHA256Authenticode { get; set; } = sha256Authenticode; + } +} diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/CertificateDetailsCreator.cs b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/CertificateDetailsCreator.cs new file mode 100644 index 000000000..e0777e36b --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/CertificateDetailsCreator.cs @@ -0,0 +1,13 @@ + +#nullable enable + +namespace WDACConfig +{ + public class CertificateDetailsCreator(string intermediateCertTBS, string intermediateCertName, string leafCertTBS, string leafCertName) + { + public string IntermediateCertTBS { get; set; } = intermediateCertTBS; + public string IntermediateCertName { get; set; } = intermediateCertName; + public string LeafCertTBS { get; set; } = leafCertTBS; + public string LeafCertName { get; set; } = leafCertName; + } +} \ No newline at end of file diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/CertificateSignerCreator.cs b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/CertificateSignerCreator.cs new file mode 100644 index 000000000..625c2615a --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/CertificateSignerCreator.cs @@ -0,0 +1,11 @@ +#nullable enable + +namespace WDACConfig +{ + public class CertificateSignerCreator(string tbs, string signerName, int siSigningScenario) + { + public string TBS { get; set; } = tbs; + public string SignerName { get; set; } = signerName; + public int SiSigningScenario { get; set; } = siSigningScenario; + } +} diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/ChainElement.cs b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/ChainElement.cs new file mode 100644 index 000000000..228ae3140 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/ChainElement.cs @@ -0,0 +1,25 @@ +using System; +using System.Security.Cryptography.X509Certificates; + +#nullable enable + +namespace WDACConfig +{ + // the enum for CertificateType + public enum CertificateType + { + Root = 0, + Intermediate = 1, + Leaf = 2 + } + + public class ChainElement(string subjectcn, string issuercn, DateTime notafter, string tbsvalue, X509Certificate2 certificate, CertificateType type) + { + public string SubjectCN { get; set; } = subjectcn; + public string IssuerCN { get; set; } = issuercn; + public DateTime NotAfter { get; set; } = notafter; + public string TBSValue { get; set; } = tbsvalue; + public X509Certificate2 Certificate { get; set; } = certificate; + public CertificateType Type { get; set; } = type; + } +} diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/ChainPackage.cs b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/ChainPackage.cs new file mode 100644 index 000000000..93e13f089 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/ChainPackage.cs @@ -0,0 +1,19 @@ +using System.Security.Cryptography.Pkcs; +using System.Security.Cryptography.X509Certificates; + +#nullable enable + +namespace WDACConfig +{ + public class ChainPackage(X509Chain certificatechain, SignedCms signedcms, WDACConfig.ChainElement rootcertificate, + WDACConfig.ChainElement[] intermediatecertificates, + WDACConfig.ChainElement leafcertificate) + { + public X509Chain CertificateChain { get; set; } = certificatechain; + public SignedCms SignedCms { get; set; } = signedcms; + public WDACConfig.ChainElement RootCertificate { get; set; } = rootcertificate; + public WDACConfig.ChainElement[] IntermediateCertificates { get; set; } = intermediatecertificates; + public WDACConfig.ChainElement LeafCertificate { get; set; } = leafcertificate; + } +} + diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/FileBasedInfoPackage.cs b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/FileBasedInfoPackage.cs new file mode 100644 index 000000000..fb8529852 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/FileBasedInfoPackage.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +#nullable enable + +namespace WDACConfig +{ + // Used by the BuildSignerAndHashObjects method to store and return the output + public class FileBasedInfoPackage(List filepublishersigners, List publishersigners, List completehashes) + { + public List FilePublisherSigners { get; set; } = filepublishersigners; + public List PublisherSigners { get; set; } = publishersigners; + public List CompleteHashes { get; set; } = completehashes; + } +} diff --git a/WDACConfig/WDACConfig Module Files/C#/Custom Types/FilePublisherSignerCreator.cs b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/FilePublisherSignerCreator.cs similarity index 96% rename from WDACConfig/WDACConfig Module Files/C#/Custom Types/FilePublisherSignerCreator.cs rename to WDACConfig/WDACConfig Module Files/C#/Types And Definitions/FilePublisherSignerCreator.cs index d07140847..815c5562f 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Custom Types/FilePublisherSignerCreator.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/FilePublisherSignerCreator.cs @@ -48,7 +48,7 @@ public FilePublisherSignerCreator( public FilePublisherSignerCreator() { // Initialize CertificateDetails to an empty list to avoid null reference issues - CertificateDetails = new List(); + CertificateDetails = []; } } } diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/HashCreator.cs b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/HashCreator.cs new file mode 100644 index 000000000..3eb1de338 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/HashCreator.cs @@ -0,0 +1,13 @@ + +#nullable enable + +namespace WDACConfig +{ + public class HashCreator(string authenticodeSHA256, string authenticodeSHA1, string fileName, int siSigningScenario) + { + public string AuthenticodeSHA256 { get; set; } = authenticodeSHA256; + public string AuthenticodeSHA1 { get; set; } = authenticodeSHA1; + public string FileName { get; set; } = fileName; + public int SiSigningScenario { get; set; } = siSigningScenario; + } +} \ No newline at end of file diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/OpusSigner.cs b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/OpusSigner.cs new file mode 100644 index 000000000..778756cac --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/OpusSigner.cs @@ -0,0 +1,11 @@ + +#nullable enable + +namespace WDACConfig +{ + public class OpusSigner(string tbsHash, string subjectCN) + { + public string TBSHash { get; set; } = tbsHash; + public string SubjectCN { get; set; } = subjectCN; + } +} diff --git a/WDACConfig/WDACConfig Module Files/C#/Custom Types/PolicyHashObj.cs b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/PolicyHashObj.cs similarity index 65% rename from WDACConfig/WDACConfig Module Files/C#/Custom Types/PolicyHashObj.cs rename to WDACConfig/WDACConfig Module Files/C#/Types And Definitions/PolicyHashObj.cs index 176b2a1df..04633850b 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Custom Types/PolicyHashObj.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/PolicyHashObj.cs @@ -5,20 +5,12 @@ // Used by WDAC Simulations namespace WDACConfig { - public class PolicyHashObj + public class PolicyHashObj(string hashvalue, string hashtype, string filepathforhash) { // Adding public getters and setters for the properties - public string HashValue { get; set; } - public string HashType { get; set; } - public string FilePathForHash { get; set; } - - // Adding a constructor to initialize the properties - public PolicyHashObj(string hashvalue, string hashtype, string filepathforhash) - { - HashValue = hashvalue; - HashType = hashtype; - FilePathForHash = filepathforhash; - } + public string HashValue { get; set; } = hashvalue; + public string HashType { get; set; } = hashtype; + public string FilePathForHash { get; set; } = filepathforhash; // Making sure any HashSet or collection using this class will only keep unique objects based on their HashValue property diff --git a/WDACConfig/WDACConfig Module Files/C#/Custom Types/PublisherSignerCreator.cs b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/PublisherSignerCreator.cs similarity index 93% rename from WDACConfig/WDACConfig Module Files/C#/Custom Types/PublisherSignerCreator.cs rename to WDACConfig/WDACConfig Module Files/C#/Types And Definitions/PublisherSignerCreator.cs index 5266016da..7728a55d3 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Custom Types/PublisherSignerCreator.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/PublisherSignerCreator.cs @@ -24,7 +24,7 @@ public PublisherSignerCreator(List certificateDetails public PublisherSignerCreator() { // Initialize CertificateDetails to an empty list to avoid null reference issues - CertificateDetails = new List(); + CertificateDetails = []; } } } diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/Signer.cs b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/Signer.cs new file mode 100644 index 000000000..8ff679ad6 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/Signer.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +#nullable enable + +namespace WDACConfig +{ + public class Signer(string id, string name, string certRoot, string certPublisher, string certIssuer, + string[] certEKU, string certOemID, string[] fileAttribRef, + Dictionary> fileAttrib, + string signerScope, bool isWHQL, bool isAllowed, bool hasEKU) + { + public string ID { get; set; } = id; + public string Name { get; set; } = name; + public string CertRoot { get; set; } = certRoot; + public string CertPublisher { get; set; } = certPublisher; + public string CertIssuer { get; set; } = certIssuer; + public string[] CertEKU { get; set; } = certEKU; + public string CertOemID { get; set; } = certOemID; + public string[] FileAttribRef { get; set; } = fileAttribRef; + public Dictionary> FileAttrib { get; set; } = fileAttrib; + public string SignerScope { get; set; } = signerScope; + public bool IsWHQL { get; set; } = isWHQL; + public bool IsAllowed { get; set; } = isAllowed; + public bool HasEKU { get; set; } = hasEKU; + } +} diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/SimulationInput.cs b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/SimulationInput.cs new file mode 100644 index 000000000..8c5d474f4 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/SimulationInput.cs @@ -0,0 +1,15 @@ + +#nullable enable + +// Used by WDAC Simulations +namespace WDACConfig +{ + public class SimulationInput(System.IO.FileInfo filepath, WDACConfig.ChainPackage[] allfilesigners, WDACConfig.Signer[] signerinfo, string[] ekuoids) + { + // Adding public getters and setters for the properties + public System.IO.FileInfo FilePath { get; set; } = filepath; + public WDACConfig.ChainPackage[] AllFileSigners { get; set; } = allfilesigners; + public WDACConfig.Signer[] SignerInfo { get; set; } = signerinfo; + public string[] EKUOIDs { get; set; } = ekuoids; + } +} diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/SimulationOutput.cs b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/SimulationOutput.cs new file mode 100644 index 000000000..bafbc6b38 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/SimulationOutput.cs @@ -0,0 +1,74 @@ +#nullable enable + +// Used by WDAC Simulations, the output of the comparer function/method +namespace WDACConfig +{ + // This class holds the details of the current file in the WDAC Simulation comparer + public class SimulationOutput( + string path, + string source, + bool isAuthorized, + string signerID, + string signerName, + string signerCertRoot, + string signerCertPublisher, + string signerScope, + string[] signerFileAttributeIDs, + string matchCriteria, + string? specificFileNameLevelMatchCriteria, + string certSubjectCN, + string certIssuerCN, + string certNotAfter, + string certTBSValue, + string filePath + ) + { + // The name of the file, which is a truncated version of its path + public string Path { get; set; } = path; + + // Source from the Comparer function is always 'Signer' + public string Source { get; set; } = source; + + // Whether the file is authorized or not + public bool IsAuthorized { get; set; } = isAuthorized; + + // Gathered from the Get-SignerInfo function + public string SignerID { get; set; } = signerID; + + // Gathered from the Get-SignerInfo function + public string SignerName { get; set; } = signerName; + + // Gathered from the Get-SignerInfo function + public string SignerCertRoot { get; set; } = signerCertRoot; + + // Gathered from the Get-SignerInfo function + public string SignerCertPublisher { get; set; } = signerCertPublisher; + + // Gathered from the Get-SignerInfo function + public string SignerScope { get; set; } = signerScope; + + // Gathered from the Get-SignerInfo function + public string[] SignerFileAttributeIDs { get; set; } = signerFileAttributeIDs; + + // The main level based on which the file is authorized + public string MatchCriteria { get; set; } = matchCriteria; + + // Only those eligible for FilePublisher, WHQLFilePublisher, or SignedVersion levels assign this value, otherwise it stays null + public string? SpecificFileNameLevelMatchCriteria { get; set; } = specificFileNameLevelMatchCriteria; + + // Subject CN of the signer that allows the file + public string CertSubjectCN { get; set; } = certSubjectCN; + + // Issuer CN of the signer that allows the file + public string CertIssuerCN { get; set; } = certIssuerCN; + + // NotAfter date of the signer that allows the file + public string CertNotAfter { get; set; } = certNotAfter; + + // TBS value of the signer that allows the file + public string CertTBSValue { get; set; } = certTBSValue; + + // Full path of the file + public string FilePath { get; set; } = filePath; + } +} diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/WinTrust.cs b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/WinTrust.cs new file mode 100644 index 000000000..10b966f35 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/WinTrust.cs @@ -0,0 +1,130 @@ +using System; +using System.Runtime.InteropServices; + +namespace WDACConfig +{ + // This class contains all of the WinTrust related functions and codes + internal partial class WinTrust + { + #region necessary logics for Authenticode and First Page hash calculation + + // a constant field that defines a flag value for the native function + // This causes/helps the GetCiFileHashes method to return the flat file hashes whenever a non-conformant file is encountered + internal const uint CryptcatadminCalchashFlagNonconformantFilesFallbackFlat = 1; + + // a method to acquire a handle to a catalog administrator context using a native function from WinTrust.dll + [DllImport("WinTrust.dll", CharSet = CharSet.Unicode)] + internal static extern bool CryptCATAdminAcquireContext2( + ref IntPtr hCatAdmin, // the first parameter: a reference to a pointer to store the handle + IntPtr pgSubsystem, // the second parameter: a pointer to a GUID that identifies the subsystem + string pwszHashAlgorithm, // the third parameter: a string that specifies the hash algorithm to use + IntPtr pStrongHashPolicy, // the fourth parameter: a pointer to a structure that specifies the strong hash policy + uint dwFlags // the fifth parameter: a flag value that controls the behavior of the function + ); + + // a method to release a handle to a catalog administrator context using a native function from WinTrust.dll + [DllImport("WinTrust.dll", CharSet = CharSet.Unicode)] + internal static extern bool CryptCATAdminReleaseContext( + IntPtr hCatAdmin, // the first parameter: a pointer to the handle to release + uint dwFlags // the second parameter: a flag value that controls the behavior of the function + ); + + // a method to calculate the hash of a file using a native function from WinTrust.dll + [DllImport("WinTrust.dll", CharSet = CharSet.Unicode)] + internal static extern bool CryptCATAdminCalcHashFromFileHandle3( + IntPtr hCatAdmin, // the first parameter: a pointer to the handle of the catalog administrator context + IntPtr hFile, // the second parameter: a pointer to the handle of the file to hash + ref int pcbHash, // the third parameter: a reference to an integer that specifies the size of the hash buffer + IntPtr pbHash, // the fourth parameter: a pointer to a buffer to store the hash value + uint dwFlags // the fifth parameter: a flag value that controls the behavior of the function + ); + + #endregion + + + + #region This section is related to the MeowParser class operations + + // P/Invoke declaration to import the 'CryptCATOpen' function from 'WinTrust.dll'. + // https://learn.microsoft.com/en-us/windows/win32/api/mscat/nf-mscat-cryptcatopen + [DllImport("WinTrust.dll", CharSet = CharSet.Auto, SetLastError = true)] + internal static extern IntPtr CryptCATOpen( + [MarshalAs(UnmanagedType.LPWStr)] string FileName, // The name of the catalog file. + uint OpenFlags, // Flags to control the function behavior. + IntPtr MainCryptProviderHandle, // Handle to the cryptographic service provider. + uint PublicVersion, // The public version number. + uint EncodingType); // The encoding type. + + // P/Invoke declaration to import the 'CryptCATEnumerateMember' function from 'WinTrust.dll'. + // https://learn.microsoft.com/en-us/windows/win32/api/mscat/nf-mscat-cryptcatenumeratemember + [DllImport("WinTrust.dll", CharSet = CharSet.Auto, SetLastError = true)] + internal static extern IntPtr CryptCATEnumerateMember( + IntPtr MeowLogHandle, // Handle to the catalog context. + IntPtr PrevCatalogMember); // Pointer to the previous catalog member. + + // P/Invoke declaration to import the 'CryptCATClose' function from 'WinTrust.dll'. + // https://learn.microsoft.com/en-us/windows/win32/api/mscat/nf-mscat-cryptcatclose + [DllImport("WinTrust.dll", CharSet = CharSet.Auto, SetLastError = true)] + internal static extern IntPtr CryptCATClose(IntPtr MainCryptProviderHandle); // Closes the catalog context. + + #endregion + + + + #region This section is related to the PageHashCalculator class + + // a method to compute the hash of the first page of a file using a native function from Wintrust.dll + [DllImport("Wintrust.dll", CharSet = CharSet.Unicode)] // an attribute to specify the DLL name and the character set + internal static extern int ComputeFirstPageHash( // the method signature + string pszAlgId, // the first parameter: the name of the hash algorithm to use + string filename, // the second parameter: the name of the file to hash + IntPtr buffer, // the third parameter: a pointer to a buffer to store the hash value + int bufferSize // the fourth parameter: the size of the buffer in bytes + ); + + #endregion + + + + #region This section is related to the AllCertificatesGrabber class and its operations + + // Enum defining WinVerifyTrust results + public enum WinVerifyTrustResult : uint + { + Success = 0, // It's Success + SubjectCertificateRevoked = 2148204812, // Subject's certificate was revoked. (CERT_E_REVOKED) + SubjectNotTrusted = 2148204548, // Subject failed the specified verification action + CertExpired = 2148204801, // This is checked for - Signer's certificate was expired. (CERT_E_EXPIRED) + UntrustedRootCert = 2148204809, // A certification chain processed correctly but terminated in a root certificate that is not trusted by the trust provider. (CERT_E_UNTRUSTEDROOT) + HashMismatch = 2148098064, // This is checked for (aka: SignatureOrFileCorrupt) - (TRUST_E_BAD_DIGEST) + ProviderUnknown = 2148204545, // Trust provider is not recognized on this system + ActionUnknown = 2148204546, // Trust provider does not support the specified action + SubjectFormUnknown = 2148204547, // Trust provider does not support the subject's form + FileNotSigned = 2148204800, // File is not signed. (TRUST_E_NOSIGNATURE) + SubjectExplicitlyDistrusted = 2148204817, // Signer's certificate is in the Untrusted Publishers store + } + + + // Constants related to WinTrust + internal const uint StateActionVerify = 1; + internal const uint StateActionClose = 2; + internal static readonly Guid GenericWinTrustVerifyActionGuid = new("{00AAC56B-CD44-11d0-8CC2-00C04FC295EE}"); + + // External method declarations for WinVerifyTrust and WTHelperProvDataFromStateData + [DllImport("wintrust.dll", CharSet = CharSet.Unicode)] + + // https://learn.microsoft.com/en-us/windows/win32/api/wintrust/nf-wintrust-winverifytrust + // Set to return a WinVerifyTrustResult enum + internal static extern WinVerifyTrustResult WinVerifyTrust( + IntPtr hwnd, + [MarshalAs(UnmanagedType.LPStruct)] Guid pgActionID, + IntPtr pWVTData); + + // https://learn.microsoft.com/en-us/windows/win32/api/wintrust/nf-wintrust-wthelperprovdatafromstatedata + [DllImport("wintrust.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern IntPtr WTHelperProvDataFromStateData(IntPtr hStateData); + + #endregion + + } +} diff --git a/WDACConfig/WDACConfig Module Files/C#/Variables/CILogIntel.cs b/WDACConfig/WDACConfig Module Files/C#/Variables/CILogIntel.cs index 7f2b7e82e..2c3a6f3b6 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Variables/CILogIntel.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Variables/CILogIntel.cs @@ -8,7 +8,7 @@ namespace WDACConfig public class CILogIntel { // Requested and Validated Signing Level Mappings: https://learn.microsoft.com/en-us/windows/security/application-security/application-control/windows-defender-application-control/operations/event-tag-explanations#requested-and-validated-signing-level - public static readonly Dictionary ReqValSigningLevels = new Dictionary + public static readonly Dictionary ReqValSigningLevels = new() { { 0 , "Signing level hasn't yet been checked"}, { 1 , "File is unsigned or has no signature that passes the active policies"}, @@ -25,7 +25,7 @@ public class CILogIntel }; // SignatureType Mappings: https://learn.microsoft.com/en-us/windows/security/application-security/application-control/windows-defender-application-control/operations/event-tag-explanations#signaturetype - public static readonly Dictionary SignatureTypeTable = new Dictionary + public static readonly Dictionary SignatureTypeTable = new() { { 0, "Unsigned or verification hasn't been attempted" }, { 1 , "Embedded signature" }, @@ -38,7 +38,7 @@ public class CILogIntel }; // VerificationError mappings: https://learn.microsoft.com/en-us/windows/security/application-security/application-control/windows-defender-application-control/operations/event-tag-explanations#verificationerror - public static readonly Dictionary VerificationErrorTable = new Dictionary + public static readonly Dictionary VerificationErrorTable = new() { { 0 , "Successfully verified signature."}, { 1 , "File has an invalid hash."}, diff --git a/WDACConfig/WDACConfig Module Files/C#/Variables/GlobalVariables.cs b/WDACConfig/WDACConfig Module Files/C#/Variables/GlobalVars.cs similarity index 90% rename from WDACConfig/WDACConfig Module Files/C#/Variables/GlobalVariables.cs rename to WDACConfig/WDACConfig Module Files/C#/Variables/GlobalVars.cs index 2845b698a..1ef292372 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Variables/GlobalVariables.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Variables/GlobalVars.cs @@ -10,7 +10,7 @@ namespace WDACConfig public static class GlobalVars { // Global variable available app-domain wide to track whether ConfigCI bootstrapping has been run or not - public static bool ConfigCIBootstrap = false; + public static bool ConfigCIBootstrap; // User Mode block rules public const string MSFTRecommendedBlockRulesURL = "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"; @@ -45,11 +45,8 @@ public static class GlobalVars // Storing the path to User Config JSON file in the WDACConfig folder in the Program Files public static readonly string UserConfigJson = Path.Combine(UserConfigDir, "UserConfigurations", "UserConfigurations.json"); - // The VerbosePreference variable of the PowerShell session - public static string? VerbosePreference; - - // The DebugPreference variable of the PowerShell session - public static string? DebugPreference; + public static bool VerbosePreference; + public static bool DebugPreference; // The value of the automatic variable $HOST from the PowerShell session // Stored using the LoggerInitializer method that is called at the beginning of each cmdlet diff --git a/WDACConfig/WDACConfig Module Files/C#/Functions/WDAC Simulation/GetFileRuleOutput.cs b/WDACConfig/WDACConfig Module Files/C#/WDAC Simulation/GetFileRuleOutput.cs similarity index 92% rename from WDACConfig/WDACConfig Module Files/C#/Functions/WDAC Simulation/GetFileRuleOutput.cs rename to WDACConfig/WDACConfig Module Files/C#/WDAC Simulation/GetFileRuleOutput.cs index 6f35fdcce..bdc5a89cc 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Functions/WDAC Simulation/GetFileRuleOutput.cs +++ b/WDACConfig/WDACConfig Module Files/C#/WDAC Simulation/GetFileRuleOutput.cs @@ -50,7 +50,7 @@ public static class GetFileRuleOutput // Add the extracted values of the current Hash rule to the output HashSet if (!string.IsNullOrEmpty(hashValue) && !string.IsNullOrEmpty(hashType) && !string.IsNullOrEmpty(filePathForHash)) { - outputHashInfoProcessing.Add(new WDACConfig.PolicyHashObj(hashValue, hashType, filePathForHash)); + _ = outputHashInfoProcessing.Add(new WDACConfig.PolicyHashObj(hashValue, hashType, filePathForHash)); } } } @@ -60,7 +60,7 @@ public static class GetFileRuleOutput // Only keep the Authenticode Hash SHA256 outputHashInfoProcessing = new HashSet(outputHashInfoProcessing.Where(obj => string.Equals(obj.HashType, "Hash Sha256", StringComparison.OrdinalIgnoreCase))); - WDACConfig.VerboseLogger.Write($"Returning {outputHashInfoProcessing.Count} file rules that are based on file hashes"); + WDACConfig.Logger.Write($"Returning {outputHashInfoProcessing.Count} file rules that are based on file hashes"); // Return the output HashSet return outputHashInfoProcessing; diff --git a/WDACConfig/WDACConfig Module Files/C#/XMLOps/SignerAndHashBuilder.cs b/WDACConfig/WDACConfig Module Files/C#/XMLOps/SignerAndHashBuilder.cs index 981648842..6058fcfe6 100644 --- a/WDACConfig/WDACConfig Module Files/C#/XMLOps/SignerAndHashBuilder.cs +++ b/WDACConfig/WDACConfig Module Files/C#/XMLOps/SignerAndHashBuilder.cs @@ -49,20 +49,20 @@ public static class SignerAndHashBuilder public static FileBasedInfoPackage BuildSignerAndHashObjects(Hashtable[] data, string incomingDataType = "MDEAH", string level = "Auto", bool publisherToHash = false) { // An array to store the Signers created with FilePublisher Level - List filePublisherSigners = new List(); + List filePublisherSigners = []; // An array to store the Signers created with Publisher Level - List publisherSigners = new List(); + List publisherSigners = []; // An array to store the FileAttributes created using Hash Level - List completeHashes = new List(); + List completeHashes = []; // Lists to separate data - List signedFilePublisherData = new List(); - List signedPublisherData = new List(); - List unsignedData = new List(); + List signedFilePublisherData = []; + List signedPublisherData = []; + List unsignedData = []; - WDACConfig.VerboseLogger.Write("BuildSignerAndHashObjects: Starting the data separation process."); + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Starting the data separation process."); // Data separation based on the level switch (level.ToLowerInvariant()) @@ -70,13 +70,13 @@ public static FileBasedInfoPackage BuildSignerAndHashObjects(Hashtable[] data, s // If Hash level is used then add everything to the Unsigned data so Hash rules will be created for them case "hash": - WDACConfig.VerboseLogger.Write("BuildSignerAndHashObjects: Using only Hash level."); + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Using only Hash level."); foreach (var item in data) { if (item == null) { - WDACConfig.VerboseLogger.Write("BuildSignerAndHashObjects: Found a null item in data."); + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Found a null item in data."); } else { @@ -88,13 +88,13 @@ public static FileBasedInfoPackage BuildSignerAndHashObjects(Hashtable[] data, s // If Publisher level is used then add all Signed data to the SignedPublisherData list and Unsigned data to the Hash list case "publisher": - WDACConfig.VerboseLogger.Write("BuildSignerAndHashObjects: Using Publisher -> Hash levels."); + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Using Publisher -> Hash levels."); foreach (var item in data) { if (item == null) { - WDACConfig.VerboseLogger.Write("BuildSignerAndHashObjects: Found a null item in data."); + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Found a null item in data."); } else if ( item.ContainsKey("SignatureStatus") && @@ -114,14 +114,14 @@ public static FileBasedInfoPackage BuildSignerAndHashObjects(Hashtable[] data, s // Detect and separate FilePublisher, Publisher and Hash (Unsigned) data if the level is Auto or FilePublisher default: - WDACConfig.VerboseLogger.Write("BuildSignerAndHashObjects: Using FilePublisher -> Publisher -> Hash levels."); + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Using FilePublisher -> Publisher -> Hash levels."); // Loop over each data foreach (var item in data) { if (item == null) { - WDACConfig.VerboseLogger.Write("BuildSignerAndHashObjects: Found a null item in data."); + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Found a null item in data."); } // If the file's version is empty or it has no file attribute, then add it to the Publishers array // because FilePublisher rule cannot be created for it @@ -147,7 +147,7 @@ public static FileBasedInfoPackage BuildSignerAndHashObjects(Hashtable[] data, s } else { - WDACConfig.VerboseLogger.Write($"BuildSignerAndHashObjects: Passing Publisher rule to the hash array for the file: {(string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? item["FileName"] : item["File Name"])}"); + WDACConfig.Logger.Write($"BuildSignerAndHashObjects: Passing Publisher rule to the hash array for the file: {(string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? item["FileName"] : item["File Name"])}"); // Add the current signed data to Unsigned data array so that Hash rules will be created for it instead unsignedData.Add(item); } @@ -165,16 +165,16 @@ public static FileBasedInfoPackage BuildSignerAndHashObjects(Hashtable[] data, s break; } - WDACConfig.VerboseLogger.Write($"BuildSignerAndHashObjects: {signedFilePublisherData.Count} FilePublisher Rules."); - WDACConfig.VerboseLogger.Write($"BuildSignerAndHashObjects: {signedPublisherData.Count} Publisher Rules."); - WDACConfig.VerboseLogger.Write($"BuildSignerAndHashObjects: {unsignedData.Count} Hash Rules."); + WDACConfig.Logger.Write($"BuildSignerAndHashObjects: {signedFilePublisherData.Count} FilePublisher Rules."); + WDACConfig.Logger.Write($"BuildSignerAndHashObjects: {signedPublisherData.Count} Publisher Rules."); + WDACConfig.Logger.Write($"BuildSignerAndHashObjects: {unsignedData.Count} Hash Rules."); - WDACConfig.VerboseLogger.Write("BuildSignerAndHashObjects: Processing FilePublisher data."); + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Processing FilePublisher data."); foreach (var signedData in signedFilePublisherData) { // Create a new FilePublisherSignerCreator object - WDACConfig.FilePublisherSignerCreator currentFilePublisherSigner = new WDACConfig.FilePublisherSignerCreator(); + WDACConfig.FilePublisherSignerCreator currentFilePublisherSigner = new(); // Get the certificate details of the current event data based on the incoming type, they can be stored under different names. // Safely casting the objects to a HashTable, returning null if the cast fails instead of throwing an exception. @@ -184,15 +184,13 @@ public static FileBasedInfoPackage BuildSignerAndHashObjects(Hashtable[] data, s if (correlatedEventsDataValues == null) { - WDACConfig.VerboseLogger.Write("BuildSignerAndHashObjects: correlatedEventsDataValues is null."); + WDACConfig.Logger.Write("BuildSignerAndHashObjects: correlatedEventsDataValues is null."); } else { // Loop through each correlated event and process the certificate details foreach (Hashtable corDataValue in correlatedEventsDataValues) { - // currentCorData to store the current SignerInfo/Correlated - WDACConfig.CertificateDetailsCreator? currentCorData = null; // If the file doesn't have Issuer TBS hash (aka Intermediate certificate hash), use the leaf cert's TBS hash and CN instead (aka publisher TBS hash) // This is according to the ConfigCI's workflow when encountering specific files @@ -203,10 +201,12 @@ public static FileBasedInfoPackage BuildSignerAndHashObjects(Hashtable[] data, s string? issuerTBSHash = corDataValue.ContainsKey("IssuerTBSHash") ? corDataValue["IssuerTBSHash"]?.ToString() : null; string? publisherTBSHash = corDataValue.ContainsKey("PublisherTBSHash") ? corDataValue["PublisherTBSHash"]?.ToString() : null; + // currentCorData to store the current SignerInfo/Correlated + CertificateDetailsCreator? currentCorData; // Perform the check with null-safe values if (string.IsNullOrWhiteSpace(issuerTBSHash) && !string.IsNullOrWhiteSpace(publisherTBSHash)) { - WDACConfig.VerboseLogger.Write($"BuildSignerAndHashObjects: Intermediate Certificate TBS hash is empty for the file: {(string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? signedData["FileName"] : signedData["File Name"])}, using the leaf certificate TBS hash instead"); + WDACConfig.Logger.Write($"BuildSignerAndHashObjects: Intermediate Certificate TBS hash is empty for the file: {(string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? signedData["FileName"] : signedData["File Name"])}, using the leaf certificate TBS hash instead"); currentCorData = new WDACConfig.CertificateDetailsCreator( corDataValue["PublisherTBSHash"]!.ToString()!, @@ -249,8 +249,7 @@ public static FileBasedInfoPackage BuildSignerAndHashObjects(Hashtable[] data, s string? sha1 = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? (signedData.ContainsKey("SHA1") ? signedData["SHA1"]?.ToString() : null) : (signedData.ContainsKey("SHA1 Hash") ? signedData["SHA1 Hash"]?.ToString() : null); - - string? siSigningScenarioString = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) + _ = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? (signedData.ContainsKey("SiSigningScenario") ? signedData["SiSigningScenario"]?.ToString() : null) : (signedData.ContainsKey("SI Signing Scenario") ? signedData["SI Signing Scenario"]?.ToString() : null); @@ -273,24 +272,24 @@ public static FileBasedInfoPackage BuildSignerAndHashObjects(Hashtable[] data, s // Check if necessary details are not empty if (string.IsNullOrWhiteSpace(currentFilePublisherSigner.AuthenticodeSHA256)) { - WDACConfig.VerboseLogger.Write($"SHA256 is empty for the file: {(string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? signedData["FileName"] : signedData["File Name"])}"); + WDACConfig.Logger.Write($"SHA256 is empty for the file: {(string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? signedData["FileName"] : signedData["File Name"])}"); } if (string.IsNullOrWhiteSpace(currentFilePublisherSigner.AuthenticodeSHA1)) { - WDACConfig.VerboseLogger.Write($"SHA1 is empty for the file: {(string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? signedData["FileName"] : signedData["File Name"])}"); + WDACConfig.Logger.Write($"SHA1 is empty for the file: {(string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? signedData["FileName"] : signedData["File Name"])}"); } // Add the completed FilePublisherSigner to the list filePublisherSigners.Add(currentFilePublisherSigner); } - WDACConfig.VerboseLogger.Write("BuildSignerAndHashObjects: Processing Publisher data."); + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Processing Publisher data."); foreach (var signedData in signedPublisherData) { // Create a new PublisherSignerCreator object - WDACConfig.PublisherSignerCreator currentPublisherSigner = new WDACConfig.PublisherSignerCreator(); + WDACConfig.PublisherSignerCreator currentPublisherSigner = new(); // Get the certificate details of the current event data based on the incoming type, they can be stored under different names ICollection? correlatedEventsDataValues = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) @@ -299,14 +298,13 @@ public static FileBasedInfoPackage BuildSignerAndHashObjects(Hashtable[] data, s if (correlatedEventsDataValues == null) { - WDACConfig.VerboseLogger.Write("BuildSignerAndHashObjects: correlatedEventsDataValues is null."); + WDACConfig.Logger.Write("BuildSignerAndHashObjects: correlatedEventsDataValues is null."); } else { // Process each correlated event foreach (Hashtable corDataValue in correlatedEventsDataValues) { - WDACConfig.CertificateDetailsCreator? currentCorData = null; // Safely access dictionary values and handle nulls string? issuerTBSHash = corDataValue.ContainsKey("IssuerTBSHash") ? corDataValue["IssuerTBSHash"]?.ToString() : null; @@ -314,10 +312,11 @@ public static FileBasedInfoPackage BuildSignerAndHashObjects(Hashtable[] data, s string? publisherTBSHash = corDataValue.ContainsKey("PublisherTBSHash") ? corDataValue["PublisherTBSHash"]?.ToString() : null; string? publisherName = corDataValue.ContainsKey("PublisherName") ? corDataValue["PublisherName"]?.ToString() : null; + CertificateDetailsCreator? currentCorData; // Perform the check with null-safe values if (string.IsNullOrWhiteSpace(issuerTBSHash) && !string.IsNullOrWhiteSpace(publisherTBSHash)) { - WDACConfig.VerboseLogger.Write($"BuildSignerAndHashObjects: Intermediate Certificate TBS hash is empty for the file: {(string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? signedData!["FileName"] : signedData!["File Name"])}, using the leaf certificate TBS hash instead"); + WDACConfig.Logger.Write($"BuildSignerAndHashObjects: Intermediate Certificate TBS hash is empty for the file: {(string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? signedData!["FileName"] : signedData!["File Name"])}, using the leaf certificate TBS hash instead"); // Create a new CertificateDetailsCreator object with the safely retrieved and used values currentCorData = new WDACConfig.CertificateDetailsCreator( @@ -355,13 +354,13 @@ public static FileBasedInfoPackage BuildSignerAndHashObjects(Hashtable[] data, s publisherSigners.Add(currentPublisherSigner); } - WDACConfig.VerboseLogger.Write("BuildSignerAndHashObjects: Processing Unsigned Hash data."); + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Processing Unsigned Hash data."); foreach (var hashData in unsignedData) { if (hashData == null) { - WDACConfig.VerboseLogger.Write("BuildSignerAndHashObjects: Found a null hashData item."); + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Found a null hashData item."); continue; } @@ -383,7 +382,7 @@ public static FileBasedInfoPackage BuildSignerAndHashObjects(Hashtable[] data, s if (string.IsNullOrWhiteSpace(sha256) || string.IsNullOrWhiteSpace(sha1) || string.IsNullOrWhiteSpace(fileName)) { - WDACConfig.VerboseLogger.Write("BuildSignerAndHashObjects: One or more necessary properties are null or empty in hashData."); + WDACConfig.Logger.Write("BuildSignerAndHashObjects: One or more necessary properties are null or empty in hashData."); continue; } @@ -395,7 +394,7 @@ public static FileBasedInfoPackage BuildSignerAndHashObjects(Hashtable[] data, s )); } - WDACConfig.VerboseLogger.Write("BuildSignerAndHashObjects: Completed the process."); + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Completed the process."); return new FileBasedInfoPackage(filePublisherSigners, publisherSigners, completeHashes); } diff --git a/WDACConfig/WDACConfig Module Files/Core/Assert-WDACConfigIntegrity.psm1 b/WDACConfig/WDACConfig Module Files/Core/Assert-WDACConfigIntegrity.psm1 index c3749cecf..367b79e7d 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Assert-WDACConfigIntegrity.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Assert-WDACConfigIntegrity.psm1 @@ -2,7 +2,7 @@ Function Assert-WDACConfigIntegrity { [CmdletBinding( DefaultParameterSetName = 'SaveLocally' )] - [OutputType([System.String], [System.Object[]])] + [OutputType([System.Collections.Generic.List[WDACConfig.WDACConfigHashEntry]])] param ( [Alias('S')] [Parameter(Mandatory = $false, ParameterSetName = 'SaveLocally')] @@ -16,112 +16,12 @@ Function Assert-WDACConfigIntegrity { [Parameter(Mandatory = $false)] [System.Management.Automation.SwitchParameter]$SkipVersionCheck ) - begin { - [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $false - [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - - Write-Verbose -Message 'Importing the required sub-modules' - Import-Module -Force -FullyQualifiedName "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Update-Self.psm1" - - # if -SkipVersionCheck wasn't passed, run the updater - if (-NOT $SkipVersionCheck) { Update-Self -InvocationStatement $MyInvocation.Statement } - - # Define the output file name and the URL of the cloud CSV file - [System.String]$OutputFileName = 'Hashes.csv' - [System.Uri]$Url = 'https://raw.githubusercontent.com/HotCakeX/Harden-Windows-Security/main/WDACConfig/Utilities/Hashes.csv' - - # Download the cloud CSV file and convert it to an array of objects - [System.Object[]]$CloudCSV = (Invoke-WebRequest -Uri $Url -ProgressAction SilentlyContinue).Content | ConvertFrom-Csv - - # An empty array to store the final results - $FinalOutput = New-Object -TypeName System.Collections.Generic.List[PSCustomObject] - } - process { - - Write-Verbose -Message 'Looping through the WDACConfig module files' - foreach ($File in ([WDACConfig.FileUtility]::GetFilesFast(([WDACConfig.GlobalVars]::ModuleRootPath), $null, '*'))) { - - # Making sure the PowerShell Gallery file in the WDACConfig module's folder is skipped - if ($File.Name -eq 'PSGetModuleInfo.xml') { - Write-Verbose -Message "Skipping the extra file: $($File.Name)" - continue - } - - # Read the file as a byte array - This way we can get hashes of a file in use by another process where Get-FileHash would fail - [System.Byte[]]$Bytes = [System.IO.File]::ReadAllBytes($File) - - #Region SHA2-512 calculation - # Create a SHA512 object - [System.Security.Cryptography.SHA512]$Sha512 = [System.Security.Cryptography.SHA512]::Create() - - # Compute the hash of the byte array - [System.Byte[]]$HashBytes = $Sha512.ComputeHash($Bytes) - - # Dispose the SHA512 object - $Sha512.Dispose() - - # Convert the hash bytes to a hexadecimal string to make it look like the output of the Get-FileHash which produces hexadecimals (0-9 and A-F) - # If [System.Convert]::ToBase64String was used, it'd return the hash in base64 format, which uses 64 symbols (A-Z, a-z, 0-9, + and /) to represent each byte - [System.String]$HashString = [System.BitConverter]::ToString($HashBytes) - - # Remove the dashes from the hexadecimal string - $HashString = $HashString.Replace('-', '') - #Endregion SHA2-512 calculation - - #Region SHA3-512 calculation - try { - [System.Security.Cryptography.SHA3_512]$SHA3_512 = [System.Security.Cryptography.SHA3_512]::Create() - - # Compute the hash of the byte array - [System.Byte[]]$SHA3_512HashBytes = $SHA3_512.ComputeHash($Bytes) - - # Dispose the SHA3_512 object - $SHA3_512.Dispose() - - # Convert the hash bytes to a hexadecimal string to make it look like the output of the Get-FileHash which produces hexadecimals (0-9 and A-F) - # If [System.Convert]::ToBase64String was used, it'd return the hash in base64 format, which uses 64 symbols (A-Z, a-z, 0-9, + and /) to represent each byte - [System.String]$SHA3_512HashString = [System.BitConverter]::ToString($SHA3_512HashBytes) - - # Remove the dashes from the hexadecimal string - $SHA3_512HashString = $SHA3_512HashString.Replace('-', '') - } - catch [System.PlatformNotSupportedException] { - Write-Verbose -Message 'The SHA3-512 algorithm is not supported on this system. Requires build 24H2 or higher.' - } - #Endregion SHA3-512 calculation - - # Create a custom object to store the relative path, file name and the hash of the file - $FinalOutput.Add([PSCustomObject]@{ - RelativePath = [System.String]([System.IO.Path]::GetRelativePath(([WDACConfig.GlobalVars]::ModuleRootPath), $File.FullName)) - FileName = [System.String]$File.Name - FileHash = [System.String]$HashString - FileHashSHA3_512 = [System.String]$SHA3_512HashString - }) - } - - if ($SaveLocally) { - Write-Verbose -Message "Saving the results to a CSV file in $((Join-Path -Path $Path -ChildPath $OutputFileName))" - $FinalOutput | Export-Csv -Path (Join-Path -Path $Path -ChildPath $OutputFileName) -Force - } - } - end { - Write-Verbose -Message 'Comparing the local files hashes with the ones in the cloud' - [System.Object[]]$ComparisonResults = Compare-Object -ReferenceObject $CloudCSV -DifferenceObject $FinalOutput -Property RelativePath, FileName, FileHash | Where-Object -Property SideIndicator -EQ '=>' - - if ($ComparisonResults) { - Write-Warning -Message 'Tampered files detected!' - Write-ColorfulTextWDACConfig -Color PinkBoldBlink -InputText 'The following files are different from the ones in the cloud:' - $ComparisonResults - } - else { - Write-ColorfulTextWDACConfig -Color NeonGreen -InputText 'All of your local WDACConfig files are genuine.' - } - } + [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) + if (-NOT $SkipVersionCheck) { Update-WDACConfigPSModule -InvocationStatement $MyInvocation.Statement } + return [WDACConfig.AssertWDACConfigIntegrity]::Invoke($SaveLocally, $Path) <# .SYNOPSIS Gets the SHA2-512 hashes of files in the WDACConfig and compares them with the ones in the cloud and shows the differences. - It also calculates the SHA3-512 hashes of the files and will completely switch to this new algorithm after Windows build 24H2 is reached GA. .DESCRIPTION The Assert-WDACConfigIntegrity function scans all the relevant files in the WDACConfig's folder and its subfolders, calculates their SHA2-512 hashes in hexadecimal format, Then it downloads the cloud CSV file from the GitHub repository and compares the hashes of the local files with the ones in the cloud. diff --git a/WDACConfig/WDACConfig Module Files/Core/Build-WDACCertificate.psm1 b/WDACConfig/WDACConfig Module Files/Core/Build-WDACCertificate.psm1 index 4fd330cf9..b075ad1c9 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Build-WDACCertificate.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Build-WDACCertificate.psm1 @@ -28,15 +28,8 @@ Function Build-WDACCertificate { [System.Management.Automation.SwitchParameter]$SkipVersionCheck ) Begin { - [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $false [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - - Write-Verbose -Message 'Importing the required sub-modules' - Import-Module -Force -FullyQualifiedName "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Update-Self.psm1" - - # if -SkipVersionCheck wasn't passed, run the updater - if (-NOT $SkipVersionCheck) { Update-Self -InvocationStatement $MyInvocation.Statement } + if (-NOT $SkipVersionCheck) { Update-WDACConfigPSModule -InvocationStatement $MyInvocation.Statement } # Define a staging area for Build-WDACCertificate cmdlet [System.IO.DirectoryInfo]$StagingArea = Join-Path -Path ([WDACConfig.GlobalVars]::UserConfigDir) -ChildPath 'StagingArea' -AdditionalChildPath 'Build-WDACCertificate' @@ -62,7 +55,7 @@ Function Build-WDACCertificate { if (!$Password) { - Write-Verbose -Message 'Prompting the user to enter a password for the certificate because it was not passed as a parameter.' + [WDACConfig.Logger]::Write('Prompting the user to enter a password for the certificate because it was not passed as a parameter.') do { [System.Security.SecureString]$Password1 = $(Write-ColorfulTextWDACConfig -Color Lavender -InputText 'Enter a password for the certificate (at least 5 characters)'; Read-Host -AsSecureString) @@ -83,7 +76,7 @@ Function Build-WDACCertificate { until ( $TheyMatch -and ($Password1.Length -ge 5) -and ($Password2.Length -ge 5) ) } - Write-Verbose -Message 'Checking if a certificate with the same common name already exists.' + [WDACConfig.Logger]::Write('Checking if a certificate with the same common name already exists.') [System.Security.Cryptography.X509Certificates.X509Certificate2[]]$DuplicateCerts = foreach ($Item in (Get-ChildItem -Path 'Cert:\CurrentUser\My' -CodeSigningCert)) { if ($Item.Subject -ieq "CN=$CommonName") { $Item @@ -109,7 +102,7 @@ Function Build-WDACCertificate { if ($BuildingMethod -ieq 'Method1') { - Write-Verbose -Message 'Building the certificate using Method1.' + [WDACConfig.Logger]::Write('Building the certificate using Method1.') [System.String]$Inf = @" [Version] @@ -183,7 +176,7 @@ ValidityPeriod = Years elseif ($BuildingMethod -eq 'Method2') { - Write-Verbose -Message 'Building the certificate using Method2.' + [WDACConfig.Logger]::Write('Building the certificate using Method2.') # Create a hashtable of parameter names and values [System.Collections.Hashtable]$Params = @{ @@ -209,7 +202,7 @@ ValidityPeriod = Years [System.String]$NewCertificateThumbprint = $NewCertificate.Thumbprint } - Write-Verbose -Message 'Finding the certificate that was just created by its thumbprint' + [WDACConfig.Logger]::Write('Finding the certificate that was just created by its thumbprint') [System.Security.Cryptography.X509Certificates.X509Certificate2]$TheCert = foreach ($Cert in (Get-ChildItem -Path 'Cert:\CurrentUser\My' -CodeSigningCert)) { if ($Cert.Thumbprint -eq $NewCertificateThumbprint) { $Cert @@ -218,22 +211,22 @@ ValidityPeriod = Years [System.IO.FileInfo]$CertificateOutputPath = Join-Path -Path ([WDACConfig.GlobalVars]::UserConfigDir) -ChildPath "$FileName.cer" - Write-Verbose -Message "Exporting the certificate (public key only) to $FileName.cer" + [WDACConfig.Logger]::Write("Exporting the certificate (public key only) to $FileName.cer") $null = Export-Certificate -Cert $TheCert -FilePath $CertificateOutputPath -Type 'CERT' -Force - Write-Verbose -Message "Exporting the certificate (public and private keys) to $FileName.pfx" + [WDACConfig.Logger]::Write("Exporting the certificate (public and private keys) to $FileName.pfx") $null = Export-PfxCertificate -Cert $TheCert -CryptoAlgorithmOption 'AES256_SHA256' -Password $Password -ChainOption 'BuildChain' -FilePath (Join-Path -Path ([WDACConfig.GlobalVars]::UserConfigDir) -ChildPath "$FileName.pfx") -Force - Write-Verbose -Message 'Removing the certificate from the certificate store' + [WDACConfig.Logger]::Write('Removing the certificate from the certificate store') $TheCert | Remove-Item -Force - Write-Verbose -Message 'Importing the certificate to the certificate store again, this time with the private key protected by VSM (Virtual Secure Mode - Virtualization Based Security)' + [WDACConfig.Logger]::Write('Importing the certificate to the certificate store again, this time with the private key protected by VSM (Virtual Secure Mode - Virtualization Based Security)') $null = Import-PfxCertificate -ProtectPrivateKey 'VSM' -FilePath (Join-Path -Path ([WDACConfig.GlobalVars]::UserConfigDir) -ChildPath "$FileName.pfx") -CertStoreLocation 'Cert:\CurrentUser\My' -Password $Password - Write-Verbose -Message 'Saving the common name of the certificate to the User configurations' + [WDACConfig.Logger]::Write('Saving the common name of the certificate to the User configurations') $null = Set-CommonWDACConfig -CertCN $CommonName - Write-Verbose -Message 'Saving the path of the .cer file of the certificate to the User configurations' + [WDACConfig.Logger]::Write('Saving the path of the .cer file of the certificate to the User configurations') $null = Set-CommonWDACConfig -CertPath $CertificateOutputPath } catch { diff --git a/WDACConfig/WDACConfig Module Files/Core/Confirm-WDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Confirm-WDACConfig.psm1 index 25c7ebeec..3e75df298 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Confirm-WDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Confirm-WDACConfig.psm1 @@ -69,12 +69,7 @@ Function Confirm-WDACConfig { return $ParamDictionary } Begin { - [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $false [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - - Write-Verbose -Message 'Importing the required sub-modules' - Import-Module -Force -FullyQualifiedName "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Update-Self.psm1" # 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 @@ -84,8 +79,7 @@ Function Confirm-WDACConfig { [System.Management.Automation.SwitchParameter]$OnlySystemPolicies = $($PSBoundParameters['OnlySystemPolicies']) [System.Management.Automation.SwitchParameter]$SkipVersionCheck = $($PSBoundParameters['SkipVersionCheck']) - # if -SkipVersionCheck wasn't passed, run the updater - if (-NOT $SkipVersionCheck) { Update-Self -InvocationStatement $MyInvocation.Statement } + if (-NOT $SkipVersionCheck) { Update-WDACConfigPSModule -InvocationStatement $MyInvocation.Statement } # Script block to show only Base policies [System.Management.Automation.ScriptBlock]$OnlyBasePoliciesBLOCK = { @@ -128,13 +122,13 @@ Function Confirm-WDACConfig { } if ($VerifyWDACStatus) { - Write-Verbose -Message 'Checking the status of WDAC using Get-CimInstance' + [WDACConfig.Logger]::Write('Checking the status of WDAC using Get-CimInstance') Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard | Select-Object -Property *codeintegrity* | Format-List Write-ColorfulTextWDACConfig -Color Lavender -InputText "2 -> Enforced`n1 -> Audit mode`n0 -> Disabled/Not running`n" } if ($CheckSmartAppControlStatus) { - Write-Verbose -Message 'Checking the status of Smart App Control using Get-MpComputerStatus' + [WDACConfig.Logger]::Write('Checking the status of Smart App Control using Get-MpComputerStatus') Get-MpComputerStatus | Select-Object -Property SmartAppControlExpiration, SmartAppControlState if ((Get-MpComputerStatus).SmartAppControlState -eq 'Eval') { Write-ColorfulTextWDACConfig -Color Pink -InputText "`nSmart App Control is in Evaluation mode." diff --git a/WDACConfig/WDACConfig Module Files/Core/ConvertTo-WDACPolicy.psm1 b/WDACConfig/WDACConfig Module Files/Core/ConvertTo-WDACPolicy.psm1 index 1ddc98bfe..eb4bea246 100644 --- a/WDACConfig/WDACConfig Module Files/Core/ConvertTo-WDACPolicy.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/ConvertTo-WDACPolicy.psm1 @@ -315,15 +315,11 @@ Function ConvertTo-WDACPolicy { return $ParamDictionary } Begin { - [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $false - [System.Boolean]$Debug = $PSBoundParameters.Debug.IsPresent ? $true : $false [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - Write-Verbose -Message 'ConvertTo-WDACPolicy: Importing the required sub-modules' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Importing the required sub-modules') # Defining list of generic modules required for this cmdlet to import [System.String[]]$ModulesToImport = @( - "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Update-Self.psm1", "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Receive-CodeIntegrityLogs.psm1", "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Set-LogPropertiesVisibility.psm1", "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Select-LogProperties.psm1", @@ -343,8 +339,7 @@ Function ConvertTo-WDACPolicy { New-Variable -Name 'ExtremeVisibility' -Value $PSBoundParameters['ExtremeVisibility'] -Force New-Variable -Name 'SkipVersionCheck' -Value $PSBoundParameters['SkipVersionCheck'] -Force - # if -SkipVersionCheck wasn't passed, run the updater - if (-NOT $SkipVersionCheck) { Update-Self -InvocationStatement $MyInvocation.Statement } + if (-NOT $SkipVersionCheck) { Update-WDACConfigPSModule -InvocationStatement $MyInvocation.Statement } # Defining a staging area for the current [System.IO.DirectoryInfo]$StagingArea = [WDACConfig.StagingArea]::NewStagingArea('ConvertTo-WDACPolicy') @@ -428,7 +423,7 @@ Function ConvertTo-WDACPolicy { # Display the logs in a grid view using the build-in cmdlet $SelectedLogs = $EventsToDisplay | Out-GridView -OutputMode Multiple -Title "Displaying $($EventsToDisplay.count) Code Integrity Logs of $LogType type(s)" - Write-Verbose -Message "ConvertTo-WDACPolicy: Selected logs count: $($SelectedLogs.count)" + [WDACConfig.Logger]::Write("ConvertTo-WDACPolicy: Selected logs count: $($SelectedLogs.count)") if (!$BasePolicyGUID -and !$BasePolicyFile -and !$PolicyToAddLogsTo) { Write-ColorfulTextWDACConfig -Color HotPink -InputText 'A more specific parameter was not provided to define what to do with the selected logs. Exiting...' @@ -447,12 +442,12 @@ Function ConvertTo-WDACPolicy { if ($null -ne $KernelProtectedFileLogs) { - Write-Verbose -Message "ConvertTo-WDACPolicy: Kernel protected files count: $($KernelProtectedFileLogs.count)" + [WDACConfig.Logger]::Write("ConvertTo-WDACPolicy: Kernel protected files count: $($KernelProtectedFileLogs.count)") - Write-Verbose -Message 'Copying the template policy to the staging area' + [WDACConfig.Logger]::Write('Copying the template policy to the staging area') Copy-Item -LiteralPath 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowAll.xml' -Destination $WDACPolicyKernelProtectedPath -Force - Write-Verbose -Message 'Emptying the policy file in preparation for the new data insertion' + [WDACConfig.Logger]::Write('Emptying the policy file in preparation for the new data insertion') Clear-CiPolicy_Semantic -Path $WDACPolicyKernelProtectedPath # Find the kernel protected files that have PFN property @@ -467,8 +462,8 @@ Function ConvertTo-WDACPolicy { # Add the Kernel protected files policy to the list of policies to merge $PolicyFilesToMerge.Add($WDACPolicyKernelProtectedPath) - Write-Verbose -Message "ConvertTo-WDACPolicy: Kernel protected files with PFN property: $($KernelProtectedFileLogsWithPFN.count)" - Write-Verbose -Message "ConvertTo-WDACPolicy: Kernel protected files without PFN property: $($KernelProtectedFileLogs.count - $KernelProtectedFileLogsWithPFN.count)" + [WDACConfig.Logger]::Write("ConvertTo-WDACPolicy: Kernel protected files with PFN property: $($KernelProtectedFileLogsWithPFN.count)") + [WDACConfig.Logger]::Write("ConvertTo-WDACPolicy: Kernel protected files without PFN property: $($KernelProtectedFileLogs.count - $KernelProtectedFileLogsWithPFN.count)") # Removing the logs that were used to create PFN rules from the rest of the logs $SelectedLogs = foreach ($Log in $SelectedLogs) { @@ -481,28 +476,28 @@ Function ConvertTo-WDACPolicy { $CurrentStep++ Write-Progress -Id 30 -Activity 'Generating the policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Copying the template policy to the staging area' + [WDACConfig.Logger]::Write('Copying the template policy to the staging area') Copy-Item -LiteralPath 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowAll.xml' -Destination $WDACPolicyPathTEMP -Force - Write-Verbose -Message 'Emptying the policy file in preparation for the new data insertion' + [WDACConfig.Logger]::Write('Emptying the policy file in preparation for the new data insertion') Clear-CiPolicy_Semantic -Path $WDACPolicyPathTEMP $CurrentStep++ Write-Progress -Id 30 -Activity 'Building Signers and file rule' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Building the Signer and Hash objects from the selected logs' + [WDACConfig.Logger]::Write('Building the Signer and Hash objects from the selected logs') [WDACConfig.FileBasedInfoPackage]$DataToUseForBuilding = [WDACConfig.SignerAndHashBuilder]::BuildSignerAndHashObjects((ConvertTo-HashtableArray $SelectedLogs), 'EVTX', $Level, $false) if ($Null -ne $DataToUseForBuilding.FilePublisherSigners -and $DataToUseForBuilding.FilePublisherSigners.Count -gt 0) { - Write-Verbose -Message 'Creating File Publisher Level rules' + [WDACConfig.Logger]::Write('Creating File Publisher Level rules') New-FilePublisherLevelRules -FilePublisherSigners $DataToUseForBuilding.FilePublisherSigners -XmlFilePath $WDACPolicyPathTEMP } if ($Null -ne $DataToUseForBuilding.PublisherSigners -and $DataToUseForBuilding.PublisherSigners.Count -gt 0) { - Write-Verbose -Message 'Creating Publisher Level rules' + [WDACConfig.Logger]::Write('Creating Publisher Level rules') New-PublisherLevelRules -PublisherSigners $DataToUseForBuilding.PublisherSigners -XmlFilePath $WDACPolicyPathTEMP } if ($Null -ne $DataToUseForBuilding.CompleteHashes -and $DataToUseForBuilding.CompleteHashes.Count -gt 0) { - Write-Verbose -Message 'Creating Hash Level rules' + [WDACConfig.Logger]::Write('Creating Hash Level rules') New-HashLevelRules -Hashes $DataToUseForBuilding.CompleteHashes -XmlFilePath $WDACPolicyPathTEMP } @@ -511,14 +506,14 @@ Function ConvertTo-WDACPolicy { $CurrentStep++ Write-Progress -Id 30 -Activity 'Performing merge operations' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Merging the Hash Level rules' + [WDACConfig.Logger]::Write('Merging the Hash Level rules') Remove-AllowElements_Semantic -Path $WDACPolicyPathTEMP Close-EmptyXmlNodes_Semantic -XmlFilePath $WDACPolicyPathTEMP $CurrentStep++ Write-Progress -Id 30 -Activity 'Making sure there are no duplicates' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Merging the Signer Level rules' + [WDACConfig.Logger]::Write('Merging the Signer Level rules') Remove-DuplicateFileAttrib_Semantic -XmlFilePath $WDACPolicyPathTEMP # 2 passes are necessary @@ -536,7 +531,7 @@ Function ConvertTo-WDACPolicy { { $null -ne $BasePolicyFile } { - Write-Verbose -Message 'ConvertTo-WDACPolicy: Associating the Supplemental policy with the user input base policy' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Associating the Supplemental policy with the user input base policy') # Objectify the user input base policy file to extract its Base policy ID $InputXMLObj = [System.Xml.XmlDocument](Get-Content -Path $BasePolicyFile) @@ -547,20 +542,20 @@ Function ConvertTo-WDACPolicy { # Configure policy rule options Set-CiRuleOptions -FilePath $WDACPolicyPath -Template Supplemental - Write-Verbose -Message 'ConvertTo-WDACPolicy: Copying the policy file to the User Config directory' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Copying the policy file to the User Config directory') Copy-Item -Path $WDACPolicyPath -Destination ([WDACConfig.GlobalVars]::UserConfigDir) -Force if ($Deploy) { $null = ConvertFrom-CIPolicy -XmlFilePath $WDACPolicyPath -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") - Write-Verbose -Message 'ConvertTo-WDACPolicy: Deploying the Supplemental policy' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Deploying the Supplemental policy') $null = &'C:\Windows\System32\CiTool.exe' --update-policy (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") -json } } { $null -ne $BasePolicyGUID } { - Write-Verbose -Message 'ConvertTo-WDACPolicy: Assigning the user input GUID to the base policy ID of the supplemental policy' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Assigning the user input GUID to the base policy ID of the supplemental policy') [System.String]$SupplementalPolicyID = Set-CIPolicyIdInfo -FilePath $WDACPolicyPath -PolicyName $SuppPolicyName -SupplementsBasePolicyID $BasePolicyGUID -ResetPolicyID [System.String]$SupplementalPolicyID = $SupplementalPolicyID.Substring(11) @@ -568,19 +563,19 @@ Function ConvertTo-WDACPolicy { # Configure policy rule options Set-CiRuleOptions -FilePath $WDACPolicyPath -Template Supplemental - Write-Verbose -Message 'ConvertTo-WDACPolicy: Copying the policy file to the User Config directory' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Copying the policy file to the User Config directory') Copy-Item -Path $WDACPolicyPath -Destination ([WDACConfig.GlobalVars]::UserConfigDir) -Force if ($Deploy) { $null = ConvertFrom-CIPolicy -XmlFilePath $WDACPolicyPath -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") - Write-Verbose -Message 'ConvertTo-WDACPolicy: Deploying the Supplemental policy' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Deploying the Supplemental policy') $null = &'C:\Windows\System32\CiTool.exe' --update-policy (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") -json } } { $null -ne $PolicyToAddLogsTo } { - Write-Verbose -Message 'ConvertTo-WDACPolicy: Adding the logs to the policy that user selected' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Adding the logs to the policy that user selected') $MacrosBackup = Checkpoint-Macros -XmlFilePathIn $PolicyToAddLogsTo -Backup @@ -597,14 +592,14 @@ Function ConvertTo-WDACPolicy { Set-HVCIOptions -Strict -FilePath $PolicyToAddLogsTo if ($null -ne $MacrosBackup) { - Write-Verbose -Message 'Restoring the Macros in the policy' + [WDACConfig.Logger]::Write('Restoring the Macros in the policy') Checkpoint-Macros -XmlFilePathOut $PolicyToAddLogsTo -Restore -MacrosBackup $MacrosBackup } if ($Deploy) { $null = ConvertFrom-CIPolicy -XmlFilePath $PolicyToAddLogsTo -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$($InputXMLObj.SiPolicy.PolicyID).cip") - Write-Verbose -Message 'ConvertTo-WDACPolicy: Deploying the policy that user selected to add the logs to' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Deploying the policy that user selected to add the logs to') $null = &'C:\Windows\System32\CiTool.exe' --update-policy (Join-Path -Path $StagingArea -ChildPath "$($InputXMLObj.SiPolicy.PolicyID).cip") -json } @@ -632,13 +627,13 @@ Function ConvertTo-WDACPolicy { $CurrentStep++ Write-Progress -Id 31 -Activity 'Optimizing the MDE CSV data' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Optimizing the MDE CSV data' + [WDACConfig.Logger]::Write('Optimizing the MDE CSV data') [System.Collections.Hashtable[]]$OptimizedCSVData = Optimize-MDECSVData -CSVPath $MDEAHLogs -StagingArea $StagingArea $CurrentStep++ Write-Progress -Id 31 -Activity 'Identifying the correlated data in the MDE CSV data' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Identifying the correlated data in the MDE CSV data' + [WDACConfig.Logger]::Write('Identifying the correlated data in the MDE CSV data') if (($null -eq $OptimizedCSVData) -or ($OptimizedCSVData.Count -eq 0)) { Write-ColorfulTextWDACConfig -Color HotPink -InputText 'No valid MDE Advanced Hunting logs available. Exiting...' @@ -681,7 +676,7 @@ Function ConvertTo-WDACPolicy { $CurrentStep++ Write-Progress -Id 31 -Activity 'Displaying the MDE Advanced Hunting logs in a GUI' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Displaying the MDE Advanced Hunting logs in a GUI' + [WDACConfig.Logger]::Write('Displaying the MDE Advanced Hunting logs in a GUI') [PSCustomObject[]]$SelectMDEAHLogs = $MDEAHLogsToDisplay | Out-GridView -OutputMode Multiple -Title "Displaying $($MDEAHLogsToDisplay.count) Microsoft Defender for Endpoint Advanced Hunting Logs" if (($null -eq $SelectMDEAHLogs) -or ($SelectMDEAHLogs.Count -eq 0)) { @@ -695,31 +690,31 @@ Function ConvertTo-WDACPolicy { # Define the path where the final MDE AH XML policy file will be saved [System.IO.FileInfo]$OutputPolicyPathMDEAH = Join-Path -Path $StagingArea -ChildPath "$SuppPolicyName.xml" - Write-Verbose -Message 'Copying the template policy to the staging area' + [WDACConfig.Logger]::Write('Copying the template policy to the staging area') Copy-Item -LiteralPath 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowAll.xml' -Destination $OutputPolicyPathMDEAH -Force - Write-Verbose -Message 'Emptying the policy file in preparation for the new data insertion' + [WDACConfig.Logger]::Write('Emptying the policy file in preparation for the new data insertion') Clear-CiPolicy_Semantic -Path $OutputPolicyPathMDEAH $CurrentStep++ Write-Progress -Id 31 -Activity 'Building the Signer and Hash objects from the selected MDE AH logs' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Building the Signer and Hash objects from the selected MDE AH logs' + [WDACConfig.Logger]::Write('Building the Signer and Hash objects from the selected MDE AH logs') [WDACConfig.FileBasedInfoPackage]$DataToUseForBuilding = [WDACConfig.SignerAndHashBuilder]::BuildSignerAndHashObjects((ConvertTo-HashtableArray $SelectMDEAHLogs), 'MDEAH', $Level, $false) $CurrentStep++ Write-Progress -Id 31 -Activity 'Creating rules for different levels' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) if ($Null -ne $DataToUseForBuilding.FilePublisherSigners -and $DataToUseForBuilding.FilePublisherSigners.Count -gt 0) { - Write-Verbose -Message 'Creating File Publisher Level rules' + [WDACConfig.Logger]::Write('Creating File Publisher Level rules') New-FilePublisherLevelRules -FilePublisherSigners $DataToUseForBuilding.FilePublisherSigners -XmlFilePath $OutputPolicyPathMDEAH } if ($Null -ne $DataToUseForBuilding.PublisherSigners -and $DataToUseForBuilding.PublisherSigners.Count -gt 0) { - Write-Verbose -Message 'Creating Publisher Level rules' + [WDACConfig.Logger]::Write('Creating Publisher Level rules') New-PublisherLevelRules -PublisherSigners $DataToUseForBuilding.PublisherSigners -XmlFilePath $OutputPolicyPathMDEAH } if ($Null -ne $DataToUseForBuilding.CompleteHashes -and $DataToUseForBuilding.CompleteHashes.Count -gt 0) { - Write-Verbose -Message 'Creating Hash Level rules' + [WDACConfig.Logger]::Write('Creating Hash Level rules') New-HashLevelRules -Hashes $DataToUseForBuilding.CompleteHashes -XmlFilePath $OutputPolicyPathMDEAH } @@ -728,7 +723,7 @@ Function ConvertTo-WDACPolicy { $CurrentStep++ Write-Progress -Id 31 -Activity 'Merging the Hash Level rules' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Merging the Hash Level rules' + [WDACConfig.Logger]::Write('Merging the Hash Level rules') Remove-AllowElements_Semantic -Path $OutputPolicyPathMDEAH Close-EmptyXmlNodes_Semantic -XmlFilePath $OutputPolicyPathMDEAH @@ -737,7 +732,7 @@ Function ConvertTo-WDACPolicy { $CurrentStep++ Write-Progress -Id 31 -Activity 'Merging the Signer Level rules' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Merging the Signer Level rules' + [WDACConfig.Logger]::Write('Merging the Signer Level rules') Remove-DuplicateFileAttrib_Semantic -XmlFilePath $OutputPolicyPathMDEAH $CurrentStep++ @@ -767,7 +762,7 @@ Function ConvertTo-WDACPolicy { { $null -ne $BasePolicyFile } { - Write-Verbose -Message 'ConvertTo-WDACPolicy: Associating the Supplemental policy with the user input base policy' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Associating the Supplemental policy with the user input base policy') # Objectify the user input base policy file to extract its Base policy ID $InputXMLObj = [System.Xml.XmlDocument](Get-Content -Path $BasePolicyFile) @@ -778,20 +773,20 @@ Function ConvertTo-WDACPolicy { # Configure policy rule options Set-CiRuleOptions -FilePath $OutputPolicyPathMDEAH -Template Supplemental - Write-Verbose -Message 'ConvertTo-WDACPolicy: Copying the policy file to the User Config directory' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Copying the policy file to the User Config directory') Copy-Item -Path $OutputPolicyPathMDEAH -Destination ([WDACConfig.GlobalVars]::UserConfigDir) -Force if ($Deploy) { $null = ConvertFrom-CIPolicy -XmlFilePath $OutputPolicyPathMDEAH -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") - Write-Verbose -Message 'ConvertTo-WDACPolicy: Deploying the Supplemental policy' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Deploying the Supplemental policy') $null = &'C:\Windows\System32\CiTool.exe' --update-policy (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") -json } } { $null -ne $BasePolicyGUID } { - Write-Verbose -Message 'ConvertTo-WDACPolicy: Assigning the user input GUID to the base policy ID of the supplemental policy' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Assigning the user input GUID to the base policy ID of the supplemental policy') [System.String]$SupplementalPolicyID = Set-CIPolicyIdInfo -FilePath $OutputPolicyPathMDEAH -PolicyName $SuppPolicyName -SupplementsBasePolicyID $BasePolicyGUID -ResetPolicyID [System.String]$SupplementalPolicyID = $SupplementalPolicyID.Substring(11) @@ -799,19 +794,19 @@ Function ConvertTo-WDACPolicy { # Configure policy rule options Set-CiRuleOptions -FilePath $OutputPolicyPathMDEAH -Template Supplemental - Write-Verbose -Message 'ConvertTo-WDACPolicy: Copying the policy file to the User Config directory' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Copying the policy file to the User Config directory') Copy-Item -Path $OutputPolicyPathMDEAH -Destination ([WDACConfig.GlobalVars]::UserConfigDir) -Force if ($Deploy) { $null = ConvertFrom-CIPolicy -XmlFilePath $OutputPolicyPathMDEAH -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") - Write-Verbose -Message 'ConvertTo-WDACPolicy: Deploying the Supplemental policy' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Deploying the Supplemental policy') $null = &'C:\Windows\System32\CiTool.exe' --update-policy (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") -json } } { $null -ne $PolicyToAddLogsTo } { - Write-Verbose -Message 'ConvertTo-WDACPolicy: Adding the logs to the policy that user selected' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Adding the logs to the policy that user selected') $MacrosBackup = Checkpoint-Macros -XmlFilePathIn $PolicyToAddLogsTo -Backup @@ -828,20 +823,20 @@ Function ConvertTo-WDACPolicy { Set-HVCIOptions -Strict -FilePath $PolicyToAddLogsTo if ($null -ne $MacrosBackup) { - Write-Verbose -Message 'Restoring the Macros in the policy' + [WDACConfig.Logger]::Write('Restoring the Macros in the policy') Checkpoint-Macros -XmlFilePathOut $PolicyToAddLogsTo -Restore -MacrosBackup $MacrosBackup } if ($Deploy) { $null = ConvertFrom-CIPolicy -XmlFilePath $PolicyToAddLogsTo -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$($InputXMLObj.SiPolicy.PolicyID).cip") - Write-Verbose -Message 'ConvertTo-WDACPolicy: Deploying the policy that user selected to add the MDE AH logs to' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Deploying the policy that user selected to add the MDE AH logs to') $null = &'C:\Windows\System32\CiTool.exe' --update-policy (Join-Path -Path $StagingArea -ChildPath "$($InputXMLObj.SiPolicy.PolicyID).cip") -json } } Default { - Write-Verbose -Message 'ConvertTo-WDACPolicy: Copying the policy file to the User Config directory' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Copying the policy file to the User Config directory') Set-CiRuleOptions -FilePath $OutputPolicyPathMDEAH -Template Supplemental Copy-Item -Path $OutputPolicyPathMDEAH -Destination ([WDACConfig.GlobalVars]::UserConfigDir) -Force } @@ -899,7 +894,7 @@ Function ConvertTo-WDACPolicy { # Display the logs in a grid view using the build-in cmdlet $SelectedLogs = $EventsToDisplay | Out-GridView -OutputMode Multiple -Title "Displaying $($EventsToDisplay.count) Code Integrity Logs of $LogType type(s)" - Write-Verbose -Message "ConvertTo-WDACPolicy: Selected logs count: $($SelectedLogs.count)" + [WDACConfig.Logger]::Write("ConvertTo-WDACPolicy: Selected logs count: $($SelectedLogs.count)") if (($null -eq $SelectedLogs) -or ( $SelectedLogs.Count -eq 0)) { Write-ColorfulTextWDACConfig -Color HotPink -InputText 'No logs were selected to create a WDAC policy from. Exiting...' @@ -909,28 +904,28 @@ Function ConvertTo-WDACPolicy { # Define the path where the final Evtx XML policy file will be saved [System.IO.FileInfo]$OutputPolicyPathEVTX = Join-Path -Path $StagingArea -ChildPath "$SuppPolicyName.xml" - Write-Verbose -Message 'Copying the template policy to the staging area' + [WDACConfig.Logger]::Write('Copying the template policy to the staging area') Copy-Item -LiteralPath 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowAll.xml' -Destination $OutputPolicyPathEVTX -Force - Write-Verbose -Message 'Emptying the policy file in preparation for the new data insertion' + [WDACConfig.Logger]::Write('Emptying the policy file in preparation for the new data insertion') Clear-CiPolicy_Semantic -Path $OutputPolicyPathEVTX $CurrentStep++ Write-Progress -Id 32 -Activity 'Building Signers and file rule' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Building the Signer and Hash objects from the selected Evtx logs' + [WDACConfig.Logger]::Write('Building the Signer and Hash objects from the selected Evtx logs') [WDACConfig.FileBasedInfoPackage]$DataToUseForBuilding = [WDACConfig.SignerAndHashBuilder]::BuildSignerAndHashObjects((ConvertTo-HashtableArray $SelectedLogs), 'EVTX', $Level, $false) if ($Null -ne $DataToUseForBuilding.FilePublisherSigners -and $DataToUseForBuilding.FilePublisherSigners.Count -gt 0) { - Write-Verbose -Message 'Creating File Publisher Level rules' + [WDACConfig.Logger]::Write('Creating File Publisher Level rules') New-FilePublisherLevelRules -FilePublisherSigners $DataToUseForBuilding.FilePublisherSigners -XmlFilePath $OutputPolicyPathEVTX } if ($Null -ne $DataToUseForBuilding.PublisherSigners -and $DataToUseForBuilding.PublisherSigners.Count -gt 0) { - Write-Verbose -Message 'Creating Publisher Level rules' + [WDACConfig.Logger]::Write('Creating Publisher Level rules') New-PublisherLevelRules -PublisherSigners $DataToUseForBuilding.PublisherSigners -XmlFilePath $OutputPolicyPathEVTX } if ($Null -ne $DataToUseForBuilding.CompleteHashes -and $DataToUseForBuilding.CompleteHashes.Count -gt 0) { - Write-Verbose -Message 'Creating Hash Level rules' + [WDACConfig.Logger]::Write('Creating Hash Level rules') New-HashLevelRules -Hashes $DataToUseForBuilding.CompleteHashes -XmlFilePath $OutputPolicyPathEVTX } @@ -939,7 +934,7 @@ Function ConvertTo-WDACPolicy { $CurrentStep++ Write-Progress -Id 32 -Activity 'Performing merge operations' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Merging the Hash Level rules' + [WDACConfig.Logger]::Write('Merging the Hash Level rules') Remove-AllowElements_Semantic -Path $OutputPolicyPathEVTX Close-EmptyXmlNodes_Semantic -XmlFilePath $OutputPolicyPathEVTX @@ -948,7 +943,7 @@ Function ConvertTo-WDACPolicy { $CurrentStep++ Write-Progress -Id 32 -Activity 'Making sure there are no duplicates' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Merging the Signer Level rules' + [WDACConfig.Logger]::Write('Merging the Signer Level rules') Remove-DuplicateFileAttrib_Semantic -XmlFilePath $OutputPolicyPathEVTX # 2 passes are necessary @@ -967,7 +962,7 @@ Function ConvertTo-WDACPolicy { { $null -ne $BasePolicyFile } { - Write-Verbose -Message 'ConvertTo-WDACPolicy: Associating the Supplemental policy with the user input base policy' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Associating the Supplemental policy with the user input base policy') # Objectify the user input base policy file to extract its Base policy ID $InputXMLObj = [System.Xml.XmlDocument](Get-Content -Path $BasePolicyFile) @@ -978,20 +973,20 @@ Function ConvertTo-WDACPolicy { # Configure policy rule options Set-CiRuleOptions -FilePath $OutputPolicyPathEVTX -Template Supplemental - Write-Verbose -Message 'ConvertTo-WDACPolicy: Copying the policy file to the User Config directory' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Copying the policy file to the User Config directory') Copy-Item -Path $OutputPolicyPathEVTX -Destination ([WDACConfig.GlobalVars]::UserConfigDir) -Force if ($Deploy) { $null = ConvertFrom-CIPolicy -XmlFilePath $OutputPolicyPathEVTX -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") - Write-Verbose -Message 'ConvertTo-WDACPolicy: Deploying the Supplemental policy' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Deploying the Supplemental policy') $null = &'C:\Windows\System32\CiTool.exe' --update-policy (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") -json } } { $null -ne $BasePolicyGUID } { - Write-Verbose -Message 'ConvertTo-WDACPolicy: Assigning the user input GUID to the base policy ID of the supplemental policy' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Assigning the user input GUID to the base policy ID of the supplemental policy') [System.String]$SupplementalPolicyID = Set-CIPolicyIdInfo -FilePath $OutputPolicyPathEVTX -PolicyName $SuppPolicyName -SupplementsBasePolicyID $BasePolicyGUID -ResetPolicyID [System.String]$SupplementalPolicyID = $SupplementalPolicyID.Substring(11) @@ -999,19 +994,19 @@ Function ConvertTo-WDACPolicy { # Configure policy rule options Set-CiRuleOptions -FilePath $OutputPolicyPathEVTX -Template Supplemental - Write-Verbose -Message 'ConvertTo-WDACPolicy: Copying the policy file to the User Config directory' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Copying the policy file to the User Config directory') Copy-Item -Path $OutputPolicyPathEVTX -Destination ([WDACConfig.GlobalVars]::UserConfigDir) -Force if ($Deploy) { $null = ConvertFrom-CIPolicy -XmlFilePath $OutputPolicyPathEVTX -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") - Write-Verbose -Message 'ConvertTo-WDACPolicy: Deploying the Supplemental policy' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Deploying the Supplemental policy') $null = &'C:\Windows\System32\CiTool.exe' --update-policy (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") -json } } { $null -ne $PolicyToAddLogsTo } { - Write-Verbose -Message 'ConvertTo-WDACPolicy: Adding the logs to the policy that user selected' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Adding the logs to the policy that user selected') $MacrosBackup = Checkpoint-Macros -XmlFilePathIn $PolicyToAddLogsTo -Backup @@ -1028,20 +1023,20 @@ Function ConvertTo-WDACPolicy { Set-HVCIOptions -Strict -FilePath $PolicyToAddLogsTo if ($null -ne $MacrosBackup) { - Write-Verbose -Message 'Restoring the Macros in the policy' + [WDACConfig.Logger]::Write('Restoring the Macros in the policy') Checkpoint-Macros -XmlFilePathOut $PolicyToAddLogsTo -Restore -MacrosBackup $MacrosBackup } if ($Deploy) { $null = ConvertFrom-CIPolicy -XmlFilePath $PolicyToAddLogsTo -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$($InputXMLObj.SiPolicy.PolicyID).cip") - Write-Verbose -Message 'ConvertTo-WDACPolicy: Deploying the policy that user selected to add the Evtx logs to' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Deploying the policy that user selected to add the Evtx logs to') $null = &'C:\Windows\System32\CiTool.exe' --update-policy (Join-Path -Path $StagingArea -ChildPath "$($InputXMLObj.SiPolicy.PolicyID).cip") -json } } Default { - Write-Verbose -Message 'ConvertTo-WDACPolicy: Copying the policy file to the User Config directory' + [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Copying the policy file to the User Config directory') Set-CiRuleOptions -FilePath $OutputPolicyPathEVTX -Template Supplemental Copy-Item -Path $OutputPolicyPathEVTX -Destination ([WDACConfig.GlobalVars]::UserConfigDir) -Force } @@ -1059,7 +1054,7 @@ Function ConvertTo-WDACPolicy { Write-Progress -Id 31 -Activity 'Complete.' -Completed Write-Progress -Id 32 -Activity 'Complete.' -Completed - if (-NOT $Debug) { + if (![WDACConfig.GlobalVars]::DebugPreference) { Remove-Item -Path $StagingArea -Recurse -Force } } diff --git a/WDACConfig/WDACConfig Module Files/Core/Deploy-SignedWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Deploy-SignedWDACConfig.psm1 index 2dcd5f09e..dcac85a65 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Deploy-SignedWDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Deploy-SignedWDACConfig.psm1 @@ -39,19 +39,12 @@ Function Deploy-SignedWDACConfig { [Parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$SkipVersionCheck ) Begin { - [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $false - [System.Boolean]$Debug = $PSBoundParameters.Debug.IsPresent ? $true : $false [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - Write-Verbose -Message 'Importing the required sub-modules' - Import-Module -Force -FullyQualifiedName @( - "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Update-Self.psm1", - "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Get-SignTool.psm1" - ) + [WDACConfig.Logger]::Write('Importing the required sub-modules') + Import-Module -Force -FullyQualifiedName @("$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Get-SignTool.psm1") - # if -SkipVersionCheck wasn't passed, run the updater - if (-NOT $SkipVersionCheck) { Update-Self -InvocationStatement $MyInvocation.Statement } + if (-NOT $SkipVersionCheck) { Update-WDACConfigPSModule -InvocationStatement $MyInvocation.Statement } if ([WDACConfig.GlobalVars]::ConfigCIBootstrap -eq $false) { Invoke-MockConfigCIBootstrap @@ -113,68 +106,68 @@ Function Deploy-SignedWDACConfig { $CurrentStep++ Write-Progress -Id 13 -Activity 'Gathering policy details' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message "Gathering policy details from: $PolicyPath" + [WDACConfig.Logger]::Write("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 'Checking if the policy type is Supplemental and if so, removing the -Supplemental parameter from the SignerRule command' + [WDACConfig.Logger]::Write('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' + [WDACConfig.Logger]::Write('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' + [WDACConfig.Logger]::Write('UMCI policy rule option does not exist in the policy, typically for Strict kernel mode policies') Add-SignerRule -FilePath $PolicyPath -CertificatePath $CertPath -Update -Kernel } } elseif ($PolicyType -eq 'Base Policy') { - Write-Verbose -Message 'Policy type is Base' + [WDACConfig.Logger]::Write('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) { - Write-Verbose -Message 'Checking whether SignTool.exe is allowed to execute in the policy or not' + [WDACConfig.Logger]::Write('Checking whether SignTool.exe is allowed to execute in the policy or not') if (-NOT (Invoke-WDACSimulation -FilePath $SignToolPathFinal -XmlFilePath $PolicyPath -BooleanOutput -NoCatalogScanning -ThreadsCount 1 -SkipVersionCheck)) { - Write-Verbose -Message 'The policy type is base policy and it applies to user mode files, yet the policy prevents SignTool.exe from executing. As a precautionary measure, scanning and including the SignTool.exe in the policy before deployment so you can modify/remove the signed policy later from the system.' + [WDACConfig.Logger]::Write('The policy type is base policy and it applies to user mode files, yet the policy prevents SignTool.exe from executing. As a precautionary measure, scanning and including the SignTool.exe in the policy before deployment so you can modify/remove the signed policy later from the system.') - Write-Verbose -Message 'Creating a temporary folder to store the symbolic link to the SignTool.exe' + [WDACConfig.Logger]::Write('Creating a temporary folder to store the symbolic link to the SignTool.exe') [System.IO.DirectoryInfo]$SymLinksStorage = New-Item -Path (Join-Path -Path $StagingArea -ChildPath 'SymLinkStorage') -ItemType Directory -Force - Write-Verbose -Message 'Creating symbolic link to the SignTool.exe' + [WDACConfig.Logger]::Write('Creating symbolic link to the SignTool.exe') $null = New-Item -ItemType SymbolicLink -Path "$SymLinksStorage\SignTool.exe" -Target $SignToolPathFinal -Force - Write-Verbose -Message 'Scanning the SignTool.exe and generating the SignTool.xml policy' + [WDACConfig.Logger]::Write('Scanning the SignTool.exe and generating the SignTool.xml policy') New-CIPolicy -ScanPath $SymLinksStorage -Level FilePublisher -Fallback None -UserPEs -UserWriteablePaths -MultiplePolicyFormat -AllowFileNameFallbacks -FilePath "$SymLinksStorage\SignTool.xml" -PathToCatroot 'C:\Program Files\Windows Defender\Offline' [System.IO.FileInfo]$AugmentedPolicyPath = Join-Path -Path $SymLinksStorage -ChildPath $PolicyPath.Name - Write-Verbose -Message 'Merging the SignTool.xml policy with the policy being signed' + [WDACConfig.Logger]::Write('Merging the SignTool.xml policy with the policy being signed') # First policy in the array should always be the main one so that its settings will be used in the merged policy $null = Merge-CIPolicy -PolicyPaths $PolicyPath, "$SymLinksStorage\SignTool.xml" -OutputFilePath $AugmentedPolicyPath - Write-Verbose -Message 'Making sure policy rule options stay the same after merging the policies' + [WDACConfig.Logger]::Write('Making sure policy rule options stay the same after merging the policies') [WDACConfig.CiPolicyUtility]::CopyCiRules($PolicyPath, $AugmentedPolicyPath) - Write-Verbose -Message 'Replacing the new policy with the old one' + [WDACConfig.Logger]::Write('Replacing the new policy with the old one') Move-Item -Path $AugmentedPolicyPath -Destination $PolicyPath -Force } else { - Write-Verbose -Message 'The base policy allows SignTool.exe to execute, no need to scan and include it in the policy' + [WDACConfig.Logger]::Write('The base policy allows SignTool.exe to execute, no need to scan and include it in the policy') } 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' + [WDACConfig.Logger]::Write('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 } } @@ -189,14 +182,14 @@ Function Deploy-SignedWDACConfig { [system.io.FileInfo]$PolicyCIPPath = Join-Path -Path $StagingArea -ChildPath "$PolicyID.cip" - Write-Verbose -Message 'Converting the policy to .CIP file' + [WDACConfig.Logger]::Write('Converting the policy to .CIP file') $null = ConvertFrom-CIPolicy -XmlFilePath $PolicyPath -BinaryFilePath $PolicyCIPPath $CurrentStep++ Write-Progress -Id 13 -Activity 'Signing the policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) [WDACConfig.CodeIntegritySigner]::InvokeCiSigning($PolicyCIPPath, $SignToolPathFinal, $CertCN) - Write-Verbose -Message 'Renaming the .p7 file to .cip' + [WDACConfig.Logger]::Write('Renaming the .p7 file to .cip') Move-Item -LiteralPath "$StagingArea\$PolicyID.cip.p7" -Destination $PolicyCIPPath -Force if ($Deploy) { @@ -207,7 +200,7 @@ Function Deploy-SignedWDACConfig { # Prompt for confirmation before proceeding if ($PSCmdlet.ShouldProcess('This PC', 'Deploying the signed policy')) { - Write-Verbose -Message 'Deploying the policy' + [WDACConfig.Logger]::Write('Deploying the policy') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $PolicyCIPPath -json Write-ColorfulTextWDACConfig -Color Lavender -InputText 'policy with the following details has been Signed and Deployed in Enforced Mode:' @@ -222,24 +215,24 @@ Function Deploy-SignedWDACConfig { if (($PolicyName -like '*Strict Kernel mode policy Enforced*')) { - Write-Verbose -Message 'The deployed policy is Strict Kernel mode' + [WDACConfig.Logger]::Write('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' + [WDACConfig.Logger]::Write('Removing the GUID of the deployed Strict Kernel mode policy from the User Configs') $null = Remove-CommonWDACConfig -StrictKernelPolicyGUID } } } elseif (($PolicyName -like '*Strict Kernel No Flights mode policy Enforced*')) { - Write-Verbose -Message 'The deployed policy is Strict Kernel No Flights mode' + [WDACConfig.Logger]::Write('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' + [WDACConfig.Logger]::Write('Removing the GUID of the deployed Strict Kernel No Flights mode policy from the User Configs') $null = Remove-CommonWDACConfig -StrictKernelNoFlightRootsPolicyGUID } } @@ -262,7 +255,7 @@ Function Deploy-SignedWDACConfig { throw $_ } Finally { - if (-NOT $Debug) { + if (![WDACConfig.GlobalVars]::DebugPreference) { Remove-Item -Path $StagingArea -Recurse -Force } } diff --git a/WDACConfig/WDACConfig Module Files/Core/Edit-SignedWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Edit-SignedWDACConfig.psm1 index 86bd6cbe3..519dbdb1b 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Edit-SignedWDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Edit-SignedWDACConfig.psm1 @@ -130,17 +130,12 @@ Function Edit-SignedWDACConfig { [System.Management.Automation.SwitchParameter]$SkipVersionCheck ) Begin { - [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $false - [System.Boolean]$Debug = $PSBoundParameters.Debug.IsPresent ? $true : $false [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - Write-Verbose -Message 'Importing the required sub-modules' + [WDACConfig.Logger]::Write('Importing the required sub-modules') Import-Module -Force -FullyQualifiedName @( "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Get-SignTool.psm1", - "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Update-Self.psm1", "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Receive-CodeIntegrityLogs.psm1", - "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\New-SnapBackGuarantee.psm1", "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Set-LogPropertiesVisibility.psm1", "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Select-LogProperties.psm1", "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Test-KernelProtectedFiles.psm1" @@ -148,8 +143,7 @@ Function Edit-SignedWDACConfig { $ModulesToImport += ([WDACConfig.FileUtility]::GetFilesFast("$([WDACConfig.GlobalVars]::ModuleRootPath)\XMLOps", $null, '.psm1')).FullName Import-Module -FullyQualifiedName $ModulesToImport -Force - # if -SkipVersionCheck wasn't passed, run the updater - if (-NOT $SkipVersionCheck) { Update-Self -InvocationStatement $MyInvocation.Statement } + if (-NOT $SkipVersionCheck) { Update-WDACConfigPSModule -InvocationStatement $MyInvocation.Statement } if ([WDACConfig.GlobalVars]::ConfigCIBootstrap -eq $false) { Invoke-MockConfigCIBootstrap @@ -230,7 +224,7 @@ Function Edit-SignedWDACConfig { [WDACConfig.EventLogUtility]::SetLogSize($LogSize ?? 0) # 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' + [WDACConfig.Logger]::Write('Getting the current date') [System.DateTime]$Date = Get-Date # A concurrent hashtable that holds the Policy XML files in its values - This array will eventually be used to create the final Supplemental policy @@ -243,22 +237,22 @@ Function Edit-SignedWDACConfig { $CurrentStep++ Write-Progress -Id 15 -Activity 'Creating the Audit mode policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Creating a copy of the original policy in the Staging Area so that the original one will be unaffected' + [WDACConfig.Logger]::Write('Creating a copy of the original policy in the Staging Area so that the original one will be unaffected') Copy-Item -Path $PolicyPath -Destination $StagingArea -Force [System.IO.FileInfo]$PolicyPath = Join-Path -Path $StagingArea -ChildPath (Split-Path -Path $PolicyPath -Leaf) - Write-Verbose -Message 'Retrieving the Base policy name and ID' + [WDACConfig.Logger]::Write('Retrieving the Base policy name and ID') [System.Xml.XmlDocument]$Xml = 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 - Write-Verbose -Message 'Creating Audit Mode CIP' + [WDACConfig.Logger]::Write('Creating Audit Mode CIP') [System.IO.FileInfo]$AuditModeCIPPath = Join-Path -Path $StagingArea -ChildPath 'AuditMode.cip' Set-CiRuleOptions -FilePath $PolicyPath -RulesToRemove 'Enabled:Unsigned System Integrity Policy' -RulesToAdd 'Enabled:Audit Mode' $null = ConvertFrom-CIPolicy -XmlFilePath $PolicyPath -BinaryFilePath $AuditModeCIPPath - Write-Verbose -Message 'Creating Enforced Mode CIP' + [WDACConfig.Logger]::Write('Creating Enforced Mode CIP') [System.IO.FileInfo]$EnforcedModeCIPPath = Join-Path -Path $StagingArea -ChildPath 'EnforcedMode.cip' Set-CiRuleOptions -FilePath $PolicyPath -RulesToRemove 'Enabled:Unsigned System Integrity Policy', 'Enabled:Audit Mode' $null = ConvertFrom-CIPolicy -XmlFilePath $PolicyPath -BinaryFilePath $EnforcedModeCIPPath @@ -268,23 +262,23 @@ Function Edit-SignedWDACConfig { [WDACConfig.CodeIntegritySigner]::InvokeCiSigning($CIP, $SignToolPathFinal, $CertCN) } - Write-Verbose -Message 'Renaming the signed CIPs to remove the .p7 extension' + [WDACConfig.Logger]::Write('Renaming the signed CIPs to remove the .p7 extension') Move-Item -LiteralPath "$StagingArea\AuditMode.cip.p7" -Destination $AuditModeCIPPath -Force Move-Item -LiteralPath "$StagingArea\EnforcedMode.cip.p7" -Destination $EnforcedModeCIPPath -Force #Region Snap-Back-Guarantee - Write-Verbose -Message 'Creating Enforced Mode SnapBack guarantee' - New-SnapBackGuarantee -Path $EnforcedModeCIPPath + [WDACConfig.Logger]::Write('Creating Enforced Mode SnapBack guarantee') + [WDACConfig.SnapBackGuarantee]::New($EnforcedModeCIPPath.FullName) $CurrentStep++ Write-Progress -Id 15 -Activity 'Deploying the Audit mode policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Deploying the Audit mode CIP' + [WDACConfig.Logger]::Write('Deploying the Audit mode CIP') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $AuditModeCIPPath -json - Write-Verbose -Message 'The Base policy with the following details has been Re-Signed and Re-Deployed in Audit Mode:' - Write-Verbose -Message "PolicyName = $PolicyName" - Write-Verbose -Message "PolicyGUID = $PolicyID" + [WDACConfig.Logger]::Write('The Base policy with the following details has been Re-Signed and Re-Deployed in Audit Mode:') + [WDACConfig.Logger]::Write("PolicyName = $PolicyName") + [WDACConfig.Logger]::Write("PolicyGUID = $PolicyID") #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 @@ -307,14 +301,14 @@ Function Edit-SignedWDACConfig { $CurrentStep++ Write-Progress -Id 15 -Activity 'Redeploying the Base policy in Enforced Mode' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Debug -Message 'Finally Block Running' + [WDACConfig.Logger]::Write('Finally Block Running') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $EnforcedModeCIPPath -json - Write-Verbose -Message 'The Base policy with the following details has been Re-Signed and Re-Deployed in Enforced Mode:' - Write-Verbose -Message "PolicyName = $PolicyName" - Write-Verbose -Message "PolicyGUID = $PolicyID" + [WDACConfig.Logger]::Write('The Base policy with the following details has been Re-Signed and Re-Deployed in Enforced Mode:') + [WDACConfig.Logger]::Write("PolicyName = $PolicyName") + [WDACConfig.Logger]::Write("PolicyGUID = $PolicyID") - Write-Verbose -Message 'Removing the SnapBack guarantee because the base policy has been successfully re-enforced' + [WDACConfig.Logger]::Write('Removing the SnapBack guarantee because the base policy has been successfully re-enforced') Unregister-ScheduledTask -TaskName 'EnforcedModeSnapBack' -Confirm:$false Remove-Item -Path (Join-Path -Path ([WDACConfig.GlobalVars]::UserConfigDir) -ChildPath 'EnforcedModeSnapBack.cmd') -Force @@ -340,7 +334,7 @@ Function Edit-SignedWDACConfig { [System.Boolean]$HasSelectedLogs = $false if ($ProgramsPaths) { - Write-Verbose -Message 'Here are the paths you selected:' + [WDACConfig.Logger]::Write('Here are the paths you selected:') if ($Verbose) { foreach ($Path in $ProgramsPaths) { $Path.FullName @@ -351,13 +345,11 @@ Function Edit-SignedWDACConfig { # Start Async job for detecting ECC-Signed files among the user-selected directories [System.Management.Automation.Job2]$ECCSignedDirectoriesJob = Start-ThreadJob -ScriptBlock { - Param ($PolicyXMLFilesArray, $ParentVerbosePreference, $ParentDebugPreference) + Param ($PolicyXMLFilesArray) $global:ProgressPreference = 'SilentlyContinue' $global:ErrorActionPreference = 'Stop' - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - Import-Module -Force -FullyQualifiedName "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Test-ECCSignedFiles.psm1" [System.IO.FileInfo]$ECCSignedFilesTempPolicyUserDirs = Join-Path -Path $using:StagingArea -ChildPath 'ECCSignedFilesTempPolicyUserDirs.xml' $ECCSignedFilesTempPolicy = Test-ECCSignedFiles -Directory $using:ProgramsPaths -Process -ECCSignedFilesTempPolicy $ECCSignedFilesTempPolicyUserDirs @@ -365,7 +357,7 @@ Function Edit-SignedWDACConfig { if ($ECCSignedFilesTempPolicy -as [System.IO.FileInfo]) { [System.Void]$PolicyXMLFilesArray.TryAdd('Hash Rules For ECC Signed Files in User selected directories', $ECCSignedFilesTempPolicy) } - } -StreamingHost $Host -ArgumentList $PolicyXMLFilesArray, $VerbosePreference, $DebugPreference + } -StreamingHost $Host -ArgumentList $PolicyXMLFilesArray $DirectoryScanJob = Start-ThreadJob -InitializationScript { $global:ProgressPreference = 'SilentlyContinue' @@ -376,11 +368,10 @@ Function Edit-SignedWDACConfig { Remove-Item -LiteralPath ".\$RandomGUID.xml" -Force } } -ScriptBlock { - Param ($ProgramsPaths, $StagingArea, $PolicyXMLFilesArray, $ParentVerbosePreference, $ParentDebugPreference) + Param ($ProgramsPaths, $StagingArea, $PolicyXMLFilesArray) $global:ProgressPreference = 'SilentlyContinue' - $VerbosePreference = $ParentVerbosePreference - # Write-Verbose -Message 'Scanning each of the folder paths that user selected' + # [WDACConfig.Logger]::Write('Scanning each of the folder paths that user selected') for ($i = 0; $i -lt $ProgramsPaths.Count; $i++) { @@ -399,20 +390,20 @@ Function Edit-SignedWDACConfig { if ($using:NoScript) { $UserInputProgramFoldersPolicyMakerHashTable['NoScript'] = $true } if (!$using:NoUserPEs) { $UserInputProgramFoldersPolicyMakerHashTable['UserPEs'] = $true } - # Write-Verbose -Message "Currently scanning: $($ProgramsPaths[$i])" + # [WDACConfig.Logger]::Write("Currently scanning: $($ProgramsPaths[$i])") New-CIPolicy @UserInputProgramFoldersPolicyMakerHashTable [System.Void]$PolicyXMLFilesArray.TryAdd("$($ProgramsPaths[$i]) Scan Results", "$StagingArea\ProgramDir_ScanResults$($i).xml") } - if ($ParentDebugPreference -eq 'Continue') { + if ([WDACConfig.GlobalVars]::DebugPreference) { Write-Output -InputObject 'The directories were scanned with the following configuration' Write-Output -InputObject $($UserInputProgramFoldersPolicyMakerHashTable | Format-Table) } - } -StreamingHost $Host -ArgumentList $ProgramsPaths, $StagingArea, $PolicyXMLFilesArray, $VerbosePreference, $DebugPreference + } -StreamingHost $Host -ArgumentList $ProgramsPaths, $StagingArea, $PolicyXMLFilesArray } else { - Write-Verbose -Message 'No directory path was selected.' + [WDACConfig.Logger]::Write('No directory path was selected.') } [System.Collections.Hashtable[]]$AuditEventLogsProcessingResults = Receive-CodeIntegrityLogs -Date $Date -Type 'Audit' @@ -421,7 +412,7 @@ Function Edit-SignedWDACConfig { $HasAuditLogs = $true } else { - Write-Verbose -Message 'No audit log events were generated during the audit period.' + [WDACConfig.Logger]::Write('No audit log events were generated during the audit period.') } if ($HasAuditLogs -and $HasFolderPaths) { @@ -429,7 +420,7 @@ Function Edit-SignedWDACConfig { } if (($null -ne $OutsideFiles) -and ($OutsideFiles.count -ne 0)) { - Write-Verbose -Message "$($OutsideFiles.count) file(s) have been found in event viewer logs that don't exist in any of the folder paths you selected." + [WDACConfig.Logger]::Write("$($OutsideFiles.count) file(s) have been found in event viewer logs that don't exist in any of the folder paths you selected.") $HasExtraFiles = $true } @@ -467,13 +458,11 @@ Function Edit-SignedWDACConfig { # Start Async job for detecting ECC-Signed files among the user-selected audit logs [System.Management.Automation.Job2]$ECCSignedAuditLogsJob = Start-ThreadJob -ScriptBlock { - Param ($PolicyXMLFilesArray, $ParentVerbosePreference, $ParentDebugPreference) + Param ($PolicyXMLFilesArray) $global:ProgressPreference = 'SilentlyContinue' $global:ErrorActionPreference = 'Stop' - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - Import-Module -Force -FullyQualifiedName "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Test-ECCSignedFiles.psm1" [System.IO.FileInfo]$ECCSignedFilesTempPolicyAuditLogs = Join-Path -Path $using:StagingArea -ChildPath 'ECCSignedFilesTempPolicyAuditLogs.xml' $ECCSignedFilesTempPolicy = Test-ECCSignedFiles -File $($using:SelectedLogs).'Full Path' -Process -ECCSignedFilesTempPolicy $ECCSignedFilesTempPolicyAuditLogs @@ -481,18 +470,18 @@ Function Edit-SignedWDACConfig { if ($ECCSignedFilesTempPolicy -as [System.IO.FileInfo]) { [System.Void]$PolicyXMLFilesArray.TryAdd('Hash Rules For ECC Signed Files in User selected Audit Logs', $ECCSignedFilesTempPolicy) } - } -StreamingHost $Host -ArgumentList $PolicyXMLFilesArray, $VerbosePreference, $DebugPreference + } -StreamingHost $Host -ArgumentList $PolicyXMLFilesArray $KernelProtectedFileLogs = Test-KernelProtectedFiles -Logs $SelectedLogs if ($null -ne $KernelProtectedFileLogs) { - Write-Verbose -Message "Kernel protected files count: $($KernelProtectedFileLogs.count)" + [WDACConfig.Logger]::Write("Kernel protected files count: $($KernelProtectedFileLogs.count)") - Write-Verbose -Message 'Copying the template policy to the staging area' + [WDACConfig.Logger]::Write('Copying the template policy to the staging area') Copy-Item -LiteralPath 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowAll.xml' -Destination $KernelProtectedPolicyPath -Force - Write-Verbose -Message 'Emptying the policy file in preparation for the new data insertion' + [WDACConfig.Logger]::Write('Emptying the policy file in preparation for the new data insertion') Clear-CiPolicy_Semantic -Path $KernelProtectedPolicyPath # Find the kernel protected files that have PFN property @@ -507,8 +496,8 @@ Function Edit-SignedWDACConfig { # Add the Kernel protected files policy to the list of policies to merge [System.Void]$PolicyXMLFilesArray.TryAdd('Kernel Protected files policy', $KernelProtectedPolicyPath) - Write-Verbose -Message "Kernel protected files with PFN property: $($KernelProtectedFileLogsWithPFN.count)" - Write-Verbose -Message "Kernel protected files without PFN property: $($KernelProtectedFileLogs.count - $KernelProtectedFileLogsWithPFN.count)" + [WDACConfig.Logger]::Write("Kernel protected files with PFN property: $($KernelProtectedFileLogsWithPFN.count)") + [WDACConfig.Logger]::Write("Kernel protected files without PFN property: $($KernelProtectedFileLogs.count - $KernelProtectedFileLogsWithPFN.count)") # Removing the logs that were used to create PFN rules, from the rest of the logs $SelectedLogs = foreach ($Item in $SelectedLogs) { @@ -518,34 +507,34 @@ Function Edit-SignedWDACConfig { } } - Write-Verbose -Message 'Copying the template policy to the staging area' + [WDACConfig.Logger]::Write('Copying the template policy to the staging area') Copy-Item -LiteralPath 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowAll.xml' -Destination $WDACPolicyPathTEMP -Force - Write-Verbose -Message 'Emptying the policy file in preparation for the new data insertion' + [WDACConfig.Logger]::Write('Emptying the policy file in preparation for the new data insertion') Clear-CiPolicy_Semantic -Path $WDACPolicyPathTEMP - Write-Verbose -Message 'Building the Signer and Hash objects from the selected logs' + [WDACConfig.Logger]::Write('Building the Signer and Hash objects from the selected logs') [WDACConfig.FileBasedInfoPackage]$DataToUseForBuilding = [WDACConfig.SignerAndHashBuilder]::BuildSignerAndHashObjects((ConvertTo-HashtableArray $SelectedLogs), 'EVTX', ($Level -eq 'FilePublisher' ? 'FilePublisher' : $Level -eq 'Publisher' ? 'Publisher' : $Level -eq 'Hash' ? 'Hash' : 'Auto'), $BoostedSecurity ? $true : $false) if ($Null -ne $DataToUseForBuilding.FilePublisherSigners -and $DataToUseForBuilding.FilePublisherSigners.Count -gt 0) { - Write-Verbose -Message 'Creating File Publisher Level rules' + [WDACConfig.Logger]::Write('Creating File Publisher Level rules') New-FilePublisherLevelRules -FilePublisherSigners $DataToUseForBuilding.FilePublisherSigners -XmlFilePath $WDACPolicyPathTEMP } if ($Null -ne $DataToUseForBuilding.PublisherSigners -and $DataToUseForBuilding.PublisherSigners.Count -gt 0) { - Write-Verbose -Message 'Creating Publisher Level rules' + [WDACConfig.Logger]::Write('Creating Publisher Level rules') New-PublisherLevelRules -PublisherSigners $DataToUseForBuilding.PublisherSigners -XmlFilePath $WDACPolicyPathTEMP } if ($Null -ne $DataToUseForBuilding.CompleteHashes -and $DataToUseForBuilding.CompleteHashes.Count -gt 0) { - Write-Verbose -Message 'Creating Hash Level rules' + [WDACConfig.Logger]::Write('Creating Hash Level rules') New-HashLevelRules -Hashes $DataToUseForBuilding.CompleteHashes -XmlFilePath $WDACPolicyPathTEMP } # MERGERS - Write-Verbose -Message 'Merging the Hash Level rules' + [WDACConfig.Logger]::Write('Merging the Hash Level rules') Remove-AllowElements_Semantic -Path $WDACPolicyPathTEMP Close-EmptyXmlNodes_Semantic -XmlFilePath $WDACPolicyPathTEMP - Write-Verbose -Message 'Merging the Signer Level rules' + [WDACConfig.Logger]::Write('Merging the Signer Level rules') Remove-DuplicateFileAttrib_Semantic -XmlFilePath $WDACPolicyPathTEMP # 2 passes are necessary @@ -584,12 +573,12 @@ Function Edit-SignedWDACConfig { # If none of the previous actions resulted in any policy XML files, exit the function if ($PolicyXMLFilesArray.Values.Count -eq 0) { - Write-Verbose -Message 'No directory path or audit logs were selected to create a supplemental policy. Exiting...' -Verbose + [WDACConfig.Logger]::Write('No directory path or audit logs were selected to create a supplemental policy. Exiting...') Return } - 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.Values | ForEach-Object -Process { Write-Verbose -Message "$_" } + [WDACConfig.Logger]::Write('The following policy xml files are going to be merged into the final Supplemental policy and be deployed on the system:') + $PolicyXMLFilesArray.Values | ForEach-Object -Process { [WDACConfig.Logger]::Write("$_") } # Merge all of the policy XML files in the array into the final Supplemental policy $CurrentStep++ @@ -602,18 +591,18 @@ Function Edit-SignedWDACConfig { $CurrentStep++ Write-Progress -Id 15 -Activity 'Creating supplemental policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Supplemental policy processing and deployment' + [WDACConfig.Logger]::Write('Supplemental policy processing and deployment') - Write-Verbose -Message 'Converting the policy to a Supplemental policy type and resetting its ID' + [WDACConfig.Logger]::Write('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' + [WDACConfig.Logger]::Write('Adding signer rule to the Supplemental policy') Add-SignerRule -FilePath $SuppPolicyPath -CertificatePath $CertPath -Update -User -Kernel Set-CiRuleOptions -FilePath $SuppPolicyPath -Template Supplemental - Write-Verbose -Message 'Setting the Supplemental policy version to 1.0.0.0' + [WDACConfig.Logger]::Write('Setting the Supplemental policy version to 1.0.0.0') Set-CIPolicyVersion -FilePath $SuppPolicyPath -Version '1.0.0.0' # Define the path for the final Supplemental policy CIP @@ -629,18 +618,18 @@ Function Edit-SignedWDACConfig { } #Endregion Boosted Security - Sandboxing - Write-Verbose -Message 'Converting the Supplemental policy to a CIP file' + [WDACConfig.Logger]::Write('Converting the Supplemental policy to a CIP file') $null = ConvertFrom-CIPolicy -XmlFilePath $SuppPolicyPath -BinaryFilePath $SupplementalCIPPath [WDACConfig.CodeIntegritySigner]::InvokeCiSigning($SupplementalCIPPath, $SignToolPathFinal, $CertCN) - Write-Verbose -Message 'Renaming the signed Supplemental policy file to remove the .p7 extension' + [WDACConfig.Logger]::Write('Renaming the signed Supplemental policy file to remove the .p7 extension') Move-Item -LiteralPath "$StagingArea\$SuppPolicyID.cip.p7" -Destination $SupplementalCIPPath -Force $CurrentStep++ Write-Progress -Id 15 -Activity 'Deploying Supplemental policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Deploying the Supplemental policy' + [WDACConfig.Logger]::Write('Deploying the Supplemental policy') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $SupplementalCIPPath -json #Endregion Supplemental-policy-processing-and-deployment @@ -658,36 +647,36 @@ Function Edit-SignedWDACConfig { Write-Progress -Id 16 -Activity 'Verifying the input files' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) #Region Input-policy-verification - Write-Verbose -Message 'Getting the IDs of the currently deployed policies on the system' + [WDACConfig.Logger]::Write('Getting the IDs of the currently deployed policies on the system') $DeployedPoliciesIDs = [System.Collections.Generic.HashSet[System.String]]::new([System.StringComparer]::InvariantCultureIgnoreCase) foreach ($Item in (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies.PolicyID) { [System.Void]$DeployedPoliciesIDs.Add("{$Item}") } - Write-Verbose -Message 'Verifying the input policy files' + [WDACConfig.Logger]::Write('Verifying the input policy files') foreach ($SuppPolicyPath in $SuppPolicyPaths) { - Write-Verbose -Message "Getting policy ID and type of: $SuppPolicyPath" + [WDACConfig.Logger]::Write("Getting policy ID and type of: $SuppPolicyPath") [System.Xml.XmlDocument]$Supplementalxml = Get-Content -Path $SuppPolicyPath [System.String]$SupplementalPolicyID = $Supplementalxml.SiPolicy.PolicyID [System.String]$SupplementalPolicyType = $Supplementalxml.SiPolicy.PolicyType # 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' + [WDACConfig.Logger]::Write('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' + [WDACConfig.Logger]::Write('Checking the deployment status of the policy') if ($DeployedPoliciesIDs -and !$DeployedPoliciesIDs.Contains($SupplementalPolicyID)) { Throw "The Selected Supplemental XML file with GUID $SupplementalPolicyID isn't deployed on the system." } } #Endregion Input-policy-verification - Write-Verbose -Message 'Backing up any possible Macros in the Supplemental policies' + [WDACConfig.Logger]::Write('Backing up any possible Macros in the Supplemental policies') $MacrosBackup = Checkpoint-Macros -XmlFilePathIn $SuppPolicyPaths -Backup $CurrentStep++ @@ -695,33 +684,33 @@ Function Edit-SignedWDACConfig { [System.IO.FileInfo]$FinalSupplementalPath = Join-Path -Path $StagingArea -ChildPath "$SuppPolicyName.xml" - Write-Verbose -Message 'Merging the Supplemental policies into a single policy file' + [WDACConfig.Logger]::Write('Merging the Supplemental policies into a single policy file') $null = Merge-CIPolicy -PolicyPaths $SuppPolicyPaths -OutputFilePath $FinalSupplementalPath # 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 $CurrentStep++ Write-Progress -Id 16 -Activity 'Removing old policies from the system' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Removing the deployed Supplemental policies that user selected from the system' + [WDACConfig.Logger]::Write('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 [System.Xml.XmlDocument]$Supplementalxml = Get-Content -Path $SuppPolicyPath [System.String]$SupplementalPolicyID = $Supplementalxml.SiPolicy.PolicyID - Write-Verbose -Message "Removing policy with ID: $SupplementalPolicyID" + [WDACConfig.Logger]::Write("Removing policy with ID: $SupplementalPolicyID") $null = &'C:\Windows\System32\CiTool.exe' --remove-policy $SupplementalPolicyID -json } $CurrentStep++ Write-Progress -Id 16 -Activity 'Configuring the final policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - 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' + [WDACConfig.Logger]::Write('Preparing the final merged Supplemental policy for deployment') + [WDACConfig.Logger]::Write('Converting the policy to a Supplemental policy type and resetting its ID') [System.String]$SuppPolicyID = Set-CIPolicyIdInfo -FilePath $FinalSupplementalPath -ResetPolicyID -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" -BasePolicyToSupplementPath $PolicyPath [System.String]$SuppPolicyID = $SuppPolicyID.Substring(11) - Write-Verbose -Message 'Adding signer rules to the Supplemental policy' + [WDACConfig.Logger]::Write('Adding signer rules to the Supplemental policy') Add-SignerRule -FilePath $FinalSupplementalPath -CertificatePath $CertPath -Update -User -Kernel Set-CiRuleOptions -FilePath $FinalSupplementalPath -RulesToRemove 'Enabled:Unsigned System Integrity Policy' @@ -730,22 +719,22 @@ Function Edit-SignedWDACConfig { [System.IO.FileInfo]$FinalSupplementalCIPPath = Join-Path -Path $StagingArea -ChildPath "$SuppPolicyID.cip" if ($null -ne $MacrosBackup) { - Write-Verbose -Message 'Restoring the Macros in the Supplemental policies' + [WDACConfig.Logger]::Write('Restoring the Macros in the Supplemental policies') Checkpoint-Macros -XmlFilePathOut $FinalSupplementalPath -Restore -MacrosBackup $MacrosBackup } - Write-Verbose -Message 'Converting the Supplemental policy to a CIP file' + [WDACConfig.Logger]::Write('Converting the Supplemental policy to a CIP file') $null = ConvertFrom-CIPolicy -XmlFilePath $FinalSupplementalPath -BinaryFilePath $FinalSupplementalCIPPath [WDACConfig.CodeIntegritySigner]::InvokeCiSigning($FinalSupplementalCIPPath, $SignToolPathFinal, $CertCN) - Write-Verbose -Message 'Renaming the signed Supplemental policy file to remove the .p7 extension' + [WDACConfig.Logger]::Write('Renaming the signed Supplemental policy file to remove the .p7 extension') Move-Item -LiteralPath "$StagingArea\$SuppPolicyID.cip.p7" -Destination $FinalSupplementalCIPPath -Force $CurrentStep++ Write-Progress -Id 16 -Activity 'Deploying the final policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Deploying the Supplemental policy' + [WDACConfig.Logger]::Write('Deploying the Supplemental policy') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalSupplementalCIPPath -json Write-ColorfulTextWDACConfig -Color TeaGreen -InputText "The Signed Supplemental policy $SuppPolicyName has been deployed on the system, replacing the old ones." @@ -755,7 +744,7 @@ Function Edit-SignedWDACConfig { # remove the old policy files at the end after ensuring the operation was successful if (!$KeepOldSupplementalPolicies) { - Write-Verbose -Message 'Removing the old policy files' + [WDACConfig.Logger]::Write('Removing the old policy files') Remove-Item -Path $SuppPolicyPaths -Force } } @@ -769,7 +758,7 @@ Function Edit-SignedWDACConfig { $CurrentStep++ Write-Progress -Id 17 -Activity 'Getting the block rules' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Getting the Use-Mode Block Rules' + [WDACConfig.Logger]::Write('Getting the Use-Mode Block Rules') # This shouldn't deploy the policy unsigned if it is already signed - requires build 24H2 features New-WDACConfig -GetUserModeBlockRules -Deploy @@ -778,7 +767,7 @@ Function Edit-SignedWDACConfig { [System.IO.FileInfo]$BasePolicyPath = Join-Path -Path $StagingArea -ChildPath 'BasePolicy.xml' - Write-Verbose -Message 'Determining the type of the new base policy' + [WDACConfig.Logger]::Write('Determining the type of the new base policy') [System.String]$Name = $null switch ($NewBasePolicyType) { @@ -786,12 +775,12 @@ Function Edit-SignedWDACConfig { 'AllowMicrosoft' { $Name = 'AllowMicrosoft' - Write-Verbose -Message "The new base policy type is $Name" + [WDACConfig.Logger]::Write("The new base policy type is $Name") - Write-Verbose -Message 'Copying the AllowMicrosoft.xml template policy file to the Staging Area' + [WDACConfig.Logger]::Write('Copying the AllowMicrosoft.xml template policy file to the Staging Area') Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowMicrosoft.xml' -Destination $BasePolicyPath -Force - Write-Verbose -Message 'Setting the policy name' + [WDACConfig.Logger]::Write('Setting the policy name') Set-CIPolicyIdInfo -FilePath $BasePolicyPath -PolicyName "$Name - $(Get-Date -Format 'MM-dd-yyyy')" Set-CiRuleOptions -FilePath $BasePolicyPath -Template Base -RequireEVSigners:$RequireEVSigners @@ -799,57 +788,57 @@ Function Edit-SignedWDACConfig { 'SignedAndReputable' { $Name = 'SignedAndReputable' - Write-Verbose -Message "The new base policy type is $Name" + [WDACConfig.Logger]::Write("The new base policy type is $Name") - Write-Verbose -Message 'Copying the AllowMicrosoft.xml template policy file to the Staging Area' + [WDACConfig.Logger]::Write('Copying the AllowMicrosoft.xml template policy file to the Staging Area') Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowMicrosoft.xml' -Destination $BasePolicyPath -Force - Write-Verbose -Message 'Setting the policy name' + [WDACConfig.Logger]::Write('Setting the policy name') Set-CIPolicyIdInfo -FilePath $BasePolicyPath -PolicyName "$Name - $(Get-Date -Format 'MM-dd-yyyy')" Set-CiRuleOptions -FilePath $BasePolicyPath -Template BaseISG -RequireEVSigners:$RequireEVSigners # Configure required services for ISG authorization - Write-Verbose -Message 'Configuring required services for ISG authorization' + [WDACConfig.Logger]::Write('Configuring required services for ISG authorization') Start-Process -FilePath 'C:\Windows\System32\appidtel.exe' -ArgumentList 'start' -NoNewWindow Start-Process -FilePath 'C:\Windows\System32\sc.exe' -ArgumentList 'config', 'appidsvc', 'start= auto' -NoNewWindow } 'DefaultWindows' { $Name = 'DefaultWindows' - Write-Verbose -Message "The new base policy type is $Name" + [WDACConfig.Logger]::Write("The new base policy type is $Name") - Write-Verbose -Message 'Copying the DefaultWindows.xml template policy file to the Staging Area' + [WDACConfig.Logger]::Write('Copying the DefaultWindows.xml template policy file to the Staging Area') Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\DefaultWindows_Enforced.xml' -Destination $BasePolicyPath -Force # Allowing SignTool to be able to run after Default Windows base policy is deployed Write-ColorfulTextWDACConfig -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 Staging Area to copy SignTool.exe to it' + [WDACConfig.Logger]::Write('Creating a new folder in the Staging Area to copy SignTool.exe to it') $null = New-Item -Path (Join-Path -Path $StagingArea -ChildPath 'TemporarySignToolFile') -ItemType Directory -Force - Write-Verbose -Message 'Copying SignTool.exe to the folder in the Staging Area' + [WDACConfig.Logger]::Write('Copying SignTool.exe to the folder in the Staging Area') Copy-Item -Path $SignToolPathFinal -Destination (Join-Path -Path $StagingArea -ChildPath 'TemporarySignToolFile') -Force - Write-Verbose -Message 'Scanning the folder in the Staging Area to create a policy for SignTool.exe' + [WDACConfig.Logger]::Write('Scanning the folder in the Staging Area to create a policy for SignTool.exe') New-CIPolicy -ScanPath (Join-Path -Path $StagingArea -ChildPath 'TemporarySignToolFile') -Level FilePublisher -Fallback Hash -UserPEs -UserWriteablePaths -MultiplePolicyFormat -AllowFileNameFallbacks -FilePath (Join-Path -Path $StagingArea -ChildPath 'SignTool.xml') if ($PSHOME -notlike 'C:\Program Files\WindowsApps\*') { - Write-Verbose -Message 'Scanning the PowerShell core directory ' + [WDACConfig.Logger]::Write('Scanning the PowerShell core directory ') Write-ColorfulTextWDACConfig -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 $PSHOME -Level FilePublisher -NoScript -Fallback Hash -UserPEs -UserWriteablePaths -MultiplePolicyFormat -AllowFileNameFallbacks -FilePath (Join-Path -Path $StagingArea -ChildPath 'AllowPowerShell.xml') - Write-Verbose -Message 'Merging the DefaultWindows.xml, AllowPowerShell.xml and SignTool.xml a single policy file' + [WDACConfig.Logger]::Write('Merging the DefaultWindows.xml, AllowPowerShell.xml and SignTool.xml a single policy file') $null = Merge-CIPolicy -PolicyPaths $BasePolicyPath, (Join-Path -Path $StagingArea -ChildPath 'AllowPowerShell.xml'), (Join-Path -Path $StagingArea -ChildPath 'SignTool.xml') -OutputFilePath $BasePolicyPath } else { - Write-Verbose -Message 'Not including the PowerShell core directory in the policy' - Write-Verbose -Message 'Merging the DefaultWindows.xml and SignTool.xml into a single policy file' + [WDACConfig.Logger]::Write('Not including the PowerShell core directory in the policy') + [WDACConfig.Logger]::Write('Merging the DefaultWindows.xml and SignTool.xml into a single policy file') $null = Merge-CIPolicy -PolicyPaths $BasePolicyPath, (Join-Path -Path $StagingArea -ChildPath 'SignTool.xml') -OutputFilePath $BasePolicyPath } - Write-Verbose -Message 'Setting the policy name' + [WDACConfig.Logger]::Write('Setting the policy name') Set-CIPolicyIdInfo -FilePath $BasePolicyPath -PolicyName "$Name - $(Get-Date -Format 'MM-dd-yyyy')" Set-CiRuleOptions -FilePath $BasePolicyPath -Template Base -RequireEVSigners:$RequireEVSigners @@ -859,7 +848,7 @@ Function Edit-SignedWDACConfig { $CurrentStep++ Write-Progress -Id 17 -Activity 'Configuring the policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Getting the policy ID of the currently deployed base policy based on the policy name that user selected' + [WDACConfig.Logger]::Write('Getting the policy ID of the currently deployed base policy based on the policy name that user selected') # In case there are multiple policies with the same name, the first one will be used [System.Object]$CurrentlyDeployedPolicy = ((&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { ($_.IsSystemPolicy -ne 'True') -and ($_.Version = [WDACConfig.CIPolicyVersion]::Measure($_.Version)) -and ($_.Friendlyname -eq $CurrentBasePolicyName) }) | Select-Object -First 1 @@ -869,32 +858,32 @@ Function Edit-SignedWDACConfig { # Increment the version and use it to deploy the updated policy [System.Version]$VersionToDeploy = [WDACConfig.VersionIncrementer]::AddVersion($CurrentVersion) - Write-Verbose -Message 'Setting the policy ID and Base policy ID to the current base policy ID in the generated XML file' + [WDACConfig.Logger]::Write('Setting the policy ID and Base policy ID to the current base policy ID in the generated XML file') [WDACConfig.PolicyEditor]::EditGUIDs($CurrentID, $BasePolicyPath) # Defining paths for the final Base policy CIP [System.IO.FileInfo]$BasePolicyCIPPath = Join-Path -Path $StagingArea -ChildPath "$CurrentID.cip" - Write-Verbose -Message 'Adding signer rules to the base policy' + [WDACConfig.Logger]::Write('Adding signer rules to the base policy') Add-SignerRule -FilePath $BasePolicyPath -CertificatePath $CertPath -Update -User -Kernel -Supplemental - Write-Verbose -Message "Setting the policy version to '$VersionToDeploy' - Previous version was '$CurrentVersion'" + [WDACConfig.Logger]::Write("Setting the policy version to '$VersionToDeploy' - Previous version was '$CurrentVersion'") Set-CIPolicyVersion -FilePath $BasePolicyPath -Version $VersionToDeploy Set-CiRuleOptions -FilePath $BasePolicyPath -RulesToRemove 'Enabled:Unsigned System Integrity Policy' - Write-Verbose -Message 'Converting the base policy to a CIP file' + [WDACConfig.Logger]::Write('Converting the base policy to a CIP file') $null = ConvertFrom-CIPolicy -XmlFilePath $BasePolicyPath -BinaryFilePath $BasePolicyCIPPath [WDACConfig.CodeIntegritySigner]::InvokeCiSigning($BasePolicyCIPPath, $SignToolPathFinal, $CertCN) - Write-Verbose -Message 'Renaming the signed base policy file to remove the .p7 extension' + [WDACConfig.Logger]::Write('Renaming the signed base policy file to remove the .p7 extension') Move-Item -LiteralPath "$StagingArea\$CurrentID.cip.p7" -Destination $BasePolicyCIPPath -Force $CurrentStep++ Write-Progress -Id 17 -Activity 'Deploying the policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Deploying the new base policy with the same GUID on the system' + [WDACConfig.Logger]::Write('Deploying the new base policy with the same GUID on the system') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $BasePolicyCIPPath -json $CurrentStep++ @@ -908,14 +897,14 @@ Function Edit-SignedWDACConfig { 'DefaultWindows' = (Join-Path -Path ([WDACConfig.GlobalVars]::UserConfigDir) -ChildPath 'DefaultWindows.xml') } - Write-Verbose -Message 'Renaming the base policy XML file to match the new base policy type' + [WDACConfig.Logger]::Write('Renaming the base policy XML file to match the new base policy type') # Copy the new base policy to the user's config directory since Staging Area is a temporary location Move-Item -Path $BasePolicyPath -Destination $PolicyFiles[$NewBasePolicyType] -Force Write-ColorfulTextWDACConfig -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' + [WDACConfig.Logger]::Write('Replacing the old signed policy path in User Configurations with the new one') $null = Set-CommonWDACConfig -SignedPolicyPath $PolicyFiles[$NewBasePolicyType] } } @@ -928,7 +917,7 @@ Function Edit-SignedWDACConfig { Write-Progress -Id $ID -Activity 'Complete.' -Completed } - if (!$Debug) { + if (![WDACConfig.GlobalVars]::DebugPreference) { Remove-Item -LiteralPath $StagingArea -Recurse -Force } } diff --git a/WDACConfig/WDACConfig Module Files/Core/Edit-WDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Edit-WDACConfig.psm1 index 077fba9a3..2fb6b4114 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Edit-WDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Edit-WDACConfig.psm1 @@ -107,16 +107,11 @@ Function Edit-WDACConfig { [Parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$SkipVersionCheck ) Begin { - [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $false - [System.Boolean]$Debug = $PSBoundParameters.Debug.IsPresent ? $true : $false [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - Write-Verbose -Message 'Importing the required sub-modules' + [WDACConfig.Logger]::Write('Importing the required sub-modules') $ModulesToImport = @( - "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Update-Self.psm1", "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Receive-CodeIntegrityLogs.psm1", - "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\New-SnapBackGuarantee.psm1", "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Set-LogPropertiesVisibility.psm1", "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Select-LogProperties.psm1", "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Test-KernelProtectedFiles.psm1" @@ -124,8 +119,7 @@ Function Edit-WDACConfig { $ModulesToImport += ([WDACConfig.FileUtility]::GetFilesFast("$([WDACConfig.GlobalVars]::ModuleRootPath)\XMLOps", $null, '.psm1')).FullName Import-Module -FullyQualifiedName $ModulesToImport -Force - # if -SkipVersionCheck wasn't passed, run the updater - if (-NOT $SkipVersionCheck) { Update-Self -InvocationStatement $MyInvocation.Statement } + if (-NOT $SkipVersionCheck) { Update-WDACConfigPSModule -InvocationStatement $MyInvocation.Statement } if ([WDACConfig.GlobalVars]::ConfigCIBootstrap -eq $false) { Invoke-MockConfigCIBootstrap @@ -173,7 +167,7 @@ Function Edit-WDACConfig { [WDACConfig.EventLogUtility]::SetLogSize($LogSize ?? 0) # 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' + [WDACConfig.Logger]::Write('Getting the current date') [System.DateTime]$Date = Get-Date # A concurrent hashtable that holds the Policy XML files in its values - This array will eventually be used to create the final Supplemental policy @@ -186,39 +180,39 @@ Function Edit-WDACConfig { $CurrentStep++ Write-Progress -Id 10 -Activity 'Creating the Audit mode policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Creating a copy of the original policy in the Staging Area so that the original one will be unaffected' + [WDACConfig.Logger]::Write('Creating a copy of the original policy in the Staging Area so that the original one will be unaffected') Copy-Item -Path $PolicyPath -Destination $StagingArea -Force [System.IO.FileInfo]$PolicyPath = Join-Path -Path $StagingArea -ChildPath (Split-Path -Path $PolicyPath -Leaf) - Write-Verbose -Message 'Retrieving the Base policy name and ID' + [WDACConfig.Logger]::Write('Retrieving the Base policy name and ID') [System.Xml.XmlDocument]$Xml = 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 - Write-Verbose -Message 'Creating Audit Mode CIP' + [WDACConfig.Logger]::Write('Creating Audit Mode CIP') [System.IO.FileInfo]$AuditModeCIPPath = Join-Path -Path $StagingArea -ChildPath 'AuditMode.cip' Set-CiRuleOptions -FilePath $PolicyPath -RulesToAdd 'Enabled:Audit Mode' $null = ConvertFrom-CIPolicy -XmlFilePath $PolicyPath -BinaryFilePath $AuditModeCIPPath - Write-Verbose -Message 'Creating Enforced Mode CIP' + [WDACConfig.Logger]::Write('Creating Enforced Mode CIP') [System.IO.FileInfo]$EnforcedModeCIPPath = Join-Path -Path $StagingArea -ChildPath 'EnforcedMode.cip' Set-CiRuleOptions -FilePath $PolicyPath -RulesToRemove 'Enabled:Audit Mode' $null = ConvertFrom-CIPolicy -XmlFilePath $PolicyPath -BinaryFilePath $EnforcedModeCIPPath #Region Snap-Back-Guarantee - Write-Verbose -Message 'Creating Enforced Mode SnapBack guarantee' - New-SnapBackGuarantee -Path $EnforcedModeCIPPath + [WDACConfig.Logger]::Write('Creating Enforced Mode SnapBack guarantee') + [WDACConfig.SnapBackGuarantee]::New($EnforcedModeCIPPath.FullName) $CurrentStep++ Write-Progress -Id 10 -Activity 'Deploying the Audit mode policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Deploying the Audit mode CIP' + [WDACConfig.Logger]::Write('Deploying the Audit mode CIP') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $AuditModeCIPPath -json - Write-Verbose -Message 'The Base policy with the following details has been Re-Deployed in Audit Mode:' - Write-Verbose -Message "PolicyName = $PolicyName" - Write-Verbose -Message "PolicyGUID = $PolicyID" + [WDACConfig.Logger]::Write('The Base policy with the following details has been Re-Deployed in Audit Mode:') + [WDACConfig.Logger]::Write("PolicyName = $PolicyName") + [WDACConfig.Logger]::Write("PolicyGUID = $PolicyID") #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 @@ -241,14 +235,14 @@ Function Edit-WDACConfig { $CurrentStep++ Write-Progress -Id 10 -Activity 'Redeploying the Base policy in Enforced Mode' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Debug -Message 'Finally Block Running' + [WDACConfig.Logger]::Write('Finally Block Running') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $EnforcedModeCIPPath -json - Write-Verbose -Message 'The Base policy with the following details has been Re-Deployed in Enforced Mode:' - Write-Verbose -Message "PolicyName = $PolicyName" - Write-Verbose -Message "PolicyGUID = $PolicyID" + [WDACConfig.Logger]::Write('The Base policy with the following details has been Re-Deployed in Enforced Mode:') + [WDACConfig.Logger]::Write("PolicyName = $PolicyName") + [WDACConfig.Logger]::Write("PolicyGUID = $PolicyID") - Write-Verbose -Message 'Removing the SnapBack guarantee because the base policy has been successfully re-enforced' + [WDACConfig.Logger]::Write('Removing the SnapBack guarantee because the base policy has been successfully re-enforced') Unregister-ScheduledTask -TaskName 'EnforcedModeSnapBack' -Confirm:$false Remove-Item -Path (Join-Path -Path ([WDACConfig.GlobalVars]::UserConfigDir) -ChildPath 'EnforcedModeSnapBack.cmd') -Force @@ -274,7 +268,7 @@ Function Edit-WDACConfig { [System.Boolean]$HasSelectedLogs = $false if ($ProgramsPaths) { - Write-Verbose -Message 'Here are the paths you selected:' + [WDACConfig.Logger]::Write('Here are the paths you selected:') if ($Verbose) { foreach ($Path in $ProgramsPaths) { $Path.FullName @@ -285,13 +279,11 @@ Function Edit-WDACConfig { # Start Async job for detecting ECC-Signed files among the user-selected directories [System.Management.Automation.Job2]$ECCSignedDirectoriesJob = Start-ThreadJob -ScriptBlock { - Param ($PolicyXMLFilesArray, $ParentVerbosePreference, $ParentDebugPreference) + Param ($PolicyXMLFilesArray) $global:ProgressPreference = 'SilentlyContinue' $global:ErrorActionPreference = 'Stop' - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - Import-Module -Force -FullyQualifiedName "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Test-ECCSignedFiles.psm1" [System.IO.FileInfo]$ECCSignedFilesTempPolicyUserDirs = Join-Path -Path $using:StagingArea -ChildPath 'ECCSignedFilesTempPolicyUserDirs.xml' $ECCSignedFilesTempPolicy = Test-ECCSignedFiles -Directory $using:ProgramsPaths -Process -ECCSignedFilesTempPolicy $ECCSignedFilesTempPolicyUserDirs @@ -299,7 +291,7 @@ Function Edit-WDACConfig { if ($ECCSignedFilesTempPolicy -as [System.IO.FileInfo]) { [System.Void]$PolicyXMLFilesArray.TryAdd('Hash Rules For ECC Signed Files in User selected directories', $ECCSignedFilesTempPolicy) } - } -StreamingHost $Host -ArgumentList $PolicyXMLFilesArray, $VerbosePreference, $DebugPreference + } -StreamingHost $Host -ArgumentList $PolicyXMLFilesArray [System.Management.Automation.Job2]$DirectoryScanJob = Start-ThreadJob -InitializationScript { $global:ProgressPreference = 'SilentlyContinue' @@ -310,11 +302,9 @@ Function Edit-WDACConfig { Remove-Item -LiteralPath ".\$RandomGUID.xml" -Force } } -ScriptBlock { - Param ($ProgramsPaths, $StagingArea, $PolicyXMLFilesArray, $ParentVerbosePreference, $ParentDebugPreference) - - $VerbosePreference = $ParentVerbosePreference + Param ($ProgramsPaths, $StagingArea, $PolicyXMLFilesArray) - # Write-Verbose -Message 'Scanning each of the folder paths that user selected' + # [WDACConfig.Logger]::Write('Scanning each of the folder paths that user selected') for ($i = 0; $i -lt $ProgramsPaths.Count; $i++) { @@ -333,20 +323,20 @@ Function Edit-WDACConfig { if ($using:NoScript) { $UserInputProgramFoldersPolicyMakerHashTable['NoScript'] = $true } if (!$using:NoUserPEs) { $UserInputProgramFoldersPolicyMakerHashTable['UserPEs'] = $true } - # Write-Verbose -Message "Currently scanning: $($ProgramsPaths[$i])" + # [WDACConfig.Logger]::Write("Currently scanning: $($ProgramsPaths[$i])") New-CIPolicy @UserInputProgramFoldersPolicyMakerHashTable [System.Void]$PolicyXMLFilesArray.TryAdd("$($ProgramsPaths[$i]) Scan Results", "$StagingArea\ProgramDir_ScanResults$($i).xml") } - if ($ParentDebugPreference -eq 'Continue') { + if ([WDACConfig.GlobalVars]::DebugPreference) { Write-Output -InputObject 'The directories were scanned with the following configuration' Write-Output -InputObject $($UserInputProgramFoldersPolicyMakerHashTable | Format-Table) } - } -StreamingHost $Host -ArgumentList $ProgramsPaths, $StagingArea, $PolicyXMLFilesArray, $VerbosePreference, $DebugPreference + } -StreamingHost $Host -ArgumentList $ProgramsPaths, $StagingArea, $PolicyXMLFilesArray } else { - Write-Verbose -Message 'No directory path was selected.' + [WDACConfig.Logger]::Write('No directory path was selected.') } [System.Collections.Hashtable[]]$AuditEventLogsProcessingResults = Receive-CodeIntegrityLogs -Date $Date -Type 'Audit' @@ -355,7 +345,7 @@ Function Edit-WDACConfig { $HasAuditLogs = $true } else { - Write-Verbose -Message 'No audit log events were generated during the audit period.' + [WDACConfig.Logger]::Write('No audit log events were generated during the audit period.') } if ($HasAuditLogs -and $HasFolderPaths) { @@ -363,7 +353,7 @@ Function Edit-WDACConfig { } if (($null -ne $OutsideFiles) -and ($OutsideFiles.count -ne 0)) { - Write-Verbose -Message "$($OutsideFiles.count) file(s) have been found in event viewer logs that don't exist in any of the folder paths you selected." + [WDACConfig.Logger]::Write("$($OutsideFiles.count) file(s) have been found in event viewer logs that don't exist in any of the folder paths you selected.") $HasExtraFiles = $true } @@ -401,13 +391,11 @@ Function Edit-WDACConfig { # Start Async job for detecting ECC-Signed files among the user-selected audit logs [System.Management.Automation.Job2]$ECCSignedAuditLogsJob = Start-ThreadJob -ScriptBlock { - Param ($PolicyXMLFilesArray, $ParentVerbosePreference, $ParentDebugPreference) + Param ($PolicyXMLFilesArray) $global:ProgressPreference = 'SilentlyContinue' $global:ErrorActionPreference = 'Stop' - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - Import-Module -Force -FullyQualifiedName "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Test-ECCSignedFiles.psm1" [System.IO.FileInfo]$ECCSignedFilesTempPolicyAuditLogs = Join-Path -Path $using:StagingArea -ChildPath 'ECCSignedFilesTempPolicyAuditLogs.xml' $ECCSignedFilesTempPolicy = Test-ECCSignedFiles -File $($using:SelectedLogs).'Full Path' -Process -ECCSignedFilesTempPolicy $ECCSignedFilesTempPolicyAuditLogs @@ -415,18 +403,18 @@ Function Edit-WDACConfig { if ($ECCSignedFilesTempPolicy -as [System.IO.FileInfo]) { [System.Void]$PolicyXMLFilesArray.TryAdd('Hash Rules For ECC Signed Files in User selected Audit Logs', $ECCSignedFilesTempPolicy) } - } -StreamingHost $Host -ArgumentList $PolicyXMLFilesArray, $VerbosePreference, $DebugPreference + } -StreamingHost $Host -ArgumentList $PolicyXMLFilesArray [PSCustomObject[]]$KernelProtectedFileLogs = Test-KernelProtectedFiles -Logs $SelectedLogs if ($null -ne $KernelProtectedFileLogs) { - Write-Verbose -Message "Kernel protected files count: $($KernelProtectedFileLogs.count)" + [WDACConfig.Logger]::Write("Kernel protected files count: $($KernelProtectedFileLogs.count)") - Write-Verbose -Message 'Copying the template policy to the staging area' + [WDACConfig.Logger]::Write('Copying the template policy to the staging area') Copy-Item -LiteralPath 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowAll.xml' -Destination $KernelProtectedPolicyPath -Force - Write-Verbose -Message 'Emptying the policy file in preparation for the new data insertion' + [WDACConfig.Logger]::Write('Emptying the policy file in preparation for the new data insertion') Clear-CiPolicy_Semantic -Path $KernelProtectedPolicyPath # Find the kernel protected files that have PFN property @@ -442,8 +430,8 @@ Function Edit-WDACConfig { # Add the Kernel protected files policy to the list of policies to merge [System.Void]$PolicyXMLFilesArray.TryAdd('Kernel Protected files policy', $KernelProtectedPolicyPath) - Write-Verbose -Message "Kernel protected files with PFN property: $($KernelProtectedFileLogsWithPFN.count)" - Write-Verbose -Message "Kernel protected files without PFN property: $($KernelProtectedFileLogs.count - $KernelProtectedFileLogsWithPFN.count)" + [WDACConfig.Logger]::Write("Kernel protected files with PFN property: $($KernelProtectedFileLogsWithPFN.count)") + [WDACConfig.Logger]::Write("Kernel protected files without PFN property: $($KernelProtectedFileLogs.count - $KernelProtectedFileLogsWithPFN.count)") # Removing the logs that were used to create PFN rules, from the rest of the logs $SelectedLogs = foreach ($Item in $SelectedLogs) { @@ -453,34 +441,34 @@ Function Edit-WDACConfig { } } - Write-Verbose -Message 'Copying the template policy to the staging area' + [WDACConfig.Logger]::Write('Copying the template policy to the staging area') Copy-Item -LiteralPath 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowAll.xml' -Destination $WDACPolicyPathTEMP -Force - Write-Verbose -Message 'Emptying the policy file in preparation for the new data insertion' + [WDACConfig.Logger]::Write('Emptying the policy file in preparation for the new data insertion') Clear-CiPolicy_Semantic -Path $WDACPolicyPathTEMP - Write-Verbose -Message 'Building the Signer and Hash objects from the selected logs' + [WDACConfig.Logger]::Write('Building the Signer and Hash objects from the selected logs') [WDACConfig.FileBasedInfoPackage]$DataToUseForBuilding = [WDACConfig.SignerAndHashBuilder]::BuildSignerAndHashObjects((ConvertTo-HashtableArray $SelectedLogs), 'EVTX', ($Level -eq 'FilePublisher' ? 'FilePublisher' : $Level -eq 'Publisher' ? 'Publisher' : $Level -eq 'Hash' ? 'Hash' : 'Auto'), $BoostedSecurity ? $true : $false) if ($Null -ne $DataToUseForBuilding.FilePublisherSigners -and $DataToUseForBuilding.FilePublisherSigners.Count -gt 0) { - Write-Verbose -Message 'Creating File Publisher Level rules' + [WDACConfig.Logger]::Write('Creating File Publisher Level rules') New-FilePublisherLevelRules -FilePublisherSigners $DataToUseForBuilding.FilePublisherSigners -XmlFilePath $WDACPolicyPathTEMP } if ($Null -ne $DataToUseForBuilding.PublisherSigners -and $DataToUseForBuilding.PublisherSigners.Count -gt 0) { - Write-Verbose -Message 'Creating Publisher Level rules' + [WDACConfig.Logger]::Write('Creating Publisher Level rules') New-PublisherLevelRules -PublisherSigners $DataToUseForBuilding.PublisherSigners -XmlFilePath $WDACPolicyPathTEMP } if ($Null -ne $DataToUseForBuilding.CompleteHashes -and $DataToUseForBuilding.CompleteHashes.Count -gt 0) { - Write-Verbose -Message 'Creating Hash Level rules' + [WDACConfig.Logger]::Write('Creating Hash Level rules') New-HashLevelRules -Hashes $DataToUseForBuilding.CompleteHashes -XmlFilePath $WDACPolicyPathTEMP } # MERGERS - Write-Verbose -Message 'Merging the Hash Level rules' + [WDACConfig.Logger]::Write('Merging the Hash Level rules') Remove-AllowElements_Semantic -Path $WDACPolicyPathTEMP Close-EmptyXmlNodes_Semantic -XmlFilePath $WDACPolicyPathTEMP - Write-Verbose -Message 'Merging the Signer Level rules' + [WDACConfig.Logger]::Write('Merging the Signer Level rules') Remove-DuplicateFileAttrib_Semantic -XmlFilePath $WDACPolicyPathTEMP # 2 passes are necessary @@ -519,12 +507,12 @@ Function Edit-WDACConfig { # If none of the previous actions resulted in any policy XML files, exit the function if ($PolicyXMLFilesArray.Values.Count -eq 0) { - Write-Verbose -Message 'No directory path or audit logs were selected to create a supplemental policy. Exiting...' -Verbose + [WDACConfig.Logger]::Write('No directory path or audit logs were selected to create a supplemental policy. Exiting...') Return } - 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.Values | ForEach-Object -Process { Write-Verbose -Message "$_" } + [WDACConfig.Logger]::Write('The following policy xml files are going to be merged into the final Supplemental policy and be deployed on the system:') + $PolicyXMLFilesArray.Values | ForEach-Object -Process { [WDACConfig.Logger]::Write("$_") } # Merge all of the policy XML files in the array into the final Supplemental policy $CurrentStep++ @@ -537,15 +525,15 @@ Function Edit-WDACConfig { $CurrentStep++ Write-Progress -Id 10 -Activity 'Creating supplemental policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Supplemental policy processing and deployment' + [WDACConfig.Logger]::Write('Supplemental policy processing and deployment') - Write-Verbose -Message 'Converting the policy to a Supplemental policy type and resetting its ID' + [WDACConfig.Logger]::Write('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) Set-CiRuleOptions -FilePath $SuppPolicyPath -Template Supplemental - Write-Verbose -Message 'Setting the Supplemental policy version to 1.0.0.0' + [WDACConfig.Logger]::Write('Setting the Supplemental policy version to 1.0.0.0') Set-CIPolicyVersion -FilePath $SuppPolicyPath -Version '1.0.0.0' # Define the path for the final Supplemental policy CIP @@ -561,13 +549,13 @@ Function Edit-WDACConfig { } #Endregion Boosted Security - Sandboxing - Write-Verbose -Message 'Convert the Supplemental policy to a CIP file' + [WDACConfig.Logger]::Write('Convert the Supplemental policy to a CIP file') $null = ConvertFrom-CIPolicy -XmlFilePath $SuppPolicyPath -BinaryFilePath $SupplementalCIPPath $CurrentStep++ Write-Progress -Id 10 -Activity 'Deploying the Supplemental policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Deploying the Supplemental policy' + [WDACConfig.Logger]::Write('Deploying the Supplemental policy') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $SupplementalCIPPath -json #Endregion Supplemental-policy-processing-and-deployment @@ -587,7 +575,7 @@ Function Edit-WDACConfig { $CurrentStep++ Write-Progress -Id 11 -Activity 'Verifying the input files' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Getting the IDs of the currently deployed policies on the system' + [WDACConfig.Logger]::Write('Getting the IDs of the currently deployed policies on the system') $DeployedPoliciesIDs = [System.Collections.Generic.HashSet[System.String]]::new([System.StringComparer]::InvariantCultureIgnoreCase) foreach ($Item in (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies.PolicyID) { @@ -595,29 +583,29 @@ Function Edit-WDACConfig { } #Region Input-policy-verification - Write-Verbose -Message 'Verifying the input policy files' + [WDACConfig.Logger]::Write('Verifying the input policy files') foreach ($SuppPolicyPath in $SuppPolicyPaths) { - Write-Verbose -Message "Getting policy ID and type of: $SuppPolicyPath" + [WDACConfig.Logger]::Write("Getting policy ID and type of: $SuppPolicyPath") [System.Xml.XmlDocument]$Supplementalxml = Get-Content -Path $SuppPolicyPath [System.String]$SupplementalPolicyID = $Supplementalxml.SiPolicy.PolicyID [System.String]$SupplementalPolicyType = $Supplementalxml.SiPolicy.PolicyType # 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' + [WDACConfig.Logger]::Write('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' + [WDACConfig.Logger]::Write('Checking the deployment status of the policy') if (!$DeployedPoliciesIDs.Contains($SupplementalPolicyID)) { Throw "The Selected Supplemental XML file with GUID $SupplementalPolicyID isn't deployed on the system." } } #Endregion Input-policy-verification - Write-Verbose -Message 'Backing up any possible Macros in the Supplemental policies' + [WDACConfig.Logger]::Write('Backing up any possible Macros in the Supplemental policies') $MacrosBackup = Checkpoint-Macros -XmlFilePathIn $SuppPolicyPaths -Backup $CurrentStep++ @@ -625,47 +613,47 @@ Function Edit-WDACConfig { [System.IO.FileInfo]$FinalSupplementalPath = Join-Path -Path $StagingArea -ChildPath "$SuppPolicyName.xml" - Write-Verbose -Message 'Merging the Supplemental policies into a single policy file' + [WDACConfig.Logger]::Write('Merging the Supplemental policies into a single policy file') $null = Merge-CIPolicy -PolicyPaths $SuppPolicyPaths -OutputFilePath $FinalSupplementalPath # 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 $CurrentStep++ Write-Progress -Id 11 -Activity 'Removing old policies from the system' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Removing the deployed Supplemental policies that user selected from the system' + [WDACConfig.Logger]::Write('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 [System.Xml.XmlDocument]$Supplementalxml = Get-Content -Path $SuppPolicyPath [System.String]$SupplementalPolicyID = $Supplementalxml.SiPolicy.PolicyID - Write-Verbose -Message "Removing policy with ID: $SupplementalPolicyID" + [WDACConfig.Logger]::Write("Removing policy with ID: $SupplementalPolicyID") $null = &'C:\Windows\System32\CiTool.exe' --remove-policy $SupplementalPolicyID -json } $CurrentStep++ Write-Progress -Id 11 -Activity 'Configuring the final policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - 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' + [WDACConfig.Logger]::Write('Preparing the final merged Supplemental policy for deployment') + [WDACConfig.Logger]::Write('Converting the policy to a Supplemental policy type and resetting its ID') [System.String]$SuppPolicyID = Set-CIPolicyIdInfo -FilePath $FinalSupplementalPath -ResetPolicyID -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" -BasePolicyToSupplementPath $PolicyPath [System.String]$SuppPolicyID = $SuppPolicyID.Substring(11) - Write-Verbose -Message 'Setting HVCI to Strict' + [WDACConfig.Logger]::Write('Setting HVCI to Strict') Set-HVCIOptions -Strict -FilePath $FinalSupplementalPath if ($null -ne $MacrosBackup) { - Write-Verbose -Message 'Restoring the Macros in the Supplemental policies' + [WDACConfig.Logger]::Write('Restoring the Macros in the Supplemental policies') Checkpoint-Macros -XmlFilePathOut $FinalSupplementalPath -Restore -MacrosBackup $MacrosBackup } - Write-Verbose -Message 'Converting the Supplemental policy to a CIP file' + [WDACConfig.Logger]::Write('Converting the Supplemental policy to a CIP file') $null = ConvertFrom-CIPolicy -XmlFilePath $FinalSupplementalPath -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$SuppPolicyID.cip") $CurrentStep++ Write-Progress -Id 11 -Activity 'Deploying the final policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Deploying the Supplemental policy' + [WDACConfig.Logger]::Write('Deploying the Supplemental policy') $null = &'C:\Windows\System32\CiTool.exe' --update-policy (Join-Path -Path $StagingArea -ChildPath "$SuppPolicyID.cip") -json Write-ColorfulTextWDACConfig -Color TeaGreen -InputText "The Supplemental policy $SuppPolicyName has been deployed on the system, replacing the old ones." @@ -675,7 +663,7 @@ Function Edit-WDACConfig { # remove the old policy files at the end after ensuring the operation was successful if (!$KeepOldSupplementalPolicies) { - Write-Verbose -Message 'Removing the old policy files' + [WDACConfig.Logger]::Write('Removing the old policy files') Remove-Item -Path $SuppPolicyPaths -Force } } @@ -689,7 +677,7 @@ Function Edit-WDACConfig { $CurrentStep++ Write-Progress -Id 12 -Activity 'Getting the block rules' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Getting the Use Mode Block Rules' + [WDACConfig.Logger]::Write('Getting the Use Mode Block Rules') New-WDACConfig -GetUserModeBlockRules -Deploy $CurrentStep++ @@ -697,7 +685,7 @@ Function Edit-WDACConfig { [System.IO.FileInfo]$BasePolicyPath = Join-Path -Path $StagingArea -ChildPath 'BasePolicy.xml' - Write-Verbose -Message 'Determining the type of the new base policy' + [WDACConfig.Logger]::Write('Determining the type of the new base policy') [System.String]$Name = $null @@ -705,12 +693,12 @@ Function Edit-WDACConfig { 'AllowMicrosoft' { $Name = 'AllowMicrosoft' - Write-Verbose -Message "The new base policy type is $Name" + [WDACConfig.Logger]::Write("The new base policy type is $Name") - Write-Verbose -Message 'Copying the AllowMicrosoft.xml template policy file to the Staging Area' + [WDACConfig.Logger]::Write('Copying the AllowMicrosoft.xml template policy file to the Staging Area') Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowMicrosoft.xml' -Destination $BasePolicyPath -Force - Write-Verbose -Message 'Setting the policy name' + [WDACConfig.Logger]::Write('Setting the policy name') Set-CIPolicyIdInfo -FilePath $BasePolicyPath -PolicyName "$Name - $(Get-Date -Format 'MM-dd-yyyy')" Set-CiRuleOptions -FilePath $BasePolicyPath -Template Base -RequireEVSigners:$RequireEVSigners @@ -718,40 +706,40 @@ Function Edit-WDACConfig { 'SignedAndReputable' { $Name = 'SignedAndReputable' - Write-Verbose -Message "The new base policy type is $Name" + [WDACConfig.Logger]::Write("The new base policy type is $Name") - Write-Verbose -Message 'Copying the AllowMicrosoft.xml template policy file to the Staging Area' + [WDACConfig.Logger]::Write('Copying the AllowMicrosoft.xml template policy file to the Staging Area') Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowMicrosoft.xml' -Destination $BasePolicyPath -Force - Write-Verbose -Message 'Setting the policy name' + [WDACConfig.Logger]::Write('Setting the policy name') Set-CIPolicyIdInfo -FilePath $BasePolicyPath -PolicyName "$Name - $(Get-Date -Format 'MM-dd-yyyy')" Set-CiRuleOptions -FilePath $BasePolicyPath -Template BaseISG -RequireEVSigners:$RequireEVSigners - Write-Verbose -Message 'Configuring required services for ISG authorization' + [WDACConfig.Logger]::Write('Configuring required services for ISG authorization') Start-Process -FilePath 'C:\Windows\System32\appidtel.exe' -ArgumentList 'start' -NoNewWindow Start-Process -FilePath 'C:\Windows\System32\sc.exe' -ArgumentList 'config', 'appidsvc', 'start= auto' -NoNewWindow } 'DefaultWindows' { $Name = 'DefaultWindows' - Write-Verbose -Message "The new base policy type is $Name" + [WDACConfig.Logger]::Write("The new base policy type is $Name") - Write-Verbose -Message 'Copying the DefaultWindows.xml template policy file to the Staging Area' + [WDACConfig.Logger]::Write('Copying the DefaultWindows.xml template policy file to the Staging Area') Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\DefaultWindows_Enforced.xml' -Destination $BasePolicyPath -Force if ($PSHOME -notlike 'C:\Program Files\WindowsApps\*') { - Write-Verbose -Message 'Scanning the PowerShell core directory ' + [WDACConfig.Logger]::Write('Scanning the PowerShell core directory ') Write-ColorfulTextWDACConfig -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 $PSHOME -Level FilePublisher -NoScript -Fallback Hash -UserPEs -UserWriteablePaths -MultiplePolicyFormat -AllowFileNameFallbacks -FilePath (Join-Path -Path $StagingArea -ChildPath 'AllowPowerShell.xml') - Write-Verbose -Message 'Merging the DefaultWindows.xml and AllowPowerShell.xml into a single policy file' + [WDACConfig.Logger]::Write('Merging the DefaultWindows.xml and AllowPowerShell.xml into a single policy file') $null = Merge-CIPolicy -PolicyPaths $BasePolicyPath, (Join-Path -Path $StagingArea -ChildPath 'AllowPowerShell.xml') -OutputFilePath $BasePolicyPath } - Write-Verbose -Message 'Setting the policy name' + [WDACConfig.Logger]::Write('Setting the policy name') Set-CIPolicyIdInfo -FilePath $BasePolicyPath -PolicyName "$Name - $(Get-Date -Format 'MM-dd-yyyy')" Set-CiRuleOptions -FilePath $BasePolicyPath -Template Base -RequireEVSigners:$RequireEVSigners @@ -761,7 +749,7 @@ Function Edit-WDACConfig { $CurrentStep++ Write-Progress -Id 12 -Activity 'Configuring the policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Getting the policy ID of the currently deployed base policy based on the policy name that user selected' + [WDACConfig.Logger]::Write('Getting the policy ID of the currently deployed base policy based on the policy name that user selected') # In case there are multiple policies with the same name, the first one will be used [System.Object]$CurrentlyDeployedPolicy = ((&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { ($_.IsSystemPolicy -ne 'True') -and ($_.Version = [WDACConfig.CIPolicyVersion]::Measure($_.Version)) -and ($_.Friendlyname -eq $CurrentBasePolicyName) }) | Select-Object -First 1 @@ -771,21 +759,21 @@ Function Edit-WDACConfig { # Increment the version and use it to deploy the updated policy [System.Version]$VersionToDeploy = [WDACConfig.VersionIncrementer]::AddVersion($CurrentVersion) - Write-Verbose -Message "This is the current ID of deployed base policy that is going to be used in the new base policy: $CurrentID" + [WDACConfig.Logger]::Write("This is the current ID of deployed base policy that is going to be used in the new base policy: $CurrentID") - Write-Verbose -Message 'Setting the policy ID and Base policy ID to the current base policy ID in the generated XML file' + [WDACConfig.Logger]::Write('Setting the policy ID and Base policy ID to the current base policy ID in the generated XML file') [WDACConfig.PolicyEditor]::EditGUIDs($CurrentID, $BasePolicyPath) - Write-Verbose -Message "Setting the policy version to '$VersionToDeploy' - Previous version was '$CurrentVersion'" + [WDACConfig.Logger]::Write("Setting the policy version to '$VersionToDeploy' - Previous version was '$CurrentVersion'") Set-CIPolicyVersion -FilePath $BasePolicyPath -Version $VersionToDeploy - Write-Verbose -Message 'Converting the base policy to a CIP file' + [WDACConfig.Logger]::Write('Converting the base policy to a CIP file') [System.IO.FileInfo]$CIPPath = ConvertFrom-CIPolicy -XmlFilePath $BasePolicyPath -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$CurrentID.cip") $CurrentStep++ Write-Progress -Id 12 -Activity 'Deploying the policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Deploying the new base policy with the same GUID on the system' + [WDACConfig.Logger]::Write('Deploying the new base policy with the same GUID on the system') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $CIPPath -json $CurrentStep++ @@ -799,14 +787,14 @@ Function Edit-WDACConfig { 'DefaultWindows' = (Join-Path -Path ([WDACConfig.GlobalVars]::UserConfigDir) -ChildPath 'DefaultWindows.xml') } - Write-Verbose -Message 'Renaming the base policy XML file to match the new base policy type' + [WDACConfig.Logger]::Write('Renaming the base policy XML file to match the new base policy type') # Copy the new base policy to the user's config directory since Staging Area is a temporary location Move-Item -Path $BasePolicyPath -Destination $PolicyFiles[$NewBasePolicyType] -Force Write-ColorfulTextWDACConfig -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' + [WDACConfig.Logger]::Write('Replacing the old unsigned policy path in User Configurations with the new one') $null = Set-CommonWDACConfig -UnsignedPolicyPath $PolicyFiles[$NewBasePolicyType] } } @@ -818,7 +806,7 @@ Function Edit-WDACConfig { foreach ($ID in 10..12) { Write-Progress -Id $ID -Activity 'Complete.' -Completed } - if (!$Debug) { + if (![WDACConfig.GlobalVars]::DebugPreference) { Remove-Item -Path $StagingArea -Recurse -Force } } diff --git a/WDACConfig/WDACConfig Module Files/Core/Get-CIPolicySetting.psm1 b/WDACConfig/WDACConfig Module Files/Core/Get-CIPolicySetting.psm1 index cf836b7b1..6524c1bc9 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Get-CIPolicySetting.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Get-CIPolicySetting.psm1 @@ -7,65 +7,9 @@ Function Get-CIPolicySetting { [Parameter(Mandatory = $true)][System.String]$ValueName, [Parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$SkipVersionCheck ) - Begin { - [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $false - [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) - - Write-Verbose -Message 'Importing the required sub-modules' - Import-Module -FullyQualifiedName "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Update-Self.psm1" -Force - - # if -SkipVersionCheck wasn't passed, run the updater - if (-NOT $SkipVersionCheck) { Update-Self -InvocationStatement $MyInvocation.Statement } - } - Process { - try { - # Create UNICODE_STRING structures - $ProviderUS = [WDACConfig.WldpQuerySecurityPolicyWrapper]::InitUnicodeString($Provider) - $KeyUS = [WDACConfig.WldpQuerySecurityPolicyWrapper]::InitUnicodeString($Key) - $ValueNameUS = [WDACConfig.WldpQuerySecurityPolicyWrapper]::InitUnicodeString($ValueName) - - # Prepare output variables - $ValueType = [WDACConfig.WLDP_SECURE_SETTING_VALUE_TYPE]::WldpNone - $ValueSize = [System.UInt64]1024 - $Value = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($ValueSize) - - $Result = [WDACConfig.WldpQuerySecurityPolicyWrapper]::WldpQuerySecurityPolicy([ref]$ProviderUS, [ref]$KeyUS, [ref]$ValueNameUS, [ref]$ValueType, $Value, [ref]$ValueSize) - - $DecodedValue = $null - - if ($Result -eq 0) { - switch ($ValueType) { - 'WldpBoolean' { - $DecodedValue = [System.Runtime.InteropServices.Marshal]::ReadByte($Value) -ne 0 - } - 'WldpString' { - $DecodedValue = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($Value) - } - 'WldpInteger' { - $DecodedValue = [System.Runtime.InteropServices.Marshal]::ReadInt32($Value) - } - } - } - - Return [PSCustomObject]@{ - Value = $DecodedValue - ValueType = $ValueType - ValueSize = $ValueSize - Status = $Result -eq 0 ? $true : $false - StatusCode = $Result - } - } - catch { - throw $_ - } - finally { - # Clean up - [System.Runtime.InteropServices.Marshal]::FreeHGlobal($ProviderUS.Buffer) - [System.Runtime.InteropServices.Marshal]::FreeHGlobal($KeyUS.Buffer) - [System.Runtime.InteropServices.Marshal]::FreeHGlobal($ValueNameUS.Buffer) - [System.Runtime.InteropServices.Marshal]::FreeHGlobal($Value) - } - } + [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) + if (-NOT $SkipVersionCheck) { Update-WDACConfigPSModule -InvocationStatement $MyInvocation.Statement } + [WDACConfig.GetCIPolicySetting]::Invoke($Provider, $Key, $ValueName) <# .SYNOPSIS Gets the secure settings value from the deployed CI policies. diff --git a/WDACConfig/WDACConfig Module Files/Core/Get-CiFileHashes.psm1 b/WDACConfig/WDACConfig Module Files/Core/Get-CiFileHashes.psm1 index 85add181f..f052b6575 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Get-CiFileHashes.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Get-CiFileHashes.psm1 @@ -1,6 +1,6 @@ Function Get-CiFileHashes { [CmdletBinding()] - [OutputType([WDACConfig.AuthenticodePageHashes])] + [OutputType([WDACConfig.CodeIntegrityHashes])] param ( [ArgumentCompleter([WDACConfig.ArgCompleter.AnyFilePathsPicker])] [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] @@ -8,19 +8,9 @@ Function Get-CiFileHashes { [Parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$SkipVersionCheck ) - [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $false [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - - # if -SkipVersionCheck wasn't passed, run the updater - if (-NOT $SkipVersionCheck) { - # Importing the required sub-module for update checking - Import-Module -FullyQualifiedName "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Update-Self.psm1" -Force - - Update-Self -InvocationStatement $MyInvocation.Statement - } - - return [WDACConfig.AuthPageHash]::GetCiFileHashes($FilePath) + if (!$SkipVersionCheck) { Update-WDACConfigPSModule -InvocationStatement $MyInvocation.Statement } + return [WDACConfig.CiFileHash]::GetCiFileHashes($FilePath) <# .SYNOPSIS Calculates the Authenticode hash and first page hash of the PEs with SHA1 and SHA256 algorithms. @@ -35,7 +25,7 @@ Function Get-CiFileHashes { .INPUTS System.IO.FileInfo .OUTPUTS - [WDACConfig.AuthenticodePageHashes] + [WDACConfig.CodeIntegrityHashes] The output has the following properties - SHA1Page: The SHA1 hash of the first page of the PE file. diff --git a/WDACConfig/WDACConfig Module Files/Core/Get-CommonWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Get-CommonWDACConfig.psm1 index 6af7b68ef..08f364b2d 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Get-CommonWDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Get-CommonWDACConfig.psm1 @@ -14,25 +14,23 @@ Function Get-CommonWDACConfig { [parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$StrictKernelModePolicyTimeOfDeployment ) begin { - [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $false if ($(Get-PSCallStack).Count -le 2) { [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) } else { [WDACConfig.LoggerInitializer]::Initialize($null, $null, $Host) } - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" # Create User configuration folder if it doesn't already exist if (-NOT ([System.IO.Directory]::Exists((Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Parent)))) { $null = New-Item -ItemType Directory -Path (Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Parent) -Force - Write-Verbose -Message 'The WDACConfig folder in Program Files has been created because it did not exist.' + [WDACConfig.Logger]::Write('The WDACConfig folder in Program Files has been created because it did not exist.') } # Create User configuration file if it doesn't already exist if (-NOT ([System.IO.File]::Exists(([WDACConfig.GlobalVars]::UserConfigJson)))) { $null = New-Item -ItemType File -Path (Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Parent) -Name (Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Leaf) -Force - Write-Verbose -Message 'The UserConfigurations.json file has been created because it did not exist.' + [WDACConfig.Logger]::Write('The UserConfigurations.json file has been created because it did not exist.') } if ($Open) { @@ -46,14 +44,14 @@ Function Get-CommonWDACConfig { # Display this message if User Configuration file is empty or only has spaces/new lines if ([System.String]::IsNullOrWhiteSpace((Get-Content -Path ([WDACConfig.GlobalVars]::UserConfigJson)))) { - Write-Verbose -Message 'Your current WDAC User Configurations is empty.' + [WDACConfig.Logger]::Write('Your current WDAC User Configurations is empty.') [System.Boolean]$ReturnAndDone = $true # return/exit from the begin block Return } - Write-Verbose -Message 'Reading the current user configurations' + [WDACConfig.Logger]::Write('Reading the current user configurations') [System.Object[]]$CurrentUserConfigurations = Get-Content -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Force # If the file exists but is corrupted and has bad values, rewrite it diff --git a/WDACConfig/WDACConfig Module Files/Core/Invoke-WDACSimulation.psm1 b/WDACConfig/WDACConfig Module Files/Core/Invoke-WDACSimulation.psm1 index b52de9f02..4784a45bb 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Invoke-WDACSimulation.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Invoke-WDACSimulation.psm1 @@ -41,8 +41,6 @@ Function Invoke-WDACSimulation { [Parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$SkipVersionCheck ) Begin { - [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $false - [System.Boolean]$Debug = $PSBoundParameters.Debug.IsPresent ? $true : $false # Only initialize the logger if this function wasn't called from another function, indicating the Verbose Preferences etc. were already set if ($(Get-PSCallStack).Count -le 2) { [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) @@ -50,21 +48,16 @@ Function Invoke-WDACSimulation { else { [WDACConfig.LoggerInitializer]::Initialize($null, $null, $Host) } - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - Write-Verbose -Message 'Importing the required sub-modules' - Import-Module -Force -FullyQualifiedName @( - "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Update-Self.psm1", - "$([WDACConfig.GlobalVars]::ModuleRootPath)\WDACSimulation\Get-SignerInfo.psm1" - ) + [WDACConfig.Logger]::Write('Importing the required sub-modules') + Import-Module -Force -FullyQualifiedName @("$([WDACConfig.GlobalVars]::ModuleRootPath)\WDACSimulation\Get-SignerInfo.psm1") - # if -SkipVersionCheck wasn't passed, run the updater - if (-NOT $SkipVersionCheck) { Update-Self -InvocationStatement $MyInvocation.Statement } + if (-NOT $SkipVersionCheck) { Update-WDACConfigPSModule -InvocationStatement $MyInvocation.Statement } # Validate the $ThreadsCount parameter if ($ThreadsCount -lt 1 -or $ThreadsCount -gt [System.Environment]::ProcessorCount) { - Write-Verbose -Message "The ThreadsCount parameter must be between 1 and $([System.Environment]::ProcessorCount), but you entered $ThreadsCount, setting it to $([System.Environment]::ProcessorCount)" + [WDACConfig.Logger]::Write("The ThreadsCount parameter must be between 1 and $([System.Environment]::ProcessorCount), but you entered $ThreadsCount, setting it to $([System.Environment]::ProcessorCount)") $ThreadsCount = [System.Environment]::ProcessorCount } @@ -77,7 +70,7 @@ Function Invoke-WDACSimulation { Start-Transcript -IncludeInvocationHeader -LiteralPath (Join-Path -Path ([WDACConfig.GlobalVars]::UserConfigDir) -ChildPath "WDAC Simulation Log $(Get-Date -Format "MM-dd-yyyy 'at' HH-mm-ss").txt") # Create a new stopwatch object to measure the execution time - Write-Verbose -Message 'Starting the stopwatch...' + [WDACConfig.Logger]::Write('Starting the stopwatch...') [System.Diagnostics.Stopwatch]$StopWatch = [Diagnostics.Stopwatch]::StartNew() Function Stop-Log { <# @@ -92,11 +85,11 @@ Function Invoke-WDACSimulation { [OutputType([System.Void])] param() - Write-Verbose -Message 'Stopping the stopwatch' + [WDACConfig.Logger]::Write('Stopping the stopwatch') $StopWatch.Stop() - Write-Verbose -Message "WDAC Simulation for $TotalSubSteps files completed in $($StopWatch.Elapsed.Hours) Hours - $($StopWatch.Elapsed.Minutes) Minutes - $($StopWatch.Elapsed.Seconds) Seconds - $($StopWatch.Elapsed.Milliseconds) Milliseconds - $($StopWatch.Elapsed.Microseconds) Microseconds - $($StopWatch.Elapsed.Nanoseconds) Nanoseconds" -Verbose + [WDACConfig.Logger]::Write("WDAC Simulation for $TotalSubSteps files completed in $($StopWatch.Elapsed.Hours) Hours - $($StopWatch.Elapsed.Minutes) Minutes - $($StopWatch.Elapsed.Seconds) Seconds - $($StopWatch.Elapsed.Milliseconds) Milliseconds - $($StopWatch.Elapsed.Microseconds) Microseconds - $($StopWatch.Elapsed.Nanoseconds) Nanoseconds") - Write-Verbose -Message 'Stopping the transcription' + [WDACConfig.Logger]::Write('Stopping the transcription') Stop-Transcript } } @@ -122,7 +115,7 @@ Function Invoke-WDACSimulation { # Using compiled regex for better performance $AllowAllRegex = [System.Text.RegularExpressions.Regex]::new('', 'Compiled') if ($AllowAllRegex.IsMatch($XMLContent)) { - Write-Verbose -Message "The supplied XML file '$($XmlFilePath.Name)' contains a rule that allows all files." + [WDACConfig.Logger]::Write("The supplied XML file '$($XmlFilePath.Name)' contains a rule that allows all files.") [System.Void]$FinalSimulationResults.TryAdd([string]$CurrentFilePath, [WDACConfig.SimulationOutput]::New( @@ -165,7 +158,7 @@ Function Invoke-WDACSimulation { ) #Region FilePath Rule Checking - Write-Verbose -Message 'Checking see if the XML policy has any FilePath rules' + [WDACConfig.Logger]::Write('Checking see if the XML policy has any FilePath rules') $FilePathRules = [System.Collections.Generic.HashSet[System.String]]@([WDACConfig.XmlFilePathExtractor]::GetFilePaths($XmlFilePath)) [System.Boolean]$HasFilePathRules = $false @@ -204,7 +197,7 @@ Function Invoke-WDACSimulation { } # 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' + [WDACConfig.Logger]::Write('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) @@ -212,7 +205,7 @@ Function Invoke-WDACSimulation { $SHA256HashesFromXML = [System.Collections.Generic.HashSet[System.String]]@(([WDACConfig.GetFileRuleOutput]::Get([System.Xml.XmlDocument]$XMLContent)).HashValue) # Get all of the file paths of the files that WDAC supports, from the user provided directory - Write-Verbose -Message 'Getting all of the file paths of the files that WDAC supports, from the user provided directory' + [WDACConfig.Logger]::Write('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) @@ -222,7 +215,7 @@ Function Invoke-WDACSimulation { if (!$CollectedFiles) { Throw 'There are no files in the selected directory that are supported by the WDAC engine.' } # Loop through each file - Write-Verbose -Message 'Looping through each supported file' + [WDACConfig.Logger]::Write('Looping through each supported file') $CurrentStep++ Write-Progress -Id 0 -Activity 'Looping through each supported file' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) @@ -292,7 +285,7 @@ Function Invoke-WDACSimulation { } try { - [WDACConfig.AuthenticodePageHashes]$CurrentFileHashResult = [WDACConfig.AuthPageHash]::GetCiFileHashes($CurrentFilePath) + [WDACConfig.CodeIntegrityHashes]$CurrentFileHashResult = [WDACConfig.CiFileHash]::GetCiFileHashes($CurrentFilePath) [System.String]$CurrentFilePathHashSHA256 = $CurrentFileHashResult.SHA256Authenticode [System.String]$CurrentFilePathHashSHA1 = $CurrentFileHashResult.SHA1Authenticode } @@ -367,7 +360,7 @@ Function Invoke-WDACSimulation { else { try { - [WDACConfig.AllCertificatesGrabber.AllFileSigners[]]$FileSignatureResults = [WDACConfig.AllCertificatesGrabber.WinTrust]::GetAllFileSigners($CurrentFilePath) + [WDACConfig.AllCertificatesGrabber.AllFileSigners[]]$FileSignatureResults = [WDACConfig.AllCertificatesGrabber]::GetAllFileSigners($CurrentFilePath) # If there is no result then check if the file is allowed by a security catalog if ($FileSignatureResults.Count -eq 0) { @@ -378,7 +371,7 @@ Function Invoke-WDACSimulation { if (!$NoCatalogScanning -and $MatchedHashResult) { - [WDACConfig.AllCertificatesGrabber.AllFileSigners]$CatalogSignerDits = ([WDACConfig.AllCertificatesGrabber.WinTrust]::GetAllFileSigners($MatchedHashResult))[0] + [WDACConfig.AllCertificatesGrabber.AllFileSigners]$CatalogSignerDits = ([WDACConfig.AllCertificatesGrabber]::GetAllFileSigners($MatchedHashResult))[0] # The file is authorized by a security catalog on the system [System.Void]$FinalSimulationResults.TryAdd([string]$CurrentFilePath, [WDACConfig.SimulationOutput]::New( @@ -513,8 +506,8 @@ Function Invoke-WDACSimulation { # Get all of the blocked files $BlockedRules = $FinalSimulationResults.Values.Where({ $_.IsAuthorized -eq $false }) - Write-Verbose -Message "Allowed files: $($AllAllowedRules.count)" - Write-Verbose -Message "Blocked files: $($BlockedRules.count)" + [WDACConfig.Logger]::Write("Allowed files: $($AllAllowedRules.count)") + [WDACConfig.Logger]::Write("Blocked files: $($BlockedRules.count)") # If the array of allowed files is not empty if (-NOT ([System.String]::IsNullOrWhiteSpace($AllAllowedRules))) { diff --git a/WDACConfig/WDACConfig Module Files/Core/New-DenyWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/New-DenyWDACConfig.psm1 index 8dbe0820e..9936e1d36 100644 --- a/WDACConfig/WDACConfig Module Files/Core/New-DenyWDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/New-DenyWDACConfig.psm1 @@ -64,16 +64,8 @@ Function New-DenyWDACConfig { [Parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$SkipVersionCheck ) Begin { - [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $false - [System.Boolean]$Debug = $PSBoundParameters.Debug.IsPresent ? $true : $false [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - - Write-Verbose -Message 'Importing the required sub-modules' - Import-Module -Force -FullyQualifiedName "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Update-Self.psm1" - - # if -SkipVersionCheck wasn't passed, run the updater - if (-NOT $SkipVersionCheck) { Update-Self -InvocationStatement $MyInvocation.Statement } + if (-NOT $SkipVersionCheck) { Update-WDACConfigPSModule -InvocationStatement $MyInvocation.Statement } if ([WDACConfig.GlobalVars]::ConfigCIBootstrap -eq $false) { Invoke-MockConfigCIBootstrap @@ -116,7 +108,7 @@ Function New-DenyWDACConfig { $CurrentStep++ Write-Progress -Id 22 -Activity 'Processing user selected Folders' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Processing Program Folders From User input' + [WDACConfig.Logger]::Write('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 @@ -135,7 +127,7 @@ Function New-DenyWDACConfig { if ($NoScript) { $UserInputProgramFoldersPolicyMakerHashTable['NoScript'] = $true } if (!$NoUserPEs) { $UserInputProgramFoldersPolicyMakerHashTable['UserPEs'] = $true } - Write-Verbose -Message "Currently scanning and creating a deny policy for the folder: $($ScanLocations[$i])" + [WDACConfig.Logger]::Write("Currently scanning and creating a deny policy for the folder: $($ScanLocations[$i])") New-CIPolicy @UserInputProgramFoldersPolicyMakerHashTable $PolicyXMLFilesArray += (Join-Path -Path $StagingArea -ChildPath "ProgramDir_ScanResults$($i).xml") @@ -144,34 +136,34 @@ Function New-DenyWDACConfig { Write-ColorfulTextWDACConfig -Color Pink -InputText 'The Deny policy with the following configuration is being created' $UserInputProgramFoldersPolicyMakerHashTable - Write-Verbose -Message 'Adding the AllowAll default template policy path to the array of policy paths to merge' + [WDACConfig.Logger]::Write('Adding the AllowAll default template policy path to the array of policy paths to merge') $PolicyXMLFilesArray += $AllowAllPolicyPath $CurrentStep++ Write-Progress -Id 22 -Activity 'Merging the policies' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Creating the final Deny base policy from the xml files in the paths array' + [WDACConfig.Logger]::Write('Creating the final Deny base policy from the xml files in the paths array') $null = Merge-CIPolicy -PolicyPaths $PolicyXMLFilesArray -OutputFilePath $FinalDenyPolicyPath $CurrentStep++ Write-Progress -Id 22 -Activity 'Creating the base policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Assigning a name and resetting the policy ID' + [WDACConfig.Logger]::Write('Assigning a name and resetting the policy ID') $null = Set-CIPolicyIdInfo -FilePath $FinalDenyPolicyPath -ResetPolicyID -PolicyName $PolicyName - Write-Verbose -Message 'Setting the policy version to 1.0.0.0' + [WDACConfig.Logger]::Write('Setting the policy version to 1.0.0.0') Set-CIPolicyVersion -FilePath $FinalDenyPolicyPath -Version '1.0.0.0' Set-CiRuleOptions -FilePath $FinalDenyPolicyPath -Template Base - Write-Verbose -Message 'Converting the policy XML to .CIP' + [WDACConfig.Logger]::Write('Converting the policy XML to .CIP') $null = ConvertFrom-CIPolicy -XmlFilePath $FinalDenyPolicyPath -BinaryFilePath $FinalDenyPolicyCIPPath if ($Deploy) { $CurrentStep++ Write-Progress -Id 22 -Activity 'Deploying the base policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Deploying the policy' + [WDACConfig.Logger]::Write('Deploying the policy') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalDenyPolicyCIPPath -json Write-ColorfulTextWDACConfig -Color Pink -InputText "A Deny Base policy with the name '$PolicyName' has been deployed." @@ -189,7 +181,7 @@ Function New-DenyWDACConfig { $CurrentStep++ Write-Progress -Id 23 -Activity 'Processing user selected Folders' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Looping through each user-selected folder paths, scanning them, creating a temp policy file based on them' + [WDACConfig.Logger]::Write('Looping through each user-selected folder paths, scanning them, creating a temp policy file based on them') powershell.exe -NoProfile -Command { # Prep the environment as a workaround for the ConfigCI bug @@ -228,28 +220,28 @@ Function New-DenyWDACConfig { Write-Progress -Id 23 -Activity 'Merging the policies' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) # Merging AllowAll default policy with our Deny temp policy - Write-Verbose -Message 'Merging AllowAll default template policy with our Deny temp policy' + [WDACConfig.Logger]::Write('Merging AllowAll default template policy with our Deny temp policy') $null = Merge-CIPolicy -PolicyPaths $AllowAllPolicyPath, $TempPolicyPath -OutputFilePath $FinalDenyPolicyPath $CurrentStep++ Write-Progress -Id 23 -Activity 'Configuring the base policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Assigning a name and resetting the policy ID' + [WDACConfig.Logger]::Write('Assigning a name and resetting the policy ID') $null = Set-CIPolicyIdInfo -FilePath $FinalDenyPolicyPath -ResetPolicyID -PolicyName $PolicyName - Write-Verbose -Message 'Setting the policy version to 1.0.0.0' + [WDACConfig.Logger]::Write('Setting the policy version to 1.0.0.0') Set-CIPolicyVersion -FilePath $FinalDenyPolicyPath -Version '1.0.0.0' Set-CiRuleOptions -FilePath $FinalDenyPolicyPath -Template Base - Write-Verbose -Message 'Converting the policy XML to .CIP' + [WDACConfig.Logger]::Write('Converting the policy XML to .CIP') $null = ConvertFrom-CIPolicy -XmlFilePath $FinalDenyPolicyPath -BinaryFilePath $FinalDenyPolicyCIPPath if ($Deploy) { $CurrentStep++ Write-Progress -Id 23 -Activity 'Deploying the base policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Deploying the policy' + [WDACConfig.Logger]::Write('Deploying the policy') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalDenyPolicyCIPPath -json Write-ColorfulTextWDACConfig -Color Pink -InputText "A Deny Base policy with the name '$PolicyName' has been deployed." @@ -277,7 +269,7 @@ Function New-DenyWDACConfig { # Change the color for the list items to plum $PSStyle.Formatting.FormatAccent = "$($PSStyle.Foreground.FromRGB(221,160,221))" - Write-Verbose -Message 'Displaying the installed Appx packages based on the supplied name' + [WDACConfig.Logger]::Write('Displaying the installed Appx packages based on the supplied name') Get-AppxPackage -Name $PackageName | Select-Object -Property Name, Publisher, version, PackageFamilyName, PackageFullName, InstallLocation, Dependencies, SignatureKind, Status # Prompt for confirmation before proceeding @@ -286,7 +278,7 @@ Function New-DenyWDACConfig { $CurrentStep++ Write-Progress -Id 24 -Activity 'Creating the base policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Creating a temporary Deny policy for the supplied Appx package name' + [WDACConfig.Logger]::Write('Creating a temporary Deny policy for the supplied Appx package name') powershell.exe -NoProfile -Command { # Get all the packages based on the supplied name [Microsoft.Windows.Appx.PackageManager.Commands.AppxPackage[]]$Package = Get-AppxPackage -Name $args[0] @@ -303,25 +295,25 @@ Function New-DenyWDACConfig { } -args $PackageName, $TempPolicyPath # Merging AllowAll default policy with our Deny temp policy - Write-Verbose -Message 'Merging AllowAll default template policy with our AppX Deny temp policy' + [WDACConfig.Logger]::Write('Merging AllowAll default template policy with our AppX Deny temp policy') $null = Merge-CIPolicy -PolicyPaths $AllowAllPolicyPath, $TempPolicyPath -OutputFilePath $FinalDenyPolicyPath - Write-Verbose -Message 'Assigning a name and resetting the policy ID' + [WDACConfig.Logger]::Write('Assigning a name and resetting the policy ID') $null = Set-CIPolicyIdInfo -FilePath $FinalDenyPolicyPath -ResetPolicyID -PolicyName $PolicyName - Write-Verbose -Message 'Setting the policy version to 1.0.0.0' + [WDACConfig.Logger]::Write('Setting the policy version to 1.0.0.0') Set-CIPolicyVersion -FilePath $FinalDenyPolicyPath -Version '1.0.0.0' Set-CiRuleOptions -FilePath $FinalDenyPolicyPath -Template Base - Write-Verbose -Message 'Converting the policy XML to .CIP' + [WDACConfig.Logger]::Write('Converting the policy XML to .CIP') $null = ConvertFrom-CIPolicy -XmlFilePath $FinalDenyPolicyPath -BinaryFilePath $FinalDenyPolicyCIPPath if ($Deploy) { $CurrentStep++ Write-Progress -Id 24 -Activity 'Deploying the base policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Deploying the policy' + [WDACConfig.Logger]::Write('Deploying the policy') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalDenyPolicyCIPPath -json Write-ColorfulTextWDACConfig -Color Pink -InputText "A Deny Base policy with the name '$PolicyName' has been deployed." @@ -412,10 +404,10 @@ Function New-DenyWDACConfig { } # Copy the final policy files to the user config directory - if (-NOT $NoCopy) { + if (!$NoCopy) { Copy-Item -Path ($Deploy ? $FinalDenyPolicyPath : $FinalDenyPolicyPath, $FinalDenyPolicyCIPPath) -Destination ([WDACConfig.GlobalVars]::UserConfigDir) -Force } - if (-NOT $Debug) { + if (![WDACConfig.GlobalVars]::DebugPreference) { Remove-Item -Path $StagingArea -Recurse -Force } } diff --git a/WDACConfig/WDACConfig Module Files/Core/New-KernelModeWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/New-KernelModeWDACConfig.psm1 index 8fe50235c..e1cf51703 100644 --- a/WDACConfig/WDACConfig Module Files/Core/New-KernelModeWDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/New-KernelModeWDACConfig.psm1 @@ -18,19 +18,12 @@ Function New-KernelModeWDACConfig { [Parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$SkipVersionCheck ) Begin { - [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $false - [System.Boolean]$Debug = $PSBoundParameters.Debug.IsPresent ? $true : $false [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - Write-Verbose -Message 'Importing the required sub-modules' - Import-Module -Force -FullyQualifiedName @( - "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Update-Self.psm1", - "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Get-KernelModeDriversAudit.psm1" - ) + [WDACConfig.Logger]::Write('Importing the required sub-modules') + Import-Module -Force -FullyQualifiedName @("$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Get-KernelModeDriversAudit.psm1") - # if -SkipVersionCheck wasn't passed, run the updater - if (-NOT $SkipVersionCheck) { Update-Self -InvocationStatement $MyInvocation.Statement } + if (-NOT $SkipVersionCheck) { Update-WDACConfigPSModule -InvocationStatement $MyInvocation.Statement } [System.IO.DirectoryInfo]$StagingArea = [WDACConfig.StagingArea]::NewStagingArea('New-KernelModeWDACConfig') @@ -64,7 +57,7 @@ Function New-KernelModeWDACConfig { [Parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$NoFlights ) begin { - Write-Verbose -Message 'Executing the Build-PrepModeStrictKernelPolicy helper function' + [WDACConfig.Logger]::Write('Executing the Build-PrepModeStrictKernelPolicy helper function') [System.IO.FileInfo]$OutputPolicyPath = Join-Path -Path $StagingArea -ChildPath ($Normal ? 'DefaultWindows_Audit_Kernel.xml' : 'DefaultWindows_Audit_Kernel_NoFlights.xml') [System.String]$PolicyName = $Normal ? 'Strict Kernel mode policy Audit' : 'Strict Kernel No Flights mode policy Audit' @@ -91,14 +84,14 @@ Function New-KernelModeWDACConfig { } process { - Write-Verbose -Message 'Copying the base policy to the Staging Area' + [WDACConfig.Logger]::Write('Copying the base policy to the Staging Area') Copy-Item -Path $TemplatePolicyPath -Destination $OutputPolicyPath -Force - Write-Verbose -Message 'Resetting the policy ID and assigning a name for the policy' + [WDACConfig.Logger]::Write('Resetting the policy ID and assigning a name for the policy') [System.String]$PolicyID = Set-CIPolicyIdInfo -FilePath $OutputPolicyPath -PolicyName "$PolicyName" -ResetPolicyID $PolicyID = $PolicyID.Substring(11) - Write-Verbose -Message 'Setting the policy version to 1.0.0.0' + [WDACConfig.Logger]::Write('Setting the policy version to 1.0.0.0') Set-CIPolicyVersion -FilePath $OutputPolicyPath -Version '1.0.0.0' Set-CiRuleOptions -FilePath $OutputPolicyPath -Template BaseKernel -RulesToAdd 'Enabled:Audit Mode' -RequireEVSigners:$EVSigners -DisableFlightSigning:$NoFlights @@ -137,14 +130,14 @@ Function New-KernelModeWDACConfig { $CurrentStep++ Write-Progress -Id 25 -Activity 'Creating the prep mode policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Building the Audit mode policy' + [WDACConfig.Logger]::Write('Building the Audit mode policy') [PSCustomObject]$AuditPolicy = Build-PrepModeStrictKernelPolicy -Normal [System.String]$PolicyID = $AuditPolicy.PolicyID [System.IO.FileInfo]$AuditPolicyPath = $AuditPolicy.PolicyPath [System.IO.FileInfo]$FinalAuditCIPPath = Join-Path -Path $StagingArea -ChildPath "$PolicyID.cip" - Write-Verbose -Message 'Converting the XML policy file to CIP binary' + [WDACConfig.Logger]::Write('Converting the XML policy file to CIP binary') $null = ConvertFrom-CIPolicy -XmlFilePath $AuditPolicyPath -BinaryFilePath $FinalAuditCIPPath # Deploy the policy if Deploy parameter is used and perform additional tasks on the system @@ -153,10 +146,10 @@ Function New-KernelModeWDACConfig { $CurrentStep++ Write-Progress -Id 25 -Activity 'Deploying the prep mode policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Setting the GUID and time of deployment of the Audit mode policy in the User Configuration file' + [WDACConfig.Logger]::Write('Setting the GUID and time of deployment of the Audit mode policy in the User Configuration file') $null = Set-CommonWDACConfig -StrictKernelPolicyGUID $PolicyID -StrictKernelModePolicyTimeOfDeployment (Get-Date) - Write-Verbose -Message 'Deploying the Strict Kernel mode policy' + [WDACConfig.Logger]::Write('Deploying the Strict Kernel mode policy') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalAuditCIPPath -json Write-ColorfulTextWDACConfig -Color HotPink -InputText 'Strict Kernel mode policy has been deployed in Audit mode, please restart your system.' } @@ -176,13 +169,13 @@ Function New-KernelModeWDACConfig { # 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 - Write-Verbose -Message 'Trying to get the GUID of Strict Kernel Audit mode policy to use for the Enforced mode policy, from the user configurations' + [WDACConfig.Logger]::Write('Trying to get the GUID of Strict Kernel Audit mode policy to use for the Enforced mode policy, from the user configurations') [System.String]$PolicyID = Get-CommonWDACConfig -StrictKernelPolicyGUID - Write-Verbose -Message 'Verifying the Policy ID in the User Config exists and is valid' + [WDACConfig.Logger]::Write('Verifying the Policy ID in the User Config exists and is valid') $ObjectGuid = [System.Guid]::Empty if ([System.Guid]::TryParse($PolicyID, [ref]$ObjectGuid)) { - Write-Verbose -Message 'Valid GUID found in User Configs for Audit mode policy' + [WDACConfig.Logger]::Write('Valid GUID found in User Configs for Audit mode policy') } else { Throw 'Invalid or nonexistent GUID in User Configs for Audit mode policy, Use the -PrepMode parameter first.' @@ -202,39 +195,39 @@ Function New-KernelModeWDACConfig { Remove-Item -LiteralPath ".\$RandomGUID.xml" -Force } - Write-Verbose -Message 'Scanning the kernel-mode drivers detected in Event viewer logs' + [WDACConfig.Logger]::Write('Scanning the kernel-mode drivers detected in Event viewer logs') [System.Collections.ArrayList]$DriverFilesObj = Get-SystemDriver -ScanPath $args[0] - Write-Verbose -Message 'Creating a policy xml file from the driver files' + [WDACConfig.Logger]::Write('Creating a policy xml file from the driver files') New-CIPolicy -MultiplePolicyFormat -Level WHQLFilePublisher -Fallback None -AllowFileNameFallbacks -FilePath $args[1] -DriverFiles $DriverFilesObj } -args $KernelModeDriversDirectory, $DriverFilesScanPolicyPath $CurrentStep++ Write-Progress -Id 26 -Activity 'Creating the final policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Not trusting the policy xml file made before restart, so building the same policy again after restart, this time in Enforced mode instead of Audit mode' + [WDACConfig.Logger]::Write('Not trusting the policy xml file made before restart, so building the same policy again after restart, this time in Enforced mode instead of Audit mode') Copy-Item -Path $TemplatePolicyPath -Destination (Join-Path -Path $StagingArea -ChildPath 'Raw_Normal.xml') -Force - Write-Verbose -Message 'Merging the base policy with the policy made from driver files, to deploy them as one policy' + [WDACConfig.Logger]::Write('Merging the base policy with the policy made from driver files, to deploy them as one policy') $null = Merge-CIPolicy -PolicyPaths (Join-Path -Path $StagingArea -ChildPath 'Raw_Normal.xml'), $DriverFilesScanPolicyPath -OutputFilePath $FinalEnforcedPolicyPath - Write-Verbose -Message 'Moving all AllowedSigners from Usermode to Kernel mode signing scenario' + [WDACConfig.Logger]::Write('Moving all AllowedSigners from Usermode to Kernel mode signing scenario') [WDACConfig.MoveUserModeToKernelMode]::Move($FinalEnforcedPolicyPath) - Write-Verbose -Message 'Setting the GUIDs for the XML policy file' + [WDACConfig.Logger]::Write('Setting the GUIDs for the XML policy file') [WDACConfig.PolicyEditor]::EditGUIDs($PolicyID, $FinalEnforcedPolicyPath) - Write-Verbose -Message 'Setting a new policy name with the current date attached to it' + [WDACConfig.Logger]::Write('Setting a new policy name with the current date attached to it') Set-CIPolicyIdInfo -FilePath $FinalEnforcedPolicyPath -PolicyName "Strict Kernel mode policy Enforced - $(Get-Date -Format 'MM-dd-yyyy')" - Write-Verbose -Message 'Setting the policy version to 1.0.0.0' + [WDACConfig.Logger]::Write('Setting the policy version to 1.0.0.0') Set-CIPolicyVersion -FilePath $FinalEnforcedPolicyPath -Version '1.0.0.0' Set-CiRuleOptions -FilePath $FinalEnforcedPolicyPath -Template BaseKernel -RequireEVSigners:$EVSigners [System.IO.FileInfo]$FinalEnforcedCIPPath = Join-Path -Path $StagingArea -ChildPath "$PolicyID.cip" - Write-Verbose -Message 'Converting the policy XML file to CIP binary' + [WDACConfig.Logger]::Write('Converting the policy XML file to CIP binary') $null = ConvertFrom-CIPolicy -XmlFilePath $FinalEnforcedPolicyPath -BinaryFilePath $FinalEnforcedCIPPath # Deploy the policy if Deploy parameter is used @@ -243,18 +236,18 @@ Function New-KernelModeWDACConfig { $CurrentStep++ Write-Progress -Id 26 -Activity 'Deploying the final policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Deploying the enforced mode policy with the same ID as the Audit mode policy, effectively overwriting it' + [WDACConfig.Logger]::Write('Deploying the enforced mode policy with the same ID as the Audit mode policy, effectively overwriting it') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalEnforcedCIPPath -json Write-ColorfulTextWDACConfig -Color HotPink -InputText 'Strict Kernel mode policy has been deployed in Enforced mode, no restart required.' - Write-Verbose -Message 'Removing the GUID and time of deployment of the StrictKernelPolicy from user configuration' + [WDACConfig.Logger]::Write('Removing the GUID and time of deployment of the StrictKernelPolicy from user configuration') $null = Remove-CommonWDACConfig -StrictKernelPolicyGUID -StrictKernelModePolicyTimeOfDeployment } else { # 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 - Write-Verbose -Message 'Removing the deployed Audit mode policy from the system since -Deploy parameter was not used to overwrite it with the enforced mode policy.' + [WDACConfig.Logger]::Write('Removing the deployed Audit mode policy from the system since -Deploy parameter was not used to overwrite it with the enforced mode policy.') $null = &'C:\Windows\System32\CiTool.exe' --remove-policy "{$PolicyID}" -json Write-ColorfulTextWDACConfig -Color HotPink -InputText "Strict Kernel mode Enforced policy has been created`n$FinalEnforcedPolicyPath" } @@ -278,14 +271,14 @@ Function New-KernelModeWDACConfig { $CurrentStep++ Write-Progress -Id 27 -Activity 'Creating the prep mode policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Building the Audit mode policy' + [WDACConfig.Logger]::Write('Building the Audit mode policy') [PSCustomObject]$AuditPolicy = Build-PrepModeStrictKernelPolicy -NoFlights [System.String]$PolicyID = $AuditPolicy.PolicyID [System.IO.FileInfo]$AuditPolicyPath = $AuditPolicy.PolicyPath [System.IO.FileInfo]$FinalAuditCIPPath = Join-Path -Path $StagingArea -ChildPath "$PolicyID.cip" - Write-Verbose -Message 'Converting the XML policy file to CIP binary' + [WDACConfig.Logger]::Write('Converting the XML policy file to CIP binary') $null = ConvertFrom-CIPolicy -XmlFilePath $AuditPolicyPath -BinaryFilePath $FinalAuditCIPPath # Deploy the policy if Deploy parameter is used and perform additional tasks on the system @@ -294,10 +287,10 @@ Function New-KernelModeWDACConfig { $CurrentStep++ Write-Progress -Id 27 -Activity 'Deploying the prep mode policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Setting the GUID and time of deployment of the Audit mode policy in the User Configuration file' + [WDACConfig.Logger]::Write('Setting the GUID and time of deployment of the Audit mode policy in the User Configuration file') $null = Set-CommonWDACConfig -StrictKernelNoFlightRootsPolicyGUID $PolicyID -StrictKernelModePolicyTimeOfDeployment (Get-Date) - Write-Verbose -Message 'Deploying the Strict Kernel mode policy' + [WDACConfig.Logger]::Write('Deploying the Strict Kernel mode policy') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalAuditCIPPath -json Write-ColorfulTextWDACConfig -Color HotPink -InputText 'Strict Kernel mode policy with no flighting root certs has been deployed in Audit mode, please restart your system.' } @@ -317,13 +310,13 @@ Function New-KernelModeWDACConfig { # 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 - Write-Verbose -Message 'Trying to get the GUID of Strict Kernel Audit mode policy to use for the Enforced mode policy, from the user configurations' + [WDACConfig.Logger]::Write('Trying to get the GUID of Strict Kernel Audit mode policy to use for the Enforced mode policy, from the user configurations') [System.String]$PolicyID = Get-CommonWDACConfig -StrictKernelNoFlightRootsPolicyGUID - Write-Verbose -Message 'Verifying the Policy ID in the User Config exists and is valid' + [WDACConfig.Logger]::Write('Verifying the Policy ID in the User Config exists and is valid') $ObjectGuid = [System.Guid]::Empty if ([System.Guid]::TryParse($PolicyID, [ref]$ObjectGuid)) { - Write-Verbose -Message 'Valid GUID found in User Configs for Audit mode policy' + [WDACConfig.Logger]::Write('Valid GUID found in User Configs for Audit mode policy') } else { Throw 'Invalid or nonexistent GUID in User Configs for Audit mode policy, Use the -PrepMode parameter first.' @@ -343,39 +336,39 @@ Function New-KernelModeWDACConfig { Remove-Item -LiteralPath ".\$RandomGUID.xml" -Force } - Write-Verbose -Message 'Scanning the kernel-mode drivers detected in Event viewer logs' + [WDACConfig.Logger]::Write('Scanning the kernel-mode drivers detected in Event viewer logs') [System.Collections.ArrayList]$DriverFilesObj = Get-SystemDriver -ScanPath $args[0] - Write-Verbose -Message 'Creating a policy xml file from the driver files' + [WDACConfig.Logger]::Write('Creating a policy xml file from the driver files') New-CIPolicy -MultiplePolicyFormat -Level WHQLFilePublisher -Fallback None -AllowFileNameFallbacks -FilePath $args[1] -DriverFiles $DriverFilesObj } -args $KernelModeDriversDirectory, $DriverFilesScanPolicyPath $CurrentStep++ Write-Progress -Id 28 -Activity 'Creating the final policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Not trusting the policy xml file made before restart, so building the same policy again after restart, this time in Enforced mode instead of Audit mode' + [WDACConfig.Logger]::Write('Not trusting the policy xml file made before restart, so building the same policy again after restart, this time in Enforced mode instead of Audit mode') Copy-Item -Path $TemplatePolicyPath -Destination (Join-Path -Path $StagingArea -ChildPath 'Raw_NoFlights.xml') -Force - Write-Verbose -Message 'Merging the base policy with the policy made from driver files, to deploy them as one policy' + [WDACConfig.Logger]::Write('Merging the base policy with the policy made from driver files, to deploy them as one policy') $null = Merge-CIPolicy -PolicyPaths (Join-Path -Path $StagingArea -ChildPath 'Raw_NoFlights.xml'), $DriverFilesScanPolicyPath -OutputFilePath $FinalEnforcedPolicyPath - Write-Verbose -Message 'Moving all AllowedSigners from Usermode to Kernel mode signing scenario' + [WDACConfig.Logger]::Write('Moving all AllowedSigners from Usermode to Kernel mode signing scenario') [WDACConfig.MoveUserModeToKernelMode]::Move($FinalEnforcedPolicyPath) - Write-Verbose -Message 'Setting the GUIDs for the XML policy file' + [WDACConfig.Logger]::Write('Setting the GUIDs for the XML policy file') [WDACConfig.PolicyEditor]::EditGUIDs($PolicyID, $FinalEnforcedPolicyPath) - Write-Verbose -Message 'Setting a new policy name with the current date attached to it' + [WDACConfig.Logger]::Write('Setting a new policy name with the current date attached to it') Set-CIPolicyIdInfo -FilePath $FinalEnforcedPolicyPath -PolicyName "Strict Kernel No Flights mode policy Enforced - $(Get-Date -Format 'MM-dd-yyyy')" - Write-Verbose -Message 'Setting the policy version to 1.0.0.0' + [WDACConfig.Logger]::Write('Setting the policy version to 1.0.0.0') Set-CIPolicyVersion -FilePath $FinalEnforcedPolicyPath -Version '1.0.0.0' Set-CiRuleOptions -FilePath $FinalEnforcedPolicyPath -Template BaseKernel -RulesToAdd 'Disabled:Flight Signing' -RequireEVSigners:$EVSigners [System.IO.FileInfo]$FinalEnforcedCIPPath = Join-Path -Path $StagingArea -ChildPath "$PolicyID.cip" - Write-Verbose -Message 'Converting the policy XML file to CIP binary' + [WDACConfig.Logger]::Write('Converting the policy XML file to CIP binary') $null = ConvertFrom-CIPolicy -XmlFilePath $FinalEnforcedPolicyPath -BinaryFilePath $FinalEnforcedCIPPath # Deploy the policy if Deploy parameter is used @@ -383,31 +376,31 @@ Function New-KernelModeWDACConfig { if ([System.IO.File]::Exists('C:\Windows\System32\ntoskrnl.exe')) { - Write-Verbose -Message 'Making sure the current Windows build can work with the NoFlightRoots Strict WDAC Policy' + [WDACConfig.Logger]::Write('Making sure the current Windows build can work with the NoFlightRoots Strict WDAC Policy') if (-NOT (Invoke-WDACSimulation -FilePath 'C:\Windows\System32\ntoskrnl.exe' -XmlFilePath $FinalEnforcedPolicyPath -BooleanOutput -NoCatalogScanning -ThreadsCount 1 -SkipVersionCheck)) { Throw 'The current Windows build cannot work with the NoFlightRoots Strict Kernel-mode Policy, please change the base to Default instead.' } } else { - Write-Verbose -Message "'C:\Windows\System32\ntoskrnl.exe' could not be found." + [WDACConfig.Logger]::Write("'C:\Windows\System32\ntoskrnl.exe' could not be found.") } $CurrentStep++ Write-Progress -Id 28 -Activity 'Deploying the final policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Deploying the enforced mode policy with the same ID as the Audit mode policy, effectively overwriting it' + [WDACConfig.Logger]::Write('Deploying the enforced mode policy with the same ID as the Audit mode policy, effectively overwriting it') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalEnforcedCIPPath -json Write-ColorfulTextWDACConfig -Color HotPink -InputText 'Strict Kernel mode policy with no flighting root certs has been deployed in Enforced mode, no restart required.' - Write-Verbose -Message 'Removing the GUID and time of deployment of the StrictKernelNoFlightRootsPolicy from user configuration' + [WDACConfig.Logger]::Write('Removing the GUID and time of deployment of the StrictKernelNoFlightRootsPolicy from user configuration') $null = Remove-CommonWDACConfig -StrictKernelNoFlightRootsPolicyGUID -StrictKernelModePolicyTimeOfDeployment } else { # 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 - Write-Verbose -Message 'Removing the deployed Audit mode policy from the system since -Deploy parameter was not used to overwrite it with the enforced mode policy.' + [WDACConfig.Logger]::Write('Removing the deployed Audit mode policy from the system since -Deploy parameter was not used to overwrite it with the enforced mode policy.') $null = &'C:\Windows\System32\CiTool.exe' --remove-policy "{$PolicyID}" -json Write-ColorfulTextWDACConfig -Color HotPink -InputText "Strict Kernel mode Enforced policy with no flighting root certs has been created`n$FinalEnforcedPolicyPath" } @@ -425,10 +418,10 @@ Function New-KernelModeWDACConfig { } finally { # Copy the final policy files to the User Config directory - if (-NOT $NoCopy) { + if (!$NoCopy) { Copy-Item -Path ($Mode -eq 'Prep' ? ($Deploy ? $AuditPolicyPath : $AuditPolicyPath, $FinalAuditCIPPath) : ($Deploy ? $FinalEnforcedPolicyPath : $FinalEnforcedPolicyPath, $FinalEnforcedCIPPath)) -Destination ([WDACConfig.GlobalVars]::UserConfigDir) -Force } - if (-NOT $Debug) { + if (![WDACConfig.GlobalVars]::DebugPreference) { Remove-Item -Path $StagingArea -Recurse -Force } } diff --git a/WDACConfig/WDACConfig Module Files/Core/New-SupplementalWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/New-SupplementalWDACConfig.psm1 index f7a1e1e5d..2a1edf432 100644 --- a/WDACConfig/WDACConfig Module Files/Core/New-SupplementalWDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/New-SupplementalWDACConfig.psm1 @@ -75,13 +75,7 @@ Function New-SupplementalWDACConfig { [Parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$SkipVersionCheck ) Begin { - [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $false - [System.Boolean]$Debug = $PSBoundParameters.Debug.IsPresent ? $true : $false [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - - Write-Verbose -Message 'Importing the required sub-modules' - Import-Module -Force -FullyQualifiedName "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Update-Self.psm1" if ($PSBoundParameters['Certificates']) { Import-Module -Force -FullyQualifiedName @( @@ -90,8 +84,7 @@ Function New-SupplementalWDACConfig { ) } - # if -SkipVersionCheck wasn't passed, run the updater - if (-NOT $SkipVersionCheck) { Update-Self -InvocationStatement $MyInvocation.Statement } + if (-NOT $SkipVersionCheck) { Update-WDACConfigPSModule -InvocationStatement $MyInvocation.Statement } if ([WDACConfig.GlobalVars]::ConfigCIBootstrap -eq $false) { Invoke-MockConfigCIBootstrap @@ -148,7 +141,7 @@ Function New-SupplementalWDACConfig { $CurrentStep++ Write-Progress -Id 19 -Activity 'Processing user selected folders' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Processing Program Folder From User input' + [WDACConfig.Logger]::Write('Processing Program Folder From User input') # Creating a hash table to dynamically add parameters based on user input and pass them to New-Cipolicy cmdlet [System.Collections.Hashtable]$PolicyMakerHashTable = @{ FilePath = $FinalSupplementalPath @@ -174,22 +167,22 @@ Function New-SupplementalWDACConfig { $CurrentStep++ Write-Progress -Id 19 -Activity 'Configuring the Supplemental policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Changing the policy type from base to Supplemental, assigning its name and resetting its policy ID' + [WDACConfig.Logger]::Write('Changing the policy type from base to Supplemental, assigning its name and resetting its policy ID') $null = Set-CIPolicyIdInfo -FilePath $FinalSupplementalPath -ResetPolicyID -BasePolicyToSupplementPath $PolicyPath -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" - Write-Verbose -Message 'Setting the Supplemental policy version to 1.0.0.0' + [WDACConfig.Logger]::Write('Setting the Supplemental policy version to 1.0.0.0') Set-CIPolicyVersion -FilePath $FinalSupplementalPath -Version '1.0.0.0' Set-CiRuleOptions -FilePath $FinalSupplementalPath -Template Supplemental - Write-Verbose -Message 'Converting the Supplemental policy XML file to a CIP file' + [WDACConfig.Logger]::Write('Converting the Supplemental policy XML file to a CIP file') $null = ConvertFrom-CIPolicy -XmlFilePath $FinalSupplementalPath -BinaryFilePath $FinalSupplementalCIPPath if ($Deploy) { $CurrentStep++ Write-Progress -Id 19 -Activity 'Deploying the Supplemental policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Deploying the Supplemental policy' + [WDACConfig.Logger]::Write('Deploying the Supplemental policy') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalSupplementalCIPPath -json Write-ColorfulTextWDACConfig -Color Pink -InputText "A Supplemental policy with the name '$SuppPolicyName' has been deployed." } @@ -206,28 +199,28 @@ Function New-SupplementalWDACConfig { Write-Progress -Id 20 -Activity 'Creating the Supplemental policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) # Using Windows PowerShell to handle serialized data since PowerShell core throws an error - Write-Verbose -Message 'Creating the Supplemental policy file' + [WDACConfig.Logger]::Write('Creating the Supplemental policy file') powershell.exe -NoProfile -Command { $RulesWildCards = New-CIPolicyRule -FilePathRule $args[0] New-CIPolicy -MultiplePolicyFormat -FilePath "$($args[2])\SupplementalPolicy $($args[1]).xml" -Rules $RulesWildCards } -args $FolderPath, $SuppPolicyName, $StagingArea - Write-Verbose -Message 'Changing the policy type from base to Supplemental, assigning its name and resetting its policy ID' + [WDACConfig.Logger]::Write('Changing the policy type from base to Supplemental, assigning its name and resetting its policy ID') $null = Set-CIPolicyIdInfo -FilePath $FinalSupplementalPath -ResetPolicyID -BasePolicyToSupplementPath $PolicyPath -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" - Write-Verbose -Message 'Setting the Supplemental policy version to 1.0.0.0' + [WDACConfig.Logger]::Write('Setting the Supplemental policy version to 1.0.0.0') Set-CIPolicyVersion -FilePath $FinalSupplementalPath -Version '1.0.0.0' Set-CiRuleOptions -FilePath $FinalSupplementalPath -Template Supplemental - Write-Verbose -Message 'Converting the Supplemental policy XML file to a CIP file' + [WDACConfig.Logger]::Write('Converting the Supplemental policy XML file to a CIP file') $null = ConvertFrom-CIPolicy -XmlFilePath $FinalSupplementalPath -BinaryFilePath $FinalSupplementalCIPPath if ($Deploy) { $CurrentStep++ Write-Progress -Id 20 -Activity 'Deploying the Supplemental policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Deploying the Supplemental policy' + [WDACConfig.Logger]::Write('Deploying the Supplemental policy') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalSupplementalCIPPath -json Write-ColorfulTextWDACConfig -Color Pink -InputText "A Supplemental policy with the name '$SuppPolicyName' has been deployed." } @@ -252,7 +245,7 @@ Function New-SupplementalWDACConfig { # Change the color for the list items to plum $PSStyle.Formatting.FormatAccent = "$($PSStyle.Foreground.FromRGB(221,160,221))" - Write-Verbose -Message 'Displaying the installed Appx packages based on the supplied name' + [WDACConfig.Logger]::Write('Displaying the installed Appx packages based on the supplied name') Get-AppxPackage -Name $PackageName | Select-Object -Property Name, Publisher, version, PackageFamilyName, PackageFullName, InstallLocation, Dependencies, SignatureKind, Status # Prompt for confirmation before proceeding @@ -261,7 +254,7 @@ Function New-SupplementalWDACConfig { $CurrentStep++ Write-Progress -Id 21 -Activity 'Creating the Supplemental policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Creating a policy for the supplied Appx package name and its dependencies (if any)' + [WDACConfig.Logger]::Write('Creating a policy for the supplied Appx package name and its dependencies (if any)') powershell.exe -NoProfile -Command { # Get all the packages based on the supplied name [Microsoft.Windows.Appx.PackageManager.Commands.AppxPackage[]]$Package = Get-AppxPackage -Name $args[0] @@ -287,22 +280,22 @@ Function New-SupplementalWDACConfig { New-CIPolicy -MultiplePolicyFormat -FilePath "$($args[2])\SupplementalPolicy $($args[1]).xml" -Rules $Rules } -args $PackageName, $SuppPolicyName, $StagingArea - Write-Verbose -Message 'Converting the policy type from base to Supplemental, assigning its name and resetting its policy ID' + [WDACConfig.Logger]::Write('Converting the policy type from base to Supplemental, assigning its name and resetting its policy ID') $null = Set-CIPolicyIdInfo -FilePath $FinalSupplementalPath -ResetPolicyID -BasePolicyToSupplementPath $PolicyPath -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" - Write-Verbose -Message 'Setting the Supplemental policy version to 1.0.0.0' + [WDACConfig.Logger]::Write('Setting the Supplemental policy version to 1.0.0.0') Set-CIPolicyVersion -FilePath $FinalSupplementalPath -Version '1.0.0.0' Set-CiRuleOptions -FilePath $FinalSupplementalPath -Template Supplemental - Write-Verbose -Message 'Converting the Supplemental policy XML file to a CIP file' + [WDACConfig.Logger]::Write('Converting the Supplemental policy XML file to a CIP file') $null = ConvertFrom-CIPolicy -XmlFilePath $FinalSupplementalPath -BinaryFilePath $FinalSupplementalCIPPath if ($Deploy) { $CurrentStep++ Write-Progress -Id 21 -Activity 'Deploying the Supplemental policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Deploying the Supplemental policy' + [WDACConfig.Logger]::Write('Deploying the Supplemental policy') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalSupplementalCIPPath -json Write-ColorfulTextWDACConfig -Color Pink -InputText "A Supplemental policy with the name '$SuppPolicyName' has been deployed." } @@ -329,10 +322,10 @@ Function New-SupplementalWDACConfig { $CurrentStep++ Write-Progress -Id 33 -Activity 'Preparing the policy template' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Copying the template policy to the staging area' + [WDACConfig.Logger]::Write('Copying the template policy to the staging area') Copy-Item -LiteralPath 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowAll.xml' -Destination $FinalSupplementalPath -Force - Write-Verbose -Message 'Emptying the policy file in preparation for the new data insertion' + [WDACConfig.Logger]::Write('Emptying the policy file in preparation for the new data insertion') Clear-CiPolicy_Semantic -Path $FinalSupplementalPath $CurrentStep++ @@ -364,22 +357,22 @@ Function New-SupplementalWDACConfig { $CurrentStep++ Write-Progress -Id 33 -Activity 'Finalizing the Supplemental policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Converting the policy type from base to Supplemental, assigning its name and resetting its policy ID' + [WDACConfig.Logger]::Write('Converting the policy type from base to Supplemental, assigning its name and resetting its policy ID') $null = Set-CIPolicyIdInfo -FilePath $FinalSupplementalPath -ResetPolicyID -BasePolicyToSupplementPath $PolicyPath -PolicyName "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')" - Write-Verbose -Message 'Setting the Supplemental policy version to 1.0.0.0' + [WDACConfig.Logger]::Write('Setting the Supplemental policy version to 1.0.0.0') Set-CIPolicyVersion -FilePath $FinalSupplementalPath -Version '1.0.0.0' Set-CiRuleOptions -FilePath $FinalSupplementalPath -Template Supplemental - Write-Verbose -Message 'Converting the Supplemental policy XML file to a CIP file' + [WDACConfig.Logger]::Write('Converting the Supplemental policy XML file to a CIP file') $null = ConvertFrom-CIPolicy -XmlFilePath $FinalSupplementalPath -BinaryFilePath $FinalSupplementalCIPPath if ($Deploy) { $CurrentStep++ Write-Progress -Id 33 -Activity 'Deploying the Supplemental policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Deploying the Supplemental policy' + [WDACConfig.Logger]::Write('Deploying the Supplemental policy') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalSupplementalCIPPath -json Write-ColorfulTextWDACConfig -Color Pink -InputText "A Supplemental policy with the name '$SuppPolicyName' has been deployed." } @@ -401,10 +394,10 @@ Function New-SupplementalWDACConfig { } # Copy the final files to the user config directory - if (-NOT $NoCopy) { + if (!$NoCopy) { Copy-Item -Path ($Deploy ? $FinalSupplementalPath : $FinalSupplementalPath, $FinalSupplementalCIPPath) -Destination ([WDACConfig.GlobalVars]::UserConfigDir) -Force } - if (-NOT $Debug) { + if (![WDACConfig.GlobalVars]::DebugPreference) { Remove-Item -Path $StagingArea -Recurse -Force } } diff --git a/WDACConfig/WDACConfig Module Files/Core/New-WDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/New-WDACConfig.psm1 index b3dcfef04..4553c9211 100644 --- a/WDACConfig/WDACConfig Module Files/Core/New-WDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/New-WDACConfig.psm1 @@ -59,13 +59,7 @@ Function New-WDACConfig { return $ParamDictionary } Begin { - [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $false - [System.Boolean]$Debug = $PSBoundParameters.Debug.IsPresent ? $true : $false [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - - Write-Verbose -Message 'Importing the required sub-modules' - Import-Module -Force -FullyQualifiedName "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Update-Self.psm1" if ([WDACConfig.GlobalVars]::ConfigCIBootstrap -eq $false) { Invoke-MockConfigCIBootstrap @@ -95,14 +89,14 @@ Function New-WDACConfig { $CurrentStep++ Write-Progress -Id 2 -Activity 'Setting up the Scheduled task' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Deleting the MSFT Driver Block list update Scheduled task if it exists' + [WDACConfig.Logger]::Write('Deleting the MSFT Driver Block list update Scheduled task if it exists') Get-ScheduledTask -TaskName 'MSFT Driver Block list update' -TaskPath '\MSFT Driver Block list update\' -ErrorAction Ignore | Unregister-ScheduledTask -Confirm:$false - Write-Verbose -Message 'Creating the MSFT Driver Block list update task' + [WDACConfig.Logger]::Write('Creating the MSFT Driver Block list update task') # 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) -[System.String]$TaskArgument = @' + [System.String]$TaskArgument = @' -NoProfile -WindowStyle Hidden -command "& {try {Invoke-WebRequest -Uri 'https://aka.ms/VulnerableDriverBlockList' -OutFile 'VulnerableDriverBlockList.zip' -ErrorAction Stop}catch{exit 1};Expand-Archive -Path '.\VulnerableDriverBlockList.zip' -DestinationPath 'VulnerableDriverBlockList' -Force;$SiPolicy_EnforcedFile = Get-ChildItem -Recurse -File -Path '.\VulnerableDriverBlockList' -Filter 'SiPolicy_Enforced.p7b' | Select-Object -First 1;Move-Item -Path $SiPolicy_EnforcedFile.FullName -Destination ($env:SystemDrive + '\Windows\System32\CodeIntegrity\SiPolicy.p7b') -Force;citool --refresh -json;Remove-Item -Path '.\VulnerableDriverBlockList' -Recurse -Force;Remove-Item -Path '.\VulnerableDriverBlockList.zip' -Force;}" '@ # Create a scheduled task action, this defines how to download and install the latest Microsoft Recommended Driver Block Rules @@ -123,7 +117,7 @@ Function New-WDACConfig { # 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' + [WDACConfig.Logger]::Write('Displaying extra info about the Microsoft recommended Drivers block list') Invoke-Command -ScriptBlock $DriversBlockListInfoGatheringSCRIPTBLOCK Write-Progress -Id 2 -Activity 'complete.' -Completed @@ -146,12 +140,12 @@ Function New-WDACConfig { $CurrentStep++ Write-Progress -Id 1 -Activity 'Refreshing the system policies' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Refreshing the system WDAC policies using CiTool.exe' + [WDACConfig.Logger]::Write('Refreshing the system WDAC policies using CiTool.exe') $null = &'C:\Windows\System32\CiTool.exe' --refresh -json Write-ColorfulTextWDACConfig -Color Pink -InputText 'SiPolicy.p7b has been deployed and policies refreshed.' - Write-Verbose -Message "Displaying extra info about the $Name" + [WDACConfig.Logger]::Write("Displaying extra info about the $Name") Invoke-Command -ScriptBlock $DriversBlockListInfoGatheringSCRIPTBLOCK } else { @@ -170,7 +164,7 @@ Function New-WDACConfig { # Get the SiPolicy node [System.Xml.XmlElement]$SiPolicyNode = $DriverBlockRulesXML.SiPolicy - Write-Verbose -Message "Removing the 'Allow all rules' from the policy" + [WDACConfig.Logger]::Write("Removing the 'Allow all rules' from the policy") # Declare the namespace manager and add the default namespace with a prefix [System.Xml.XmlNamespaceManager]$NameSpace = New-Object -TypeName System.Xml.XmlNamespaceManager -ArgumentList $DriverBlockRulesXML.NameTable @@ -226,7 +220,7 @@ Function New-WDACConfig { Set-CiRuleOptions -FilePath $XMLPath -RulesToRemove 'Enabled:Audit Mode' - Write-Verbose -Message "Displaying extra info about the $Name" + [WDACConfig.Logger]::Write("Displaying extra info about the $Name") Invoke-Command -ScriptBlock $DriversBlockListInfoGatheringSCRIPTBLOCK # Copy the result to the User Config directory at the end @@ -259,16 +253,16 @@ Function New-WDACConfig { Get-BlockRules - Write-Verbose -Message 'Copying the AllowMicrosoft.xml from Windows directory to the Staging Area' + [WDACConfig.Logger]::Write('Copying the AllowMicrosoft.xml from Windows directory to the Staging Area') Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowMicrosoft.xml' -Destination $FinalPolicyPath -Force $CurrentStep++ Write-Progress -Id 3 -Activity 'Configuring the policy settings' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Resetting the policy ID and assigning policy name' + [WDACConfig.Logger]::Write('Resetting the policy ID and assigning policy name') $null = Set-CIPolicyIdInfo -FilePath $FinalPolicyPath -PolicyName "$Name - $(Get-Date -Format 'MM-dd-yyyy')" -ResetPolicyID - Write-Verbose -Message 'Setting policy version to 1.0.0.0' + [WDACConfig.Logger]::Write('Setting policy version to 1.0.0.0') Set-CIPolicyVersion -FilePath $FinalPolicyPath -Version '1.0.0.0' Set-CiRuleOptions -FilePath $FinalPolicyPath -Template Base -TestMode:$TestMode -RequireEVSigners:$RequireEVSigners -ScriptEnforcement:$EnableScriptEnforcement -EnableAuditMode:$Audit @@ -277,10 +271,10 @@ Function New-WDACConfig { $CurrentStep++ Write-Progress -Id 3 -Activity 'Creating CIP file' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Converting the policy file to .CIP binary' + [WDACConfig.Logger]::Write('Converting the policy file to .CIP binary') [System.IO.FileInfo]$CIPPath = ConvertFrom-CIPolicy -XmlFilePath $FinalPolicyPath -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$Name.cip") - Write-Verbose -Message "Deploying the $Name policy" + [WDACConfig.Logger]::Write("Deploying the $Name policy") $null = &'C:\Windows\System32\CiTool.exe' --update-policy $CIPPath -json } Copy-Item -Path $FinalPolicyPath -Destination ([WDACConfig.GlobalVars]::UserConfigDir) -Force @@ -311,7 +305,7 @@ Function New-WDACConfig { [System.IO.FileInfo]$FinalPolicyPath = Join-Path -Path $StagingArea -ChildPath "$Name.xml" - Write-Verbose -Message 'Copying the DefaultWindows_Enforced.xml from Windows directory to the Staging Area' + [WDACConfig.Logger]::Write('Copying the DefaultWindows_Enforced.xml from Windows directory to the Staging Area') Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\DefaultWindows_Enforced.xml' -Destination $FinalPolicyPath -Force $CurrentStep++ @@ -322,17 +316,17 @@ Function New-WDACConfig { Write-ColorfulTextWDACConfig -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 $PSHOME -Level FilePublisher -NoScript -Fallback Hash -UserPEs -UserWriteablePaths -MultiplePolicyFormat -FilePath (Join-Path -Path $StagingArea -ChildPath 'AllowPowerShell.xml') - Write-Verbose -Message "Merging the policy files to create the final $Name.xml policy" + [WDACConfig.Logger]::Write("Merging the policy files to create the final $Name.xml policy") $null = Merge-CIPolicy -PolicyPaths $FinalPolicyPath, (Join-Path -Path $StagingArea -ChildPath 'AllowPowerShell.xml') -OutputFilePath $FinalPolicyPath } $CurrentStep++ Write-Progress -Id 7 -Activity 'Configuring policy settings' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Resetting the policy ID and assigning policy name' + [WDACConfig.Logger]::Write('Resetting the policy ID and assigning policy name') $null = Set-CIPolicyIdInfo -FilePath $FinalPolicyPath -PolicyName "$Name - $(Get-Date -Format 'MM-dd-yyyy')" -ResetPolicyID - Write-Verbose -Message 'Setting the policy version to 1.0.0.0' + [WDACConfig.Logger]::Write('Setting the policy version to 1.0.0.0') Set-CIPolicyVersion -FilePath $FinalPolicyPath -Version '1.0.0.0' Set-CiRuleOptions -FilePath $FinalPolicyPath -Template Base -TestMode:$TestMode -RequireEVSigners:$RequireEVSigners -ScriptEnforcement:$EnableScriptEnforcement -EnableAuditMode:$Audit @@ -341,10 +335,10 @@ Function New-WDACConfig { $CurrentStep++ Write-Progress -Id 7 -Activity 'Creating the CIP file' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Converting the policy file to .CIP binary' + [WDACConfig.Logger]::Write('Converting the policy file to .CIP binary') [System.IO.FileInfo]$CIPPath = ConvertFrom-CIPolicy -XmlFilePath $FinalPolicyPath -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$Name.cip") - Write-Verbose -Message 'Deploying the policy' + [WDACConfig.Logger]::Write('Deploying the policy') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $CIPPath -json } @@ -367,7 +361,7 @@ Function New-WDACConfig { [System.IO.FileInfo]$FinalPolicyPath = Join-Path -Path $StagingArea -ChildPath "$Name.xml" } Process { - Write-Verbose -Message "Getting the latest $Name from the official Microsoft GitHub repository" + [WDACConfig.Logger]::Write("Getting the latest $Name from the official Microsoft GitHub repository") [System.String]$MSFTRecommendedBlockRulesAsString = (Invoke-WebRequest -Uri ([WDACConfig.GlobalVars]::MSFTRecommendedBlockRulesURL) -ProgressAction SilentlyContinue).Content # Load the Block Rules into a variable after extracting them from the markdown string @@ -377,22 +371,22 @@ Function New-WDACConfig { Set-CiRuleOptions -FilePath $FinalPolicyPath -RulesToRemove 'Enabled:Audit Mode' -RulesToAdd 'Enabled:Update Policy No Reboot' - Write-Verbose -Message 'Assigning policy name and resetting policy ID' + [WDACConfig.Logger]::Write('Assigning policy name and resetting policy ID') $null = Set-CIPolicyIdInfo -ResetPolicyID -FilePath $FinalPolicyPath -PolicyName $Name if ($Deploy) { - Write-Verbose -Message "Checking if the $Name policy is already deployed" + [WDACConfig.Logger]::Write("Checking if the $Name policy is already deployed") [System.String]$CurrentlyDeployedBlockRulesGUID = ((&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { ($_.IsSystemPolicy -ne 'True') -and ($_.PolicyID -eq $_.BasePolicyID) -and ($_.FriendlyName -eq $Name) }).PolicyID if (-NOT ([System.String]::IsNullOrWhiteSpace($CurrentlyDeployedBlockRulesGUID))) { - Write-Verbose -Message "$Name policy is already deployed, updating it using the same GUID." + [WDACConfig.Logger]::Write("$Name policy is already deployed, updating it using the same GUID.") [WDACConfig.PolicyEditor]::EditGUIDs($CurrentlyDeployedBlockRulesGUID, $FinalPolicyPath) } [System.IO.FileInfo]$CIPPath = ConvertFrom-CIPolicy -XmlFilePath $FinalPolicyPath -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$Name.cip") - Write-Verbose -Message "Deploying the $Name policy" + [WDACConfig.Logger]::Write("Deploying the $Name policy") $null = &'C:\Windows\System32\CiTool.exe' --update-policy $CIPPath -json } else { @@ -425,7 +419,7 @@ Function New-WDACConfig { Get-BlockRules - Write-Verbose -Message 'Copying the AllowMicrosoft.xml from Windows directory to the Staging Area' + [WDACConfig.Logger]::Write('Copying the AllowMicrosoft.xml from Windows directory to the Staging Area') Copy-Item -Path 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowMicrosoft.xml' -Destination $FinalPolicyPath -Force $CurrentStep++ @@ -436,10 +430,10 @@ Function New-WDACConfig { $CurrentStep++ Write-Progress -Id 6 -Activity 'Configuring the policy settings' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Resetting the policy ID and assigning policy name' + [WDACConfig.Logger]::Write('Resetting the policy ID and assigning policy name') $null = Set-CIPolicyIdInfo -FilePath $FinalPolicyPath -ResetPolicyID -PolicyName "$Name - $(Get-Date -Format 'MM-dd-yyyy')" - Write-Verbose -Message 'Setting the policy version to 1.0.0.0' + [WDACConfig.Logger]::Write('Setting the policy version to 1.0.0.0') Set-CIPolicyVersion -FilePath $FinalPolicyPath -Version '1.0.0.0' if ($Deploy) { @@ -447,17 +441,17 @@ Function New-WDACConfig { $CurrentStep++ Write-Progress -Id 6 -Activity 'Creating the CIP file' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Converting the policy to .CIP binary' + [WDACConfig.Logger]::Write('Converting the policy to .CIP binary') [System.IO.FileInfo]$CIPPath = ConvertFrom-CIPolicy -XmlFilePath $FinalPolicyPath -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$Name.cip") $CurrentStep++ Write-Progress -Id 6 -Activity 'Configuring Windows Services' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Configuring required services for ISG authorization' + [WDACConfig.Logger]::Write('Configuring required services for ISG authorization') Start-Process -FilePath 'C:\Windows\System32\appidtel.exe' -ArgumentList 'start' -NoNewWindow Start-Process -FilePath 'C:\Windows\System32\sc.exe' -ArgumentList 'config', 'appidsvc', 'start= auto' -NoNewWindow - Write-Verbose -Message 'Deploying the policy' + [WDACConfig.Logger]::Write('Deploying the policy') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $CIPPath -json } @@ -489,8 +483,7 @@ Function New-WDACConfig { } } - # if -SkipVersionCheck wasn't passed, run the updater - if (-NOT $SkipVersionCheck) { Update-Self -InvocationStatement $MyInvocation.Statement } + if (-NOT $SkipVersionCheck) { Update-WDACConfigPSModule -InvocationStatement $MyInvocation.Statement } } process { @@ -512,7 +505,7 @@ Function New-WDACConfig { throw $_ } Finally { - if (-NOT $Debug) { + if (![WDACConfig.GlobalVars]::DebugPreference) { Remove-Item -Path $StagingArea -Recurse -Force } } diff --git a/WDACConfig/WDACConfig Module Files/Core/Remove-CommonWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Remove-CommonWDACConfig.psm1 index 4448baee4..144b27d9b 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Remove-CommonWDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Remove-CommonWDACConfig.psm1 @@ -18,25 +18,22 @@ Function Remove-CommonWDACConfig { [Parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$Force ) begin { - [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $false if ($(Get-PSCallStack).Count -le 2) { [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) } else { [WDACConfig.LoggerInitializer]::Initialize($null, $null, $Host) } - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - # Create User configuration folder if it doesn't already exist if (-NOT ([System.IO.Directory]::Exists((Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Parent)))) { $null = New-Item -ItemType Directory -Path (Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Parent) -Force - Write-Verbose -Message 'The WDACConfig folder in Program Files has been created because it did not exist.' + [WDACConfig.Logger]::Write('The WDACConfig folder in Program Files has been created because it did not exist.') } # Create User configuration file if it doesn't already exist if (-NOT ([System.IO.File]::Exists(([WDACConfig.GlobalVars]::UserConfigJson)))) { $null = New-Item -ItemType File -Path (Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Parent) -Name (Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Leaf) -Force - Write-Verbose -Message 'The UserConfigurations.json file has been created because it did not exist.' + [WDACConfig.Logger]::Write('The UserConfigurations.json file has been created because it did not exist.') } # Detecting if Confirm switch is used to bypass the confirmation prompts @@ -52,7 +49,7 @@ Function Remove-CommonWDACConfig { if ($PSCmdlet.ShouldProcess('This PC', 'Delete the entire User Configurations for WDACConfig module')) { Remove-Item -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Force - Write-Verbose -Message 'User Configurations for WDACConfig module have been deleted.' + [WDACConfig.Logger]::Write('User Configurations for WDACConfig module have been deleted.') } # set a boolean value that returns from the Process and End blocks as well @@ -90,7 +87,7 @@ Function Remove-CommonWDACConfig { if ($true -eq $ReturnAndDone) { return } if ($SignedPolicyPath) { - Write-Verbose -Message 'Removing the SignedPolicyPath' + [WDACConfig.Logger]::Write('Removing the SignedPolicyPath') $UserConfigurationsObject.SignedPolicyPath = '' } else { @@ -98,7 +95,7 @@ Function Remove-CommonWDACConfig { } if ($UnsignedPolicyPath) { - Write-Verbose -Message 'Removing the UnsignedPolicyPath' + [WDACConfig.Logger]::Write('Removing the UnsignedPolicyPath') $UserConfigurationsObject.UnsignedPolicyPath = '' } else { @@ -106,7 +103,7 @@ Function Remove-CommonWDACConfig { } if ($SignToolPath) { - Write-Verbose -Message 'Removing the SignToolPath' + [WDACConfig.Logger]::Write('Removing the SignToolPath') $UserConfigurationsObject.SignToolCustomPath = '' } else { @@ -114,7 +111,7 @@ Function Remove-CommonWDACConfig { } if ($CertPath) { - Write-Verbose -Message 'Removing the CertPath' + [WDACConfig.Logger]::Write('Removing the CertPath') $UserConfigurationsObject.CertificatePath = '' } else { @@ -122,7 +119,7 @@ Function Remove-CommonWDACConfig { } if ($CertCN) { - Write-Verbose -Message 'Removing the CertCN' + [WDACConfig.Logger]::Write('Removing the CertCN') $UserConfigurationsObject.CertificateCommonName = '' } else { @@ -130,7 +127,7 @@ Function Remove-CommonWDACConfig { } if ($StrictKernelPolicyGUID) { - Write-Verbose -Message 'Removing the StrictKernelPolicyGUID' + [WDACConfig.Logger]::Write('Removing the StrictKernelPolicyGUID') $UserConfigurationsObject.StrictKernelPolicyGUID = '' } else { @@ -138,7 +135,7 @@ Function Remove-CommonWDACConfig { } if ($StrictKernelNoFlightRootsPolicyGUID) { - Write-Verbose -Message 'Removing the StrictKernelNoFlightRootsPolicyGUID' + [WDACConfig.Logger]::Write('Removing the StrictKernelNoFlightRootsPolicyGUID') $UserConfigurationsObject.StrictKernelNoFlightRootsPolicyGUID = '' } else { @@ -146,7 +143,7 @@ Function Remove-CommonWDACConfig { } if ($LastUpdateCheck) { - Write-Verbose -Message 'Removing the LastUpdateCheck' + [WDACConfig.Logger]::Write('Removing the LastUpdateCheck') $UserConfigurationsObject.LastUpdateCheck = '' } else { @@ -154,7 +151,7 @@ Function Remove-CommonWDACConfig { } if ($StrictKernelModePolicyTimeOfDeployment) { - Write-Verbose -Message 'Removing the Strict Kernel-Mode Policy Time Of Deployment' + [WDACConfig.Logger]::Write('Removing the Strict Kernel-Mode Policy Time Of Deployment') $UserConfigurationsObject.StrictKernelModePolicyTimeOfDeployment = '' } else { @@ -168,7 +165,7 @@ Function Remove-CommonWDACConfig { $UserConfigurationsJSON = $UserConfigurationsObject | ConvertTo-Json try { - Write-Verbose -Message 'Validating the JSON against the schema' + [WDACConfig.Logger]::Write('Validating the JSON against the schema') [System.Boolean]$IsValid = Test-Json -Json $UserConfigurationsJSON -SchemaFile "$([WDACConfig.GlobalVars]::ModuleRootPath)\Resources\User Configurations\Schema.json" } catch { @@ -178,7 +175,7 @@ Function Remove-CommonWDACConfig { if ($IsValid) { # Update the User Configurations file - Write-Verbose -Message 'Saving the changes' + [WDACConfig.Logger]::Write('Saving the changes') $UserConfigurationsJSON | Set-Content -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Force } else { diff --git a/WDACConfig/WDACConfig Module Files/Core/Remove-WDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Remove-WDACConfig.psm1 index b9499abf7..191517a4a 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Remove-WDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Remove-WDACConfig.psm1 @@ -154,24 +154,17 @@ Function Remove-WDACConfig { [Parameter(Mandatory = $False)][System.Management.Automation.SwitchParameter]$SkipVersionCheck ) Begin { - [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $False [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - Write-Verbose -Message 'Importing the required sub-modules' - Import-Module -Force -FullyQualifiedName @( - "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Update-Self.psm1", - "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Get-SignTool.psm1", - "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Remove-SupplementalSigners.psm1" - ) + [WDACConfig.Logger]::Write('Importing the required sub-modules') + Import-Module -Force -FullyQualifiedName @("$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Get-SignTool.psm1") - # if -SkipVersionCheck wasn't passed, run the updater - if (-NOT $SkipVersionCheck) { Update-Self -InvocationStatement $MyInvocation.Statement } + if (-NOT $SkipVersionCheck) { Update-WDACConfigPSModule -InvocationStatement $MyInvocation.Statement } [System.IO.DirectoryInfo]$StagingArea = [WDACConfig.StagingArea]::NewStagingArea('Remove-WDACConfig') #Region User-Configurations-Processing-Validation - Write-Verbose -Message 'Validating and processing user configurations' + [WDACConfig.Logger]::Write('Validating and processing user configurations') if ($PSCmdlet.ParameterSetName -eq 'Signed Base') { @@ -240,7 +233,7 @@ Function Remove-WDACConfig { # If a signed policy is being removed if ($SignedBase) { - Write-Verbose -Message 'Looping over each selected policy XML file' + [WDACConfig.Logger]::Write('Looping over each selected policy XML file') foreach ($PolicyPath in $PolicyPaths) { # The total number of the main steps for the progress bar to render @@ -250,12 +243,12 @@ Function Remove-WDACConfig { $CurrentStep++ Write-Progress -Id 18 -Activity 'Parsing the XML Policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Converting the XML file to an XML object' + [WDACConfig.Logger]::Write('Converting the XML file to an XML object') [System.Xml.XmlDocument]$Xml = Get-Content -Path $PolicyPath - Write-Verbose -Message 'Extracting the Policy ID from the XML object' + [WDACConfig.Logger]::Write('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" + [WDACConfig.Logger]::Write("The policy ID of the currently processing xml file is $PolicyID") # Extracting the policy name from the selected XML policy file [System.String]$PolicyName = foreach ($Item in $Xml.SiPolicy.Settings.Setting) { @@ -265,7 +258,7 @@ Function Remove-WDACConfig { } # 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' + [WDACConfig.Logger]::Write('Making sure the selected XML policy is deployed on the system') Try { [System.Guid[]]$CurrentPolicyIDs = foreach ($Item in (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies) { @@ -285,8 +278,8 @@ Function Remove-WDACConfig { $CurrentStep++ Write-Progress -Id 18 -Activity 'Processing the policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Making sure SupplementalPolicySigners do not exist in the XML policy' - Remove-SupplementalSigners -Path $PolicyPath + [WDACConfig.Logger]::Write('Making sure SupplementalPolicySigners do not exist in the XML policy') + [WDACConfig.CiPolicyHandler]::RemoveSupplementalSigners($PolicyPath.FullName) Set-CiRuleOptions -FilePath $PolicyPath -RulesToAdd 'Enabled:Unsigned System Integrity Policy' @@ -308,7 +301,7 @@ Function Remove-WDACConfig { # Prompt for confirmation before proceeding if ($PSCmdlet.ShouldProcess('This PC', 'Deploying the signed policy')) { - Write-Verbose -Message 'Deploying the newly signed CIP file' + [WDACConfig.Logger]::Write('Deploying the newly signed CIP file') $null = &'C:\Windows\System32\CiTool.exe' --update-policy $PolicyCIPPath -json Write-ColorfulTextWDACConfig -Color Lavender -InputText "Policy with the following details has been Re-signed and Re-deployed in Unsigned mode.`nPlease restart your system." @@ -338,7 +331,7 @@ Function Remove-WDACConfig { } }) - Write-Verbose -Message "$($NameID.count) policy IDs have been gathered from the supplied policy names and are going to be removed from the system" + [WDACConfig.Logger]::Write("$($NameID.count) policy IDs have been gathered from the supplied policy names and are going to be removed from the system") foreach ($ID in $NameID) { $null = &'C:\Windows\System32\CiTool.exe' --remove-policy "{$ID}" -json diff --git a/WDACConfig/WDACConfig Module Files/Core/Set-CiRuleOptions.psm1 b/WDACConfig/WDACConfig Module Files/Core/Set-CiRuleOptions.psm1 index e504d0c03..de3a82538 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Set-CiRuleOptions.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Set-CiRuleOptions.psm1 @@ -37,13 +37,11 @@ Function Set-CiRuleOptions { [System.Management.Automation.SwitchParameter]$RemoveAll ) Begin { - [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $false [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" Import-Module -FullyQualifiedName "$([WDACConfig.GlobalVars]::ModuleRootPath)\XMLOps\Close-EmptyXmlNodes_Semantic.psm1" -Force - Write-Verbose -Message "Set-CiRuleOptions: Configuring the policy rule options for: $($FilePath.Name)" + [WDACConfig.Logger]::Write("Set-CiRuleOptions: Configuring the policy rule options for: $($FilePath.Name)") [System.Collections.Hashtable]$Intel = ConvertFrom-Json -AsHashtable -InputObject (Get-Content -Path "$([WDACConfig.GlobalVars]::ModuleRootPath)\Resources\PolicyRuleOptions.Json" -Raw) @@ -155,10 +153,10 @@ Function Set-CiRuleOptions { Compare-Object -ReferenceObject ([System.Int32[]]$RuleOptionsToImplement) -DifferenceObject ([System.Int32[]]$ExistingRuleOptions.Keys) | ForEach-Object -Process { if ($_.SideIndicator -eq '<=') { - Write-Verbose -Message "Set-CiRuleOptions: Adding Rule Option: $($Intel[[System.String]$_.InputObject])" + [WDACConfig.Logger]::Write("Set-CiRuleOptions: Adding Rule Option: $($Intel[[System.String]$_.InputObject])") } else { - Write-Verbose -Message "Set-CiRuleOptions: Removing Rule Option: $($Intel[[System.String]$_.InputObject])" + [WDACConfig.Logger]::Write("Set-CiRuleOptions: Removing Rule Option: $($Intel[[System.String]$_.InputObject])") } } diff --git a/WDACConfig/WDACConfig Module Files/Core/Set-CommonWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Set-CommonWDACConfig.psm1 index a44dd74aa..8ac36922a 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Set-CommonWDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Set-CommonWDACConfig.psm1 @@ -75,15 +75,12 @@ Function Set-CommonWDACConfig { [parameter(Mandatory = $false)][System.DateTime]$StrictKernelModePolicyTimeOfDeployment ) begin { - [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $false if ($(Get-PSCallStack).Count -le 2) { [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) } else { [WDACConfig.LoggerInitializer]::Initialize($null, $null, $Host) } - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - if (!$CertCN -And !$CertPath -And !$SignToolPath -And !$UnsignedPolicyPath -And !$SignedPolicyPath -And !$StrictKernelPolicyGUID -And !$StrictKernelNoFlightRootsPolicyGUID -And !$LastUpdateCheck -And !$StrictKernelModePolicyTimeOfDeployment) { Throw [System.ArgumentException] 'No parameter was selected.' } @@ -97,17 +94,17 @@ Function Set-CommonWDACConfig { # Create User configuration folder if it doesn't already exist if (-NOT ([System.IO.Directory]::Exists((Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Parent)))) { $null = New-Item -ItemType Directory -Path (Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Parent) -Force - Write-Verbose -Message 'The WDACConfig folder in Program Files has been created because it did not exist.' + [WDACConfig.Logger]::Write('The WDACConfig folder in Program Files has been created because it did not exist.') } # Create User configuration file if it doesn't already exist if (-NOT ([System.IO.File]::Exists(([WDACConfig.GlobalVars]::UserConfigJson)))) { $null = New-Item -ItemType File -Path (Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Parent) -Name (Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Leaf) -Force - Write-Verbose -Message 'The UserConfigurations.json file has been created because it did not exist.' + [WDACConfig.Logger]::Write('The UserConfigurations.json file has been created because it did not exist.') } # Trying to read the current user configurations - Write-Verbose -Message 'Trying to read the current user configurations' + [WDACConfig.Logger]::Write('Trying to read the current user configurations') [System.Object[]]$CurrentUserConfigurations = Get-Content -Path ([WDACConfig.GlobalVars]::UserConfigJson) # If the file exists but is corrupted and has bad values, rewrite it @@ -115,7 +112,7 @@ Function Set-CommonWDACConfig { $CurrentUserConfigurations = $CurrentUserConfigurations | ConvertFrom-Json } catch { - Write-Verbose -Message 'The user configurations file exists but is corrupted and has bad values, rewriting it' + [WDACConfig.Logger]::Write('The user configurations file exists but is corrupted and has bad values, rewriting it') Set-Content -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Value '' } @@ -134,86 +131,86 @@ Function Set-CommonWDACConfig { } process { - Write-Verbose -Message 'Processing each user configuration property' + [WDACConfig.Logger]::Write('Processing each user configuration property') if ($SignedPolicyPath) { - Write-Verbose -Message 'Saving the supplied Signed Policy path in user configurations.' + [WDACConfig.Logger]::Write('Saving the supplied Signed Policy path in user configurations.') $UserConfigurationsObject.SignedPolicyPath = $SignedPolicyPath.FullName } else { - Write-Verbose -Message 'No changes to the Signed Policy path property was detected.' + [WDACConfig.Logger]::Write('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.' + [WDACConfig.Logger]::Write('Saving the supplied Unsigned Policy path in user configurations.') $UserConfigurationsObject.UnsignedPolicyPath = $UnsignedPolicyPath.FullName } else { - Write-Verbose -Message 'No changes to the Unsigned Policy path property was detected.' + [WDACConfig.Logger]::Write('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.' + [WDACConfig.Logger]::Write('Saving the supplied SignTool path in user configurations.') $UserConfigurationsObject.SignToolCustomPath = $SignToolPath.FullName } else { - Write-Verbose -Message 'No changes to the Signtool path property was detected.' + [WDACConfig.Logger]::Write('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.' + [WDACConfig.Logger]::Write('Saving the supplied Certificate path in user configurations.') $UserConfigurationsObject.CertificatePath = $CertPath.FullName } else { - Write-Verbose -Message 'No changes to the Certificate path property was detected.' + [WDACConfig.Logger]::Write('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.' + [WDACConfig.Logger]::Write('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.' + [WDACConfig.Logger]::Write('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.' + [WDACConfig.Logger]::Write('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.' + [WDACConfig.Logger]::Write('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.' + [WDACConfig.Logger]::Write('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.' + [WDACConfig.Logger]::Write('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.' + [WDACConfig.Logger]::Write('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.' + [WDACConfig.Logger]::Write('No changes to the Last Update Check property was detected.') $UserConfigurationsObject.LastUpdateCheck = $CurrentUserConfigurations.LastUpdateCheck } if ($StrictKernelModePolicyTimeOfDeployment) { - Write-Verbose -Message 'Saving the supplied Strict Kernel-Mode Policy Time Of Deployment in user configurations.' + [WDACConfig.Logger]::Write('Saving the supplied Strict Kernel-Mode Policy Time Of Deployment in user configurations.') $UserConfigurationsObject.StrictKernelModePolicyTimeOfDeployment = $StrictKernelModePolicyTimeOfDeployment } else { - Write-Verbose -Message 'No changes to the Strict Kernel-Mode Policy Time Of Deployment property was detected.' + [WDACConfig.Logger]::Write('No changes to the Strict Kernel-Mode Policy Time Of Deployment property was detected.') $UserConfigurationsObject.StrictKernelModePolicyTimeOfDeployment = $CurrentUserConfigurations.StrictKernelModePolicyTimeOfDeployment } } @@ -222,7 +219,7 @@ Function Set-CommonWDACConfig { $UserConfigurationsJSON = $UserConfigurationsObject | ConvertTo-Json try { - Write-Verbose -Message 'Validating the JSON against the schema' + [WDACConfig.Logger]::Write('Validating the JSON against the schema') [System.Boolean]$IsValid = Test-Json -Json $UserConfigurationsJSON -SchemaFile "$([WDACConfig.GlobalVars]::ModuleRootPath)\Resources\User Configurations\Schema.json" } catch { @@ -232,7 +229,7 @@ Function Set-CommonWDACConfig { if ($IsValid) { # Update the User Configurations file - Write-Verbose -Message 'Saving the changes' + [WDACConfig.Logger]::Write('Saving the changes') $UserConfigurationsJSON | Set-Content -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Force # Display the updated User Configurations diff --git a/WDACConfig/WDACConfig Module Files/CoreExt/PSDefaultParameterValues.ps1 b/WDACConfig/WDACConfig Module Files/CoreExt/PSDefaultParameterValues.ps1 deleted file mode 100644 index 92c5af6dc..000000000 --- a/WDACConfig/WDACConfig Module Files/CoreExt/PSDefaultParameterValues.ps1 +++ /dev/null @@ -1,59 +0,0 @@ -# $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 -# Main cmdlets that are also used within other main cmdlets are mentioned here too. -$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 - 'Remove-Module:Verbose' = $false - 'Export-ModuleMember:Verbose' = $false - 'Add-Type:Verbose' = $false - 'Get-WinEvent:Verbose' = $false - 'Test-Path:ErrorAction' = 'SilentlyContinue' - 'Receive-CodeIntegrityLogs:Verbose' = $Verbose - 'Get-SignTool:Verbose' = $Verbose - 'Update-Self:Verbose' = $Verbose - 'New-SnapBackGuarantee:Verbose' = $Verbose - 'Get-KernelModeDriversAudit:Verbose' = $Verbose - 'Get-SignerInfo:Verbose' = $Verbose - 'Get-CertificateDetails:Verbose' = $Verbose - 'Compare-SignerAndCertificate:Verbose' = $Verbose - 'Compare-SignerAndCertificate:Debug' = $Debug - 'Remove-SupplementalSigners:Verbose' = $Verbose - 'Set-LogPropertiesVisibility:Verbose' = $Verbose - 'Test-KernelProtectedFiles:Verbose' = $Verbose - 'Set-CiRuleOptions:Verbose' = $Verbose - 'New-WDACConfig:Verbose' = $Verbose - 'Get-KernelModeDrivers:Verbose' = $Verbose - 'New-Macros:Verbose' = $Verbose - 'Checkpoint-Macros:Verbose' = $Verbose - 'Test-ECCSignedFiles:Verbose' = $Verbose - - 'Clear-CiPolicy_Semantic:Verbose' = $Verbose - 'Close-EmptyXmlNodes_Semantic:Verbose' = $Verbose - 'Compare-CorrelatedData:Verbose' = $Verbose - 'Merge-Signers_Semantic:Verbose' = $Verbose - 'New-FilePublisherLevelRules:Verbose' = $Verbose - 'New-HashLevelRules:Verbose' = $Verbose - 'New-PublisherLevelRules:Verbose' = $Verbose - 'Optimize-MDECSVData:Verbose' = $Verbose - 'Remove-AllowElements_Semantic:Verbose' = $Verbose - 'Remove-DuplicateFileAttrib_Semantic:Verbose' = $Verbose - 'Remove-UnreferencedFileRuleRefs:Verbose' = $Verbose - 'New-CertificateSignerRules:Verbose' = $Verbose - - 'Clear-CiPolicy_Semantic:Debug' = $Debug - 'Close-EmptyXmlNodes_Semantic:Debug' = $Debug - 'Compare-CorrelatedData:Debug' = $Debug - 'Merge-Signers_Semantic:Debug' = $Debug - 'New-FilePublisherLevelRules:Debug' = $Debug - 'New-HashLevelRules:Debug' = $Debug - 'New-PublisherLevelRules:Debug' = $Debug - 'Optimize-MDECSVData:Debug' = $Debug - 'Remove-AllowElements_Semantic:Debug' = $Debug - 'Remove-DuplicateFileAttrib_Semantic:Debug' = $Debug - 'Remove-UnreferencedFileRuleRefs:Debug' = $Debug - 'New-CertificateSignerRules:Debug' = $Debug -} diff --git a/WDACConfig/WDACConfig Module Files/Shared/Get-KernelModeDrivers.psm1 b/WDACConfig/WDACConfig Module Files/Shared/Get-KernelModeDrivers.psm1 index 59b0deff5..bd3e57129 100644 --- a/WDACConfig/WDACConfig Module Files/Shared/Get-KernelModeDrivers.psm1 +++ b/WDACConfig/WDACConfig Module Files/Shared/Get-KernelModeDrivers.psm1 @@ -32,7 +32,7 @@ Function Get-KernelModeDrivers { Begin { # Import the ConfigCI assembly resources if they are not already imported if (-NOT ('Microsoft.SecureBoot.UserConfig.ImportParser' -as [System.Type]) ) { - [WDACConfig.VerboseLogger]::Write('Importing the ConfigCI assembly resources') + [WDACConfig.Logger]::Write('Importing the ConfigCI assembly resources') Add-Type -Path ([System.String](PowerShell.exe -NoProfile -Command { (Get-Command -Name Merge-CIPolicy).DLL })) } @@ -157,8 +157,8 @@ Function Get-KernelModeDrivers { } } - [WDACConfig.VerboseLogger]::Write("Number of sys files: $($DriverFiles.Count)") - [WDACConfig.VerboseLogger]::Write("Number of potential kernel-mode DLLs: $($PotentialKernelModeDlls.Count)") + [WDACConfig.Logger]::Write("Number of sys files: $($DriverFiles.Count)") + [WDACConfig.Logger]::Write("Number of potential kernel-mode DLLs: $($PotentialKernelModeDlls.Count)") # Scan all of the .dll files to see if they are kernel-mode drivers foreach ($KernelDll in $PotentialKernelModeDlls) { @@ -168,10 +168,10 @@ Function Get-KernelModeDrivers { } } - [WDACConfig.VerboseLogger]::Write("Number of kernel-mode DLLs folder: $($KernelModeDlls.Count)") + [WDACConfig.Logger]::Write("Number of kernel-mode DLLs folder: $($KernelModeDlls.Count)") } End { - [WDACConfig.VerboseLogger]::Write("Returning $($DriverFiles.Count) kernel-mode driver file paths") + [WDACConfig.Logger]::Write("Returning $($DriverFiles.Count) kernel-mode driver file paths") Return $DriverFiles } } diff --git a/WDACConfig/WDACConfig Module Files/Shared/Get-KernelModeDriversAudit.psm1 b/WDACConfig/WDACConfig Module Files/Shared/Get-KernelModeDriversAudit.psm1 index 5ee56a560..b47aa06ea 100644 --- a/WDACConfig/WDACConfig Module Files/Shared/Get-KernelModeDriversAudit.psm1 +++ b/WDACConfig/WDACConfig Module Files/Shared/Get-KernelModeDriversAudit.psm1 @@ -20,9 +20,6 @@ Function Get-KernelModeDriversAudit { [Parameter(Mandatory = $true)][System.IO.DirectoryInfo]$SavePath ) begin { - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - - [WDACConfig.VerboseLogger]::Write('Importing the required sub-modules') Import-Module -FullyQualifiedName "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Receive-CodeIntegrityLogs.psm1" -Force [System.IO.FileInfo[]]$KernelModeDriversPaths = @() @@ -33,28 +30,28 @@ Function Get-KernelModeDriversAudit { # Get the Code Integrity event logs for kernel mode drivers that have been loaded since the audit mode policy has been deployed [System.Collections.Hashtable[]]$RawData = Receive-CodeIntegrityLogs -Date (Get-CommonWDACConfig -StrictKernelModePolicyTimeOfDeployment) -Type 'Audit' - [WDACConfig.VerboseLogger]::Write("RawData count: $($RawData.count)") + [WDACConfig.Logger]::Write("RawData count: $($RawData.count)") - [WDACConfig.VerboseLogger]::Write('Saving the file paths to a variable') + [WDACConfig.Logger]::Write('Saving the file paths to a variable') [System.IO.FileInfo[]]$KernelModeDriversPaths = $RawData.'File Name' - [WDACConfig.VerboseLogger]::Write('Filtering based on files that exist with .sys and .dll extensions') + [WDACConfig.Logger]::Write('Filtering based on files that exist with .sys and .dll extensions') $KernelModeDriversPaths = foreach ($Item in $KernelModeDriversPaths) { if (($Item.Extension -in ('.sys', '.dll')) -and $Item.Exists) { $Item } } - [WDACConfig.VerboseLogger]::Write("KernelModeDriversPaths count after filtering based on files that exist with .sys and .dll extensions: $($KernelModeDriversPaths.count)") + [WDACConfig.Logger]::Write("KernelModeDriversPaths count after filtering based on files that exist with .sys and .dll extensions: $($KernelModeDriversPaths.count)") - [WDACConfig.VerboseLogger]::Write('Removing duplicates based on file path') + [WDACConfig.Logger]::Write('Removing duplicates based on file path') $KernelModeDriversPaths = foreach ($Item in ($KernelModeDriversPaths | Group-Object -Property 'FullName')) { $Item.Group[0] } - [WDACConfig.VerboseLogger]::Write("KernelModeDriversPaths count after deduplication based on file path: $($KernelModeDriversPaths.count)") + [WDACConfig.Logger]::Write("KernelModeDriversPaths count after deduplication based on file path: $($KernelModeDriversPaths.count)") - [WDACConfig.VerboseLogger]::Write('Creating symbolic links to the driver files') + [WDACConfig.Logger]::Write('Creating symbolic links to the driver files') Foreach ($File in $KernelModeDriversPaths) { $null = New-Item -ItemType SymbolicLink -Path (Join-Path -Path $SavePath -ChildPath $File.Name) -Target $File.FullName } diff --git a/WDACConfig/WDACConfig Module Files/Shared/Get-SignTool.psm1 b/WDACConfig/WDACConfig Module Files/Shared/Get-SignTool.psm1 index 437be95b5..2860a4e7c 100644 --- a/WDACConfig/WDACConfig Module Files/Shared/Get-SignTool.psm1 +++ b/WDACConfig/WDACConfig Module Files/Shared/Get-SignTool.psm1 @@ -18,8 +18,6 @@ Function Get-SignTool { [parameter(Mandatory = $false)][System.IO.FileInfo]$SignToolExePathInput ) Begin { - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - [System.IO.DirectoryInfo]$StagingArea = [WDACConfig.StagingArea]::NewStagingArea('Get-SignTool') } @@ -30,7 +28,7 @@ Function Get-SignTool { # If Sign tool path wasn't provided by parameter, try to detect it automatically if (!$SignToolExePathInput) { - [WDACConfig.VerboseLogger]::Write('SignTool.exe path was not provided by parameter, trying to detect it automatically') + [WDACConfig.Logger]::Write('SignTool.exe path was not provided by parameter, trying to detect it automatically') try { if ($Env:PROCESSOR_ARCHITECTURE -eq 'AMD64') { @@ -57,24 +55,24 @@ Function Get-SignTool { if ($PSCmdlet.ShouldContinue('Would you like to try to download it from the official Microsoft server? It will be saved in the WDACConfig directory in Program Files.', 'SignTool.exe path was not provided, it could not be automatically detected on the system, nor could it be found in the common WDAC user configurations.')) { if (-NOT (Get-PackageSource | Where-Object -FilterScript { $_.Name -ieq 'nuget.org' })) { - [WDACConfig.VerboseLogger]::Write('Registering the nuget.org package source because it was not found in the system.') + [WDACConfig.Logger]::Write('Registering the nuget.org package source because it was not found in the system.') $null = Register-PackageSource -Name 'nuget.org' -ProviderName 'NuGet' -Location 'https://api.nuget.org/v3/index.json' } - [WDACConfig.VerboseLogger]::Write('Finding the latest version of the Microsoft.Windows.SDK.BuildTools package from NuGet') + [WDACConfig.Logger]::Write('Finding the latest version of the Microsoft.Windows.SDK.BuildTools package from NuGet') # Use a script block to convert the Version property to a semantic version object for proper sorting based on the version number [Microsoft.PackageManagement.Packaging.SoftwareIdentity[]]$Package = Find-Package -Name 'Microsoft.Windows.SDK.BuildTools' -Source 'nuget.org' -AllVersions -Force -MinimumVersion '10.0.22621.3233' [Microsoft.PackageManagement.Packaging.SoftwareIdentity]$Package = $Package | Sort-Object -Property { [System.Version]$_.Version } -Descending | Select-Object -First 1 - [WDACConfig.VerboseLogger]::Write('Downloading SignTool.exe from NuGet...') + [WDACConfig.Logger]::Write('Downloading SignTool.exe from NuGet...') $null = Save-Package -InputObject $Package -Path $StagingArea -Force - [WDACConfig.VerboseLogger]::Write('Extracting the nupkg') + [WDACConfig.Logger]::Write('Extracting the nupkg') Expand-Archive -Path "$StagingArea\*.nupkg" -DestinationPath $StagingArea -Force - [WDACConfig.VerboseLogger]::Write('Detecting the CPU Arch') + [WDACConfig.Logger]::Write('Detecting the CPU Arch') switch ($Env:PROCESSOR_ARCHITECTURE) { 'AMD64' { [System.String]$CPUArch = 'x64' } 'ARM64' { [System.String]$CPUArch = 'arm64' } @@ -93,7 +91,7 @@ Function Get-SignTool { } # If Sign tool path was provided by parameter, use it else { - [WDACConfig.VerboseLogger]::Write('SignTool.exe path was provided by parameter') + [WDACConfig.Logger]::Write('SignTool.exe path was provided by parameter') $SignToolExePathOutput = $SignToolExePathInput } @@ -103,7 +101,7 @@ Function Get-SignTool { # At this point the SignTool.exe path was either provided by user, was found in the user configs, was detected automatically or was downloaded from NuGet try { # Validate the SignTool executable - [WDACConfig.VerboseLogger]::Write("Validating the SignTool executable: $SignToolExePathOutput") + [WDACConfig.Logger]::Write("Validating the SignTool executable: $SignToolExePathOutput") # Setting the minimum version of SignTool that is allowed to be executed [System.Version]$WindowsSdkVersion = '10.0.22621.2428' [System.Boolean]$GreenFlag1 = (((Get-Item -Path $SignToolExePathOutput).VersionInfo).ProductVersionRaw -ge $WindowsSdkVersion) @@ -124,9 +122,9 @@ Function Get-SignTool { Throw [System.Security.VerificationException] 'The SignTool executable was found but could not be verified. Please download the latest Windows SDK to get the newest SignTool executable. Official download link: http://aka.ms/WinSDK' } else { - [WDACConfig.VerboseLogger]::Write('SignTool executable was found and verified successfully.') + [WDACConfig.Logger]::Write('SignTool executable was found and verified successfully.') - [WDACConfig.VerboseLogger]::Write('Setting the SignTool path in the common WDAC user configurations') + [WDACConfig.Logger]::Write('Setting the SignTool path in the common WDAC user configurations') $null = Set-CommonWDACConfig -SignToolPath $SignToolExePathOutput return $SignToolExePathOutput diff --git a/WDACConfig/WDACConfig Module Files/Shared/New-SnapBackGuarantee.psm1 b/WDACConfig/WDACConfig Module Files/Shared/New-SnapBackGuarantee.psm1 deleted file mode 100644 index a4ce08d5d..000000000 --- a/WDACConfig/WDACConfig Module Files/Shared/New-SnapBackGuarantee.psm1 +++ /dev/null @@ -1,75 +0,0 @@ -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 Path - The path to the EnforcedMode.cip file that will be used to revert the base policy to enforced mode in case of a reboot. - .INPUTS - System.IO.FileInfo - .OUTPUTS - System.Void - #> - [CmdletBinding()] - [OutputType([System.Void])] - Param( - [parameter(Mandatory = $true)] - [System.IO.FileInfo]$Path - ) - - # Using CMD and Scheduled Task Method - - [WDACConfig.VerboseLogger]::Write('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 `"$([WDACConfig.GlobalVars]::UserConfigDir)\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 - $null = Register-ScheduledTask -TaskName 'EnforcedModeSnapBack' -Action $TaskAction -Trigger $TaskTrigger -Principal $Principal -Settings $TaskSettings -Force - - # Saving the EnforcedModeSnapBack.cmd file to the UserConfig directory in Program Files - # It contains the instructions to revert the base policy to enforced mode - Set-Content -Force -LiteralPath (Join-Path -Path ([WDACConfig.GlobalVars]::UserConfigDir) -ChildPath 'EnforcedModeSnapBack.cmd') -Value @" -REM Deploying the Enforced Mode SnapBack CI Policy -CiTool --update-policy "$Path" -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 "$Path" -REM Deleting this CMD file itself -del "%~f0" -"@ - -} -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 - $null = 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 -#> - -# 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 -#> - diff --git a/WDACConfig/WDACConfig Module Files/Shared/Receive-CodeIntegrityLogs.psm1 b/WDACConfig/WDACConfig Module Files/Shared/Receive-CodeIntegrityLogs.psm1 index eccaa7b3f..c5a7846aa 100644 --- a/WDACConfig/WDACConfig Module Files/Shared/Receive-CodeIntegrityLogs.psm1 +++ b/WDACConfig/WDACConfig Module Files/Shared/Receive-CodeIntegrityLogs.psm1 @@ -65,8 +65,6 @@ Function Receive-CodeIntegrityLogs { [Parameter(Mandatory = $false)][System.IO.FileInfo[]]$EVTXFilePaths ) Begin { - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - Function Test-NotEmpty ($Data) { <# .SYNOPSIS @@ -94,7 +92,7 @@ Function Receive-CodeIntegrityLogs { [WDACConfig.DriveLetterMapper+DriveMapping[]]$DriveLettersGlobalRootFix = [WDACConfig.DriveLetterMapper]::GetGlobalRootDrives() } catch { - [WDACConfig.VerboseLogger]::Write('Receive-CodeIntegrityLogs: Could not get the drive mappings from the system using the primary method, trying the alternative method now') + [WDACConfig.Logger]::Write('Receive-CodeIntegrityLogs: Could not get the drive mappings from the system using the primary method, trying the alternative method now') # Set the flag to true indicating the alternative method is being used $AlternativeDriveLetterFix = $true @@ -111,7 +109,7 @@ Function Receive-CodeIntegrityLogs { if ($Category -in 'All', 'CodeIntegrity') { Try { - [WDACConfig.VerboseLogger]::Write('Receive-CodeIntegrityLogs: Collecting the Code Integrity Operational logs') + [WDACConfig.Logger]::Write('Receive-CodeIntegrityLogs: Collecting the Code Integrity Operational logs') switch ($LogSource) { 'EVTXFiles' { # Get all of the Code Integrity logs from the specified EVTX files @@ -124,19 +122,19 @@ Function Receive-CodeIntegrityLogs { } } catch { - [WDACConfig.VerboseLogger]::Write("Receive-CodeIntegrityLogs: Could not collect the Code Integrity Operational logs, the number of logs collected is $($CiRawEventLogs.Count)") + [WDACConfig.Logger]::Write("Receive-CodeIntegrityLogs: Could not collect the Code Integrity Operational logs, the number of logs collected is $($CiRawEventLogs.Count)") } [Microsoft.PowerShell.Commands.GroupInfo[]]$CiGroupedEvents = $CiRawEventLogs | Group-Object -Property ActivityId - [WDACConfig.VerboseLogger]::Write("Receive-CodeIntegrityLogs: Grouped the Code Integrity logs by ActivityId. The total number of groups is $($CiGroupedEvents.Count) and the total number of logs in the groups is $($CiGroupedEvents.Group.Count)") + [WDACConfig.Logger]::Write("Receive-CodeIntegrityLogs: Grouped the Code Integrity logs by ActivityId. The total number of groups is $($CiGroupedEvents.Count) and the total number of logs in the groups is $($CiGroupedEvents.Group.Count)") } else { - [WDACConfig.VerboseLogger]::Write('Receive-CodeIntegrityLogs: Skipping the collection of the Code Integrity logs') + [WDACConfig.Logger]::Write('Receive-CodeIntegrityLogs: Skipping the collection of the Code Integrity logs') } if ($Category -in 'All', 'AppLocker') { Try { - [WDACConfig.VerboseLogger]::Write('Receive-CodeIntegrityLogs: Collecting the AppLocker logs') + [WDACConfig.Logger]::Write('Receive-CodeIntegrityLogs: Collecting the AppLocker logs') switch ($LogSource) { 'EVTXFiles' { # Get all of the AppLocker logs from the specified EVTX files @@ -149,14 +147,14 @@ Function Receive-CodeIntegrityLogs { } } catch { - [WDACConfig.VerboseLogger]::Write("Receive-CodeIntegrityLogs: Could not collect the AppLocker logs, the number of logs collected is $($AppLockerRawEventLogs.Count)") + [WDACConfig.Logger]::Write("Receive-CodeIntegrityLogs: Could not collect the AppLocker logs, the number of logs collected is $($AppLockerRawEventLogs.Count)") } [Microsoft.PowerShell.Commands.GroupInfo[]]$AppLockerGroupedEvents = $AppLockerRawEventLogs | Group-Object -Property ActivityId - [WDACConfig.VerboseLogger]::Write("Receive-CodeIntegrityLogs: Grouped the AppLocker logs by ActivityId. The total number of groups is $($AppLockerGroupedEvents.Count) and the total number of logs in the groups is $($AppLockerGroupedEvents.Group.Count)") + [WDACConfig.Logger]::Write("Receive-CodeIntegrityLogs: Grouped the AppLocker logs by ActivityId. The total number of groups is $($AppLockerGroupedEvents.Count) and the total number of logs in the groups is $($AppLockerGroupedEvents.Group.Count)") } else { - [WDACConfig.VerboseLogger]::Write('Receive-CodeIntegrityLogs: Skipping the collection of the AppLocker logs') + [WDACConfig.Logger]::Write('Receive-CodeIntegrityLogs: Skipping the collection of the AppLocker logs') } # Add Code Integrity and AppLocker logs to a single array based on the selected category @@ -237,7 +235,7 @@ Function Receive-CodeIntegrityLogs { Process { if ($EventPackageCollection.count -eq 0) { - [WDACConfig.VerboseLogger]::Write('Receive-CodeIntegrityLogs: No logs were collected') + [WDACConfig.Logger]::Write('Receive-CodeIntegrityLogs: No logs were collected') return } @@ -259,9 +257,6 @@ Function Receive-CodeIntegrityLogs { $DriveLetterMappings = $using:DriveLetterMappings $LogSource = $using:LogSource - # Set the VerbosePreference to the parent scope's value - $VerbosePreference = $using:VerbosePreference - # Loop over each event package in the collection foreach ($EventPackage in $_.GetEnumerator()) { @@ -272,7 +267,7 @@ Function Receive-CodeIntegrityLogs { $Xml = [System.Xml.XmlDocument]$Event.ToXml() if ($null -eq $Xml.event.EventData.data) { - [WDACConfig.VerboseLogger]::Write("Receive-CodeIntegrityLogs: Skipping Main event data for: $($Log['File Name'])") + [WDACConfig.Logger]::Write("Receive-CodeIntegrityLogs: Skipping Main event data for: $($Log['File Name'])") continue } @@ -356,11 +351,11 @@ Function Receive-CodeIntegrityLogs { $Log.UserId = [System.String]($ObjSID.Translate([System.Security.Principal.NTAccount])).Value } Catch { - [WDACConfig.VerboseLogger]::Write("Receive-CodeIntegrityLogs: Could not translate the SID $($Log.UserId) to a username for the Activity ID $($Log['ActivityId']) for the file $($Log['File Name'])") + [WDACConfig.Logger]::Write("Receive-CodeIntegrityLogs: Could not translate the SID $($Log.UserId) to a username for the Activity ID $($Log['ActivityId']) for the file $($Log['File Name'])") } } else { - [WDACConfig.VerboseLogger]::Write("Receive-CodeIntegrityLogs: The UserId property is null for the Activity ID $($Log['ActivityId']) for the file $($Log['File Name'])") + [WDACConfig.Logger]::Write("Receive-CodeIntegrityLogs: The UserId property is null for the Activity ID $($Log['ActivityId']) for the file $($Log['File Name'])") } } @@ -381,7 +376,7 @@ Function Receive-CodeIntegrityLogs { $XmlCorrelated = [System.Xml.XmlDocument]$CorrelatedEvent.ToXml() if ($null -eq $XmlCorrelated.event.EventData.data) { - [WDACConfig.VerboseLogger]::Write("Receive-CodeIntegrityLogs: Skipping Publisher check for: '$($Log['File Name'])' due to missing correlated event data") + [WDACConfig.Logger]::Write("Receive-CodeIntegrityLogs: Skipping Publisher check for: '$($Log['File Name'])' due to missing correlated event data") continue } @@ -419,14 +414,14 @@ Function Receive-CodeIntegrityLogs { } } - Write-Debug -Message "Receive-CodeIntegrityLogs: The number of unique publishers in the correlated events is $($Publishers.Count)" + [WDACConfig.Logger]::Write("Receive-CodeIntegrityLogs: The number of unique publishers in the correlated events is $($Publishers.Count)") $Log['Publishers'] = $Publishers # Add a new property to detect whether this log is signed or not # Primarily used by the BuildSignerAndHashObjects Method and for Evtx log sources $Log['SignatureStatus'] = $Publishers.Count -ge 1 ? 'Signed' : 'Unsigned' - Write-Debug -Message "Receive-CodeIntegrityLogs: The number of correlated events is $($CorrelatedLogs.Count)" + [WDACConfig.Logger]::Write("Receive-CodeIntegrityLogs: The number of correlated events is $($CorrelatedLogs.Count)") $Log['SignerInfo'] = $CorrelatedLogs } @@ -510,16 +505,16 @@ Function Receive-CodeIntegrityLogs { 'OnlyExisting' { Switch ($Type) { 'Audit' { - [WDACConfig.VerboseLogger]::Write("Receive-CodeIntegrityLogs: Returning $($Output.Existing.Audit.Values.Count) Audit Code Integrity logs for files on the disk.") + [WDACConfig.Logger]::Write("Receive-CodeIntegrityLogs: Returning $($Output.Existing.Audit.Values.Count) Audit Code Integrity logs for files on the disk.") Return $Output.Existing.Audit.Values } 'Blocked' { - [WDACConfig.VerboseLogger]::Write("Receive-CodeIntegrityLogs: Returning $($Output.Existing.Blocked.Values.Count) Blocked Code Integrity logs for files on the disk.") + [WDACConfig.Logger]::Write("Receive-CodeIntegrityLogs: Returning $($Output.Existing.Blocked.Values.Count) Blocked Code Integrity logs for files on the disk.") Return $Output.Existing.Blocked.Values } 'All' { $AllOutput = $Output.Existing.Blocked.Values + $Output.Existing.Audit.Values - [WDACConfig.VerboseLogger]::Write("Receive-CodeIntegrityLogs: Returning $($AllOutput.Count) Code Integrity logs for files on the disk.") + [WDACConfig.Logger]::Write("Receive-CodeIntegrityLogs: Returning $($AllOutput.Count) Code Integrity logs for files on the disk.") Return $AllOutput } } @@ -527,16 +522,16 @@ Function Receive-CodeIntegrityLogs { Default { Switch ($Type) { 'Audit' { - [WDACConfig.VerboseLogger]::Write("Receive-CodeIntegrityLogs: Returning $($Output.All.Audit.Values.Count) Audit Code Integrity logs.") + [WDACConfig.Logger]::Write("Receive-CodeIntegrityLogs: Returning $($Output.All.Audit.Values.Count) Audit Code Integrity logs.") Return $Output.All.Audit.Values } 'Blocked' { - [WDACConfig.VerboseLogger]::Write("Receive-CodeIntegrityLogs: Returning $($Output.All.Blocked.Values.Count) Blocked Code Integrity logs.") + [WDACConfig.Logger]::Write("Receive-CodeIntegrityLogs: Returning $($Output.All.Blocked.Values.Count) Blocked Code Integrity logs.") Return $Output.All.Blocked.Values } 'All' { $AllOutput = $Output.All.Audit.Values + $Output.All.Blocked.Values - [WDACConfig.VerboseLogger]::Write("Receive-CodeIntegrityLogs: Returning $($AllOutput.Count) Code Integrity logs.") + [WDACConfig.Logger]::Write("Receive-CodeIntegrityLogs: Returning $($AllOutput.Count) Code Integrity logs.") Return $AllOutput } } diff --git a/WDACConfig/WDACConfig Module Files/Shared/Remove-SupplementalSigners.psm1 b/WDACConfig/WDACConfig Module Files/Shared/Remove-SupplementalSigners.psm1 deleted file mode 100644 index 9f78dd26a..000000000 --- a/WDACConfig/WDACConfig Module Files/Shared/Remove-SupplementalSigners.psm1 +++ /dev/null @@ -1,102 +0,0 @@ -Function Remove-SupplementalSigners { - <# -.SYNOPSIS - Removes the entire SupplementalPolicySigners block - and any Signer in Signers node that have the same ID as the SignerIds of the SupplementalPolicySigner(s) in ... node - from a CI policy XML file -.NOTES - It doesn't do anything if the input policy file has no SupplementalPolicySigners block. - It will also always check if the Signers node is not empty, like - - - - if it is then it will close it: - - The function can run infinite number of times on the same file. -.PARAMETER Path - The path to the CI policy XML file -.INPUTS - System.IO.FileInfo -.OUTPUTS - System.Void -#> - [CmdletBinding()] - [OutputType([System.Void])] - param ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)] - [System.IO.FileInfo]$Path - ) - Begin { - # Make sure the input file is compliant with the CI policy schema - if (![WDACConfig.CiPolicyTest]::TestCiPolicy($Path, $null)) { - - throw 'The XML file is not compliant with the CI policy schema' - } - - # Get the XML content from the file - [System.Xml.XmlDocument]$XMLContent = Get-Content -Path $Path - } - Process { - - # Get the SiPolicy node - [System.Xml.XmlElement]$SiPolicyNode = $XMLContent.SiPolicy - - # Declare the namespace manager and add the default namespace with a prefix - [System.Xml.XmlNamespaceManager]$NameSpace = New-Object -TypeName System.Xml.XmlNamespaceManager -ArgumentList $XMLContent.NameTable - $NameSpace.AddNamespace('ns', 'urn:schemas-microsoft-com:sipolicy') - - # Check if the SupplementalPolicySigners node exists and has child nodes - if ($SiPolicyNode.SupplementalPolicySigners -and $SiPolicyNode.SupplementalPolicySigners.HasChildNodes) { - - [WDACConfig.VerboseLogger]::Write('Removing the SupplementalPolicySigners block and their corresponding Signers') - - # Select the SupplementalPolicySigners node using XPath and the namespace manager - [System.Xml.XmlElement[]]$NodesToRemove_SupplementalPolicySigners = $SiPolicyNode.SelectNodes('//ns:SupplementalPolicySigners', $NameSpace) - - # Get the SignerIds of the nodes inside of the SupplementalPolicySigners nodes - ... - [System.Xml.XmlElement[]]$SupplementalPolicySignerIDs = $SiPolicyNode.SupplementalPolicySigners.SelectNodes("//ns:SupplementalPolicySigner[starts-with(@SignerId, 'ID_SIGNER_')]", $NameSpace) - - # Get the unique SignerIds - [System.String[]]$SupplementalPolicySignerIDs = $SupplementalPolicySignerIDs.SignerId | Select-Object -Unique - - # Select all the Signer nodes in ... that have the same ID as the SignerIds of the SupplementalPolicySigners nodes - $NodesToRemove_Signers = foreach ($SignerID in $SupplementalPolicySignerIDs) { - $SiPolicyNode.Signers.SelectNodes("//ns:Signer[@ID='$SignerID']", $NameSpace) - } - - # Loop through the Signer nodes to remove - foreach ($SignerNode in $NodesToRemove_Signers) { - # Remove the Signer from the Signers node - [System.Void]$SiPolicyNode.Signers.RemoveChild($SignerNode) - } - - # Loop through the .. nodes to remove, in case there are multiple! - foreach ($Node in $NodesToRemove_SupplementalPolicySigners) { - - # Remove the node from the parent node which is $SiPolicyNode - [System.Void]$SiPolicyNode.RemoveChild($Node) - } - } - - # Check if the Signers node is empty, if it is then close it - if (-NOT $SiPolicyNode.Signers.HasChildNodes) { - - # Create a new self-closing element with the same name and attributes as the old one - [System.Xml.XmlElement]$NewSignersNode = $XMLContent.CreateElement('Signers', 'urn:schemas-microsoft-com:sipolicy') - - foreach ($Attribute in $SiPolicyNode.Signers.Attributes) { - $NewSignersNode.SetAttribute($Attribute.Name, $Attribute.Value) - } - - # Select the Signers node using XPath and the namespace manager - [System.Xml.XmlElement]$OldSignersNode = $XMLContent.SelectSingleNode('//ns:Signers', $NameSpace) - - # Replace the old element with the new one - [System.Void]$SiPolicyNode.ReplaceChild($NewSignersNode, $OldSignersNode) - } - } - End { - $XMLContent.Save($Path) - } -} -Export-ModuleMember -Function 'Remove-SupplementalSigners' diff --git a/WDACConfig/WDACConfig Module Files/Shared/Set-LogPropertiesVisibility.psm1 b/WDACConfig/WDACConfig Module Files/Shared/Set-LogPropertiesVisibility.psm1 index 94517239f..b0d8b5ea6 100644 --- a/WDACConfig/WDACConfig Module Files/Shared/Set-LogPropertiesVisibility.psm1 +++ b/WDACConfig/WDACConfig Module Files/Shared/Set-LogPropertiesVisibility.psm1 @@ -20,8 +20,6 @@ Function Set-LogPropertiesVisibility { [Parameter(Mandatory = $true)][PSCustomObject[]]$EventsToDisplay ) Begin { - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - Switch ($LogType) { 'Evtx/Local' { [System.String[]]$PropertiesToDisplay = @('TimeCreated', 'File Name', 'Full Path', 'Process Name', 'ProductName', 'OriginalFileName', 'InternalName', 'PackageFamilyName', 'FileVersion', 'Publishers', 'PolicyName', 'SI Signing Scenario') diff --git a/WDACConfig/WDACConfig Module Files/Shared/Test-ECCSignedFiles.psm1 b/WDACConfig/WDACConfig Module Files/Shared/Test-ECCSignedFiles.psm1 index 4bad07acf..0e898cb15 100644 --- a/WDACConfig/WDACConfig Module Files/Shared/Test-ECCSignedFiles.psm1 +++ b/WDACConfig/WDACConfig Module Files/Shared/Test-ECCSignedFiles.psm1 @@ -36,7 +36,7 @@ Function Test-ECCSignedFiles { [Parameter(Mandatory = $true)][System.IO.FileInfo]$ECCSignedFilesTempPolicy ) Begin { - [WDACConfig.VerboseLogger]::Write('Test-ECCSignedFiles: Importing the required sub-modules') + [WDACConfig.Logger]::Write('Test-ECCSignedFiles: Importing the required sub-modules') Import-Module -Force -FullyQualifiedName @( "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Get-KernelModeDrivers.psm1", "$([WDACConfig.GlobalVars]::ModuleRootPath)\XMLOps\New-HashLevelRules.psm1", @@ -48,7 +48,7 @@ Function Test-ECCSignedFiles { } Process { - [WDACConfig.VerboseLogger]::Write("Test-ECCSignedFiles: Processing $($WDACSupportedFiles.Count) WDAC compliant files to check for ECC signatures.") + [WDACConfig.Logger]::Write("Test-ECCSignedFiles: Processing $($WDACSupportedFiles.Count) WDAC compliant files to check for ECC signatures.") # The check for existence is mainly for the files detected in audit logs that no longer exist on the disk # Audit logs or MDE data simply don't have the data related to the file's signature algorithm, so only local files can be checked @@ -62,7 +62,7 @@ Function Test-ECCSignedFiles { if ($AuthResult.Status -ieq 'Valid') { if (($AuthResult.SignerCertificate.PublicKey.Oid.Value).Contains('1.2.840.10045.2.1')) { - # [WDACConfig.VerboseLogger]::Write("Test-ECCSignedFiles: The file '$Path' is signed with ECC algorithm. Will create Hash Level rules for it.") + # [WDACConfig.Logger]::Write("Test-ECCSignedFiles: The file '$Path' is signed with ECC algorithm. Will create Hash Level rules for it.") $Path } } @@ -82,9 +82,9 @@ Function Test-ECCSignedFiles { foreach ($ECCSignedFile in $ECCSignedFiles) { - [WDACConfig.VerboseLogger]::Write("Test-ECCSignedFiles: Creating Hash Level rules for the ECC signed file '$ECCSignedFile'.") + [WDACConfig.Logger]::Write("Test-ECCSignedFiles: Creating Hash Level rules for the ECC signed file '$ECCSignedFile'.") - [WDACConfig.AuthenticodePageHashes]$HashOutput = [WDACConfig.AuthPageHash]::GetCiFileHashes($ECCSignedFile) + [WDACConfig.CodeIntegrityHashes]$HashOutput = [WDACConfig.CiFileHash]::GetCiFileHashes($ECCSignedFile) $CompleteHashes.Add([WDACConfig.HashCreator]::New( $HashOutput.SHA256Authenticode, @@ -104,7 +104,7 @@ Function Test-ECCSignedFiles { Return $ECCSignedFilesTempPolicy } else { - [WDACConfig.VerboseLogger]::Write('Test-ECCSignedFiles: No ECC signed files found. Exiting the function.') + [WDACConfig.Logger]::Write('Test-ECCSignedFiles: No ECC signed files found. Exiting the function.') Return $null } } diff --git a/WDACConfig/WDACConfig Module Files/Shared/Test-KernelProtectedFiles.psm1 b/WDACConfig/WDACConfig Module Files/Shared/Test-KernelProtectedFiles.psm1 index e4c433217..c8ce9d9d4 100644 --- a/WDACConfig/WDACConfig Module Files/Shared/Test-KernelProtectedFiles.psm1 +++ b/WDACConfig/WDACConfig Module Files/Shared/Test-KernelProtectedFiles.psm1 @@ -16,7 +16,7 @@ Function Test-KernelProtectedFiles { [Parameter(Mandatory = $true)][PSCustomObject[]]$Logs ) Begin { - [WDACConfig.VerboseLogger]::Write('Test-KernelProtectedFiles: Checking for Kernel-Protected files') + [WDACConfig.Logger]::Write('Test-KernelProtectedFiles: Checking for Kernel-Protected files') } Process { # Looping through every existing file with .exe and .dll extensions to check if they are kernel protected @@ -34,7 +34,7 @@ Function Test-KernelProtectedFiles { $Log } catch { - [WDACConfig.VerboseLogger]::Write("Test-KernelProtectedFiles: An unexpected error occurred while checking the file: $($Log.'Full Path')") + [WDACConfig.Logger]::Write("Test-KernelProtectedFiles: An unexpected error occurred while checking the file: $($Log.'Full Path')") } } } diff --git a/WDACConfig/WDACConfig Module Files/Shared/Update-self.psm1 b/WDACConfig/WDACConfig Module Files/Shared/Update-self.psm1 deleted file mode 100644 index 0994c40b5..000000000 --- a/WDACConfig/WDACConfig Module Files/Shared/Update-self.psm1 +++ /dev/null @@ -1,103 +0,0 @@ -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 - .PARAMETER InvocationStatement - The command that was used to invoke the main function/cmdlet that invoked the Update-Self function, this is used to re-run the command after the module has been updated. - It checks to make sure the Update-Self function was called by an authorized command, that is one of the main cmdlets of the WDACConfig module, otherwise it will throw an error. - The parameter also shouldn't contain any backtick or semicolon characters used to chain commands together. - .NOTES - Even if the main cmdlets of the module are called with semicolons like this: Get-Date;New-WDACConfig -GetDriverBlockRules -Verbose -Deploy;Get-Host - Since the Update-Self function only receives the invocation statement from the main cmdlet/function, anything before or after the semicolons are automatically dropped and will not run after the module is auto updated. - So from the example above, only this part gets executed after auto update: New-WDACConfig -GetDriverBlockRules -Verbose -Deploy - The ValidatePattern attribute is just an extra layer of security. - .INPUTS - System.String - .OUTPUTS - System.String - #> - [CmdletBinding()] - [OutputType([System.String])] - param( - [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)] - [ValidatePattern('^(Confirm-WDACConfig|Deploy-SignedWDACConfig|Edit-SignedWDACConfig|Edit-WDACConfig|Invoke-WDACSimulation|New-DenyWDACConfig|New-KernelModeWDACConfig|New-SupplementalWDACConfig|New-WDACConfig|Remove-WDACConfig|Assert-WDACConfigIntegrity|Build-WDACCertificate|Get-CiFileHashes|ConvertTo-WDACPolicy|Get-CIPolicySetting)(?!.*[;`]).*$', ErrorMessage = 'Either Update-Self function was called with an unauthorized command or it contains semicolon and/or backtick')] - [System.String]$InvocationStatement - ) - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - - try { - # Get the last update check time - [WDACConfig.VerboseLogger]::Write('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 - [WDACConfig.VerboseLogger]::Write('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 occurred more than 30 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 30) -or $PerformOnlineUpdateCheck) { - - [WDACConfig.VerboseLogger]::Write("Performing online update check because the last update check was performed $($TimeDiff ?? [System.Char]::ConvertFromUtf32(8734)) minutes ago") - - [System.Version]$CurrentVersion = (Test-ModuleManifest -Path "$([WDACConfig.GlobalVars]::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.' - } - } - - # Reset the last update timer to the current time - [WDACConfig.VerboseLogger]::Write('Resetting the last update timer to the current time') - $null = Set-CommonWDACConfig -LastUpdateCheck $(Get-Date) - - if ($CurrentVersion -lt $LatestVersion) { - - Write-Output -InputObject "$($PSStyle.Foreground.FromRGB(255,0,230))The currently installed module's version is $CurrentVersion while the latest version is $LatestVersion - Auto Updating the module...$($PSStyle.Reset)" - - # Remove the old module version from the current session - Remove-Module -Name 'WDACConfig' -Force -WarningAction SilentlyContinue - - try { - Uninstall-Module -Name 'WDACConfig' -AllVersions -Force -ErrorAction Ignore -WarningAction SilentlyContinue - } - catch {} - - Install-Module -Name 'WDACConfig' -RequiredVersion $LatestVersion -Scope AllUsers -Force - # Will not import the new module version in the current session because of the constant variables. New version is automatically imported when the main cmdlet is run in a new session. - - # Make sure the old version isn't run after update - Write-Output -InputObject "$($PSStyle.Foreground.FromRGB(152,255,152))Update has been successful, running your command now$($PSStyle.Reset)" - - try { - # Try to re-run the command that invoked the Update-Self function in a new session after the module is updated. - pwsh.exe -NoProfile -NoLogo -NoExit -command $InvocationStatement - } - catch { - Throw 'Could not relaunch PowerShell after update. Please close and reopen PowerShell to run your command again.' - } - } - } - else { - [WDACConfig.VerboseLogger]::Write("Skipping online update check because the last update check was performed $TimeDiff minutes ago") - } -} -Export-ModuleMember -Function 'Update-Self' diff --git a/WDACConfig/WDACConfig Module Files/WDACConfig.psd1 b/WDACConfig/WDACConfig Module Files/WDACConfig.psd1 index 955fc6740..7e6c4851d 100644 --- a/WDACConfig/WDACConfig Module Files/WDACConfig.psd1 +++ b/WDACConfig/WDACConfig Module Files/WDACConfig.psd1 @@ -2,7 +2,7 @@ # https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_module_manifests RootModule = 'WDACConfig.psm1' - ModuleVersion = '0.4.5' + ModuleVersion = '0.4.6' CompatiblePSEditions = @('Core') GUID = '79920947-efb5-48c1-a567-5b02ebe74793' Author = 'HotCakeX' @@ -105,9 +105,9 @@ This is an advanced PowerShell module for WDAC (Windows Defender Application Con 'ConvertTo-WDACPolicy', 'Get-CiFileHashes', 'Set-CiRuleOptions', - 'Get-CIPolicySetting') + 'Get-CIPolicySetting', + 'Update-WDACConfigPSModule') - # 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 = @{ PSData = @{ Tags = @('WDAC', 'Windows-Defender-Application-Control', 'Windows', 'Security', 'Microsoft', 'Application-Control', 'App-Control-for-Business', 'Application-Whitelisting', 'BYOVD') diff --git a/WDACConfig/WDACConfig Module Files/WDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/WDACConfig.psm1 index c0b975537..9aca1aa0a 100644 --- a/WDACConfig/WDACConfig Module Files/WDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/WDACConfig.psm1 @@ -1,15 +1,4 @@ -<# -------- Guidance for code readers -------- -The $PSDefaultParameterValues located in "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" is imported via dot-sourcing to the current session of each main cmdlet/internal function that calls any (other) internal function or uses any of the cmdlets defined in that file, prior to everything else. -At the beginning of each main cmdlet, 2 custom $Verbose and/or $Debug variables are defined which help to take actions based on Verbose/Debug preferences and also pass the $VerbosePreference and $DebugPreference to the subsequent sub-functions/modules being called from the main cmdlets. - -E.g., - -this captures the $Debug preference from the command line: -[System.Boolean]$Debug = $PSBoundParameters.Debug.IsPresent ? $true : $false - -Then in the PSDefaultParameterValues.ps1 file, there is 'Do-Something:Debug' = $Debug -So that essentially means any instance of 'Do-Something' cmdlet in the code is actually 'Do-Something -Debug:$Debug' - +<# Load order of the WDACConfig module: 1. ScriptsToProcess defined in the manifest @@ -27,6 +16,107 @@ if (!$IsWindows) { #Requires -RunAsAdministrator +Function Update-WDACConfigPSModule { + <# + .SYNOPSIS + Make sure the latest version of the module is installed and if not, automatically update it, clean up any old versions + .PARAMETER InvocationStatement + The command that was used to invoke the main function/cmdlet that invoked the Update-WDACConfigPSModule function, this is used to re-run the command after the module has been updated. + It checks to make sure the Update-WDACConfigPSModule function was called by an authorized command, that is one of the main cmdlets of the WDACConfig module, otherwise it will throw an error. + The parameter also shouldn't contain any backtick or semicolon characters used to chain commands together. + .NOTES + Even if the main cmdlets of the module are called with semicolons like this: Get-Date;New-WDACConfig -GetDriverBlockRules -Verbose -Deploy;Get-Host + Since the Update-WDACConfigPSModule function only receives the invocation statement from the main cmdlet/function, anything before or after the semicolons are automatically dropped and will not run after the module is auto updated. + So from the example above, only this part gets executed after auto update: New-WDACConfig -GetDriverBlockRules -Verbose -Deploy + The ValidatePattern attribute is just an extra layer of security. + .INPUTS + System.String + .OUTPUTS + System.String + #> + [CmdletBinding()] + [OutputType([System.String])] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)] + [ValidatePattern('^(Confirm-WDACConfig|Deploy-SignedWDACConfig|Edit-SignedWDACConfig|Edit-WDACConfig|Invoke-WDACSimulation|New-DenyWDACConfig|New-KernelModeWDACConfig|New-SupplementalWDACConfig|New-WDACConfig|Remove-WDACConfig|Assert-WDACConfigIntegrity|Build-WDACCertificate|Get-CiFileHashes|ConvertTo-WDACPolicy|Get-CIPolicySetting)(?!.*[;`]).*$', ErrorMessage = 'Either Update-WDACConfigPSModule function was called with an unauthorized command or it contains semicolon and/or backtick')] + [System.String]$InvocationStatement + ) + try { + # Get the last update check time + [WDACConfig.Logger]::Write('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 + [WDACConfig.Logger]::Write('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 occurred more than 30 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 30) -or $PerformOnlineUpdateCheck) { + + [WDACConfig.Logger]::Write("Performing online update check because the last update check was performed $($TimeDiff ?? [System.Char]::ConvertFromUtf32(8734)) minutes ago") + + [System.Version]$CurrentVersion = (Test-ModuleManifest -Path "$([WDACConfig.GlobalVars]::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.' + } + } + + # Reset the last update timer to the current time + [WDACConfig.Logger]::Write('Resetting the last update timer to the current time') + $null = Set-CommonWDACConfig -LastUpdateCheck $(Get-Date) + + if ($CurrentVersion -lt $LatestVersion) { + + Write-Output -InputObject "$($PSStyle.Foreground.FromRGB(255,0,230))The currently installed module's version is $CurrentVersion while the latest version is $LatestVersion - Auto Updating the module...$($PSStyle.Reset)" + + # Remove the old module version from the current session + Remove-Module -Name 'WDACConfig' -Force -WarningAction SilentlyContinue + + try { + Uninstall-Module -Name 'WDACConfig' -AllVersions -Force -ErrorAction Ignore -WarningAction SilentlyContinue + } + catch {} + + Install-Module -Name 'WDACConfig' -RequiredVersion $LatestVersion -Scope AllUsers -Force + # Will not import the new module version in the current session because of the constant variables. New version is automatically imported when the main cmdlet is run in a new session. + + # Make sure the old version isn't run after update + Write-Output -InputObject "$($PSStyle.Foreground.FromRGB(152,255,152))Update has been successful, running your command now$($PSStyle.Reset)" + + try { + # Try to re-run the command that invoked the Update-WDACConfigPSModule function in a new session after the module is updated. + pwsh.exe -NoProfile -NoLogo -NoExit -command $InvocationStatement + } + catch { + Throw 'Could not relaunch PowerShell after update. Please close and reopen PowerShell to run your command again.' + } + } + } + else { + [WDACConfig.Logger]::Write("Skipping online update check because the last update check was performed $TimeDiff minutes ago") + } +} + # Unimportant actions that don't need to be terminating if they fail try { # Set PSReadline tab completion to complete menu for easier access to available parameters - Only for the current session diff --git a/WDACConfig/WDACConfig Module Files/WDACSimulation/Compare-SignerAndCertificate.psm1 b/WDACConfig/WDACConfig Module Files/WDACSimulation/Compare-SignerAndCertificate.psm1 index bf1cffefe..875fd3ef3 100644 --- a/WDACConfig/WDACConfig Module Files/WDACSimulation/Compare-SignerAndCertificate.psm1 +++ b/WDACConfig/WDACConfig Module Files/WDACSimulation/Compare-SignerAndCertificate.psm1 @@ -15,9 +15,6 @@ Function Compare-SignerAndCertificate { [Parameter(Mandatory = $true)][WDACConfig.SimulationInput]$SimulationInput ) Begin { - [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $false - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - # Get the extended file attributes [WDACConfig.ExFileInfo]$ExtendedFileInfo = [WDACConfig.ExFileInfo]::GetExtendedFileInfo($SimulationInput.FilePath) } @@ -32,13 +29,13 @@ Function Compare-SignerAndCertificate { continue } - # Write-Debug -Message "Checking the signer: $($Signer.Name)" + [WDACConfig.Logger]::Write("Checking the signer: $($Signer.Name)") # If the signer has any EKUs, try to match it with the file's EKU OIDs if ($Signer.HasEKU) { - Write-Debug -Message 'The signer has EKUs' - # Write-Debug -Message "The current file has $($SimulationInput.EKUOIDs.Count) EKUs" + [WDACConfig.Logger]::Write('The signer has EKUs') + [WDACConfig.Logger]::Write("The current file has $($SimulationInput.EKUOIDs.Count) EKUs") # Check if any of the Signer's OIDs match any of the file's certificates' OIDs (which are basically Leaf certificates' EKU OIDs) # This is used for all levels, not just WHQL levels @@ -53,12 +50,12 @@ Function Compare-SignerAndCertificate { # If both the file and signer had EKUs and they match if ($EKUsMatch) { - Write-Debug -Message "The EKUs of the signer matched with the file's EKUs" + [WDACConfig.Logger]::Write("The EKUs of the signer matched with the file's EKUs") # If the signer and file have matching EKUs and the signer is WHQL then start checking for OemID if ($Signer.IsWHQL) { - Write-Debug -Message 'The signer is WHQL' + [WDACConfig.Logger]::Write('The signer is WHQL') # At this point the file is definitely WHQL-Signed @@ -133,7 +130,7 @@ Function Compare-SignerAndCertificate { if (($null -ne $ExtendedFileInfo.$KeyItem) -and ($ExtendedFileInfo.$KeyItem -eq $FileAttrib.$KeyItem)) { - [WDACConfig.VerboseLogger]::Write("The SpecificFileNameLevel is $KeyItem") + [WDACConfig.Logger]::Write("The SpecificFileNameLevel is $KeyItem") # If there was a match then assign the $KeyItem which is the name of the SpecificFileNameLevel option to the $CurrentFileInfo.SpecificFileNameLevelMatchCriteria @@ -255,7 +252,7 @@ Function Compare-SignerAndCertificate { } else { - Write-Debug -Message "The signer had EKUs but they didn't match with the file's EKUs" + [WDACConfig.Logger]::Write("The signer had EKUs but they didn't match with the file's EKUs") # If the signer has EKU but it didn't match with the file's EKU then skip the current signer # as it shouldn't be used for any other levels Continue @@ -322,7 +319,7 @@ Function Compare-SignerAndCertificate { if (($null -ne $ExtendedFileInfo.$KeyItem) -and ($ExtendedFileInfo.$KeyItem -eq $FileAttrib.$KeyItem)) { - [WDACConfig.VerboseLogger]::Write("The SpecificFileNameLevel is $KeyItem") + [WDACConfig.Logger]::Write("The SpecificFileNameLevel is $KeyItem") # If there was a match then assign the $KeyItem which is the name of the SpecificFileNameLevel option to the $CurrentFileInfo.SpecificFileNameLevelMatchCriteria # And break out of the loop by validating the signer as suitable for FilePublisher level @@ -498,7 +495,7 @@ Function Compare-SignerAndCertificate { if (($null -ne $ExtendedFileInfo.$KeyItem) -and ($ExtendedFileInfo.$KeyItem -eq $FileAttrib.$KeyItem)) { - [WDACConfig.VerboseLogger]::Write("The SpecificFileNameLevel is $KeyItem") + [WDACConfig.Logger]::Write("The SpecificFileNameLevel is $KeyItem") # If there was a match then assign the $KeyItem which is the name of the SpecificFileNameLevel option to the $CurrentFileInfo.SpecificFileNameLevelMatchCriteria # And break out of the loop by validating the signer as suitable for FilePublisher level diff --git a/WDACConfig/WDACConfig Module Files/WDACSimulation/Get-CertificateDetails.psm1 b/WDACConfig/WDACConfig Module Files/WDACSimulation/Get-CertificateDetails.psm1 index 854795e24..7c972151b 100644 --- a/WDACConfig/WDACConfig Module Files/WDACSimulation/Get-CertificateDetails.psm1 +++ b/WDACConfig/WDACConfig Module Files/WDACSimulation/Get-CertificateDetails.psm1 @@ -19,9 +19,6 @@ Function Get-CertificateDetails { [Parameter(Mandatory = $true)][WDACConfig.AllCertificatesGrabber.AllFileSigners[]]$CompleteSignatureResult ) Begin { - [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $false - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - $FinalObject = New-Object -TypeName System.Collections.Generic.List[WDACConfig.ChainPackage] } diff --git a/WDACConfig/WDACConfig Module Files/WDACSimulation/Get-SignerInfo.psm1 b/WDACConfig/WDACConfig Module Files/WDACSimulation/Get-SignerInfo.psm1 index d32db3d83..a3d0655f0 100644 --- a/WDACConfig/WDACConfig Module Files/WDACSimulation/Get-SignerInfo.psm1 +++ b/WDACConfig/WDACConfig Module Files/WDACSimulation/Get-SignerInfo.psm1 @@ -16,8 +16,6 @@ Function Get-SignerInfo { [System.Xml.XmlDocument]$XML ) begin { - [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $false - # Get User Mode Signers IDs $AllowedUMCISigners = [System.Collections.Generic.HashSet[System.String]]@(($XML.SiPolicy.SigningScenarios.SigningScenario.Where({ $_.value -eq '12' })).ProductSigners.AllowedSigners.AllowedSigner.SignerId) $DeniedUMCISigners = [System.Collections.Generic.HashSet[System.String]]@(($XML.SiPolicy.SigningScenarios.SigningScenario.Where({ $_.value -eq '12' })).ProductSigners.DeniedSigners.DeniedSigner.SignerId) diff --git a/WDACConfig/WDACConfig Module Files/XMLOps/Checkpoint-Macros.psm1 b/WDACConfig/WDACConfig Module Files/XMLOps/Checkpoint-Macros.psm1 index 3e218fbe1..506c52668 100644 --- a/WDACConfig/WDACConfig Module Files/XMLOps/Checkpoint-Macros.psm1 +++ b/WDACConfig/WDACConfig Module Files/XMLOps/Checkpoint-Macros.psm1 @@ -65,7 +65,7 @@ Function Checkpoint-Macros { $Xml.Load($XmlFilePathOut.FullName) if ($null -eq $MacrosBackup) { - [WDACConfig.VerboseLogger]::Write('Checkpoint-Macros: No Macros nodes to restore.') + [WDACConfig.Logger]::Write('Checkpoint-Macros: No Macros nodes to restore.') return } diff --git a/WDACConfig/WDACConfig Module Files/XMLOps/Clear-CiPolicy_Semantic.psm1 b/WDACConfig/WDACConfig Module Files/XMLOps/Clear-CiPolicy_Semantic.psm1 index 630af97da..78db70133 100644 --- a/WDACConfig/WDACConfig Module Files/XMLOps/Clear-CiPolicy_Semantic.psm1 +++ b/WDACConfig/WDACConfig Module Files/XMLOps/Clear-CiPolicy_Semantic.psm1 @@ -18,8 +18,6 @@ Function Clear-CiPolicy_Semantic { [Parameter(Mandatory = $true)][System.IO.FileInfo]$Path ) Begin { - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - # Load the XML file [System.Xml.XmlDocument]$Xml = Get-Content -Path $Path diff --git a/WDACConfig/WDACConfig Module Files/XMLOps/Close-EmptyXmlNodes_Semantic.psm1 b/WDACConfig/WDACConfig Module Files/XMLOps/Close-EmptyXmlNodes_Semantic.psm1 index 3b9dacd5c..9f87c4c68 100644 --- a/WDACConfig/WDACConfig Module Files/XMLOps/Close-EmptyXmlNodes_Semantic.psm1 +++ b/WDACConfig/WDACConfig Module Files/XMLOps/Close-EmptyXmlNodes_Semantic.psm1 @@ -40,7 +40,6 @@ Function Close-EmptyXmlNodes_Semantic { [Parameter(Mandatory = $true)][System.IO.FileInfo]$XmlFilePath ) Begin { - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" # Define the base node names that should not be removed even if empty [System.String[]]$BaseNodeNames = @('SiPolicy', 'Rules', 'EKUs', 'FileRules', 'Signers', 'SigningScenarios', 'UpdatePolicySigners', 'CiSigners', 'HvciOptions', 'BasePolicyID', 'PolicyID') diff --git a/WDACConfig/WDACConfig Module Files/XMLOps/Compare-CorrelatedData.psm1 b/WDACConfig/WDACConfig Module Files/XMLOps/Compare-CorrelatedData.psm1 index 991327227..0b45287c1 100644 --- a/WDACConfig/WDACConfig Module Files/XMLOps/Compare-CorrelatedData.psm1 +++ b/WDACConfig/WDACConfig Module Files/XMLOps/Compare-CorrelatedData.psm1 @@ -41,13 +41,10 @@ Function Compare-CorrelatedData { [Parameter(Mandatory = $true)][System.String]$LogType ) Begin { - [System.Boolean]$Debug = $PSBoundParameters.Debug.IsPresent ? $true : $false - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - # Group the events based on the EtwActivityId, which is the unique identifier for each group of correlated events [Microsoft.PowerShell.Commands.GroupInfo[]]$GroupedEvents = $OptimizedCSVData | Group-Object -Property EtwActivityId - [WDACConfig.VerboseLogger]::Write("Compare-CorrelatedData: Total number of groups: $($GroupedEvents.Count)") + [WDACConfig.Logger]::Write("Compare-CorrelatedData: Total number of groups: $($GroupedEvents.Count)") # Create a collection to store the packages of logs to return at the end [System.Collections.Hashtable]$EventPackageCollections = @{} @@ -72,7 +69,7 @@ Function Compare-CorrelatedData { } } Catch { - [WDACConfig.VerboseLogger]::Write("Event Timestamp for the file '$($GroupData.FileName)' was invalid") + [WDACConfig.Logger]::Write("Event Timestamp for the file '$($GroupData.FileName)' was invalid") } } @@ -137,7 +134,7 @@ Function Compare-CorrelatedData { # If it does, check if the current log is signed and the main log is unsigned if (($EventPackageCollections[$UniqueAuditMainEventDataKey]['SignatureStatus'] -eq 'Unsigned') -and ($TempAuditHashTable['SignatureStatus'] -eq 'Signed')) { - Write-Debug -Message "The unsigned log of the file $($TempAuditHashTable['FileName']) is being replaced with its signed log." + [WDACConfig.Logger]::Write("The unsigned log of the file $($TempAuditHashTable['FileName']) is being replaced with its signed log.") # Remove the Unsigned log from the main HashTable $EventPackageCollections.Remove($UniqueAuditMainEventDataKey) @@ -215,7 +212,7 @@ Function Compare-CorrelatedData { # If it does, check if the current log is signed and the main log is unsigned if (($EventPackageCollections[$UniqueBlockedMainEventDataKey]['SignatureStatus'] -eq 'Unsigned') -and ($TempBlockedHashTable['SignatureStatus'] -eq 'Signed')) { - Write-Debug -Message "The unsigned log of the file $($TempBlockedHashTable['FileName']) is being replaced with its signed log." + [WDACConfig.Logger]::Write("The unsigned log of the file $($TempBlockedHashTable['FileName']) is being replaced with its signed log.") # Remove the Unsigned log from the main HashTable $EventPackageCollections.Remove($UniqueBlockedMainEventDataKey) @@ -236,8 +233,8 @@ Function Compare-CorrelatedData { End { - if ($Debug) { - [WDACConfig.VerboseLogger]::Write('Compare-CorrelatedData: Debug parameter was used, exporting data to Json...') + if ([WDACConfig.GlobalVars]::DebugPreference) { + [WDACConfig.Logger]::Write('Compare-CorrelatedData: Debug parameter was used, exporting data to Json...') # Outputs the entire data to a JSON file for debugging purposes with max details $EventPackageCollections | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path -Path $StagingArea -ChildPath 'Pass2.Json') -Force diff --git a/WDACConfig/WDACConfig Module Files/XMLOps/Merge-Signers_Semantic.psm1 b/WDACConfig/WDACConfig Module Files/XMLOps/Merge-Signers_Semantic.psm1 index cbd0fe703..3c2402336 100644 --- a/WDACConfig/WDACConfig Module Files/XMLOps/Merge-Signers_Semantic.psm1 +++ b/WDACConfig/WDACConfig Module Files/XMLOps/Merge-Signers_Semantic.psm1 @@ -25,8 +25,6 @@ Function Merge-Signers_Semantic { [Parameter(Mandatory = $true)][System.IO.FileInfo]$XmlFilePath ) Begin { - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - [System.Xml.XmlDocument]$Xml = Get-Content -Path $XmlFilePath } @@ -72,7 +70,7 @@ Function Merge-Signers_Semantic { $FileRulesValidID_HashSet = [System.Collections.Generic.HashSet[System.String]]@($FileRulesElements.ID) if ($SignerNodes.Count -eq 0) { - [WDACConfig.VerboseLogger]::Write('Merge-Signers: No Signer nodes found in the XML file. Exiting the function.') + [WDACConfig.Logger]::Write('Merge-Signers: No Signer nodes found in the XML file. Exiting the function.') Return } @@ -108,7 +106,7 @@ Function Merge-Signers_Semantic { [System.Int64]$After = $Signer.FileAttribRef.count if ($Before -ne $After) { - [WDACConfig.VerboseLogger]::Write("Merge-Signers: Removed $($Before - $After) FileAttribRef elements from Signer with ID $($Signer.ID).") + [WDACConfig.Logger]::Write("Merge-Signers: Removed $($Before - $After) FileAttribRef elements from Signer with ID $($Signer.ID).") } # Determine the Signing Scenario based on the AllowedSigners @@ -145,7 +143,7 @@ Function Merge-Signers_Semantic { [System.Void]$UniqueFilePublisherSigners131[$FilePublisherKey]['Signer'].AppendChild($Xml.ImportNode($FileAttribRef, $true)) } - [WDACConfig.VerboseLogger]::Write("Merge-Signers: Merged FilePublisher signer for Signing Scenario 131 with IDs: $($UniqueFilePublisherSigners131[$FilePublisherKey].ID) and $($Signer.ID). Their FileAttribRefs are merged.") + [WDACConfig.Logger]::Write("Merge-Signers: Merged FilePublisher signer for Signing Scenario 131 with IDs: $($UniqueFilePublisherSigners131[$FilePublisherKey].ID) and $($Signer.ID). Their FileAttribRefs are merged.") } } # If the signer is part of Signing Scenario 12 @@ -180,7 +178,7 @@ Function Merge-Signers_Semantic { [System.Void]$UniqueFilePublisherSigners12[$FilePublisherKey]['Signer'].AppendChild($Xml.ImportNode($FileAttribRef, $true)) } - [WDACConfig.VerboseLogger]::Write("Merge-Signers: Merged FilePublisher signer for Signing Scenario 12 with IDs: $($UniqueFilePublisherSigners12[$FilePublisherKey].ID) and $($Signer.ID). Their FileAttribRefs are merged.") + [WDACConfig.Logger]::Write("Merge-Signers: Merged FilePublisher signer for Signing Scenario 12 with IDs: $($UniqueFilePublisherSigners12[$FilePublisherKey].ID) and $($Signer.ID). Their FileAttribRefs are merged.") } } } @@ -211,7 +209,7 @@ Function Merge-Signers_Semantic { $UniquePublisherSigners131[$PublisherKey] = $PublisherKeyTemp } else { - [WDACConfig.VerboseLogger]::Write("Merge-Signers: Excluded Publisher signer for Signing Scenario 131 with ID: $($Signer.ID). Only one Publisher signer is allowed per TBS, Name, and CertPublisher.") + [WDACConfig.Logger]::Write("Merge-Signers: Excluded Publisher signer for Signing Scenario 131 with ID: $($Signer.ID). Only one Publisher signer is allowed per TBS, Name, and CertPublisher.") } } # If the signer is part of Signing Scenario 12 @@ -235,7 +233,7 @@ Function Merge-Signers_Semantic { $UniquePublisherSigners12[$PublisherKey] = $PublisherKeyTemp } else { - [WDACConfig.VerboseLogger]::Write("Merge-Signers: Excluded Publisher signer for Signing Scenario 12 with ID: $($Signer.ID). Only one Publisher signer is allowed per TBS, Name, and CertPublisher.") + [WDACConfig.Logger]::Write("Merge-Signers: Excluded Publisher signer for Signing Scenario 12 with ID: $($Signer.ID). Only one Publisher signer is allowed per TBS, Name, and CertPublisher.") } } } diff --git a/WDACConfig/WDACConfig Module Files/XMLOps/New-CertificateSignerRules.psm1 b/WDACConfig/WDACConfig Module Files/XMLOps/New-CertificateSignerRules.psm1 index 5104b2839..c8fa2eee4 100644 --- a/WDACConfig/WDACConfig Module Files/XMLOps/New-CertificateSignerRules.psm1 +++ b/WDACConfig/WDACConfig Module Files/XMLOps/New-CertificateSignerRules.psm1 @@ -21,8 +21,6 @@ Function New-CertificateSignerRules { [Parameter(Mandatory = $true)][System.IO.FileInfo]$XmlFilePath ) Begin { - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - # Load the XML file [System.Xml.XmlDocument]$Xml = Get-Content -Path $XmlFilePath diff --git a/WDACConfig/WDACConfig Module Files/XMLOps/New-FilePublisherLevelRules.psm1 b/WDACConfig/WDACConfig Module Files/XMLOps/New-FilePublisherLevelRules.psm1 index b41fb70e0..6e2a33fe0 100644 --- a/WDACConfig/WDACConfig Module Files/XMLOps/New-FilePublisherLevelRules.psm1 +++ b/WDACConfig/WDACConfig Module Files/XMLOps/New-FilePublisherLevelRules.psm1 @@ -20,9 +20,7 @@ Function New-FilePublisherLevelRules { [Parameter(Mandatory = $true)][System.IO.FileInfo]$XmlFilePath ) Begin { - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - - [WDACConfig.VerboseLogger]::Write("New-FilePublisherLevelRules: There are $($FilePublisherSigners.Count) File Publisher Signers to be added to the XML file") + [WDACConfig.Logger]::Write("New-FilePublisherLevelRules: There are $($FilePublisherSigners.Count) File Publisher Signers to be added to the XML file") # Load the XML file [System.Xml.XmlDocument]$Xml = Get-Content -Path $XmlFilePath diff --git a/WDACConfig/WDACConfig Module Files/XMLOps/New-HashLevelRules.psm1 b/WDACConfig/WDACConfig Module Files/XMLOps/New-HashLevelRules.psm1 index 614827dc5..b61aa0204 100644 --- a/WDACConfig/WDACConfig Module Files/XMLOps/New-HashLevelRules.psm1 +++ b/WDACConfig/WDACConfig Module Files/XMLOps/New-HashLevelRules.psm1 @@ -21,9 +21,7 @@ Function New-HashLevelRules { [Parameter(Mandatory = $true)][System.IO.FileInfo]$XmlFilePath ) Begin { - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - - [WDACConfig.VerboseLogger]::Write("New-HashLevelRules: There are $($Hashes.Count) Hash rules to be added to the XML file") + [WDACConfig.Logger]::Write("New-HashLevelRules: There are $($Hashes.Count) Hash rules to be added to the XML file") # Load the XML file [System.Xml.XmlDocument]$Xml = Get-Content -Path $XmlFilePath diff --git a/WDACConfig/WDACConfig Module Files/XMLOps/New-Macros.psm1 b/WDACConfig/WDACConfig Module Files/XMLOps/New-Macros.psm1 index 7311c73fd..de019792c 100644 --- a/WDACConfig/WDACConfig Module Files/XMLOps/New-Macros.psm1 +++ b/WDACConfig/WDACConfig Module Files/XMLOps/New-Macros.psm1 @@ -37,7 +37,7 @@ Function New-Macros { [System.Void]$Macros.Add($ExFileInfo.OriginalFileName) } else { - [WDACConfig.VerboseLogger]::Write("New-Macros: OriginalFileName property is empty for the file: $($Exe.FullName)") + [WDACConfig.Logger]::Write("New-Macros: OriginalFileName property is empty for the file: $($Exe.FullName)") } } } diff --git a/WDACConfig/WDACConfig Module Files/XMLOps/New-PFNLevelRules.psm1 b/WDACConfig/WDACConfig Module Files/XMLOps/New-PFNLevelRules.psm1 index 3be99000f..4aab03291 100644 --- a/WDACConfig/WDACConfig Module Files/XMLOps/New-PFNLevelRules.psm1 +++ b/WDACConfig/WDACConfig Module Files/XMLOps/New-PFNLevelRules.psm1 @@ -7,8 +7,6 @@ Function New-PFNLevelRules { [Parameter(Mandatory = $true)][System.IO.FileInfo]$XmlFilePath ) Begin { - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - # Load the XML file [System.Xml.XmlDocument]$Xml = Get-Content -Path $XmlFilePath diff --git a/WDACConfig/WDACConfig Module Files/XMLOps/New-PublisherLevelRules.psm1 b/WDACConfig/WDACConfig Module Files/XMLOps/New-PublisherLevelRules.psm1 index 1fecb5163..2337ec147 100644 --- a/WDACConfig/WDACConfig Module Files/XMLOps/New-PublisherLevelRules.psm1 +++ b/WDACConfig/WDACConfig Module Files/XMLOps/New-PublisherLevelRules.psm1 @@ -20,9 +20,7 @@ Function New-PublisherLevelRules { [Parameter(Mandatory = $true)][System.IO.FileInfo]$XmlFilePath ) Begin { - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - - [WDACConfig.VerboseLogger]::Write("New-PublisherLevelRules: There are $($PublisherSigners.Count) Publisher Signers to be added to the XML file") + [WDACConfig.Logger]::Write("New-PublisherLevelRules: There are $($PublisherSigners.Count) Publisher Signers to be added to the XML file") # Load the XML file [System.Xml.XmlDocument]$Xml = Get-Content -Path $XmlFilePath diff --git a/WDACConfig/WDACConfig Module Files/XMLOps/Optimize-MDECSVData.psm1 b/WDACConfig/WDACConfig Module Files/XMLOps/Optimize-MDECSVData.psm1 index 569d55291..f54609cfe 100644 --- a/WDACConfig/WDACConfig Module Files/XMLOps/Optimize-MDECSVData.psm1 +++ b/WDACConfig/WDACConfig Module Files/XMLOps/Optimize-MDECSVData.psm1 @@ -22,15 +22,12 @@ Function Optimize-MDECSVData { [Parameter(Mandatory = $true)][System.IO.DirectoryInfo]$StagingArea ) Begin { - [System.Boolean]$Debug = $PSBoundParameters.Debug.IsPresent ? $true : $false - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - Try { # Get the number of enabled CPU cores $CPUEnabledCores = [System.Int64](Get-CimInstance -ClassName Win32_Processor -Verbose:$false).NumberOfEnabledCore } Catch { - [WDACConfig.VerboseLogger]::Write('Optimize-MDECSVData: Unable to detect the number of enabled CPU cores, defaulting to 5...') + [WDACConfig.Logger]::Write('Optimize-MDECSVData: Unable to detect the number of enabled CPU cores, defaulting to 5...') } } @@ -71,9 +68,9 @@ Function Optimize-MDECSVData { End { - if ($Debug) { + if ([WDACConfig.GlobalVars]::DebugPreference) { - [WDACConfig.VerboseLogger]::Write('Optimize-MDECSVData: Debug parameter was used, exporting the new array to a CSV file...') + [WDACConfig.Logger]::Write('Optimize-MDECSVData: Debug parameter was used, exporting the new array to a CSV file...') # Initialize a HashSet to keep track of all property names (aka keys in the HashTable Array) $PropertyNames = [System.Collections.Generic.HashSet[System.String]] @() diff --git a/WDACConfig/WDACConfig Module Files/XMLOps/Remove-AllowElements_Semantic.psm1 b/WDACConfig/WDACConfig Module Files/XMLOps/Remove-AllowElements_Semantic.psm1 index 97d337c03..e7a8471cf 100644 --- a/WDACConfig/WDACConfig Module Files/XMLOps/Remove-AllowElements_Semantic.psm1 +++ b/WDACConfig/WDACConfig Module Files/XMLOps/Remove-AllowElements_Semantic.psm1 @@ -20,8 +20,6 @@ function Remove-AllowElements_Semantic { [Parameter(Mandatory = $true)][System.IO.FileInfo]$Path ) Begin { - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - # Load the XML file [System.Xml.XmlDocument]$Xml = Get-Content -Path $Path diff --git a/WDACConfig/WDACConfig Module Files/XMLOps/Remove-DuplicateFileAttrib_Semantic.psm1 b/WDACConfig/WDACConfig Module Files/XMLOps/Remove-DuplicateFileAttrib_Semantic.psm1 index 53206a57c..0bc549075 100644 --- a/WDACConfig/WDACConfig Module Files/XMLOps/Remove-DuplicateFileAttrib_Semantic.psm1 +++ b/WDACConfig/WDACConfig Module Files/XMLOps/Remove-DuplicateFileAttrib_Semantic.psm1 @@ -23,8 +23,6 @@ function Remove-DuplicateFileAttrib_Semantic { [Parameter(Mandatory = $true)][System.IO.FileInfo]$XmlFilePath ) Begin { - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - # Load the XML file [System.Xml.XmlDocument]$XmlDoc = New-Object -TypeName System.Xml.XmlDocument $XmlDoc.Load($XmlFilePath) @@ -146,7 +144,7 @@ function Remove-DuplicateFileAttrib_Semantic { } } # Remove the duplicate FileAttrib element - [WDACConfig.VerboseLogger]::Write("Remove-DuplicateFileAttrib: Removed duplicate FileAttrib with ID: $($FileAttribToRemove.GetAttribute('ID'))") + [WDACConfig.Logger]::Write("Remove-DuplicateFileAttrib: Removed duplicate FileAttrib with ID: $($FileAttribToRemove.GetAttribute('ID'))") [System.Void]$FileAttribToRemove.ParentNode.RemoveChild($FileAttribToRemove) } } diff --git a/WDACConfig/WDACConfig Module Files/XMLOps/Remove-UnreferencedFileRuleRefs.psm1 b/WDACConfig/WDACConfig Module Files/XMLOps/Remove-UnreferencedFileRuleRefs.psm1 index 58f5844b7..fa4682f90 100644 --- a/WDACConfig/WDACConfig Module Files/XMLOps/Remove-UnreferencedFileRuleRefs.psm1 +++ b/WDACConfig/WDACConfig Module Files/XMLOps/Remove-UnreferencedFileRuleRefs.psm1 @@ -15,8 +15,6 @@ function Remove-UnreferencedFileRuleRefs { [Parameter(Mandatory = $true)][System.IO.FileInfo]$XmlFilePath ) Begin { - . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" - [System.Xml.XmlDocument]$XmlContent = Get-Content $XmlFilePath } Process { diff --git a/WDACConfig/WDACConfig.code-workspace b/WDACConfig/WDACConfig.code-workspace index bfa3e9ab8..90f30c5b1 100644 --- a/WDACConfig/WDACConfig.code-workspace +++ b/WDACConfig/WDACConfig.code-workspace @@ -258,7 +258,13 @@ "XDRs", "Zune" ], - "cSpell.language": "en,en-US,en-GB" + "cSpell.language": "en,en-US,en-GB", + "files.exclude": { + "**/WinUI3": true, + "**/.vs": true, + "**/obj": true, + "**/bin": true + } }, "extensions": { "recommendations": [ diff --git a/WDACConfig/WDACConfig.csproj b/WDACConfig/WDACConfig.csproj index 713c655cc..f4a6d9c1f 100644 --- a/WDACConfig/WDACConfig.csproj +++ b/WDACConfig/WDACConfig.csproj @@ -43,9 +43,21 @@ en-US - + + + + + + + + + + + + + diff --git a/WDACConfig/WinUI3/.editorconfig b/WDACConfig/WinUI3/.editorconfig new file mode 100644 index 000000000..b72f1f784 --- /dev/null +++ b/WDACConfig/WinUI3/.editorconfig @@ -0,0 +1,274 @@ +[*.cs] + +# Spell Checker dictionary +spelling_exclusion_path = .\exclusion.dic + +# Miscellaneous settings +trim_trailing_whitespace = true + +# CA1304: Specify CultureInfo +dotnet_diagnostic.CA1304.severity = error + +# CA1305: Specify IFormatProvider +dotnet_diagnostic.CA1305.severity = error + +# CA1307: Specify StringComparison for clarity +dotnet_diagnostic.CA1307.severity = error + +# CA1310: Specify StringComparison for correctness +dotnet_diagnostic.CA1310.severity = error + +# CA1401: P/Invokes should not be visible +dotnet_diagnostic.CA1401.severity = error + +# CA1303: Do not pass literals as localized parameters +dotnet_diagnostic.CA1303.severity = silent + +# CA1309: Use ordinal string comparison +dotnet_diagnostic.CA1309.severity = error + +# CA1311: Specify a culture or use an invariant version +dotnet_diagnostic.CA1311.severity = error + +# CA1416: Validate platform compatibility +dotnet_diagnostic.CA1416.severity = error + +# CA5384: Do Not Use Digital Signature Algorithm (DSA) +dotnet_diagnostic.CA5384.severity = error + +# CA1508: Avoid dead conditional code +dotnet_diagnostic.CA1508.severity = error + +# CA1200: Avoid using cref tags with a prefix +dotnet_diagnostic.CA1200.severity = error + +# CA1812: Avoid uninstantiated internal classes +dotnet_diagnostic.CA1812.severity = error + +# CA1825: Avoid zero-length array allocations +dotnet_diagnostic.CA1825.severity = error + +# CA1841: Prefer Dictionary.Contains methods +dotnet_diagnostic.CA1841.severity = error + +# CA1845: Use span-based 'string.Concat' +dotnet_diagnostic.CA1845.severity = error + +# CA1851: Possible multiple enumerations of 'IEnumerable' collection +dotnet_diagnostic.CA1851.severity = error + +# CA1855: Prefer 'Clear' over 'Fill' +dotnet_diagnostic.CA1855.severity = error + +# CA1865: Use char overload +dotnet_diagnostic.CA1865.severity = error + +# CA1866: Use char overload +dotnet_diagnostic.CA1866.severity = error + +# CA2014: Do not use stackalloc in loops +dotnet_diagnostic.CA2014.severity = error + +# CA2200: Rethrow to preserve stack details +dotnet_diagnostic.CA2200.severity = error + +# CA1821: Remove empty Finalizers +dotnet_diagnostic.CA1821.severity = error + +# CA1820: Test for empty strings using string length +dotnet_diagnostic.CA1820.severity = error + +# CA2251: Use 'string.Equals' +dotnet_diagnostic.CA2251.severity = error + +# CA1064: Exceptions should be public +dotnet_diagnostic.CA1064.severity = error + +# CA1040: Avoid empty interfaces +dotnet_diagnostic.CA1040.severity = error + +# CA1816: Dispose methods should call SuppressFinalize +dotnet_diagnostic.CA1816.severity = error + +# CA2153: Do Not Catch Corrupted State Exceptions +dotnet_diagnostic.CA2153.severity = error + +# CA2300: Do not use insecure deserializer BinaryFormatter +dotnet_diagnostic.CA2300.severity = error + +# CA2302: Ensure BinaryFormatter.Binder is set before calling BinaryFormatter.Deserialize +dotnet_diagnostic.CA2302.severity = error + +# CA2327: Do not use insecure JsonSerializerSettings +dotnet_diagnostic.CA2327.severity = error + +# CA3012: Review code for regex injection vulnerabilities +dotnet_diagnostic.CA3012.severity = error + +# CA3011: Review code for DLL injection vulnerabilities +dotnet_diagnostic.CA3011.severity = error + +# CA2217: Do not mark enums with FlagsAttribute +dotnet_diagnostic.CA2217.severity = error + +# CA1069: Enums values should not be duplicated +dotnet_diagnostic.CA1069.severity = error + +# CA1823: Avoid unused private fields +dotnet_diagnostic.CA1823.severity = error + +# CA1836: Prefer IsEmpty over Count +dotnet_diagnostic.CA1836.severity = error + +# CA2000: Dispose objects before losing scope +dotnet_diagnostic.CA2000.severity = error + +# CA1830: Prefer strongly-typed Append and Insert method overloads on StringBuilder +dotnet_diagnostic.CA1830.severity = error + +# CA1822: Mark members as static +dotnet_diagnostic.CA1822.severity = error + +# CA1050: Declare types in namespaces +dotnet_diagnostic.CA1050.severity = error + +# IDE0290: Use primary constructor | https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0290 +dotnet_diagnostic.IDE0290.severity = error + +# IDE0220: Add explicit cast in foreach loop | https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0220 +dotnet_diagnostic.IDE0220.severity = error + +# CA1868: Unnecessary call to 'Contains(item)' +dotnet_diagnostic.CA1868.severity = error + +# CA1869: Cache and reuse 'JsonSerializerOptions' instances +dotnet_diagnostic.CA1869.severity = error + +# IDE0090: Simplify new expression | https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0090 +dotnet_diagnostic.IDE0090.severity = error + +# IDE0059: Remove unnecessary value assignment | https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0059 +dotnet_diagnostic.IDE0059.severity = error + +# CA1829: Use Length/Count property instead of Count() when available +dotnet_diagnostic.CA1829.severity = error + +# CA1860: Avoid using 'Enumerable.Any()' extension method +dotnet_diagnostic.CA1860.severity = error + +# CA1854: Prefer the 'IDictionary.TryGetValue(TKey, out TValue)' method +dotnet_diagnostic.CA1854.severity = error + +# CA2208: Instantiate argument exceptions correctly +dotnet_diagnostic.CA2208.severity = error + +# IDE0100: Remove redundant equality +dotnet_diagnostic.IDE0100.severity = error + +# IDE0041: Use 'is null' check +dotnet_diagnostic.IDE0041.severity = error + +# IDE0028: Simplify collection initialization +dotnet_diagnostic.IDE0028.severity = error + +# CA1510: Use ArgumentNullException throw helper +dotnet_diagnostic.CA1510.severity = error + +# CA1509: Invalid entry in code metrics rule specification file +dotnet_diagnostic.CA1509.severity = error + +# CA1512: Use ArgumentOutOfRangeException throw helper +dotnet_diagnostic.CA1512.severity = error + +# CA1511: Use ArgumentException throw helper +dotnet_diagnostic.CA1511.severity = error + +# CA1514: Avoid redundant length argument +dotnet_diagnostic.CA1514.severity = error + +# CA1802: Use literals where appropriate +dotnet_diagnostic.CA1802.severity = error + +# CA1805: Do not initialize unnecessarily +dotnet_diagnostic.CA1805.severity = error + +# CA1806: Do not ignore method results +dotnet_diagnostic.CA1806.severity = error + +# CA1819: Properties should not return arrays +dotnet_diagnostic.CA1819.severity = silent + +# CA1834: Consider using 'StringBuilder.Append(char)' when applicable +dotnet_diagnostic.CA1834.severity = error + +# CA1832: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +dotnet_diagnostic.CA1832.severity = error + +# CA1846: Prefer 'AsSpan' over 'Substring' +dotnet_diagnostic.CA1846.severity = error + +# CA1862: Use the 'StringComparison' method overloads to perform case-insensitive string comparisons +dotnet_diagnostic.CA1862.severity = error + +# CA1863: Use 'CompositeFormat' +dotnet_diagnostic.CA1863.severity = error + +# CA1838: Avoid 'StringBuilder' parameters for P/Invokes +dotnet_diagnostic.CA1838.severity = error + +# CA1871: Do not pass a nullable struct to 'ArgumentNullException.ThrowIfNull' +dotnet_diagnostic.CA1871.severity = error + +# CA1872: Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' +dotnet_diagnostic.CA1872.severity = error + +# CA1864: Prefer the 'IDictionary.TryAdd(TKey, TValue)' method +dotnet_diagnostic.CA1864.severity = error + +# CA1861: Avoid constant arrays as arguments +dotnet_diagnostic.CA1861.severity = error + +# CA1858: Use 'StartsWith' instead of 'IndexOf' +dotnet_diagnostic.CA1858.severity = error + +# CA1853: Unnecessary call to 'Dictionary.ContainsKey(key)' +dotnet_diagnostic.CA1853.severity = error + +# CA1826: Do not use Enumerable methods on indexable collections +dotnet_diagnostic.CA1826.severity = error + +# CA1827: Do not use Count() or LongCount() when Any() can be used +dotnet_diagnostic.CA1827.severity = error + +# CA1828: Do not use CountAsync() or LongCountAsync() when AnyAsync() can be used +dotnet_diagnostic.CA1828.severity = error + +# IDE0019: Use pattern matching to avoid 'as' followed by a 'null' check +dotnet_diagnostic.IDE0019.severity = error + +# IDE0038: Use pattern matching to avoid 'is' check followed by a cast +dotnet_diagnostic.IDE0038.severity = error + +# IDE0058: Expression value is never used +dotnet_diagnostic.IDE0058.severity = error + +# CA2201: Do not raise reserved exception types +dotnet_diagnostic.CA2201.severity = error + +# IDE0240: Remove redundant nullable directive +dotnet_diagnostic.IDE0240.severity = silent + +# IDE0040: Add accessibility modifiers +dotnet_diagnostic.IDE0040.severity = error + +# IDE0010: Add missing cases +dotnet_diagnostic.IDE0010.severity = error + +# IDE0120: Simplify LINQ expression +dotnet_diagnostic.IDE0120.severity = error + +# IDE0110: Remove unnecessary discard +dotnet_diagnostic.IDE0110.severity = error + +# CA2101: Specify marshaling for P/Invoke string arguments +dotnet_diagnostic.CA2101.severity = error diff --git a/WDACConfig/WinUI3/App.xaml b/WDACConfig/WinUI3/App.xaml new file mode 100644 index 000000000..f14abf7a5 --- /dev/null +++ b/WDACConfig/WinUI3/App.xaml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/WDACConfig/WinUI3/App.xaml.cs b/WDACConfig/WinUI3/App.xaml.cs new file mode 100644 index 000000000..267a6db75 --- /dev/null +++ b/WDACConfig/WinUI3/App.xaml.cs @@ -0,0 +1,38 @@ +using Microsoft.UI.Xaml; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace WDACConfig +{ + /// + /// Provides application-specific behavior to supplement the default Application class. + /// + public partial class App : Application + { + /// + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). + /// + public App() + { + this.InitializeComponent(); + } + + /// + /// Invoked when the application is launched. + /// + /// Details about the launch request and process. + protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) + { + m_window = new MainWindow(); + m_window.Activate(); + } + + private Window? m_window; + + + // Adding this public property to expose the window + public static Window? MainWindow => ((App)Current).m_window; + } +} diff --git a/WDACConfig/WinUI3/Assets/BadgeLogo.scale-100.png b/WDACConfig/WinUI3/Assets/BadgeLogo.scale-100.png new file mode 100644 index 0000000000000000000000000000000000000000..24a7e0735e0fb8d9b2473766f01f3dd47bc67dba GIT binary patch literal 233 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9E$svykh8Km+7C^*~G z#WAE}&fBR6`5F{>oJ}A8uaA7#AbDd_p2MSqu_lVIlVzJra{9h7ryQ92_{>J9D_OP; zp^x@n`(7RJMkD^&0g(e;o8{UVL-+jCUwfcyI!AcJpRXO}3{y^AyPy_PXUu-$`a}!v zO1?A&frcY{ChQZxq`ArWOW0?o4R^H;eKPF*@lI}wmvkqc|S{O1&S&!OtstyZ?CF`M_>Hfg!7FJI-SAO)z4*}Q$iB}t$b#h literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/BadgeLogo.scale-150.png b/WDACConfig/WinUI3/Assets/BadgeLogo.scale-150.png new file mode 100644 index 0000000000000000000000000000000000000000..2af1e041875a83fb8f7c324e883996b4bd0a4196 GIT binary patch literal 293 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBI14-?iy0WiR6&^0Gf3qFQ1H5^ zi(^Q|oVU|&b2TUkxN!gaAHS}J<)+N{N9#n*^d~NpP0PKMTRC&va>fY)IP1ftmt|7qVWoqt#%^ppIH}&nJc`0o)ws@yYK$OC4zbj ze>Cv9I&IywCUO(U3ip4hwn>dPu|>D*Djas^U-;A#W$<=k;NCWqyhy(d%UpFErRui1 otG=4?`KjiCfAj6L1R@3CZl3xcQ#gRcB&JXR%6v;9?DynM{MHaR zUpY@cbPzW~oImA0dFno9t;DE;aIPI)gQX7!*aeUbH0|J|a({HL#7wMF7l5h;85*VN znh^k1iUL-h){OwDLKLXEVeJTjGNZt0D_9Z8fZ+B->=cTGaC@PNfN<|dJt4wg>OCN& zD}v%OY6+oP&Q#3<77*zh92$r+8yw9D7sPS}+_aV8M8kUd-n@`^n|bcM&oRY%7vpkg z#sm!p9@d713?0lmZwexEPSF`M`Z7 zMx6D|zUKdbErSkyWKt=ZXi)#n?*B!mXCI!=cMUnbk!fD*|2O6Lg!)82#Q&I;tYhKV zpcY|yY%^D%kU(i@wuRdPuOCG=#~Xah4#nH5haaiC!CU|Jm3ge->Rqy|qWjvsnQWvU z{Z+mAaQWM&XAU;2xMlaX1u(8>+BhXZ^N-_40qQDxd`(Q??<3%B>7JCm% zwlimhHkJEI`LI^7J>FTh^kCK78+(|nV&@vmaevo~4RGg-+pTuzSc9LC&Gt1r_zuac zRJ<22+Rwd8M=@!0*ooW>SI$7|r-$Y-Yk79uStjvIA zEj!nfN$U^epZnJrt129pKalRM+K?gm;sNUp2E|QMn}E(xP7qL34F1s^!o(IA8?@~~ zYY7v}>8uk{{}k3X9LzIK;fwRob%bP0l+XkK)vPi^ literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/LargeTile.scale-100.png b/WDACConfig/WinUI3/Assets/LargeTile.scale-100.png new file mode 100644 index 0000000000000000000000000000000000000000..2fab5cc3ad71e56f2dee471dcc4ada1968bab949 GIT binary patch literal 5482 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 Qp3)iWnLeq}dG+pp00lk5!TkI$@fLTvh(+mK( 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 literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/LargeTile.scale-150.png b/WDACConfig/WinUI3/Assets/LargeTile.scale-150.png new file mode 100644 index 0000000000000000000000000000000000000000..73dfe6001027286a1e6535b6c7011d99c2f03ce3 GIT binary patch literal 8538 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 D=7#x~ literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/LargeTile.scale-200.png b/WDACConfig/WinUI3/Assets/LargeTile.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..a794e9d1bfbd6cd5b39b3d18fdda23f3dbcbc0f9 GIT binary patch literal 12012 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 literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/LargeTile.scale-400.png b/WDACConfig/WinUI3/Assets/LargeTile.scale-400.png new file mode 100644 index 0000000000000000000000000000000000000000..6f02a825c0c3ec13d2fe03fa22694b2f09e12d55 GIT binary patch literal 27812 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℘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 literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/LockScreenLogo.scale-200.png b/WDACConfig/WinUI3/Assets/LockScreenLogo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..7440f0d4bf7c7e26e4e36328738c68e624ee851e GIT binary patch literal 432 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezr3(FqV6|IEGZ*x-#9g>~Mkr+x6^F zy~CDX2QIMs&Gcs3RnRBoxBA!*(Mfw0KTCYuYk0WlEIV>qBmPl! zq4ukrvfADX@#p8fbLY(H47N+k`FZ(FZh?cDro7>{8mkBO3>^oaIx`3!Jl)Qq)HI!+ z(S=1{o~eT)&W^=Ea8C`-17(Jv5(nHFJ{dOjGdxLVkY_y6&S1whfuFI4MM0kF0f&cO zPDVpV%nz;Id$>+0Ga5e9625-JcI)oq=#Pa3p^>8BB}21BUw@eN!-6@w%X+^`+Vn?! zryu|3T>kVWNBYyBc=7Y6H#s1Ah!OI_nezW zXTqOdkv2Az6KKBV=$yHdF^R3Fqw(TZEoNSZX>reXJ#bwX42%f|Pgg&ebxsLQ010xn AssI20 literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/SmallTile.scale-100.png b/WDACConfig/WinUI3/Assets/SmallTile.scale-100.png new file mode 100644 index 0000000000000000000000000000000000000000..d97771c13e0a96ed7a01f79a6750101477b4dd74 GIT binary patch literal 1788 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 literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/SmallTile.scale-400.png b/WDACConfig/WinUI3/Assets/SmallTile.scale-400.png new file mode 100644 index 0000000000000000000000000000000000000000..ef6868e7475ea90360c5a89c33fa54bb7c59f6e7 GIT binary patch literal 6811 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 U9lt8#-zE|*HC@$m<(DD<2cZj`+W-In literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/SplashScreen.scale-100.png b/WDACConfig/WinUI3/Assets/SplashScreen.scale-100.png new file mode 100644 index 0000000000000000000000000000000000000000..480722505ec6d16b7d6f978b52b4ceb316a275bc GIT binary patch literal 5714 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 literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/SplashScreen.scale-125.png b/WDACConfig/WinUI3/Assets/SplashScreen.scale-125.png new file mode 100644 index 0000000000000000000000000000000000000000..7b18bd5bb3e0cfbf84537c3995491328aa0befc9 GIT binary patch literal 7212 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 h#r!|Jfci#7Sd`FtGjkHhovdOo+gZYFX-@&Et; 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�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 literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/SplashScreen.scale-400.png b/WDACConfig/WinUI3/Assets/SplashScreen.scale-400.png new file mode 100644 index 0000000000000000000000000000000000000000..24a29ba9cfbec9d94bc535cf180ecfd2fc1470aa GIT binary patch literal 32940 zcmeEu`9IWa__uR9ryWVCY{wFnEy5Gq$OdeJw&+6QyX3!Hh5{ zYqAqFW-!^;!5Cv_p3nF=-{*P$fampkemGvQ8u7XB`?{~|eO=f4y6*c^_>Jqvy!(&u z=i%YuHMw%>77x$90v?_}3ij>>e)1P#>pSqV&+Uq}2M>?%3GScYOm4}}^YA?7F}d{j z9aQQJRrNO>p1n`c@UJ^*><+d4{r7@hz!3238TNmFztZ@XJLUG*h3fyl=PKeqJ^Y7< z|1j_$2L8jqe;D`=1OH**KMeebf&Vb@9|r!zz<(I{4+H;U;6Dufhk^g+7#O0Iv$Pt2 zthIC6Jg!@)sMS>NJ^JYAJ!3&3$MCVZu&ss2EAON)8y-0vb@tG`I8leFsKb9&2vl6Y zLmEncs<`ju;Xl<6oZkDf19q+USH1Ax7alA@9-klYIp43>)uq)5v=A)d3Q#bS zy0R=U=VP!I)(|3#xhgT#<#5nAsD4*$qr<{PF^(v^L^ixOM2TkUj#x`v)Yv*JS>>sJ zvN-O=;(IA%D88i0bgzjb=M-I8-`t(Gz&d9m1=BE&BVhfk0w1&ab;z4e*W!>q`J2n- zFayp_RByL+LhJ#q0L_}eiE=+9tGisaeb7q0RT2vunzOA7PQfn>^zF27M7kVau$UxM`jf8ndw#;$A`OZd7ae|1RwenqJ^q1`s7pvs$Y5>* z4L^kH?65?isy%0Yd>z){1(6^R=&r>iB;{A0l4V9LU3tRp&k2~=ipcpBnE2^=nQc#a zD=)UmX2mpK+%iT>T>q>zNjE?`Nr}1!^^|4i8mADTOa9LOMz)7xjTbgGp~)MUontG9 z=bNnT#p5;{N~&gL>~Y>#xtc(Y`~l~Fdg9b!LoekIroNURj8d$pU)!^8==V{;X1ZL( zCdD!_6t4RylHf_#y35J(&t5fFP%&WH8w%_DH((gclWqfOblvUkjVlX)8~2LQ8>e_q ztk5?$j}A@8KU-6~crdd9U0hO45dUt=c!7k69&)&{dp(_2N^i4U&CbBcm(N5k_Y*P$ zthI)-u(?iu08%~e-O-~(OxY?!o?o8X1~c6q^PR?MAJ9Eq4W$_*irDsEZnBF1wkBYb zyw(2z&omeo%4@3{UREyqJT%GO+Wq>SEe6ZD>Mxgcp367cy|!hX?wCo)EGfaZ+|lTp z*`OVqyubNL%0$@?z52$j>Rog`{P}* z#TvXSmC-P@Gs1UKWkn#Rw)QuQyC83w0BqucD!y>CQml(PUt-|Mmaw_xuwU1#YlSyzr8FZI@$4<7*kol#~=Im=_2IjV(b6dP6#CI)a z4z+5WpX+;$E6M9?+XpLO|8cJ+x9aI)8>+6aO;@EjSvzC-)YNCrhcs z2-)>uZV6X=@Rg4&mJFbO80+6<-{T7K7l`0n=A0kN_A$?Q&6y=Favo{)?Zpye4)iNU zI2dV01yPb_Z_^xp?NU&&+kDp!-<%Z3tS~vB!7T`@fI%YxU9U90<#KJ9*7dB?5z97o zu!X>`;{w+dg%Ec1h~!)^1@xnb*Roe`)+lJ-<$ zNri@amrwStO~2wEghcr*k_ION0mphfBTts#^>S2dagyGXQ$=(Bt44FUoel7V8(P5j>w4YFD;q=2N-xF7}Sz zaqj2}Gf;R})*?oKyxp^HjV^1tX8z-Ha%L1c2zJ26Z}>{s=A{1Q1-en42Jv(MN<82U z;pIDfACLv1T%xt`!NXo)irL>7xAoj+I@IvH2h&FLBoVJpggfY{M%6lDzl?MvNz<=l zA@@MdFG_$L_n30A*BSrii`^Q~qAdHNi-c@o2)pU4=)DYHU`cA$W79obuA@Ru>1WQK z+i)`Q8>Md*PjzsmpAg?MUbBF`-+B`)U~&~$-w;?IZa3{cszK~o(I9Rm@rBG!hXzbV z8C0=^B)9H#XL0jfyC@h6bfM-|%v8hp$~C{ZXKSXBlurcjA~$T($ZD+y@pyu6Fe$Ba zY>CeKZe$V5JE6S%j9b&@9BuI#9er*Jph^u=p#@%|v@LhHUp1lj?)Bk} z!5({H(S}M}P-UKypvsD#G}Aea7p4m~=|>D#B0b;gw^Pp?xhPQ!i);j>Zarbw!?8t6 z1Uig5=Cw%T1~8BD~S8p`sKh%rk;TxBS#|FcbgIl%Yho9 zogbn@>>{zFxT{eA+hw0IMt)saoUx*X?$K#YtQ#AoS|AScEp3i|4Qzr`E_AAGS#kIS znM~S1pheLXle?|EcR@hFhra`k~ z+7s7sNX}+t{>L3b_&`D4JNa*tM1qdeUmyG#s8q}X5OIfnW$`JgV?U$y#IcVC>e-E> z+?461YTjD4xva7Q?=Irv@rK^^1Yf0>7m=yU?_`5GZx+chNP}f7%f>+ zBR|x=aqy2fC5%%JE^XS3x7gdFSC4Uo4FexIJY0#$NcUzsceA*{oT=KG>APwPY51#m z^Yvpts`FrgK3v=0GVDUV_mvAO0l8--cD1=4WJH25b!^HOh5; z>OnxyJ)OJa-Um26GK^NUf&|uET~xStudNdnxUxq?{|-BfJ7F`Z?+g6rm)1Nw%{Cj6 zvL*|iy0EIhfMn&CJ}M=8TqwUoXu(paSd(aY1$t#|x{Yravo|`kJ|NWIcWsK%v{cpULGvY6vp+*`I@VA{6w`0xE($NW(0ju4xVOr1qo5y2&7s6j-6BW(Z zU^$)x9#{KP)h!`U)-WTgeTR$O3=@A=qR9&So<7|&+}KgD<0!wo{Uo>bW~KE=il(dz zNVUZ@vNpcI7&ix)WK#XPQ?WM`-x0VNGY$^U3G3CalBs*&zM3`}$)>))MaiRM!%y?32px?MAt3lPowXJBT zAv1lTTS8*1F0iU^`Ik@3nsZ*K4p$HD4xlyF+-BHFhP^HFU=NgYACs(%AX3-sUeAig z6<`KhCB!f4YRNIb4zbH!SHEqsb_G(PUjSfAeqDuYySX0Q;y$y)^SDnC~?K z=;S^@+A6hm8b%|`N4CywhP+fN`E(( znS1od&jPz{ z&iinDt9^8ZHb#3X!e!wL^ttWKy&v|Bp^PD)s$M>qJQi6hyGE*5BZu&;CDiVT@3^_K z{?FATDLN%nq^J}fN!7m9)aX{@a~Fk0)7oy6(!B=Av!sbRQrro`NKTpxdU}kFMfR1f zW%ZopdaoWxLKgFZg6*dK!7Z1>5sX;pqAKB8v4Yn+tq>xWp`Mm?LYFT>C{)Hq8{@p>!s3GZ^_$^ z@-)2*j?gWc`&tp>vITYM&f?L9GT1el#I{a{-Gyf3-UI%I-7M}(%_p`)Do~LJ4xbOL zKNnyI+q?lh-~0v+uw4^fSX~2Tb*miU_yEt{0}QyI53V2~X#SZ&c=(p!?a}=lc%0PD zH<|u?m-SKK2jeaLcl7vyXTRP9%U(S>3Bf|rloZd{y@6GlFw5=Q4lFNy2aeC=y0l)kU?Wl9zJDbH9OsDx8^RHaPGGv0~6(M-%@DwpZE-`4Hr=H|uZ%VGGb zV?1M!)f^?{9X;-BU+;mu68IKi68k_Zvi*+)aMk~Qv0c3X|DzI?!gaI`?jiRgC66!< zXs`l5p>H<&?u|Mer0wUrqmh_;S50@@?I-^H_lYB_Hi@C6w9t0tY62YgUBg5l|57Pj zO3ox~64kz4VzhzOn#UvWAo=3=6vhiD>(#ZyY5A(u+^BwS$*!qBS+k3uPdNU3B*WFo zg}EK*c;rm9P_4hGSRczqoEP@|fgrv_aw@=(6N&?$Eh-JdLp2ssEHgg7|kS~ddMnDu|_lj33w;8Izf-NoK&?dv= z?9B9^7Y~;N&ey7Dn_v*r8XDke^Mz~MqYIioM3M_%J}R}qeyK!R>9kdcbY!+C_0(i7 zQ&hwXJm(hHLPIoz{WP|u;jS@3dTYB4^-K))mwroY>)7A;G>p63w@K(kZouGqU7=NZ z3X#LKQ5-IY)=--;tLF!o)BW$QB<$ipu+BGobHLld;bh;^FY}u2lKvygoPng8vX^EL z+P2`S-Y1Xx{X=BaMnx()yC^`r82(=WE17H5MG+}!!emF~u7WSg=KF%8|h zrBLk@xQreA32WuIFWaO&sQRX7wB6a!YgNm!uRtKwV@;7EuKj2Y}6Xn7yVq}#H^hI4(r#184oNFdbn2ye5w}~IiYK>8HqYH!VkG&8LQXC0K_@;_2nk7f z99wx}@E`ix@I0xBE@p!k?tC33euA1@9E^S8HS2IiV)iz77dOBXiAV94P83-iaN)i2 zLj*6G18R_z`Jcw(5|EV@r{Jbap8$&A{a#8!9X^N6ypN^)u}(r1Ubc5Y1MKRqx}iXMzaW z#(Mq1E_EiimG`w8475*;?)NaMCPpOh`|35b|M+R>9qr*m9(0L|vb!lEu07g%di%KJ z-z_hGdD8$BZHVQy6HJ`z)??ZX)|Xd_XlR7gR6h5MN)xj2gvZYz5B~jaTk3fbac@Ig zv>|?twq;|K_I=Ub3iq}uR6@V`(i5kl$x;2|{zDNT?f*}7v`0esq8%PFsbve4c4iCA zP8cUNupHeMG&g`*rz{aJit$tAj`*mD({>|(t?(%B1raf!4n&Qf82EnkK2CeSu12w2 zH>UsSfLD`2P{r=V7`vN&1e?PbkIN(M6@DjPB5jMv1E#3ANzG>=wifj;KBl`SwU zy7z4@R@ri>Z(NAYwfeQp-E!hWegah#! zW?Vl^NFtq3q!&dF6J7^BYh>Ekmob(IXCaXe#t}?=V%PzHd-9 zJ5r)~H2HEQfG>$~@{~?2c`9wXOF~=Q^Ip`q-P}a-HfM)tA9jG>{kpU_de4pby7V<) zF^+r)7JnzU?(hDw!6AbGV-nFGR|`|4a3cX>Ad*~PS%tS|ALR<_VY02`#Y9E=J@#HZ z7ipWO2yt)g??wK)kQAHU!B76YqZs3OEe_9X75`jw%-H?aLx(a4Lt7m!jXR_sQRsJv z%1=|LxHJ6^Q$|OLJ{N))-LAc1RNT{i@q6JZQSyhA14-i!w2F)44smzuFX9vT_l`AP zA0~V*86r&A!s6?6ceWzE?Yq8LqlRLRDZI;p4pT23RCn*cwpOKN9Ok;YQg?`7>5ncT zalAqy>{M!Dwi>wZI#}k$de3S?y=oG zJFh*Xoco>xO(ZUx^%d_Oylk76tI&i2$FX^3URz{&LPopN9a1WaA2hi<}Raync3X~ z$1U;LPfTj!bVD`abH@y+s)CJ=q@~Lzm;6Y8kE$jbeHC7ZIW`m7OAj`*IupYXd_cVS zsOyWL_+(v9SH0~cS(nn);?+{9Oe zqp(_29w{=04#$cevs##l$L^+IEdRJrG{K?x}h|QLoRz=f_peD zF{!q-FuhZWRUX#Y;GoqaoDKo%0@O6h%*>|nJ^GDClu(90$Oaa8Kq4DGO0IAyHfn`wg{eT7njN0alX zrW_3GVElO-X^%s1@Wb*Xwp&`t=%quzMkY?V2uuDjB%bQ&a+HEu>9tlDbfK`;mHOTn z9|&ul3#}Pm($z|Jo9L=h-J`24ru)VG;|Z6$WQthjdVAWS>9W6O-@;e^i^sdp+^xMl z{J>13kMf{4G1T(PC3q3B28ERobxAZfxegG%%S2bm^cD(R*@aU2YNb0yt`P}bOPi0G3zX~AS-e3hsvLPBLu_ANvlhU!$-;J?#d#lM$u=}@ zFw+RJ;AXb8pI9c9Ts=vKoH+$KFhrP?t=nwv;WK!#pPN^nrtf&v56yIT$jco1iWPtD zxz+c%PM`V|hX@^?g$i{K%$=WlHUCb?cy95wB*)Y^$ik0vOKbB(WHmpTF1j>pTc!>b zeH`?#@ti=48C*xbs|6;=q$a3XcO@{X-J@fK13!jcoSwiZ-o=osDZ7cS$k|=Xm4g8? z7HvCtbJ)gPy6l!>wn_KxJJS!iwL}v@Eb2Nk=k(V2tC5mxQO7XhHHqSf^wBetp;iMc zne)#Z1#E3=iFuDCa0u#QoD!Y&^>IkRA3#d)wvFKx!nIr(N7{@pPgmwP06XP*4NF(z z)-1l}!m|;L(tMOLDS&TGoLCPrzb1k|f8Epakr*yom`TmF6U7C5=p!vvnlyyy`;&z0 zU=GmL=k8YB4%~T~)wjtO6Piv1G89pr=~upt$?= z>0F~bmDgg(AMRyD6N&JLw8J<@Yt)=-EU<{%ch3}xHmf-FvSC>^rrZpUb`CxXO$i`* zgH-?HBZ~2TfE7Ct(PktK*A1UjO)T1uSLB1iYM=|=mk?cx{q_@jhJnwdHNa#Tmfsf? zH(FFNhpxYV4lpUthVT$?0lfOfvTS5Ix<)#d^5{4YE4p2f`1`YLmg`^{rC_crKqIW` z?n-Q?G|aT-8gqlCs06bD1VxYi0s_Mj?#VgOd& zWejJ$;8nkP>k>R(1CI!&tO@sa-Kh&eHa}uf7xMKq7S&4nWg&h+&~bcI3O022#o^gp zCvcg_nKyp}>)lsf=}|=nPE2ZUb*JI?Qj+x~3WTKY=+M96GgTkf)qiMxj|u?j>=PEX zqg)=SJnU){=MdkQ_-S-qw{vEA*|_+%{m}=q}sslBt%j`4+;Og)6!C46 zf5AM$dLvf))s(dnkA;}H_W6+0Lxe|peU%zsEE@sb=8|qbOfVvh5#O-ou7xlbph2~- zdulMLD`SK_$U>r!pydX9&G3PSm3L$;Eiag5Ma!D4EqEg( zWRuN!7YpHvArTJ2cN~D~#Gfm^p`0;1a#5>;F+^almjmSH)MqL#Wte~i7+S^GF~a#H zIhD=|G3-UW-mwBl;D8xXf^*Em9s=k*yS1sqTY)q_ zaa$ppEGKq0Fx6QAzv=d*mbf=Yuq8z;jI7PUiVhq}|H8?%6T|hNI|a!z0DKwXIf>p9 zS(fXwhKZ6-$mY4}+o?ixr`b2DV;oS-Uc_y7FUR{b@Qx>cn*Lxot|n6QUO7r32}HO( ztzB$C&UeG3CR`yVAKv^)PEx+?t~3#7fB0V|5xqSXA?u5#J)QuV2&b4fs1+1e8Itm8 z2>-*_wq#%>wS+zY<~yLpuTVKKDOyQvDP=SNw1~yqUK=#_%WF{PjZNEb;O$+&F3O-?oZg#|N_8!Z zC|47gZz$dFOVFhszbu5jzMmRmlLgo_U_GgY16KozRgB+^^Qp~F+zSU*%iRo^|Xxm&#a;&tc_`q9u$;&HWmeP7c3%^^EVs5UvV(Jg`2ak zy;&g}i|=bbm+7x$QeXYCm2EuBdHv&2O1NR-Op&7?jbCF!ai*)v^Rvs3g&Sh4$;y+o zCt{C}R#on;jtf45Hw$`Mx14!$zJ6Ru6DLHQ?`#ZyAV470yJpnmQ3#ZSPXo+=bpG>J z^`BB^U1x5j00v(Ei~(+JFb^H!zs;;jL0ud2%t&a2D)II@dl;T=UvmY$c1HQY#4!Uv z-lwHrUAT2^9p_|?#0ZD>e;k_V@zxp1!Y+(b zZsue0!+)3b$wk${MiGVB6tV_#uet6-L4Gt@KVX{o0ehS-()l`Q%SP#8+MHD>8cQkY zS(O;8`%r2U*UBDs_?bK=SZL^&O?B?~T|bqvVf61tc8Z4nfMHGK^~5DTHPI>TQ>@mz zC)1+CNRi)c(?bWxt{goNHQXDU>stBdv@|i+z{5*xu$mmqA&qlF0upD>dsAVhpA=7( zpcCXYAJhrshK~No6;id);b&*?C8)3z6*O^mCUYvWya+-AZf--w0p&bgHlHmN%Ws7Z z^suUjSp~AG^FNj=KIv+l!?J^4p7FMdoNM|y{CCp{c-^w9HP5#PB3E}$WvX|6DFqXo zw>+3JyN=h?X#R|wX{(Pxe)df+jn^!~e}@e*&fZtBKMBKx4`qH|u5v6GHp7|8pv)X%FlEYk;Hyb=cfWeh9H*Tk>;aLXw$2w(-pEc%Ul*d}|8g8vv?tXr-zlGRI;d?7cS z@>{QvHIovpQDwvC7I-}6@n9x)!JhM1LlkS)*_0_3`<=`>Ma71{&AaQGFy?@4z`vOI zAj$F5V!x@nJsMQ>gz{S~pFL_bw3q3y0R@@ar1Z?$S;P z0=CQ>B5K$Fczl}}mGE+Kg!KE8z9wp;u(H&2&|dw2Tx|_rT9te)?x$Zs+W2|4yw|{L*ItWlv9uUBBL$ zA(2vIYZ0sFQy-htx9}*=b3jbT((^6mjQ^|wa_>0eo+uy1A>;8F?N5az9j$xIYX=&n ziGKAm#G&iL4IUa)hZKsO0`WGX@G9`5X%Q*%&ue*Mhxa_tjD3Vh))pSp+uem-)=QuX`>zv4!iQii|o#K=oI@wqZ} zh~GUjuxbFi%#B)J2V-GZ#QCikMt3q`KbQgAOc!P{4Apd;rg{qVHAcVuSh&(5J#>2L zQ)_vmQm>5v{903unx*ck)U~`SyT-8c+`ihSvfzqv1i%B5T^zCE4KOkg%Do*0aA_Q; zA;PL~198PmVic3Rqw<{tJ(8Ep61L(vHbX98Qbp+Im9~?b=MeqXmbHdx9R+)Hhr?^I zfI$n4&-IV;E?TP-!+B|5i9gKXxbicpZr`j2d;CN!Yl)65>Z|Lph7_`x=#Lp8OF&*4=y; zAO#y8{AvFA6dk?L(Uu4AvAcN*PQDI@+WsX#i;3Y?5?NQL+0xU)$caAdV+8R!n8~V# zW&#S~nqW|aomtC(Y;<*i1d9Ty@~iYqOeL%BlZClzPQ-oZ1!3Yvym(&vs|NltRsLOs ztuu!&*L=#*D)(5|P4&N%gn*{_r#jaGiwzMLOof>??p~=tYbe-4LBhTsrckB

^Oh zhY0A~x+5kFt2+#xe>=e4zv=v*F+F_4A>i|SzhG~E#DlyO4zwz{vfLrZ0NsJ)O&wlJdZuuMg5MqX!cRP2Df#-Ud6#^-e+7;J`l0eg^?s_WG+(yb`=_nq~xdO46Jx z3q#PMg0wv~`{oQ3`h43qT%jqhEE}d*Rq;&NB-WA5n)*M6VSY9V|dzhBqdJ- zXjR+$JkT2aha6-(mzDw4R*~fW4k_fG5P9*QitXkle;w?Ap(2Olxl@~nY)9Gx=co^p z25eq+&3c_gXaIq9j73KZv)=gFNkl^h1g13>aF1iCGFS06%2iyjU>Y%woivB z^p88pF$a<3?s9@WTGNsdYV+iRGy9h?vD#C>Hb55&dn6TmTZRZg^C#2dT6vSVajnKM z3c*?{n0nFh${te}7~0IXqj6hz{X>M{Y*Z923!9;G3<0s)F&G$I6;F%O**MW92rIR% zu5vNq_AXp{A<*f^dcfCDDopexHAw@`0Lsp!U+aG;}-PZ z8~^vb+Gw59bHqX`OT7kHO2)-_yLeXCdlO`Go;>^m?N~d-aKm9-{ z3E*0|7krX}7dyd__ZkVRpu?IIxd%yE|5|}LvJQ(?*!3Qwkpciq0$-=xS4*7xHh2IC zJ7V@?^4d*dh>ki~I0}(@Dv+<}@^ok{q?0uGEH`w!uyO+DW3Ep4*sLZGE5{HP``V{} zvwiRq2qTqqe*>vOSxdp6Ga%Wf2!#<1DV27DxN!2A#W?b27ODcSOLNZamTkT7&ETqlO<1|fsH9g7;gws}Z-8>)1Vo39&pco`j&Hr3BEvZ6 zpF739H^7q%UOyIoZ-!j&wDU^~=t}}F%N+pj=yVS^$M(8H*H?PemdEW%!VxLMmz)mc z&Cc49mV(IvCqGl>exXSs9u7@o>P+fC2|v4D@M=~qvP>({Rv`ZF?06&uz2W?wC2~iv zo|KHJU20SxH7ipB_DkpLjdMe*637r)$Bf`Wy)0UtYbZfm)fGv+d9UQn7Nz|p#P*6d zJ*gGhZ=5H=?Weyzy<-#Yjd55Ns^OU*=dUGl`i*+i$hv$n`Qv40ptR+;XY~~{@%?IM zwU&!Et2GG7^biUW+7MFzWS#0=3rh$ioGTW^(j@Lu*KX~|3U)9RZRlLv_@!;mL_(F& z$h+47?1!*E#?^>;;x#2)x#~7g@Xd3880>LoNk=x2hI2pLdLE+vJZA>Is`}`~9 zO;}H15d{be=R-K98dXc|mm`{|ieDUO#K%tMo5AZ;-;yh~fb{=FkcO(& zknj;Aii!HpJJiCOBePZZF$R}Lm+iM0d%%$X_UewlO&!*@;9pj!Uewy)6JQ;081C;R zB@M0@ACMtp8^T?qDMUbEPY;rJ|I83!sO%&p@7g%NO%!W+ZD<{Ale~SrGnL;22~&!g zD~erj_>w(Wx9V0j<({ib3quTPb!3$>v^t(QU10<*X9&3}$q+02{(_Wiq%OGvm#IzG zI@={&ARe6ebP^WiK)GPeca)V*N~q8}V%H{pui7#hsJ%{W;M4li7>VopW$PMvJ578z zqk$t~sW;}5R$@Q_+GrMnOdDy)^zphSg103+aD%>6j2+s}VLaMB6~j#>GhR#X_9nZh z+ggonEMjk`+4a^6mwO1(#v!a{^C1cYLIazHn%<5i@AoLI!U^`dhcYcHoS8!8z%4Ks z9eB3QFz(;e31P(_O0W1>QXuq59G_-4kWG^&)+}Q@SDIl1_UxnO*%Z=J=y;XXPoNz2 zs;{(SZh%4P?n&@?aabd5c~oU4V9A{x_MGh?%myN{vbg;5>PBN9tJhtwhg0IgW4?g8vZ#$GlOU7ma|&HFmBez z$}h_dBLhvtAwqt_c(2Y*H(mXaS&@Fv3354 z_zejA+=1E{Y2sf{T3SBo!3?E68HM%psuUkBQQC*Zg3NU1aI9dLD2L4E6fUP^Y~tKa zI3BE*K(JI4D?frC1$;UN2c_nFk6Ph)bHd4GkWQ-uz4IK0x!J6l{OMh>7);grnfPmQ z$0Uj7FaWTsl_*URV**BGx}2I4_*07FwTE`0A11g#zUl4P^nU$_m50W%xFlLCE#eL?pCX$5y6zVC^d)y;H%N_FrNM`WZ7{MbS zl~k0;a2-2;s!;c#5!!2JJ6nvM-sVDIZ$^ElGrI?#IcmPwOE5So2Y;Z`+#yZ;kr{?f z50yq&%MsuBeAY6nUGlU^Aty6!PD5ZmE`%cspVrLQUHgg@mbTfqN5Zym{5o~SnppVy zYAfISY@LV10`ajcKs|RvfF{qECaBkPz9VaOL_7nQ#!EnBH&3Wx3>JJfEUv~S;K62P z6KGBI2Qv#gTknyMoY43>JCP&91j=x;UQsyZc-J7CjC~9dLkkFl)AppriuZpe z@bCGJ_Oe9-S`^N>*+Ag7l5^BCbMco08+S?4H{E8o3%fCGP|vDI6&M?NmCP*zlQM;> zTh}z9v1*#)ZF0mjRWceWWNmXBrC@mB7Q3R2&g!6SH&lyvfkW|kMI!wFGQizAqGK2r zk@H__9qm)+G?aqDN7Nx#{HJ(W=L7F%9CTmP^P} zN>jdOsyDfNR|hzcyR#szS;sO}10zi|9wsbaVcM)^$BN(xp~ZP($Kwg2p_=^xPT(%6 z!C>qYzM7ElGa64w`Y>9&TyP<>hP#7@fpBOFg4;YqWUls&c13sG+A9JA^lcg#fgcSH zs%LQirvG@KSM$$Q=*1dgV$R|b0o&io0qy6kxt*?__S%RmfD1?}H3IM_twU&7w=wJ7|uz5(q!)sf?s9fr6(jc76ObgR{v zMqyWhqx&A^&ip4V&k2hf%|OH%&_x*FdAwWd<#!;_xF+5GBp1@+pH}a*=)Ol^q3ilj>&4?6xE$8m z3e<|zeNKo@)fd!T4t3#OzDL?&y~{=Ywch=?mXz-60KGm1nMS!-op=~esFD~cb^_X> zTzpaAfG+>WCbd#Q61VAXqqGUX*#tH3l87OOGd_-A+G8VGF!ZMfMlhuXmWD%X7r)NO zAisHTR`0-D2ZXm;G3On^A#%@Jo0;LtMYZPrrMKTTJR}O(dH@)wrBGZehdl;)=lrZr zCZr$5y~jRfgab&MXrF9*N`=J`7)+P=a9s>k3+ zZEa*%{({Ppu4u||ajc9HeDI_IO-xUosw=t{q7YMz2I>v37sa>suigPJoIPNuK(~+X z+E0O~k^kPFY%4FqyI2KWuP?ut{3`WuTClY-++#e|8OE|v1KLxQg{43zd{S)_Vezw)f&71K-|Yy?^RF4cms_s(W(Mat6W=xrWyAqULn zsm?8zC*ww9BPZ+iOinPArdGQG_s6OMm0H+7KkiPXEkety6VF0TrOAt11Nn91=ne&8EoxQjKZC)_5-P<~2Me1PO+x}&?D z949-~Zy(U${K?ywYPW@}dpP_HeKV%t-3V@U*PUu=zH$RWguMWUCKC>I+^PF8Df?)G;{3lF%L*Q2X*3IellSviAzIE;>~BCh&qE$MPSAq~uW zZgJ5hWJ8HCzhc1cQYzuo_vFFb0)E*`T*D0K*+@Q-mL@7aFCl1@zjyJyOA^j?QMjYP zha~gW!#<&~h!cBu5uxvxIW@MstAzZxt>35D!P{Y4e=gzZrKLV^Ig6e*$+QUf18Ff$ z?m!k;jhasuA&q>g8tusfiYb78+}hhaD9Q#-|5O&^_Lw_ zhDig=5pw6B2_nLR>>9GSJN%I#UL9Z!2qSov+BDMZXA7(ag%}(Cr4TECBUAw?3ZNiK zp|jKaDqwtNfhp?O9cvG(#Cr?2xKkfQedO)lODa6Jo(ZRReU>3Uv-BrIfrf#r*oObg z@U*441WsM=FizcmTbbtpWg=Umebq4Xp-zF%EQ89u*PFQSLAgcj$eH<4-JVpnJoH{6 z?p&$ftGIIy>$)(9%{7_;IA(3SJ+#;2)ZzT7Dw|t0&5+2gHVg9@@(;jh66=WoCN{$C zBQ#!~8?Jfxy-3$ql{7iM+yLIk*Wd9XXqKqA>zrNb%!cR9(OhWZR;}qPu{`TbaQ0^b zWs0xS$_$+@v_;}{4>n^sd4r{q>FkLdJ=Cv3w+5d|iLJ-uW)Em3mB)p>RSqWH8f|nk z%$UEYUWEZ-%&ZCA$%JrZYR}a89PR8Ey&aWBZwJi3{^@8UVhWINIZ5$GcH`T8s~DFP zMzO)@9>FJ=zAwmjGNEiSz7=c^e48(7TWy>tjc?t#+ebJvlaaoc?l=Ezep7O@fPKCt zR%gYL@3IRP=LK9nIrR?@zU6&?0>H`i@yMoh{vFb)50chP3xe;*HzwOxVz&$n3l^%O zB#DcM{oZMKy$Zt;)RoR3#fy{J$ExFI(=A^UeP%M^xVN{Et`jf(lgl~~KG5|KY+gmE1`aaT``uxGYx9M%#H zOZuI7r2#}-kOe(A>_h&_4}JxOqzY-1LY??0)PTEB>t@81ps;#pms3Hcy+(;85|YuK zd;B8A33}SVX{8>=TQ9-A9DKhL zY$!GV#*d@p-y`o&vf|V8n<5%7?+p%Pd{LL-vU>*8pnhmRc3c4kHl&q25Njt3qOdAZ*H? z#tYnXYVb`bu+N9PVQaDh%$Ri}={j3Tx)?Zt1b7|vwW(|Ai8joGRfWjL`ZR!p+bhdW zg=UnEtmqzoudd!!Bs@5;hHcD@f7cr1oy`prlWUp{%jT7*{nrw8!QiNS8Qf3945ta< z5U^|0Nu(=UwEy&%r%aV^umo7PIH0l&KFV$s{L!|;UesP=D} zX`jB5huv?Sa-v6KvTbZ`{Vbx${7p0Y;c~LjUlH5gCP7^z%z+O6RDNgFO^4~(jrzC* zQ~LyO3s>IJ;={+4C9CmQuez|V!Sx=lDo9cm2*}#)sPI%~XV3$OB*sSd(6cRaQuz-} z;g-MT1@YgyuEzJ6S~fupNG@#*&`iH3K)T`4x5d^WE<*Y^eV#YjQ_!-#QYz5cKRm4B zf&hvV{fSBo(d2av*W11xZVn#NwC}#GIrHUF;{}e#)yUjACAs4*U150Re@b&oQP@1O z!p1efP)%Nl;C2?^Nl)L78*j~`uyp(fqy+}qd0P+n`eW$Z2hVG=?EvHbG$`<4&8mZW z?iU4&pVaT)%f!c*v9tEC6lb(cj^gSh_KZapmmgkeTx(cz9S8^n6Rnmv=v^9Wh29M* ziJxtVUq~Op_*k&6)bK=vdOpV~p#w{qNlw8b~_3bR&@iIp% z2|o$InRp(VZ^NZMU%8^Baz3r1%HXAJhM8|}(D_O&Fr!VO1Zdp zp<9SX_!llMY--YFAY9@(acF0a&L|3yRO2{_<~6l7H581`+U?(s zXOD6|`E0FWGWHH8X6LK{uMsdl0`D1gv8zla@++VtJ$~{a8~z#&vXF@z{Pzfcr%4Q- zz><=WNE5*B^3bfbV&GEi6sAjkqsEHvLkkE*b88*#S0H5L?E-8Oo)##hxs`=z7jrg$ zWVp>Od)Fv?})k&%giod5|qM zt7T0S?WrZe^PLu3_g^-XGNqt0>DjMBfES`13Ihk`$Mme~z)*7A6byO%(VuBsnF9%% z{PNwMUMBDFxFQ@(q2_h|@vpiLExd3ftN}$oKSUBl{`H+#lXca7dt*d>t&{^!-f6Op zn_U=!=1M__UzwbK58O4v^nABGh?LlXgWwM{1ec19h7egEk;>Yvk|w)u+qgeRGn|d! zZgD5Nfx?(^BP)G#pi*>?&yIko9kum6YntmL%nubMTE;IZHCF$?D9TOYjo&?gZ#Vja zT!cB;)m2r6oYq^e2RHTw92XSM>*HVk1=ZVmbztSrr<}ESP?E5pto9A6-M&@NO;h%# znFRiTh4c#iI2R0m>2F(=M=4&E$iJMvu27}Nj&CcIn*3Dxju4JL35_+9zY^CoMtIk= zatHNgsVvmB!s+bN6<{b&Gw-%X@JOh8A`}#T9p|P-C9jCQqFZ0ny!ibRTz8kt;m)g{ zO3{p|Oi}MDy(O*fK}z7D^oO1dA`6IU-k47|WoNUcj<6M%t`uixzasul^e@}W&y4oV zj{-A|N9;CYpUND!E!o^O-JQ4vvpHp-A|~&TM39cQp4k&_vVNy|u0YVkT7mN%t$|MK^GBptr zAz>&!nIB_A02xFem;?e6!X!iQ&B>ut(E)Jy*DdopYxr)zrD{s_iU-4v77c_ z0@5=9CW?%~^=t!e7wo{w*_1^ac2j$|%wxge;odKy+(awv*^r;^MHoo&2qJlDKweSE zJF@ZZ70IRC#MtJ6s-io0;HA>un@+=Mzh(^WCHp<&6GbZ01dvc?fM{d`t?LI@HdnuB zs0!EECUN_6IHj(cgNuijojO%fH-h;t<&pM@#N3xbTodbmDZycNR1IL(#^XacZ_P7z zZ028hkE3WErE!)@ru-PJ?y*8WUDVO%^4$CSJoke@Cx>G}FP#sdshdd1d5?dc(&wR$ zp8n!Z$l>KP=E)bV@>=;N`Kfh&zLPl$p2FqxX&f+_?=g_q_MfGF=)~*PYGx+Iq~vPC z>UHQYtF0$L2Twgd8a8*&bskzR3yIv<1Pty`N9%Mbk5j)8qs!@%8%bEYkVZX3$$x;9 zEi&zU$kb5D4fM~=k*LJ2vS`W=kbB`Np95Q>jn^}4-)ov=8BIPMcP5{3yU5XLs4n(V zIk7l+<@#P#Klvmpc{02Cm*vRH#pPfo+Icnu{ zmBJ&vko7q{`(?#Yx-E2e^wpE7My|f*nWVj(LI3bzN=oZmvxb!n&h^wx@*1CfefM`y(~6Cj@0r#<4lqR zJ(a0IwXw6Nf1Jo{SUWwN2>11aBIv7au`>E&igMRUdQ~W6v7^7DkCyqF>$z^v_&3+j zSo!TNJThf3Sdk1E+2jH8@4*61&60tb3|-}QiOuZ4u~wP%MT!rVLh99rSIorOiTu>< zp)n$jDl+*la5Uvz!2#C>{__~QTmfD)Z`A|h@+HrFCDVz54_T?v3AZnroNwAZ#;w1#c_n>%xYRdiV|VKKsod)h%YCj(qLsXgDQkW-2&GcHL803D*;G zjk2h}CB2ewx3|=F)>8D;)N>=ehcB+bOqk9qHa;f*`j5wXkdeHMmtFg9m(s|R^gfc0 zaHAy>=zMUyWOMo6LZ8a{b<%y$poJ!HuH9!!W6S zj|ZCf;MxYkcpJ;Q>^zQ3t+cUXpXTGs2rja?vPY0{>Y0ojnyPNTpSMYKh;hC9wj9RQ z9HcZa=^VAx8Kt}ky11PdcbEZT;jYOaYGwtFt_FgEcB_! z%Ce4=16#(mU%gxEuO;M2tBt1Yw?OX7yNnvtOHA%dNbXOtV^Rk&6LZo5qk4)sHCSp` zG*Eg&@5OfXCpELx*Qz@<-#)ft6GDD2tb|iyrP{FDF<9*Nti3+b(>%%qDu~Ddsoch_ z6O_o77TF3T{#gcnJ+Yu@*u$D();c1CRD-<0HAAmzf1g$|#4Y%TfD{x#`Yp;3<2jnW zVjKNCJybx3szuP%**a-)C6==!R#kz^H!2+pYax?;bq2F827EP!GThsnsi3zt72l$1 z5dq_zY3ht%<29uAj;`t!H_-!^lcwIa^|(yt^b|MT^5rw@_u}CC5r{A&phUw>P)I^+ z;&z8!p63&OgoGNGB%2-&P;j;9`}9hzLoBqv@SpFI(%~G8J_bIEt7 zoIq-6T$b?u2^5~?vIP2+7D~nD6^Cf*9PKib0@R?<9|YvS#?QE+vmfdf3?l1V<;v_j z*l%B-9BCdHu62k`u=8=W^BQV|GXI+*Nkd?P*pq5mxyt!1BQ{j)fYTNgrP`n|SF+Vd zA8fwu%XK*y6s|cngf%meh*SX<5=;jaQdLb7yrl^0rpx-{W@IsDPqNZN7NCMz1J(4M zYaw$l+7kOXz1?$j?CA5hF?f(VzAJp&<`@C5@@EP|V7XsEhm3G-<=45B);cey)yKexPESSeC=Q zwyn2OTz`9Y0M6vX$14!(1Tc?XHyFH<#N7_M?sXgesGPcns^9Nep2I}pwhN#a9FUW#| zM>F^CKooi1!Ys3XIeSnKw(Z6?zzI($iIp^du8)#?^%U~#_IEZ`3Q0Mf?rI8Y`lc7j zIZ1J()&UbvO=LPsc{D(IQ=xx&cL4;r-v>QJ*YclR$ZfLZ+Etv9e2#Gv??y@bHS_Fa z<@((*SFxu?SAR8fb~6g+=fpmf62euK0N}z-H%@yfWnf=j`q=L6YK4l#l7A;_{mK9J zPrEti-hrB)^IW$NkF+{;$EbeYJbNQ}#E8z%z%;W8H`4-?v#?HEWh+bDsKkP_BghoI zqqV;6mUFM!j49t`@+*sHq^rJ@Jd7RB02Ulo148CT`tm}d@hQr?yZt6;Nu=iE-D9a* z#mR({J3H0$uHcs3e9I$kB8Z~%~^Exq5q(0VK|LfEnPMt#NyrGJR@KoV&mT=4nDZ>zlrNTHzZQQMr zGLw1M%I}OuA*-e*C|0ZNK<#BFQT?_jEZY}+=Y$BmyUC>lZGPP4 zzLCMs|Jb#gz<(UWn5pU_uv)?#PoPYPF9zPnm}oqIDQ#`@DE!hC3)f=D`x9=WClaW4 z@aMQndW0uyf?OZEJs^oK_Q;A34kSN49~k5R3wQA9siWnokgkx!YnMr7ss4l1Tg(-^ z=@DTQwxEdx8B3^w?n?Jbx_29AD_*sqc(&h)?VXo(KVe#>Wag{xXf>51(??{mdkB`x zHHR>6y6n|k+bw@|H)#3)jLdiY->RP-URIoK?UebcC z>D(G9Umw}sH7KkCb(#hgu2BohPuypv3;1ILwf+;5djuF>H^F@!X1By!8OVJ=NOkvq9=F5~E;h-7kUV>`)&xkYmg zJ(Lk`>$t@>4pWY07BddnJe?QM|L{C7zQ5n+_wM)doA2%EEGwlZB_bjs>+0g@`=iVM zK|<_D+8#+<{!x;*Tmq9tM3nXZ;fSlR?l%z;DN$F)ix6ZLhmjdO9id6y-m39)Wy=;l?*g%`b5-z%2xsCnN0*% z_a@4#d%wvoe2_R7W=c@Z#la|qK`fPb*2&F8W1jb!0dB1Ya)2*5Jom+P+$sWKDe+rw z3HykBngjIBQTP$*yFVK#D7V@FPY*#i%bu@d$*gpHWHWNUd1=YKXr(v4ltx0tI#1!Xf~^xqJu?!i7vu|v6tvHuotG}< zenapPQVe8jH^ng0uLlx{58v%83{s=lDejk>k)ycS!=86D-+>eZ(UAeyrjh%ou@%&bL|W+ z9`~|ysZQ3q=yMjd>lE}{(B!uM=PHX}Kq@BvWiu~NSi?TeWzEaYEg3}poNJ;Urm5j) zg{W?0R4n2*d8JLFUdcd}8ROQ$YP-p+aI0^MZER{El7yf1;%aK?uC~e zoYbk`D^+|ETv(I!AvGlFbYjms36VW4^1RnUXxnF5ca#@(=|En>!`42|jNBhqy*D*7 z_u{k31^panD3$lF>Onsz)O>bBYbD`IaG+*6!|ia?Kytw_g-Jir5I#;?xgS-k^yr%a zrqqP_x1t9wSZ$=)VK!dx+9+tyd%RAjch7xm=4I9$m*yt9aiy@pBx)E!P;&L+Y=gDd~!KALjFJ+Dbl+E*N|7d$Iz|Huwf(1v!5dv8pyDWCD@ zeytgDIF~YR|^KL>e9j)+Og_~U%JNwM(yA`_CwD@SAoj%I}dS^<>?=_)% z#slLlpZ!IPyYH=yALv4W<}OxSc(`vGB!KT)N(4BQgbRDy{Ew^_#!eB{k2WS9@fTU2 z9TizWm8oG|5r%-aU_aehb4X#@EuCV9LJACbgUM8UjV&IZl&Ftq!X?{J^aOYJmud8v zoBw8^QG454XAQHkx?UCu;U5ammQ~1bP^^}Q*<>H2DY<;3-Ns1AGYVW?1zwJhA8_md zgC8}syn zz<)SfXPe^3!RR#*n#(9CYx|UdUG7u!jN_#2DHl5=CtRwm{P8nY7<4nTuqp@k`=V`6 zdjzE1Ro(>eHE7dI4;KD9HASsokuN<_9`}cEMI$`pJ-ZD6SlBGCi5M9q%9??>BYlIF zzrU$)4leF@9quZpom~u~aXMcNTHjfhE5+>z+#yhZxAM5F&KDelSDSyozwwm88Gu17 zd4EmcNk{|aYz{=6aT+De6+P9*upb?OfX+{G?bb$4Cx3nTO`Vj~%uWohudVdwFjr97 zaUs$hIfZu_SYu?Wp5$QRHyab$tCzcJeblmgwa&>0@Xr;ykXhB@&;S!`gF3^9FxVvT zj%W%69`n)j;%|*@XjRz8)Y52h)ChlgtivE4LDMf7U&9KVEK`ubdeKKaF6u{Lwuu!~ z+_7oguHeJ3g$%TsL4cboNx&*@CqHX$kH6fA*+Ld`RlOl-Wz*68N_9XyGbc^tKH8@p zY(yBYQ_JZCy5qj6luRePzJ`Sas7FHB&!p1pK1HkTwC8F%-5kqrt0DDa4yc|`U8$qQ zOf2b%*m~S#jXpN zw{AAh?}-+;8Rw?e-Uyxia)~I0(pEhkG30x<;lr&PGmDP)CW$r~IgK0aZiqE8EQGz3 z7ZroAv(qT|DP#V5C45wm_6mEC2twBmk4gZQ{CNeM5e3SAwoTs;a(oiro$f1M6*VD~ zG7b~gwh5P|$n#Ou@6OiGeKTMEw3uV?igLL5Cx)_}oGSaG6loA;@?yw0D0*k$SP*pc znsH^AlY4dOcM3IN2yJO6@z=u{aCAcl+J~flgkZIm@q`vh8V&wjyFXHDKnSHgDEc$+$-~ z%w5wAviLXccCMRM^bRmN6(m`0s^quI@*AhdMIphGmp-@v)yt2!%w6*fu{1lql{Mb~ z;Wemx_i-brECWExe_B?ARgXqnOcXwugd2WvM@;9P(LX=d(Um&X8WYdZqS=Wb%kyNv zv-D(O=-mx))F0v)coO)>*VtLH^iHH3PZjHw+sdFLm?EU@o;qgOJmr@|W>%!lq^Y^o zQ12C-@rDebzI$AIGv?TK05XwJ;}l@O+9| zpH@7rWx`kgvmcL=VB2j@8usv#Aj&ThGSiAGQQtp(BD)b?RJ`)D@S)>87%|>Dk>|7< z`yI`rdzlJ%J16qm_CIzDBOx#&8IaXW3g;G4b6mNXWs69ge8y_f3huoGm6QnJW(2@& mE7^%*$NwKh=D)@z6dw$!O;4Sho%!L2BCbxJjx7$?Z~q5!o%Fo` literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/Square150x150Logo.scale-125.png b/WDACConfig/WinUI3/Assets/Square150x150Logo.scale-125.png new file mode 100644 index 0000000000000000000000000000000000000000..eb5cc587f700e0b6026b8255a3ad52ce2cdef5d8 GIT binary patch literal 3237 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 literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/Square150x150Logo.scale-200.png b/WDACConfig/WinUI3/Assets/Square150x150Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..85235b4d37e2d963eecde8db865434fefd792df1 GIT binary patch literal 5160 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 literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/Square150x150Logo.scale-400.png b/WDACConfig/WinUI3/Assets/Square150x150Logo.scale-400.png new file mode 100644 index 0000000000000000000000000000000000000000..4911b2b1bac216dd212fcdeb57f7e4693fd3d1cc GIT binary patch literal 11606 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 %y4mzgievjIjx? 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 literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png b/WDACConfig/WinUI3/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png new file mode 100644 index 0000000000000000000000000000000000000000..fc1e4298625efed5c60c00ed60383fceb57f86d8 GIT binary patch literal 718 zcmV;<0x|uGP)H>jQ z1SVw|L`cv>J=m0qMQTv*VTf8#Sxi23(}&VR3Wo-xmmtU_%f`X+a&*p|bN0D(CTpP7 z2wJc=`?A)z*4q4=$A22X)Ww#;OFJ!)gVUo4Fh#%j`ABQ;KLJW&7mJ8VB2;o!69Q0| z0D+)p-R%>dwuz>Z!`DA702B{2ln5RvVkp`wo1vHwxJ6@Dg>4ux!szWe;ur zaJ=OV07*qoM6N<$g3ek$ Avj6}9 literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png b/WDACConfig/WinUI3/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png new file mode 100644 index 0000000000000000000000000000000000000000..df3c0ed29e2a87e53adeb0270e649ecf40c239b4 GIT binary patch literal 1129 zcmV-v1eW`WP)E-VXc3+b1dhG;aw#3o>T2dE{n zHWA_nAJn!V3~9yKXwlf1LQ=p`QeR2y2ZQ}m6s$2ON~Fd<0u)Hic^*jvAe z`}Ei1mUX|dOysxk9Dr0GT-oyRv3~`4@a#KzLPH}r^eU!tNJjvSff;%k6w313Uv5## z-uqkS;j!fbYA-7~pAq7g2ns{q#gz7>U~;RL;m>m;QTm^LLO{9n#0kVXw1BrtL{)iy z+s;-A^__6VWq-gM8T>IcfyPF43!v}^{S_YcG`NnFp?eL3vV@4*a3@U48p+IW38f&W zEHICj$9+Tt={T(=zb^^YJdp$XvtPn8=An|0R>86Jw+eFejI+Ugv z%&QxG%38C)9VkaRF4-WLR>Vy%nT1Hqgh$sAF#O5A37}$d767a~65_TJaN;(ainidw z_z*h&xB$P$J8$hAK)T65T)FTt4SbGIEcsp_8jIoJV^yduD8*l91jmnFq!USK$wlhM zhMNGc97dIqe28V*7S{1>Tyv)28XN}KsPq+V5;3DueDwGZyiiaATe{fQ(*jGA@N*C8 zFfxwMw@BLduxnWmbZpbLP#U@y2iI>$=g3cZec&?$GBY8mM9qmw94ej>7cRDb-HguB zi^$E$q4K3WOcaRY4$lKz?dm_PG$yHdxk=qRa;FWx9w=X1gvrq{Z2w{(;7YVs)S~jf zQrM1-=lAbI$3PEqv+jU}k(A3S(U0oPJV1Eg_d{8Gio3K-V}qZ0&~fQB%3F5htCtR7 z+XGKw+Dwg~7mJ@xi8sB9Z~9LmH+vOjo^phHb;y|075=>c$^w_1NI+{K|2HVQhn%bs zzP;3gXAZwfkatmC@@#U=T=BfnYU($Y%6z67C1(wbR1pwqI@J|?yX1%#^lej9HgbZi zW*~2%W5gQ$Y3ujKB)l{ z3Y|aqATK9PoU9R+`%66Tt4`OO2e@9}Z3f;d-mGglO^~$-NGL#dT|NWmDy$~!6kYag zYb1Sl#8qjl&DX!|HkSn?V!F9|IJl>{lmo|Dz_?qPHnO~#V7j8rV>j9^VT-iIudY;^ z!bi^yFQm2TsEF+E9`e^b^r)dS+q8hOQ6}nJ%-BNBL?0l?xO+lLRE9r2H@=w8lCvoG zUf*~OD9f(fSWh3ETIQp%VGzisfo8c4qpf{)OYL74FdLKgC+Y&VPju=^y|1JIbKKq6 v+WY%fvUkPPbh_bWLhzB5uD@--KaIZuffD58m+J1000000NkvXXu0mjf4?P;D literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png b/WDACConfig/WinUI3/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png new file mode 100644 index 0000000000000000000000000000000000000000..e9d1b5fff42adf093ce77f4e44558de40f29238d GIT binary patch literal 10620 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 TgeTz2DFH1tJ=Gdz>!|+$q(C9j literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png b/WDACConfig/WinUI3/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png new file mode 100644 index 0000000000000000000000000000000000000000..898ed6293f90a8344b8f304cb63785234c57ca07 GIT binary patch literal 1415 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 literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/Square44x44Logo.altform-unplated_targetsize-16.png b/WDACConfig/WinUI3/Assets/Square44x44Logo.altform-unplated_targetsize-16.png new file mode 100644 index 0000000000000000000000000000000000000000..fc1e4298625efed5c60c00ed60383fceb57f86d8 GIT binary patch literal 718 zcmV;<0x|uGP)H>jQ z1SVw|L`cv>J=m0qMQTv*VTf8#Sxi23(}&VR3Wo-xmmtU_%f`X+a&*p|bN0D(CTpP7 z2wJc=`?A)z*4q4=$A22X)Ww#;OFJ!)gVUo4Fh#%j`ABQ;KLJW&7mJ8VB2;o!69Q0| z0D+)p-R%>dwuz>Z!`DA702B{2ln5RvVkp`wo1vHwxJ6@Dg>4ux!szWe;ur zaJ=OV07*qoM6N<$g3ek$ Avj6}9 literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/Square44x44Logo.altform-unplated_targetsize-256.png b/WDACConfig/WinUI3/Assets/Square44x44Logo.altform-unplated_targetsize-256.png new file mode 100644 index 0000000000000000000000000000000000000000..e9d1b5fff42adf093ce77f4e44558de40f29238d GIT binary patch literal 10620 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 TgeTz2DFH1tJ=Gdz>!|+$q(C9j literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/Square44x44Logo.altform-unplated_targetsize-32.png b/WDACConfig/WinUI3/Assets/Square44x44Logo.altform-unplated_targetsize-32.png new file mode 100644 index 0000000000000000000000000000000000000000..898ed6293f90a8344b8f304cb63785234c57ca07 GIT binary patch literal 1415 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 literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/Square44x44Logo.scale-100.png b/WDACConfig/WinUI3/Assets/Square44x44Logo.scale-100.png new file mode 100644 index 0000000000000000000000000000000000000000..11ac925921ee0bd61bebcdc1c7dec06cf52e14d6 GIT binary patch literal 1466 zcmV;r1x5OaP)Y)M2xRCt{2m;q>1bsWdP|NndU-n-p-w{r(Vh&EZ8lqj_|oIyC9 zB`IN~R+f<>P8rf9L8Y9V6B7wVw^j~H+;B>XL=7WUtl4y1qcka{&=fPbhTd+syL)%< z{a^og(`#ebyqoV7VP815J>LEPpZEL!-tYVSPsBqe@j(NmsuAFHYXmsm8UaqXECJxm|I>93PrOU6^{9@9jcpS>_E#VvmO-lPU||kwX4TYCuy@4oF(Mjt{vo1}#o^}> zO6JAlJ7h$H;V<~F3oQW#-)R$0^M{hU{jT9OJ@$$J%}a-8Iw>UuObAIb+}*|a)6)c7 z!Z$9dHSV8raK>Ob1lbS}6bv{dr7yE7>iz(8*iQc~tejYc+hfY=Z z4s7o3P>H$?yDVoWKAg28c4^=9rN}*43XNBR$O|MD4I7x(HG)wUVJ!l;;@}O04Cdzd z+=1O)v?_ji@7bdW85$I%DZ>UuH*ZBCl*VEGYA^Dnu_zdmgPLn6p=U^ti3r0q4zo4| zf)3wJEPt$U0BmLRL2N&F1YVaX{^}8d@`p9LEd%D}a6wZe&{fq8kD#t%vNQ&@&#ZtV z*f2I@G)m9bLvz^qI3YpjEvuvB29`~lJpgvg$vSL5dmC2O;yYqUnmKn|unhRhcdboS z5nx1FEPEkd3U2p!=($nAZo~X>xd?Un@Mc39B!_~)Egy;}&%>TMtK%^?@85~aFZaUh z_8@|ee$fdx5(E`9eL$_7QysVJi=7QWwp#{_BVc4BsV0iJo2Mdn z$>Vh6^k3)jV$GU3^sD)EF)r&7{AMle*{OX*rF z36B~PJ=XL+qec&cF36c0|IS@u&FqGve+nVgCd-jD@bz6SVfTBpN+8g$LHcU>|Da8k znKe5Ethobx_%UbZlIw@t!j=P@>aINA=-N25N^~nFtVfMq>ks|X-co$FWHai|G@|nG zM;vx^VpuM!NMON8Rp)`W##9(mHV!|Xu_iOnJefG8Sw?SU0Q8x^e?$JRw-Mky+MCHU zt0#^@d1qna$nhK5MQebTl-5t(=_)G?R+6`X64!AKo!1}QA={yH!?i04-*I!72YWVE zY%o-;>u1an3BDpu=~*KX`7ag%UP;L%8rm^&w*}6%pRVxVz#+#i$L1%~TeusZ*MWo=D%> z&n|NJ(|^}>w)&s?mSa||W!V7Vv+cw&S4m#3;dPbs{#r`-w8raoXOOWn|ADq))oUVI zdg3r&M*d^1cU@tfCNOl>xBO&fd{b#TB6aOJVL9j>jr{jA-5LQ-w?=@|t%nZyU*&2* UyDCs%`~Uy|07*qoM6N<$f}vcv8~^|S literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/Square44x44Logo.scale-125.png b/WDACConfig/WinUI3/Assets/Square44x44Logo.scale-125.png new file mode 100644 index 0000000000000000000000000000000000000000..09c868a3449985d840ffbd77b52686f8b80426a5 GIT binary patch literal 1888 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 literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/Square44x44Logo.scale-200.png b/WDACConfig/WinUI3/Assets/Square44x44Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..bcd5f052c1d64aebe867aea08c9a0440c9d70f7f GIT binary patch literal 2970 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 literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/Square44x44Logo.scale-400.png b/WDACConfig/WinUI3/Assets/Square44x44Logo.scale-400.png new file mode 100644 index 0000000000000000000000000000000000000000..d9c0c13b3edb182d3e455a9e7ab6fcdc185d9ce0 GIT binary patch literal 5731 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 LhFWzRPI3PQkBs86 literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/Square44x44Logo.targetsize-16.png b/WDACConfig/WinUI3/Assets/Square44x44Logo.targetsize-16.png new file mode 100644 index 0000000000000000000000000000000000000000..c462961ea7ad4861a81db04eea98ddd6fed4999e GIT binary patch literal 540 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 literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/Square44x44Logo.targetsize-256.png b/WDACConfig/WinUI3/Assets/Square44x44Logo.targetsize-256.png new file mode 100644 index 0000000000000000000000000000000000000000..3c1f6e451ec9ba02742143744324e6091abc4c74 GIT binary patch literal 8300 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 TidXe900000NkvXXu0mjf`Su)7 literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/Square44x44Logo.targetsize-48.png b/WDACConfig/WinUI3/Assets/Square44x44Logo.targetsize-48.png new file mode 100644 index 0000000000000000000000000000000000000000..08903612783083df7dce4f1ec87106ed99daee68 GIT binary patch literal 1686 zcmV;H25I?;P)0K-5f0x( zSnmKRVE{5zN%r00mOzZ0m4=)4C!DsuF3ZxUs+GrXh74d@S-L<~ekPGgstR=_FPM0# zsm=c*>E#3VOciCwq6-WnnYvLn@$|YpD%0FHi&R>@@ThCV0A~D}Zc!9!1IJk*%5f^e z)5JrbrDlFSyC6HD& zTL)To0gz-m!#p-BTk%Q&%WEySRZ)VL=Zeegeh- zQ|el;v$rnl0^ozNJAH=PL>#2k0mRzVQcJshVjV$E1{N^5&w~OWpgOzmY?uM`{*%fD z7c(R~F=1)b>T$v=iii%kFpk0IUqFu?Kr>(OXhzBtiI^Po6b@7rLkJhZfexv?#|ogC zuWfC@D`TeMJQyd)&|T!hrBm7`OIrt zFzF?Hdi`riY%=pC5QYp8vqQm6VhW`%Q!dd1$>Rkz8W3(Yvqj}mik{{LgUR>2uB{o% zzs-b`N(iIoZXF;Iwbz(K0#G@=0TQcgv~^lhJ59BC+Ax{MW8>5m*s#d4#r(JzaVjlfzP70W3wEX9#>3l)jj+O{KIj&Uuw7X3h&QTFTnXw75&TZmjQ)A2eAbB2ixZCsGcs4?r-pXKkM$ z)xX{$SP{&#;IPpUx-fgzMfyr z%>UlMc>^==5+Art+@8T$8Wezi?-hr2`|P#g4Bvw)ol9y@h~oDSV5~J3HO=){xHD}) z^S+XrU5cu%wI9FY7%>1JM8@SJ>$ch1oK@JYI6R%t0}!{d=;%glp8Gbkf6T>i_irIC zvLEwI)NEj>U)wSiFGDWo?9Kd&D2PQQ!Z^1lguqG)!OV&?No8r_>a#@in-?z_ zYyVv_R3AgHMsv=2!#2#C?c-;5^Q}|tU$AsaRh?qz#O_n<%u7w|%*{#t^19taMX0LpVI+e|J1*J`T z#?<{t*|yJTFwK&le1=!#EJZsLh|<>TQ&&s$j-_eD&cv;slxT)W#B^7U{(~db8U+Zo gMgc;tQGihEKfiy-vKEK%@Bjb+07*qoM6N<$g2HSwZ2$lO literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/StoreLogo.backup.png b/WDACConfig/WinUI3/Assets/StoreLogo.backup.png new file mode 100644 index 0000000000000000000000000000000000000000..a4586f26bdf7841cad10f39cdffe2aca3af252c1 GIT binary patch literal 456 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2o;fF!p=8IEGZ*dUM0H=rDtTTVkd2 z(%lbKn@VS_lUaADVB&;Z6F#LM+mPsa?e>FnHo;HND^!P`-lX%BH~FOg%y&x+t*x!? zg$#_1A1kgsSvO(fw`bOmo;lrJX8byO1j^gf7qohR%mmt z@L)WX;>gqgK|tWJvQ5j;4;=gt4HXVKSMYRv5RhY5vS~TqfK_NAP*r{h!!g^BZ;w4r z7CGdsai)y;fJQc`7{Zc2b==h%o`Op$|bg6a&nL{*m7-=0>k4M4-PXlU;G-?%*(*g>iFt^ U$m#7DfHB12>FVdQ&MBb@0G`#n8vpm;t53)Fx~!(Ch|UrGk%^8K8BLB;Ys|X=-&!2pI>AMu8SlN2^Uj zTapFB?k3s2ckkot`EFPtCeCM1M;r~R$sa7Cgat-9mCX)hE z-Q6^Ym~by)X0@T|m60?3z!(b=LXR6j4)#9yyLa>b%)EkBcW(F=EpRmwN)`%*E-(#L zJ3)ZT5-|KDlCS_2%|Eks)nAMRYv^0H<7B>`oi7k_OIKq92*Q(sTaxr>9L#tE_Yz4xoo)sBD@G7y~1! z01>G~i{-|d`&KP56Z+43Yj@`4ZdrrO=?cy^DO-yk^C*=?&<2M`52S-g3ZBCZ6O2e4 zXpww7VeIXh`&RBW8QIpm^!FcUqmN^d`JZfeg)T&9Q9|&+@I!Q%`C?&#WDLcvH|z+& zM$8cvfD$Sc$tz$)T|TR2#T$&k0Yj3GLIL?#-@@k-S%Xv`ZLATCq{aHM>c&kEex{#4 zAO0F;aGmHWpv+`o##D5pk;z z6$&LqQxdQAJ+$+PBM@NbQ=+fegn)ev|;QG1Neve!TYdd#=Q6lgR?D&3D0&*q9?s<>H_piVG zi(?Pm_#OD@{2BP|={BhFmRjqW6UhyPT#C>PTp%3;7Y*a()cGE8OD?dDe8z0+2wM=o zmOD`@GBSksHNjit8D*M)XCjD%p+W7zr{GZkDX8$3*~SR&vDGcr${qol+CTa2!;vs7 zsjP-VL598meH)6s9uTOI(mGBd+WvDKs!A*D1D*1>MZ%6juqExGk^p83m~i+aJp9$U z&~VdYYrE3zfi3@h9m>ngvA3d^EyK-(3K85FPo#msY*pK~723|8fU5FoqYz}g2VoC` zFwo-|MAy`lM3KNyd98KD4aulG-CO?6FCe(Anmr#65eS_A6OJ9z_4$55IVI8+gku!61klSAczb0rsqYdOU=t!P2HRaNy(-%bgSmrG!XYh-vyU z#~|Urn;&}W?mSK1(v7?Vy;6!KH?*911E%dSg>4V~e9W;Y?x_U>m0SPXHYh1C!wh7A zhGqZ$HNS!T=FT4n@uT2sIPmTvs4BylP*YpN;y_n768fo7YqxC&nKJ!SQD`Bu7zBNM zvFR=K!S*A2tpBfussHXzt-!8-^=Y840@(M+uVL}c_t-+bh7i*$Dlf%ES`HvC0f=U_ zq&l7&M4*2(V_E|?H3eXdbuS(d-im1{Vd@qvx3bW6WnY2&(?C=uAtn;cR)(U*G@`wj zG05PiLuWk=HM<43yqb-SZ<{BA`Q`UkjJqZ)2+kpkyffT2*nFZtV-PNqU_N(N`Vcyt;X+nt{dTC@4$ZAt;EBLg_09czy8Mv!kaVo%Z>|*p7KZkE zkN@sNRDx&GIyYEZEt!64(hA0hxZtk|HFq7(+p}R326WM2dwbQoZ``V~swYwTb)-;| zs9OUT*Ar-ktuUxIhT2ZV^ZndOnKTY;{U8SLLA|%3rj_YzqlxL+)Yyh|``$S<8PDS~ xsUy=!pyMC-b>Cz@BPmn=$u002ovPDHLkV1l*5?SB9O literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/StoreLogo.scale-125.png b/WDACConfig/WinUI3/Assets/StoreLogo.scale-125.png new file mode 100644 index 0000000000000000000000000000000000000000..57e8a957c7036aaedbbeb29a88427b87a7157e92 GIT binary patch literal 2669 zcmV-z3X=7SP)j{00009a7bBm000&x z000&x0ZCFM@Bjb`A4x<(RCt{2nh$Uk)g8xw@9p01<&w)?Ab&sxF{#GEX{UqW=zviI zRm1@b9f}p1f|Z&e1O^qP1OkEZM}trb3|deSA(<)|5J$k0K;=&%N}SCie1Smr8b@F|d)vj4ug?tE9(r%T`m9xr$d8_--Lk~*Bxk$YiQQFFBF7d7}tk1l8N^zQtnBuIXmhn*6Dja^(fbjf2on8JBc#)yxQxbz{0Fwb zdI}?aj9~bdy4m+EgQ^+C*pm{IJV!DYOUeimNSg42jx0(goZ&+_S0_MZ1F<6F_MFOS^z0gcx6p)_F(Q)F149rBKWSGZkn8i0umu|^Xe>L6?*bbokd)S{5rY&Pg` zz7I?+52uM7I;@3dfbI?7242%x56|~`6t;|c1L6!uc;n!oVdKB{K}u3`SO)06 z@QRCb?|nrel`m# z8ZN;Z=Vgd9nL5?_-49*|xFU&8wLPMbPVqaxiV6TPhjyoq!%&;chAf@nq9noO&kLc# zaT%;uYv5-Ez|fxF0&?pHpw=g}LiT0{$n@J}iiMb%m_Rd2e-HdoAJ+mG=>4Zq@ZFcn zAtf;>sG$)inyWBvc!V0%Oia*9BalQ9?ANZr)P6&uzqJqi>D+#}B09ioj@M^^Hh@|k zEglNEQh(}64O-{5-5x;|p=)(@d8h@Tq?$wtnwpy6&GhFX=aG@>;h*<-045*X05#2a zY5+@M1Ki~5J196Gz}5AFtKR4nflv;uBAW0XnX5EPlASdfEkqFgmQkMVY_Y-X4?TAs zTv1wl3T%6FI{fzN2B>qjK!P>l76T}$Ys?z}8Ca-o2m($&Q}(Zc-PQkqUibCZ6;lw) zD2W!kjM>VN;VUx!U`yNf%2~v-gOwDOKS{JTuv3Iro*s6~xl}VXz>w)M?vq9EgR4>v zOdSoNz{`zJxcc3B7&S0c6I`HOYSM;zu)Fd&r1VSzq5i6_uA~@N_K9b&EZ$?&Did(1 zt2y)U!>lD{Fmd`%XegC%I-22~{aawkFJHUmd@A*T;t_Ach_%^p{<}(ux7;uQVGy*O zy8@%ro`j-_d79u8m*>E)qjWZxkO;oItAU9-2pbI`HG2;SDiaW%;rKlaF!Koynr>$9 znE*?_d=Cr^3kyfRddvC#eIJB_b3TA)*G_{fXB}AE3}92HtWP`xpUhedMxMXry6fP_ z_d`lbFZG!25nU;$Onc4z?m)!?!Z)uxTyDu7R)Klur^F}uErY51ntS$uywBIEzx^2? zJ*6LfHEW}4fXj}0>W)5m?%@pBHGPf$;jANgQc9pI`jyh(X|SuP+fg2Hzc1(vj|R<6 zv{~1J2E5`C41Jh6v8OHrWE4(;NBaH*N?uzX6>zoA%03{VO~J|qg_i`hm2r9(0?QZ#x?5b+Fvs-O>iep68sKI{44gh&atl=` zMp&z-ye{~f{4d<`3x>@G7Vo5!>%bF?5DgG1a7Bm?pCFLeYc`$MwX33gN$V&m+hxto z{0znMzf#>l(72=10HPqk#1*q50&SZ&uHSl4EoNlN@G?8yICaR|8&eRw&nn1n*jLtbxsadJ@BO2h~YLuvOnp1|alL z-l#0_3O;z@!+Egh%wM5*Vsdy%SlsG@bY(fIe%C)WS8)Cmawf;I>~E#<^E2l#X1T z7Yut4#c^2yhrf2U8^9V5`_7-FagzaSLIOae4&wz-TR(rlc1QWD&RpN=9G6Rf=wzMvj3LYDy)MPRF` z4g5fj(KjKS`-GRh-E>;C<%}3(MZVqC03&r9APgzs#3StE>Z-@Wfhzx57M%(|b-nI@ z&$cLIwAF=*sR4dXoZ7o0 z%IhL}lAn4-hcnEv%DXrlYCk?PBT~1G=%$5ZN!g6}1;Z~gJT6r7)L`$hMuYybW+L~E{k%bEJ!cc?I_7@I532`LhJ^#rLmEMd&OgdK&CVnpVZ92hNGzM)j12lu7q*p zp0fPRu@W1YIZ6~H-rzAxnWrnFfvvE%<-|NF33cC0zoNTQSyCd~OJJ@wcX%ln&^i>$ zJ){{7+gsZw{Qwflsoj3!Kve4nU9?MUTUL6&{MLX$7KsNT|!UsGHwbfo574Wqt~TS&2>DVC<4 zZ_7V)gnkW)U;2xw41qCp?A}{bbTVkBH5$5YFDEz_9^6d7Hs7x2E9!o4W^|$M!QZXA btKt6-C5@8h{H5A@00000NkvXXu0mjfU)~Fh literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/StoreLogo.scale-150.png b/WDACConfig/WinUI3/Assets/StoreLogo.scale-150.png new file mode 100644 index 0000000000000000000000000000000000000000..d53cc85758178916f6e52a3de93136f4c42e7cb0 GIT binary patch literal 3092 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 X`j({Un9}5Nq>Y78(NwNhv_}3P5SzEP literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/Wide310x150Logo.scale-100.png b/WDACConfig/WinUI3/Assets/Wide310x150Logo.scale-100.png new file mode 100644 index 0000000000000000000000000000000000000000..cd1a4d793276e6b9ee253d100681dfe1c9c09168 GIT binary patch literal 2788 zcmds3`#Teg8{aw6)p94fo*b79a~%$|B)2f+n9E2b$z?KZRIEep4V9W}L_%s*OhzcT zl^yrXY?d8UDKeL73yts2_s=*#eBSr{;qyMv=lwkI`#f)sC)^2m`1oM}004AxK64HL z_yr=Wi3j(K;-B;yo+urPb@q=302H+T#V;=BeqRFs4ijC@*!`JQzVhKJp*&3W%{rUc z6(8L}{Wf-P()ll#+oQ2qS(1xnhDObyvyX@~nNn4^PfdumjG|N3X4B3zrX5GhwA4vU zR~^eA*F1&vvmXo5kRfLczRbX_TFub}4_2nttUH=Jn&De3f(f%`Yr5?!jBPWrGrR+L z1Dq=HqZ|ML9y`te>h1qiFp4x}c5a%`@`vPLZ8Pz#W~LcIyGmCtfHsx(8R1(N*0%f=T}t$u-xv6UvmL@mSJt!I~bku|`Bu(zG)7)ABc=1W&7 zREZ!_)6)0{kivmh3RSHryW>H`x@ms4u}nJV7Ct{l3%wy=_`HAGTiIxNW5EElixGl} zSzyUQDE`Z-8Bdu`8hi)U5y_&n@>|-_P}J-FNp_a;ygy7rLJ+Kmvxd+c6C~dZ+LZW# z&yA?7xzd`^$L<_S1eZno^V4GCIPbQ}mU7LYPDu$lvh95QuOo_0Py}7433~Mr#6*r90lQA`*{e7>I0FJOq z;IR&3*pc63<|k;JKsFUlx554m>pO`Q_T-fsTfD7GLWhpt!> zcTiLDBdhReks}6pV3CM+vs@7Amuu7)#uZd)#P2TvhB^Iz-D59(wFbO(!W#dY4h&EK z^6;-^%J>*VNvLf;H6_!$^7EwM8&7G-y&yRCA1s;6;W&&KyE; zPX~%C8KXnw=ep%T)slquffnl_Pja=wgg5xkVGRM%G(*elAV?%~SJ>*-P&T@kx(`(P zEF*N^VU1{AUBW0H6mu(BBLJQ6#W8!a)@i^99zmQE`k43UE@o;OwnEb&i&L&bPw;_o7LG^7MHA0GpFBSfEH=1f|wC7%yc{;$&-31Hg$ok z*AF?Z)03H&&uacIEbXXWX&&hBQn~2SRqPn{`ZT1Q_p+Q!fvX93dkWP$cxCZ@G^;qe zx*BmQ^BM-~5w`+;xy3*5vAro6J08#TifRw4VDRErH%yU9wF<`b<1;kMPJo4Zd`6Jm znHeJuSyN^A6RmoIpbEZULnA_MmSVfddxAkGea{QgcU7?EJuE(R?5+A7xY>}hQM0I0 zz$|Ne$B@-govnksOOk0lMxC{MNlL?|1NXg55uEI%>Pyca2hY97n9g@krss$+@E4xO z+%X;h0B`~!LM~WU= zt=1cJ%Vp5}2LL^p)nw@N@1E`plMI~AddQ|v5=Y)rebbMHSluS6*f3A5l*BOB1+edq z223a4k60_W1o-9M1pP!DZTpqv{aEjZDyZJu31%zFC&`zW&7ojJiZ?C?YHP-#QfS7SMR)uP=rq$<~Hu| zFl!s01`Y_GG~wnJK+M1f{eH%Lk>|+k6MQYQ>!r=&+#iEK--=zi$VREGc;6Zp$4)Q? zrh!8sS2H()T{=*6yft~YVUnXn(!L}d|2-P??wWmBm1=0n)-CUC_&~?jTIOdRSJ*j% zmWVsu6*60Bp64wgWDOna>_YZ#GhMZcUww%I@>y zn_S(%y7N>KA8f!0XYC-TSNwm!i5HjfwCTw_cZ5moY}X}UWFkTtPkR}&iaaOx9a}C+ zweoSN;v9Bo9;*-ax|R-nRGqSwj}B)i%xjg5OoF9nS*QW3EW+Vf8)5l}|6t%6juOjmxd79`HyG$85Zo5 k0!t&D6a=vJU&Bwu22yq1(gwLt^tJ+A9N=dfPhUv;ANvMK_y7O^ literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/Wide310x150Logo.scale-125.png b/WDACConfig/WinUI3/Assets/Wide310x150Logo.scale-125.png new file mode 100644 index 0000000000000000000000000000000000000000..4ad52ce3583b0b67de515639be49a210d515dcf6 GIT binary patch literal 3514 zcmeHK_fr$f8VwjAa)Sgx2m~e4Ef|9|DTWA&2u2VUrASj0=|nn&rWXlaKtPa0FhaZ( zr56o7fJjid5{iIGQvwM@B$SZ6-1pm?_b0r!Gh1f%e6wf2Ip6HrBpZx{gt&q@005A% zykLF>01$d8Sib{_3dXS6u~NabH{ileEC3*@_D>hGyrQ})0Q*>)qpyY)u1rQ}T$_(> zTbstxnh-7h)ytJu8eYY5UPE5mNUz$Q>o6g$q>;x6@PK)RZ#mvz*g7Sz+6$OTlpw3u zx8GMMD~#zL&G>vZ2?_GDc9S~w(13W*<>Y}IPa7gb$95uU!&uxOkMFp#PP9a_oGD5@ zLY%2cCU$o=igSAK^kRfCz_j-mN5C;B4h{gwpaB5QcmTi#CU~ZJVCz2!7(r37-e;;sJk(zW0kBABTpd7yRXDZdCiLh z-s8>vCLyjm5Mbec-e#+;v!67x!Ton%AMUc=*FnyPiK{hK#3kMb%}?Pu#wcQ^StDw) zqVFYQPHlaL`9;qziP`4_Znl-dLC|5|z2EWFaq8w3efbueZ1j=kpL;nQds=C-QEQUx zcsuEWxxtuaejMG*IL9sO67yUC@MY|RnQmvZL4#;qVl}^j( zsJdO?UOjKEy>S@9G&w_#rq$@33~demvfB!!*zTFPfSgp13L1W1+Jo0td9!d-8vDu+ zX^)@G54d|mLoT7S`YkPbV6U8=^<58!q!M#j=$_W#V@$ zH!oUoJ4;h*CdV43O3y&@HPZ^+%C~Mb4x;+!mNg2upDE0WeOLNunbDX2(X>PVkB$|K z%)tBZb|&$#Du71A~~~D$hqofghY&LEGcac5kfkdfG6jHn_RlE`ktwrzQ)kk~jyJbJO{M1DAx@S!tz z>L`+)WpySh&FSrTOt?y&g#t|BvGD6dXaHue4C*9H{r18P`M7ZEr0sR*@D``HL7DHe zEUl{5`<@BERzL&N7J*2EpaF~0ey&9NhKNv^J5b1Dc;-~xpWCp=y~xMQE0_AS zs@`k<)~z!;c*F(?59#5A$3(2p^#9`YWxy&ITWz&^L8Xix3-o)cT7<+Se3Ylo|SIZ~>tcW~lB zs_CYg{`T-g4xy;f^$%Y!?XH+C(m?JokHyU*=C!AL~?4b!q(w(ze zw@EcG-xlO)pec=86^Xby{{VslG#V&$?M2?e8AjI=H^?@>a_(VZr+k~YHY3h)ehv2Y zP>mhIfMUtR(n9BoSs!y;SYOqpi!~J^j@Myxox(>RRYy&|-=Ex_Hdasl#&F3_t>Ry& zgCBU>C`*fK^v)M)ZHbu53kpbNFu4jG#M8H~EkV(g{meW{%E!5_*H?E%b1o(3j54$Zq7K=svxfKX zbGk2g?J-6E^xaYF3@8?n6^zE3YM)H;~?pEX2P%rMd=(fBp-eNH0 z*}`RhvgvY`rxUki!_`Z!Nn+LVxN(^*MmrxH(ptPRXMpfH?rGl!3ZWmd6!p@hy*E`>6D6RHc>iPZ513_{KeCIN+^2xizB@isMbkR-4S g@PFt2-#?gbRDgDAbab-nB0$hA&tc3f&$`C{3!#$_DgXcg literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/Wide310x150Logo.scale-150.png b/WDACConfig/WinUI3/Assets/Wide310x150Logo.scale-150.png new file mode 100644 index 0000000000000000000000000000000000000000..b2a3650df547837317beb091495899cf3d6f428d GIT binary patch literal 4290 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 A`2YX_ literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/Wide310x150Logo.scale-200.png b/WDACConfig/WinUI3/Assets/Wide310x150Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..480722505ec6d16b7d6f978b52b4ceb316a275bc GIT binary patch literal 5714 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 literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Assets/Wide310x150Logo.scale-400.png b/WDACConfig/WinUI3/Assets/Wide310x150Logo.scale-400.png new file mode 100644 index 0000000000000000000000000000000000000000..76d787a293b0d665d2effdf80ad42fc9544edd2c GIT binary patch literal 13305 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 literal 0 HcmV?d00001 diff --git a/WDACConfig/WinUI3/Invoke-TacticalMSIXDeployment.ps1 b/WDACConfig/WinUI3/Invoke-TacticalMSIXDeployment.ps1 new file mode 100644 index 000000000..a326b7878 --- /dev/null +++ b/WDACConfig/WinUI3/Invoke-TacticalMSIXDeployment.ps1 @@ -0,0 +1,193 @@ +#Requires -RunAsAdministrator +#Requires -Version 5.1 +Function Invoke-TacticalMSIXDeployment { + <# + .DESCRIPTION + This script installs the WDACConfig MSIX package on the system. + It does so by securely generating a unique self-signed certificate on the user's system and then using it to sign the MSIX package. + Everything happens locally and no certificate comes from outside of the device. + The certificate is added to the Local Machine's Trust Root Certification Authorities Store with only public keys, ensuring no private key exists to be used to sign anything else. + Its existence with public key is needed so that you can use the WDACConfig app; without it the app will not launch as it will be considered untrusted by the system. + The 2 files, WDACConfig.dll and WDACConfig.exe inside of the MSIX app installation folder will be added to the Attack Surface Reduction rules exclusion list if they don't already exist in there, so the app will work properly. + The script creates a new directory in the TEMP directory for its operations and it will be deleted at the end of the script. + The script checks for the existence of any previous self-signed certificates generated by it and will remove them if it detects any, guaranteeing no unnecessary leftover remains on the user's system. + .PARAMETER MSIXPath + Mandatory. The path to the WDACConfig MSIX file + .PARAMETER SignTool + Optional. The path to the Microsoft's Signtool.exe + If not provided, the script automatically downloads the latest SignTool.exe from the Microsoft website in Nuget and will use it for the signing operations. + .LINK + https://github.com/HotCakeX/Harden-Windows-Security + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)][System.String]$MSIXPath, + [Parameter(Mandatory = $False)][System.String]$SignTool + ) + Begin { + $script:ErrorActionPreference = 'Stop' + + Write-Verbose -Message 'Creating the working directory in the TEMP directory' + [System.String]$CommonName = 'SelfSignedCertForWDACConfig' + [System.String]$WorkingDir = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), $CommonName) + [System.String]$CertificateOutputPath = [System.IO.Path]::Combine($WorkingDir, "$CommonName.cer") + [System.String]$PFXCertificateOutputPath = [System.IO.Path]::Combine($WorkingDir, "$CommonName.pfx") + [System.String]$Pass = '123' + [System.Security.SecureString]$PassSS = ConvertTo-SecureString $Pass -AsPlainText -Force + [System.String]$HashingAlgorithm = 'Sha512' + + if ([System.IO.Directory]::Exists($WorkingDir)) { + [System.IO.Directory]::Delete($WorkingDir, $true) + } + $null = [System.IO.Directory]::CreateDirectory($WorkingDir) + + Write-Verbose -Message "Checking if a certificate with the common name '$CommonName' already exists." + [System.String[]]$CertStoresToCheck = @('Cert:\LocalMachine\My', 'Cert:\LocalMachine\Root', 'Cert:\LocalMachine\CA', 'Cert:\LocalMachine\TrustedPublisher', 'Cert:\CurrentUser\My', 'Cert:\CurrentUser\Root', 'Cert:\CurrentUser\CA', 'Cert:\CurrentUser\TrustedPublisher') + foreach ($Store in $CertStoresToCheck) { + foreach ($Item in (Get-ChildItem -Path $Store)) { + if ($Item.Subject -ieq "CN=$CommonName") { + Write-Verbose -Message "A certificate with the common name '$CommonName' in the store '$Store' already exists. Removing it." + $Item | Remove-Item -Force + } + } + } + } + process { + Write-Verbose -Message 'Building the certificate' + # Create a hashtable of parameter names and values + [System.Collections.Hashtable]$Params = @{ + Subject = "CN=$CommonName" + FriendlyName = $CommonName + CertStoreLocation = 'Cert:\CurrentUser\My' + KeyExportPolicy = 'ExportableEncrypted' + KeyLength = '4096' + KeyAlgorithm = 'RSA' + HashAlgorithm = $HashingAlgorithm + KeySpec = 'Signature' + KeyUsage = 'DigitalSignature' + KeyUsageProperty = 'Sign' + Type = 'CodeSigningCert' + NotAfter = [System.DateTime](Get-Date).AddYears(100) + TextExtension = @('2.5.29.19={text}CA:FALSE', '2.5.29.37={text}1.3.6.1.5.5.7.3.3', '1.3.6.1.4.1.311.21.10={text}oid=1.3.6.1.5.5.7.3.3') + } + + [System.Security.Cryptography.X509Certificates.X509Certificate2]$NewCertificate = New-SelfSignedCertificate @params + + # Save the thumbprint of the certificate to a variable + [System.String]$NewCertificateThumbprint = $NewCertificate.Thumbprint + + Write-Verbose -Message 'Finding the certificate that was just created by its thumbprint' + [System.Security.Cryptography.X509Certificates.X509Certificate2]$TheCert = foreach ($Cert in (Get-ChildItem -Path 'Cert:\CurrentUser\My' -CodeSigningCert)) { + if ($Cert.Thumbprint -eq $NewCertificateThumbprint) { + $Cert + } + } + + Write-Verbose -Message 'Exporting the certificate (public key only)' + $null = Export-Certificate -Cert $TheCert -FilePath $CertificateOutputPath -Type 'CERT' -Force + + Write-Verbose -Message 'Exporting the certificate (public and private keys)' + $null = Export-PfxCertificate -Cert $TheCert -CryptoAlgorithmOption 'AES256_SHA256' -Password $PassSS -ChainOption 'BuildChain' -FilePath $PFXCertificateOutputPath -Force + + Write-Verbose -Message "Removing the certificate from the 'Current User/Personal' store" + $TheCert | Remove-Item -Force + + Write-Verbose -Message "Importing the certificate to the 'Local Machine/Trusted Root Certification Authorities' store with the private key protected by VSM (Virtual Secure Mode - Virtualization Based Security)" + $null = Import-PfxCertificate -ProtectPrivateKey 'VSM' -FilePath $PFXCertificateOutputPath -CertStoreLocation 'Cert:\LocalMachine\Root' -Password $PassSS + + if ([System.String]::IsNullOrWhiteSpace($SignTool)) { + + Write-Verbose -Message 'Checking if nuget source is available in PowerShell' + if (-NOT (Get-PackageSource | Where-Object -FilterScript { $_.Name -ieq 'nuget.org' })) { + Write-Verbose -Message 'Registering the nuget.org package source because it was not found in the system.' + $null = Register-PackageSource -Name 'nuget.org' -ProviderName 'NuGet' -Location 'https://api.nuget.org/v3/index.json' + } + + Write-Verbose -Message 'Finding the latest version of the Microsoft.Windows.SDK.BuildTools package from NuGet' + + # Minimum is simply used to limit the number of the fetched packages + [Microsoft.PackageManagement.Packaging.SoftwareIdentity[]]$Package = Find-Package -Name 'Microsoft.Windows.SDK.BuildTools' -Source 'nuget.org' -AllVersions -Force -MinimumVersion '10.0.22621.3233' + [Microsoft.PackageManagement.Packaging.SoftwareIdentity]$Package = $Package | Sort-Object -Property { [System.Version]$_.Version } -Descending | Select-Object -First 1 + + Write-Verbose -Message 'Downloading SignTool.exe from NuGet...' + $null = Save-Package -InputObject $Package -Path $WorkingDir -Force + + Write-Verbose -Message 'Extracting the nupkg' + Expand-Archive -Path "$WorkingDir\*.nupkg" -DestinationPath $WorkingDir -Force + + Write-Verbose -Message 'Detecting the CPU Arch' + switch ($Env:PROCESSOR_ARCHITECTURE) { + 'AMD64' { [System.String]$CPUArch = 'x64'; break } + 'ARM64' { [System.String]$CPUArch = 'arm64'; break } + default { Throw [System.PlatformNotSupportedException] 'Only AMD64 and ARM64 architectures are supported.' } + } + + Write-Verbose -Message 'Finding the Signtool.exe path in the extracted directory' + $SignTool = "$WorkingDir\bin\*\$CPUArch\signtool.exe" + } + + # https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe + Write-Verbose -Message 'Signing the MSIX package' + . $SignTool sign /debug /n $CommonName /fd $HashingAlgorithm /f $PFXCertificateOutputPath /p $Pass $MSIXPath + + Write-Verbose -Message 'Checking for existence of the application in unpacked format' + $PossibleExistingApp = Get-AppxPackage -Name 'WDACConfig.AppControl' + if ($null -ne $PossibleExistingApp) { + if ($PossibleExistingApp.IsDevelopmentMode -eq $true) { + if ($PossibleExistingApp.SignatureKind -eq 'None') { + # Without this step, the installing would fail + Write-Verbose -Message 'The MSIX package is already installed in an unpacked format. Removing it and installing it again from the MSIX file.' + $PossibleExistingApp | Remove-AppxPackage -AllUsers + } + } + } + + Write-Verbose -Message 'Installing the MSIX Package' + # -DeferRegistrationWhenPackagesAreInUse shouldn't be used because then during ASR exclusions the correct path of the application won't be detected and added to the exclusion list + # -ForceTargetApplicationShutdown will shutdown the application if its open. This step is necessary for ASR rules exclusions later. + # So either the ASR Rules exclusions must happen or the app gets installed after user closes it + Add-AppPackage -Path $MSIXPath -ForceUpdateFromAnyVersion -ForceTargetApplicationShutdown + + Write-Verbose -Message "Finding the certificate that was just created by its thumbprint again from the 'Local Machine/Trusted Root Certification Authorities' store" + [System.Security.Cryptography.X509Certificates.X509Certificate2]$TheCert2 = foreach ($Cert2 in (Get-ChildItem -Path 'Cert:\LocalMachine\Root' -CodeSigningCert)) { + if ($Cert.Thumbprint -eq $NewCertificateThumbprint) { + $Cert2 + } + } + + Write-Verbose -Message 'Removing the certificate that has private + public keys' + $TheCert2 | Remove-Item -Force + + Write-Verbose -Message "Adding the certificate to the 'Local Machine/Trusted Root Certification Authorities' store with public key only." + Write-Verbose -Message 'This safely stores the certificate on your device, ensuring its private key does not exist so cannot be used to sign anything else.' + $null = Import-Certificate -FilePath $CertificateOutputPath -CertStoreLocation 'Cert:\LocalMachine\Root' + + Write-Verbose -Message 'Cleaning up the working directory in the TEMP directory after all tasks have been completed.' + [System.IO.Directory]::Delete($WorkingDir, $true) + + Write-Verbose -Message 'Finding the WDACConfig.AppControl installation directory' + [System.String]$InstalledAppLocation = (Get-AppxPackage -Name 'WDACConfig.AppControl').InstallLocation + + Write-Verbose -Message 'Getting the list of Attack Surface Reduction Rules exclusions' + [System.Collections.Generic.List[System.String]]$CurrentASRExclusions = (Get-MpPreference).AttackSurfaceReductionOnlyExclusions + + foreach ($Item in (Get-ChildItem -Path $InstalledAppLocation -Recurse -Include '*.dll', '*.exe')) { + if ($Item.Name -in 'WDACConfig.exe', 'WDACConfig.dll') { + # If the ASR Rules exclusions list is either empty or it doesn't contain the WDACConfig files, then add them + if ($null -eq $CurrentASRExclusions -or !$CurrentASRExclusions.Contains($Item.FullName)) { + Write-Verbose -Message "Adding $($Item.FullName) to the ASR Rules exclusions because it didn't exist there." + Add-MpPreference -AttackSurfaceReductionOnlyExclusions $Item.FullName + } + } + } + } +} + +# Get the current folder of this script file +[System.String]$ScriptFilePath = ($MyInvocation.MyCommand.path | Split-Path -Parent) +# Find the latest MSI file +[System.String]$MSIX = Get-Item -Path "$ScriptFilePath\MSIXOutput\WDACConfig*\WDACConfig*.msix" | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 + +Push-Location -Path $ScriptFilePath +Invoke-TacticalMSIXDeployment -MSIXPath $MSIX -Verbose -SignTool .\signtool.exe +Pop-Location diff --git a/WDACConfig/WinUI3/MainWindow.xaml b/WDACConfig/WinUI3/MainWindow.xaml new file mode 100644 index 000000000..642408f04 --- /dev/null +++ b/WDACConfig/WinUI3/MainWindow.xaml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WDACConfig/WinUI3/MainWindow.xaml.cs b/WDACConfig/WinUI3/MainWindow.xaml.cs new file mode 100644 index 000000000..04b7a7ae8 --- /dev/null +++ b/WDACConfig/WinUI3/MainWindow.xaml.cs @@ -0,0 +1,59 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace WDACConfig +{ + public sealed partial class MainWindow : Window + { + public MainWindow() + { + this.InitializeComponent(); + + // https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.window.extendscontentintotitlebar + // Make title bar Mica + ExtendsContentIntoTitleBar = true; + } + + // Event handler for the main navigation menu + private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) + { + // Check if the item is selected + if (args.SelectedItem is NavigationViewItem selectedItem) + { + string? selectedTag = selectedItem.Tag?.ToString(); + + // Navigate to the page based on the Tag + switch (selectedTag) + { + case "Home": + _ = ContentFrame.Navigate(typeof(Pages.Home)); + break; + case "CreatePolicy": + _ = ContentFrame.Navigate(typeof(Pages.CreatePolicy)); + break; + case "BlockRules": + _ = ContentFrame.Navigate(typeof(Pages.BlockRules)); + break; + case "GetCIHashes": + _ = ContentFrame.Navigate(typeof(Pages.GetCIHashes)); + break; + // Doesn't need XAML nav item because it's included by default in the navigation view + case "Settings": + _ = ContentFrame.Navigate(typeof(Pages.Settings)); + break; + case "GitHubDocumentation": + _ = ContentFrame.Navigate(typeof(Pages.GitHubDocumentation)); + break; + case "MicrosoftDocumentation": + _ = ContentFrame.Navigate(typeof(Pages.MicrosoftDocumentation)); + break; + case "GetSecurePolicySettings": + _ = ContentFrame.Navigate(typeof(Pages.GetSecurePolicySettings)); + break; + default: + break; + } + } + } + } +} diff --git a/WDACConfig/WinUI3/Package.appxmanifest b/WDACConfig/WinUI3/Package.appxmanifest new file mode 100644 index 000000000..62834bfcc --- /dev/null +++ b/WDACConfig/WinUI3/Package.appxmanifest @@ -0,0 +1,53 @@ + + + + + + + + + + WDACConfig + SelfSignedCertForWDACConfig + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WDACConfig/WinUI3/Pages/BlockRules.xaml b/WDACConfig/WinUI3/Pages/BlockRules.xaml new file mode 100644 index 000000000..2364560a3 --- /dev/null +++ b/WDACConfig/WinUI3/Pages/BlockRules.xaml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/WDACConfig/WinUI3/Pages/BlockRules.xaml.cs b/WDACConfig/WinUI3/Pages/BlockRules.xaml.cs new file mode 100644 index 000000000..28484f142 --- /dev/null +++ b/WDACConfig/WinUI3/Pages/BlockRules.xaml.cs @@ -0,0 +1,15 @@ +using Microsoft.UI.Xaml.Controls; + +namespace WDACConfig.Pages +{ + public sealed partial class BlockRules : Page + { + public BlockRules() + { + this.InitializeComponent(); + + // Make sure navigating to/from this page maintains its state + this.NavigationCacheMode = Microsoft.UI.Xaml.Navigation.NavigationCacheMode.Enabled; + } + } +} diff --git a/WDACConfig/WinUI3/Pages/CreatePolicy.xaml b/WDACConfig/WinUI3/Pages/CreatePolicy.xaml new file mode 100644 index 000000000..a81629455 --- /dev/null +++ b/WDACConfig/WinUI3/Pages/CreatePolicy.xaml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/WDACConfig/WinUI3/Pages/CreatePolicy.xaml.cs b/WDACConfig/WinUI3/Pages/CreatePolicy.xaml.cs new file mode 100644 index 000000000..3a96e0771 --- /dev/null +++ b/WDACConfig/WinUI3/Pages/CreatePolicy.xaml.cs @@ -0,0 +1,15 @@ +using Microsoft.UI.Xaml.Controls; + +namespace WDACConfig.Pages +{ + public sealed partial class CreatePolicy : Page + { + public CreatePolicy() + { + this.InitializeComponent(); + + // Make sure navigating to/from this page maintains its state + this.NavigationCacheMode = Microsoft.UI.Xaml.Navigation.NavigationCacheMode.Enabled; + } + } +} diff --git a/WDACConfig/WinUI3/Pages/GetCIHashes.xaml b/WDACConfig/WinUI3/Pages/GetCIHashes.xaml new file mode 100644 index 000000000..5a7aa37aa --- /dev/null +++ b/WDACConfig/WinUI3/Pages/GetCIHashes.xaml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WDACConfig/WinUI3/Pages/GitHubDocumentation.xaml.cs b/WDACConfig/WinUI3/Pages/GitHubDocumentation.xaml.cs new file mode 100644 index 000000000..0dc982756 --- /dev/null +++ b/WDACConfig/WinUI3/Pages/GitHubDocumentation.xaml.cs @@ -0,0 +1,77 @@ +using Microsoft.UI; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using System; + +namespace WDACConfig.Pages +{ + public sealed partial class GitHubDocumentation : Page + { + public GitHubDocumentation() + { + this.InitializeComponent(); + + // Background color of the WebView2 while content is loading + GitHubDocumentationWebView2.DefaultBackgroundColor = Colors.Black; + + // Handle navigation events to manage button state + GitHubDocumentationWebView2.NavigationCompleted += WebView2_NavigationCompleted; + + // Make sure navigating to/from this page maintains its state + this.NavigationCacheMode = Microsoft.UI.Xaml.Navigation.NavigationCacheMode.Enabled; + } + + // Event handler for Back button + private void BackButton_Click(object sender, RoutedEventArgs e) + { + if (GitHubDocumentationWebView2.CanGoBack) + { + GitHubDocumentationWebView2.GoBack(); + } + } + + // Event handler for Forward button + private void ForwardButton_Click(object sender, RoutedEventArgs e) + { + if (GitHubDocumentationWebView2.CanGoForward) + { + GitHubDocumentationWebView2.GoForward(); + } + } + + // Event handler for Reload button + private void ReloadButton_Click(object sender, RoutedEventArgs e) + { + GitHubDocumentationWebView2.Reload(); + } + + // Event handler for Home button + private void HomeButton_Click(object sender, RoutedEventArgs e) + { + GitHubDocumentationWebView2.Source = new Uri("https://github.com/HotCakeX/Harden-Windows-Security/wiki/Introduction"); + } + + // Update the state of navigation buttons when navigation is completed so that the Back/Forward buttons will be enabled only when they can be used + + private void WebView2_NavigationCompleted(object sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs e) + { + + // The following checks are required to prevent any errors when intentionally spam navigating between pages and elements extremely fast + try + { + + // Check if the WebView2 control or its CoreWebView2 instance is disposed + if (GitHubDocumentationWebView2 != null && GitHubDocumentationWebView2.CoreWebView2 != null) + { + BackButton.IsEnabled = GitHubDocumentationWebView2.CanGoBack; + ForwardButton.IsEnabled = GitHubDocumentationWebView2.CanGoForward; + } + } + catch (ObjectDisposedException ex) + { + // Log the exception, but avoid crashing the app + System.Diagnostics.Debug.WriteLine("WebView2 in GitHub Documentation Page has been disposed: " + ex.Message); + } + } + } +} diff --git a/WDACConfig/WinUI3/Pages/Home.xaml b/WDACConfig/WinUI3/Pages/Home.xaml new file mode 100644 index 000000000..d26bcd230 --- /dev/null +++ b/WDACConfig/WinUI3/Pages/Home.xaml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/WDACConfig/WinUI3/Pages/Home.xaml.cs b/WDACConfig/WinUI3/Pages/Home.xaml.cs new file mode 100644 index 000000000..2ee1e3559 --- /dev/null +++ b/WDACConfig/WinUI3/Pages/Home.xaml.cs @@ -0,0 +1,18 @@ +using Microsoft.UI.Xaml.Controls; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace WDACConfig.Pages +{ + public sealed partial class Home : Page + { + public Home() + { + this.InitializeComponent(); + + // Make sure navigating to/from this page maintains its state + this.NavigationCacheMode = Microsoft.UI.Xaml.Navigation.NavigationCacheMode.Enabled; + } + } +} diff --git a/WDACConfig/WinUI3/Pages/MicrosoftDocumentation.xaml b/WDACConfig/WinUI3/Pages/MicrosoftDocumentation.xaml new file mode 100644 index 000000000..c58a17801 --- /dev/null +++ b/WDACConfig/WinUI3/Pages/MicrosoftDocumentation.xaml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WDACConfig/WinUI3/Pages/MicrosoftDocumentation.xaml.cs b/WDACConfig/WinUI3/Pages/MicrosoftDocumentation.xaml.cs new file mode 100644 index 000000000..3e912b428 --- /dev/null +++ b/WDACConfig/WinUI3/Pages/MicrosoftDocumentation.xaml.cs @@ -0,0 +1,74 @@ +using Microsoft.UI; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml; +using System; + +namespace WDACConfig.Pages +{ + public sealed partial class MicrosoftDocumentation : Page + { + public MicrosoftDocumentation() + { + this.InitializeComponent(); + // Set background color of WebView2 while content is loading + MicrosoftDocumentationWebView2.DefaultBackgroundColor = Colors.Black; + + // Handle navigation events to manage button state + MicrosoftDocumentationWebView2.NavigationCompleted += WebView2_NavigationCompleted; + + // Make sure navigating to/from this page maintains its state + this.NavigationCacheMode = Microsoft.UI.Xaml.Navigation.NavigationCacheMode.Enabled; + } + + // Event handler for Back button + private void BackButton_Click(object sender, RoutedEventArgs e) + { + if (MicrosoftDocumentationWebView2.CanGoBack) + { + MicrosoftDocumentationWebView2.GoBack(); + } + } + + // Event handler for Forward button + private void ForwardButton_Click(object sender, RoutedEventArgs e) + { + if (MicrosoftDocumentationWebView2.CanGoForward) + { + MicrosoftDocumentationWebView2.GoForward(); + } + } + + // Event handler for Reload button + private void ReloadButton_Click(object sender, RoutedEventArgs e) + { + MicrosoftDocumentationWebView2.Reload(); + } + + // Event handler for Home button + private void HomeButton_Click(object sender, RoutedEventArgs e) + { + MicrosoftDocumentationWebView2.Source = new Uri("https://learn.microsoft.com/en-us/windows/security/application-security/application-control/windows-defender-application-control/wdac"); + } + + // Update the state of navigation buttons when navigation is completed so that the Back/Forward buttons will be enabled only when they can be used + private void WebView2_NavigationCompleted(object sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs e) + { + // The following checks are required to prevent any errors when intentionally spam navigating between pages and elements extremely fast + try + { + // Check if the WebView2 control or its CoreWebView2 instance is disposed + if (MicrosoftDocumentationWebView2 != null && MicrosoftDocumentationWebView2.CoreWebView2 != null) + { + BackButton.IsEnabled = MicrosoftDocumentationWebView2.CanGoBack; + ForwardButton.IsEnabled = MicrosoftDocumentationWebView2.CanGoForward; + } + } + + catch (ObjectDisposedException ex) + { + // Log the exception, but avoid crashing the app + System.Diagnostics.Debug.WriteLine("WebView2 in Microsoft Documentation page has been disposed: " + ex.Message); + } + } + } +} diff --git a/WDACConfig/WinUI3/Pages/Settings.xaml b/WDACConfig/WinUI3/Pages/Settings.xaml new file mode 100644 index 000000000..fa50deda2 --- /dev/null +++ b/WDACConfig/WinUI3/Pages/Settings.xaml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/WDACConfig/WinUI3/Pages/Settings.xaml.cs b/WDACConfig/WinUI3/Pages/Settings.xaml.cs new file mode 100644 index 000000000..c77f6d3d8 --- /dev/null +++ b/WDACConfig/WinUI3/Pages/Settings.xaml.cs @@ -0,0 +1,15 @@ +using Microsoft.UI.Xaml.Controls; + +namespace WDACConfig.Pages +{ + public sealed partial class Settings : Page + { + public Settings() + { + this.InitializeComponent(); + + // Make sure navigating to/from this page maintains its state + this.NavigationCacheMode = Microsoft.UI.Xaml.Navigation.NavigationCacheMode.Enabled; + } + } +} diff --git a/WDACConfig/WinUI3/Properties/PublishProfiles/win-arm64.pubxml b/WDACConfig/WinUI3/Properties/PublishProfiles/win-arm64.pubxml new file mode 100644 index 000000000..866091bd0 --- /dev/null +++ b/WDACConfig/WinUI3/Properties/PublishProfiles/win-arm64.pubxml @@ -0,0 +1,13 @@ + + + + + FileSystem + ARM64 + bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ + true + False + + \ No newline at end of file diff --git a/WDACConfig/WinUI3/Properties/PublishProfiles/win-x64.pubxml b/WDACConfig/WinUI3/Properties/PublishProfiles/win-x64.pubxml new file mode 100644 index 000000000..c2b581f37 --- /dev/null +++ b/WDACConfig/WinUI3/Properties/PublishProfiles/win-x64.pubxml @@ -0,0 +1,13 @@ + + + + + FileSystem + x64 + bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ + true + False + + \ No newline at end of file diff --git a/WDACConfig/WinUI3/Properties/launchSettings.json b/WDACConfig/WinUI3/Properties/launchSettings.json new file mode 100644 index 000000000..f7587b806 --- /dev/null +++ b/WDACConfig/WinUI3/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "WDACConfig (Package)": { + "commandName": "MsixPackage" + }, + "WDACConfig (Unpackaged)": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/WDACConfig/WinUI3/Shared Logics/Logging/Logger.cs b/WDACConfig/WinUI3/Shared Logics/Logging/Logger.cs new file mode 100644 index 000000000..324ca9ed9 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Logging/Logger.cs @@ -0,0 +1,27 @@ +#nullable enable + +namespace WDACConfig +{ + public static class Logger + { + ///

+ /// Write a verbose message to the console + /// The verbose messages are not redirectable in PowerShell + /// https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.host.pshostuserinterface + /// + /// + public static void Write(string message) + { + try + { + if (GlobalVars.VerbosePreference) + { + GlobalVars.Host?.UI.WriteVerboseLine(message); + } + } + // Do not do anything if errors occur + // Since many methods write to the console asynchronously this can throw errors + catch { } + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Logging/LoggerInitializer.cs b/WDACConfig/WinUI3/Shared Logics/Logging/LoggerInitializer.cs new file mode 100644 index 000000000..d40f28a62 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Logging/LoggerInitializer.cs @@ -0,0 +1,43 @@ +using System; +using System.Management.Automation.Host; + +#nullable enable + +namespace WDACConfig +{ + public class LoggerInitializer + { + /// + /// Gets the VerbosePreference, DebugPreference, and Host from the PowerShell session, each main cmdlet of the WDACConfig module + /// + /// + /// + /// + public static void Initialize(string verbosePreference, string debugPreference, PSHost host) + { + + if (!string.IsNullOrWhiteSpace(verbosePreference)) + { + if (string.Equals(verbosePreference, "Continue", StringComparison.OrdinalIgnoreCase) || + string.Equals(verbosePreference, "Inquire", StringComparison.OrdinalIgnoreCase)) + { + GlobalVars.VerbosePreference = true; + } + } + + if (!string.IsNullOrWhiteSpace(debugPreference)) + { + if (string.Equals(debugPreference, "Continue", StringComparison.OrdinalIgnoreCase) || + string.Equals(debugPreference, "Inquire", StringComparison.OrdinalIgnoreCase)) + { + GlobalVars.DebugPreference = true; + } + } + + if (host != null) + { + GlobalVars.Host = host; + } + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Main Cmdlets/AssertWDACConfigIntegrity.cs b/WDACConfig/WinUI3/Shared Logics/Main Cmdlets/AssertWDACConfigIntegrity.cs new file mode 100644 index 000000000..04dd0b649 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Main Cmdlets/AssertWDACConfigIntegrity.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Security.Cryptography; +using System.Text; + +#nullable enable + +namespace WDACConfig +{ + // This class defines Hash entries for each file in the WDACConfig PowerShell module based on the cloud CSV + public class WDACConfigHashEntry(string? relativePath, string? fileName, string? fileHash, string? fileHashSHA3_512) + { + public string? RelativePath { get; set; } = relativePath; + public string? FileName { get; set; } = fileName; + public string? FileHash { get; set; } = fileHash; + public string? FileHashSHA3_512 { get; set; } = fileHashSHA3_512; + } + + + public class AssertWDACConfigIntegrity + { + /// + /// Hashes all of the files in the WDACConfig, download the cloud hashes, compares them with each other and report back hash mismatches + /// + /// + /// Location where new hash results will be saved + public static List? Invoke(bool SaveLocally, string? path) + { + + // Defining the output file name and the URL of the cloud CSV file + string OutputFileName = "Hashes.csv"; + string url = "https://raw.githubusercontent.com/HotCakeX/Harden-Windows-Security/main/WDACConfig/Utilities/Hashes.csv"; + + // Parse the CSV content + List ParsedCSVList = []; + + // Hash details of the current PowerShell files + List CurrentFileHashes = []; + + using HttpClient client = new(); + + // Download CSV content synchronously + string csvData = client.GetStringAsync(url).Result; + + // Parse the CSV content + ParsedCSVList = ParseCSV(csvData); + + // Get all of the files in the PowerShell module directory + List files = WDACConfig.FileUtility.GetFilesFast([new DirectoryInfo(WDACConfig.GlobalVars.ModuleRootPath!)], null, ["*"]); + + // Loop over each file + foreach (FileInfo file in files) + { + + // Making sure the PowerShell Gallery file in the WDACConfig module's folder is skipped + if (file.Name == "PSGetModuleInfo.xml") + { + continue; + } + + // Read the file as a byte array - This way we can get hashes of a file in use by another process where Get-FileHash would fail + byte[] Bytes = File.ReadAllBytes(file.FullName); + + // Compute the hash of the byte array + Byte[] HashBytes = SHA512.HashData(Bytes); + + // Convert the hash bytes to a hexadecimal string to make it look like the output of the Get-FileHash which produces hexadecimals (0-9 and A-F) + // If [System.Convert]::ToBase64String was used, it'd return the hash in base64 format, which uses 64 symbols (A-Z, a-z, 0-9, + and /) to represent each byte + String HashString = BitConverter.ToString(HashBytes); + + // Remove the dashes from the hexadecimal string + HashString = HashString.Replace("-", "", StringComparison.OrdinalIgnoreCase); + + // Add the file details to the list + CurrentFileHashes.Add(new WDACConfigHashEntry( + Path.GetRelativePath(WDACConfig.GlobalVars.ModuleRootPath!, file.FullName), + file.Name, + HashString, + null)); + } + + // Save the current files' hashes to a CSV in the user defined directory path + if (SaveLocally) + { + ExportToCsv(Path.Combine(path!, OutputFileName), CurrentFileHashes); + } + + // A list to store mismatches + List MismatchedEntries = []; + + // Compare the two lists + foreach (WDACConfigHashEntry currentFileHash in CurrentFileHashes) + { + // Find the corresponding entry in ParsedCSVList + WDACConfigHashEntry? matchingParsedEntry = ParsedCSVList.FirstOrDefault( + p => RemoveQuotes(p.RelativePath) == RemoveQuotes(currentFileHash.RelativePath) && + RemoveQuotes(p.FileName) == RemoveQuotes(currentFileHash.FileName) && + RemoveQuotes(p.FileHash) == RemoveQuotes(currentFileHash.FileHash) + ); + + // If there's no matching entry or the hashes are different, add to the mismatch list + if (matchingParsedEntry is null) + { + MismatchedEntries.Add(currentFileHash); + } + } + + if (MismatchedEntries.Count > 0) + { + Console.WriteLine("The following files are different from the ones in the cloud:"); + return MismatchedEntries; + } + else + { + Console.WriteLine("All of your local WDACConfig files are genuine."); + return null; + } + } + + /// + /// Parses the CSV content and returns a list of WDACConfigHashEntry objects + /// + /// + /// + private static List ParseCSV(string csvData) + { + var entries = new List(); + + using (StringReader reader = new(csvData)) + { + string? line; + bool isHeader = true; + + while ((line = reader.ReadLine()) != null) + { + // Skip the header + if (isHeader) + { + isHeader = false; + continue; + } + + // Split the CSV line by commas + var fields = line.Split(','); + + if (fields.Length == 4) + { + entries.Add(new WDACConfigHashEntry + ( + fields[0], + fields[1], + fields[2], + fields[3] + )); + } + } + } + + return entries; + } + + + /// + /// Exports the list of WDACConfigHashEntry objects to a CSV file + /// + /// + /// + private static void ExportToCsv(string outputPath, List entries) + { + // Ensure we create a new file or overwrite an existing one + using (StreamWriter writer = new(outputPath, false, Encoding.UTF8)) + { + // Write the CSV header + writer.WriteLine(""" +"RelativePath","FileName","FileHash","FileHashSHA3_512" +"""); + + // Write each entry in the list + foreach (var entry in entries) + { + string relativePath = EscapeCsv(entry.RelativePath); + string fileName = EscapeCsv(entry.FileName); + string fileHash = EscapeCsv(entry.FileHash); + string fileHashSHA3_512 = EscapeCsv(entry.FileHashSHA3_512); + + // Write the CSV row + writer.WriteLine($"{relativePath},{fileName},{fileHash},{fileHashSHA3_512}"); + } + } + } + + + /// + /// Escapes the content of a field for CSV (quotes any value with commas). + /// + /// The string to escape. + /// Escaped CSV string + private static string EscapeCsv(string? field) + { + // If the field is null or empty, return an empty string + if (string.IsNullOrWhiteSpace(field)) return ""; + + // Add quotes around each field regardless of whether they already include comma, quotes etc. + return $"\"{field.Replace("\"", "\"\"", StringComparison.OrdinalIgnoreCase)}\""; + + } + + + /// + /// Helper function to remove double quotes from the strings + /// + /// + /// + private static string? RemoveQuotes(string? input) + { + return input?.Trim('"'); + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Main Cmdlets/GetCIPolicySetting.cs b/WDACConfig/WinUI3/Shared Logics/Main Cmdlets/GetCIPolicySetting.cs new file mode 100644 index 000000000..0ec5372f4 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Main Cmdlets/GetCIPolicySetting.cs @@ -0,0 +1,81 @@ +using System.Runtime.InteropServices; + +#nullable enable + +namespace WDACConfig +{ + + public class SecurePolicySetting(object? Value, WDACConfig.WLDP_SECURE_SETTING_VALUE_TYPE ValueType, uint ValueSize, bool Status, int StatusCode) + { + public object? Value { get; set; } = Value; + public WDACConfig.WLDP_SECURE_SETTING_VALUE_TYPE ValueType { get; set; } = ValueType; + public uint ValueSize { get; set; } = ValueSize; + public bool Status { get; set; } = Status; + public int StatusCode { get; set; } = StatusCode; + } + + public class GetCIPolicySetting + { + + public static SecurePolicySetting Invoke(string provider, string key, string valueName) + { + + // Create UNICODE_STRING structures + var ProviderUS = WDACConfig.WldpQuerySecurityPolicyWrapper.InitUnicodeString(provider); + var KeyUS = WDACConfig.WldpQuerySecurityPolicyWrapper.InitUnicodeString(key); + var ValueNameUS = WDACConfig.WldpQuerySecurityPolicyWrapper.InitUnicodeString(valueName); + + // Prepare output variables + uint ValueSize = 1024; // Changed to uint to match the P/Invoke declaration + var Value = Marshal.AllocHGlobal((int)ValueSize); + + var result = WldpQuerySecurityPolicyWrapper.WldpQuerySecurityPolicy( + ref ProviderUS, + ref KeyUS, + ref ValueNameUS, + out WDACConfig.WLDP_SECURE_SETTING_VALUE_TYPE ValueType, + Value, + ref ValueSize + ); + + object? decodedValue = null; + + if (result == 0) + { + switch (ValueType) + { + case WDACConfig.WLDP_SECURE_SETTING_VALUE_TYPE.WldpBoolean: + decodedValue = Marshal.ReadByte(Value) != 0; + break; + case WDACConfig.WLDP_SECURE_SETTING_VALUE_TYPE.WldpString: + decodedValue = Marshal.PtrToStringUni(Value); + break; + case WDACConfig.WLDP_SECURE_SETTING_VALUE_TYPE.WldpInteger: + decodedValue = Marshal.ReadInt32(Value); + break; + case WDACConfig.WLDP_SECURE_SETTING_VALUE_TYPE.WldpNone: + break; + case WDACConfig.WLDP_SECURE_SETTING_VALUE_TYPE.WldpFlag: + break; + default: + break; + } + } + + // Free up the resources + Marshal.FreeHGlobal(ProviderUS.Buffer); + Marshal.FreeHGlobal(KeyUS.Buffer); + Marshal.FreeHGlobal(ValueNameUS.Buffer); + Marshal.FreeHGlobal(Value); + + return new SecurePolicySetting( + decodedValue, + ValueType, + ValueSize, + result == 0, + result + ); + } + + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Main Cmdlets/GetCiFileHashes.cs b/WDACConfig/WinUI3/Shared Logics/Main Cmdlets/GetCiFileHashes.cs new file mode 100644 index 000000000..d3abc2bd4 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Main Cmdlets/GetCiFileHashes.cs @@ -0,0 +1,89 @@ +using System; +using System.Globalization; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +#nullable enable + +namespace WDACConfig +{ + public static class CiFileHash + { + /// + /// Method that outputs all 4 kinds of hashes + /// + /// The path to the file that is going to be hashed + /// WDACConfig.CodeIntegrityHashes object that contains all 4 kinds of hashes + public static CodeIntegrityHashes GetCiFileHashes(string filePath) + { + return new CodeIntegrityHashes( + PageHashCalculator.GetPageHash("SHA1", filePath), + PageHashCalculator.GetPageHash("SHA256", filePath), + GetAuthenticodeHash(filePath, "SHA1"), + GetAuthenticodeHash(filePath, "SHA256") + ); + } + + private static string? GetAuthenticodeHash(string filePath, string hashAlgorithm) + { + // A StringBuilder object to store the hash value as a hexadecimal string + StringBuilder hashString = new(64); + IntPtr contextHandle = IntPtr.Zero; + IntPtr hashValue = IntPtr.Zero; + + try + { + using FileStream fileStream = File.OpenRead(filePath); + // DangerousGetHandle returns the handle to the file stream + nint fileStreamHandle = fileStream.SafeFileHandle.DangerousGetHandle(); + + if (fileStreamHandle == IntPtr.Zero) + { + return null; + } + + if (!WinTrust.CryptCATAdminAcquireContext2(ref contextHandle, IntPtr.Zero, hashAlgorithm, IntPtr.Zero, 0)) + { + throw new InvalidOperationException($"Could not acquire context for {hashAlgorithm}"); + } + + int hashSize = 0; + + if (!WinTrust.CryptCATAdminCalcHashFromFileHandle3(contextHandle, fileStreamHandle, ref hashSize, IntPtr.Zero, WinTrust.CryptcatadminCalchashFlagNonconformantFilesFallbackFlat)) + { + throw new InvalidOperationException($"Could not hash {filePath} using {hashAlgorithm}"); + } + + hashValue = Marshal.AllocHGlobal(hashSize); + + if (!WinTrust.CryptCATAdminCalcHashFromFileHandle3(contextHandle, fileStreamHandle, ref hashSize, hashValue, WinTrust.CryptcatadminCalchashFlagNonconformantFilesFallbackFlat)) + { + throw new InvalidOperationException($"Could not hash {filePath} using {hashAlgorithm}"); + } + + for (int offset = 0; offset < hashSize; offset++) + { + // Marshal.ReadByte returns a byte from the hashValue buffer at the specified offset + byte b = Marshal.ReadByte(hashValue, offset); + // Append the byte to the hashString as a hexadecimal string + _ = hashString.Append(b.ToString("X2", CultureInfo.InvariantCulture)); + } + } + finally + { + if (hashValue != IntPtr.Zero) + { + Marshal.FreeHGlobal(hashValue); + } + + if (contextHandle != IntPtr.Zero) + { + _ = WinTrust.CryptCATAdminReleaseContext(contextHandle, 0); + } + } + + return hashString.ToString(); + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Main Cmdlets/TestCiPolicy.cs b/WDACConfig/WinUI3/Shared Logics/Main Cmdlets/TestCiPolicy.cs new file mode 100644 index 000000000..06922ae4d --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Main Cmdlets/TestCiPolicy.cs @@ -0,0 +1,112 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Security.Cryptography.Pkcs; +using System.Security.Cryptography.X509Certificates; +using System.Xml; +using System.Xml.Schema; + +#nullable enable + +namespace WDACConfig +{ + public static class CiPolicyTest + { + public static object? TestCiPolicy(string xmlFilePath, string cipFilePath) + { + // Make sure the parameters are mutually exclusive + if (!string.IsNullOrEmpty(xmlFilePath) && !string.IsNullOrEmpty(cipFilePath)) + { + throw new ArgumentException("Only one of xmlFilePath or cipFilePath should be provided."); + } + + // Check if XML file path was provided + if (!string.IsNullOrEmpty(xmlFilePath)) + { + // Get the Code Integrity Schema file path + string schemaPath = WDACConfig.GlobalVars.CISchemaPath; + + // Make sure the schema file exists + if (!File.Exists(schemaPath)) + { + throw new FileNotFoundException($"The Code Integrity Schema file could not be found at: {schemaPath}"); + } + + // Make sure the input XML file exists + if (!File.Exists(xmlFilePath)) + { + throw new FileNotFoundException($"The file {xmlFilePath} does not exist."); + } + + // Validate XML file against schema + try + { + // Create the XmlReaderSettings object + XmlReaderSettings settings = new(); + + // Add schema to settings + _ = settings.Schemas.Add(null, schemaPath); + + // Set the validation settings + settings.ValidationType = ValidationType.Schema; + + // Set the validation flags to report warnings + settings.ValidationFlags |= XmlSchemaValidationFlags.ReportValidationWarnings; + + // Set the validation event handler + settings.ValidationEventHandler += (sender, args) => + { + throw new XmlSchemaValidationException($"Validation error in {xmlFilePath}: {args.Message}"); + }; + + // Create an XmlDocument object + XmlDocument xmlDoc = new(); + // Load the input XML document + xmlDoc.Load(xmlFilePath); + + using XmlReader reader = XmlReader.Create(new StringReader(xmlDoc.OuterXml), settings); + // Validate the XML document + while (reader.Read()) { } + + return true; + } + catch (XmlSchemaValidationException ex) + { + throw new InvalidOperationException($"Validation error in {xmlFilePath}: {ex.Message}"); + } + } + + // Check if CIP file path was provided + else if (!string.IsNullOrEmpty(cipFilePath)) + { + // Code to read signed CIP file + try + { + // Create a new SignedCms object to store the signed message + SignedCms signedCms = new(); + + // Decode the signed message from the file specified by cipFilePath + // The file is read as a byte array because the SignedCms.Decode() method expects a byte array as input + // https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.pkcs.signedcms.decode + signedCms.Decode(File.ReadAllBytes(cipFilePath)); + + X509Certificate2Collection certificates = signedCms.Certificates; + X509Certificate2[] certificateArray = new X509Certificate2[certificates.Count]; + certificates.CopyTo(certificateArray, 0); + + // Return an array of X509Certificate2 objects that represent the certificates used to sign the message + return certificateArray; + } + catch (CryptographicException) + { + // "The file cipFilePath does not contain a valid signature." + return null; + } + } + else + { + throw new InvalidOperationException("Either xmlFilePath or cipFilePath must be provided."); + } + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/AllCertificatesGrabber.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/AllCertificatesGrabber.cs new file mode 100644 index 000000000..7acb4272b --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/AllCertificatesGrabber.cs @@ -0,0 +1,335 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Security.Cryptography.Pkcs; +using System.Security.Cryptography.X509Certificates; + +#nullable enable + +// The following functions and methods use the Windows APIs to grab all of the certificates from a signed file + +namespace WDACConfig +{ + + // a class to throw a custom exception when the certificate has HashMismatch + public class ExceptionHashMismatchInCertificate(string message, string functionName) : Exception($"{functionName}: {message}") + { + } + + // Represents a signed CMS and its certificate chain + public class AllFileSigners(SignedCms signerCertificate, X509Chain certificateChain) + { + public SignedCms Signer { get; } = signerCertificate; + public X509Chain Chain { get; } = certificateChain; + } + + public static class AllCertificatesGrabber + { + // Structure defining signer information for cryptographic providers + // https://learn.microsoft.com/en-us/windows/win32/api/wintrust/ns-wintrust-crypt_provider_sgnr + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct CryptProviderSigner + { + private uint cbStruct; // Size of structure + private System.Runtime.InteropServices.ComTypes.FILETIME sftVerifyAsOf; // Verification time + private uint csCertChain; // Number of certificates in the chain + private IntPtr pasCertChain; // Pointer to certificate chain + private uint dwSignerType; // Type of signer + private IntPtr psSigner; // Pointer to signer + private uint dwError; // Error code + internal uint csCounterSigners; // Number of countersigners + internal IntPtr pasCounterSigners; // Pointer to countersigners + public IntPtr pChainContext; // Pointer to chain context + } + + // Structure defining provider data for cryptographic operations + // https://learn.microsoft.com/en-us/windows/win32/api/wintrust/ns-wintrust-crypt_provider_data + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct CryptProviderData + { + private uint cbStruct; // Size of structure + private IntPtr pWintrustData; // Pointer to WinTrustData + private bool fOpenedFile; // Flag indicating if file is open + private IntPtr hWndParent; // Handle to parent window + private IntPtr pgActionId; // Pointer to action ID + private IntPtr hProv; // Handle to provider + private uint dwError; // Error code + private uint dwRegSecuritySettings; // Security settings + private uint dwRegPolicySettings; // Policy settings + private IntPtr psPfns; // Pointer to provider functions + private uint cdwTrustStepErrors; // Number of trust step errors + private IntPtr padwTrustStepErrors; // Pointer to trust step errors + private uint chStores; // Number of stores + private IntPtr pahStores; // Pointer to stores + private uint dwEncoding; // Encoding type + public IntPtr hMsg; // Handle to message + public uint csSigners; // Number of signers + public IntPtr pasSigners; // Pointer to signers + } + + // Structure defining signature settings for WinTrust + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public class WinTrustSignatureSettings + { + public uint cbStruct = (uint)Marshal.SizeOf(typeof(WinTrustSignatureSettings)); // Size of structure + public uint dwIndex; // Index of the signature + public uint dwFlags = 3; // Flags for signature verification + public uint SecondarySignersCount; // Number of secondary signatures + public uint dwVerifiedSigIndex; // Index of verified signature + public IntPtr pCryptoPolicy = IntPtr.Zero; // Pointer to cryptographic policy + + // Default constructor initializes dwIndex to unsigned integer 0 + public WinTrustSignatureSettings() + { + dwIndex = 0U; + } + + // Constructor initializes with given index + public WinTrustSignatureSettings(uint index) + { + dwIndex = index; + } + } + + // Structure defining file information for WinTrust + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public class FileInfoForWinTrust + { + private uint StructSize = (uint)Marshal.SizeOf(typeof(FileInfoForWinTrust)); // Size of structure + private IntPtr FilePath; // File path pointer + private IntPtr hFile = IntPtr.Zero; // File handle pointer + private IntPtr pgKnownSubject = IntPtr.Zero; // Pointer to known subject + + // Default constructor initializes FilePath to null + public FileInfoForWinTrust() + { + FilePath = IntPtr.Zero; + } + + // Constructor initializes FilePath with the given filePath + public FileInfoForWinTrust(string filePath) + { + FilePath = Marshal.StringToCoTaskMemAuto(filePath); + } + + // Destructor frees allocated memory for FilePath + ~FileInfoForWinTrust() + { + Marshal.FreeCoTaskMem(FilePath); + } + } + + // Structure defining overall trust data for WinTrust + // https://learn.microsoft.com/en-us/windows/win32/api/wintrust/ns-wintrust-wintrust_data + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public class WinTrustData + { + public uint StructSize = (uint)Marshal.SizeOf(typeof(WinTrustData)); // Size of structure + public IntPtr PolicyCallbackData = IntPtr.Zero; // Pointer to policy callback data + public IntPtr SIPClientData = IntPtr.Zero; // Pointer to SIP client data + public uint UIChoice = 2; // UI choice for trust verification + public uint RevocationChecks; // Revocation checks + public uint UnionChoice = 1; // Union choice for trust verification + public IntPtr FileInfoPtr; // Pointer to file information + public uint StateAction = WinTrust.StateActionVerify; // State action for trust verification + public IntPtr StateData = IntPtr.Zero; // Pointer to state data + [MarshalAs(UnmanagedType.LPTStr)] + private string? URLReference; // URL reference for trust verification + public uint ProvFlags = 4112; // Provider flags for trust verification + public uint UIContext; // UI context for trust verification + public IntPtr pSignatureSettings; // Pointer to signature settings + + // Constructor initializes with file path and index + public WinTrustData(string filepath, uint Index) + { + // Initialize FileInfoForWinTrust + FileInfoPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(FileInfoForWinTrust))); + + // Initialize pSignatureSettings + pSignatureSettings = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(WinTrustSignatureSettings))); + + // Convert TrustedData to pointer and assign to FileInfoPtr + Marshal.StructureToPtr(new FileInfoForWinTrust(filepath), FileInfoPtr, false); + + // Convert providerData to pointer and assign to pSignatureSettings + Marshal.StructureToPtr(new WinTrustSignatureSettings(Index), pSignatureSettings, false); + } + + // Destructor frees allocated memory for FileInfoPtr and pSignatureSettings + ~WinTrustData() + { + Marshal.FreeCoTaskMem(FileInfoPtr); // Free memory allocated to FileInfoPtr + Marshal.FreeCoTaskMem(pSignatureSettings); // Free memory allocated to pSignatureSettings + } + } + + // Interop with crypt32.dll for cryptographic functions + internal static class Crypt32DLL + { + internal const int EncodedMessageParameter = 29; + + // External method declaration for CryptMsgGetParam + [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] + // https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptmsggetparam + internal static extern bool CryptMsgGetParam( + IntPtr hCryptMsg, + int dwParamType, + int dwIndex, + byte[]? pvData, + ref int pcbData + ); + } + + // This is the main method used to retrieve all signers for a given file + public static List GetAllFileSigners(string FilePath) + { + // List to hold all file signers + List AllFileSigners = []; + uint maxSigners = uint.MaxValue; // Maximum number of signers to process, initially set to maximum possible value + uint Index = 0; // Index of the current signer being processed + + do + { + WinTrustData? TrustedData = null; // Declare a WinTrustData structure variable + IntPtr winTrustDataPointer = IntPtr.Zero; // Pointer to WinTrustData structure + + try + { + // Initialize WinTrustData structure for file at FilePath and given index + TrustedData = new WinTrustData(FilePath, Index); + + // Allocate memory for WinTrustData structure and convert TrustedData to a pointer + winTrustDataPointer = Marshal.AllocHGlobal(Marshal.SizeOf(TrustedData)); + Marshal.StructureToPtr(TrustedData, winTrustDataPointer, false); + + // Call WinVerifyTrust to verify trust on the file + WinTrust.WinVerifyTrustResult verifyTrustResult = WinTrust.WinVerifyTrust( + IntPtr.Zero, + WinTrust.GenericWinTrustVerifyActionGuid, + winTrustDataPointer + ); + + // Update TrustedData with data from the pointer + Marshal.PtrToStructure(winTrustDataPointer, TrustedData); + + // Check signature settings and process the signer's certificate + if (maxSigners == uint.MaxValue) + { + // First, checking if TrustedData.pSignatureSettings is not IntPtr.Zero (which means it is not null) + if (TrustedData.pSignatureSettings != IntPtr.Zero) + { + // Using the generic overload of Marshal.PtrToStructure for better type safety and performance + var signatureSettings = Marshal.PtrToStructure(TrustedData.pSignatureSettings); + + // Ensuring that the structure is not null before accessing its members + if (signatureSettings != null) + { + maxSigners = signatureSettings.SecondarySignersCount; + } + } + } + + // If the certificate is expired, continue to the next iteration + if (verifyTrustResult == WinTrust.WinVerifyTrustResult.CertExpired) + { + continue; + } + + // if there is a hash mismatch in the file, throw an exception + if (verifyTrustResult == WinTrust.WinVerifyTrustResult.HashMismatch) + { + // Throw a custom exception that will be caught by Invoke-WDACPolicySimulation cmdlet + throw new ExceptionHashMismatchInCertificate($"WinTrust return code: {verifyTrustResult}", "The file is tampered with and there is a Hash Mismatch."); + } + + // If there is valid state data + if (TrustedData.StateData != IntPtr.Zero) + { + // Get provider data from state data + CryptProviderData providerData = Marshal.PtrToStructure(WinTrust.WTHelperProvDataFromStateData(TrustedData.StateData)); + + int pcbData = 0; // Size of data in bytes + + // https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptmsggetparam + // Get size of encoded message + if (providerData.hMsg != IntPtr.Zero && Crypt32DLL.CryptMsgGetParam( + providerData.hMsg, // Handle to the cryptographic message + Crypt32DLL.EncodedMessageParameter, // Parameter type to retrieve (encoded message) + 0, // Index of the parameter to retrieve + null, // Pointer to the buffer that receives the data (null to get the size) + ref pcbData // Size of the data in bytes (output parameter) + ) + ) + { + // Array to hold encoded message data + byte[] numArray = new byte[pcbData]; + + // Retrieve the encoded message and decode it + if (Crypt32DLL.CryptMsgGetParam( + providerData.hMsg, // Handle to the cryptographic message + Crypt32DLL.EncodedMessageParameter, // Parameter type to retrieve (encoded message) + 0, // Index of the parameter to retrieve + numArray, // Pointer to the buffer that receives the data + ref pcbData // Size of the data in bytes (output parameter) + ) + ) + { + // Initialize SignedCms object and decode the encoded message + SignedCms signerCertificate = new(); + signerCertificate.Decode(numArray); + + // Initialize X509Chain object based on signer's certificate chain context + // Check if csSigners is less than or equal to 0 + if (providerData.csSigners <= 0U) + { + // If csSigners is 0 or negative, create a new X509Chain without parameters + using X509Chain certificateChain = new(); + + // Add signer's certificate and certificate chain to AllFileSigners list + AllFileSigners.Add(new AllFileSigners(signerCertificate, certificateChain)); + } + else + { + // Otherwise, get the CryptProviderSigner structure from pasSigners pointer + // Using the generic overload to marshal the structure for better performance and type safety + CryptProviderSigner signer = Marshal.PtrToStructure(providerData.pasSigners); + + // Initialize X509Chain with the pChainContext from the signer structure + using X509Chain certificateChain = new(signer.pChainContext); + + // Add signer's certificate and certificate chain to AllFileSigners list + AllFileSigners.Add(new AllFileSigners(signerCertificate, certificateChain)); + } + } + } + } + } + finally + { + +#pragma warning disable CA1508 // Avoid dead conditional code + + if (TrustedData != null) + { + // Set StateAction to close the WinTrustData structure + TrustedData.StateAction = WinTrust.StateActionClose; + + // Convert TrustedData back to pointer and call WinVerifyTrust to close the structure + Marshal.StructureToPtr(TrustedData, winTrustDataPointer, false); + _ = WinTrust.WinVerifyTrust(IntPtr.Zero, WinTrust.GenericWinTrustVerifyActionGuid, winTrustDataPointer); + } + +#pragma warning restore CA1508 // Avoid dead conditional code + + // Free memory allocated to winTrustDataPointer + Marshal.FreeHGlobal(winTrustDataPointer); + + // Increment Index for the next signer + Index++; + } + } while (Index < maxSigners + 1U); // Continue loop until all signers are processed + + return AllFileSigners; // Return list of all file signers + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/CIPolicyVersion.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/CIPolicyVersion.cs new file mode 100644 index 000000000..b959bde57 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/CIPolicyVersion.cs @@ -0,0 +1,46 @@ +using System; + +#nullable enable + +namespace WDACConfig +{ + public static class CIPolicyVersion + { + /// + /// Converts a 64-bit unsigned integer into a version type, used for converting the numbers from CiTool.exe output to proper versions. + /// + /// The 64-bit unsigned integer as a string. + /// The version string in the format 'part1.part2.part3.part4'. + public static string Measure(string number) + { + try + { + // Validate input + if (string.IsNullOrEmpty(number)) + { + throw new ArgumentException("Input number cannot be null or empty."); + } + + // Convert the string to a 64-bit integer + if (!ulong.TryParse(number, out ulong num)) + { + throw new FormatException("Input string is not a valid 64-bit unsigned integer."); + } + + // Extract the version parts by splitting the 64-bit integer into four 16-bit segments and convert each segment to its respective part of the version number + ushort part1 = (ushort)((num & 0xFFFF000000000000) >> 48); // mask isolates the highest 16 bits of a 64-bit number. + ushort part2 = (ushort)((num & 0x0000FFFF00000000) >> 32); // mask isolates the next 16 bits. + ushort part3 = (ushort)((num & 0x00000000FFFF0000) >> 16); // mask isolates the third set of 16 bits. + ushort part4 = (ushort)(num & 0x000000000000FFFF); // mask isolates the lowest 16 bits. + + // Form the version string + return $"{part1}.{part2}.{part3}.{part4}"; + } + catch (Exception ex) + { + WDACConfig.Logger.Write($"Error converting number to version: {ex.Message}"); + return number; + } + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/CertificateHelper.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/CertificateHelper.cs new file mode 100644 index 000000000..e9ed356d1 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/CertificateHelper.cs @@ -0,0 +1,111 @@ +using System; +using System.Formats.Asn1; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; + +#nullable enable + +namespace WDACConfig +{ + // A class to throw a custom exception when the certificate collection cannot be obtained during WDAC Simulation + public class ExceptionFailedToGetCertificateCollection(string message, string functionName) : Exception($"{functionName}: {message}") + { + } + + public class CertificateHelper + { + public static string GetTBSCertificate(X509Certificate2 cert) + // Calculates the TBS value of a certificate + { + // Get the raw data of the certificate + byte[] rawData = cert.RawData; + + // Create an ASN.1 reader to parse the certificate + AsnReader asnReader = new(rawData, AsnEncodingRules.DER); + + // Read the certificate sequence + AsnReader certificate = asnReader.ReadSequence(); + + // Read the TBS (To be signed) value of the certificate + ReadOnlyMemory tbsCertificate = certificate.ReadEncodedValue(); + + // Read the signature algorithm sequence + AsnReader signatureAlgorithm = certificate.ReadSequence(); + + // Read the algorithm OID of the signature + string algorithmOid = signatureAlgorithm.ReadObjectIdentifier(); + + // Define a hash function based on the algorithm OID + HashAlgorithm hashFunction = algorithmOid switch + { + "1.2.840.113549.1.1.4" => MD5.Create(), + "1.2.840.113549.1.1.5" or "1.3.14.3.2.29" => SHA1.Create(), // sha-1WithRSAEncryption + "1.2.840.113549.1.1.11" => SHA256.Create(), + "1.2.840.113549.1.1.12" => SHA384.Create(), + "1.2.840.113549.1.1.13" => SHA512.Create(), + // These are less likely to be used since Code Integrity doesn't support their OIDs + "1.2.840.10040.4.3" => SHA1.Create(), + "2.16.840.1.101.3.4.3.2" => SHA256.Create(), + "2.16.840.1.101.3.4.3.3" => SHA384.Create(), + "2.16.840.1.101.3.4.3.4" => SHA512.Create(), + "1.2.840.10045.4.1" => SHA1.Create(), + "1.2.840.10045.4.3.2" => SHA256.Create(), + "1.2.840.10045.4.3.3" => SHA384.Create(), + "1.2.840.10045.4.3.4" => SHA512.Create(), + _ => throw new InvalidOperationException($"No handler for algorithm {algorithmOid}"), + }; + using (hashFunction) + { + // Compute the hash of the TBS value using the hash function + byte[] hash = hashFunction.ComputeHash(tbsCertificate.ToArray()); + + // Convert the hash to a hex string + string hexStringOutput = Convert.ToHexString(hash); + + return hexStringOutput; + } + } + + public static string ConvertHexToOID(string hex) + // Converts a hexadecimal string to an OID + // Used for converting hexadecimal values found in the EKU sections of the WDAC policies to their respective OIDs. + { + if (string.IsNullOrEmpty(hex)) + { + throw new ArgumentException("Hex string cannot be null or empty", nameof(hex)); + } + + // Convert the hexadecimal string to a byte array by looping through the string in pairs of two characters + // and converting each pair to a byte using the base 16 (hexadecimal) system + byte[] numArray = new byte[hex.Length / 2]; + for (int i = 0; i < hex.Length; i += 2) + { + numArray[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16); + } + + // Change the first byte from 1 to 6 because the hexadecimal string is missing the tag and length bytes + // that are required for the ASN.1 encoding of an OID + // The tag byte indicates the type of the data, and for an OID it is 6 + // The length byte indicates the number of bytes that follow the tag byte + // and for this example it is 10 (0A in hexadecimal) + numArray[0] = 6; + + // Create an AsnReader object with the default encoding rules + // This is a class that can read the ASN.1 BER, CER, and DER data formats + // BER (Basic Encoding Rules) is the most flexible and widely used encoding rule + // CER (Canonical Encoding Rules) is a subset of BER that ensures a unique encoding + // DER (Distinguished Encoding Rules) is a subset of CER that ensures a deterministic encoding + // The AsnReader object takes the byte array as input and the encoding rule as an argument + AsnReader asnReader = new(numArray, AsnEncodingRules.BER); + + // Read the OID as an ObjectIdentifier + // This is a method of the AsnReader class that returns the OID as a string + // The first two numbers are derived from the first byte of the encoded data + // The rest of the numbers are derived from the subsequent bytes using a base 128 (variable-length) system + string oid = asnReader.ReadObjectIdentifier(); + + // Return the OID value as string + return oid; + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/CiPolicyUtility.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/CiPolicyUtility.cs new file mode 100644 index 000000000..7dede707a --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/CiPolicyUtility.cs @@ -0,0 +1,66 @@ +using System; +using System.IO; +using System.Xml; + +#nullable enable + +namespace WDACConfig +{ + public static class CiPolicyUtility + { + /// + /// Copies the rules from one CI policy XML file to another. + /// + /// The source CI policy XML file path. + /// The destination CI policy XML file path. + public static void CopyCiRules(string sourceFilePath, string destinationFilePath) + { + // Validate file paths + if (string.IsNullOrWhiteSpace(sourceFilePath)) + { + throw new ArgumentException("Source file path cannot be null or empty.", nameof(sourceFilePath)); + } + if (string.IsNullOrWhiteSpace(destinationFilePath)) + { + throw new ArgumentException("Destination file path cannot be null or empty.", nameof(destinationFilePath)); + } + if (!File.Exists(sourceFilePath)) + { + throw new FileNotFoundException("Source file not found.", sourceFilePath); + } + if (!File.Exists(destinationFilePath)) + { + throw new FileNotFoundException("Destination file not found.", destinationFilePath); + } + + // Load the XML files as XmlDocument objects + XmlDocument sourceXmlDoc = new(); + sourceXmlDoc.Load(sourceFilePath); + + XmlDocument destinationXmlDoc = new(); + destinationXmlDoc.Load(destinationFilePath); + + // Create an XmlNamespaceManager to handle the default namespace + XmlNamespaceManager nsmgr = new(sourceXmlDoc.NameTable); + nsmgr.AddNamespace("ns", "urn:schemas-microsoft-com:sipolicy"); + + // Select the Rules node in the source XML document + XmlNode? sourceRulesNode = sourceXmlDoc.SelectSingleNode("/ns:SiPolicy/ns:Rules", nsmgr) ?? throw new InvalidOperationException("The node was not found in the source XML file."); + + // Select the SiPolicy node in the destination XML document + XmlNode? destinationSiPolicyNode = destinationXmlDoc.SelectSingleNode("/ns:SiPolicy", nsmgr) ?? throw new InvalidOperationException("The node was not found in the destination XML file."); + + // Select the existing Rules node in the destination XML document + XmlNode? destinationRulesNode = destinationSiPolicyNode.SelectSingleNode("ns:Rules", nsmgr) ?? throw new InvalidOperationException("The node was not found in the destination XML file."); + + // Replace the rules block in destinationXmlDoc with the rules block in sourceXmlDoc + // Use the ImportNode method to create a copy of the rules node from $SourceFileContent + // The second parameter ($true) indicates a deep clone, meaning that the node and its descendants are copied + // https://learn.microsoft.com/en-us/dotnet/api/system.xml.xmldocument.importnode + XmlNode importedRulesNode = destinationXmlDoc.ImportNode(sourceRulesNode, true); + _ = destinationSiPolicyNode.ReplaceChild(importedRulesNode, destinationRulesNode); + + destinationXmlDoc.Save(destinationFilePath); + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/CodeIntegritySigner.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/CodeIntegritySigner.cs new file mode 100644 index 000000000..9b2f914ee --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/CodeIntegritySigner.cs @@ -0,0 +1,68 @@ +using System; +using System.Diagnostics; +using System.IO; + +#nullable enable + +namespace WDACConfig +{ + public static class CodeIntegritySigner + { + /// + /// Invokes SignTool.exe to sign a Code Integrity Policy file. + /// + /// + /// + /// + /// + /// + public static void InvokeCiSigning(FileInfo ciPath, FileInfo signToolPathFinal, string certCN) + { + // Validate inputs + ArgumentNullException.ThrowIfNull(ciPath); + ArgumentNullException.ThrowIfNull(signToolPathFinal); + if (string.IsNullOrEmpty(certCN)) throw new ArgumentException("Certificate Common Name cannot be null or empty.", nameof(certCN)); + + // Build the arguments for the process + string arguments = $"sign /v /n \"{certCN}\" /p7 . /p7co 1.3.6.1.4.1.311.79.1 /fd certHash \"{ciPath.Name}\""; + + // Set up the process start info + ProcessStartInfo startInfo = new() + { + FileName = signToolPathFinal.FullName, + Arguments = arguments, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + WorkingDirectory = ciPath.DirectoryName // Set the working directory so that SignTool.exe will know where the .cip file is and where to save the output + }; + + // Start the process + using Process process = new() { StartInfo = startInfo }; + _ = process.Start(); + + // Wait for the process to exit + process.WaitForExit(); + + // Read the output and error streams + string output = process.StandardOutput.ReadToEnd(); + string error = process.StandardError.ReadToEnd(); + + // Log the output and error + WDACConfig.Logger.Write(output); + + // Check if there is any error and throw an exception if there is + if (!string.IsNullOrEmpty(error)) + { + throw new InvalidOperationException($"SignTool failed with exit code {process.ExitCode}. Error: {error}"); + } + + // Check the exit code + if (process.ExitCode != 0) + { + throw new InvalidOperationException($"SignTool failed with exit code {process.ExitCode}. Error: {error}"); + } + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/Crypt32CertCN.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/Crypt32CertCN.cs new file mode 100644 index 000000000..e90fb77ef --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/Crypt32CertCN.cs @@ -0,0 +1,68 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +#nullable enable + +#pragma warning disable CA1838 // Avoid 'StringBuilder' parameters for P/Invoke methods + +namespace WDACConfig +{ + public class CryptoAPI + { + // Importing function from crypt32.dll to access certificate information + // https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certgetnamestringa + [DllImport("crypt32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern bool CertGetNameString( + IntPtr pCertContext, // the handle property of the certificate object + int dwType, + int dwFlags, + IntPtr pvTypePara, + StringBuilder pszNameString, + int cchNameString + ); + + // Define constants for the name types + public const int CERT_NAME_SIMPLE_DISPLAY_TYPE = 4; // Display type for simple names + public const int CERT_NAME_ATTR_TYPE = 3; // Display type for attributes + public const int CERT_NAME_ISSUER_FLAG = 0x1; // Flag indicating that the issuer name should be retrieved + + // Define a helper method to get the name string + public static string GetNameString(IntPtr pCertContext, int dwType, string? pvTypePara, bool isIssuer) + { + // Allocate a buffer for the name string, setting it big to handle longer names if needed + const int bufferSize = 1024; + StringBuilder nameString = new(bufferSize); + + // Convert the pvTypePara to a pointer if needed + IntPtr pvTypeParaPtr = IntPtr.Zero; + if (!string.IsNullOrEmpty(pvTypePara)) + { + // Using Unicode encoding for better compatibility + pvTypeParaPtr = Marshal.StringToHGlobalUni(pvTypePara); + } + + // Set flags to retrieve issuer name if needed + int flags = isIssuer ? CERT_NAME_ISSUER_FLAG : 0; + + // Call the CertGetNameString function to get the name string + bool result = CertGetNameString( + pCertContext, + dwType, + flags, + pvTypeParaPtr, + nameString, + nameString.Capacity + ); + + // Free the pointer if allocated + if (pvTypeParaPtr != IntPtr.Zero) + { + Marshal.FreeHGlobal(pvTypeParaPtr); + } + + // Return the name string or an empty string if failed + return result ? nameString.ToString() : string.Empty; + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/DriveLetterMapper.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/DriveLetterMapper.cs new file mode 100644 index 000000000..8a8349140 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/DriveLetterMapper.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +#nullable enable + +#pragma warning disable CA1838 // Avoid 'StringBuilder' parameters for P/Invoke methods + +namespace WDACConfig +{ + public static class DriveLetterMapper + { + // Importing the GetVolumePathNamesForVolumeNameW function from kernel32.dll + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool GetVolumePathNamesForVolumeNameW( + [MarshalAs(UnmanagedType.LPWStr)] string lpszVolumeName, + [MarshalAs(UnmanagedType.LPWStr)][Out] StringBuilder lpszVolumeNamePaths, + uint cchBuferLength, + ref UInt32 lpcchReturnLength); + + // Importing the FindFirstVolume function from kernel32.dll + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern IntPtr FindFirstVolume( + [Out] StringBuilder lpszVolumeName, + uint cchBufferLength); + + // Importing the FindNextVolume function from kernel32.dll + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern bool FindNextVolume( + IntPtr hFindVolume, + [Out] StringBuilder lpszVolumeName, + uint cchBufferLength); + + // Importing the QueryDosDevice function from kernel32.dll + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern uint QueryDosDevice( + string lpDeviceName, + StringBuilder lpTargetPath, + int ucchMax); + + // Class to store drive mapping information + public class DriveMapping + { + // Property to store drive letter + public string? DriveLetter { get; set; } + // Property to store device path + public string? DevicePath { get; set; } + // Property to store volume name + public string? VolumeName { get; set; } + } + + /// + /// A method that gets the DriveLetter mappings in the global root namespace + /// And fixes these: \Device\Harddiskvolume + /// + /// A list of DriveMapping objects containing drive information + /// + public static List GetGlobalRootDrives() + { + // List to store drive mappings + var drives = new List(); + // Maximum buffer size for volume names, paths, and mount points + uint max = 65535; + // StringBuilder for storing volume names + var sbVolumeName = new StringBuilder((int)max); + // StringBuilder for storing path names + var sbPathName = new StringBuilder((int)max); + // StringBuilder for storing mount points + var sbMountPoint = new StringBuilder((int)max); + // Variable to store the length of the return string + uint lpcchReturnLength = 0; + + // Get the first volume handle + IntPtr volumeHandle = FindFirstVolume(sbVolumeName, max); + + // Check if the volume handle is valid + if (volumeHandle == IntPtr.Zero) + { + throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); + } + + // Loop through all the volumes + do + { + // Convert the volume name to a string + string volume = sbVolumeName.ToString(); + // Get the mount point for the volume + _ = GetVolumePathNamesForVolumeNameW(volume, sbMountPoint, max, ref lpcchReturnLength); + // Get the device path for the volume + uint returnLength = QueryDosDevice(volume.Substring(4, volume.Length - 5), sbPathName, (int)max); + + // Check if the device path is found + if (returnLength > 0) + { + // Add the drive mapping to the list + drives.Add(new DriveMapping + { + DriveLetter = sbMountPoint.ToString(), + VolumeName = volume, + DevicePath = sbPathName.ToString() + }); + } + else + { + // Add the drive mapping with no mount point found + drives.Add(new DriveMapping + { + DriveLetter = null, + VolumeName = volume, + DevicePath = "No mountpoint found" + }); + } + + } while (FindNextVolume(volumeHandle, sbVolumeName, max)); // Continue until there are no more volumes + + // Return the list of drive mappings + return drives; + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/DriversBlockRulesFetcher.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/DriversBlockRulesFetcher.cs new file mode 100644 index 000000000..cbe56fdcd --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/DriversBlockRulesFetcher.cs @@ -0,0 +1,70 @@ +using System; +using System.IO; +using System.Net.Http; + +#nullable enable + +namespace WDACConfig +{ + public class DriversBlockRulesFetcher + { + + /// + /// A method to fetch the Vulnerable Driver Block List from the Microsoft servers and deploy it to the system + /// + /// The directory to use for temporary files + /// + public static void Fetch(string StagingArea) + { + // The location where the downloaded zip file will be saved + string DownloadSaveLocation = System.IO.Path.Combine(StagingArea, "VulnerableDriverBlockList.zip"); + + // The location where the zip file will be extracted + string ZipExtractionDir = System.IO.Path.Combine(StagingArea, "VulnerableDriverBlockList"); + + // The link to download the zip file + string DriversBlockListZipDownloadLink = "https://aka.ms/VulnerableDriverBlockList"; + + // Get the system drive + string? systemDrive = Environment.GetEnvironmentVariable("SystemDrive"); + + // Initialize the final destination of the SiPolicy file + string SiPolicyFinalDestination; + if (systemDrive != null) + { + // Construct the final destination of the SiPolicy file + SiPolicyFinalDestination = System.IO.Path.Combine(systemDrive, "Windows", "System32", "CodeIntegrity", "SiPolicy.p7b"); + } + else + { + throw new InvalidOperationException("SystemDrive environment variable is null"); + } + + // Download the zip file + using (HttpClient client = new()) + { + // Download the file synchronously + byte[] fileBytes = client.GetByteArrayAsync(DriversBlockListZipDownloadLink).GetAwaiter().GetResult(); + File.WriteAllBytes(DownloadSaveLocation, fileBytes); + } + + // Extract the contents of the zip file, overwriting any existing files + System.IO.Compression.ZipFile.ExtractToDirectory(DownloadSaveLocation, ZipExtractionDir, true); + + // Get the path of the SiPolicy file + string[] SiPolicyPaths = System.IO.Directory.GetFiles(ZipExtractionDir, "SiPolicy_Enforced.p7b", System.IO.SearchOption.AllDirectories); + + // Make sure to get only one file is there is more than one (which is unexpected) + string SiPolicyPath = SiPolicyPaths[0]; + + // If the SiPolicy file already exists, delete it + if (File.Exists(SiPolicyFinalDestination)) + { + File.Delete(SiPolicyFinalDestination); + } + + // Move the SiPolicy file to the final destination, renaming it in the process + File.Move(SiPolicyPath, SiPolicyFinalDestination); + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/EditGUIDs.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/EditGUIDs.cs new file mode 100644 index 000000000..0d1475c31 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/EditGUIDs.cs @@ -0,0 +1,43 @@ +using System.IO; +using System.Xml; + +#nullable enable + +namespace WDACConfig +{ + public static class PolicyEditor + { + public static void EditGuids(string policyIdInput, FileInfo policyFilePathInput) + // Swaps the PolicyID and BasePolicyID GUIDs in a WDAC policy XML file for Base policies. + // Shouldn't be used for supplemental policies. + { + string policyId = "{" + policyIdInput + "}"; + + // Load the XML document + XmlDocument xmlDoc = new(); + xmlDoc.Load(policyFilePathInput.FullName); + + // Define the new values for PolicyID and BasePolicyID + string newPolicyId = policyId; + string newBasePolicyId = policyId; + + // Select the nodes and update their values + XmlNamespaceManager nsMgr = new(xmlDoc.NameTable); + nsMgr.AddNamespace("ns", "urn:schemas-microsoft-com:sipolicy"); + + XmlNode? policyIdNode = xmlDoc.SelectSingleNode("/ns:SiPolicy/ns:PolicyID", nsMgr); + if (policyIdNode != null) + { + policyIdNode.InnerText = newPolicyId; + } + + XmlNode? basePolicyIdNode = xmlDoc.SelectSingleNode("/ns:SiPolicy/ns:BasePolicyID", nsMgr); + if (basePolicyIdNode != null) + { + basePolicyIdNode.InnerText = newBasePolicyId; + } + + xmlDoc.Save(policyFilePathInput.FullName); + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/EventLogUtility.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/EventLogUtility.cs new file mode 100644 index 000000000..ea9d82081 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/EventLogUtility.cs @@ -0,0 +1,60 @@ +using System; +using System.Diagnostics.Eventing.Reader; +using System.IO; + +#nullable enable + +namespace WDACConfig +{ + public static class EventLogUtility + { + /// + /// Increase Code Integrity Operational Event Logs size from the default 1MB to user-defined size. + /// Also automatically increases the log size by 1MB if the current free space is less than 1MB and the current maximum log size is less than or equal to 10MB. + /// This is to prevent infinitely expanding the max log size automatically. + /// + /// Size of the Code Integrity Operational Event Log + public static void SetLogSize(ulong logSize = 0) + { + WDACConfig.Logger.Write("Set-SetLogSize method started..."); + + string logName = "Microsoft-Windows-CodeIntegrity/Operational"; + + using var logConfig = new EventLogConfiguration(logName); + string logFilePath = Environment.ExpandEnvironmentVariables(logConfig.LogFilePath); + FileInfo logFileInfo = new(logFilePath); + long currentLogFileSize = logFileInfo.Length; + long currentLogMaxSize = logConfig.MaximumSizeInBytes; + + if (logSize == 0) + { + if ((currentLogMaxSize - currentLogFileSize) < 1 * 1024 * 1024) + { + if (currentLogMaxSize <= 10 * 1024 * 1024) + { + WDACConfig.Logger.Write("Increasing the Code Integrity log size by 1MB because its current free space is less than 1MB."); + logConfig.MaximumSizeInBytes = currentLogMaxSize + 1 * 1024 * 1024; + logConfig.IsEnabled = true; + logConfig.SaveChanges(); + } + } + } + else + { + // Check if the provided log size is greater than 1100 KB + // To prevent from disabling the log or setting it to a very small size that is lower than its default size + if (logSize > 1100 * 1024) + { + WDACConfig.Logger.Write($"Setting Code Integrity log size to {logSize}."); + logConfig.MaximumSizeInBytes = (long)logSize; + logConfig.IsEnabled = true; + logConfig.SaveChanges(); + } + else + { + WDACConfig.Logger.Write("Provided log size is less than or equal to 1100 KB. No changes made."); + } + } + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/FileDirectoryPathComparer.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/FileDirectoryPathComparer.cs new file mode 100644 index 000000000..a3db9579d --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/FileDirectoryPathComparer.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; + +#nullable enable + +namespace WDACConfig +{ + public class FileDirectoryPathComparer + { + /// + /// Method that takes 2 arrays, one containing file paths and the other containing folder paths. + /// It checks them and returns the unique file paths that are not in any of the folder paths. + /// Performs this check recursively, so it works if a filepath is in a sub-directory of a folder path. + /// It works even if the file paths or folder paths are non-existent/deleted, but they still need to be valid file/folder paths. + /// + /// Array of directory paths + /// Array of file paths + /// List of unique file paths that don't reside in any of the directory paths (or their sub-directory paths) + public static List TestFilePath(string[] directoryPaths, string[] filePaths) + { + HashSet output = new(StringComparer.OrdinalIgnoreCase); + + // Loop through each file path + foreach (string file in filePaths) + { + bool isInDirectory = false; + + // Loop through each directory path + foreach (string directory in directoryPaths) + { + // Check if the file path starts with the directory path + if (file.StartsWith(directory, StringComparison.OrdinalIgnoreCase)) + { + // The file is inside the directory or its sub-directories + isInDirectory = true; + break; + } + } + + // Output the file path if it is not inside any of the directory paths + if (!isInDirectory) + { + _ = output.Add(file); + } + } + + // Return the unique file paths that don't reside in any of the directory paths (or their sub-directory paths) + return new List(output); + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/GetExtendedFileAttrib.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/GetExtendedFileAttrib.cs new file mode 100644 index 000000000..19a4bb97f --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/GetExtendedFileAttrib.cs @@ -0,0 +1,163 @@ +using System; +using System.Globalization; +using System.Runtime.InteropServices; + +#nullable enable + +namespace WDACConfig +{ + public class ExFileInfo + { + // Constants used for encoding fallback and error handling + private const string UnicodeFallbackCode = "04B0"; + private const string Cp1252FallbackCode = "04E4"; + public const int FILE_VER_GET_NEUTRAL = 2; + public const int HR_ERROR_RESOURCE_TYPE_NOT_FOUND = -2147023083; + + // Properties to hold file information + public string? OriginalFileName { get; private set; } + public string? InternalName { get; private set; } + public string? ProductName { get; private set; } + public string? Version { get; private set; } + public string? FileDescription { get; private set; } + + // Importing external functions from Version.dll to work with file version info + // https://learn.microsoft.com/he-il/windows/win32/api/winver/nf-winver-getfileversioninfosizeexa + [DllImport("Version.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern int GetFileVersionInfoSizeEx(uint dwFlags, string filename, out int handle); + + // https://learn.microsoft.com/he-il/windows/win32/api/winver/nf-winver-verqueryvaluea + [DllImport("Version.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern bool VerQueryValue(IntPtr block, string subBlock, out IntPtr buffer, out int len); + + // https://learn.microsoft.com/he-il/windows/win32/api/winver/nf-winver-getfileversioninfoexa + [DllImport("Version.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern bool GetFileVersionInfoEx(uint dwFlags, string filename, int handle, int len, byte[] data); + + // Private constructor to prevent direct instantiation + private ExFileInfo() { } + + // Static method to get extended file info + public static ExFileInfo GetExtendedFileInfo(string filePath) + { + var ExFileInfo = new ExFileInfo(); + + // Get the size of the version information block + int versionInfoSize = GetFileVersionInfoSizeEx(FILE_VER_GET_NEUTRAL, filePath, out int handle); + if (versionInfoSize == 0) return ExFileInfo; + + // Allocate array for version data and retrieve it + var versionData = new byte[versionInfoSize]; + if (!GetFileVersionInfoEx(FILE_VER_GET_NEUTRAL, filePath, handle, versionInfoSize, versionData)) + return ExFileInfo; + + try + { + var spanData = new Span(versionData); + + // Extract version from the version data + if (!TryGetVersion(spanData, out var version)) + throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error())!; + + ExFileInfo.Version = CheckAndSetNull(version); + + // Extract locale and encoding information + if (!TryGetLocaleAndEncoding(spanData, out var locale, out var encoding)) + throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error())!; + + // Retrieve various file information based on locale and encoding + ExFileInfo.OriginalFileName = CheckAndSetNull(GetLocalizedResource(spanData, encoding!, locale!, "\\OriginalFileName")); + ExFileInfo.InternalName = CheckAndSetNull(GetLocalizedResource(spanData, encoding!, locale!, "\\InternalName")); + ExFileInfo.FileDescription = CheckAndSetNull(GetLocalizedResource(spanData, encoding!, locale!, "\\FileDescription")); + ExFileInfo.ProductName = CheckAndSetNull(GetLocalizedResource(spanData, encoding!, locale!, "\\ProductName")); + } + catch + { + // In case of an error, set all properties to null + ExFileInfo.Version = null; + ExFileInfo.OriginalFileName = null; + ExFileInfo.InternalName = null; + ExFileInfo.FileDescription = null; + ExFileInfo.ProductName = null; + } + return ExFileInfo; + } + + // Extract the version from the data + private static bool TryGetVersion(Span data, out string? version) + { + version = null; + // Query the root block for version info + if (!VerQueryValue(Marshal.UnsafeAddrOfPinnedArrayElement(data.ToArray(), 0), "\\", out var buffer, out _)) + return false; + + // Marshal the version info structure + var fileInfo = Marshal.PtrToStructure(buffer); + + // Construct version string + version = $"{fileInfo.dwFileVersionMS >> 16}.{fileInfo.dwFileVersionMS & ushort.MaxValue}.{fileInfo.dwFileVersionLS >> 16}.{fileInfo.dwFileVersionLS & ushort.MaxValue}"; + return true; + } + + // Extract locale and encoding information from the data + private static bool TryGetLocaleAndEncoding(Span data, out string? locale, out string? encoding) + { + locale = null; + encoding = null; + // Query the translation block for locale and encoding + if (!VerQueryValue(Marshal.UnsafeAddrOfPinnedArrayElement(data.ToArray(), 0), "\\VarFileInfo\\Translation", out var buffer, out _)) + return false; + + // Copy the translation values + short[] translations = new short[2]; + Marshal.Copy(buffer, translations, 0, 2); + + // Convert the translation values to hex strings + locale = translations[0].ToString("X4", CultureInfo.InvariantCulture); + encoding = translations[1].ToString("X4", CultureInfo.InvariantCulture); + return true; + } + + // Get localized resource string based on encoding and locale + private static string? GetLocalizedResource(Span versionBlock, string encoding, string locale, string resource) + { + var encodings = new[] { encoding, Cp1252FallbackCode, UnicodeFallbackCode }; + foreach (var enc in encodings) + { + var subBlock = $"StringFileInfo\\{locale}{enc}{resource}"; + if (VerQueryValue(Marshal.UnsafeAddrOfPinnedArrayElement(versionBlock.ToArray(), 0), subBlock, out var buffer, out _)) + return Marshal.PtrToStringAuto(buffer); + + // If error is not resource type not found, throw the error + if (Marshal.GetHRForLastWin32Error() != HR_ERROR_RESOURCE_TYPE_NOT_FOUND) + throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error())!; + } + return null; + } + + // Check if a string is null or whitespace and return null if it is + private static string? CheckAndSetNull(string? value) + { + return string.IsNullOrWhiteSpace(value) ? null : value; + } + + // Structure to hold file version information + [StructLayout(LayoutKind.Sequential)] + private struct FileVersionInfo + { + public uint dwSignature; + public uint dwStrucVersion; + public uint dwFileVersionMS; + public uint dwFileVersionLS; + public uint dwProductVersionMS; + public uint dwProductVersionLS; + public uint dwFileFlagsMask; + public uint dwFileFlags; + public uint dwFileOS; + public uint dwFileType; + public uint dwFileSubtype; + public uint dwFileDateMS; + public uint dwFileDateLS; + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/GetFilesFast.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/GetFilesFast.cs new file mode 100644 index 000000000..1fb43c0bb --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/GetFilesFast.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.IO; + +#nullable enable + +namespace WDACConfig +{ + public class FileUtility + { + /// + /// A flexible and fast method that can accept directory paths and file paths as input and return a list of FileInfo objects that are compliant with the WDAC policy. + /// It supports custom extensions to filter by as well. + /// + /// Directories to process. + /// Files to process. + /// Extensions to filter by. If null or empty, default WDAC supported extensions are used. + /// List of FileInfo objects. + public static List GetFilesFast( + DirectoryInfo[] directories, + FileInfo[]? files, + string[] extensionsToFilterBy) + { + + // Use the Default WDAC supported extensions and make them case-insensitive + HashSet extensions = new(StringComparer.InvariantCultureIgnoreCase) + { + ".sys", ".exe", ".com", ".dll", ".rll", ".ocx", ".msp", ".mst", ".msi", + ".js", ".vbs", ".ps1", ".appx", ".bin", ".bat", ".hxs", ".mui", ".lex", ".mof" + }; + + // If custom extensions are provided, use them and make them case-insensitive + if (extensionsToFilterBy != null && extensionsToFilterBy.Length > 0) + { + extensions = new HashSet(extensionsToFilterBy, StringComparer.InvariantCultureIgnoreCase); + } + + // Define a HashSet to store the final output + HashSet output = []; + + EnumerationOptions options = new() + { + IgnoreInaccessible = true, + RecurseSubdirectories = true, + AttributesToSkip = FileAttributes.None + }; + + // Process directories if provided + if (directories != null && directories.Length > 0) + { + foreach (DirectoryInfo directory in directories) + { + IEnumerator enumerator = directory.EnumerateFiles("*", options).GetEnumerator(); + while (true) + { + try + { + // Move to the next file + if (!enumerator.MoveNext()) + { + // If we reach the end of the enumeration, we break out of the loop + break; + } + + // Check if the file extension is in the Extensions HashSet or Wildcard was used + if (extensions.Contains(enumerator.Current.Extension) || extensions.Contains("*")) + { + _ = output.Add(enumerator.Current); + } + } + catch { } + } + } + } + + // If files are provided, process them + if (files != null && files.Length > 0) + { + foreach (FileInfo file in files) + { + if (extensions.Contains(file.Extension)) + { + _ = output.Add(file); + } + } + } + + return new List(output); + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/GetOpusData.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/GetOpusData.cs new file mode 100644 index 000000000..00b0279e1 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/GetOpusData.cs @@ -0,0 +1,249 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Security.Cryptography.Pkcs; + +#nullable enable + +namespace WDACConfig +{ + public static class Opus + { + internal static class Crypt32 + { + // Using the DllImport attribute to import functions from crypt32.dll + // More info: https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptdecodeobject + [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] + internal static extern bool CryptDecodeObject( + uint dwCertEncodingType, // Specifies the encoding type used in the encoded message + IntPtr lpszStructType, // Pointer to a null-terminated ANSI string that identifies the type of the structure to be decoded + [In] byte[] pbEncoded, // Pointer to a buffer that contains the encoded structure + uint cbEncoded, // Size, in bytes, of the pbEncoded buffer + uint dwFlags, // Flags that modify the behavior of the function + [Out] IntPtr pvStructInto, // Pointer to a buffer that receives the decoded structure + ref uint pcbStructInfo // Pointer to a variable that specifies the size, in bytes, of the pvStructInfo buffer + ); + } + + // More info about this at the end of the code + public const uint SPC_SP_OPUS_INFO_STRUCT = 2007; + // for the SpcSpOpusInfo structure + public const string SPC_SP_OPUS_INFO_OBJID = "1.3.6.1.4.1.311.2.1.12"; + + // https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-oshared/91755632-4b0d-44ca-89a9-9699afbbd268 + // Rust implementation: https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/Security/WinTrust/struct.SPC_SP_OPUS_INFO.html + public struct OpusInfoObj + { + // Declaring a public field CertOemID of type string with LPWStr marshaling + [MarshalAs(UnmanagedType.LPWStr)] + public string CertOemID; + public IntPtr PublisherInfo; + public IntPtr MoreInfo; // not always present + } + + // Declaring a public static method GetOpusData that returns a List of OpusInfoObj, taking a SignedCms parameter + // https://learn.microsoft.com/en-us/windows/win32/seccrypto/example-c-program--verifying-the-signature-of-a-pe-file + // https://view.officeapps.live.com/op/view.aspx?src=https%3A%2F%2Fdownload.microsoft.com%2Fdownload%2F9%2Fc%2F5%2F9c5b2167-8017-4bae-9fde-d599bac8184a%2FAuthenticode_PE.docx + public static List GetOpusData(SignedCms signature) + { + // Initializing a new List of OpusInfoObj to store the output data to return + List OEMOpusData = []; + + // Iterating through each SignerInfo in the SignerInfos collection of the signature + foreach (SignerInfo signerInfo in signature.SignerInfos) + { + // Iterating through each CryptographicAttributeObject in the SignedAttributes collection of the signerInfo + foreach (CryptographicAttributeObject signedAttribute in signerInfo.SignedAttributes) + { + // Checking if the OID value of the signed attribute matches the Opus SPC_SP_OPUS_INFO_OBJID + if (string.Equals(signedAttribute.Oid.Value, Opus.SPC_SP_OPUS_INFO_OBJID, StringComparison.Ordinal)) + { + // Initializing pcbStructInfo to 0 + uint pcbStructInfo = 0; + // Initializing decodedDataPtr to IntPtr.Zero + IntPtr decodedDataPtr = IntPtr.Zero; + + try + { + AsnEncodedData asnEncodedData = signedAttribute.Values[0]; // Retrieving the first value from the signed attribute's Values collection + + // Decoding ASN.1-encoded data using CryptDecodeObject + if (!Crypt32.CryptDecodeObject(65537U, (IntPtr)(long)Opus.SPC_SP_OPUS_INFO_STRUCT, asnEncodedData.RawData, (uint)asnEncodedData.RawData.Length, 0U, IntPtr.Zero, ref pcbStructInfo)) + { + // If CryptDecodeObject fails, ignore + } + else + { + // Allocating unmanaged memory to decodedDataPtr based on pcbStructInfo size + decodedDataPtr = Marshal.AllocCoTaskMem((int)pcbStructInfo); + + // Decoding ASN.1-encoded data again into decodedDataPtr + if (!Crypt32.CryptDecodeObject(65537U, (IntPtr)(long)Opus.SPC_SP_OPUS_INFO_STRUCT, asnEncodedData.RawData, (uint)asnEncodedData.RawData.Length, 0U, decodedDataPtr, ref pcbStructInfo)) + { + // If CryptDecodeObject fails, ignore + } + else + { + // Converting the unmanaged memory block to OpusInfoObj structure + Opus.OpusInfoObj structure = Marshal.PtrToStructure(decodedDataPtr)!; + // Adding the structure to OEMOpusData list + OEMOpusData.Add(structure); + } + } + } + finally + { + // Freeing the allocated unmanaged memory (decodedDataPtr) + Marshal.FreeCoTaskMem(decodedDataPtr); + } + } + } + } + return OEMOpusData; + } + } +} + +// Constants + +// WINTRUST_MAX_HEADER_BYTES_TO_MAP_DEFAULT = $00A00000 +// WINTRUST_MAX_HASH_BYTES_TO_MAP_DEFAULT = $00100000 +// WTD_UI_ALL = 1 +// WTD_UI_NONE = 2 +// WTD_UI_NOBAD = 3 +// WTD_UI_NOGOOD = 4 +// WTD_REVOKE_NONE = $00000000 +// WTD_REVOKE_WHOLECHAIN = $00000001 +// WTD_CHOICE_FILE = 1 +// WTD_CHOICE_CATALOG = 2 +// WTD_CHOICE_BLOB = 3 +// WTD_CHOICE_SIGNER = 4 +// WTD_CHOICE_CERT = 5 +// WTD_STATEACTION_IGNORE = $00000000 +// WTD_STATEACTION_VERIFY = $00000001 +// WTD_STATEACTION_CLOSE = $00000002 +// WTD_STATEACTION_AUTO_CACHE = $00000003 +// WTD_STATEACTION_AUTO_CACHE_FLUSH = $00000004 +// WTD_PROV_FLAGS_MASK = $0000FFFF +// WTD_USE_IE4_TRUST_FLAG = $00000001 +// WTD_NO_IE4_CHAIN_FLAG = $00000002 +// WTD_NO_POLICY_USAGE_FLAG = $00000004 +// WTD_REVOCATION_CHECK_NONE = $00000010 +// WTD_REVOCATION_CHECK_END_CERT = $00000020 +// WTD_REVOCATION_CHECK_CHAIN = $00000040 +// WTD_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT = $00000080 +// WTD_SAFER_FLAG = $00000100 +// WTD_HASH_ONLY_FLAG = $00000200 +// WTD_USE_DEFAULT_OSVER_CHECK = $00000400 +// WTD_LIFETIME_SIGNING_FLAG = $00000800 +// WTD_CACHE_ONLY_URL_RETRIEVAL = $00001000 +// WTD_UICONTEXT_EXECUTE = 0 +// WTD_UICONTEXT_INSTALL = 1 +// WTCI_DONT_OPEN_STORES = $00000001 +// WTCI_OPEN_ONLY_ROOT = $00000002 +// WTCI_USE_LOCAL_MACHINE = $00000004 +// WTPF_TRUSTTEST = $00000020 +// WTPF_TESTCANBEVALID = $00000080 +// WTPF_IGNOREEXPIRATION = $00000100 +// WTPF_IGNOREREVOKATION = $00000200 +// WTPF_OFFLINEOK_IND = $00000400 +// WTPF_OFFLINEOK_COM = $00000800 +// WTPF_OFFLINEOKNBU_IND = $00001000 +// WTPF_OFFLINEOKNBU_COM = $00002000 +// WTPF_VERIFY_V1_OFF = $00010000 +// WTPF_IGNOREREVOCATIONONTS = $00020000 +// WTPF_ALLOWONLYPERTRUST = $00040000 +// TRUSTERROR_STEP_WVTPARAMS = 0 +// TRUSTERROR_STEP_FILEIO = 2 +// TRUSTERROR_STEP_SIP = 3 +// TRUSTERROR_STEP_SIPSUBJINFO = 5 +// TRUSTERROR_STEP_CATALOGFILE = 6 +// TRUSTERROR_STEP_CERTSTORE = 7 +// TRUSTERROR_STEP_MESSAGE = 8 +// TRUSTERROR_STEP_MSG_SIGNERCOUNT = 9 +// TRUSTERROR_STEP_MSG_INNERCNTTYPE = 10 +// TRUSTERROR_STEP_MSG_INNERCNT = 11 +// TRUSTERROR_STEP_MSG_STORE = 12 +// TRUSTERROR_STEP_MSG_SIGNERINFO = 13 +// TRUSTERROR_STEP_MSG_SIGNERCERT = 14 +// TRUSTERROR_STEP_MSG_CERTCHAIN = 15 +// TRUSTERROR_STEP_MSG_COUNTERSIGINFO = 16 +// TRUSTERROR_STEP_MSG_COUNTERSIGCERT = 17 +// TRUSTERROR_STEP_VERIFY_MSGHASH = 18 +// TRUSTERROR_STEP_VERIFY_MSGINDIRECTDATA = 19 +// TRUSTERROR_STEP_FINAL_WVTINIT = 30 +// TRUSTERROR_STEP_FINAL_INITPROV = 31 +// TRUSTERROR_STEP_FINAL_OBJPROV = 32 +// TRUSTERROR_STEP_FINAL_SIGPROV = 33 +// TRUSTERROR_STEP_FINAL_CERTPROV = 34 +// TRUSTERROR_STEP_FINAL_CERTCHKPROV = 35 +// TRUSTERROR_STEP_FINAL_POLICYPROV = 36 +// TRUSTERROR_STEP_FINAL_UIPROV = 37 +// TRUSTERROR_MAX_STEPS = 38 +// CPD_USE_NT5_CHAIN_FLAG = $80000000 +// CPD_REVOCATION_CHECK_NONE = $00010000 +// CPD_REVOCATION_CHECK_END_CERT = $00020000 +// CPD_REVOCATION_CHECK_CHAIN = $00040000 +// CPD_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT = $00080000 +// CPD_UISTATE_MODE_PROMPT = $00000000 +// CPD_UISTATE_MODE_BLOCK = $00000001 +// CPD_UISTATE_MODE_ALLOW = $00000002 +// CPD_UISTATE_MODE_MASK = $00000003 +// CERT_CONFIDENCE_SIG = $10000000 +// CERT_CONFIDENCE_TIME = $01000000 +// CERT_CONFIDENCE_TIMENEST = $00100000 +// CERT_CONFIDENCE_AUTHIDEXT = $00010000 +// CERT_CONFIDENCE_HYGIENE = $00001000 +// CERT_CONFIDENCE_HIGHEST = $11111000 +// DWACTION_ALLOCANDFILL = 1 +// DWACTION_FREE = 2 +// szOID_TRUSTED_CODESIGNING_CA_LIST = "1.3.6.1.4.1.311.2.2.1" +// szOID_TRUSTED_CLIENT_AUTH_CA_LIST = "1.3.6.1.4.1.311.2.2.2" +// szOID_TRUSTED_SERVER_AUTH_CA_LIST = "1.3.6.1.4.1.311.2.2.3" +// SPC_TIME_STAMP_REQUEST_OBJID = "1.3.6.1.4.1.311.3.2.1" +// SPC_INDIRECT_DATA_OBJID = "1.3.6.1.4.1.311.2.1.4" +// SPC_SP_AGENCY_INFO_OBJID = "1.3.6.1.4.1.311.2.1.10" +// SPC_STATEMENT_TYPE_OBJID = "1.3.6.1.4.1.311.2.1.11" +// SPC_SP_OPUS_INFO_OBJID = "1.3.6.1.4.1.311.2.1.12" +// SPC_CERT_EXTENSIONS_OBJID = "1.3.6.1.4.1.311.2.1.14" +// SPC_PE_IMAGE_DATA_OBJID = "1.3.6.1.4.1.311.2.1.15" +// SPC_RAW_FILE_DATA_OBJID = "1.3.6.1.4.1.311.2.1.18" +// SPC_STRUCTURED_STORAGE_DATA_OBJID = "1.3.6.1.4.1.311.2.1.19" +// SPC_JAVA_CLASS_DATA_OBJID = "1.3.6.1.4.1.311.2.1.20" +// SPC_INDIVIDUAL_SP_KEY_PURPOSE_OBJID = "1.3.6.1.4.1.311.2.1.21" +// SPC_COMMERCIAL_SP_KEY_PURPOSE_OBJID = "1.3.6.1.4.1.311.2.1.22" +// SPC_CAB_DATA_OBJID = "1.3.6.1.4.1.311.2.1.25" +// SPC_GLUE_RDN_OBJID = "1.3.6.1.4.1.311.2.1.25" +// SPC_MINIMAL_CRITERIA_OBJID = "1.3.6.1.4.1.311.2.1.26" +// SPC_FINANCIAL_CRITERIA_OBJID = "1.3.6.1.4.1.311.2.1.27" +// SPC_LINK_OBJID = "1.3.6.1.4.1.311.2.1.28" +// SPC_SIGINFO_OBJID = "1.3.6.1.4.1.311.2.1.30" +// SPC_PE_IMAGE_PAGE_HASHES_V1_OBJID = "1.3.6.1.4.1.311.2.3.1" +// SPC_PE_IMAGE_PAGE_HASHES_V2_OBJID = "1.3.6.1.4.1.311.2.3.2" +// CAT_NAMEVALUE_OBJID = "1.3.6.1.4.1.311.12.2.1" +// CAT_MEMBERINFO_OBJID = "1.3.6.1.4.1.311.12.2.2" +// SPC_SP_AGENCY_INFO_STRUCT =(2000) +// SPC_MINIMAL_CRITERIA_STRUCT =(2001) +// SPC_FINANCIAL_CRITERIA_STRUCT =(2002) +// SPC_INDIRECT_DATA_CONTENT_STRUCT =(2003) +// SPC_PE_IMAGE_DATA_STRUCT =(2004) +// SPC_LINK_STRUCT =(2005) +// SPC_STATEMENT_TYPE_STRUCT =(2006) +// SPC_SP_OPUS_INFO_STRUCT =(2007) +// SPC_CAB_DATA_STRUCT =(2008) +// SPC_JAVA_CLASS_DATA_STRUCT =(2009) +// SPC_SIGINFO_STRUCT =(2130) +// CAT_NAMEVALUE_STRUCT =(2221) +// CAT_MEMBERINFO_STRUCT =(2222) +// SPC_UUID_LENGTH = 16 +// WIN_CERT_REVISION_1_0 =($0100) +// WIN_CERT_REVISION_2_0 =($0200) +// WIN_CERT_TYPE_X509 =($0001) +// WIN_CERT_TYPE_PKCS_SIGNED_DATA =($0002) +// WIN_CERT_TYPE_RESERVED_1 =($0003) +// WIN_CERT_TYPE_TS_STACK_SIGNED =($0004) +// WT_TRUSTDBDIALOG_NO_UI_FLAG = $00000001 +// WT_TRUSTDBDIALOG_ONLY_PUB_TAB_FLAG = $00000002 +// WT_TRUSTDBDIALOG_WRITE_LEGACY_REG_FLAG = $00000100 +// WT_TRUSTDBDIALOG_WRITE_IEAK_STORE_FLAG = $00000200 diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/Initializer.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/Initializer.cs new file mode 100644 index 000000000..6671759fb --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/Initializer.cs @@ -0,0 +1,55 @@ +using Microsoft.Win32; +using System; +using System.Globalization; + +#nullable enable + +namespace WDACConfig +{ + // Prepares the environment. It also runs commands that would otherwise run in the default constructor for the GlobalVars Class + public class Initializer + { + /// These are the codes that were present in the GlobalVar class's default constructor but defining them as a separate method allows any errors thrown in them to be properly displayed in PowerShell instead of showing an error occurred in the default constructor of a class + public static void Initialize() + { + using (RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion") ?? throw new InvalidOperationException("Could not get the current Windows version from the registry")) + { + object? ubrValue = key.GetValue("UBR"); + if (ubrValue != null && int.TryParse(ubrValue.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out int ubr)) + { + WDACConfig.GlobalVars.UBR = ubr; + } + else + { + throw new InvalidOperationException("The UBR value could not be retrieved from the registry: HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"); + } + } + + // Concatenate OSBuildNumber and UBR to form the final string + WDACConfig.GlobalVars.FullOSBuild = $"{WDACConfig.GlobalVars.OSBuildNumber}.{WDACConfig.GlobalVars.UBR}"; + + // Convert the FullOSBuild and RequiredBuild strings to decimals so that we can compare them + if (!TryParseBuildVersion(WDACConfig.GlobalVars.FullOSBuild, out decimal fullOSBuild)) + { + throw new FormatException("The OS build version strings are not in a correct format."); + } + + // Make sure the current OS build is equal or greater than the required build number + if (!(fullOSBuild >= WDACConfig.GlobalVars.Requiredbuild)) + { + throw new PlatformNotSupportedException($"You are not using the latest build of the Windows OS. A minimum build of {WDACConfig.GlobalVars.Requiredbuild} is required but your OS build is {fullOSBuild}\nPlease go to Windows Update to install the updates and then try again."); + } + } + + // This method gracefully parses the OS build version strings to decimals + // and performs this in a culture-independent way + // in languages such as Swedish where the decimal separator is , instead of . + // this will work properly + // in PowerShell we can see the separator by running: (Get-Culture).NumberFormat.NumberDecimalSeparator + private static bool TryParseBuildVersion(string buildVersion, out decimal result) + { + // Use CultureInfo.InvariantCulture for parsing + return decimal.TryParse(buildVersion, NumberStyles.Number, CultureInfo.InvariantCulture, out result); + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/MeowOpener.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/MeowOpener.cs new file mode 100644 index 000000000..3eb4052e5 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/MeowOpener.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Xml; + +#nullable enable + +namespace WDACConfig +{ + // Declares a public static class that cannot be instantiated. + public static class MeowParser + { + // P/Invoke declaration to import the 'CryptAcquireContext' function from 'AdvApi32.dll'. + // https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptacquirecontexta + [DllImport("AdvApi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + internal static extern bool CryptAcquireContext( + out IntPtr MainCryptProviderHandle, // Output parameter to receive the handle of the cryptographic service provider. + [MarshalAs(UnmanagedType.LPWStr)] string Container, // The name of the key container within the cryptographic service provider. + [MarshalAs(UnmanagedType.LPWStr)] string Provider, // The name of the cryptographic service provider. + uint ProviderType, // The type of provider to acquire. + uint Flags); // Flags to control the function behavior. + + // P/Invoke declaration to import the 'CryptReleaseContext' function from 'AdvApi32.dll'. + // https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptreleasecontext + [DllImport("AdvApi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + internal static extern bool CryptReleaseContext(IntPtr MainCryptProviderHandle, uint Flags); // Releases the handle acquired by 'CryptAcquireContext'. + + // Defines a structure with sequential layout to match the native structure. + // https://learn.microsoft.com/en-us/windows/win32/api/mscat/ns-mscat-cryptcatmember + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + internal struct MeowMemberCrypt + { + public uint StructureSize; // Size of the structure. + [MarshalAs(UnmanagedType.LPWStr)] + public string Hashes; // The hashes associated with the catalog member. + [MarshalAs(UnmanagedType.LPWStr)] + public string FileName; // The file name of the catalog member. + public Guid SubjectType; // The subject type GUID. + public uint MemberFlags; // Flags associated with the member. + public IntPtr IndirectDataStructure; // Pointer to the indirect data structure. + public uint CertVersion; // The certificate version. + private uint Reserved1; // Reserved for future use. + private IntPtr Reserved2; // Reserved for future use. + } + + // A public static method that returns a HashSet of strings. + public static HashSet GetHashes(string SecurityCatalogFilePath) + { + HashSet OutputHashSet = []; // Initializes a new HashSet to store the hashes. + + // Creates a new XmlDocument instance. + XmlDocument PurrfectCatalogXMLDoc = new() + { + XmlResolver = null // Disables the XML resolver for security reasons. + }; + + IntPtr MainCryptProviderHandle = IntPtr.Zero; // Initializes the handle to zero. + IntPtr MeowLogHandle = IntPtr.Zero; // Initializes the catalog context handle to zero. + IntPtr KittyPointer = IntPtr.Zero; // Pointer to iterate through catalog members, initialized to zero. + + try + { + // Attempts to acquire a cryptographic context. + if (!CryptAcquireContext(out MainCryptProviderHandle, string.Empty, string.Empty, 1, 4026531840)) + { + // If the context is not acquired, the error can be captured (commented out). + // int lastWin32Error = Marshal.GetLastWin32Error(); + } + + // Opens the catalog file and gets a handle to the catalog context. + MeowLogHandle = WinTrust.CryptCATOpen(SecurityCatalogFilePath, 0, MainCryptProviderHandle, 0, 0); + if (MeowLogHandle == IntPtr.Zero) + { + // If the handle is not obtained, the error can be captured (commented out). + // int lastWin32Error = Marshal.GetLastWin32Error(); + } + + // Creates an XML element to represent the catalog file. + XmlElement catalogElement = PurrfectCatalogXMLDoc.CreateElement("MeowFile"); + _ = PurrfectCatalogXMLDoc.AppendChild(catalogElement); // Appends the element to the XML document. + + // Iterates through the catalog members. + while ((KittyPointer = WinTrust.CryptCATEnumerateMember(MeowLogHandle, KittyPointer)) != IntPtr.Zero) + { + // Converts the pointer to a structure. + MeowMemberCrypt member = Marshal.PtrToStructure(KittyPointer); + _ = OutputHashSet.Add(member.Hashes); // Adds the hashes to the HashSet. + } + } + finally + { + // Releases the cryptographic context and closes the catalog context in the finally block to ensure resources are freed. + if (MainCryptProviderHandle != IntPtr.Zero) + _ = CryptReleaseContext(MainCryptProviderHandle, 0); + + if (MeowLogHandle != IntPtr.Zero) + _ = WinTrust.CryptCATClose(MeowLogHandle); + } + return OutputHashSet; // Returns the HashSet containing the hashes. + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/MoveUserModeToKernelMode.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/MoveUserModeToKernelMode.cs new file mode 100644 index 000000000..141a53ea9 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/MoveUserModeToKernelMode.cs @@ -0,0 +1,121 @@ +using System; +using System.Xml; + +#nullable enable + +namespace WDACConfig +{ + public static class MoveUserModeToKernelMode + { + /// + /// 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 + /// This is used during the creation of Strict Kernel-mode WDAC policy for complete BYOVD protection scenario. + /// It doesn't consider node in the SigningScenario 12 when deleting it because for kernel-mode policy everything is signed and we don't deal with unsigned files. + /// + /// The path to the XML file + /// + public static void Move(string filePath) + { + try + { + // Create an XmlDocument object + XmlDocument xml = new(); + + // Load the XML file + xml.Load(filePath); + + // Create an XmlNameSpaceManager object + XmlNamespaceManager nsManager = new(xml.NameTable); + // Define namespace + nsManager.AddNamespace("sip", "urn:schemas-microsoft-com:sipolicy"); + + // Get all SigningScenario nodes in the XML file + XmlNodeList? signingScenarios = xml.SelectNodes("//sip:SigningScenario", nsManager); + + // Variables to store SigningScenario nodes with specific values 12 and 131 + XmlNode? signingScenario12 = null; + XmlNode? signingScenario131 = null; + + // If there is no SigningScenarios block in the XML then exit the method + if (signingScenarios == null) + { + return; + } + + // Find SigningScenario nodes with Value 12 and 131 + foreach (XmlNode signingScenario in signingScenarios) + { + string? valueAttr = signingScenario.Attributes?["Value"]?.Value; + + if (string.Equals(valueAttr, "12", StringComparison.OrdinalIgnoreCase)) + { + signingScenario12 = signingScenario; + } + else if (string.Equals(valueAttr, "131", StringComparison.OrdinalIgnoreCase)) + { + signingScenario131 = signingScenario; + } + } + + // If both SigningScenario nodes were found + if (signingScenario12 != null && signingScenario131 != null) + { + // Get AllowedSigners from SigningScenario with Value 12 + XmlNode? allowedSigners12 = signingScenario12.SelectSingleNode("./sip:ProductSigners/sip:AllowedSigners", nsManager); + + // If AllowedSigners node exists in SigningScenario 12 and has child nodes + if (allowedSigners12 != null && allowedSigners12.HasChildNodes) + { + // Loop through each child node of AllowedSigners in SigningScenario 12 + foreach (XmlNode allowedSignerNode in allowedSigners12.ChildNodes) + { + // Ensure we're only working with XmlElement nodes and not comments or anything else + + // This line is a pattern matching statement: + // allowedSignerNode is the current node from the foreach loop. + // The is keyword checks if allowedSignerNode is of type XmlElement. + // If the check is successful, allowedSigner is created as a new variable within the scope of the if block, and it is assigned the value of allowedSignerNode. + // Essentially, allowedSigner is created implicitly as part of the pattern matching expression. + + if (allowedSignerNode is XmlElement allowedSigner) + { + // Create a new AllowedSigner node + XmlNode newAllowedSigner = xml.CreateElement("AllowedSigner", "urn:schemas-microsoft-com:sipolicy"); + + // Create a SignerId attribute for the new AllowedSigner node + XmlAttribute newSignerIdAttr = xml.CreateAttribute("SignerId"); + + // Set the value of the new SignerId attribute to the value of the existing SignerId attribute + newSignerIdAttr.Value = allowedSigner.Attributes["SignerId"]!.Value; + + // Append the new SignerId attribute to the new AllowedSigner node + _ = newAllowedSigner.Attributes!.Append(newSignerIdAttr); + + // Find the AllowedSigners node in SigningScenario 131 + XmlNode? allowedSigners131 = signingScenario131.SelectSingleNode("./sip:ProductSigners/sip:AllowedSigners", nsManager); + + // If the AllowedSigners node exists in SigningScenario 131 + if (allowedSigners131 != null) + { + // Append the new AllowedSigner node to the AllowedSigners node in SigningScenario 131 + _ = allowedSigners131.AppendChild(newAllowedSigner); + } + } + } + + // Remove SigningScenario with Value 12 completely after moving all of its AllowedSigners to SigningScenario with the value of 131 + _ = (signingScenario12.ParentNode?.RemoveChild(signingScenario12)); + } + } + + // Save the modified XML document back to the file + xml.Save(filePath); + } + catch (Exception ex) + { + throw new InvalidOperationException($"An error occurred: {ex.Message}", ex); + } + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/PageHashCalc.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/PageHashCalc.cs new file mode 100644 index 000000000..5d5e6c1c6 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/PageHashCalc.cs @@ -0,0 +1,74 @@ +using System; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Text; + +#nullable enable + +namespace WDACConfig +{ + /// + /// necessary logics for Page hash calculation + /// + public static class PageHashCalculator + { + + // a method to get the hash of the first page of a file as a hexadecimal string + public static string? GetPageHash(string algName, string fileName) // the method signature + { + // initialize the buffer pointer to zero + IntPtr buffer = IntPtr.Zero; + + // initialize the buffer size to zero + int bufferSize = 0; + + // create a string builder to append the hash value + StringBuilder stringBuilder = new(62); + + try + { + // call the native function with the given parameters and store the return value + int firstPageHash1 = WinTrust.ComputeFirstPageHash(algName, fileName, buffer, bufferSize); + + // if the return value is zero, it means the function failed + if (firstPageHash1 == 0) + { + // return null to indicate an error + return null; + } + + // allocate memory for the buffer using the return value as the size + buffer = Marshal.AllocHGlobal(firstPageHash1); + + // call the native function again with the same parameters and the allocated buffer + int firstPageHash2 = WinTrust.ComputeFirstPageHash(algName, fileName, buffer, firstPageHash1); + + // if the return value is zero, it means the function failed + if (firstPageHash2 == 0) + { + // return null to indicate an error + return null; + } + + // loop through the buffer bytes + for (int ofs = 0; ofs < firstPageHash2; ++ofs) + + // read each byte, convert it to a hexadecimal string, and append it to the string builder + _ = stringBuilder.Append(Marshal.ReadByte(buffer, ofs).ToString("X2", CultureInfo.InvariantCulture)); + + // return the final string + return stringBuilder.ToString(); + } + // a finally block to execute regardless of the outcome + finally + { + // if the buffer pointer is not zero, it means it was allocated + if (buffer != IntPtr.Zero) + { + // free the allocated memory + Marshal.FreeHGlobal(buffer); + } + } + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/RemoveSupplementalSigners.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/RemoveSupplementalSigners.cs new file mode 100644 index 000000000..d319c14d8 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/RemoveSupplementalSigners.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Xml; + +#nullable enable + +namespace WDACConfig +{ + public class CiPolicyHandler + { + /// + /// Removes the entire SupplementalPolicySigners block + /// and any Signer in Signers node that have the same ID as the SignerIds of the SupplementalPolicySigner(s) in ... node + /// from a CI policy XML file + /// + /// It doesn't do anything if the input policy file has no SupplementalPolicySigners block. + /// It will also always check if the Signers node is not empty, like + /// + /// + /// if it is then it will close it: + /// The function can run infinite number of times on the same file. + /// + /// The path to the CI policy XML file + /// + public static void RemoveSupplementalSigners(string path) + { + + // Validate input XML file compliance with CI policy schema + if (WDACConfig.CiPolicyTest.TestCiPolicy(path, "") is not true) + { + throw new InvalidOperationException("The input XML file is not compliant with the CI policy schema"); + } + + // Load XML document + XmlDocument xmlDoc = new(); + xmlDoc.Load(path); + + // Create namespace manager and add the default namespace with a prefix + XmlNamespaceManager namespaceManager = new(xmlDoc.NameTable); + namespaceManager.AddNamespace("ns", "urn:schemas-microsoft-com:sipolicy"); + + // Get SiPolicy node + XmlNode? siPolicyNode = xmlDoc.SelectSingleNode("//ns:SiPolicy", namespaceManager) ?? throw new InvalidOperationException("Invalid XML structure, SiPolicy node not found"); + + // Check if SupplementalPolicySigners exists and has child nodes + XmlNodeList? supplementalPolicySignersNodes = siPolicyNode.SelectNodes("ns:SupplementalPolicySigners", namespaceManager); + + if (supplementalPolicySignersNodes is not null && supplementalPolicySignersNodes.Count > 0) + { + Console.WriteLine("Removing the SupplementalPolicySigners blocks and corresponding Signers"); + + // Store SignerIds to remove + var signerIds = new HashSet(); + + // Loop through each SupplementalPolicySigners node + foreach (XmlNode supplementalPolicySignersNode in supplementalPolicySignersNodes) + { + var supplementalPolicySigners = supplementalPolicySignersNode.SelectNodes("ns:SupplementalPolicySigner", namespaceManager); + + // Get unique SignerIds + foreach (XmlElement node in supplementalPolicySigners!) + { + _ = signerIds.Add(node.GetAttribute("SignerId")); + } + + // Remove the entire SupplementalPolicySigners node + _ = siPolicyNode.RemoveChild(supplementalPolicySignersNode); + } + + // Remove corresponding Signers + foreach (var signerId in signerIds) + { + XmlNodeList? signersToRemove = siPolicyNode.SelectNodes($"ns:Signers/ns:Signer[@ID='{signerId}']", namespaceManager); + if (signersToRemove != null) + { + foreach (XmlNode signerNode in signersToRemove) + { + _ = siPolicyNode.SelectSingleNode("ns:Signers", namespaceManager)?.RemoveChild(signerNode); + } + } + } + } + + + // Check if the Signers node is empty, if so, close it properly + XmlNode? signersNode = siPolicyNode.SelectSingleNode("ns:Signers", namespaceManager); + + if (signersNode is not null && !signersNode.HasChildNodes) + { + // Create a new self-closing element with the same name and attributes as the old one + XmlElement newSignersNode = xmlDoc.CreateElement("Signers", "urn:schemas-microsoft-com:sipolicy"); + + if (signersNode.Attributes is not null) + { + + foreach (XmlAttribute attribute in signersNode.Attributes) + { + newSignersNode.SetAttribute(attribute.Name, attribute.Value); + } + + _ = siPolicyNode.ReplaceChild(newSignersNode, signersNode); + } + } + + // Save the updated XML content back to the file + xmlDoc.Save(path); + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/SecureStringComparer.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/SecureStringComparer.cs new file mode 100644 index 000000000..302813a24 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/SecureStringComparer.cs @@ -0,0 +1,62 @@ +using System; +using System.Runtime.InteropServices; +using System.Security; + +#nullable enable + +namespace WDACConfig +{ + public static class SecureStringComparer + { + /// + /// Safely compares two SecureString objects without decrypting them. + /// Outputs true if they are equal, or false otherwise. + /// + /// First secure string + /// Second secure string to compare with the first secure string + /// true if the SecureStrings are equal; otherwise, false. + public static bool Compare(SecureString secureString1, SecureString secureString2) + { + IntPtr bstr1 = IntPtr.Zero; + IntPtr bstr2 = IntPtr.Zero; + + try + { + bstr1 = Marshal.SecureStringToBSTR(secureString1); + bstr2 = Marshal.SecureStringToBSTR(secureString2); + + int length1 = Marshal.ReadInt32(bstr1, -4); + int length2 = Marshal.ReadInt32(bstr2, -4); + + if (length1 != length2) + { + return false; + } + + for (int i = 0; i < length1; ++i) + { + byte b1 = Marshal.ReadByte(bstr1, i); + byte b2 = Marshal.ReadByte(bstr2, i); + + if (b1 != b2) + { + return false; + } + } + + return true; + } + finally + { + if (bstr1 != IntPtr.Zero) + { + Marshal.ZeroFreeBSTR(bstr1); + } + if (bstr2 != IntPtr.Zero) + { + Marshal.ZeroFreeBSTR(bstr2); + } + } + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/SnapBackGuarantee.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/SnapBackGuarantee.cs new file mode 100644 index 000000000..7ac12bba9 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/SnapBackGuarantee.cs @@ -0,0 +1,178 @@ +using System; +using System.IO; +using System.Management; + +#nullable enable + +namespace WDACConfig +{ + public class SnapBackGuarantee + { + + /// + /// A method 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. + /// + /// The path to the EnforcedMode.cip file that will be used to revert the base policy to enforced mode in case of a reboot. + /// + public static void New(string path) + { + + Logger.Write("Creating the scheduled task for Snap Back Guarantee"); + + // Initialize ManagementScope to interact with Task Scheduler's WMI namespace + var scope = new ManagementScope(@"root\Microsoft\Windows\TaskScheduler"); + // Establish connection to the WMI namespace + scope.Connect(); + + #region Action + // Creating a scheduled task action + using ManagementClass actionClass = new(scope, new ManagementPath("PS_ScheduledTask"), null); + + // Prepare method parameters for creating the task action + var actionInParams = actionClass.GetMethodParameters("NewActionByExec"); + actionInParams["Execute"] = "cmd.exe"; + + // The PowerShell command to run, downloading and deploying the drivers block list + actionInParams["Argument"] = $"/c \"{GlobalVars.UserConfigDir}\\EnforcedModeSnapBack.cmd\""; + + // Execute the WMI method to create the action + ManagementBaseObject actionResult = actionClass.InvokeMethod("NewActionByExec", actionInParams, null); + + // Check if the action was created successfully + if ((uint)actionResult["ReturnValue"] != 0) + { + throw new InvalidOperationException($"Failed to create task action: {((uint)actionResult["ReturnValue"])}"); + } + + // Extract CIM instance for further use in task registration + ManagementBaseObject actionCimInstance = (ManagementBaseObject)actionResult["cmdletOutput"]; + + #endregion + + + #region Principal + // Create a scheduled task principal and assign the SYSTEM account's SID to it so that the task will run under its context + using ManagementClass principalClass = new(scope, new ManagementPath("PS_ScheduledTask"), null); + + // Prepare method parameters to set up the principal (user context) + ManagementBaseObject principalInParams = principalClass.GetMethodParameters("NewPrincipalByUser"); + principalInParams["UserId"] = "S-1-5-18"; // SYSTEM SID (runs with the highest system privileges) + principalInParams["LogonType"] = 2; // S4U logon type, allows the task to run without storing credentials + principalInParams["RunLevel"] = 1; // Highest run level, ensuring the task runs with elevated privileges + + // Execute the WMI method to create the principal + ManagementBaseObject principalResult = principalClass.InvokeMethod("NewPrincipalByUser", principalInParams, null); + + // Check if the principal was created successfully + if ((uint)principalResult["ReturnValue"] != 0) + { + throw new InvalidOperationException($"Failed to create task principal: {((uint)principalResult["ReturnValue"])}"); + } + + // Extract CIM instance for further use in task registration + ManagementBaseObject principalCimInstance = (ManagementBaseObject)principalResult["cmdletOutput"]; + #endregion + + + #region Trigger + // Create a trigger for the scheduled task + using ManagementClass triggerClass = new(scope, new ManagementPath("PS_ScheduledTask"), null); + + ManagementBaseObject triggerInParams = triggerClass.GetMethodParameters("NewTriggerByLogon"); + triggerInParams["AtLogOn"] = true; + + // Execute the WMI method to create the trigger + ManagementBaseObject triggerResult = triggerClass.InvokeMethod("NewTriggerByLogon", triggerInParams, null); + + // Check if the trigger was created successfully + if ((uint)triggerResult["ReturnValue"] != 0) + { + throw new InvalidOperationException($"Failed to create task trigger: {((uint)triggerResult["ReturnValue"])}"); + } + + // Extract CIM instance for further use in task registration + var triggerCimInstance = (ManagementBaseObject)triggerResult["cmdletOutput"]; + #endregion + + + #region Settings + // Define advanced settings for the scheduled task + using ManagementClass settingsClass = new(scope, new ManagementPath("PS_ScheduledTask"), null); + + // Prepare method parameters to define advanced settings for the task + ManagementBaseObject settingsInParams = settingsClass.GetMethodParameters("NewSettings"); + settingsInParams["AllowStartIfOnBatteries"] = true; // Allow the task to start if the system is on battery + settingsInParams["DontStopIfGoingOnBatteries"] = true; // Ensure the task isn't stopped if the system switches to battery power + settingsInParams["Compatibility"] = 4; + // 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. + settingsInParams["Priority"] = 0; + settingsInParams["Hidden"] = true; + settingsInParams["RestartCount"] = 2; // Number of allowed task restarts on failure + settingsInParams["RestartInterval"] = ManagementDateTimeConverter.ToDmtfTimeInterval(TimeSpan.FromMinutes(3)); // Wait 3 minutes between restarts (converted to DMTF format) + + // Execute the WMI method to set the task's advanced settings + ManagementBaseObject settingsResult = settingsClass.InvokeMethod("NewSettings", settingsInParams, null); + if ((uint)settingsResult["ReturnValue"] != 0) + { + throw new InvalidOperationException($"Failed to define task settings: {((uint)settingsResult["ReturnValue"])}"); + } + + // Extract CIM instance for further use in task registration + ManagementBaseObject settingsCimInstance = (ManagementBaseObject)settingsResult["cmdletOutput"]; + #endregion + + + #region Register Task + // Register the scheduled task. If the task's state is disabled, it will be overwritten with a new task that is enabled + using ManagementClass registerClass = new(scope, new ManagementPath("PS_ScheduledTask"), null); + + // Prepare method parameters to register the task + ManagementBaseObject registerInParams = registerClass.GetMethodParameters("RegisterByPrincipal"); + registerInParams["Force"] = true; // Overwrite any existing task with the same name + registerInParams["Principal"] = principalCimInstance; + registerInParams["Action"] = new ManagementBaseObject[] { actionCimInstance }; + registerInParams["Trigger"] = new ManagementBaseObject[] { triggerCimInstance }; + registerInParams["Settings"] = settingsCimInstance; + registerInParams["TaskName"] = "EnforcedModeSnapBack"; + + // Execute the WMI method to register the task + var registerResult = registerClass.InvokeMethod("RegisterByPrincipal", registerInParams, null); + + // Check if the task was registered successfully + if ((uint)registerResult["ReturnValue"] != 0) + { + throw new InvalidOperationException($"Failed to register the task: {((uint)registerResult["ReturnValue"])}"); + } + #endregion + + Logger.Write("Successfully created the Microsoft Recommended Driver Block Rules auto updater scheduled task."); + + + + // Saving the EnforcedModeSnapBack.cmd file to the UserConfig directory in Program Files + // It contains the instructions to revert the base policy to enforced mode + + string savePath = Path.Combine(GlobalVars.UserConfigDir, "EnforcedModeSnapBack.cmd"); + + string contentToBeSaved = $@" +REM Deploying the Enforced Mode SnapBack CI Policy +CiTool --update-policy ""{path}"" -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 ""{path}"" +REM Deleting this CMD file itself +del ""%~f0"" +"; + + + // Write to file (overwrite if exists) + File.WriteAllText(savePath, contentToBeSaved); + + + // An alternative way to do this which is less reliable because RunOnce key can be deleted by 3rd party programs during installation etc. + + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/StagingArea.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/StagingArea.cs new file mode 100644 index 000000000..6254c443b --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/StagingArea.cs @@ -0,0 +1,34 @@ +using System; +using System.IO; + +#nullable enable + +namespace WDACConfig +{ + public static class StagingArea + { + public static DirectoryInfo NewStagingArea(string cmdletName) + { + if (string.IsNullOrEmpty(cmdletName)) + { + throw new ArgumentException("CmdletName cannot be null or empty", nameof(cmdletName)); + } + + string userConfigDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WDACConfig"); + + // Define a staging area for the cmdlet + string stagingArea = Path.Combine(userConfigDir, "StagingArea", cmdletName); + + // Delete it if it already exists with possible content from previous runs + if (Directory.Exists(stagingArea)) + { + Directory.Delete(stagingArea, true); + } + + // Create the staging area for the cmdlet + DirectoryInfo stagingAreaInfo = Directory.CreateDirectory(stagingArea); + + return stagingAreaInfo; + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/VersionIncrementer.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/VersionIncrementer.cs new file mode 100644 index 000000000..b4357e705 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/VersionIncrementer.cs @@ -0,0 +1,36 @@ +using System; + +#nullable enable + +namespace WDACConfig +{ + public class VersionIncrementer + { + public static Version AddVersion(Version version) + // This can recursively increment an input version by one, and is aware of the max limit + { + ArgumentNullException.ThrowIfNull(version); + + if (version.Revision < int.MaxValue) + { + return new Version(version.Major, version.Minor, version.Build, version.Revision + 1); + } + else if (version.Build < int.MaxValue) + { + return new Version(version.Major, version.Minor, version.Build + 1, 0); + } + else if (version.Minor < int.MaxValue) + { + return new Version(version.Major, version.Minor + 1, 0, 0); + } + else if (version.Major < int.MaxValue) + { + return new Version(version.Major + 1, 0, 0, 0); + } + else + { + throw new InvalidOperationException("Version has reached its maximum value."); + } + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/WldpQuerySecurityPolicy.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/WldpQuerySecurityPolicy.cs new file mode 100644 index 000000000..bcb917e4b --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/WldpQuerySecurityPolicy.cs @@ -0,0 +1,45 @@ +using System; +using System.Runtime.InteropServices; + +#nullable enable + +namespace WDACConfig +{ + public enum WLDP_SECURE_SETTING_VALUE_TYPE + { + WldpBoolean = 0, + WldpInteger = 1, + WldpNone = 2, + WldpString = 3, + WldpFlag = 4 + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct UNICODE_STRING + { + public ushort Length; + public ushort MaximumLength; + public IntPtr Buffer; + } + + public class WldpQuerySecurityPolicyWrapper + { + [DllImport("Wldp.dll", CharSet = CharSet.Unicode)] + internal static extern int WldpQuerySecurityPolicy( + ref UNICODE_STRING Provider, + ref UNICODE_STRING Key, + ref UNICODE_STRING ValueName, + out WLDP_SECURE_SETTING_VALUE_TYPE ValueType, + IntPtr Value, + ref uint ValueSize); + + public static UNICODE_STRING InitUnicodeString(string s) + { + UNICODE_STRING us; + us.Length = (ushort)(s.Length * 2); + us.MaximumLength = (ushort)((s.Length * 2) + 2); + us.Buffer = Marshal.StringToHGlobalUni(s); + return us; + } + } +} \ No newline at end of file diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/XmlFilePathExtractor.cs b/WDACConfig/WinUI3/Shared Logics/Other Functions/XmlFilePathExtractor.cs new file mode 100644 index 000000000..4ec8bb807 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Other Functions/XmlFilePathExtractor.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Xml; + +#nullable enable + +namespace WDACConfig +{ + public class XmlFilePathExtractor + { + public static HashSet GetFilePaths(string xmlFilePath) + { + // Initialize HashSet with StringComparer.OrdinalIgnoreCase to ensure case-insensitive, ordinal comparison + HashSet filePaths = new(StringComparer.OrdinalIgnoreCase); + + XmlDocument doc = new(); + doc.Load(xmlFilePath); + + // Create and configure XmlNamespaceManager + XmlNamespaceManager nsmgr = new(doc.NameTable); + nsmgr.AddNamespace("ns", "urn:schemas-microsoft-com:sipolicy"); + + // Select all nodes with the "Allow" tag + XmlNodeList? allowNodes = doc.SelectNodes("//ns:Allow", nsmgr); + + if (allowNodes != null) + { + + foreach (XmlNode node in allowNodes) + { + // Ensure node.Attributes is not null + if (node.Attributes != null && node.Attributes["FilePath"] != null) + { + // Add the file path to the HashSet + _ = filePaths.Add(node.Attributes["FilePath"]!.Value); + } + } + } + + return filePaths; + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Types And Definitions/AuthenticodePageHashes.cs b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/AuthenticodePageHashes.cs new file mode 100644 index 000000000..7edf4b4a3 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/AuthenticodePageHashes.cs @@ -0,0 +1,12 @@ +#nullable enable + +namespace WDACConfig +{ + public class CodeIntegrityHashes(string? sha1Page, string? sha256Page, string? sha1Authenticode, string? sha256Authenticode) + { + public string? SHA1Page { get; set; } = sha1Page; + public string? SHA256Page { get; set; } = sha256Page; + public string? SHa1Authenticode { get; set; } = sha1Authenticode; + public string? SHA256Authenticode { get; set; } = sha256Authenticode; + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Types And Definitions/CertificateDetailsCreator.cs b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/CertificateDetailsCreator.cs new file mode 100644 index 000000000..e0777e36b --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/CertificateDetailsCreator.cs @@ -0,0 +1,13 @@ + +#nullable enable + +namespace WDACConfig +{ + public class CertificateDetailsCreator(string intermediateCertTBS, string intermediateCertName, string leafCertTBS, string leafCertName) + { + public string IntermediateCertTBS { get; set; } = intermediateCertTBS; + public string IntermediateCertName { get; set; } = intermediateCertName; + public string LeafCertTBS { get; set; } = leafCertTBS; + public string LeafCertName { get; set; } = leafCertName; + } +} \ No newline at end of file diff --git a/WDACConfig/WinUI3/Shared Logics/Types And Definitions/CertificateSignerCreator.cs b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/CertificateSignerCreator.cs new file mode 100644 index 000000000..625c2615a --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/CertificateSignerCreator.cs @@ -0,0 +1,11 @@ +#nullable enable + +namespace WDACConfig +{ + public class CertificateSignerCreator(string tbs, string signerName, int siSigningScenario) + { + public string TBS { get; set; } = tbs; + public string SignerName { get; set; } = signerName; + public int SiSigningScenario { get; set; } = siSigningScenario; + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Types And Definitions/ChainElement.cs b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/ChainElement.cs new file mode 100644 index 000000000..228ae3140 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/ChainElement.cs @@ -0,0 +1,25 @@ +using System; +using System.Security.Cryptography.X509Certificates; + +#nullable enable + +namespace WDACConfig +{ + // the enum for CertificateType + public enum CertificateType + { + Root = 0, + Intermediate = 1, + Leaf = 2 + } + + public class ChainElement(string subjectcn, string issuercn, DateTime notafter, string tbsvalue, X509Certificate2 certificate, CertificateType type) + { + public string SubjectCN { get; set; } = subjectcn; + public string IssuerCN { get; set; } = issuercn; + public DateTime NotAfter { get; set; } = notafter; + public string TBSValue { get; set; } = tbsvalue; + public X509Certificate2 Certificate { get; set; } = certificate; + public CertificateType Type { get; set; } = type; + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Types And Definitions/ChainPackage.cs b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/ChainPackage.cs new file mode 100644 index 000000000..93e13f089 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/ChainPackage.cs @@ -0,0 +1,19 @@ +using System.Security.Cryptography.Pkcs; +using System.Security.Cryptography.X509Certificates; + +#nullable enable + +namespace WDACConfig +{ + public class ChainPackage(X509Chain certificatechain, SignedCms signedcms, WDACConfig.ChainElement rootcertificate, + WDACConfig.ChainElement[] intermediatecertificates, + WDACConfig.ChainElement leafcertificate) + { + public X509Chain CertificateChain { get; set; } = certificatechain; + public SignedCms SignedCms { get; set; } = signedcms; + public WDACConfig.ChainElement RootCertificate { get; set; } = rootcertificate; + public WDACConfig.ChainElement[] IntermediateCertificates { get; set; } = intermediatecertificates; + public WDACConfig.ChainElement LeafCertificate { get; set; } = leafcertificate; + } +} + diff --git a/WDACConfig/WinUI3/Shared Logics/Types And Definitions/FileBasedInfoPackage.cs b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/FileBasedInfoPackage.cs new file mode 100644 index 000000000..fb8529852 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/FileBasedInfoPackage.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +#nullable enable + +namespace WDACConfig +{ + // Used by the BuildSignerAndHashObjects method to store and return the output + public class FileBasedInfoPackage(List filepublishersigners, List publishersigners, List completehashes) + { + public List FilePublisherSigners { get; set; } = filepublishersigners; + public List PublisherSigners { get; set; } = publishersigners; + public List CompleteHashes { get; set; } = completehashes; + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Types And Definitions/FilePublisherSignerCreator.cs b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/FilePublisherSignerCreator.cs new file mode 100644 index 000000000..815c5562f --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/FilePublisherSignerCreator.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; + +#nullable enable + +namespace WDACConfig +{ + public class FilePublisherSignerCreator + { + public List CertificateDetails { get; set; } + public Version? FileVersion { get; set; } + public string? FileDescription { get; set; } + public string? InternalName { get; set; } + public string? OriginalFileName { get; set; } + public string? PackageFamilyName { get; set; } + public string? ProductName { get; set; } + public string? FileName { get; set; } + public string? AuthenticodeSHA256 { get; set; } + public string? AuthenticodeSHA1 { get; set; } + public int SiSigningScenario { get; set; } + + public FilePublisherSignerCreator( + List certificateDetails, + Version fileVersion, + string? fileDescription, + string? internalName, + string? originalFileName, + string? packageFamilyName, + string? productName, + string? fileName, + string? authenticodeSHA256, + string? authenticodeSHA1, + int siSigningScenario) + { + CertificateDetails = certificateDetails; + FileVersion = fileVersion; + FileDescription = fileDescription; + InternalName = internalName; + OriginalFileName = originalFileName; + PackageFamilyName = packageFamilyName; + ProductName = productName; + FileName = fileName; + AuthenticodeSHA256 = authenticodeSHA256; + AuthenticodeSHA1 = authenticodeSHA1; + SiSigningScenario = siSigningScenario; + } + + public FilePublisherSignerCreator() + { + // Initialize CertificateDetails to an empty list to avoid null reference issues + CertificateDetails = []; + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Types And Definitions/HashCreator.cs b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/HashCreator.cs new file mode 100644 index 000000000..3eb1de338 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/HashCreator.cs @@ -0,0 +1,13 @@ + +#nullable enable + +namespace WDACConfig +{ + public class HashCreator(string authenticodeSHA256, string authenticodeSHA1, string fileName, int siSigningScenario) + { + public string AuthenticodeSHA256 { get; set; } = authenticodeSHA256; + public string AuthenticodeSHA1 { get; set; } = authenticodeSHA1; + public string FileName { get; set; } = fileName; + public int SiSigningScenario { get; set; } = siSigningScenario; + } +} \ No newline at end of file diff --git a/WDACConfig/WinUI3/Shared Logics/Types And Definitions/OpusSigner.cs b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/OpusSigner.cs new file mode 100644 index 000000000..778756cac --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/OpusSigner.cs @@ -0,0 +1,11 @@ + +#nullable enable + +namespace WDACConfig +{ + public class OpusSigner(string tbsHash, string subjectCN) + { + public string TBSHash { get; set; } = tbsHash; + public string SubjectCN { get; set; } = subjectCN; + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Types And Definitions/PolicyHashObj.cs b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/PolicyHashObj.cs new file mode 100644 index 000000000..04633850b --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/PolicyHashObj.cs @@ -0,0 +1,35 @@ +using System; + +#nullable enable + +// Used by WDAC Simulations +namespace WDACConfig +{ + public class PolicyHashObj(string hashvalue, string hashtype, string filepathforhash) + { + // Adding public getters and setters for the properties + public string HashValue { get; set; } = hashvalue; + public string HashType { get; set; } = hashtype; + public string FilePathForHash { get; set; } = filepathforhash; + + // Making sure any HashSet or collection using this class will only keep unique objects based on their HashValue property + + // Override the Equals method + public override bool Equals(object? obj) + { + if (obj == null || GetType() != obj.GetType()) + { + return false; + } + + var other = (PolicyHashObj)obj; + return string.Equals(HashValue, other.HashValue, StringComparison.OrdinalIgnoreCase); + } + + // Override the GetHashCode method + public override int GetHashCode() + { + return HashValue != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(HashValue) : 0; + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Types And Definitions/PublisherSignerCreator.cs b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/PublisherSignerCreator.cs new file mode 100644 index 000000000..7728a55d3 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/PublisherSignerCreator.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; + +#nullable enable + +namespace WDACConfig +{ + public class PublisherSignerCreator + { + public List CertificateDetails { get; set; } + public string? FileName { get; set; } + public string? AuthenticodeSHA256 { get; set; } + public string? AuthenticodeSHA1 { get; set; } + public int SiSigningScenario { get; set; } + + public PublisherSignerCreator(List certificateDetails, string fileName, string authenticodeSHA256, string authenticodeSHA1, int siSigningScenario) + { + CertificateDetails = certificateDetails; + FileName = fileName; + AuthenticodeSHA256 = authenticodeSHA256; + AuthenticodeSHA1 = authenticodeSHA1; + SiSigningScenario = siSigningScenario; + } + + public PublisherSignerCreator() + { + // Initialize CertificateDetails to an empty list to avoid null reference issues + CertificateDetails = []; + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Types And Definitions/Signer.cs b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/Signer.cs new file mode 100644 index 000000000..8ff679ad6 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/Signer.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +#nullable enable + +namespace WDACConfig +{ + public class Signer(string id, string name, string certRoot, string certPublisher, string certIssuer, + string[] certEKU, string certOemID, string[] fileAttribRef, + Dictionary> fileAttrib, + string signerScope, bool isWHQL, bool isAllowed, bool hasEKU) + { + public string ID { get; set; } = id; + public string Name { get; set; } = name; + public string CertRoot { get; set; } = certRoot; + public string CertPublisher { get; set; } = certPublisher; + public string CertIssuer { get; set; } = certIssuer; + public string[] CertEKU { get; set; } = certEKU; + public string CertOemID { get; set; } = certOemID; + public string[] FileAttribRef { get; set; } = fileAttribRef; + public Dictionary> FileAttrib { get; set; } = fileAttrib; + public string SignerScope { get; set; } = signerScope; + public bool IsWHQL { get; set; } = isWHQL; + public bool IsAllowed { get; set; } = isAllowed; + public bool HasEKU { get; set; } = hasEKU; + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Types And Definitions/SimulationInput.cs b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/SimulationInput.cs new file mode 100644 index 000000000..8c5d474f4 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/SimulationInput.cs @@ -0,0 +1,15 @@ + +#nullable enable + +// Used by WDAC Simulations +namespace WDACConfig +{ + public class SimulationInput(System.IO.FileInfo filepath, WDACConfig.ChainPackage[] allfilesigners, WDACConfig.Signer[] signerinfo, string[] ekuoids) + { + // Adding public getters and setters for the properties + public System.IO.FileInfo FilePath { get; set; } = filepath; + public WDACConfig.ChainPackage[] AllFileSigners { get; set; } = allfilesigners; + public WDACConfig.Signer[] SignerInfo { get; set; } = signerinfo; + public string[] EKUOIDs { get; set; } = ekuoids; + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Types And Definitions/SimulationOutput.cs b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/SimulationOutput.cs new file mode 100644 index 000000000..bafbc6b38 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/SimulationOutput.cs @@ -0,0 +1,74 @@ +#nullable enable + +// Used by WDAC Simulations, the output of the comparer function/method +namespace WDACConfig +{ + // This class holds the details of the current file in the WDAC Simulation comparer + public class SimulationOutput( + string path, + string source, + bool isAuthorized, + string signerID, + string signerName, + string signerCertRoot, + string signerCertPublisher, + string signerScope, + string[] signerFileAttributeIDs, + string matchCriteria, + string? specificFileNameLevelMatchCriteria, + string certSubjectCN, + string certIssuerCN, + string certNotAfter, + string certTBSValue, + string filePath + ) + { + // The name of the file, which is a truncated version of its path + public string Path { get; set; } = path; + + // Source from the Comparer function is always 'Signer' + public string Source { get; set; } = source; + + // Whether the file is authorized or not + public bool IsAuthorized { get; set; } = isAuthorized; + + // Gathered from the Get-SignerInfo function + public string SignerID { get; set; } = signerID; + + // Gathered from the Get-SignerInfo function + public string SignerName { get; set; } = signerName; + + // Gathered from the Get-SignerInfo function + public string SignerCertRoot { get; set; } = signerCertRoot; + + // Gathered from the Get-SignerInfo function + public string SignerCertPublisher { get; set; } = signerCertPublisher; + + // Gathered from the Get-SignerInfo function + public string SignerScope { get; set; } = signerScope; + + // Gathered from the Get-SignerInfo function + public string[] SignerFileAttributeIDs { get; set; } = signerFileAttributeIDs; + + // The main level based on which the file is authorized + public string MatchCriteria { get; set; } = matchCriteria; + + // Only those eligible for FilePublisher, WHQLFilePublisher, or SignedVersion levels assign this value, otherwise it stays null + public string? SpecificFileNameLevelMatchCriteria { get; set; } = specificFileNameLevelMatchCriteria; + + // Subject CN of the signer that allows the file + public string CertSubjectCN { get; set; } = certSubjectCN; + + // Issuer CN of the signer that allows the file + public string CertIssuerCN { get; set; } = certIssuerCN; + + // NotAfter date of the signer that allows the file + public string CertNotAfter { get; set; } = certNotAfter; + + // TBS value of the signer that allows the file + public string CertTBSValue { get; set; } = certTBSValue; + + // Full path of the file + public string FilePath { get; set; } = filePath; + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Types And Definitions/WinTrust.cs b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/WinTrust.cs new file mode 100644 index 000000000..10b966f35 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Types And Definitions/WinTrust.cs @@ -0,0 +1,130 @@ +using System; +using System.Runtime.InteropServices; + +namespace WDACConfig +{ + // This class contains all of the WinTrust related functions and codes + internal partial class WinTrust + { + #region necessary logics for Authenticode and First Page hash calculation + + // a constant field that defines a flag value for the native function + // This causes/helps the GetCiFileHashes method to return the flat file hashes whenever a non-conformant file is encountered + internal const uint CryptcatadminCalchashFlagNonconformantFilesFallbackFlat = 1; + + // a method to acquire a handle to a catalog administrator context using a native function from WinTrust.dll + [DllImport("WinTrust.dll", CharSet = CharSet.Unicode)] + internal static extern bool CryptCATAdminAcquireContext2( + ref IntPtr hCatAdmin, // the first parameter: a reference to a pointer to store the handle + IntPtr pgSubsystem, // the second parameter: a pointer to a GUID that identifies the subsystem + string pwszHashAlgorithm, // the third parameter: a string that specifies the hash algorithm to use + IntPtr pStrongHashPolicy, // the fourth parameter: a pointer to a structure that specifies the strong hash policy + uint dwFlags // the fifth parameter: a flag value that controls the behavior of the function + ); + + // a method to release a handle to a catalog administrator context using a native function from WinTrust.dll + [DllImport("WinTrust.dll", CharSet = CharSet.Unicode)] + internal static extern bool CryptCATAdminReleaseContext( + IntPtr hCatAdmin, // the first parameter: a pointer to the handle to release + uint dwFlags // the second parameter: a flag value that controls the behavior of the function + ); + + // a method to calculate the hash of a file using a native function from WinTrust.dll + [DllImport("WinTrust.dll", CharSet = CharSet.Unicode)] + internal static extern bool CryptCATAdminCalcHashFromFileHandle3( + IntPtr hCatAdmin, // the first parameter: a pointer to the handle of the catalog administrator context + IntPtr hFile, // the second parameter: a pointer to the handle of the file to hash + ref int pcbHash, // the third parameter: a reference to an integer that specifies the size of the hash buffer + IntPtr pbHash, // the fourth parameter: a pointer to a buffer to store the hash value + uint dwFlags // the fifth parameter: a flag value that controls the behavior of the function + ); + + #endregion + + + + #region This section is related to the MeowParser class operations + + // P/Invoke declaration to import the 'CryptCATOpen' function from 'WinTrust.dll'. + // https://learn.microsoft.com/en-us/windows/win32/api/mscat/nf-mscat-cryptcatopen + [DllImport("WinTrust.dll", CharSet = CharSet.Auto, SetLastError = true)] + internal static extern IntPtr CryptCATOpen( + [MarshalAs(UnmanagedType.LPWStr)] string FileName, // The name of the catalog file. + uint OpenFlags, // Flags to control the function behavior. + IntPtr MainCryptProviderHandle, // Handle to the cryptographic service provider. + uint PublicVersion, // The public version number. + uint EncodingType); // The encoding type. + + // P/Invoke declaration to import the 'CryptCATEnumerateMember' function from 'WinTrust.dll'. + // https://learn.microsoft.com/en-us/windows/win32/api/mscat/nf-mscat-cryptcatenumeratemember + [DllImport("WinTrust.dll", CharSet = CharSet.Auto, SetLastError = true)] + internal static extern IntPtr CryptCATEnumerateMember( + IntPtr MeowLogHandle, // Handle to the catalog context. + IntPtr PrevCatalogMember); // Pointer to the previous catalog member. + + // P/Invoke declaration to import the 'CryptCATClose' function from 'WinTrust.dll'. + // https://learn.microsoft.com/en-us/windows/win32/api/mscat/nf-mscat-cryptcatclose + [DllImport("WinTrust.dll", CharSet = CharSet.Auto, SetLastError = true)] + internal static extern IntPtr CryptCATClose(IntPtr MainCryptProviderHandle); // Closes the catalog context. + + #endregion + + + + #region This section is related to the PageHashCalculator class + + // a method to compute the hash of the first page of a file using a native function from Wintrust.dll + [DllImport("Wintrust.dll", CharSet = CharSet.Unicode)] // an attribute to specify the DLL name and the character set + internal static extern int ComputeFirstPageHash( // the method signature + string pszAlgId, // the first parameter: the name of the hash algorithm to use + string filename, // the second parameter: the name of the file to hash + IntPtr buffer, // the third parameter: a pointer to a buffer to store the hash value + int bufferSize // the fourth parameter: the size of the buffer in bytes + ); + + #endregion + + + + #region This section is related to the AllCertificatesGrabber class and its operations + + // Enum defining WinVerifyTrust results + public enum WinVerifyTrustResult : uint + { + Success = 0, // It's Success + SubjectCertificateRevoked = 2148204812, // Subject's certificate was revoked. (CERT_E_REVOKED) + SubjectNotTrusted = 2148204548, // Subject failed the specified verification action + CertExpired = 2148204801, // This is checked for - Signer's certificate was expired. (CERT_E_EXPIRED) + UntrustedRootCert = 2148204809, // A certification chain processed correctly but terminated in a root certificate that is not trusted by the trust provider. (CERT_E_UNTRUSTEDROOT) + HashMismatch = 2148098064, // This is checked for (aka: SignatureOrFileCorrupt) - (TRUST_E_BAD_DIGEST) + ProviderUnknown = 2148204545, // Trust provider is not recognized on this system + ActionUnknown = 2148204546, // Trust provider does not support the specified action + SubjectFormUnknown = 2148204547, // Trust provider does not support the subject's form + FileNotSigned = 2148204800, // File is not signed. (TRUST_E_NOSIGNATURE) + SubjectExplicitlyDistrusted = 2148204817, // Signer's certificate is in the Untrusted Publishers store + } + + + // Constants related to WinTrust + internal const uint StateActionVerify = 1; + internal const uint StateActionClose = 2; + internal static readonly Guid GenericWinTrustVerifyActionGuid = new("{00AAC56B-CD44-11d0-8CC2-00C04FC295EE}"); + + // External method declarations for WinVerifyTrust and WTHelperProvDataFromStateData + [DllImport("wintrust.dll", CharSet = CharSet.Unicode)] + + // https://learn.microsoft.com/en-us/windows/win32/api/wintrust/nf-wintrust-winverifytrust + // Set to return a WinVerifyTrustResult enum + internal static extern WinVerifyTrustResult WinVerifyTrust( + IntPtr hwnd, + [MarshalAs(UnmanagedType.LPStruct)] Guid pgActionID, + IntPtr pWVTData); + + // https://learn.microsoft.com/en-us/windows/win32/api/wintrust/nf-wintrust-wthelperprovdatafromstatedata + [DllImport("wintrust.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern IntPtr WTHelperProvDataFromStateData(IntPtr hStateData); + + #endregion + + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Variables/CILogIntel.cs b/WDACConfig/WinUI3/Shared Logics/Variables/CILogIntel.cs new file mode 100644 index 000000000..2c3a6f3b6 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Variables/CILogIntel.cs @@ -0,0 +1,74 @@ +using System.Collections.Generic; + +#nullable enable + +namespace WDACConfig +{ + // Application Control event tags intelligence + public class CILogIntel + { + // Requested and Validated Signing Level Mappings: https://learn.microsoft.com/en-us/windows/security/application-security/application-control/windows-defender-application-control/operations/event-tag-explanations#requested-and-validated-signing-level + public static readonly Dictionary ReqValSigningLevels = new() + { + { 0 , "Signing level hasn't yet been checked"}, + { 1 , "File is unsigned or has no signature that passes the active policies"}, + { 2 , "Trusted by Windows Defender Application Control policy"}, + { 3 , "Developer signed code"}, + { 4 , "Authenticode signed"}, + { 5 , "Microsoft Store signed app PPL (Protected Process Light)"}, + { 6 , "Microsoft Store-signed"}, + { 7 , "Signed by an Antimalware vendor whose product is using AMPPL"}, + { 8 , "Microsoft signed"}, + { 11 , "Only used for signing of the .NET NGEN compiler"}, + { 12 , "Windows signed"}, + { 14 , "Windows Trusted Computing Base signed"} + }; + + // SignatureType Mappings: https://learn.microsoft.com/en-us/windows/security/application-security/application-control/windows-defender-application-control/operations/event-tag-explanations#signaturetype + public static readonly Dictionary SignatureTypeTable = new() + { + { 0, "Unsigned or verification hasn't been attempted" }, + { 1 , "Embedded signature" }, + { 2 , "Cached signature; presence of a CI EA means the file was previously verified" }, + { 3 , "Cached catalog verified via Catalog Database or searching catalog directly" }, + { 4 , "Uncached catalog verified via Catalog Database or searching catalog directly" }, + { 5 , "Successfully verified using an EA that informs CI that catalog to try first" }, + { 6 , "AppX / MSIX package catalog verified" }, + { 7 , "File was verified" } + }; + + // VerificationError mappings: https://learn.microsoft.com/en-us/windows/security/application-security/application-control/windows-defender-application-control/operations/event-tag-explanations#verificationerror + public static readonly Dictionary VerificationErrorTable = new() + { + { 0 , "Successfully verified signature."}, + { 1 , "File has an invalid hash."}, + { 2 , "File contains shared writable sections."}, + { 3 , "File isn't signed."}, + { 4 , "Revoked signature."}, + { 5 , "Expired signature."}, + { 6 , "File is signed using a weak hashing algorithm, which doesn't meet the minimum policy."}, + { 7 , "Invalid root certificate."}, + { 8 , "Signature was unable to be validated; generic error."}, + { 9 , "Signing time not trusted."}, + { 10 , "The file must be signed using page hashes for this scenario."}, + { 11 , "Page hash mismatch."}, + { 12 , "Not valid for a PPL (Protected Process Light)."}, + { 13 , "Not valid for a PP (Protected Process)."}, + { 14 , "The signature is missing the required ARM processor EKU."}, + { 15 , "Failed WHQL check."}, + { 16 , "Default policy signing level not met."}, + { 17 , "Custom policy signing level not met; returned when signature doesn't validate against an SBCP-defined set of certs."}, + { 18 , "Custom signing level not met; returned if signature fails to match CISigners in UMCI."}, + { 19 , "Binary is revoked based on its file hash."}, + { 20 , "SHA1 cert hash's timestamp is missing or after valid cutoff as defined by Weak Crypto Policy."}, + { 21 , "Failed to pass Windows Defender Application Control policy."}, + { 22 , "Not Isolated User Mode (IUM) signed; indicates an attempt to load a standard Windows binary into a virtualization-based security (VBS) trustlet."}, + { 23 , "Invalid image hash. This error can indicate file corruption or a problem with the file's signature. Signatures using elliptic curve cryptography (ECC), such as ECDSA, return this VerificationError."}, + { 24 , "Flight root not allowed; indicates trying to run flight-signed code on production OS."}, + { 25 , "Anti-cheat policy violation."}, + { 26 , "Explicitly denied by WDAC policy."}, + { 27 , "The signing chain appears to be tampered / invalid."}, + { 28 , "Resource page hash mismatch."} + }; + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/Variables/GlobalVars.cs b/WDACConfig/WinUI3/Shared Logics/Variables/GlobalVars.cs new file mode 100644 index 000000000..1ef292372 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/Variables/GlobalVars.cs @@ -0,0 +1,55 @@ +using System; +using System.IO; +using System.Management.Automation.Host; + +#nullable enable + +namespace WDACConfig +{ + // This class defines constant variables and makes them available app-domain-wide for PowerShell + public static class GlobalVars + { + // Global variable available app-domain wide to track whether ConfigCI bootstrapping has been run or not + public static bool ConfigCIBootstrap; + + // User Mode block rules + public const string MSFTRecommendedBlockRulesURL = "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"; + + // Kernel Mode block rules + public const string MSFTRecommendedDriverBlockRulesURL = "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"; + + // Minimum required OS build number + public const decimal Requiredbuild = 22621.3447M; + + // Current OS build version + public static decimal OSBuildNumber = Environment.OSVersion.Version.Build; + + // Update Build Revision (UBR) number + public static int UBR; + + // Stores the value of $PSScriptRoot to allow the internal functions to use it when navigating the module structure + // It's set by PowerShell code outside of C# + public static string? ModuleRootPath; + + // Create full OS build number as seen in Windows Settings + public static string? FullOSBuild; + + // Storing the path to the WDAC Code Integrity Schema XSD file + public static readonly string CISchemaPath = Path.Combine( + Environment.GetEnvironmentVariable("SystemDrive") + @"\", + "Windows", "schemas", "CodeIntegrity", "cipolicy.xsd"); + + // Storing the path to the WDACConfig folder in the Program Files + public static readonly string UserConfigDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WDACConfig"); + + // Storing the path to User Config JSON file in the WDACConfig folder in the Program Files + public static readonly string UserConfigJson = Path.Combine(UserConfigDir, "UserConfigurations", "UserConfigurations.json"); + + public static bool VerbosePreference; + public static bool DebugPreference; + + // The value of the automatic variable $HOST from the PowerShell session + // Stored using the LoggerInitializer method that is called at the beginning of each cmdlet + public static PSHost? Host; + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/WDAC Simulation/GetFileRuleOutput.cs b/WDACConfig/WinUI3/Shared Logics/WDAC Simulation/GetFileRuleOutput.cs new file mode 100644 index 000000000..bdc5a89cc --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/WDAC Simulation/GetFileRuleOutput.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml; + +#nullable enable + +namespace WDACConfig +{ + public static class GetFileRuleOutput + { + /// + /// A function that accepts WDAC policy XML content and creates an output array that contains the file rules that are based on file hashes. + /// The function is intentionally not made to handle Allow all rules since checking for their existence happens in the main cmdlet. + /// + /// + /// + public static HashSet Get(XmlDocument xml) + { + // Create an empty HashSet to store the output + var outputHashInfoProcessing = new HashSet(); + + // Get the namespace manager + var nsmgr = new XmlNamespaceManager(xml.NameTable); + nsmgr.AddNamespace("si", "urn:schemas-microsoft-com:sipolicy"); + + // Loop through each file rule in the XML file + var fileRules = xml.SelectNodes("//si:FileRules/si:Allow", nsmgr); + if (fileRules != null) + { + foreach (XmlNode fileRule in fileRules) + { + if (fileRule.Attributes != null) + { + // Extract the hash value from the Hash attribute + var hashValue = fileRule.Attributes["Hash"]?.InnerText; + + // Extract the hash type and file path from the FriendlyName attribute using regex + var friendlyName = fileRule.Attributes["FriendlyName"]?.InnerText; + if (!string.IsNullOrEmpty(friendlyName)) + { + // Extract the hash type from the FriendlyName attribute using regex + var hashTypeMatch = System.Text.RegularExpressions.Regex.Match(friendlyName, @".* (Hash (Sha1|Sha256|Page Sha1|Page Sha256|Authenticode SIP Sha256))$", RegexOptions.IgnoreCase); + var hashType = hashTypeMatch.Success ? hashTypeMatch.Groups[1].Value : string.Empty; + + // Extract the file path from the FriendlyName attribute using regex + var filePathForHash = System.Text.RegularExpressions.Regex.Replace(friendlyName, @" (Hash (Sha1|Sha256|Page Sha1|Page Sha256|Authenticode SIP Sha256))$", string.Empty, RegexOptions.IgnoreCase); + + // Add the extracted values of the current Hash rule to the output HashSet + if (!string.IsNullOrEmpty(hashValue) && !string.IsNullOrEmpty(hashType) && !string.IsNullOrEmpty(filePathForHash)) + { + _ = outputHashInfoProcessing.Add(new WDACConfig.PolicyHashObj(hashValue, hashType, filePathForHash)); + } + } + } + } + } + + // Only keep the Authenticode Hash SHA256 + outputHashInfoProcessing = new HashSet(outputHashInfoProcessing.Where(obj => string.Equals(obj.HashType, "Hash Sha256", StringComparison.OrdinalIgnoreCase))); + + WDACConfig.Logger.Write($"Returning {outputHashInfoProcessing.Count} file rules that are based on file hashes"); + + // Return the output HashSet + return outputHashInfoProcessing; + } + } +} diff --git a/WDACConfig/WinUI3/Shared Logics/XMLOps/SignerAndHashBuilder.cs b/WDACConfig/WinUI3/Shared Logics/XMLOps/SignerAndHashBuilder.cs new file mode 100644 index 000000000..6058fcfe6 --- /dev/null +++ b/WDACConfig/WinUI3/Shared Logics/XMLOps/SignerAndHashBuilder.cs @@ -0,0 +1,402 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; + +#nullable enable + +namespace WDACConfig +{ + public static class SignerAndHashBuilder + { + /// + /// Creates Signer and Hash objects from the input data + /// + /// Types created for Signed Data: FilePublisher, Publisher + /// Types created for Unsigned Data: Hash + /// + /// Behavior when the level is set to "Auto" or "FilePublisher": + /// FilePublisher Signers are created for files that have the necessary details for a FilePublisher rule + /// Publisher Signers are created for files that don't have the necessary details for a FilePublisher rule + /// Hashes are created for the unsigned data + /// + /// Behavior when the level is set to "Publisher": + /// PublisherSigners are created for all of the Signed files + /// Hashes are created for all of the unsigned files + /// + /// Behavior when the level is set to "Hash": + /// Hashes are created for all of the files, whether they are signed or unsigned + /// + /// The output is a single object with nested properties for the Signed data and Hashes + /// + /// Both Publisher and FilePublisher signers first check if the file has both Issuer and Publisher TBS hashes, if they are present then both of them will be used to create the Signer. + /// If the file is missing the Issuer TBS hash, then the Publisher certificate will be used for both Publisher and Issuer details (TBS and Name) + /// This will essentially create the Signers based on LeafCertificate Level. + /// + /// The New-FilePublisherLevelRules and New-PublisherLevelRules functions both are able to create rules based on different signer WDAC levels. + /// + /// The other way around, where Publisher TBS hash is missing but Issuer TBS is present, would create a PCACertificate level Signer, but that is not implemented yet. + /// Its use case is not clear yet and there haven't been any files with that condition yet. + /// + /// The Data to be processed. These are the logs selected by the user and contain both signed and unsigned data. + /// + /// The type of data that is being processed. This is used to determine the property names in the input data. + /// The default value is 'MDEAH' (Microsoft Defender Application Guard Event and Hash) and the other value is 'EVTX' (Event Log evtx files). + /// + /// Auto, FilePublisher, Publisher, Hash + /// It will pass any publisher rules to the hash array. E.g when sandboxing-like behavior using Macros and AppIDs are used. + /// + public static FileBasedInfoPackage BuildSignerAndHashObjects(Hashtable[] data, string incomingDataType = "MDEAH", string level = "Auto", bool publisherToHash = false) + { + // An array to store the Signers created with FilePublisher Level + List filePublisherSigners = []; + + // An array to store the Signers created with Publisher Level + List publisherSigners = []; + + // An array to store the FileAttributes created using Hash Level + List completeHashes = []; + + // Lists to separate data + List signedFilePublisherData = []; + List signedPublisherData = []; + List unsignedData = []; + + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Starting the data separation process."); + + // Data separation based on the level + switch (level.ToLowerInvariant()) + { + // If Hash level is used then add everything to the Unsigned data so Hash rules will be created for them + case "hash": + + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Using only Hash level."); + + foreach (var item in data) + { + if (item == null) + { + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Found a null item in data."); + } + else + { + unsignedData.Add(item); + } + } + break; + + // If Publisher level is used then add all Signed data to the SignedPublisherData list and Unsigned data to the Hash list + case "publisher": + + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Using Publisher -> Hash levels."); + + foreach (var item in data) + { + if (item == null) + { + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Found a null item in data."); + } + else if ( + item.ContainsKey("SignatureStatus") && + item["SignatureStatus"] != null && + string.Equals(item["SignatureStatus"]?.ToString(), "Signed", StringComparison.OrdinalIgnoreCase) && + !publisherToHash) + { + signedPublisherData.Add(item); + } + else + { + unsignedData.Add(item); + } + } + break; + + // Detect and separate FilePublisher, Publisher and Hash (Unsigned) data if the level is Auto or FilePublisher + default: + + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Using FilePublisher -> Publisher -> Hash levels."); + + // Loop over each data + foreach (var item in data) + { + if (item == null) + { + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Found a null item in data."); + } + // If the file's version is empty or it has no file attribute, then add it to the Publishers array + // because FilePublisher rule cannot be created for it + else if ( + item.ContainsKey("SignatureStatus") && + item["SignatureStatus"] != null && + string.Equals(item["SignatureStatus"]?.ToString(), "Signed", StringComparison.OrdinalIgnoreCase)) + { + // Safely get values from the item and check for null or whitespace + bool hasNoFileAttributes = string.IsNullOrWhiteSpace(item.ContainsKey("OriginalFileName") ? item["OriginalFileName"]?.ToString() : null) && + string.IsNullOrWhiteSpace(item.ContainsKey("InternalName") ? item["InternalName"]?.ToString() : null) && + string.IsNullOrWhiteSpace(item.ContainsKey("FileDescription") ? item["FileDescription"]?.ToString() : null) && + string.IsNullOrWhiteSpace(item.ContainsKey("ProductName") ? item["ProductName"]?.ToString() : null); + + bool hasNoFileVersion = string.IsNullOrWhiteSpace(item.ContainsKey("FileVersion") ? item["FileVersion"]?.ToString() : null); + + if (hasNoFileAttributes || hasNoFileVersion) + { + // if PublisherToHash is not used then add it to the Publisher array normally + if (!publisherToHash) + { + signedPublisherData.Add(item); + } + else + { + WDACConfig.Logger.Write($"BuildSignerAndHashObjects: Passing Publisher rule to the hash array for the file: {(string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? item["FileName"] : item["File Name"])}"); + // Add the current signed data to Unsigned data array so that Hash rules will be created for it instead + unsignedData.Add(item); + } + } + else + { + signedFilePublisherData.Add(item); + } + } + else + { + unsignedData.Add(item); + } + } + break; + } + + WDACConfig.Logger.Write($"BuildSignerAndHashObjects: {signedFilePublisherData.Count} FilePublisher Rules."); + WDACConfig.Logger.Write($"BuildSignerAndHashObjects: {signedPublisherData.Count} Publisher Rules."); + WDACConfig.Logger.Write($"BuildSignerAndHashObjects: {unsignedData.Count} Hash Rules."); + + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Processing FilePublisher data."); + + foreach (var signedData in signedFilePublisherData) + { + // Create a new FilePublisherSignerCreator object + WDACConfig.FilePublisherSignerCreator currentFilePublisherSigner = new(); + + // Get the certificate details of the current event data based on the incoming type, they can be stored under different names. + // Safely casting the objects to a HashTable, returning null if the cast fails instead of throwing an exception. + ICollection? correlatedEventsDataValues = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) + ? (signedData["CorrelatedEventsData"] as Hashtable)?.Values + : (signedData["SignerInfo"] as Hashtable)?.Values; + + if (correlatedEventsDataValues == null) + { + WDACConfig.Logger.Write("BuildSignerAndHashObjects: correlatedEventsDataValues is null."); + } + else + { + // Loop through each correlated event and process the certificate details + foreach (Hashtable corDataValue in correlatedEventsDataValues) + { + + // If the file doesn't have Issuer TBS hash (aka Intermediate certificate hash), use the leaf cert's TBS hash and CN instead (aka publisher TBS hash) + // This is according to the ConfigCI's workflow when encountering specific files + // MDE doesn't generate Issuer TBS hash for some files + // For those files, the FilePublisher rule will be created with the file's leaf Certificate details only (Publisher certificate) + + // Safely access dictionary values and handle nulls + string? issuerTBSHash = corDataValue.ContainsKey("IssuerTBSHash") ? corDataValue["IssuerTBSHash"]?.ToString() : null; + string? publisherTBSHash = corDataValue.ContainsKey("PublisherTBSHash") ? corDataValue["PublisherTBSHash"]?.ToString() : null; + + // currentCorData to store the current SignerInfo/Correlated + CertificateDetailsCreator? currentCorData; + // Perform the check with null-safe values + if (string.IsNullOrWhiteSpace(issuerTBSHash) && !string.IsNullOrWhiteSpace(publisherTBSHash)) + { + WDACConfig.Logger.Write($"BuildSignerAndHashObjects: Intermediate Certificate TBS hash is empty for the file: {(string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? signedData["FileName"] : signedData["File Name"])}, using the leaf certificate TBS hash instead"); + + currentCorData = new WDACConfig.CertificateDetailsCreator( + corDataValue["PublisherTBSHash"]!.ToString()!, + corDataValue["PublisherName"]!.ToString()!, + corDataValue["PublisherTBSHash"]!.ToString()!, + corDataValue["PublisherName"]!.ToString()! + ); + + } + else + { + currentCorData = new WDACConfig.CertificateDetailsCreator( + corDataValue["IssuerTBSHash"]!.ToString()!, + corDataValue["IssuerName"]!.ToString()!, + corDataValue["PublisherTBSHash"]!.ToString()!, + corDataValue["PublisherName"]!.ToString()! + ); + } + + // Add the Certificate details to the CurrentFilePublisherSigner's CertificateDetails property + currentFilePublisherSigner.CertificateDetails.Add(currentCorData); + + } + } + + #region Initialize properties with null-safe checks + string? fileVersionString = signedData.ContainsKey("FileVersion") ? signedData["FileVersion"]?.ToString() : null; + string? fileDescription = signedData.ContainsKey("FileDescription") ? signedData["FileDescription"]?.ToString() : null; + string? internalName = signedData.ContainsKey("InternalName") ? signedData["InternalName"]?.ToString() : null; + string? originalFileName = signedData.ContainsKey("OriginalFileName") ? signedData["OriginalFileName"]?.ToString() : null; + string? productName = signedData.ContainsKey("ProductName") ? signedData["ProductName"]?.ToString() : null; + string? fileName = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) + ? (signedData.ContainsKey("FileName") ? signedData["FileName"]?.ToString() : null) + : (signedData.ContainsKey("File Name") ? signedData["File Name"]?.ToString() : null); + + string? sha256 = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) + ? (signedData.ContainsKey("SHA256") ? signedData["SHA256"]?.ToString() : null) + : (signedData.ContainsKey("SHA256 Hash") ? signedData["SHA256 Hash"]?.ToString() : null); + + string? sha1 = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) + ? (signedData.ContainsKey("SHA1") ? signedData["SHA1"]?.ToString() : null) + : (signedData.ContainsKey("SHA1 Hash") ? signedData["SHA1 Hash"]?.ToString() : null); + _ = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) + ? (signedData.ContainsKey("SiSigningScenario") ? signedData["SiSigningScenario"]?.ToString() : null) + : (signedData.ContainsKey("SI Signing Scenario") ? signedData["SI Signing Scenario"]?.ToString() : null); + + // Assign properties, handle null or missing values + currentFilePublisherSigner.FileVersion = !string.IsNullOrWhiteSpace(fileVersionString) + ? Version.Parse(fileVersionString) + : null; // Assign null if fileVersionString is null or empty + + currentFilePublisherSigner.FileDescription = fileDescription; + currentFilePublisherSigner.InternalName = internalName; + currentFilePublisherSigner.OriginalFileName = originalFileName; + currentFilePublisherSigner.ProductName = productName; + currentFilePublisherSigner.FileName = fileName; + currentFilePublisherSigner.AuthenticodeSHA256 = sha256; + currentFilePublisherSigner.AuthenticodeSHA1 = sha1; + + currentFilePublisherSigner.SiSigningScenario = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? int.Parse(signedData["SiSigningScenario"]!.ToString()!, CultureInfo.InvariantCulture) : (string.Equals(signedData["SI Signing Scenario"]!.ToString(), "Kernel-Mode", StringComparison.OrdinalIgnoreCase) ? 0 : 1); + #endregion + + // Check if necessary details are not empty + if (string.IsNullOrWhiteSpace(currentFilePublisherSigner.AuthenticodeSHA256)) + { + WDACConfig.Logger.Write($"SHA256 is empty for the file: {(string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? signedData["FileName"] : signedData["File Name"])}"); + } + + if (string.IsNullOrWhiteSpace(currentFilePublisherSigner.AuthenticodeSHA1)) + { + WDACConfig.Logger.Write($"SHA1 is empty for the file: {(string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? signedData["FileName"] : signedData["File Name"])}"); + } + + // Add the completed FilePublisherSigner to the list + filePublisherSigners.Add(currentFilePublisherSigner); + } + + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Processing Publisher data."); + + foreach (var signedData in signedPublisherData) + { + // Create a new PublisherSignerCreator object + WDACConfig.PublisherSignerCreator currentPublisherSigner = new(); + + // Get the certificate details of the current event data based on the incoming type, they can be stored under different names + ICollection? correlatedEventsDataValues = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) + ? (signedData?["CorrelatedEventsData"] as Hashtable)?.Values + : (signedData?["SignerInfo"] as Hashtable)?.Values; + + if (correlatedEventsDataValues == null) + { + WDACConfig.Logger.Write("BuildSignerAndHashObjects: correlatedEventsDataValues is null."); + } + else + { + // Process each correlated event + foreach (Hashtable corDataValue in correlatedEventsDataValues) + { + + // Safely access dictionary values and handle nulls + string? issuerTBSHash = corDataValue.ContainsKey("IssuerTBSHash") ? corDataValue["IssuerTBSHash"]?.ToString() : null; + string? issuerName = corDataValue.ContainsKey("IssuerName") ? corDataValue["IssuerName"]?.ToString() : null; + string? publisherTBSHash = corDataValue.ContainsKey("PublisherTBSHash") ? corDataValue["PublisherTBSHash"]?.ToString() : null; + string? publisherName = corDataValue.ContainsKey("PublisherName") ? corDataValue["PublisherName"]?.ToString() : null; + + CertificateDetailsCreator? currentCorData; + // Perform the check with null-safe values + if (string.IsNullOrWhiteSpace(issuerTBSHash) && !string.IsNullOrWhiteSpace(publisherTBSHash)) + { + WDACConfig.Logger.Write($"BuildSignerAndHashObjects: Intermediate Certificate TBS hash is empty for the file: {(string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? signedData!["FileName"] : signedData!["File Name"])}, using the leaf certificate TBS hash instead"); + + // Create a new CertificateDetailsCreator object with the safely retrieved and used values + currentCorData = new WDACConfig.CertificateDetailsCreator( + publisherTBSHash, + publisherName!, + publisherTBSHash, + publisherName! + ); + } + else + { + // Create a new CertificateDetailsCreator object with the safely retrieved and used values + currentCorData = new WDACConfig.CertificateDetailsCreator( + issuerTBSHash!, + issuerName!, + publisherTBSHash!, + publisherName! + ); + } + + // Add the Certificate details to the CurrentPublisherSigner's CertificateDetails property + currentPublisherSigner.CertificateDetails.Add(currentCorData); + } + } + + // Need to spend more time on this part to properly inspect how the methods getting data from the current method handle the nulls in this properties +#nullable disable + currentPublisherSigner.FileName = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? signedData["FileName"].ToString() : signedData["File Name"].ToString(); + currentPublisherSigner.AuthenticodeSHA256 = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? signedData["SHA256"].ToString() : signedData["SHA256 Hash"].ToString(); + currentPublisherSigner.AuthenticodeSHA1 = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? signedData["SHA1"].ToString() : signedData["SHA1 Hash"].ToString(); + currentPublisherSigner.SiSigningScenario = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? int.Parse(signedData["SiSigningScenario"].ToString(), CultureInfo.InvariantCulture) : (string.Equals(signedData["SI Signing Scenario"].ToString(), "Kernel-Mode", StringComparison.OrdinalIgnoreCase) ? 0 : 1); +#nullable enable + + // Add the completed PublisherSigner to the list + publisherSigners.Add(currentPublisherSigner); + } + + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Processing Unsigned Hash data."); + + foreach (var hashData in unsignedData) + { + if (hashData == null) + { + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Found a null hashData item."); + continue; + } + + string? sha256 = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) + ? (hashData.ContainsKey("SHA256") ? hashData["SHA256"]?.ToString() : null) + : (hashData.ContainsKey("SHA256 Hash") ? hashData["SHA256 Hash"]?.ToString() : null); + + string? sha1 = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) + ? (hashData.ContainsKey("SHA1") ? hashData["SHA1"]?.ToString() : null) + : (hashData.ContainsKey("SHA1 Hash") ? hashData["SHA1 Hash"]?.ToString() : null); + + string? fileName = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) + ? (hashData.ContainsKey("FileName") ? hashData["FileName"]?.ToString() : null) + : (hashData.ContainsKey("File Name") ? hashData["File Name"]?.ToString() : null); + + int siSigningScenario = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) + ? (hashData.ContainsKey("SiSigningScenario") ? int.Parse(hashData["SiSigningScenario"]?.ToString()!, CultureInfo.InvariantCulture) : 1) + : (hashData.ContainsKey("SI Signing Scenario") ? (string.Equals(hashData["SI Signing Scenario"]?.ToString(), "Kernel-Mode", StringComparison.OrdinalIgnoreCase) ? 0 : 1) : 1); + + if (string.IsNullOrWhiteSpace(sha256) || string.IsNullOrWhiteSpace(sha1) || string.IsNullOrWhiteSpace(fileName)) + { + WDACConfig.Logger.Write("BuildSignerAndHashObjects: One or more necessary properties are null or empty in hashData."); + continue; + } + + completeHashes.Add(new WDACConfig.HashCreator( + sha256, + sha1, + fileName, + siSigningScenario + )); + } + + WDACConfig.Logger.Write("BuildSignerAndHashObjects: Completed the process."); + + return new FileBasedInfoPackage(filePublisherSigners, publisherSigners, completeHashes); + } + } +} diff --git a/WDACConfig/WinUI3/WDACConfig.csproj b/WDACConfig/WinUI3/WDACConfig.csproj new file mode 100644 index 000000000..28fd24954 --- /dev/null +++ b/WDACConfig/WinUI3/WDACConfig.csproj @@ -0,0 +1,148 @@ + + + + WinExe + net9.0-windows10.0.22621.0 + 10.0.22621.0 + WDACConfig + app.manifest + x64;ARM64 + win-x64;win-arm64 + win10-x64;win10-arm64 + win-$(Platform).pubxml + true + true + enable + 10.0.22621.38 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + true + + + + False + True + True + True + 10.0.22621.0 + disable + An application that simplifies management of Application Control in Windows. + https://github.com/HotCakeX/Harden-Windows-Security + https://github.com/HotCakeX/Harden-Windows-Security + App Control,WDAC,WDACConfig + https://github.com/HotCakeX/Harden-Windows-Security/releases + + + False + False + SHA512 + True + + MSIXOutput\ + True + True + Auto + x64 + 0 + + + + True + + + True + + + True + + + True + + + + + + + + + + + + + + + + + + + MSBuild:Compile + + + + \ No newline at end of file diff --git a/WDACConfig/WinUI3/WDACConfig.csproj.user b/WDACConfig/WinUI3/WDACConfig.csproj.user new file mode 100644 index 000000000..a0b69f13c --- /dev/null +++ b/WDACConfig/WinUI3/WDACConfig.csproj.user @@ -0,0 +1,47 @@ + + + + J:\Cloned Repositories\Harden-Windows-Security\WDACConfig\IconFullSize.png + J:\Cloned Repositories\Harden-Windows-Security\WDACConfig\IconFullSize.png + J:\Cloned Repositories\Harden-Windows-Security\WDACConfig\IconFullSize.png + J:\Cloned Repositories\Harden-Windows-Security\WDACConfig\IconFullSize.png + J:\Cloned Repositories\Harden-Windows-Security\WDACConfig\IconFullSize.png + J:\Cloned Repositories\Harden-Windows-Security\WDACConfig\IconFullSize.png + J:\Cloned Repositories\Harden-Windows-Security\WDACConfig\IconFullSize.png + J:\Cloned Repositories\Harden-Windows-Security\WDACConfig\IconFullSize.png + J:\Cloned Repositories\Harden-Windows-Security\WDACConfig\IconFullSize.png + SideloadOnly + False + x64 + False + + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + \ No newline at end of file diff --git a/WDACConfig/WinUI3/WDACConfig.sln b/WDACConfig/WinUI3/WDACConfig.sln new file mode 100644 index 000000000..45b5461ca --- /dev/null +++ b/WDACConfig/WinUI3/WDACConfig.sln @@ -0,0 +1,34 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35309.182 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WDACConfig", "WDACConfig.csproj", "{8467BDD7-CAF9-478A-B74C-894D30C73E3A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8467BDD7-CAF9-478A-B74C-894D30C73E3A}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {8467BDD7-CAF9-478A-B74C-894D30C73E3A}.Debug|ARM64.Build.0 = Debug|ARM64 + {8467BDD7-CAF9-478A-B74C-894D30C73E3A}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {8467BDD7-CAF9-478A-B74C-894D30C73E3A}.Debug|x64.ActiveCfg = Debug|x64 + {8467BDD7-CAF9-478A-B74C-894D30C73E3A}.Debug|x64.Build.0 = Debug|x64 + {8467BDD7-CAF9-478A-B74C-894D30C73E3A}.Debug|x64.Deploy.0 = Debug|x64 + {8467BDD7-CAF9-478A-B74C-894D30C73E3A}.Release|ARM64.ActiveCfg = Release|ARM64 + {8467BDD7-CAF9-478A-B74C-894D30C73E3A}.Release|ARM64.Build.0 = Release|ARM64 + {8467BDD7-CAF9-478A-B74C-894D30C73E3A}.Release|ARM64.Deploy.0 = Release|ARM64 + {8467BDD7-CAF9-478A-B74C-894D30C73E3A}.Release|x64.ActiveCfg = Release|x64 + {8467BDD7-CAF9-478A-B74C-894D30C73E3A}.Release|x64.Build.0 = Release|x64 + {8467BDD7-CAF9-478A-B74C-894D30C73E3A}.Release|x64.Deploy.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {CA8BD1BF-7695-4E58-96BF-8CE787407C15} + EndGlobalSection +EndGlobal diff --git a/WDACConfig/WinUI3/app.manifest b/WDACConfig/WinUI3/app.manifest new file mode 100644 index 000000000..7b592664b --- /dev/null +++ b/WDACConfig/WinUI3/app.manifest @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + PerMonitorV2 + + + \ No newline at end of file diff --git a/WDACConfig/WinUI3/exclusion.dic b/WDACConfig/WinUI3/exclusion.dic new file mode 100644 index 000000000..e69de29bb diff --git a/WDACConfig/exclusion.dic b/WDACConfig/exclusion.dic new file mode 100644 index 000000000..648975bca --- /dev/null +++ b/WDACConfig/exclusion.dic @@ -0,0 +1 @@ +Namez diff --git a/WDACConfig/icon.png b/WDACConfig/icon.png index 353261deee28fca9fe646367ef3d7b37dc667c3c..30a0f61c31269b0417aec0d814d6e3bfdeeb2b7d 100644 GIT binary patch delta 2078 zcmV+(2;uj{CBG1mIDZH6Nkl4{R3Y9mk(C7Zw%nDx>d<8|~YISs;V9??dm@ zLFgTIgDo?ek!AM1f`h&%ML>hH7VpZv$2MmmE;*Wg;?3Vi1;4bn!bdYd$b$UJ>JrLvJF2c0j3NQQ~po6 z8Giz=m%r-WB~<`ab!wBNR$_lH!8;MquPwb-_CjM78t&i&+Kt%K6VaspZzth97tl8v zShnWrT+kTCN6jtEDoB0 z>?idLWiQ0%5El@&^z(j)jSE6Rx_zA|F2L`gaUlsXWuO8~Du9jvlK99Z1ye@24Rb@L#yVF8qHj?)o<-4t*&{L<&q6GYaSk(47l#5Wdav z3#X|7obM#;F#N!^pnRTQ^KBJm3_jrwggpr_+mXj4Y{W0QA>bsD(5wVR_q0Ow>nc?5 zZiVO@tK1PlB7CRIP(waJTmZK8L-~du?SIsu`ZWbAUulNQmsdb##|o(0u`(^g7DG$` zsVm?4M(lm^0+RaMc?sxDWWw)Q0gLyx!GY6%g&ke*LfxyYq3RDUjwO)n`HNqDhR7~B zV#;s1Az&+s@E_f8q_4C-PWIRcwCX`@{us`?PO567ZS=Q(t-% zx-Xp@G>~HsBoGOoMBC|C|<5(Y&dfASXS?u>g|Q5rxZ8Chq2Nfce*A3Y1#-z5l{Lpfq%Ig{-jku z3RL)EiR(rh8sVM*gb<^O>v9%APy*90{J;IPD?s7f{hxmpxy>k5_!g@Oy}n6!B%t9- z{FQJ6KM8pOqlm9K566^_<|D z0EAExtBUWElF*RDNC@1OZ-0j`aql3nVH82kOMpufQXYqISBb>m<+*^21o7n{Nl5t+ zekjKM4*A7sMVP>R{9>0=%VumfyqDn4B(0zxtVxuFz(A4RMx zJQ;KWp%}jw4P{tWXeGHs4U-H1N=V|!KU5n;SVd?cb@MJvW`CvA6OQo`8cr~+wX+yi zIOn&3P>eqtlDMJyH=_q*lv;NiqY4N85)g{o3LVrNO`Ee32)0X3aVy&)}RfS!S1;n^Nj%&cBJF?#{r z#c)H-B6`&m|??PMioz$Nc^d=#J5dcI(y_m-%6oSC=?2XLZMJ7C>LP-4~NWXQDC>$jQ{`u07*qo IM6N<$f~#BVm;e9( delta 4792 zcmV;p5=ZU75X2>rIDZm$Nkl33O9s`o=GdI^!~m%DCW)Fp7!`Zi6eLI11xB z;>@_9e;E;^1!OO6Ny`8t$RM&K?Vlu$wmC6rJ?34bM&P(pDkP^Sih63zf6 z+y+Wm+SDzl*416eI(rCNuBWh;?U^fKF3jEPxG-G^I^Qa01a0ZpuSfDYI_xA)4dX{sTJ_qJ>5M% zhik3sTLW*o%pZRJ6$Pjx@UJXD%~geZB$ zcH>|5bypUk_Nqb{t}X=KyAb+67eaSUA+*3>1T% ztp=BEf>UaP)55tewSmbt!j_?d@j;0Pd;w~%E`*`?8Ti_3M9^F-g8DiURM&}|it9y7 zCA}q=Qh(?1MOG0}ySc9f+_pNnT&^HHoz5WJj~G!mq|B%4MWpJXxlRQ2^&%+yh@iNk z2=W_?P<>O8R(502DC&F>sN82k0JnJl>si!6*2r4vrbsn8ldY}+yY+J zM+D^!MNr(>0w1!Qi&1$?F)D5?{#n|$Xejl26o07v%Rvdpf)eHj-rY2ZX*bMpDV%b9 z+rbBzQP@GhQ3++ABHyY<)y+ka-BOIoTZ>WAw;1KO6{D=57^VG+b0z)7WAgjvUrwFN z0(Gh%sN9c0k=`+~}O0Mu_U zfyzk&C0qn5=P)Q?gD>zP@Ih6d$HQk|t$MMbv>Ql$tG>S&lD~*i za=RGfJ4#SIpah~jOHg=M2@39(Ab+3)d4KnmGzjl0IV`+SvM}d$p=dD>w{%D@lY9#KU{`i9w|fiUw_MR zba0vG$YatMVi15((|+(_+Re>H;Fjv)vO7Bkyx*sxkpcY`jnYR;NxMN&$f}nh_g)Ev zLEuaMz#sc-8L|hLB0xl0W0O9Nb1uG6aC*DTAnn6WUR zzpa5}ctr^C66D-pir)s6;?x7BIDh$|yXucWREA$3F2gat>dW!-;Bp*&ln0N)kC)@n z6H;_(03qAWwS{0msD|@|IV5yfCxZ_%!@?*uLzb#W!4u_YZ8xOqLxDf|q!d3r)kOjL zwVTWH;e4VL&U7`LznbAZLk}-mAu`C#B)r4FUhS6n_Vwkm9E& zrTFnFDGm(j!~jkY6r*{B6wPDgXv)yQwV@U+iM3VW@eH3~m>`otj8Y3U`|2Ufq9K{5 zMd1_`PK~Pet9k-{|I-!NH?*Sj0%&S#;vEU>a}7MxiX9FIZvy_uAyOQ8x&r%$R$$*V z71;A^Cj{VaH|HyOhsVBEzkk;QfA_G;*adK!5~A5>#WL3LS&oC9<6z}DSU3(QmV=4h z%X1tWShvY*{?8Z|W|l=G%fd$B#uz?vAow4itHiFq#V7#Zi>&wVCKliw9EU~^)OwDC zf#aZKIcPWzN|u9y^kTWb43=1{OLd&qASr&CH3nRB5_8TSvqofN&e(9jSV| z(*vHg6^?^&pA*}MHDOhc_CpUFMvrEEKdcEkdr8Y;pkv%YG=H`eyw~okm+@7*$rOMAe6Ysz09P#Oh1B z2E4Dox}+J$vdE{w0ovh!DMIjG`&K;x|J|!q`1ZBx=m!vP{3BBM7SHXZ%* zzwom>{_c(@l*m|U?RJ>X0-rb#{JJ;fSo=?T^aBVtZFaWx8zbxP`CA@~fB0D*Z+Xmx zDu)BLwtuq+K-$gMuUBK;$ZD*8Lyk57lw9h*TU|IK_xW;;!|Kbr z9&A>GUh}6WR5~1}IZFT*qt&nK3HY!6Eyt=i<&LNaV7Ri7_MfqIPn$7Xd#kIHr2ms! z&cm}^T@E%YLLW-!ps?Ff9|pY5X5(F_ZREkP`G1!$d<9m%Dc46m0J?W!xzBKG%e0xj ztg+Pq$iiA3=UzZNG&BA~*gw(9w;$SOM`IZ9Uh8ERKk!*^De&dn3aorvUKaHLbiE6I z)A|G_kcu%o4>H(;(7p1lgxBARj(o<9s?@sX{cs~i#b>BVSo(iTEE%mxiGO+k%08kIp@v(PBP!aoEok#>l#mdP%yo7- z`~y?k2j6P7V)HBwmcQ!{|MT}%SUg%WJn8{d_9?z0)KsywUojjS_nlCr`>=cn{4N%Q zBlEg0goCM^AN)wc6W3@hSoWSj_$8xN_}_n2`1F0%^-&Li58Xxei zY>WyEKUCw>v1%+Br+hSK0f0jH(*DJCsF7~XQeC^pKWxmI5RNT#w&C`atS?2l#?-AD zM;CS+eq&=JvbWZ@fOl8D7rxpc5TH91ONnn6uk@Rx_l$HG4=D#@W7<7;V}D~9epgEF zaNyW-F1pVM1pZ?8rpwvjw+5crWVB$#WKAG=eDaYR^FPuor(&(*+ly}(4G=ennx)ro zt?kg5bkr3<-99zM;ACjT~|K}w>@U86w?{&^kbyyS#e!K>Alhmi@OjHb@Izt6_ zmFy2SOJ6Wd3cJbPp`jH z57k*J99a6S7k=Q;9pzd>hlif5WG&v|4k3Gqvwb0SZ$|d=*5OAA-s@vO)cRHZyd(|g zCTsBD2^wT3Yo6}30LWpq+}NH zzpQ2jz~L&I+`cw*712LE>B0WbJii}a7RJE*SGuMd>m zcKqQ|*6-+!=iJmUBPw9lTH6iY9x4Qz9lvg1`GMS!@Ye;y?+AEeT4%zN6kSl&d*E|v z(=`35uGFzd$`)paIj|;7R>E%U7<{|OR^Jdd`iH>7|Jnz>HGewr&1MVMFVP2qpFLTN z^eH-|PSwt*x>^UGue|8!qh;byCk4sTo$_^bhrrtdLa4FX_z+HRXR-cm9f4kwUewq%mrt2_srfvom zZ`w8zJq`?!{v3E<&3km`z+2U5P&al6ye$e3Q>}p~)_STtLArrmg|e%3S{QV9I% zx}zgUq6Za^`sq2zm3y9%Rs|kd+vz#Fb6~UrM!hvI!JExy{*GtqJiX6F)(89yJ*Lmp zDN<7Oy{UNC?xE6qc0E^VYdN|TaDwE=WF;*1wzz{|V=!Uar~06FlQKhx88h{mHdAMv zk!Bc3oqr43`Mhl8_U9{3hdR1LX6X-1Qo&T?|4Ha7@Lu>U7t_J*W`>@JKRrcvdU}ff zHR@c_wijecp^onGv-Gc2Vece0Oa^PGf)54XU@&3DVmb_XJ*LglV`{2?GIcI$%ZSRk zfk$_IXX$rNQlVC5>5{4^R;x@{vOpiyZd$<4(tl&}EIN}qm$m6-+29Aa4ah8&y5 zD4~{^Iu$%|rL+N`Wa`6K{ghN1AE(hPsdHUqkM!GDtJZqqhnl5d_nrdMlMU?x-yZm) zf(B%yhHW>Pl15{4ntn~p_jfyU|6u*=)k{Oq(kuAk)~rzq8%ij%r-w@WmAz!>E1pJjg!>xzc8eldxMjd>8y%F0s*MzBh z5BPK%6KB(lsNb_z{ac=%^``u^|46rzpRx3g`E3wrp;MTn24AHz;)~De@a-m@#>cbu zrzg&)qd!y|k6QVT{GAo=C^#}pe`cin%zum}nKoONq(Z%nZG|-OLQp zZb;Rq)7%7s;ce=C((-qe&wMdTX%02q+A?i6R*i$3YhW^(+YFwxoLxI=kuo!UyO}hb z#)R3lDM>*8KL5|?v+48qH2s&nuP6-!A2{^1YK{)oQd7k6Wilf^Ut(yb-O!jMpnowT zgRV#x=ORkd{ij*Kb0! z8&dTX1qMtI81_vRn6BvR_a7G$2+)1u7`0%*SXEQFxf_1`W1<$T7SpJf8U4T$SILa{ zVksTeZl?Hwr!hf5Hzj2n(kLW;seez9J{_liYW_HlF5LKscY`Ejx*j{X)u6W4gt|Ht z_U)}hY8u^YyCGGdB%lrBGYwrcLUOK1FqZ!F+$8n>!10ezJ5vkh>+#7a5wx2O1Clch zdz0tI`C5KFDRZKBq+pWP$d7;c?8k;*u?*2}G7Lz{q%Gs;7{(-8PCF!+pntn5eX{Pb zx9VG6yYaRgPt_+A_!;z}2?E_s(JznlO6Zm{RXc9xG_BPae(UXqwvNv zy3h0!-M4{N@7r#YGfB%a?EUD!`kSNqy%X}$lvH}? Date: Fri, 27 Sep 2024 14:48:54 +0300 Subject: [PATCH 2/5] Updated GU Converted more PS to C# code --- .../Main files/C#/Others/CiToolRunner.cs | 2 +- .../C#/ArgumentCompleters/BasePolicyNamez.cs | 48 +-- .../DirectorySelector.cs | 0 .../C#/Other Functions/CIPolicyVersion.cs | 46 -- .../AllCertificatesGrabber.cs | 0 .../CertCNz.cs | 1 + .../CertificateHelper.cs | 0 .../CiPolicyUtility.cs | 0 .../C#/Shared Logics/CiToolHelper.cs | 397 ++++++++++++++++++ .../Crypt32CertCN.cs | 0 .../C#/Shared Logics/DeviceGuardInfo.cs | 56 +++ .../DriveLetterMapper.cs | 0 .../DriversBlockRulesFetcher.cs | 0 .../EditGUIDs.cs | 0 .../EventLogUtility.cs | 0 .../FileDirectoryPathComparer.cs | 0 .../C#/Shared Logics/FilePicker.cs | 150 +++++++ .../GetExtendedFileAttrib.cs | 0 .../GetFilesFast.cs | 0 .../GetOpusData.cs | 0 .../Initializer.cs | 0 .../C#/{ => Shared Logics}/Logging/Logger.cs | 4 +- .../Logging/LoggerInitializer.cs | 0 .../Main Cmdlets/AssertWDACConfigIntegrity.cs | 0 .../Main Cmdlets/GetCIPolicySetting.cs | 0 .../Main Cmdlets/GetCiFileHashes.cs | 0 .../Main Cmdlets/TestCiPolicy.cs | 0 .../Main Cmdlets/UserConfiguration.cs | 303 +++++++++++++ .../MeowOpener.cs | 0 .../MoveUserModeToKernelMode.cs | 0 .../PageHashCalc.cs | 0 .../RemoveSupplementalSigners.cs | 0 .../ScanLevelz.cs | 2 + .../SecureStringComparer.cs | 0 .../C#/Shared Logics/SignToolHelper.cs} | 4 +- .../SnapBackGuarantee.cs | 0 .../StagingArea.cs | 0 .../AuthenticodePageHashes.cs | 0 .../CertificateDetailsCreator.cs | 0 .../CertificateSignerCreator.cs | 0 .../Types And Definitions/ChainElement.cs | 0 .../Types And Definitions/ChainPackage.cs | 0 .../FileBasedInfoPackage.cs | 0 .../FilePublisherSignerCreator.cs | 0 .../Types And Definitions/HashCreator.cs | 0 .../Types And Definitions/OpusSigner.cs | 0 .../Types And Definitions/PolicyHashObj.cs | 0 .../PublisherSignerCreator.cs | 0 .../Types And Definitions/Signer.cs | 0 .../Types And Definitions/SimulationInput.cs | 0 .../Types And Definitions/SimulationOutput.cs | 0 .../Types And Definitions/WinTrust.cs | 0 .../Variables/CILogIntel.cs | 0 .../Variables/GlobalVars.cs | 0 .../VersionIncrementer.cs | 0 .../WDAC Simulation/GetFileRuleOutput.cs | 0 .../WldpQuerySecurityPolicy.cs | 0 .../XMLOps/SignerAndHashBuilder.cs | 0 .../XmlFilePathExtractor.cs | 0 .../Core/Build-WDACCertificate.psm1 | 4 +- .../Core/Confirm-WDACConfig.psm1 | 57 ++- .../Core/ConvertTo-WDACPolicy.psm1 | 41 +- .../Core/Deploy-SignedWDACConfig.psm1 | 24 +- .../Core/Edit-SignedWDACConfig.psm1 | 50 ++- .../Core/Edit-WDACConfig.psm1 | 31 +- .../Core/Get-CommonWDACConfig.psm1 | 139 +----- .../Core/New-DenyWDACConfig.psm1 | 24 +- .../Core/New-KernelModeWDACConfig.psm1 | 34 +- .../Core/New-SupplementalWDACConfig.psm1 | 14 +- .../Core/New-WDACConfig.psm1 | 14 +- .../Core/Remove-CommonWDACConfig.psm1 | 173 +------- .../Core/Remove-WDACConfig.psm1 | 28 +- .../Core/Set-CommonWDACConfig.psm1 | 188 +-------- .../Shared/Get-KernelModeDriversAudit.psm1 | 2 +- .../Shared/Get-SignTool.psm1 | 2 +- .../WDACConfig Module Files/WDACConfig.psm1 | 4 +- WDACConfig/WinUI3/.editorconfig | 3 + WDACConfig/WinUI3/App.xaml.cs | 19 +- WDACConfig/WinUI3/MainWindow.xaml | 4 + WDACConfig/WinUI3/MainWindow.xaml.cs | 3 + WDACConfig/WinUI3/NativeMethods.txt | 8 + WDACConfig/WinUI3/Package.appxmanifest | 2 +- WDACConfig/WinUI3/Pages/GetCIHashes.xaml.cs | 96 +++-- .../WinUI3/Pages/GitHubDocumentation.xaml | 3 +- .../WinUI3/Pages/MicrosoftDocumentation.xaml | 3 +- .../WinUI3/Pages/ViewCurrentPolicies.xaml | 98 +++++ .../WinUI3/Pages/ViewCurrentPolicies.xaml.cs | 43 ++ .../WinUI3/Properties/launchSettings.json | 4 +- .../AllCertificatesGrabber.cs | 0 WDACConfig/WinUI3/Shared Logics/CertCNz.cs | 47 +++ .../CertificateHelper.cs | 0 .../{Other Functions => }/CiPolicyUtility.cs | 0 .../WinUI3/Shared Logics/CiToolHelper.cs | 397 ++++++++++++++++++ .../{Other Functions => }/Crypt32CertCN.cs | 0 .../WinUI3/Shared Logics/DeviceGuardInfo.cs | 56 +++ .../DriveLetterMapper.cs | 0 .../DriversBlockRulesFetcher.cs | 0 .../{Other Functions => }/EditGUIDs.cs | 0 .../{Other Functions => }/EventLogUtility.cs | 0 .../FileDirectoryPathComparer.cs | 0 WDACConfig/WinUI3/Shared Logics/FilePicker.cs | 148 +++++++ .../GetExtendedFileAttrib.cs | 0 .../{Other Functions => }/GetFilesFast.cs | 0 .../{Other Functions => }/GetOpusData.cs | 0 .../{Other Functions => }/Initializer.cs | 0 .../WinUI3/Shared Logics/Logging/Logger.cs | 4 +- .../Main Cmdlets/UserConfiguration.cs | 303 +++++++++++++ .../{Other Functions => }/MeowOpener.cs | 0 .../MoveUserModeToKernelMode.cs | 0 .../Other Functions/CIPolicyVersion.cs | 46 -- .../{Other Functions => }/PageHashCalc.cs | 0 .../RemoveSupplementalSigners.cs | 0 WDACConfig/WinUI3/Shared Logics/ScanLevelz.cs | 21 + .../SecureStringComparer.cs | 0 .../Shared Logics/SignToolHelper.cs} | 4 +- .../SnapBackGuarantee.cs | 0 .../{Other Functions => }/StagingArea.cs | 0 .../VersionIncrementer.cs | 0 .../WldpQuerySecurityPolicy.cs | 0 .../XmlFilePathExtractor.cs | 0 WDACConfig/WinUI3/WDACConfig.csproj | 26 +- WDACConfig/WinUI3/WDACConfig.csproj.user | 3 + WDACConfig/WinUI3/app.manifest | 10 + 123 files changed, 2355 insertions(+), 838 deletions(-) rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => }/DirectorySelector.cs (100%) delete mode 100644 WDACConfig/WDACConfig Module Files/C#/Other Functions/CIPolicyVersion.cs rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => Shared Logics}/AllCertificatesGrabber.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ArgumentCompleters => Shared Logics}/CertCNz.cs (98%) rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => Shared Logics}/CertificateHelper.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => Shared Logics}/CiPolicyUtility.cs (100%) create mode 100644 WDACConfig/WDACConfig Module Files/C#/Shared Logics/CiToolHelper.cs rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => Shared Logics}/Crypt32CertCN.cs (100%) create mode 100644 WDACConfig/WDACConfig Module Files/C#/Shared Logics/DeviceGuardInfo.cs rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => Shared Logics}/DriveLetterMapper.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => Shared Logics}/DriversBlockRulesFetcher.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => Shared Logics}/EditGUIDs.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => Shared Logics}/EventLogUtility.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => Shared Logics}/FileDirectoryPathComparer.cs (100%) create mode 100644 WDACConfig/WDACConfig Module Files/C#/Shared Logics/FilePicker.cs rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => Shared Logics}/GetExtendedFileAttrib.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => Shared Logics}/GetFilesFast.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => Shared Logics}/GetOpusData.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => Shared Logics}/Initializer.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/Logging/Logger.cs (64%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/Logging/LoggerInitializer.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/Main Cmdlets/AssertWDACConfigIntegrity.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/Main Cmdlets/GetCIPolicySetting.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/Main Cmdlets/GetCiFileHashes.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/Main Cmdlets/TestCiPolicy.cs (100%) create mode 100644 WDACConfig/WDACConfig Module Files/C#/Shared Logics/Main Cmdlets/UserConfiguration.cs rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => Shared Logics}/MeowOpener.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => Shared Logics}/MoveUserModeToKernelMode.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => Shared Logics}/PageHashCalc.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => Shared Logics}/RemoveSupplementalSigners.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ArgumentCompleters => Shared Logics}/ScanLevelz.cs (94%) rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => Shared Logics}/SecureStringComparer.cs (100%) rename WDACConfig/{WinUI3/Shared Logics/Other Functions/CodeIntegritySigner.cs => WDACConfig Module Files/C#/Shared Logics/SignToolHelper.cs} (94%) rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => Shared Logics}/SnapBackGuarantee.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => Shared Logics}/StagingArea.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/Types And Definitions/AuthenticodePageHashes.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/Types And Definitions/CertificateDetailsCreator.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/Types And Definitions/CertificateSignerCreator.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/Types And Definitions/ChainElement.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/Types And Definitions/ChainPackage.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/Types And Definitions/FileBasedInfoPackage.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/Types And Definitions/FilePublisherSignerCreator.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/Types And Definitions/HashCreator.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/Types And Definitions/OpusSigner.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/Types And Definitions/PolicyHashObj.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/Types And Definitions/PublisherSignerCreator.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/Types And Definitions/Signer.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/Types And Definitions/SimulationInput.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/Types And Definitions/SimulationOutput.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/Types And Definitions/WinTrust.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/Variables/CILogIntel.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/Variables/GlobalVars.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => Shared Logics}/VersionIncrementer.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/WDAC Simulation/GetFileRuleOutput.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => Shared Logics}/WldpQuerySecurityPolicy.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{ => Shared Logics}/XMLOps/SignerAndHashBuilder.cs (100%) rename WDACConfig/WDACConfig Module Files/C#/{Other Functions => Shared Logics}/XmlFilePathExtractor.cs (100%) create mode 100644 WDACConfig/WinUI3/NativeMethods.txt create mode 100644 WDACConfig/WinUI3/Pages/ViewCurrentPolicies.xaml create mode 100644 WDACConfig/WinUI3/Pages/ViewCurrentPolicies.xaml.cs rename WDACConfig/WinUI3/Shared Logics/{Other Functions => }/AllCertificatesGrabber.cs (100%) create mode 100644 WDACConfig/WinUI3/Shared Logics/CertCNz.cs rename WDACConfig/WinUI3/Shared Logics/{Other Functions => }/CertificateHelper.cs (100%) rename WDACConfig/WinUI3/Shared Logics/{Other Functions => }/CiPolicyUtility.cs (100%) create mode 100644 WDACConfig/WinUI3/Shared Logics/CiToolHelper.cs rename WDACConfig/WinUI3/Shared Logics/{Other Functions => }/Crypt32CertCN.cs (100%) create mode 100644 WDACConfig/WinUI3/Shared Logics/DeviceGuardInfo.cs rename WDACConfig/WinUI3/Shared Logics/{Other Functions => }/DriveLetterMapper.cs (100%) rename WDACConfig/WinUI3/Shared Logics/{Other Functions => }/DriversBlockRulesFetcher.cs (100%) rename WDACConfig/WinUI3/Shared Logics/{Other Functions => }/EditGUIDs.cs (100%) rename WDACConfig/WinUI3/Shared Logics/{Other Functions => }/EventLogUtility.cs (100%) rename WDACConfig/WinUI3/Shared Logics/{Other Functions => }/FileDirectoryPathComparer.cs (100%) create mode 100644 WDACConfig/WinUI3/Shared Logics/FilePicker.cs rename WDACConfig/WinUI3/Shared Logics/{Other Functions => }/GetExtendedFileAttrib.cs (100%) rename WDACConfig/WinUI3/Shared Logics/{Other Functions => }/GetFilesFast.cs (100%) rename WDACConfig/WinUI3/Shared Logics/{Other Functions => }/GetOpusData.cs (100%) rename WDACConfig/WinUI3/Shared Logics/{Other Functions => }/Initializer.cs (100%) create mode 100644 WDACConfig/WinUI3/Shared Logics/Main Cmdlets/UserConfiguration.cs rename WDACConfig/WinUI3/Shared Logics/{Other Functions => }/MeowOpener.cs (100%) rename WDACConfig/WinUI3/Shared Logics/{Other Functions => }/MoveUserModeToKernelMode.cs (100%) delete mode 100644 WDACConfig/WinUI3/Shared Logics/Other Functions/CIPolicyVersion.cs rename WDACConfig/WinUI3/Shared Logics/{Other Functions => }/PageHashCalc.cs (100%) rename WDACConfig/WinUI3/Shared Logics/{Other Functions => }/RemoveSupplementalSigners.cs (100%) create mode 100644 WDACConfig/WinUI3/Shared Logics/ScanLevelz.cs rename WDACConfig/WinUI3/Shared Logics/{Other Functions => }/SecureStringComparer.cs (100%) rename WDACConfig/{WDACConfig Module Files/C#/Other Functions/CodeIntegritySigner.cs => WinUI3/Shared Logics/SignToolHelper.cs} (94%) rename WDACConfig/WinUI3/Shared Logics/{Other Functions => }/SnapBackGuarantee.cs (100%) rename WDACConfig/WinUI3/Shared Logics/{Other Functions => }/StagingArea.cs (100%) rename WDACConfig/WinUI3/Shared Logics/{Other Functions => }/VersionIncrementer.cs (100%) rename WDACConfig/WinUI3/Shared Logics/{Other Functions => }/WldpQuerySecurityPolicy.cs (100%) rename WDACConfig/WinUI3/Shared Logics/{Other Functions => }/XmlFilePathExtractor.cs (100%) diff --git a/Harden-Windows-Security Module/Main files/C#/Others/CiToolRunner.cs b/Harden-Windows-Security Module/Main files/C#/Others/CiToolRunner.cs index 98ed9b81e..b27bd7cc1 100644 --- a/Harden-Windows-Security Module/Main files/C#/Others/CiToolRunner.cs +++ b/Harden-Windows-Security Module/Main files/C#/Others/CiToolRunner.cs @@ -15,7 +15,7 @@ public class CiToolRunner /// Converts a 64-bit unsigned integer into a version type, used for converting the numbers from CiTool.exe output to proper versions. /// /// The 64-bit unsigned integer as a string. - /// The version string in the format 'part1.part2.part3.part4'. + /// The parsed version private static Version Measure(string number) { try diff --git a/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/BasePolicyNamez.cs b/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/BasePolicyNamez.cs index 105b68691..02ea277aa 100644 --- a/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/BasePolicyNamez.cs +++ b/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/BasePolicyNamez.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Text.Json; #nullable enable @@ -12,44 +10,24 @@ public class BasePolicyNamez : IValidateSetValuesGenerator // Argument tab auto-completion and ValidateSet for Non-System Policy names public string[] GetValidValues() { - // Run CiTool.exe and capture the output - ProcessStartInfo startInfo = new() - { - FileName = @"C:\Windows\System32\CiTool.exe", - Arguments = "-lp -json", - RedirectStandardOutput = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - using Process process = new() - { StartInfo = startInfo }; - _ = process.Start(); - - string jsonOutput = process.StandardOutput.ReadToEnd(); - process.WaitForExit(); - - // Parse the JSON output - JsonDocument jsonDoc = JsonDocument.Parse(jsonOutput); - JsonElement policiesElement = jsonDoc.RootElement.GetProperty("Policies"); - - List validValues = []; + List? BasePolicies = CiToolHelper.GetPolicies(false, true, false); - foreach (JsonElement policyElement in policiesElement.EnumerateArray()) + if (BasePolicies is not null) { - bool isSystemPolicy = policyElement.GetProperty("IsSystemPolicy").GetBoolean(); - string? policyId = policyElement.GetProperty("PolicyID").GetString(); - string? basePolicyId = policyElement.GetProperty("BasePolicyID").GetString(); - string? friendlyName = policyElement.GetProperty("FriendlyName").GetString(); - - // Use ordinal, case-insensitive comparison for the policy IDs - if (!isSystemPolicy && string.Equals(policyId, basePolicyId, StringComparison.OrdinalIgnoreCase) && friendlyName != null) + List BasePolicyNames = []; + foreach (CiPolicyInfo policy in BasePolicies) { - validValues.Add(friendlyName); + if (policy.FriendlyName is not null) + { + BasePolicyNames.Add(policy.FriendlyName); + } } + return BasePolicyNames.ToArray(); + } + else + { + return Array.Empty(); } - - return validValues.ToArray(); } } } diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/DirectorySelector.cs b/WDACConfig/WDACConfig Module Files/C#/DirectorySelector.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/DirectorySelector.cs rename to WDACConfig/WDACConfig Module Files/C#/DirectorySelector.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/CIPolicyVersion.cs b/WDACConfig/WDACConfig Module Files/C#/Other Functions/CIPolicyVersion.cs deleted file mode 100644 index b959bde57..000000000 --- a/WDACConfig/WDACConfig Module Files/C#/Other Functions/CIPolicyVersion.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; - -#nullable enable - -namespace WDACConfig -{ - public static class CIPolicyVersion - { - /// - /// Converts a 64-bit unsigned integer into a version type, used for converting the numbers from CiTool.exe output to proper versions. - /// - /// The 64-bit unsigned integer as a string. - /// The version string in the format 'part1.part2.part3.part4'. - public static string Measure(string number) - { - try - { - // Validate input - if (string.IsNullOrEmpty(number)) - { - throw new ArgumentException("Input number cannot be null or empty."); - } - - // Convert the string to a 64-bit integer - if (!ulong.TryParse(number, out ulong num)) - { - throw new FormatException("Input string is not a valid 64-bit unsigned integer."); - } - - // Extract the version parts by splitting the 64-bit integer into four 16-bit segments and convert each segment to its respective part of the version number - ushort part1 = (ushort)((num & 0xFFFF000000000000) >> 48); // mask isolates the highest 16 bits of a 64-bit number. - ushort part2 = (ushort)((num & 0x0000FFFF00000000) >> 32); // mask isolates the next 16 bits. - ushort part3 = (ushort)((num & 0x00000000FFFF0000) >> 16); // mask isolates the third set of 16 bits. - ushort part4 = (ushort)(num & 0x000000000000FFFF); // mask isolates the lowest 16 bits. - - // Form the version string - return $"{part1}.{part2}.{part3}.{part4}"; - } - catch (Exception ex) - { - WDACConfig.Logger.Write($"Error converting number to version: {ex.Message}"); - return number; - } - } - } -} diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/AllCertificatesGrabber.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/AllCertificatesGrabber.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/AllCertificatesGrabber.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/AllCertificatesGrabber.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/CertCNz.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/CertCNz.cs similarity index 98% rename from WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/CertCNz.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/CertCNz.cs index 89cb5429e..c2c11ec25 100644 --- a/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/CertCNz.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/CertCNz.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Management.Automation; using System.Security.Cryptography.X509Certificates; #nullable enable diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/CertificateHelper.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/CertificateHelper.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/CertificateHelper.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/CertificateHelper.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/CiPolicyUtility.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/CiPolicyUtility.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/CiPolicyUtility.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/CiPolicyUtility.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/CiToolHelper.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/CiToolHelper.cs new file mode 100644 index 000000000..5aacf9219 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/CiToolHelper.cs @@ -0,0 +1,397 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Text; +using System.Text.Json; + +#nullable enable + +namespace WDACConfig +{ + // Class to represent a policy with various attributes + public class CiPolicyInfo + { + public string? PolicyID { get; set; } // Unique identifier for the policy + public string? BasePolicyID { get; set; } // Identifier for the base policy + public string? FriendlyName { get; set; } // Human-readable name of the policy + public Version? Version { get; set; } // Version object representing the policy version + public string? VersionString { get; set; } // Original version string from the policy data + public bool IsSystemPolicy { get; set; } // Indicates if it's a system policy + public bool IsSignedPolicy { get; set; } // Indicates if the policy is signed + public bool IsOnDisk { get; set; } // Indicates if the policy is present on disk + public bool IsEnforced { get; set; } // Indicates if the policy is enforced + public bool IsAuthorized { get; set; } // Indicates if the policy is authorized + public List? PolicyOptions { get; set; } // List of options or settings related to the policy + + + // A property to format PolicyOptions as a comma-separated string + public string PolicyOptionsDisplay => PolicyOptions != null ? string.Join(", ", PolicyOptions) : string.Empty; + } + + + // This class contains all the necessary logics to interact with CiTool.exe + // Any code that wants to use CiTool.exe must go through this class rather than contacting it directly + public static class CiToolHelper + { + /// + /// Converts a 64-bit unsigned integer into a version type, used for converting the numbers from CiTool.exe output to proper versions. + /// + /// The 64-bit unsigned integer as a string. + /// The parsed version + public static Version Measure(string number) + { + try + { + // Validate input, ensuring it's not null or empty + if (string.IsNullOrEmpty(number)) + { + return new Version(0, 0, 0, 0); + } + + // Convert the input string to a 64-bit unsigned integer + if (!ulong.TryParse(number, out ulong num)) + { + throw new FormatException("Input string is not a valid 64-bit unsigned integer."); + } + + // Split the 64-bit integer into four 16-bit segments for the version parts + ushort part1 = (ushort)((num & 0xFFFF000000000000) >> 48); // Highest 16 bits + ushort part2 = (ushort)((num & 0x0000FFFF00000000) >> 32); // Next 16 bits + ushort part3 = (ushort)((num & 0x00000000FFFF0000) >> 16); // Third 16 bits + ushort part4 = (ushort)(num & 0x000000000000FFFF); // Lowest 16 bits + + // Form the version string and attempt to parse it into a Version object, don't need the bool output of the parse result + _ = Version.TryParse($"{part1}.{part2}.{part3}.{part4}"!, out Version? VersionOutput); + + // Return the constructed Version object + return VersionOutput!; + } + catch (Exception ex) + { + // Handle errors by printing an error message and returning a default version of 0.0.0.0 + WDACConfig.Logger.Write($"Error converting number to version: {ex.Message}"); + return new Version(0, 0, 0, 0); + } + } + + + /// + /// Gets a list of WDAC policies on the system with filtering + /// + /// Will include System policies in the output + /// Will include Base policies in the output + /// Will include Supplemental policies in the output + /// + /// + public static List GetPolicies(bool SystemPolicies = false, bool BasePolicies = false, bool SupplementalPolicies = false) + { + // Create an empty list of Policy objects to return at the end + var policies = new List(); + + // Combine the path to CiTool.exe using the system's special folder path + string ciToolPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "CiTool.exe"); + + // Set up the process start info to run CiTool.exe with necessary arguments + ProcessStartInfo processStartInfo = new() + { + FileName = ciToolPath, + Arguments = "-lp -json", // Arguments to list policies and output as JSON + RedirectStandardOutput = true, // Capture the standard output + UseShellExecute = false, // Do not use the OS shell to start the process + CreateNoWindow = true // Run the process without creating a window + }; + + // Start the process and capture the output + using Process? process = Process.Start(processStartInfo) ?? throw new InvalidOperationException("There was a problem running the CiTool.exe in the GetPolicies method."); + + // Read all output as a string + string jsonOutput = process.StandardOutput.ReadToEnd(); + + // Wait for the process to complete + process.WaitForExit(); + + if (process.ExitCode != 0) + { + throw new InvalidOperationException($"Command execution failed with error code {process.ExitCode}"); + } + + // Parse the JSON into a JsonElement for easy traversal + using JsonDocument document = JsonDocument.Parse(Encoding.UTF8.GetBytes(jsonOutput)); + + var rootElement = document.RootElement; + + // If "Policies" property exists and is an array, start processing each policy + if (rootElement.TryGetProperty("Policies", out JsonElement policiesElement) && policiesElement.ValueKind == JsonValueKind.Array) + { + foreach (JsonElement policyElement in policiesElement.EnumerateArray()) + { + // Create a new Policy object and populate its properties from the JSON data + CiPolicyInfo? policy = new() + { + PolicyID = policyElement.GetPropertyOrDefault("PolicyID", string.Empty), + BasePolicyID = policyElement.GetPropertyOrDefault("BasePolicyID", string.Empty), + FriendlyName = policyElement.GetPropertyOrDefault("FriendlyName", string.Empty), + Version = Measure(policyElement.GetProperty("Version").GetUInt64().ToString(CultureInfo.InvariantCulture)), + VersionString = policyElement.GetPropertyOrDefault("VersionString", string.Empty), + IsSystemPolicy = policyElement.GetPropertyOrDefault("IsSystemPolicy", false), + IsSignedPolicy = policyElement.GetPropertyOrDefault("IsSignedPolicy", false), + IsOnDisk = policyElement.GetPropertyOrDefault("IsOnDisk", false), + IsEnforced = policyElement.GetPropertyOrDefault("IsEnforced", false), + IsAuthorized = policyElement.GetPropertyOrDefault("IsAuthorized", false), + PolicyOptions = policyElement.GetPolicyOptionsOrDefault() + }; + + // Add the policy to the list based on filtering options + + // If the policy is System and SystemPolicies parameter was used then add it to the list + if (SystemPolicies && policy.IsSystemPolicy) { policies.Add(policy); } + + // If the policy is Not System, and the policy is Base and BasePolicies parameter was used then add it to the list + else if (BasePolicies && !policy.IsSystemPolicy && policy.BasePolicyID == policy.PolicyID) { policies.Add(policy); } + + // If the policy is Not System, and the policy is supplemental and the SupplementalPolicies parameter was used then add it to the list + else if (SupplementalPolicies && !policy.IsSystemPolicy && policy.BasePolicyID != policy.PolicyID) { policies.Add(policy); } + } + + // Return the list of policies + return policies; + } + + // Return an empty list if no policies were found + return policies; + } + + + /// + /// Removes a deployed WDAC policy from the system + /// + /// The GUID which is the policy ID of the policy to be removed. + /// + public static void RemovePolicy(string policyId) + { + if (string.IsNullOrWhiteSpace(policyId)) + { + throw new ArgumentException("Policy ID cannot be null or empty.", nameof(policyId)); + } + + // Remove any curly brackets or double quotes from the policy ID + // They will be added automatically later by the method + policyId = policyId.Trim('"', '"'); + policyId = policyId.Trim('{', '}'); + + // Combine the path to CiTool.exe using the system's special folder path + string ciToolPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "CiTool.exe"); + + // Set up the process start info to run CiTool.exe with necessary arguments + ProcessStartInfo processStartInfo = new() + { + FileName = ciToolPath, + Arguments = $"--remove-policy \"{{{policyId}}}\" -json", // Arguments to remove a WDAC policy + RedirectStandardOutput = true, // Capture the standard output + UseShellExecute = false, // Do not use the OS shell to start the process + CreateNoWindow = true // Run the process without creating a window + }; + + // Start the process and capture the output + using Process? process = Process.Start(processStartInfo) ?? throw new InvalidOperationException("There was a problem running the CiTool.exe in the GetPolicies method."); + + // Read all output as a string + string jsonOutput = process.StandardOutput.ReadToEnd(); + + // Wait for the process to complete + process.WaitForExit(); + + if (process.ExitCode != 0) + { + throw new InvalidOperationException($"Command execution failed with error code {process.ExitCode}"); + } + } + + + + /// + /// Deploys a Code Integrity policy on the system by accepting the .CIP file path + /// + /// + /// + /// + /// + public static void UpdatePolicy(string CipPath) + { + if (string.IsNullOrWhiteSpace(CipPath)) + { + throw new ArgumentException("CipPath cannot be null or empty.", nameof(CipPath)); + } + + if (!File.Exists(CipPath)) + { + throw new FileNotFoundException($"The file '{CipPath}' does not exist.", CipPath); + } + + // Combine the path to CiTool.exe using the system's special folder path + string ciToolPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "CiTool.exe"); + + // Set up the process start info to run CiTool.exe with necessary arguments + ProcessStartInfo processStartInfo = new() + { + FileName = ciToolPath, + Arguments = $"--update-policy \"{CipPath}\" -json", // Arguments to update the WDAC policy + RedirectStandardOutput = true, // Capture the standard output + UseShellExecute = false, // Do not use the OS shell to start the process + CreateNoWindow = true // Run the process without creating a window + }; + + // Start the process and capture the output + using Process? process = Process.Start(processStartInfo) ?? throw new InvalidOperationException("There was a problem running the CiTool.exe in the UpdatePolicy method."); + + // Read all output as a string + string jsonOutput = process.StandardOutput.ReadToEnd(); + + // Wait for the process to complete + process.WaitForExit(); + + if (process.ExitCode != 0) + { + throw new InvalidOperationException($"Command execution failed with error code {process.ExitCode}"); + } + } + + + /// + /// Refreshes the currently deployed policies on the system + /// + /// + public static void RefreshPolicy() + { + // Combine the path to CiTool.exe using the system's special folder path + string ciToolPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "CiTool.exe"); + + // Set up the process start info to run CiTool.exe with the refresh argument + ProcessStartInfo processStartInfo = new() + { + FileName = ciToolPath, + Arguments = "--refresh -json", // Arguments to refresh WDAC policies + RedirectStandardOutput = true, // Capture the standard output + UseShellExecute = false, // Do not use the OS shell to start the process + CreateNoWindow = true // Run the process without creating a window + }; + + // Start the process and capture the output + using Process? process = Process.Start(processStartInfo) ?? throw new InvalidOperationException("There was a problem running the CiTool.exe in the RefreshPolicy method."); + + // Read all output as a string + string jsonOutput = process.StandardOutput.ReadToEnd(); + + // Wait for the process to complete + process.WaitForExit(); + + if (process.ExitCode != 0) + { + throw new InvalidOperationException($"Command execution failed with error code {process.ExitCode}"); + } + } + + } + + + + + // Extension methods for JsonElement to simplify retrieving properties with default values + public static class JsonElementExtensions + { + + /// + /// Retrieves the value of a string property from a JSON element. If the property does not exist + /// or is not a string, the provided default value is returned. + /// + /// The JSON element from which to retrieve the property. + /// The name of the property to retrieve. + /// The default value to return if the property does not exist or is not a string. + /// The value of the property as a string if it exists and is of type string; otherwise, returns the default value. + public static string? GetPropertyOrDefault(this JsonElement element, string propertyName, string defaultValue) + { + // Attempt to retrieve the property with the specified name from the JSON element. + // Check if the property exists and if its value is of type string. + return element.TryGetProperty(propertyName, out JsonElement value) && value.ValueKind == JsonValueKind.String + // If the property exists and is a string, return its value. + ? value.GetString() + // Otherwise, return the provided default value. + : defaultValue; + } + + /// + /// Retrieves the value of a boolean property from a JSON element. If the property does not exist + /// or is not a boolean, the provided default value is returned. + /// + /// The JSON element from which to retrieve the property. + /// The name of the property to retrieve. + /// The default value to return if the property does not exist or is not a boolean. + /// The value of the property as a boolean if it exists and is of type boolean; otherwise, returns the default value. + public static bool GetPropertyOrDefault(this JsonElement element, string propertyName, bool defaultValue) + { + // Attempt to retrieve the property with the specified name from the JSON element. + // Check if the property exists and if its value is of type boolean. + return element.TryGetProperty(propertyName, out JsonElement value) && + (value.ValueKind == JsonValueKind.True || value.ValueKind == JsonValueKind.False) + // If the property exists and is of type boolean, return true or false based on the property's value. + ? value.GetBoolean() + // Otherwise, return the provided default value. + : defaultValue; + } + + /// + /// Retrieves a list of policy options from a JSON element. If no policy options are found or the + /// element is not in the expected format, an empty list is returned. + /// + /// The JSON element containing the policy options. + /// A list of policy options as strings. Returns an empty list if no options are found + /// or if the element is not formatted correctly. + public static List GetPolicyOptionsOrDefault(this JsonElement element) + { + // Attempt to retrieve the "PolicyOptions" property from the JSON element. + if (element.TryGetProperty("PolicyOptions", out JsonElement value)) + { + // Check if the retrieved value is an array. + if (value.ValueKind == JsonValueKind.Array) + { + // Initialize a new list to hold the policy options. + var options = new List(); + + // Iterate through each item in the array. + foreach (var item in value.EnumerateArray()) + { + // Get the string representation of the item. + var str = item.GetString(); + + // Add the string to the options list if it is not null. + if (str != null) + { + options.Add(str); + } + } + + // Return the list of policy options. + return options; + } + // Check if the retrieved value is a single string. + else if (value.ValueKind == JsonValueKind.String) + { + // Get the string representation of the single value. + var str = value.GetString(); + + // Return a list containing the single string if it is not null. + if (str != null) + { + return [str]; + } + } + } + + // If the "PolicyOptions" property is not found or is not in the expected format, return an empty list. + return []; + } + } + +} diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/Crypt32CertCN.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Crypt32CertCN.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/Crypt32CertCN.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Crypt32CertCN.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/DeviceGuardInfo.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/DeviceGuardInfo.cs new file mode 100644 index 000000000..2a7fadb5e --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/DeviceGuardInfo.cs @@ -0,0 +1,56 @@ +using System.Linq; +using System.Management; + +#nullable enable + +namespace WDACConfig +{ + + public class DeviceGuardStatus + { + public uint? UsermodeCodeIntegrityPolicyEnforcementStatus { get; set; } + public uint? CodeIntegrityPolicyEnforcementStatus { get; set; } + } + + public class DeviceGuardInfo + { + /// + /// Get the Device Guard status information from the Win32_DeviceGuard WMI class + /// + /// + public static DeviceGuardStatus? GetDeviceGuardStatus() + { + // Define the WMI query to get the Win32_DeviceGuard class information + string query = "SELECT UsermodeCodeIntegrityPolicyEnforcementStatus, CodeIntegrityPolicyEnforcementStatus FROM Win32_DeviceGuard"; + + // Define the scope (namespace) for the query + string scope = @"\\.\root\Microsoft\Windows\DeviceGuard"; + + // Create a ManagementScope object for the WMI namespace + ManagementScope managementScope = new(scope); + + // Create an ObjectQuery to specify the WMI query + ObjectQuery objectQuery = new(query); + + // Create a ManagementObjectSearcher to execute the query + using (ManagementObjectSearcher searcher = new(managementScope, objectQuery)) + { + // Execute the query and retrieve the results + foreach (ManagementObject obj in searcher.Get().Cast()) + { + // Create an instance of the custom class to hold the result + DeviceGuardStatus status = new() + { + // Retrieve the relevant properties and assign them to the class + UsermodeCodeIntegrityPolicyEnforcementStatus = obj["UsermodeCodeIntegrityPolicyEnforcementStatus"] as uint?, + CodeIntegrityPolicyEnforcementStatus = obj["CodeIntegrityPolicyEnforcementStatus"] as uint? + }; + + return status; // Return the first instance (assuming one instance) + } + } + + return new DeviceGuardStatus(); + } + } +} diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/DriveLetterMapper.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/DriveLetterMapper.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/DriveLetterMapper.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/DriveLetterMapper.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/DriversBlockRulesFetcher.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/DriversBlockRulesFetcher.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/DriversBlockRulesFetcher.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/DriversBlockRulesFetcher.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/EditGUIDs.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/EditGUIDs.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/EditGUIDs.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/EditGUIDs.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/EventLogUtility.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/EventLogUtility.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/EventLogUtility.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/EventLogUtility.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/FileDirectoryPathComparer.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/FileDirectoryPathComparer.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/FileDirectoryPathComparer.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/FileDirectoryPathComparer.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/FilePicker.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/FilePicker.cs new file mode 100644 index 000000000..7132ca139 --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/FilePicker.cs @@ -0,0 +1,150 @@ +using System; +using System.Runtime.InteropServices; + +#nullable enable + +namespace WDACConfig +{ + public class FilePicker + { + // Usage example + // All params are optional + + // FilePicker.ShowFilePicker( + // "Choose a Configuration File", + // ("XML Files", "*.xml"), + // ("All Files", "*.*") + public static string? ShowFilePicker(string title = "Select a file", params (string Description, string Extension)[] filters) + { + IFileOpenDialog dialog = (IFileOpenDialog)new FileOpenDialog(); + + try + { + // Set the dialog title + dialog.SetTitle(title); + + // Create the file type filters based on the passed parameters + if (filters != null && filters.Length > 0) + { + COMDLG_FILTERSPEC[] filterSpecs = new COMDLG_FILTERSPEC[filters.Length]; + for (int i = 0; i < filters.Length; i++) + { + filterSpecs[i] = new COMDLG_FILTERSPEC + { + pszName = filters[i].Description, + pszSpec = filters[i].Extension + }; + } + + // Set the file type filters for the dialog + dialog.SetFileTypes((uint)filters.Length, filterSpecs); + } + + // Show the File Open Dialog and check the return value + int hr = dialog.Show(IntPtr.Zero); + if (hr != 0) // Check if the user canceled + { + // If canceled, return null + return null; + } + + // Retrieve the selected item + dialog.GetResult(out IShellItem result); + + // Get the file path from the selected item + result.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out IntPtr ppszFilePath); + + // Convert the file path to a string + string? filePath = Marshal.PtrToStringAuto(ppszFilePath); + + // Free the memory allocated for the file path + Marshal.FreeCoTaskMem(ppszFilePath); + + return filePath; + } + catch (COMException) + { + // cancellation or errors + return null; + } + finally + { + // Release COM objects + _ = Marshal.ReleaseComObject(dialog); + } + } + + // COM interfaces and classes + [ComImport] + [Guid("d57c7288-d4ad-4768-be02-9d969532d960")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IFileOpenDialog + { + [PreserveSig] int Show(IntPtr hwndParent); // Displays the dialog box + void SetFileTypes(uint cFileTypes, [MarshalAs(UnmanagedType.LPArray)] COMDLG_FILTERSPEC[] rgFilterSpec); + void SetFileTypeIndex(uint iFileType); + void GetFileTypeIndex(out uint piFileType); + void Advise(IntPtr pfde, out uint pdwCookie); + void Unadvise(uint dwCookie); + void SetOptions(FOS fos); + void GetOptions(out FOS pfos); + void SetDefaultFolder(IShellItem psi); + void SetFolder(IShellItem psi); + void GetFolder(out IShellItem ppsi); + void GetCurrentSelection(out IShellItem ppsi); + void SetFileName([MarshalAs(UnmanagedType.LPWStr)] string pszName); + void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName); + void SetTitle([MarshalAs(UnmanagedType.LPWStr)] string pszTitle); + void SetOkButtonLabel([MarshalAs(UnmanagedType.LPWStr)] string pszText); + void SetFileNameLabel([MarshalAs(UnmanagedType.LPWStr)] string pszLabel); + void GetResult(out IShellItem ppsi); // Gets the result of the user's file selection + void AddPlace(IShellItem psi, FDAP fdap); + void SetDefaultExtension([MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension); + void Close(int hr); + void SetClientGuid(ref Guid guid); + void ClearClientData(); + void SetFilter(IntPtr pFilter); + void GetResults(out IntPtr ppenum); + void GetSelectedItems(out IntPtr ppsai); + } + + [ComImport] + [Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")] + private class FileOpenDialog { } + + [ComImport] + [Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IShellItem + { + void BindToHandler(IntPtr pbc, ref Guid bhid, ref Guid riid, out IntPtr ppv); + void GetParent(out IShellItem ppsi); + void GetDisplayName(SIGDN sigdnName, out IntPtr ppszName); + void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs); + void Compare(IShellItem psi, uint hint, out int piOrder); + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private struct COMDLG_FILTERSPEC + { + public string pszName; + public string pszSpec; + } + + private enum SIGDN : uint + { + SIGDN_FILESYSPATH = 0x80058000, + } + + private enum FOS : uint + { + FOS_FORCEFILESYSTEM = 0x00000040, // Ensure that items returned are file system items. + } + + private enum FDAP + { + FDAP_BOTTOM = 0x00000000, + FDAP_TOP = 0x00000001, + } + } +} diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/GetExtendedFileAttrib.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/GetExtendedFileAttrib.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/GetExtendedFileAttrib.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/GetExtendedFileAttrib.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/GetFilesFast.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/GetFilesFast.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/GetFilesFast.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/GetFilesFast.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/GetOpusData.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/GetOpusData.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/GetOpusData.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/GetOpusData.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/Initializer.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Initializer.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/Initializer.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Initializer.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Logging/Logger.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Logging/Logger.cs similarity index 64% rename from WDACConfig/WDACConfig Module Files/C#/Logging/Logger.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Logging/Logger.cs index 324ca9ed9..56fc6066f 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Logging/Logger.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Logging/Logger.cs @@ -6,7 +6,6 @@ public static class Logger { /// /// Write a verbose message to the console - /// The verbose messages are not redirectable in PowerShell /// https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.host.pshostuserinterface /// /// @@ -16,6 +15,9 @@ public static void Write(string message) { if (GlobalVars.VerbosePreference) { + // The messages generated by WriteVerboseLine method are normally not redirected on the PowerShell console, + // But the PowerShellExecutor.ExecuteScript() method in the Harden Windows Security, that runs PowerShell commands using Management.Automation namespace, + // Captures and redirects them to the GUI's textbox, just like the regular Write-Verbose messages. GlobalVars.Host?.UI.WriteVerboseLine(message); } } diff --git a/WDACConfig/WDACConfig Module Files/C#/Logging/LoggerInitializer.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Logging/LoggerInitializer.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Logging/LoggerInitializer.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Logging/LoggerInitializer.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Main Cmdlets/AssertWDACConfigIntegrity.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Main Cmdlets/AssertWDACConfigIntegrity.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Main Cmdlets/AssertWDACConfigIntegrity.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Main Cmdlets/AssertWDACConfigIntegrity.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Main Cmdlets/GetCIPolicySetting.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Main Cmdlets/GetCIPolicySetting.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Main Cmdlets/GetCIPolicySetting.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Main Cmdlets/GetCIPolicySetting.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Main Cmdlets/GetCiFileHashes.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Main Cmdlets/GetCiFileHashes.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Main Cmdlets/GetCiFileHashes.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Main Cmdlets/GetCiFileHashes.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Main Cmdlets/TestCiPolicy.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Main Cmdlets/TestCiPolicy.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Main Cmdlets/TestCiPolicy.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Main Cmdlets/TestCiPolicy.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Main Cmdlets/UserConfiguration.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Main Cmdlets/UserConfiguration.cs new file mode 100644 index 000000000..ca840913d --- /dev/null +++ b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Main Cmdlets/UserConfiguration.cs @@ -0,0 +1,303 @@ +using System; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; + +#nullable enable + +namespace WDACConfig +{ + + // This is to ensure the Serialize method works when trimming is enabled + + /* + [JsonSerializable(typeof(UserConfiguration))] + public partial class UserConfigurationContext : JsonSerializerContext + { + } + */ + + + // Represents an instance of the User configurations JSON settings file + // Maintains the order of the properties when writing to the JSON file + // Includes the methods for interacting with user configurations JSON file + public partial class UserConfiguration( + string? signedPolicyPath, + string? unsignedPolicyPath, + string? signToolCustomPath, + string? certificateCommonName, + string? certificatePath, + Guid? strictKernelPolicyGUID, + Guid? strictKernelNoFlightRootsPolicyGUID, + DateTime? lastUpdateCheck, + DateTime? strictKernelModePolicyTimeOfDeployment + ) + { + [JsonPropertyOrder(1)] + public string? SignedPolicyPath { get; set; } = signedPolicyPath; + + [JsonPropertyOrder(2)] + public string? UnsignedPolicyPath { get; set; } = unsignedPolicyPath; + + [JsonPropertyOrder(3)] + public string? SignToolCustomPath { get; set; } = signToolCustomPath; + + [JsonPropertyOrder(4)] + public string? CertificateCommonName { get; set; } = certificateCommonName; + + [JsonPropertyOrder(5)] + public string? CertificatePath { get; set; } = certificatePath; + + [JsonPropertyOrder(6)] + public Guid? StrictKernelPolicyGUID { get; set; } = strictKernelPolicyGUID; + + [JsonPropertyOrder(7)] + public Guid? StrictKernelNoFlightRootsPolicyGUID { get; set; } = strictKernelNoFlightRootsPolicyGUID; + + [JsonPropertyOrder(8)] + public DateTime? LastUpdateCheck { get; set; } = lastUpdateCheck; + + [JsonPropertyOrder(9)] + public DateTime? StrictKernelModePolicyTimeOfDeployment { get; set; } = strictKernelModePolicyTimeOfDeployment; + + + + // Used by the static methods, when trimming is not enabled + private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true }; + + + + /// + /// Sets user configuration settings to the JSON file + /// By default all params are null, so use named parameters when calling this method for easy invocation + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static UserConfiguration Set( + string? SignedPolicyPath = null, + string? UnsignedPolicyPath = null, + string? SignToolCustomPath = null, + string? CertificateCommonName = null, + string? CertificatePath = null, + Guid? StrictKernelPolicyGUID = null, + Guid? StrictKernelNoFlightRootsPolicyGUID = null, + DateTime? LastUpdateCheck = null, + DateTime? StrictKernelModePolicyTimeOfDeployment = null + ) + { + // Validate certificateCommonName + if (!string.IsNullOrWhiteSpace(CertificateCommonName)) + { + CertCNz certCNz = new(); + string[] certCommonNames = certCNz.GetValidValues(); + + if (!certCommonNames.Contains(CertificateCommonName)) + { + throw new InvalidOperationException($"{CertificateCommonName} does not belong to a subject CN of any of the deployed certificates"); + } + } + + + Logger.Write("Trying to parse and read the current user configurations file"); + UserConfiguration UserConfiguration = ReadUserConfiguration(); + + // Modify the properties based on the input + if (!string.IsNullOrWhiteSpace(SignedPolicyPath)) UserConfiguration.SignedPolicyPath = SignedPolicyPath; + if (!string.IsNullOrWhiteSpace(UnsignedPolicyPath)) UserConfiguration.UnsignedPolicyPath = UnsignedPolicyPath; + if (!string.IsNullOrWhiteSpace(SignToolCustomPath)) UserConfiguration.SignToolCustomPath = SignToolCustomPath; + if (!string.IsNullOrWhiteSpace(CertificateCommonName)) UserConfiguration.CertificateCommonName = CertificateCommonName; + if (!string.IsNullOrWhiteSpace(CertificatePath)) UserConfiguration.CertificatePath = CertificatePath; + if (StrictKernelPolicyGUID.HasValue) UserConfiguration.StrictKernelPolicyGUID = StrictKernelPolicyGUID; + if (StrictKernelNoFlightRootsPolicyGUID.HasValue) UserConfiguration.StrictKernelNoFlightRootsPolicyGUID = StrictKernelNoFlightRootsPolicyGUID; + if (LastUpdateCheck.HasValue) UserConfiguration.LastUpdateCheck = LastUpdateCheck; + if (StrictKernelModePolicyTimeOfDeployment.HasValue) UserConfiguration.StrictKernelModePolicyTimeOfDeployment = StrictKernelModePolicyTimeOfDeployment; + + // Write the updated properties back to the JSON file + WriteUserConfiguration(UserConfiguration); + + return UserConfiguration; + } + + + + + public static UserConfiguration Get() + { + // Read the current configuration + UserConfiguration currentConfig = ReadUserConfiguration(); + return currentConfig; + } + + + + + public static void Remove( + bool SignedPolicyPath = false, + bool UnsignedPolicyPath = false, + bool SignToolCustomPath = false, + bool CertificateCommonName = false, + bool CertificatePath = false, + bool StrictKernelPolicyGUID = false, + bool StrictKernelNoFlightRootsPolicyGUID = false, + bool LastUpdateCheck = false, + bool StrictKernelModePolicyTimeOfDeployment = false + ) + { + // Read the current configuration + UserConfiguration currentConfig = ReadUserConfiguration(); + + // Remove properties by setting them to null based on the specified flags + if (SignedPolicyPath) currentConfig.SignedPolicyPath = null; + if (UnsignedPolicyPath) currentConfig.UnsignedPolicyPath = null; + if (SignToolCustomPath) currentConfig.SignToolCustomPath = null; + if (CertificateCommonName) currentConfig.CertificateCommonName = null; + if (CertificatePath) currentConfig.CertificatePath = null; + if (StrictKernelPolicyGUID) currentConfig.StrictKernelPolicyGUID = null; + if (StrictKernelNoFlightRootsPolicyGUID) currentConfig.StrictKernelNoFlightRootsPolicyGUID = null; + if (LastUpdateCheck) currentConfig.LastUpdateCheck = null; + if (StrictKernelModePolicyTimeOfDeployment) currentConfig.StrictKernelModePolicyTimeOfDeployment = null; + + // Write the updated configuration back to the JSON file + WriteUserConfiguration(currentConfig); + + Logger.Write("The specified properties have been removed and set to null in the UserConfigurations.json file."); + } + + + private static UserConfiguration ReadUserConfiguration() + { + try + { + + // Create the WDACConfig folder in Program Files if it doesn't exist + if (!Directory.Exists(GlobalVars.UserConfigDir)) + { + _ = Directory.CreateDirectory(GlobalVars.UserConfigDir); + Logger.Write("The WDACConfig folder in Program Files has been created because it did not exist."); + } + + // Create User configuration folder in the WDACConfig folder if it doesn't already exist + string UserConfigDir = Path.Combine(GlobalVars.UserConfigDir, "UserConfigurations"); + if (!Directory.Exists(UserConfigDir)) + { + _ = Directory.CreateDirectory(UserConfigDir); + Logger.Write("The WDACConfig folder in Program Files has been created because it did not exist."); + } + + // Read the JSON file + string json = File.ReadAllText(GlobalVars.UserConfigJson); + return ParseJson(json); + } + catch (Exception ex) + { + // Log the error if JSON is corrupted or any other error occurs + Logger.Write($"Error reading or parsing the user configuration file: {ex.Message}. A new configuration with default values will be created."); + + // Create a new configuration with default values and write it to the file + UserConfiguration defaultConfig = new(null, null, null, null, null, null, null, null, null); + WriteUserConfiguration(defaultConfig); + + return defaultConfig; + } + } + + + private static UserConfiguration ParseJson(string json) + { + using JsonDocument doc = JsonDocument.Parse(json); + var root = doc.RootElement; + + return new UserConfiguration( + TryGetStringProperty(root, "SignedPolicyPath"), + TryGetStringProperty(root, "UnsignedPolicyPath"), + TryGetStringProperty(root, "SignToolCustomPath"), + TryGetStringProperty(root, "CertificateCommonName"), + TryGetStringProperty(root, "CertificatePath"), + + TryGetGuidProperty(root, "StrictKernelPolicyGUID"), + TryGetGuidProperty(root, "StrictKernelNoFlightRootsPolicyGUID"), + + TryGetDateTimeProperty(root, "LastUpdateCheck"), + TryGetDateTimeProperty(root, "StrictKernelModePolicyTimeOfDeployment") + ); + + static string? TryGetStringProperty(JsonElement root, string propertyName) + { + try + { + return root.TryGetProperty(propertyName, out var propertyValue) ? propertyValue.GetString() : null; + } + catch + { + return null; + } + } + + static Guid? TryGetGuidProperty(JsonElement root, string propertyName) + { + try + { + return root.TryGetProperty(propertyName, out var propertyValue) ? Guid.TryParse(propertyValue.GetString(), out var guid) ? guid : null : null; + } + catch + { + return null; + } + } + + static DateTime? TryGetDateTimeProperty(JsonElement root, string propertyName) + { + try + { + return root.TryGetProperty(propertyName, out var propertyValue) ? propertyValue.GetDateTime() : null; + } + catch + { + return null; + } + } + + } + + private static void WriteUserConfiguration(UserConfiguration userConfiguration) + { + // Create a JSON object from the UserConfiguration properties + var json = new + { + SignedPolicyPath = userConfiguration.SignedPolicyPath, + UnsignedPolicyPath = userConfiguration.UnsignedPolicyPath, + SignToolCustomPath = userConfiguration.SignToolCustomPath, + CertificateCommonName = userConfiguration.CertificateCommonName, + CertificatePath = userConfiguration.CertificatePath, + StrictKernelPolicyGUID = userConfiguration.StrictKernelPolicyGUID?.ToString(), + StrictKernelNoFlightRootsPolicyGUID = userConfiguration.StrictKernelNoFlightRootsPolicyGUID?.ToString(), + LastUpdateCheck = userConfiguration.LastUpdateCheck?.ToString("o"), + StrictKernelModePolicyTimeOfDeployment = userConfiguration.StrictKernelModePolicyTimeOfDeployment?.ToString("o") + }; + + // Serialize the object to JSON using the new context + // Trimming enabled + // string jsonString = JsonSerializer.Serialize(json, UserConfigurationContext.Default.UserConfiguration); + + + // Serialize the object to JSON + // Trimming disabled + string jsonString = JsonSerializer.Serialize(json, UserConfiguration.JsonOptions); + + // Write the JSON string to the file + File.WriteAllText(GlobalVars.UserConfigJson, jsonString); + Logger.Write("The UserConfigurations.json file has been updated successfully."); + } + + } +} diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/MeowOpener.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/MeowOpener.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/MeowOpener.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/MeowOpener.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/MoveUserModeToKernelMode.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/MoveUserModeToKernelMode.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/MoveUserModeToKernelMode.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/MoveUserModeToKernelMode.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/PageHashCalc.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/PageHashCalc.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/PageHashCalc.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/PageHashCalc.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/RemoveSupplementalSigners.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/RemoveSupplementalSigners.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/RemoveSupplementalSigners.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/RemoveSupplementalSigners.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/ScanLevelz.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/ScanLevelz.cs similarity index 94% rename from WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/ScanLevelz.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/ScanLevelz.cs index 06d2fef09..00418311b 100644 --- a/WDACConfig/WDACConfig Module Files/C#/ArgumentCompleters/ScanLevelz.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/ScanLevelz.cs @@ -1,3 +1,5 @@ +using System.Management.Automation; + #nullable enable namespace WDACConfig diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/SecureStringComparer.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/SecureStringComparer.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/SecureStringComparer.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/SecureStringComparer.cs diff --git a/WDACConfig/WinUI3/Shared Logics/Other Functions/CodeIntegritySigner.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/SignToolHelper.cs similarity index 94% rename from WDACConfig/WinUI3/Shared Logics/Other Functions/CodeIntegritySigner.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/SignToolHelper.cs index 9b2f914ee..cc4af4054 100644 --- a/WDACConfig/WinUI3/Shared Logics/Other Functions/CodeIntegritySigner.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/SignToolHelper.cs @@ -6,7 +6,7 @@ namespace WDACConfig { - public static class CodeIntegritySigner + public static class SignToolHelper { /// /// Invokes SignTool.exe to sign a Code Integrity Policy file. @@ -16,7 +16,7 @@ public static class CodeIntegritySigner /// /// /// - public static void InvokeCiSigning(FileInfo ciPath, FileInfo signToolPathFinal, string certCN) + public static void Sign(FileInfo ciPath, FileInfo signToolPathFinal, string certCN) { // Validate inputs ArgumentNullException.ThrowIfNull(ciPath); diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/SnapBackGuarantee.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/SnapBackGuarantee.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/SnapBackGuarantee.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/SnapBackGuarantee.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/StagingArea.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/StagingArea.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/StagingArea.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/StagingArea.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/AuthenticodePageHashes.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/AuthenticodePageHashes.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Types And Definitions/AuthenticodePageHashes.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/AuthenticodePageHashes.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/CertificateDetailsCreator.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/CertificateDetailsCreator.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Types And Definitions/CertificateDetailsCreator.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/CertificateDetailsCreator.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/CertificateSignerCreator.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/CertificateSignerCreator.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Types And Definitions/CertificateSignerCreator.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/CertificateSignerCreator.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/ChainElement.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/ChainElement.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Types And Definitions/ChainElement.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/ChainElement.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/ChainPackage.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/ChainPackage.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Types And Definitions/ChainPackage.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/ChainPackage.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/FileBasedInfoPackage.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/FileBasedInfoPackage.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Types And Definitions/FileBasedInfoPackage.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/FileBasedInfoPackage.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/FilePublisherSignerCreator.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/FilePublisherSignerCreator.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Types And Definitions/FilePublisherSignerCreator.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/FilePublisherSignerCreator.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/HashCreator.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/HashCreator.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Types And Definitions/HashCreator.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/HashCreator.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/OpusSigner.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/OpusSigner.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Types And Definitions/OpusSigner.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/OpusSigner.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/PolicyHashObj.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/PolicyHashObj.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Types And Definitions/PolicyHashObj.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/PolicyHashObj.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/PublisherSignerCreator.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/PublisherSignerCreator.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Types And Definitions/PublisherSignerCreator.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/PublisherSignerCreator.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/Signer.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/Signer.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Types And Definitions/Signer.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/Signer.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/SimulationInput.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/SimulationInput.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Types And Definitions/SimulationInput.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/SimulationInput.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/SimulationOutput.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/SimulationOutput.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Types And Definitions/SimulationOutput.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/SimulationOutput.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Types And Definitions/WinTrust.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/WinTrust.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Types And Definitions/WinTrust.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/WinTrust.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Variables/CILogIntel.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Variables/CILogIntel.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Variables/CILogIntel.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Variables/CILogIntel.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Variables/GlobalVars.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Variables/GlobalVars.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Variables/GlobalVars.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/Variables/GlobalVars.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/VersionIncrementer.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/VersionIncrementer.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/VersionIncrementer.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/VersionIncrementer.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/WDAC Simulation/GetFileRuleOutput.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/WDAC Simulation/GetFileRuleOutput.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/WDAC Simulation/GetFileRuleOutput.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/WDAC Simulation/GetFileRuleOutput.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/WldpQuerySecurityPolicy.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/WldpQuerySecurityPolicy.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/WldpQuerySecurityPolicy.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/WldpQuerySecurityPolicy.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/XMLOps/SignerAndHashBuilder.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/SignerAndHashBuilder.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/XMLOps/SignerAndHashBuilder.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/SignerAndHashBuilder.cs diff --git a/WDACConfig/WDACConfig Module Files/C#/Other Functions/XmlFilePathExtractor.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XmlFilePathExtractor.cs similarity index 100% rename from WDACConfig/WDACConfig Module Files/C#/Other Functions/XmlFilePathExtractor.cs rename to WDACConfig/WDACConfig Module Files/C#/Shared Logics/XmlFilePathExtractor.cs diff --git a/WDACConfig/WDACConfig Module Files/Core/Build-WDACCertificate.psm1 b/WDACConfig/WDACConfig Module Files/Core/Build-WDACCertificate.psm1 index b075ad1c9..2fd7e84e4 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Build-WDACCertificate.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Build-WDACCertificate.psm1 @@ -224,10 +224,10 @@ ValidityPeriod = Years $null = Import-PfxCertificate -ProtectPrivateKey 'VSM' -FilePath (Join-Path -Path ([WDACConfig.GlobalVars]::UserConfigDir) -ChildPath "$FileName.pfx") -CertStoreLocation 'Cert:\CurrentUser\My' -Password $Password [WDACConfig.Logger]::Write('Saving the common name of the certificate to the User configurations') - $null = Set-CommonWDACConfig -CertCN $CommonName + $null = [WDACConfig.UserConfiguration]::Set($null, $null, $null, $CommonName, $null, $null, $null, $null , $null) [WDACConfig.Logger]::Write('Saving the path of the .cer file of the certificate to the User configurations') - $null = Set-CommonWDACConfig -CertPath $CertificateOutputPath + $null = [WDACConfig.UserConfiguration]::Set($null, $null, $null, $null, $CertificateOutputPath, $null, $null, $null , $null) } catch { throw $_ diff --git a/WDACConfig/WDACConfig Module Files/Core/Confirm-WDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Confirm-WDACConfig.psm1 index 3e75df298..af6551559 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Confirm-WDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Confirm-WDACConfig.psm1 @@ -10,7 +10,6 @@ Function Confirm-WDACConfig { [Parameter(Mandatory = $false, ParameterSetName = 'Check SmartAppControl Status')][System.Management.Automation.SwitchParameter]$CheckSmartAppControlStatus ) DynamicParam { - # Add the dynamic parameters to the param dictionary $ParamDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() @@ -81,34 +80,13 @@ Function Confirm-WDACConfig { if (-NOT $SkipVersionCheck) { Update-WDACConfigPSModule -InvocationStatement $MyInvocation.Statement } - # Script block to show only Base policies - [System.Management.Automation.ScriptBlock]$OnlyBasePoliciesBLOCK = { - [System.Object[]]$BasePolicies = foreach ($Item in (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies) { - if (($Item.IsSystemPolicy -eq $OnlySystemPolicies) -and ($Item.PolicyID -eq $Item.BasePolicyID)) { - $Item.Version = [WDACConfig.CIPolicyVersion]::Measure($Item.Version) - $Item - } - } - - Write-ColorfulTextWDACConfig -Color Lavender -InputText "`nThere are currently $(($BasePolicies.count)) Base policies deployed" - $BasePolicies - } - # Script block to show only Supplemental policies - [System.Management.Automation.ScriptBlock]$OnlySupplementalPoliciesBLOCK = { - [System.Object[]]$SupplementalPolicies = foreach ($Item in (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies) { - if (($Item.IsSystemPolicy -eq $OnlySystemPolicies) -and ($Item.PolicyID -ne $Item.BasePolicyID)) { - $Item.Version = [WDACConfig.CIPolicyVersion]::Measure($Item.Version) - $Item - } - } - - Write-ColorfulTextWDACConfig -Color Lavender -InputText "`nThere are currently $(($SupplementalPolicies.count)) Supplemental policies deployed`n" - $SupplementalPolicies - } - # If no main parameter was passed, run all of them if (!$ListActivePolicies -and !$VerifyWDACStatus -and !$CheckSmartAppControlStatus) { - $ListActivePolicies = $true + + [System.Collections.Generic.List[WDACConfig.CiPolicyInfo]]$PoliciesDeployedResults = [WDACConfig.CiToolHelper]::GetPolicies($false, $true, $true) + Write-Host -Object "$($PoliciesDeployedResults.count) policies are deployed" + $PoliciesDeployedResults + $VerifyWDACStatus = $true $CheckSmartAppControlStatus = $true } @@ -116,14 +94,31 @@ Function Confirm-WDACConfig { process { if ($ListActivePolicies) { - if ($OnlyBasePolicies) { &$OnlyBasePoliciesBLOCK } - if ($OnlySupplementalPolicies) { &$OnlySupplementalPoliciesBLOCK } - if (!$OnlyBasePolicies -and !$OnlySupplementalPolicies) { &$OnlyBasePoliciesBLOCK; &$OnlySupplementalPoliciesBLOCK } + if ($OnlyBasePolicies) { + [System.Collections.Generic.List[WDACConfig.CiPolicyInfo]]$OnlyBasePoliciesResults = [WDACConfig.CiToolHelper]::GetPolicies($false, $true, $false) + Write-ColorfulTextWDACConfig -Color Lavender -InputText "$($OnlyBasePoliciesResults.count) base policies are deployed" + $OnlyBasePoliciesResults + } + elseif ($OnlySupplementalPolicies) { + [System.Collections.Generic.List[WDACConfig.CiPolicyInfo]]$OnlySupplementalPoliciesResults = [WDACConfig.CiToolHelper]::GetPolicies($false, $false, $true) + Write-ColorfulTextWDACConfig -Color Lavender -InputText "$($OnlySupplementalPoliciesResults.count) Supplemental policies are deployed" + $OnlySupplementalPoliciesResults + } + elseif ($OnlySystemPolicies) { + [System.Collections.Generic.List[WDACConfig.CiPolicyInfo]]$OnlySystemPoliciesResults = [WDACConfig.CiToolHelper]::GetPolicies($true, $false, $false) + Write-ColorfulTextWDACConfig -Color Lavender -InputText "$($OnlySystemPoliciesResults.count) System policies are deployed" + $OnlySystemPoliciesResults + } + else { + [System.Collections.Generic.List[WDACConfig.CiPolicyInfo]]$PoliciesDeployedResults = [WDACConfig.CiToolHelper]::GetPolicies($false, $true, $true) + Write-ColorfulTextWDACConfig -Color Lavender -InputText "$($PoliciesDeployedResults.count) policies are deployed" + $PoliciesDeployedResults + } } if ($VerifyWDACStatus) { [WDACConfig.Logger]::Write('Checking the status of WDAC using Get-CimInstance') - Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard | Select-Object -Property *codeintegrity* | Format-List + [WDACConfig.DeviceGuardInfo]::GetDeviceGuardStatus() Write-ColorfulTextWDACConfig -Color Lavender -InputText "2 -> Enforced`n1 -> Audit mode`n0 -> Disabled/Not running`n" } diff --git a/WDACConfig/WDACConfig Module Files/Core/ConvertTo-WDACPolicy.psm1 b/WDACConfig/WDACConfig Module Files/Core/ConvertTo-WDACPolicy.psm1 index eb4bea246..801196e30 100644 --- a/WDACConfig/WDACConfig Module Files/Core/ConvertTo-WDACPolicy.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/ConvertTo-WDACPolicy.psm1 @@ -22,13 +22,7 @@ Function ConvertTo-WDACPolicy { [ArgumentCompleter({ param($CommandName, $ParameterName, $WordToComplete, $CommandAst, $fakeBoundParameters) - [System.String[]]$PolicyGUIDs = foreach ($Policy in (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies) { - if ($Policy.IsSystemPolicy -ne 'True') { - if ($Policy.PolicyID -eq $Policy.BasePolicyID) { - $Policy.PolicyID - } - } - } + [System.String[]]$PolicyGUIDs = [WDACConfig.CiToolHelper]::GetPolicies($false, $true, $false).PolicyID $Existing = $CommandAst.FindAll({ $args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] @@ -56,11 +50,7 @@ Function ConvertTo-WDACPolicy { [ArgumentCompleter({ param($CommandName, $ParameterName, $WordToComplete, $CommandAst, $FakeBoundParameters) - [System.String[]]$Policies = foreach ($Policy in (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies) { - if ($Policy.FriendlyName -and ($Policy.PolicyID -eq $Policy.BasePolicyID)) { - $Policy.FriendlyName - } - } + [System.String[]]$Policies = [WDACConfig.CiToolHelper]::GetPolicies($true, $true, $false).FriendlyName $Existing = $CommandAst.FindAll({ $args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] @@ -549,8 +539,7 @@ Function ConvertTo-WDACPolicy { $null = ConvertFrom-CIPolicy -XmlFilePath $WDACPolicyPath -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Deploying the Supplemental policy') - - $null = &'C:\Windows\System32\CiTool.exe' --update-policy (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") -json + [WDACConfig.CiToolHelper]::UpdatePolicy((Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip")) } } { $null -ne $BasePolicyGUID } { @@ -570,8 +559,7 @@ Function ConvertTo-WDACPolicy { $null = ConvertFrom-CIPolicy -XmlFilePath $WDACPolicyPath -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Deploying the Supplemental policy') - - $null = &'C:\Windows\System32\CiTool.exe' --update-policy (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") -json + [WDACConfig.CiToolHelper]::UpdatePolicy((Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip")) } } { $null -ne $PolicyToAddLogsTo } { @@ -600,8 +588,7 @@ Function ConvertTo-WDACPolicy { $null = ConvertFrom-CIPolicy -XmlFilePath $PolicyToAddLogsTo -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$($InputXMLObj.SiPolicy.PolicyID).cip") [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Deploying the policy that user selected to add the logs to') - - $null = &'C:\Windows\System32\CiTool.exe' --update-policy (Join-Path -Path $StagingArea -ChildPath "$($InputXMLObj.SiPolicy.PolicyID).cip") -json + [WDACConfig.CiToolHelper]::UpdatePolicy((Join-Path -Path $StagingArea -ChildPath "$($InputXMLObj.SiPolicy.PolicyID).cip")) } } } @@ -780,8 +767,7 @@ Function ConvertTo-WDACPolicy { $null = ConvertFrom-CIPolicy -XmlFilePath $OutputPolicyPathMDEAH -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Deploying the Supplemental policy') - - $null = &'C:\Windows\System32\CiTool.exe' --update-policy (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") -json + [WDACConfig.CiToolHelper]::UpdatePolicy((Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip")) } } { $null -ne $BasePolicyGUID } { @@ -801,8 +787,7 @@ Function ConvertTo-WDACPolicy { $null = ConvertFrom-CIPolicy -XmlFilePath $OutputPolicyPathMDEAH -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Deploying the Supplemental policy') - - $null = &'C:\Windows\System32\CiTool.exe' --update-policy (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") -json + [WDACConfig.CiToolHelper]::UpdatePolicy((Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip")) } } { $null -ne $PolicyToAddLogsTo } { @@ -831,8 +816,7 @@ Function ConvertTo-WDACPolicy { $null = ConvertFrom-CIPolicy -XmlFilePath $PolicyToAddLogsTo -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$($InputXMLObj.SiPolicy.PolicyID).cip") [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Deploying the policy that user selected to add the MDE AH logs to') - - $null = &'C:\Windows\System32\CiTool.exe' --update-policy (Join-Path -Path $StagingArea -ChildPath "$($InputXMLObj.SiPolicy.PolicyID).cip") -json + [WDACConfig.CiToolHelper]::UpdatePolicy((Join-Path -Path $StagingArea -ChildPath "$($InputXMLObj.SiPolicy.PolicyID).cip")) } } Default { @@ -980,8 +964,7 @@ Function ConvertTo-WDACPolicy { $null = ConvertFrom-CIPolicy -XmlFilePath $OutputPolicyPathEVTX -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Deploying the Supplemental policy') - - $null = &'C:\Windows\System32\CiTool.exe' --update-policy (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") -json + [WDACConfig.CiToolHelper]::UpdatePolicy((Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip")) } } { $null -ne $BasePolicyGUID } { @@ -1001,8 +984,7 @@ Function ConvertTo-WDACPolicy { $null = ConvertFrom-CIPolicy -XmlFilePath $OutputPolicyPathEVTX -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Deploying the Supplemental policy') - - $null = &'C:\Windows\System32\CiTool.exe' --update-policy (Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip") -json + [WDACConfig.CiToolHelper]::UpdatePolicy((Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip")) } } { $null -ne $PolicyToAddLogsTo } { @@ -1031,8 +1013,7 @@ Function ConvertTo-WDACPolicy { $null = ConvertFrom-CIPolicy -XmlFilePath $PolicyToAddLogsTo -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$($InputXMLObj.SiPolicy.PolicyID).cip") [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Deploying the policy that user selected to add the Evtx logs to') - - $null = &'C:\Windows\System32\CiTool.exe' --update-policy (Join-Path -Path $StagingArea -ChildPath "$($InputXMLObj.SiPolicy.PolicyID).cip") -json + [WDACConfig.CiToolHelper]::UpdatePolicy((Join-Path -Path $StagingArea -ChildPath "$($InputXMLObj.SiPolicy.PolicyID).cip")) } } Default { diff --git a/WDACConfig/WDACConfig Module Files/Core/Deploy-SignedWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Deploy-SignedWDACConfig.psm1 index dcac85a65..462e6c32a 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Deploy-SignedWDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Deploy-SignedWDACConfig.psm1 @@ -59,13 +59,13 @@ Function Deploy-SignedWDACConfig { [System.IO.FileInfo]$SignToolPathFinal = Get-SignTool -SignToolExePathInput $SignToolPath } # If it is null, then Get-SignTool will behave the same as if it was called without any arguments. else { - [System.IO.FileInfo]$SignToolPathFinal = Get-SignTool -SignToolExePathInput (Get-CommonWDACConfig -SignToolPath) + [System.IO.FileInfo]$SignToolPathFinal = Get-SignTool -SignToolExePathInput ([WDACConfig.UserConfiguration]::Get().SignToolCustomPath) } # If CertPath parameter wasn't provided by user, check if a valid value exists in user configs, if so, use it, otherwise throw an error if (!$CertPath ) { - if ([System.IO.File]::Exists((Get-CommonWDACConfig -CertPath))) { - [System.IO.FileInfo]$CertPath = Get-CommonWDACConfig -CertPath + if ([System.IO.File]::Exists(([WDACConfig.UserConfiguration]::Get().CertificatePath))) { + [System.IO.FileInfo]$CertPath = [WDACConfig.UserConfiguration]::Get().CertificatePath } else { throw 'CertPath parameter cannot be empty and no valid user configuration was found for it. Use the Build-WDACCertificate cmdlet to create one.' @@ -74,8 +74,8 @@ Function Deploy-SignedWDACConfig { # If CertCN was not provided by user, check if a valid value exists in user configs, if so, use it, otherwise throw an error if (!$CertCN) { - if ([WDACConfig.CertCNz]::new().GetValidValues() -contains (Get-CommonWDACConfig -CertCN)) { - [System.String]$CertCN = Get-CommonWDACConfig -CertCN + if ([WDACConfig.CertCNz]::new().GetValidValues() -contains ([WDACConfig.UserConfiguration]::Get().CertificateCommonName)) { + [System.String]$CertCN = [WDACConfig.UserConfiguration]::Get().CertificateCommonName } else { throw 'CertCN parameter cannot be empty and no valid user configuration was found for it.' @@ -187,7 +187,7 @@ Function Deploy-SignedWDACConfig { $CurrentStep++ Write-Progress -Id 13 -Activity 'Signing the policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - [WDACConfig.CodeIntegritySigner]::InvokeCiSigning($PolicyCIPPath, $SignToolPathFinal, $CertCN) + [WDACConfig.SignToolHelper]::Sign($PolicyCIPPath, $SignToolPathFinal, $CertCN) [WDACConfig.Logger]::Write('Renaming the .p7 file to .cip') Move-Item -LiteralPath "$StagingArea\$PolicyID.cip.p7" -Destination $PolicyCIPPath -Force @@ -201,7 +201,7 @@ Function Deploy-SignedWDACConfig { if ($PSCmdlet.ShouldProcess('This PC', 'Deploying the signed policy')) { [WDACConfig.Logger]::Write('Deploying the policy') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $PolicyCIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($PolicyCIPPath) Write-ColorfulTextWDACConfig -Color Lavender -InputText 'policy with the following details has been Signed and Deployed in Enforced Mode:' Write-ColorfulTextWDACConfig -Color MintGreen -InputText "PolicyName = $PolicyName" @@ -210,8 +210,8 @@ Function Deploy-SignedWDACConfig { #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 + [System.String]$StrictKernelPolicyGUID = [WDACConfig.UserConfiguration]::Get().StrictKernelPolicyGUID + [System.String]$StrictKernelNoFlightRootsPolicyGUID = [WDACConfig.UserConfiguration]::Get().StrictKernelNoFlightRootsPolicyGUID if (($PolicyName -like '*Strict Kernel mode policy Enforced*')) { @@ -221,7 +221,7 @@ Function Deploy-SignedWDACConfig { if ($($PolicyID.TrimStart('{').TrimEnd('}')) -eq $StrictKernelPolicyGUID) { [WDACConfig.Logger]::Write('Removing the GUID of the deployed Strict Kernel mode policy from the User Configs') - $null = Remove-CommonWDACConfig -StrictKernelPolicyGUID + [WDACConfig.UserConfiguration]::Remove($false, $false, $false, $false, $false, $true, $false, $false, $false) } } } @@ -233,7 +233,7 @@ Function Deploy-SignedWDACConfig { if ($($PolicyID.TrimStart('{').TrimEnd('}')) -eq $StrictKernelNoFlightRootsPolicyGUID) { [WDACConfig.Logger]::Write('Removing the GUID of the deployed Strict Kernel No Flights mode policy from the User Configs') - $null = Remove-CommonWDACConfig -StrictKernelNoFlightRootsPolicyGUID + [WDACConfig.UserConfiguration]::Remove($false, $false, $false, $false, $false, $false, $true, $false, $false) } } } @@ -269,7 +269,7 @@ Function 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 + Windows Defender Application Control .FUNCTIONALITY Using official Microsoft methods, Signs and Deploys WDAC policies, accepts signed or unsigned policies and deploys them (Windows Defender Application Control) .PARAMETER CertPath diff --git a/WDACConfig/WDACConfig Module Files/Core/Edit-SignedWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Edit-SignedWDACConfig.psm1 index 519dbdb1b..0067d0ef5 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Edit-SignedWDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Edit-SignedWDACConfig.psm1 @@ -36,10 +36,8 @@ Function Edit-SignedWDACConfig { # Get the currently deployed policy IDs and save them in a HashSet $CurrentPolicyIDs = [System.Collections.Generic.HashSet[System.String]]::new([System.StringComparer]::InvariantCultureIgnoreCase) - foreach ($Item in (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies) { - if ($Item.IsSystemPolicy -ne 'True') { - [System.Void]$CurrentPolicyIDs.Add("{$($Item.policyID)}") - } + foreach ($Item in [WDACConfig.CiToolHelper]::GetPolicies($false, $true, $true).policyID) { + [System.Void]$CurrentPolicyIDs.Add("{$($Item)}") } if ($RedFlag1 -or $RedFlag2) { @@ -158,13 +156,13 @@ Function Edit-SignedWDACConfig { [System.IO.FileInfo]$SignToolPathFinal = Get-SignTool -SignToolExePathInput $SignToolPath } # If it is null, then Get-SignTool will behave the same as if it was called without any arguments. else { - [System.IO.FileInfo]$SignToolPathFinal = Get-SignTool -SignToolExePathInput (Get-CommonWDACConfig -SignToolPath) + [System.IO.FileInfo]$SignToolPathFinal = Get-SignTool -SignToolExePathInput ([WDACConfig.UserConfiguration]::Get().SignToolCustomPath) } # If CertPath parameter wasn't provided by user, check if a valid value exists in user configs, if so, use it, otherwise throw an error if (!$CertPath ) { - if ([System.IO.File]::Exists((Get-CommonWDACConfig -CertPath))) { - [System.IO.FileInfo]$CertPath = Get-CommonWDACConfig -CertPath + if ([System.IO.File]::Exists(([WDACConfig.UserConfiguration]::Get().CertificatePath))) { + [System.IO.FileInfo]$CertPath = [WDACConfig.UserConfiguration]::Get().CertificatePath } else { throw 'CertPath parameter cannot be empty and no valid user configuration was found for it. Use the Build-WDACCertificate cmdlet to create one.' @@ -173,8 +171,8 @@ Function Edit-SignedWDACConfig { # If CertCN was not provided by user, check if a valid value exists in user configs, if so, use it, otherwise throw an error if (!$CertCN) { - if ([WDACConfig.CertCNz]::new().GetValidValues() -contains (Get-CommonWDACConfig -CertCN)) { - [System.String]$CertCN = Get-CommonWDACConfig -CertCN + if ([WDACConfig.CertCNz]::new().GetValidValues() -contains ([WDACConfig.UserConfiguration]::Get().CertificateCommonName)) { + [System.String]$CertCN = [WDACConfig.UserConfiguration]::Get().CertificateCommonName } else { throw 'CertCN parameter cannot be empty and no valid user configuration was found for it.' @@ -190,8 +188,8 @@ Function Edit-SignedWDACConfig { if ($PSCmdlet.ParameterSetName -in 'AllowNewApps', 'MergeSupplementalPolicies') { # If PolicyPath was not provided by user, check if a valid value exists in user configs, if so, use it, otherwise throw an error if (!$PolicyPath) { - if ([System.IO.File]::Exists((Get-CommonWDACConfig -SignedPolicyPath))) { - $PolicyPath = Get-CommonWDACConfig -SignedPolicyPath + if ([System.IO.File]::Exists(([WDACConfig.UserConfiguration]::Get().SignedPolicyPath))) { + $PolicyPath = [WDACConfig.UserConfiguration]::Get().SignedPolicyPath } else { throw 'PolicyPath parameter cannot be empty and no valid user configuration was found for SignedPolicyPath.' @@ -259,7 +257,7 @@ Function Edit-SignedWDACConfig { # Sign both CIPs foreach ($CIP in ($AuditModeCIPPath, $EnforcedModeCIPPath)) { - [WDACConfig.CodeIntegritySigner]::InvokeCiSigning($CIP, $SignToolPathFinal, $CertCN) + [WDACConfig.SignToolHelper]::Sign($CIP, $SignToolPathFinal, $CertCN) } [WDACConfig.Logger]::Write('Renaming the signed CIPs to remove the .p7 extension') @@ -274,7 +272,7 @@ Function Edit-SignedWDACConfig { Write-Progress -Id 15 -Activity 'Deploying the Audit mode policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) [WDACConfig.Logger]::Write('Deploying the Audit mode CIP') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $AuditModeCIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($AuditModeCIPPath) [WDACConfig.Logger]::Write('The Base policy with the following details has been Re-Signed and Re-Deployed in Audit Mode:') [WDACConfig.Logger]::Write("PolicyName = $PolicyName") @@ -302,7 +300,7 @@ Function Edit-SignedWDACConfig { Write-Progress -Id 15 -Activity 'Redeploying the Base policy in Enforced Mode' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) [WDACConfig.Logger]::Write('Finally Block Running') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $EnforcedModeCIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($EnforcedModeCIPPath) [WDACConfig.Logger]::Write('The Base policy with the following details has been Re-Signed and Re-Deployed in Enforced Mode:') [WDACConfig.Logger]::Write("PolicyName = $PolicyName") @@ -621,7 +619,7 @@ Function Edit-SignedWDACConfig { [WDACConfig.Logger]::Write('Converting the Supplemental policy to a CIP file') $null = ConvertFrom-CIPolicy -XmlFilePath $SuppPolicyPath -BinaryFilePath $SupplementalCIPPath - [WDACConfig.CodeIntegritySigner]::InvokeCiSigning($SupplementalCIPPath, $SignToolPathFinal, $CertCN) + [WDACConfig.SignToolHelper]::Sign($SupplementalCIPPath, $SignToolPathFinal, $CertCN) [WDACConfig.Logger]::Write('Renaming the signed Supplemental policy file to remove the .p7 extension') Move-Item -LiteralPath "$StagingArea\$SuppPolicyID.cip.p7" -Destination $SupplementalCIPPath -Force @@ -630,7 +628,7 @@ Function Edit-SignedWDACConfig { Write-Progress -Id 15 -Activity 'Deploying Supplemental policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) [WDACConfig.Logger]::Write('Deploying the Supplemental policy') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $SupplementalCIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($SupplementalCIPPath) #Endregion Supplemental-policy-processing-and-deployment @@ -650,7 +648,7 @@ Function Edit-SignedWDACConfig { [WDACConfig.Logger]::Write('Getting the IDs of the currently deployed policies on the system') $DeployedPoliciesIDs = [System.Collections.Generic.HashSet[System.String]]::new([System.StringComparer]::InvariantCultureIgnoreCase) - foreach ($Item in (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies.PolicyID) { + foreach ($Item in [WDACConfig.CiToolHelper]::GetPolicies($true, $true, $true).policyID) { [System.Void]$DeployedPoliciesIDs.Add("{$Item}") } @@ -699,7 +697,7 @@ Function Edit-SignedWDACConfig { [System.String]$SupplementalPolicyID = $Supplementalxml.SiPolicy.PolicyID [WDACConfig.Logger]::Write("Removing policy with ID: $SupplementalPolicyID") - $null = &'C:\Windows\System32\CiTool.exe' --remove-policy $SupplementalPolicyID -json + [WDACConfig.CiToolHelper]::RemovePolicy($SupplementalPolicyID) } $CurrentStep++ @@ -726,7 +724,7 @@ Function Edit-SignedWDACConfig { [WDACConfig.Logger]::Write('Converting the Supplemental policy to a CIP file') $null = ConvertFrom-CIPolicy -XmlFilePath $FinalSupplementalPath -BinaryFilePath $FinalSupplementalCIPPath - [WDACConfig.CodeIntegritySigner]::InvokeCiSigning($FinalSupplementalCIPPath, $SignToolPathFinal, $CertCN) + [WDACConfig.SignToolHelper]::Sign($FinalSupplementalCIPPath, $SignToolPathFinal, $CertCN) [WDACConfig.Logger]::Write('Renaming the signed Supplemental policy file to remove the .p7 extension') Move-Item -LiteralPath "$StagingArea\$SuppPolicyID.cip.p7" -Destination $FinalSupplementalCIPPath -Force @@ -735,7 +733,7 @@ Function Edit-SignedWDACConfig { Write-Progress -Id 16 -Activity 'Deploying the final policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) [WDACConfig.Logger]::Write('Deploying the Supplemental policy') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalSupplementalCIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($FinalSupplementalCIPPath) Write-ColorfulTextWDACConfig -Color TeaGreen -InputText "The Signed Supplemental policy $SuppPolicyName has been deployed on the system, replacing the old ones." @@ -850,7 +848,7 @@ Function Edit-SignedWDACConfig { [WDACConfig.Logger]::Write('Getting the policy ID of the currently deployed base policy based on the policy name that user selected') # In case there are multiple policies with the same name, the first one will be used - [System.Object]$CurrentlyDeployedPolicy = ((&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { ($_.IsSystemPolicy -ne 'True') -and ($_.Version = [WDACConfig.CIPolicyVersion]::Measure($_.Version)) -and ($_.Friendlyname -eq $CurrentBasePolicyName) }) | Select-Object -First 1 + [WDACConfig.CiPolicyInfo]$CurrentlyDeployedPolicy = [WDACConfig.CiToolHelper]::GetPolicies($false, $true, $true) | Where-Object -FilterScript { $_.Friendlyname -eq $CurrentBasePolicyName } | Select-Object -First 1 [System.String]$CurrentID = $CurrentlyDeployedPolicy.BasePolicyID [System.Version]$CurrentVersion = $CurrentlyDeployedPolicy.Version @@ -875,7 +873,7 @@ Function Edit-SignedWDACConfig { [WDACConfig.Logger]::Write('Converting the base policy to a CIP file') $null = ConvertFrom-CIPolicy -XmlFilePath $BasePolicyPath -BinaryFilePath $BasePolicyCIPPath - [WDACConfig.CodeIntegritySigner]::InvokeCiSigning($BasePolicyCIPPath, $SignToolPathFinal, $CertCN) + [WDACConfig.SignToolHelper]::Sign($BasePolicyCIPPath, $SignToolPathFinal, $CertCN) [WDACConfig.Logger]::Write('Renaming the signed base policy file to remove the .p7 extension') Move-Item -LiteralPath "$StagingArea\$CurrentID.cip.p7" -Destination $BasePolicyCIPPath -Force @@ -884,7 +882,7 @@ Function Edit-SignedWDACConfig { Write-Progress -Id 17 -Activity 'Deploying the policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) [WDACConfig.Logger]::Write('Deploying the new base policy with the same GUID on the system') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $BasePolicyCIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($BasePolicyCIPPath) $CurrentStep++ Write-Progress -Id 17 -Activity 'Cleaning up' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) @@ -903,9 +901,9 @@ Function Edit-SignedWDACConfig { Write-ColorfulTextWDACConfig -Color Pink -InputText "Base Policy has been successfully updated to $NewBasePolicyType" - if (Get-CommonWDACConfig -SignedPolicyPath) { + if ([WDACConfig.UserConfiguration]::Get().SignedPolicyPath) { [WDACConfig.Logger]::Write('Replacing the old signed policy path in User Configurations with the new one') - $null = Set-CommonWDACConfig -SignedPolicyPath $PolicyFiles[$NewBasePolicyType] + $null = [WDACConfig.UserConfiguration]::Set($PolicyFiles[$NewBasePolicyType], $null, $null, $null, $null, $null, $null, $null , $null) } } } @@ -933,7 +931,7 @@ Function Edit-SignedWDACConfig { All of the files the cmdlet creates and interacts with are stored in the following directory: C:\Program Files\WDACConfig\StagingArea\Edit-SignedWDACConfig .COMPONENT - Windows Defender Application Control, ConfigCI PowerShell module + Windows Defender Application Control .FUNCTIONALITY Using official Microsoft methods, Edits Signed WDAC policies deployed on the system (Windows Defender Application Control) .PARAMETER AllowNewApps diff --git a/WDACConfig/WDACConfig Module Files/Core/Edit-WDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Edit-WDACConfig.psm1 index 2fb6b4114..291599156 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Edit-WDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Edit-WDACConfig.psm1 @@ -36,10 +36,8 @@ Function Edit-WDACConfig { # Get the currently deployed policy IDs and save them in a HashSet $CurrentPolicyIDs = [System.Collections.Generic.HashSet[System.String]]::new([System.StringComparer]::InvariantCultureIgnoreCase) - foreach ($Item in (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies) { - if ($Item.IsSystemPolicy -ne 'True') { - [System.Void]$CurrentPolicyIDs.Add("{$($Item.policyID)}") - } + foreach ($Item in [WDACConfig.CiToolHelper]::GetPolicies($false, $true, $true).policyID) { + [System.Void]$CurrentPolicyIDs.Add("{$($Item)}") } if (!$RedFlag1 -and !$RedFlag2) { @@ -133,8 +131,8 @@ Function Edit-WDACConfig { if ($PSCmdlet.ParameterSetName -in 'AllowNewApps', 'MergeSupplementalPolicies') { # If PolicyPath was not provided by user, check if a valid value exists in user configs, if so, use it, otherwise throw an error if (!$PolicyPath) { - if ([System.IO.File]::Exists((Get-CommonWDACConfig -UnsignedPolicyPath))) { - $PolicyPath = Get-CommonWDACConfig -UnsignedPolicyPath + if ([System.IO.File]::Exists(([WDACConfig.UserConfiguration]::Get().UnsignedPolicyPath))) { + $PolicyPath = [WDACConfig.UserConfiguration]::Get().UnsignedPolicyPath } else { throw 'PolicyPath parameter cannot be empty and no valid user configuration was found for UnsignedPolicyPath.' @@ -208,7 +206,7 @@ Function Edit-WDACConfig { Write-Progress -Id 10 -Activity 'Deploying the Audit mode policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) [WDACConfig.Logger]::Write('Deploying the Audit mode CIP') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $AuditModeCIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($AuditModeCIPPath) [WDACConfig.Logger]::Write('The Base policy with the following details has been Re-Deployed in Audit Mode:') [WDACConfig.Logger]::Write("PolicyName = $PolicyName") @@ -236,7 +234,7 @@ Function Edit-WDACConfig { Write-Progress -Id 10 -Activity 'Redeploying the Base policy in Enforced Mode' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) [WDACConfig.Logger]::Write('Finally Block Running') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $EnforcedModeCIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($EnforcedModeCIPPath) [WDACConfig.Logger]::Write('The Base policy with the following details has been Re-Deployed in Enforced Mode:') [WDACConfig.Logger]::Write("PolicyName = $PolicyName") @@ -556,7 +554,7 @@ Function Edit-WDACConfig { Write-Progress -Id 10 -Activity 'Deploying the Supplemental policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) [WDACConfig.Logger]::Write('Deploying the Supplemental policy') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $SupplementalCIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($SupplementalCIPPath) #Endregion Supplemental-policy-processing-and-deployment @@ -577,8 +575,7 @@ Function Edit-WDACConfig { [WDACConfig.Logger]::Write('Getting the IDs of the currently deployed policies on the system') $DeployedPoliciesIDs = [System.Collections.Generic.HashSet[System.String]]::new([System.StringComparer]::InvariantCultureIgnoreCase) - - foreach ($Item in (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies.PolicyID) { + foreach ($Item in [WDACConfig.CiToolHelper]::GetPolicies($true, $true, $true).policyID) { [System.Void]$DeployedPoliciesIDs.Add("{$Item}") } @@ -628,7 +625,7 @@ Function Edit-WDACConfig { [System.String]$SupplementalPolicyID = $Supplementalxml.SiPolicy.PolicyID [WDACConfig.Logger]::Write("Removing policy with ID: $SupplementalPolicyID") - $null = &'C:\Windows\System32\CiTool.exe' --remove-policy $SupplementalPolicyID -json + [WDACConfig.CiToolHelper]::RemovePolicy($SupplementalPolicyID) } $CurrentStep++ @@ -654,7 +651,7 @@ Function Edit-WDACConfig { Write-Progress -Id 11 -Activity 'Deploying the final policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) [WDACConfig.Logger]::Write('Deploying the Supplemental policy') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy (Join-Path -Path $StagingArea -ChildPath "$SuppPolicyID.cip") -json + [WDACConfig.CiToolHelper]::UpdatePolicy((Join-Path -Path $StagingArea -ChildPath "$SuppPolicyID.cip")) Write-ColorfulTextWDACConfig -Color TeaGreen -InputText "The Supplemental policy $SuppPolicyName has been deployed on the system, replacing the old ones." @@ -751,7 +748,7 @@ Function Edit-WDACConfig { [WDACConfig.Logger]::Write('Getting the policy ID of the currently deployed base policy based on the policy name that user selected') # In case there are multiple policies with the same name, the first one will be used - [System.Object]$CurrentlyDeployedPolicy = ((&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { ($_.IsSystemPolicy -ne 'True') -and ($_.Version = [WDACConfig.CIPolicyVersion]::Measure($_.Version)) -and ($_.Friendlyname -eq $CurrentBasePolicyName) }) | Select-Object -First 1 + [WDACConfig.CiPolicyInfo]$CurrentlyDeployedPolicy = [WDACConfig.CiToolHelper]::GetPolicies($false, $true, $true) | Where-Object -FilterScript { $_.Friendlyname -eq $CurrentBasePolicyName } | Select-Object -First 1 [System.String]$CurrentID = $CurrentlyDeployedPolicy.BasePolicyID [System.Version]$CurrentVersion = $CurrentlyDeployedPolicy.Version @@ -774,7 +771,7 @@ Function Edit-WDACConfig { Write-Progress -Id 12 -Activity 'Deploying the policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) [WDACConfig.Logger]::Write('Deploying the new base policy with the same GUID on the system') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $CIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($CIPPath) $CurrentStep++ Write-Progress -Id 12 -Activity 'Cleaning up' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) @@ -793,9 +790,9 @@ Function Edit-WDACConfig { Write-ColorfulTextWDACConfig -Color Pink -InputText "Base Policy has been successfully updated to $NewBasePolicyType" - if (Get-CommonWDACConfig -UnsignedPolicyPath) { + if ([WDACConfig.UserConfiguration]::Get().UnsignedPolicyPath) { [WDACConfig.Logger]::Write('Replacing the old unsigned policy path in User Configurations with the new one') - $null = Set-CommonWDACConfig -UnsignedPolicyPath $PolicyFiles[$NewBasePolicyType] + $null = [WDACConfig.UserConfiguration]::Set($null, $PolicyFiles[$NewBasePolicyType], $null, $null, $null, $null, $null, $null , $null) } } } diff --git a/WDACConfig/WDACConfig Module Files/Core/Get-CommonWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Get-CommonWDACConfig.psm1 index 08f364b2d..c0a75d0bd 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Get-CommonWDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Get-CommonWDACConfig.psm1 @@ -1,145 +1,14 @@ Function Get-CommonWDACConfig { - [CmdletBinding()] - [OutputType([System.Object[]], [System.DateTime], [System.String], [System.Guid])] - 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, - [parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$StrictKernelModePolicyTimeOfDeployment - ) - begin { - if ($(Get-PSCallStack).Count -le 2) { - [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) - } - else { - [WDACConfig.LoggerInitializer]::Initialize($null, $null, $Host) - } - - # Create User configuration folder if it doesn't already exist - if (-NOT ([System.IO.Directory]::Exists((Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Parent)))) { - $null = New-Item -ItemType Directory -Path (Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Parent) -Force - [WDACConfig.Logger]::Write('The WDACConfig folder in Program Files has been created because it did not exist.') - } - - # Create User configuration file if it doesn't already exist - if (-NOT ([System.IO.File]::Exists(([WDACConfig.GlobalVars]::UserConfigJson)))) { - $null = New-Item -ItemType File -Path (Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Parent) -Name (Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Leaf) -Force - [WDACConfig.Logger]::Write('The UserConfigurations.json file has been created because it did not exist.') - } - - if ($Open) { - . ([WDACConfig.GlobalVars]::UserConfigJson) - - # set a boolean value that returns from the Process and End blocks as well - [System.Boolean]$ReturnAndDone = $true - # return/exit from the begin block - Return - } - - # Display this message if User Configuration file is empty or only has spaces/new lines - if ([System.String]::IsNullOrWhiteSpace((Get-Content -Path ([WDACConfig.GlobalVars]::UserConfigJson)))) { - [WDACConfig.Logger]::Write('Your current WDAC User Configurations is empty.') - - [System.Boolean]$ReturnAndDone = $true - # return/exit from the begin block - Return - } - - [WDACConfig.Logger]::Write('Reading the current user configurations') - [System.Object[]]$CurrentUserConfigurations = Get-Content -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Force - - # If the file exists but is corrupted and has bad values, rewrite it - try { - [System.Collections.Hashtable]$CurrentUserConfigurations = $CurrentUserConfigurations | ConvertFrom-Json -AsHashtable - } - catch { - Write-Warning -Message 'The UserConfigurations.json was corrupted, clearing it.' - Set-Content -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Value '' - - [System.Boolean]$ReturnAndDone = $true - # return/exit from the begin block - Return - } - } - - process { - # return/exit from the process block - if ($true -eq $ReturnAndDone) { return } - - # Remove any empty values from the hashtable - foreach ($Item in @($CurrentUserConfigurations.keys)) { - if (!$CurrentUserConfigurations[$Item]) { - $CurrentUserConfigurations.Remove($Item) - } - } - } - - end { - # return/exit from the end block - 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 { return ($CurrentUserConfigurations.SignedPolicyPath ?? $null) } - $UnsignedPolicyPath.IsPresent { return ($CurrentUserConfigurations.UnsignedPolicyPath ?? $null) } - $SignToolPath.IsPresent { return ($CurrentUserConfigurations.SignToolCustomPath ?? $null) } - $CertCN.IsPresent { return ($CurrentUserConfigurations.CertificateCommonName ?? $null) } - $StrictKernelPolicyGUID.IsPresent { return ($CurrentUserConfigurations.StrictKernelPolicyGUID ?? $null) } - $StrictKernelNoFlightRootsPolicyGUID.IsPresent { return ($CurrentUserConfigurations.StrictKernelNoFlightRootsPolicyGUID ?? $null) } - $CertPath.IsPresent { return ($CurrentUserConfigurations.CertificatePath ?? $null) } - $LastUpdateCheck.IsPresent { return ($CurrentUserConfigurations.LastUpdateCheck ?? $null) } - $StrictKernelModePolicyTimeOfDeployment.IsPresent { return ($CurrentUserConfigurations.StrictKernelModePolicyTimeOfDeployment ?? $null) } - Default { - # If no parameter is present - Return $CurrentUserConfigurations - } - } - } + [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) + [WDACConfig.UserConfiguration]::Get() <# .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 + Reads and gets the values from the User Config Json file, .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 -.PARAMETER LastUpdateCheck - Shows the date of the last update check -.PARAMETER StrictKernelModePolicyTimeOfDeployment - Shows the date of the last Strict Kernel mode policy deployment -.PARAMETER Verbose - Shows verbose messages -.INPUTS - System.Management.Automation.SwitchParameter -.OUTPUTS - System.Object[] - System.DateTime - System.String - System.Guid + Reads and gets the values from the User Config Json file #> } diff --git a/WDACConfig/WDACConfig Module Files/Core/New-DenyWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/New-DenyWDACConfig.psm1 index 9936e1d36..d5776274b 100644 --- a/WDACConfig/WDACConfig Module Files/Core/New-DenyWDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/New-DenyWDACConfig.psm1 @@ -164,7 +164,7 @@ Function New-DenyWDACConfig { Write-Progress -Id 22 -Activity 'Deploying the base policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) [WDACConfig.Logger]::Write('Deploying the policy') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalDenyPolicyCIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($FinalDenyPolicyCIPPath) Write-ColorfulTextWDACConfig -Color Pink -InputText "A Deny Base policy with the name '$PolicyName' has been deployed." } @@ -242,7 +242,7 @@ Function New-DenyWDACConfig { Write-Progress -Id 23 -Activity 'Deploying the base policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) [WDACConfig.Logger]::Write('Deploying the policy') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalDenyPolicyCIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($FinalDenyPolicyCIPPath) Write-ColorfulTextWDACConfig -Color Pink -InputText "A Deny Base policy with the name '$PolicyName' has been deployed." } @@ -314,7 +314,7 @@ Function New-DenyWDACConfig { Write-Progress -Id 24 -Activity 'Deploying the base policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) [WDACConfig.Logger]::Write('Deploying the policy') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalDenyPolicyCIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($FinalDenyPolicyCIPPath) Write-ColorfulTextWDACConfig -Color Pink -InputText "A Deny Base policy with the name '$PolicyName' has been deployed." } @@ -343,39 +343,39 @@ Function New-DenyWDACConfig { Write-Progress -Id 29 -Activity 'Creating the wildcard deny policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) # Using Windows PowerShell to handle serialized data since PowerShell core throws an error - Write-Verbose -Message 'Creating the deny policy file' + [WDACConfig.Logger]::Write('Creating the deny policy file') powershell.exe -NoProfile -Command { $RulesWildCards = New-CIPolicyRule -Deny -FilePathRule $args[0] New-CIPolicy -MultiplePolicyFormat -FilePath $args[1] -Rules $RulesWildCards } -args $FolderPath, $TempPolicyPath # Merging AllowAll default policy with our Deny temp policy - Write-Verbose -Message 'Merging AllowAll default template policy with our Wildcard Deny temp policy' + [WDACConfig.Logger]::Write('Merging AllowAll default template policy with our Wildcard Deny temp policy') $null = Merge-CIPolicy -PolicyPaths $AllowAllPolicyPath, $TempPolicyPath -OutputFilePath $FinalDenyPolicyPath $CurrentStep++ Write-Progress -Id 29 -Activity 'Configuring the wildcard deny policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Assigning a name and resetting the policy ID' + [WDACConfig.Logger]::Write('Assigning a name and resetting the policy ID') $null = Set-CIPolicyIdInfo -FilePath $FinalDenyPolicyPath -ResetPolicyID -PolicyName $PolicyName - Write-Verbose -Message 'Setting the policy version to 1.0.0.0' + [WDACConfig.Logger]::Write('Setting the policy version to 1.0.0.0') Set-CIPolicyVersion -FilePath $FinalDenyPolicyPath -Version '1.0.0.0' Set-CiRuleOptions -FilePath $FinalDenyPolicyPath -Template Base - Write-Verbose -Message 'Converting the policy XML to .CIP' + [WDACConfig.Logger]::Write('Converting the policy XML to .CIP') $null = ConvertFrom-CIPolicy -XmlFilePath $FinalDenyPolicyPath -BinaryFilePath $FinalDenyPolicyCIPPath if ($Deploy) { $CurrentStep++ Write-Progress -Id 29 -Activity 'Deploying the base policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - Write-Verbose -Message 'Deploying the policy' - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalDenyPolicyCIPPath -json + [WDACConfig.Logger]::Write('Deploying the policy') + [WDACConfig.CiToolHelper]::UpdatePolicy($FinalDenyPolicyCIPPath) if ($EmbeddedVerboseOutput) { - Write-Verbose -Message "A Deny Base policy with the name '$PolicyName' has been deployed." + [WDACConfig.Logger]::Write("A Deny Base policy with the name '$PolicyName' has been deployed.") } else { Write-ColorfulTextWDACConfig -Color Pink -InputText "A Deny Base policy with the name '$PolicyName' has been deployed." @@ -421,7 +421,7 @@ Function New-DenyWDACConfig { .DESCRIPTION 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 .FUNCTIONALITY Using official Microsoft methods, Removes Signed and unsigned deployed WDAC policies (Windows Defender Application Control) .PARAMETER PolicyName diff --git a/WDACConfig/WDACConfig Module Files/Core/New-KernelModeWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/New-KernelModeWDACConfig.psm1 index e1cf51703..00cf274e6 100644 --- a/WDACConfig/WDACConfig Module Files/Core/New-KernelModeWDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/New-KernelModeWDACConfig.psm1 @@ -64,21 +64,21 @@ Function New-KernelModeWDACConfig { if ($Normal) { # Check if there is a pending Audit mode Kernel mode WDAC policy already available in User Config file - [System.String]$CurrentStrictKernelPolicyGUID = Get-CommonWDACConfig -StrictKernelPolicyGUID + [System.String]$CurrentStrictKernelPolicyGUID = [WDACConfig.UserConfiguration]::Get().StrictKernelPolicyGUID If ($null -ne $CurrentStrictKernelPolicyGUID) { # Check if the pending Audit mode Kernel mode WDAC policy is deployed on the system - [System.String]$CurrentStrictKernelPolicyGUIDConfirmation = ((&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { $_.PolicyID -eq $CurrentStrictKernelPolicyGUID }).policyID + [System.String]$CurrentStrictKernelPolicyGUIDConfirmation = ([WDACConfig.CiToolHelper]::GetPolicies($false, $true, $true) | Where-Object -FilterScript { $_.PolicyID -eq $CurrentStrictKernelPolicyGUID }).policyID } } if ($NoFlights) { # Check if there is a pending Audit mode Kernel mode WDAC NoFlightRoots policy already available in User Config file - [System.String]$CurrentStrictKernelNoFlightRootsPolicyGUID = Get-CommonWDACConfig -StrictKernelNoFlightRootsPolicyGUID + [System.String]$CurrentStrictKernelNoFlightRootsPolicyGUID = [WDACConfig.UserConfiguration]::Get().StrictKernelNoFlightRootsPolicyGUID If ($null -ne $CurrentStrictKernelNoFlightRootsPolicyGUID) { # Check if the pending Audit mode Kernel mode WDAC NoFlightRoots policy is deployed on the system - [System.String]$CurrentStrictKernelPolicyGUIDConfirmation = ((&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { $_.PolicyID -eq $CurrentStrictKernelNoFlightRootsPolicyGUID }).policyID + [System.String]$CurrentStrictKernelPolicyGUIDConfirmation = ([WDACConfig.CiToolHelper]::GetPolicies($false, $true, $true) | Where-Object -FilterScript { $_.PolicyID -eq $CurrentStrictKernelNoFlightRootsPolicyGUID }).policyID } } } @@ -147,10 +147,10 @@ Function New-KernelModeWDACConfig { Write-Progress -Id 25 -Activity 'Deploying the prep mode policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) [WDACConfig.Logger]::Write('Setting the GUID and time of deployment of the Audit mode policy in the User Configuration file') - $null = Set-CommonWDACConfig -StrictKernelPolicyGUID $PolicyID -StrictKernelModePolicyTimeOfDeployment (Get-Date) + $null = [WDACConfig.UserConfiguration]::Set($null, $null, $null, $null, $null, $PolicyID, $null, $null , (Get-Date)) [WDACConfig.Logger]::Write('Deploying the Strict Kernel mode policy') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalAuditCIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($FinalAuditCIPPath) Write-ColorfulTextWDACConfig -Color HotPink -InputText 'Strict Kernel mode policy has been deployed in Audit mode, please restart your system.' } else { @@ -170,7 +170,7 @@ Function New-KernelModeWDACConfig { # 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 [WDACConfig.Logger]::Write('Trying to get the GUID of Strict Kernel Audit mode policy to use for the Enforced mode policy, from the user configurations') - [System.String]$PolicyID = Get-CommonWDACConfig -StrictKernelPolicyGUID + [System.String]$PolicyID = [WDACConfig.UserConfiguration]::Get().StrictKernelPolicyGUID [WDACConfig.Logger]::Write('Verifying the Policy ID in the User Config exists and is valid') $ObjectGuid = [System.Guid]::Empty @@ -237,18 +237,18 @@ Function New-KernelModeWDACConfig { Write-Progress -Id 26 -Activity 'Deploying the final policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) [WDACConfig.Logger]::Write('Deploying the enforced mode policy with the same ID as the Audit mode policy, effectively overwriting it') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalEnforcedCIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($FinalEnforcedCIPPath) Write-ColorfulTextWDACConfig -Color HotPink -InputText 'Strict Kernel mode policy has been deployed in Enforced mode, no restart required.' [WDACConfig.Logger]::Write('Removing the GUID and time of deployment of the StrictKernelPolicy from user configuration') - $null = Remove-CommonWDACConfig -StrictKernelPolicyGUID -StrictKernelModePolicyTimeOfDeployment + [WDACConfig.UserConfiguration]::Remove($false, $false, $false, $false, $false, $true, $false, $false, $true) } else { # 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 [WDACConfig.Logger]::Write('Removing the deployed Audit mode policy from the system since -Deploy parameter was not used to overwrite it with the enforced mode policy.') - $null = &'C:\Windows\System32\CiTool.exe' --remove-policy "{$PolicyID}" -json + [WDACConfig.CiToolHelper]::RemovePolicy($PolicyID) Write-ColorfulTextWDACConfig -Color HotPink -InputText "Strict Kernel mode Enforced policy has been created`n$FinalEnforcedPolicyPath" } Write-Progress -Id 26 -Activity 'Complete.' -Completed @@ -288,10 +288,10 @@ Function New-KernelModeWDACConfig { Write-Progress -Id 27 -Activity 'Deploying the prep mode policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) [WDACConfig.Logger]::Write('Setting the GUID and time of deployment of the Audit mode policy in the User Configuration file') - $null = Set-CommonWDACConfig -StrictKernelNoFlightRootsPolicyGUID $PolicyID -StrictKernelModePolicyTimeOfDeployment (Get-Date) + $null = [WDACConfig.UserConfiguration]::Set($null, $null, $null, $null, $null, $null, $PolicyID, $null , (Get-Date)) [WDACConfig.Logger]::Write('Deploying the Strict Kernel mode policy') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalAuditCIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($FinalAuditCIPPath) Write-ColorfulTextWDACConfig -Color HotPink -InputText 'Strict Kernel mode policy with no flighting root certs has been deployed in Audit mode, please restart your system.' } else { @@ -311,7 +311,7 @@ Function New-KernelModeWDACConfig { # 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 [WDACConfig.Logger]::Write('Trying to get the GUID of Strict Kernel Audit mode policy to use for the Enforced mode policy, from the user configurations') - [System.String]$PolicyID = Get-CommonWDACConfig -StrictKernelNoFlightRootsPolicyGUID + [System.String]$PolicyID = [WDACConfig.UserConfiguration]::Get().StrictKernelNoFlightRootsPolicyGUID [WDACConfig.Logger]::Write('Verifying the Policy ID in the User Config exists and is valid') $ObjectGuid = [System.Guid]::Empty @@ -390,18 +390,18 @@ Function New-KernelModeWDACConfig { Write-Progress -Id 28 -Activity 'Deploying the final policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) [WDACConfig.Logger]::Write('Deploying the enforced mode policy with the same ID as the Audit mode policy, effectively overwriting it') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalEnforcedCIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($FinalEnforcedCIPPath) Write-ColorfulTextWDACConfig -Color HotPink -InputText 'Strict Kernel mode policy with no flighting root certs has been deployed in Enforced mode, no restart required.' [WDACConfig.Logger]::Write('Removing the GUID and time of deployment of the StrictKernelNoFlightRootsPolicy from user configuration') - $null = Remove-CommonWDACConfig -StrictKernelNoFlightRootsPolicyGUID -StrictKernelModePolicyTimeOfDeployment + [WDACConfig.UserConfiguration]::Remove($false, $false, $false, $false, $false, $false, $true, $false, $true) } else { # 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 [WDACConfig.Logger]::Write('Removing the deployed Audit mode policy from the system since -Deploy parameter was not used to overwrite it with the enforced mode policy.') - $null = &'C:\Windows\System32\CiTool.exe' --remove-policy "{$PolicyID}" -json + [WDACConfig.CiToolHelper]::RemovePolicy($PolicyID) Write-ColorfulTextWDACConfig -Color HotPink -InputText "Strict Kernel mode Enforced policy with no flighting root certs has been created`n$FinalEnforcedPolicyPath" } Write-Progress -Id 28 -Activity 'Complete.' -Completed @@ -435,7 +435,7 @@ Function New-KernelModeWDACConfig { .DESCRIPTION Using official Microsoft methods, configure and use Windows Defender Application Control .COMPONENT - Windows Defender Application Control, ConfigCI PowerShell module + Windows Defender Application Control .FUNCTIONALITY Creates Kernel only mode WDAC policy capable of protecting against BYOVD attacks category .PARAMETER Base diff --git a/WDACConfig/WDACConfig Module Files/Core/New-SupplementalWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/New-SupplementalWDACConfig.psm1 index 2a1edf432..d00be0ab5 100644 --- a/WDACConfig/WDACConfig Module Files/Core/New-SupplementalWDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/New-SupplementalWDACConfig.psm1 @@ -96,8 +96,8 @@ Function New-SupplementalWDACConfig { #Region User-Configurations-Processing-Validation # If PolicyPath was not provided by user, check if a valid value exists in user configs, if so, use it, otherwise throw an error if (!$PolicyPath) { - if ([System.IO.File]::Exists((Get-CommonWDACConfig -UnsignedPolicyPath))) { - $PolicyPath = Get-CommonWDACConfig -UnsignedPolicyPath + if ([System.IO.File]::Exists(([WDACConfig.UserConfiguration]::Get().UnsignedPolicyPath))) { + $PolicyPath = [WDACConfig.UserConfiguration]::Get().UnsignedPolicyPath } else { throw 'PolicyPath parameter cannot be empty and no valid user configuration was found for UnsignedPolicyPath.' @@ -183,7 +183,7 @@ Function New-SupplementalWDACConfig { Write-Progress -Id 19 -Activity 'Deploying the Supplemental policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) [WDACConfig.Logger]::Write('Deploying the Supplemental policy') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalSupplementalCIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($FinalSupplementalCIPPath) Write-ColorfulTextWDACConfig -Color Pink -InputText "A Supplemental policy with the name '$SuppPolicyName' has been deployed." } Write-Progress -Id 19 -Activity 'Complete.' -Completed @@ -221,7 +221,7 @@ Function New-SupplementalWDACConfig { Write-Progress -Id 20 -Activity 'Deploying the Supplemental policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) [WDACConfig.Logger]::Write('Deploying the Supplemental policy') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalSupplementalCIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($FinalSupplementalCIPPath) Write-ColorfulTextWDACConfig -Color Pink -InputText "A Supplemental policy with the name '$SuppPolicyName' has been deployed." } Write-Progress -Id 20 -Activity 'Complete.' -Completed @@ -296,7 +296,7 @@ Function New-SupplementalWDACConfig { Write-Progress -Id 21 -Activity 'Deploying the Supplemental policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) [WDACConfig.Logger]::Write('Deploying the Supplemental policy') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalSupplementalCIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($FinalSupplementalCIPPath) Write-ColorfulTextWDACConfig -Color Pink -InputText "A Supplemental policy with the name '$SuppPolicyName' has been deployed." } } @@ -373,7 +373,7 @@ Function New-SupplementalWDACConfig { Write-Progress -Id 33 -Activity 'Deploying the Supplemental policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) [WDACConfig.Logger]::Write('Deploying the Supplemental policy') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $FinalSupplementalCIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($FinalSupplementalCIPPath) Write-ColorfulTextWDACConfig -Color Pink -InputText "A Supplemental policy with the name '$SuppPolicyName' has been deployed." } @@ -412,7 +412,7 @@ Function New-SupplementalWDACConfig { .DESCRIPTION Using official Microsoft methods, configure and use Windows Defender Application Control .COMPONENT - Windows Defender Application Control, ConfigCI PowerShell module + Windows Defender Application Control .FUNCTIONALITY Automate various tasks related to Windows Defender Application Control (WDAC) .PARAMETER Normal diff --git a/WDACConfig/WDACConfig Module Files/Core/New-WDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/New-WDACConfig.psm1 index 4553c9211..b76fa5f20 100644 --- a/WDACConfig/WDACConfig Module Files/Core/New-WDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/New-WDACConfig.psm1 @@ -140,8 +140,8 @@ Function New-WDACConfig { $CurrentStep++ Write-Progress -Id 1 -Activity 'Refreshing the system policies' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - [WDACConfig.Logger]::Write('Refreshing the system WDAC policies using CiTool.exe') - $null = &'C:\Windows\System32\CiTool.exe' --refresh -json + [WDACConfig.Logger]::Write('Refreshing the system WDAC policies') + [WDACConfig.CiToolHelper]::RefreshPolicy() Write-ColorfulTextWDACConfig -Color Pink -InputText 'SiPolicy.p7b has been deployed and policies refreshed.' @@ -275,7 +275,7 @@ Function New-WDACConfig { [System.IO.FileInfo]$CIPPath = ConvertFrom-CIPolicy -XmlFilePath $FinalPolicyPath -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$Name.cip") [WDACConfig.Logger]::Write("Deploying the $Name policy") - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $CIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($CIPPath) } Copy-Item -Path $FinalPolicyPath -Destination ([WDACConfig.GlobalVars]::UserConfigDir) -Force Write-FinalOutput -Paths $FinalPolicyPath @@ -339,7 +339,7 @@ Function New-WDACConfig { [System.IO.FileInfo]$CIPPath = ConvertFrom-CIPolicy -XmlFilePath $FinalPolicyPath -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$Name.cip") [WDACConfig.Logger]::Write('Deploying the policy') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $CIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($CIPPath) } # Copy the result to the User Config directory at the end @@ -377,7 +377,7 @@ Function New-WDACConfig { if ($Deploy) { [WDACConfig.Logger]::Write("Checking if the $Name policy is already deployed") - [System.String]$CurrentlyDeployedBlockRulesGUID = ((&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies | Where-Object -FilterScript { ($_.IsSystemPolicy -ne 'True') -and ($_.PolicyID -eq $_.BasePolicyID) -and ($_.FriendlyName -eq $Name) }).PolicyID + [System.String]$CurrentlyDeployedBlockRulesGUID = ([WDACConfig.CiToolHelper]::GetPolicies($false, $true, $false) | Where-Object -FilterScript { $_.FriendlyName -eq $Name }).PolicyID if (-NOT ([System.String]::IsNullOrWhiteSpace($CurrentlyDeployedBlockRulesGUID))) { [WDACConfig.Logger]::Write("$Name policy is already deployed, updating it using the same GUID.") @@ -387,7 +387,7 @@ Function New-WDACConfig { [System.IO.FileInfo]$CIPPath = ConvertFrom-CIPolicy -XmlFilePath $FinalPolicyPath -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$Name.cip") [WDACConfig.Logger]::Write("Deploying the $Name policy") - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $CIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($CIPPath) } else { Copy-Item -Path $FinalPolicyPath -Destination ([WDACConfig.GlobalVars]::UserConfigDir) -Force @@ -452,7 +452,7 @@ Function New-WDACConfig { Start-Process -FilePath 'C:\Windows\System32\sc.exe' -ArgumentList 'config', 'appidsvc', 'start= auto' -NoNewWindow [WDACConfig.Logger]::Write('Deploying the policy') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $CIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($CIPPath) } Copy-Item -Path $FinalPolicyPath -Destination ([WDACConfig.GlobalVars]::UserConfigDir) -Force diff --git a/WDACConfig/WDACConfig Module Files/Core/Remove-CommonWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Remove-CommonWDACConfig.psm1 index 144b27d9b..8d9a86564 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Remove-CommonWDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Remove-CommonWDACConfig.psm1 @@ -1,6 +1,5 @@ Function Remove-CommonWDACConfig { [CmdletBinding( - SupportsShouldProcess = $true, PositionalBinding = $false, ConfirmImpact = 'High' )] @@ -13,175 +12,11 @@ Function Remove-CommonWDACConfig { [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, [parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$StrictKernelModePolicyTimeOfDeployment, [Parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$Force ) - begin { - if ($(Get-PSCallStack).Count -le 2) { - [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) - } - else { - [WDACConfig.LoggerInitializer]::Initialize($null, $null, $Host) - } - # Create User configuration folder if it doesn't already exist - if (-NOT ([System.IO.Directory]::Exists((Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Parent)))) { - $null = New-Item -ItemType Directory -Path (Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Parent) -Force - [WDACConfig.Logger]::Write('The WDACConfig folder in Program Files has been created because it did not exist.') - } - - # Create User configuration file if it doesn't already exist - if (-NOT ([System.IO.File]::Exists(([WDACConfig.GlobalVars]::UserConfigJson)))) { - $null = New-Item -ItemType File -Path (Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Parent) -Name (Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Leaf) -Force - [WDACConfig.Logger]::Write('The UserConfigurations.json file has been created because it did not exist.') - } - - # Detecting if Confirm switch is used to bypass the confirmation prompts - if ($Force -and -Not $Confirm) { - $ConfirmPreference = 'None' - } - - # 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 -And !$StrictKernelModePolicyTimeOfDeployment) { - - # Prompt for confirmation before deleting the entire User Configurations - if ($PSCmdlet.ShouldProcess('This PC', 'Delete the entire User Configurations for WDACConfig module')) { - - Remove-Item -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Force - [WDACConfig.Logger]::Write('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 - # Exit the begin block - Return - } - - # Read the current user configurations - [System.Object[]]$CurrentUserConfigurations = Get-Content -Path ([WDACConfig.GlobalVars]::UserConfigJson) - - # If the file exists but is corrupted and has bad values, rewrite it - try { - $CurrentUserConfigurations = $CurrentUserConfigurations | ConvertFrom-Json - } - catch { - Set-Content -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Value '' - } - - # A hashtable to hold the User configurations - [System.Collections.Hashtable]$UserConfigurationsObject = @{ - SignedPolicyPath = '' - UnsignedPolicyPath = '' - SignToolCustomPath = '' - CertificateCommonName = '' - CertificatePath = '' - StrictKernelPolicyGUID = '' - StrictKernelNoFlightRootsPolicyGUID = '' - LastUpdateCheck = '' - StrictKernelModePolicyTimeOfDeployment = '' - } - } - process { - # Exit the process block - if ($true -eq $ReturnAndDone) { return } - - if ($SignedPolicyPath) { - [WDACConfig.Logger]::Write('Removing the SignedPolicyPath') - $UserConfigurationsObject.SignedPolicyPath = '' - } - else { - $UserConfigurationsObject.SignedPolicyPath = $CurrentUserConfigurations.SignedPolicyPath - } - - if ($UnsignedPolicyPath) { - [WDACConfig.Logger]::Write('Removing the UnsignedPolicyPath') - $UserConfigurationsObject.UnsignedPolicyPath = '' - } - else { - $UserConfigurationsObject.UnsignedPolicyPath = $CurrentUserConfigurations.UnsignedPolicyPath - } - - if ($SignToolPath) { - [WDACConfig.Logger]::Write('Removing the SignToolPath') - $UserConfigurationsObject.SignToolCustomPath = '' - } - else { - $UserConfigurationsObject.SignToolCustomPath = $CurrentUserConfigurations.SignToolCustomPath - } - - if ($CertPath) { - [WDACConfig.Logger]::Write('Removing the CertPath') - $UserConfigurationsObject.CertificatePath = '' - } - else { - $UserConfigurationsObject.CertificatePath = $CurrentUserConfigurations.CertificatePath - } - - if ($CertCN) { - [WDACConfig.Logger]::Write('Removing the CertCN') - $UserConfigurationsObject.CertificateCommonName = '' - } - else { - $UserConfigurationsObject.CertificateCommonName = $CurrentUserConfigurations.CertificateCommonName - } - - if ($StrictKernelPolicyGUID) { - [WDACConfig.Logger]::Write('Removing the StrictKernelPolicyGUID') - $UserConfigurationsObject.StrictKernelPolicyGUID = '' - } - else { - $UserConfigurationsObject.StrictKernelPolicyGUID = $CurrentUserConfigurations.StrictKernelPolicyGUID - } - - if ($StrictKernelNoFlightRootsPolicyGUID) { - [WDACConfig.Logger]::Write('Removing the StrictKernelNoFlightRootsPolicyGUID') - $UserConfigurationsObject.StrictKernelNoFlightRootsPolicyGUID = '' - } - else { - $UserConfigurationsObject.StrictKernelNoFlightRootsPolicyGUID = $CurrentUserConfigurations.StrictKernelNoFlightRootsPolicyGUID - } - - if ($LastUpdateCheck) { - [WDACConfig.Logger]::Write('Removing the LastUpdateCheck') - $UserConfigurationsObject.LastUpdateCheck = '' - } - else { - $UserConfigurationsObject.LastUpdateCheck = $CurrentUserConfigurations.LastUpdateCheck - } - - if ($StrictKernelModePolicyTimeOfDeployment) { - [WDACConfig.Logger]::Write('Removing the Strict Kernel-Mode Policy Time Of Deployment') - $UserConfigurationsObject.StrictKernelModePolicyTimeOfDeployment = '' - } - else { - $UserConfigurationsObject.StrictKernelModePolicyTimeOfDeployment = $CurrentUserConfigurations.StrictKernelModePolicyTimeOfDeployment - } - } - end { - # Exit the end block - if ($true -eq $ReturnAndDone) { return } - - $UserConfigurationsJSON = $UserConfigurationsObject | ConvertTo-Json - - try { - [WDACConfig.Logger]::Write('Validating the JSON against the schema') - [System.Boolean]$IsValid = Test-Json -Json $UserConfigurationsJSON -SchemaFile "$([WDACConfig.GlobalVars]::ModuleRootPath)\Resources\User Configurations\Schema.json" - } - catch { - Write-Warning -Message "$_`nclearing it." - Set-Content -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Value '' -Force - } - - if ($IsValid) { - # Update the User Configurations file - [WDACConfig.Logger]::Write('Saving the changes') - $UserConfigurationsJSON | Set-Content -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Force - } - else { - Throw 'The User Configurations file is not valid.' - } - } + [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) + [WDACConfig.UserConfiguration]::Remove($SignedPolicyPath, $UnsignedPolicyPath, $SignToolPath, $CertCN, $CertPath, $StrictKernelPolicyGUID, $StrictKernelNoFlightRootsPolicyGUID, $LastUpdateCheck, $StrictKernelModePolicyTimeOfDeployment) <# .SYNOPSIS Removes common values for parameters used by WDACConfig module @@ -190,7 +25,7 @@ Function 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 + Windows Defender Application Control, 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 @@ -207,8 +42,6 @@ Function Remove-CommonWDACConfig { 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 .PARAMETER StrictKernelModePolicyTimeOfDeployment Removes the StrictKernelModePolicyTimeOfDeployment from User Configs .INPUTS diff --git a/WDACConfig/WDACConfig Module Files/Core/Remove-WDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Remove-WDACConfig.psm1 index 191517a4a..3a290c651 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Remove-WDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Remove-WDACConfig.psm1 @@ -48,8 +48,8 @@ Function Remove-WDACConfig { # Get a list of policies using the CiTool, excluding system policies and policies that aren't on disk. # by adding "{ $_.FriendlyName }" we make sure the auto completion works when at least one of the policies doesn't have a friendly name - $Policies = foreach ($Item in (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies) { - if (($Item.IsOnDisk -eq 'True') -and ($Item.IsSystemPolicy -ne 'True') -and $Item.FriendlyName) { + $Policies = foreach ($Item in [WDACConfig.CiToolHelper]::GetPolicies($false, $true, $true)) { + if (($Item.IsOnDisk -eq 'True') -and $Item.FriendlyName) { $Item } } @@ -106,8 +106,8 @@ Function Remove-WDACConfig { 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 = foreach ($Item in (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies) { - if (($Item.IsOnDisk -eq 'True') -and ($Item.IsSystemPolicy -ne 'True')) { + $Policies = foreach ($Item in [WDACConfig.CiToolHelper]::GetPolicies($false, $true, $true)) { + if ($Item.IsOnDisk -eq 'True') { $Item } } @@ -173,13 +173,13 @@ Function Remove-WDACConfig { [System.IO.FileInfo]$SignToolPathFinal = Get-SignTool -SignToolExePathInput $SignToolPath } # If it is null, then Get-SignTool will behave the same as if it was called without any arguments. else { - [System.IO.FileInfo]$SignToolPathFinal = Get-SignTool -SignToolExePathInput (Get-CommonWDACConfig -SignToolPath) + [System.IO.FileInfo]$SignToolPathFinal = Get-SignTool -SignToolExePathInput ([WDACConfig.UserConfiguration]::Get().SignToolCustomPath) } # If CertCN was not provided by user, check if a valid value exists in user configs, if so, use it, otherwise throw an error if (!$CertCN) { - if ([WDACConfig.CertCNz]::new().GetValidValues() -contains (Get-CommonWDACConfig -CertCN)) { - [System.String]$CertCN = Get-CommonWDACConfig -CertCN + if ([WDACConfig.CertCNz]::new().GetValidValues() -contains ([WDACConfig.UserConfiguration]::Get().CertificateCommonName)) { + [System.String]$CertCN = [WDACConfig.UserConfiguration]::Get().CertificateCommonName } else { throw 'CertCN parameter cannot be empty and no valid user configuration was found for it.' @@ -261,7 +261,7 @@ Function Remove-WDACConfig { [WDACConfig.Logger]::Write('Making sure the selected XML policy is deployed on the system') Try { - [System.Guid[]]$CurrentPolicyIDs = foreach ($Item in (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies) { + [System.Guid[]]$CurrentPolicyIDs = foreach ($Item in [WDACConfig.CiToolHelper]::GetPolicies($false, $true, $true)) { if ($Item.IsSystemPolicy -ne 'True') { "{$($Item.PolicyID)}" } @@ -291,7 +291,7 @@ Function Remove-WDACConfig { $CurrentStep++ Write-Progress -Id 18 -Activity 'Signing the policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - [WDACConfig.CodeIntegritySigner]::InvokeCiSigning($PolicyCIPPath, $SignToolPathFinal, $CertCN) + [WDACConfig.SignToolHelper]::Sign($PolicyCIPPath, $SignToolPathFinal, $CertCN) # Fixing the extension name of the newly signed CIP file Move-Item -Path (Join-Path -Path $StagingArea -ChildPath "$PolicyID.cip.p7") -Destination $PolicyCIPPath -Force @@ -302,7 +302,7 @@ Function Remove-WDACConfig { if ($PSCmdlet.ShouldProcess('This PC', 'Deploying the signed policy')) { [WDACConfig.Logger]::Write('Deploying the newly signed CIP file') - $null = &'C:\Windows\System32\CiTool.exe' --update-policy $PolicyCIPPath -json + [WDACConfig.CiToolHelper]::UpdatePolicy($PolicyCIPPath) Write-ColorfulTextWDACConfig -Color Lavender -InputText "Policy with the following details has been Re-signed and Re-deployed in Unsigned mode.`nPlease restart your system." Write-ColorfulTextWDACConfig -Color MintGreen -InputText "PolicyName = $PolicyName" @@ -317,14 +317,14 @@ Function Remove-WDACConfig { # If IDs were supplied by user foreach ($ID in $PolicyIDs ) { - $null = &'C:\Windows\System32\CiTool.exe' --remove-policy "{$ID}" -json + [WDACConfig.CiToolHelper]::RemovePolicy($ID) Write-ColorfulTextWDACConfig -Color Lavender -InputText "Policy with the ID $ID has been successfully removed." } # If names were supplied by user # HashSet to store Unique Policy IDs based on the input name, this will take care of the situations where multiple policies with the same name are deployed $NameID = [System.Collections.Generic.HashSet[System.String]]@(foreach ($PolicyName in $PolicyNames) { - foreach ($Item in (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies ) { + foreach ($Item in [WDACConfig.CiToolHelper]::GetPolicies($true, $true, $true)) { if (($Item.IsOnDisk -eq 'True') -and ($Item.FriendlyName -eq $PolicyName)) { $Item.PolicyID } @@ -334,7 +334,7 @@ Function Remove-WDACConfig { [WDACConfig.Logger]::Write("$($NameID.count) policy IDs have been gathered from the supplied policy names and are going to be removed from the system") foreach ($ID in $NameID) { - $null = &'C:\Windows\System32\CiTool.exe' --remove-policy "{$ID}" -json + [WDACConfig.CiToolHelper]::RemovePolicy($ID) Write-ColorfulTextWDACConfig -Color Lavender -InputText "Policy with the ID $ID has been successfully removed." } } @@ -356,7 +356,7 @@ Function 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 + Windows Defender Application Control .FUNCTIONALITY Using official Microsoft methods, Removes Signed and unsigned deployed WDAC policies (Windows Defender Application Control) .PARAMETER PolicyNames diff --git a/WDACConfig/WDACConfig Module Files/Core/Set-CommonWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Set-CommonWDACConfig.psm1 index 8ac36922a..8a8e4386d 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Set-CommonWDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Set-CommonWDACConfig.psm1 @@ -67,178 +67,10 @@ Function Set-CommonWDACConfig { 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.IO.FileInfo]$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, - [parameter(Mandatory = $false)][System.DateTime]$StrictKernelModePolicyTimeOfDeployment + [parameter(Mandatory = $false)][System.IO.FileInfo]$SignedPolicyPath ) - begin { - if ($(Get-PSCallStack).Count -le 2) { - [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) - } - else { - [WDACConfig.LoggerInitializer]::Initialize($null, $null, $Host) - } - if (!$CertCN -And !$CertPath -And !$SignToolPath -And !$UnsignedPolicyPath -And !$SignedPolicyPath -And !$StrictKernelPolicyGUID -And !$StrictKernelNoFlightRootsPolicyGUID -And !$LastUpdateCheck -And !$StrictKernelModePolicyTimeOfDeployment) { - Throw [System.ArgumentException] 'No parameter was selected.' - } - - if ($CertCN) { - if ([WDACConfig.CertCNz]::new().GetValidValues() -notcontains $CertCN) { - throw "$CertCN does not belong to a subject CN of any of the deployed certificates" - } - } - - # Create User configuration folder if it doesn't already exist - if (-NOT ([System.IO.Directory]::Exists((Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Parent)))) { - $null = New-Item -ItemType Directory -Path (Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Parent) -Force - [WDACConfig.Logger]::Write('The WDACConfig folder in Program Files has been created because it did not exist.') - } - - # Create User configuration file if it doesn't already exist - if (-NOT ([System.IO.File]::Exists(([WDACConfig.GlobalVars]::UserConfigJson)))) { - $null = New-Item -ItemType File -Path (Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Parent) -Name (Split-Path -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Leaf) -Force - [WDACConfig.Logger]::Write('The UserConfigurations.json file has been created because it did not exist.') - } - - # Trying to read the current user configurations - [WDACConfig.Logger]::Write('Trying to read the current user configurations') - [System.Object[]]$CurrentUserConfigurations = Get-Content -Path ([WDACConfig.GlobalVars]::UserConfigJson) - - # If the file exists but is corrupted and has bad values, rewrite it - try { - $CurrentUserConfigurations = $CurrentUserConfigurations | ConvertFrom-Json - } - catch { - [WDACConfig.Logger]::Write('The user configurations file exists but is corrupted and has bad values, rewriting it') - Set-Content -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Value '' - } - - # A hashtable to hold the User configurations - [System.Collections.Hashtable]$UserConfigurationsObject = @{ - SignedPolicyPath = '' - UnsignedPolicyPath = '' - SignToolCustomPath = '' - CertificateCommonName = '' - CertificatePath = '' - StrictKernelPolicyGUID = '' - StrictKernelNoFlightRootsPolicyGUID = '' - LastUpdateCheck = '' - StrictKernelModePolicyTimeOfDeployment = '' - } - } - process { - - [WDACConfig.Logger]::Write('Processing each user configuration property') - - if ($SignedPolicyPath) { - [WDACConfig.Logger]::Write('Saving the supplied Signed Policy path in user configurations.') - $UserConfigurationsObject.SignedPolicyPath = $SignedPolicyPath.FullName - } - else { - [WDACConfig.Logger]::Write('No changes to the Signed Policy path property was detected.') - $UserConfigurationsObject.SignedPolicyPath = $CurrentUserConfigurations.SignedPolicyPath - } - - if ($UnsignedPolicyPath) { - [WDACConfig.Logger]::Write('Saving the supplied Unsigned Policy path in user configurations.') - $UserConfigurationsObject.UnsignedPolicyPath = $UnsignedPolicyPath.FullName - } - else { - [WDACConfig.Logger]::Write('No changes to the Unsigned Policy path property was detected.') - $UserConfigurationsObject.UnsignedPolicyPath = $CurrentUserConfigurations.UnsignedPolicyPath - } - - if ($SignToolPath) { - [WDACConfig.Logger]::Write('Saving the supplied SignTool path in user configurations.') - $UserConfigurationsObject.SignToolCustomPath = $SignToolPath.FullName - } - else { - [WDACConfig.Logger]::Write('No changes to the Signtool path property was detected.') - $UserConfigurationsObject.SignToolCustomPath = $CurrentUserConfigurations.SignToolCustomPath - } - - if ($CertPath) { - [WDACConfig.Logger]::Write('Saving the supplied Certificate path in user configurations.') - $UserConfigurationsObject.CertificatePath = $CertPath.FullName - } - else { - [WDACConfig.Logger]::Write('No changes to the Certificate path property was detected.') - $UserConfigurationsObject.CertificatePath = $CurrentUserConfigurations.CertificatePath - } - - if ($CertCN) { - [WDACConfig.Logger]::Write('Saving the supplied Certificate common name in user configurations.') - $UserConfigurationsObject.CertificateCommonName = $CertCN - } - else { - [WDACConfig.Logger]::Write('No changes to the Certificate common name property was detected.') - $UserConfigurationsObject.CertificateCommonName = $CurrentUserConfigurations.CertificateCommonName - } - - if ($StrictKernelPolicyGUID) { - [WDACConfig.Logger]::Write('Saving the supplied Strict Kernel policy GUID in user configurations.') - $UserConfigurationsObject.StrictKernelPolicyGUID = $StrictKernelPolicyGUID - } - else { - [WDACConfig.Logger]::Write('No changes to the Strict Kernel policy GUID property was detected.') - $UserConfigurationsObject.StrictKernelPolicyGUID = $CurrentUserConfigurations.StrictKernelPolicyGUID - } - - if ($StrictKernelNoFlightRootsPolicyGUID) { - [WDACConfig.Logger]::Write('Saving the supplied Strict Kernel NoFlightRoot policy GUID in user configurations.') - $UserConfigurationsObject.StrictKernelNoFlightRootsPolicyGUID = $StrictKernelNoFlightRootsPolicyGUID - } - else { - [WDACConfig.Logger]::Write('No changes to the Strict Kernel NoFlightRoot policy GUID property was detected.') - $UserConfigurationsObject.StrictKernelNoFlightRootsPolicyGUID = $CurrentUserConfigurations.StrictKernelNoFlightRootsPolicyGUID - } - - if ($LastUpdateCheck) { - [WDACConfig.Logger]::Write('Saving the supplied Last Update Check in user configurations.') - $UserConfigurationsObject.LastUpdateCheck = $LastUpdateCheck - } - else { - [WDACConfig.Logger]::Write('No changes to the Last Update Check property was detected.') - $UserConfigurationsObject.LastUpdateCheck = $CurrentUserConfigurations.LastUpdateCheck - } - - if ($StrictKernelModePolicyTimeOfDeployment) { - [WDACConfig.Logger]::Write('Saving the supplied Strict Kernel-Mode Policy Time Of Deployment in user configurations.') - $UserConfigurationsObject.StrictKernelModePolicyTimeOfDeployment = $StrictKernelModePolicyTimeOfDeployment - } - else { - [WDACConfig.Logger]::Write('No changes to the Strict Kernel-Mode Policy Time Of Deployment property was detected.') - $UserConfigurationsObject.StrictKernelModePolicyTimeOfDeployment = $CurrentUserConfigurations.StrictKernelModePolicyTimeOfDeployment - } - } - end { - - $UserConfigurationsJSON = $UserConfigurationsObject | ConvertTo-Json - - try { - [WDACConfig.Logger]::Write('Validating the JSON against the schema') - [System.Boolean]$IsValid = Test-Json -Json $UserConfigurationsJSON -SchemaFile "$([WDACConfig.GlobalVars]::ModuleRootPath)\Resources\User Configurations\Schema.json" - } - catch { - Write-Warning -Message "$_`nclearing it." - Set-Content -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Value '' -Force - } - - if ($IsValid) { - # Update the User Configurations file - [WDACConfig.Logger]::Write('Saving the changes') - $UserConfigurationsJSON | Set-Content -Path ([WDACConfig.GlobalVars]::UserConfigJson) -Force - - # Display the updated User Configurations - $UserConfigurationsObject - } - else { - Throw 'The User Configurations file is not valid.' - } - } + [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) + [WDACConfig.UserConfiguration]::Set($SignedPolicyPath, $UnsignedPolicyPath, $SignToolPath, $CertCN, $CertPath, $null, $null, $null, $null) <# .SYNOPSIS Add/Change common values for parameters used by WDACConfig module @@ -247,7 +79,7 @@ Function 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 + Windows Defender Application Control, 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 @@ -260,20 +92,8 @@ Function Set-CommonWDACConfig { 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 -.PARAMETER LastUpdateCheck - Last time the Update policy was checked for updates - Used internally by the module -.PARAMETER StrictKernelModePolicyTimeOfDeployment - Time of deployment of the Strict Kernel-Mode policy - Used internally by the module .INPUTS System.IO.FileInfo - System.DateTime - System.Guid System.String .OUTPUTS System.Object[] diff --git a/WDACConfig/WDACConfig Module Files/Shared/Get-KernelModeDriversAudit.psm1 b/WDACConfig/WDACConfig Module Files/Shared/Get-KernelModeDriversAudit.psm1 index b47aa06ea..df7e38e4c 100644 --- a/WDACConfig/WDACConfig Module Files/Shared/Get-KernelModeDriversAudit.psm1 +++ b/WDACConfig/WDACConfig Module Files/Shared/Get-KernelModeDriversAudit.psm1 @@ -28,7 +28,7 @@ Function Get-KernelModeDriversAudit { process { # Get the Code Integrity event logs for kernel mode drivers that have been loaded since the audit mode policy has been deployed - [System.Collections.Hashtable[]]$RawData = Receive-CodeIntegrityLogs -Date (Get-CommonWDACConfig -StrictKernelModePolicyTimeOfDeployment) -Type 'Audit' + [System.Collections.Hashtable[]]$RawData = Receive-CodeIntegrityLogs -Date ([WDACConfig.UserConfiguration]::Get().StrictKernelModePolicyTimeOfDeployment) -Type 'Audit' [WDACConfig.Logger]::Write("RawData count: $($RawData.count)") diff --git a/WDACConfig/WDACConfig Module Files/Shared/Get-SignTool.psm1 b/WDACConfig/WDACConfig Module Files/Shared/Get-SignTool.psm1 index 2860a4e7c..dba951c86 100644 --- a/WDACConfig/WDACConfig Module Files/Shared/Get-SignTool.psm1 +++ b/WDACConfig/WDACConfig Module Files/Shared/Get-SignTool.psm1 @@ -125,7 +125,7 @@ Function Get-SignTool { [WDACConfig.Logger]::Write('SignTool executable was found and verified successfully.') [WDACConfig.Logger]::Write('Setting the SignTool path in the common WDAC user configurations') - $null = Set-CommonWDACConfig -SignToolPath $SignToolExePathOutput + $null = [WDACConfig.UserConfiguration]::Set($null, $null, $SignToolExePathOutput, $null, $null, $null, $null, $null , $null) return $SignToolExePathOutput } diff --git a/WDACConfig/WDACConfig Module Files/WDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/WDACConfig.psm1 index 9aca1aa0a..22d4fec7e 100644 --- a/WDACConfig/WDACConfig Module Files/WDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/WDACConfig.psm1 @@ -44,7 +44,7 @@ Function Update-WDACConfigPSModule { try { # Get the last update check time [WDACConfig.Logger]::Write('Getting the last update check time') - [System.DateTime]$UserConfigDate = Get-CommonWDACConfig -LastUpdateCheck + [System.DateTime]$UserConfigDate = [WDACConfig.UserConfiguration]::Get().LastUpdateCheck } catch { # If the User Config file doesn't exist then set this flag to perform online update check @@ -83,7 +83,7 @@ Function Update-WDACConfigPSModule { # Reset the last update timer to the current time [WDACConfig.Logger]::Write('Resetting the last update timer to the current time') - $null = Set-CommonWDACConfig -LastUpdateCheck $(Get-Date) + $null = [WDACConfig.UserConfiguration]::Set($null, $null, $null, $null, $null, $null, $null, $(Get-Date) , $null) if ($CurrentVersion -lt $LatestVersion) { diff --git a/WDACConfig/WinUI3/.editorconfig b/WDACConfig/WinUI3/.editorconfig index b72f1f784..3ab9b4c35 100644 --- a/WDACConfig/WinUI3/.editorconfig +++ b/WDACConfig/WinUI3/.editorconfig @@ -272,3 +272,6 @@ dotnet_diagnostic.IDE0110.severity = error # CA2101: Specify marshaling for P/Invoke string arguments dotnet_diagnostic.CA2101.severity = error + +# IL2026: Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code +dotnet_diagnostic.IL2026.severity = error diff --git a/WDACConfig/WinUI3/App.xaml.cs b/WDACConfig/WinUI3/App.xaml.cs index 267a6db75..e31f4ea87 100644 --- a/WDACConfig/WinUI3/App.xaml.cs +++ b/WDACConfig/WinUI3/App.xaml.cs @@ -1,4 +1,6 @@ using Microsoft.UI.Xaml; +using System.Diagnostics; +using System; // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. @@ -17,6 +19,7 @@ public partial class App : Application public App() { this.InitializeComponent(); + // this.UnhandledException += App_UnhandledException; } /// @@ -34,5 +37,19 @@ protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs ar // Adding this public property to expose the window public static Window? MainWindow => ((App)Current).m_window; + + + + /* + private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) + { + Console.WriteLine($"Unhandled exception: {e.Exception.Message}"); + e.Handled = true; // Prevent the app from crashing + } + } + */ + + } -} + +} \ No newline at end of file diff --git a/WDACConfig/WinUI3/MainWindow.xaml b/WDACConfig/WinUI3/MainWindow.xaml index 642408f04..9242e01c8 100644 --- a/WDACConfig/WinUI3/MainWindow.xaml +++ b/WDACConfig/WinUI3/MainWindow.xaml @@ -41,6 +41,10 @@ + + + + diff --git a/WDACConfig/WinUI3/MainWindow.xaml.cs b/WDACConfig/WinUI3/MainWindow.xaml.cs index 04b7a7ae8..5b9d0b313 100644 --- a/WDACConfig/WinUI3/MainWindow.xaml.cs +++ b/WDACConfig/WinUI3/MainWindow.xaml.cs @@ -50,6 +50,9 @@ private void NavigationView_SelectionChanged(NavigationView sender, NavigationVi case "GetSecurePolicySettings": _ = ContentFrame.Navigate(typeof(Pages.GetSecurePolicySettings)); break; + case "ViewCurrentPolicies": + _ = ContentFrame.Navigate(typeof(Pages.ViewCurrentPolicies)); + break; default: break; } diff --git a/WDACConfig/WinUI3/NativeMethods.txt b/WDACConfig/WinUI3/NativeMethods.txt new file mode 100644 index 000000000..637c269ec --- /dev/null +++ b/WDACConfig/WinUI3/NativeMethods.txt @@ -0,0 +1,8 @@ +CoCreateInstance +FileSaveDialog +IFileSaveDialog +SHCreateItemFromParsingName +GetOpenFileNameA +GetOpenFileNameW +GetSaveFileNameA +GetSaveFileNameW diff --git a/WDACConfig/WinUI3/Package.appxmanifest b/WDACConfig/WinUI3/Package.appxmanifest index 62834bfcc..2a60c7198 100644 --- a/WDACConfig/WinUI3/Package.appxmanifest +++ b/WDACConfig/WinUI3/Package.appxmanifest @@ -48,6 +48,6 @@ - + diff --git a/WDACConfig/WinUI3/Pages/GetCIHashes.xaml.cs b/WDACConfig/WinUI3/Pages/GetCIHashes.xaml.cs index 47d604ec9..57d2e4c1d 100644 --- a/WDACConfig/WinUI3/Pages/GetCIHashes.xaml.cs +++ b/WDACConfig/WinUI3/Pages/GetCIHashes.xaml.cs @@ -1,55 +1,104 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -using Windows.Storage.Pickers; -using Windows.Storage; using System; -using System.Threading.Tasks; +using System.Runtime.InteropServices; +using System.Text; + +#pragma warning disable CA1401 namespace WDACConfig.Pages { public sealed partial class GetCIHashes : Page { + + // Using P/Invoke because when running as Admin, WinAppSDK's file picker doesn't work. + // https://learn.microsoft.com/en-us/uwp/api/windows.storage.pickers.filesavepicker?view=winrt-26100#in-a-desktop-app-that-requires-elevation + + /* + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct OPENFILENAME + { + public int lStructSize; + public IntPtr hwndOwner; + public IntPtr hInstance; + public string lpstrFilter; + public string lpstrCustomFilter; + public int nMaxCustFilter; + public int nFilterIndex; + public string lpstrFile; + public int nMaxFile; + public string lpstrFileTitle; + public int nMaxFileTitle; + public string lpstrInitialDir; + public string lpstrTitle; + public int Flags; + public ushort nFileOffset; + public ushort nFileExtension; + public string lpstrDefExt; + public IntPtr lCustData; + public IntPtr lpfnHook; + public string lpTemplateName; + public IntPtr pvReserved; + public int dwReserved; + public int FlagsEx; + } + + private const int OFN_EXPLORER = 0x00080000; + private const int OFN_FILEMUSTEXIST = 0x00001000; + private const int OFN_PATHMUSTEXIST = 0x00000800; + // private const int OFN_ALLOWMULTISELECT = 0x00000200; + + private const int MAX_PATH = 260; + + [DllImport("comdlg32.dll", CharSet = CharSet.Auto)] + public static extern bool GetOpenFileName(ref OPENFILENAME ofn); + + */ + public GetCIHashes() { this.InitializeComponent(); - - // Make sure navigating to/from this page maintains its state this.NavigationCacheMode = Microsoft.UI.Xaml.Navigation.NavigationCacheMode.Enabled; } - // Event handler for the file picker button when clicked - private async void PickFile_Click(object sender, RoutedEventArgs e) + private void PickFile_Click(object sender, RoutedEventArgs e) { - StorageFile? file = await PickFileAsync(); - if (file != null) + string? selectedFile = WDACConfig.FilePicker.ShowFilePicker(); + if (!string.IsNullOrEmpty(selectedFile)) { // Call the method that generates the hashes - CodeIntegrityHashes hashes = CiFileHash.GetCiFileHashes(file.Path); + CodeIntegrityHashes hashes = CiFileHash.GetCiFileHashes(selectedFile); // Display the hashes in the UI UpdateUIWithHashes(hashes); } } - // Method to open the file picker and return the selected file - private static async Task PickFileAsync() + /* + + private static string? OpenFileDialog() { - FileOpenPicker picker = new() + OPENFILENAME ofn = new() { - SuggestedStartLocation = PickerLocationId.DocumentsLibrary + lStructSize = Marshal.SizeOf(typeof(OPENFILENAME)), + hwndOwner = IntPtr.Zero, + lpstrFilter = "All Files (*.*)\0*.*\0", + lpstrFile = new string('\0', MAX_PATH), + nMaxFile = MAX_PATH, + lpstrTitle = "Select a file", + Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST }; - // Allow any file type - picker.FileTypeFilter.Add("*"); - - // WinUI 3 specific way to initialize picker for desktop apps - var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow); - WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd); - - return await picker.PickSingleFileAsync(); + if (GetOpenFileName(ref ofn)) + { + return ofn.lpstrFile; + } + return null; } - // Method to update the UI with the CodeIntegrityHashes object + */ + private void UpdateUIWithHashes(CodeIntegrityHashes hashes) { Sha1PageTextBox.Text = hashes.SHA1Page ?? "N/A"; @@ -57,6 +106,5 @@ private void UpdateUIWithHashes(CodeIntegrityHashes hashes) Sha1AuthenticodeTextBox.Text = hashes.SHa1Authenticode ?? "N/A"; Sha256AuthenticodeTextBox.Text = hashes.SHA256Authenticode ?? "N/A"; } - } } diff --git a/WDACConfig/WinUI3/Pages/GitHubDocumentation.xaml b/WDACConfig/WinUI3/Pages/GitHubDocumentation.xaml index 67b0f1ca4..7a72f1397 100644 --- a/WDACConfig/WinUI3/Pages/GitHubDocumentation.xaml +++ b/WDACConfig/WinUI3/Pages/GitHubDocumentation.xaml @@ -6,7 +6,6 @@ xmlns:local="using:WDACConfig.Pages" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:controls="http://schemas.microsoft.com/winfx/2006/xaml/presentation" mc:Ignorable="d"> @@ -52,7 +51,7 @@ - @@ -52,7 +51,7 @@ - + + + + + + + + + + + + + + + +