Skip to content

Commit

Permalink
Removed dependency on WDACConfig
Browse files Browse the repository at this point in the history
No longer depends on WDACConfig, everything is included natively.
  • Loading branch information
HotCakeX committed Jan 2, 2025
1 parent 1757055 commit c02c55c
Show file tree
Hide file tree
Showing 8 changed files with 434 additions and 165 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,43 @@
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.Json;

// The following code is exact mirror of the same code in AppControl Manager's codebase
namespace HardenWindowsSecurity;

internal static class CiToolRunner
// Class to represent a policy with various attributes
public sealed class CiPolicyInfo
{
public string? PolicyID { get; set; } // Unique identifier for the policy
public string? BasePolicyID { get; set; } // Identifier for the base policy
public string? FriendlyName { get; set; } // Human-readable name of the policy
public Version? Version { get; set; } // Version object representing the policy version
public string? VersionString { get; set; } // Original version string from the policy data
public bool IsSystemPolicy { get; set; } // Indicates if it's a system policy
public bool IsSignedPolicy { get; set; } // Indicates if the policy is signed
public bool IsOnDisk { get; set; } // Indicates if the policy is present on disk
public bool IsEnforced { get; set; } // Indicates if the policy is enforced
public bool IsAuthorized { get; set; } // Indicates if the policy is authorized
internal List<string>? PolicyOptions { get; set; } // List of options or settings related to the policy


// A property to format PolicyOptions as a comma-separated string
public string PolicyOptionsDisplay => PolicyOptions is not null ? string.Join(", ", PolicyOptions) : string.Empty;
}


