From 8dff2c9f14b96581ebb3d7e1a892bac440c73883 Mon Sep 17 00:00:00 2001 From: Violet Hansen Date: Sat, 16 Nov 2024 17:59:36 +0200 Subject: [PATCH] AppControl Manager v1.3.0.0 and WDACConfig v0.4.9 (#398) Look at the PR notes for full change log: https://github.com/HotCakeX/Harden-Windows-Security/pull/398 * Added multiple pages and functionalities Added multiple pages and functionalities * Final part of the development Final part of the development --------- Signed-off-by: Violet Hansen --- .../Build AppControl Manager MSIX Package.yml | 2 +- AppControl Manager/.editorconfig | 21 + AppControl Manager/App.xaml.cs | 75 +- AppControl Manager/AppControl Manager.csproj | 65 +- AppControl Manager/MainWindow.xaml | 106 +- AppControl Manager/MainWindow.xaml.cs | 389 +++++- AppControl Manager/Package.appxmanifest | 2 +- .../Pages/AllowNewApps/AllowNewApps.xaml | 57 + .../Pages/AllowNewApps/AllowNewApps.xaml.cs | 105 ++ .../AllowNewAppsEventLogsDataGrid.xaml | 153 +++ .../AllowNewAppsEventLogsDataGrid.xaml.cs | 528 ++++++++ .../AllowNewAppsLocalFilesDataGrid.xaml | 151 +++ .../AllowNewAppsLocalFilesDataGrid.xaml.cs | 526 ++++++++ .../Pages/AllowNewApps/AllowNewAppsStart.xaml | 191 +++ .../AllowNewApps/AllowNewAppsStart.xaml.cs | 776 ++++++++++++ .../Pages/ConfigurePolicyRuleOptions.xaml | 23 +- .../Pages/ConfigurePolicyRuleOptions.xaml.cs | 13 +- AppControl Manager/Pages/CreatePolicy.xaml | 28 +- AppControl Manager/Pages/Deployment.xaml | 18 + AppControl Manager/Pages/Deployment.xaml.cs | 18 + .../Pages/EventLogsPolicyCreation.xaml | 397 ++++++ .../Pages/EventLogsPolicyCreation.xaml.cs | 976 ++++++++++++++ AppControl Manager/Pages/GetCIHashes.xaml | 59 +- .../Pages/GetSecurePolicySettings.xaml | 86 +- .../Pages/GitHubDocumentation.xaml | 2 +- .../Pages/GitHubDocumentation.xaml.cs | 2 +- AppControl Manager/Pages/Logs.xaml | 22 +- AppControl Manager/Pages/Logs.xaml.cs | 8 +- .../Pages/MDEAHPolicyCreation.xaml | 371 ++++++ .../Pages/MDEAHPolicyCreation.xaml.cs | 990 +++++++++++++++ .../Pages/MicrosoftDocumentation.xaml | 2 +- .../Pages/MicrosoftDocumentation.xaml.cs | 2 +- AppControl Manager/Pages/Settings.xaml | 75 +- AppControl Manager/Pages/Settings.xaml.cs | 166 ++- AppControl Manager/Pages/Simulation.xaml | 131 +- AppControl Manager/Pages/Simulation.xaml.cs | 231 +++- .../SystemInformation/CodeIntegrityInfo.xaml | 9 +- .../SystemInformation/SystemInformation.xaml | 39 +- .../SystemInformation.xaml.cs | 9 +- .../ViewCurrentPolicies.xaml | 48 +- .../ViewCurrentPolicies.xaml.cs | 156 ++- AppControl Manager/Pages/Update.xaml | 19 +- .../Shared Logics/AllCertificatesGrabber.cs | 82 +- .../CheckPolicyDeploymentStatus.cs | 4 +- .../Shared Logics/CiToolHelper.cs | 10 +- .../Shared Logics/CodeIntegrityInfo.cs | 14 +- .../Shared Logics/ConfigureISGServices.cs | 7 +- .../Shared Logics/DriveLetterMapper.cs | 122 -- .../Shared Logics/EventLogUtility.cs | 41 +- .../Shared Logics/FileSystemPicker.cs | 4 +- .../Shared Logics/GetExtendedFileAttrib.cs | 36 +- .../Shared Logics/GetFilesFast.cs | 4 +- .../Shared Logics/GetOpusData.cs | 6 +- .../Shared Logics/Initializer.cs | 2 +- .../CILogIntel.cs | 10 +- .../IntelGathering/DriveLetterMapper.cs | 138 ++ .../IntelGathering/EventAction.cs | 10 + .../IntelGathering/FileIdentity.cs | 70 + .../IntelGathering/FileIdentityComparer.cs | 91 ++ .../IntelGathering/FileIdentityOrigin.cs | 11 + .../FileIdentitySignatureBasedHashSet.cs | 85 ++ .../FileIdentityTimeBasedHashSet.cs | 90 ++ .../IntelGathering/FileSignerInfo.cs | 29 + .../IntelGathering/FileSignerInfoComparer.cs | 62 + .../IntelGathering/GetEventLogsData.cs | 1122 +++++++++++++++++ .../GetMDEAdvancedHuntingLogsData.cs | 616 +++++++++ .../IntelGathering/KernelModeDrivers.cs | 380 ++++++ .../IntelGathering/KernelUserVerdict.cs | 19 + .../IntelGathering/LocalFilesScan.cs | 239 ++++ .../IntelGathering/MDEAdvancedHuntingData.cs | 73 ++ .../IntelGathering/OptimizeMDECSVData.cs | 277 ++++ .../IntelGathering/PlatformInvocations.cs | 82 ++ .../IntelGathering/PrepareEmptyPolicy.cs | 31 + .../IntelGathering/ScanLevels.cs | 10 + .../IntelGathering/SignatureStatus.cs | 10 + .../Logging/LoggerInitializer.cs | 2 +- .../Main Cmdlets/AssertWDACConfigIntegrity.cs | 8 +- .../Main Cmdlets/BasePolicyCreator.cs | 6 +- .../Main Cmdlets/GetCIPolicySetting.cs | 2 +- .../Main Cmdlets/InvokeWDACSimulation.cs | 12 +- .../Main Cmdlets/SetCiRuleOptions.cs | 12 +- .../Shared Logics/MeowOpener.cs | 44 +- .../Shared Logics/MoveUserModeToKernelMode.cs | 6 +- .../Shared Logics/PowerShellExecutor.cs | 17 +- .../Shared Logics/ProcessStarter.cs | 56 + .../RemoveSupplementalSigners.cs | 6 +- .../Shared Logics/SnapBackGuarantee.cs | 26 +- .../Shared Logics/TaskSchedulerHelper.cs | 75 ++ .../Types And Definitions/ChainElement.cs | 12 +- .../CodeIntegrityPolicy.cs | 27 +- .../Types And Definitions/FileAttrib.cs | 20 + .../FileAttribComparer .cs | 83 ++ .../Types And Definitions/PolicyHashObj.cs | 4 +- .../Types And Definitions/WinTrust.cs | 42 +- .../WDAC Simulation/Arbitrator.cs | 5 +- .../WDAC Simulation/GetCertificateDetails.cs | 95 +- .../WDAC Simulation/GetFileRuleOutput.cs | 21 +- .../WDAC Simulation/GetSignerInfo.cs | 8 +- .../Shared Logics/WldpQuerySecurityPolicy.cs | 6 +- .../{ => XMLOps}/CiPolicyUtility.cs | 0 .../XMLOps/ClearCiPolicySemantic.cs | 9 +- .../Shared Logics/{ => XMLOps}/EditGUIDs.cs | 0 .../XMLOps/MergeSignersSemantic.cs | 17 +- .../XMLOps/NewCertificateSignerRules.cs | 6 + .../XMLOps/NewFilePublisherLevelRules.cs | 13 +- .../Shared Logics/XMLOps/NewHashLevelRules.cs | 6 + .../Shared Logics/XMLOps/NewPFNLevelRules.cs | 8 + .../XMLOps/NewPublisherLevelRules.cs | 10 + .../XMLOps/RemoveAllowElementsSemantic.cs | 11 +- .../RemoveDuplicateFileAttribSemantic.cs | 275 ++-- .../XMLOps/SignerAndHashBuilder.cs | 341 ++--- .../Shared Logics/XMLOps/UpdateHvciOptions.cs | 2 +- .../Shared Logics/XMLOps/XMLOps.cs | 12 +- .../Shared Logics/XmlFilePathExtractor.cs | 4 +- .../Unique Logic/AppSettings.cs | 44 + .../Unique Logic/AppThemeManager.cs | 19 + .../NavigationBackgroundManager.cs | 16 + .../NavigationViewLocationManager.cs | 17 + .../Unique Logic/SoundManager.cs | 16 + .../Unique Logic/ThemeManager.cs | 17 + AppControl Manager/app.manifest | 2 +- AppControl Manager/exclusion.dic | 5 + WDACConfig/.editorconfig | 9 + WDACConfig/Program.cs | 1 + WDACConfig/Utilities/Hashes.csv | 133 +- WDACConfig/Utilities/Invoke-WDACConfig.ps1 | 4 - .../Shared Logics/MoveUserModeToKernelMode.cs | 6 +- .../CodeIntegrityPolicy.cs | 27 +- .../XMLOps/ClearCiPolicySemantic.cs | 9 +- .../XMLOps/MergeSignersSemantic.cs | 17 +- .../XMLOps/NewCertificateSignerRules.cs | 6 + .../XMLOps/NewFilePublisherLevelRules.cs | 13 +- .../Shared Logics/XMLOps/NewHashLevelRules.cs | 6 + .../Shared Logics/XMLOps/NewPFNLevelRules.cs | 8 + .../XMLOps/NewPublisherLevelRules.cs | 10 + .../XMLOps/RemoveAllowElementsSemantic.cs | 11 +- .../C#/Shared Logics/XMLOps/XMLOps.cs | 9 +- .../Core/ConvertTo-WDACPolicy.psm1 | 851 +------------ .../Core/Deploy-SignedWDACConfig.psm1 | 2 + .../Core/Edit-WDACConfig.psm1 | 371 +----- .../Core/Invoke-WDACSimulation.psm1 | 113 +- .../Core/New-KernelModeWDACConfig.psm1 | 4 + .../Help/ConvertTo-WDACPolicy.md | 424 ------- .../Help/ConvertTo-WDACPolicy.xml | 867 ------------- .../WDACConfig Module Files/WDACConfig.psd1 | 2 +- .../XMLOps/Compare-CorrelatedData.psm1 | 245 ---- .../XMLOps/Optimize-MDECSVData.psm1 | 93 -- WDACConfig/WDACConfig.csproj | 2 +- WDACConfig/exclusion.dic | 1 + 149 files changed, 11399 insertions(+), 4124 deletions(-) create mode 100644 AppControl Manager/Pages/AllowNewApps/AllowNewApps.xaml create mode 100644 AppControl Manager/Pages/AllowNewApps/AllowNewApps.xaml.cs create mode 100644 AppControl Manager/Pages/AllowNewApps/AllowNewAppsEventLogsDataGrid.xaml create mode 100644 AppControl Manager/Pages/AllowNewApps/AllowNewAppsEventLogsDataGrid.xaml.cs create mode 100644 AppControl Manager/Pages/AllowNewApps/AllowNewAppsLocalFilesDataGrid.xaml create mode 100644 AppControl Manager/Pages/AllowNewApps/AllowNewAppsLocalFilesDataGrid.xaml.cs create mode 100644 AppControl Manager/Pages/AllowNewApps/AllowNewAppsStart.xaml create mode 100644 AppControl Manager/Pages/AllowNewApps/AllowNewAppsStart.xaml.cs create mode 100644 AppControl Manager/Pages/Deployment.xaml create mode 100644 AppControl Manager/Pages/Deployment.xaml.cs create mode 100644 AppControl Manager/Pages/EventLogsPolicyCreation.xaml create mode 100644 AppControl Manager/Pages/EventLogsPolicyCreation.xaml.cs create mode 100644 AppControl Manager/Pages/MDEAHPolicyCreation.xaml create mode 100644 AppControl Manager/Pages/MDEAHPolicyCreation.xaml.cs delete mode 100644 AppControl Manager/Shared Logics/DriveLetterMapper.cs rename AppControl Manager/Shared Logics/{Variables => IntelGathering}/CILogIntel.cs (93%) create mode 100644 AppControl Manager/Shared Logics/IntelGathering/DriveLetterMapper.cs create mode 100644 AppControl Manager/Shared Logics/IntelGathering/EventAction.cs create mode 100644 AppControl Manager/Shared Logics/IntelGathering/FileIdentity.cs create mode 100644 AppControl Manager/Shared Logics/IntelGathering/FileIdentityComparer.cs create mode 100644 AppControl Manager/Shared Logics/IntelGathering/FileIdentityOrigin.cs create mode 100644 AppControl Manager/Shared Logics/IntelGathering/FileIdentitySignatureBasedHashSet.cs create mode 100644 AppControl Manager/Shared Logics/IntelGathering/FileIdentityTimeBasedHashSet.cs create mode 100644 AppControl Manager/Shared Logics/IntelGathering/FileSignerInfo.cs create mode 100644 AppControl Manager/Shared Logics/IntelGathering/FileSignerInfoComparer.cs create mode 100644 AppControl Manager/Shared Logics/IntelGathering/GetEventLogsData.cs create mode 100644 AppControl Manager/Shared Logics/IntelGathering/GetMDEAdvancedHuntingLogsData.cs create mode 100644 AppControl Manager/Shared Logics/IntelGathering/KernelModeDrivers.cs create mode 100644 AppControl Manager/Shared Logics/IntelGathering/KernelUserVerdict.cs create mode 100644 AppControl Manager/Shared Logics/IntelGathering/LocalFilesScan.cs create mode 100644 AppControl Manager/Shared Logics/IntelGathering/MDEAdvancedHuntingData.cs create mode 100644 AppControl Manager/Shared Logics/IntelGathering/OptimizeMDECSVData.cs create mode 100644 AppControl Manager/Shared Logics/IntelGathering/PlatformInvocations.cs create mode 100644 AppControl Manager/Shared Logics/IntelGathering/PrepareEmptyPolicy.cs create mode 100644 AppControl Manager/Shared Logics/IntelGathering/ScanLevels.cs create mode 100644 AppControl Manager/Shared Logics/IntelGathering/SignatureStatus.cs create mode 100644 AppControl Manager/Shared Logics/ProcessStarter.cs create mode 100644 AppControl Manager/Shared Logics/TaskSchedulerHelper.cs create mode 100644 AppControl Manager/Shared Logics/Types And Definitions/FileAttrib.cs create mode 100644 AppControl Manager/Shared Logics/Types And Definitions/FileAttribComparer .cs rename AppControl Manager/Shared Logics/{ => XMLOps}/CiPolicyUtility.cs (100%) rename AppControl Manager/Shared Logics/{ => XMLOps}/EditGUIDs.cs (100%) create mode 100644 AppControl Manager/Unique Logic/AppSettings.cs create mode 100644 AppControl Manager/Unique Logic/AppThemeManager.cs create mode 100644 AppControl Manager/Unique Logic/NavigationBackgroundManager.cs create mode 100644 AppControl Manager/Unique Logic/NavigationViewLocationManager.cs create mode 100644 AppControl Manager/Unique Logic/SoundManager.cs create mode 100644 AppControl Manager/Unique Logic/ThemeManager.cs delete mode 100644 WDACConfig/WDACConfig Module Files/Help/ConvertTo-WDACPolicy.md delete mode 100644 WDACConfig/WDACConfig Module Files/Help/ConvertTo-WDACPolicy.xml delete mode 100644 WDACConfig/WDACConfig Module Files/XMLOps/Compare-CorrelatedData.psm1 delete mode 100644 WDACConfig/WDACConfig Module Files/XMLOps/Optimize-MDECSVData.psm1 diff --git a/.github/workflows/Build AppControl Manager MSIX Package.yml b/.github/workflows/Build AppControl Manager MSIX Package.yml index 528f5a0ca..65a433ab8 100644 --- a/.github/workflows/Build AppControl Manager MSIX Package.yml +++ b/.github/workflows/Build AppControl Manager MSIX Package.yml @@ -55,7 +55,7 @@ jobs: - name: Installing the necessary programs run: | winget source update - winget install --id Microsoft.DotNet.SDK.Preview --exact --accept-package-agreements --accept-source-agreements --uninstall-previous --force --source winget + winget install --id Microsoft.DotNet.SDK.9 --exact --accept-package-agreements --accept-source-agreements --uninstall-previous --force --source winget winget install --id Microsoft.VisualStudio.2022.BuildTools --exact --accept-package-agreements --accept-source-agreements --uninstall-previous --force --source winget winget install --id Microsoft.WindowsSDK.10.0.26100 --exact --accept-package-agreements --accept-source-agreements --uninstall-previous --force --source winget # https://github.com/microsoft/winget-cli/issues/1705 diff --git a/AppControl Manager/.editorconfig b/AppControl Manager/.editorconfig index a5af0cba9..bf6e470b8 100644 --- a/AppControl Manager/.editorconfig +++ b/AppControl Manager/.editorconfig @@ -344,3 +344,24 @@ dotnet_diagnostic.CA1507.severity = error # IDE0001: Simplify name dotnet_diagnostic.IDE0001.severity = error + +# SYSLIB1045: Convert to 'GeneratedRegexAttribute'. +dotnet_diagnostic.SYSLIB1045.severity = error + +# Use collection expression for array (IDE0300) +dotnet_diagnostic.IDE0300.severity = error + +# Inline variable declaration (IDE0018) +dotnet_diagnostic.IDE0018.severity = error + +# IDE0044: Add readonly modifier +dotnet_diagnostic.IDE0044.severity = error + +# IDE0031: Use null propagation +dotnet_diagnostic.IDE0031.severity = error + +# SYSLIB1092: The usage of 'LibraryImportAttribute' does not follow recommendations. +dotnet_diagnostic.SYSLIB1092.severity = error + +# CA2263: Prefer generic overload when type is known +dotnet_diagnostic.CA2263.severity = error diff --git a/AppControl Manager/App.xaml.cs b/AppControl Manager/App.xaml.cs index f9b3dabfd..1bd2952fe 100644 --- a/AppControl Manager/App.xaml.cs +++ b/AppControl Manager/App.xaml.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Windows.ApplicationModel; +using static WDACConfig.AppSettings; // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. @@ -30,6 +31,11 @@ public partial class App : Application // Convert it to a normal Version object internal static readonly Version currentAppVersion = new(packageVersion.Major, packageVersion.Minor, packageVersion.Build, packageVersion.Revision); + // Check if another instance of AppControl Manager is running + private static bool IsUniqueAppInstance; + + private static Mutex? _mutex; + private const string MutexName = "AppControlManagerRunning"; /// /// Initializes the singleton application object. This is the first line of authored code @@ -45,14 +51,72 @@ public App() // to handle unhandled exceptions this.UnhandledException += App_UnhandledException; + + + #region + + // Check for the SoundSetting in the local settings + bool soundSetting = AppSettings.GetSetting(SettingKeys.SoundSetting); + + if (soundSetting) + { + ElementSoundPlayer.State = ElementSoundPlayerState.On; + ElementSoundPlayer.SpatialAudioMode = ElementSpatialAudioMode.On; + } + else + { + ElementSoundPlayer.State = ElementSoundPlayerState.Off; + ElementSoundPlayer.SpatialAudioMode = ElementSpatialAudioMode.Off; + } + + // Subscribe to the SoundSettingChanged event to listen for changes globally + SoundManager.SoundSettingChanged += OnSoundSettingChanged; + + #endregion + + } + + + + /// + /// Event handler for when the sound setting is changed. + /// + /// + private void OnSoundSettingChanged(bool isSoundOn) + { + // Set the global sound state based on the event + if (isSoundOn) + { + ElementSoundPlayer.State = ElementSoundPlayerState.On; + ElementSoundPlayer.SpatialAudioMode = ElementSpatialAudioMode.On; + } + else + { + ElementSoundPlayer.State = ElementSoundPlayerState.Off; + ElementSoundPlayer.SpatialAudioMode = ElementSpatialAudioMode.Off; + } } + + /// /// Invoked when the application is launched. /// /// Details about the launch request and process. protected override void OnLaunched(LaunchActivatedEventArgs args) { + + // Creates a named Mutex with the specified unique name + // The first parameter specifies that this instance initially owns the Mutex if created successfully + // The third parameter indicates whether this application instance is the first/unique one + // If "IsUniqueAppInstance" is true, it means no other instance of the app is running; otherwise, another instance exists and it will be false + _mutex = new Mutex(true, MutexName, out IsUniqueAppInstance); + + if (!IsUniqueAppInstance) + { + Logger.Write("There is another instance of the AppControl Manager running!"); + } + m_window = new MainWindow(); m_window.Closed += Window_Closed; // Assign event handler for the window closed event m_window.Activate(); @@ -87,19 +151,24 @@ private async void App_UnhandledException(object sender, Microsoft.UI.Xaml.Unhan /// private void Window_Closed(object sender, WindowEventArgs e) { - // Clean up the staging area - if (Directory.Exists(GlobalVars.StagingArea)) + // Clean up the staging area only if there are no other instance of the AppControl Manager running + // Don't want to disrupt their workflow + if (Directory.Exists(GlobalVars.StagingArea) && IsUniqueAppInstance) { Directory.Delete(GlobalVars.StagingArea, true); } + + // Release the Mutex + _mutex?.Dispose(); } + /// /// Displays a ContentDialog with the error message. /// private async Task ShowErrorDialogAsync(Exception ex) { - if (m_window != null) + if (m_window is not null) { // Wait for the semaphore before showing a new error dialog await _dialogSemaphore.WaitAsync(); diff --git a/AppControl Manager/AppControl Manager.csproj b/AppControl Manager/AppControl Manager.csproj index 643b4f0c0..29ce5a8cf 100644 --- a/AppControl Manager/AppControl Manager.csproj +++ b/AppControl Manager/AppControl Manager.csproj @@ -19,7 +19,14 @@ true true enable - 10.0.26100.38 + + + 10.0.26100.56 + + - + @@ -140,11 +149,18 @@ + + + + + + + @@ -189,6 +205,26 @@ \ + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + MSBuild:Compile @@ -229,5 +265,28 @@ MSBuild:Compile + + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + \ No newline at end of file diff --git a/AppControl Manager/MainWindow.xaml b/AppControl Manager/MainWindow.xaml index 5e8413031..0fec45a25 100644 --- a/AppControl Manager/MainWindow.xaml +++ b/AppControl Manager/MainWindow.xaml @@ -58,32 +58,44 @@ + PaneTitle="Menu" + IsBackButtonVisible="Auto" + IsBackEnabled="True" + BackRequested="NavView_BackRequested" + ItemInvoked="NavigationView_ItemInvoked" + AlwaysShowHeader="True"> + + + + + + + + + - + - - - - - - - - - - - - @@ -100,21 +112,60 @@ - + - + - + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -134,7 +185,8 @@ - + + diff --git a/AppControl Manager/MainWindow.xaml.cs b/AppControl Manager/MainWindow.xaml.cs index 1716137e9..95295f32d 100644 --- a/AppControl Manager/MainWindow.xaml.cs +++ b/AppControl Manager/MainWindow.xaml.cs @@ -1,14 +1,24 @@ +using Microsoft.UI; +using Microsoft.UI.Composition.SystemBackdrops; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using static WDACConfig.AppSettings; namespace WDACConfig { public sealed partial class MainWindow : Window { + public MainWindowViewModel ViewModel { get; } + // Dictionary to store the display names and associated NavigationViewItems + private readonly Dictionary menuItems = []; + + public MainWindow() { this.InitializeComponent(); @@ -17,6 +27,18 @@ public MainWindow() // Make title bar Mica ExtendsContentIntoTitleBar = true; + // Subscribe to the global BackDrop change event + ThemeManager.BackDropChanged += OnBackgroundChanged; + + // Subscribe to the NavigationView Content background change event + NavigationBackgroundManager.NavViewBackgroundChange += OnNavigationBackgroundChanged; + + // Subscribe to the global NavigationView location change event + NavigationViewLocationManager.NavigationViewLocationChanged += OnNavigationViewLocationChanged; + + // Subscribe to the global App theme change event + AppThemeManager.AppThemeChanged += OnAppThemeChanged; + #region // Use the singleton instance of AppUpdate class @@ -64,57 +86,342 @@ public MainWindow() // Set the "CreatePolicy" item as selected in the NavigationView MainNavigation.SelectedItem = MainNavigation.MenuItems.OfType() .First(item => item.Tag.ToString() == "CreatePolicy"); + + // Set the initial NavigationView header + MainNavigation.Header = "Create Policy"; + + PopulateMenuItems(); + + + // Set the initial background setting based on the user's settings + OnNavigationBackgroundChanged(AppSettings.GetSetting(SettingKeys.NavViewBackground)); + + // Set the initial BackDrop setting based on the user's settings + OnBackgroundChanged(AppSettings.GetSetting(SettingKeys.BackDropBackground)); + + // Set the initial App Theme based on the user's settings + OnAppThemeChanged(AppSettings.GetSetting(SettingKeys.AppTheme)); + } - // Event handler for the main navigation menu - private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) + + + /// + /// Event handler for the global NavigationView location change event + /// + /// + private void OnNavigationViewLocationChanged(string newLocation) { - // Check if the item is selected - if (args.SelectedItem is NavigationViewItem selectedItem) + // Set the NavigationView's location based on the event + switch (newLocation) { - string? selectedTag = selectedItem.Tag?.ToString(); + case "Left": + { + // MainNavigation has no margins by default + MainNavigation.Margin = new Thickness(0); - // Navigate to the page based on the Tag - switch (selectedTag) - { - case "CreatePolicy": - _ = ContentFrame.Navigate(typeof(Pages.CreatePolicy)); - 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)); + MainNavigation.PaneDisplayMode = NavigationViewPaneDisplayMode.Left; break; - case "GetSecurePolicySettings": - _ = ContentFrame.Navigate(typeof(Pages.GetSecurePolicySettings)); - break; - case "SystemInformation": - _ = ContentFrame.Navigate(typeof(Pages.SystemInformation)); - break; - case "ConfigurePolicyRuleOptions": - _ = ContentFrame.Navigate(typeof(Pages.ConfigurePolicyRuleOptions)); - break; - case "Logs": - _ = ContentFrame.Navigate(typeof(Pages.Logs)); - break; - case "Simulation": - _ = ContentFrame.Navigate(typeof(Pages.Simulation)); - break; - case "Update": - _ = ContentFrame.Navigate(typeof(Pages.Update)); + } + case "Top": + { + // Needs some top margin when it's set to top + MainNavigation.Margin = new Thickness(0, 40, 0, 0); + + MainNavigation.PaneDisplayMode = NavigationViewPaneDisplayMode.Top; break; - default: + } + default: + { + // MainNavigation has no margins by default + MainNavigation.Margin = new Thickness(0); break; + } + }; + + } + + + /// + /// Note: Keeping it transparent would probably not be good for accessibility. + /// Changing it during runtime is not possible without trigger a theme change: Light/Dark. + /// Application.RequestedTheme is read-only, so we us RootGrid which is the origin of all other elements. + /// + /// + private void OnNavigationBackgroundChanged(bool isBackgroundOn) + { + // Get the current theme + ElementTheme currentTheme = RootGrid.ActualTheme; + + // Calculate the opposite theme + ElementTheme oppositeTheme = currentTheme == ElementTheme.Dark ? ElementTheme.Light : ElementTheme.Dark; + + // Switch to opposite theme + RootGrid.RequestedTheme = oppositeTheme; + + // Perform NavigationView background changes based on the settings' page's button + if (isBackgroundOn) + { + MainNavigation.Resources["NavigationViewContentBackground"] = new SolidColorBrush(Colors.Transparent); + } + else + { + _ = MainNavigation.Resources.Remove("NavigationViewContentBackground"); + } + + // Switch back to the current theme + RootGrid.RequestedTheme = currentTheme; + } + + + + + + /// + /// Event handler for the global BackgroundChanged event. When user selects a different background for the app, this will be triggered. + /// + /// + private void OnBackgroundChanged(string? selectedBackdrop) + { + // Update the SystemBackdrop based on the selected background + // The Default is set in the XAML + switch (selectedBackdrop) + { + case "MicaAlt": + this.SystemBackdrop = new MicaBackdrop { Kind = MicaKind.BaseAlt }; + break; + case "Mica": + this.SystemBackdrop = new MicaBackdrop { Kind = MicaKind.Base }; + break; + case "Acrylic": + this.SystemBackdrop = new DesktopAcrylicBackdrop(); + break; + default: + break; + } + } + + + + /// + /// Event handler for the global AppThemeChanged event + /// + /// + private void OnAppThemeChanged(string? newTheme) + { + + // Get the current system color mode + // UISettings uiSettings = new(); + // ElementTheme currentColorMode = uiSettings.GetColorValue(UIColorType.Background) == Colors.Black + // ? ElementTheme.Dark + // : ElementTheme.Light; + + + // Better approach that doesn't require instantiating a new UISettings object + ElementTheme currentColorMode = Application.Current.RequestedTheme == ApplicationTheme.Dark + ? ElementTheme.Dark + : ElementTheme.Light; + + + // Set the requested theme based on the event + // If "Use System Setting" is used, the current system color mode will be assigned which can be either light/dark + RootGrid.RequestedTheme = newTheme switch + { + "Light" => ElementTheme.Light, + "Dark" => ElementTheme.Dark, + _ => currentColorMode, + }; + } + + + /// + /// Populate the dictionary with menu items for search purposes + /// + private void PopulateMenuItems() + { + foreach (NavigationViewItem item in MainNavigation.MenuItems.OfType()) + { + menuItems[item.Content.ToString()!] = item; + + // If there are sub-items, add those as well + if (item.MenuItems is not null && item.MenuItems.Count > 0) + { + foreach (NavigationViewItem subItem in item.MenuItems.OfType()) + { + menuItems[subItem.Content.ToString()!] = subItem; + } + } + } + } + + /// + /// Event handler for the AutoSuggestBox text change event + /// + /// + /// + private void SearchBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) + { + if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput) + { + string query = sender.Text.ToLower(); + + // Filter menu items based on the search query + List suggestions = menuItems.Keys + .Where(name => name.Contains(query, System.StringComparison.OrdinalIgnoreCase)) + .ToList(); + + + // Set the filtered items as suggestions in the AutoSuggestBox + sender.ItemsSource = suggestions; + } + } + + /// + /// Event handler for when a suggestion is chosen in the AutoSuggestBox + /// + /// + /// + private void SearchBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) + { + // Get the selected item's name and find the corresponding NavigationViewItem + string? chosenItemName = args.SelectedItem?.ToString(); + if (chosenItemName is not null && menuItems.TryGetValue(chosenItemName, out NavigationViewItem? selectedItem)) + { + // Select the item in the NavigationView + MainNavigation.SelectedItem = selectedItem; + + if (selectedItem is not null) + { + // Directly call NavigateToMenuItem with the selected item's tag + string? selectedTag = selectedItem.Tag?.ToString(); + + if (selectedTag is not null) + { + NavigateToMenuItem(selectedTag); + } } } } + + + /// + /// Event handler for main navigation menu selection change + /// + /// + /// + private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) + { + if (args.SelectedItem is NavigationViewItem selectedItem) + { + // Play sound for selection change + ElementSoundPlayer.Play(ElementSoundKind.MoveNext); + + string selectedTag = selectedItem.Tag?.ToString()!; + NavigateToMenuItem(selectedTag); + } + } + + + /// + /// Separate method to handle navigation based on the selected tag + /// + /// + private void NavigateToMenuItem(string selectedTag) + { + switch (selectedTag) + { + case "CreatePolicy": + _ = ContentFrame.Navigate(typeof(Pages.CreatePolicy)); + 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; + case "SystemInformation": + _ = ContentFrame.Navigate(typeof(Pages.SystemInformation)); + break; + case "ConfigurePolicyRuleOptions": + _ = ContentFrame.Navigate(typeof(Pages.ConfigurePolicyRuleOptions)); + break; + case "Logs": + _ = ContentFrame.Navigate(typeof(Pages.Logs)); + break; + case "Simulation": + _ = ContentFrame.Navigate(typeof(Pages.Simulation)); + break; + case "Update": + _ = ContentFrame.Navigate(typeof(Pages.Update)); + break; + case "Deployment": + _ = ContentFrame.Navigate(typeof(Pages.Deployment)); + break; + case "EventLogsPolicyCreation": + _ = ContentFrame.Navigate(typeof(Pages.EventLogsPolicyCreation)); + break; + case "MDEAHPolicyCreation": + _ = ContentFrame.Navigate(typeof(Pages.MDEAHPolicyCreation)); + break; + case "AllowNewApps": + _ = ContentFrame.Navigate(typeof(Pages.AllowNewApps)); + break; + default: + break; + } + } + + + /// + /// Event handlers for the back button in the NavigationView + /// + /// + /// + private void NavView_BackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args) + { + if (ContentFrame.CanGoBack) + { + + // Don't go back if the nav pane is overlayed. + /* + if (MainNavigation.IsPaneOpen && + (MainNavigation.DisplayMode == NavigationViewDisplayMode.Compact || + MainNavigation.DisplayMode == NavigationViewDisplayMode.Minimal)) + */ + + // Play sound for back navigation + ElementSoundPlayer.Play(ElementSoundKind.GoBack); + + ContentFrame.GoBack(); + } + } + + + /// + /// Set the NavigationView's header to the Navigation view item's content + /// + /// + /// + private void NavigationView_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args) + { + if (MainNavigation.SelectedItem is NavigationViewItem item) + { + + // Must be nullable because when NavigationViewPaneDisplayMode is top, this is null. + sender.Header = item.Content?.ToString(); + } + } + + } } diff --git a/AppControl Manager/Package.appxmanifest b/AppControl Manager/Package.appxmanifest index 6d939f326..169b9a00d 100644 --- a/AppControl Manager/Package.appxmanifest +++ b/AppControl Manager/Package.appxmanifest @@ -11,7 +11,7 @@ + Version="1.3.0.0" /> diff --git a/AppControl Manager/Pages/AllowNewApps/AllowNewApps.xaml b/AppControl Manager/Pages/AllowNewApps/AllowNewApps.xaml new file mode 100644 index 000000000..eb4482b81 --- /dev/null +++ b/AppControl Manager/Pages/AllowNewApps/AllowNewApps.xaml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AppControl Manager/Pages/AllowNewApps/AllowNewApps.xaml.cs b/AppControl Manager/Pages/AllowNewApps/AllowNewApps.xaml.cs new file mode 100644 index 000000000..1f47e54a1 --- /dev/null +++ b/AppControl Manager/Pages/AllowNewApps/AllowNewApps.xaml.cs @@ -0,0 +1,105 @@ +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Navigation; +using System; +using System.Linq; + +// 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 +{ + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + public sealed partial class AllowNewApps : Page + { + // A static instance of the AllowNewApps class which will hold the single, shared instance of the page + private static AllowNewApps? _instance; + + public AllowNewApps() + { + this.InitializeComponent(); + + // Assign this instance to the static field + _instance = this; + + // Make sure navigating to/from this page maintains its state + this.NavigationCacheMode = NavigationCacheMode.Enabled; + + // Navigate to the AllowNewAppsStart page when the window is loaded + _ = ContentFrame.Navigate(typeof(AllowNewAppsStart)); + + // Set the "LocalFiles" item as selected in the NavigationView + AllowNewAppsNavigation.SelectedItem = AllowNewAppsNavigation.MenuItems.OfType() + .First(item => item.Tag.ToString() == "Start"); + + DisableAllowNewAppsNavigationItem("LocalFiles"); + DisableAllowNewAppsNavigationItem("EventLogs"); + } + + + // Public property to access the singleton instance from other classes + public static AllowNewApps Instance => _instance ?? throw new InvalidOperationException("AllowNewApps is not initialized."); + + + // Event handler for the 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 "Start": + _ = ContentFrame.Navigate(typeof(AllowNewAppsStart)); + break; + case "LocalFiles": + _ = ContentFrame.Navigate(typeof(AllowNewAppsLocalFilesDataGrid)); + break; + case "EventLogs": + _ = ContentFrame.Navigate(typeof(AllowNewAppsEventLogsDataGrid)); + break; + default: + break; + } + } + } + + + /// + /// Disables a navigation item by its tag. + /// + /// The tag of the navigation item to disable. + internal void DisableAllowNewAppsNavigationItem(string tag) + { + NavigationViewItem? item = AllowNewAppsNavigation.MenuItems + .OfType() + .FirstOrDefault(i => i.Tag?.ToString() == tag); + + if (item is not null) + { + item.IsEnabled = false; + } + } + + /// + /// Enables a navigation item by its tag. + /// + /// The tag of the navigation item to enable. + internal void EnableAllowNewAppsNavigationItem(string tag) + { + NavigationViewItem? item = AllowNewAppsNavigation.MenuItems + .OfType() + .FirstOrDefault(i => i.Tag?.ToString() == tag); + + if (item is not null) + { + item.IsEnabled = true; + } + } + + } +} diff --git a/AppControl Manager/Pages/AllowNewApps/AllowNewAppsEventLogsDataGrid.xaml b/AppControl Manager/Pages/AllowNewApps/AllowNewAppsEventLogsDataGrid.xaml new file mode 100644 index 000000000..f6a787766 --- /dev/null +++ b/AppControl Manager/Pages/AllowNewApps/AllowNewAppsEventLogsDataGrid.xaml @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AppControl Manager/Pages/AllowNewApps/AllowNewAppsEventLogsDataGrid.xaml.cs b/AppControl Manager/Pages/AllowNewApps/AllowNewAppsEventLogsDataGrid.xaml.cs new file mode 100644 index 000000000..0118c2127 --- /dev/null +++ b/AppControl Manager/Pages/AllowNewApps/AllowNewAppsEventLogsDataGrid.xaml.cs @@ -0,0 +1,528 @@ +using CommunityToolkit.WinUI.UI.Controls; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Navigation; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using WDACConfig.IntelGathering; +using Windows.ApplicationModel.DataTransfer; + +// 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 +{ + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + public sealed partial class AllowNewAppsEventLogsDataGrid : Page + { + + // A static instance of the AllowNewAppsEventLogsDataGrid class which will hold the single, shared instance of the page + private static AllowNewAppsEventLogsDataGrid? _instance; + + public AllowNewAppsEventLogsDataGrid() + { + this.InitializeComponent(); + + // Make sure navigating to/from this page maintains its state + this.NavigationCacheMode = NavigationCacheMode.Enabled; + + // Assign this instance to the static field + _instance = this; + + } + + // Public property to access the singleton instance from other classes + // It's okay it's nullable, null check will happen before accessing it + public static AllowNewAppsEventLogsDataGrid? Instance => _instance; + + #region + + // Without the following steps, when the user begins data fetching process and then navigates away from this page + // Upon arrival at this page again, the DataGrid loses its virtualization, causing the UI to hang for extended periods of time + // But after nullifying DataGrid's ItemsSource when page is navigated from and reassigning it when page is navigated to, + // We tackle that problem. Data will sill be stored in the ObservableCollection when page is not in focus, + // But DataGrid's source will pick them up only when page is navigated to. + protected override void OnNavigatedTo(NavigationEventArgs e) + { + base.OnNavigatedTo(e); + FileIdentitiesDataGrid.ItemsSource = AllowNewAppsStart.Instance.EventLogsFileIdentities; + + // Update the logs when user switches to this page + UpdateTotalLogs(); + } + + protected override void OnNavigatedFrom(NavigationEventArgs e) + { + base.OnNavigatedFrom(e); + FileIdentitiesDataGrid.ItemsSource = null; + } + + #endregion + + + /// + /// Event handler for the SearchBox text change + /// + private void SearchBox_TextChanged(object sender, TextChangedEventArgs e) + { + ApplyFilters(); + } + + + /// + /// Applies the date and search filters to the data grid + /// + private void ApplyFilters() + { + + // Get the search term from the SearchBox, converting it to lowercase for case-insensitive searching + string searchTerm = SearchBox.Text.Trim().ToLowerInvariant(); + + // Start with all items from the complete list, 'AllowNewAppsStart.Instance.EventLogsAllFileIdentities' + // This list is used as the base set for filtering to preserve original data + IEnumerable filteredResults = AllowNewAppsStart.Instance.EventLogsAllFileIdentities.AsEnumerable(); + + // Apply the search filter if there is a non-empty search term + if (!string.IsNullOrWhiteSpace(searchTerm)) + { + + // Filter results further to match the search term across multiple properties, case-insensitively + filteredResults = filteredResults.Where(output => + (output.FileName is not null && output.FileName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.SignatureStatus.ToString().Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.Action.ToString().Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.OriginalFileName is not null && output.OriginalFileName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.InternalName is not null && output.InternalName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.FileDescription is not null && output.FileDescription.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.ProductName is not null && output.ProductName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.FileVersion is not null && output.FileVersion.ToString().Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.PackageFamilyName is not null && output.PackageFamilyName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.PolicyName is not null && output.PolicyName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.ComputerName is not null && output.ComputerName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.FilePath is not null && output.FilePath.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.SHA256FlatHash is not null && output.SHA256FlatHash.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.SHA256Hash is not null && output.SHA256Hash.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + output.FilePublishersToDisplay.Contains(searchTerm, StringComparison.OrdinalIgnoreCase) + ); + } + + // Clear the current contents of the ObservableCollection + AllowNewAppsStart.Instance.EventLogsFileIdentities.Clear(); + + // Populate the ObservableCollection with the filtered results + // This triggers the UI to update the DataGrid based on the filtered data + foreach (FileIdentity result in filteredResults) + { + AllowNewAppsStart.Instance.EventLogsFileIdentities.Add(result); + } + + // Explicitly set the DataGrid's ItemsSource to ensure the data refreshes + FileIdentitiesDataGrid.ItemsSource = AllowNewAppsStart.Instance.EventLogsFileIdentities; + + // Update any visual or text element showing the total logs count + UpdateTotalLogs(); + } + + + /// + /// Event handler for the Clear Data button + /// + /// + /// + private void ClearDataButton_Click(object sender, RoutedEventArgs e) + { + AllowNewAppsStart.Instance.EventLogsFileIdentities.Clear(); + AllowNewAppsStart.Instance.EventLogsAllFileIdentities.Clear(); + + UpdateTotalLogs(true); + } + + + + + // https://learn.microsoft.com/en-us/windows/communitytoolkit/controls/datagrid_guidance/group_sort_filter + // Column sorting logic for the entire DataGrid + private void FileIdentitiesDataGrid_Sorting(object sender, DataGridColumnEventArgs e) + { + // Sort the column based on its tag and current sort direction + if (string.Equals(e.Column.Tag?.ToString(), "FileName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.FileName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "TimeCreated", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.TimeCreated); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SignatureStatus", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SignatureStatus); + } + else if (string.Equals(e.Column.Tag?.ToString(), "Action", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.Action); + } + else if (string.Equals(e.Column.Tag?.ToString(), "OriginalFileName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.OriginalFileName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "InternalName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.InternalName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "FileDescription", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.FileDescription); + } + else if (string.Equals(e.Column.Tag?.ToString(), "ProductName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.ProductName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "FileVersion", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.FileVersion); + } + else if (string.Equals(e.Column.Tag?.ToString(), "PackageFamilyName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.PackageFamilyName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SHA256Hash", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SHA256Hash); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SHA1Hash", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SHA1Hash); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SHA256FlatHash", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SHA256FlatHash); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SHA1FlatHash", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SHA1FlatHash); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SISigningScenario", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SISigningScenario); + } + else if (string.Equals(e.Column.Tag?.ToString(), "FilePath", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.FilePath); + } + else if (string.Equals(e.Column.Tag?.ToString(), "ComputerName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.ComputerName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "PolicyGUID", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.PolicyGUID); + } + else if (string.Equals(e.Column.Tag?.ToString(), "PolicyName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.PolicyName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "FilePublishersToDisplay", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.FilePublishersToDisplay); + } + + + // Clear SortDirection for other columns + foreach (DataGridColumn column in FileIdentitiesDataGrid.Columns) + { + if (column != e.Column) + { + column.SortDirection = null; + } + } + } + + + /// + /// Helper method for sorting any column on the DataGird + /// + /// + /// + /// + private void SortColumn(DataGridColumnEventArgs e, Func keySelector) + { + // Check if the search box is empty or not + bool isSearchEmpty = string.IsNullOrWhiteSpace(SearchBox.Text); + + // Get the collection to sort based on the search box status + // Allowing us to sort only the items in the search results + List collectionToSort = isSearchEmpty ? AllowNewAppsStart.Instance.EventLogsAllFileIdentities : [.. AllowNewAppsStart.Instance.EventLogsFileIdentities]; + + // Perform the sorting based on the current SortDirection (ascending or descending) + if (e.Column.SortDirection is null || e.Column.SortDirection == DataGridSortDirection.Ascending) + { + // Descending: custom order depending on column type + AllowNewAppsStart.Instance.EventLogsFileIdentities = [.. collectionToSort.OrderByDescending(keySelector)]; + + // Set the column direction to Descending + e.Column.SortDirection = DataGridSortDirection.Descending; + } + else + { + // Ascending: custom order depending on column type + AllowNewAppsStart.Instance.EventLogsFileIdentities = [.. collectionToSort.OrderBy(keySelector)]; + e.Column.SortDirection = DataGridSortDirection.Ascending; + } + + // Update the ItemsSource of the DataGrid + // Required for sort + search to work properly, even though binding to the ObservableCollection already happens in XAML + FileIdentitiesDataGrid.ItemsSource = AllowNewAppsStart.Instance.EventLogsFileIdentities; + } + + + /// + /// Selects all of the displayed rows on the DataGrid + /// + /// + /// + private void SelectAll_Click(object sender, RoutedEventArgs e) + { + _ = DispatcherQueue.TryEnqueue(() => + { + // Clear existing selections + FileIdentitiesDataGrid.SelectedItems.Clear(); + + foreach (FileIdentity fileIdentity in AllowNewAppsStart.Instance.EventLogsFileIdentities) + { + _ = FileIdentitiesDataGrid.SelectedItems.Add(fileIdentity); // Select each item + } + + }); + } + + + /// + /// De-selects all of the displayed rows on the DataGrid + /// + /// + /// + private void DeSelectAll_Click(object sender, RoutedEventArgs e) + { + FileIdentitiesDataGrid.SelectedItems.Clear(); // Deselect all rows by clearing SelectedItems + } + + + + /// + /// Deletes the selected row from the results + /// + /// + /// + private void DataGridFlyoutMenuDelete_Click(object sender, RoutedEventArgs e) + { + // Collect the selected items to delete + List itemsToDelete = FileIdentitiesDataGrid.SelectedItems.Cast().ToList(); + + // Remove each selected item from the FileIdentities collection + foreach (FileIdentity item in itemsToDelete) + { + _ = AllowNewAppsStart.Instance.EventLogsFileIdentities.Remove(item); + } + + UpdateTotalLogs(); + } + + + + /// + /// Copies the selected rows to the clipboard in a formatted manner, with each property labeled for clarity. + /// + /// The event sender. + /// The event arguments. + private void DataGridFlyoutMenuCopy_Click(object sender, RoutedEventArgs e) + { + // Check if there are selected items in the DataGrid + if (FileIdentitiesDataGrid.SelectedItems.Count > 0) + { + // Initialize StringBuilder to store all selected rows' data with labels + StringBuilder dataBuilder = new(); + + // Loop through each selected item in the DataGrid + foreach (var selectedItem in FileIdentitiesDataGrid.SelectedItems) + { + if (selectedItem is FileIdentity selectedRow) + { + // Append each row's formatted data to the StringBuilder + _ = dataBuilder.AppendLine(ConvertRowToText(selectedRow)); + + // Add a separator between rows for readability in multi-row copies + _ = dataBuilder.AppendLine(new string('-', 50)); + } + } + + // Create a DataPackage to hold the text data + DataPackage dataPackage = new(); + + // Set the formatted text as the content of the DataPackage + dataPackage.SetText(dataBuilder.ToString()); + + // Copy the DataPackage content to the clipboard + Clipboard.SetContent(dataPackage); + } + } + + /// + /// Converts the properties of a FileIdentity row into a labeled, formatted string for copying to clipboard. + /// + /// The selected FileIdentity row from the DataGrid. + /// A formatted string of the row's properties with labels. + private static string ConvertRowToText(FileIdentity row) + { + // Use StringBuilder to format each property with its label for easy reading + return new StringBuilder() + .AppendLine($"File Name: {row.FileName}") + .AppendLine($"Time Created: {row.TimeCreated}") + .AppendLine($"Signature Status: {row.SignatureStatus}") + .AppendLine($"Action: {row.Action}") + .AppendLine($"Original File Name: {row.OriginalFileName}") + .AppendLine($"Internal Name: {row.InternalName}") + .AppendLine($"File Description: {row.FileDescription}") + .AppendLine($"Product Name: {row.ProductName}") + .AppendLine($"File Version: {row.FileVersion}") + .AppendLine($"Package Family Name: {row.PackageFamilyName}") + .AppendLine($"SHA256 Hash: {row.SHA256Hash}") + .AppendLine($"SHA1 Hash: {row.SHA1Hash}") + .AppendLine($"SHA256 Flat Hash: {row.SHA256FlatHash}") + .AppendLine($"SHA1 Flat Hash: {row.SHA1FlatHash}") + .AppendLine($"Signing Scenario: {row.SISigningScenario}") + .AppendLine($"File Path: {row.FilePath}") + .AppendLine($"Computer Name: {row.ComputerName}") + .AppendLine($"Policy GUID: {row.PolicyGUID}") + .AppendLine($"Policy Name: {row.PolicyName}") + .AppendLine($"File Publishers: {row.FilePublishersToDisplay}") + .ToString(); + } + + + + /// + /// Event handler for the Copy Individual Items SubMenu. It will populate the submenu items in the flyout of the data grid. + /// + /// + /// + private void FileIdentitiesDataGrid_Loaded(object sender, RoutedEventArgs e) + { + // Ensure the CopyIndividualItemsSubMenu is available + if (CopyIndividualItemsSubMenu is null) + { + return; + } + + // Clear any existing items to avoid duplication if reloaded + CopyIndividualItemsSubMenu.Items.Clear(); + + // Create a dictionary to map headers to their specific click event methods + Dictionary copyActions = new() + { + { "File Name", CopyFileName_Click }, + { "Time Created", CopyTimeCreated_Click }, + { "Signature Status", CopySignatureStatus_Click }, + { "Action", CopyAction_Click }, + { "Original File Name", CopyOriginalFileName_Click }, + { "Internal Name", CopyInternalName_Click }, + { "File Description", CopyFileDescription_Click }, + { "Product Name", CopyProductName_Click }, + { "File Version", CopyFileVersion_Click }, + { "Package Family Name", CopyPackageFamilyName_Click }, + { "SHA256 Hash", CopySHA256Hash_Click }, + { "SHA1 Hash", CopySHA1Hash_Click }, + { "SHA256 Flat Hash", CopySHA256FlatHash_Click }, + { "SHA1 Flat Hash", CopySHA1FlatHash_Click }, + { "Signing Scenario", CopySigningScenario_Click }, + { "File Path", CopyFilePath_Click }, + { "Computer Name", CopyComputerName_Click }, + { "Policy GUID", CopyPolicyGUID_Click }, + { "Policy Name", CopyPolicyName_Click }, + { "File Publishers", CopyFilePublishersToDisplay_Click } + }; + + // Add menu items with specific click events for each column + foreach (DataGridColumn column in FileIdentitiesDataGrid.Columns) + { + string headerText = column.Header.ToString()!; + + if (copyActions.TryGetValue(headerText, out RoutedEventHandler? value)) + { + // Create a new MenuFlyout Item + MenuFlyoutItem menuItem = new() { Text = $"Copy {headerText}" }; + + // Set the click event for the menu item + menuItem.Click += value; + + // Add the menu item to the submenu + CopyIndividualItemsSubMenu.Items.Add(menuItem); + } + } + } + + // Click event handlers for each property + private void CopyFileName_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.FileName); + private void CopyTimeCreated_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.TimeCreated.ToString()); + private void CopySignatureStatus_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.SignatureStatus.ToString()); + private void CopyAction_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.Action.ToString()); + private void CopyOriginalFileName_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.OriginalFileName); + private void CopyInternalName_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.InternalName); + private void CopyFileDescription_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.FileDescription); + private void CopyProductName_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.ProductName); + private void CopyFileVersion_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.FileVersion?.ToString()); + private void CopyPackageFamilyName_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.PackageFamilyName); + private void CopySHA256Hash_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.SHA256Hash); + private void CopySHA1Hash_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.SHA1Hash); + private void CopySHA256FlatHash_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.SHA256FlatHash); + private void CopySHA1FlatHash_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.SHA1FlatHash); + private void CopySigningScenario_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.SISigningScenario.ToString()); + private void CopyFilePath_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.FilePath); + private void CopyComputerName_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.ComputerName); + private void CopyPolicyGUID_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.PolicyGUID.ToString()); + private void CopyPolicyName_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.PolicyName); + private void CopyFilePublishersToDisplay_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.FilePublishersToDisplay.ToString()); + + + + /// + /// Helper method to copy a specified property to clipboard without reflection + /// + /// Function that retrieves the desired property value as a string + private void CopyToClipboard(Func getProperty) + { + if (FileIdentitiesDataGrid.SelectedItem is FileIdentity selectedItem) + { + string? propertyValue = getProperty(selectedItem); + if (propertyValue is not null) + { + DataPackage dataPackage = new(); + dataPackage.SetText(propertyValue); + Clipboard.SetContent(dataPackage); + } + } + } + + + + /// + /// Updates the total logs count displayed on the UI + /// + internal void UpdateTotalLogs(bool? Zero = null) + { + if (Zero == true) + { + TotalCountOfTheFilesTextBox.Text = $"Total logs: 0"; + } + else + { + TotalCountOfTheFilesTextBox.Text = $"Total logs: {AllowNewAppsStart.Instance.EventLogsFileIdentities.Count}"; + } + } + + + } +} diff --git a/AppControl Manager/Pages/AllowNewApps/AllowNewAppsLocalFilesDataGrid.xaml b/AppControl Manager/Pages/AllowNewApps/AllowNewAppsLocalFilesDataGrid.xaml new file mode 100644 index 000000000..c6fa40fdf --- /dev/null +++ b/AppControl Manager/Pages/AllowNewApps/AllowNewAppsLocalFilesDataGrid.xaml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AppControl Manager/Pages/AllowNewApps/AllowNewAppsLocalFilesDataGrid.xaml.cs b/AppControl Manager/Pages/AllowNewApps/AllowNewAppsLocalFilesDataGrid.xaml.cs new file mode 100644 index 000000000..4bd695615 --- /dev/null +++ b/AppControl Manager/Pages/AllowNewApps/AllowNewAppsLocalFilesDataGrid.xaml.cs @@ -0,0 +1,526 @@ +using CommunityToolkit.WinUI.UI.Controls; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Navigation; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using WDACConfig.IntelGathering; +using Windows.ApplicationModel.DataTransfer; + +// 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 +{ + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + public sealed partial class AllowNewAppsLocalFilesDataGrid : Page + { + + // A static instance of the AllowNewAppsLocalFilesDataGrid class which will hold the single, shared instance of the page + private static AllowNewAppsLocalFilesDataGrid? _instance; + + public AllowNewAppsLocalFilesDataGrid() + { + this.InitializeComponent(); + + // Make sure navigating to/from this page maintains its state + this.NavigationCacheMode = NavigationCacheMode.Enabled; + + // Assign this instance to the static field + _instance = this; + + } + + // Public property to access the singleton instance from other classes + // It's okay it's nullable, null check will happen before accessing it + public static AllowNewAppsLocalFilesDataGrid? Instance => _instance; + + + #region + + // Without the following steps, when the user begins data fetching process and then navigates away from this page + // Upon arrival at this page again, the DataGrid loses its virtualization, causing the UI to hang for extended periods of time + // But after nullifying DataGrid's ItemsSource when page is navigated from and reassigning it when page is navigated to, + // We tackle that problem. Data will sill be stored in the ObservableCollection when page is not in focus, + // But DataGrid's source will pick them up only when page is navigated to. + protected override void OnNavigatedTo(NavigationEventArgs e) + { + base.OnNavigatedTo(e); + FileIdentitiesDataGrid.ItemsSource = AllowNewAppsStart.Instance.LocalFilesFileIdentities; + + // Update the logs when user switches to this page + UpdateTotalLogs(); + } + + protected override void OnNavigatedFrom(NavigationEventArgs e) + { + base.OnNavigatedFrom(e); + FileIdentitiesDataGrid.ItemsSource = null; + } + + #endregion + + + /// + /// Event handler for the SearchBox text change + /// + private void SearchBox_TextChanged(object sender, TextChangedEventArgs e) + { + ApplyFilters(); + } + + + /// + /// Applies the date and search filters to the data grid + /// + private void ApplyFilters() + { + + // Get the search term from the SearchBox, converting it to lowercase for case-insensitive searching + string searchTerm = SearchBox.Text.Trim().ToLowerInvariant(); + + // Start with all items from the complete list, 'AllFileIdentities' + // This list is used as the base set for filtering to preserve original data + IEnumerable filteredResults = AllowNewAppsStart.Instance.LocalFilesAllFileIdentities.AsEnumerable(); + + // Apply the search filter if there is a non-empty search term + if (!string.IsNullOrWhiteSpace(searchTerm)) + { + + // Filter results further to match the search term across multiple properties, case-insensitively + filteredResults = filteredResults.Where(output => + (output.FileName is not null && output.FileName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.SignatureStatus.ToString().Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.Action.ToString().Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.OriginalFileName is not null && output.OriginalFileName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.InternalName is not null && output.InternalName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.FileDescription is not null && output.FileDescription.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.ProductName is not null && output.ProductName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.FileVersion is not null && output.FileVersion.ToString().Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.PackageFamilyName is not null && output.PackageFamilyName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.SHA1PageHash is not null && output.SHA1PageHash.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.FilePath is not null && output.FilePath.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.SHA256FlatHash is not null && output.SHA256FlatHash.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.SHA256Hash is not null && output.SHA256Hash.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + output.FilePublishersToDisplay.Contains(searchTerm) + ); + } + + // Clear the current contents of the ObservableCollection + AllowNewAppsStart.Instance.LocalFilesFileIdentities.Clear(); + + // Populate the ObservableCollection with the filtered results + // This triggers the UI to update the DataGrid based on the filtered data + foreach (FileIdentity result in filteredResults) + { + AllowNewAppsStart.Instance.LocalFilesFileIdentities.Add(result); + } + + // Explicitly set the DataGrid's ItemsSource to ensure the data refreshes + FileIdentitiesDataGrid.ItemsSource = AllowNewAppsStart.Instance.LocalFilesFileIdentities; + + // Update any visual or text element showing the total logs count + UpdateTotalLogs(); + } + + + /// + /// Event handler for the Clear Data button + /// + /// + /// + private void ClearDataButton_Click(object sender, RoutedEventArgs e) + { + AllowNewAppsStart.Instance.LocalFilesFileIdentities.Clear(); + AllowNewAppsStart.Instance.LocalFilesAllFileIdentities.Clear(); + + UpdateTotalLogs(true); + } + + + + + // https://learn.microsoft.com/en-us/windows/communitytoolkit/controls/datagrid_guidance/group_sort_filter + // Column sorting logic for the entire DataGrid + private void FileIdentitiesDataGrid_Sorting(object sender, DataGridColumnEventArgs e) + { + // Sort the column based on its tag and current sort direction + if (string.Equals(e.Column.Tag?.ToString(), "FileName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.FileName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SignatureStatus", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SignatureStatus); + } + else if (string.Equals(e.Column.Tag?.ToString(), "Action", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.Action); + } + else if (string.Equals(e.Column.Tag?.ToString(), "OriginalFileName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.OriginalFileName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "InternalName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.InternalName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "FileDescription", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.FileDescription); + } + else if (string.Equals(e.Column.Tag?.ToString(), "ProductName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.ProductName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "FileVersion", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.FileVersion); + } + else if (string.Equals(e.Column.Tag?.ToString(), "PackageFamilyName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.PackageFamilyName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SHA256Hash", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SHA256Hash); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SHA1Hash", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SHA1Hash); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SHA256FlatHash", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SHA256FlatHash); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SHA1FlatHash", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SHA1FlatHash); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SISigningScenario", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SISigningScenario); + } + else if (string.Equals(e.Column.Tag?.ToString(), "FilePath", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.FilePath); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SHA1PageHash", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SHA1PageHash); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SHA256PageHash", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SHA256PageHash); + } + else if (string.Equals(e.Column.Tag?.ToString(), "HasWHQLSigner", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.HasWHQLSigner); + } + else if (string.Equals(e.Column.Tag?.ToString(), "FilePublishersToDisplay", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.FilePublishersToDisplay); + } + else if (string.Equals(e.Column.Tag?.ToString(), "IsECCSigned", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.IsECCSigned); + } + + // Clear SortDirection for other columns + foreach (DataGridColumn column in FileIdentitiesDataGrid.Columns) + { + if (column != e.Column) + { + column.SortDirection = null; + } + } + } + + + /// + /// Helper method for sorting any column on the DataGird + /// + /// + /// + /// + private void SortColumn(DataGridColumnEventArgs e, Func keySelector) + { + // Check if the search box is empty or not + bool isSearchEmpty = string.IsNullOrWhiteSpace(SearchBox.Text); + + // Get the collection to sort based on the search box status + // Allowing us to sort only the items in the search results + List collectionToSort = isSearchEmpty ? AllowNewAppsStart.Instance.LocalFilesAllFileIdentities : [.. AllowNewAppsStart.Instance.LocalFilesFileIdentities]; + + // Perform the sorting based on the current SortDirection (ascending or descending) + if (e.Column.SortDirection is null || e.Column.SortDirection == DataGridSortDirection.Ascending) + { + // Descending: custom order depending on column type + AllowNewAppsStart.Instance.LocalFilesFileIdentities = [.. collectionToSort.OrderByDescending(keySelector)]; + + // Set the column direction to Descending + e.Column.SortDirection = DataGridSortDirection.Descending; + } + else + { + // Ascending: custom order depending on column type + AllowNewAppsStart.Instance.LocalFilesFileIdentities = [.. collectionToSort.OrderBy(keySelector)]; + e.Column.SortDirection = DataGridSortDirection.Ascending; + } + + // Update the ItemsSource of the DataGrid + // Required for sort + search to work properly, even though binding to the ObservableCollection already happens in XAML + FileIdentitiesDataGrid.ItemsSource = AllowNewAppsStart.Instance.LocalFilesFileIdentities; + } + + + /// + /// Selects all of the displayed rows on the DataGrid + /// + /// + /// + private void SelectAll_Click(object sender, RoutedEventArgs e) + { + _ = DispatcherQueue.TryEnqueue(() => + { + // Clear existing selections + FileIdentitiesDataGrid.SelectedItems.Clear(); + + foreach (FileIdentity fileIdentity in AllowNewAppsStart.Instance.LocalFilesFileIdentities) + { + _ = FileIdentitiesDataGrid.SelectedItems.Add(fileIdentity); // Select each item + } + + }); + } + + + /// + /// De-selects all of the displayed rows on the DataGrid + /// + /// + /// + private void DeSelectAll_Click(object sender, RoutedEventArgs e) + { + FileIdentitiesDataGrid.SelectedItems.Clear(); // Deselect all rows by clearing SelectedItems + } + + + + /// + /// Deletes the selected row from the results + /// + /// + /// + private void DataGridFlyoutMenuDelete_Click(object sender, RoutedEventArgs e) + { + // Collect the selected items to delete + List itemsToDelete = FileIdentitiesDataGrid.SelectedItems.Cast().ToList(); + + // Remove each selected item from the FileIdentities collection + foreach (FileIdentity item in itemsToDelete) + { + _ = AllowNewAppsStart.Instance.LocalFilesFileIdentities.Remove(item); + } + + UpdateTotalLogs(); + } + + + + /// + /// Copies the selected rows to the clipboard in a formatted manner, with each property labeled for clarity. + /// + /// The event sender. + /// The event arguments. + private void DataGridFlyoutMenuCopy_Click(object sender, RoutedEventArgs e) + { + // Check if there are selected items in the DataGrid + if (FileIdentitiesDataGrid.SelectedItems.Count > 0) + { + // Initialize StringBuilder to store all selected rows' data with labels + StringBuilder dataBuilder = new(); + + // Loop through each selected item in the DataGrid + foreach (var selectedItem in FileIdentitiesDataGrid.SelectedItems) + { + if (selectedItem is FileIdentity selectedRow) + { + // Append each row's formatted data to the StringBuilder + _ = dataBuilder.AppendLine(ConvertRowToText(selectedRow)); + + // Add a separator between rows for readability in multi-row copies + _ = dataBuilder.AppendLine(new string('-', 50)); + } + } + + // Create a DataPackage to hold the text data + DataPackage dataPackage = new(); + + // Set the formatted text as the content of the DataPackage + dataPackage.SetText(dataBuilder.ToString()); + + // Copy the DataPackage content to the clipboard + Clipboard.SetContent(dataPackage); + } + } + + /// + /// Converts the properties of a FileIdentity row into a labeled, formatted string for copying to clipboard. + /// + /// The selected FileIdentity row from the DataGrid. + /// A formatted string of the row's properties with labels. + private static string ConvertRowToText(FileIdentity row) + { + // Use StringBuilder to format each property with its label for easy reading + return new StringBuilder() + .AppendLine($"File Name: {row.FileName}") + .AppendLine($"Signature Status: {row.SignatureStatus}") + .AppendLine($"Action: {row.Action}") + .AppendLine($"Original File Name: {row.OriginalFileName}") + .AppendLine($"Internal Name: {row.InternalName}") + .AppendLine($"File Description: {row.FileDescription}") + .AppendLine($"Product Name: {row.ProductName}") + .AppendLine($"File Version: {row.FileVersion}") + .AppendLine($"Package Family Name: {row.PackageFamilyName}") + .AppendLine($"SHA256 Hash: {row.SHA256Hash}") + .AppendLine($"SHA1 Hash: {row.SHA1Hash}") + .AppendLine($"SHA256 Flat Hash: {row.SHA256FlatHash}") + .AppendLine($"SHA1 Flat Hash: {row.SHA1FlatHash}") + .AppendLine($"Signing Scenario: {row.SISigningScenario}") + .AppendLine($"File Path: {row.FilePath}") + .AppendLine($"SHA1 Page Hash: {row.SHA1PageHash}") + .AppendLine($"SHA256 Page Hash: {row.SHA256PageHash}") + .AppendLine($"Has WHQL Signer: {row.HasWHQLSigner}") + .AppendLine($"File Publishers: {row.FilePublishersToDisplay}") + .AppendLine($"Is ECC Signed: {row.IsECCSigned}") + .ToString(); + } + + + + /// + /// Event handler for the Copy Individual Items SubMenu. It will populate the submenu items in the flyout of the data grid. + /// + /// + /// + private void FileIdentitiesDataGrid_Loaded(object sender, RoutedEventArgs e) + { + // Ensure the CopyIndividualItemsSubMenu is available + if (CopyIndividualItemsSubMenu is null) + { + return; + } + + // Clear any existing items to avoid duplication if reloaded + CopyIndividualItemsSubMenu.Items.Clear(); + + // Create a dictionary to map headers to their specific click event methods + Dictionary copyActions = new() + { + { "File Name", CopyFileName_Click }, + { "Signature Status", CopySignatureStatus_Click }, + { "Action", CopyAction_Click }, + { "Original File Name", CopyOriginalFileName_Click }, + { "Internal Name", CopyInternalName_Click }, + { "File Description", CopyFileDescription_Click }, + { "Product Name", CopyProductName_Click }, + { "File Version", CopyFileVersion_Click }, + { "Package Family Name", CopyPackageFamilyName_Click }, + { "SHA256 Hash", CopySHA256Hash_Click }, + { "SHA1 Hash", CopySHA1Hash_Click }, + { "SHA256 Flat Hash", CopySHA256FlatHash_Click }, + { "SHA1 Flat Hash", CopySHA1FlatHash_Click }, + { "Signing Scenario", CopySigningScenario_Click }, + { "File Path", CopyFilePath_Click }, + { "SHA1 Page Hash", CopySHA1PageHash_Click }, + { "SHA256 Page Hash", CopySHA256PageHash_Click }, + { "Has WHQL Signer", CopyHasWHQLSigner_Click }, + { "File Publishers", CopyFilePublishersToDisplay_Click }, + { "Is ECC Signed", CopyIsECCSigned_Click } + }; + + // Add menu items with specific click events for each column + foreach (DataGridColumn column in FileIdentitiesDataGrid.Columns) + { + string headerText = column.Header.ToString()!; + + if (copyActions.TryGetValue(headerText, out RoutedEventHandler? value)) + { + // Create a new MenuFlyout Item + MenuFlyoutItem menuItem = new() { Text = $"Copy {headerText}" }; + + // Set the click event for the menu item + menuItem.Click += value; + + // Add the menu item to the submenu + CopyIndividualItemsSubMenu.Items.Add(menuItem); + } + } + } + + // Click event handlers for each property + private void CopyFileName_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.FileName); + private void CopySignatureStatus_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.SignatureStatus.ToString()); + private void CopyAction_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.Action.ToString()); + private void CopyOriginalFileName_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.OriginalFileName); + private void CopyInternalName_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.InternalName); + private void CopyFileDescription_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.FileDescription); + private void CopyProductName_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.ProductName); + private void CopyFileVersion_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.FileVersion?.ToString()); + private void CopyPackageFamilyName_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.PackageFamilyName); + private void CopySHA256Hash_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.SHA256Hash); + private void CopySHA1Hash_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.SHA1Hash); + private void CopySHA256FlatHash_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.SHA256FlatHash); + private void CopySHA1FlatHash_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.SHA1FlatHash); + private void CopySigningScenario_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.SISigningScenario.ToString()); + private void CopyFilePath_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.FilePath); + private void CopySHA1PageHash_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.SHA1PageHash); + private void CopySHA256PageHash_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.SHA256PageHash); + private void CopyHasWHQLSigner_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.HasWHQLSigner.ToString()); + private void CopyFilePublishersToDisplay_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.FilePublishersToDisplay.ToString()); + private void CopyIsECCSigned_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.IsECCSigned.ToString()); + + + /// + /// Helper method to copy a specified property to clipboard without reflection + /// + /// Function that retrieves the desired property value as a string + private void CopyToClipboard(Func getProperty) + { + if (FileIdentitiesDataGrid.SelectedItem is FileIdentity selectedItem) + { + string? propertyValue = getProperty(selectedItem); + if (propertyValue is not null) + { + DataPackage dataPackage = new(); + dataPackage.SetText(propertyValue); + Clipboard.SetContent(dataPackage); + } + } + } + + + + /// + /// Updates the total logs count displayed on the UI + /// + internal void UpdateTotalLogs(bool? Zero = null) + { + if (Zero == true) + { + TotalCountOfTheFilesTextBox.Text = $"Total logs: 0"; + } + else + { + TotalCountOfTheFilesTextBox.Text = $"Total logs: {AllowNewAppsStart.Instance.LocalFilesFileIdentities.Count}"; + } + } + + + } +} diff --git a/AppControl Manager/Pages/AllowNewApps/AllowNewAppsStart.xaml b/AppControl Manager/Pages/AllowNewApps/AllowNewAppsStart.xaml new file mode 100644 index 000000000..636acdd77 --- /dev/null +++ b/AppControl Manager/Pages/AllowNewApps/AllowNewAppsStart.xaml @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + Use this page to Allow + new or already installed apps or files to run on the system. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Browse for a XML policy file to add the logs to + + + + + + + + + FilePublisher + Publisher + Hash + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AppControl Manager/Pages/EventLogsPolicyCreation.xaml.cs b/AppControl Manager/Pages/EventLogsPolicyCreation.xaml.cs new file mode 100644 index 000000000..c0e4ba3fa --- /dev/null +++ b/AppControl Manager/Pages/EventLogsPolicyCreation.xaml.cs @@ -0,0 +1,976 @@ +using CommunityToolkit.WinUI.UI.Controls; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Navigation; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using WDACConfig.IntelGathering; +using Windows.ApplicationModel.DataTransfer; + + +namespace WDACConfig.Pages +{ + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + public sealed partial class EventLogsPolicyCreation : Page + { + + // To store the FileIdentities displayed on the DataGrid + // Binding happens on the XAML but methods related to search update the ItemSource of the DataGrid from code behind otherwise there will not be an expected result + public ObservableCollection FileIdentities { get; set; } + + // Store all outputs for searching, used as a temporary storage for filtering + // If ObservableCollection were used directly, any filtering or modification could remove items permanently + // from the collection, making it difficult to reset or apply different filters without re-fetching data. + private readonly List AllFileIdentities; + + private string? CodeIntegrityEVTX; // To store the Code Integrity EVTX file path + private string? AppLockerEVTX; // To store the AppLocker EVTX file path + + // The user selected scan level + private ScanLevels scanLevel = ScanLevels.FilePublisher; + + + // Variables to hold the data supplied by the UI elements + private Guid? BasePolicyGUID; + private string? PolicyToAddLogsTo; + private string? BasePolicyXMLFile; + + + public EventLogsPolicyCreation() + { + this.InitializeComponent(); + + // Make sure navigating to/from this page maintains its state + this.NavigationCacheMode = NavigationCacheMode.Enabled; + + // Initialize the lists + FileIdentities = []; + AllFileIdentities = []; + + // Add the DateChanged event handler + FilterByDateCalendarPicker.DateChanged += FilterByDateCalendarPicker_DateChanged; + } + + + #region + + // Without the following steps, when the user begins data fetching process and then navigates away from this page + // Upon arrival at this page again, the DataGrid loses its virtualization, causing the UI to hang for extended periods of time + // But after nullifying DataGrid's ItemsSource when page is navigated from and reassigning it when page is navigated to, + // We tackle that problem. Data will sill be stored in the ObservableCollection when page is not in focus, + // But DataGrid's source will pick them up only when page is navigated to. + protected override void OnNavigatedTo(NavigationEventArgs e) + { + base.OnNavigatedTo(e); + FileIdentitiesDataGrid.ItemsSource = FileIdentities; + } + + protected override void OnNavigatedFrom(NavigationEventArgs e) + { + base.OnNavigatedFrom(e); + FileIdentitiesDataGrid.ItemsSource = null; + } + + #endregion + + + /// + /// Event handler for the CalendarDatePicker date changed event + /// + private void FilterByDateCalendarPicker_DateChanged(CalendarDatePicker sender, CalendarDatePickerDateChangedEventArgs args) + { + ApplyFilters(); + } + + + /// + /// Event handler for the SearchBox text change + /// + private void SearchBox_TextChanged(object sender, TextChangedEventArgs e) + { + ApplyFilters(); + } + + + /// + /// Applies the date and search filters to the data grid + /// + private void ApplyFilters() + { + // Get the selected date from the CalendarDatePicker (if any) + DateTimeOffset? selectedDate = FilterByDateCalendarPicker.Date; + + // Get the search term from the SearchBox, converting it to lowercase for case-insensitive searching + string searchTerm = SearchBox.Text.Trim().ToLowerInvariant(); + + // Start with all items from the complete list, 'AllFileIdentities' + // This list is used as the base set for filtering to preserve original data + var filteredResults = AllFileIdentities.AsEnumerable(); + + // Apply the date filter if a date is selected in the CalendarDatePicker + if (selectedDate.HasValue) + { + // Filter results to include only items where 'TimeCreated' is greater than or equal to the selected date + filteredResults = filteredResults.Where(item => item.TimeCreated >= selectedDate.Value); + } + + // Apply the search filter if there is a non-empty search term + if (!string.IsNullOrWhiteSpace(searchTerm)) + { + + // Filter results further to match the search term across multiple properties, case-insensitively + filteredResults = filteredResults.Where(output => + (output.FileName is not null && output.FileName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.SignatureStatus.ToString().Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.Action.ToString().Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.OriginalFileName is not null && output.OriginalFileName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.InternalName is not null && output.InternalName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.FileDescription is not null && output.FileDescription.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.ProductName is not null && output.ProductName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.FileVersion is not null && output.FileVersion.ToString().Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.PackageFamilyName is not null && output.PackageFamilyName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.PolicyName is not null && output.PolicyName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.ComputerName is not null && output.ComputerName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.FilePath is not null && output.FilePath.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.SHA256FlatHash is not null && output.SHA256FlatHash.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.SHA256Hash is not null && output.SHA256Hash.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + output.FilePublishersToDisplay.Contains(searchTerm) + ); + } + + // Clear the current contents of the ObservableCollection + FileIdentities.Clear(); + + // Populate the ObservableCollection with the filtered results + // This triggers the UI to update the DataGrid based on the filtered data + foreach (FileIdentity result in filteredResults) + { + FileIdentities.Add(result); + } + + // Explicitly set the DataGrid's ItemsSource to ensure the data refreshes + FileIdentitiesDataGrid.ItemsSource = FileIdentities; + + // Update any visual or text element showing the total logs count + UpdateTotalLogs(); + } + + + + + /// + /// Event handler for the ScanLogs click + /// + /// + /// + private async void ScanLogs_Click(object sender, RoutedEventArgs e) + { + + try + { + // Disable the scan button initially + ScanLogs.IsEnabled = false; + + // Display the progress ring on the ScanLogs button + ScanLogsProgressRing.IsActive = true; + ScanLogsProgressRing.Visibility = Visibility.Visible; + + + // Disable the Policy creator button while scan is being performed + CreatePolicyButton.IsEnabled = false; + + // Clear the FileIdentities before getting and showing the new ones + FileIdentities.Clear(); + AllFileIdentities.Clear(); + + UpdateTotalLogs(true); + + // Grab the App Control Logs + HashSet Output = await GetEventLogsData.GetAppControlEvents(CodeIntegrityEvtxFilePath: CodeIntegrityEVTX, AppLockerEvtxFilePath: AppLockerEVTX); + + // Store all of the data in the ObservableCollection and List + foreach (FileIdentity fileIdentity in Output) + { + AllFileIdentities.Add(fileIdentity); + + FileIdentities.Add(fileIdentity); + } + + UpdateTotalLogs(); + } + + finally + { + // Enable the button again + ScanLogs.IsEnabled = true; + + // Clear the selected file paths + CodeIntegrityEVTX = null; + AppLockerEVTX = null; + + // Stop displaying the Progress Ring + ScanLogsProgressRing.IsActive = false; + ScanLogsProgressRing.Visibility = Visibility.Collapsed; + + + // Enable the Policy creator button again + CreatePolicyButton.IsEnabled = true; + } + } + + + + /// + /// Event handler for the select Code Integrity EVTX file path button + /// + /// + /// + private void SelectCodeIntegrityEVTXFiles_Click(object sender, RoutedEventArgs e) + { + string? selectedFile = FileSystemPicker.ShowFilePicker(); + if (!string.IsNullOrEmpty(selectedFile)) + { + // Store the selected XML file path + CodeIntegrityEVTX = selectedFile; + + Logger.Write($"Selected {selectedFile} for Code Integrity EVTX log scanning"); + } + } + + + /// + /// Event handler for the select AppLocker EVTX file path button + /// + /// + /// + private void SelectAppLockerEVTXFiles_Click(object sender, RoutedEventArgs e) + { + string? selectedFile = FileSystemPicker.ShowFilePicker(); + if (!string.IsNullOrEmpty(selectedFile)) + { + // Store the selected XML file path + AppLockerEVTX = selectedFile; + + Logger.Write($"Selected {selectedFile} for AppLocker EVTX log scanning"); + } + } + + + + /// + /// Event handler for the Clear Data button + /// + /// + /// + private void ClearDataButton_Click(object sender, RoutedEventArgs e) + { + FileIdentities.Clear(); + AllFileIdentities.Clear(); + + UpdateTotalLogs(true); + } + + + + + // https://learn.microsoft.com/en-us/windows/communitytoolkit/controls/datagrid_guidance/group_sort_filter + // Column sorting logic for the entire DataGrid + private void FileIdentitiesDataGrid_Sorting(object sender, DataGridColumnEventArgs e) + { + // Sort the column based on its tag and current sort direction + if (string.Equals(e.Column.Tag?.ToString(), "FileName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.FileName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "TimeCreated", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.TimeCreated); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SignatureStatus", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SignatureStatus); + } + else if (string.Equals(e.Column.Tag?.ToString(), "Action", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.Action); + } + else if (string.Equals(e.Column.Tag?.ToString(), "OriginalFileName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.OriginalFileName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "InternalName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.InternalName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "FileDescription", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.FileDescription); + } + else if (string.Equals(e.Column.Tag?.ToString(), "ProductName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.ProductName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "FileVersion", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.FileVersion); + } + else if (string.Equals(e.Column.Tag?.ToString(), "PackageFamilyName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.PackageFamilyName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SHA256Hash", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SHA256Hash); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SHA1Hash", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SHA1Hash); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SHA256FlatHash", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SHA256FlatHash); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SHA1FlatHash", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SHA1FlatHash); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SISigningScenario", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SISigningScenario); + } + else if (string.Equals(e.Column.Tag?.ToString(), "FilePath", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.FilePath); + } + else if (string.Equals(e.Column.Tag?.ToString(), "ComputerName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.ComputerName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "PolicyGUID", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.PolicyGUID); + } + else if (string.Equals(e.Column.Tag?.ToString(), "PolicyName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.PolicyName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "FilePublishersToDisplay", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.FilePublishersToDisplay); + } + + // Clear SortDirection for other columns + foreach (DataGridColumn column in FileIdentitiesDataGrid.Columns) + { + if (column != e.Column) + { + column.SortDirection = null; + } + } + } + + + /// + /// Helper method for sorting any column on the DataGird + /// + /// + /// + /// + private void SortColumn(DataGridColumnEventArgs e, Func keySelector) + { + // Check if the search box is empty or not + bool isSearchEmpty = string.IsNullOrWhiteSpace(SearchBox.Text); + + // Get the collection to sort based on the search box status + // Allowing us to sort only the items in the search results + List collectionToSort = isSearchEmpty ? AllFileIdentities : [.. FileIdentities]; + + // Perform the sorting based on the current SortDirection (ascending or descending) + if (e.Column.SortDirection is null || e.Column.SortDirection == DataGridSortDirection.Ascending) + { + // Descending: custom order depending on column type + FileIdentities = [.. collectionToSort.OrderByDescending(keySelector)]; + + // Set the column direction to Descending + e.Column.SortDirection = DataGridSortDirection.Descending; + } + else + { + // Ascending: custom order depending on column type + FileIdentities = [.. collectionToSort.OrderBy(keySelector)]; + e.Column.SortDirection = DataGridSortDirection.Ascending; + } + + // Update the ItemsSource of the DataGrid + // Required for sort + search to work properly, even though binding to the ObservableCollection already happens in XAML + FileIdentitiesDataGrid.ItemsSource = FileIdentities; + } + + + /// + /// Selects all of the displayed rows on the DataGrid + /// + /// + /// + private void SelectAll_Click(object sender, RoutedEventArgs e) + { + _ = DispatcherQueue.TryEnqueue(() => + { + // Clear existing selections + FileIdentitiesDataGrid.SelectedItems.Clear(); + + foreach (FileIdentity fileIdentity in FileIdentities) + { + _ = FileIdentitiesDataGrid.SelectedItems.Add(fileIdentity); // Select each item + } + + }); + } + + + /// + /// De-selects all of the displayed rows on the DataGrid + /// + /// + /// + private void DeSelectAll_Click(object sender, RoutedEventArgs e) + { + FileIdentitiesDataGrid.SelectedItems.Clear(); // Deselect all rows by clearing SelectedItems + } + + + + /// + /// Deletes the selected row from the results + /// + /// + /// + private void DataGridFlyoutMenuDelete_Click(object sender, RoutedEventArgs e) + { + // Collect the selected items to delete + List itemsToDelete = FileIdentitiesDataGrid.SelectedItems.Cast().ToList(); + + // Remove each selected item from the FileIdentities collection + foreach (FileIdentity item in itemsToDelete) + { + _ = FileIdentities.Remove(item); + } + + UpdateTotalLogs(); + } + + + + /// + /// Copies the selected rows to the clipboard in a formatted manner, with each property labeled for clarity. + /// + /// The event sender. + /// The event arguments. + private void DataGridFlyoutMenuCopy_Click(object sender, RoutedEventArgs e) + { + // Check if there are selected items in the DataGrid + if (FileIdentitiesDataGrid.SelectedItems.Count > 0) + { + // Initialize StringBuilder to store all selected rows' data with labels + StringBuilder dataBuilder = new(); + + // Loop through each selected item in the DataGrid + foreach (var selectedItem in FileIdentitiesDataGrid.SelectedItems) + { + if (selectedItem is FileIdentity selectedRow) + { + // Append each row's formatted data to the StringBuilder + _ = dataBuilder.AppendLine(ConvertRowToText(selectedRow)); + + // Add a separator between rows for readability in multi-row copies + _ = dataBuilder.AppendLine(new string('-', 50)); + } + } + + // Create a DataPackage to hold the text data + DataPackage dataPackage = new(); + + // Set the formatted text as the content of the DataPackage + dataPackage.SetText(dataBuilder.ToString()); + + // Copy the DataPackage content to the clipboard + Clipboard.SetContent(dataPackage); + } + } + + /// + /// Converts the properties of a FileIdentity row into a labeled, formatted string for copying to clipboard. + /// + /// The selected FileIdentity row from the DataGrid. + /// A formatted string of the row's properties with labels. + private static string ConvertRowToText(FileIdentity row) + { + // Use StringBuilder to format each property with its label for easy reading + return new StringBuilder() + .AppendLine($"File Name: {row.FileName}") + .AppendLine($"Time Created: {row.TimeCreated}") + .AppendLine($"Signature Status: {row.SignatureStatus}") + .AppendLine($"Action: {row.Action}") + .AppendLine($"Original File Name: {row.OriginalFileName}") + .AppendLine($"Internal Name: {row.InternalName}") + .AppendLine($"File Description: {row.FileDescription}") + .AppendLine($"Product Name: {row.ProductName}") + .AppendLine($"File Version: {row.FileVersion}") + .AppendLine($"Package Family Name: {row.PackageFamilyName}") + .AppendLine($"SHA256 Hash: {row.SHA256Hash}") + .AppendLine($"SHA1 Hash: {row.SHA1Hash}") + .AppendLine($"SHA256 Flat Hash: {row.SHA256FlatHash}") + .AppendLine($"SHA1 Flat Hash: {row.SHA1FlatHash}") + .AppendLine($"Signing Scenario: {row.SISigningScenario}") + .AppendLine($"File Path: {row.FilePath}") + .AppendLine($"Computer Name: {row.ComputerName}") + .AppendLine($"Policy GUID: {row.PolicyGUID}") + .AppendLine($"Policy Name: {row.PolicyName}") + .AppendLine($"File Publishers: {row.FilePublishersToDisplay}") + .ToString(); + } + + + + /// + /// Event handler for the Copy Individual Items SubMenu. It will populate the submenu items in the flyout of the data grid. + /// + /// + /// + private void FileIdentitiesDataGrid_Loaded(object sender, RoutedEventArgs e) + { + // Ensure the CopyIndividualItemsSubMenu is available + if (CopyIndividualItemsSubMenu is null) + { + return; + } + + // Clear any existing items to avoid duplication if reloaded + CopyIndividualItemsSubMenu.Items.Clear(); + + // Create a dictionary to map headers to their specific click event methods + Dictionary copyActions = new() + { + { "File Name", CopyFileName_Click }, + { "Time Created", CopyTimeCreated_Click }, + { "Signature Status", CopySignatureStatus_Click }, + { "Action", CopyAction_Click }, + { "Original File Name", CopyOriginalFileName_Click }, + { "Internal Name", CopyInternalName_Click }, + { "File Description", CopyFileDescription_Click }, + { "Product Name", CopyProductName_Click }, + { "File Version", CopyFileVersion_Click }, + { "Package Family Name", CopyPackageFamilyName_Click }, + { "SHA256 Hash", CopySHA256Hash_Click }, + { "SHA1 Hash", CopySHA1Hash_Click }, + { "SHA256 Flat Hash", CopySHA256FlatHash_Click }, + { "SHA1 Flat Hash", CopySHA1FlatHash_Click }, + { "Signing Scenario", CopySigningScenario_Click }, + { "File Path", CopyFilePath_Click }, + { "Computer Name", CopyComputerName_Click }, + { "Policy GUID", CopyPolicyGUID_Click }, + { "Policy Name", CopyPolicyName_Click }, + { "File Publishers", CopyFilePublishersToDisplay_Click } + }; + + // Add menu items with specific click events for each column + foreach (DataGridColumn column in FileIdentitiesDataGrid.Columns) + { + string headerText = column.Header.ToString()!; + + if (copyActions.TryGetValue(headerText, out RoutedEventHandler? value)) + { + // Create a new MenuFlyout Item + MenuFlyoutItem menuItem = new() { Text = $"Copy {headerText}" }; + + // Set the click event for the menu item + menuItem.Click += value; + + // Add the menu item to the submenu + CopyIndividualItemsSubMenu.Items.Add(menuItem); + } + } + } + + // Click event handlers for each property + private void CopyFileName_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.FileName); + private void CopyTimeCreated_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.TimeCreated.ToString()); + private void CopySignatureStatus_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.SignatureStatus.ToString()); + private void CopyAction_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.Action.ToString()); + private void CopyOriginalFileName_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.OriginalFileName); + private void CopyInternalName_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.InternalName); + private void CopyFileDescription_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.FileDescription); + private void CopyProductName_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.ProductName); + private void CopyFileVersion_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.FileVersion?.ToString()); + private void CopyPackageFamilyName_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.PackageFamilyName); + private void CopySHA256Hash_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.SHA256Hash); + private void CopySHA1Hash_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.SHA1Hash); + private void CopySHA256FlatHash_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.SHA256FlatHash); + private void CopySHA1FlatHash_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.SHA1FlatHash); + private void CopySigningScenario_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.SISigningScenario.ToString()); + private void CopyFilePath_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.FilePath); + private void CopyComputerName_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.ComputerName); + private void CopyPolicyGUID_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.PolicyGUID.ToString()); + private void CopyPolicyName_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.PolicyName); + private void CopyFilePublishersToDisplay_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.FilePublishersToDisplay.ToString()); + + + /// + /// Helper method to copy a specified property to clipboard without reflection + /// + /// Function that retrieves the desired property value as a string + private void CopyToClipboard(Func getProperty) + { + if (FileIdentitiesDataGrid.SelectedItem is FileIdentity selectedItem) + { + string? propertyValue = getProperty(selectedItem); + if (propertyValue is not null) + { + DataPackage dataPackage = new(); + dataPackage.SetText(propertyValue); + Clipboard.SetContent(dataPackage); + } + } + } + + + + /// + /// Updates the total logs count displayed on the UI + /// + private void UpdateTotalLogs(bool? Zero = null) + { + if (Zero == true) + { + TotalCountOfTheFilesTextBox.Text = $"Total logs: 0"; + } + else + { + TotalCountOfTheFilesTextBox.Text = $"Total logs: {FileIdentities.Count}"; + } + } + + + /// + /// Changes the main button's text that creates the policy, based on the selected method of creation + /// + /// + private void CreatePolicyButtonTextChange(string text) + { + CreatePolicyButton.Content = text; + } + + + /// + /// The button that browses for XML file the logs will be added to + /// + /// + /// + private void AddToPolicyButton_Click(object sender, RoutedEventArgs e) + { + + string? selectedFile = FileSystemPicker.ShowFilePicker(title: "Select a XML file", ("XML Files", "*.xml")); + if (!string.IsNullOrEmpty(selectedFile)) + { + // Store the selected XML file path + PolicyToAddLogsTo = selectedFile; + + Logger.Write($"Selected {PolicyToAddLogsTo} to add the logs to."); + + CreatePolicyButtonTextChange("Add logs to the selected policy"); + } + + } + + + /// + /// The button to browse for the XML file the supplemental policy that will be created will belong to + /// + /// + /// + private void BasePolicyFileButton_Click(object sender, RoutedEventArgs e) + { + + string? selectedFile = FileSystemPicker.ShowFilePicker(title: "Select a XML file", ("XML Files", "*.xml")); + if (!string.IsNullOrEmpty(selectedFile)) + { + // Store the selected XML file path + BasePolicyXMLFile = selectedFile; + + Logger.Write($"Selected {BasePolicyXMLFile} to associate the Supplemental policy with."); + + CreatePolicyButtonTextChange("Create Policy for Selected Base"); + } + + } + + + + /// + /// The button to submit a base policy GUID that will be used to set the base policy ID in the Supplemental policy file that will be created. + /// + /// + /// + /// + private void BaseGUIDSubmitButton_Click(object sender, RoutedEventArgs e) + { + if (Guid.TryParse(BaseGUIDTextBox.Text, out Guid guid)) + { + BasePolicyGUID = guid; + + CreatePolicyButtonTextChange("Create Policy for Base GUID"); + } + else + { + throw new ArgumentException("Invalid GUID"); + } + + } + + + /// + /// When the main button responsible for creating policy is pressed + /// + /// + /// + private async void CreatePolicyButton_Click(SplitButton sender, SplitButtonClickEventArgs args) + { + + try + { + + // Disable the policy creator button + CreatePolicyButton.IsEnabled = false; + + // Disable the scan logs button + ScanLogs.IsEnabled = false; + + // Display the progress ring on the ScanLogs button + ScanLogsProgressRing.IsActive = true; + ScanLogsProgressRing.Visibility = Visibility.Visible; + + + if (FileIdentities.Count == 0) + { + throw new InvalidOperationException("There are no logs. Use the scan button first."); + } + + + if (PolicyToAddLogsTo is null && BasePolicyXMLFile is null && BasePolicyGUID is null) + { + throw new InvalidOperationException("You must select an option from the policy creation list"); + } + + + // Create a policy name if it wasn't provided + DateTime now = DateTime.Now; + string formattedDate = now.ToString("MM-dd-yyyy 'at' HH-mm-ss"); + + + // Get the policy name from the UI text box + string? policyName = PolicyNameTextBox.Text; + + // If the UI text box was empty or whitespace then set policy name manually + if (string.IsNullOrWhiteSpace(policyName)) + { + policyName = $"Supplemental policy from event logs - {formattedDate}"; + } + + + + // All of the File Identities that will be used to put in the policy XML file + List SelectedLogs = []; + + // Check if there are selected items in the DataGrid + if (FileIdentitiesDataGrid.SelectedItems.Count > 0) + { + // convert every selected item to FileIdentity and store it in the list + foreach (FileIdentity item in FileIdentitiesDataGrid.SelectedItems) + { + SelectedLogs.Add(item); + } + } + + // If no item was selected from the DataGrid, use everything in the ObservableCollection + else + { + SelectedLogs = [.. FileIdentities]; + } + + + + // If user selected to deploy the policy + // Need to retrieve it while we're still at the UI thread + bool DeployAtTheEnd = DeployPolicyToggle.IsChecked; + + + await Task.Run(() => + { + + // Create a new Staging Area + DirectoryInfo stagingArea = StagingArea.NewStagingArea("PolicyCreator"); + + // Get the path to an empty policy file + string EmptyPolicyPath = PrepareEmptyPolicy.Prepare(stagingArea.FullName); + + // Separate the signed and unsigned data + FileBasedInfoPackage DataPackage = SignerAndHashBuilder.BuildSignerAndHashObjects(data: SelectedLogs, level: scanLevel); + + // Insert the data into the empty policy file + XMLOps.Initiate(DataPackage, EmptyPolicyPath); + + + + if (PolicyToAddLogsTo is not null) + { + + // Backup any possible Macros so they won't be lost during merge operations + var MacrosBackup = Macros.Backup(PolicyToAddLogsTo); + + // Set policy name and reset the policy ID of our new policy + string supplementalPolicyID = SetCiPolicyInfo.Set(EmptyPolicyPath, true, policyName, null, null); + + // Remove all policy rule options prior to merging the policies since we don't need to add/remove any policy rule options to/from the user input policy + CiRuleOptions.Set(filePath: EmptyPolicyPath, RemoveAll: true); + + // Merge the created policy with the user-selected policy which will result in adding the new rules to it + PolicyMerger.Merge([PolicyToAddLogsTo, EmptyPolicyPath], PolicyToAddLogsTo); + + UpdateHvciOptions.Update(PolicyToAddLogsTo); + + // Restore any possible Macros + Macros.Restore(PolicyToAddLogsTo, MacrosBackup); + + + // If user selected to deploy the policy + if (DeployAtTheEnd) + { + + string CIPPath = Path.Combine(stagingArea.FullName, $"{supplementalPolicyID}.cip"); + + PolicyToCIPConverter.Convert(PolicyToAddLogsTo, CIPPath); + + CiToolHelper.UpdatePolicy(CIPPath); + + } + } + + else if (BasePolicyXMLFile is not null) + { + string OutputPath = Path.Combine(GlobalVars.UserConfigDir, $"{policyName}.xml"); + + // Instantiate the user selected Base policy - To get its BasePolicyID + CodeIntegrityPolicy codeIntegrityPolicy = new(BasePolicyXMLFile, null); + + // Set the BasePolicyID of our new policy to the one from user selected policy + string supplementalPolicyID = SetCiPolicyInfo.Set(EmptyPolicyPath, true, policyName, codeIntegrityPolicy.BasePolicyID, null); + + // Configure policy rule options + CiRuleOptions.Set(filePath: EmptyPolicyPath, template: CiRuleOptions.PolicyTemplate.Supplemental); + + // Set policy version + SetCiPolicyInfo.Set(EmptyPolicyPath, new Version("1.0.0.0")); + + // Copying the policy file to the User Config directory - outside of the temporary staging area + File.Copy(EmptyPolicyPath, OutputPath, true); + + + // If user selected to deploy the policy + if (DeployAtTheEnd) + { + + string CIPPath = Path.Combine(stagingArea.FullName, $"{supplementalPolicyID}.cip"); + + PolicyToCIPConverter.Convert(OutputPath, CIPPath); + + CiToolHelper.UpdatePolicy(CIPPath); + + } + + } + else if (BasePolicyGUID is not null) + { + string OutputPath = Path.Combine(GlobalVars.UserConfigDir, $"{policyName}.xml"); + + + // Set the BasePolicyID of our new policy to the one supplied by user + string supplementalPolicyID = SetCiPolicyInfo.Set(EmptyPolicyPath, true, policyName, BasePolicyGUID.ToString(), null); + + + // Configure policy rule options + CiRuleOptions.Set(filePath: EmptyPolicyPath, template: CiRuleOptions.PolicyTemplate.Supplemental); + + + // Set policy version + SetCiPolicyInfo.Set(EmptyPolicyPath, new Version("1.0.0.0")); + + // Copying the policy file to the User Config directory - outside of the temporary staging area + File.Copy(EmptyPolicyPath, OutputPath, true); + + + // If user selected to deploy the policy + if (DeployAtTheEnd) + { + + string CIPPath = Path.Combine(stagingArea.FullName, $"{supplementalPolicyID}.cip"); + + PolicyToCIPConverter.Convert(OutputPath, CIPPath); + + CiToolHelper.UpdatePolicy(CIPPath); + + } + + } + + + }); + + } + + finally + { + + // Enable the policy creator button again + CreatePolicyButton.IsEnabled = true; + + + // enable the scan logs button again + ScanLogs.IsEnabled = true; + + // Display the progress ring on the ScanLogs button + ScanLogsProgressRing.IsActive = false; + ScanLogsProgressRing.Visibility = Visibility.Collapsed; + + } + + } + + + /// + /// Scan level selection event handler for ComboBox + /// + /// + /// + /// + private void ScanLevelComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (ScanLevelComboBox.SelectedItem is ComboBoxItem selectedItem) + { + string selectedText = selectedItem.Content.ToString()!; + + if (!Enum.TryParse(selectedText, out scanLevel)) + { + throw new InvalidOperationException($"{selectedText} is not a valid Scan Level"); + } + } + } + + } +} diff --git a/AppControl Manager/Pages/GetCIHashes.xaml b/AppControl Manager/Pages/GetCIHashes.xaml index b9328f271..abe15a951 100644 --- a/AppControl Manager/Pages/GetCIHashes.xaml +++ b/AppControl Manager/Pages/GetCIHashes.xaml @@ -6,35 +6,52 @@ 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="using:CommunityToolkit.WinUI.Controls" mc:Ignorable="d"> - - - - - + - + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Browse for a XML policy file to add the logs to + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FilePublisher + Publisher + Hash + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AppControl Manager/Pages/MDEAHPolicyCreation.xaml.cs b/AppControl Manager/Pages/MDEAHPolicyCreation.xaml.cs new file mode 100644 index 000000000..6e33562d7 --- /dev/null +++ b/AppControl Manager/Pages/MDEAHPolicyCreation.xaml.cs @@ -0,0 +1,990 @@ +using CommunityToolkit.WinUI.UI.Controls; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Navigation; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using WDACConfig.IntelGathering; +using Windows.ApplicationModel.DataTransfer; + + +namespace WDACConfig.Pages +{ + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + public sealed partial class MDEAHPolicyCreation : Page + { + // To store the FileIdentities displayed on the DataGrid + // Binding happens on the XAML but methods related to search update the ItemSource of the DataGrid from code behind otherwise there will not be an expected result + public ObservableCollection FileIdentities { get; set; } + + // Store all outputs for searching, used as a temporary storage for filtering + // If ObservableCollection were used directly, any filtering or modification could remove items permanently + // from the collection, making it difficult to reset or apply different filters without re-fetching data. + private readonly List AllFileIdentities; + + private string? MDEAdvancedHuntingLogs; // To store the MDE Advanced Hunting CSV log file path + + // Variables to hold the data supplied by the UI elements + private Guid? BasePolicyGUID; + private string? PolicyToAddLogsTo; + private string? BasePolicyXMLFile; + + // The user selected scan level + private ScanLevels scanLevel = ScanLevels.FilePublisher; + + + + public MDEAHPolicyCreation() + { + this.InitializeComponent(); + + // Make sure navigating to/from this page maintains its state + this.NavigationCacheMode = NavigationCacheMode.Enabled; + + // Initialize the lists + FileIdentities = []; + AllFileIdentities = []; + + // Add the DateChanged event handler + FilterByDateCalendarPicker.DateChanged += FilterByDateCalendarPicker_DateChanged; + } + + + #region + + // Without the following steps, when the user begins data fetching process and then navigates away from this page + // Upon arrival at this page again, the DataGrid loses its virtualization, causing the UI to hang for extended periods of time + // But after nullifying DataGrid's ItemsSource when page is navigated from and reassigning it when page is navigated to, + // We tackle that problem. Data will sill be stored in the ObservableCollection when page is not in focus, + // But DataGrid's source will pick them up only when page is navigated to. + protected override void OnNavigatedTo(NavigationEventArgs e) + { + base.OnNavigatedTo(e); + FileIdentitiesDataGrid.ItemsSource = FileIdentities; + } + + protected override void OnNavigatedFrom(NavigationEventArgs e) + { + base.OnNavigatedFrom(e); + FileIdentitiesDataGrid.ItemsSource = null; + } + + #endregion + + + /// + /// Event handler for the CalendarDatePicker date changed event + /// + private void FilterByDateCalendarPicker_DateChanged(CalendarDatePicker sender, CalendarDatePickerDateChangedEventArgs args) + { + ApplyFilters(); + } + + + /// + /// Event handler for the SearchBox text change + /// + private void SearchBox_TextChanged(object sender, TextChangedEventArgs e) + { + ApplyFilters(); + } + + + /// + /// Applies the date and search filters to the data grid + /// + private void ApplyFilters() + { + // Get the selected date from the CalendarDatePicker (if any) + DateTimeOffset? selectedDate = FilterByDateCalendarPicker.Date; + + // Get the search term from the SearchBox, converting it to lowercase for case-insensitive searching + string searchTerm = SearchBox.Text.Trim().ToLowerInvariant(); + + // Start with all items from the complete list, 'AllFileIdentities' + // This list is used as the base set for filtering to preserve original data + var filteredResults = AllFileIdentities.AsEnumerable(); + + // Apply the date filter if a date is selected in the CalendarDatePicker + if (selectedDate.HasValue) + { + // Filter results to include only items where 'TimeCreated' is greater than or equal to the selected date + filteredResults = filteredResults.Where(item => item.TimeCreated >= selectedDate.Value); + } + + // Apply the search filter if there is a non-empty search term + if (!string.IsNullOrWhiteSpace(searchTerm)) + { + + // Filter results further to match the search term across multiple properties, case-insensitively + filteredResults = filteredResults.Where(output => + (output.FileName is not null && output.FileName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.SignatureStatus.ToString().Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.Action.ToString().Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.OriginalFileName is not null && output.OriginalFileName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.InternalName is not null && output.InternalName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.FileDescription is not null && output.FileDescription.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.FileVersion is not null && output.FileVersion.ToString().Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.PolicyName is not null && output.PolicyName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.ComputerName is not null && output.ComputerName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.FilePath is not null && output.FilePath.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.SHA256FlatHash is not null && output.SHA256FlatHash.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.SHA256Hash is not null && output.SHA256Hash.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + output.FilePublishersToDisplay.Contains(searchTerm) + ); + } + + // Clear the current contents of the ObservableCollection + FileIdentities.Clear(); + + // Populate the ObservableCollection with the filtered results + // This triggers the UI to update the DataGrid based on the filtered data + foreach (FileIdentity result in filteredResults) + { + FileIdentities.Add(result); + } + + // Explicitly set the DataGrid's ItemsSource to ensure the data refreshes + FileIdentitiesDataGrid.ItemsSource = FileIdentities; + + // Update any visual or text element showing the total logs count + UpdateTotalLogs(); + } + + + + + /// + /// Event handler for the ScanLogs click + /// + /// + /// + private async void ScanLogs_Click(object sender, RoutedEventArgs e) + { + + try + { + // Disable the scan button initially + ScanLogs.IsEnabled = false; + + // Display the progress ring on the ScanLogs button + ScanLogsProgressRing.IsActive = true; + ScanLogsProgressRing.Visibility = Visibility.Visible; + + + // Disable the Policy creator button while scan is being performed + CreatePolicyButton.IsEnabled = false; + + // Clear the FileIdentities before getting and showing the new ones + FileIdentities.Clear(); + AllFileIdentities.Clear(); + + UpdateTotalLogs(true); + + // To store the output of the MDE Advanced Hunting logs scan + HashSet Output = []; + + // Grab the App Control Logs + + await Task.Run(() => + { + + if (MDEAdvancedHuntingLogs is null) + { + throw new InvalidOperationException("No MDE Advanced Hunting log was provided"); + } + + List MDEAHCSVData = OptimizeMDECSVData.Optimize(MDEAdvancedHuntingLogs); + + if (MDEAHCSVData.Count > 0) + { + Output = GetMDEAdvancedHuntingLogsData.Retrieve(MDEAHCSVData); + } + else + { + throw new InvalidOperationException("No results detected in the selected MDE Advanced Hunting CSV logs."); + } + + }); + + + // Store all of the data in the ObservableCollection and List + foreach (FileIdentity fileIdentity in Output) + { + AllFileIdentities.Add(fileIdentity); + + FileIdentities.Add(fileIdentity); + } + + UpdateTotalLogs(); + } + + finally + { + // Enable the button again + ScanLogs.IsEnabled = true; + + + // Stop displaying the Progress Ring + ScanLogsProgressRing.IsActive = false; + ScanLogsProgressRing.Visibility = Visibility.Collapsed; + + + // Enable the Policy creator button again + CreatePolicyButton.IsEnabled = true; + } + } + + + + /// + /// Event handler for the select Code Integrity EVTX file path button + /// + /// + /// + private void BrowseForLogs_Click(object sender, RoutedEventArgs e) + { + string? selectedFile = FileSystemPicker.ShowFilePicker(); + if (!string.IsNullOrEmpty(selectedFile)) + { + // Store the selected XML file path + MDEAdvancedHuntingLogs = selectedFile; + + Logger.Write($"Selected {selectedFile} for MDE Advanced Hunting scan"); + + + ScanLogs.IsEnabled = true; + + } + } + + + + + /// + /// Event handler for the Clear Data button + /// + /// + /// + private void ClearDataButton_Click(object sender, RoutedEventArgs e) + { + FileIdentities.Clear(); + AllFileIdentities.Clear(); + + UpdateTotalLogs(true); + } + + + + + + // https://learn.microsoft.com/en-us/windows/communitytoolkit/controls/datagrid_guidance/group_sort_filter + // Column sorting logic for the entire DataGrid + private void FileIdentitiesDataGrid_Sorting(object sender, DataGridColumnEventArgs e) + { + // Sort the column based on its tag and current sort direction + if (string.Equals(e.Column.Tag?.ToString(), "FileName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.FileName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "TimeCreated", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.TimeCreated); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SignatureStatus", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SignatureStatus); + } + else if (string.Equals(e.Column.Tag?.ToString(), "Action", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.Action); + } + else if (string.Equals(e.Column.Tag?.ToString(), "OriginalFileName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.OriginalFileName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "InternalName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.InternalName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "FileDescription", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.FileDescription); + } + else if (string.Equals(e.Column.Tag?.ToString(), "FileVersion", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.FileVersion); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SHA256Hash", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SHA256Hash); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SHA1Hash", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SHA1Hash); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SHA256FlatHash", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SHA256FlatHash); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SHA1FlatHash", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SHA1FlatHash); + } + else if (string.Equals(e.Column.Tag?.ToString(), "SISigningScenario", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.SISigningScenario); + } + else if (string.Equals(e.Column.Tag?.ToString(), "FilePath", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.FilePath); + } + else if (string.Equals(e.Column.Tag?.ToString(), "ComputerName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.ComputerName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "PolicyGUID", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.PolicyGUID); + } + else if (string.Equals(e.Column.Tag?.ToString(), "PolicyName", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.PolicyName); + } + else if (string.Equals(e.Column.Tag?.ToString(), "FilePublishersToDisplay", StringComparison.OrdinalIgnoreCase)) + { + SortColumn(e, output => output.FilePublishersToDisplay); + } + + // Clear SortDirection for other columns + foreach (DataGridColumn column in FileIdentitiesDataGrid.Columns) + { + if (column != e.Column) + { + column.SortDirection = null; + } + } + } + + + /// + /// Helper method for sorting any column on the DataGird + /// + /// + /// + /// + private void SortColumn(DataGridColumnEventArgs e, Func keySelector) + { + // Check if the search box is empty or not + bool isSearchEmpty = string.IsNullOrWhiteSpace(SearchBox.Text); + + // Get the collection to sort based on the search box status + // Allowing us to sort only the items in the search results + List collectionToSort = isSearchEmpty ? AllFileIdentities : [.. FileIdentities]; + + // Perform the sorting based on the current SortDirection (ascending or descending) + if (e.Column.SortDirection is null || e.Column.SortDirection == DataGridSortDirection.Ascending) + { + // Descending: custom order depending on column type + FileIdentities = [.. collectionToSort.OrderByDescending(keySelector)]; + + // Set the column direction to Descending + e.Column.SortDirection = DataGridSortDirection.Descending; + } + else + { + // Ascending: custom order depending on column type + FileIdentities = [.. collectionToSort.OrderBy(keySelector)]; + e.Column.SortDirection = DataGridSortDirection.Ascending; + } + + // Update the ItemsSource of the DataGrid + // Required for sort + search to work properly, even though binding to the ObservableCollection already happens in XAML + FileIdentitiesDataGrid.ItemsSource = FileIdentities; + } + + + /// + /// Selects all of the displayed rows on the DataGrid + /// + /// + /// + private void SelectAll_Click(object sender, RoutedEventArgs e) + { + _ = DispatcherQueue.TryEnqueue(() => + { + // Clear existing selections + FileIdentitiesDataGrid.SelectedItems.Clear(); + + foreach (FileIdentity fileIdentity in FileIdentities) + { + _ = FileIdentitiesDataGrid.SelectedItems.Add(fileIdentity); // Select each item + } + + }); + } + + + /// + /// De-selects all of the displayed rows on the DataGrid + /// + /// + /// + private void DeSelectAll_Click(object sender, RoutedEventArgs e) + { + FileIdentitiesDataGrid.SelectedItems.Clear(); // Deselect all rows by clearing SelectedItems + } + + + + /// + /// Deletes the selected row from the results + /// + /// + /// + private void DataGridFlyoutMenuDelete_Click(object sender, RoutedEventArgs e) + { + // Collect the selected items to delete + List itemsToDelete = FileIdentitiesDataGrid.SelectedItems.Cast().ToList(); + + // Remove each selected item from the FileIdentities ObservableCollection, they won't be included in the policy + foreach (FileIdentity item in itemsToDelete) + { + _ = FileIdentities.Remove(item); + } + + UpdateTotalLogs(); + } + + + + /// + /// Copies the selected rows to the clipboard, formatting each property with its value. + /// + /// The event sender. + /// The event arguments. + private void DataGridFlyoutMenuCopy_Click(object sender, RoutedEventArgs e) + { + // Check if there are selected items in the DataGrid + if (FileIdentitiesDataGrid.SelectedItems.Count > 0) + { + // StringBuilder to store all selected rows' data with labels + StringBuilder dataBuilder = new(); + + // Loop through each selected item + foreach (var selectedItem in FileIdentitiesDataGrid.SelectedItems) + { + if (selectedItem is FileIdentity selectedRow) + { + // Append each row's data with property labels + _ = dataBuilder.AppendLine(ConvertRowToText(selectedRow)); + _ = dataBuilder.AppendLine(new string('-', 50)); // Separator between rows + } + } + + // Create a DataPackage and set the formatted text as the content + DataPackage dataPackage = new(); + dataPackage.SetText(dataBuilder.ToString()); + + // Copy to clipboard + Clipboard.SetContent(dataPackage); + } + } + + /// + /// Converts a row's properties and values into a formatted string for clipboard copy. + /// + /// The selected row from the DataGrid. + /// A formatted string of the row's properties and values. + private static string ConvertRowToText(FileIdentity row) + { + // Format each property in the row with labels for readability + return new StringBuilder() + .AppendLine($"File Name: {row.FileName}") + .AppendLine($"Time Created: {row.TimeCreated}") + .AppendLine($"Signature Status: {row.SignatureStatus}") + .AppendLine($"Action: {row.Action}") + .AppendLine($"Original File Name: {row.OriginalFileName}") + .AppendLine($"Internal Name: {row.InternalName}") + .AppendLine($"File Description: {row.FileDescription}") + .AppendLine($"File Version: {row.FileVersion}") + .AppendLine($"SHA256 Hash: {row.SHA256Hash}") + .AppendLine($"SHA1 Hash: {row.SHA1Hash}") + .AppendLine($"SHA256 Flat Hash: {row.SHA256FlatHash}") + .AppendLine($"SHA1 Flat Hash: {row.SHA1FlatHash}") + .AppendLine($"Signing Scenario: {row.SISigningScenario}") + .AppendLine($"File Path: {row.FilePath}") + .AppendLine($"Computer Name: {row.ComputerName}") + .AppendLine($"Policy GUID: {row.PolicyGUID}") + .AppendLine($"Policy Name: {row.PolicyName}") + .AppendLine($"File Publishers: {row.FilePublishersToDisplay}") + .ToString(); + } + + + + + /// + /// Event handler for the Copy Individual Items SubMenu. Populates items in the flyout of the data grid. + /// + /// + /// + private void FileIdentitiesDataGrid_Loaded(object sender, RoutedEventArgs e) + { + // Find the Copy Individual Items SubMenu + if (CopyIndividualItemsSubMenu is null) + { + return; + } + + // Clear any existing items to avoid duplication if reloaded + CopyIndividualItemsSubMenu.Items.Clear(); + + // Create a dictionary to map headers to their specific click event methods + Dictionary copyActions = new() + { + { "File Name", CopyFileName_Click }, + { "Time Created", CopyTimeCreated_Click }, + { "Signature Status", CopySignatureStatus_Click }, + { "Action", CopyAction_Click }, + { "Original File Name", CopyOriginalFileName_Click }, + { "Internal Name", CopyInternalName_Click }, + { "File Description", CopyFileDescription_Click }, + { "File Version", CopyFileVersion_Click }, + { "SHA256 Hash", CopySHA256Hash_Click }, + { "SHA1 Hash", CopySHA1Hash_Click }, + { "SHA256 Flat Hash", CopySHA256FlatHash_Click }, + { "SHA1 Flat Hash", CopySHA1FlatHash_Click }, + { "Signing Scenario", CopySigningScenario_Click }, + { "File Path", CopyFilePath_Click }, + { "Computer Name", CopyComputerName_Click }, + { "Policy GUID", CopyPolicyGUID_Click }, + { "Policy Name", CopyPolicyName_Click }, + { "File Publishers", CopyFilePublishersToDisplay_Click } + }; + + // Create and add menu items with specific click events for each column + foreach (DataGridColumn column in FileIdentitiesDataGrid.Columns) + { + // Get the header text of the column + string headerText = column.Header.ToString()!; + + if (copyActions.TryGetValue(headerText, out RoutedEventHandler? handler)) + { + // Create a new MenuFlyout Item + MenuFlyoutItem menuItem = new() { Text = $"Copy {headerText}" }; + + // Set the click event for the menu item + menuItem.Click += handler; + + // Add the menu item to the submenu + CopyIndividualItemsSubMenu.Items.Add(menuItem); + } + } + } + + // Click event handlers for each property + private void CopyFileName_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard("FileName"); + private void CopyTimeCreated_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard("TimeCreated"); + private void CopySignatureStatus_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard("SignatureStatus"); + private void CopyAction_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard("Action"); + private void CopyOriginalFileName_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard("OriginalFileName"); + private void CopyInternalName_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard("InternalName"); + private void CopyFileDescription_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard("FileDescription"); + private void CopyFileVersion_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard("FileVersion"); + private void CopySHA256Hash_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard("SHA256Hash"); + private void CopySHA1Hash_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard("SHA1Hash"); + private void CopySHA256FlatHash_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard("SHA256FlatHash"); + private void CopySHA1FlatHash_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard("SHA1FlatHash"); + private void CopySigningScenario_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard("SISigningScenario"); + private void CopyFilePath_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard("FilePath"); + private void CopyComputerName_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard("ComputerName"); + private void CopyPolicyGUID_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard("PolicyGUID"); + private void CopyPolicyName_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard("PolicyName"); + private void CopyFilePublishersToDisplay_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard("FilePublishersToDisplay"); + + + /// + /// Helper method to copy a specified property value to the clipboard + /// + /// + private void CopyPropertyToClipboard(string propertyName) + { + if (FileIdentitiesDataGrid.SelectedItem is not FileIdentity selectedItem) + { + return; + } + + // Directly retrieve the value of the specified property + string? propertyValue = propertyName switch + { + "FileName" => selectedItem.FileName, + "TimeCreated" => selectedItem.TimeCreated.ToString(), + "SignatureStatus" => selectedItem.SignatureStatus.ToString(), + "Action" => selectedItem.Action.ToString(), + "OriginalFileName" => selectedItem.OriginalFileName, + "InternalName" => selectedItem.InternalName, + "FileDescription" => selectedItem.FileDescription, + "FileVersion" => selectedItem.FileVersion?.ToString(), + "SHA256Hash" => selectedItem.SHA256Hash, + "SHA1Hash" => selectedItem.SHA1Hash, + "SHA256FlatHash" => selectedItem.SHA256FlatHash, + "SHA1FlatHash" => selectedItem.SHA1FlatHash, + "SISigningScenario" => selectedItem.SISigningScenario.ToString(), + "FilePath" => selectedItem.FilePath, + "ComputerName" => selectedItem.ComputerName, + "PolicyGUID" => selectedItem.PolicyGUID.ToString(), + "PolicyName" => selectedItem.PolicyName, + "FilePublishersToDisplay" => selectedItem.FilePublishersToDisplay.ToString(), + _ => null + }; + + if (!string.IsNullOrEmpty(propertyValue)) + { + DataPackage dataPackage = new(); + dataPackage.SetText(propertyValue); + Clipboard.SetContent(dataPackage); + } + } + + + + /// + /// Updates the total logs count displayed on the UI + /// + private void UpdateTotalLogs(bool? Zero = null) + { + if (Zero == true) + { + TotalCountOfTheFilesTextBox.Text = $"Total logs: 0"; + } + else + { + TotalCountOfTheFilesTextBox.Text = $"Total logs: {FileIdentities.Count}"; + } + } + + + /// + /// Changes the main button's text that creates the policy, based on the selected method of creation + /// + /// + private void CreatePolicyButtonTextChange(string text) + { + CreatePolicyButton.Content = text; + } + + + /// + /// The button that browses for XML file the logs will be added to + /// + /// + /// + private void AddToPolicyButton_Click(object sender, RoutedEventArgs e) + { + + string? selectedFile = FileSystemPicker.ShowFilePicker(title: "Select a XML file", ("XML Files", "*.xml")); + if (!string.IsNullOrEmpty(selectedFile)) + { + // Store the selected XML file path + PolicyToAddLogsTo = selectedFile; + + Logger.Write($"Selected {PolicyToAddLogsTo} to add the logs to."); + + CreatePolicyButtonTextChange("Add logs to the selected policy"); + } + + } + + + /// + /// The button to browse for the XML file the supplemental policy that will be created will belong to + /// + /// + /// + private void BasePolicyFileButton_Click(object sender, RoutedEventArgs e) + { + + string? selectedFile = FileSystemPicker.ShowFilePicker(title: "Select a XML file", ("XML Files", "*.xml")); + if (!string.IsNullOrEmpty(selectedFile)) + { + // Store the selected XML file path + BasePolicyXMLFile = selectedFile; + + Logger.Write($"Selected {BasePolicyXMLFile} to associate the Supplemental policy with."); + + CreatePolicyButtonTextChange("Create Policy for Selected Base"); + } + + } + + + + /// + /// The button to submit a base policy GUID that will be used to set the base policy ID in the Supplemental policy file that will be created. + /// + /// + /// + /// + private void BaseGUIDSubmitButton_Click(object sender, RoutedEventArgs e) + { + if (Guid.TryParse(BaseGUIDTextBox.Text, out Guid guid)) + { + BasePolicyGUID = guid; + + CreatePolicyButtonTextChange("Create Policy for Base GUID"); + } + else + { + throw new ArgumentException("Invalid GUID"); + } + + } + + + /// + /// When the main button responsible for creating policy is pressed + /// + /// + /// + private async void CreatePolicyButton_Click(SplitButton sender, SplitButtonClickEventArgs args) + { + + try + { + + // Disable the policy creator button + CreatePolicyButton.IsEnabled = false; + + // Disable the scan logs button + ScanLogs.IsEnabled = false; + + // Display the progress ring on the ScanLogs button + ScanLogsProgressRing.IsActive = true; + ScanLogsProgressRing.Visibility = Visibility.Visible; + + + if (FileIdentities.Count == 0) + { + throw new InvalidOperationException("There are no logs. Use the scan button first."); + } + + + if (PolicyToAddLogsTo is null && BasePolicyXMLFile is null && BasePolicyGUID is null) + { + throw new InvalidOperationException("You must select an option from the policy creation list"); + } + + + // Create a policy name if it wasn't provided + DateTime now = DateTime.Now; + string formattedDate = now.ToString("MM-dd-yyyy 'at' HH-mm-ss"); + + + // Get the policy name from the UI text box + string? policyName = PolicyNameTextBox.Text; + + // If the UI text box was empty or whitespace then set policy name manually + if (string.IsNullOrWhiteSpace(policyName)) + { + policyName = $"Supplemental policy from MDE Advanced Hunting logs - {formattedDate}"; + } + + + + // All of the File Identities that will be used to put in the policy XML file + List SelectedLogs = []; + + // Check if there are selected items in the DataGrid + if (FileIdentitiesDataGrid.SelectedItems.Count > 0) + { + // convert every selected item to FileIdentity and store it in the list + foreach (FileIdentity item in FileIdentitiesDataGrid.SelectedItems) + { + SelectedLogs.Add(item); + } + } + + // If no item was selected from the DataGrid, use everything in the ObservableCollection + else + { + SelectedLogs = [.. FileIdentities]; + } + + + + // If user selected to deploy the policy + // Need to retrieve it while we're still at the UI thread + bool DeployAtTheEnd = DeployPolicyToggle.IsChecked; + + + await Task.Run(() => + { + + // Create a new Staging Area + DirectoryInfo stagingArea = StagingArea.NewStagingArea("PolicyCreatorMDEAH"); + + // Get the path to an empty policy file + string EmptyPolicyPath = PrepareEmptyPolicy.Prepare(stagingArea.FullName); + + // Separate the signed and unsigned data + FileBasedInfoPackage DataPackage = SignerAndHashBuilder.BuildSignerAndHashObjects(data: SelectedLogs, level: scanLevel); + + // Insert the data into the empty policy file + XMLOps.Initiate(DataPackage, EmptyPolicyPath); + + + + if (PolicyToAddLogsTo is not null) + { + + // Backup any possible Macros so they won't be lost during merge operations + var MacrosBackup = Macros.Backup(PolicyToAddLogsTo); + + // Set policy name and reset the policy ID of our new policy + string supplementalPolicyID = SetCiPolicyInfo.Set(EmptyPolicyPath, true, policyName, null, null); + + // Remove all policy rule options prior to merging the policies since we don't need to add/remove any policy rule options to/from the user input policy + CiRuleOptions.Set(filePath: EmptyPolicyPath, RemoveAll: true); + + // Merge the created policy with the user-selected policy which will result in adding the new rules to it + PolicyMerger.Merge([PolicyToAddLogsTo, EmptyPolicyPath], PolicyToAddLogsTo); + + UpdateHvciOptions.Update(PolicyToAddLogsTo); + + // Restore any possible Macros + Macros.Restore(PolicyToAddLogsTo, MacrosBackup); + + + // If user selected to deploy the policy + if (DeployAtTheEnd) + { + + string CIPPath = Path.Combine(stagingArea.FullName, $"{supplementalPolicyID}.cip"); + + PolicyToCIPConverter.Convert(PolicyToAddLogsTo, CIPPath); + + CiToolHelper.UpdatePolicy(CIPPath); + + } + } + + else if (BasePolicyXMLFile is not null) + { + string OutputPath = Path.Combine(GlobalVars.UserConfigDir, $"{policyName}.xml"); + + // Instantiate the user selected Base policy - To get its BasePolicyID + CodeIntegrityPolicy codeIntegrityPolicy = new(BasePolicyXMLFile, null); + + // Set the BasePolicyID of our new policy to the one from user selected policy + string supplementalPolicyID = SetCiPolicyInfo.Set(EmptyPolicyPath, true, policyName, codeIntegrityPolicy.BasePolicyID, null); + + // Configure policy rule options + CiRuleOptions.Set(filePath: EmptyPolicyPath, template: CiRuleOptions.PolicyTemplate.Supplemental); + + // Set policy version + SetCiPolicyInfo.Set(EmptyPolicyPath, new Version("1.0.0.0")); + + // Copying the policy file to the User Config directory - outside of the temporary staging area + File.Copy(EmptyPolicyPath, OutputPath, true); + + + // If user selected to deploy the policy + if (DeployAtTheEnd) + { + + string CIPPath = Path.Combine(stagingArea.FullName, $"{supplementalPolicyID}.cip"); + + PolicyToCIPConverter.Convert(OutputPath, CIPPath); + + CiToolHelper.UpdatePolicy(CIPPath); + + } + + } + else if (BasePolicyGUID is not null) + { + string OutputPath = Path.Combine(GlobalVars.UserConfigDir, $"{policyName}.xml"); + + + // Set the BasePolicyID of our new policy to the one supplied by user + string supplementalPolicyID = SetCiPolicyInfo.Set(EmptyPolicyPath, true, policyName, BasePolicyGUID.ToString(), null); + + + // Configure policy rule options + CiRuleOptions.Set(filePath: EmptyPolicyPath, template: CiRuleOptions.PolicyTemplate.Supplemental); + + + // Set policy version + SetCiPolicyInfo.Set(EmptyPolicyPath, new Version("1.0.0.0")); + + // Copying the policy file to the User Config directory - outside of the temporary staging area + File.Copy(EmptyPolicyPath, OutputPath, true); + + + // If user selected to deploy the policy + if (DeployAtTheEnd) + { + + string CIPPath = Path.Combine(stagingArea.FullName, $"{supplementalPolicyID}.cip"); + + PolicyToCIPConverter.Convert(OutputPath, CIPPath); + + CiToolHelper.UpdatePolicy(CIPPath); + + } + + } + + + }); + + } + + finally + { + + // Enable the policy creator button again + CreatePolicyButton.IsEnabled = true; + + + // enable the scan logs button again + ScanLogs.IsEnabled = true; + + // Display the progress ring on the ScanLogs button + ScanLogsProgressRing.IsActive = false; + ScanLogsProgressRing.Visibility = Visibility.Collapsed; + + } + + } + + + /// + /// Scan level selection event handler for ComboBox + /// + /// + /// + /// + private void ScanLevelComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (ScanLevelComboBox.SelectedItem is ComboBoxItem selectedItem) + { + string selectedText = selectedItem.Content.ToString()!; + + if (!Enum.TryParse(selectedText, out scanLevel)) + { + throw new InvalidOperationException($"{selectedText} is not a valid Scan Level"); + } + } + } + + } +} diff --git a/AppControl Manager/Pages/MicrosoftDocumentation.xaml b/AppControl Manager/Pages/MicrosoftDocumentation.xaml index 121d7642c..05a1c091e 100644 --- a/AppControl Manager/Pages/MicrosoftDocumentation.xaml +++ b/AppControl Manager/Pages/MicrosoftDocumentation.xaml @@ -15,7 +15,7 @@ - + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + @@ -161,7 +197,30 @@ ScrollViewer.VerticalScrollMode="Auto" ScrollViewer.HorizontalScrollMode="Auto" Sorting="SimulationDataGrid_Sorting" - CanUserSortColumns="True"> + CanUserSortColumns="True" + Loaded="SimulationDataGrid_Loaded"> + + + + + + + + + + + + + + + + + + + + + + diff --git a/AppControl Manager/Pages/Simulation.xaml.cs b/AppControl Manager/Pages/Simulation.xaml.cs index 657ee3e7c..afa392b45 100644 --- a/AppControl Manager/Pages/Simulation.xaml.cs +++ b/AppControl Manager/Pages/Simulation.xaml.cs @@ -3,51 +3,82 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Navigation; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; +using System.Text; using System.Threading.Tasks; +using Windows.ApplicationModel.DataTransfer; namespace WDACConfig.Pages { public sealed partial class Simulation : Page { public ObservableCollection SimulationOutputs { get; set; } - private List AllSimulationOutputs; // Store all outputs for searching + private readonly List AllSimulationOutputs; // Store all outputs for searching private List filePaths; // For selected file paths - private List folderPaths; // For selected folder paths + private readonly List folderPaths; // For selected folder paths private string? xmlFilePath; // For selected XML file path private List catRootPaths; // For selected Cat Root paths public Simulation() { this.InitializeComponent(); - this.NavigationCacheMode = Microsoft.UI.Xaml.Navigation.NavigationCacheMode.Enabled; + this.NavigationCacheMode = NavigationCacheMode.Enabled; SimulationOutputs = []; AllSimulationOutputs = []; filePaths = []; folderPaths = []; catRootPaths = []; + + } + + + + #region + + // Without the following steps, when the user begins data fetching process and then navigates away from this page + // Upon arrival at this page again, the DataGrid loses its virtualization, causing the UI to hang for extended periods of time + // But after nullifying DataGrid's ItemsSource when page is navigated from and reassigning it when page is navigated to, + // We tackle that problem. Data will sill be stored in the ObservableCollection when page is not in focus, + // But DataGrid's source will pick them up only when page is navigated to. + protected override void OnNavigatedTo(NavigationEventArgs e) + { + base.OnNavigatedTo(e); + SimulationDataGrid.ItemsSource = SimulationOutputs; } + protected override void OnNavigatedFrom(NavigationEventArgs e) + { + base.OnNavigatedFrom(e); + SimulationDataGrid.ItemsSource = null; + } + + #endregion + + + + // Event handler for the Begin Simulation button private async void BeginSimulationButton_Click(object sender, RoutedEventArgs e) { try { // Collect values from UI elements - bool noCatRootScanning = (NoCatRootScanningToggle.IsChecked == true); + bool noCatRootScanning = (NoCatRootScanningToggle.IsChecked); double radialGaugeValue = ScalabilityRadialGauge.Value; // Value from radial gauge - bool CSVOutput = (CSVOutputToggle.IsChecked == true); + bool CSVOutput = (CSVOutputToggle.IsChecked); BeginSimulationButton.IsEnabled = false; ScalabilityRadialGauge.IsEnabled = false; // Run the simulation - var result = await Task.Run(() => + ConcurrentDictionary result = await Task.Run(() => { return InvokeWDACSimulation.Invoke( filePaths, @@ -69,11 +100,11 @@ private async void BeginSimulationButton_Click(object sender, RoutedEventArgs e) TotalCountOfTheFilesTextBox.Text = result.Count.ToString(CultureInfo.InvariantCulture); // Update the ObservableCollection on the UI thread - foreach (var entry in result) + foreach (KeyValuePair entry in result) { - var simOutput = entry.Value; + SimulationOutput simOutput = entry.Value; - var simulationOutput = new SimulationOutput( + SimulationOutput simulationOutput = new( simOutput.Path, simOutput.Source, simOutput.IsAuthorized, @@ -112,7 +143,10 @@ private async void BeginSimulationButton_Click(object sender, RoutedEventArgs e) // Event handler for the Select XML File button private void SelectXmlFileButton_Click(object sender, RoutedEventArgs e) { - string? selectedFile = FileSystemPicker.ShowFilePicker(); + string? selectedFile = FileSystemPicker.ShowFilePicker( + "Select an XML file", + ("XML Files", "*.xml")); + if (!string.IsNullOrEmpty(selectedFile)) { // Store the selected XML file path @@ -127,7 +161,7 @@ private void SelectXmlFileButton_Click(object sender, RoutedEventArgs e) private void SelectFilesButton_Click(object sender, RoutedEventArgs e) { List? selectedFiles = FileSystemPicker.ShowMultiFilePicker(); - if (selectedFiles != null && selectedFiles.Count != 0) + if (selectedFiles is not null && selectedFiles.Count != 0) { filePaths = [.. selectedFiles]; } @@ -147,7 +181,7 @@ private void SelectFoldersButton_Click(object sender, RoutedEventArgs e) private void CatRootPathsButton_Click(object sender, RoutedEventArgs e) { List? selectedCatRoots = FileSystemPicker.ShowMultiFilePicker(); - if (selectedCatRoots != null && selectedCatRoots.Count != 0) + if (selectedCatRoots is not null && selectedCatRoots.Count != 0) { catRootPaths = [.. selectedCatRoots]; } @@ -170,6 +204,9 @@ private void ClearDataButton_Click(object sender, RoutedEventArgs e) SimulationOutputs.Clear(); // Clear the full data AllSimulationOutputs.Clear(); + + // set the total count to 0 after clearing all the data + TotalCountOfTheFilesTextBox.Text = "0"; } // Event handler for the SearchBox text change @@ -179,19 +216,19 @@ private void SearchBox_TextChanged(object sender, TextChangedEventArgs e) // Perform a case-insensitive search in all relevant fields List filteredResults = AllSimulationOutputs.Where(output => - (output.Path != null && output.Path.Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase)) || - (output.Source != null && output.Source.Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase)) || - (output.MatchCriteria != null && output.MatchCriteria.Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase)) || - (output.SpecificFileNameLevelMatchCriteria != null && output.SpecificFileNameLevelMatchCriteria.Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase)) || - (output.CertSubjectCN != null && output.CertSubjectCN.Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase)) || - (output.SignerName != null && output.SignerName.Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase)) || - (output.FilePath != null && output.FilePath.Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase)) + (output.Path is not null && output.Path.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.Source is not null && output.Source.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.MatchCriteria is not null && output.MatchCriteria.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.SpecificFileNameLevelMatchCriteria is not null && output.SpecificFileNameLevelMatchCriteria.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.CertSubjectCN is not null && output.CertSubjectCN.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.SignerName is not null && output.SignerName.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) || + (output.FilePath is not null && output.FilePath.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) ).ToList(); // Update the ObservableCollection on the UI thread with the filtered results SimulationOutputs.Clear(); - foreach (var result in filteredResults) + foreach (SimulationOutput result in filteredResults) { SimulationOutputs.Add(result); } @@ -211,9 +248,7 @@ private void SimulationDataGrid_Sorting(object sender, DataGridColumnEventArgs e if (e.Column.SortDirection is null || e.Column.SortDirection is DataGridSortDirection.Ascending) { // Descending: First True, then False - SimulationOutputs = new ObservableCollection( - AllSimulationOutputs.OrderBy(output => !output.IsAuthorized) - ); + SimulationOutputs = [.. AllSimulationOutputs.OrderBy(output => !output.IsAuthorized)]; // Set the column direction to Descending e.Column.SortDirection = DataGridSortDirection.Descending; @@ -221,9 +256,7 @@ private void SimulationDataGrid_Sorting(object sender, DataGridColumnEventArgs e else { // Ascending: First False, then True - SimulationOutputs = new ObservableCollection( - AllSimulationOutputs.OrderBy(output => output.IsAuthorized) - ); + SimulationOutputs = [.. AllSimulationOutputs.OrderBy(output => output.IsAuthorized)]; e.Column.SortDirection = DataGridSortDirection.Ascending; } @@ -231,7 +264,7 @@ private void SimulationDataGrid_Sorting(object sender, DataGridColumnEventArgs e SimulationDataGrid.ItemsSource = SimulationOutputs; // Clear SortDirection for other columns - foreach (var column in SimulationDataGrid.Columns) + foreach (DataGridColumn column in SimulationDataGrid.Columns) { if (column != e.Column) { @@ -241,5 +274,149 @@ private void SimulationDataGrid_Sorting(object sender, DataGridColumnEventArgs e } } + + + + + /// + /// Populates the "Copy Individual Items" submenu in the flyout when the DataGrid is loaded. + /// + private void SimulationDataGrid_Loaded(object sender, RoutedEventArgs e) + { + if (CopyIndividualItemsSubMenu is null) + { + return; + } + + // Clear any existing items to avoid duplicates if reloaded + CopyIndividualItemsSubMenu.Items.Clear(); + + // Define headers and their associated click events for individual copy actions + Dictionary copyActions = new() + { + { "Path", CopyPath_Click }, + { "Source", CopySource_Click }, + { "Is Authorized", CopyIsAuthorized_Click }, + { "Match Criteria", CopyMatchCriteria_Click }, + { "Specific File Name Criteria", CopySpecificFileNameCriteria_Click }, + { "Signer ID", CopySignerID_Click }, + { "Signer Name", CopySignerName_Click }, + { "Signer Cert Root", CopySignerCertRoot_Click }, + { "Signer Cert Publisher", CopySignerCertPublisher_Click }, + { "Signer Scope", CopySignerScope_Click }, + { "Cert Subject CN", CopyCertSubjectCN_Click }, + { "Cert Issuer CN", CopyCertIssuerCN_Click }, + { "Cert Not After", CopyCertNotAfter_Click }, + { "Cert TBS Value", CopyCertTBSValue_Click }, + { "File Path", CopyFilePath_Click } + }; + + // Add each column header as an individual copy option in the flyout submenu + foreach (KeyValuePair action in copyActions) + { + // Create a new menu item for each column header + MenuFlyoutItem menuItem = new() { Text = $"Copy {action.Key}" }; + + // Set the click event for the menu item + menuItem.Click += action.Value; + + // Add the menu item to the submenu + CopyIndividualItemsSubMenu.Items.Add(menuItem); + } + } + + // Event handlers for each column copy action + private void CopyPath_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard(nameof(SimulationOutput.Path)); + private void CopySource_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard(nameof(SimulationOutput.Source)); + private void CopyIsAuthorized_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard(nameof(SimulationOutput.IsAuthorized)); + private void CopyMatchCriteria_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard(nameof(SimulationOutput.MatchCriteria)); + private void CopySpecificFileNameCriteria_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard(nameof(SimulationOutput.SpecificFileNameLevelMatchCriteria)); + private void CopySignerID_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard(nameof(SimulationOutput.SignerID)); + private void CopySignerName_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard(nameof(SimulationOutput.SignerName)); + private void CopySignerCertRoot_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard(nameof(SimulationOutput.SignerCertRoot)); + private void CopySignerCertPublisher_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard(nameof(SimulationOutput.SignerCertPublisher)); + private void CopySignerScope_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard(nameof(SimulationOutput.SignerScope)); + private void CopyCertSubjectCN_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard(nameof(SimulationOutput.CertSubjectCN)); + private void CopyCertIssuerCN_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard(nameof(SimulationOutput.CertIssuerCN)); + private void CopyCertNotAfter_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard(nameof(SimulationOutput.CertNotAfter)); + private void CopyCertTBSValue_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard(nameof(SimulationOutput.CertTBSValue)); + private void CopyFilePath_Click(object sender, RoutedEventArgs e) => CopyPropertyToClipboard(nameof(SimulationOutput.FilePath)); + + /// + /// Copies the specified property of the selected row to the clipboard. + /// + /// Name of the property to copy + private void CopyPropertyToClipboard(string propertyName) + { + // Get the currently selected item in the DataGrid + if (SimulationDataGrid.SelectedItem is not SimulationOutput selectedItem) + { + return; + } + + // Retrieve the property value directly based on the property name + string? propertyValue = propertyName switch + { + nameof(SimulationOutput.Path) => selectedItem.Path, + nameof(SimulationOutput.Source) => selectedItem.Source, + nameof(SimulationOutput.IsAuthorized) => selectedItem.IsAuthorized.ToString(), + nameof(SimulationOutput.MatchCriteria) => selectedItem.MatchCriteria, + nameof(SimulationOutput.SpecificFileNameLevelMatchCriteria) => selectedItem.SpecificFileNameLevelMatchCriteria, + nameof(SimulationOutput.SignerID) => selectedItem.SignerID, + nameof(SimulationOutput.SignerName) => selectedItem.SignerName, + nameof(SimulationOutput.SignerCertRoot) => selectedItem.SignerCertRoot, + nameof(SimulationOutput.SignerCertPublisher) => selectedItem.SignerCertPublisher, + nameof(SimulationOutput.SignerScope) => selectedItem.SignerScope, + nameof(SimulationOutput.CertSubjectCN) => selectedItem.CertSubjectCN, + nameof(SimulationOutput.CertIssuerCN) => selectedItem.CertIssuerCN, + nameof(SimulationOutput.CertNotAfter) => selectedItem.CertNotAfter, + nameof(SimulationOutput.CertTBSValue) => selectedItem.CertTBSValue, + nameof(SimulationOutput.FilePath) => selectedItem.FilePath, + _ => null + }; + + if (!string.IsNullOrEmpty(propertyValue)) + { + DataPackage dataPackage = new(); + dataPackage.SetText(propertyValue); + Clipboard.SetContent(dataPackage); + } + } + + + /// + /// Copies all column values of the selected row to the clipboard. + /// + private void SimulationDataGrid_CopyRow_Click(object sender, RoutedEventArgs e) + { + if (SimulationDataGrid.SelectedItem is not SimulationOutput selectedItem) + { + return; + } + + string rowData = new StringBuilder() + .AppendLine($"Path: {selectedItem.Path}") + .AppendLine($"Source: {selectedItem.Source}") + .AppendLine($"Is Authorized: {selectedItem.IsAuthorized}") + .AppendLine($"Match Criteria: {selectedItem.MatchCriteria}") + .AppendLine($"Specific File Name Criteria: {selectedItem.SpecificFileNameLevelMatchCriteria}") + .AppendLine($"Signer ID: {selectedItem.SignerID}") + .AppendLine($"Signer Name: {selectedItem.SignerName}") + .AppendLine($"Signer Cert Root: {selectedItem.SignerCertRoot}") + .AppendLine($"Signer Cert Publisher: {selectedItem.SignerCertPublisher}") + .AppendLine($"Signer Scope: {selectedItem.SignerScope}") + .AppendLine($"Cert Subject CN: {selectedItem.CertSubjectCN}") + .AppendLine($"Cert Issuer CN: {selectedItem.CertIssuerCN}") + .AppendLine($"Cert Not After: {selectedItem.CertNotAfter}") + .AppendLine($"Cert TBS Value: {selectedItem.CertTBSValue}") + .AppendLine($"File Path: {selectedItem.FilePath}") + .ToString(); + + DataPackage dataPackage = new(); + dataPackage.SetText(rowData); + Clipboard.SetContent(dataPackage); + } + + } } diff --git a/AppControl Manager/Pages/SystemInformation/CodeIntegrityInfo.xaml b/AppControl Manager/Pages/SystemInformation/CodeIntegrityInfo.xaml index d895f5a21..b56fc5720 100644 --- a/AppControl Manager/Pages/SystemInformation/CodeIntegrityInfo.xaml +++ b/AppControl Manager/Pages/SystemInformation/CodeIntegrityInfo.xaml @@ -9,15 +9,12 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> - + - - - - @@ -53,7 +64,7 @@ - + + CanUserSortColumns="True" + Loaded="DeployedPoliciesDataGrid_Loaded"> + + + + + + + + + + + + + + + + + + diff --git a/AppControl Manager/Pages/SystemInformation/ViewCurrentPolicies.xaml.cs b/AppControl Manager/Pages/SystemInformation/ViewCurrentPolicies.xaml.cs index 793ecfee6..af9c83a2a 100644 --- a/AppControl Manager/Pages/SystemInformation/ViewCurrentPolicies.xaml.cs +++ b/AppControl Manager/Pages/SystemInformation/ViewCurrentPolicies.xaml.cs @@ -5,7 +5,9 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Text; using System.Threading.Tasks; +using Windows.ApplicationModel.DataTransfer; namespace WDACConfig.Pages { @@ -15,7 +17,7 @@ public sealed partial class ViewCurrentPolicies : Page public ObservableCollection AllPolicies { get; set; } // Store all outputs for searching - private List AllPoliciesOutput; + private readonly List AllPoliciesOutput; // Keep track of the currently selected policy private CiPolicyInfo? selectedPolicy; @@ -281,9 +283,7 @@ private void SortColumn(DataGridColumnEventArgs e, Func keyS if (e.Column.SortDirection is null || e.Column.SortDirection == DataGridSortDirection.Ascending) { // Descending: custom order depending on column type - AllPolicies = new ObservableCollection( - collectionToSort.OrderByDescending(keySelector) - ); + AllPolicies = [.. collectionToSort.OrderByDescending(keySelector)]; // Set the column direction to Descending e.Column.SortDirection = DataGridSortDirection.Descending; @@ -291,14 +291,156 @@ private void SortColumn(DataGridColumnEventArgs e, Func keyS else { // Ascending: custom order depending on column type - AllPolicies = new ObservableCollection( - collectionToSort.OrderBy(keySelector) - ); + AllPolicies = [.. collectionToSort.OrderBy(keySelector)]; e.Column.SortDirection = DataGridSortDirection.Ascending; } // Update the ItemsSource of the DataGrid DeployedPolicies.ItemsSource = AllPolicies; } + + + + + /// + /// Event handler for the Copy Individual Items SubMenu. It will populate the submenu items in the flyout of the data grid. + /// + /// + /// + private void DeployedPoliciesDataGrid_Loaded(object sender, RoutedEventArgs e) + { + // Ensure the CopyIndividualItemsSubMenu is available + if (CopyIndividualItemsSubMenu is null) + { + return; + } + + // Clear any existing items to avoid duplication if reloaded + CopyIndividualItemsSubMenu.Items.Clear(); + + // Create a dictionary to map headers to their specific click event methods + Dictionary copyActions = new() + { + { "Policy ID", CopyPolicyID_Click }, + { "Base Policy ID", CopyBasePolicyID_Click }, + { "Friendly Name", CopyFriendlyName_Click }, + { "Version", CopyVersion_Click }, + { "Is Authorized", CopyIsAuthorized_Click }, + { "Is Enforced", CopyIsEnforced_Click }, + { "Is On Disk", CopyIsOnDisk_Click }, + { "Is Signed Policy", CopyIsSignedPolicy_Click }, + { "Is System Policy", CopyIsSystemPolicy_Click }, + { "Policy Options", CopyPolicyOptionsDisplay_Click } + }; + + // Add menu items with specific click events for each column + foreach (DataGridColumn column in DeployedPolicies.Columns) + { + string headerText = column.Header.ToString()!; + + if (copyActions.TryGetValue(headerText, out RoutedEventHandler? value)) + { + // Create a new MenuFlyout Item + MenuFlyoutItem menuItem = new() { Text = $"Copy {headerText}" }; + + // Set the click event for the menu item + menuItem.Click += value; + + // Add the menu item to the submenu + CopyIndividualItemsSubMenu.Items.Add(menuItem); + } + } + } + + // Click event handlers for each property + private void CopyPolicyID_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.PolicyID?.ToString()); + private void CopyBasePolicyID_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.BasePolicyID?.ToString()); + private void CopyFriendlyName_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.FriendlyName); + private void CopyVersion_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.Version?.ToString()); + private void CopyIsAuthorized_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.IsAuthorized.ToString()); + private void CopyIsEnforced_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.IsEnforced.ToString()); + private void CopyIsOnDisk_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.IsOnDisk.ToString()); + private void CopyIsSignedPolicy_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.IsSignedPolicy.ToString()); + private void CopyIsSystemPolicy_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.IsSystemPolicy.ToString()); + private void CopyPolicyOptionsDisplay_Click(object sender, RoutedEventArgs e) => CopyToClipboard((item) => item.PolicyOptionsDisplay); + + /// + /// Helper method to copy a specified property to clipboard without reflection + /// + /// Function that retrieves the desired property value as a string + private void CopyToClipboard(Func getProperty) + { + if (DeployedPolicies.SelectedItem is CiPolicyInfo selectedItem) + { + string? propertyValue = getProperty(selectedItem); + if (propertyValue is not null) + { + DataPackage dataPackage = new(); + dataPackage.SetText(propertyValue); + Clipboard.SetContent(dataPackage); + } + } + } + + /// + /// Copies the selected rows to the clipboard in a formatted manner, with each property labeled for clarity. + /// + /// The event sender. + /// The event arguments. + private void DataGridFlyoutMenuCopy_Click(object sender, RoutedEventArgs e) + { + // Check if there are selected items in the DataGrid + if (DeployedPolicies.SelectedItems.Count > 0) + { + // Initialize StringBuilder to store all selected rows' data with labels + StringBuilder dataBuilder = new(); + + // Loop through each selected item in the DataGrid + foreach (var selectedItem in DeployedPolicies.SelectedItems) + { + if (selectedItem is CiPolicyInfo selectedRow) + { + // Append each row's formatted data to the StringBuilder + _ = dataBuilder.AppendLine(ConvertRowToText(selectedRow)); + + // Add a separator between rows for readability in multi-row copies + _ = dataBuilder.AppendLine(new string('-', 50)); + } + } + + // Create a DataPackage to hold the text data + DataPackage dataPackage = new(); + + // Set the formatted text as the content of the DataPackage + dataPackage.SetText(dataBuilder.ToString()); + + // Copy the DataPackage content to the clipboard + Clipboard.SetContent(dataPackage); + } + } + + /// + /// Converts the properties of a CiPolicyInfo row into a labeled, formatted string for copying to clipboard. + /// + /// The selected CiPolicyInfo row from the DataGrid. + /// A formatted string of the row's properties with labels. + private static string ConvertRowToText(CiPolicyInfo row) + { + // Use StringBuilder to format each property with its label for easy reading + return new StringBuilder() + .AppendLine($"Policy ID: {row.PolicyID}") + .AppendLine($"Base Policy ID: {row.BasePolicyID}") + .AppendLine($"Friendly Name: {row.FriendlyName}") + .AppendLine($"Version: {row.Version}") + .AppendLine($"Is Authorized: {row.IsAuthorized}") + .AppendLine($"Is Enforced: {row.IsEnforced}") + .AppendLine($"Is On Disk: {row.IsOnDisk}") + .AppendLine($"Is Signed Policy: {row.IsSignedPolicy}") + .AppendLine($"Is System Policy: {row.IsSystemPolicy}") + .AppendLine($"Policy Options: {row.PolicyOptionsDisplay}") + .ToString(); + } + + } } diff --git a/AppControl Manager/Pages/Update.xaml b/AppControl Manager/Pages/Update.xaml index 7a8154983..b4ec0e215 100644 --- a/AppControl Manager/Pages/Update.xaml +++ b/AppControl Manager/Pages/Update.xaml @@ -26,7 +26,7 @@ - + @@ -34,7 +34,19 @@ - + + + + + + Check for updates in this page. You can also enable automatic update check which will happen once at startup and you will be notified whenever a new version is available. + + + + + + + + diff --git a/AppControl Manager/Shared Logics/AllCertificatesGrabber.cs b/AppControl Manager/Shared Logics/AllCertificatesGrabber.cs index 50c2ccc58..69aa115b5 100644 --- a/AppControl Manager/Shared Logics/AllCertificatesGrabber.cs +++ b/AppControl Manager/Shared Logics/AllCertificatesGrabber.cs @@ -25,20 +25,20 @@ public sealed class AllFileSigners(SignedCms signerCertificate, X509Chain certif public X509Chain Chain { get; } = certificateChain; } - public static class AllCertificatesGrabber + public partial 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 readonly 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 + private readonly uint csCertChain; // Number of certificates in the chain + private readonly IntPtr pasCertChain; // Pointer to certificate chain + private readonly uint dwSignerType; // Type of signer + private readonly IntPtr psSigner; // Pointer to signer + private readonly uint dwError; // Error code internal uint csCounterSigners; // Number of countersigners internal IntPtr pasCounterSigners; // Pointer to countersigners public IntPtr pChainContext; // Pointer to chain context @@ -49,21 +49,21 @@ public struct CryptProviderSigner [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 + private readonly uint cbStruct; // Size of structure + private readonly IntPtr pWintrustData; // Pointer to WinTrustData + private readonly bool fOpenedFile; // Flag indicating if file is open + private readonly IntPtr hWndParent; // Handle to parent window + private readonly IntPtr pgActionId; // Pointer to action ID + private readonly IntPtr hProv; // Handle to provider + private readonly uint dwError; // Error code + private readonly uint dwRegSecuritySettings; // Security settings + private readonly uint dwRegPolicySettings; // Policy settings + private readonly IntPtr psPfns; // Pointer to provider functions + private readonly uint cdwTrustStepErrors; // Number of trust step errors + private readonly IntPtr padwTrustStepErrors; // Pointer to trust step errors + private readonly uint chStores; // Number of stores + private readonly IntPtr pahStores; // Pointer to stores + private readonly uint dwEncoding; // Encoding type public IntPtr hMsg; // Handle to message public uint csSigners; // Number of signers public IntPtr pasSigners; // Pointer to signers @@ -73,7 +73,7 @@ public struct CryptProviderData [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public class WinTrustSignatureSettings { - public uint cbStruct = (uint)Marshal.SizeOf(typeof(WinTrustSignatureSettings)); // Size of structure + public uint cbStruct = (uint)Marshal.SizeOf(); // 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 @@ -97,10 +97,10 @@ public WinTrustSignatureSettings(uint index) [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 + private readonly uint StructSize = (uint)Marshal.SizeOf(); // Size of structure + private readonly IntPtr FilePath; // File path pointer + private readonly IntPtr hFile = IntPtr.Zero; // File handle pointer + private readonly IntPtr pgKnownSubject = IntPtr.Zero; // Pointer to known subject // Default constructor initializes FilePath to null public FileInfoForWinTrust() @@ -126,7 +126,7 @@ public FileInfoForWinTrust(string filePath) [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public class WinTrustData { - public uint StructSize = (uint)Marshal.SizeOf(typeof(WinTrustData)); // Size of structure + public uint StructSize = (uint)Marshal.SizeOf(); // 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 @@ -136,7 +136,7 @@ public class WinTrustData 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 + private readonly 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 @@ -145,10 +145,10 @@ public class WinTrustData public WinTrustData(string filepath, uint Index) { // Initialize FileInfoForWinTrust - FileInfoPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(FileInfoForWinTrust))); + FileInfoPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf()); // Initialize pSignatureSettings - pSignatureSettings = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(WinTrustSignatureSettings))); + pSignatureSettings = Marshal.AllocCoTaskMem(Marshal.SizeOf()); // Convert TrustedData to pointer and assign to FileInfoPtr Marshal.StructureToPtr(new FileInfoForWinTrust(filepath), FileInfoPtr, false); @@ -166,22 +166,24 @@ public WinTrustData(string filepath, uint Index) } // Interop with crypt32.dll for cryptographic functions - internal static class Crypt32DLL + internal static partial class Crypt32DLL { internal const int EncodedMessageParameter = 29; // External method declaration for CryptMsgGetParam - [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [LibraryImport("crypt32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] // https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptmsggetparam - internal static extern bool CryptMsgGetParam( + internal static partial bool CryptMsgGetParam( IntPtr hCryptMsg, int dwParamType, int dwIndex, - byte[]? pvData, + [Out] byte[]? pvData, // pvData is populated by CryptMsgGetParam with data from the cryptographic message ref int pcbData - ); + ); } + // This is the main method used to retrieve all signers for a given file public static List GetAllFileSigners(string FilePath) { @@ -207,7 +209,7 @@ public static List GetAllFileSigners(string FilePath) // Call WinVerifyTrust to verify trust on the file WinTrust.WinVerifyTrustResult verifyTrustResult = WinTrust.WinVerifyTrust( IntPtr.Zero, - WinTrust.GenericWinTrustVerifyActionGuid, + WinTrust.GenericWinTrustVerifyActionGuid, winTrustDataPointer ); @@ -221,10 +223,10 @@ public static List GetAllFileSigners(string FilePath) if (TrustedData.pSignatureSettings != IntPtr.Zero) { // Using the generic overload of Marshal.PtrToStructure for better type safety and performance - var signatureSettings = Marshal.PtrToStructure(TrustedData.pSignatureSettings); + WinTrustSignatureSettings? signatureSettings = Marshal.PtrToStructure(TrustedData.pSignatureSettings); // Ensuring that the structure is not null before accessing its members - if (signatureSettings != null) + if (signatureSettings is not null) { maxSigners = signatureSettings.SecondarySignersCount; } @@ -307,7 +309,7 @@ public static List GetAllFileSigners(string FilePath) } finally { - if (TrustedData != null) + if (TrustedData is not null) { // Set StateAction to close the WinTrustData structure TrustedData.StateAction = WinTrust.StateActionClose; diff --git a/AppControl Manager/Shared Logics/CheckPolicyDeploymentStatus.cs b/AppControl Manager/Shared Logics/CheckPolicyDeploymentStatus.cs index ee8b74534..3969faeca 100644 --- a/AppControl Manager/Shared Logics/CheckPolicyDeploymentStatus.cs +++ b/AppControl Manager/Shared Logics/CheckPolicyDeploymentStatus.cs @@ -18,10 +18,10 @@ public static bool IsDeployed(string policyXMLFile) { // Create a new HashSet with case-insensitive string comparison - var currentPolicyIDs = new HashSet(StringComparer.InvariantCultureIgnoreCase); + HashSet currentPolicyIDs = new(StringComparer.InvariantCultureIgnoreCase); // Get all of the deployed policies on the system - var policies = CiToolHelper.GetPolicies(false, true, true); + List policies = CiToolHelper.GetPolicies(false, true, true); // Loop through each policy and add its ID to the HashSet foreach (CiPolicyInfo item in policies) diff --git a/AppControl Manager/Shared Logics/CiToolHelper.cs b/AppControl Manager/Shared Logics/CiToolHelper.cs index 872ccd4e5..d1164a69d 100644 --- a/AppControl Manager/Shared Logics/CiToolHelper.cs +++ b/AppControl Manager/Shared Logics/CiToolHelper.cs @@ -27,7 +27,7 @@ public sealed class CiPolicyInfo // A property to format PolicyOptions as a comma-separated string - public string PolicyOptionsDisplay => PolicyOptions != null ? string.Join(", ", PolicyOptions) : string.Empty; + public string PolicyOptionsDisplay => PolicyOptions is not null ? string.Join(", ", PolicyOptions) : string.Empty; } @@ -365,10 +365,10 @@ public static List GetPolicyOptionsOrDefault(this JsonElement element) foreach (var item in value.EnumerateArray()) { // Get the string representation of the item. - var str = item.GetString(); + string? str = item.GetString(); // Add the string to the options list if it is not null. - if (str != null) + if (str is not null) { options.Add(str); } @@ -381,10 +381,10 @@ public static List GetPolicyOptionsOrDefault(this JsonElement element) else if (value.ValueKind == JsonValueKind.String) { // Get the string representation of the single value. - var str = value.GetString(); + string? str = value.GetString(); // Return a list containing the single string if it is not null. - if (str != null) + if (str is not null) { return [str]; } diff --git a/AppControl Manager/Shared Logics/CodeIntegrityInfo.cs b/AppControl Manager/Shared Logics/CodeIntegrityInfo.cs index beed9560f..64a0f708b 100644 --- a/AppControl Manager/Shared Logics/CodeIntegrityInfo.cs +++ b/AppControl Manager/Shared Logics/CodeIntegrityInfo.cs @@ -19,7 +19,7 @@ internal sealed class SystemCodeIntegrityInfo internal required List CodeIntegrityDetails { get; set; } } - internal static class DetailsRetrieval + internal static partial class DetailsRetrieval { private const int SystemCodeIntegrityInformation = 103; @@ -31,8 +31,14 @@ private struct SYSTEM_CODEINTEGRITY_INFORMATION } // https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation#system_codeintegrity_information - [DllImport("ntdll.dll")] - private static extern int NtQuerySystemInformation(int SystemInformationClass, IntPtr SystemInformation, int SystemInformationLength, ref int ReturnLength); + [LibraryImport("ntdll.dll", SetLastError = true)] + private static partial int NtQuerySystemInformation( + int SystemInformationClass, + IntPtr SystemInformation, + int SystemInformationLength, + ref int ReturnLength + ); + private static List GetCodeIntegrityDetails(uint options) { @@ -84,7 +90,7 @@ internal static SystemCodeIntegrityInfo Get() SYSTEM_CODEINTEGRITY_INFORMATION sci = new() { - Length = (uint)Marshal.SizeOf(typeof(SYSTEM_CODEINTEGRITY_INFORMATION)) + Length = (uint)Marshal.SizeOf() }; IntPtr buffer = Marshal.AllocHGlobal((int)sci.Length); diff --git a/AppControl Manager/Shared Logics/ConfigureISGServices.cs b/AppControl Manager/Shared Logics/ConfigureISGServices.cs index 8a82adfd1..9b6c0ea2c 100644 --- a/AppControl Manager/Shared Logics/ConfigureISGServices.cs +++ b/AppControl Manager/Shared Logics/ConfigureISGServices.cs @@ -9,10 +9,9 @@ public static void Configure() { Logger.Write("Configuring and starting the required ISG related services"); - _ = PowerShellExecutor.ExecuteScript(""" -appidtel.exe start -sc.exe config appidsvc start=auto -"""); + ProcessStarter.RunCommand("appidtel.exe", "start"); + + ProcessStarter.RunCommand("sc.exe", "config appidsvc start=auto"); } } diff --git a/AppControl Manager/Shared Logics/DriveLetterMapper.cs b/AppControl Manager/Shared Logics/DriveLetterMapper.cs deleted file mode 100644 index 9052300d2..000000000 --- a/AppControl Manager/Shared Logics/DriveLetterMapper.cs +++ /dev/null @@ -1,122 +0,0 @@ -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 - List drives = []; - // 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[4..^1], 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/AppControl Manager/Shared Logics/EventLogUtility.cs b/AppControl Manager/Shared Logics/EventLogUtility.cs index 8bbbcf794..7543a4e01 100644 --- a/AppControl Manager/Shared Logics/EventLogUtility.cs +++ b/AppControl Manager/Shared Logics/EventLogUtility.cs @@ -8,6 +8,11 @@ namespace WDACConfig { public static class EventLogUtility { + + + private const string logName = "Microsoft-Windows-CodeIntegrity/Operational"; + + /// /// 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. @@ -16,11 +21,9 @@ public static class EventLogUtility /// Size of the Code Integrity Operational Event Log public static void SetLogSize(ulong logSize = 0) { - Logger.Write("Set-SetLogSize method started..."); - - string logName = "Microsoft-Windows-CodeIntegrity/Operational"; + Logger.Write("Setting the Code Integrity Log Size"); - using var logConfig = new EventLogConfiguration(logName); + using EventLogConfiguration logConfig = new(logName); string logFilePath = Environment.ExpandEnvironmentVariables(logConfig.LogFilePath); FileInfo logFileInfo = new(logFilePath); long currentLogFileSize = logFileInfo.Length; @@ -56,5 +59,35 @@ public static void SetLogSize(ulong logSize = 0) } } } + + + /// + /// Gets the Code Integrity Operational Log Max capacity in Double + /// + /// + public static double GetCurrentLogSize() + { + Logger.Write("Getting the Code Integrity Log Capacity"); + + try + { + using EventLogConfiguration logConfig = new(logName); + long logCapacityBytes = logConfig.MaximumSizeInBytes; + + // Convert bytes to megabytes + double logCapacityMB = logCapacityBytes / (1024.0 * 1024.0); + + Logger.Write($"Log capacity: {logCapacityMB:F2} MB."); + + return logCapacityMB; + } + catch (Exception ex) + { + Logger.Write($"An error occurred while retrieving the log capacity: {ex.Message}"); + throw; + } + } + + } } diff --git a/AppControl Manager/Shared Logics/FileSystemPicker.cs b/AppControl Manager/Shared Logics/FileSystemPicker.cs index ad760d355..551ed2aa4 100644 --- a/AppControl Manager/Shared Logics/FileSystemPicker.cs +++ b/AppControl Manager/Shared Logics/FileSystemPicker.cs @@ -30,7 +30,7 @@ public static class FileSystemPicker dialog.SetTitle(title); // Create the file type filters based on the passed parameters - if (filters != null && filters.Length > 0) + if (filters is not null && filters.Length > 0) { COMDLG_FILTERSPEC[] filterSpecs = new COMDLG_FILTERSPEC[filters.Length]; for (int i = 0; i < filters.Length; i++) @@ -97,7 +97,7 @@ public static class FileSystemPicker { dialog.SetTitle(title); - if (filters != null && filters.Length > 0) + if (filters is not null && filters.Length > 0) { COMDLG_FILTERSPEC[] filterSpecs = new COMDLG_FILTERSPEC[filters.Length]; for (int i = 0; i < filters.Length; i++) diff --git a/AppControl Manager/Shared Logics/GetExtendedFileAttrib.cs b/AppControl Manager/Shared Logics/GetExtendedFileAttrib.cs index 6f8c7bb56..d7e28a845 100644 --- a/AppControl Manager/Shared Logics/GetExtendedFileAttrib.cs +++ b/AppControl Manager/Shared Logics/GetExtendedFileAttrib.cs @@ -6,7 +6,7 @@ namespace WDACConfig { - public sealed class ExFileInfo + public sealed partial class ExFileInfo { // Constants used for encoding fallback and error handling private const string UnicodeFallbackCode = "04B0"; @@ -23,16 +23,18 @@ public sealed class ExFileInfo // 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); + [LibraryImport("Version.dll", EntryPoint = "GetFileVersionInfoSizeExW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] + private static partial 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); + [LibraryImport("Version.dll", EntryPoint = "VerQueryValueW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial 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); + [LibraryImport("Version.dll", EntryPoint = "GetFileVersionInfoExW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool GetFileVersionInfoEx(uint dwFlags, string filename, int handle, int len, [Out] byte[] data); // Private constructor to prevent direct instantiation private ExFileInfo() { } @@ -40,14 +42,18 @@ private ExFileInfo() { } // Static method to get extended file info public static ExFileInfo GetExtendedFileInfo(string filePath) { - var ExFileInfo = new ExFileInfo(); + ExFileInfo ExFileInfo = new(); // Get the size of the version information block int versionInfoSize = GetFileVersionInfoSizeEx(FILE_VER_GET_NEUTRAL, filePath, out int handle); - if (versionInfoSize == 0) return ExFileInfo; + + if (versionInfoSize == 0) + { + return ExFileInfo; + } // Allocate array for version data and retrieve it - var versionData = new byte[versionInfoSize]; + byte[] versionData = new byte[versionInfoSize]; if (!GetFileVersionInfoEx(FILE_VER_GET_NEUTRAL, filePath, handle, versionInfoSize, versionData)) return ExFileInfo; @@ -92,7 +98,7 @@ private static bool TryGetVersion(Span data, out Version? version) return false; // Marshal the version info structure - var fileInfo = Marshal.PtrToStructure(buffer); + FileVersionInfo fileInfo = Marshal.PtrToStructure(buffer); // Construct Version object from version info version = new Version( @@ -126,10 +132,12 @@ private static bool TryGetLocaleAndEncoding(Span data, out string? locale, // 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) + string[] encodings = [encoding, Cp1252FallbackCode, UnicodeFallbackCode]; + + foreach (string enc in encodings) { - var subBlock = $"StringFileInfo\\{locale}{enc}{resource}"; + string subBlock = $"StringFileInfo\\{locale}{enc}{resource}"; + if (VerQueryValue(Marshal.UnsafeAddrOfPinnedArrayElement(versionBlock.ToArray(), 0), subBlock, out var buffer, out _)) return Marshal.PtrToStringAuto(buffer); diff --git a/AppControl Manager/Shared Logics/GetFilesFast.cs b/AppControl Manager/Shared Logics/GetFilesFast.cs index e59c60d3e..286ececb6 100644 --- a/AppControl Manager/Shared Logics/GetFilesFast.cs +++ b/AppControl Manager/Shared Logics/GetFilesFast.cs @@ -46,7 +46,7 @@ public static List GetFilesFast( }; // Process directories if provided - if (directories != null && directories.Length > 0) + if (directories is not null && directories.Length > 0) { foreach (DirectoryInfo directory in directories) { @@ -74,7 +74,7 @@ public static List GetFilesFast( } // If files are provided, process them - if (files != null && files.Length > 0) + if (files is not null && files.Length > 0) { foreach (FileInfo file in files) { diff --git a/AppControl Manager/Shared Logics/GetOpusData.cs b/AppControl Manager/Shared Logics/GetOpusData.cs index 06f48edc4..49db01e35 100644 --- a/AppControl Manager/Shared Logics/GetOpusData.cs +++ b/AppControl Manager/Shared Logics/GetOpusData.cs @@ -12,7 +12,7 @@ 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( @@ -24,6 +24,8 @@ internal static extern bool CryptDecodeObject( [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 @@ -57,7 +59,7 @@ public static List GetOpusData(SignedCms signature) 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)) + if (string.Equals(signedAttribute.Oid.Value, Opus.SPC_SP_OPUS_INFO_OBJID, StringComparison.OrdinalIgnoreCase)) { // Initializing pcbStructInfo to 0 uint pcbStructInfo = 0; diff --git a/AppControl Manager/Shared Logics/Initializer.cs b/AppControl Manager/Shared Logics/Initializer.cs index fa44250d6..e1f30cecf 100644 --- a/AppControl Manager/Shared Logics/Initializer.cs +++ b/AppControl Manager/Shared Logics/Initializer.cs @@ -20,7 +20,7 @@ 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)) + if (ubrValue is not null && int.TryParse(ubrValue.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out int ubr)) { GlobalVars.UBR = ubr; } diff --git a/AppControl Manager/Shared Logics/Variables/CILogIntel.cs b/AppControl Manager/Shared Logics/IntelGathering/CILogIntel.cs similarity index 93% rename from AppControl Manager/Shared Logics/Variables/CILogIntel.cs rename to AppControl Manager/Shared Logics/IntelGathering/CILogIntel.cs index e8d6eb702..bf17c564f 100644 --- a/AppControl Manager/Shared Logics/Variables/CILogIntel.cs +++ b/AppControl Manager/Shared Logics/IntelGathering/CILogIntel.cs @@ -2,13 +2,13 @@ #nullable enable -namespace WDACConfig +namespace WDACConfig.IntelGathering { // Application Control event tags intelligence - public static class CILogIntel + internal static class CILogIntel { // Requested and Validated Signing Level Mappings: https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/operations/event-tag-explanations#requested-and-validated-signing-level - public static readonly Dictionary ReqValSigningLevels = new() + internal 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 static class CILogIntel }; // SignatureType Mappings: https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/operations/event-tag-explanations#signaturetype - public static readonly Dictionary SignatureTypeTable = new() + internal static readonly Dictionary SignatureTypeTable = new() { { 0, "Unsigned or verification hasn't been attempted" }, { 1 , "Embedded signature" }, @@ -38,7 +38,7 @@ public static class CILogIntel }; // VerificationError mappings: https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/operations/event-tag-explanations#verificationerror - public static readonly Dictionary VerificationErrorTable = new() + internal static readonly Dictionary VerificationErrorTable = new() { { 0 , "Successfully verified signature."}, { 1 , "File has an invalid hash."}, diff --git a/AppControl Manager/Shared Logics/IntelGathering/DriveLetterMapper.cs b/AppControl Manager/Shared Logics/IntelGathering/DriveLetterMapper.cs new file mode 100644 index 000000000..9974564bb --- /dev/null +++ b/AppControl Manager/Shared Logics/IntelGathering/DriveLetterMapper.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +#nullable enable + +namespace WDACConfig.IntelGathering +{ + public static partial class DriveLetterMapper + { + [LibraryImport("kernel32.dll", SetLastError = true, StringMarshalling = StringMarshalling.Utf16, EntryPoint = "FindFirstVolumeW")] + private static partial IntPtr FindFirstVolume( + [MarshalUsing(CountElementName = "cchBufferLength")][Out] char[] lpszVolumeName, + uint cchBufferLength); + + [LibraryImport("kernel32.dll", SetLastError = true, StringMarshalling = StringMarshalling.Utf16, EntryPoint = "FindNextVolumeW")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool FindNextVolume( + IntPtr hFindVolume, + [MarshalUsing(CountElementName = "cchBufferLength")][Out] char[] lpszVolumeName, + uint cchBufferLength); + + [LibraryImport("kernel32.dll", SetLastError = true, StringMarshalling = StringMarshalling.Utf16, EntryPoint = "QueryDosDeviceW")] + private static partial uint QueryDosDevice( + string lpDeviceName, + [MarshalUsing(CountElementName = "ucchMax")][Out] char[] lpTargetPath, + int ucchMax); + + [LibraryImport("kernel32.dll", SetLastError = true, StringMarshalling = StringMarshalling.Utf16, EntryPoint = "GetVolumePathNamesForVolumeNameW")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool GetVolumePathNamesForVolumeNameW( + [MarshalAs(UnmanagedType.LPWStr)] string lpszVolumeName, + [MarshalUsing(CountElementName = "cchBuferLength")][Out] char[] lpszVolumeNamePaths, + uint cchBuferLength, + ref uint lpcchReturnLength); + + + // Class to store drive mapping information + public sealed 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 + List drives = []; + // Maximum buffer size for volume names, paths, and mount points + uint max = 65535; + // char[] for storing volume names + char[] volumeNameBuffer = new char[max]; + // char[] for storing path names + char[] pathNameBuffer = new char[max]; + // char[] for storing mount points + char[] mountPointBuffer = new char[max]; + // Variable to store the length of the return string + uint lpcchReturnLength = 0; + + // Get the first volume handle + IntPtr volumeHandle = FindFirstVolume(volumeNameBuffer, 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, trimming any leftover null characters + string volume = new string(volumeNameBuffer).TrimEnd('\0'); + // Get the mount point for the volume + _ = GetVolumePathNamesForVolumeNameW(volume, mountPointBuffer, max, ref lpcchReturnLength); + // Get the device path for the volume + uint returnLength = QueryDosDevice(volume[4..^1], pathNameBuffer, (int)max); + + // Check if the device path is found + if (returnLength > 0) + { + // Add a new drive mapping to the list with valid details + drives.Add(new DriveMapping + { + // Extract the drive letter (mount point) from the buffer + // Use Array.IndexOf to locate the first null character ('\0') + // If null is not found, use the entire length of the buffer + // Replace ":\" with ":" for consistent formatting + DriveLetter = new string(mountPointBuffer, 0, Array.IndexOf(mountPointBuffer, '\0') >= 0 + ? Array.IndexOf(mountPointBuffer, '\0') + : mountPointBuffer.Length) + .Replace(@":\", ":", StringComparison.OrdinalIgnoreCase), + + // Assign the current volume name + VolumeName = volume, + + // Extract the device path from the buffer + // Use Array.IndexOf to locate the first null character ('\0') + // If null is not found, use the entire length of the buffer + DevicePath = new string(pathNameBuffer, 0, Array.IndexOf(pathNameBuffer, '\0') >= 0 + ? Array.IndexOf(pathNameBuffer, '\0') + : pathNameBuffer.Length) + }); + } + else + { + // Add a new drive mapping with "No mountpoint found" when the path is invalid + drives.Add(new DriveMapping + { + // No drive letter since the mount point is unavailable + DriveLetter = null, + + // Assign the current volume name + VolumeName = volume, + + // Assign a placeholder string indicating no mount point is found + DevicePath = "No mountpoint found" + }); + } + + } while (FindNextVolume(volumeHandle, volumeNameBuffer, max)); // Continue until there are no more volumes + + // Return the list of drive mappings + return drives; + } + } +} diff --git a/AppControl Manager/Shared Logics/IntelGathering/EventAction.cs b/AppControl Manager/Shared Logics/IntelGathering/EventAction.cs new file mode 100644 index 000000000..27cfe764c --- /dev/null +++ b/AppControl Manager/Shared Logics/IntelGathering/EventAction.cs @@ -0,0 +1,10 @@ +#nullable enable + +namespace WDACConfig.IntelGathering +{ + public enum EventAction + { + Audit, + Block + } +} diff --git a/AppControl Manager/Shared Logics/IntelGathering/FileIdentity.cs b/AppControl Manager/Shared Logics/IntelGathering/FileIdentity.cs new file mode 100644 index 000000000..2b0ae22b4 --- /dev/null +++ b/AppControl Manager/Shared Logics/IntelGathering/FileIdentity.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; + +#nullable enable + +namespace WDACConfig.IntelGathering +{ + public sealed class FileIdentity + { + // The origin of this File Identity object, where it came from and how it was compiled + public FileIdentityOrigin Origin { get; set; } + + // Whether the file is signed or unsigned + public SignatureStatus SignatureStatus { get; set; } + + // Properties related to logs + public EventAction Action { get; set; } + public int EventID { get; set; } + public DateTime? TimeCreated { get; set; } + public string? ComputerName { get; set; } + public Guid? PolicyGUID { get; set; } + public bool? UserWriteable { get; set; } + public string? ProcessName { get; set; } + public string? RequestedSigningLevel { get; set; } + public string? ValidatedSigningLevel { get; set; } + public string? Status { get; set; } + public long? USN { get; set; } + public string? PolicyName { get; set; } + public string? PolicyID { get; set; } + public string? PolicyHash { get; set; } + public string? UserID { get; set; } + + + // Properties applicable to files in general + public string? FilePath { get; set; } + public string? FileName { get; set; } + public string? SHA1Hash { get; set; } // SHA1 Authenticode Hash with fallback to Flat hash for incompatible files + public string? SHA256Hash { get; set; } // SHA256 Authenticode Hash with fallback to Flat hash for incompatible files + public string? SHA1PageHash { get; set; } // 1st Page hash - Local file scanning provides this + public string? SHA256PageHash { get; set; } // 1st Page hash - Local file scanning provides this + public string? SHA1FlatHash { get; set; } // Flat file hashes - Event logs provide this + public string? SHA256FlatHash { get; set; } // Flat file hashes - Event logs provide this + public int SISigningScenario { get; set; } // 1 for user mode files - 0 for kernel mode files + public string? OriginalFileName { get; set; } + public string? InternalName { get; set; } + public string? FileDescription { get; set; } + public string? ProductName { get; set; } + public Version? FileVersion { get; set; } + public string? PackageFamilyName { get; set; } + + // Signer and certificate information with a custom comparer to ensure data with the same PublisherTBSHash and IssuerTBSHash do not exist + public HashSet FileSignerInfos { get; set; } = new HashSet(new FileSignerInfoComparer()); + + + // Just for display purposes, only contains CNs of the . FileSignerInfos is the one that has actual signing data. + public HashSet FilePublishers { get; set; } = new HashSet(StringComparer.OrdinalIgnoreCase); + + // Computed property to join FilePublishers into a comma-separated string + public string FilePublishersToDisplay => string.Join(", ", FilePublishers); + + // If the file has a WHQL signer + public bool? HasWHQLSigner { get; set; } + + + // Determines whether the file is signed by ECC algorithm or not + // AppControl does not support ECC Signed files yet + public bool? IsECCSigned { get; set; } + } + +} diff --git a/AppControl Manager/Shared Logics/IntelGathering/FileIdentityComparer.cs b/AppControl Manager/Shared Logics/IntelGathering/FileIdentityComparer.cs new file mode 100644 index 000000000..4120f4099 --- /dev/null +++ b/AppControl Manager/Shared Logics/IntelGathering/FileIdentityComparer.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; + +#nullable enable + +namespace WDACConfig.IntelGathering +{ + /// + /// A custom equality comparer for the FileIdentity class. + /// This comparer is used to determine the uniqueness of FileIdentity instances + /// based on specific properties. + /// + public sealed class FileIdentityComparer : IEqualityComparer + { + /// + /// Determines whether two FileIdentity instances are equal. + /// The instances are considered equal if all six specified properties are the same. + /// + /// + /// Both FileIdentity Instances Are Null: + /// Result: Equal(true). + /// + /// + /// One Instance Is Null: + /// Result: Not equal(false). + /// + /// + /// Both Instances Are Not Null, with Some Properties Null: + /// If a property is null in both instances, that property is considered equal. + /// If a property is null in one instance but has a value in the other, that property is considered not equal. + /// If all specified properties are equal (including handling of nulls), the instances are equal; otherwise, they are not. + /// + /// + /// The first FileIdentity instance to compare. + /// The second FileIdentity instance to compare. + /// true if the instances are equal; otherwise, false. + public bool Equals(FileIdentity? x, FileIdentity? y) + { + // If both are null, they are considered equal + if (x is null && y is null) + return true; + + // If one is null and the other is not, they are not equal + if (x is null || y is null) + return false; + + // Compare the specified properties for equality using string comparison + return string.Equals(x.SHA256Hash, y.SHA256Hash, StringComparison.OrdinalIgnoreCase) && + string.Equals(x.SHA256FlatHash, y.SHA256FlatHash, StringComparison.OrdinalIgnoreCase) && + string.Equals(x.ProductName, y.ProductName, StringComparison.OrdinalIgnoreCase) && + string.Equals(x.InternalName, y.InternalName, StringComparison.OrdinalIgnoreCase) && + string.Equals(x.OriginalFileName, y.OriginalFileName, StringComparison.OrdinalIgnoreCase) && + string.Equals(x.FileDescription, y.FileDescription, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Returns a hash code for the given FileIdentity instance. + /// The hash code is computed based on the six specified properties. + /// + /// The FileIdentity instance for which to get the hash code. + /// A hash code for the given FileIdentity instance. + public int GetHashCode(FileIdentity? obj) + { + // Return a default hash code (0) if obj is null to avoid exceptions + if (obj is null) return 0; + + // Initialize a hash variable + int hash = 17; + + // Combine hash codes of the specified properties using a common technique + + // unchecked allows overflow but does not decrease accuracy of the HashSet + // When implementing GetHashCode, the important aspect is that the same input will always yield the same output. + // Even if that output results in a wrapped value due to overflow, it will consistently represent that specific object. + + unchecked + { + hash = hash * 31 + (obj.SHA256Hash?.GetHashCode(StringComparison.OrdinalIgnoreCase) ?? 0); + hash = hash * 31 + (obj.SHA256FlatHash?.GetHashCode(StringComparison.OrdinalIgnoreCase) ?? 0); + hash = hash * 31 + (obj.ProductName?.GetHashCode(StringComparison.OrdinalIgnoreCase) ?? 0); + hash = hash * 31 + (obj.InternalName?.GetHashCode(StringComparison.OrdinalIgnoreCase) ?? 0); + hash = hash * 31 + (obj.OriginalFileName?.GetHashCode(StringComparison.OrdinalIgnoreCase) ?? 0); + hash = hash * 31 + (obj.FileDescription?.GetHashCode(StringComparison.OrdinalIgnoreCase) ?? 0); + } + + // Return the computed hash code + return hash; + } + } + +} diff --git a/AppControl Manager/Shared Logics/IntelGathering/FileIdentityOrigin.cs b/AppControl Manager/Shared Logics/IntelGathering/FileIdentityOrigin.cs new file mode 100644 index 000000000..ab3b74614 --- /dev/null +++ b/AppControl Manager/Shared Logics/IntelGathering/FileIdentityOrigin.cs @@ -0,0 +1,11 @@ +#nullable enable + +namespace WDACConfig.IntelGathering +{ + public enum FileIdentityOrigin + { + EventLog, + MDEAdvancedHunting, + DirectFileScan + } +} diff --git a/AppControl Manager/Shared Logics/IntelGathering/FileIdentitySignatureBasedHashSet.cs b/AppControl Manager/Shared Logics/IntelGathering/FileIdentitySignatureBasedHashSet.cs new file mode 100644 index 000000000..33e90ce8f --- /dev/null +++ b/AppControl Manager/Shared Logics/IntelGathering/FileIdentitySignatureBasedHashSet.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; + +#nullable enable + +namespace WDACConfig.IntelGathering +{ + /// + /// A custom collection that manages a set of FileIdentity objects, + /// prioritizing signed FileIdentity items over unsigned ones when adding items + /// with identical properties, based on the custom equality comparer. + /// Mostly used for MDE Advanced Hunting logs. + /// + /// If an equivalent item (based on the FileIdentityComparer which takes priority) already exists, the method checks the SignatureStatus of both the existing item and the new item: + /// If the existing item is unsigned and the new item is signed: The unsigned item is removed, and the signed item is added to the set. + /// If the existing item is already signed: The new item, signed or unsigned, will simply not be added because they are considered equal according to the FileIdentityComparer. + /// + public sealed class FileIdentitySignatureBasedHashSet + { + // A HashSet to store FileIdentity objects with a custom comparer. + // This comparer defines equality based on selected properties in that comparer, ignoring SignatureStatus for now. + private readonly HashSet _set; + + /// + /// Initializes a new instance of the FileIdentitySignatureBasedHashSet class. + /// + public FileIdentitySignatureBasedHashSet() + { + _set = new HashSet(new FileIdentityComparer()); + } + + /// + /// Expose the internal HashSet so we can access it directly. + /// + public HashSet FileIdentitiesInternal => _set; + + /// + /// Adds a FileIdentity item to the set. + /// + /// The FileIdentity item to add. + /// True if a new item is added or an unsigned item is replaced; false otherwise. + public bool Add(FileIdentity item) + { + // Check if an equivalent item (based on FileIdentityComparer) already exists in the set + if (_set.TryGetValue(item, out FileIdentity? existingItem)) + { + // If an equivalent unsigned item exists, replace it with the signed item + if (existingItem.SignatureStatus == SignatureStatus.Unsigned && item.SignatureStatus == SignatureStatus.Signed) + { + Logger.Write($"Replacing an unsigned FileIdentity item with a signed one in MDE Advanced Hunting Logs for the file with name {existingItem.FileName} and SHA256 hash {existingItem.SHA256Hash}."); + + // Remove the existing unsigned item and add the signed one + _ = _set.Remove(existingItem); + _ = _set.Add(item); + return true; // Indicate that an item was replaced + } + + // If an equivalent signed item already exists, do not add the unsigned item + return false; + } + + // If no equivalent item exists, add the new item to the set + _ = _set.Add(item); + return true; + } + + /// + /// Checks if the set contains an item equivalent to the specified FileIdentity item. + /// + /// The FileIdentity item to check for. + /// True if an equivalent item exists in the set; false otherwise. + public bool Contains(FileIdentity item) => _set.Contains(item); + + /// + /// Removes an equivalent FileIdentity item from the set, if it exists. + /// + /// The FileIdentity item to remove. + /// True if the item was removed; false if it did not exist in the set. + public bool Remove(FileIdentity item) => _set.Remove(item); + + /// + /// Gets the count of items in the set. + /// + public int Count => _set.Count; + } +} diff --git a/AppControl Manager/Shared Logics/IntelGathering/FileIdentityTimeBasedHashSet.cs b/AppControl Manager/Shared Logics/IntelGathering/FileIdentityTimeBasedHashSet.cs new file mode 100644 index 000000000..0d0cf358b --- /dev/null +++ b/AppControl Manager/Shared Logics/IntelGathering/FileIdentityTimeBasedHashSet.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; + +#nullable enable + +namespace WDACConfig.IntelGathering +{ + /// + /// A custom collection that manages a set of FileIdentity objects, + /// prioritizing newer FileIdentity items over older ones when adding items + /// with identical properties, based on the custom equality comparer. + /// Used by event logs scanning. + /// + /// If an equivalent item (based on the FileIdentityComparer which takes priority) already exists, + /// the method checks the TimeCreated property of both the existing item and the new item: + /// If the existing item is older and the new item is newer: The older item is removed, + /// and the newer item is added to the set. + /// If the existing item is newer or has the same timestamp, the new item will not be added + /// because they are considered equal or the existing one is preferred. + /// + public sealed class FileIdentityTimeBasedHashSet + { + // A HashSet to store FileIdentity objects with a custom comparer. + // This comparer defines equality based on selected properties in that comparer, ignoring TimeCreated for now. + private readonly HashSet _set; + + /// + /// Initializes a new instance of the FileIdentityTimeBasedHashSet class. + /// + public FileIdentityTimeBasedHashSet() + { + _set = new HashSet(new FileIdentityComparer()); + } + + /// + /// Expose the internal HashSet so we can access it directly. + /// + public HashSet FileIdentitiesInternal => _set; + + /// + /// Adds a FileIdentity item to the set, replacing an older equivalent item if it exists. + /// + /// The FileIdentity item to add. + /// True if a new item is added or an older item is replaced; false otherwise. + public bool Add(FileIdentity item) + { + // Check if an equivalent item (based on FileIdentityComparer) already exists in the set + if (_set.TryGetValue(item, out FileIdentity? existingItem)) + { + // If an equivalent older item exists, replace it with the newer item + if (existingItem.TimeCreated.HasValue && item.TimeCreated.HasValue && + existingItem.TimeCreated < item.TimeCreated) + { + Logger.Write($"Replacing an older FileIdentity item with a newer one in MDE Advanced Hunting Logs " + + $"for the file with name {existingItem.FileName} and SHA256 hash {existingItem.SHA256Hash}."); + + // Remove the existing older item and add the newer one + _ = _set.Remove(existingItem); + _ = _set.Add(item); + return true; // Indicate that an item was replaced + } + + // If an equivalent newer item already exists, do not add the older item + return false; + } + + // If no equivalent item exists, add the new item to the set + _ = _set.Add(item); + return true; + } + + /// + /// Checks if the set contains an item equivalent to the specified FileIdentity item. + /// + /// The FileIdentity item to check for. + /// True if an equivalent item exists in the set; false otherwise. + public bool Contains(FileIdentity item) => _set.Contains(item); + + /// + /// Removes an equivalent FileIdentity item from the set, if it exists. + /// + /// The FileIdentity item to remove. + /// True if the item was removed; false if it did not exist in the set. + public bool Remove(FileIdentity item) => _set.Remove(item); + + /// + /// Gets the count of items in the set. + /// + public int Count => _set.Count; + } +} diff --git a/AppControl Manager/Shared Logics/IntelGathering/FileSignerInfo.cs b/AppControl Manager/Shared Logics/IntelGathering/FileSignerInfo.cs new file mode 100644 index 000000000..220780971 --- /dev/null +++ b/AppControl Manager/Shared Logics/IntelGathering/FileSignerInfo.cs @@ -0,0 +1,29 @@ +using System; + +#nullable enable + +namespace WDACConfig.IntelGathering +{ + public sealed class FileSignerInfo + { + public int? TotalSignatureCount { get; set; } + public int? Signature { get; set; } + public string? Hash { get; set; } + public bool? PageHash { get; set; } + public string? SignatureType { get; set; } + public string? ValidatedSigningLevel { get; set; } + public string? VerificationError { get; set; } + public int? Flags { get; set; } + public DateTime? NotValidBefore { get; set; } + public DateTime? NotValidAfter { get; set; } + public string? PublisherName { get; set; } + public string? IssuerName { get; set; } + public string? PublisherTBSHash { get; set; } + public string? IssuerTBSHash { get; set; } + public string? OPUSInfo { get; set; } + public string? EKUs { get; set; } + public int? KnownRoot { get; set; } + public bool? IsWHQL { get; set; } + } + +} diff --git a/AppControl Manager/Shared Logics/IntelGathering/FileSignerInfoComparer.cs b/AppControl Manager/Shared Logics/IntelGathering/FileSignerInfoComparer.cs new file mode 100644 index 000000000..a862a6ba9 --- /dev/null +++ b/AppControl Manager/Shared Logics/IntelGathering/FileSignerInfoComparer.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; + +#nullable enable + +namespace WDACConfig.IntelGathering +{ + /// + /// A custom equality comparer for the FileSignerInfo class. + /// This comparer is used to determine the uniqueness of FileSignerInfo instances + /// based on PublisherTBSHash and IssuerTBSHash properties. + /// + public sealed class FileSignerInfoComparer : IEqualityComparer + { + /// + /// Determines whether two FileSignerInfo instances are equal. + /// The instances are considered equal if both the PublisherTBSHash + /// and IssuerTBSHash properties are equal. + /// + /// The first FileSignerInfo instance to compare. + /// The second FileSignerInfo instance to compare. + /// true if the instances are equal; otherwise, false. + public bool Equals(FileSignerInfo? x, FileSignerInfo? y) + { + // If both are null, they are considered equal + if (x is null && y is null) + return true; + + // If either instance is null, they are not considered equal + if (x is null || y is null) + return false; + + // Compare the PublisherTBSHash and IssuerTBSHash properties for equality + return x.PublisherTBSHash == y.PublisherTBSHash && x.IssuerTBSHash == y.IssuerTBSHash; + } + + /// + /// Returns a hash code for the given FileSignerInfo instance. + /// The hash code is computed based on the PublisherTBSHash and IssuerTBSHash properties. + /// + /// The FileSignerInfo instance for which to get the hash code. + /// A hash code for the given FileSignerInfo instance. + public int GetHashCode(FileSignerInfo obj) + { + + int hashPublisher; + int hashIssuer; + + // Get hash codes for both properties, using case-insensitive comparison for strings + // Using unchecked to avoid exceptions from overflow + unchecked + { + hashPublisher = obj.PublisherTBSHash?.GetHashCode(StringComparison.OrdinalIgnoreCase) ?? 0; + hashIssuer = obj.IssuerTBSHash?.GetHashCode(StringComparison.OrdinalIgnoreCase) ?? 0; + } + + // Combine the hash codes using XOR to produce a single hash code for the instance + // Reducing collisions by using 397 prime number + return unchecked((hashPublisher * 397) ^ hashIssuer); + } + } +} diff --git a/AppControl Manager/Shared Logics/IntelGathering/GetEventLogsData.cs b/AppControl Manager/Shared Logics/IntelGathering/GetEventLogsData.cs new file mode 100644 index 000000000..e2346fc72 --- /dev/null +++ b/AppControl Manager/Shared Logics/IntelGathering/GetEventLogsData.cs @@ -0,0 +1,1122 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Eventing.Reader; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using System.Xml; + +#nullable enable + +namespace WDACConfig.IntelGathering +{ + internal static class GetEventLogsData + { + // Code Integrity event path + private const string CodeIntegrityLogPath = "Microsoft-Windows-CodeIntegrity/Operational"; + + // AppLocker event path + private const string AppLockerLogPath = "Microsoft-Windows-AppLocker/MSI and Script"; + + // Get the drive letter mappings + private static readonly List DriveLettersGlobalRootFix = DriveLetterMapper.GetGlobalRootDrives(); + + // Get "OSDrive:\Windows\System32" string + private static readonly string FullSystem32Path = Environment.SystemDirectory; + + + /// + /// Retrieves the Code Integrity events from the local and EVTX files + /// + /// + private static HashSet CodeIntegrityEventsRetriever(string? EvtxFilePath = null) + { + + // HashSet to store the output, ensures the data are unique and are time-prioritized + FileIdentityTimeBasedHashSet fileIdentities = new(); + + // query xPath for the following Code Integrity event IDs: + // 3076 - Audit + // 3077 - Block + // 3089 - Correlated + string query = "*[System[(EventID=3076 or EventID=3077 or EventID=3089)]]"; + + EventLogQuery eventQuery; + + + if (EvtxFilePath is null) + { + // Initialize the EventLogQuery with the log path and query + eventQuery = new(CodeIntegrityLogPath, PathType.LogName, query); + } + else + { + // Initialize the EventLogQuery with the input evtx log path and query + eventQuery = new(EvtxFilePath, PathType.FilePath, query); + } + + + // Use EventLogReader to read the events + List rawEvents = []; + + // Read the events from the system based on the query + using (EventLogReader logReader = new(eventQuery)) + { + EventRecord eventRecord; + + // Read each event that matches the query + while ((eventRecord = logReader.ReadEvent()) is not null) + { + // Add the event to the list + rawEvents.Add(eventRecord); + } + } + + // Make sure there are events to process + if (rawEvents.Count == 0) + { + Logger.Write("No Code Integrity logs found"); + return fileIdentities.FileIdentitiesInternal; + } + + // Group all events based on their ActivityId property + IEnumerable> groupedEvents = rawEvents.GroupBy(e => e.ActivityId); + + // Iterate over each group of events + foreach (IGrouping group in groupedEvents) + { + // Access the ActivityId for the group (key) + Guid? activityId = group.Key; + + if (activityId is null) + { + continue; + } + + // There are either blocked or audit events in each group + // If there are more than 1 of either block or audit events, selecting the first one because that means the same event was triggered by multiple deployed policies + + // Get the possible audit event in the group + EventRecord? possibleAuditEvent = group.FirstOrDefault(g => g.Id == 3076); + // Get the possible blocked event + EventRecord? possibleBlockEvent = group.FirstOrDefault(g => g.Id == 3077); + // Get the possible correlated data + IEnumerable? correlatedEvents = group.Where(g => g.Id == 3089); + + + // If the current group belongs to an Audit event + if (possibleAuditEvent is not null) + { + // Use the ToXml method of the EventRecord to convert the entire event to XML but as string + string xmlString = possibleAuditEvent.ToXml(); + + // Create an XmlDocument and load the XML string, convert it to XML document + XmlDocument xmlDocument = new(); + xmlDocument.LoadXml(xmlString); + + // Create a namespace manager for the XML document + XmlNamespaceManager namespaceManager = new(xmlDocument.NameTable); + namespaceManager.AddNamespace("evt", "http://schemas.microsoft.com/win/2004/08/events/event"); + + #region Get File name and fix file path + string? FilePath = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='File Name']"); + + string? FileName = null; + + if (FilePath is not null) + { + + // Sometimes the file name begins with System32 so we prepend the Windows directory to create a full resolvable path + if (FilePath.StartsWith("System32", StringComparison.OrdinalIgnoreCase)) + { + FilePath = FilePath.Replace("System32", FullSystem32Path, StringComparison.OrdinalIgnoreCase); + } + else + { + // Only attempt to resolve path using current system's drives if EVTX files are not being used since they can be from other systems + if (EvtxFilePath is null) + { + FilePath = ResolvePath(FilePath); + } + } + + // Doesn't matter if the file exists or not + FileName = Path.GetFileName(FilePath); + } + #endregion + + + // This increases the processing time a LOT + /* + #region Resolve UserID + string? UserIDString = null; + + if (possibleAuditEvent.UserId is not null) + { + try + { + // If the user account SID doesn't exist on the system it'll throw error + UserIDString = possibleAuditEvent.UserId.Translate(typeof(NTAccount)).Value; + } + catch + { + UserIDString = possibleAuditEvent.UserId?.ToString(); + } + } + #endregion + */ + + + // Make sure the file has SHA256 Hash + string? SHA256Hash = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='SHA256 Hash']"); + + if (SHA256Hash is null) + { + continue; + } + + // Extract values using XPath + FileIdentity eventData = new() + { + // These don't require to be retrieved from XML, they are part of the node/section + Origin = FileIdentityOrigin.EventLog, + Action = EventAction.Audit, + EventID = possibleAuditEvent.Id, + TimeCreated = possibleAuditEvent.TimeCreated, + ComputerName = possibleAuditEvent.MachineName, + UserID = possibleAuditEvent.UserId?.ToString(), + + // Need to be retrieved from the XML because they are part of the node of the Event, otherwise their property names wouldn't be available + FilePath = FilePath, + FileName = FileName, + ProcessName = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='Process Name']"), + RequestedSigningLevel = GetValidatedRequestedSigningLevel(GetIntValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='Requested Signing Level']")), + ValidatedSigningLevel = GetValidatedRequestedSigningLevel(GetIntValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='Validated Signing Level']")), + Status = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='Status']"), + SHA1Hash = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='SHA1 Hash']"), + SHA256Hash = SHA256Hash, + SHA1FlatHash = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='SHA1 Flat Hash']"), + SHA256FlatHash = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='SHA256 Flat Hash']"), + USN = GetLongValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='USN']"), + SISigningScenario = GetIntValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='SI Signing Scenario']") ?? 1, + PolicyName = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='PolicyName']"), + PolicyID = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='PolicyID']"), + PolicyHash = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='PolicyHash']"), + OriginalFileName = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='OriginalFileName']"), + InternalName = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='InternalName']"), + FileDescription = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='FileDescription']"), + ProductName = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='ProductName']"), + PolicyGUID = GetGuidValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='PolicyGUID']"), + UserWriteable = GetBooleanValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='UserWriteable']"), + PackageFamilyName = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='PackageFamilyName']") + }; + + // Set the FileVersion using helper method + string? fileVersionString = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='FileVersion']"); + eventData.FileVersion = SetFileVersion(fileVersionString); + + + // If there are correlated events - for signer information of the file + if (correlatedEvents is not null) + { + + // Iterate over each correlated event - files can have multiple signers + foreach (EventRecord correlatedEvent in correlatedEvents) + { + // Use the ToXml method of the EventRecord to convert the entire event to XML but as string + string xmlStringCore = correlatedEvent.ToXml(); + + // Create an XmlDocument and load the XML string, convert it to XML document + XmlDocument xmlDocumentCore = new(); + xmlDocumentCore.LoadXml(xmlStringCore); + + // Create a namespace manager for the XML document + XmlNamespaceManager namespaceManagerCore = new(xmlDocumentCore.NameTable); + namespaceManagerCore.AddNamespace("evt", "http://schemas.microsoft.com/win/2004/08/events/event"); + + + // Skip signers that don't have PublisherTBSHash (aka LeafCertificate TBS Hash) or PublisherName + // They have "Unknown" as their IssuerName and PublisherName too + // Leaf certificate is a must have for signed files + string? PublisherTBSHash = GetStringValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='PublisherTBSHash']"); + + string? PublisherName = GetStringValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='PublisherName']"); + + if (PublisherTBSHash is null || PublisherName is null) + { + continue; + } + + // Extract values using XPath + FileSignerInfo signerInfo = new() + { + + TotalSignatureCount = GetIntValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='TotalSignatureCount']"), + Signature = GetIntValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='Signature']"), + Hash = GetStringValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='Hash']"), + PageHash = GetBooleanValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='PageHash']"), + SignatureType = GetSignatureType(GetIntValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='SignatureType']")), + ValidatedSigningLevel = GetValidatedRequestedSigningLevel(GetIntValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='ValidatedSigningLevel']")), + VerificationError = GetVerificationError(GetIntValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='VerificationError']")), + Flags = GetIntValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='Flags']"), + NotValidBefore = GetEventDataDateTimeValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='NotValidBefore']"), + NotValidAfter = GetEventDataDateTimeValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='NotValidAfter']"), + PublisherName = PublisherName, + IssuerName = GetStringValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='IssuerName']"), + PublisherTBSHash = PublisherTBSHash, + IssuerTBSHash = GetStringValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='IssuerTBSHash']"), + OPUSInfo = GetStringValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='OPUSInfo']"), + EKUs = GetStringValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='EKUs']"), + KnownRoot = GetIntValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='KnownRoot']") + }; + + + // Add the CN of the current signer to the FilePublishers HashSet of the FileIdentity + _ = eventData.FilePublishers.Add(PublisherName); + + + // Add the current signer info/correlated event data to the main event package + _ = eventData.FileSignerInfos.Add(signerInfo); + } + } + + + // Set the SignatureStatus based on the number of signers + eventData.SignatureStatus = eventData.FileSignerInfos.Count > 0 ? SignatureStatus.Signed : SignatureStatus.Unsigned; + + + // Add the entire event package to the output list + _ = fileIdentities.Add(eventData); + + + continue; + } + + // If the current group belongs to an Audit event + if (possibleBlockEvent is not null) + { + + // Use the ToXml method of the EventRecord to convert the entire event to XML but as string + string xmlString = possibleBlockEvent.ToXml(); + + // Create an XmlDocument and load the XML string, convert it to XML document + XmlDocument xmlDocument = new(); + xmlDocument.LoadXml(xmlString); + + // Create a namespace manager for the XML document + XmlNamespaceManager namespaceManager = new(xmlDocument.NameTable); + namespaceManager.AddNamespace("evt", "http://schemas.microsoft.com/win/2004/08/events/event"); + + #region Get File name and fix file path + string? FilePath = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='File Name']"); + + string? FileName = null; + + if (FilePath is not null) + { + + // Sometimes the file name begins with System32 so we prepend the Windows directory to create a full resolvable path + if (FilePath.StartsWith("System32", StringComparison.OrdinalIgnoreCase)) + { + FilePath = FilePath.Replace("System32", FullSystem32Path, StringComparison.OrdinalIgnoreCase); + } + else + { + // Only attempt to resolve path using current system's drives if EVTX files are not being used since they can be from other systems + if (EvtxFilePath is null) + { + FilePath = ResolvePath(FilePath); + } + } + + // Doesn't matter if the file exists or not + FileName = Path.GetFileName(FilePath); + } + #endregion + + + /* + #region Resolve UserID + string? UserIDString = null; + + if (possibleBlockEvent.UserId is not null) + { + try + { + // If the user account SID doesn't exist on the system it'll throw error + UserIDString = possibleBlockEvent.UserId.Translate(typeof(NTAccount)).Value; + } + catch + { + UserIDString = possibleBlockEvent.UserId?.ToString(); + } + } + #endregion + */ + + + // Make sure the file has SHA256 Hash + string? SHA256Hash = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='SHA256 Hash']"); + + if (SHA256Hash is null) + { + continue; + } + + + // Extract values using XPath + FileIdentity eventData = new() + { + // These don't require to be retrieved from XML, they are part of the node/section + Origin = FileIdentityOrigin.EventLog, + Action = EventAction.Block, + EventID = possibleBlockEvent.Id, + TimeCreated = possibleBlockEvent.TimeCreated, + ComputerName = possibleBlockEvent.MachineName, + UserID = possibleBlockEvent.UserId?.ToString(), + + // Need to be retrieved from the XML because they are part of the node of the Event, otherwise their property names wouldn't be available + FilePath = FilePath, + FileName = FileName, + ProcessName = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='Process Name']"), + RequestedSigningLevel = GetValidatedRequestedSigningLevel(GetIntValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='Requested Signing Level']")), + ValidatedSigningLevel = GetValidatedRequestedSigningLevel(GetIntValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='Validated Signing Level']")), + Status = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='Status']"), + SHA1Hash = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='SHA1 Hash']"), + SHA256Hash = SHA256Hash, + SHA1FlatHash = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='SHA1 Flat Hash']"), + SHA256FlatHash = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='SHA256 Flat Hash']"), + USN = GetLongValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='USN']"), + SISigningScenario = GetIntValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='SI Signing Scenario']") ?? 1, + PolicyName = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='PolicyName']"), + PolicyID = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='PolicyID']"), + PolicyHash = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='PolicyHash']"), + OriginalFileName = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='OriginalFileName']"), + InternalName = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='InternalName']"), + FileDescription = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='FileDescription']"), + ProductName = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='ProductName']"), + PolicyGUID = GetGuidValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='PolicyGUID']"), + UserWriteable = GetBooleanValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='UserWriteable']"), + PackageFamilyName = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='PackageFamilyName']") + }; + + // Safely set the FileVersion using helper method + string? fileVersionString = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='FileVersion']"); + eventData.FileVersion = SetFileVersion(fileVersionString); + + + // If there are correlated events - for signer information of the file + if (correlatedEvents is not null) + { + + // Iterate over each correlated event - files can have multiple signers + foreach (EventRecord correlatedEvent in correlatedEvents) + { + + // Use the ToXml method of the EventRecord to convert the entire event to XML but as string + string xmlStringCore = correlatedEvent.ToXml(); + + // Create an XmlDocument and load the XML string, convert it to XML document + XmlDocument xmlDocumentCore = new(); + xmlDocumentCore.LoadXml(xmlStringCore); + + // Create a namespace manager for the XML document + XmlNamespaceManager namespaceManagerCore = new(xmlDocumentCore.NameTable); + namespaceManagerCore.AddNamespace("evt", "http://schemas.microsoft.com/win/2004/08/events/event"); + + + // Skip signers that don't have PublisherTBSHash (aka LeafCertificate TBS Hash) + // They have "Unknown" as their IssuerName and PublisherName too + // Leaf certificate is a must have for signed files + string? PublisherTBSHash = GetStringValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='PublisherTBSHash']"); + + string? PublisherName = GetStringValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='PublisherName']"); + + if (PublisherTBSHash is null || PublisherName is null) + { + continue; + } + + + // Extract values using XPath + FileSignerInfo signerInfo = new() + { + + TotalSignatureCount = GetIntValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='TotalSignatureCount']"), + Signature = GetIntValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='Signature']"), + Hash = GetStringValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='Hash']"), + PageHash = GetBooleanValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='PageHash']"), + SignatureType = GetSignatureType(GetIntValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='SignatureType']")), + ValidatedSigningLevel = GetValidatedRequestedSigningLevel(GetIntValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='ValidatedSigningLevel']")), + VerificationError = GetVerificationError(GetIntValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='VerificationError']")), + Flags = GetIntValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='Flags']"), + NotValidBefore = GetEventDataDateTimeValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='NotValidBefore']"), + NotValidAfter = GetEventDataDateTimeValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='NotValidAfter']"), + PublisherName = PublisherName, + IssuerName = GetStringValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='IssuerName']"), + PublisherTBSHash = PublisherTBSHash, + IssuerTBSHash = GetStringValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='IssuerTBSHash']"), + OPUSInfo = GetStringValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='OPUSInfo']"), + EKUs = GetStringValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='EKUs']"), + KnownRoot = GetIntValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='KnownRoot']") + }; + + + // Add the CN of the current signer to the FilePublishers HashSet of the FileIdentity + _ = eventData.FilePublishers.Add(PublisherName); + + // Add the current signer info/correlated event data to the main event package + _ = eventData.FileSignerInfos.Add(signerInfo); + + + } + } + + // Set the SignatureStatus based on the number of signers + eventData.SignatureStatus = eventData.FileSignerInfos.Count > 0 ? SignatureStatus.Signed : SignatureStatus.Unsigned; + + // Add the populated EventData instance to the list + _ = fileIdentities.Add(eventData); + + + continue; + } + } + + Logger.Write($"Total Code Integrity logs: {fileIdentities.Count}"); + + // Return the output + return fileIdentities.FileIdentitiesInternal; + } + + + + + + + + /// + /// Retrieves the AppLocker events from the local and EVTX files + /// + /// + private static HashSet AppLockerEventsRetriever(string? EvtxFilePath = null) + { + + // HashSet to store the output, ensures the data are unique + FileIdentityTimeBasedHashSet fileIdentities = new(); + + // query xPath for the following AppLocker event IDs: + // 8028 - Audit + // 8029 - Block + // 8038 - Correlated + string query = "*[System[(EventID=8028 or EventID=8029 or EventID=8038)]]"; + + + EventLogQuery eventQuery; + + + if (EvtxFilePath is null) + { + // Initialize the EventLogQuery with the log path and query + eventQuery = new(AppLockerLogPath, PathType.LogName, query); + } + else + { + // Initialize the EventLogQuery with the input EVTX log path and query + eventQuery = new(EvtxFilePath, PathType.FilePath, query); + } + + + // Use EventLogReader to read the events + List rawEvents = []; + + // Read the events from the system based on the query + using (EventLogReader logReader = new(eventQuery)) + { + EventRecord eventRecord; + + // Read each event that matches the query + while ((eventRecord = logReader.ReadEvent()) is not null) + { + // Add the event to the list + rawEvents.Add(eventRecord); + } + } + + // Make sure there are events to process + if (rawEvents.Count == 0) + { + Logger.Write("No AppLocker events found."); + return fileIdentities.FileIdentitiesInternal; + } + + // Group all events based on their ActivityId property + IEnumerable> groupedEvents = rawEvents.GroupBy(e => e.ActivityId); + + // Iterate over each group of events + foreach (IGrouping group in groupedEvents) + { + // Access the ActivityId for the group (key) + Guid? activityId = group.Key; + + if (activityId is null) + { + continue; + } + + // There are either blocked or audit events in each group + // If there are more than 1 of either block or audit events, selecting the first one because that means the same event was triggered by multiple deployed policies + + // Get the possible audit event in the group + EventRecord? possibleAuditEvent = group.FirstOrDefault(g => g.Id == 8028); + // Get the possible blocked event + EventRecord? possibleBlockEvent = group.FirstOrDefault(g => g.Id == 8029); + // Get the possible correlated data + IEnumerable? correlatedEvents = group.Where(g => g.Id == 8038); + + + // If the current group belongs to an Audit event + if (possibleAuditEvent is not null) + { + // Use the ToXml method of the EventRecord to convert the entire event to XML but as string + string xmlString = possibleAuditEvent.ToXml(); + + // Create an XmlDocument and load the XML string, convert it to XML document + XmlDocument xmlDocument = new(); + xmlDocument.LoadXml(xmlString); + + // Create a namespace manager for the XML document + XmlNamespaceManager namespaceManager = new(xmlDocument.NameTable); + namespaceManager.AddNamespace("evt", "http://schemas.microsoft.com/win/2004/08/events/event"); + + + #region Get File name - the file path doesn't need fixing like Code integrity ones + string? FilePath = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='FilePath']"); + + string? FileName = null; + + if (FilePath is not null) + { + + // Sometimes the file name begins with System32 so we prepend the Windows directory to create a full resolvable path + if (FilePath.StartsWith("System32", StringComparison.OrdinalIgnoreCase)) + { + FilePath = FilePath.Replace("System32", FullSystem32Path, StringComparison.OrdinalIgnoreCase); + } + + // Doesn't matter if the file exists or not + FileName = Path.GetFileName(FilePath); + } + #endregion + + + /* + #region Resolve UserID + string? UserIDString = null; + + if (possibleAuditEvent.UserId is not null) + { + try + { + // If the user account SID doesn't exist on the system it'll throw error + UserIDString = possibleAuditEvent.UserId.Translate(typeof(NTAccount)).Value; + } + catch + { + UserIDString = possibleAuditEvent.UserId?.ToString(); + } + } + #endregion + */ + + + // Make sure the file has SHA256 Hash + string? SHA256Hash = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='SHA256 Hash']"); + + if (SHA256Hash is null) + { + continue; + } + + + // Extract values using XPath + FileIdentity eventData = new() + { + // These don't require to be retrieved from XML, they are part of the node/section + Origin = FileIdentityOrigin.EventLog, + Action = EventAction.Block, + EventID = possibleAuditEvent.Id, + TimeCreated = possibleAuditEvent.TimeCreated, + ComputerName = possibleAuditEvent.MachineName, + UserID = possibleAuditEvent.UserId?.ToString(), + + // Need to be retrieved from the XML because they are part of the node of the Event, otherwise their property names wouldn't be available + FilePath = FilePath, + FileName = FileName, + SHA1Hash = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='Sha1Hash']"), + SHA256Hash = SHA256Hash, + USN = GetLongValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='USN']"), + SISigningScenario = 1, // AppLocker doesn't apply to Kernel mode files, so all of these logs have Signing Scenario 1 for User-Mode files + OriginalFileName = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='OriginalFilename']"), + InternalName = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='InternalName']"), + FileDescription = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='FileDescription']"), + ProductName = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='ProductName']"), + UserWriteable = GetBooleanValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='UserWriteable']") + }; + + // Safely set the FileVersion using helper method + string? fileVersionString = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='FileVersion']"); + eventData.FileVersion = SetFileVersion(fileVersionString); + + + // If there are correlated events - for signer information of the file + if (correlatedEvents is not null) + { + + // Iterate over each correlated event - files can have multiple signers + foreach (EventRecord correlatedEvent in correlatedEvents) + { + // Use the ToXml method of the EventRecord to convert the entire event to XML but as string + string xmlStringCore = correlatedEvent.ToXml(); + + // Create an XmlDocument and load the XML string, convert it to XML document + XmlDocument xmlDocumentCore = new(); + xmlDocumentCore.LoadXml(xmlStringCore); + + // Create a namespace manager for the XML document + XmlNamespaceManager namespaceManagerCore = new(xmlDocumentCore.NameTable); + namespaceManagerCore.AddNamespace("evt", "http://schemas.microsoft.com/win/2004/08/events/event"); + + + // Skip signers that don't have PublisherTBSHash (aka LeafCertificate TBS Hash) + // They have "Unknown" as their IssuerName and PublisherName too + // Leaf certificate is a must have for signed files + string? PublisherTBSHash = GetStringValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='PublisherTBSHash']"); + + string? PublisherName = GetStringValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='PublisherName']"); + + if (PublisherTBSHash is null || PublisherName is null) + { + continue; + } + + // Extract values using XPath + FileSignerInfo signerInfo = new() + { + TotalSignatureCount = GetIntValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='TotalSignatureCount']"), + Signature = GetIntValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='Signature']"), + PublisherName = PublisherName, + IssuerName = GetStringValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='IssuerName']"), + PublisherTBSHash = PublisherTBSHash, + IssuerTBSHash = GetStringValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='IssuerTBSHash']"), + }; + + // Add the CN of the current signer to the FilePublishers HashSet of the FileIdentity + _ = eventData.FilePublishers.Add(PublisherName); + + // Add the current signer info/correlated event data to the main event package + _ = eventData.FileSignerInfos.Add(signerInfo); + + } + } + + + // Set the SignatureStatus based on the number of signers + eventData.SignatureStatus = eventData.FileSignerInfos.Count > 0 ? SignatureStatus.Signed : SignatureStatus.Unsigned; + + + // Add the entire event package to the output list + _ = fileIdentities.Add(eventData); + + + continue; + } + + // If the current group belongs to an Audit event + if (possibleBlockEvent is not null) + { + + // Use the ToXml method of the EventRecord to convert the entire event to XML but as string + string xmlString = possibleBlockEvent.ToXml(); + + // Create an XmlDocument and load the XML string, convert it to XML document + XmlDocument xmlDocument = new(); + xmlDocument.LoadXml(xmlString); + + // Create a namespace manager for the XML document + XmlNamespaceManager namespaceManager = new(xmlDocument.NameTable); + namespaceManager.AddNamespace("evt", "http://schemas.microsoft.com/win/2004/08/events/event"); + + + #region Get File name - the file path doesn't need fixing like Code integrity ones + string? FilePath = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='FilePath']"); + + string? FileName = null; + + if (FilePath is not null) + { + + // Sometimes the file name begins with System32 so we prepend the Windows directory to create a full resolvable path + if (FilePath.StartsWith("System32", StringComparison.OrdinalIgnoreCase)) + { + FilePath = FilePath.Replace("System32", FullSystem32Path, StringComparison.OrdinalIgnoreCase); + } + + // Doesn't matter if the file exists or not + FileName = Path.GetFileName(FilePath); + } + #endregion + + + /* + #region Resolve UserID + string? UserIDString = null; + + if (possibleBlockEvent.UserId is not null) + { + try + { + // If the user account SID doesn't exist on the system it'll throw error + UserIDString = possibleBlockEvent.UserId.Translate(typeof(NTAccount)).Value; + } + catch + { + UserIDString = possibleBlockEvent.UserId?.ToString(); + } + } + #endregion + */ + + + // Make sure the file has SHA256 Hash + string? SHA256Hash = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='SHA256 Hash']"); + + if (SHA256Hash is null) + { + continue; + } + + + // Extract values using XPath + FileIdentity eventData = new() + { + // These don't require to be retrieved from XML, they are part of the node/section + Origin = FileIdentityOrigin.EventLog, + Action = EventAction.Block, + EventID = possibleBlockEvent.Id, + TimeCreated = possibleBlockEvent.TimeCreated, + ComputerName = possibleBlockEvent.MachineName, + UserID = possibleBlockEvent.UserId?.ToString(), + + // Need to be retrieved from the XML because they are part of the node of the Event, otherwise their property names wouldn't be available + FilePath = FilePath, + FileName = FileName, + SHA1Hash = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='Sha1Hash']"), + SHA256Hash = SHA256Hash, + USN = GetLongValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='USN']"), + SISigningScenario = 1, // AppLocker doesn't apply to Kernel mode files, so all of these logs have Signing Scenario 1 for User-Mode files + OriginalFileName = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='OriginalFilename']"), + InternalName = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='InternalName']"), + FileDescription = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='FileDescription']"), + ProductName = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='ProductName']"), + UserWriteable = GetBooleanValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='UserWriteable']") + }; + + // Safely set the FileVersion using helper method + string? fileVersionString = GetStringValue(xmlDocument, namespaceManager, "//evt:EventData/evt:Data[@Name='FileVersion']"); + eventData.FileVersion = SetFileVersion(fileVersionString); + + + // If there are correlated events - for signer information of the file + if (correlatedEvents is not null) + { + + // Iterate over each correlated event - files can have multiple signers + foreach (EventRecord correlatedEvent in correlatedEvents) + { + + // Use the ToXml method of the EventRecord to convert the entire event to XML but as string + string xmlStringCore = correlatedEvent.ToXml(); + + // Create an XmlDocument and load the XML string, convert it to XML document + XmlDocument xmlDocumentCore = new(); + xmlDocumentCore.LoadXml(xmlStringCore); + + // Create a namespace manager for the XML document + XmlNamespaceManager namespaceManagerCore = new(xmlDocumentCore.NameTable); + namespaceManagerCore.AddNamespace("evt", "http://schemas.microsoft.com/win/2004/08/events/event"); + + + // Skip signers that don't have PublisherTBSHash (aka LeafCertificate TBS Hash) + // They have "Unknown" as their IssuerName and PublisherName too + // Leaf certificate is a must have for signed files + string? PublisherTBSHash = GetStringValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='PublisherTBSHash']"); + + string? PublisherName = GetStringValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='PublisherName']"); + + if (PublisherTBSHash is null || PublisherName is null) + { + continue; + } + + // Extract values using XPath + FileSignerInfo signerInfo = new() + { + TotalSignatureCount = GetIntValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='TotalSignatureCount']"), + Signature = GetIntValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='Signature']"), + PublisherName = PublisherName, + IssuerName = GetStringValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='IssuerName']"), + PublisherTBSHash = PublisherTBSHash, + IssuerTBSHash = GetStringValue(xmlDocumentCore, namespaceManagerCore, "//evt:EventData/evt:Data[@Name='IssuerTBSHash']"), + }; + + + // Add the CN of the current signer to the FilePublishers HashSet of the FileIdentity + _ = eventData.FilePublishers.Add(PublisherName); + + + // Add the current signer info/correlated event data to the main event package + _ = eventData.FileSignerInfos.Add(signerInfo); + + + } + } + + // Set the SignatureStatus based on the number of signers + eventData.SignatureStatus = eventData.FileSignerInfos.Count > 0 ? SignatureStatus.Signed : SignatureStatus.Unsigned; + + // Add the populated EventData instance to the list + _ = fileIdentities.Add(eventData); + + + continue; + } + } + + Logger.Write($"Total AppLocker logs: {fileIdentities.Count}"); + + // Return the output + return fileIdentities.FileIdentitiesInternal; + } + + + + + #region Helper methods to extract values + + /// + /// Method to safely set FileVersion from a nullable string + /// + /// + /// + private static Version? SetFileVersion(string? versionString) + { + if (!string.IsNullOrWhiteSpace(versionString) && Version.TryParse(versionString, out Version? version)) + { + return version; + } + return null; + } + + /// + /// Method to safely get an integer value from the XML document + /// + /// + /// + /// + /// + private static int? GetIntValue(XmlDocument xmlDoc, XmlNamespaceManager nsManager, string xpath) + { + XmlNode? node = xmlDoc.SelectSingleNode(xpath, nsManager); + return node is not null && int.TryParse(node.InnerText, NumberStyles.Integer, CultureInfo.InvariantCulture, out int result) ? result : null; + } + + + + /// + /// Only works for the node of the Event + /// + private static DateTime? GetEventDataDateTimeValue(XmlDocument xmlDoc, XmlNamespaceManager nsManager, string xpath) + { + XmlNode? node = xmlDoc.SelectSingleNode(xpath, nsManager); + return node is not null && DateTime.TryParse(node.InnerText, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out DateTime result) ? result : null; + } + + + /// + /// Returns null if the string is null, empty or whitespaces + /// + private static string? GetStringValue(XmlDocument xmlDoc, XmlNamespaceManager nsManager, string xpath) + { + XmlNode? node = xmlDoc.SelectSingleNode(xpath, nsManager); + return string.IsNullOrWhiteSpace(node?.InnerText) ? null : node?.InnerText; + } + + private static long? GetLongValue(XmlDocument xmlDoc, XmlNamespaceManager nsManager, string xpath) + { + XmlNode? node = xmlDoc.SelectSingleNode(xpath, nsManager); + return node is not null && long.TryParse(node.InnerText, NumberStyles.Integer, CultureInfo.InvariantCulture, out long result) ? result : null; + } + + private static Guid? GetGuidValue(XmlDocument xmlDoc, XmlNamespaceManager nsManager, string xpath) + { + XmlNode? node = xmlDoc.SelectSingleNode(xpath, nsManager); + return node is not null && Guid.TryParse(node.InnerText, out Guid guid) ? guid : null; + } + + private static bool? GetBooleanValue(XmlDocument xmlDoc, XmlNamespaceManager nsManager, string xpath) + { + XmlNode? node = xmlDoc.SelectSingleNode(xpath, nsManager); + return node is not null && bool.TryParse(node.InnerText, out bool result) ? result : null; + } + + + /// + /// Resolves the Validated/Requested Signing Level int to friendly string + /// + /// + /// + private static string? GetValidatedRequestedSigningLevel(int? SigningLevelInt) + { + if (SigningLevelInt.HasValue) + { + _ = CILogIntel.ReqValSigningLevels.TryGetValue(SigningLevelInt.Value, out string? SigningLevel); + + return SigningLevel; + } + else + { + return null; + } + } + + + /// + /// Resolves the VerificationError int to a friendly string + /// + /// + /// + private static string? GetVerificationError(int? VerificationError) + { + if (VerificationError.HasValue) + { + _ = CILogIntel.VerificationErrorTable.TryGetValue(VerificationError.Value, out string? VerificationErrorString); + return VerificationErrorString; + } + else + { + return null; + } + } + + + /// + /// Resolves the SignatureType int to a friendly string + /// + /// + /// + private static string? GetSignatureType(int? SignatureType) + { + if (SignatureType.HasValue) + { + _ = CILogIntel.SignatureTypeTable.TryGetValue(SignatureType.Value, out string? SignatureTypeString); + return SignatureTypeString; + } + else + { + return null; + } + } + + + /// + /// Replaces global root NT paths to the normal paths + /// + /// + /// + private static string ResolvePath(string path) + { + // Find the matching DriveMapping for the device path prefix + foreach (DriveLetterMapper.DriveMapping mapping in DriveLettersGlobalRootFix) + { + if (mapping.DevicePath is not null && path.StartsWith(mapping.DevicePath, StringComparison.OrdinalIgnoreCase)) + { + // Replace the device path with the corresponding drive letter + return path.Replace(mapping.DevicePath, mapping.DriveLetter, StringComparison.OrdinalIgnoreCase); + } + } + + // If no mapping was found, return the original path + return path; + } + + #endregion + + + + + #region Async processing + + /// + /// Gets Code Integrity and AppLocker event logs Asynchronously + /// + /// + public static async Task> GetAppControlEvents(string? CodeIntegrityEvtxFilePath = null, string? AppLockerEvtxFilePath = null, int EventsToCapture = 0) + { + // Output + HashSet combinedResult = []; + + + if (EventsToCapture == 0) + { + + // Start both tasks in parallel + Task> codeIntegrityTask = Task.Run(() => CodeIntegrityEventsRetriever(CodeIntegrityEvtxFilePath)); + Task> appLockerTask = Task.Run(() => AppLockerEventsRetriever(AppLockerEvtxFilePath)); + + // Await both tasks to complete + _ = await Task.WhenAll(codeIntegrityTask, appLockerTask); + + // Keep the Code Integrity task's HashSet output since it's the main category and will have the majority of the events + combinedResult = codeIntegrityTask.Result; + + // If there are AppLocker logs + if (appLockerTask.Result.Count > 0) + { + + // Add elements from the AppLocker task's result, using Add to preserve uniqueness since the HashSet has its custom comparer + foreach (FileIdentity item in appLockerTask.Result) + { + _ = combinedResult.Add(item); + } + } + + } + + else if (EventsToCapture == 1) + { + // Only starts the Code integrity events capture task + combinedResult = await Task.Run(() => CodeIntegrityEventsRetriever(CodeIntegrityEvtxFilePath)); + } + + else if (EventsToCapture == 2) + { + // Only starts the AppLocker events capture task + combinedResult = await Task.Run(() => AppLockerEventsRetriever(AppLockerEvtxFilePath)); + } + + + Logger.Write($"Total logs count: {combinedResult.Count}"); + + // Return the combined set + return combinedResult; + } + + + #endregion + + + } + +} diff --git a/AppControl Manager/Shared Logics/IntelGathering/GetMDEAdvancedHuntingLogsData.cs b/AppControl Manager/Shared Logics/IntelGathering/GetMDEAdvancedHuntingLogsData.cs new file mode 100644 index 000000000..3ce2d8846 --- /dev/null +++ b/AppControl Manager/Shared Logics/IntelGathering/GetMDEAdvancedHuntingLogsData.cs @@ -0,0 +1,616 @@ + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + + +#nullable enable + +namespace WDACConfig.IntelGathering +{ + internal static class GetMDEAdvancedHuntingLogsData + { + + /// + /// Finds the correlated events in the CSV data and groups them together based on the EtwActivityId. + /// Ensures that each Audit or Blocked event has its correlated Signing information events grouped together. + /// CodeIntegrity and AppLocker logs are considered separately in each group of EtwActivityId. + /// + /// + /// + internal static HashSet Retrieve(List data) + { + + + // HashSet to store the output, ensures the data are unique and signed data are prioritized over unsigned data + FileIdentitySignatureBasedHashSet fileIdentities = new(); + + + // Group the events based on the EtwActivityId, which is the unique identifier for each group of correlated events + IEnumerable> groupedEvents = data.GroupBy(e => e.EtwActivityId!); + + + // Iterate over each group of logs + foreach (IGrouping group in groupedEvents) + { + // Access the EtwActivityId for the group (key) + string? EtwActivityId = group.Key; + + if (EtwActivityId is null) + { + continue; + } + + // There are either blocked or audit type events in each group and they can be CodeIntegrity and AppLocker type at the same time + // If there are more than 1 of either block or audit events, selecting the first one because that means the same event was triggered by multiple deployed policies + + // Get the possible CodeIntegrity audit event in the group + MDEAdvancedHuntingData? possibleCodeIntegrityAuditEvent = group.FirstOrDefault(g => string.Equals(g.ActionType, "AppControlCodeIntegrityPolicyAudited", StringComparison.OrdinalIgnoreCase)); + // Get the possible CodeIntegrity blocked event in the group + MDEAdvancedHuntingData? possibleCodeIntegrityBlockEvent = group.FirstOrDefault(g => string.Equals(g.ActionType, "AppControlCodeIntegrityPolicyBlocked", StringComparison.OrdinalIgnoreCase)); + + // Get the possible AppLocker audit event in the group + MDEAdvancedHuntingData? possibleAppLockerAuditEvent = group.FirstOrDefault(g => string.Equals(g.ActionType, "AppControlCIScriptAudited", StringComparison.OrdinalIgnoreCase)); + // Get the possible AppLocker blocked event in the group + MDEAdvancedHuntingData? possibleAppLockerBlockEvent = group.FirstOrDefault(g => string.Equals(g.ActionType, "AppControlCIScriptBlocked", StringComparison.OrdinalIgnoreCase)); + + // Get the possible correlated data + List correlatedEvents = group.Where(g => string.Equals(g.ActionType, "AppControlCodeIntegritySigningInformation", StringComparison.OrdinalIgnoreCase)).ToList(); + + + // The SHA256 must be available in Audit/Block type of events for either Code Integrity or AppLocker + // It doesn't need to exist in the correlated SigningInformation event for MDE Advanced Hunting + + + // If the current group has Code Integrity Audit log + if (possibleCodeIntegrityAuditEvent is not null) + { + + if (possibleCodeIntegrityAuditEvent.SHA256 is null) + { + continue; + } + + + // Extract values using XPath + FileIdentity eventData = new() + { + + Origin = FileIdentityOrigin.MDEAdvancedHunting, + Action = EventAction.Audit, + TimeCreated = GetEventDataDateTimeValue(possibleCodeIntegrityAuditEvent.Timestamp), + ComputerName = possibleCodeIntegrityAuditEvent.DeviceName, + UserID = possibleCodeIntegrityAuditEvent.InitiatingProcessAccountName, + + FilePath = possibleCodeIntegrityAuditEvent.FolderPath, + FileName = possibleCodeIntegrityAuditEvent.FileName, + ProcessName = possibleCodeIntegrityAuditEvent.ProcessName, + RequestedSigningLevel = GetValidatedRequestedSigningLevel(GetIntValue(possibleCodeIntegrityAuditEvent.RequestedSigningLevel)), + ValidatedSigningLevel = GetValidatedRequestedSigningLevel(GetIntValue(possibleCodeIntegrityAuditEvent.ValidatedSigningLevel)), + Status = possibleCodeIntegrityAuditEvent.StatusCode, + SHA1Hash = possibleCodeIntegrityAuditEvent.SHA1, + SHA256Hash = possibleCodeIntegrityAuditEvent.SHA256, + SHA1FlatHash = possibleCodeIntegrityAuditEvent.Sha1FlatHash, + SHA256FlatHash = possibleCodeIntegrityAuditEvent.Sha256FlatHash, + USN = GetLongValue(possibleCodeIntegrityAuditEvent.USN), + SISigningScenario = GetIntValue(possibleCodeIntegrityAuditEvent.SiSigningScenario) ?? 1, + PolicyName = possibleCodeIntegrityAuditEvent.PolicyName, + PolicyID = possibleCodeIntegrityAuditEvent.PolicyID, + PolicyHash = possibleCodeIntegrityAuditEvent.PolicyHash, + OriginalFileName = possibleCodeIntegrityAuditEvent.OriginalFileName, + InternalName = possibleCodeIntegrityAuditEvent.InternalName, + FileDescription = possibleCodeIntegrityAuditEvent.FileDescription, + PolicyGUID = GetGuidValue(possibleCodeIntegrityAuditEvent.PolicyGuid), + UserWriteable = possibleCodeIntegrityAuditEvent.UserWriteable + }; + + // Set the FileVersion using helper method + string? fileVersionString = possibleCodeIntegrityAuditEvent.FileVersion; + eventData.FileVersion = SetFileVersion(fileVersionString); + + + // If there are correlated events - for signer information of the file + if (correlatedEvents.Count > 0) + { + + // Iterate over each correlated event - files can have multiple signers + foreach (MDEAdvancedHuntingData correlatedEvent in correlatedEvents) + { + + // Skip signers that don't have PublisherTBSHash (aka LeafCertificate TBS Hash) or PublisherName + // They have "Unknown" as their IssuerName and PublisherName too + // Leaf certificate is a must have for signed files + string? PublisherTBSHash = correlatedEvent.PublisherTBSHash; + + string? PublisherName = correlatedEvent.PublisherName; + + if (PublisherTBSHash is null || PublisherName is null) + { + continue; + } + + // Extract values using XPath + FileSignerInfo signerInfo = new() + { + + TotalSignatureCount = GetIntValue(correlatedEvent.TotalSignatureCount), + Signature = GetIntValue(correlatedEvent.Signature), + Hash = correlatedEvent.Hash, + SignatureType = GetSignatureType(GetIntValue(correlatedEvent.SignatureType)), + ValidatedSigningLevel = GetValidatedRequestedSigningLevel(GetIntValue(correlatedEvent.ValidatedSigningLevel)), + VerificationError = GetVerificationError(GetIntValue(correlatedEvent.VerificationError)), + Flags = GetIntValue(correlatedEvent.Flags), + NotValidBefore = GetEventDataDateTimeValue(correlatedEvent.NotValidBefore), + NotValidAfter = GetEventDataDateTimeValue(correlatedEvent.NotValidAfter), + PublisherName = PublisherName, + IssuerName = correlatedEvent.IssuerName, + PublisherTBSHash = PublisherTBSHash, + IssuerTBSHash = correlatedEvent.IssuerTBSHash + }; + + // Add the CN of the current signer to the FilePublishers HashSet of the FileIdentity + _ = eventData.FilePublishers.Add(PublisherName); + + // Add the current signer info/correlated event data to the main event package + _ = eventData.FileSignerInfos.Add(signerInfo); + } + } + + + // Set the SignatureStatus based on the number of signers + eventData.SignatureStatus = eventData.FileSignerInfos.Count > 0 ? SignatureStatus.Signed : SignatureStatus.Unsigned; + + + // Add the entire event package to the output list + _ = fileIdentities.Add(eventData); + + + } + + + + + + + // If the current group has Code Integrity Blocked log + else if (possibleCodeIntegrityBlockEvent is not null) + { + + if (possibleCodeIntegrityBlockEvent.SHA256 is null) + { + continue; + } + + + // Extract values using XPath + FileIdentity eventData = new() + { + + Origin = FileIdentityOrigin.MDEAdvancedHunting, + Action = EventAction.Audit, + TimeCreated = GetEventDataDateTimeValue(possibleCodeIntegrityBlockEvent.Timestamp), + ComputerName = possibleCodeIntegrityBlockEvent.DeviceName, + UserID = possibleCodeIntegrityBlockEvent.InitiatingProcessAccountName, + + FilePath = possibleCodeIntegrityBlockEvent.FolderPath, + FileName = possibleCodeIntegrityBlockEvent.FileName, + ProcessName = possibleCodeIntegrityBlockEvent.ProcessName, + RequestedSigningLevel = GetValidatedRequestedSigningLevel(GetIntValue(possibleCodeIntegrityBlockEvent.RequestedSigningLevel)), + ValidatedSigningLevel = GetValidatedRequestedSigningLevel(GetIntValue(possibleCodeIntegrityBlockEvent.ValidatedSigningLevel)), + Status = possibleCodeIntegrityBlockEvent.StatusCode, + SHA1Hash = possibleCodeIntegrityBlockEvent.SHA1, + SHA256Hash = possibleCodeIntegrityBlockEvent.SHA256, + SHA1FlatHash = possibleCodeIntegrityBlockEvent.Sha1FlatHash, + SHA256FlatHash = possibleCodeIntegrityBlockEvent.Sha256FlatHash, + USN = GetLongValue(possibleCodeIntegrityBlockEvent.USN), + SISigningScenario = GetIntValue(possibleCodeIntegrityBlockEvent.SiSigningScenario) ?? 1, + PolicyName = possibleCodeIntegrityBlockEvent.PolicyName, + PolicyID = possibleCodeIntegrityBlockEvent.PolicyID, + PolicyHash = possibleCodeIntegrityBlockEvent.PolicyHash, + OriginalFileName = possibleCodeIntegrityBlockEvent.OriginalFileName, + InternalName = possibleCodeIntegrityBlockEvent.InternalName, + FileDescription = possibleCodeIntegrityBlockEvent.FileDescription, + PolicyGUID = GetGuidValue(possibleCodeIntegrityBlockEvent.PolicyGuid), + UserWriteable = possibleCodeIntegrityBlockEvent.UserWriteable + }; + + // Set the FileVersion using helper method + string? fileVersionString = possibleCodeIntegrityBlockEvent.FileVersion; + eventData.FileVersion = SetFileVersion(fileVersionString); + + + // If there are correlated events - for signer information of the file + if (correlatedEvents.Count > 0) + { + + // Iterate over each correlated event - files can have multiple signers + foreach (MDEAdvancedHuntingData correlatedEvent in correlatedEvents) + { + + // Skip signers that don't have PublisherTBSHash (aka LeafCertificate TBS Hash) or PublisherName + // They have "Unknown" as their IssuerName and PublisherName too + // Leaf certificate is a must have for signed files + string? PublisherTBSHash = correlatedEvent.PublisherTBSHash; + + string? PublisherName = correlatedEvent.PublisherName; + + if (PublisherTBSHash is null || PublisherName is null) + { + continue; + } + + // Extract values using XPath + FileSignerInfo signerInfo = new() + { + + TotalSignatureCount = GetIntValue(correlatedEvent.TotalSignatureCount), + Signature = GetIntValue(correlatedEvent.Signature), + Hash = correlatedEvent.Hash, + SignatureType = GetSignatureType(GetIntValue(correlatedEvent.SignatureType)), + ValidatedSigningLevel = GetValidatedRequestedSigningLevel(GetIntValue(correlatedEvent.ValidatedSigningLevel)), + VerificationError = GetVerificationError(GetIntValue(correlatedEvent.VerificationError)), + Flags = GetIntValue(correlatedEvent.Flags), + NotValidBefore = GetEventDataDateTimeValue(correlatedEvent.NotValidBefore), + NotValidAfter = GetEventDataDateTimeValue(correlatedEvent.NotValidAfter), + PublisherName = PublisherName, + IssuerName = correlatedEvent.IssuerName, + PublisherTBSHash = PublisherTBSHash, + IssuerTBSHash = correlatedEvent.IssuerTBSHash + }; + + // Add the CN of the current signer to the FilePublishers HashSet of the FileIdentity + _ = eventData.FilePublishers.Add(PublisherName); + + // Add the current signer info/correlated event data to the main event package + _ = eventData.FileSignerInfos.Add(signerInfo); + } + } + + + // Set the SignatureStatus based on the number of signers + eventData.SignatureStatus = eventData.FileSignerInfos.Count > 0 ? SignatureStatus.Signed : SignatureStatus.Unsigned; + + + // Add the entire event package to the output list + _ = fileIdentities.Add(eventData); + + + } + + + // If the current group has AppLocker Audit log + if (possibleAppLockerAuditEvent is not null) + { + + + if (possibleAppLockerAuditEvent.SHA256 is null) + { + continue; + } + + + // Extract values using XPath + FileIdentity eventData = new() + { + + Origin = FileIdentityOrigin.MDEAdvancedHunting, + Action = EventAction.Audit, + TimeCreated = GetEventDataDateTimeValue(possibleAppLockerAuditEvent.Timestamp), + ComputerName = possibleAppLockerAuditEvent.DeviceName, + UserID = possibleAppLockerAuditEvent.InitiatingProcessAccountName, + + FilePath = possibleAppLockerAuditEvent.FolderPath, + FileName = possibleAppLockerAuditEvent.FileName, + ProcessName = possibleAppLockerAuditEvent.ProcessName, + RequestedSigningLevel = GetValidatedRequestedSigningLevel(GetIntValue(possibleAppLockerAuditEvent.RequestedSigningLevel)), + ValidatedSigningLevel = GetValidatedRequestedSigningLevel(GetIntValue(possibleAppLockerAuditEvent.ValidatedSigningLevel)), + Status = possibleAppLockerAuditEvent.StatusCode, + SHA1Hash = possibleAppLockerAuditEvent.SHA1, + SHA256Hash = possibleAppLockerAuditEvent.SHA256, + SHA1FlatHash = possibleAppLockerAuditEvent.Sha1FlatHash, + SHA256FlatHash = possibleAppLockerAuditEvent.Sha256FlatHash, + USN = GetLongValue(possibleAppLockerAuditEvent.USN), + SISigningScenario = GetIntValue(possibleAppLockerAuditEvent.SiSigningScenario) ?? 1, + PolicyName = possibleAppLockerAuditEvent.PolicyName, + PolicyID = possibleAppLockerAuditEvent.PolicyID, + PolicyHash = possibleAppLockerAuditEvent.PolicyHash, + OriginalFileName = possibleAppLockerAuditEvent.OriginalFileName, + InternalName = possibleAppLockerAuditEvent.InternalName, + FileDescription = possibleAppLockerAuditEvent.FileDescription, + PolicyGUID = GetGuidValue(possibleAppLockerAuditEvent.PolicyGuid), + UserWriteable = possibleAppLockerAuditEvent.UserWriteable + }; + + // Set the FileVersion using helper method + string? fileVersionString = possibleAppLockerAuditEvent.FileVersion; + eventData.FileVersion = SetFileVersion(fileVersionString); + + + // If there are correlated events - for signer information of the file + if (correlatedEvents.Count > 0) + { + + // Iterate over each correlated event - files can have multiple signers + foreach (MDEAdvancedHuntingData correlatedEvent in correlatedEvents) + { + + // Skip signers that don't have PublisherTBSHash (aka LeafCertificate TBS Hash) or PublisherName + // They have "Unknown" as their IssuerName and PublisherName too + // Leaf certificate is a must have for signed files + string? PublisherTBSHash = correlatedEvent.PublisherTBSHash; + + string? PublisherName = correlatedEvent.PublisherName; + + if (PublisherTBSHash is null || PublisherName is null) + { + continue; + } + + // Extract values using XPath + FileSignerInfo signerInfo = new() + { + + TotalSignatureCount = GetIntValue(correlatedEvent.TotalSignatureCount), + Signature = GetIntValue(correlatedEvent.Signature), + Hash = correlatedEvent.Hash, + SignatureType = GetSignatureType(GetIntValue(correlatedEvent.SignatureType)), + ValidatedSigningLevel = GetValidatedRequestedSigningLevel(GetIntValue(correlatedEvent.ValidatedSigningLevel)), + VerificationError = GetVerificationError(GetIntValue(correlatedEvent.VerificationError)), + Flags = GetIntValue(correlatedEvent.Flags), + NotValidBefore = GetEventDataDateTimeValue(correlatedEvent.NotValidBefore), + NotValidAfter = GetEventDataDateTimeValue(correlatedEvent.NotValidAfter), + PublisherName = PublisherName, + IssuerName = correlatedEvent.IssuerName, + PublisherTBSHash = PublisherTBSHash, + IssuerTBSHash = correlatedEvent.IssuerTBSHash + }; + + // Add the CN of the current signer to the FilePublishers HashSet of the FileIdentity + _ = eventData.FilePublishers.Add(PublisherName); + + // Add the current signer info/correlated event data to the main event package + _ = eventData.FileSignerInfos.Add(signerInfo); + } + } + + + // Set the SignatureStatus based on the number of signers + eventData.SignatureStatus = eventData.FileSignerInfos.Count > 0 ? SignatureStatus.Signed : SignatureStatus.Unsigned; + + + // Add the entire event package to the output list + _ = fileIdentities.Add(eventData); + + } + + // If the current group has AppLocker Blocked log + else if (possibleAppLockerBlockEvent is not null) + { + + + if (possibleAppLockerBlockEvent.SHA256 is null) + { + continue; + } + + + // Extract values using XPath + FileIdentity eventData = new() + { + + Origin = FileIdentityOrigin.MDEAdvancedHunting, + Action = EventAction.Audit, + TimeCreated = GetEventDataDateTimeValue(possibleAppLockerBlockEvent.Timestamp), + ComputerName = possibleAppLockerBlockEvent.DeviceName, + UserID = possibleAppLockerBlockEvent.InitiatingProcessAccountName, + + FilePath = possibleAppLockerBlockEvent.FolderPath, + FileName = possibleAppLockerBlockEvent.FileName, + ProcessName = possibleAppLockerBlockEvent.ProcessName, + RequestedSigningLevel = GetValidatedRequestedSigningLevel(GetIntValue(possibleAppLockerBlockEvent.RequestedSigningLevel)), + ValidatedSigningLevel = GetValidatedRequestedSigningLevel(GetIntValue(possibleAppLockerBlockEvent.ValidatedSigningLevel)), + Status = possibleAppLockerBlockEvent.StatusCode, + SHA1Hash = possibleAppLockerBlockEvent.SHA1, + SHA256Hash = possibleAppLockerBlockEvent.SHA256, + SHA1FlatHash = possibleAppLockerBlockEvent.Sha1FlatHash, + SHA256FlatHash = possibleAppLockerBlockEvent.Sha256FlatHash, + USN = GetLongValue(possibleAppLockerBlockEvent.USN), + SISigningScenario = GetIntValue(possibleAppLockerBlockEvent.SiSigningScenario) ?? 1, + PolicyName = possibleAppLockerBlockEvent.PolicyName, + PolicyID = possibleAppLockerBlockEvent.PolicyID, + PolicyHash = possibleAppLockerBlockEvent.PolicyHash, + OriginalFileName = possibleAppLockerBlockEvent.OriginalFileName, + InternalName = possibleAppLockerBlockEvent.InternalName, + FileDescription = possibleAppLockerBlockEvent.FileDescription, + PolicyGUID = GetGuidValue(possibleAppLockerBlockEvent.PolicyGuid), + UserWriteable = possibleAppLockerBlockEvent.UserWriteable + }; + + // Set the FileVersion using helper method + string? fileVersionString = possibleAppLockerBlockEvent.FileVersion; + eventData.FileVersion = SetFileVersion(fileVersionString); + + + // If there are correlated events - for signer information of the file + if (correlatedEvents.Count > 0) + { + + // Iterate over each correlated event - files can have multiple signers + foreach (MDEAdvancedHuntingData correlatedEvent in correlatedEvents) + { + + // Skip signers that don't have PublisherTBSHash (aka LeafCertificate TBS Hash) or PublisherName + // They have "Unknown" as their IssuerName and PublisherName too + // Leaf certificate is a must have for signed files + string? PublisherTBSHash = correlatedEvent.PublisherTBSHash; + + string? PublisherName = correlatedEvent.PublisherName; + + if (PublisherTBSHash is null || PublisherName is null) + { + continue; + } + + // Extract values using XPath + FileSignerInfo signerInfo = new() + { + + TotalSignatureCount = GetIntValue(correlatedEvent.TotalSignatureCount), + Signature = GetIntValue(correlatedEvent.Signature), + Hash = correlatedEvent.Hash, + SignatureType = GetSignatureType(GetIntValue(correlatedEvent.SignatureType)), + ValidatedSigningLevel = GetValidatedRequestedSigningLevel(GetIntValue(correlatedEvent.ValidatedSigningLevel)), + VerificationError = GetVerificationError(GetIntValue(correlatedEvent.VerificationError)), + Flags = GetIntValue(correlatedEvent.Flags), + NotValidBefore = GetEventDataDateTimeValue(correlatedEvent.NotValidBefore), + NotValidAfter = GetEventDataDateTimeValue(correlatedEvent.NotValidAfter), + PublisherName = PublisherName, + IssuerName = correlatedEvent.IssuerName, + PublisherTBSHash = PublisherTBSHash, + IssuerTBSHash = correlatedEvent.IssuerTBSHash + }; + + // Add the CN of the current signer to the FilePublishers HashSet of the FileIdentity + _ = eventData.FilePublishers.Add(PublisherName); + + // Add the current signer info/correlated event data to the main event package + _ = eventData.FileSignerInfos.Add(signerInfo); + } + } + + + // Set the SignatureStatus based on the number of signers + eventData.SignatureStatus = eventData.FileSignerInfos.Count > 0 ? SignatureStatus.Signed : SignatureStatus.Unsigned; + + + // Add the entire event package to the output list + _ = fileIdentities.Add(eventData); + + + } + + } + + + // Return the internal data which is the right return type + return fileIdentities.FileIdentitiesInternal; + } + + + + + #region Helper methods to extract values + + /// + /// Method to safely set FileVersion from a nullable string + /// + /// + /// + private static Version? SetFileVersion(string? versionString) + { + if (!string.IsNullOrWhiteSpace(versionString) && Version.TryParse(versionString, out Version? version)) + { + return version; + } + return null; + } + + + /// + /// Method to safely get an integer value from string + /// + /// + /// + private static int? GetIntValue(string? data) + { + return data is not null && int.TryParse(data, NumberStyles.Integer, CultureInfo.InvariantCulture, out int result) ? result : null; + } + + + + /// + /// Converts string to DateTime + /// + private static DateTime? GetEventDataDateTimeValue(string? data) + { + return data is not null && DateTime.TryParse(data, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out DateTime result) ? result : null; + } + + + + private static long? GetLongValue(string? data) + { + return data is not null && long.TryParse(data, NumberStyles.Integer, CultureInfo.InvariantCulture, out long result) ? result : null; + } + + private static Guid? GetGuidValue(string? data) + { + return data is not null && Guid.TryParse(data, out Guid guid) ? guid : null; + } + + + + /// + /// Resolves the Validated/Requested Signing Level int to friendly string + /// + /// + /// + private static string? GetValidatedRequestedSigningLevel(int? SigningLevelInt) + { + if (SigningLevelInt.HasValue) + { + _ = CILogIntel.ReqValSigningLevels.TryGetValue(SigningLevelInt.Value, out string? SigningLevel); + + return SigningLevel; + } + else + { + return null; + } + } + + + /// + /// Resolves the VerificationError int to a friendly string + /// + /// + /// + private static string? GetVerificationError(int? VerificationError) + { + if (VerificationError.HasValue) + { + _ = CILogIntel.VerificationErrorTable.TryGetValue(VerificationError.Value, out string? VerificationErrorString); + return VerificationErrorString; + } + else + { + return null; + } + } + + + /// + /// Resolves the SignatureType int to a friendly string + /// + /// + /// + private static string? GetSignatureType(int? SignatureType) + { + if (SignatureType.HasValue) + { + _ = CILogIntel.SignatureTypeTable.TryGetValue(SignatureType.Value, out string? SignatureTypeString); + return SignatureTypeString; + } + else + { + return null; + } + } + + + #endregion + + + } +} \ No newline at end of file diff --git a/AppControl Manager/Shared Logics/IntelGathering/KernelModeDrivers.cs b/AppControl Manager/Shared Logics/IntelGathering/KernelModeDrivers.cs new file mode 100644 index 000000000..f417978d7 --- /dev/null +++ b/AppControl Manager/Shared Logics/IntelGathering/KernelModeDrivers.cs @@ -0,0 +1,380 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; + +#pragma warning disable CS0649 + + +#nullable enable + +namespace WDACConfig.IntelGathering +{ + internal static class KernelModeDrivers + { + private static readonly IntPtr INVALID_HANDLE_VALUE = new(-1); + private static readonly Guid CRYPT_SUBJTYPE_CABINET_IMAGE = new("C689AABA-8E78-11d0-8C47-00C04FC295EE"); + private static readonly Guid CRYPT_SUBJTYPE_CATALOG_IMAGE = new("DE351A43-8E59-11d0-8C47-00C04FC295EE"); + private static readonly Guid CRYPT_SUBJTYPE_CTL_IMAGE = new("9BA61D3F-E73A-11d0-8CD2-00C04FC295EE"); + + public struct IMAGE_IMPORT_DESCRIPTOR + { + public uint CharacteristicsOrOriginalFirstThunk; + public uint TimeDateStamp; + public uint ForwarderChain; + public uint Name; + public uint FirstThunk; + } + + + private static IntPtr OpenFile(string path, out int error) + { + error = 0; + IntPtr fileHandle = PlatformInvocations.CreateFileW(path, 2147483648U, 1U, IntPtr.Zero, 3U, 33554432U, IntPtr.Zero); + IntPtr invalidHandleValue = INVALID_HANDLE_VALUE; + + if (fileHandle != invalidHandleValue) + { + return fileHandle; + } + + error = Marshal.GetLastWin32Error(); + return fileHandle; + } + + + + internal static KernelUserVerdict CheckKernelUserModeStatus(string filePath) + { + + // To store the import names + List importNames = []; + + uint localPointerFileSizeHigh = 0; + IntPtr fileMappingView = IntPtr.Zero; + IntPtr fileHandle = IntPtr.Zero; + IntPtr fileMappingHandle = IntPtr.Zero; + IntPtr foundHeader = IntPtr.Zero; + uint size = 0; + + // Output variables + bool hasSIP = false; + bool isPE = false; + UserOrKernelMode Verdict = UserOrKernelMode.UserMode; + + try + { + fileHandle = OpenFile(filePath, out int error); + + if (fileHandle == INVALID_HANDLE_VALUE) + { + Logger.Write($"CouldNotOpenFile {filePath}. Error: {error}"); + + return new KernelUserVerdict + { + Verdict = Verdict, + IsPE = isPE, + HasSIP = hasSIP, + Imports = importNames + }; + } + + hasSIP = PlatformInvocations.CryptSIPRetrieveSubjectGuid(filePath, fileHandle, out Guid pgActionID); + + if (!hasSIP) + { + return new KernelUserVerdict + { + Verdict = Verdict, + IsPE = isPE, + HasSIP = hasSIP, + Imports = importNames + }; + } + + if (pgActionID.Equals(CRYPT_SUBJTYPE_CATALOG_IMAGE)) + { + hasSIP = false; + + return new KernelUserVerdict + { + Verdict = Verdict, + IsPE = isPE, + HasSIP = hasSIP, + Imports = importNames + }; + } + + if (pgActionID.Equals(CRYPT_SUBJTYPE_CTL_IMAGE) || pgActionID.Equals(CRYPT_SUBJTYPE_CABINET_IMAGE)) + { + hasSIP = false; + + return new KernelUserVerdict + { + Verdict = Verdict, + IsPE = isPE, + HasSIP = hasSIP, + Imports = importNames + }; + } + + uint fileSize = PlatformInvocations.GetFileSize(fileHandle, ref localPointerFileSizeHigh); + + if (fileSize == uint.MaxValue || localPointerFileSizeHigh != 0U) + { + error = Marshal.GetLastWin32Error(); + + Logger.Write($"GetFileSizeFailed for file {filePath} with error {error}"); + + return new KernelUserVerdict + { + Verdict = Verdict, + IsPE = isPE, + HasSIP = hasSIP, + Imports = importNames + }; + } + + if (fileSize == 0U) + { + return new KernelUserVerdict + { + Verdict = Verdict, + IsPE = isPE, + HasSIP = hasSIP, + Imports = importNames + }; + } + + // Generate a new GUID and convert it to a string to ensure a unique name for the file mapping + string localPointerName = Guid.NewGuid().ToString(); + + // Create a file mapping object, associating the file with a memory region. + // - fileHandle: File handle to map. + // - IntPtr.Zero: No security attributes specified. + // - 2U: Map as read-write (PAGE_READWRITE). + // - lpFileSizeHigh, fileSize: High and low 32-bit file size for large files. + // - localPointerName: Unique name derived from the GUID to prevent name collisions in the global namespace. + fileMappingHandle = PlatformInvocations.CreateFileMapping(fileHandle, + IntPtr.Zero, + 2U, + localPointerFileSizeHigh, + fileSize, + localPointerName + ); + + error = Marshal.GetLastWin32Error(); + + if (fileMappingHandle == IntPtr.Zero) + { + Logger.Write($"CreateFileMappingFailed for the file {filePath} with error {error}"); + + return new KernelUserVerdict + { + Verdict = Verdict, + IsPE = isPE, + HasSIP = hasSIP, + Imports = importNames + }; + + } + + if (error == 183) + { + Logger.Write($"CreateFileMappingAlreadyExists for the file {filePath} with error {error}"); + + return new KernelUserVerdict + { + Verdict = Verdict, + IsPE = isPE, + HasSIP = hasSIP, + Imports = importNames + }; + } + + // Map a view of the file into the process's address space using the file mapping handle. + // - fileMappingHandle: Handle to the file mapping object created earlier. + // - 4U: Map the view with read-only access (PAGE_READONLY). + // - 0U, 0U: Offsets within the file to map the view from (start at the beginning of the file). + // - IntPtr.Zero: Specifies the desired view size; passing IntPtr.Zero means the entire file is mapped. + fileMappingView = PlatformInvocations.MapViewOfFile(fileMappingHandle, + 4U, + 0U, + 0U, + IntPtr.Zero + ); + + + if (fileMappingView == IntPtr.Zero) + { + error = Marshal.GetLastWin32Error(); + + Logger.Write($"MapViewOfFileFailed for the file {filePath} with error {error}"); + + return new KernelUserVerdict + { + Verdict = Verdict, + IsPE = isPE, + HasSIP = hasSIP, + Imports = importNames + }; + } + + IntPtr ntHeaders = PlatformInvocations.ImageNtHeader(fileMappingView); + + if (ntHeaders == IntPtr.Zero) + { + error = Marshal.GetLastWin32Error(); + + if (error == 193) + { + return new KernelUserVerdict + { + Verdict = Verdict, + IsPE = isPE, + HasSIP = hasSIP, + Imports = importNames + }; + } + + + Logger.Write($"ImageNtHeaderFailed for the file {filePath} with error {error}"); + + return new KernelUserVerdict + { + Verdict = Verdict, + IsPE = isPE, + HasSIP = hasSIP, + Imports = importNames + }; + } + + isPE = true; + + // Retrieve a pointer to the specified directory entry data in the mapped file image. + // - fileMappingView: A pointer to the mapped view of the file (memory-mapped region). + // - 0: The index of the directory entry to access (0 refers to the Export Table in PE headers). + // - 1: The type of data being accessed (1 indicates the Data Directory in PE format). + // - ref size: A reference to the variable that will hold the size of the retrieved data. + // - ref foundHeader: A reference to the variable that will store the header information of the directory entry. + IntPtr dataEx = PlatformInvocations.ImageDirectoryEntryToDataEx(fileMappingView, 0, 1, ref size, ref foundHeader); + + if (dataEx == IntPtr.Zero) + { + error = Marshal.GetLastWin32Error(); + + if (error == 0) + { + return new KernelUserVerdict + { + Verdict = Verdict, + IsPE = isPE, + HasSIP = hasSIP, + Imports = importNames + }; + } + + + Logger.Write($"ImageDirectoryEntryToDataExFailed for the file {filePath} with error {error}"); + + return new KernelUserVerdict + { + Verdict = Verdict, + IsPE = isPE, + HasSIP = hasSIP, + Imports = importNames + }; + } + + // Collect all of the file's imports + for (int offset = 0; ; offset += Marshal.SizeOf()) + { + // Get the pointer to the current IMAGE_IMPORT_DESCRIPTOR in unmanaged memory + IntPtr currentImportDescriptorPtr = (IntPtr)((long)dataEx + offset); + + // Marshal the IMAGE_IMPORT_DESCRIPTOR from unmanaged memory + IMAGE_IMPORT_DESCRIPTOR importDescriptor = Marshal.PtrToStructure(currentImportDescriptorPtr); + + // Check if the CharacteristicsOrOriginalFirstThunk is 0, indicating the end of the list + if (importDescriptor.CharacteristicsOrOriginalFirstThunk == 0) + { + break; + } + + // Get the RVA for the import name + IntPtr importNamePtr = PlatformInvocations.ImageRvaToVa(ntHeaders, fileMappingView, importDescriptor.Name, IntPtr.Zero); + + // Marshal the string from the unmanaged memory + string? importName = Marshal.PtrToStringAnsi(importNamePtr); + + if (importName is not null) + { + importNames.Add(importName); + } + } + + + + // If any of these DLLs are found in the imports list, the file is (likely) a user-mode PE. + // When a binary (such as a .exe or .dll) imports any of these user-mode libraries, it indicates that the binary relies on user-space functions, which are designed for normal applications. + // E.g., functions like CreateFile, MessageBox, or CreateWindow etc. are provided by kernel32.dll and user32.dll for user-mode applications, not for code running in kernel mode. + // Kernel-mode components do not interact with these user-mode DLLs. Instead, they access the kernel directly through SysCalls and low-level APIs. + List userModeDlls = ["kernel32.dll", "kernelbase.dll", "mscoree.dll", "ntdll.dll", "user32.dll"]; + + Verdict = importNames.Any(import => userModeDlls.Any(dll => string.Equals(import, dll, StringComparison.OrdinalIgnoreCase))) ? UserOrKernelMode.UserMode : UserOrKernelMode.KernelMode; + + // Return the actual output which happens when no errors occurred before + return new KernelUserVerdict + { + Verdict = Verdict, + IsPE = isPE, + HasSIP = hasSIP, + Imports = importNames + }; + } + + catch (AccessViolationException) + { + return new KernelUserVerdict + { + Verdict = Verdict, + IsPE = isPE, + HasSIP = hasSIP, + Imports = importNames + }; + } + + finally + { + if (fileMappingView != IntPtr.Zero) + { + if (PlatformInvocations.UnmapViewOfFile(fileMappingView) == 0) + { + int lastWin32Error = Marshal.GetLastWin32Error(); + + Logger.Write($"UnmapViewOfFileFailed for the file {filePath} with error {lastWin32Error}"); + } + } + if (fileMappingHandle != IntPtr.Zero && fileMappingHandle != INVALID_HANDLE_VALUE) + { + if (!PlatformInvocations.CloseHandle(fileMappingHandle)) + { + int lastWin32Error = Marshal.GetLastWin32Error(); + + Logger.Write($"CouldNotCloseMapHandle for the file {filePath} with error {lastWin32Error}"); + + } + } + if (fileHandle != IntPtr.Zero && fileHandle != INVALID_HANDLE_VALUE) + { + if (!PlatformInvocations.CloseHandle(fileHandle)) + { + int lastWin32Error = Marshal.GetLastWin32Error(); + + Logger.Write($"CouldNotCloseFileHandle for the file {filePath} with error {lastWin32Error}"); + } + } + } + } + } +} diff --git a/AppControl Manager/Shared Logics/IntelGathering/KernelUserVerdict.cs b/AppControl Manager/Shared Logics/IntelGathering/KernelUserVerdict.cs new file mode 100644 index 000000000..d5ee17bdb --- /dev/null +++ b/AppControl Manager/Shared Logics/IntelGathering/KernelUserVerdict.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace WDACConfig.IntelGathering +{ + internal sealed class KernelUserVerdict + { + public required UserOrKernelMode Verdict { get; set; } + public required bool IsPE { get; set; } + public required bool HasSIP { get; set; } + public required List Imports { get; set; } + } + + + internal enum UserOrKernelMode + { + UserMode, + KernelMode + } +} diff --git a/AppControl Manager/Shared Logics/IntelGathering/LocalFilesScan.cs b/AppControl Manager/Shared Logics/IntelGathering/LocalFilesScan.cs new file mode 100644 index 000000000..e0b3f5104 --- /dev/null +++ b/AppControl Manager/Shared Logics/IntelGathering/LocalFilesScan.cs @@ -0,0 +1,239 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Security.Cryptography.Pkcs; +using System.Security.Cryptography.X509Certificates; + +namespace WDACConfig.IntelGathering +{ + public static class LocalFilesScan + { + + private const string WHQLOid = "1.3.6.1.4.1.311.10.3.5"; + + private const string ECCOID = "1.2.840.10045.2.1"; + + + public static HashSet Scan(List files) + { + // HashSet to store the output, ensures the data are unique + HashSet fileIdentities = new(new FileIdentityComparer()); + + + foreach (FileInfo file in files) + { + + // To track whether ECC Signed signature has been detected or not + // Once it's been set to true, it won't be changed to false anymore for the current file + bool IsECCSigned = false; + + // String path of the current file + string fileString = file.ToString(); + + #region Gather File information + + // Get the Code integrity hashes of the file + CodeIntegrityHashes fileHashes = CiFileHash.GetCiFileHashes(fileString); + + // Get the extended file attributes + ExFileInfo ExtendedFileInfo = ExFileInfo.GetExtendedFileInfo(fileString); + + // Get the certificate details of the file + List FileSignatureResults = AllCertificatesGrabber.GetAllFileSigners(fileString); + + List ekuOIDs = []; + + bool fileIsSigned = false; + + if (FileSignatureResults.Count > 0) + { + fileIsSigned = true; + } + + #endregion + + FileIdentity currentFileIdentity = new() + { + Origin = FileIdentityOrigin.DirectFileScan, + SignatureStatus = fileIsSigned ? SignatureStatus.Signed : SignatureStatus.Unsigned, + FilePath = fileString, + FileName = file.Name, + SHA1Hash = fileHashes.SHa1Authenticode, + SHA256Hash = fileHashes.SHA256Authenticode, + SHA1PageHash = fileHashes.SHA1Page, + SHA256PageHash = fileHashes.SHA256Page, + SISigningScenario = KernelModeDrivers.CheckKernelUserModeStatus(fileString).Verdict is UserOrKernelMode.UserMode ? 1 : 0, + OriginalFileName = ExtendedFileInfo.OriginalFileName, + InternalName = ExtendedFileInfo.InternalName, + FileDescription = ExtendedFileInfo.FileDescription, + ProductName = ExtendedFileInfo.ProductName, + FileVersion = ExtendedFileInfo.Version + }; + + if (fileIsSigned) + { + + // The EKU OIDs of the primary signer of the file, just like the output of the Get-AuthenticodeSignature cmdlet, the ones that AppControl policy uses for EKU-based authorization + // Only the leaf certificate of the primary signer has EKUs, others such as root or intermediate have KUs only. + ekuOIDs = FileSignatureResults + .Where(p => p.Signer?.SignerInfos is not null) + .SelectMany(p => p.Signer.SignerInfos.Cast()) + .Where(info => info.Certificate is not null) + .SelectMany(info => info.Certificate!.Extensions.OfType()) + .SelectMany(ext => ext.EnhancedKeyUsages.Cast()) + .Select(oid => oid.Value) + .ToList()!; + + // Check if the file has WHQL signer + bool HasWHQLSigner = ekuOIDs.Contains(WHQLOid); + + // Assign the FileIdentity's property. + // Indicating the current FileIdentity contains an item in FileSignerInfos property that is a WHQL signer. + currentFileIdentity.HasWHQLSigner = HasWHQLSigner; + + // Get all of the certificates of the file + List FileSignerInfo = GetCertificateDetails.Get([.. FileSignatureResults]); + + + // Iterate through the certificates of the file + foreach (ChainPackage package in FileSignerInfo) + { + + string? CurrentOpusData = null; + + try + { + // Try to get the Opus data of the current chain (essentially the current chain's leaf certificate) + CurrentOpusData = Opus.GetOpusData(package.SignedCms).Select(p => p.CertOemID).First(); + } + catch + { + Logger.Write($"Failed to get the Opus data of the current chain package"); + } + + + + // If the Leaf Certificate exists in the current package + // Indicating that the current signer of the file is a normal certificate with Leaf/Intermediate(s)/Root + if (package.LeafCertificate is not null) + { + + // See if the leaf certificate in the current signer has WHQL OID for its EKU + bool WHQLConfirmed = package.LeafCertificate.Certificate!.Extensions.OfType() + .Any(eku => eku.EnhancedKeyUsages.Cast() + .Any(oid => oid.Value is not null && oid.Value.Contains(WHQLOid, StringComparison.OrdinalIgnoreCase))); + + + // Get the TBSHash of the Issuer certificate of the Leaf Certificate of the current file's signer + string IssuerTBSHash = CertificateHelper.GetTBSCertificate(package.LeafCertificate.Issuer); + + FileSignerInfo signerInfo = new() + { + TotalSignatureCount = FileSignerInfo.Count, + NotValidAfter = package.LeafCertificate?.NotAfter, + NotValidBefore = package.LeafCertificate?.NotBefore, + PublisherName = package.LeafCertificate?.SubjectCN, + IssuerName = package.LeafCertificate?.IssuerCN, + PublisherTBSHash = package.LeafCertificate?.TBSValue, + IssuerTBSHash = IssuerTBSHash, + OPUSInfo = CurrentOpusData, + IsWHQL = WHQLConfirmed, + EKUs = WHQLConfirmed ? WHQLOid : ekuOIDs.First() // If the Leaf certificate has WHQL EKU then assign that EKU's OID here, otherwise assign the first OID of the leaf certificate of the file. + }; + + + // Add the CN of the file's leaf certificate to the FilePublishers HashSet of the current FileIdentity + if (package.LeafCertificate?.SubjectCN is not null) + { + _ = currentFileIdentity.FilePublishers.Add(package.LeafCertificate.SubjectCN); + + // Check to see if it hasn't already been determined that the file is ECC signed + // We don't want to find an ECC signed certificate and then overwrite the property's value and set it to false by the next non-ECC signed certificate + if (!IsECCSigned) + { + + // Check see if the file is ECC-Signed + currentFileIdentity.IsECCSigned = string.Equals(package.LeafCertificate.Certificate?.PublicKey?.EncodedKeyValue.Oid?.Value, ECCOID, StringComparison.OrdinalIgnoreCase); + + if (currentFileIdentity.IsECCSigned == true) + { + // Set it to true so we don't search for ECC Signed certificates in other signers of the file + IsECCSigned = true; + + Logger.Write($"ECC Signed File Detected: {currentFileIdentity.FilePath}. Will create Hash rules for it."); + } + } + } + + + _ = currentFileIdentity.FileSignerInfos.Add(signerInfo); + + } + + // If Leaf certificate is null, according to the GetCertificateDetails class's logic, + // use Root certificate. That means the current signer of the file is a root certificate. + else if (package.RootCertificate is not null) + { + + + // See if the root certificate in the current signer has WHQL OID for its EKU + bool WHQLConfirmed = package.RootCertificate.Certificate!.Extensions.OfType() + .Any(eku => eku.EnhancedKeyUsages.Cast() + .Any(oid => oid.Value is not null && oid.Value.Contains(WHQLOid, StringComparison.OrdinalIgnoreCase))); + + + FileSignerInfo signerInfo = new() + { + TotalSignatureCount = FileSignerInfo.Count, + NotValidAfter = package.RootCertificate.NotAfter, + NotValidBefore = package.RootCertificate.NotBefore, + PublisherName = package.RootCertificate.SubjectCN, + IssuerName = package.RootCertificate.IssuerCN, + PublisherTBSHash = package.RootCertificate.TBSValue, + IssuerTBSHash = package.RootCertificate.TBSValue, + OPUSInfo = CurrentOpusData, + IsWHQL = WHQLConfirmed, + EKUs = WHQLConfirmed ? WHQLOid : ekuOIDs.First() // If the root certificate has WHQL EKU then assign that EKU's OID here, otherwise assign the first OID of the root certificate of the file. + }; + + + // Add the CN of the file's root certificate to the FilePublishers HashSet of the current FileIdentity + if (package.RootCertificate.SubjectCN is not null) + { + _ = currentFileIdentity.FilePublishers.Add(package.RootCertificate.SubjectCN); + + + // Check to see if it hasn't already been determined that the file is ECC signed + // We don't want to find an ECC signed certificate and then overwrite the property's value and set it to false by the next non-ECC signed certificate + if (!IsECCSigned) + { + // Check see if the file is ECC-Signed + currentFileIdentity.IsECCSigned = string.Equals(package.RootCertificate.Certificate?.PublicKey?.EncodedKeyValue.Oid?.Value, ECCOID, StringComparison.OrdinalIgnoreCase); + + if (currentFileIdentity.IsECCSigned == true) + { + // Set it to true so we don't search for ECC Signed certificates in other signers of the file + IsECCSigned = true; + + Logger.Write($"ECC Signed File Detected: {currentFileIdentity.FilePath}. Will create Hash rules for it."); + } + } + + } + + _ = currentFileIdentity.FileSignerInfos.Add(signerInfo); + } + } + } + + // Add the current file's identity to the output HashSet + _ = fileIdentities.Add(currentFileIdentity); + + } + + return fileIdentities; + } + } +} diff --git a/AppControl Manager/Shared Logics/IntelGathering/MDEAdvancedHuntingData.cs b/AppControl Manager/Shared Logics/IntelGathering/MDEAdvancedHuntingData.cs new file mode 100644 index 000000000..551aea6f0 --- /dev/null +++ b/AppControl Manager/Shared Logics/IntelGathering/MDEAdvancedHuntingData.cs @@ -0,0 +1,73 @@ +#nullable enable + +namespace WDACConfig.IntelGathering +{ + // Define a public class to store the structure of the new CSV data + public sealed class MDEAdvancedHuntingData + { + public string? Timestamp { get; set; } + public string? DeviceId { get; set; } + public string? DeviceName { get; set; } + public string? ActionType { get; set; } + public string? FileName { get; set; } + public string? FolderPath { get; set; } + public string? SHA1 { get; set; } + public string? SHA256 { get; set; } + public string? InitiatingProcessSHA1 { get; set; } + public string? InitiatingProcessSHA256 { get; set; } + public string? InitiatingProcessMD5 { get; set; } + public string? InitiatingProcessFileName { get; set; } + public string? InitiatingProcessFileSize { get; set; } + public string? InitiatingProcessFolderPath { get; set; } + public string? InitiatingProcessId { get; set; } + public string? InitiatingProcessCommandLine { get; set; } + public string? InitiatingProcessCreationTime { get; set; } + public string? InitiatingProcessAccountDomain { get; set; } + public string? InitiatingProcessAccountName { get; set; } + public string? InitiatingProcessAccountSid { get; set; } + public string? InitiatingProcessVersionInfoCompanyName { get; set; } + public string? InitiatingProcessVersionInfoProductName { get; set; } + public string? InitiatingProcessVersionInfoProductVersion { get; set; } + public string? InitiatingProcessVersionInfoInternalFileName { get; set; } + public string? InitiatingProcessVersionInfoOriginalFileName { get; set; } + public string? InitiatingProcessVersionInfoFileDescription { get; set; } + public string? InitiatingProcessParentId { get; set; } + public string? InitiatingProcessParentFileName { get; set; } + public string? InitiatingProcessParentCreationTime { get; set; } + public string? InitiatingProcessLogonId { get; set; } + public string? ReportId { get; set; } + + // Additional Fields JSON properties + public string? PolicyID { get; set; } + public string? PolicyName { get; set; } + public string? RequestedSigningLevel { get; set; } + public string? ValidatedSigningLevel { get; set; } + public string? ProcessName { get; set; } + public string? StatusCode { get; set; } + public string? Sha1FlatHash { get; set; } + public string? Sha256FlatHash { get; set; } + public string? USN { get; set; } + public string? SiSigningScenario { get; set; } + public string? PolicyHash { get; set; } + public string? PolicyGuid { get; set; } + public bool? UserWriteable { get; set; } + public string? OriginalFileName { get; set; } + public string? InternalName { get; set; } + public string? FileDescription { get; set; } + public string? FileVersion { get; set; } + public string? EtwActivityId { get; set; } + public string? IssuerName { get; set; } + public string? IssuerTBSHash { get; set; } + public string? NotValidAfter { get; set; } + public string? NotValidBefore { get; set; } + public string? PublisherName { get; set; } + public string? PublisherTBSHash { get; set; } + public string? SignatureType { get; set; } + public string? TotalSignatureCount { get; set; } + public string? VerificationError { get; set; } + public string? Signature { get; set; } + public string? Hash { get; set; } + public string? Flags { get; set; } + public string? PolicyBits { get; set; } + } +} diff --git a/AppControl Manager/Shared Logics/IntelGathering/OptimizeMDECSVData.cs b/AppControl Manager/Shared Logics/IntelGathering/OptimizeMDECSVData.cs new file mode 100644 index 000000000..aed7caade --- /dev/null +++ b/AppControl Manager/Shared Logics/IntelGathering/OptimizeMDECSVData.cs @@ -0,0 +1,277 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; + +#nullable enable + +namespace WDACConfig.IntelGathering +{ + + internal static partial class OptimizeMDECSVData + { + + + /// + /// Public method of this class. + /// Optimizes the MDE CSV data by adding the nested properties in the "AdditionalFields" property to the parent record as first-level properties, all in one class + /// + /// + /// + public static List Optimize(string CSVFilePath) + { + List csvRecords = ReadCsv(CSVFilePath); + + return csvRecords; + } + + + + /// + /// Converts an entire MDE Advanced Hunting CSV file into a list of classes + /// + /// + /// + /// + private static List ReadCsv(string filePath) + { + // Create a list to store each CSV row record + List records = []; + + // Read the CSV file line by line + using StreamReader reader = new(filePath); + + // Read the header line + string? header = reader.ReadLine() ?? throw new InvalidDataException("CSV file is empty or header is missing."); + + // Read the remaining lines of the CSV file until the end of the stream is reached (EOF) + while (!reader.EndOfStream) + { + // Read the next line + string? line = reader.ReadLine(); + if (line is null) continue; + + // Split the line by commas + string[] values = ParseCsvLine(line); + + // Initialize a new CsvRecord instance + // P.S not all rows have the same properties + MDEAdvancedHuntingData record = new() + { + Timestamp = values.Length > 0 ? values[0] : null, + DeviceId = values.Length > 1 ? values[1] : null, + DeviceName = values.Length > 2 ? values[2] : null, + ActionType = values.Length > 3 ? values[3] : null, + FileName = values.Length > 4 ? values[4] : null, + FolderPath = values.Length > 5 ? values[5] : null, + SHA1 = values.Length > 6 ? values[6] : null, + SHA256 = values.Length > 7 ? values[7] : null, + InitiatingProcessSHA1 = values.Length > 8 ? values[8] : null, + InitiatingProcessSHA256 = values.Length > 9 ? values[9] : null, + InitiatingProcessMD5 = values.Length > 10 ? values[10] : null, + InitiatingProcessFileName = values.Length > 11 ? values[11] : null, + InitiatingProcessFileSize = values.Length > 12 ? values[12] : null, + InitiatingProcessFolderPath = values.Length > 13 ? values[13] : null, + InitiatingProcessId = values.Length > 14 ? values[14] : null, + InitiatingProcessCommandLine = values.Length > 15 ? values[15] : null, + InitiatingProcessCreationTime = values.Length > 16 ? values[16] : null, + InitiatingProcessAccountDomain = values.Length > 17 ? values[17] : null, + InitiatingProcessAccountName = values.Length > 18 ? values[18] : null, + InitiatingProcessAccountSid = values.Length > 19 ? values[19] : null, + InitiatingProcessVersionInfoCompanyName = values.Length > 20 ? values[20] : null, + InitiatingProcessVersionInfoProductName = values.Length > 21 ? values[21] : null, + InitiatingProcessVersionInfoProductVersion = values.Length > 22 ? values[22] : null, + InitiatingProcessVersionInfoInternalFileName = values.Length > 23 ? values[23] : null, + InitiatingProcessVersionInfoOriginalFileName = values.Length > 24 ? values[24] : null, + InitiatingProcessVersionInfoFileDescription = values.Length > 25 ? values[25] : null, + InitiatingProcessParentId = values.Length > 26 ? values[26] : null, + InitiatingProcessParentFileName = values.Length > 27 ? values[27] : null, + InitiatingProcessParentCreationTime = values.Length > 28 ? values[28] : null, + InitiatingProcessLogonId = values.Length > 29 ? values[29] : null, + ReportId = values.Length > 30 ? values[30] : null + }; + + // Parse the AdditionalFields JSON if it exists + if (values.Length > 31 && !string.IsNullOrWhiteSpace(values[31])) + { + // Get the JSON string from the CSV which is in the AdditionalFields property + string additionalFieldsString = values[31]; + + // Format the JSON string so the next method won't throw error + string FormattedJSONString = EnsureAllValuesAreQuoted(additionalFieldsString); + + // Deserialize the JSON content into a dictionary + Dictionary? additionalFields = JsonSerializer.Deserialize>(FormattedJSONString); + + + if (additionalFields is not null) + { + // Populate the new properties from the JSON + record.PolicyID = additionalFields.TryGetValue("PolicyID", out string? PolicyID) ? PolicyID : null; + record.PolicyName = additionalFields.TryGetValue("PolicyName", out string? PolicyName) ? PolicyName : null; + record.RequestedSigningLevel = additionalFields.TryGetValue("Requested Signing Level", out string? RequestedSigningLevel) ? RequestedSigningLevel : null; + record.ValidatedSigningLevel = additionalFields.TryGetValue("Validated Signing Level", out string? ValidatedSigningLevel) ? ValidatedSigningLevel : null; + record.ProcessName = additionalFields.TryGetValue("ProcessName", out string? ProcessName) ? ProcessName : null; + record.StatusCode = additionalFields.TryGetValue("StatusCode", out string? StatusCode) ? StatusCode : null; + record.Sha1FlatHash = additionalFields.TryGetValue("Sha1FlatHash", out string? Sha1FlatHash) ? Sha1FlatHash : null; + record.Sha256FlatHash = additionalFields.TryGetValue("Sha256FlatHash", out string? Sha256FlatHash) ? Sha256FlatHash : null; + record.USN = additionalFields.TryGetValue("USN", out string? USN) ? USN : null; + record.SiSigningScenario = additionalFields.TryGetValue("SiSigningScenario", out string? SiSigningScenario) ? SiSigningScenario : null; + record.PolicyHash = additionalFields.TryGetValue("PolicyHash", out string? PolicyHash) ? PolicyHash : null; + record.PolicyGuid = additionalFields.TryGetValue("PolicyGuid", out string? PolicyGuid) ? PolicyGuid : null; + record.UserWriteable = additionalFields.TryGetValue("UserWriteable", out string? UserWriteable) ? bool.Parse(UserWriteable) : null; + record.OriginalFileName = additionalFields.TryGetValue("OriginalFileName", out string? OriginalFileName) ? OriginalFileName : null; + record.InternalName = additionalFields.TryGetValue("InternalName", out string? InternalName) ? InternalName : null; + record.FileDescription = additionalFields.TryGetValue("FileDescription", out string? FileDescription) ? FileDescription : null; + record.FileVersion = additionalFields.TryGetValue("FileVersion", out string? FileVersion) ? FileVersion : null; + record.EtwActivityId = additionalFields.TryGetValue("EtwActivityId", out string? EtwActivityId) ? EtwActivityId : null; + record.IssuerName = additionalFields.TryGetValue("IssuerName", out string? IssuerName) ? IssuerName : null; + record.IssuerTBSHash = additionalFields.TryGetValue("IssuerTBSHash", out string? IssuerTBSHash) ? IssuerTBSHash : null; + record.NotValidAfter = additionalFields.TryGetValue("NotValidAfter", out string? NotValidAfter) ? NotValidAfter : null; + record.NotValidBefore = additionalFields.TryGetValue("NotValidBefore", out string? NotValidBefore) ? NotValidBefore : null; + record.PublisherName = additionalFields.TryGetValue("PublisherName", out string? PublisherName) ? PublisherName : null; + record.PublisherTBSHash = additionalFields.TryGetValue("PublisherTBSHash", out string? PublisherTBSHash) ? PublisherTBSHash : null; + record.SignatureType = additionalFields.TryGetValue("SignatureType", out string? SignatureType) ? SignatureType : null; + record.TotalSignatureCount = additionalFields.TryGetValue("TotalSignatureCount", out string? TotalSignatureCount) ? TotalSignatureCount : null; + record.VerificationError = additionalFields.TryGetValue("VerificationError", out string? VerificationError) ? VerificationError : null; + record.Signature = additionalFields.TryGetValue("Signature", out string? Signature) ? Signature : null; + record.Hash = additionalFields.TryGetValue("Hash", out string? Hash) ? Hash : null; + record.Flags = additionalFields.TryGetValue("Flags", out string? Flags) ? Flags : null; + record.PolicyBits = additionalFields.TryGetValue("PolicyBits", out string? PolicyBits) ? PolicyBits : null; + } + } + + // Add the populated record to the list + records.Add(record); + } + + return records; + } + + + /// + /// Ensures the JSON string is well formatted. If a field has no double quotes, it will add them around it. + /// + /// + /// + private static string EnsureAllValuesAreQuoted(string jsonString) + { + // Regex to match unquoted values that are not inside quotes + Regex regex = JsonFixerRegex(); + + // Replace the matched unquoted values with the same value wrapped in double quotes + string result = regex.Replace(jsonString, match => $"\"{match.Value.Trim()}\""); + + return result; + } + + + + /// + /// Parses each line/row of the CSV file + /// + /// + /// + private static string[] ParseCsvLine(string line) + { + List fields = []; + StringBuilder currentField = new(); + bool inQuotes = false; + + // Iterate through each character in the line + for (int i = 0; i < line.Length; i++) + { + char c = line[i]; + + // Handle quotes + if (c == '"') + { + // Handle escaped quotes ("") + if (inQuotes && i + 1 < line.Length && line[i + 1] == '"') + { + _ = currentField.Append('"'); // Append a single quote if it's an escape sequence + i++; // Skip the next quote + } + else + { + inQuotes = !inQuotes; // Toggle the inQuotes flag + } + } + + // Handle commas + else if (c == ',' && !inQuotes) + { + // When we hit a comma outside of quotes, the field is complete + fields.Add(currentField.ToString()); + _ = currentField.Clear(); + } + + else + { + // Add characters to the current field + _ = currentField.Append(c); + } + } + + // Add the last field to the list + fields.Add(currentField.ToString()); + + // Clean up: Remove the outermost quotes for non-JSON fields + // If a field has more than one set/pair of double quotes around it, only one pair will be removed + for (int i = 0; i < fields.Count; i++) + { + string field = fields[i]; + + // If the field is a JSON field, we don't want to remove quotes + if (field.StartsWith("{", StringComparison.OrdinalIgnoreCase) && field.EndsWith("}", StringComparison.OrdinalIgnoreCase)) + { + continue; // Skip JSON fields + } + + // Remove leading and trailing quotes if they exist (for non-JSON fields) + if (field.StartsWith("\"", StringComparison.OrdinalIgnoreCase) && field.EndsWith("\"", StringComparison.OrdinalIgnoreCase) && field.Length > 1) + { + fields[i] = field[1..^1]; + } + } + + return [.. fields]; + } + + + + + // 1. (?<=:) + // Positive Lookbehind: Asserts that the match must be preceded by a colon `:`. + // Ensures that we're matching a value that appears immediately after a key-value colon. + // + // 2. \s* + // Matches zero or more whitespace characters following the colon. + // Allows for optional spaces between the colon and the value. + // + // 3. (?!\"\") + // Negative Lookahead: Ensures that the match is NOT followed by a double quote `"`. + // This prevents already quoted values from being matched. + // + // 4. ([^\"",\s]+) + // Capturing Group: Matches one or more characters that are not a double quote `"`, + // comma `,`, or whitespace. This captures unquoted strings up to a comma, closing + // brace, or space, allowing only unquoted single-word values to be matched. + // + // 5. (?=\s*,|\s*}) + // Positive Lookahead: Asserts that the match must be followed by either a comma `,` + // (indicating another key-value pair) or a closing brace `}`, with optional whitespace. + // This confirms the end of the unquoted value within the JSON structure. + // + // Summary: + // Some MDE AH AdditionalFields JSON content have unquoted fields, this takes care of them. + // The regex Will Fail if the field that is not quoted contains a comma(s), space(s) or double quote(s) in it, before the comma that marks the end of the field. + // This is because the regex is designed to match unquoted fields that are single words/digits. + // + [GeneratedRegex(@"(?<=:)\s*(?!\"")([^\"",\s]+)(?=\s*,|\s*})", RegexOptions.Compiled)] + private static partial Regex JsonFixerRegex(); + + } +} diff --git a/AppControl Manager/Shared Logics/IntelGathering/PlatformInvocations.cs b/AppControl Manager/Shared Logics/IntelGathering/PlatformInvocations.cs new file mode 100644 index 000000000..d401cbf69 --- /dev/null +++ b/AppControl Manager/Shared Logics/IntelGathering/PlatformInvocations.cs @@ -0,0 +1,82 @@ +using System; +using System.Runtime.InteropServices; + +namespace WDACConfig.IntelGathering +{ + internal static partial class PlatformInvocations + { + + // https://learn.microsoft.com/en-us/windows/win32/api/mssip/nf-mssip-cryptsipretrievesubjectguid + [LibraryImport("crypt32.dll", EntryPoint = "CryptSIPRetrieveSubjectGuid", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool CryptSIPRetrieveSubjectGuid( + string FileName, + IntPtr hFileIn, + out Guid pgActionID); + + // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew + [LibraryImport("kernel32.dll", EntryPoint = "CreateFileW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] + internal static partial IntPtr CreateFileW( + string lpFileName, + uint dwDesiredAccess, + uint dwShareMode, + IntPtr lpSecurityAttributes, + uint dwCreationDisposition, + uint dwFlagsAndAttributes, + IntPtr hTemplateFile); + + // https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool CloseHandle(IntPtr hObject); + + // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createfilemappinga + [LibraryImport("kernel32.dll", EntryPoint = "CreateFileMappingW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] + internal static partial IntPtr CreateFileMapping( + IntPtr hFile, + IntPtr pFileMappingAttributes, + uint flProtect, + uint dwMaximumSizeHigh, + uint dwMaximumSizeLow, + string lpName); + + // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfilesize + [LibraryImport("kernel32.dll", SetLastError = true)] + internal static partial uint GetFileSize(IntPtr hFile, ref uint lpFileSizeHigh); + + // https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile + [LibraryImport("kernel32.dll", SetLastError = true)] + internal static partial IntPtr MapViewOfFile( + IntPtr hFileMappingObject, + uint dwDesiredAccess, + uint dwFileOffsetHigh, + uint dwFileOffsetLow, + IntPtr dwNumberOfBytesToMap); + + // https://learn.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-imagedirectoryentrytodataex + [LibraryImport("DbgHelp.dll", SetLastError = true)] + internal static partial IntPtr ImageDirectoryEntryToDataEx( + IntPtr Base, + int MappedAsImage, + ushort DirectoryEntry, + ref uint Size, + ref IntPtr FoundHeader); + + // https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-unmapviewoffile + [LibraryImport("kernel32.dll", SetLastError = true)] + internal static partial int UnmapViewOfFile(IntPtr lpBaseAddress); + + // https://learn.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-imagentheader + [LibraryImport("DbgHelp.dll", SetLastError = true)] + internal static partial IntPtr ImageNtHeader(IntPtr ImageBase); + + // https://learn.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-imagervatova + [LibraryImport("DbgHelp.dll", SetLastError = true)] + internal static partial IntPtr ImageRvaToVa( + IntPtr NtHeaders, + IntPtr Base, + uint Rva, + IntPtr LastRvaSection); + + } +} diff --git a/AppControl Manager/Shared Logics/IntelGathering/PrepareEmptyPolicy.cs b/AppControl Manager/Shared Logics/IntelGathering/PrepareEmptyPolicy.cs new file mode 100644 index 000000000..1c2a6e741 --- /dev/null +++ b/AppControl Manager/Shared Logics/IntelGathering/PrepareEmptyPolicy.cs @@ -0,0 +1,31 @@ +using System.IO; + +#nullable enable + +namespace WDACConfig.IntelGathering +{ + public static class PrepareEmptyPolicy + { + + /// + /// Copies one of the template Code Integrity policies to the directory it receives, empties it and returns its path + /// + /// + /// + public static string Prepare(string directory) + { + + string pathToReturn = Path.Combine(directory, "EmptyPolicyFile.xml"); + + Logger.Write("Copying the template policy to the staging area"); + + File.Copy(@"C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowAll.xml", pathToReturn, true); + + Logger.Write("Emptying the policy file in preparation for the new data insertion"); + ClearCiPolicySemantic.Clear(pathToReturn); + + return pathToReturn; + + } + } +} diff --git a/AppControl Manager/Shared Logics/IntelGathering/ScanLevels.cs b/AppControl Manager/Shared Logics/IntelGathering/ScanLevels.cs new file mode 100644 index 000000000..d7cb1edae --- /dev/null +++ b/AppControl Manager/Shared Logics/IntelGathering/ScanLevels.cs @@ -0,0 +1,10 @@ +namespace WDACConfig.IntelGathering +{ + // The levels used by the BuildSignerAndHashObjects method + public enum ScanLevels + { + FilePublisher, + Publisher, + Hash + } +} diff --git a/AppControl Manager/Shared Logics/IntelGathering/SignatureStatus.cs b/AppControl Manager/Shared Logics/IntelGathering/SignatureStatus.cs new file mode 100644 index 000000000..775f05d4c --- /dev/null +++ b/AppControl Manager/Shared Logics/IntelGathering/SignatureStatus.cs @@ -0,0 +1,10 @@ +#nullable enable + +namespace WDACConfig.IntelGathering +{ + public enum SignatureStatus + { + Signed, + Unsigned + } +} diff --git a/AppControl Manager/Shared Logics/Logging/LoggerInitializer.cs b/AppControl Manager/Shared Logics/Logging/LoggerInitializer.cs index c7b8c378c..9a8206925 100644 --- a/AppControl Manager/Shared Logics/Logging/LoggerInitializer.cs +++ b/AppControl Manager/Shared Logics/Logging/LoggerInitializer.cs @@ -34,7 +34,7 @@ public static void Initialize(string verbosePreference, string debugPreference, } } - if (host != null) + if (host is not null) { GlobalVars.Host = host; } diff --git a/AppControl Manager/Shared Logics/Main Cmdlets/AssertWDACConfigIntegrity.cs b/AppControl Manager/Shared Logics/Main Cmdlets/AssertWDACConfigIntegrity.cs index 571606c9c..54d52b704 100644 --- a/AppControl Manager/Shared Logics/Main Cmdlets/AssertWDACConfigIntegrity.cs +++ b/AppControl Manager/Shared Logics/Main Cmdlets/AssertWDACConfigIntegrity.cs @@ -61,7 +61,7 @@ public static class AssertWDACConfigIntegrity byte[] Bytes = File.ReadAllBytes(file.FullName); // Compute the hash of the byte array - Byte[] HashBytes = SHA3_512.HashData(Bytes); + 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 @@ -134,7 +134,7 @@ private static List ParseCSV(string csvData) string? line; bool isHeader = true; - while ((line = reader.ReadLine()) != null) + while ((line = reader.ReadLine()) is not null) { // Skip the header if (isHeader) @@ -144,7 +144,7 @@ private static List ParseCSV(string csvData) } // Split the CSV line by commas - var fields = line.Split(','); + string[] fields = line.Split(','); if (fields.Length == 3) { @@ -177,7 +177,7 @@ private static void ExportToCsv(string outputPath, List ent """); // Write each entry in the list - foreach (var entry in entries) + foreach (WDACConfigHashEntry entry in entries) { string relativePath = EscapeCsv(entry.RelativePath); string fileName = EscapeCsv(entry.FileName); diff --git a/AppControl Manager/Shared Logics/Main Cmdlets/BasePolicyCreator.cs b/AppControl Manager/Shared Logics/Main Cmdlets/BasePolicyCreator.cs index d533bb6c5..a1aae0bc5 100644 --- a/AppControl Manager/Shared Logics/Main Cmdlets/BasePolicyCreator.cs +++ b/AppControl Manager/Shared Logics/Main Cmdlets/BasePolicyCreator.cs @@ -10,6 +10,8 @@ using System.Text.RegularExpressions; using System.Xml; +#pragma warning disable SYSLIB1045 // Since this file is going to be used in WDACConfig PowerShell module and PowerShell does not support source code generation for Regex, supressing this error + #nullable enable namespace WDACConfig @@ -25,7 +27,7 @@ public static void SetAutoUpdateDriverBlockRules() Logger.Write("Creating scheduled task for fast weekly Microsoft recommended driver block list update"); // Initialize ManagementScope to interact with Task Scheduler's WMI namespace - var scope = new ManagementScope(@"root\Microsoft\Windows\TaskScheduler"); + ManagementScope scope = new(@"root\Microsoft\Windows\TaskScheduler"); // Establish connection to the WMI namespace scope.Connect(); @@ -224,7 +226,7 @@ public sealed class DriverBlockListInfo // Use Regex to find the version string version = string.Empty; - var match = Regex.Match(markdownContent, @"(.*?)<\/VersionEx>"); + Match match = Regex.Match(markdownContent, @"(.*?)<\/VersionEx>"); if (match.Success) { version = match.Groups[1].Value; diff --git a/AppControl Manager/Shared Logics/Main Cmdlets/GetCIPolicySetting.cs b/AppControl Manager/Shared Logics/Main Cmdlets/GetCIPolicySetting.cs index 7c09e2a7c..03d9597b0 100644 --- a/AppControl Manager/Shared Logics/Main Cmdlets/GetCIPolicySetting.cs +++ b/AppControl Manager/Shared Logics/Main Cmdlets/GetCIPolicySetting.cs @@ -29,7 +29,7 @@ public static SecurePolicySetting Invoke(string provider, string key, string val uint ValueSize = 1024; // Changed to uint to match the P/Invoke declaration var Value = Marshal.AllocHGlobal((int)ValueSize); - var result = WldpQuerySecurityPolicyWrapper.WldpQuerySecurityPolicy( + int result = WldpQuerySecurityPolicyWrapper.WldpQuerySecurityPolicy( ref ProviderUS, ref KeyUS, ref ValueNameUS, diff --git a/AppControl Manager/Shared Logics/Main Cmdlets/InvokeWDACSimulation.cs b/AppControl Manager/Shared Logics/Main Cmdlets/InvokeWDACSimulation.cs index 1437b0924..b83f83d8e 100644 --- a/AppControl Manager/Shared Logics/Main Cmdlets/InvokeWDACSimulation.cs +++ b/AppControl Manager/Shared Logics/Main Cmdlets/InvokeWDACSimulation.cs @@ -83,14 +83,14 @@ public static void ExportToCsv(ConcurrentDictionary fi List csvLines = []; // Create header instead of using reflection to get the properties' names of the SimulationOutput class - var header = "\"Path\",\"Source\",\"IsAuthorized\",\"SignerID\",\"SignerName\",\"SignerCertRoot\",\"SignerCertPublisher\",\"SignerScope\",\"SignerFileAttributeIDs\",\"MatchCriteria\",\"SpecificFileNameLevelMatchCriteria\",\"CertSubjectCN\",\"CertIssuerCN\",\"CertNotAfter\",\"CertTBSValue\",\"FilePath\""; + string header = "\"Path\",\"Source\",\"IsAuthorized\",\"SignerID\",\"SignerName\",\"SignerCertRoot\",\"SignerCertPublisher\",\"SignerScope\",\"SignerFileAttributeIDs\",\"MatchCriteria\",\"SpecificFileNameLevelMatchCriteria\",\"CertSubjectCN\",\"CertIssuerCN\",\"CertNotAfter\",\"CertTBSValue\",\"FilePath\""; csvLines.Add(header); // Iterate through the SimulationOutput instances and format each line - foreach (var output in finalResults.Values) + foreach (SimulationOutput output in finalResults.Values) { - var values = new List - { + List values = + [ $"\"{output.Path}\"", $"\"{output.Source}\"", $"\"{output.IsAuthorized}\"", @@ -99,7 +99,7 @@ public static void ExportToCsv(ConcurrentDictionary fi $"\"{output.SignerCertRoot}\"", $"\"{output.SignerCertPublisher}\"", $"\"{output.SignerScope}\"", - output.SignerFileAttributeIDs != null ? $"\"{string.Join(",", output.SignerFileAttributeIDs)}\"" : "\"\"", + output.SignerFileAttributeIDs is not null ? $"\"{string.Join(",", output.SignerFileAttributeIDs)}\"" : "\"\"", $"\"{output.MatchCriteria}\"", $"\"{output.SpecificFileNameLevelMatchCriteria}\"", $"\"{output.CertSubjectCN}\"", @@ -107,7 +107,7 @@ public static void ExportToCsv(ConcurrentDictionary fi $"\"{output.CertNotAfter}\"", $"\"{output.CertTBSValue}\"", $"\"{output.FilePath}\"" - }; + ]; csvLines.Add(string.Join(",", values)); } diff --git a/AppControl Manager/Shared Logics/Main Cmdlets/SetCiRuleOptions.cs b/AppControl Manager/Shared Logics/Main Cmdlets/SetCiRuleOptions.cs index cc60e61ca..29e6ec594 100644 --- a/AppControl Manager/Shared Logics/Main Cmdlets/SetCiRuleOptions.cs +++ b/AppControl Manager/Shared Logics/Main Cmdlets/SetCiRuleOptions.cs @@ -182,7 +182,7 @@ public enum PolicyRuleOptions foreach (XmlNode node in optionNodes) { - if (node.Value != null) + if (node.Value is not null) { _ = validOptions.Add(node.Value); } @@ -409,22 +409,22 @@ public static void Set( #region Compare the existing rule options in the policy XML file with the rule options to implement // Get keys from the ExistingRuleOptions dictionary - var existingRuleKeys = ExistingRuleOptions.Keys.ToArray(); + int[] existingRuleKeys = [.. ExistingRuleOptions.Keys]; // Find elements in RuleOptionsToImplement that are not in ExistingRuleOptions.Keys - var toAdd = RuleOptionsToImplement.Except(existingRuleKeys); + IEnumerable toAdd = RuleOptionsToImplement.Except(existingRuleKeys); // Find elements in ExistingRuleOptions.Keys that are not in RuleOptionsToImplement - var toRemove = existingRuleKeys.Except(RuleOptionsToImplement); + IEnumerable toRemove = existingRuleKeys.Except(RuleOptionsToImplement); - foreach (var option in toAdd) + foreach (int option in toAdd) { _ = PolicyRuleOptionsActualInverted.TryGetValue(option, out string? parsed); Logger.Write($"Adding Rule Option: {parsed}"); } - foreach (var option in toRemove) + foreach (int option in toRemove) { _ = PolicyRuleOptionsActualInverted.TryGetValue(option, out string? parsed); diff --git a/AppControl Manager/Shared Logics/MeowOpener.cs b/AppControl Manager/Shared Logics/MeowOpener.cs index 31b02fcb8..28a24de7e 100644 --- a/AppControl Manager/Shared Logics/MeowOpener.cs +++ b/AppControl Manager/Shared Logics/MeowOpener.cs @@ -8,22 +8,23 @@ namespace WDACConfig { // Declares a public static class that cannot be instantiated. - public static class MeowParser + public static partial 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'. + // P/Invoke declaration to import the 'BCryptOpenAlgorithmProvider' function from 'bcrypt.dll'. + // https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptopenalgorithmprovider + [LibraryImport("bcrypt.dll", EntryPoint = "BCryptOpenAlgorithmProvider", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool BCryptOpenAlgorithmProvider( + out IntPtr phAlgorithm, // Output parameter to receive the handle of the cryptographic algorithm. + string pszAlgId, // The algorithm identifier (e.g., AES, SHA256, etc.). + string? pszImplementation, // The implementation name (null for default). + uint dwFlags); // Flags to control the function behavior. + + // P/Invoke declaration to import the 'BCryptCloseAlgorithmProvider' function from 'bcrypt.dll'. + // https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptclosealgorithmprovider + [LibraryImport("bcrypt.dll", EntryPoint = "BCryptCloseAlgorithmProvider", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool BCryptCloseAlgorithmProvider(IntPtr hAlgorithm, uint dwFlags); // Releases the algorithm handle acquired by 'BCryptOpenAlgorithmProvider'. // Defines a structure with sequential layout to match the native structure. // https://learn.microsoft.com/en-us/windows/win32/api/mscat/ns-mscat-cryptcatmember @@ -39,8 +40,8 @@ internal struct MeowMemberCrypt 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. + private readonly uint Reserved1; // Reserved for future use. + private readonly IntPtr Reserved2; // Reserved for future use. } // A public static method that returns a HashSet of strings. @@ -62,12 +63,12 @@ public static HashSet GetHashes(string SecurityCatalogFilePath) try { - // Attempts to acquire a cryptographic context. - if (!CryptAcquireContext(out MainCryptProviderHandle, string.Empty, string.Empty, 1, 4026531840)) + // Attempts to acquire a cryptographic context using the CNG API. + if (!BCryptOpenAlgorithmProvider(out MainCryptProviderHandle, "SHA256", null, 0)) { // If the context is not acquired, capture the error code. int lastWin32Error = Marshal.GetLastWin32Error(); - Logger.Write($"CryptAcquireContext failed with error code: {lastWin32Error}"); + Logger.Write($"BCryptOpenAlgorithmProvider failed with error code: {lastWin32Error}"); } // Opens the catalog file and gets a handle to the catalog context. @@ -80,7 +81,6 @@ public static HashSet GetHashes(string SecurityCatalogFilePath) Logger.Write($"CryptCATOpen failed with error code: {lastWin32Error}"); } - // Creates an XML element to represent the catalog file. XmlElement catalogElement = PurrfectCatalogXMLDoc.CreateElement("MeowFile"); @@ -101,7 +101,7 @@ public static HashSet GetHashes(string SecurityCatalogFilePath) { // 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); + _ = BCryptCloseAlgorithmProvider(MainCryptProviderHandle, 0); if (MeowLogHandle != IntPtr.Zero) _ = WinTrust.CryptCATClose(MeowLogHandle); diff --git a/AppControl Manager/Shared Logics/MoveUserModeToKernelMode.cs b/AppControl Manager/Shared Logics/MoveUserModeToKernelMode.cs index 78a6242dd..679385a18 100644 --- a/AppControl Manager/Shared Logics/MoveUserModeToKernelMode.cs +++ b/AppControl Manager/Shared Logics/MoveUserModeToKernelMode.cs @@ -10,7 +10,7 @@ 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. + /// This is used during the creation of Strict Kernel-mode AppControl 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 @@ -22,7 +22,7 @@ public static void Move(string filePath) CodeIntegrityPolicy codeIntegrityPolicy = new(filePath, null); // Get AllowedSigners from SigningScenario with Value 12 - XmlNode? allowedSigners12 = codeIntegrityPolicy.UMCI_SigningScenarioNode.SelectSingleNode("./ns:ProductSigners/ns:AllowedSigners", codeIntegrityPolicy.NamespaceManager); + XmlNode? allowedSigners12 = codeIntegrityPolicy.UMCI_SigningScenarioNode?.SelectSingleNode("./ns:ProductSigners/ns:AllowedSigners", codeIntegrityPolicy.NamespaceManager); // If AllowedSigners node exists in SigningScenario 12 and has child nodes if (allowedSigners12 is not null && allowedSigners12.HasChildNodes) @@ -69,7 +69,7 @@ public static void Move(string filePath) } // Remove SigningScenario with Value 12 completely after moving all of its AllowedSigners to SigningScenario with the value of 131 - _ = (codeIntegrityPolicy.UMCI_SigningScenarioNode.ParentNode?.RemoveChild(codeIntegrityPolicy.UMCI_SigningScenarioNode)); + _ = (codeIntegrityPolicy.UMCI_SigningScenarioNode?.ParentNode?.RemoveChild(codeIntegrityPolicy.UMCI_SigningScenarioNode)); } // Save the modified XML document back to the file diff --git a/AppControl Manager/Shared Logics/PowerShellExecutor.cs b/AppControl Manager/Shared Logics/PowerShellExecutor.cs index a24449a2f..063c60470 100644 --- a/AppControl Manager/Shared Logics/PowerShellExecutor.cs +++ b/AppControl Manager/Shared Logics/PowerShellExecutor.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.ObjectModel; using System.Linq; using System.Management.Automation; @@ -39,8 +40,8 @@ public static class PowerShellExecutor { if (sender is not null) { - var outputStream = (PSDataCollection)sender; - var output = outputStream[args.Index]?.ToString(); + PSDataCollection outputStream = (PSDataCollection)sender; + string? output = outputStream[args.Index]?.ToString(); Logger.Write($"Output: {output}"); } }; @@ -51,7 +52,7 @@ public static class PowerShellExecutor { if (sender is not null) { - var verboseStream = (PSDataCollection)sender; + PSDataCollection verboseStream = (PSDataCollection)sender; Logger.Write($"Verbose: {verboseStream[args.Index].Message}"); } }; @@ -61,7 +62,7 @@ public static class PowerShellExecutor { if (sender is not null) { - var warningStream = (PSDataCollection)sender; + PSDataCollection warningStream = (PSDataCollection)sender; Logger.Write($"Warning: {warningStream[args.Index].Message}"); } }; @@ -72,9 +73,9 @@ public static class PowerShellExecutor if (sender is not null) { // Get the error details - var errorStream = (PSDataCollection)sender; - var error = errorStream[args.Index]; - var errorMessage = $"Error: {error.Exception.Message}\n" + + PSDataCollection errorStream = (PSDataCollection)sender; + ErrorRecord error = errorStream[args.Index]; + string errorMessage = $"Error: {error.Exception.Message}\n" + $"Category: {error.CategoryInfo.Category}\n" + $"Target: {error.TargetObject}\n" + $"Script StackTrace: {error.ScriptStackTrace}\n" + @@ -102,7 +103,7 @@ public static class PowerShellExecutor if (returnOutput) { // Use Invoke to run the script and collect output - var results = psInstance.Invoke(); + Collection results = psInstance.Invoke(); return results.Count != 0 ? results.FirstOrDefault()?.ToString() : null; } else diff --git a/AppControl Manager/Shared Logics/ProcessStarter.cs b/AppControl Manager/Shared Logics/ProcessStarter.cs new file mode 100644 index 000000000..30dc78308 --- /dev/null +++ b/AppControl Manager/Shared Logics/ProcessStarter.cs @@ -0,0 +1,56 @@ +using System; +using System.Diagnostics; + +#nullable enable + +namespace WDACConfig +{ + internal static class ProcessStarter + { + internal static void RunCommand(string command, string? arguments = null) + { + + ProcessStartInfo processInfo; + + if (arguments is not null) + { + processInfo = new() + { + FileName = command, + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + } + else + { + processInfo = new() + { + FileName = command, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + } + + using Process process = new(); + process.StartInfo = processInfo; + _ = process.Start(); + + // Capture output and errors + string output = process.StandardOutput.ReadToEnd(); + string error = process.StandardError.ReadToEnd(); + + process.WaitForExit(); + + if (process.ExitCode != 0) + { + throw new InvalidOperationException($"Command '{command} {arguments}' failed with exit code {process.ExitCode}. Error: {error}"); + } + + } + } +} diff --git a/AppControl Manager/Shared Logics/RemoveSupplementalSigners.cs b/AppControl Manager/Shared Logics/RemoveSupplementalSigners.cs index bf81e4948..e23b2484c 100644 --- a/AppControl Manager/Shared Logics/RemoveSupplementalSigners.cs +++ b/AppControl Manager/Shared Logics/RemoveSupplementalSigners.cs @@ -47,7 +47,7 @@ public static void RemoveSupplementalSigners(string path) // Loop through each SupplementalPolicySigners node foreach (XmlNode supplementalPolicySignersNode in supplementalPolicySignersNodes) { - var supplementalPolicySigners = supplementalPolicySignersNode.SelectNodes("ns:SupplementalPolicySigner", codeIntegrityPolicy.NamespaceManager); + XmlNodeList? supplementalPolicySigners = supplementalPolicySignersNode.SelectNodes("ns:SupplementalPolicySigner", codeIntegrityPolicy.NamespaceManager); // Get unique SignerIds foreach (XmlElement node in supplementalPolicySigners!) @@ -60,10 +60,10 @@ public static void RemoveSupplementalSigners(string path) } // Remove corresponding Signers - foreach (var signerId in signerIds) + foreach (string signerId in signerIds) { XmlNodeList? signersToRemove = codeIntegrityPolicy.SiPolicyNode.SelectNodes($"ns:Signers/ns:Signer[@ID='{signerId}']", codeIntegrityPolicy.NamespaceManager); - if (signersToRemove != null) + if (signersToRemove is not null) { foreach (XmlNode signerNode in signersToRemove) { diff --git a/AppControl Manager/Shared Logics/SnapBackGuarantee.cs b/AppControl Manager/Shared Logics/SnapBackGuarantee.cs index 327d43648..493521e97 100644 --- a/AppControl Manager/Shared Logics/SnapBackGuarantee.cs +++ b/AppControl Manager/Shared Logics/SnapBackGuarantee.cs @@ -9,6 +9,8 @@ namespace WDACConfig public static class SnapBackGuarantee { + private static readonly string savePath = Path.Combine(GlobalVars.UserConfigDir, "EnforcedModeSnapBack.cmd"); + /// /// 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. @@ -26,7 +28,7 @@ public static void Create(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"); + ManagementScope scope = new(@"root\Microsoft\Windows\TaskScheduler"); // Establish connection to the WMI namespace scope.Connect(); @@ -35,7 +37,7 @@ public static void Create(string path) 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"] = "cmd.exe"; // The PowerShell command to run, downloading and deploying the drivers block list @@ -97,7 +99,7 @@ public static void Create(string path) } // Extract CIM instance for further use in task registration - var triggerCimInstance = (ManagementBaseObject)triggerResult["cmdletOutput"]; + ManagementBaseObject triggerCimInstance = (ManagementBaseObject)triggerResult["cmdletOutput"]; #endregion @@ -142,7 +144,7 @@ public static void Create(string path) registerInParams["TaskName"] = "EnforcedModeSnapBack"; // 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) @@ -158,7 +160,7 @@ public static void Create(string path) // 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 @@ -179,5 +181,19 @@ REM Deleting this CMD file itself // An alternative way to do this which is less reliable because RunOnce key can be deleted by 3rd party programs during installation etc. } + + + /// + /// Removes the SnapBack guarantee scheduled task and the related .bat file + /// + public static void Remove() + { + TaskSchedulerHelper.Delete("EnforcedModeSnapBack", @"\", ""); + + if (Path.Exists(savePath)) + { + File.Delete(savePath); + } + } } } diff --git a/AppControl Manager/Shared Logics/TaskSchedulerHelper.cs b/AppControl Manager/Shared Logics/TaskSchedulerHelper.cs new file mode 100644 index 000000000..02caa6fca --- /dev/null +++ b/AppControl Manager/Shared Logics/TaskSchedulerHelper.cs @@ -0,0 +1,75 @@ +using System; +using System.Linq; +using System.Management; + +namespace WDACConfig +{ + internal static class TaskSchedulerHelper + { + /// + /// Deletes a scheduled task if it exists + /// + /// The task name to be deleted + /// The path where the task is located + /// The folder name of the task must not have and back slashes in it + /// + public static void Delete(string taskName, string taskPath, string taskFolderName) + { + try + { + // The WMI query to select the specific instance of MSFT_ScheduledTask + string query = "SELECT * FROM MSFT_ScheduledTask"; + + // Defining the WMI namespace + string scope = @"\\.\Root\Microsoft\Windows\TaskScheduler"; + + // Creating a ManagementObjectSearcher instance with the query and scope + using ManagementObjectSearcher searcher = new(scope, query); + + // Execute the WMI query and retrieve the results + using ManagementObjectCollection results = searcher.Get(); + + // If no tasks were found, return false + if (results.Count == 0) + { + Logger.Write("No tasks found in Task Scheduler."); + return; + } + + // Iterate through each ManagementObject in the results + foreach (ManagementObject obj in results.Cast()) + { + string? name = obj["TaskName"]?.ToString(); + string? path = obj["TaskPath"]?.ToString(); + + // Match based on taskName and taskPath + if (string.Equals(name, taskName, StringComparison.OrdinalIgnoreCase) && + string.Equals(path, taskPath, StringComparison.OrdinalIgnoreCase)) + { + try + { + // Call DeleteInstance to delete the task + obj.Delete(); + + Logger.Write($"Task '{taskName}' with path '{taskPath}' was deleted successfully."); + + return; + } + catch (ManagementException ex) + { + Logger.Write($"Failed to delete task '{taskName}' with path '{taskPath}': {ex.Message}"); + return; + } + } + } + + Logger.Write($"No task found with the name '{taskName}' and path '{taskPath}'."); + } + catch (ManagementException e) + { + // for any ManagementException that may occur during the WMI query execution + Logger.Write($"An error occurred while querying for WMI data: {e.Message}"); + } + } + } +} diff --git a/AppControl Manager/Shared Logics/Types And Definitions/ChainElement.cs b/AppControl Manager/Shared Logics/Types And Definitions/ChainElement.cs index e7ac1844b..f920d3eb8 100644 --- a/AppControl Manager/Shared Logics/Types And Definitions/ChainElement.cs +++ b/AppControl Manager/Shared Logics/Types And Definitions/ChainElement.cs @@ -13,13 +13,15 @@ public enum CertificateType Leaf = 2 } - public sealed class ChainElement(string subjectcn, string issuercn, DateTime notafter, string tbsvalue, X509Certificate2 certificate, CertificateType type) + public sealed class ChainElement(string subjectCN, string issuerCN, DateTime notAfter, DateTime notBefore, string tbsValue, X509Certificate2 certificate, CertificateType type, X509Certificate2 issuer) { - 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 string SubjectCN { get; set; } = subjectCN; + public string IssuerCN { get; set; } = issuerCN; + public DateTime NotAfter { get; set; } = notAfter; + public DateTime NotBefore { get; set; } = notBefore; + public string TBSValue { get; set; } = tbsValue; public X509Certificate2 Certificate { get; set; } = certificate; public CertificateType Type { get; set; } = type; + public X509Certificate2 Issuer { get; set; } = issuer; } } diff --git a/AppControl Manager/Shared Logics/Types And Definitions/CodeIntegrityPolicy.cs b/AppControl Manager/Shared Logics/Types And Definitions/CodeIntegrityPolicy.cs index af062fac7..b59ab9625 100644 --- a/AppControl Manager/Shared Logics/Types And Definitions/CodeIntegrityPolicy.cs +++ b/AppControl Manager/Shared Logics/Types And Definitions/CodeIntegrityPolicy.cs @@ -23,15 +23,18 @@ internal sealed class CodeIntegrityPolicy // Their assignments in other methods must happen through their respective nodes exposed by the instantiated class internal string PolicyType { get; } + internal string PolicyID { get; } + internal string BasePolicyID { get; } + internal XmlNode PolicyIDNode { get; } internal XmlNode BasePolicyIDNode { get; } internal XmlNode SignersNode { get; } - internal XmlNode UMCI_SigningScenarioNode { get; } + internal XmlNode? UMCI_SigningScenarioNode { get; } internal XmlNode KMCI_SigningScenarioNode { get; } - internal XmlNode UMCI_ProductSignersNode { get; } + internal XmlNode? UMCI_ProductSignersNode { get; } internal XmlNode KMCI_ProductSignersNode { get; } internal XmlNode CiSignersNode { get; } @@ -67,17 +70,15 @@ internal CodeIntegrityPolicy(string? xmlFilePath, XmlDocument? xmlDocument) SignersNode = SiPolicyNode.SelectSingleNode("ns:Signers", NamespaceManager) ?? throw new InvalidOperationException("Signers node not found"); - // Find the SigningScenario Node for User Mode - UMCI_SigningScenarioNode = SiPolicyNode.SelectSingleNode("ns:SigningScenarios/ns:SigningScenario[@Value='12']", NamespaceManager) - ?? throw new InvalidOperationException("UMCI Signing Scenario node not found"); + // Find the SigningScenario Node for User Mode - It is nullable because Kernel-Mode Strict policy won't have this section + UMCI_SigningScenarioNode = SiPolicyNode.SelectSingleNode("ns:SigningScenarios/ns:SigningScenario[@Value='12']", NamespaceManager); // Find the SigningScenario Node for Kernel Mode KMCI_SigningScenarioNode = SiPolicyNode.SelectSingleNode("ns:SigningScenarios/ns:SigningScenario[@Value='131']", NamespaceManager) ?? throw new InvalidOperationException("KMCI Signing Scenario node not found"); - // Find the ProductSigners Node for User Mode - UMCI_ProductSignersNode = SiPolicyNode.SelectSingleNode("ns:SigningScenarios/ns:SigningScenario[@Value='12']/ns:ProductSigners", NamespaceManager) - ?? throw new InvalidOperationException("UMCI Product Signers node not found"); + // Find the ProductSigners Node for User Mode - It is nullable because Kernel-Mode Strict policy won't have this section + UMCI_ProductSignersNode = SiPolicyNode.SelectSingleNode("ns:SigningScenarios/ns:SigningScenario[@Value='12']/ns:ProductSigners", NamespaceManager); // Find the ProductSigners Node for Kernel Mode KMCI_ProductSignersNode = SiPolicyNode.SelectSingleNode("ns:SigningScenarios/ns:SigningScenario[@Value='131']/ns:ProductSigners", NamespaceManager) @@ -149,12 +150,18 @@ internal CodeIntegrityPolicy(string? xmlFilePath, XmlDocument? xmlDocument) _ = SiPolicyNode.AppendChild(newBasePolicyIDNode); BasePolicyIDNode = newBasePolicyIDNode; + + BasePolicyID = newRandomGUIDString; } else { BasePolicyIDNode = basePolicyIDNode; + + + BasePolicyID = basePolicyIDNode.InnerText; } + #endregion @@ -174,10 +181,14 @@ internal CodeIntegrityPolicy(string? xmlFilePath, XmlDocument? xmlDocument) _ = SiPolicyNode.AppendChild(newPolicyIDNode); PolicyIDNode = newPolicyIDNode; + + PolicyID = newRandomGUIDString; } else { PolicyIDNode = policyIDNode; + + PolicyID = policyIDNode.InnerText; } #endregion diff --git a/AppControl Manager/Shared Logics/Types And Definitions/FileAttrib.cs b/AppControl Manager/Shared Logics/Types And Definitions/FileAttrib.cs new file mode 100644 index 000000000..32b2e1ad6 --- /dev/null +++ b/AppControl Manager/Shared Logics/Types And Definitions/FileAttrib.cs @@ -0,0 +1,20 @@ +using System.Xml; + +namespace WDACConfig +{ + // This class represents a node within a Code Integrity XML file + public sealed class FileAttrib + { + public required XmlNode Node { get; set; } + public required XmlNode Signer { get; set; } + public required XmlNode AllowedSigner { get; set; } + public required XmlNode FileAttribRef { get; set; } + public required string Id { get; set; } + public string? MinimumFileVersion { get; set; } + public string? FileDescription { get; set; } + public string? FileName { get; set; } + public string? InternalName { get; set; } + public string? FilePath { get; set; } + public string? ProductName { get; set; } + } +} diff --git a/AppControl Manager/Shared Logics/Types And Definitions/FileAttribComparer .cs b/AppControl Manager/Shared Logics/Types And Definitions/FileAttribComparer .cs new file mode 100644 index 000000000..afb383b09 --- /dev/null +++ b/AppControl Manager/Shared Logics/Types And Definitions/FileAttribComparer .cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; + +namespace WDACConfig +{ + /// + /// A custom equality comparer for the FileAttrib class. + /// This comparer is used to determine the uniqueness of FileAttrib instances + /// based on specific properties. + /// + public sealed class FileAttribComparer : IEqualityComparer + { + /// + /// Determines whether two FileAttrib instances are equal. + /// The instances are considered equal if all six specified properties are the same. + /// + /// Both FileAttrib Instances Are Null: + /// Result: Equal (true). + /// + /// One Instance Is Null: + /// Result: Not equal (false). + /// + /// Both Instances Are Not Null, with Some Properties Null: + /// If a property is null in both instances, that property is considered equal. + /// If a property is null in one instance but has a value in the other, that property is considered not equal. + /// If all specified properties are equal (including handling of nulls), the instances are equal; otherwise, they are not. + /// + /// The first FileAttrib instance to compare. + /// The second FileAttrib instance to compare. + /// true if the instances are equal; otherwise, false. + public bool Equals(FileAttrib? x, FileAttrib? y) + { + // If both are null, they are considered equal + if (x is null && y is null) + return true; + + // If one is null and the other is not, they are not equal + if (x is null || y is null) + return false; + + // Compare the specified properties for equality using string comparison + return string.Equals(x.MinimumFileVersion, y.MinimumFileVersion, StringComparison.OrdinalIgnoreCase) && + string.Equals(x.FileDescription, y.FileDescription, StringComparison.OrdinalIgnoreCase) && + string.Equals(x.FileName, y.FileName, StringComparison.OrdinalIgnoreCase) && + string.Equals(x.InternalName, y.InternalName, StringComparison.OrdinalIgnoreCase) && + string.Equals(x.FilePath, y.FilePath, StringComparison.OrdinalIgnoreCase) && + string.Equals(x.ProductName, y.ProductName, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Returns a hash code for the given FileAttrib instance. + /// The hash code is computed based on the six specified properties. + /// + /// The FileAttrib instance for which to get the hash code. + /// A hash code for the given FileAttrib instance. + public int GetHashCode(FileAttrib? obj) + { + // Return a default hash code (0) if obj is null to avoid exceptions + if (obj is null) return 0; + + // Initialize a hash variable + int hash = 17; + + // Combine hash codes of the specified properties using a common technique + // unchecked allows overflow but does not decrease accuracy of the HashSet. + // When implementing GetHashCode, the important aspect is that the same input will always yield the same output. + // Even if that output results in a wrapped value due to overflow, it will consistently represent that specific object. + + unchecked + { + hash = hash * 31 + (obj.MinimumFileVersion?.GetHashCode(StringComparison.OrdinalIgnoreCase) ?? 0); + hash = hash * 31 + (obj.FileDescription?.GetHashCode(StringComparison.OrdinalIgnoreCase) ?? 0); + hash = hash * 31 + (obj.FileName?.GetHashCode(StringComparison.OrdinalIgnoreCase) ?? 0); + hash = hash * 31 + (obj.InternalName?.GetHashCode(StringComparison.OrdinalIgnoreCase) ?? 0); + hash = hash * 31 + (obj.FilePath?.GetHashCode(StringComparison.OrdinalIgnoreCase) ?? 0); + hash = hash * 31 + (obj.ProductName?.GetHashCode(StringComparison.OrdinalIgnoreCase) ?? 0); + } + + // Return the computed hash code + return hash; + } + } +} diff --git a/AppControl Manager/Shared Logics/Types And Definitions/PolicyHashObj.cs b/AppControl Manager/Shared Logics/Types And Definitions/PolicyHashObj.cs index 87de7475c..3e3cae2a1 100644 --- a/AppControl Manager/Shared Logics/Types And Definitions/PolicyHashObj.cs +++ b/AppControl Manager/Shared Logics/Types And Definitions/PolicyHashObj.cs @@ -22,14 +22,14 @@ public override bool Equals(object? obj) return false; } - var other = (PolicyHashObj)obj; + PolicyHashObj 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; + return HashValue is not null ? StringComparer.OrdinalIgnoreCase.GetHashCode(HashValue) : 0; } } } diff --git a/AppControl Manager/Shared Logics/Types And Definitions/WinTrust.cs b/AppControl Manager/Shared Logics/Types And Definitions/WinTrust.cs index d8a829e85..a754dd97e 100644 --- a/AppControl Manager/Shared Logics/Types And Definitions/WinTrust.cs +++ b/AppControl Manager/Shared Logics/Types And Definitions/WinTrust.cs @@ -15,8 +15,9 @@ internal static partial class WinTrust 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( + [LibraryImport("WinTrust.dll", StringMarshalling = StringMarshalling.Utf16)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial 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 @@ -25,15 +26,17 @@ internal static extern bool CryptCATAdminAcquireContext2( ); // 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( + [LibraryImport("WinTrust.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial 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( + [LibraryImport("WinTrust.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial 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 @@ -47,27 +50,27 @@ internal static extern bool CryptCATAdminCalcHashFromFileHandle3( #region This section is related to the MeowParser class operations - // P/Invoke declaration to import the 'CryptCATOpen' function from 'WinTrust.dll'. + // 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( + [LibraryImport("WinTrust.dll", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] + internal static partial 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'. + // 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( + [LibraryImport("WinTrust.dll", SetLastError = true)] + internal static partial 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'. + // 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. + [LibraryImport("WinTrust.dll", SetLastError = true)] + internal static partial IntPtr CryptCATClose(IntPtr MainCryptProviderHandle); // Closes the catalog context. #endregion @@ -76,8 +79,8 @@ internal static extern IntPtr CryptCATEnumerateMember( #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 + [LibraryImport("Wintrust.dll", StringMarshalling = StringMarshalling.Utf16)] // an attribute to specify the DLL name and the character set + internal static partial 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 @@ -112,6 +115,7 @@ public enum WinVerifyTrustResult : uint 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)] @@ -123,8 +127,8 @@ internal static extern WinVerifyTrustResult WinVerifyTrust( 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); + [LibraryImport("wintrust.dll", SetLastError = true)] + internal static partial IntPtr WTHelperProvDataFromStateData(IntPtr hStateData); #endregion diff --git a/AppControl Manager/Shared Logics/WDAC Simulation/Arbitrator.cs b/AppControl Manager/Shared Logics/WDAC Simulation/Arbitrator.cs index 31040d995..d404217d4 100644 --- a/AppControl Manager/Shared Logics/WDAC Simulation/Arbitrator.cs +++ b/AppControl Manager/Shared Logics/WDAC Simulation/Arbitrator.cs @@ -59,7 +59,8 @@ internal static SimulationOutput Compare(SimulationInput simulationInput) // 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 bool EKUsMatch = false; - foreach (var EKU in signer.CertEKU!) + + foreach (string EKU in signer.CertEKU!) { if (simulationInput.EKUOIDs is not null && simulationInput.EKUOIDs.Contains(EKU)) { @@ -101,7 +102,7 @@ internal static SimulationOutput Compare(SimulationInput simulationInput) // Loop through each candidate WHQL chain package foreach (ChainPackage chainPackage in WHQLChainPackagesCandidates) { - List? CurrentOpusData = []; + List CurrentOpusData = []; try { diff --git a/AppControl Manager/Shared Logics/WDAC Simulation/GetCertificateDetails.cs b/AppControl Manager/Shared Logics/WDAC Simulation/GetCertificateDetails.cs index e40a5ce02..948eb797b 100644 --- a/AppControl Manager/Shared Logics/WDAC Simulation/GetCertificateDetails.cs +++ b/AppControl Manager/Shared Logics/WDAC Simulation/GetCertificateDetails.cs @@ -31,60 +31,93 @@ public static List Get(AllFileSigners[] completeSignatureResult) X509Chain currentChain = completeSignatureResult[i].Chain; SignedCms currentSignedCms = completeSignatureResult[i].Signer; + // Get the number of certificates in the current chain uint certificatesInChainCount = (uint)currentChain.ChainElements.Count; switch (certificatesInChainCount) { - // If the chain includes a Root, Leaf and at least one Intermediate certificate + // If the chain includes a Root, Leaf, and at least one Intermediate certificate case > 2: + + #region Root Certificate + // The last certificate in the chain is the Root certificate X509Certificate2 currentRootCertificate = currentChain.ChainElements[^1].Certificate; - var rootCertificate = new ChainElement( + // Create the root certificate element + ChainElement rootCertificate = new( CryptoAPI.GetNameString(currentRootCertificate.Handle, CryptoAPI.CERT_NAME_SIMPLE_DISPLAY_TYPE, null, false), // SubjectCN CryptoAPI.GetNameString(currentRootCertificate.Handle, CryptoAPI.CERT_NAME_SIMPLE_DISPLAY_TYPE, null, true), // IssuerCN currentRootCertificate.NotAfter, + currentRootCertificate.NotBefore, CertificateHelper.GetTBSCertificate(currentRootCertificate), currentRootCertificate, // Append the certificate object itself to the output object as well - CertificateType.Root + CertificateType.Root, + currentRootCertificate // root certificate's issuer is itself ); - // An array to hold the Intermediate Certificate(s) of the current chain + #endregion + + #region Intermediate Certificate(s) + + // List to hold the Intermediate Certificate(s) of the current chain List intermediateCertificates = []; - // All the certificates in between are Intermediate certificates + // Loop through intermediate certificates, which are all certificates in between the root and leaf certificates + // That is why we start from 1 and end at certificatesInChainCount - 1 (excluding the root and leaf certificates) for (int j = 1; j < certificatesInChainCount - 1; j++) { + // Get the current intermediate certificate X509Certificate2 cert = currentChain.ChainElements[j].Certificate; - // Create a collection of intermediate certificates + // Get the issuer certificate for the current intermediate certificate (which will be the next certificate in the chain) + X509Certificate2 intermediateIssuerCertificate = currentChain.ChainElements[j + 1].Certificate; + + // Add the intermediate certificate to the list intermediateCertificates.Add(new ChainElement( CryptoAPI.GetNameString(cert.Handle, CryptoAPI.CERT_NAME_SIMPLE_DISPLAY_TYPE, null, false), CryptoAPI.GetNameString(cert.Handle, CryptoAPI.CERT_NAME_SIMPLE_DISPLAY_TYPE, null, true), cert.NotAfter, + cert.NotBefore, CertificateHelper.GetTBSCertificate(cert), cert, // Append the certificate object itself to the output object as well - CertificateType.Intermediate + CertificateType.Intermediate, + intermediateIssuerCertificate )); } + #endregion + + #region Leaf Certificate + // The first certificate in the chain is the Leaf certificate X509Certificate2 currentLeafCertificate = currentChain.ChainElements[0].Certificate; - var leafCertificate = new ChainElement( + // The issuer of the leaf certificate will be the first intermediate certificate or root if none + X509Certificate2 leafIssuerCertificate = intermediateCertificates.Count > 0 + ? intermediateCertificates[0].Certificate + : rootCertificate.Certificate; + + // Create the leaf certificate element + ChainElement leafCertificate = new( CryptoAPI.GetNameString(currentLeafCertificate.Handle, CryptoAPI.CERT_NAME_SIMPLE_DISPLAY_TYPE, null, false), CryptoAPI.GetNameString(currentLeafCertificate.Handle, CryptoAPI.CERT_NAME_SIMPLE_DISPLAY_TYPE, null, true), currentLeafCertificate.NotAfter, + currentLeafCertificate.NotBefore, CertificateHelper.GetTBSCertificate(currentLeafCertificate), currentLeafCertificate, // Append the certificate object itself to the output object as well - CertificateType.Leaf + CertificateType.Leaf, + leafIssuerCertificate // Set issuer for the leaf certificate ); + #endregion + + // Add the final package with root, intermediate, and leaf certificates finalObject.Add(new ChainPackage( currentChain, // The entire current chain of the certificate currentSignedCms, // The entire current SignedCms object rootCertificate, - [.. intermediateCertificates], + [.. intermediateCertificates], // Spread the intermediate certificates list leafCertificate )); @@ -92,35 +125,54 @@ public static List Get(AllFileSigners[] completeSignatureResult) // If the chain only includes a Root and Leaf certificate case 2: + + #region Root Certificate + // The last certificate in the chain is the Root certificate currentRootCertificate = currentChain.ChainElements[^1].Certificate; + // Create the root certificate element rootCertificate = new ChainElement( CryptoAPI.GetNameString(currentRootCertificate.Handle, CryptoAPI.CERT_NAME_SIMPLE_DISPLAY_TYPE, null, false), // SubjectCN CryptoAPI.GetNameString(currentRootCertificate.Handle, CryptoAPI.CERT_NAME_SIMPLE_DISPLAY_TYPE, null, true), // IssuerCN currentRootCertificate.NotAfter, + currentRootCertificate.NotBefore, CertificateHelper.GetTBSCertificate(currentRootCertificate), currentRootCertificate, // Append the certificate object itself to the output object as well - CertificateType.Root + CertificateType.Root, + currentRootCertificate // root certificate's issuer is itself ); + #endregion + + #region Leaf Certificate + // The first certificate in the chain is the Leaf certificate currentLeafCertificate = currentChain.ChainElements[0].Certificate; + // The issuer of the leaf certificate will be the root certificate + leafIssuerCertificate = rootCertificate.Certificate; + + // Create the leaf certificate element leafCertificate = new ChainElement( CryptoAPI.GetNameString(currentLeafCertificate.Handle, CryptoAPI.CERT_NAME_SIMPLE_DISPLAY_TYPE, null, false), CryptoAPI.GetNameString(currentLeafCertificate.Handle, CryptoAPI.CERT_NAME_SIMPLE_DISPLAY_TYPE, null, true), currentLeafCertificate.NotAfter, + currentLeafCertificate.NotBefore, CertificateHelper.GetTBSCertificate(currentLeafCertificate), currentLeafCertificate, // Append the certificate object itself to the output object as well - CertificateType.Leaf + CertificateType.Leaf, + leafIssuerCertificate // Set issuer for the leaf certificate ); + #endregion + + // Add the final package with root and leaf certificates finalObject.Add(new ChainPackage( currentChain, // The entire current chain of the certificate currentSignedCms, // The entire current SignedCms object rootCertificate, - null, + null, // No intermediate certificates leafCertificate )); @@ -128,32 +180,43 @@ public static List Get(AllFileSigners[] completeSignatureResult) // If the chain only includes a Root certificate case 1: + + #region Root Certificate + // The only certificate in the chain is the Root certificate currentRootCertificate = currentChain.ChainElements[0].Certificate; + // Create the root certificate element rootCertificate = new ChainElement( CryptoAPI.GetNameString(currentRootCertificate.Handle, CryptoAPI.CERT_NAME_SIMPLE_DISPLAY_TYPE, null, false), // SubjectCN CryptoAPI.GetNameString(currentRootCertificate.Handle, CryptoAPI.CERT_NAME_SIMPLE_DISPLAY_TYPE, null, true), // IssuerCN currentRootCertificate.NotAfter, + currentRootCertificate.NotBefore, CertificateHelper.GetTBSCertificate(currentRootCertificate), currentRootCertificate, // Append the certificate object itself to the output object as well - CertificateType.Root + CertificateType.Root, + currentRootCertificate // root certificate's issuer is itself ); + #endregion + + // Add the final package with only the root certificate finalObject.Add(new ChainPackage( currentChain, // The entire current chain of the certificate currentSignedCms, // The entire current SignedCms object rootCertificate, - null, - null + null, // No intermediate certificates + null // No leaf certificate )); break; + default: break; } } + // Return the final list of chain packages return finalObject; } } diff --git a/AppControl Manager/Shared Logics/WDAC Simulation/GetFileRuleOutput.cs b/AppControl Manager/Shared Logics/WDAC Simulation/GetFileRuleOutput.cs index 420588c23..18d95a2c1 100644 --- a/AppControl Manager/Shared Logics/WDAC Simulation/GetFileRuleOutput.cs +++ b/AppControl Manager/Shared Logics/WDAC Simulation/GetFileRuleOutput.cs @@ -8,7 +8,7 @@ namespace WDACConfig { - public static class GetFileRuleOutput + public partial class GetFileRuleOutput { /// /// A function that accepts an App Control policy XML content and creates an output array that contains the file rules that are based on file hashes. @@ -26,7 +26,7 @@ public static HashSet Get(XmlDocument xml) 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); + XmlNodeList? fileRules = xml.SelectNodes("//si:FileRules/si:Allow", nsmgr); if (fileRules is not null) { foreach (XmlNode fileRule in fileRules) @@ -34,18 +34,18 @@ public static HashSet Get(XmlDocument xml) if (fileRule.Attributes is not null) { // Extract the hash value from the Hash attribute - var hashValue = fileRule.Attributes["Hash"]?.InnerText; + string? hashValue = fileRule.Attributes["Hash"]?.InnerText; // Extract the hash type and file path from the FriendlyName attribute using regex - var friendlyName = fileRule.Attributes["FriendlyName"]?.InnerText; + string? friendlyName = fileRule.Attributes["FriendlyName"]?.InnerText; if (!string.IsNullOrEmpty(friendlyName)) { // Extract the hash type from the FriendlyName attribute using regex - var hashTypeMatch = 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; + Match hashTypeMatch = MyRegex().Match(friendlyName); + string hashType = hashTypeMatch.Success ? hashTypeMatch.Groups[1].Value : string.Empty; // Extract the file path from the FriendlyName attribute using regex - var filePathForHash = Regex.Replace(friendlyName, @" (Hash (Sha1|Sha256|Page Sha1|Page Sha256|Authenticode SIP Sha256))$", string.Empty, RegexOptions.IgnoreCase); + string filePathForHash = MyRegex1().Replace(friendlyName, string.Empty); // Add the extracted values of the current Hash rule to the output HashSet if (!string.IsNullOrEmpty(hashValue) && !string.IsNullOrEmpty(hashType) && !string.IsNullOrEmpty(filePathForHash)) @@ -65,5 +65,12 @@ public static HashSet Get(XmlDocument xml) // Return the output HashSet return outputHashInfoProcessing; } + + [GeneratedRegex(@".* (Hash (Sha1|Sha256|Page Sha1|Page Sha256|Authenticode SIP Sha256))$", RegexOptions.Compiled | RegexOptions.IgnoreCase)] + private static partial Regex MyRegex(); + + [GeneratedRegex(@" (Hash (Sha1|Sha256|Page Sha1|Page Sha256|Authenticode SIP Sha256))$", RegexOptions.Compiled | RegexOptions.IgnoreCase)] + private static partial Regex MyRegex1(); + } } diff --git a/AppControl Manager/Shared Logics/WDAC Simulation/GetSignerInfo.cs b/AppControl Manager/Shared Logics/WDAC Simulation/GetSignerInfo.cs index eba616e45..2a1e7e8b3 100644 --- a/AppControl Manager/Shared Logics/WDAC Simulation/GetSignerInfo.cs +++ b/AppControl Manager/Shared Logics/WDAC Simulation/GetSignerInfo.cs @@ -101,7 +101,7 @@ public static List Get(XmlDocument xmlContent) // Get all of the Signer nodes in the Signers node XmlNodeList? signerNodes = codeIntegrityPolicy.SiPolicyNode.SelectNodes("ns:Signers/ns:Signer", codeIntegrityPolicy.NamespaceManager); - if (signerNodes != null) + if (signerNodes is not null) { // Loop through each Signer node and extract all of their information foreach (XmlNode signer in signerNodes) @@ -229,9 +229,9 @@ public static List Get(XmlDocument xmlContent) // Iterate through the rule IDs and find matching FileAttrib nodes in the dictionary that holds the FileAttrib nodes in the node // Get all the FileAttribs associated with the signer - foreach (var id in ruleIds) + foreach (string id in ruleIds) { - if (fileAttribDictionary.TryGetValue(id, out var matchingFileAttrib)) + if (fileAttribDictionary.TryGetValue(id, out XmlNode? matchingFileAttrib)) { FileAttribsAssociatedWithTheSigner.Add(matchingFileAttrib); } @@ -411,7 +411,7 @@ private static HashSet GetSignerIds(XmlNode siPolicyNode, XmlNamespaceMa signerNodes = siPolicyNode.SelectNodes($"ns:SigningScenarios/ns:SigningScenario[@Value='{scenarioValue}']/ns:ProductSigners/ns:DeniedSigners/ns:DeniedSigner", namespaceManager); } - if (signerNodes != null) + if (signerNodes is not null) { foreach (XmlNode signerNode in signerNodes) { diff --git a/AppControl Manager/Shared Logics/WldpQuerySecurityPolicy.cs b/AppControl Manager/Shared Logics/WldpQuerySecurityPolicy.cs index 094c4a9e7..c702d2b60 100644 --- a/AppControl Manager/Shared Logics/WldpQuerySecurityPolicy.cs +++ b/AppControl Manager/Shared Logics/WldpQuerySecurityPolicy.cs @@ -22,10 +22,10 @@ public struct UNICODE_STRING public IntPtr Buffer; } - public static class WldpQuerySecurityPolicyWrapper + public static partial class WldpQuerySecurityPolicyWrapper { - [DllImport("Wldp.dll", CharSet = CharSet.Unicode)] - internal static extern int WldpQuerySecurityPolicy( + [LibraryImport("Wldp.dll")] + internal static partial int WldpQuerySecurityPolicy( ref UNICODE_STRING Provider, ref UNICODE_STRING Key, ref UNICODE_STRING ValueName, diff --git a/AppControl Manager/Shared Logics/CiPolicyUtility.cs b/AppControl Manager/Shared Logics/XMLOps/CiPolicyUtility.cs similarity index 100% rename from AppControl Manager/Shared Logics/CiPolicyUtility.cs rename to AppControl Manager/Shared Logics/XMLOps/CiPolicyUtility.cs diff --git a/AppControl Manager/Shared Logics/XMLOps/ClearCiPolicySemantic.cs b/AppControl Manager/Shared Logics/XMLOps/ClearCiPolicySemantic.cs index 1e6e28040..f486d384f 100644 --- a/AppControl Manager/Shared Logics/XMLOps/ClearCiPolicySemantic.cs +++ b/AppControl Manager/Shared Logics/XMLOps/ClearCiPolicySemantic.cs @@ -23,10 +23,15 @@ public static void Clear(string xmlFilePath) baseNodes.Add(codeIntegrityPolicy.SiPolicyNode.SelectSingleNode("ns:EKUs", codeIntegrityPolicy.NamespaceManager)!); baseNodes.Add(codeIntegrityPolicy.SiPolicyNode.SelectSingleNode("ns:FileRules", codeIntegrityPolicy.NamespaceManager)!); baseNodes.Add(codeIntegrityPolicy.SiPolicyNode.SelectSingleNode("ns:Signers", codeIntegrityPolicy.NamespaceManager)!); - baseNodes.Add(codeIntegrityPolicy.UMCI_ProductSignersNode); + + if (codeIntegrityPolicy.UMCI_ProductSignersNode is not null) + { + baseNodes.Add(codeIntegrityPolicy.UMCI_ProductSignersNode); + } + baseNodes.Add(codeIntegrityPolicy.KMCI_ProductSignersNode); - XmlNode? fileRulesRefUMC = codeIntegrityPolicy.UMCI_ProductSignersNode.SelectSingleNode("ns:FileRulesRef", codeIntegrityPolicy.NamespaceManager); + XmlNode? fileRulesRefUMC = codeIntegrityPolicy.UMCI_ProductSignersNode?.SelectSingleNode("ns:FileRulesRef", codeIntegrityPolicy.NamespaceManager); if (fileRulesRefUMC is not null) { baseNodes.Add(fileRulesRefUMC); diff --git a/AppControl Manager/Shared Logics/EditGUIDs.cs b/AppControl Manager/Shared Logics/XMLOps/EditGUIDs.cs similarity index 100% rename from AppControl Manager/Shared Logics/EditGUIDs.cs rename to AppControl Manager/Shared Logics/XMLOps/EditGUIDs.cs diff --git a/AppControl Manager/Shared Logics/XMLOps/MergeSignersSemantic.cs b/AppControl Manager/Shared Logics/XMLOps/MergeSignersSemantic.cs index b57524be1..b62730f7d 100644 --- a/AppControl Manager/Shared Logics/XMLOps/MergeSignersSemantic.cs +++ b/AppControl Manager/Shared Logics/XMLOps/MergeSignersSemantic.cs @@ -29,7 +29,7 @@ public int GetHashCode(XmlNode obj) // A HashSet to store unique XmlNode objects internal sealed class UniqueXmlNodeSet { - private HashSet nodes; + private readonly HashSet nodes; // Constructor initializes the HashSet with XmlNodeComparer as the comparer logic public UniqueXmlNodeSet() @@ -91,7 +91,7 @@ internal static void Merge(string xmlFilePath) CodeIntegrityPolicy codeIntegrityPolicy = new(xmlFilePath, null); // Get the User Mode Signing Scenario node - XmlNode? allowedSigners12 = codeIntegrityPolicy.UMCI_ProductSignersNode.SelectSingleNode("ns:AllowedSigners", codeIntegrityPolicy.NamespaceManager); + XmlNode? allowedSigners12 = codeIntegrityPolicy.UMCI_ProductSignersNode?.SelectSingleNode("ns:AllowedSigners", codeIntegrityPolicy.NamespaceManager); // Get the Kernel Mode Signing Scenario node XmlNode? allowedSigners131 = codeIntegrityPolicy.KMCI_ProductSignersNode.SelectSingleNode("ns:AllowedSigners", codeIntegrityPolicy.NamespaceManager); @@ -130,7 +130,7 @@ internal static void Merge(string xmlFilePath) foreach (XmlNode node in fileRulesElements) { string? id = node?.Attributes?["ID"]?.Value; - if (id != null) + if (id is not null) { _ = fileRulesValidID_HashSet.Add(id); } @@ -396,15 +396,10 @@ internal static void Merge(string xmlFilePath) codeIntegrityPolicy.SignersNode.RemoveAll(); // Clear the existing AllowedSigners and CiSigners nodes from any type of Signer - if (allowedSigners131 is not null) - { - allowedSigners131.RemoveAll(); - } + allowedSigners131?.RemoveAll(); + + allowedSigners12?.RemoveAll(); - if (allowedSigners12 is not null) - { - allowedSigners12.RemoveAll(); - } codeIntegrityPolicy.CiSignersNode.RemoveAll(); diff --git a/AppControl Manager/Shared Logics/XMLOps/NewCertificateSignerRules.cs b/AppControl Manager/Shared Logics/XMLOps/NewCertificateSignerRules.cs index ad0261305..709c0da63 100644 --- a/AppControl Manager/Shared Logics/XMLOps/NewCertificateSignerRules.cs +++ b/AppControl Manager/Shared Logics/XMLOps/NewCertificateSignerRules.cs @@ -20,6 +20,12 @@ public static void Create(string xmlFilePath, List sig // Instantiate the policy CodeIntegrityPolicy codeIntegrityPolicy = new(xmlFilePath, null); + // This method isn't suitable for strict Kernel-Mode policy + if (codeIntegrityPolicy.UMCI_ProductSignersNode is null) + { + throw new InvalidOperationException("NewCertificateSignerRules.Create method isn't suitable for strict Kernel-Mode policy"); + } + #region // Find AllowedSigners node in each ProductSigners node diff --git a/AppControl Manager/Shared Logics/XMLOps/NewFilePublisherLevelRules.cs b/AppControl Manager/Shared Logics/XMLOps/NewFilePublisherLevelRules.cs index 800d0083f..e009f16b9 100644 --- a/AppControl Manager/Shared Logics/XMLOps/NewFilePublisherLevelRules.cs +++ b/AppControl Manager/Shared Logics/XMLOps/NewFilePublisherLevelRules.cs @@ -28,6 +28,13 @@ internal static void Create(string xmlFilePath, List // Instantiate the policy CodeIntegrityPolicy codeIntegrityPolicy = new(xmlFilePath, null); + // This method isn't suitable for strict Kernel-Mode policy + if (codeIntegrityPolicy.UMCI_ProductSignersNode is null) + { + throw new InvalidOperationException("NewFilePublisherLevelRules.Create method isn't suitable for strict Kernel-Mode policy"); + } + + #region // Find AllowedSigners node in each ProductSigners node @@ -87,17 +94,17 @@ internal static void Create(string xmlFilePath, List newFileAttribNode.SetAttribute("FileName", filePublisherData.OriginalFileName); } - if (!string.IsNullOrWhiteSpace(filePublisherData.InternalName)) + else if (!string.IsNullOrWhiteSpace(filePublisherData.InternalName)) { newFileAttribNode.SetAttribute("InternalName", filePublisherData.InternalName); } - if (!string.IsNullOrWhiteSpace(filePublisherData.FileDescription)) + else if (!string.IsNullOrWhiteSpace(filePublisherData.FileDescription)) { newFileAttribNode.SetAttribute("FileDescription", filePublisherData.FileDescription); } - if (!string.IsNullOrWhiteSpace(filePublisherData.ProductName)) + else if (!string.IsNullOrWhiteSpace(filePublisherData.ProductName)) { newFileAttribNode.SetAttribute("ProductName", filePublisherData.ProductName); } diff --git a/AppControl Manager/Shared Logics/XMLOps/NewHashLevelRules.cs b/AppControl Manager/Shared Logics/XMLOps/NewHashLevelRules.cs index 047a2b430..d9f6ea17b 100644 --- a/AppControl Manager/Shared Logics/XMLOps/NewHashLevelRules.cs +++ b/AppControl Manager/Shared Logics/XMLOps/NewHashLevelRules.cs @@ -28,6 +28,12 @@ public static void Create(string xmlFilePath, List hashes) // Instantiate the policy CodeIntegrityPolicy codeIntegrityPolicy = new(xmlFilePath, null); + // This method isn't suitable for strict Kernel-Mode policy + if (codeIntegrityPolicy.UMCI_ProductSignersNode is null) + { + throw new InvalidOperationException("NewHashLevelRules.Create method isn't suitable for strict Kernel-Mode policy"); + } + Logger.Write($"NewHashLevelRules: There are {hashes.Count} Hash rules to be added to the XML file '{xmlFilePath}'"); #region diff --git a/AppControl Manager/Shared Logics/XMLOps/NewPFNLevelRules.cs b/AppControl Manager/Shared Logics/XMLOps/NewPFNLevelRules.cs index 241381d3c..8ff857fe7 100644 --- a/AppControl Manager/Shared Logics/XMLOps/NewPFNLevelRules.cs +++ b/AppControl Manager/Shared Logics/XMLOps/NewPFNLevelRules.cs @@ -20,6 +20,14 @@ public static void Create(string xmlFilePath, List packageFamilyNames) // Instantiate the policy CodeIntegrityPolicy codeIntegrityPolicy = new(xmlFilePath, null); + + // This method isn't suitable for strict Kernel-Mode policy + if (codeIntegrityPolicy.UMCI_ProductSignersNode is null) + { + throw new InvalidOperationException("NewPFNLevelRules.Create method isn't suitable for strict Kernel-Mode policy"); + } + + // Find the FileRules node XmlNode fileRulesNode = codeIntegrityPolicy.SiPolicyNode.SelectSingleNode("ns:FileRules", codeIntegrityPolicy.NamespaceManager) ?? throw new InvalidOperationException("file rules node could not be found."); diff --git a/AppControl Manager/Shared Logics/XMLOps/NewPublisherLevelRules.cs b/AppControl Manager/Shared Logics/XMLOps/NewPublisherLevelRules.cs index 312f3cc51..6611d652d 100644 --- a/AppControl Manager/Shared Logics/XMLOps/NewPublisherLevelRules.cs +++ b/AppControl Manager/Shared Logics/XMLOps/NewPublisherLevelRules.cs @@ -28,6 +28,16 @@ internal static void Create(string xmlFilePath, List pub // Instantiate the policy CodeIntegrityPolicy codeIntegrityPolicy = new(xmlFilePath, null); + + + // This method isn't suitable for strict Kernel-Mode policy + if (codeIntegrityPolicy.UMCI_ProductSignersNode is null) + { + throw new InvalidOperationException("NewPublisherLevelRules.Create method isn't suitable for strict Kernel-Mode policy"); + } + + + #region // Find AllowedSigners node in each ProductSigners node diff --git a/AppControl Manager/Shared Logics/XMLOps/RemoveAllowElementsSemantic.cs b/AppControl Manager/Shared Logics/XMLOps/RemoveAllowElementsSemantic.cs index e1393ed04..b34ce452e 100644 --- a/AppControl Manager/Shared Logics/XMLOps/RemoveAllowElementsSemantic.cs +++ b/AppControl Manager/Shared Logics/XMLOps/RemoveAllowElementsSemantic.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Xml; #nullable enable @@ -26,6 +27,14 @@ internal static void Remove(string xmlFilePath) // Instantiate the policy CodeIntegrityPolicy codeIntegrityPolicy = new(xmlFilePath, null); + + // This method isn't suitable for strict Kernel-Mode policy + if (codeIntegrityPolicy.UMCI_ProductSignersNode is null) + { + throw new InvalidOperationException("RemoveAllowElementsSemantic.Remove method isn't suitable for strict Kernel-Mode policy"); + } + + // Get the node XmlNode fileRulesNode = codeIntegrityPolicy.SiPolicyNode.SelectSingleNode("ns:FileRules", codeIntegrityPolicy.NamespaceManager)!; diff --git a/AppControl Manager/Shared Logics/XMLOps/RemoveDuplicateFileAttribSemantic.cs b/AppControl Manager/Shared Logics/XMLOps/RemoveDuplicateFileAttribSemantic.cs index 55bb19bfc..06444fcbc 100644 --- a/AppControl Manager/Shared Logics/XMLOps/RemoveDuplicateFileAttribSemantic.cs +++ b/AppControl Manager/Shared Logics/XMLOps/RemoveDuplicateFileAttribSemantic.cs @@ -1,5 +1,5 @@ -using System.Collections.Generic; -using System.Linq; +using System; +using System.Collections.Generic; using System.Xml; #nullable enable @@ -15,10 +15,13 @@ internal static class RemoveDuplicateFileAttribSemantic /// In order to be considered fully duplicate, they must also be associated with Signers whose IDs are in the same SigningScenario. /// /// So for example, if two nodes have the same FileName and MinimumFileVersion, but they are associated with 2 different Signers, one in kernel mode and the other in user mode signing scenario, they are not considered duplicates. - /// After deduplication, the function updates the FileAttribRef RuleID for associated Signers by setting the RuleID of the removed duplicate FileAttrib elements to the RuleID of the unique remaining FileAttrib element. /// /// This is according to the CI Schema /// + /// Any stray node is also removed. They are nodes that don't have an associated node in any node.er> + /// + /// If needed a custom HashSet can be created based on the custom comparer so that between 2 identical fileAttribs, one with lower version will be kept while other one will be removed + /// /// /// internal static void Remove(string xmlFilePath) @@ -27,6 +30,16 @@ internal static void Remove(string xmlFilePath) // Instantiate the policy CodeIntegrityPolicy codeIntegrityPolicy = new(xmlFilePath, null); + + + // This method isn't suitable for strict Kernel-Mode policy + if (codeIntegrityPolicy.UMCI_ProductSignersNode is null) + { + throw new InvalidOperationException("RemoveDuplicateFileAttribSemantic.Remove method isn't suitable for strict Kernel-Mode policy"); + } + + + // Get all of the nodes inside the node XmlNodeList? fileAttribNodes = codeIntegrityPolicy.SiPolicyNode.SelectNodes("ns:FileRules//ns:FileAttrib", codeIntegrityPolicy.NamespaceManager); @@ -36,218 +49,116 @@ internal static void Remove(string xmlFilePath) return; } - // A Dictionary to store FileAttrib nodes based on their properties - Dictionary> fileAttribNodeCollection = []; + // To store each nodes and its associated details for Kernel-Mode + HashSet kernelModeFileAttribs = new(new FileAttribComparer()); - // A Dictionary to store each unique FileAttrib key along with its associated unique Signer IDs - Dictionary> fileAttribSignerCollection = []; + // To store each nodes and its associated details for User-Mode + HashSet userModeFileAttribs = new(new FileAttribComparer()); - // A Dictionary to store each Signer ID (associated with the current FileAttrib node) and its associated node's ID - Dictionary allowedSignerCollection = []; - // Iterate through each FileAttrib nodes - foreach (XmlNode fileAttrib in fileAttribNodes) + // Iterate over each node + foreach (XmlNode item in fileAttribNodes) { + // Get the ID + string ID = item.Attributes!["ID"]!.Value; - // Get the relevant properties of the current node - string fileAttribID = fileAttrib.Attributes!["ID"]!.Value; - - string? MinimumFileVersion = fileAttrib.Attributes?["MinimumFileVersion"]?.Value; - string? FileName = fileAttrib.Attributes?["FileName"]?.Value; - string? InternalName = fileAttrib.Attributes?["InternalName"]?.Value; - string? FileDescription = fileAttrib.Attributes?["FileDescription"]?.Value; - string? FilePath = fileAttrib.Attributes?["FilePath"]?.Value; - string? ProductName = fileAttrib.Attributes?["ProductName"]?.Value; - + // Get the node that contains the node with the same RuleID as the ID of the current node in the loop + XmlNode signer = codeIntegrityPolicy.SignersNode.SelectSingleNode($"ns:Signer[ns:FileAttribRef/@RuleID='{ID}']", codeIntegrityPolicy.NamespaceManager)!; - // Generate a unique key based on relevant properties - string uniqueKey = $"{MinimumFileVersion}-{FileName}-{InternalName}-{FileDescription}-{FilePath}-{ProductName}"; + // Get the Signer's ID + string signerID = signer.Attributes!["ID"]!.Value; - // Check if the key already exists in the dictionary - if (!fileAttribNodeCollection.TryGetValue(uniqueKey, out List? possibleFileAttrib)) - { - // If not, add the key and store the FileAttrib node as a list - _ = fileAttribNodeCollection.TryAdd(uniqueKey, [fileAttrib]); - } + // Get the from the signer that is associated with the current node + XmlNode? fileAttribRefNode = signer.SelectSingleNode($"ns:FileAttribRef[@RuleID='{ID}']", codeIntegrityPolicy.NamespaceManager); - // If the key already exists, append the current FileAttrib node to the existing list of nodes - else + if (fileAttribRefNode is null) { - possibleFileAttrib.Add(fileAttrib); - } - - // At this point, each node in the XML file is identified and grouped together based on their properties - - // Get the Signer ID associated with the current FileAttrib - XmlNode? signer = codeIntegrityPolicy.SignersNode.SelectSingleNode($"ns:Signer[ns:FileAttribRef/@RuleID='{fileAttribID}']", codeIntegrityPolicy.NamespaceManager); - - string signerID; - - if (signer is not null) - { - signerID = signer.Attributes!["ID"]!.Value; - } - else - { - // Signer ID cannot be null! + // It's a stray node so don't include it in any HashSet, which will essentially mean we will not include it in the final XML file continue; } - // Add the Unique FileAttrib key and its associated Signer ID to the dictionary - if (!fileAttribSignerCollection.TryGetValue(uniqueKey, out HashSet? possibleFileAttribSigner)) - { - // If not, add the key and store the Signer ID as value - _ = fileAttribSignerCollection.TryAdd(uniqueKey, [signerID]); - } - - // If the key already exists, append the Signer ID to the existing HashSet of strings - else - { - _ = possibleFileAttribSigner.Add(signerID); - } - - - // Get the ID of the node that is associated with the current Signer ID - // Checks all nodes throughout the XML - XmlNode? allowedSigner = codeIntegrityPolicy.SiPolicyNode.SelectSingleNode($"//ns:SigningScenario[ns:ProductSigners/ns:AllowedSigners/ns:AllowedSigner[@SignerId='{signerID}']]", codeIntegrityPolicy.NamespaceManager); - - if (allowedSigner is null) - { - // each signer must have an node! - continue; - } - - string allowedSignerID = allowedSigner.Attributes!["ID"]!.Value; - - - // Add the Signer ID and its associated allowedSigner ID to the dictionary - if (!allowedSignerCollection.ContainsKey(signerID)) - { - _ = allowedSignerCollection.TryAdd(signerID, allowedSignerID); - } - } - - - // Iterate through the dictionary to find and remove duplicate nodes - foreach (string key in fileAttribNodeCollection.Keys) - { - - // Only proceed if there are more than one FileAttrib node for this key - // Indicating that more than 1 node with the same exact attributes (except for ID and FriendlyName) exist - if (fileAttribNodeCollection.TryGetValue(key, out List? possibleExistingNodes) && possibleExistingNodes.Count == 1) - { - continue; - } + // Try to get the node in both SigningScenarios, give us the node itself and tells us which scenario this FileAttrib belongs to (through its Signer association). + XmlNode? UMCI = codeIntegrityPolicy.UMCI_ProductSignersNode.SelectSingleNode($"ns:AllowedSigners/ns:AllowedSigner[@SignerId='{signerID}']", codeIntegrityPolicy.NamespaceManager); + XmlNode? KMCI = codeIntegrityPolicy.KMCI_ProductSignersNode.SelectSingleNode($"ns:AllowedSigners/ns:AllowedSigner[@SignerId='{signerID}']", codeIntegrityPolicy.NamespaceManager); - if (possibleExistingNodes is null) + if (UMCI is null && KMCI is null) { continue; } - // Get the associated SignerID of each duplicate node - // Each node is associated with a node and we get that node's ID - _ = fileAttribSignerCollection.TryGetValue(key, out HashSet? signerIDs); + // Get attributes of the current node + string? MinimumFileVersion = item.Attributes?["MinimumFileVersion"]?.Value; + string? FileName = item.Attributes?["FileName"]?.Value; + string? InternalName = item.Attributes?["InternalName"]?.Value; + string? FileDescription = item.Attributes?["FileDescription"]?.Value; + string? FilePath = item.Attributes?["FilePath"]?.Value; + string? ProductName = item.Attributes?["ProductName"]?.Value; - if (signerIDs is null) + FileAttrib fileAttrib = new() { - continue; - } - - // A HashSet to store the unique AllowedSigner IDs associated with the Signer IDs - HashSet allowedSignerCol = []; - - // Get the unique node IDs associated with the Signer IDs - // Each node associated with each duplicate node has an node - foreach (string id in signerIDs) + Node = item, + Signer = signer, + AllowedSigner = UMCI ?? KMCI!, + FileAttribRef = fileAttribRefNode, + Id = ID, + MinimumFileVersion = MinimumFileVersion, + FileDescription = FileDescription, + FileName = FileName, + InternalName = InternalName, + FilePath = FilePath, + ProductName = ProductName + }; + + + // If the current node belongs to User-Mode Signing Scenario + if (UMCI is not null) { - _ = allowedSignerCollection.TryGetValue(id, out string? possibleSigningScenario); - - if (possibleSigningScenario is not null) + // If the node is a duplicate one based on the custom comparer, then not only we don't include it in the final XML file + // We should also remove its associated node from the node + if (!userModeFileAttribs.Add(fileAttrib)) { - _ = allowedSignerCol.Add(possibleSigningScenario); + _ = fileAttrib.FileAttribRef.ParentNode?.RemoveChild(fileAttrib.FileAttribRef); } } - - // If there are multiple unique AllowedSigner IDs associated with this set of Signer IDs - if (allowedSignerCol.Count > 1) - { - // Skip deduplication as the Signer IDs are in different Signing scenarios, meaning both User and Kernel nodes are involved so it shouldn't be touched - // According to the schema, each signer must have only 1 node in only one SigningScenario. - // The same signer cannot belong to kernel Mode and User Mode scenarios at the same time. - // This logic is based on that. - continue; - } + // If the current node belongs to Kernel-Mode Signing Scenario else { - // Remove duplicates by keeping only the first FileAttrib element - XmlNode firstFileAttrib = possibleExistingNodes.First(); - - string? firstFileAttribID = firstFileAttrib.Attributes?["ID"]?.Value; - - if (firstFileAttribID is null) + // If the node is a duplicate one based on the custom comparer, then not only we don't include it in the final XML file + // We should also remove its associated node from the node + if (!kernelModeFileAttribs.Add(fileAttrib)) { - continue; + _ = fileAttrib.FileAttribRef.ParentNode?.RemoveChild(fileAttrib.FileAttribRef); } + } + } - // Iterate through the remaining FileAttrib elements, starting from the 2nd element - for (int i = 1; i < possibleExistingNodes.Count; i++) - { - - // Get the duplicate FileAttrib element to remove based on the index - XmlNode duplicateFileAttrib = possibleExistingNodes[i]; - - string? duplicateFileAttribID = duplicateFileAttrib?.Attributes?["ID"]?.Value; - - if (duplicateFileAttribID is null) - { - continue; - } - - // Update FileAttribRef RuleID for associated Signers - foreach (string id in signerIDs) - { - - // Get the Signer element associated with this Signer ID - XmlNode? signer = codeIntegrityPolicy.SignersNode.SelectSingleNode($"ns:Signer[@ID='{id}']", codeIntegrityPolicy.NamespaceManager); - - if (signer is null) - { - continue; - } - - - // Get the FileAttribRef node associated with the duplicate FileAttrib node, from the signer - XmlNode? fileAttribRef = signer.SelectSingleNode($"ns:FileAttribRef[@RuleID='{duplicateFileAttribID}']", codeIntegrityPolicy.NamespaceManager); - - if (fileAttribRef is null) - { - continue; - } - - // Updating the RuleID of the duplicate of the Signer before removing it and setting it to the RuleID of the unique remaining FileAttrib element - - XmlNodeList signerAttribs = signer.SelectNodes("ns:FileAttribRef", codeIntegrityPolicy.NamespaceManager)!; - - List signerAttribsIDs = []; - - foreach (XmlElement signerAttr in signerAttribs) - { - signerAttribsIDs.Add(signerAttr.GetAttribute("RuleID", codeIntegrityPolicy.NameSpaceURI)); - } - - if (!signerAttribsIDs.Contains(firstFileAttribID)) - { - ((XmlElement)fileAttribRef).SetAttribute("RuleID", firstFileAttribID); - } - } + // Remove all nodes inside the node + foreach (XmlNode node in fileAttribNodes) + { + // Remove each node from its parent + _ = node.ParentNode?.RemoveChild(node); + } - _ = duplicateFileAttrib?.ParentNode?.RemoveChild(duplicateFileAttrib); - } + // Add the nodes back to the node + if (kernelModeFileAttribs.Count > 0) + { + foreach (FileAttrib item in kernelModeFileAttribs) + { + // Add the node back to the parent + _ = codeIntegrityPolicy.SiPolicyNode.SelectSingleNode("ns:FileRules", codeIntegrityPolicy.NamespaceManager)!.AppendChild(item.Node); + } + } + if (userModeFileAttribs.Count > 0) + { + foreach (FileAttrib item in userModeFileAttribs) + { + // Add the node back to the parent + _ = codeIntegrityPolicy.SiPolicyNode.SelectSingleNode("ns:FileRules", codeIntegrityPolicy.NamespaceManager)!.AppendChild(item.Node); } - } + // Save the changes to the XML file codeIntegrityPolicy.XmlDocument.Save(xmlFilePath); } diff --git a/AppControl Manager/Shared Logics/XMLOps/SignerAndHashBuilder.cs b/AppControl Manager/Shared Logics/XMLOps/SignerAndHashBuilder.cs index d3b2428c6..801d42400 100644 --- a/AppControl Manager/Shared Logics/XMLOps/SignerAndHashBuilder.cs +++ b/AppControl Manager/Shared Logics/XMLOps/SignerAndHashBuilder.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections; using System.Collections.Generic; -using System.Globalization; +using WDACConfig.IntelGathering; #nullable enable @@ -39,97 +37,82 @@ public static class SignerAndHashBuilder /// 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) + public static FileBasedInfoPackage BuildSignerAndHashObjects(List data, ScanLevels level = ScanLevels.FilePublisher, bool publisherToHash = false) { - // An array to store the Signers created with FilePublisher Level + // To store the Signers created with FilePublisher Level List filePublisherSigners = []; - // An array to store the Signers created with Publisher Level + // To store the Signers created with Publisher Level List publisherSigners = []; - // An array to store the FileAttributes created using Hash Level + // To store the FileAttributes created using Hash Level List completeHashes = []; - // Lists to separate data - List signedFilePublisherData = []; - List signedPublisherData = []; - List unsignedData = []; + // Lists to separate data initially + List signedFilePublisherData = []; + List signedPublisherData = []; + List unsignedData = []; Logger.Write("BuildSignerAndHashObjects: Starting the data separation process."); // Data separation based on the level - switch (level.ToLowerInvariant()) + switch (level) { // If Hash level is used then add everything to the Unsigned data so Hash rules will be created for them - case "hash": + case ScanLevels.Hash: Logger.Write("BuildSignerAndHashObjects: Using only Hash level."); - foreach (Hashtable item in data) - { - if (item is null) - { - Logger.Write("BuildSignerAndHashObjects: Found a null item in data."); - } - else - { - unsignedData.Add(item); - } - } + // Assign the entire data to the unsigned Data list + unsignedData = data; + break; - // If Publisher level is used then add all Signed data to the SignedPublisherData list and Unsigned data to the Hash list - case "publisher": + // If Publisher level is used then add all Signed data to the SignedPublisherData list and Unsigned data to the Hash list + case ScanLevels.Publisher: Logger.Write("BuildSignerAndHashObjects: Using Publisher -> Hash levels."); - foreach (Hashtable item in data) + foreach (FileIdentity item in data) { - if (item is null) - { - Logger.Write("BuildSignerAndHashObjects: Found a null item in data."); - } - else if (string.Equals(item["SignatureStatus"]?.ToString(), "Signed", StringComparison.OrdinalIgnoreCase) && !publisherToHash) + // If the current data is signed and publisherToHash is not used, which would indicate Hash level rules must be created for Publisher level data + // And make sure the file is not ECC Signed + if (item.SignatureStatus is SignatureStatus.Signed && !publisherToHash && item.IsECCSigned != true) { signedPublisherData.Add(item); } else { + // Assign the data to the Hash list unsignedData.Add(item); } } break; - // Detect and separate FilePublisher, Publisher and Hash (Unsigned) data if the level is Auto or FilePublisher - default: + case ScanLevels.FilePublisher: + + // Detect and separate FilePublisher, Publisher and Hash (Unsigned) data if the level is Auto or FilePublisher Logger.Write("BuildSignerAndHashObjects: Using FilePublisher -> Publisher -> Hash levels."); // Loop over each data - foreach (Hashtable item in data) + foreach (FileIdentity item in data) { - if (item is null) - { - 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 (string.Equals(item["SignatureStatus"]?.ToString(), "Signed", StringComparison.OrdinalIgnoreCase)) + if (item.SignatureStatus is SignatureStatus.Signed && item.IsECCSigned != true) { - // Safely get values from the item and check for null or whitespace - bool hasNoFileAttributes = string.IsNullOrWhiteSpace(item["OriginalFileName"]?.ToString()) && - string.IsNullOrWhiteSpace(item["InternalName"]?.ToString()) && - string.IsNullOrWhiteSpace(item["FileDescription"]?.ToString()) && - string.IsNullOrWhiteSpace(item["ProductName"]?.ToString()); + // Get values from the item and check for null, empty or whitespace + bool hasNoFileAttributes = string.IsNullOrWhiteSpace(item.OriginalFileName) && + string.IsNullOrWhiteSpace(item.InternalName) && + string.IsNullOrWhiteSpace(item.FileDescription) && + string.IsNullOrWhiteSpace(item.ProductName); - bool hasNoFileVersion = string.IsNullOrWhiteSpace(item["FileVersion"]?.ToString()); + bool hasNoFileVersion = item.FileVersion is null; if (hasNoFileAttributes || hasNoFileVersion) { @@ -140,22 +123,28 @@ public static FileBasedInfoPackage BuildSignerAndHashObjects(Hashtable[] data, s } else { - Logger.Write($"BuildSignerAndHashObjects: Passing Publisher rule to the hash array for the file: {(string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? item["FileName"] : item["File Name"])}"); + Logger.Write($"BuildSignerAndHashObjects: Passing Publisher rule to the hash array for the file: {item.FilePath}"); // Add the current signed data to Unsigned data array so that Hash rules will be created for it instead unsignedData.Add(item); } } else { + // If the file has the required info for a FilePublisher rule level creation, add the data to the FilePublisher list signedFilePublisherData.Add(item); } } else { + // Add the data to the Hash list unsignedData.Add(item); } } break; + + default: + + break; } Logger.Write($"BuildSignerAndHashObjects: {signedFilePublisherData.Count} FilePublisher Rules."); @@ -164,214 +153,140 @@ public static FileBasedInfoPackage BuildSignerAndHashObjects(Hashtable[] data, s Logger.Write("BuildSignerAndHashObjects: Processing FilePublisher data."); - foreach (Hashtable signedData in signedFilePublisherData) + foreach (FileIdentity signedData in signedFilePublisherData) { // Create a new FilePublisherSignerCreator object 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 is null) - { - Logger.Write("BuildSignerAndHashObjects: correlatedEventsDataValues is null."); - } - else + // Loop through each correlated event and process the certificate details + foreach (FileSignerInfo corDataValue in signedData.FileSignerInfos) { - // 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) + // 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["IssuerTBSHash"]?.ToString(); - string? publisherTBSHash = corDataValue["PublisherTBSHash"]?.ToString(); - - // currentCorData to store the current SignerInfo/Correlated - CertificateDetailsCreator? currentCorData; - // Perform the check with null-safe values - if (string.IsNullOrWhiteSpace(issuerTBSHash) && !string.IsNullOrWhiteSpace(publisherTBSHash)) - { - 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"); + string? issuerTBSHash = corDataValue.IssuerTBSHash; + string? publisherTBSHash = corDataValue.PublisherTBSHash; - currentCorData = new CertificateDetailsCreator( - corDataValue["PublisherTBSHash"]!.ToString()!, - corDataValue["PublisherName"]!.ToString()!, - corDataValue["PublisherTBSHash"]!.ToString()!, - corDataValue["PublisherName"]!.ToString()! - ); + // currentCorData to store the current SignerInfo/Correlated + CertificateDetailsCreator? currentCorData; - } - else - { - currentCorData = new CertificateDetailsCreator( - corDataValue["IssuerTBSHash"]!.ToString()!, - corDataValue["IssuerName"]!.ToString()!, - corDataValue["PublisherTBSHash"]!.ToString()!, - corDataValue["PublisherName"]!.ToString()! - ); - } + if (string.IsNullOrWhiteSpace(issuerTBSHash) && !string.IsNullOrWhiteSpace(publisherTBSHash)) + { + Logger.Write($"BuildSignerAndHashObjects: Intermediate Certificate TBS hash is empty for the file: {signedData.FilePath}, using the leaf certificate TBS hash instead"); - // Add the Certificate details to the CurrentFilePublisherSigner's CertificateDetails property - currentFilePublisherSigner.CertificateDetails.Add(currentCorData); + currentCorData = new CertificateDetailsCreator( + corDataValue.PublisherTBSHash!, + corDataValue.PublisherName!, + corDataValue.PublisherTBSHash!, + corDataValue.PublisherName! + ); } + else + { + currentCorData = new CertificateDetailsCreator( + corDataValue.IssuerTBSHash!, + corDataValue.IssuerName!, + corDataValue.PublisherTBSHash!, + corDataValue.PublisherName! + ); + } + + // Add the Certificate details to the CurrentFilePublisherSigner's CertificateDetails property + currentFilePublisherSigner.CertificateDetails.Add(currentCorData); + } #region Initialize properties - string? fileVersionString = signedData["FileVersion"]?.ToString(); - string? fileDescription = signedData["FileDescription"]?.ToString(); - string? internalName = signedData["InternalName"]?.ToString(); - string? originalFileName = signedData["OriginalFileName"]?.ToString(); - string? productName = signedData["ProductName"]?.ToString(); - string? fileName = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) - ? (signedData["FileName"]?.ToString()) - : (signedData["File Name"]?.ToString()); - - string? sha256 = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) - ? (signedData["SHA256"]?.ToString()) - : (signedData["SHA256 Hash"]?.ToString()); - - string? sha1 = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) - ? (signedData["SHA1"]?.ToString()) - : (signedData["SHA1 Hash"]?.ToString()); - _ = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) - ? (signedData["SiSigningScenario"]?.ToString()) - : (signedData["SI Signing Scenario"]?.ToString()); - - // 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)) - { - Logger.Write($"SHA256 is empty for the file: {(string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) ? signedData["FileName"] : signedData["File Name"])}"); - } + currentFilePublisherSigner.FileVersion = signedData.FileVersion; + currentFilePublisherSigner.FileDescription = signedData.FileDescription; + currentFilePublisherSigner.InternalName = signedData.InternalName; + currentFilePublisherSigner.OriginalFileName = signedData.OriginalFileName; + currentFilePublisherSigner.ProductName = signedData.ProductName; + currentFilePublisherSigner.FileName = signedData.FilePath; + currentFilePublisherSigner.AuthenticodeSHA256 = signedData.SHA256Hash; + currentFilePublisherSigner.AuthenticodeSHA1 = signedData.SHA1Hash; + + currentFilePublisherSigner.SiSigningScenario = signedData.SISigningScenario; + #endregion - if (string.IsNullOrWhiteSpace(currentFilePublisherSigner.AuthenticodeSHA1)) - { - 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); } + Logger.Write("BuildSignerAndHashObjects: Processing Publisher data."); - foreach (Hashtable signedData in signedPublisherData) + foreach (FileIdentity signedData in signedPublisherData) { // Create a new PublisherSignerCreator object 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 is null) - { - Logger.Write("BuildSignerAndHashObjects: correlatedEventsDataValues is null."); - } - else + // Process each correlated event + foreach (FileSignerInfo corDataValue in signedData.FileSignerInfos) { - // Process each correlated event - foreach (Hashtable corDataValue in correlatedEventsDataValues) - { - // Safely access dictionary values and handle nulls - string? issuerTBSHash = corDataValue["IssuerTBSHash"]?.ToString(); - string? issuerName = corDataValue["IssuerName"]?.ToString(); - string? publisherTBSHash = corDataValue["PublisherTBSHash"]?.ToString(); - string? publisherName = corDataValue["PublisherName"]?.ToString(); + string? issuerTBSHash = corDataValue.IssuerTBSHash; + string? issuerName = corDataValue.IssuerName; + string? publisherTBSHash = corDataValue.PublisherTBSHash; + string? publisherName = corDataValue.PublisherName; - CertificateDetailsCreator? currentCorData; - // Perform the check with null-safe values - if (string.IsNullOrWhiteSpace(issuerTBSHash) && !string.IsNullOrWhiteSpace(publisherTBSHash)) - { - 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 CertificateDetailsCreator( - publisherTBSHash, - publisherName!, - publisherTBSHash, - publisherName! - ); - } - else - { - // Create a new CertificateDetailsCreator object with the safely retrieved and used values - currentCorData = new CertificateDetailsCreator( - issuerTBSHash!, - issuerName!, - publisherTBSHash!, - publisherName! - ); - } + CertificateDetailsCreator? currentCorData; - // Add the Certificate details to the CurrentPublisherSigner's CertificateDetails property - currentPublisherSigner.CertificateDetails.Add(currentCorData); + if (string.IsNullOrWhiteSpace(issuerTBSHash) && !string.IsNullOrWhiteSpace(publisherTBSHash)) + { + Logger.Write($"BuildSignerAndHashObjects: Intermediate Certificate TBS hash is empty for the file: {signedData.FilePath}, using the leaf certificate TBS hash instead"); + + // Create a new CertificateDetailsCreator object with the retrieved and used values + currentCorData = new CertificateDetailsCreator( + publisherTBSHash, + publisherName!, + publisherTBSHash, + publisherName! + ); + } + else + { + // Create a new CertificateDetailsCreator object with the retrieved and used values + currentCorData = new 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 + + currentPublisherSigner.FileName = signedData.FilePath; + currentPublisherSigner.AuthenticodeSHA256 = signedData.SHA256Hash; + currentPublisherSigner.AuthenticodeSHA1 = signedData.SHA1Hash; + currentPublisherSigner.SiSigningScenario = signedData.SISigningScenario; + // Add the completed PublisherSigner to the list publisherSigners.Add(currentPublisherSigner); } + Logger.Write("BuildSignerAndHashObjects: Processing Unsigned Hash data."); - foreach (Hashtable hashData in unsignedData) + foreach (FileIdentity hashData in unsignedData) { - if (hashData is null) - { - Logger.Write("BuildSignerAndHashObjects: Found a null hashData item."); - continue; - } - - string? sha256 = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) - ? (hashData["SHA256"]?.ToString()) - : (hashData["SHA256 Hash"]?.ToString()); - - string? sha1 = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) - ? (hashData["SHA1"]?.ToString()) - : (hashData["SHA1 Hash"]?.ToString()); - - string? fileName = string.Equals(incomingDataType, "MDEAH", StringComparison.OrdinalIgnoreCase) - ? (hashData["FileName"]?.ToString()) - : (hashData["File Name"]?.ToString()); - 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); + string? sha256 = hashData.SHA256Hash; + string? sha1 = hashData.SHA1Hash; + string? fileName = hashData.FilePath; + int siSigningScenario = hashData.SISigningScenario; if (string.IsNullOrWhiteSpace(sha256) || string.IsNullOrWhiteSpace(sha1) || string.IsNullOrWhiteSpace(fileName)) { diff --git a/AppControl Manager/Shared Logics/XMLOps/UpdateHvciOptions.cs b/AppControl Manager/Shared Logics/XMLOps/UpdateHvciOptions.cs index 7a28cc861..0f042c261 100644 --- a/AppControl Manager/Shared Logics/XMLOps/UpdateHvciOptions.cs +++ b/AppControl Manager/Shared Logics/XMLOps/UpdateHvciOptions.cs @@ -23,7 +23,7 @@ public static void Update(string filePath) XmlNode? hvciOptionsNode = codeIntegrityPolicy.SiPolicyNode.SelectSingleNode("ns:HvciOptions", codeIntegrityPolicy.NamespaceManager); // If HvciOptions node exists - if (hvciOptionsNode != null) + if (hvciOptionsNode is not null) { // Ensure the value is "2" if (hvciOptionsNode.InnerText != "2") diff --git a/AppControl Manager/Shared Logics/XMLOps/XMLOps.cs b/AppControl Manager/Shared Logics/XMLOps/XMLOps.cs index 9205f7f53..c63c4168f 100644 --- a/AppControl Manager/Shared Logics/XMLOps/XMLOps.cs +++ b/AppControl Manager/Shared Logics/XMLOps/XMLOps.cs @@ -23,9 +23,15 @@ public static void Initiate(FileBasedInfoPackage incomingData, string xmlFilePat Logger.Write("Merging the Signer Level rules"); RemoveDuplicateFileAttribSemantic.Remove(xmlFilePath); - // 2 passes are needed - MergeSignersSemantic.Merge(xmlFilePath); - MergeSignersSemantic.Merge(xmlFilePath); + // 2 passes are needed - Needs improvements + // MergeSignersSemantic.Merge(xmlFilePath); + // MergeSignersSemantic.Merge(xmlFilePath); + + // Adding this so that the Merge cmdlet won't complain + CloseEmptyXmlNodesSemantic.Close(xmlFilePath); + + // Replacement for the above method + PolicyMerger.Merge([xmlFilePath], xmlFilePath); // This method runs twice, once for signed data and once for unsigned data CloseEmptyXmlNodesSemantic.Close(xmlFilePath); diff --git a/AppControl Manager/Shared Logics/XmlFilePathExtractor.cs b/AppControl Manager/Shared Logics/XmlFilePathExtractor.cs index 6990efe2c..8a17cd67b 100644 --- a/AppControl Manager/Shared Logics/XmlFilePathExtractor.cs +++ b/AppControl Manager/Shared Logics/XmlFilePathExtractor.cs @@ -19,13 +19,13 @@ public static HashSet GetFilePaths(string xmlFilePath) // Select all nodes with the "Allow" tag XmlNodeList? allowNodes = codeIntegrityPolicy.XmlDocument.SelectNodes("//ns:Allow", codeIntegrityPolicy.NamespaceManager); - if (allowNodes != null) + if (allowNodes is not null) { foreach (XmlNode node in allowNodes) { // Ensure node.Attributes is not null - if (node.Attributes != null && node.Attributes["FilePath"] != null) + if (node.Attributes is not null && node.Attributes["FilePath"] is not null) { // Add the file path to the HashSet _ = filePaths.Add(node.Attributes["FilePath"]!.Value); diff --git a/AppControl Manager/Unique Logic/AppSettings.cs b/AppControl Manager/Unique Logic/AppSettings.cs new file mode 100644 index 000000000..691e0ec78 --- /dev/null +++ b/AppControl Manager/Unique Logic/AppSettings.cs @@ -0,0 +1,44 @@ +using Windows.Storage; + +namespace WDACConfig +{ + // https://learn.microsoft.com/en-us/uwp/api/windows.storage.applicationdata.localsettings + + public static class AppSettings + { + // Save setting to local storage with a specific key and value + public static void SaveSetting(SettingKeys key, object? value) + { + ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings; + localSettings.Values[key.ToString()] = value; + } + + // Retrieve setting from local storage with a specific key + public static T? GetSetting(SettingKeys key) + { + var localSettings = ApplicationData.Current.LocalSettings; + + // Check if the key exists and get the value + if (localSettings.Values.TryGetValue(key.ToString(), out object? value)) + { + // Return value cast to T (for value types, this works with nullable types as well) + return value is T result ? result : default; + } + + // Return default value (null for reference types, or default(T) for value types) + return default; + } + + + // Enum for the setting keys + // Used when saving and retrieving settings + public enum SettingKeys + { + SoundSetting, + NavViewBackground, + NavViewPaneDisplayMode, + AppTheme, + BackDropBackground + } + } +} diff --git a/AppControl Manager/Unique Logic/AppThemeManager.cs b/AppControl Manager/Unique Logic/AppThemeManager.cs new file mode 100644 index 000000000..312255e29 --- /dev/null +++ b/AppControl Manager/Unique Logic/AppThemeManager.cs @@ -0,0 +1,19 @@ +using System; + +namespace WDACConfig +{ + public static class AppThemeManager + { + + // The static event for App theme dark/light changes + // MainWindow listens to this + public static event Action? AppThemeChanged; + + // Method to raise the event + public static void OnAppThemeChanged(string newLocation) + { + AppThemeChanged?.Invoke(newLocation); + } + + } +} diff --git a/AppControl Manager/Unique Logic/NavigationBackgroundManager.cs b/AppControl Manager/Unique Logic/NavigationBackgroundManager.cs new file mode 100644 index 000000000..2dcf1a70e --- /dev/null +++ b/AppControl Manager/Unique Logic/NavigationBackgroundManager.cs @@ -0,0 +1,16 @@ +using System; + +namespace WDACConfig +{ + public static class NavigationBackgroundManager + { + // Event for when the NavigationView background changes + public static event Action? NavViewBackgroundChange; + + // Method to invoke the event + public static void OnNavigationBackgroundChanged(bool isBackgroundOn) + { + NavViewBackgroundChange?.Invoke(isBackgroundOn); + } + } +} diff --git a/AppControl Manager/Unique Logic/NavigationViewLocationManager.cs b/AppControl Manager/Unique Logic/NavigationViewLocationManager.cs new file mode 100644 index 000000000..29d2a7faa --- /dev/null +++ b/AppControl Manager/Unique Logic/NavigationViewLocationManager.cs @@ -0,0 +1,17 @@ +using System; + +namespace WDACConfig +{ + public static class NavigationViewLocationManager + { + // The static event for NavigationView location changes + // MainWindow listens to this to set the NavigationView's location + public static event Action? NavigationViewLocationChanged; + + // Method to raise the event when the background is changed + public static void OnNavigationViewLocationChanged(string newLocation) + { + NavigationViewLocationChanged?.Invoke(newLocation); + } + } +} diff --git a/AppControl Manager/Unique Logic/SoundManager.cs b/AppControl Manager/Unique Logic/SoundManager.cs new file mode 100644 index 000000000..a7e45aba1 --- /dev/null +++ b/AppControl Manager/Unique Logic/SoundManager.cs @@ -0,0 +1,16 @@ +using System; + +namespace WDACConfig +{ + public static class SoundManager + { + // Event to notify when the sound setting is changed + public static event Action? SoundSettingChanged; + + // Method to invoke the event + public static void OnSoundSettingChanged(bool isSoundOn) + { + SoundSettingChanged?.Invoke(isSoundOn); + } + } +} diff --git a/AppControl Manager/Unique Logic/ThemeManager.cs b/AppControl Manager/Unique Logic/ThemeManager.cs new file mode 100644 index 000000000..376c3afb4 --- /dev/null +++ b/AppControl Manager/Unique Logic/ThemeManager.cs @@ -0,0 +1,17 @@ +using System; + +namespace WDACConfig +{ + public static class ThemeManager + { + // The static event for background changes + // MainWindow listens to this to set the app theme + public static event Action? BackDropChanged; + + // Method to raise the event when the background is changed + public static void OnBackgroundChanged(string newBackground) + { + BackDropChanged?.Invoke(newBackground); + } + } +} diff --git a/AppControl Manager/app.manifest b/AppControl Manager/app.manifest index afc44e71a..4a1df02ce 100644 --- a/AppControl Manager/app.manifest +++ b/AppControl Manager/app.manifest @@ -1,6 +1,6 @@ - + diff --git a/AppControl Manager/exclusion.dic b/AppControl Manager/exclusion.dic index e69de29bb..f829609af 100644 --- a/AppControl Manager/exclusion.dic +++ b/AppControl Manager/exclusion.dic @@ -0,0 +1,5 @@ +microsoft +sipolicy +Incrementer +Wldp +wintrust diff --git a/WDACConfig/.editorconfig b/WDACConfig/.editorconfig index a5af0cba9..1151f257a 100644 --- a/WDACConfig/.editorconfig +++ b/WDACConfig/.editorconfig @@ -344,3 +344,12 @@ dotnet_diagnostic.CA1507.severity = error # IDE0001: Simplify name dotnet_diagnostic.IDE0001.severity = error + +# Use collection expression for array (IDE0300) +dotnet_diagnostic.IDE0300.severity = error + +# Inline variable declaration (IDE0018) +dotnet_diagnostic.IDE0018.severity = error + +# SYSLIB1054: Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time +dotnet_diagnostic.SYSLIB1054.severity = error diff --git a/WDACConfig/Program.cs b/WDACConfig/Program.cs index 7f44e0fa1..46a59a49d 100644 --- a/WDACConfig/Program.cs +++ b/WDACConfig/Program.cs @@ -5,6 +5,7 @@ public class Program { public static void Main() { + } } } diff --git a/WDACConfig/Utilities/Hashes.csv b/WDACConfig/Utilities/Hashes.csv index 146ee82f8..5f282702b 100644 --- a/WDACConfig/Utilities/Hashes.csv +++ b/WDACConfig/Utilities/Hashes.csv @@ -1,132 +1 @@ -"RelativePath","FileName","FileHash" -".NETAssembliesToLoad.txt",".NETAssembliesToLoad.txt","E6C32EF634D7288434F693AA0D4DE78068524F28C755412D81FAC3973F96AC82C636569FFA999F75EC2109F20961DE6B9EDBDC5A0325519B7D907CE16DCF116B" -"WDACConfig.psd1","WDACConfig.psd1","0340A24631DEAE29391A8FD5DB45A0A8B85AFDA98A6AFBE71D28FA8E247AA3486C2B9D26780C4CBA6F1649C810A67BA1C3062812DB795E91A78041772CE22676" -"WDACConfig.psm1","WDACConfig.psm1","16B41F5A4D704593D1F834C27D21138B7AF20EE76DDA050BAD04266422CAD333B6584C558E4304A32C4EE08BB54B9B005BEA12900F471F5D8517EF10B1E2F517" -"Core\Assert-WDACConfigIntegrity.psm1","Assert-WDACConfigIntegrity.psm1","02A11FE01CB4599FBD77A01589B7B716EA5C5301F6BEEB59ACB1CC3CF582876F75624CAF1CB8E979919E62D3C87612DDD2DE511C44EF6F37FCECA16BFE8705CC" -"Core\Build-WDACCertificate.psm1","Build-WDACCertificate.psm1","8BBE806D2247D33957806DC1A36E0A5582733830A40929C308E5ADB8B739BF8E8AA94BDC451874ABF2A9CB657A58DBD48D6315C4B93CBDD15DE4B4884B082D73" -"Core\Confirm-WDACConfig.psm1","Confirm-WDACConfig.psm1","5370AC726BA397E677996088640837FA3B334794B0AB37DFBF4F530E2CB568652EB0643B8E72A9D448020D87F4C9C886B9FB137C1CA5573C0C7AC90DFC8D84B9" -"Core\ConvertTo-WDACPolicy.psm1","ConvertTo-WDACPolicy.psm1","2EB594F55018F460B2635174097FE98BFD288F3719623D487F581722309E0994DCDC573C666237DF10C97E8B8B8BA7616D94D7317CD834427245971C58BF0BF5" -"Core\Deploy-SignedWDACConfig.psm1","Deploy-SignedWDACConfig.psm1","01F019488269FC2F5D217571BA8FB259676C46FD5B4D01451B3D96E5FCBA891F6BC78242BFB5D324C6B92F2833B2732D0CE88024C35025A84EE35EBE442AC26D" -"Core\Edit-SignedWDACConfig.psm1","Edit-SignedWDACConfig.psm1","A2C4033B43021A92EEE03508E8FD266C3C55BF5CA3945F8A777DE3971367551FA1DD8FA80961CA628FF57845214E0E5432962DF938F94D60E73FD80634496BB7" -"Core\Edit-WDACConfig.psm1","Edit-WDACConfig.psm1","F5C53EA638C0B1E3E0505AF3CF7DE4C5EAFABE5D4E76FAEEAC243E50D2FF294B9EF23AA646B1C2353CFE0AB73EA9CE887FFF056E8382C2CB6ADB45C2B5CF5450" -"Core\Get-CiFileHashes.psm1","Get-CiFileHashes.psm1","9C3D2088BD21DDD210EEB0DB50FC6C293E5C17DD33AEEC85A32A396540267835D529EA836F450D2131D4C05025B685E7E63280C4E2BBD63555AEE0B7F06D4B23" -"Core\Get-CIPolicySetting.psm1","Get-CIPolicySetting.psm1","04C20AA9D8992D075E0019F5878B60AFC9ED44DC64593A3F22FFAF82DA232FB03B2AE0C3CCBB02C245677C8D0679B575DA76274015EEBAADC3CF46956BE8002D" -"Core\Get-CommonWDACConfig.psm1","Get-CommonWDACConfig.psm1","E2745FA18505D7F180D87A983339FF2EEBBE5D2F3546689BFA7EBEE2C97EAB2CFAB5921A24138F7BB5473FEC7C46115ECC316631C81A4DC06AC24348168E426F" -"Core\Invoke-WDACSimulation.psm1","Invoke-WDACSimulation.psm1","96AFADA483BFBC95F639B7C2FB206F13FDFA06CDFAD6D0B977B441B01E523F20411B8DD4666612FA08B60F1CF394CCF9A6EDE39C27706FA964AD286FFF3AA85E" -"Core\New-DenyWDACConfig.psm1","New-DenyWDACConfig.psm1","6AB261EB5CBDCFE9F690F7F65C3D09D686042D145C6B351A4EF02D73A5588847C420B8B8AA5D67D109E3F4D1208E48FAB651EFB967D341F250EF3109021E81F9" -"Core\New-KernelModeWDACConfig.psm1","New-KernelModeWDACConfig.psm1","116D1127896B2CE4A94639B557CFDC2070094A25AC453A49822E553BD7A4C0181B9A5754A31910219985BAF7335541A05F73C4CB9D515F5A2722259C7E379EDE" -"Core\New-SupplementalWDACConfig.psm1","New-SupplementalWDACConfig.psm1","13BE2AC721FB0823473602331BAB6DBBDE55FB801B44D0690C1D083859CB4020BB7146C9B746C7A8EAF0F20551DB7E20AE3D822EB0FDFC7D9AB123B45E497C29" -"Core\New-WDACConfig.psm1","New-WDACConfig.psm1","B2E00DC36B4E9ED156AB1790F77726939A1E7AB710FEFB8772F71927350B2BC7374D50B60F6AB180AC3AD1E74B0531FC76C39D747B3BE782A20705043BA0266D" -"Core\Remove-CommonWDACConfig.psm1","Remove-CommonWDACConfig.psm1","56D0D122E1FD2D9EC8AFD4BF8ADF9D9FE6D88B482C452B1D44BBE14747BF2A0540A5E6E7D7F4595DD6F5876852A45B83DAC280F7A9847653E2C89F5891754333" -"Core\Remove-WDACConfig.psm1","Remove-WDACConfig.psm1","E760A5345DD2C00CE5CF80D1C71FB9A8E54F4B057EC26B95F1372182848BCBB8A02F78BA34A6C57519235C88589DFBAF0E3DFB8713B55CDD5EC368F99A756F5B" -"Core\Set-CommonWDACConfig.psm1","Set-CommonWDACConfig.psm1","D6A1853B414DC8F7BFEDFD38DB22D162B32617FC6554D6F7968E513E5157D60FA191D9749DF20FB913685C25F3FF08E91DD989FFBCE9C4125D43D7DB7FDF850C" -"Core\Test-CiPolicy.psm1","Test-CiPolicy.psm1","CA29D08054EB2AB4347A3B7FDA593E9681C992F9AD309E1B98A726736967A822E318A3484B31319C0D6E3936032F194A6D683942CE764FB59B84E2EA0B7FB079" -"Help\ConvertTo-WDACPolicy.md","ConvertTo-WDACPolicy.md","A7946266BDE5AFA80CDEDFEA94ECB57D3ECC5236DD3447D0DEB52350AC36E1B965BBB1C7A9FD274DE2587A8A5301B94543F35C5483A97648A47C5F2484582C43" -"Help\ConvertTo-WDACPolicy.xml","ConvertTo-WDACPolicy.xml","D21591C3CB614680602AD7E48568F45D44E35E9FF5C0BB03FEADBDB10BCB4D1BEA397B5A5578939662A1ADF9D2FADAE815762E54CEFCED81BE5CF7B354547C9E" -"Public\MockConfigCIBootstrap.psm1","MockConfigCIBootstrap.psm1","DE8231BA6C5F3F6C2546FE2441AF440767AB351E544B721ED770E3C7E35F40408C8987F3695F4D32C5F4B1EF2F45B9FA54AD10C1CEACF06E695DA395A193A16F" -"Public\PSCustomObjectArrayToHashtableArray.psm1","PSCustomObjectArrayToHashtableArray.psm1","DE2B991E67278273C8431E428A49D517003CB786E9F15FADF0264462D234BDD45C19B80171139647791D1C8A68B8CAE9F8CE998C69154EE281D77786BA278794" -"Public\Write-ColorfulText.psm1","Write-ColorfulText.psm1","1BBB212E2E2F179A4B52FE30CF9D025F0EDE9000E8F4AC418FC8FB7B77834DB6F123C9D1CD13082EF8CEB2391A83485F17772DE94B74DD01EAA7B4F6CB408FB2" -"Public\Write-FinalOutput.psm1","Write-FinalOutput.psm1","A42114346920BDC4D567D8923F362D145659B6919B5F50CA4A88DEF961A6528A34AB89A076862F1DF8BBD46E5835BDE156E2EF8819A0DBB7637DC71F2666CEEB" -"Shared\Get-KernelModeDrivers.psm1","Get-KernelModeDrivers.psm1","64A62A94BB5595912D2AD59B900660C0554BD5DA2058880D44547D0EB67EBF11E5C63B9163090CF547DE87BA1B4602275406EADFC8181D8E6DACE01FD8FCC441" -"Shared\Get-KernelModeDriversAudit.psm1","Get-KernelModeDriversAudit.psm1","B58FDB7613DA624C961AEB3C459C64D0B97BB72B864A8AAE49DB34DC3024A8F802442F6976B360D108C040BB3F1EE5637184843480115EC92F54FB1C47984EF3" -"Shared\Receive-CodeIntegrityLogs.psm1","Receive-CodeIntegrityLogs.psm1","A0526AB1C5867D4A1F1FD6CFDFDE53558BB742B8DA9F5BA80105DF89727E13DEB3466AACD2CFC7AA6C1DC07651FB9E2F8B77C86E02BE4F83C2FF5874C6668F3E" -"Shared\Select-LogProperties.psm1","Select-LogProperties.psm1","699A7913CA43F3C84C6D6487DCEECAA65D799A1A71FD6DE9E70D2D37A0F406C8CDDA81E0E6ECF88303FDFE6C9034033F2205D8C67B7A4D7667ABE733AB632702" -"Shared\Set-LogPropertiesVisibility.psm1","Set-LogPropertiesVisibility.psm1","3FD27FACC66BCA254B433221002F8879434810155A72BE8F49DA78EA5C9A6A052D080081245BAB6FA04E04349E1D53A204A4F0333D4422A989B0C83B09AF82DF" -"Shared\Test-ECCSignedFiles.psm1","Test-ECCSignedFiles.psm1","C0BDA01B0A776A60A6824E57BB4B4E6B538C3A0EB10AB5A83210D5371FB4F2C3EBB76B2C5291590C17B57B9A5B678513B7610B11E540DE682A923FE1633742BD" -"Shared\Test-KernelProtectedFiles.psm1","Test-KernelProtectedFiles.psm1","AEAEF37410C83784945E1806201FD8CEF857464B92B6B1FFF31D30191C6173A57EEBA04BD185B87C0AEE6D9749287576278266FBB305DE403BD27FF9FC5F7AC8" -"XMLOps\Compare-CorrelatedData.psm1","Compare-CorrelatedData.psm1","3A67ED315722C9B403E977B53F5930A3A1B057A7BCDB47CA4A8EFF050DB47F8E90DEFBB800191AA30928851131581BE1C8DA19BA407FC71C93452245699E14C3" -"XMLOps\New-Macros.psm1","New-Macros.psm1","5EFD293DCECD7CA29915C2F60CBC844B933CF8082357E507D53366F0CDA71F54E5DA810FDE52FA3011E082035EF97FA4D8BF1A8754109A293F20598C2A278541" -"XMLOps\Optimize-MDECSVData.psm1","Optimize-MDECSVData.psm1","EAFF6E04A9920E7682364424DD80A4A16853A1B5F4BA6D7ACFC8CC753E0F1F7F469FE0F4C1C66D83A6116FD9B1D91E96ED9A433BC40551846AFCF9EAE808C7CD" -"C#\ArgumentCompleters\ArgumentCompleterAttribute.cs","ArgumentCompleterAttribute.cs","927A9129E19683C0C031AA53C4A13A5E30F19C1D982FA737A7CE6697757CA8D958179F1A238BAB8684A8DC842BCD5BB6BB99327D07C1AE51B867480C5C238B35" -"C#\ArgumentCompleters\BasePolicyNamez.cs","BasePolicyNamez.cs","D9E4FEE538B984CC337458B229638FD266B608E0C64DD5AD0735C1E0179E46FA8A3ECE3F2EAA1BE044B57B4EFF65DB7B69B540F81661014754F293CB3D899A19" -"C#\Shared Logics\AllCertificatesGrabber.cs","AllCertificatesGrabber.cs","4E575541198BBFA482EDCD5469E8DF12D3AB9ACBBB7B8CC2D3BDCE2B608DDDCFFDE6055F78200506056209C66D85750D1A71CD0492B97F5849A2BE2DE6856531" -"C#\Shared Logics\CertCNz.cs","CertCNz.cs","C0A6933F7D76BA0901A0D8E2BC1DFA97C111B2DE13E404566576F23DE44EF95DB20BFBEE88045DC8310D9EFF4F8347400D581AAFBB39B657544EC0EBD05DC361" -"C#\Shared Logics\CertificateHelper.cs","CertificateHelper.cs","28C7627373D7C0006C97AE215ACE53A2279D64E6E7B39C3103FE16958A6A55DFD0E847E594FD511A235CCD452458A944BC2947AD2C336B4B57EA3A2E3CEAA165" -"C#\Shared Logics\CheckPolicyDeploymentStatus.cs","CheckPolicyDeploymentStatus.cs","AF9185985BEBA92E11C6E8FC0F8CB8185F72EC0DC16619CB8E422226AC28D70152EC26FD4D89A672AFFB41283C3E2311F299423B9E9141614D965338D12C4E91" -"C#\Shared Logics\CiPolicyUtility.cs","CiPolicyUtility.cs","7E3E50F658A6F25922E09C4307B92F98A9B4F338ACFD6F1F6ADA7A7DCC39EE827A27DCF171609FF3A967EBA23D941660E29311637AA23000D468EB3DBC4E87EF" -"C#\Shared Logics\CiToolHelper.cs","CiToolHelper.cs","D8EFBD0105967DB2608ED614426374148BA48CF45360C2E893EB6CF90731A8DF183A63CE9D083946E6EF195CF77077A1DA5C0DD19777D76E7C52E494C53B8710" -"C#\Shared Logics\CodeIntegrityInfo.cs","CodeIntegrityInfo.cs","7C2D4520436DF3D5C2F605F800F539FB28152216975C5950A6BBAAE883D8CA3EE60137B30AAF097B63DDDFAA320BB3065F47F76840BD04DC979B98E56C8D370E" -"C#\Shared Logics\ConfigureISGServices.cs","ConfigureISGServices.cs","9346BBFD21F313D67CEDD92F574BBE7F2E47A818036E8978C308A5F01892718EB053554BD9F30DE245430872EBA57B8AA83EB562E1F54F46132D387A6ACC690C" -"C#\Shared Logics\Crypt32CertCN.cs","Crypt32CertCN.cs","34331FCB065277AB1A7131D967303AC4107A51DB3C4253133E559CB2AF7C19F01F68151C91C9ACABB9E496280BF42AE638740315E83F75E3331797E6BE891366" -"C#\Shared Logics\DeviceGuardInfo.cs","DeviceGuardInfo.cs","6E13AD95B78365061ACE867FDF73EE1A07C1534E8CD5FB0D2ADE80362810512C777FAAE7E0FA5EF03FF52D311D2D5914F9036C338F12B49281B87EDF2CE4C1E2" -"C#\Shared Logics\DirectorySelector.cs","DirectorySelector.cs","6CF74319FE092069F285902C27269FD42BC88E1BF14AFD16E3163EE4778120BC27B6F0A0844E2D3C132710C0A05F5182B4488B374F53E30F860FBB025045790F" -"C#\Shared Logics\DriveLetterMapper.cs","DriveLetterMapper.cs","769B1173576BB118810F1C3E80A57229714497C89D3B574265417209F652B65422385E78F0DA2EFD098431305117983F8C63848825BDF3D3799D6985C5831899" -"C#\Shared Logics\EditGUIDs.cs","EditGUIDs.cs","25046E665E1864A1B208EDF78F9C540E3AF84BF8DEADC5BDD594DE2B70ABEF3318A39D41F8EEFDC6093F47AB46A687C6A02145BEA701422C0CBEFEBBAC2B4F79" -"C#\Shared Logics\EventLogUtility.cs","EventLogUtility.cs","7A186F7BD615225F4A074649D9C721EEA9252202894957471199625E85F61980A71771A207CB7B0748F99E293D135442BC55100E996212E713023A94009E72AE" -"C#\Shared Logics\FileDirectoryPathComparer.cs","FileDirectoryPathComparer.cs","70393FF77AF424E6CBFF7C1E0F044097C885B887141B2FE5586E504234FC346CFBE9F813324DED8022EC268DE5191C8A87245CC4A9652EF603BE6A76A1193365" -"C#\Shared Logics\FileSystemPicker.cs","FileSystemPicker.cs","8E0912C4100E8911DE8922D81FC2062E769E874D239629970CEAEBBE42C664F17161AC0288EBB5301475CB3BAAF02C6C9302CEC9E9975C65D8B87B903A073A7D" -"C#\Shared Logics\GetExtendedFileAttrib.cs","GetExtendedFileAttrib.cs","80C337ADC5FEA01701BC855C9E3EA7F5ACD8CC28EE89AD3196C96A949BE5ABC0AF6559B82D069679C487A8A8F121BC08D11DAD696731B0F490B9C1501CE227E0" -"C#\Shared Logics\GetFilesFast.cs","GetFilesFast.cs","EBD2E71033A34E6A28EEA2485CE04C88E02F2E2551FD0E290B97B71747E57A62AB4165558B1044605783EDBC03D2B7257C3752459B6C5FD3E15D73FB44A4B34F" -"C#\Shared Logics\GetOpusData.cs","GetOpusData.cs","D28DE719079B96152631B37AC07F2C8951E1917EB65532401FBF8B034DD397A3B20B3096B0A8F8198A997FC2BFAB3C5D46EB8E42C5A0A9EAB7F0D39613D0C5EC" -"C#\Shared Logics\Initializer.cs","Initializer.cs","B1324B100FB8AEF6BA849DB17E34366F064B8E2BF07C5D6CA1CC1ECCC58B2C9A77C5B350ADB7B50D031FCC5796C2B05A9F7D56B329797F6020A91E6B0EEFF8B2" -"C#\Shared Logics\MeowOpener.cs","MeowOpener.cs","C3A3133FE8AF875CB22F685E34392736EDCD016BEE198C203CB1E0355B75744A781A77FAC592BCE8D1C89328E72D02707702F594A38DF6A66064FD5907497BE2" -"C#\Shared Logics\MoveUserModeToKernelMode.cs","MoveUserModeToKernelMode.cs","F734412337684361433AE16F22767EDC9CE7803CFAAFD5269BF7BFE3F1E3C08BA6BC8A40A91CF2DB6D20E401EF129963E0B739AAE430B5437F520D54F3AD044F" -"C#\Shared Logics\PageHashCalc.cs","PageHashCalc.cs","5F3D472E9D257D1E2A51A1E944453D8D33125339C6BD23B3B7B461CB9A68EF68A478419A1940C2A687DCCB6ACD7EACD8123BA64A318C589AB61302F16FBF7A6F" -"C#\Shared Logics\PolicyFileSigningStatusDetection.cs","PolicyFileSigningStatusDetection.cs","75EA8A3B375E3737FA0B975BEE7A031B1A2542672162292F4ADA8272986F011470A06CFF1B3B1A5AEE4680C497A1F6F5C46F5B3F8EFD1130766B6FB32955F30F" -"C#\Shared Logics\PolicyMerger.cs","PolicyMerger.cs","0A97EE4D53B607E6041C9ED6B6EAE10675D7F796D9FBB8439EE5AE55885FF30306785F1433502D69E80BEF111195399B9F8FBCB91B0E7968179247AD70C52C62" -"C#\Shared Logics\PolicyToCIPConverter.cs","PolicyToCIPConverter.cs","EE82DF5A74EB2FF943FC8AB126B693A1D0A9A609BB98B34DED9CE1E7BC98DD99C9F6B5493038C301728A656A69148F9C60DE49C6392D77DF8714D2DB1E4B88FE" -"C#\Shared Logics\PowerShellExecutor.cs","PowerShellExecutor.cs","06CF44AE0EEBFB61ED00F70D3CEB42EAEC75B86EB00868AF0122BB76C8ABDA7A04D52FE28166B0E5095621B5E127D5B0DD3B5AD27AFAF47EEBD8C1A8B8D708D8" -"C#\Shared Logics\RemoveSupplementalSigners.cs","RemoveSupplementalSigners.cs","A41B31564158DA491B1248BD21DE7BB3D1404A06F41260D04DFEA30DAC2CD23B9CAF8AFCE0178137E50354E2A3323338BAB10946C080FF4FF7A9D8DE82ECC0CE" -"C#\Shared Logics\ScanLevelz.cs","ScanLevelz.cs","263B7DD2D3ED75D04D797EDB4936F31C36E0E71467E00696C05B9E8F1C2E6FFE7D166FA1605CE5CFC1B0CAD03F834E38D2770B307819CD2081547FDE96DA0108" -"C#\Shared Logics\SecureStringComparer.cs","SecureStringComparer.cs","3FE4DD0A2B3067B0DF49D68D8D96F2694EB7C125F19CD3B884B0D3FA32A6BEBB7E7BE99E689F643F4BD5793CD4F78AAE920FEA0D37E0B22A4ED873BDF6391FA2" -"C#\Shared Logics\SignToolHelper.cs","SignToolHelper.cs","B4B0FFDA675704367B7BD7237F79A04D50B5181F5ED6FA614454BDBA7FE9FF28FA9561C4F14680D244C67F7A491FE1616FEC42B663F4C59DA85C8683BEB2D0F0" -"C#\Shared Logics\SnapBackGuarantee.cs","SnapBackGuarantee.cs","4C28B954142B4C4359A83AC3B2B5B70987E678C60A02E826A921E38FE2F2FD9A3D362A7CA13638D2DAB8EFBA7EC7F3624FEA1085D5503F1B9F17919C8AE2708D" -"C#\Shared Logics\StagingArea.cs","StagingArea.cs","CE96269B1A64CF1B18F47800A921E9964D9FB874C767129128251C6D295345612C4D69090A76333E3B9F476C43FCF97A4D1F98574532EEFA5E3A8296D42B0BB6" -"C#\Shared Logics\SupplementalForSelf.cs","SupplementalForSelf.cs","98EF852BD40B7F35581DB612A1CD055DA1ACA3B6ACB2964CE41483EF3E945851D427BF895630E4B720DD59C6246C04196882D66207DAAC0EB04D9B240902BC35" -"C#\Shared Logics\VersionIncrementer.cs","VersionIncrementer.cs","3D81C7BF799527542E25E64F31AA092096982C2B8685C5A2958C6B37DB8360FC945488E4F74545088C497088A6A0D53B0301E83F667D068B5B3787706B2A42E1" -"C#\Shared Logics\WldpQuerySecurityPolicy.cs","WldpQuerySecurityPolicy.cs","C59ECC72230FE759175B8FCBFD61FF426832026F8CD7B6C057C8CAC7DD0207102ED0F71FB7B2CC3D007199237F34E8AE906E83A31F34CF7EF682BD73A5F17146" -"C#\Shared Logics\XmlFilePathExtractor.cs","XmlFilePathExtractor.cs","27F1DA7CD86112CCA9E05FA5F658D80B04089B32F306BAE84698706778237F01214C3C45E4B0F277AAE03B822F095306CADBACB864277D19F50948DBCF5CE835" -"Resources\WDAC Policies\DefaultWindows_Enforced_Kernel.xml","DefaultWindows_Enforced_Kernel.xml","846663A7B0CAD90A2305F3C3322D6C2CFA6277B7E4B083CB478FF409DB29A7D0D71318845B884518B8D2F87B66A5EA327D4EB2D39A9707D1EE41B0237812FFD6" -"Resources\WDAC Policies\DefaultWindows_Enforced_Kernel_NoFlights.xml","DefaultWindows_Enforced_Kernel_NoFlights.xml","7E4BC35A3F0840C8F3921FB260CE84660DC3CAACB7850A1AEF13AFC48B0E069D27562C5632444926BF60B44A0E0FF522D0215F1F7DD5E1A7E51A45E86AB7F44C" -"Resources\WDAC Policies-Archived\DefaultWindows_Enforced_Kernel.xml","DefaultWindows_Enforced_Kernel.xml","BDC7B623386570F383B4A113BF06C7FF6A5A4271AFE572B5D68EEBC161CD650B62E70636527DFBEF09A8F95E66899CEEC424AA22CD00BBEF6D7888759D812F8D" -"Resources\WDAC Policies-Archived\DefaultWindows_Enforced_Kernel_NoFlights.xml","DefaultWindows_Enforced_Kernel_NoFlights.xml","D02BCCFA3C35E179A634AFCDE04259C43F8FBD619A4D0D2F7BAC1A8A9FBC58D3EBC7EE89B1B2EC6B3C17BD6EC38ADB501B271AEA3037B980D10EAB9AFA3B8308" -"Resources\WDAC Policies-Archived\Readme.md","Readme.md","66F9B622C333505E782F1AF1509BFBDABF1AD5167042064593FEEA5245D6CFAFE60DBDA5231D600D4157BC424E49F77D33302CE77B79D1D30CA8E29ECFDB31F1" -"C#\Shared Logics\Logging\Logger.cs","Logger.cs","E4BD45274BD7694F802F3763FD85A9F57F5E0F640A3DA93DBD2F413C6D79B5A40083114DB8ABC702209A5C5F4FCF09A9725C687D2CB78053E6A78EB532DDF94E" -"C#\Shared Logics\Logging\LoggerInitializer.cs","LoggerInitializer.cs","578BED33DB8660CD483D47E859DE2C0C06EEAC76683F09EB1120BE845258CFCB434D5A699717FCF172C9234C4266AA4A1620E1F301C5F6BDD2F2C1F1DC2B0A62" -"C#\Shared Logics\Main Cmdlets\AssertWDACConfigIntegrity.cs","AssertWDACConfigIntegrity.cs","939114329DF729ABCBFB5D415CA23CDEC5692DF29F49D1246943C3D538B0476B51C333FC4F59C646E64E37897DBF74CC3850EFAE45706EB5ED86F48171B78F6C" -"C#\Shared Logics\Main Cmdlets\BasePolicyCreator.cs","BasePolicyCreator.cs","A972EA8A47806A1EC3BB072481FB931C10D9FFB211218A53ACDDD5352F6954C562FE6A8F95E2A57C9BA122DBE68AD1E5972B22C47ACA00DB7E13629012085A31" -"C#\Shared Logics\Main Cmdlets\GetCiFileHashes.cs","GetCiFileHashes.cs","00289303FCC91BDAF131EAEE4D9F0CB1995687115EA341E5FCF0854D9E7E62BA3F5818CD5202CE7126E4253336E50623506FDB40947EAD60B3911EE57BB50BD2" -"C#\Shared Logics\Main Cmdlets\GetCIPolicySetting.cs","GetCIPolicySetting.cs","55657785110CAB08330F0B68B5C3BC40C580C468AF45F52049E2F47D881FBDECB558ACD8AFEA71BAC317C08D5219901EABE8DA6F84290E2BC9137A6EA01B333F" -"C#\Shared Logics\Main Cmdlets\InvokeWDACSimulation.cs","InvokeWDACSimulation.cs","D75871D39F0910C2D4F4AED6587532B310A946281BC5AD8E61CAA4D886FBC68FABD9704087A322D75B52ADC43C2170C198278A8CC077908BE3C203FC390D0925" -"C#\Shared Logics\Main Cmdlets\SetCiRuleOptions.cs","SetCiRuleOptions.cs","78A6691EBDC9203281FC0046CB6E5A5931A155D0E2456C0BBE8ACC11978614CF9BFB303912B28DA744B519B43765601908DF6682B5D0FB2F3B2D03045E69D37F" -"C#\Shared Logics\Main Cmdlets\TestCiPolicy.cs","TestCiPolicy.cs","2F7E09E0F19E49AC6D461D4144186FD3E48EC2B1A7E13A824E52D24C40A6BEEEB781B647C28BE3D4584CBEDFA1F90B5B2545CBDAE74950BB5FCEF8D9D7CDB796" -"C#\Shared Logics\Main Cmdlets\UserConfiguration.cs","UserConfiguration.cs","C67B03C643AB2908A283B947A83C4657684F109B38D13D0C9E69DAB7ACA776258B9239368C9B983BB6557B7DFFFE87BC4BAFB61EE85D5CE40D23D864AC229A2A" -"C#\Shared Logics\Types And Definitions\AuthenticodePageHashes.cs","AuthenticodePageHashes.cs","20F0F29D49094EDCE1F19E0FB1616A995D60A71ACAC3F4B3C02E2CF210EA18AA114B0320A90AC0CDEF55AA29E2C1E7423F241389A822E6E70F0BA2004D8990F7" -"C#\Shared Logics\Types And Definitions\CertificateDetailsCreator.cs","CertificateDetailsCreator.cs","AE5EA36F90B9A52650ABA7B6FF66F33FC5CB4211F9227C37144845B11B868AA818EC7BA4A0DB3D7D0075D16D4F59C78CB9B038DE4560A2BB045A8A67EE7B0873" -"C#\Shared Logics\Types And Definitions\CertificateSignerCreator.cs","CertificateSignerCreator.cs","60B551672F3BA4EB73C41CE5D14C388006CAE835F46C0110148E51CEDEF736229C04899DFDAAFA97442777CEA73B04455667C8FB2C106D65DF30D896A15FEB91" -"C#\Shared Logics\Types And Definitions\ChainElement.cs","ChainElement.cs","84C9563941AE8BF0477CE04AD512B05BD33E48C55CEEB5EDE31F590991E47E1A49698AE739301D7FA6BE079DFA9E6BEE1E0810423505F6D2F327882C324B7E7F" -"C#\Shared Logics\Types And Definitions\ChainPackage.cs","ChainPackage.cs","27E20438EE2CD6F9BB6B8A79774FC634A343430A728FF37C13CDB069991B54A0B92CB31F1070DEA6F7602F6CE17EA24ABB0562AA99FF42396DDA20B4F74D881D" -"C#\Shared Logics\Types And Definitions\CodeIntegrityPolicy.cs","CodeIntegrityPolicy.cs","F77F73FA43D006319B9BFD6F91726659DE1F8FBCB15134B6EE485314B79F488E7579AD4E2807D826AB00543A713216C02B38E5D55A8A1674B36B6CED74A5B8F1" -"C#\Shared Logics\Types And Definitions\FileBasedInfoPackage.cs","FileBasedInfoPackage.cs","12B0229B63CBE004DA0616E369CBC66DE49C49EB6B921333B25B655257922C0171E20AFEE5B14FFFF5BF1C42E6B4DEEBE1D4E7120754C64BF7934E3F091CDEF2" -"C#\Shared Logics\Types And Definitions\FilePublisherSignerCreator.cs","FilePublisherSignerCreator.cs","3453E2825F32E3574D6326B48B961D9A12805648F6529DC4208C3231EA4ACE8CC9E7D83A0525EB46611F97F297CDD6819444A92F3B36996BFC4B7E6F685B82EA" -"C#\Shared Logics\Types And Definitions\HashCreator.cs","HashCreator.cs","790FAF0942CD645687608546004497AECABFC2AA3DA827C738337A2AE95075208A87FD591D173B37BEAE8AB6EE676BD4A8B6655B0059276DB375713566014BD0" -"C#\Shared Logics\Types And Definitions\OpusSigner.cs","OpusSigner.cs","18752EFF4D6D734D8FEF2F9A5763481F509497561C5ACD9C3088CCB1EDA043A41369F17EF6BAFDBD9E9714F76CBEBB69E82797D0FF63736E51A5EFB147492895" -"C#\Shared Logics\Types And Definitions\PolicyHashObj.cs","PolicyHashObj.cs","D9DDB2225BB4F205633C532E5191799488A879DE0B9F40B78729590F864164A782C2E16419508728CF7362475E6CA64AA62618180A1CFB588E1EE0E347DCE2C8" -"C#\Shared Logics\Types And Definitions\PublisherSignerCreator.cs","PublisherSignerCreator.cs","C4B16013F1315B54F3D3032B89222A7F74A387332321E2EE4602A01D85CB71750145BCB9940B4009CA3601E341F9D909166AD7CABE844551FA8F3AFF77274D9E" -"C#\Shared Logics\Types And Definitions\Signer.cs","Signer.cs","2D44FF70EF03752DE368609BE6EFA667DCC9D4A1AC27819670AFDD2E491C7EFA93017AE748A5D412580F258AAFD18D012B1A40167EFA7D85CE8E98CE3849DD4B" -"C#\Shared Logics\Types And Definitions\SimulationInput.cs","SimulationInput.cs","AA72A34E17098D68FA580462D874B15EFDAE1E767D1A3773AB1C051A708B2F2F8BEDE86C6969D4689F32F640797B19C8B6CC83C17DA28AC9CF47ADFB92B89246" -"C#\Shared Logics\Types And Definitions\SimulationOutput.cs","SimulationOutput.cs","8B690467AB853F65CCB5D4066D313C3A13DF064D8235A49F8F5D856F9CCFD2CC9A792671F9A72018B6E513C13681C4D5EA9A7118B406E3B9E1714190D12EC6CB" -"C#\Shared Logics\Types And Definitions\UpdateCheckResponse.cs","UpdateCheckResponse.cs","101C382198A6A34139180DBA44836F118D8A2BE6D52040E7E6F23F1AD6AB70334F285219DE4740C985F6F26C36D61B658D8945E646C81BBFBDE1B5C6899F2EC2" -"C#\Shared Logics\Types And Definitions\WinTrust.cs","WinTrust.cs","C1C27E1294F2BB1AF89992A4B8EBBB2C9121253FC8E4B769CE3020B36695BF6A7231A8807EED5E8A5A7C07ED87B6E395C9D6DB0993250F7185A946349DE76BE0" -"C#\Shared Logics\Variables\CILogIntel.cs","CILogIntel.cs","3E74487685EF2274E5536251C9C420CFD0E42931C7B4AB8E97A66D0A2232CD3B936EFEB774BD25DCD52785D768A4975F29366EA1ECB3559BC5D8116E45CC1C47" -"C#\Shared Logics\Variables\GlobalVars.cs","GlobalVars.cs","0723DA8272A3CA6E109FC9D3BB41CADE18504C4A8AD75D079ACCAF24257923DC7F645D32E6402BAC595D96CFF7ED36F604D7D91835E7A66944DD39F7CAE0720C" -"C#\Shared Logics\WDAC Simulation\Arbitrator.cs","Arbitrator.cs","FC421E779F4C08F597358CA805E3D7D00A62A726773B7BE69614AF15252E396CB1218473861617AA8A9F1739234D93F54FB1D9D1F080E5752147C4F28C69965D" -"C#\Shared Logics\WDAC Simulation\GetCertificateDetails.cs","GetCertificateDetails.cs","2D5BDF67A60E7F77F59C2782DD6AAC6104E544CA7DE1DFB2F7475E6B2E397DCA4720172518AF29B6127C5ACEE5C7397CCCBCCB0DCD56873938A42DE28C286642" -"C#\Shared Logics\WDAC Simulation\GetFileRuleOutput.cs","GetFileRuleOutput.cs","317C4E465D3E8222DA51087B60C598C8900C231C3A50ACB0A32B76D249F8AA33D16E9662419DEADA29615D3408AE8375A0E6879FDE6922E46959D5A94F329DA1" -"C#\Shared Logics\WDAC Simulation\GetSignerInfo.cs","GetSignerInfo.cs","87AE8BA4DF39E0C28643A3C40D4408BFE78565B73EF8D96C296F6D11F964BB2EAC61E55278D9A4B93EA4171CA0AE143CE2C003888A2F56351C271648B2FCC0AC" -"C#\Shared Logics\XMLOps\ClearCiPolicySemantic.cs","ClearCiPolicySemantic.cs","C35F9CEEAB1E29030D13DC5F264D137832437CCEFA4D095073CDCDA28C6F0B935852CA5BE8BBF0BDB3137B836845BE558A9CEA3A401004C3BA02E6A48382B8FB" -"C#\Shared Logics\XMLOps\CloseEmptyXmlNodesSemantic.cs","CloseEmptyXmlNodesSemantic.cs","0194BFA2CE8540F6B19DD967107279CDFCDDE55696C738102484F3EAD2CFB88AFCABCF8ADB42E3064CDC6B8C999AFD9605C371DF1297162E83E023655BD65D47" -"C#\Shared Logics\XMLOps\Macros.cs","Macros.cs","08CD23FBD304B4B668A7DACAE3C165D07E362721989FFDE6C9FEE65F46994DB1B8BD8FCBA8AD9B20F24B3C226CC5E1684B3F1203FBA97C64D3C2FA071AE6CD48" -"C#\Shared Logics\XMLOps\MergeSignersSemantic.cs","MergeSignersSemantic.cs","E5160B8F09EA05D0481729698D233AD9E9C8E0706D082DBEDB0D0008C8A4D393EB59610F5F07301D9E974FD3BD7C34205AC11B109924A9EEFF2FC3034FBC7567" -"C#\Shared Logics\XMLOps\NewCertificateSignerRules.cs","NewCertificateSignerRules.cs","217FDAB22584A4E5C8727C8B79BC8DDAD97FA71663529007BAB859306CA08943581E3BC0A886D6AB9AC4B1771E5BA3431053C51A5ED7A775F506759990451C62" -"C#\Shared Logics\XMLOps\NewFilePublisherLevelRules.cs","NewFilePublisherLevelRules.cs","A6403F2AC4B5E4B3773157E071A1C3D4871C5336A45784371622E169E8F105A7DC51843E05270515E4E0B561000C34D5F746CC31C6EDB952ABF39FAB59C66DFE" -"C#\Shared Logics\XMLOps\NewHashLevelRules.cs","NewHashLevelRules.cs","094145388EFB521A37E5E3AC50C5569BEB6E07F4673BEF41ADDD37C9EAA19AEA8212AE20A9155B4601F875494ADAB30AAC94DEC8606517AF9BDB9FDE67C95D3A" -"C#\Shared Logics\XMLOps\NewPFNLevelRules.cs","NewPFNLevelRules.cs","5F4C9083851110DAB6CB585A63B838341CF3B7C276EB37CC5F6AE3155C56F7B385AAA76E119E95B2306EBA155709AEBF116837A4EDE2DD20C4C236E20945A1F5" -"C#\Shared Logics\XMLOps\NewPublisherLevelRules.cs","NewPublisherLevelRules.cs","9A6F62F75883F3EB71EB8B9E45971E712B9A69244751ADBCDDA61F06FDB19957C1DFEFA8A30E8A573504F87BF8ACF114FB4BE248F6F0B01CBD627D8A15A219E5" -"C#\Shared Logics\XMLOps\RemoveAllowElementsSemantic.cs","RemoveAllowElementsSemantic.cs","1847471BC267B98EAABF35760F1E6653D7B16A518B8BAA3F43200D63D476BB1163FF24B80371FC8F9B19A7382F170927B483D30DA4130A2BBDC7951B9BA96E84" -"C#\Shared Logics\XMLOps\RemoveDuplicateFileAttribSemantic.cs","RemoveDuplicateFileAttribSemantic.cs","EA7457347521808663D25A7AF5144FDB54B437A6D580F96193FD6820F9CF63005E48CA730CFA23573A7F1C850422137545FB7E020594D9A9954CC12F143AE87E" -"C#\Shared Logics\XMLOps\RemoveUnreferencedFileRuleRefs.cs","RemoveUnreferencedFileRuleRefs.cs","F81FA9C9F2C3251A67CDD9EC8374C8C4CFA5289C1727B90E4CB1381C5D04804F5E69D88DC853D1E5905D65336F8D17B71E92416F3AB5C54D72D3B12B95EC00D0" -"C#\Shared Logics\XMLOps\SetCiPolicyInfo.cs","SetCiPolicyInfo.cs","EAB6AAFDE8BE454A57A6DCB3638C2CC80F7FDBB3C244BD57FB87CE3F3DCB09B41451C527333BF73E38EF8AB60EC61F0314C11D79F1981ADB004BA9FF6EE129A5" -"C#\Shared Logics\XMLOps\SignerAndHashBuilder.cs","SignerAndHashBuilder.cs","15B481D5F8E0757C36915ECE0B9FAF3CA93D4FEC085FCCEE41701E0011B4BD020875C83474E3EAA987B6C14ECB94D9F2B9DCEF281A6AED5B3239645D505A7C69" -"C#\Shared Logics\XMLOps\UpdateHvciOptions.cs","UpdateHvciOptions.cs","6EF7C4576AA63DBD11FFEBA1A21843D9785426345FA408C78C98C3CA28EC978BE1229220CBB2E2BE031C1B4C97AAD6DDB255423ACF68626DFE6D6305070C4513" -"C#\Shared Logics\XMLOps\XMLOps.cs","XMLOps.cs","EE07E89C0DE0EB1E3FBC9B0BD44CD1CC24D15207126D44C319520705A1940C75C3ADE08D14C614A79EAE89DECA4EE8AFA71898FA4AF546DB2852CB77C75C645F" + \ No newline at end of file diff --git a/WDACConfig/Utilities/Invoke-WDACConfig.ps1 b/WDACConfig/Utilities/Invoke-WDACConfig.ps1 index 90249bb36..dab5dc6fb 100644 --- a/WDACConfig/Utilities/Invoke-WDACConfig.ps1 +++ b/WDACConfig/Utilities/Invoke-WDACConfig.ps1 @@ -8,7 +8,3 @@ Import-Module -FullyQualifiedName "$ScriptFilePath\..\WDACConfig Module Files\WD # Replace with any cmdlet of the WDACConfig module that is going to be debugged # Assert-WDACConfigIntegrity -SaveLocally -Verbose - -# Converts the markdown help file to XML format for the ConvertTo-WDACPolicy cmdlet -# New-ExternalHelp -Path "$ScriptFilePath\..\WDACConfig Module Files\Help\ConvertTo-WDACPolicy.md" -OutputPath "$ScriptFilePath\..\WDACConfig Module Files\Help\ConvertTo-WDACPolicy.xml" -Force | Out-Null -# Get-Help ConvertTo-WDACPolicy -Full diff --git a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/MoveUserModeToKernelMode.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/MoveUserModeToKernelMode.cs index 78a6242dd..679385a18 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/MoveUserModeToKernelMode.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/MoveUserModeToKernelMode.cs @@ -10,7 +10,7 @@ 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. + /// This is used during the creation of Strict Kernel-mode AppControl 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 @@ -22,7 +22,7 @@ public static void Move(string filePath) CodeIntegrityPolicy codeIntegrityPolicy = new(filePath, null); // Get AllowedSigners from SigningScenario with Value 12 - XmlNode? allowedSigners12 = codeIntegrityPolicy.UMCI_SigningScenarioNode.SelectSingleNode("./ns:ProductSigners/ns:AllowedSigners", codeIntegrityPolicy.NamespaceManager); + XmlNode? allowedSigners12 = codeIntegrityPolicy.UMCI_SigningScenarioNode?.SelectSingleNode("./ns:ProductSigners/ns:AllowedSigners", codeIntegrityPolicy.NamespaceManager); // If AllowedSigners node exists in SigningScenario 12 and has child nodes if (allowedSigners12 is not null && allowedSigners12.HasChildNodes) @@ -69,7 +69,7 @@ public static void Move(string filePath) } // Remove SigningScenario with Value 12 completely after moving all of its AllowedSigners to SigningScenario with the value of 131 - _ = (codeIntegrityPolicy.UMCI_SigningScenarioNode.ParentNode?.RemoveChild(codeIntegrityPolicy.UMCI_SigningScenarioNode)); + _ = (codeIntegrityPolicy.UMCI_SigningScenarioNode?.ParentNode?.RemoveChild(codeIntegrityPolicy.UMCI_SigningScenarioNode)); } // Save the modified XML document back to the file diff --git a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/CodeIntegrityPolicy.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/CodeIntegrityPolicy.cs index af062fac7..b59ab9625 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/CodeIntegrityPolicy.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/Types And Definitions/CodeIntegrityPolicy.cs @@ -23,15 +23,18 @@ internal sealed class CodeIntegrityPolicy // Their assignments in other methods must happen through their respective nodes exposed by the instantiated class internal string PolicyType { get; } + internal string PolicyID { get; } + internal string BasePolicyID { get; } + internal XmlNode PolicyIDNode { get; } internal XmlNode BasePolicyIDNode { get; } internal XmlNode SignersNode { get; } - internal XmlNode UMCI_SigningScenarioNode { get; } + internal XmlNode? UMCI_SigningScenarioNode { get; } internal XmlNode KMCI_SigningScenarioNode { get; } - internal XmlNode UMCI_ProductSignersNode { get; } + internal XmlNode? UMCI_ProductSignersNode { get; } internal XmlNode KMCI_ProductSignersNode { get; } internal XmlNode CiSignersNode { get; } @@ -67,17 +70,15 @@ internal CodeIntegrityPolicy(string? xmlFilePath, XmlDocument? xmlDocument) SignersNode = SiPolicyNode.SelectSingleNode("ns:Signers", NamespaceManager) ?? throw new InvalidOperationException("Signers node not found"); - // Find the SigningScenario Node for User Mode - UMCI_SigningScenarioNode = SiPolicyNode.SelectSingleNode("ns:SigningScenarios/ns:SigningScenario[@Value='12']", NamespaceManager) - ?? throw new InvalidOperationException("UMCI Signing Scenario node not found"); + // Find the SigningScenario Node for User Mode - It is nullable because Kernel-Mode Strict policy won't have this section + UMCI_SigningScenarioNode = SiPolicyNode.SelectSingleNode("ns:SigningScenarios/ns:SigningScenario[@Value='12']", NamespaceManager); // Find the SigningScenario Node for Kernel Mode KMCI_SigningScenarioNode = SiPolicyNode.SelectSingleNode("ns:SigningScenarios/ns:SigningScenario[@Value='131']", NamespaceManager) ?? throw new InvalidOperationException("KMCI Signing Scenario node not found"); - // Find the ProductSigners Node for User Mode - UMCI_ProductSignersNode = SiPolicyNode.SelectSingleNode("ns:SigningScenarios/ns:SigningScenario[@Value='12']/ns:ProductSigners", NamespaceManager) - ?? throw new InvalidOperationException("UMCI Product Signers node not found"); + // Find the ProductSigners Node for User Mode - It is nullable because Kernel-Mode Strict policy won't have this section + UMCI_ProductSignersNode = SiPolicyNode.SelectSingleNode("ns:SigningScenarios/ns:SigningScenario[@Value='12']/ns:ProductSigners", NamespaceManager); // Find the ProductSigners Node for Kernel Mode KMCI_ProductSignersNode = SiPolicyNode.SelectSingleNode("ns:SigningScenarios/ns:SigningScenario[@Value='131']/ns:ProductSigners", NamespaceManager) @@ -149,12 +150,18 @@ internal CodeIntegrityPolicy(string? xmlFilePath, XmlDocument? xmlDocument) _ = SiPolicyNode.AppendChild(newBasePolicyIDNode); BasePolicyIDNode = newBasePolicyIDNode; + + BasePolicyID = newRandomGUIDString; } else { BasePolicyIDNode = basePolicyIDNode; + + + BasePolicyID = basePolicyIDNode.InnerText; } + #endregion @@ -174,10 +181,14 @@ internal CodeIntegrityPolicy(string? xmlFilePath, XmlDocument? xmlDocument) _ = SiPolicyNode.AppendChild(newPolicyIDNode); PolicyIDNode = newPolicyIDNode; + + PolicyID = newRandomGUIDString; } else { PolicyIDNode = policyIDNode; + + PolicyID = policyIDNode.InnerText; } #endregion diff --git a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/ClearCiPolicySemantic.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/ClearCiPolicySemantic.cs index 1e6e28040..f486d384f 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/ClearCiPolicySemantic.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/ClearCiPolicySemantic.cs @@ -23,10 +23,15 @@ public static void Clear(string xmlFilePath) baseNodes.Add(codeIntegrityPolicy.SiPolicyNode.SelectSingleNode("ns:EKUs", codeIntegrityPolicy.NamespaceManager)!); baseNodes.Add(codeIntegrityPolicy.SiPolicyNode.SelectSingleNode("ns:FileRules", codeIntegrityPolicy.NamespaceManager)!); baseNodes.Add(codeIntegrityPolicy.SiPolicyNode.SelectSingleNode("ns:Signers", codeIntegrityPolicy.NamespaceManager)!); - baseNodes.Add(codeIntegrityPolicy.UMCI_ProductSignersNode); + + if (codeIntegrityPolicy.UMCI_ProductSignersNode is not null) + { + baseNodes.Add(codeIntegrityPolicy.UMCI_ProductSignersNode); + } + baseNodes.Add(codeIntegrityPolicy.KMCI_ProductSignersNode); - XmlNode? fileRulesRefUMC = codeIntegrityPolicy.UMCI_ProductSignersNode.SelectSingleNode("ns:FileRulesRef", codeIntegrityPolicy.NamespaceManager); + XmlNode? fileRulesRefUMC = codeIntegrityPolicy.UMCI_ProductSignersNode?.SelectSingleNode("ns:FileRulesRef", codeIntegrityPolicy.NamespaceManager); if (fileRulesRefUMC is not null) { baseNodes.Add(fileRulesRefUMC); diff --git a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/MergeSignersSemantic.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/MergeSignersSemantic.cs index b57524be1..b62730f7d 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/MergeSignersSemantic.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/MergeSignersSemantic.cs @@ -29,7 +29,7 @@ public int GetHashCode(XmlNode obj) // A HashSet to store unique XmlNode objects internal sealed class UniqueXmlNodeSet { - private HashSet nodes; + private readonly HashSet nodes; // Constructor initializes the HashSet with XmlNodeComparer as the comparer logic public UniqueXmlNodeSet() @@ -91,7 +91,7 @@ internal static void Merge(string xmlFilePath) CodeIntegrityPolicy codeIntegrityPolicy = new(xmlFilePath, null); // Get the User Mode Signing Scenario node - XmlNode? allowedSigners12 = codeIntegrityPolicy.UMCI_ProductSignersNode.SelectSingleNode("ns:AllowedSigners", codeIntegrityPolicy.NamespaceManager); + XmlNode? allowedSigners12 = codeIntegrityPolicy.UMCI_ProductSignersNode?.SelectSingleNode("ns:AllowedSigners", codeIntegrityPolicy.NamespaceManager); // Get the Kernel Mode Signing Scenario node XmlNode? allowedSigners131 = codeIntegrityPolicy.KMCI_ProductSignersNode.SelectSingleNode("ns:AllowedSigners", codeIntegrityPolicy.NamespaceManager); @@ -130,7 +130,7 @@ internal static void Merge(string xmlFilePath) foreach (XmlNode node in fileRulesElements) { string? id = node?.Attributes?["ID"]?.Value; - if (id != null) + if (id is not null) { _ = fileRulesValidID_HashSet.Add(id); } @@ -396,15 +396,10 @@ internal static void Merge(string xmlFilePath) codeIntegrityPolicy.SignersNode.RemoveAll(); // Clear the existing AllowedSigners and CiSigners nodes from any type of Signer - if (allowedSigners131 is not null) - { - allowedSigners131.RemoveAll(); - } + allowedSigners131?.RemoveAll(); + + allowedSigners12?.RemoveAll(); - if (allowedSigners12 is not null) - { - allowedSigners12.RemoveAll(); - } codeIntegrityPolicy.CiSignersNode.RemoveAll(); diff --git a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/NewCertificateSignerRules.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/NewCertificateSignerRules.cs index ad0261305..709c0da63 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/NewCertificateSignerRules.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/NewCertificateSignerRules.cs @@ -20,6 +20,12 @@ public static void Create(string xmlFilePath, List sig // Instantiate the policy CodeIntegrityPolicy codeIntegrityPolicy = new(xmlFilePath, null); + // This method isn't suitable for strict Kernel-Mode policy + if (codeIntegrityPolicy.UMCI_ProductSignersNode is null) + { + throw new InvalidOperationException("NewCertificateSignerRules.Create method isn't suitable for strict Kernel-Mode policy"); + } + #region // Find AllowedSigners node in each ProductSigners node diff --git a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/NewFilePublisherLevelRules.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/NewFilePublisherLevelRules.cs index 800d0083f..e009f16b9 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/NewFilePublisherLevelRules.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/NewFilePublisherLevelRules.cs @@ -28,6 +28,13 @@ internal static void Create(string xmlFilePath, List // Instantiate the policy CodeIntegrityPolicy codeIntegrityPolicy = new(xmlFilePath, null); + // This method isn't suitable for strict Kernel-Mode policy + if (codeIntegrityPolicy.UMCI_ProductSignersNode is null) + { + throw new InvalidOperationException("NewFilePublisherLevelRules.Create method isn't suitable for strict Kernel-Mode policy"); + } + + #region // Find AllowedSigners node in each ProductSigners node @@ -87,17 +94,17 @@ internal static void Create(string xmlFilePath, List newFileAttribNode.SetAttribute("FileName", filePublisherData.OriginalFileName); } - if (!string.IsNullOrWhiteSpace(filePublisherData.InternalName)) + else if (!string.IsNullOrWhiteSpace(filePublisherData.InternalName)) { newFileAttribNode.SetAttribute("InternalName", filePublisherData.InternalName); } - if (!string.IsNullOrWhiteSpace(filePublisherData.FileDescription)) + else if (!string.IsNullOrWhiteSpace(filePublisherData.FileDescription)) { newFileAttribNode.SetAttribute("FileDescription", filePublisherData.FileDescription); } - if (!string.IsNullOrWhiteSpace(filePublisherData.ProductName)) + else if (!string.IsNullOrWhiteSpace(filePublisherData.ProductName)) { newFileAttribNode.SetAttribute("ProductName", filePublisherData.ProductName); } diff --git a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/NewHashLevelRules.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/NewHashLevelRules.cs index 047a2b430..d9f6ea17b 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/NewHashLevelRules.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/NewHashLevelRules.cs @@ -28,6 +28,12 @@ public static void Create(string xmlFilePath, List hashes) // Instantiate the policy CodeIntegrityPolicy codeIntegrityPolicy = new(xmlFilePath, null); + // This method isn't suitable for strict Kernel-Mode policy + if (codeIntegrityPolicy.UMCI_ProductSignersNode is null) + { + throw new InvalidOperationException("NewHashLevelRules.Create method isn't suitable for strict Kernel-Mode policy"); + } + Logger.Write($"NewHashLevelRules: There are {hashes.Count} Hash rules to be added to the XML file '{xmlFilePath}'"); #region diff --git a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/NewPFNLevelRules.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/NewPFNLevelRules.cs index 241381d3c..8ff857fe7 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/NewPFNLevelRules.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/NewPFNLevelRules.cs @@ -20,6 +20,14 @@ public static void Create(string xmlFilePath, List packageFamilyNames) // Instantiate the policy CodeIntegrityPolicy codeIntegrityPolicy = new(xmlFilePath, null); + + // This method isn't suitable for strict Kernel-Mode policy + if (codeIntegrityPolicy.UMCI_ProductSignersNode is null) + { + throw new InvalidOperationException("NewPFNLevelRules.Create method isn't suitable for strict Kernel-Mode policy"); + } + + // Find the FileRules node XmlNode fileRulesNode = codeIntegrityPolicy.SiPolicyNode.SelectSingleNode("ns:FileRules", codeIntegrityPolicy.NamespaceManager) ?? throw new InvalidOperationException("file rules node could not be found."); diff --git a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/NewPublisherLevelRules.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/NewPublisherLevelRules.cs index 312f3cc51..6611d652d 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/NewPublisherLevelRules.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/NewPublisherLevelRules.cs @@ -28,6 +28,16 @@ internal static void Create(string xmlFilePath, List pub // Instantiate the policy CodeIntegrityPolicy codeIntegrityPolicy = new(xmlFilePath, null); + + + // This method isn't suitable for strict Kernel-Mode policy + if (codeIntegrityPolicy.UMCI_ProductSignersNode is null) + { + throw new InvalidOperationException("NewPublisherLevelRules.Create method isn't suitable for strict Kernel-Mode policy"); + } + + + #region // Find AllowedSigners node in each ProductSigners node diff --git a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/RemoveAllowElementsSemantic.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/RemoveAllowElementsSemantic.cs index e1393ed04..b34ce452e 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/RemoveAllowElementsSemantic.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/RemoveAllowElementsSemantic.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Xml; #nullable enable @@ -26,6 +27,14 @@ internal static void Remove(string xmlFilePath) // Instantiate the policy CodeIntegrityPolicy codeIntegrityPolicy = new(xmlFilePath, null); + + // This method isn't suitable for strict Kernel-Mode policy + if (codeIntegrityPolicy.UMCI_ProductSignersNode is null) + { + throw new InvalidOperationException("RemoveAllowElementsSemantic.Remove method isn't suitable for strict Kernel-Mode policy"); + } + + // Get the node XmlNode fileRulesNode = codeIntegrityPolicy.SiPolicyNode.SelectSingleNode("ns:FileRules", codeIntegrityPolicy.NamespaceManager)!; diff --git a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/XMLOps.cs b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/XMLOps.cs index 9205f7f53..3fe8e28d7 100644 --- a/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/XMLOps.cs +++ b/WDACConfig/WDACConfig Module Files/C#/Shared Logics/XMLOps/XMLOps.cs @@ -23,9 +23,12 @@ public static void Initiate(FileBasedInfoPackage incomingData, string xmlFilePat Logger.Write("Merging the Signer Level rules"); RemoveDuplicateFileAttribSemantic.Remove(xmlFilePath); - // 2 passes are needed - MergeSignersSemantic.Merge(xmlFilePath); - MergeSignersSemantic.Merge(xmlFilePath); + // 2 passes are needed - Needs improvements + // MergeSignersSemantic.Merge(xmlFilePath); + // MergeSignersSemantic.Merge(xmlFilePath); + + // Replacement for the above method + PolicyMerger.Merge([xmlFilePath], xmlFilePath); // This method runs twice, once for signed data and once for unsigned data CloseEmptyXmlNodesSemantic.Close(xmlFilePath); diff --git a/WDACConfig/WDACConfig Module Files/Core/ConvertTo-WDACPolicy.psm1 b/WDACConfig/WDACConfig Module Files/Core/ConvertTo-WDACPolicy.psm1 index 1a05801fe..e39c03b87 100644 --- a/WDACConfig/WDACConfig Module Files/Core/ConvertTo-WDACPolicy.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/ConvertTo-WDACPolicy.psm1 @@ -3,864 +3,17 @@ Function ConvertTo-WDACPolicy { DefaultParameterSetName = 'All' )] param( - [ArgumentCompleter([WDACConfig.ArgCompleter.XmlFilePathsPicker])] - [Alias('AddLogs')] - [ValidateScript({ [WDACConfig.CiPolicyTest]::TestCiPolicy($_, $null) })] [Parameter(Mandatory = $false, ParameterSetName = 'In-Place Upgrade')] [System.IO.FileInfo]$PolicyToAddLogsTo, - - [ArgumentCompleter([WDACConfig.ArgCompleter.XmlFilePathsPicker])] - [Alias('BaseFile')] - [ValidateScript({ [WDACConfig.CiPolicyTest]::TestCiPolicy($_, $null) })] [Parameter(Mandatory = $false, ParameterSetName = 'Base-Policy File Association')] [System.IO.FileInfo]$BasePolicyFile, - - [Alias('Lvl')] - [ValidateSet('Auto', 'FilePublisher', 'Publisher', 'Hash')] [Parameter(Mandatory = $false)][System.String]$Level = 'Auto', - - [ArgumentCompleter({ - param($CommandName, $ParameterName, $WordToComplete, $CommandAst, $fakeBoundParameters) - - [System.String[]]$PolicyGUIDs = [WDACConfig.CiToolHelper]::GetPolicies($false, $true, $false).PolicyID - - $Existing = $CommandAst.FindAll({ - $args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] - }, $false).Value - - foreach ($Item in $PolicyGUIDs) { - if ($Item -notin $Existing) { - "'{0}'" -f $Item - } - } - })] [Parameter(Mandatory = $false, ParameterSetName = 'Base-Policy GUID Association')] - [Alias('BaseGUID')] - [System.Guid]$BasePolicyGUID, - - [Alias('Name')] - [ValidateCount(1, 232)] - [ValidatePattern('^[a-zA-Z0-9 \-]+$', ErrorMessage = 'The policy name can only contain alphanumeric, space and dash (-) characters.')] + [Alias('BaseGUID')][System.Guid]$BasePolicyGUID, [Parameter(Mandatory = $false)][System.String]$SuppPolicyName, - - [Alias('Src')] - [ValidateSet('MDEAdvancedHunting', 'LocalEventLogs', 'EVTXFiles')] [Parameter(Mandatory = $false)][System.String]$Source = 'LocalEventLogs', - - [ArgumentCompleter({ - param($CommandName, $ParameterName, $WordToComplete, $CommandAst, $FakeBoundParameters) - - [System.String[]]$Policies = [WDACConfig.CiToolHelper]::GetPolicies($true, $true, $false).FriendlyName - - $Existing = $CommandAst.FindAll({ - $args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] - }, $false).Value - - foreach ($Policy in $Policies) { - if ($Policy -notin $Existing) { - "'{0}'" -f $Policy - } - } - })] - [Alias('FilterNames')] [Parameter(Mandatory = $false)][System.String[]]$FilterByPolicyNames, - - [Alias('Duration')] - [ValidateSet('Minutes', 'Hours', 'Days')] [Parameter(Mandatory = $false)][System.String]$TimeSpan ) - - DynamicParam { - - # Create a new dynamic parameter dictionary - $ParamDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() - - # If TimeSpanAgo parameter was used, create a mandatory parameter to ask for the value - if ($PSBoundParameters['TimeSpan']) { - - # Create a parameter attribute collection - $TimeSpanAgo_AttributesCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute] - - # Create a mandatory attribute and add it to the collection - [System.Management.Automation.ParameterAttribute]$TimeSpanAgo_MandatoryAttrib = New-Object -TypeName System.Management.Automation.ParameterAttribute - $TimeSpanAgo_MandatoryAttrib.Mandatory = $true - $TimeSpanAgo_AttributesCollection.Add($TimeSpanAgo_MandatoryAttrib) - - # Create an alias attribute and add it to the collection - $TimeSpanAgo_AliasAttrib = New-Object -TypeName System.Management.Automation.AliasAttribute -ArgumentList 'Past' - $TimeSpanAgo_AttributesCollection.Add($TimeSpanAgo_AliasAttrib) - - # Create a dynamic parameter object with the attributes already assigned: Name, Type, and Attributes Collection - [System.Management.Automation.RuntimeDefinedParameter]$TimeSpanAgo = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter('TimeSpanAgo', [System.UInt64], $TimeSpanAgo_AttributesCollection) - - # Add the dynamic parameter object to the dictionary - $ParamDictionary.Add('TimeSpanAgo', $TimeSpanAgo) - } - - # Offer different parameters based on the source selected - switch ($PSBoundParameters['Source']) { - - # If user selected 'MDEAdvancedHunting' as the source, then create a mandatory parameter to ask for the .CSV file(s) path(s) - 'MDEAdvancedHunting' { - # Opens File picker GUI so that user can select .CSV files - [System.Management.Automation.ScriptBlock]$ArgumentCompleterCSVFilePathsPicker = { - # Create a new OpenFileDialog object - [System.Windows.Forms.OpenFileDialog]$Dialog = New-Object -TypeName 'System.Windows.Forms.OpenFileDialog' - # Set the filter to show only CSV files - $Dialog.Filter = 'CSV files (*.CSV)|*.CSV' - # Set the title of the dialog - $Dialog.Title = 'Select Microsoft Defender for Endpoint Advanced Hunting CSV files' - # Allow multiple CSV files to be selected - $Dialog.Multiselect = $true - $Dialog.ShowPreview = $true - # Show the dialog and get the result - [System.String]$Result = $Dialog.ShowDialog() - # If the user clicked OK, return the selected file paths - if ($Result -eq 'OK') { - return "`"$($Dialog.FileNames -join '","')`"" - } - } - - # Create a parameter attribute collection - $MDEAHLogs_AttributesCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute] - - # Create an argument completer attribute and add it to the collection - [System.Management.Automation.ArgumentCompleterAttribute]$MDEAHLogs_ArgumentCompleterAttrib = New-Object -TypeName System.Management.Automation.ArgumentCompleterAttribute($ArgumentCompleterCSVFilePathsPicker) - $MDEAHLogs_AttributesCollection.Add($MDEAHLogs_ArgumentCompleterAttrib) - - # Create a mandatory attribute and add it to the collection - [System.Management.Automation.ParameterAttribute]$MDEAHLogs_MandatoryAttrib = New-Object -TypeName System.Management.Automation.ParameterAttribute - $MDEAHLogs_MandatoryAttrib.Mandatory = $true - $MDEAHLogs_AttributesCollection.Add($MDEAHLogs_MandatoryAttrib) - - # Create an alias attribute and add it to the collection - $MDEAHLogs_AliasAttrib = New-Object -TypeName System.Management.Automation.AliasAttribute -ArgumentList 'MDELogs' - $MDEAHLogs_AttributesCollection.Add($MDEAHLogs_AliasAttrib) - - # Create a dynamic parameter object with the attributes already assigned: Name, Type, and Attributes Collection - [System.Management.Automation.RuntimeDefinedParameter]$MDEAHLogs = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter('MDEAHLogs', [System.IO.FileInfo[]], $MDEAHLogs_AttributesCollection) - - # Add the dynamic parameter object to the dictionary - $ParamDictionary.Add('MDEAHLogs', $MDEAHLogs) - - break - } - - 'EVTXFiles' { - - # Opens File picker GUI so that user can select .EVTX files - [System.Management.Automation.ScriptBlock]$ArgumentCompleterEVTXFilePathsPicker = { - # Create a new OpenFileDialog object - [System.Windows.Forms.OpenFileDialog]$Dialog = New-Object -TypeName 'System.Windows.Forms.OpenFileDialog' - # Set the filter to show only EVTX files - $Dialog.Filter = 'EVTX files (*.evtx)|*.evtx' - # Set the title of the dialog - $Dialog.Title = 'Select .evtx files to convert to WDAC policy' - # Allow multiple EVTX files to be selected - $Dialog.Multiselect = $true - $Dialog.ShowPreview = $true - # Show the dialog and get the result - [System.String]$Result = $Dialog.ShowDialog() - # If the user clicked OK, return the selected file paths - if ($Result -eq 'OK') { - return "`"$($Dialog.FileNames -join '","')`"" - } - } - - # Create a parameter attribute collection - $EVTXLogs_AttributesCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute] - - # Create an argument completer attribute and add it to the collection - [System.Management.Automation.ArgumentCompleterAttribute]$EVTXLogs_ArgumentCompleterAttrib = New-Object -TypeName System.Management.Automation.ArgumentCompleterAttribute($ArgumentCompleterEVTXFilePathsPicker) - $EVTXLogs_AttributesCollection.Add($EVTXLogs_ArgumentCompleterAttrib) - - # Create a mandatory attribute and add it to the collection - [System.Management.Automation.ParameterAttribute]$EVTXLogs_MandatoryAttrib = New-Object -TypeName System.Management.Automation.ParameterAttribute - $EVTXLogs_MandatoryAttrib.Mandatory = $true - $EVTXLogs_AttributesCollection.Add($EVTXLogs_MandatoryAttrib) - - # Create an alias attribute and add it to the collection - $EVTXLogs_AliasAttrib = New-Object -TypeName System.Management.Automation.AliasAttribute -ArgumentList 'Evtx' - $EVTXLogs_AttributesCollection.Add($EVTXLogs_AliasAttrib) - - # Create a dynamic parameter object with the attributes already assigned: Name, Type, and Attributes Collection - [System.Management.Automation.RuntimeDefinedParameter]$EVTXLogs = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter('EVTXLogs', [System.IO.FileInfo[]], $EVTXLogs_AttributesCollection) - - # Add the dynamic parameter object to the dictionary - $ParamDictionary.Add('EVTXLogs', $EVTXLogs) - - break - } - } - - #Region-KernelModeOnly-Parameter - - # Create a parameter attribute collection - $KernelModeOnly_AttributesCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute] - - # Create a mandatory attribute and add it to the collection - [System.Management.Automation.ParameterAttribute]$KernelModeOnly_MandatoryAttrib = New-Object -TypeName System.Management.Automation.ParameterAttribute - $KernelModeOnly_MandatoryAttrib.Mandatory = $false - $KernelModeOnly_AttributesCollection.Add($KernelModeOnly_MandatoryAttrib) - - # Create an alias attribute and add it to the collection - $KernelModeOnly_AliasAttrib = New-Object -TypeName System.Management.Automation.AliasAttribute -ArgumentList 'KMode' - $KernelModeOnly_AttributesCollection.Add($KernelModeOnly_AliasAttrib) - - # Create a dynamic parameter object with the attributes already assigned: Name, Type, and Attributes Collection - [System.Management.Automation.RuntimeDefinedParameter]$KernelModeOnly = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter('KernelModeOnly', [switch], $KernelModeOnly_AttributesCollection) - - # Add the dynamic parameter object to the dictionary - $ParamDictionary.Add('KernelModeOnly', $KernelModeOnly) - - #Endregion-KernelModeOnly-Parameter - - #Region-LogType-Parameter - - # Create a parameter attribute collection - $LogType_AttributesCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute] - - # Create a mandatory attribute and add it to the collection - [System.Management.Automation.ParameterAttribute]$LogType_MandatoryAttrib = New-Object -TypeName System.Management.Automation.ParameterAttribute - $LogType_MandatoryAttrib.Mandatory = $false - $LogType_AttributesCollection.Add($LogType_MandatoryAttrib) - - # Create a ValidateSet attribute with the allowed values - [System.Management.Automation.ValidateSetAttribute]$LogType_ValidateSetAttrib = New-Object -TypeName System.Management.Automation.ValidateSetAttribute('Audit', 'Blocked', 'All') - $LogType_AttributesCollection.Add($LogType_ValidateSetAttrib) - - # Create an alias attribute and add it to the collection - $LogType_AliasAttrib = New-Object -TypeName System.Management.Automation.AliasAttribute -ArgumentList 'LogKind' - $LogType_AttributesCollection.Add($LogType_AliasAttrib) - - # Create a dynamic parameter object with the attributes already assigned: Name, Type, and Attributes Collection - [System.Management.Automation.RuntimeDefinedParameter]$LogType = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter('LogType', [System.String], $LogType_AttributesCollection) - - # Add the dynamic parameter object to the dictionary - $ParamDictionary.Add('LogType', $LogType) - - #Endregion-LogType-Parameter - - #Region-Deploy-Parameter - - # Create a parameter attribute collection - $Deploy_AttributesCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute] - - # Create a mandatory attribute and add it to the collection - [System.Management.Automation.ParameterAttribute]$Deploy_MandatoryAttrib = New-Object -TypeName System.Management.Automation.ParameterAttribute - $Deploy_MandatoryAttrib.Mandatory = $false - $Deploy_AttributesCollection.Add($Deploy_MandatoryAttrib) - - # Create an alias attribute and add it to the collection - $Deploy_AliasAttrib = New-Object -TypeName System.Management.Automation.AliasAttribute -ArgumentList 'Up' - $Deploy_AttributesCollection.Add($Deploy_AliasAttrib) - - # Create a dynamic parameter object with the attributes already assigned: Name, Type, and Attributes Collection - [System.Management.Automation.RuntimeDefinedParameter]$Deploy = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter('Deploy', [switch], $Deploy_AttributesCollection) - - # Add the dynamic parameter object to the dictionary - $ParamDictionary.Add('Deploy', $Deploy) - - #Endregion-Deploy-Parameter - - #Region-ExtremeVisibility-Parameter - - # Create a parameter attribute collection - $ExtremeVisibility_AttributesCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute] - - # Create a mandatory attribute and add it to the collection - [System.Management.Automation.ParameterAttribute]$ExtremeVisibility_MandatoryAttrib = New-Object -TypeName System.Management.Automation.ParameterAttribute - $ExtremeVisibility_MandatoryAttrib.Mandatory = $false - $ExtremeVisibility_AttributesCollection.Add($ExtremeVisibility_MandatoryAttrib) - - $ExtremeVisibility_AliasAttrib = New-Object -TypeName System.Management.Automation.AliasAttribute -ArgumentList 'XVis' - $ExtremeVisibility_AttributesCollection.Add($ExtremeVisibility_AliasAttrib) - - # Create a dynamic parameter object with the attributes already assigned: Name, Type, and Attributes Collection - [System.Management.Automation.RuntimeDefinedParameter]$ExtremeVisibility = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter('ExtremeVisibility', [switch], $ExtremeVisibility_AttributesCollection) - - # Add the dynamic parameter object to the dictionary - $ParamDictionary.Add('ExtremeVisibility', $ExtremeVisibility) - - #Endregion-ExtremeVisibility-Parameter - - return $ParamDictionary - } - Begin { - [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) - - [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\Receive-CodeIntegrityLogs.psm1", - "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Set-LogPropertiesVisibility.psm1", - "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Select-LogProperties.psm1", - "$([WDACConfig.GlobalVars]::ModuleRootPath)\Shared\Test-KernelProtectedFiles.psm1" - ) - # Add XML Ops module to the list of modules to import - $ModulesToImport += ([WDACConfig.FileUtility]::GetFilesFast("$([WDACConfig.GlobalVars]::ModuleRootPath)\XMLOps", $null, '.psm1')).FullName - Import-Module -FullyQualifiedName $ModulesToImport -Force - - # Since Dynamic parameters are only available in the parameter dictionary, we have to access them using $PSBoundParameters or assign them manually to another variable in the function's scope - New-Variable -Name 'TimeSpanAgo' -Value $PSBoundParameters['TimeSpanAgo'] -Force - New-Variable -Name 'MDEAHLogs' -Value $PSBoundParameters['MDEAHLogs'] -Force - New-Variable -Name 'EVTXLogs' -Value $PSBoundParameters['EVTXLogs'] -Force - New-Variable -Name 'KernelModeOnly' -Value $PSBoundParameters['KernelModeOnly'] -Force - New-Variable -Name 'LogType' -Value ($PSBoundParameters['LogType'] ?? 'All') -Force - New-Variable -Name 'Deploy' -Value $PSBoundParameters['Deploy'] -Force - New-Variable -Name 'ExtremeVisibility' -Value $PSBoundParameters['ExtremeVisibility'] -Force - - Update-WDACConfigPSModule -InvocationStatement $MyInvocation.Statement - - # Defining a staging area for the current - [System.IO.DirectoryInfo]$StagingArea = [WDACConfig.StagingArea]::NewStagingArea('ConvertTo-WDACPolicy') - - # If TimeSpan parameter was selected - if ($TimeSpan) { - - # Get the current time - [System.DateTime]$CurrentDateTime = Get-Date - - # Create the $StartTime variable based on the user input TimeSpanAgo parameter - switch ($TimeSpan) { - 'Minutes' { [System.DateTime]$StartTime = $CurrentDateTime.AddMinutes(-$TimeSpanAgo) -as [System.DateTime] } - 'Hours' { [System.DateTime]$StartTime = $CurrentDateTime.AddHours(-$TimeSpanAgo) -as [System.DateTime] } - 'Days' { [System.DateTime]$StartTime = $CurrentDateTime.AddDays(-$TimeSpanAgo) -as [System.DateTime] } - } - } - - # Save the current date in a variable as string - [System.String]$CurrentDate = $(Get-Date -Format "MM-dd-yyyy 'at' HH-mm-ss") - } - - Process { - - Try { - - Switch ($Source) { - - 'LocalEventLogs' { - - # Define the policy name if it wasn't provided by the user - [System.String]$SuppPolicyName = $PSBoundParameters['SuppPolicyName'] ?? "Supplemental Policy from event logs - $CurrentDate" - - # The path to the final Supplemental WDAC Policy file - [System.IO.FileInfo]$WDACPolicyPath = Join-Path -Path $StagingArea -ChildPath "$SuppPolicyName.xml" - - # The path to the temp WDAC Policy file - [System.IO.FileInfo]$WDACPolicyPathTEMP = Join-Path -Path $StagingArea -ChildPath "TEMP $SuppPolicyName.xml" - - # The path to the Kernel protected files WDAC Policy file - [System.IO.FileInfo]$WDACPolicyKernelProtectedPath = Join-Path -Path $StagingArea -ChildPath "Kernel Protected Files $SuppPolicyName.xml" - - # The paths to the policy files to be merged together to produce the final Supplemental policy - $PolicyFilesToMerge = New-Object -TypeName System.Collections.Generic.List[System.IO.FileInfo] - - # The total number of the main steps for the progress bar to render - [System.UInt16]$TotalSteps = 4 - [System.UInt16]$CurrentStep = 0 - - $CurrentStep++ - Write-Progress -Id 30 -Activity "Collecting $LogType events" -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - - if ($null -ne $StartTime -and $StartTime -is [System.DateTime]) { - [PSCustomObject[]]$EventsToDisplay = Receive-CodeIntegrityLogs -PostProcessing OnlyExisting -PolicyName:$FilterByPolicyNames -Date $StartTime -Type:$LogType - } - else { - [PSCustomObject[]]$EventsToDisplay = Receive-CodeIntegrityLogs -PostProcessing OnlyExisting -PolicyName:$FilterByPolicyNames -Type:$LogType - } - - [PSCustomObject[]]$EventsToDisplay = Select-LogProperties -Logs $EventsToDisplay - - # If the KernelModeOnly switch is used, then filter the events by the 'Requested Signing Level' property - if ($KernelModeOnly) { - $EventsToDisplay = foreach ($Event in $EventsToDisplay) { - if ($Event.'SI Signing Scenario' -eq 'Kernel-Mode') { - $Event - } - } - } - - if (($null -eq $EventsToDisplay) -and ($EventsToDisplay.Count -eq 0)) { - Write-ColorfulTextWDACConfig -Color HotPink -InputText 'No logs were found to display based on the current filters. Exiting...' - return - } - - # If the ExtremeVisibility switch is used, then display all the properties of the logs without any filtering - if (-NOT $ExtremeVisibility) { - Set-LogPropertiesVisibility -LogType Evtx/Local -EventsToDisplay $EventsToDisplay - } - - # 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)" - - [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...' - return - } - - # If the user has selected any logs, then create an AppControl policy for them, otherwise return - if ($null -eq $SelectedLogs) { - return - } - - $CurrentStep++ - Write-Progress -Id 30 -Activity 'Checking for kernel-protected files' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - - $KernelProtectedFileLogs = Test-KernelProtectedFiles -Logs $SelectedLogs - - if ($null -ne $KernelProtectedFileLogs) { - - [WDACConfig.Logger]::Write("ConvertTo-WDACPolicy: Kernel protected files count: $($KernelProtectedFileLogs.count)") - - [WDACConfig.Logger]::Write('Copying the template policy to the staging area') - Copy-Item -LiteralPath 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowAll.xml' -Destination $WDACPolicyKernelProtectedPath -Force - - [WDACConfig.Logger]::Write('Emptying the policy file in preparation for the new data insertion') - [WDACConfig.ClearCiPolicySemantic]::Clear($WDACPolicyKernelProtectedPath) - - # Find the kernel protected files that have PFN property - $KernelProtectedFileLogsWithPFN = foreach ($Log in $KernelProtectedFileLogs) { - if ($Log.PackageFamilyName) { - $Log - } - } - - [WDACConfig.NewPFNLevelRules]::Create($WDACPolicyKernelProtectedPath, $KernelProtectedFileLogsWithPFN.PackageFamilyName) - - # Add the Kernel protected files policy to the list of policies to merge - $PolicyFilesToMerge.Add($WDACPolicyKernelProtectedPath) - - [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) { - if ($Log -notin $KernelProtectedFileLogsWithPFN) { - $Log - } - } - } - - $CurrentStep++ - Write-Progress -Id 30 -Activity 'Generating the policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - - [WDACConfig.Logger]::Write('Copying the template policy to the staging area') - Copy-Item -LiteralPath 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowAll.xml' -Destination $WDACPolicyPathTEMP -Force - - [WDACConfig.Logger]::Write('Emptying the policy file in preparation for the new data insertion') - [WDACConfig.ClearCiPolicySemantic]::Clear($WDACPolicyPathTEMP) - - $CurrentStep++ - Write-Progress -Id 30 -Activity 'Building the Signer and file rule' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - - [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) - - [WDACConfig.XMLOps]::Initiate($DataToUseForBuilding, $WDACPolicyPathTEMP) - - $PolicyFilesToMerge.Add($WDACPolicyPathTEMP) - - $null = Merge-CIPolicy -PolicyPaths $PolicyFilesToMerge -OutputFilePath $WDACPolicyPath - - Switch ($True) { - - { $null -ne $BasePolicyFile } { - - [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) - - [System.String]$SupplementalPolicyID = [WDACConfig.SetCiPolicyInfo]::Set($WDACPolicyPath, $true, $SuppPolicyName, $InputXMLObj.SiPolicy.BasePolicyID, $null) - - # Configure policy rule options - [WDACConfig.CiRuleOptions]::Set($WDACPolicyPath, [WDACConfig.CiRuleOptions+PolicyTemplate]::Supplemental, $null, $null, $null, $null, $null, $null, $null, $null, $null) - - [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") - - [WDACConfig.CiToolHelper]::UpdatePolicy((Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip")) - } - } - { $null -ne $BasePolicyGUID } { - - [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Assigning the user input GUID to the base policy ID of the supplemental policy') - - [System.String]$SupplementalPolicyID = [WDACConfig.SetCiPolicyInfo]::Set($WDACPolicyPath, $true, $SuppPolicyName, $BasePolicyGUID, $null) - - # Configure policy rule options - [WDACConfig.CiRuleOptions]::Set($WDACPolicyPath, [WDACConfig.CiRuleOptions+PolicyTemplate]::Supplemental, $null, $null, $null, $null, $null, $null, $null, $null, $null) - - [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") - - [WDACConfig.CiToolHelper]::UpdatePolicy((Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip")) - } - } - { $null -ne $PolicyToAddLogsTo } { - [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Adding the logs to the policy that user selected') - - $MacrosBackup = [WDACConfig.Macros]::Backup($PolicyToAddLogsTo) - - # Objectify the user input policy file to extract its policy ID - $InputXMLObj = [System.Xml.XmlDocument](Get-Content -Path $PolicyToAddLogsTo) - - $null = [WDACConfig.SetCiPolicyInfo]::Set($WDACPolicyPath, $true, $SuppPolicyName, $null, $null) - - # Remove all policy rule options prior to merging the policies since we don't need to add/remove any policy rule options to/from the user input policy - [WDACConfig.CiRuleOptions]::Set($WDACPolicyPath, $null, $null, $null, $null, $null, $null, $null, $null, $null, $true) - - $null = Merge-CIPolicy -PolicyPaths $PolicyToAddLogsTo, $WDACPolicyPath -OutputFilePath $PolicyToAddLogsTo - - [WDACConfig.UpdateHvciOptions]::Update($PolicyToAddLogsTo) - [WDACConfig.Macros]::Restore($PolicyToAddLogsTo, $MacrosBackup) - - if ($Deploy) { - $null = ConvertFrom-CIPolicy -XmlFilePath $PolicyToAddLogsTo -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$($InputXMLObj.SiPolicy.PolicyID).cip") - - [WDACConfig.CiToolHelper]::UpdatePolicy((Join-Path -Path $StagingArea -ChildPath "$($InputXMLObj.SiPolicy.PolicyID).cip")) - } - } - } - - #Endregion Base To Supplemental Policy Association and Deployment - } - 'MDEAdvancedHunting' { - - # Define the policy name if it wasn't provided by the user - [System.String]$SuppPolicyName = $PSBoundParameters['SuppPolicyName'] ?? "Supplemental Policy from MDE Advanced Hunting - $CurrentDate" - - # The total number of the main steps for the progress bar to render - [System.UInt16]$TotalSteps = 5 - [System.UInt16]$CurrentStep = 0 - - $CurrentStep++ - Write-Progress -Id 31 -Activity 'Optimizing the MDE CSV data' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - - [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) - - [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...' - return - } - - if ($TimeSpan) { - [System.Collections.Hashtable]$EventPackageCollections = Compare-CorrelatedData -OptimizedCSVData $OptimizedCSVData -StagingArea $StagingArea -StartTime $StartTime -PolicyNamesToFilter:$FilterByPolicyNames -LogType:$LogType - } - else { - [System.Collections.Hashtable]$EventPackageCollections = Compare-CorrelatedData -OptimizedCSVData $OptimizedCSVData -StagingArea $StagingArea -PolicyNamesToFilter:$FilterByPolicyNames -LogType:$LogType - } - - # Selecting all of the properties of each log to be displayed - $MDEAHLogsToDisplay = $EventPackageCollections.Values -as [PSCustomObject] | Select-Object -Property * - - # If the KernelModeOnly switch is used, then filter the logs by the 'SiSigningScenario' property - if ($KernelModeOnly) { - $MDEAHLogsToDisplay = foreach ($Event in $MDEAHLogsToDisplay) { - if ($Event.'SiSigningScenario' -eq '0') { - $Event - } - } - } - - if (($null -eq $MDEAHLogsToDisplay) -or ($MDEAHLogsToDisplay.Count -eq 0)) { - Write-ColorfulTextWDACConfig -Color HotPink -InputText 'No MDE Advanced Hunting logs available based on the selected filters. Exiting...' - return - } - - #Region Out-GridView properties visibility settings - # If the ExtremeVisibility switch is used, then display all the properties of the logs without any filtering - if (-NOT $ExtremeVisibility) { - Set-LogPropertiesVisibility -LogType MDEAH -EventsToDisplay $MDEAHLogsToDisplay - } - #Endregion Out-GridView properties visibility settings - - $CurrentStep++ - Write-Progress -Id 31 -Activity 'Displaying the MDE Advanced Hunting logs in a GUI' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - - [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)) { - Write-ColorfulTextWDACConfig -Color HotPink -InputText 'No MDE Advanced Hunting logs were selected to create an AppControl policy from. Exiting...' - return - } - - $CurrentStep++ - Write-Progress -Id 31 -Activity 'Preparing an empty policy to save the logs to' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - - # 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" - - [WDACConfig.Logger]::Write('Copying the template policy to the staging area') - Copy-Item -LiteralPath 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowAll.xml' -Destination $OutputPolicyPathMDEAH -Force - - [WDACConfig.Logger]::Write('Emptying the policy file in preparation for the new data insertion') - [WDACConfig.ClearCiPolicySemantic]::Clear($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) - - [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) - - [WDACConfig.XMLOps]::Initiate($DataToUseForBuilding, $OutputPolicyPathMDEAH) - - #Region Base To Supplemental Policy Association and Deployment - Switch ($True) { - - { $null -ne $BasePolicyFile } { - - [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) - - [System.String]$SupplementalPolicyID = [WDACConfig.SetCiPolicyInfo]::Set($OutputPolicyPathMDEAH, $true, $SuppPolicyName, $InputXMLObj.SiPolicy.BasePolicyID, $null) - - # Configure policy rule options - [WDACConfig.CiRuleOptions]::Set($OutputPolicyPathMDEAH, [WDACConfig.CiRuleOptions+PolicyTemplate]::Supplemental, $null, $null, $null, $null, $null, $null, $null, $null, $null) - - [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") - - [WDACConfig.CiToolHelper]::UpdatePolicy((Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip")) - } - } - { $null -ne $BasePolicyGUID } { - - [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Assigning the user input GUID to the base policy ID of the supplemental policy') - - [System.String]$SupplementalPolicyID = [WDACConfig.SetCiPolicyInfo]::Set($OutputPolicyPathMDEAH, $true, $SuppPolicyName, $BasePolicyGUID, $null) - - # Configure policy rule options - [WDACConfig.CiRuleOptions]::Set($OutputPolicyPathMDEAH, [WDACConfig.CiRuleOptions+PolicyTemplate]::Supplemental, $null, $null, $null, $null, $null, $null, $null, $null, $null) - - [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") - - [WDACConfig.CiToolHelper]::UpdatePolicy((Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip")) - } - } - { $null -ne $PolicyToAddLogsTo } { - [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Adding the logs to the policy that user selected') - - $MacrosBackup = [WDACConfig.Macros]::Backup($PolicyToAddLogsTo) - - # Objectify the user input policy file to extract its policy ID - $InputXMLObj = [System.Xml.XmlDocument](Get-Content -Path $PolicyToAddLogsTo) - - $null = [WDACConfig.SetCiPolicyInfo]::Set($OutputPolicyPathMDEAH, $true, $SuppPolicyName, $null, $null) - - # Remove all policy rule options prior to merging the policies since we don't need to add/remove any policy rule options to/from the user input policy - [WDACConfig.CiRuleOptions]::Set($OutputPolicyPathMDEAH, $null, $null, $null, $null, $null, $null, $null, $null, $null, $true) - - $null = Merge-CIPolicy -PolicyPaths $PolicyToAddLogsTo, $OutputPolicyPathMDEAH -OutputFilePath $PolicyToAddLogsTo - - [WDACConfig.UpdateHvciOptions]::Update($PolicyToAddLogsTo) - [WDACConfig.Macros]::Restore($PolicyToAddLogsTo, $MacrosBackup) - - if ($Deploy) { - $null = ConvertFrom-CIPolicy -XmlFilePath $PolicyToAddLogsTo -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$($InputXMLObj.SiPolicy.PolicyID).cip") - - [WDACConfig.CiToolHelper]::UpdatePolicy((Join-Path -Path $StagingArea -ChildPath "$($InputXMLObj.SiPolicy.PolicyID).cip")) - } - } - Default { - [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Copying the policy file to the User Config directory') - [WDACConfig.CiRuleOptions]::Set($OutputPolicyPathMDEAH, [WDACConfig.CiRuleOptions+PolicyTemplate]::Supplemental, $null, $null, $null, $null, $null, $null, $null, $null, $null) - Copy-Item -Path $OutputPolicyPathMDEAH -Destination ([WDACConfig.GlobalVars]::UserConfigDir) -Force - } - } - #Endregion Base To Supplemental Policy Association and Deployment - } - 'EVTXFiles' { - - # Define the policy name if it wasn't provided by the user - [System.String]$SuppPolicyName = $PSBoundParameters['SuppPolicyName'] ?? "Supplemental Policy from Evtx files - $CurrentDate" - - # The total number of the main steps for the progress bar to render - [System.UInt16]$TotalSteps = 4 - [System.UInt16]$CurrentStep = 0 - - $CurrentStep++ - Write-Progress -Id 32 -Activity 'Processing the selected Evtx files' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - - if ($null -ne $StartTime -and $StartTime -is [System.DateTime]) { - [PSCustomObject[]]$EventsToDisplay = Receive-CodeIntegrityLogs -PolicyName:$FilterByPolicyNames -Date $StartTime -Type:$LogType -LogSource EVTXFiles -EVTXFilePaths $EVTXLogs - } - else { - [PSCustomObject[]]$EventsToDisplay = Receive-CodeIntegrityLogs -PolicyName:$FilterByPolicyNames -Type:$LogType -LogSource EVTXFiles -EVTXFilePaths $EVTXLogs - } - - [PSCustomObject[]]$EventsToDisplay = Select-LogProperties -Logs $EventsToDisplay - - # If the KernelModeOnly switch is used, then filter the events by the 'Requested Signing Level' property - if ($KernelModeOnly) { - $EventsToDisplay = foreach ($Event in $EventsToDisplay) { - if ($Event.'SI Signing Scenario' -eq 'Kernel-Mode') { - $Event - } - } - } - - if (($null -eq $EventsToDisplay) -and ($EventsToDisplay.Count -eq 0)) { - Write-ColorfulTextWDACConfig -Color HotPink -InputText 'No logs were found to display based on the current filters. Exiting...' - return - } - - #Region Out-GridView properties visibility settings - - # If the ExtremeVisibility switch is used, then display all the properties of the logs without any filtering - if (-NOT $ExtremeVisibility) { - Set-LogPropertiesVisibility -LogType Evtx/Local -EventsToDisplay $EventsToDisplay - } - - #Endregion Out-GridView properties visibility settings - - $CurrentStep++ - Write-Progress -Id 32 -Activity 'Displaying the logs' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - - # 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)" - - [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...' - return - } - - # Define the path where the final Evtx XML policy file will be saved - [System.IO.FileInfo]$OutputPolicyPathEVTX = Join-Path -Path $StagingArea -ChildPath "$SuppPolicyName.xml" - - [WDACConfig.Logger]::Write('Copying the template policy to the staging area') - Copy-Item -LiteralPath 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowAll.xml' -Destination $OutputPolicyPathEVTX -Force - - [WDACConfig.Logger]::Write('Emptying the policy file in preparation for the new data insertion') - [WDACConfig.ClearCiPolicySemantic]::Clear($OutputPolicyPathEVTX) - - $CurrentStep++ - Write-Progress -Id 32 -Activity 'Building the Signer and file rule' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - - [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) - - [WDACConfig.XMLOps]::Initiate($DataToUseForBuilding, $OutputPolicyPathEVTX) - - #Region Base To Supplemental Policy Association and Deployment - - $CurrentStep++ - Write-Progress -Id 32 -Activity 'Generating the final policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - - Switch ($True) { - - { $null -ne $BasePolicyFile } { - - [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) - - [System.String]$SupplementalPolicyID = [WDACConfig.SetCiPolicyInfo]::Set($OutputPolicyPathEVTX, $true, $SuppPolicyName, $InputXMLObj.SiPolicy.BasePolicyID, $null) - - # Configure policy rule options - [WDACConfig.CiRuleOptions]::Set($OutputPolicyPathEVTX, [WDACConfig.CiRuleOptions+PolicyTemplate]::Supplemental, $null, $null, $null, $null, $null, $null, $null, $null, $null) - - [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") - - [WDACConfig.CiToolHelper]::UpdatePolicy((Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip")) - } - } - { $null -ne $BasePolicyGUID } { - - [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Assigning the user input GUID to the base policy ID of the supplemental policy') - - [System.String]$SupplementalPolicyID = [WDACConfig.SetCiPolicyInfo]::Set($OutputPolicyPathEVTX, $true, $SuppPolicyName, $BasePolicyGUID, $null) - - # Configure policy rule options - [WDACConfig.CiRuleOptions]::Set($OutputPolicyPathEVTX, [WDACConfig.CiRuleOptions+PolicyTemplate]::Supplemental, $null, $null, $null, $null, $null, $null, $null, $null, $null) - - [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") - - [WDACConfig.CiToolHelper]::UpdatePolicy((Join-Path -Path $StagingArea -ChildPath "$SupplementalPolicyID.cip")) - } - } - { $null -ne $PolicyToAddLogsTo } { - [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Adding the logs to the policy that user selected') - - $MacrosBackup = [WDACConfig.Macros]::Backup($PolicyToAddLogsTo) - - # Objectify the user input policy file to extract its policy ID - $InputXMLObj = [System.Xml.XmlDocument](Get-Content -Path $PolicyToAddLogsTo) - - $null = [WDACConfig.SetCiPolicyInfo]::Set($OutputPolicyPathEVTX, $true, $SuppPolicyName, $null, $null) - - # Remove all policy rule options prior to merging the policies since we don't need to add/remove any policy rule options to/from the user input policy - [WDACConfig.CiRuleOptions]::Set($OutputPolicyPathEVTX, $null, $null, $null, $null, $null, $null, $null, $null, $null, $true) - - $null = Merge-CIPolicy -PolicyPaths $PolicyToAddLogsTo, $OutputPolicyPathEVTX -OutputFilePath $PolicyToAddLogsTo - - [WDACConfig.UpdateHvciOptions]::Update($PolicyToAddLogsTo) - [WDACConfig.Macros]::Restore($PolicyToAddLogsTo, $MacrosBackup) - - if ($Deploy) { - $null = ConvertFrom-CIPolicy -XmlFilePath $PolicyToAddLogsTo -BinaryFilePath (Join-Path -Path $StagingArea -ChildPath "$($InputXMLObj.SiPolicy.PolicyID).cip") - - [WDACConfig.CiToolHelper]::UpdatePolicy((Join-Path -Path $StagingArea -ChildPath "$($InputXMLObj.SiPolicy.PolicyID).cip")) - } - } - Default { - [WDACConfig.Logger]::Write('ConvertTo-WDACPolicy: Copying the policy file to the User Config directory') - [WDACConfig.CiRuleOptions]::Set($OutputPolicyPathEVTX, [WDACConfig.CiRuleOptions+PolicyTemplate]::Supplemental, $null, $null, $null, $null, $null, $null, $null, $null, $null) - Copy-Item -Path $OutputPolicyPathEVTX -Destination ([WDACConfig.GlobalVars]::UserConfigDir) -Force - } - } - #Endregion Base To Supplemental Policy Association and Deployment - } - } - } - catch { - throw $_ - } - Finally { - Write-Progress -Id 30 -Activity 'Complete.' -Completed - Write-Progress -Id 31 -Activity 'Complete.' -Completed - Write-Progress -Id 32 -Activity 'Complete.' -Completed - - if (![WDACConfig.GlobalVars]::DebugPreference) { - Remove-Item -Path $StagingArea -Recurse -Force - } - } - } - # .EXTERNALHELP ..\Help\ConvertTo-WDACPolicy.xml + Write-Host -ForegroundColor Green -Object "This function's job has been completely added to the new AppControl Manager app. It offers a complete graphical user interface (GUI) for easy usage. Please refer to this GitHub page to see how to install and use it:`nhttps://github.com/HotCakeX/Harden-Windows-Security/wiki/AppControl-Manager" } \ No newline at end of file diff --git a/WDACConfig/WDACConfig Module Files/Core/Deploy-SignedWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Deploy-SignedWDACConfig.psm1 index bfd148a6a..013219865 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Deploy-SignedWDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Deploy-SignedWDACConfig.psm1 @@ -130,6 +130,7 @@ Function Deploy-SignedWDACConfig { # 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) { + <# [WDACConfig.Logger]::Write('Checking whether SignTool.exe is allowed to execute in the policy or not') if (!([WDACConfig.InvokeWDACSimulation]::Invoke($SignToolPathFinal, $PolicyPath, $true))) { @@ -159,6 +160,7 @@ Function Deploy-SignedWDACConfig { else { [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 } diff --git a/WDACConfig/WDACConfig Module Files/Core/Edit-WDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/Edit-WDACConfig.psm1 index ce6ab7d4e..0b4898f11 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Edit-WDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Edit-WDACConfig.psm1 @@ -136,376 +136,7 @@ Function Edit-WDACConfig { try { if ($AllowNewApps) { - [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 - [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 - $PolicyXMLFilesArray = [System.Collections.Concurrent.ConcurrentDictionary[System.String, System.IO.FileInfo]]::new() - - # The total number of the main steps for the progress bar to render - [System.UInt16]$TotalSteps = 8 - [System.UInt16]$CurrentStep = 0 - - $CurrentStep++ - Write-Progress -Id 10 -Activity 'Creating the Audit mode policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - - [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) - - [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 - - [WDACConfig.Logger]::Write('Creating Audit Mode CIP') - [System.IO.FileInfo]$AuditModeCIPPath = Join-Path -Path $StagingArea -ChildPath 'AuditMode.cip' - [WDACConfig.CiRuleOptions]::Set($PolicyPath, $null, @([WDACConfig.CiRuleOptions+PolicyRuleOptions]::EnabledAuditMode), $null, $null, $null, $null, $null, $null, $null, $null) - $null = ConvertFrom-CIPolicy -XmlFilePath $PolicyPath -BinaryFilePath $AuditModeCIPPath - - [WDACConfig.Logger]::Write('Creating Enforced Mode CIP') - [System.IO.FileInfo]$EnforcedModeCIPPath = Join-Path -Path $StagingArea -ChildPath 'EnforcedMode.cip' - [WDACConfig.CiRuleOptions]::Set($PolicyPath, $null, $null, @([WDACConfig.CiRuleOptions+PolicyRuleOptions]::EnabledAuditMode), $null, $null, $null, $null, $null, $null, $null) - $null = ConvertFrom-CIPolicy -XmlFilePath $PolicyPath -BinaryFilePath $EnforcedModeCIPPath - - #Region Snap-Back-Guarantee - [WDACConfig.Logger]::Write('Creating Enforced Mode SnapBack guarantee') - [WDACConfig.SnapBackGuarantee]::Create($EnforcedModeCIPPath.FullName) - - $CurrentStep++ - Write-Progress -Id 10 -Activity 'Deploying the Audit mode policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - - [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") - [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 - Try { - #Region User-Interaction - $CurrentStep++ - Write-Progress -Id 10 -Activity 'Waiting for user input' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - - Write-ColorfulTextWDACConfig -Color Pink -InputText 'Audit mode deployed, start installing/running your programs now' - Write-ColorfulTextWDACConfig -Color HotPink -InputText 'When you are finished, Press Enter, you will have the option to select directories to scan' - Pause - Write-ColorfulTextWDACConfig -Color Lavender -InputText 'Select directories to scan' - [System.IO.DirectoryInfo[]]$ProgramsPaths = [WDACConfig.DirectorySelector]::SelectDirectories() - #Endregion User-Interaction - } - catch { - Throw $_ - } - finally { - $CurrentStep++ - 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') - [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") - [WDACConfig.Logger]::Write("PolicyGUID = $PolicyID") - - [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 - } - - $CurrentStep++ - Write-Progress -Id 10 -Activity 'Processing Audit event logs and directories' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - - # Path for the final Supplemental policy XML - [System.IO.FileInfo]$SuppPolicyPath = Join-Path -Path $StagingArea -ChildPath "Supplemental Policy - $SuppPolicyName.xml" - # Path for the kernel protected files policy XML - [System.IO.FileInfo]$KernelProtectedPolicyPath = Join-Path -Path $StagingArea -ChildPath "Kernel Protected Files - $SuppPolicyName.xml" - # Path for the temp policy file generated from the audits logs captured during the audit phase - [System.IO.FileInfo]$WDACPolicyPathTEMP = Join-Path -Path $StagingArea -ChildPath "TEMP policy for Audits logs - $SuppPolicyName.xml" - - # Flag indicating user has selected directory path(s) - [System.Boolean]$HasFolderPaths = $false - # Flag indicating audit event logs have been detected during the audit phase - [System.Boolean]$HasAuditLogs = $false - # Flag indicating files have been found in audit event logs during the audit phase that are not inside of any of the user-selected directory paths - [System.Boolean]$HasExtraFiles = $false - # Flag indicating whether the user has selected any logs from the audit logs GUI displayed to them - [System.Boolean]$HasSelectedLogs = $false - - if ($ProgramsPaths) { - [WDACConfig.Logger]::Write('Here are the paths you selected:') - if ($Verbose) { - foreach ($Path in $ProgramsPaths) { - $Path.FullName - } - } - - $HasFolderPaths = $true - - # Start Async job for detecting ECC-Signed files among the user-selected directories - [System.Management.Automation.Job2]$ECCSignedDirectoriesJob = Start-ThreadJob -ScriptBlock { - Param ($PolicyXMLFilesArray) - - $global:ProgressPreference = 'SilentlyContinue' - $global:ErrorActionPreference = 'Stop' - - 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 - - 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 - - [System.Management.Automation.Job2]$DirectoryScanJob = Start-ThreadJob -InitializationScript { - $global:ProgressPreference = 'SilentlyContinue' - # pre-load the ConfigCI module - if ([System.IO.Directory]::Exists('C:\Program Files\Windows Defender\Offline')) { - [System.String]$RandomGUID = [System.Guid]::NewGuid().ToString() - New-CIPolicy -UserPEs -ScanPath 'C:\Program Files\Windows Defender\Offline' -Level hash -FilePath ".\$RandomGUID.xml" -NoShadowCopy -PathToCatroot 'C:\Program Files\Windows Defender\Offline' -WarningAction SilentlyContinue - Remove-Item -LiteralPath ".\$RandomGUID.xml" -Force - } - } -ScriptBlock { - Param ($ProgramsPaths, $StagingArea, $PolicyXMLFilesArray) - - # [WDACConfig.Logger]::Write('Scanning each of the folder paths that user selected') - - for ($i = 0; $i -lt $ProgramsPaths.Count; $i++) { - - # Creating a hash table to dynamically add parameters based on user input and pass them to New-Cipolicy cmdlet - [System.Collections.Hashtable]$UserInputProgramFoldersPolicyMakerHashTable = @{ - FilePath = "$StagingArea\ProgramDir_ScanResults$($i).xml" - ScanPath = $ProgramsPaths[$i] - Level = $using:Level - Fallback = $using:Fallbacks - MultiplePolicyFormat = $true - UserWriteablePaths = $true - AllowFileNameFallbacks = $true - } - # Assess user input parameters and add the required parameters to the hash table - if ($using:SpecificFileNameLevel) { $UserInputProgramFoldersPolicyMakerHashTable['SpecificFileNameLevel'] = $using:SpecificFileNameLevel } - if ($using:NoScript) { $UserInputProgramFoldersPolicyMakerHashTable['NoScript'] = $true } - if (!$using:NoUserPEs) { $UserInputProgramFoldersPolicyMakerHashTable['UserPEs'] = $true } - - # [WDACConfig.Logger]::Write("Currently scanning: $($ProgramsPaths[$i])") - New-CIPolicy @UserInputProgramFoldersPolicyMakerHashTable - - [System.Void]$PolicyXMLFilesArray.TryAdd("$($ProgramsPaths[$i]) Scan Results", "$StagingArea\ProgramDir_ScanResults$($i).xml") - } - - 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 - } - else { - [WDACConfig.Logger]::Write('No directory path was selected.') - } - - [System.Collections.Hashtable[]]$AuditEventLogsProcessingResults = Receive-CodeIntegrityLogs -Date $Date -Type 'Audit' - - if (($null -ne $AuditEventLogsProcessingResults) -and ($AuditEventLogsProcessingResults.count -ne 0)) { - $HasAuditLogs = $true - } - else { - [WDACConfig.Logger]::Write('No audit log events were generated during the audit period.') - } - - if ($HasAuditLogs -and $HasFolderPaths) { - $OutsideFiles = [System.Collections.Generic.HashSet[System.String]]@([WDACConfig.FileDirectoryPathComparer]::TestFilePath($ProgramsPaths, $AuditEventLogsProcessingResults.'File Name')) - } - - if (($null -ne $OutsideFiles) -and ($OutsideFiles.count -ne 0)) { - [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 - } - - # If user selected directory paths and there were files outside of those paths in the audit logs - if ($HasExtraFiles) { - - # Get only the log of the files that were found in event viewer logs but are not in any user selected directories - [PSCustomObject[]]$LogsToShow = foreach ($Item in $AuditEventLogsProcessingResults) { - if ($OutsideFiles.Contains($Item.'File Name')) { - $Item - } - } - - [PSCustomObject[]]$LogsToShow = Select-LogProperties -Logs $LogsToShow - Set-LogPropertiesVisibility -LogType Evtx/Local -EventsToDisplay $LogsToShow - - Write-ColorfulTextWDACConfig -Color Pink -InputText 'Displaying files detected outside of any directories you selected' - - [PSCustomObject[]]$SelectedLogs = $LogsToShow | Out-GridView -OutputMode Multiple -Title "Displaying $($LogsToShow.count) Audit Code Integrity and AppLocker Logs" - } - # If user did not select any directory paths but there were files found during the audit phase in the audit event logs - elseif (!$HasFolderPaths -and $HasAuditLogs) { - [PSCustomObject[]]$LogsToShow = Select-LogProperties -Logs $AuditEventLogsProcessingResults - Set-LogPropertiesVisibility -LogType Evtx/Local -EventsToDisplay $LogsToShow - - Write-ColorfulTextWDACConfig -Color Pink -InputText 'Displaying files detected outside of any directories you selected' - - [PSCustomObject[]]$SelectedLogs = $LogsToShow | Out-GridView -OutputMode Multiple -Title "Displaying $($LogsToShow.count) Audit Code Integrity Logs" - } - - # if user selected any logs - if (($null -ne $SelectedLogs) -and ($SelectedLogs.count -gt 0)) { - - $HasSelectedLogs = $true - - # Start Async job for detecting ECC-Signed files among the user-selected audit logs - [System.Management.Automation.Job2]$ECCSignedAuditLogsJob = Start-ThreadJob -ScriptBlock { - Param ($PolicyXMLFilesArray) - - $global:ProgressPreference = 'SilentlyContinue' - $global:ErrorActionPreference = 'Stop' - - 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 - - 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 - - [PSCustomObject[]]$KernelProtectedFileLogs = Test-KernelProtectedFiles -Logs $SelectedLogs - - if ($null -ne $KernelProtectedFileLogs) { - - [WDACConfig.Logger]::Write("Kernel protected files count: $($KernelProtectedFileLogs.count)") - - [WDACConfig.Logger]::Write('Copying the template policy to the staging area') - Copy-Item -LiteralPath 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowAll.xml' -Destination $KernelProtectedPolicyPath -Force - - [WDACConfig.Logger]::Write('Emptying the policy file in preparation for the new data insertion') - [WDACConfig.ClearCiPolicySemantic]::Clear($KernelProtectedPolicyPath) - - # Find the kernel protected files that have PFN property - $KernelProtectedFileLogsWithPFN = New-Object -TypeName 'System.Collections.Generic.List[PSCustomObject]' - $KernelProtectedFileLogsWithPFN = foreach ($Item in $KernelProtectedFileLogs) { - if ($Item.PackageFamilyName) { - $Item - } - } - - [WDACConfig.NewPFNLevelRules]::Create($KernelProtectedPolicyPath, $KernelProtectedFileLogsWithPFN.PackageFamilyName) - - # Add the Kernel protected files policy to the list of policies to merge - [System.Void]$PolicyXMLFilesArray.TryAdd('Kernel Protected files policy', $KernelProtectedPolicyPath) - - [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) { - if (!$KernelProtectedFileLogsWithPFN.Contains($Item)) { - $Item - } - } - } - - [WDACConfig.Logger]::Write('Copying the template policy to the staging area') - Copy-Item -LiteralPath 'C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowAll.xml' -Destination $WDACPolicyPathTEMP -Force - - [WDACConfig.Logger]::Write('Emptying the policy file in preparation for the new data insertion') - [WDACConfig.ClearCiPolicySemantic]::Clear($WDACPolicyPathTEMP) - - [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) - - [WDACConfig.XMLOps]::Initiate($DataToUseForBuilding, $WDACPolicyPathTEMP) - - # Add the policy XML file to the array that holds policy XML files - [System.Void]$PolicyXMLFilesArray.TryAdd('Temp WDAC Policy', $WDACPolicyPathTEMP) - } - - #Region Async-Jobs-Management - - if ($HasFolderPaths) { - $null = Wait-Job -Job $DirectoryScanJob - # Redirecting Verbose and Debug output streams because they are automatically displayed already on the console using StreamingHost parameter - Receive-Job -Job $DirectoryScanJob 4>$null 5>$null - Remove-Job -Job $DirectoryScanJob -Force - - $null = Wait-Job -Job $ECCSignedDirectoriesJob - # Redirecting Verbose and Debug output streams because they are automatically displayed already on the console using StreamingHost parameter - Receive-Job -Job $ECCSignedDirectoriesJob 4>$null 5>$null - Remove-Job -Job $ECCSignedDirectoriesJob -Force - } - - if ($HasSelectedLogs) { - $null = Wait-Job -Job $ECCSignedAuditLogsJob - # Redirecting Verbose and Debug output streams because they are automatically displayed already on the console using StreamingHost parameter - Receive-Job -Job $ECCSignedAuditLogsJob 4>$null 5>$null - Remove-Job -Job $ECCSignedAuditLogsJob -Force - } - - #Endregion Async-Jobs-Management - - # If none of the previous actions resulted in any policy XML files, exit the function - if ($PolicyXMLFilesArray.Values.Count -eq 0) { - [WDACConfig.Logger]::Write('No directory path or audit logs were selected to create a supplemental policy. Exiting...') - Return - } - - [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++ - Write-Progress -Id 10 -Activity 'Merging the policies' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - - $null = Merge-CIPolicy -PolicyPaths $PolicyXMLFilesArray.Values -OutputFilePath $SuppPolicyPath - - #Region Supplemental-policy-processing-and-deployment - - $CurrentStep++ - Write-Progress -Id 10 -Activity 'Creating supplemental policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) - - [WDACConfig.Logger]::Write('Supplemental policy processing and deployment') - - [WDACConfig.Logger]::Write('Converting the policy to a Supplemental policy type and resetting its ID') - [System.String]$SuppPolicyID = [WDACConfig.SetCiPolicyInfo]::Set($SuppPolicyPath, $true, "$SuppPolicyName - $(Get-Date -Format 'MM-dd-yyyy')", $null, $PolicyPath) - - [WDACConfig.CiRuleOptions]::Set($SuppPolicyPath, [WDACConfig.CiRuleOptions+PolicyTemplate]::Supplemental, $null, $null, $null, $null, $null, $null, $null, $null, $null) - - [WDACConfig.SetCiPolicyInfo]::Set($SuppPolicyPath, ([version]'1.0.0.0')) - - # Define the path for the final Supplemental policy CIP - [System.IO.FileInfo]$SupplementalCIPPath = Join-Path -Path $StagingArea -ChildPath "$SuppPolicyID.cip" - - #Region Boosted Security - Sandboxing - # The AppIDs association must happen at the end right before converting the policy to binary because merge-cipolicy and other ConfigCI cmdlets remove the Macros - if ($BoostedSecurity) { - [System.Collections.Hashtable]$InputObject = @{} - $InputObject['SelectedDirectoryPaths'] = $ProgramsPaths - $InputObject['SelectedAuditLogs'] = $AuditEventLogsProcessingResults - New-Macros -XmlFilePath $SuppPolicyPath -InputObject $InputObject - } - #Endregion Boosted Security - Sandboxing - - [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) - - [WDACConfig.CiToolHelper]::UpdatePolicy($SupplementalCIPPath) - - #Endregion Supplemental-policy-processing-and-deployment - - # Copy the Supplemental policy to the user's config directory since Staging Area is a temporary location - Copy-Item -Path $SuppPolicyPath -Destination ([WDACConfig.GlobalVars]::UserConfigDir) -Force - - Write-FinalOutput -Paths $SuppPolicyPath + Write-Host -ForegroundColor Green -Object "This parameter's job has been completely added to the new AppControl Manager app. It offers a complete graphical user interface (GUI) for easy usage. Please refer to this GitHub page to see how to install and use it:`nhttps://github.com/HotCakeX/Harden-Windows-Security/wiki/AppControl-Manager" } if ($MergeSupplementalPolicies) { diff --git a/WDACConfig/WDACConfig Module Files/Core/Invoke-WDACSimulation.psm1 b/WDACConfig/WDACConfig Module Files/Core/Invoke-WDACSimulation.psm1 index f2a235de2..58c18a259 100644 --- a/WDACConfig/WDACConfig Module Files/Core/Invoke-WDACSimulation.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/Invoke-WDACSimulation.psm1 @@ -1,113 +1,16 @@ Function Invoke-WDACSimulation { [CmdletBinding()] Param( - [ArgumentCompleter([WDACConfig.ArgCompleter.XmlFilePathsPicker])] - [Alias('X')][Parameter(Mandatory = $true)][string]$XmlFilePath, + [Parameter(Mandatory = $true)][string]$XmlFilePath, [ArgumentCompleter([WDACConfig.ArgCompleter.FolderPicker])] - [Alias('D')][Parameter(Mandatory = $false)][string[]]$FolderPath, + [Parameter(Mandatory = $false)][string[]]$FolderPath, [ArgumentCompleter([WDACConfig.ArgCompleter.MultipleAnyFilePathsPicker])] - [Alias('F')][Parameter(Mandatory = $false)][string[]]$FilePath, - [Alias('C')][Parameter(Mandatory = $false)][switch]$CSVOutput, - [Alias('N')][Parameter(Mandatory = $false)][switch]$NoCatalogScanning, + [Parameter(Mandatory = $false)][string[]]$FilePath, + [Parameter(Mandatory = $false)][switch]$CSVOutput, + [Parameter(Mandatory = $false)][switch]$NoCatalogScanning, [ArgumentCompleter([WDACConfig.ArgCompleter.FolderPicker])] - [Alias('Cat')][Parameter(Mandatory = $false)][string[]]$CatRootPath, - [Alias('CPU')][Parameter(Mandatory = $false)][System.UInt32]$ThreadsCount = 2 + [Parameter(Mandatory = $false)][string[]]$CatRootPath, + [Parameter(Mandatory = $false)][System.UInt32]$ThreadsCount ) - [WDACConfig.LoggerInitializer]::Initialize($VerbosePreference, $DebugPreference, $Host) - Update-WDACConfigPSModule -InvocationStatement $MyInvocation.Statement - $FinalSimulationResults = [WDACConfig.InvokeWDACSimulation]::Invoke($FilePath, $FolderPath, $XmlFilePath, $NoCatalogScanning, $CSVOutput, $CatRootPath, $ThreadsCount) - - # Change the color of the Table header to SkyBlue - $PSStyle.Formatting.TableHeader = "$($PSStyle.Foreground.FromRGB(135,206,235))" - - if ($FinalSimulationResults.Count -gt 10000) { - # If the result is too big and the user forgot to use CSV Output then output everything to CSV instead of trying to display on the console - if (!$CSVOutput) { - $FinalSimulationResults.Values | Sort-Object -Property IsAuthorized -Descending | Export-Csv -LiteralPath (Join-Path -Path ([WDACConfig.GlobalVars]::UserConfigDir) -ChildPath "WDAC Simulation Output $(Get-Date -Format "MM-dd-yyyy 'at' HH-mm-ss").csv") -Force - } - Return "The number of files is too many to display on the console. Saving the results in a CSV file in '$((Join-Path -Path ([WDACConfig.GlobalVars]::UserConfigDir) -ChildPath "WDAC Simulation Output $(Get-Date -Format "MM-dd-yyyy 'at' HH-mm-ss").csv"))'" - } - - # Return the final main output array as a table - Return $FinalSimulationResults.Values | Select-Object -Property 'Path', - @{ - Label = 'Source' - Expression = - { switch ($_.Source) { - { $_ -eq 'Signer' } { $Color = "$($PSStyle.Foreground.FromRGB(152,255,152))" } - { $_ -eq 'Hash' } { $Color = "$($PSStyle.Foreground.FromRGB(255,255,49))" } - { $_ -eq 'Unsigned' } { $Color = "$($PSStyle.Foreground.FromRGB(255,20,147))" } - } - "$Color$($_.Source)$($PSStyle.Reset)" - } - }, - @{ - Label = 'IsAuthorized' - Expression = - { - switch ($_.IsAuthorized) { - { $_ -eq $true } { $Color = "$($PSStyle.Foreground.FromRGB(255,0,255))"; break } - { $_ -eq $false } { $Color = "$($PSStyle.Foreground.FromRGB(255,165,0))$($PSStyle.Blink)"; break } - } - "$Color$($_.IsAuthorized)$($PSStyle.Reset)" - } - }, - @{ - Label = 'MatchCriteria' - Expression = { - # If the MatchCriteria starts with 'UnknownError', truncate it to 50 characters. The full string will be displayed in the CSV output file. If it does not then just display it as it is - $_.MatchCriteria -match 'UnknownError' ? $_.MatchCriteria.Substring(0, 50) + '...' : "$($_.MatchCriteria)" - } - }, - @{ - Label = 'SpecificFileName' - Expression = { - $_.SpecificFileNameLevelMatchCriteria - } - } | Sort-Object -Property IsAuthorized | Format-Table - - <# -.SYNOPSIS - Simulates the deployment of the WDAC policy. It can produce a very detailed CSV file that contains the output of the simulation. - On the console, it can display a table that shows the file path, source, MatchCriteria, and whether the file is allowed or not. - The console results are color coded for easier reading. - - Properties explanation: - - FilePath: The name of the file gathered from its full path. (the actual long path of the file is not displayed in the console output, only in the CSV file) - Source: The source of the file's MatchCriteria, e.g., 'Signer' (For signed files only), 'Hash' (For signed and unsigned files), 'Unsigned' (For unsigned files only) - MatchCriteria: The reason the file is allowed or not. For files authorized by FilePublisher level, it will show the specific file name level that the file is authorized by. (https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/select-types-of-rules-to-create#table-3--specificfilenamelevel-options) - IsAuthorized: A boolean value that indicates whether the file is allowed or not. -.LINK - https://github.com/HotCakeX/Harden-Windows-Security/wiki/Invoke-WDACSimulation -.DESCRIPTION - Simulates the deployment of an App Control policy by analyzing a folder (recursively) or files and checking which of the detected files are allowed by a user selected policy xml file -.PARAMETER FolderPath - Browse for a folders to include in the simulation -.PARAMETER FilePath - Browse for files to include in the simulation -.PARAMETER XmlFilePath - Browse for the App Control policy XML file -.PARAMETER NoCatalogScanning - Bypass the scanning of the security catalogs on the system -.PARAMETER CatRootPath - Provide path(s) to directories where security catalog .cat files are located. If not provided, the default path is C:\Windows\System32\CatRoot -.PARAMETER CSVOutput - Exports the output to a CSV file. The CSV output is saved in the WDACConfig folder: C:\Program Files\WDACConfig -.PARAMETER ThreadsCount - The number of the concurrent/parallel tasks to use when performing App Control Simulation. - By default it uses 2 parallel tasks. Minimum allowed value is 1. -.INPUTS - System.IO.FileInfo[] - System.IO.DirectoryInfo[] - System.Management.Automation.SwitchParameter -.OUTPUTS - System.Collections.Generic.List[WDACConfig.SimulationOutput] -.EXAMPLE - Invoke-WDACSimulation -FolderPath 'C:\Windows\System32' -XmlFilePath 'C:\Users\HotCakeX\Desktop\Policy.xml' - This example will simulate the deployment of the policy.xml file against the C:\Windows\System32 folder -.NOTES - WDAC templates such as 'Default Windows' and 'Allow Microsoft' don't have CertPublisher element in their Signers because they don't target a leaf certificate, - thus they weren't created using FilePublisher level, they were created using Publisher or Root certificate levels to allow Microsoft's wellknown certificates. -#> + Write-Host -ForegroundColor Green -Object "This function's job has been completely added to the new AppControl Manager app. It offers a complete graphical user interface (GUI) for easy usage. Please refer to this GitHub page to see how to install and use it:`nhttps://github.com/HotCakeX/Harden-Windows-Security/wiki/AppControl-Manager" } \ No newline at end of file diff --git a/WDACConfig/WDACConfig Module Files/Core/New-KernelModeWDACConfig.psm1 b/WDACConfig/WDACConfig Module Files/Core/New-KernelModeWDACConfig.psm1 index cf91a116d..57d2c9f21 100644 --- a/WDACConfig/WDACConfig Module Files/Core/New-KernelModeWDACConfig.psm1 +++ b/WDACConfig/WDACConfig Module Files/Core/New-KernelModeWDACConfig.psm1 @@ -363,11 +363,15 @@ Function New-KernelModeWDACConfig { # Deploy the policy if Deploy parameter is used if ($Deploy) { + + <# + [WDACConfig.Logger]::Write('Making sure the current Windows build can work with the NoFlightRoots Strict WDAC Policy') if (!([WDACConfig.InvokeWDACSimulation]::Invoke('C:\Windows\System32\ntoskrnl.exe', $FinalEnforcedPolicyPath, $true))) { Throw 'The current Windows build cannot work with the NoFlightRoots Strict Kernel-mode Policy, please change the base to Default instead.' } + #> $CurrentStep++ Write-Progress -Id 28 -Activity 'Deploying the final policy' -Status "Step $CurrentStep/$TotalSteps" -PercentComplete ($CurrentStep / $TotalSteps * 100) diff --git a/WDACConfig/WDACConfig Module Files/Help/ConvertTo-WDACPolicy.md b/WDACConfig/WDACConfig Module Files/Help/ConvertTo-WDACPolicy.md deleted file mode 100644 index af477b32c..000000000 --- a/WDACConfig/WDACConfig Module Files/Help/ConvertTo-WDACPolicy.md +++ /dev/null @@ -1,424 +0,0 @@ ---- -external help file: ConvertTo-WDACPolicy.xml -Module Name: WDACConfig -online version: https://github.com/HotCakeX/Harden-Windows-Security/wiki/ConvertTo-WDACPolicy -schema: 2.0.0 ---- - -# ConvertTo-WDACPolicy - -## SYNOPSIS -This is a multi-purpose cmdlet that offers a wide range of functionalities that can either be used separately or mixed together for very detailed and specific tasks. - -It currently supports Code Integrity and AppLocker logs from the following sources: Local Event logs, Evtx log files and Microsoft Defender for Endpoint Advanced Hunting results. - -The cmdlet displays the logs in a GUI and allows the user to select the logs to be processed further. - -The logs can be filtered based on many criteria using the available parameters. - -The output of this cmdlet is a Supplemental Application Control (WDAC) policy. -Based on the input parameters, it can be associated with a base policy or merged with an existing Base or Supplemental policy. - -## SYNTAX - -### In-Place Upgrade -``` -ConvertTo-WDACPolicy - [-PolicyToAddLogsTo ] - [-Source ] - [-SuppPolicyName] - [-Level ] - [-MDEAHLogs ] - [-EVTXLogs ] - [-FilterByPolicyNames ] - [-TimeSpan ] - [-TimeSpanAgo ] - [-KernelModeOnly] - [-LogType ] - [-Deploy] - [-ExtremeVisibility] - [] -``` - -### Base-Policy File Association -``` -ConvertTo-WDACPolicy - [-BasePolicyFile ] - [-Source ] - [-SuppPolicyName] - [-Level ] - [-MDEAHLogs ] - [-EVTXLogs ] - [-FilterByPolicyNames ] - [-TimeSpan ] - [-TimeSpanAgo ] - [-KernelModeOnly] - [-LogType ] - [-Deploy] - [-ExtremeVisibility] - [] -``` - -### Base-Policy GUID Association -``` -ConvertTo-WDACPolicy - [-BasePolicyGUID ] - [-Source ] - [-SuppPolicyName] - [-Level ] - [-MDEAHLogs ] - [-EVTXLogs ] - [-FilterByPolicyNames ] - [-TimeSpan ] - [-TimeSpanAgo ] - [-KernelModeOnly] - [-LogType ] - [-Deploy] - [-ExtremeVisibility] - [] -``` - -## DESCRIPTION -The cmdlet can be used for local and remote systems. You can utilize this cmdlet to create App Control for Business policies from MDE Advanced Hunting and then deploy them using Microsoft Intune to your endpoints. - -You can utilize this cmdlet to use the evtx log files you aggregated from your endpoints and create a WDAC policy from them. - -This offers scalability and flexibility in managing your security policies. - -## EXAMPLES - -### EXAMPLE 1 -``` -ConvertTo-WDACPolicy -PolicyToAddLogsTo "C:\Users\Admin\AllowMicrosoftPlusBlockRules.xml" -Verbose -``` - -This example will display the Code Integrity and AppLocker logs in a GUI and allow the user to select the logs to add to the specified policy file. - -### EXAMPLE 2 -``` -ConvertTo-WDACPolicy -Verbose -BasePolicyGUID '{ACE9058C-8A24-47F4-86F0-A33FAB5073E3}' -``` - -This example will display the Code Integrity and AppLocker logs in a GUI and allow the user to select the logs to create a new supplemental policy and associate it with the specified base policy GUID. - -### EXAMPLE 3 -``` -ConvertTo-WDACPolicy -BasePolicyFile "C:\Users\Admin\AllowMicrosoftPlusBlockRules.xml" -``` - -This example will display the Code Integrity and AppLocker logs in a GUI and allow the user to select the logs to create a new supplemental policy and associate it with the specified base policy file. - -### EXAMPLE 4 -``` -ConvertTo-WDACPolicy -``` - -This example will display the Code Integrity and AppLocker logs in a GUI and takes no further action. - -### EXAMPLE 5 -``` -ConvertTo-WDACPolicy -FilterByPolicyNames 'VerifiedAndReputableDesktopFlightSupplemental','WindowsE_Lockdown_Flight_Policy_Supplemental' -Verbose -``` - -This example will filter the Code Integrity and AppLocker logs by the specified policy names and display them in a GUI. It will also display verbose messages on the console. - -### EXAMPLE 6 -``` -ConvertTo-WDACPolicy -FilterByPolicyNames 'Microsoft Windows Driver Policy - Enforced' -TimeSpan Minutes -TimeSpanAgo 10 -``` - -This example will filter the local Code Integrity and AppLocker logs by the specified policy name and the number of minutes ago from the current time and display them in a GUI. -So, it will display the logs that are 10 minutes old and are associated with the specified policy name. - -### EXAMPLE 7 -``` -ConvertTo-WDACPolicy -BasePolicyFile "C:\Program Files\WDACConfig\DefaultWindowsPlusBlockRules.xml" -Source MDEAdvancedHunting -MDEAHLogs "C:\Users\Admin\Downloads\New query.csv" -Deploy -TimeSpan Days -TimeSpanAgo 2 -``` - -This example will create a new supplemental policy from the selected MDE Advanced Hunting logs and associate it with the specified base policy file and it will deploy it on the system. -The displayed logs will be from the last 2 days. You will be able to select the logs to create the policy from in the GUI. - -### EXAMPLE 8 -``` -ConvertTo-WDACPolicy -BasePolicyGUID '{89CD611D-5557-4833-B73D-716B979AEE3D}' -Source EVTXFiles -EVTXLogs "C:\Users\HotCakeX\App Locker logs.evtx","C:\Users\HotCakeX\Code Integrity LOGS.evtx" -``` - -This example will create a new supplemental policy from the selected EVTX files and associate it with the specified base policy GUID. - -## PARAMETERS - -### -PolicyToAddLogsTo -The policy to add the selected logs to, it can either be a base or supplemental policy. - -```yaml -Type: FileInfo -Parameter Sets: In-Place Upgrade -Aliases: AddLogs - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -BasePolicyFile -The base policy file to associate the supplemental policy with - -```yaml -Type: FileInfo -Parameter Sets: Base-Policy File Association -Aliases: BaseFile - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -BasePolicyGUID -The GUID of the base policy to associate the supplemental policy with - -```yaml -Type: Guid -Parameter Sets: Base-Policy GUID Association -Aliases: BaseGUID - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Source -The source of the logs: Local Event logs (LocalEventLogs), Microsoft Defender for Endpoint Advanced Hunting results (MDEAdvancedHunting) or EVTX files (EVTXFiles). -Supports validate set. - -```yaml -Type: String -Parameter Sets: (All) -Aliases: Src - -Required: False -Position: Named -Default value: LocalEventLogs -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -SuppPolicyName -The name of the supplemental policy to create. If not specified, the cmdlet will generate a proper name based on the selected source and time. - -```yaml -Type: String -Parameter Sets: (All) -Aliases: Name - -Required: False -Position: Named -Default value: -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Level -The level determining rule generation can be one of the following: Auto, FilePublisher, Publisher, or Hash. - -The fallback level is always Hash. - -By default, which is the same as not using this parameter, the most secure levels are prioritized. If a log contains the requisite details for the FilePublisher level, it will be utilized. If not, the Publisher level will be attempted. Should this also fail, the Hash level will be employed. - -```yaml -Type: String -Parameter Sets: (All) -Aliases: Lvl - -Required: False -Position: Named -Default value: Auto -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -MDEAHLogs -The path(s) to use MDE AH CSV files. This is a dynamic parameter and will only be available if the Source parameter is set to MDEAdvancedHunting. - -```yaml -Type: FileInfo[] -Parameter Sets: (All) -Aliases: MDELogs - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -EVTXLogs -The path(s) of EVTX files to use. -This is a dynamic parameter and will only be available if the Source parameter is set to EVTXFiles. - -```yaml -Type: FileInfo[] -Parameter Sets: (All) -Aliases: Evtx - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -FilterByPolicyNames -The names of the policies to filter the logs by. -Supports auto-completion, press TAB key to view the list of the deployed base policy names to choose from. -It will not display the policies that are already selected on the command line. -You can manually enter the name of the policies that are no longer available on the system or are from remote systems in case of MDE Advanced Hunting logs. - -```yaml -Type: String[] -Parameter Sets: (All) -Aliases: FilterNames - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -TimeSpan -The unit of time to use when filtering the logs by the time. -The allowed values are: Minutes, Hours, Days - -```yaml -Type: String -Parameter Sets: (All) -Aliases: Duration - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -TimeSpanAgo -The number of the selected time unit to go back in time from the current time. - -```yaml -Type: String -Parameter Sets: (All) -Aliases: Past - -Required: True -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -KernelModeOnly -If used, will filter the logs by including only the Kernel-Mode logs. You can use this parameter to easily create Supplemental policies for Strict Kernel-Mode WDAC policy. - -More info available here: https://github.com/HotCakeX/Harden-Windows-Security/wiki/WDAC-policy-for-BYOVD-Kernel-mode-only-protection - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: KMode - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -LogType -The type of logs to display: Audit or Blocked. If not specified, All types will be displayed. - -```yaml -Type: String -Parameter Sets: (All) -Aliases: LogKind - -Required: False -Position: Named -Default value: All -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Deploy -If used, will deploy the policy on the system - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: Up - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ExtremeVisibility -If used, will display all the properties of the logs without any filtering. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: XVis - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -Contains the preference for the progress action - -```yaml -Type: ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### System.IO.FileInfo -### System.Guid -### System.String -### System.String[] -### System.UInt64 -### System.Management.Automation.SwitchParameter - -## OUTPUTS - -### System.String - -## NOTES - -## RELATED LINKS - -[Cmdlet Guide](https://github.com/HotCakeX/Harden-Windows-Security/wiki/ConvertTo-WDACPolicy) - -[YouTube video guide for MDE Advanced Hunting usage](https://www.youtube.com/watch?v=oyz0jFzOOGA) diff --git a/WDACConfig/WDACConfig Module Files/Help/ConvertTo-WDACPolicy.xml b/WDACConfig/WDACConfig Module Files/Help/ConvertTo-WDACPolicy.xml deleted file mode 100644 index 740aca79e..000000000 --- a/WDACConfig/WDACConfig Module Files/Help/ConvertTo-WDACPolicy.xml +++ /dev/null @@ -1,867 +0,0 @@ - - - - - ConvertTo-WDACPolicy - ConvertTo - WDACPolicy - - This is a multi-purpose cmdlet that offers a wide range of functionalities that can either be used separately or mixed together for very detailed and specific tasks. - It currently supports Code Integrity and AppLocker logs from the following sources: Local Event logs, Evtx log files and Microsoft Defender for Endpoint Advanced Hunting results. - The cmdlet displays the logs in a GUI and allows the user to select the logs to be processed further. - The logs can be filtered based on many criteria using the available parameters. - The output of this cmdlet is a Supplemental Application Control (WDAC) policy. Based on the input parameters, it can be associated with a base policy or merged with an existing Base or Supplemental policy. - - - - The cmdlet can be used for local and remote systems. You can utilize this cmdlet to create App Control for Business policies from MDE Advanced Hunting and then deploy them using Microsoft Intune to your endpoints. - You can utilize this cmdlet to use the evtx log files you aggregated from your endpoints and create a WDAC policy from them. - This offers scalability and flexibility in managing your security policies. - - - - ConvertTo-WDACPolicy - - PolicyToAddLogsTo - - The policy to add the selected logs to, it can either be a base or supplemental policy. - - FileInfo - - FileInfo - - - None - - - Source - - The source of the logs: Local Event logs (LocalEventLogs), Microsoft Defender for Endpoint Advanced Hunting results (MDEAdvancedHunting) or EVTX files (EVTXFiles). Supports validate set. - - String - - String - - - LocalEventLogs - - - SuppPolicyName - - The name of the supplemental policy to create. If not specified, the cmdlet will generate a proper name based on the selected source and time. - - String - - String - - - <Depends on the selected source and time> - - - Level - - The level determining rule generation can be one of the following: Auto, FilePublisher, Publisher, or Hash. - The fallback level is always Hash. - By default, which is the same as not using this parameter, the most secure levels are prioritized. If a log contains the requisite details for the FilePublisher level, it will be utilized. If not, the Publisher level will be attempted. Should this also fail, the Hash level will be employed. - - String - - String - - - Auto - - - MDEAHLogs - - The path(s) to use MDE AH CSV files. This is a dynamic parameter and will only be available if the Source parameter is set to MDEAdvancedHunting. - - FileInfo[] - - FileInfo[] - - - None - - - EVTXLogs - - The path(s) of EVTX files to use. This is a dynamic parameter and will only be available if the Source parameter is set to EVTXFiles. - - FileInfo[] - - FileInfo[] - - - None - - - FilterByPolicyNames - - The names of the policies to filter the logs by. Supports auto-completion, press TAB key to view the list of the deployed base policy names to choose from. It will not display the policies that are already selected on the command line. You can manually enter the name of the policies that are no longer available on the system or are from remote systems in case of MDE Advanced Hunting logs. - - String[] - - String[] - - - None - - - TimeSpan - - The unit of time to use when filtering the logs by the time. The allowed values are: Minutes, Hours, Days - - String - - String - - - None - - - TimeSpanAgo - - The number of the selected time unit to go back in time from the current time. - - String - - String - - - None - - - KernelModeOnly - - If used, will filter the logs by including only the Kernel-Mode logs. You can use this parameter to easily create Supplemental policies for Strict Kernel-Mode WDAC policy. - More info available here: https://github.com/HotCakeX/Harden-Windows-Security/wiki/WDAC-policy-for-BYOVD-Kernel-mode-only-protection - - - SwitchParameter - - - False - - - LogType - - The type of logs to display: Audit or Blocked. If not specified, All types will be displayed. - - String - - String - - - All - - - Deploy - - If used, will deploy the policy on the system - - - SwitchParameter - - - False - - - ExtremeVisibility - - If used, will display all the properties of the logs without any filtering. - - - SwitchParameter - - - False - - - ProgressAction - - Contains the preference for the progress action - - ActionPreference - - ActionPreference - - - None - - - - ConvertTo-WDACPolicy - - BasePolicyFile - - The base policy file to associate the supplemental policy with - - FileInfo - - FileInfo - - - None - - - Source - - The source of the logs: Local Event logs (LocalEventLogs), Microsoft Defender for Endpoint Advanced Hunting results (MDEAdvancedHunting) or EVTX files (EVTXFiles). Supports validate set. - - String - - String - - - LocalEventLogs - - - SuppPolicyName - - The name of the supplemental policy to create. If not specified, the cmdlet will generate a proper name based on the selected source and time. - - String - - String - - - <Depends on the selected source and time> - - - Level - - The level determining rule generation can be one of the following: Auto, FilePublisher, Publisher, or Hash. - The fallback level is always Hash. - By default, which is the same as not using this parameter, the most secure levels are prioritized. If a log contains the requisite details for the FilePublisher level, it will be utilized. If not, the Publisher level will be attempted. Should this also fail, the Hash level will be employed. - - String - - String - - - Auto - - - MDEAHLogs - - The path(s) to use MDE AH CSV files. This is a dynamic parameter and will only be available if the Source parameter is set to MDEAdvancedHunting. - - FileInfo[] - - FileInfo[] - - - None - - - EVTXLogs - - The path(s) of EVTX files to use. This is a dynamic parameter and will only be available if the Source parameter is set to EVTXFiles. - - FileInfo[] - - FileInfo[] - - - None - - - FilterByPolicyNames - - The names of the policies to filter the logs by. Supports auto-completion, press TAB key to view the list of the deployed base policy names to choose from. It will not display the policies that are already selected on the command line. You can manually enter the name of the policies that are no longer available on the system or are from remote systems in case of MDE Advanced Hunting logs. - - String[] - - String[] - - - None - - - TimeSpan - - The unit of time to use when filtering the logs by the time. The allowed values are: Minutes, Hours, Days - - String - - String - - - None - - - TimeSpanAgo - - The number of the selected time unit to go back in time from the current time. - - String - - String - - - None - - - KernelModeOnly - - If used, will filter the logs by including only the Kernel-Mode logs. You can use this parameter to easily create Supplemental policies for Strict Kernel-Mode WDAC policy. - More info available here: https://github.com/HotCakeX/Harden-Windows-Security/wiki/WDAC-policy-for-BYOVD-Kernel-mode-only-protection - - - SwitchParameter - - - False - - - LogType - - The type of logs to display: Audit or Blocked. If not specified, All types will be displayed. - - String - - String - - - All - - - Deploy - - If used, will deploy the policy on the system - - - SwitchParameter - - - False - - - ExtremeVisibility - - If used, will display all the properties of the logs without any filtering. - - - SwitchParameter - - - False - - - ProgressAction - - Contains the preference for the progress action - - ActionPreference - - ActionPreference - - - None - - - - ConvertTo-WDACPolicy - - BasePolicyGUID - - The GUID of the base policy to associate the supplemental policy with - - Guid - - Guid - - - None - - - Source - - The source of the logs: Local Event logs (LocalEventLogs), Microsoft Defender for Endpoint Advanced Hunting results (MDEAdvancedHunting) or EVTX files (EVTXFiles). Supports validate set. - - String - - String - - - LocalEventLogs - - - SuppPolicyName - - The name of the supplemental policy to create. If not specified, the cmdlet will generate a proper name based on the selected source and time. - - String - - String - - - <Depends on the selected source and time> - - - Level - - The level determining rule generation can be one of the following: Auto, FilePublisher, Publisher, or Hash. - The fallback level is always Hash. - By default, which is the same as not using this parameter, the most secure levels are prioritized. If a log contains the requisite details for the FilePublisher level, it will be utilized. If not, the Publisher level will be attempted. Should this also fail, the Hash level will be employed. - - String - - String - - - Auto - - - MDEAHLogs - - The path(s) to use MDE AH CSV files. This is a dynamic parameter and will only be available if the Source parameter is set to MDEAdvancedHunting. - - FileInfo[] - - FileInfo[] - - - None - - - EVTXLogs - - The path(s) of EVTX files to use. This is a dynamic parameter and will only be available if the Source parameter is set to EVTXFiles. - - FileInfo[] - - FileInfo[] - - - None - - - FilterByPolicyNames - - The names of the policies to filter the logs by. Supports auto-completion, press TAB key to view the list of the deployed base policy names to choose from. It will not display the policies that are already selected on the command line. You can manually enter the name of the policies that are no longer available on the system or are from remote systems in case of MDE Advanced Hunting logs. - - String[] - - String[] - - - None - - - TimeSpan - - The unit of time to use when filtering the logs by the time. The allowed values are: Minutes, Hours, Days - - String - - String - - - None - - - TimeSpanAgo - - The number of the selected time unit to go back in time from the current time. - - String - - String - - - None - - - KernelModeOnly - - If used, will filter the logs by including only the Kernel-Mode logs. You can use this parameter to easily create Supplemental policies for Strict Kernel-Mode WDAC policy. - More info available here: https://github.com/HotCakeX/Harden-Windows-Security/wiki/WDAC-policy-for-BYOVD-Kernel-mode-only-protection - - - SwitchParameter - - - False - - - LogType - - The type of logs to display: Audit or Blocked. If not specified, All types will be displayed. - - String - - String - - - All - - - Deploy - - If used, will deploy the policy on the system - - - SwitchParameter - - - False - - - ExtremeVisibility - - If used, will display all the properties of the logs without any filtering. - - - SwitchParameter - - - False - - - ProgressAction - - Contains the preference for the progress action - - ActionPreference - - ActionPreference - - - None - - - - - - PolicyToAddLogsTo - - The policy to add the selected logs to, it can either be a base or supplemental policy. - - FileInfo - - FileInfo - - - None - - - BasePolicyFile - - The base policy file to associate the supplemental policy with - - FileInfo - - FileInfo - - - None - - - BasePolicyGUID - - The GUID of the base policy to associate the supplemental policy with - - Guid - - Guid - - - None - - - Source - - The source of the logs: Local Event logs (LocalEventLogs), Microsoft Defender for Endpoint Advanced Hunting results (MDEAdvancedHunting) or EVTX files (EVTXFiles). Supports validate set. - - String - - String - - - LocalEventLogs - - - SuppPolicyName - - The name of the supplemental policy to create. If not specified, the cmdlet will generate a proper name based on the selected source and time. - - String - - String - - - <Depends on the selected source and time> - - - Level - - The level determining rule generation can be one of the following: Auto, FilePublisher, Publisher, or Hash. - The fallback level is always Hash. - By default, which is the same as not using this parameter, the most secure levels are prioritized. If a log contains the requisite details for the FilePublisher level, it will be utilized. If not, the Publisher level will be attempted. Should this also fail, the Hash level will be employed. - - String - - String - - - Auto - - - MDEAHLogs - - The path(s) to use MDE AH CSV files. This is a dynamic parameter and will only be available if the Source parameter is set to MDEAdvancedHunting. - - FileInfo[] - - FileInfo[] - - - None - - - EVTXLogs - - The path(s) of EVTX files to use. This is a dynamic parameter and will only be available if the Source parameter is set to EVTXFiles. - - FileInfo[] - - FileInfo[] - - - None - - - FilterByPolicyNames - - The names of the policies to filter the logs by. Supports auto-completion, press TAB key to view the list of the deployed base policy names to choose from. It will not display the policies that are already selected on the command line. You can manually enter the name of the policies that are no longer available on the system or are from remote systems in case of MDE Advanced Hunting logs. - - String[] - - String[] - - - None - - - TimeSpan - - The unit of time to use when filtering the logs by the time. The allowed values are: Minutes, Hours, Days - - String - - String - - - None - - - TimeSpanAgo - - The number of the selected time unit to go back in time from the current time. - - String - - String - - - None - - - KernelModeOnly - - If used, will filter the logs by including only the Kernel-Mode logs. You can use this parameter to easily create Supplemental policies for Strict Kernel-Mode WDAC policy. - More info available here: https://github.com/HotCakeX/Harden-Windows-Security/wiki/WDAC-policy-for-BYOVD-Kernel-mode-only-protection - - SwitchParameter - - SwitchParameter - - - False - - - LogType - - The type of logs to display: Audit or Blocked. If not specified, All types will be displayed. - - String - - String - - - All - - - Deploy - - If used, will deploy the policy on the system - - SwitchParameter - - SwitchParameter - - - False - - - ExtremeVisibility - - If used, will display all the properties of the logs without any filtering. - - SwitchParameter - - SwitchParameter - - - False - - - ProgressAction - - Contains the preference for the progress action - - ActionPreference - - ActionPreference - - - None - - - - - - System.IO.FileInfo - - - - - - - - System.Guid - - - - - - - - System.String - - - - - - - - System.String[] - - - - - - - - System.UInt64 - - - - - - - - System.Management.Automation.SwitchParameter - - - - - - - - - - System.String - - - - - - - - - - - - - - -------------------------- EXAMPLE 1 -------------------------- - ConvertTo-WDACPolicy -PolicyToAddLogsTo "C:\Users\Admin\AllowMicrosoftPlusBlockRules.xml" -Verbose - - This example will display the Code Integrity and AppLocker logs in a GUI and allow the user to select the logs to add to the specified policy file. - - - - -------------------------- EXAMPLE 2 -------------------------- - ConvertTo-WDACPolicy -Verbose -BasePolicyGUID '{ACE9058C-8A24-47F4-86F0-A33FAB5073E3}' - - This example will display the Code Integrity and AppLocker logs in a GUI and allow the user to select the logs to create a new supplemental policy and associate it with the specified base policy GUID. - - - - -------------------------- EXAMPLE 3 -------------------------- - ConvertTo-WDACPolicy -BasePolicyFile "C:\Users\Admin\AllowMicrosoftPlusBlockRules.xml" - - This example will display the Code Integrity and AppLocker logs in a GUI and allow the user to select the logs to create a new supplemental policy and associate it with the specified base policy file. - - - - -------------------------- EXAMPLE 4 -------------------------- - ConvertTo-WDACPolicy - - This example will display the Code Integrity and AppLocker logs in a GUI and takes no further action. - - - - -------------------------- EXAMPLE 5 -------------------------- - ConvertTo-WDACPolicy -FilterByPolicyNames 'VerifiedAndReputableDesktopFlightSupplemental','WindowsE_Lockdown_Flight_Policy_Supplemental' -Verbose - - This example will filter the Code Integrity and AppLocker logs by the specified policy names and display them in a GUI. It will also display verbose messages on the console. - - - - -------------------------- EXAMPLE 6 -------------------------- - ConvertTo-WDACPolicy -FilterByPolicyNames 'Microsoft Windows Driver Policy - Enforced' -TimeSpan Minutes -TimeSpanAgo 10 - - This example will filter the local Code Integrity and AppLocker logs by the specified policy name and the number of minutes ago from the current time and display them in a GUI. So, it will display the logs that are 10 minutes old and are associated with the specified policy name. - - - - -------------------------- EXAMPLE 7 -------------------------- - ConvertTo-WDACPolicy -BasePolicyFile "C:\Program Files\WDACConfig\DefaultWindowsPlusBlockRules.xml" -Source MDEAdvancedHunting -MDEAHLogs "C:\Users\Admin\Downloads\New query.csv" -Deploy -TimeSpan Days -TimeSpanAgo 2 - - This example will create a new supplemental policy from the selected MDE Advanced Hunting logs and associate it with the specified base policy file and it will deploy it on the system. The displayed logs will be from the last 2 days. You will be able to select the logs to create the policy from in the GUI. - - - - -------------------------- EXAMPLE 8 -------------------------- - ConvertTo-WDACPolicy -BasePolicyGUID '{89CD611D-5557-4833-B73D-716B979AEE3D}' -Source EVTXFiles -EVTXLogs "C:\Users\HotCakeX\App Locker logs.evtx","C:\Users\HotCakeX\Code Integrity LOGS.evtx" - - This example will create a new supplemental policy from the selected EVTX files and associate it with the specified base policy GUID. - - - - - - Cmdlet Guide - https://github.com/HotCakeX/Harden-Windows-Security/wiki/ConvertTo-WDACPolicy - - - YouTube video guide for MDE Advanced Hunting usage - https://www.youtube.com/watch?v=oyz0jFzOOGA - - - - \ No newline at end of file diff --git a/WDACConfig/WDACConfig Module Files/WDACConfig.psd1 b/WDACConfig/WDACConfig Module Files/WDACConfig.psd1 index 1609b5dc8..13e75fa91 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.8.2' + ModuleVersion = '0.4.9' CompatiblePSEditions = @('Core') GUID = '79920947-efb5-48c1-a567-5b02ebe74793' Author = 'HotCakeX' diff --git a/WDACConfig/WDACConfig Module Files/XMLOps/Compare-CorrelatedData.psm1 b/WDACConfig/WDACConfig Module Files/XMLOps/Compare-CorrelatedData.psm1 deleted file mode 100644 index 45084d69c..000000000 --- a/WDACConfig/WDACConfig Module Files/XMLOps/Compare-CorrelatedData.psm1 +++ /dev/null @@ -1,245 +0,0 @@ -Function Compare-CorrelatedData { - <# - .SYNOPSIS - Finds the correlated events in the new CSV data and groups them together based on the EtwActivityId - Ensures that each Audit or Blocked event has its correlated Signing information events grouped together as nested HashTables - - The correlated data of each log is unique based on 4 main properties, Publisher TBS and name, Issuer TBS and name - - The output of this function does not contain duplicates. Neither duplicate files nor duplicate signer information for each file. - - If 2 logs for the same file exist and one of them contains the signing information while the other one doesn't, the one with signing information is kept. - .PARAMETER OptimizedCSVData - The CSV data to be processed, they should be the output of the Optimize-MDECSVData function - .PARAMETER StagingArea - The path to the directory where the debug CSV file will be saved which are the outputs of this function - .PARAMETER Debug - A switch parameter to enable debugging actions such as exporting the correlated event data to a JSON file - .PARAMETER StartTime - A DateTime object that specifies the start time of the logs to be processed. If this parameter is not specified, all logs will be processed. - .PARAMETER PolicyNamesToFilter - An array of strings that specifies the policy names to filter the logs by. If this parameter is not specified, all logs will be processed. - .PARAMETER LogType - A string that specifies the type of logs to process. The only valid values are 'Audit' and 'Blocked' - .INPUTS - System.Collections.Hashtable[] - System.DateTime - System.String[] - System.String - .OUTPUTS - System.Collections.Hashtable - #> - [CmdletBinding()] - Param ( - [Parameter(Mandatory = $true)][System.Collections.Hashtable[]]$OptimizedCSVData, - [Parameter(Mandatory = $true)][System.IO.DirectoryInfo]$StagingArea, - [Parameter(Mandatory = $false)][System.DateTime]$StartTime, - [AllowNull()] - [Parameter(Mandatory = $false)][System.String[]]$PolicyNamesToFilter, - [ValidateSet('Audit', 'Blocked', 'All')] - [Parameter(Mandatory = $true)][System.String]$LogType - ) - Begin { - # 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.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 = @{} - } - - Process { - - # Loop over each group of logs - Foreach ($RawLogGroup in $GroupedEvents) { - - # Store the group data in a HashTable array - [System.Collections.Hashtable[]]$GroupData = $RawLogGroup.Group - - if ($StartTime) { - try { - # Try to prase the TimeStamp string as DateTime type - [System.DateTime]$CurrentEventTimeStamp = [System.DateTime]::Parse((($GroupData.Timestamp) | Select-Object -First 1)) - - # If the current log's TimeStamp is older than the time frame specified by the user then skip this iteration/log completely - if ($CurrentEventTimeStamp -lt $StartTime ) { - Continue - } - } - Catch { - [WDACConfig.Logger]::Write("Event Timestamp for the file '$($GroupData.FileName)' was invalid") - } - } - - # Detect the Audit events only if the LogType parameter is set to 'Audit' - if ($LogType -in 'Audit', 'All') { - - # Process Audit events for Code Integrity and AppLocker - if (($GroupData.ActionType -contains 'AppControlCodeIntegrityPolicyAudited') -or ($GroupData.ActionType -contains 'AppControlCIScriptAudited')) { - - # Create a temporary HashTable to store the main event, its correlated events and its type - [System.Collections.Hashtable]$TempAuditHashTable = @{ - CorrelatedEventsData = @{} - Type = 'Audit' - SignatureStatus = '' - } - - # Finding the main Audit event in the group - # Only selecting the first event because when multiple Audit policies of the same type are deployed on the system, they same event is generated for each of them - [System.Collections.Hashtable]$AuditTemp = $GroupData | - Where-Object -FilterScript { $_['ActionType'] -in ('AppControlCodeIntegrityPolicyAudited', 'AppControlCIScriptAudited') } | Select-Object -First 1 - - # If the user provided policy names to filter the logs by - if ($null -ne $PolicyNamesToFilter) { - # Skip this iteration if the policy name of the current log is not in the list of policy names to filter by - if ($AuditTemp.PolicyName -notin $PolicyNamesToFilter) { - Continue - } - } - - # Generating a unique key for the hashtable based on file's properties - [System.String]$UniqueAuditMainEventDataKey = $AuditTemp.FileName + '|' + $AuditTemp.SHA256 + '|' + $AuditTemp.SHA1 + '|' + $AuditTemp.FileVersion - - # Adding the main event to the temporary HashTable, each key/value pair - foreach ($Data in $AuditTemp.GetEnumerator()) { - $TempAuditHashTable[$Data.Key] = $Data.Value - } - - # Looping over the signer infos and adding the unique publisher/issuer pairs to the correlated events data - foreach ($SignerInfo in ($GroupData | Where-Object -FilterScript { $_.ActionType -eq 'AppControlCodeIntegritySigningInformation' })) { - - # If the PublisherTBSHash or IssuerTBSHash is null, skip this iteration, usually in these situations the Issuer name and Publisher names are set to 'unknown' - if (($null -eq $SignerInfo.PublisherTBSHash) -or ($null -eq $SignerInfo.IssuerTBSHash)) { - Continue - } - - [System.String]$UniqueAuditSignerKey = $SignerInfo.PublisherTBSHash + '|' + - $SignerInfo.PublisherName + '|' + - $SignerInfo.IssuerName + '|' + - $SignerInfo.IssuerTBSHash - - if (-NOT $TempAuditHashTable['CorrelatedEventsData'].Contains($UniqueAuditSignerKey)) { - $TempAuditHashTable['CorrelatedEventsData'][$UniqueAuditSignerKey] = $SignerInfo - } - } - - # Determining whether this log package is signed or unsigned - $TempAuditHashTable['SignatureStatus'] = $TempAuditHashTable.CorrelatedEventsData.Count -eq 0 ? 'Unsigned' : 'Signed' - - # Check see if the main hashtable already contains the same file (key) - if ($EventPackageCollections.ContainsKey($UniqueAuditMainEventDataKey)) { - - # 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')) { - - [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) - - # Add the current Audit event with a unique identifiable key to the main HashTable - # This way we are replacing the Unsigned log with the Signed log that has more data, for the same file, providing the ability to create signature based rules for that file instead of hash based rule - $EventPackageCollections[$UniqueAuditMainEventDataKey] += $TempAuditHashTable - } - } - else { - # Add the current Audit event with a unique identifiable key to the main HashTable - $EventPackageCollections[$UniqueAuditMainEventDataKey] += $TempAuditHashTable - } - } - } - - # Detect the blocked events only if the LogType parameter is set to 'Blocked' - if ($LogType -in 'Blocked', 'All') { - - # Process Blocked events for Code Integrity and AppLocker - if (($GroupData.ActionType -contains 'AppControlCodeIntegrityPolicyBlocked') -or ($GroupData.ActionType -contains 'AppControlCIScriptBlocked')) { - - # Create a temporary HashTable to store the main event, its correlated events and its type - [System.Collections.Hashtable]$TempBlockedHashTable = @{ - CorrelatedEventsData = @{} - Type = 'Blocked' - SignatureStatus = '' - } - - # Finding the main block event in the group - # Only selecting the first event because when multiple enforced policies of the same type are deployed on the system, they same event might be generated for each of them - [System.Collections.Hashtable]$BlockedTemp = $GroupData | - Where-Object -FilterScript { $_['ActionType'] -in ('AppControlCodeIntegrityPolicyBlocked', 'AppControlCIScriptBlocked') } | Select-Object -First 1 - - # If the user provided policy names to filter the logs by - if ($null -ne $PolicyNamesToFilter) { - # Skip this iteration if the policy name of the current log is not in the list of policy names to filter by - if ($BlockedTemp.PolicyName -notin $PolicyNamesToFilter) { - Continue - } - } - - # Generating a unique key for the hashtable based on file's properties - [System.String]$UniqueBlockedMainEventDataKey = $BlockedTemp.FileName + '|' + $BlockedTemp.SHA256 + '|' + $BlockedTemp.SHA1 + '|' + $BlockedTemp.FileVersion - - # Adding the main event to the temporary HashTable, each key/value pair - foreach ($Data in $BlockedTemp.GetEnumerator()) { - $TempBlockedHashTable[$Data.Key] = $Data.Value - } - - # Looping over the signer infos and adding the unique publisher/issuer pairs to the correlated events data - foreach ($SignerInfo in ($GroupData | Where-Object -FilterScript { $_.ActionType -eq 'AppControlCodeIntegritySigningInformation' })) { - - # If the PublisherTBSHash or IssuerTBSHash is null, skip this iteration, usually in these situations the Issuer name and Publisher names are set to 'unknown' - if (($null -eq $SignerInfo.PublisherTBSHash) -or ($null -eq $SignerInfo.IssuerTBSHash)) { - Continue - } - - [System.String]$UniqueBlockedSignerKey = $SignerInfo.PublisherTBSHash + '|' + - $SignerInfo.PublisherName + '|' + - $SignerInfo.IssuerName + '|' + - $SignerInfo.IssuerTBSHash - - if (-NOT $TempBlockedHashTable['CorrelatedEventsData'].Contains($UniqueBlockedSignerKey)) { - $TempBlockedHashTable['CorrelatedEventsData'][$UniqueBlockedSignerKey] = $SignerInfo - } - } - - # Determining whether this log package is signed or unsigned - $TempBlockedHashTable['SignatureStatus'] = $TempBlockedHashTable.CorrelatedEventsData.Count -eq 0 ? 'Unsigned' : 'Signed' - - # Check see if the main hashtable already contains the same file (key) - if ($EventPackageCollections.ContainsKey($UniqueBlockedMainEventDataKey)) { - - # 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')) { - - [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) - - # Add the current Audit event with a unique identifiable key to the main HashTable - # This way we are replacing the Unsigned log with the Signed log that has more data, for the same file, providing the ability to create signature based rules for that file instead of hash based rule - $EventPackageCollections[$UniqueBlockedMainEventDataKey] += $TempBlockedHashTable - } - } - else { - # Add the current Audit event with a unique identifiable key to the main HashTable - $EventPackageCollections[$UniqueBlockedMainEventDataKey] += $TempBlockedHashTable - } - } - } - } - } - - End { - - 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 - } - - Return $EventPackageCollections - } -} -Export-ModuleMember -Function 'Compare-CorrelatedData' \ No newline at end of file diff --git a/WDACConfig/WDACConfig Module Files/XMLOps/Optimize-MDECSVData.psm1 b/WDACConfig/WDACConfig Module Files/XMLOps/Optimize-MDECSVData.psm1 deleted file mode 100644 index 6fb24e366..000000000 --- a/WDACConfig/WDACConfig Module Files/XMLOps/Optimize-MDECSVData.psm1 +++ /dev/null @@ -1,93 +0,0 @@ -Function Optimize-MDECSVData { - <# - .SYNOPSIS - Optimizes the MDE CSV data by adding the nested properties in the "AdditionalFields" property to the parent record as first-level properties - .DESCRIPTION - The function runs each CSV file in parallel for fast processing based on the number of CPU cores available - .PARAMETER CSVPaths - The path to the CSV file containing the Microsoft Defender for Endpoint Advanced Hunting data - .PARAMETER Debug - A switch parameter to enable debugging actions such as exporting the new array to a CSV file - .PARAMETER StagingArea - The path to the directory where the debug CSV file will be saved which are the outputs of this function - .INPUTS - System.IO.FileInfo[] - .OUTPUTS - System.Collections.Hashtable[] - #> - [CmdletBinding()] - Param ( - [Parameter(Mandatory = $true)][System.IO.FileInfo[]]$CSVPaths, - [Parameter(Mandatory = $true)][System.IO.DirectoryInfo]$StagingArea - ) - Begin { - Try { - # Get the number of enabled CPU cores - $CPUEnabledCores = [System.Int64](Get-CimInstance -ClassName Win32_Processor -Verbose:$false).NumberOfEnabledCore - } - Catch { - [WDACConfig.Logger]::Write('Optimize-MDECSVData: Unable to detect the number of enabled CPU cores, defaulting to 5...') - } - } - - Process { - - # Create a new HashTable array to hold the updated data from the original CSVs - [System.Collections.Hashtable[]]$NewCsvData = $CSVPaths | ForEach-Object -ThrottleLimit ($CPUEnabledCores ?? 5) -Parallel { - - # Read the initial MDE AH CSV export and save them into a variable - [System.Object[]]$CsvData += Import-Csv -Path $_ - - # Add the nested properties in the "AdditionalFields" property to the parent record as first-level properties - foreach ($Row in $CsvData) { - - # Create a new HashTable for the combined data - [System.Collections.Hashtable]$CurrentRowHashTable = @{} - - # For each row in the CSV data, create a new object to hold the updated properties, except for the "AdditionalFields" property - foreach ($Property in $Row.PSObject.Properties) { - if ($Property.Name -ne 'AdditionalFields') { - $CurrentRowHashTable[$Property.Name] = $Property.Value - } - } - - # Convert the AdditionalFields JSON string to a HashTable - [System.Collections.Hashtable]$JsonConverted = $Row.AdditionalFields | ConvertFrom-Json -AsHashtable - - # Add each Key/Value pairs from the additional fields HashTable to the CurrentRow HashTable - foreach ($Item in $JsonConverted.GetEnumerator()) { - $CurrentRowHashTable[$Item.Name] = $Item.Value - } - - # Send the new HashTable to the pipeline to be saved in the HashTable Array - [System.Collections.Hashtable]$CurrentRowHashTable - } - } - } - - End { - - if ([WDACConfig.GlobalVars]::DebugPreference) { - - [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]] @() - - # Loop through each HashTable's keys in the new updated CSV data to find and add any new key names to the list that are not already present - # These are the property names from the AdditionalFields - foreach ($Obj in $NewCsvData.Keys) { - if (-NOT $PropertyNames.Contains($Obj)) { - $PropertyNames += $Obj - } - } - - # Export the new array to a CSV file containing all of the original properties and the new properties from the AdditionalFields - # guarantees that no property gets lost during CSV export - $NewCsvData | Select-Object -Property $PropertyNames | Export-Csv -Path (Join-Path -Path $StagingArea -ChildPath 'Pass1.csv') -Force - } - - Return $NewCsvData - } -} -Export-ModuleMember -Function 'Optimize-MDECSVData' \ No newline at end of file diff --git a/WDACConfig/WDACConfig.csproj b/WDACConfig/WDACConfig.csproj index 35a725cc2..03896d932 100644 --- a/WDACConfig/WDACConfig.csproj +++ b/WDACConfig/WDACConfig.csproj @@ -43,7 +43,7 @@ - + diff --git a/WDACConfig/exclusion.dic b/WDACConfig/exclusion.dic index 648975bca..02211323e 100644 --- a/WDACConfig/exclusion.dic +++ b/WDACConfig/exclusion.dic @@ -1 +1,2 @@ Namez +Infos