mirror of
https://github.com/GhostPack/SharpDPAPI
synced 2026-06-08 11:11:23 +00:00
360 lines
14 KiB
C#
360 lines
14 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text.RegularExpressions;
|
|
using Microsoft.Win32;
|
|
|
|
namespace SharpDPAPI.Commands
|
|
{
|
|
public class Search : ICommand
|
|
{
|
|
public static string CommandName => "search";
|
|
delegate void ProcessFileCallback(string path, bool showErrors);
|
|
|
|
|
|
private static readonly byte[] dpapiBlobHeader =
|
|
{
|
|
// Version(4 bytes) | DPAPI Proivder Guid(16-bytes - df9d8cd0-1501-11d1-8c7a-00c04fc297eb)
|
|
0x01, 0x00, 0x00, 0x00, 0xD0, 0x8C, 0x9D, 0xDF, 0x01, 0x15, 0xD1, 0x11, 0x8C, 0x7A, 0x00, 0xC0, 0x4F, 0xC2, 0x97, 0xEB
|
|
};
|
|
|
|
private readonly byte[][] dpapiBlobSearches =
|
|
{
|
|
dpapiBlobHeader,
|
|
|
|
// The following are potential base64 representations of the DPAPI provider GUID
|
|
// Generated by putting dpapiProviderGuid into the script here: https://www.leeholmes.com/blog/2017/09/21/searching-for-content-in-base-64-strings/
|
|
System.Text.Encoding.ASCII.GetBytes("AAAA0Iyd3wEV0RGMegDAT8KX6"),
|
|
System.Text.Encoding.ASCII.GetBytes("AQAAANCMnd8BFdERjHoAwE/Cl+"),
|
|
System.Text.Encoding.ASCII.GetBytes("EAAADQjJ3fARXREYx6AMBPwpfr"),
|
|
|
|
// Hex string representation
|
|
System.Text.Encoding.ASCII.GetBytes("01000000D08C9DDF0115D1118C7A00C04FC297EB")
|
|
};
|
|
|
|
public void Execute(Dictionary<string, string> arguments)
|
|
{
|
|
Console.WriteLine("\r\n[*] Action: Searching for DPAPI blobs");
|
|
|
|
if (!arguments.ContainsKey("/type"))
|
|
arguments["/type"] = "registry";
|
|
|
|
Console.WriteLine($"[*] Search type: {arguments["/type"]}");
|
|
|
|
switch (arguments["/type"])
|
|
{
|
|
case "registry":
|
|
SearchRegistry(arguments);
|
|
break;
|
|
|
|
case "folder":
|
|
SearchFolder(arguments);
|
|
break;
|
|
|
|
case "file":
|
|
SearchFile(arguments);
|
|
break;
|
|
|
|
case "base64":
|
|
SearchBase64(arguments);
|
|
break;
|
|
|
|
default:
|
|
throw new ArgumentException($"Unknown /type parameter '{arguments["/type"]}'");
|
|
}
|
|
}
|
|
|
|
private void SearchFolder(Dictionary<string, string> arguments)
|
|
{
|
|
if (!arguments.ContainsKey("/path"))
|
|
throw new ArgumentException("/path argument not specified");
|
|
|
|
var path = arguments["/path"];
|
|
var showErrors = arguments.ContainsKey("/showErrors");
|
|
|
|
if (!Directory.Exists(path))
|
|
throw new ArgumentException($"The folder '{path}' does not exist");
|
|
|
|
uint maxBytes = 1024;
|
|
if (arguments.ContainsKey("/maxBytes") && !uint.TryParse(arguments["/maxBytes"], out maxBytes))
|
|
throw new ArgumentException($"Invalid uint value '{arguments["/maxBytes"]}' in the /maxBytes argument");
|
|
|
|
Console.WriteLine($"[*] Searching for the folder {path} for files potentially containing DPAPI blobs in the first {maxBytes} bytes\n");
|
|
|
|
FindFiles(path, maxBytes, showErrors, (filePath, displayErrors) =>
|
|
{
|
|
try
|
|
{
|
|
if (FileContainsDpapiBlob(filePath, maxBytes))
|
|
Console.WriteLine(filePath);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (displayErrors)
|
|
Console.WriteLine($"File Processing ERROR: {filePath} - {e.Message}");
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
private void FindFiles(string path, uint maxBytes, bool showErrors, ProcessFileCallback processFile)
|
|
{
|
|
// Modified largely from https://developerslogblog.wordpress.com/2020/02/25/c-how-to-find-all-files-recursively-in-a-folder/
|
|
var paths = new List<string>();
|
|
var directoriesQueue = new Queue();
|
|
directoriesQueue.Enqueue(path);
|
|
|
|
while (directoriesQueue.Count > 0)
|
|
{
|
|
var currentPath = (string)directoriesQueue.Dequeue();
|
|
|
|
try
|
|
{
|
|
//Console.WriteLine("Processing folder " + currentPath);
|
|
var directories = Directory.GetDirectories(currentPath);
|
|
foreach (var directory in directories)
|
|
directoriesQueue.Enqueue(directory);
|
|
|
|
foreach (var file in Directory.GetFiles(currentPath))
|
|
processFile(file, showErrors);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (showErrors)
|
|
Console.WriteLine($"ERROR: {e}");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private void SearchFile(Dictionary<string, string> arguments)
|
|
{
|
|
if (!arguments.ContainsKey("/path"))
|
|
throw new ArgumentException("/path argument not specified");
|
|
|
|
var path = arguments["/path"];
|
|
|
|
if (!File.Exists(path))
|
|
throw new ArgumentException($"The file '{path}' does not exist");
|
|
|
|
uint maxBytes = 1024;
|
|
if (arguments.ContainsKey("/maxBytes") && !uint.TryParse(arguments["/maxBytes"], out maxBytes))
|
|
throw new ArgumentException($"Invalid uint value '{arguments["/maxBytes"]}' in the /maxBytes argument");
|
|
|
|
Console.WriteLine($"[*] Searching for DPAPI blobs in the file {path}\n");
|
|
if (FileContainsDpapiBlob(path, maxBytes))
|
|
Console.WriteLine($"Found potential DPAPI blob at {path}");
|
|
else
|
|
Console.WriteLine("No DPAPI blob found");
|
|
}
|
|
|
|
private bool FileContainsDpapiBlob(string path, uint bytesToSearch)
|
|
{
|
|
var fileContents = new byte[bytesToSearch];
|
|
using (var file = new FileStream(path, FileMode.Open))
|
|
{
|
|
file.Read(fileContents, 0, (int)bytesToSearch);
|
|
}
|
|
|
|
return ContainsDpapiBlob(fileContents);
|
|
}
|
|
|
|
private void SearchBase64(Dictionary<string, string> arguments)
|
|
{
|
|
if (!arguments.ContainsKey("/base64"))
|
|
throw new ArgumentException("/base64 argument not found");
|
|
|
|
ContainsDpapiBlob(Convert.FromBase64String(arguments["/base64"]));
|
|
}
|
|
|
|
private void SearchRegistry(Dictionary<string, string> arguments)
|
|
{
|
|
var showErrors = arguments.ContainsKey("/showErrors");
|
|
|
|
|
|
if (arguments.ContainsKey("/path"))
|
|
{
|
|
string keyPath;
|
|
RegistryHive hive;
|
|
// Based on Seatbelt's code here: https://github.com/GhostPack/Seatbelt/blob/master/Seatbelt/Commands/Windows/RegistryValueCommand.cs#L43-L60
|
|
var path = arguments["/path"];
|
|
var separatorPos = path.IndexOf("\\");
|
|
|
|
if (separatorPos == -1) // e.g. HKLM
|
|
{
|
|
hive = GetHive(path);
|
|
keyPath = "\\";
|
|
}
|
|
else if (separatorPos == path.Length) // e.g. HKLM\
|
|
{
|
|
var hiveStr = path.Substring(0, separatorPos);
|
|
hive = GetHive(hiveStr);
|
|
keyPath = "\\";
|
|
}
|
|
else // e.g. HKLM\Software
|
|
{
|
|
var hiveStr = path.Substring(0, separatorPos);
|
|
hive = GetHive(hiveStr);
|
|
keyPath = path.Substring(separatorPos + 1);
|
|
}
|
|
|
|
Console.WriteLine($"[*] Searching the key '{path}' for DPAPI blobs. Hive: {hive} Path: {keyPath}{(showErrors ? " (Displaying all errors)" : "")}\n");
|
|
|
|
var root = RegistryKey.OpenRemoteBaseKey(hive, "").OpenSubKey(keyPath, RegistryKeyPermissionCheck.ReadSubTree);
|
|
foreach (var match in FindRegistryBlobs(root, showErrors))
|
|
{
|
|
Console.WriteLine(match);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine($"[*] Searching the key HLKM and HKCU hives for DPAPI blobs\n");
|
|
|
|
var matchingKeys = new List<string>();
|
|
Console.WriteLine("[*] Searching USERS hive:\n");
|
|
foreach (var match in FindRegistryBlobs(Registry.Users.OpenSubKey("\\", RegistryKeyPermissionCheck.ReadSubTree), showErrors))
|
|
{
|
|
Console.WriteLine(match);
|
|
}
|
|
|
|
matchingKeys.Clear();
|
|
Console.WriteLine("\n\n[*] Searching HKLM hive:\n");
|
|
|
|
foreach (var match in FindRegistryBlobs(Registry.LocalMachine.OpenSubKey("\\", RegistryKeyPermissionCheck.ReadSubTree), showErrors))
|
|
{
|
|
Console.WriteLine(match);
|
|
}
|
|
}
|
|
}
|
|
|
|
// From Seatbelt: https://github.com/GhostPack/Seatbelt/blob/master/Seatbelt/Util/RegistryUtil.cs#L440
|
|
public static RegistryHive GetHive(string name)
|
|
{
|
|
switch (name.ToUpper())
|
|
{
|
|
case "HKCR":
|
|
case "HKEY_CLASSES_ROOT":
|
|
return RegistryHive.ClassesRoot;
|
|
|
|
case "HKEY_CURRENT_CONFIG":
|
|
return RegistryHive.CurrentConfig;
|
|
|
|
case "HKCU":
|
|
case "HKEY_CURRENT_USER":
|
|
return RegistryHive.CurrentUser;
|
|
|
|
case "HKLM":
|
|
case "HKEY_LOCAL_MACHINE":
|
|
return RegistryHive.LocalMachine;
|
|
|
|
case "HKEY_PERFORMANCE_DATA":
|
|
return RegistryHive.PerformanceData;
|
|
|
|
case "HKU":
|
|
case "HKEY_USERS":
|
|
return RegistryHive.Users;
|
|
|
|
default:
|
|
throw new Exception("UnknownRegistryHive");
|
|
}
|
|
}
|
|
|
|
private IEnumerable<string> FindRegistryBlobs(RegistryKey root, bool reportErrors)
|
|
{
|
|
var toCheck = new LinkedList<RegistryKey>();
|
|
toCheck.AddLast(root);
|
|
Console.WriteLine("Root: " + root);
|
|
|
|
while (toCheck.Count > 0)
|
|
{
|
|
root = toCheck.First.Value;
|
|
toCheck.RemoveFirst();
|
|
|
|
if (root == null)
|
|
continue;
|
|
|
|
var valueNames = new string[] { };
|
|
|
|
try
|
|
{
|
|
valueNames = root.GetValueNames();
|
|
}
|
|
catch
|
|
{
|
|
if (reportErrors)
|
|
Console.WriteLine($"ERROR: Could not list values for {root}");
|
|
}
|
|
|
|
foreach (var name in valueNames)
|
|
{
|
|
var valueKind = root.GetValueKind(name);
|
|
switch (valueKind)
|
|
{
|
|
case RegistryValueKind.MultiString:
|
|
case RegistryValueKind.ExpandString:
|
|
case RegistryValueKind.String:
|
|
var value = root.GetValue(name);
|
|
|
|
var str = value.GetType() == typeof(string[])
|
|
? string.Join("",(string[])value)
|
|
: (string)value;
|
|
|
|
// Regex generated by putting dpapiProviderGuid into the script here: https://www.leeholmes.com/blog/2017/09/21/searching-for-content-in-base-64-strings/
|
|
if (Regex.IsMatch(str, "(AAAA0Iyd3wEV0RGMegDAT8KX6|AQAAANCMnd8BFdERjHoAwE/Cl+|EAAADQjJ3fARXREYx6AMBPwpfr|01000000D08C9DDF0115D1118C7A00C04FC297EB)"))
|
|
{
|
|
yield return $"{root.Name} ! {(name == "" ? "Default" : name)}";
|
|
}
|
|
|
|
break;
|
|
|
|
case RegistryValueKind.Binary:
|
|
if (ContainsDpapiBlob((byte[])root.GetValue(name)))
|
|
{
|
|
yield return $"{root.Name} ! {(name == "" ? "Default" : name)}";
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
var subkeyNames = new string[] { };
|
|
|
|
try
|
|
{
|
|
subkeyNames = root.GetSubKeyNames();
|
|
}
|
|
catch
|
|
{
|
|
if (reportErrors)
|
|
Console.WriteLine($"ERROR: Could not list subkeys of {root}");
|
|
}
|
|
foreach (var sub in subkeyNames)
|
|
{
|
|
try
|
|
{
|
|
var subkey = root.OpenSubKey(sub);
|
|
toCheck.AddLast(subkey);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (reportErrors)
|
|
Console.WriteLine($"ERROR: Failed to open {root}\\{sub}. Message: {e.Message}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool ContainsDpapiBlob(byte[] bytes)
|
|
{
|
|
//return bytes.Contains(dpapiProviderGuid);
|
|
foreach (var searchBytes in dpapiBlobSearches)
|
|
{
|
|
if (bytes.Contains(searchBytes))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
} |