// This class contains all the necessary logics to interact with CiTool.exe
// Any code that wants to use CiTool.exe must go through this class rather than contacting it directly
internal static class CiToolHelper
{
/// <summary>
/// Converts a 64-bit unsigned integer into a version type, used for converting the numbers from CiTool.exe output to proper versions.
/// </summary>
/// <param name="number">The 64-bit unsigned integer as a string.</param>
/// <returns>The parsed version</returns>
private static Version Measure(string number)
internal static Version Measure(string number)
{
try
{
Expand Down Expand Up @@ -45,27 +70,21 @@ private static Version Measure(string number)
catch (Exception ex)
{
// Handle errors by printing an error message and returning a default version of 0.0.0.0
Logger.LogMessage($"Error converting number to version: {ex.Message}", LogTypeIntel.Error);
Logger.LogMessage($"Error converting number to version: {ex.Message}", LogTypeIntel.Information);
return new Version(0, 0, 0, 0);
}
}

internal static JsonSerializerOptions Options => new()
{
// Ignore case when matching JSON property names
PropertyNameCaseInsensitive = true,
};


/// <summary>
/// Gets a list of AppControl policies on the system with filtering
/// Gets a list of App Control policies on the system with filtering
/// </summary>
/// <param name="SystemPolicies">Will include System policies in the output</param>
/// <param name="BasePolicies">Will include Base policies in the output</param>
/// <param name="SupplementalPolicies">Will include Supplemental policies in the output</param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
internal static List<CiPolicyInfo> RunCiTool(JsonSerializerOptions options, bool SystemPolicies = false, bool BasePolicies = false, bool SupplementalPolicies = false)
internal static List<CiPolicyInfo> GetPolicies(bool SystemPolicies = false, bool BasePolicies = false, bool SupplementalPolicies = false)
{
// Create an empty list of Policy objects to return at the end
List<CiPolicyInfo> policies = [];
Expand All @@ -83,9 +102,8 @@ internal static List<CiPolicyInfo> RunCiTool(JsonSerializerOptions options, bool
CreateNoWindow = true // Run the process without creating a window
};


// Start the process and capture the output
using Process? process = Process.Start(processStartInfo) ?? throw new InvalidOperationException("There was a problem running the CiTool.exe in the RunCiTool method.");
using Process? process = Process.Start(processStartInfo) ?? throw new InvalidOperationException("There was a problem running the CiTool.exe in the GetPolicies method.");

// Read all output as a string
string jsonOutput = process.StandardOutput.ReadToEnd();
Expand All @@ -95,12 +113,13 @@ internal static List<CiPolicyInfo> RunCiTool(JsonSerializerOptions options, bool

if (process.ExitCode != 0)
{
// Throw an exception with the error message
throw new InvalidOperationException($"Command execution failed with error code {process.ExitCode}");
}

// Deserialize the JSON into a JsonElement for easy traversal
var rootElement = JsonSerializer.Deserialize<JsonElement>(jsonOutput, options);
// Parse the JSON into a JsonElement for easy traversal
using JsonDocument document = JsonDocument.Parse(Encoding.UTF8.GetBytes(jsonOutput));

JsonElement rootElement = document.RootElement;

// If "Policies" property exists and is an array, start processing each policy
if (rootElement.TryGetProperty("Policies", out JsonElement policiesElement) && policiesElement.ValueKind == JsonValueKind.Array)
Expand Down Expand Up @@ -145,9 +164,9 @@ internal static List<CiPolicyInfo> RunCiTool(JsonSerializerOptions options, bool


/// <summary>
/// Removes a deployed AppControl policy from the system
/// Removes a deployed App Control policy from the system
/// </summary>
/// <param name="policyId">the GUID which is the policy ID of the policy to be removed, with the curly brackets {} wrapped with double quotes "" </param>
/// <param name="policyId">The GUID which is the policy ID of the policy to be removed.</param>
/// <exception cref="ArgumentException"></exception>
internal static void RemovePolicy(string policyId)
{
Expand All @@ -156,36 +175,183 @@ internal static void RemovePolicy(string policyId)
throw new ArgumentException("Policy ID cannot be null or empty.", nameof(policyId));
}

// Remove any curly brackets or double quotes from the policy ID
// They will be added automatically later by the method
policyId = policyId.Trim('"', '"');
policyId = policyId.Trim('{', '}');

// Combine the path to CiTool.exe using the system's special folder path
string ciToolPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "CiTool.exe");

// Set up the process start info to run CiTool.exe with necessary arguments
ProcessStartInfo processStartInfo = new()
{
FileName = ciToolPath,
Arguments = $"--remove-policy \"{{{policyId}}}\" -json", // Arguments to remove a AppControl policy
Arguments = $"--remove-policy \"{{{policyId}}}\" -json", // Arguments to remove an App Control policy
RedirectStandardOutput = true, // Capture the standard output
UseShellExecute = false, // Do not use the OS shell to start the process
CreateNoWindow = true // Run the process without creating a window
};

// Start the process and capture the output
using Process? process = Process.Start(processStartInfo) ?? throw new InvalidOperationException("There was a problem running the CiTool.exe in the RunCiTool method.");
using Process? process = Process.Start(processStartInfo) ?? throw new InvalidOperationException("There was a problem running the CiTool.exe in the GetPolicies method.");

// Don't need the output if successful
_ = process.StandardOutput.ReadToEnd();
// Read all output as a string
string jsonOutput = process.StandardOutput.ReadToEnd();

// Wait for the process to complete
process.WaitForExit();

if (process.ExitCode != 0)
{
// Throw an exception with the error message
throw new InvalidOperationException($"Command execution failed with error code {process.ExitCode}");
throw new InvalidOperationException($"Command execution failed with error code {process.ExitCode}. Output: {jsonOutput}");
}
}




/// <summary>
/// Removes multiple deployed App Control policy from the system
/// </summary>
/// <param name="policyIds">The GUIDs which are the policy IDs of the policies to be removed.</param>
/// <exception cref="ArgumentException"></exception>
internal static void RemovePolicy(List<string> policyIds)
{

foreach (string policyId in policyIds)
{

if (string.IsNullOrWhiteSpace(policyId))
{
continue;
}

// Remove any curly brackets or double quotes from the policy ID
// They will be added automatically later by the method
string ID = policyId.Trim('"', '"');
ID = ID.Trim('{', '}');

// Combine the path to CiTool.exe using the system's special folder path
string ciToolPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "CiTool.exe");

// Set up the process start info to run CiTool.exe with necessary arguments
ProcessStartInfo processStartInfo = new()
{
FileName = ciToolPath,
Arguments = $"--remove-policy \"{{{ID}}}\" -json", // Arguments to remove an App Control policy
RedirectStandardOutput = true, // Capture the standard output
UseShellExecute = false, // Do not use the OS shell to start the process
CreateNoWindow = true // Run the process without creating a window
};

// Start the process and capture the output
using Process? process = Process.Start(processStartInfo) ?? throw new InvalidOperationException("There was a problem running the CiTool.exe in the GetPolicies method.");

// Read all output as a string
string jsonOutput = process.StandardOutput.ReadToEnd();

// Wait for the process to complete
process.WaitForExit();

if (process.ExitCode != 0)
{
throw new InvalidOperationException($"Command execution failed with error code {process.ExitCode}. Output: {jsonOutput}");
}
}
}



/// <summary>
/// Deploys a Code Integrity policy on the system by accepting the .CIP file path
/// </summary>
/// <param name="CipPath"></param>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="FileNotFoundException"></exception>
/// <exception cref="InvalidOperationException"></exception>
internal static void UpdatePolicy(string CipPath)
{
if (string.IsNullOrWhiteSpace(CipPath))
{
throw new ArgumentException("CipPath cannot be null or empty.", nameof(CipPath));
}

if (!File.Exists(CipPath))
{
throw new FileNotFoundException($"The file '{CipPath}' does not exist.", CipPath);
}

// Combine the path to CiTool.exe using the system's special folder path
string ciToolPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "CiTool.exe");

Logger.LogMessage($"Deploying the following CIP file: {CipPath}", LogTypeIntel.Information);

// Set up the process start info to run CiTool.exe with necessary arguments
ProcessStartInfo processStartInfo = new()
{
FileName = ciToolPath,
Arguments = $"--update-policy \"{CipPath}\" -json", // Arguments to update the App Control policy
RedirectStandardOutput = true, // Capture the standard output
UseShellExecute = false, // Do not use the OS shell to start the process
CreateNoWindow = true // Run the process without creating a window
};

// Start the process and capture the output
using Process? process = Process.Start(processStartInfo) ?? throw new InvalidOperationException("There was a problem running the CiTool.exe in the UpdatePolicy method.");

// Read all output as a string
string jsonOutput = process.StandardOutput.ReadToEnd();

// Wait for the process to complete
process.WaitForExit();

if (process.ExitCode != 0)
{
throw new InvalidOperationException($"Command execution failed with error code {process.ExitCode}. Output: {jsonOutput}");
}
}


/// <summary>
/// Refreshes the currently deployed policies on the system
/// </summary>
/// <exception cref="InvalidOperationException"></exception>
internal static void RefreshPolicy()
{
// Combine the path to CiTool.exe using the system's special folder path
string ciToolPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "CiTool.exe");

// Set up the process start info to run CiTool.exe with the refresh argument
ProcessStartInfo processStartInfo = new()
{
FileName = ciToolPath,
Arguments = "--refresh -json", // Arguments to refresh App Control policies
RedirectStandardOutput = true, // Capture the standard output
UseShellExecute = false, // Do not use the OS shell to start the process
CreateNoWindow = true // Run the process without creating a window
};

// Start the process and capture the output
using Process? process = Process.Start(processStartInfo) ?? throw new InvalidOperationException("There was a problem running the CiTool.exe in the RefreshPolicy method.");

// Read all output as a string
string jsonOutput = process.StandardOutput.ReadToEnd();

// Wait for the process to complete
process.WaitForExit();

if (process.ExitCode != 0)
{
throw new InvalidOperationException($"Command execution failed with error code {process.ExitCode}. Output: {jsonOutput}");
}
}

}




