Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Harden Windows Security v0.6.8 #376

Merged
merged 14 commits into from
Oct 29, 2024
Merged
3 changes: 3 additions & 0 deletions Harden-Windows-Security Module/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -341,3 +341,6 @@ dotnet_diagnostic.CA2265.severity = error

# CA1507: Use nameof to express symbol names
dotnet_diagnostic.CA1507.severity = error

# IDE0001: Simplify name
dotnet_diagnostic.IDE0001.severity = error
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
System
System.IO
System.Xml
System.Net
System.Data
WindowsBase
System.Xaml
System.Linq
System.Memory
System.Console
System.Windows
System.CodeDom
System.Runtime
System.Net.Http
System.Xml.Linq
Expand All @@ -30,11 +28,9 @@ System.Threading.Tasks
System.Linq.Expressions
System.Threading.Thread
System.Xml.ReaderWriter
WindowsFormsIntegration
System.DirectoryServices
Microsoft.Win32.Registry
System.Security.Principal
System.Reflection.Metadata
System.Diagnostics.Process
Microsoft.Win32.Primitives
System.Diagnostics.EventLog
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ public static void AddTpmAndStartupKeyProtector(string DriveLetter, string Start
{
// If the key wasn't saved successfully, remove the protector as a safety measure

var deleteKeyProtectorArgs = VolumeInfo.GetMethodParameters("DeleteKeyProtector");
ManagementBaseObject deleteKeyProtectorArgs = VolumeInfo.GetMethodParameters("DeleteKeyProtector");
deleteKeyProtectorArgs["VolumeKeyProtectorID"] = ProtectKeyWithTPMAndStartupKeyMethodInvocationResult["VolumeKeyProtectorID"];
_ = VolumeInfo.InvokeMethod("DeleteKeyProtector", deleteKeyProtectorArgs, null);

Expand Down Expand Up @@ -619,7 +619,7 @@ public static void AddSidProtector(string DriveLetter, string SID, bool ServiceA
SecurityIdentifier SIDResult = new(SID);

// Prepare the method with arguments
var protectWithSidArgs = VolumeInfo.GetMethodParameters("ProtectKeyWithAdSid");
ManagementBaseObject protectWithSidArgs = VolumeInfo.GetMethodParameters("ProtectKeyWithAdSid");
protectWithSidArgs["FriendlyName"] = null;
protectWithSidArgs["SidString"] = SIDResult.Value;
protectWithSidArgs["Flags"] = flags;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public partial class BitLocker
{

// Class that stores the information about each key protector
public class KeyProtector
public sealed class KeyProtector
{
public KeyProtectorType? KeyProtectorType { get; set; }
public string? KeyProtectorID { get; set; }
Expand All @@ -33,7 +33,7 @@ public class KeyProtector
}

// class that stores the information about BitLocker protected volumes
public class BitLockerVolume
public sealed class BitLockerVolume
{
public string? MountPoint { get; set; }
public EncryptionMethod? EncryptionMethod { get; set; }
Expand Down Expand Up @@ -224,7 +224,7 @@ public static BitLockerVolume GetEncryptedVolumeInfo(string targetVolume)
try
{
// Try to use the GetEncryptionMethod method to get the EncryptionMethod and EncryptionMethodFlags properties
var currentEncryptionMethod = InvokeCimMethod(volume, "GetEncryptionMethod", null);
ManagementBaseObject? currentEncryptionMethod = InvokeCimMethod(volume, "GetEncryptionMethod", null);
if (currentEncryptionMethod is not null && Convert.ToUInt32(currentEncryptionMethod["ReturnValue"], CultureInfo.InvariantCulture) == 0)
{
uint EncryptionMethodNum = Convert.ToUInt32(currentEncryptionMethod["EncryptionMethod"], CultureInfo.InvariantCulture);
Expand Down Expand Up @@ -470,21 +470,15 @@ public static List<BitLockerVolume> GetAllEncryptedVolumeInfo(bool OnlyNonOSDriv
BitLockerVolume volume = GetEncryptedVolumeInfo(driveLetter + ":");

// If only Non-OS Drives are requested, skip any other drive types
if (OnlyNonOSDrives)
if (OnlyNonOSDrives && volume.VolumeType is not VolumeType.FixedDisk)
{
if (volume.VolumeType is not VolumeType.FixedDisk)
{
continue;
}
continue;
}

// If only Removable Drives are requested, skip any other drive types
if (OnlyRemovableDrives)
if (OnlyRemovableDrives && volume.VolumeType is not VolumeType.Removable)
{
if (volume.VolumeType is not VolumeType.Removable)
{
continue;
}
continue;
}

volumes.Add(volume);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,18 @@ internal static void Disable(string DriveLetter)
if (HasErrorsOccurred) { return; }


if (VolumeInfoExtended.ConversionStatus is BitLocker.ConversionStatus.FullyDecrypted)
if (VolumeInfoExtended.ConversionStatus is ConversionStatus.FullyDecrypted)
{
Logger.LogMessage($"The drive {DriveLetter} is already decrypted", LogTypeIntel.InformationInteractionRequired);
return;
}

if (VolumeInfoExtended.ConversionStatus is ConversionStatus.DecryptionInProgress)
{
Logger.LogMessage($"The drive {DriveLetter} is being decrypted, please wait.", LogTypeIntel.InformationInteractionRequired);
return;
}


if (VolumeInfoExtended.VolumeType is VolumeType.OperationSystem)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ internal static void Enable(string DriveLetter, OSEncryptionType OSEncryptionTyp
if (HasErrorsOccurred) { return; }

// If the drive is fully encrypted, check its key protectors
if (VolumeInfoExtended.ConversionStatus is BitLocker.ConversionStatus.FullyEncrypted)
if (VolumeInfoExtended.ConversionStatus is ConversionStatus.FullyEncrypted)
{
Logger.LogMessage($"The OS drive is fully encrypted, will check if it conforms to the selected {OSEncryptionType} level.", LogTypeIntel.Information);

Expand All @@ -74,7 +74,7 @@ internal static void Enable(string DriveLetter, OSEncryptionType OSEncryptionTyp


// Get the key protectors of the OS Drive after making sure it is fully encrypted
List<BitLocker.KeyProtectorType?> KeyProtectors = VolumeInfoExtended.KeyProtector!
List<KeyProtectorType?> KeyProtectors = VolumeInfoExtended.KeyProtector!
.Select(kp => kp.KeyProtectorType).ToList();

if (KeyProtectors is null || KeyProtectors.Count == 0)
Expand All @@ -85,7 +85,7 @@ internal static void Enable(string DriveLetter, OSEncryptionType OSEncryptionTyp
}

// If Normal security level was selected
if (OSEncryptionType is BitLocker.OSEncryptionType.Normal)
if (OSEncryptionType is OSEncryptionType.Normal)
{
// If all the required key protectors for Normal security level are present, then return from the method
if (KeyProtectors.Contains(BitLocker.KeyProtectorType.RecoveryPassword) && KeyProtectors.Contains(BitLocker.KeyProtectorType.TpmPin))
Expand Down Expand Up @@ -188,7 +188,7 @@ internal static void Enable(string DriveLetter, OSEncryptionType OSEncryptionTyp
}

// Continue with full encryption if the drive is fully decrypted
else if (VolumeInfoExtended.ConversionStatus is BitLocker.ConversionStatus.FullyDecrypted)
else if (VolumeInfoExtended.ConversionStatus is ConversionStatus.FullyDecrypted)
{

// Prepare the method with arguments
Expand Down Expand Up @@ -348,7 +348,7 @@ internal static void Enable(string DriveLetter, bool FreePlusUsedSpace)


// Make sure the OS Drive is encrypted first, or else we would add recovery password key protector and then get error about the same problem during auto-unlock key protector enablement
BitLockerVolume OSDriveVolumeInfo = HardenWindowsSecurity.BitLocker.GetEncryptedVolumeInfo(Environment.GetEnvironmentVariable("SystemDrive") ?? "C:\\");
BitLockerVolume OSDriveVolumeInfo = BitLocker.GetEncryptedVolumeInfo(Environment.GetEnvironmentVariable("SystemDrive") ?? "C:\\");
if (OSDriveVolumeInfo.ProtectionStatus is not BitLocker.ProtectionStatus.Protected)
{
Logger.LogMessage($"Operation System drive must be encrypted first before encrypting Non-OS drives.", LogTypeIntel.ErrorInteractionRequired);
Expand All @@ -357,7 +357,7 @@ internal static void Enable(string DriveLetter, bool FreePlusUsedSpace)
}

// If the drive is already fully encrypted, check its key protectors
if (VolumeInfoExtended.ConversionStatus is BitLocker.ConversionStatus.FullyEncrypted)
if (VolumeInfoExtended.ConversionStatus is ConversionStatus.FullyEncrypted)
{

Logger.LogMessage($"The drive {DriveLetter} is fully encrypted, will check its key protectors.", LogTypeIntel.Information);
Expand All @@ -369,7 +369,7 @@ internal static void Enable(string DriveLetter, bool FreePlusUsedSpace)
}

// Get the key protectors of the Drive after making sure it is fully encrypted
List<BitLocker.KeyProtectorType?> KeyProtectors = VolumeInfoExtended.KeyProtector!
List<KeyProtectorType?> KeyProtectors = VolumeInfoExtended.KeyProtector!
.Select(kp => kp.KeyProtectorType).ToList();

if (KeyProtectors is null || KeyProtectors.Count == 0)
Expand All @@ -385,7 +385,7 @@ internal static void Enable(string DriveLetter, bool FreePlusUsedSpace)

#region
// Delete any possible old leftover ExternalKey key protectors
List<BitLocker.KeyProtector> ExternalKeys = VolumeInfoExtended.KeyProtector!.Where(kp => kp.KeyProtectorType is KeyProtectorType.ExternalKey).ToList();
List<KeyProtector> ExternalKeys = VolumeInfoExtended.KeyProtector!.Where(kp => kp.KeyProtectorType is KeyProtectorType.ExternalKey).ToList();

// This step ensures any leftover or unbound external key key protectors will be removed and a working one will be added
// If the current one is working and bound, it won't be removed and will be gracefully skipped over.
Expand Down Expand Up @@ -430,11 +430,11 @@ internal static void Enable(string DriveLetter, bool FreePlusUsedSpace)
#region
// Check for presence of multiple recovery password key protectors

List<BitLocker.KeyProtector> PasswordProtectors = VolumeInfoExtended.KeyProtector!.Where(kp => kp.KeyProtectorType is KeyProtectorType.RecoveryPassword).ToList();
List<KeyProtector> PasswordProtectors = VolumeInfoExtended.KeyProtector!.Where(kp => kp.KeyProtectorType is KeyProtectorType.RecoveryPassword).ToList();

if (PasswordProtectors.Count > 1)
{
Logger.LogMessage($"drive {DriveLetter} has {PasswordProtectors} recovery password key protectors. Usually only one is enough.", LogTypeIntel.Information);
Logger.LogMessage($"drive {DriveLetter} has {PasswordProtectors.Count} recovery password key protectors. Usually only one is enough.", LogTypeIntel.Information);
}
#endregion

Expand Down Expand Up @@ -468,7 +468,7 @@ internal static void Enable(string DriveLetter, bool FreePlusUsedSpace)
}

// If the drive is fully decrypted, begin full drive encryption
else if (VolumeInfoExtended.ConversionStatus is BitLocker.ConversionStatus.FullyDecrypted)
else if (VolumeInfoExtended.ConversionStatus is ConversionStatus.FullyDecrypted)
{

// Prepare the method with arguments
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ public static void RemoveKeyProtector(string DriveLetter, string KeyProtectorID,
}


if (DetectedKeyProtector.KeyProtectorType is BitLocker.KeyProtectorType.TpmNetworkKey)
if (DetectedKeyProtector.KeyProtectorType is KeyProtectorType.TpmNetworkKey)
{
Logger.LogMessage($"The detected Key Protector type is TpmNetworkKey, it must be disabled and removed using group policies.", LogTypeIntel.Warning);
return;
}


if (DetectedKeyProtector.KeyProtectorType is BitLocker.KeyProtectorType.PublicKey)
if (DetectedKeyProtector.KeyProtectorType is KeyProtectorType.PublicKey)
{
Logger.LogMessage($"Removal of PublicKey type key protector not supported yet.", LogTypeIntel.Warning);
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public static dynamic GetMpComputerStatus()
// Make sure the results isn't empty
if (results.Count > 0)
{
var result = results.Cast<ManagementBaseObject>().FirstOrDefault();
ManagementBaseObject? result = results.Cast<ManagementBaseObject>().FirstOrDefault();

if (result is not null)
{
Expand All @@ -41,13 +41,13 @@ public static dynamic GetMpComputerStatus()
}
else
{
throw new HardenWindowsSecurity.PowerShellExecutionException("WMI query for 'MSFT_MpComputerStatus' failed");
throw new PowerShellExecutionException("WMI query for 'MSFT_MpComputerStatus' failed");
}
}
catch (ManagementException ex)
{
string errorMessage = $"WMI query for 'MSFT_MpComputerStatus' failed: {ex.Message}";
throw new HardenWindowsSecurity.PowerShellExecutionException(errorMessage, ex);
throw new PowerShellExecutionException(errorMessage, ex);
}
}

Expand All @@ -56,9 +56,10 @@ private static dynamic ConvertToDynamic(ManagementBaseObject managementObject)
{
// Creating a dynamic object to store the properties of the ManagementBaseObject
dynamic expandoObject = new ExpandoObject();
var dictionary = (IDictionary<string, object>)expandoObject;

foreach (var property in managementObject.Properties)
IDictionary<string, object> dictionary = expandoObject;

foreach (PropertyData property in managementObject.Properties)
{
if (property.Type == CimType.DateTime && property.Value is string dmtfTime)
{
Expand Down Expand Up @@ -102,14 +103,14 @@ public static void ManageMpPreference<T>(string preferenceName, T preferenceValu
try
{
// Connect to the WMI namespace
var scope = new ManagementScope(@"\\.\ROOT\Microsoft\Windows\Defender");
ManagementScope scope = new(@"\\.\ROOT\Microsoft\Windows\Defender");
scope.Connect();

// Create an instance of the MSFT_MpPreference class
using var mpPreferenceClass = new ManagementClass(scope, new ManagementPath("MSFT_MpPreference"), null);
using ManagementClass mpPreferenceClass = new(scope, new ManagementPath("MSFT_MpPreference"), null);

// Get the available methods for the class
var methodParams = mpPreferenceClass.GetMethodParameters(MethodName);
ManagementBaseObject methodParams = mpPreferenceClass.GetMethodParameters(MethodName);

if (preferenceValue is null)
{
Expand Down Expand Up @@ -157,11 +158,11 @@ public static void ManageMpPreference<T>(string preferenceName, T preferenceValu
// Invoke the method to apply the settings
_ = mpPreferenceClass.InvokeMethod(MethodName, methodParams, null);

HardenWindowsSecurity.Logger.LogMessage($"{preferenceName} set to {preferenceValue} (Type: {typeof(T).Name}) successfully.", LogTypeIntel.Information);
Logger.LogMessage($"{preferenceName} set to {preferenceValue} (Type: {typeof(T).Name}) successfully.", LogTypeIntel.Information);
}
catch (Exception ex)
{
HardenWindowsSecurity.Logger.LogMessage($"Error setting {preferenceName}: {ex.Message}- You might need to update your OS first.", LogTypeIntel.Warning);
Logger.LogMessage($"Error setting {preferenceName}: {ex.Message}- You might need to update your OS first.", LogTypeIntel.Warning);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,17 @@ public static List<ManagementObject> GetFirewallRules(string ruleGroup, ushort d
// catch exceptions specific to WMI
catch (ManagementException mex)
{
HardenWindowsSecurity.Logger.LogMessage($"WMI ManagementException: {mex.Message}", LogTypeIntel.Error);
Logger.LogMessage($"WMI ManagementException: {mex.Message}", LogTypeIntel.Error);
}
// Catch block for unauthorized access exceptions
catch (UnauthorizedAccessException uex)
{
HardenWindowsSecurity.Logger.LogMessage($"UnauthorizedAccessException: {uex.Message}", LogTypeIntel.Error);
Logger.LogMessage($"UnauthorizedAccessException: {uex.Message}", LogTypeIntel.Error);
}
// General catch block for any other exceptions
catch (Exception ex)
{
HardenWindowsSecurity.Logger.LogMessage($"An error occurred: {ex.Message}", LogTypeIntel.Error);
Logger.LogMessage($"An error occurred: {ex.Message}", LogTypeIntel.Error);
}

return results;
Expand Down Expand Up @@ -254,10 +254,10 @@ void DeleteFirewallRules(CimSession cimSession, string ruleName, string policySt
options.SetCustomOption("PolicyStore", policyStore, mustComply: true);

// Check for existing rules with the same name and delete them
var existingRules = cimSession.EnumerateInstances("root/StandardCimv2", "MSFT_NetFirewallRule", options)
IEnumerable<CimInstance> existingRules = cimSession.EnumerateInstances("root/StandardCimv2", "MSFT_NetFirewallRule", options)
.Where(instance => instance.CimInstanceProperties["ElementName"].Value.ToString() == ruleName);

foreach (var rule in existingRules)
foreach (CimInstance rule in existingRules)
{
cimSession.DeleteInstance("root/StandardCimv2", rule, options);
Logger.LogMessage($"Deleted existing firewall rule: {ruleName}", LogTypeIntel.Information);
Expand Down
Loading
Loading