// Extension methods for JsonElement to simplify retrieving properties with default values
internal static class JsonElementExtensions
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace HardenWindowsSecurity;

internal static class PolicyToCIPConverter
{
/// <summary>
/// Converts a XML policy file to CIP binary file using the ConvertFrom-CIPolicy cmdlet of the ConfigCI module
/// </summary>
/// <param name="XmlFilePath"></param>
/// <param name="BinaryFilePath"></param>
internal static void Convert(string XmlFilePath, string BinaryFilePath)
{

// Escape the output policy path for PowerShell
string escapedXMLFile = $"\\\"{XmlFilePath}\\\"";

// Escape the output policy path for PowerShell
string escapedOutputCIP = $"\\\"{BinaryFilePath}\\\"";

// Construct the PowerShell script
string script = $"ConvertFrom-CIPolicy -XmlFilePath {escapedXMLFile} -BinaryFilePath {escapedOutputCIP}";

Logger.LogMessage($"PowerShell code that will be executed: {script}", LogTypeIntel.Information);

// Execute the command
ProcessStarter.RunCommand("powershell.exe", $"-NoProfile -Command \"{script}\"");
}

}
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
using System;
using System.Collections.Generic;
using System.IO;

namespace HardenWindowsSecurity;

public static partial class DownloadsDefenseMeasures
{
/// <summary>
/// Blocks certain dangerous script hosts using AppControl policy
/// Blocks certain dangerous script hosts using App Control policy
/// </summary>
/// <exception cref="ArgumentNullException"></exception>
public static void DangerousScriptHostsBlocking()
Expand All @@ -21,22 +22,31 @@ public static void DangerousScriptHostsBlocking()
string CIPPath = Path.Combine(GlobalVars.WorkingDir, "Dangerous-Script-Hosts-Blocking.cip");
string XMLPath = Path.Combine(GlobalVars.path, "Resources", "Dangerous-Script-Hosts-Blocking.xml");

// Use string interpolation without the @ symbol for multiline
string script = $@"
$CurrentBasePolicyNames = [System.Collections.Generic.HashSet[System.String]]@(
((&""$env:SystemDrive\Windows\System32\CiTool.exe"" -lp -json | ConvertFrom-Json).Policies |
Where-Object -FilterScript {{ ($_.IsSystemPolicy -ne 'True') -and ($_.PolicyID -eq $_.BasePolicyID) }}).FriendlyName
)
if (($null -eq $CurrentBasePolicyNames) -or (-NOT ($CurrentBasePolicyNames.Contains('Dangerous-Script-Hosts-Blocking')))) {{
$null = ConvertFrom-CIPolicy -XmlFilePath '{XMLPath}' -BinaryFilePath '{CIPPath}'
$null = CiTool.exe --update-policy '{CIPPath}' -json
}}
else {{
Write-Verbose -Message 'The Dangerous-Script-Hosts-Blocking policy is already deployed' -Verbose
}}
";

_ = PowerShellExecutor.ExecuteScript(script);
// Run the CiTool and retrieve a list of base policies
List<CiPolicyInfo> policies = CiToolHelper.GetPolicies(SystemPolicies: false, BasePolicies: true, SupplementalPolicies: false);

bool isFound = false;

// loop over all policies
foreach (CiPolicyInfo item in policies)
{
// find the policy with the right name
if (string.Equals(item.FriendlyName, "Dangerous-Script-Hosts-Blocking", StringComparison.OrdinalIgnoreCase))
{
isFound = true;
break;
}
}

// If the Dangerous-Script-Hosts-Blocking is not deployed
if (!isFound)
{
PolicyToCIPConverter.Convert(XMLPath, CIPPath);
CiToolHelper.UpdatePolicy(CIPPath);
}
else
{
Logger.LogMessage("The Dangerous-Script-Hosts-Blocking policy is already deployed", LogTypeIntel.Information);
}
}
}
Loading

0 comments on commit c02c55c

Please sign in to comment.