Merge branch 'master' into update_PEASS-winpeas-Inside_Ink_Dragon__Revealing_the_Rel_20251216_185841

This commit is contained in:
SirBroccoli
2026-01-17 15:29:29 +01:00
committed by GitHub
7 changed files with 1403 additions and 6 deletions
+2 -5
View File
@@ -74,11 +74,6 @@ winpeas.exe -lolbas #Execute also additional LOLBAS search check
The goal of this project is to search for possible **Privilege Escalation Paths** in Windows environments.
New in this version:
- Detect potential GPO abuse by flagging writable SYSVOL paths for GPOs applied to the current host and by highlighting membership in the "Group Policy Creator Owners" group.
- Highlight disconnected high-privilege RDP sessions so you can plan LSASS/token theft when admins leave idle sessions (Ink Dragon-style escalation).
It should take only a **few seconds** to execute almost all the checks and **some seconds/minutes during the lasts checks searching for known filenames** that could contain passwords (the time depened on the number of files in your home folder). By default only **some** filenames that could contain credentials are searched, you can use the **searchall** parameter to search all the list (this could will add some minutes).
The tool is based on **[SeatBelt](https://github.com/GhostPack/Seatbelt)**.
@@ -87,6 +82,7 @@ The tool is based on **[SeatBelt](https://github.com/GhostPack/Seatbelt)**.
- Active Directory quick checks now include:
- gMSA readable managed passwords: enumerate msDS-GroupManagedServiceAccount objects and report those where the current user/group is allowed to retrieve the managed password (PrincipalsAllowedToRetrieveManagedPassword).
- AD object control surfaces: parse ACLs for high-value objects plus a sampled set of users/groups/computers and flag when the current security principal already has GenericAll/GenericWrite/WriteDacl/WriteOwner or attribute-specific rights (SPN, UAC, msDS-AllowedToActOnBehalfOfOtherIdentity, sidHistory, member, unicodePwd, replication) that can be abused for password resets, Kerberoasting, delegation/RBCD, DCSync, or stealth persistence.
- AD CS (ESC4) hygiene: enumerate published certificate templates and highlight templates where the current user/group has dangerous control rights (GenericAll/WriteDacl/WriteOwner/WriteProperty/ExtendedRight) that could allow template abuse (e.g., ESC4 -> ESC1).
These checks are lightweight, read-only, and only run when the host is domain-joined.
@@ -154,6 +150,7 @@ Once you have installed and activated it you need to:
- [x] Applocker Configuration & bypass suggestions
- [x] Printers
- [x] Named Pipes
- [x] Named Pipe ACL abuse candidates
- [x] AMSI Providers
- [x] SysMon
- [x] .NET Versions
@@ -1,11 +1,12 @@
using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text;
using winPEAS.Helpers;
using winPEAS.Helpers.Registry;
using winPEAS.Info.FilesInfo.Certificates;
namespace winPEAS.Checks
{
@@ -19,10 +20,16 @@ namespace winPEAS.Checks
new List<Action>
{
PrintGmsaReadableByCurrentPrincipal,
PrintAdObjectControlPaths,
PrintAdcsMisconfigurations
}.ForEach(action => CheckRunner.Run(action, isDebug));
}
private const int SampleObjectLimit = 120;
private const int MaxFindingsToPrint = 40;
private static readonly Dictionary<Guid, string> GuidNameCache = new Dictionary<Guid, string>();
private static readonly object GuidCacheLock = new object();
private static HashSet<string> GetCurrentSidSet()
{
var sids = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
@@ -65,6 +72,596 @@ namespace winPEAS.Checks
: null;
}
// Highlight objects where the current principal already has useful write/control rights
private void PrintAdObjectControlPaths()
{
try
{
Beaprint.MainPrint("AD object control surfaces");
Beaprint.LinkPrint(
"https://book.hacktricks.wiki/en/windows-hardening/active-directory-methodology/index.html#acl-abuse",
"Look for objects where you have GenericAll/GenericWrite/attribute rights for ACL abuse (password reset, SPN/UAC/RBCD, sidHistory, delegation, DCSync).");
if (!Checks.IsPartOfDomain)
{
Beaprint.GrayPrint(" [-] Host is not domain-joined. Skipping.");
return;
}
var defaultNC = GetRootDseProp("defaultNamingContext");
var schemaNC = GetRootDseProp("schemaNamingContext");
var configNC = GetRootDseProp("configurationNamingContext");
if (string.IsNullOrEmpty(defaultNC))
{
Beaprint.GrayPrint(" [-] Could not resolve defaultNamingContext.");
return;
}
var sidSet = GetCurrentSidSet();
var processedDns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var findings = new List<AdObjectFinding>();
foreach (var target in EnumerateHighValueTargets(defaultNC))
{
var finding = AnalyzeDirectoryObject(target.DistinguishedName, target.Label, sidSet, schemaNC, configNC);
if (finding == null)
{
continue;
}
if (processedDns.Add(finding.DistinguishedName))
{
findings.Add(finding);
}
}
try
{
using (var baseDe = new DirectoryEntry("LDAP://" + defaultNC))
using (var ds = new DirectorySearcher(baseDe))
{
ds.PageSize = 200;
ds.SizeLimit = SampleObjectLimit;
ds.SearchScope = SearchScope.Subtree;
ds.SecurityMasks = SecurityMasks.Dacl;
ds.Filter = "(|(objectClass=user)(objectClass=group)(objectClass=computer))";
ds.PropertiesToLoad.Add("distinguishedName");
ds.PropertiesToLoad.Add("sAMAccountName");
ds.PropertiesToLoad.Add("name");
using (var results = ds.FindAll())
{
foreach (SearchResult r in results)
{
var dn = GetProp(r, "distinguishedName");
if (string.IsNullOrEmpty(dn) || processedDns.Contains(dn))
{
continue;
}
var label = GetProp(r, "sAMAccountName") ?? GetProp(r, "name") ?? dn;
var finding = AnalyzeDirectoryObject(dn, label, sidSet, schemaNC, configNC);
if (finding != null && processedDns.Add(finding.DistinguishedName))
{
findings.Add(finding);
}
}
}
}
}
catch (Exception ex)
{
Beaprint.GrayPrint(" [!] LDAP sampling failed: " + ex.Message);
}
if (findings.Count == 0)
{
Beaprint.GrayPrint(" [-] No impactful ACLs detected for the current principal (sampled set).");
return;
}
var ordered = findings
.OrderByDescending(f => f.MaxScore)
.ThenBy(f => f.DisplayName, StringComparer.OrdinalIgnoreCase)
.ToList();
var truncated = ordered.Count > MaxFindingsToPrint;
if (truncated)
{
ordered = ordered.Take(MaxFindingsToPrint).ToList();
}
Beaprint.GrayPrint($" [+] Found {findings.Count} object(s) where your principal has abuse-friendly rights:");
foreach (var finding in ordered)
{
Beaprint.BadPrint($" -> {finding.DisplayName} ({finding.ClassName})");
Beaprint.GrayPrint(" DN: " + finding.DistinguishedName);
foreach (var impact in finding.Impacts.OrderByDescending(i => i.Score))
{
Beaprint.GrayPrint($" * {impact.Impact}: {impact.Detail}");
}
}
if (truncated)
{
Beaprint.GrayPrint($" [!] Additional {findings.Count - MaxFindingsToPrint} object(s) not shown (enable domain mode or run winPEAS with more time to enumerate all objects).");
}
}
catch (Exception ex)
{
Beaprint.PrintException(ex.Message);
}
}
private static IEnumerable<(string Label, string DistinguishedName)> EnumerateHighValueTargets(string defaultNC)
{
return new List<(string, string)>
{
("Domain Root", defaultNC),
("AdminSDHolder", $"CN=AdminSDHolder,CN=System,{defaultNC}"),
("Domain Controllers OU", $"OU=Domain Controllers,{defaultNC}"),
("Domain Controllers group", $"CN=Domain Controllers,CN=Users,{defaultNC}"),
("Domain Admins", $"CN=Domain Admins,CN=Users,{defaultNC}"),
("Enterprise Admins", $"CN=Enterprise Admins,CN=Users,{defaultNC}"),
("Schema Admins", $"CN=Schema Admins,CN=Users,{defaultNC}"),
("Administrators", $"CN=Administrators,CN=Builtin,{defaultNC}"),
("Account Operators", $"CN=Account Operators,CN=Builtin,{defaultNC}"),
("Backup Operators", $"CN=Backup Operators,CN=Builtin,{defaultNC}"),
("Group Policy Creator Owners", $"CN=Group Policy Creator Owners,CN=Users,{defaultNC}"),
("krbtgt", $"CN=krbtgt,CN=Users,{defaultNC}")
};
}
private static AdObjectFinding AnalyzeDirectoryObject(string dn, string label, HashSet<string> sidSet, string schemaNC, string configNC)
{
if (string.IsNullOrEmpty(dn))
{
return null;
}
try
{
using (var entry = new DirectoryEntry("LDAP://" + dn))
{
entry.Options.SecurityMasks = SecurityMasks.Owner | SecurityMasks.Dacl;
entry.RefreshCache();
return EvaluateSecurity(entry, label ?? dn, sidSet, schemaNC, configNC);
}
}
catch (Exception)
{
return null;
}
}
private static AdObjectFinding EvaluateSecurity(DirectoryEntry entry, string label, HashSet<string> sidSet, string schemaNC, string configNC)
{
ActiveDirectorySecurity security;
try
{
security = entry.ObjectSecurity;
}
catch
{
return null;
}
if (security == null)
{
return null;
}
var finding = new AdObjectFinding
{
DisplayName = label ?? entry.Name,
DistinguishedName = entry.Properties?["distinguishedName"]?.Value as string ?? entry.Path,
ClassName = entry.SchemaClassName ?? "object"
};
var seenImpacts = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
try
{
var ownerSid = security.GetOwner(typeof(SecurityIdentifier)) as SecurityIdentifier;
if (ownerSid != null && sidSet.Contains(ownerSid.Value))
{
var impact = new AdAccessImpact
{
Impact = "Object owner",
Detail = "You own this object and can rewrite its ACL to grant full control.",
Score = 3
};
finding.Impacts.Add(impact);
seenImpacts.Add(impact.Impact);
}
}
catch
{
// ignore owner lookup issues
}
AuthorizationRuleCollection rules;
try
{
rules = security.GetAccessRules(true, true, typeof(SecurityIdentifier));
}
catch
{
return finding.Impacts.Count > 0 ? finding : null;
}
foreach (ActiveDirectoryAccessRule rule in rules)
{
if (rule == null || rule.AccessControlType != AccessControlType.Allow)
{
continue;
}
if (!(rule.IdentityReference is SecurityIdentifier sid))
{
continue;
}
if (!sidSet.Contains(sid.Value))
{
continue;
}
foreach (var impact in MapRuleToImpacts(rule, schemaNC, configNC))
{
if (impact == null)
{
continue;
}
var key = impact.Impact + "|" + impact.Detail;
if (seenImpacts.Add(key))
{
finding.Impacts.Add(impact);
}
}
}
return finding.Impacts.Count > 0 ? finding : null;
}
private static IEnumerable<AdAccessImpact> MapRuleToImpacts(ActiveDirectoryAccessRule rule, string schemaNC, string configNC)
{
var impacts = new List<AdAccessImpact>();
var rights = rule.ActiveDirectoryRights;
if ((rights & ActiveDirectoryRights.GenericAll) != 0)
{
impacts.Add(new AdAccessImpact
{
Impact = "GenericAll",
Detail = "Full control -> reset password, add group members, edit SPNs/UAC, change ACLs.",
Score = 5
});
return impacts;
}
if ((rights & ActiveDirectoryRights.GenericWrite) != 0)
{
impacts.Add(new AdAccessImpact
{
Impact = "GenericWrite",
Detail = "Can modify most attributes (logon scripts, SPNs, UAC, etc.).",
Score = 4
});
}
if ((rights & ActiveDirectoryRights.WriteDacl) != 0)
{
impacts.Add(new AdAccessImpact
{
Impact = "WriteDACL",
Detail = "Can edit the ACL to grant yourself additional rights/persistence.",
Score = 4
});
}
if ((rights & ActiveDirectoryRights.WriteOwner) != 0)
{
impacts.Add(new AdAccessImpact
{
Impact = "WriteOwner",
Detail = "Can take ownership and then modify the DACL.",
Score = 3
});
}
if ((rights & ActiveDirectoryRights.CreateChild) != 0)
{
impacts.Add(new AdAccessImpact
{
Impact = "CreateChild",
Detail = "Can create new users/computers/groups under this container (great for planting attack principals).",
Score = 3
});
}
if ((rights & ActiveDirectoryRights.ExtendedRight) != 0)
{
var extImpact = MapExtendedRightImpact(rule.ObjectType, schemaNC, configNC);
if (extImpact != null)
{
impacts.Add(extImpact);
}
}
if ((rights & ActiveDirectoryRights.WriteProperty) != 0)
{
var attrImpact = MapAttributeWriteImpact(rule.ObjectType, schemaNC, configNC, false);
if (attrImpact != null)
{
impacts.Add(attrImpact);
}
}
if ((rights & ActiveDirectoryRights.Self) != 0)
{
var validatedImpact = MapAttributeWriteImpact(rule.ObjectType, schemaNC, configNC, true);
if (validatedImpact != null)
{
impacts.Add(validatedImpact);
}
}
return impacts;
}
private static AdAccessImpact MapExtendedRightImpact(Guid objectType, string schemaNC, string configNC)
{
if (objectType == Guid.Empty)
{
return null;
}
var name = GetGuidFriendlyName(objectType, schemaNC, configNC)?.ToLowerInvariant();
if (string.IsNullOrEmpty(name))
{
return null;
}
if (name.Contains("reset password") || name.Contains("user-force-change-password"))
{
return new AdAccessImpact
{
Impact = "ResetPassword right",
Detail = "Can reset the target account password without knowing the current value.",
Score = 5
};
}
if (name.Contains("replicating directory changes"))
{
return new AdAccessImpact
{
Impact = "Replication (DCSync)",
Detail = "Has replication rights (part of DCSync privilege to dump NTDS hashes).",
Score = name.Contains("filtered") ? 5 : 4
};
}
return null;
}
private static AdAccessImpact MapAttributeWriteImpact(Guid objectType, string schemaNC, string configNC, bool validatedWrite)
{
if (objectType == Guid.Empty)
{
return new AdAccessImpact
{
Impact = validatedWrite ? "Validated write (broad)" : "WriteProperty (broad)",
Detail = "ACE applies to most attributes. Consider SPN/UAC/sidHistory abuse paths.",
Score = 3
};
}
var attributeName = GetGuidFriendlyName(objectType, schemaNC, configNC);
if (string.IsNullOrEmpty(attributeName))
{
return null;
}
var lower = attributeName.ToLowerInvariant();
if (lower.Contains("member"))
{
return new AdAccessImpact
{
Impact = "Group membership control",
Detail = "Can edit the 'member' attribute -> add principals to this group.",
Score = 5
};
}
if (lower.Contains("serviceprincipalname") || lower.Contains("validated-spn"))
{
return new AdAccessImpact
{
Impact = "SPN control",
Detail = "Can set servicePrincipalName -> Kerberoast or constrained delegation abuse.",
Score = 4
};
}
if (lower.Contains("useraccountcontrol"))
{
return new AdAccessImpact
{
Impact = "UAC control",
Detail = "Can toggle UserAccountControl bits (AS-REP roastable, delegation, unconstrained).",
Score = 4
};
}
if (lower.Contains("msds-allowedtoactonbehalfofotheridentity"))
{
return new AdAccessImpact
{
Impact = "RBCD control",
Detail = "Can edit msDS-AllowedToActOnBehalfOfOtherIdentity -> configure Resource-Based Constrained Delegation.",
Score = 5
};
}
if (lower.Contains("msds-allowedtodelegateto"))
{
return new AdAccessImpact
{
Impact = "Delegation target control",
Detail = "Can edit msDS-AllowedToDelegateTo -> establish constrained delegation paths.",
Score = 4
};
}
if (lower.Contains("sidhistory"))
{
return new AdAccessImpact
{
Impact = "sidHistory control",
Detail = "Can add privileged SIDs into sidHistory for stealth escalation/persistence.",
Score = 4
};
}
if (lower.Contains("unicodepwd") || lower.Contains("userpassword"))
{
return new AdAccessImpact
{
Impact = "Password write",
Detail = "Can directly set unicodePwd/userPassword -> immediate account takeover.",
Score = 5
};
}
return null;
}
private static string GetGuidFriendlyName(Guid guid, string schemaNC, string configNC)
{
if (guid == Guid.Empty)
{
return null;
}
lock (GuidCacheLock)
{
if (GuidNameCache.TryGetValue(guid, out var cached))
{
return cached;
}
}
string resolved = null;
if (!string.IsNullOrEmpty(schemaNC))
{
resolved = LookupGuidInSchema(guid, schemaNC);
}
if (resolved == null && !string.IsNullOrEmpty(configNC))
{
resolved = LookupGuidInExtendedRights(guid, configNC);
}
if (string.IsNullOrEmpty(resolved))
{
resolved = guid.ToString();
}
lock (GuidCacheLock)
{
if (!GuidNameCache.ContainsKey(guid))
{
GuidNameCache[guid] = resolved;
}
return GuidNameCache[guid];
}
}
private static string LookupGuidInSchema(Guid guid, string schemaNC)
{
try
{
using (var schema = new DirectoryEntry("LDAP://" + schemaNC))
using (var searcher = new DirectorySearcher(schema))
{
searcher.Filter = $"(schemaIDGUID={GuidToLdapFilter(guid)})";
searcher.PropertiesToLoad.Add("lDAPDisplayName");
searcher.PropertiesToLoad.Add("name");
var result = searcher.FindOne();
if (result != null)
{
return GetProp(result, "lDAPDisplayName") ?? GetProp(result, "name");
}
}
}
catch
{
// ignore schema lookup errors
}
return null;
}
private static string LookupGuidInExtendedRights(Guid guid, string configNC)
{
try
{
var extendedRightsDn = $"CN=Extended-Rights,{configNC}";
using (var rights = new DirectoryEntry("LDAP://" + extendedRightsDn))
using (var searcher = new DirectorySearcher(rights))
{
searcher.Filter = $"(rightsGuid={guid})";
searcher.PropertiesToLoad.Add("displayName");
searcher.PropertiesToLoad.Add("name");
var result = searcher.FindOne();
if (result != null)
{
return GetProp(result, "displayName") ?? GetProp(result, "name");
}
}
}
catch
{
// ignore extended rights lookup issues
}
return null;
}
private static string GuidToLdapFilter(Guid guid)
{
var bytes = guid.ToByteArray();
var sb = new StringBuilder();
foreach (var b in bytes)
{
sb.Append($"\\{b:X2}");
}
return sb.ToString();
}
private class AdObjectFinding
{
public string DisplayName { get; set; }
public string DistinguishedName { get; set; }
public string ClassName { get; set; }
public List<AdAccessImpact> Impacts { get; } = new List<AdAccessImpact>();
public int MaxScore => Impacts.Count == 0 ? 0 : Impacts.Max(i => i.Score);
}
private class AdAccessImpact
{
public string Impact { get; set; }
public string Detail { get; set; }
public int Score { get; set; }
}
// Detect gMSA objects where the current principal (or one of its groups) can retrieve the managed password
private void PrintGmsaReadableByCurrentPrincipal()
{
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using winPEAS.Helpers;
using winPEAS.Helpers.Registry;
using winPEAS.Info.ServicesInfo;
namespace winPEAS.Checks
@@ -34,6 +36,8 @@ namespace winPEAS.Checks
PrintModifiableServices,
PrintWritableRegServices,
PrintPathDllHijacking,
PrintLegacySignedKernelDrivers,
PrintKernelQuickIndicators,
}.ForEach(action => CheckRunner.Run(action, isDebug));
}
@@ -206,5 +210,146 @@ namespace winPEAS.Checks
}
}
void PrintLegacySignedKernelDrivers()
{
try
{
Beaprint.MainPrint("Kernel drivers with weak/legacy signatures");
Beaprint.LinkPrint("https://research.checkpoint.com/2025/cracking-valleyrat-from-builder-secrets-to-kernel-rootkits/",
"Legacy cross-signed drivers (pre-July-2015) can still grant kernel execution on modern Windows");
List<ServicesInfoHelper.KernelDriverInfo> drivers = ServicesInfoHelper.GetKernelDriverInfos();
if (drivers.Count == 0)
{
Beaprint.InfoPrint(" Unable to enumerate kernel services");
return;
}
var suspiciousDrivers = drivers.Where(d => d.Signature != null && (!d.Signature.IsSigned || d.Signature.IsLegacyExpired))
.OrderBy(d => d.Name)
.ToList();
if (suspiciousDrivers.Count == 0)
{
Beaprint.InfoPrint(" No unsigned or legacy-signed kernel drivers detected");
return;
}
foreach (var driver in suspiciousDrivers)
{
var signature = driver.Signature ?? new ServicesInfoHelper.KernelDriverSignatureInfo();
List<string> reasons = new List<string>();
if (!signature.IsSigned)
{
reasons.Add("unsigned or signature missing");
}
else if (signature.IsLegacyExpired)
{
reasons.Add("signed with certificate that expired before 29-Jul-2015 (legacy exception)");
}
if (!string.IsNullOrEmpty(driver.StartMode) &&
(driver.StartMode.Equals("System", StringComparison.OrdinalIgnoreCase) ||
driver.StartMode.Equals("Boot", StringComparison.OrdinalIgnoreCase)))
{
reasons.Add($"loads at early boot (Start={driver.StartMode})");
}
if (string.Equals(driver.Name, "kernelquick", StringComparison.OrdinalIgnoreCase))
{
reasons.Add("service name matches ValleyRAT rootkit loader");
}
string reason = reasons.Count > 0 ? string.Join("; ", reasons) : "Potentially risky driver";
string signatureLine = signature.IsSigned
? $"Subject: {signature.Subject}; Issuer: {signature.Issuer}; Valid: {FormatDate(signature.NotBefore)} - {FormatDate(signature.NotAfter)}"
: $"Signature issue: {signature.Error ?? "Unsigned"}";
Beaprint.BadPrint($" {driver.Name} ({driver.DisplayName})");
Beaprint.NoColorPrint($" Path : {driver.PathName}");
Beaprint.NoColorPrint($" Start/State: {driver.StartMode}/{driver.State}");
Beaprint.NoColorPrint($" Reason : {reason}");
Beaprint.NoColorPrint($" Signature : {signatureLine}");
}
}
catch (Exception ex)
{
Beaprint.PrintException(ex.Message);
}
}
void PrintKernelQuickIndicators()
{
try
{
Beaprint.MainPrint("KernelQuick / ValleyRAT rootkit indicators");
bool found = false;
Dictionary<string, object> serviceValues = RegistryHelper.GetRegValues("HKLM", @"SYSTEM\\CurrentControlSet\\Services\\kernelquick");
if (serviceValues != null)
{
found = true;
string imagePath = serviceValues.ContainsKey("ImagePath") ? serviceValues["ImagePath"].ToString() : "Unknown";
string start = serviceValues.ContainsKey("Start") ? serviceValues["Start"].ToString() : "Unknown";
Beaprint.BadPrint(" Service HKLM\\SYSTEM\\CurrentControlSet\\Services\\kernelquick present");
Beaprint.NoColorPrint($" ImagePath : {imagePath}");
Beaprint.NoColorPrint($" Start : {start}");
}
foreach (var path in new[] { @"SOFTWARE\\KernelQuick", @"SOFTWARE\\WOW6432Node\\KernelQuick", @"SYSTEM\\CurrentControlSet\\Services\\kernelquick" })
{
Dictionary<string, object> values = RegistryHelper.GetRegValues("HKLM", path);
if (values == null)
continue;
var kernelQuickValues = values.Where(k => k.Key.StartsWith("KernelQuick_", StringComparison.OrdinalIgnoreCase)).ToList();
if (kernelQuickValues.Count == 0)
continue;
found = true;
Beaprint.BadPrint($" Registry values under HKLM\\{path}");
foreach (var kv in kernelQuickValues)
{
string displayValue = kv.Value is byte[] bytes ? $"(binary) {bytes.Length} bytes" : string.Format("{0}", kv.Value);
Beaprint.NoColorPrint($" {kv.Key} = {displayValue}");
}
}
Dictionary<string, object> ipdatesValues = RegistryHelper.GetRegValues("HKLM", @"SOFTWARE\\IpDates");
if (ipdatesValues != null)
{
found = true;
Beaprint.BadPrint(" Possible kernel shellcode staging key HKLM\\SOFTWARE\\IpDates");
foreach (var kv in ipdatesValues)
{
string displayValue = kv.Value is byte[] bytes ? $"(binary) {bytes.Length} bytes" : string.Format("{0}", kv.Value);
Beaprint.NoColorPrint($" {kv.Key} = {displayValue}");
}
}
if (!found)
{
Beaprint.InfoPrint(" No KernelQuick-specific registry indicators were found");
}
else
{
Beaprint.LinkPrint("https://research.checkpoint.com/2025/cracking-valleyrat-from-builder-secrets-to-kernel-rootkits/",
"KernelQuick_* values and HKLM\\SOFTWARE\\IpDates are used by the ValleyRAT rootkit to hide files and stage APC payloads");
}
}
catch (Exception ex)
{
Beaprint.PrintException(ex.Message);
}
}
private string FormatDate(DateTime? dateTime)
{
return dateTime.HasValue ? dateTime.Value.ToString("yyyy-MM-dd HH:mm") : "n/a";
}
}
}
@@ -89,6 +89,7 @@ namespace winPEAS.Checks
AppLockerHelper.PrintAppLockerPolicy,
PrintPrintersWMIInfo,
PrintNamedPipes,
PrintNamedPipeAbuseCandidates,
PrintAMSIProviders,
PrintSysmon,
PrintDotNetVersions
@@ -857,6 +858,48 @@ namespace winPEAS.Checks
}
}
private static void PrintNamedPipeAbuseCandidates()
{
Beaprint.MainPrint("Named Pipes with Low-Priv Write Access to Privileged Servers");
try
{
var candidates = NamedPipeSecurityAnalyzer.GetNamedPipeAbuseCandidates().ToList();
if (!candidates.Any())
{
Beaprint.NoColorPrint(" No risky named pipe ACLs were found.\n");
return;
}
foreach (var candidate in candidates)
{
var aclSummary = candidate.LowPrivilegeAces.Any()
? string.Join("; ", candidate.LowPrivilegeAces.Select(ace =>
$"{ace.Principal} [{ace.RightsDescription}]").Where(s => !string.IsNullOrEmpty(s)))
: "Unknown";
var serverSummary = candidate.Processes.Any()
? string.Join("; ", candidate.Processes.Select(proc =>
$"{proc.ProcessName} (PID {proc.Pid}, {proc.UserName ?? proc.UserSid})"))
: "No privileged handles observed (service idle or access denied)";
var color = candidate.HasPrivilegedServer ? Beaprint.ansi_color_bad : Beaprint.ansi_color_yellow;
Beaprint.ColorPrint($" \\\\.\\pipe\\{candidate.Name}", color);
Beaprint.NoColorPrint($" Low-priv ACLs : {aclSummary}");
Beaprint.NoColorPrint($" Observed owners: {serverSummary}");
Beaprint.NoColorPrint($" SDDL : {candidate.Sddl}");
Beaprint.PrintLineSeparator();
}
}
catch (Exception ex)
{
Beaprint.PrintException(ex.Message);
}
}
private void PrintAMSIProviders()
{
Beaprint.MainPrint("Enumerating AMSI registered providers");
@@ -2,11 +2,14 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.ServiceProcess;
using System.Text.RegularExpressions;
using winPEAS.Helpers;
@@ -276,6 +279,109 @@ namespace winPEAS.Info.ServicesInfo
}
private static readonly DateTime LegacyDriverCutoff = new DateTime(2015, 7, 29);
public static List<KernelDriverInfo> GetKernelDriverInfos()
{
List<KernelDriverInfo> drivers = new List<KernelDriverInfo>();
try
{
using (ManagementObjectSearcher wmiData = new ManagementObjectSearcher(@"root\cimv2", "SELECT Name,DisplayName,PathName,StartMode,State,ServiceType FROM win32_service"))
{
using (ManagementObjectCollection data = wmiData.Get())
{
foreach (ManagementObject result in data)
{
string serviceType = GetStringOrEmpty(result["ServiceType"]);
if (string.IsNullOrEmpty(serviceType) || !serviceType.ToLowerInvariant().Contains("kernel driver"))
continue;
string binaryPath = MyUtils.ReconstructExecPath(GetStringOrEmpty(result["PathName"]));
drivers.Add(new KernelDriverInfo
{
Name = GetStringOrEmpty(result["Name"]),
DisplayName = GetStringOrEmpty(result["DisplayName"]),
StartMode = GetStringOrEmpty(result["StartMode"]),
State = GetStringOrEmpty(result["State"]),
PathName = binaryPath,
Signature = GetDriverSignatureInfo(binaryPath)
});
}
}
}
}
catch (Exception ex)
{
Beaprint.PrintException(ex.Message);
}
return drivers;
}
private static KernelDriverSignatureInfo GetDriverSignatureInfo(string binaryPath)
{
KernelDriverSignatureInfo info = new KernelDriverSignatureInfo
{
FilePath = binaryPath,
IsSigned = false
};
if (string.IsNullOrEmpty(binaryPath) || !File.Exists(binaryPath))
{
info.Error = "Binary not found";
return info;
}
try
{
using (var baseCertificate = X509Certificate.CreateFromSignedFile(binaryPath))
using (var certificate = new X509Certificate2(baseCertificate))
{
info.IsSigned = true;
info.Subject = certificate.Subject;
info.Issuer = certificate.Issuer;
info.NotBefore = certificate.NotBefore;
info.NotAfter = certificate.NotAfter;
info.IsLegacyExpired = certificate.NotAfter < LegacyDriverCutoff;
}
}
catch (CryptographicException cryptoEx)
{
info.Error = cryptoEx.Message;
}
catch (Exception ex)
{
info.Error = ex.Message;
}
return info;
}
internal class KernelDriverInfo
{
public string Name { get; set; }
public string DisplayName { get; set; }
public string PathName { get; set; }
public string StartMode { get; set; }
public string State { get; set; }
public KernelDriverSignatureInfo Signature { get; set; }
}
internal class KernelDriverSignatureInfo
{
public string FilePath { get; set; }
public bool IsSigned { get; set; }
public string Subject { get; set; }
public string Issuer { get; set; }
public DateTime? NotBefore { get; set; }
public DateTime? NotAfter { get; set; }
public bool IsLegacyExpired { get; set; }
public string Error { get; set; }
}
//////////////////////////////////////
//////// PATH DLL Hijacking /////////
//////////////////////////////////////
@@ -0,0 +1,508 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Principal;
using winPEAS.Helpers;
using winPEAS.Native;
namespace winPEAS.Info.SystemInfo.NamedPipes
{
internal static class NamedPipeSecurityAnalyzer
{
private const string DeviceNamedPipePrefix = @"\Device\NamedPipe\";
private static readonly char[] CandidateSeparators = { '\\', '/', '-', ':', '(' };
private static readonly HashSet<string> LowPrivSidSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"S-1-1-0", // Everyone
"S-1-5-11", // Authenticated Users
"S-1-5-32-545", // Users
"S-1-5-32-546", // Guests
"S-1-5-32-547", // Power Users
"S-1-5-32-554", // Pre-Windows 2000 Compatible Access
"S-1-5-32-555", // Remote Desktop Users
"S-1-5-32-558", // Performance Log Users
"S-1-5-32-559", // Performance Monitor Users
"S-1-5-32-562", // Distributed COM Users
"S-1-5-32-569", // Remote Management Users
"S-1-5-4", // Interactive
"S-1-5-2", // Network
"S-1-5-1", // Dialup
"S-1-5-7" // Anonymous Logon
};
private static readonly HashSet<string> LowPrivPrincipalKeywords = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"everyone",
"authenticated users",
"users",
"guests",
"power users",
"remote desktop users",
"remote management users",
"distributed com users",
"anonymous logon",
"interactive",
"network",
"local",
"batch",
"iis_iusrs"
};
private static readonly HashSet<string> PrivilegedSidSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"S-1-5-18", // SYSTEM
"S-1-5-19", // LOCAL SERVICE
"S-1-5-20", // NETWORK SERVICE
"S-1-5-32-544" // Administrators
};
private static readonly (string Label, FileSystemRights Right)[] DangerousRightsMap = new[]
{
("FullControl", FileSystemRights.FullControl),
("Modify", FileSystemRights.Modify),
("Write", FileSystemRights.Write),
("WriteData", FileSystemRights.WriteData),
("AppendData", FileSystemRights.AppendData),
("CreateFiles", FileSystemRights.CreateFiles),
("CreateDirectories", FileSystemRights.CreateDirectories),
("WriteAttributes", FileSystemRights.WriteAttributes),
("WriteExtendedAttributes", FileSystemRights.WriteExtendedAttributes),
("Delete", FileSystemRights.Delete),
("ChangePermissions", FileSystemRights.ChangePermissions),
("TakeOwnership", FileSystemRights.TakeOwnership)
};
public static IEnumerable<NamedPipeSecurityIssue> GetNamedPipeAbuseCandidates()
{
var insecurePipes = DiscoverInsecurePipes();
if (!insecurePipes.Any())
{
return Enumerable.Empty<NamedPipeSecurityIssue>();
}
AttachProcesses(insecurePipes);
return insecurePipes.Values
.Where(issue => issue.LowPrivilegeAces.Any())
.OrderByDescending(issue => issue.HasPrivilegedServer)
.ThenBy(issue => issue.Name)
.ToList();
}
private static Dictionary<string, NamedPipeSecurityIssue> DiscoverInsecurePipes()
{
var result = new Dictionary<string, NamedPipeSecurityIssue>(StringComparer.OrdinalIgnoreCase);
foreach (var pipe in NamedPipes.GetNamedPipeInfos())
{
if (string.IsNullOrWhiteSpace(pipe.Sddl) || pipe.Sddl.Equals("ERROR", StringComparison.OrdinalIgnoreCase))
continue;
try
{
var descriptor = new RawSecurityDescriptor(pipe.Sddl);
if (descriptor.DiscretionaryAcl == null)
continue;
foreach (GenericAce ace in descriptor.DiscretionaryAcl)
{
if (!(ace is CommonAce commonAce))
continue;
var sid = commonAce.SecurityIdentifier;
if (sid == null || !IsLowPrivilegePrincipal(sid))
continue;
if (!HasDangerousWriteRights(commonAce.AccessMask))
continue;
var rights = DescribeRights(commonAce.AccessMask).ToList();
if (!rights.Any())
continue;
if (!result.TryGetValue(pipe.Name, out var issue))
{
issue = new NamedPipeSecurityIssue(pipe.Name, pipe.Sddl, NormalizePipeName(pipe.Name));
result[pipe.Name] = issue;
}
var account = ResolveSidToName(sid);
issue.AddLowPrivPrincipal(account, sid.Value, rights);
}
}
catch
{
// Ignore malformed SDDL strings
}
}
return result;
}
private static void AttachProcesses(Dictionary<string, NamedPipeSecurityIssue> insecurePipes)
{
if (!insecurePipes.Any())
return;
var lookup = BuildLookup(insecurePipes.Values);
if (!lookup.Any())
return;
List<HandlesHelper.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> handles;
try
{
handles = HandlesHelper.GetAllHandlers();
}
catch
{
return;
}
var currentProcess = Kernel32.GetCurrentProcess();
var processCache = new Dictionary<int, NamedPipeProcessInfo>();
foreach (var handle in handles)
{
IntPtr processHandle = IntPtr.Zero;
IntPtr duplicatedHandle = IntPtr.Zero;
try
{
int pid = GetPid(handle);
if (pid <= 0)
continue;
processHandle = Kernel32.OpenProcess(
HandlesHelper.ProcessAccessFlags.DupHandle | HandlesHelper.ProcessAccessFlags.QueryLimitedInformation,
false,
pid);
if (processHandle == IntPtr.Zero)
continue;
if (!Kernel32.DuplicateHandle(processHandle, handle.HandleValue, currentProcess, out duplicatedHandle, 0, false, HandlesHelper.DUPLICATE_SAME_ACCESS))
continue;
var typeName = HandlesHelper.GetObjectType(duplicatedHandle);
if (!string.Equals(typeName, "File", StringComparison.OrdinalIgnoreCase))
continue;
var objectName = HandlesHelper.GetObjectName(duplicatedHandle);
if (string.IsNullOrEmpty(objectName) || !objectName.StartsWith(DeviceNamedPipePrefix, StringComparison.OrdinalIgnoreCase))
continue;
var normalizedHandleName = NormalizePipeName(objectName.Substring(DeviceNamedPipePrefix.Length));
var candidates = GetCandidateKeys(normalizedHandleName);
bool matched = false;
foreach (var candidate in candidates)
{
if (!lookup.TryGetValue(candidate, out var matchedIssues))
continue;
if (!processCache.TryGetValue(pid, out var processInfo))
{
var raw = HandlesHelper.getProcInfoById(pid);
processInfo = new NamedPipeProcessInfo(raw.pid, raw.name, raw.userName, raw.userSid, IsHighPrivilegeAccount(raw.userSid, raw.userName));
processCache[pid] = processInfo;
}
foreach (var issue in matchedIssues)
{
issue.AddProcess(processInfo);
}
matched = true;
break;
}
if (!matched)
continue;
}
catch
{
// Ignore per-handle failures
}
finally
{
if (duplicatedHandle != IntPtr.Zero)
{
Kernel32.CloseHandle(duplicatedHandle);
}
if (processHandle != IntPtr.Zero)
{
Kernel32.CloseHandle(processHandle);
}
}
}
}
private static Dictionary<string, List<NamedPipeSecurityIssue>> BuildLookup(IEnumerable<NamedPipeSecurityIssue> issues)
{
var lookup = new Dictionary<string, List<NamedPipeSecurityIssue>>(StringComparer.OrdinalIgnoreCase);
foreach (var issue in issues)
{
foreach (var key in GetCandidateKeys(issue.NormalizedName))
{
if (!lookup.TryGetValue(key, out var list))
{
list = new List<NamedPipeSecurityIssue>();
lookup[key] = list;
}
if (!list.Contains(issue))
{
list.Add(issue);
}
}
}
return lookup;
}
private static IEnumerable<string> GetCandidateKeys(string normalizedName)
{
if (string.IsNullOrEmpty(normalizedName))
return Array.Empty<string>();
var candidates = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
normalizedName
};
foreach (var separator in CandidateSeparators)
{
var idx = normalizedName.IndexOf(separator);
if (idx > 0)
{
candidates.Add(normalizedName.Substring(0, idx));
}
}
return candidates;
}
private static string NormalizePipeName(string rawName)
{
if (string.IsNullOrWhiteSpace(rawName))
return string.Empty;
var normalized = rawName.Replace('/', '\\').Trim();
while (normalized.StartsWith("\\", StringComparison.Ordinal))
{
normalized = normalized.Substring(1);
}
return normalized.ToLowerInvariant();
}
private static bool HasDangerousWriteRights(int accessMask)
{
var rights = (FileSystemRights)accessMask;
foreach (var entry in DangerousRightsMap)
{
if ((rights & entry.Right) == entry.Right)
return true;
}
return false;
}
private static IEnumerable<string> DescribeRights(int accessMask)
{
var rights = (FileSystemRights)accessMask;
var descriptions = new List<string>();
foreach (var entry in DangerousRightsMap)
{
if ((rights & entry.Right) == entry.Right)
{
descriptions.Add(entry.Label);
if (entry.Right == FileSystemRights.FullControl)
break;
}
}
if (!descriptions.Any())
{
descriptions.Add($"0x{accessMask:x}");
}
return descriptions;
}
private static bool IsLowPrivilegePrincipal(SecurityIdentifier sid)
{
if (sid == null)
return false;
if (LowPrivSidSet.Contains(sid.Value))
return true;
var accountName = ResolveSidToName(sid);
if (string.IsNullOrEmpty(accountName))
return false;
return LowPrivPrincipalKeywords.Any(keyword => accountName.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0);
}
private static string ResolveSidToName(SecurityIdentifier sid)
{
if (sid == null)
return string.Empty;
try
{
return sid.Translate(typeof(NTAccount)).Value;
}
catch
{
return sid.Value;
}
}
private static bool IsHighPrivilegeAccount(string sid, string userName)
{
if (!string.IsNullOrEmpty(sid))
{
if (PrivilegedSidSet.Contains(sid))
return true;
if (sid.StartsWith("S-1-5-80-", StringComparison.OrdinalIgnoreCase)) // Service SID
return true;
if (sid.StartsWith("S-1-5-82-", StringComparison.OrdinalIgnoreCase)) // AppPool / service-like SIDs
return true;
}
if (!string.IsNullOrEmpty(userName))
{
if (string.Equals(userName, HandlesHelper.elevatedProcess, StringComparison.OrdinalIgnoreCase))
return true;
var normalized = userName.ToUpperInvariant();
if (normalized.Contains("SYSTEM") || normalized.Contains("LOCAL SERVICE") || normalized.Contains("NETWORK SERVICE"))
return true;
if (normalized.StartsWith("NT SERVICE\\", StringComparison.Ordinal))
return true;
if (normalized.EndsWith("$", StringComparison.Ordinal) && normalized.Contains("\\"))
return true;
}
return false;
}
private static int GetPid(HandlesHelper.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX handle)
{
unchecked
{
if (IntPtr.Size == 4)
{
return (int)handle.UniqueProcessId.ToUInt32();
}
return (int)handle.UniqueProcessId.ToUInt64();
}
}
}
internal class NamedPipeSecurityIssue
{
private readonly Dictionary<string, NamedPipePrincipalAccess> _principalAccess = new Dictionary<string, NamedPipePrincipalAccess>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<int, NamedPipeProcessInfo> _processes = new Dictionary<int, NamedPipeProcessInfo>();
public NamedPipeSecurityIssue(string name, string sddl, string normalizedName)
{
Name = name;
Sddl = sddl;
NormalizedName = normalizedName;
}
public string Name { get; }
public string Sddl { get; }
public string NormalizedName { get; }
public IReadOnlyCollection<NamedPipePrincipalAccess> LowPrivilegeAces => _principalAccess.Values;
public IReadOnlyCollection<NamedPipeProcessInfo> Processes => _processes.Values;
public bool HasPrivilegedServer => _processes.Values.Any(process => process.IsHighPrivilege);
public void AddLowPrivPrincipal(string principal, string sid, IEnumerable<string> rights)
{
if (string.IsNullOrEmpty(sid))
return;
if (!_principalAccess.TryGetValue(sid, out var access))
{
access = new NamedPipePrincipalAccess(principal, sid);
_principalAccess[sid] = access;
}
access.AddRights(rights);
}
public void AddProcess(NamedPipeProcessInfo process)
{
if (process == null)
return;
if (!_processes.ContainsKey(process.Pid))
{
_processes[process.Pid] = process;
}
}
}
internal class NamedPipePrincipalAccess
{
private readonly HashSet<string> _rights = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
public NamedPipePrincipalAccess(string principal, string sid)
{
Principal = principal;
Sid = sid;
}
public string Principal { get; }
public string Sid { get; }
public string RightsDescription => _rights.Count == 0 ? string.Empty : string.Join("|", _rights.OrderBy(r => r));
public IEnumerable<string> Rights => _rights;
public void AddRights(IEnumerable<string> rights)
{
if (rights == null)
return;
foreach (var right in rights)
{
if (!string.IsNullOrWhiteSpace(right))
{
_rights.Add(right.Trim());
}
}
}
}
internal class NamedPipeProcessInfo
{
public NamedPipeProcessInfo(int pid, string processName, string userName, string userSid, bool isHighPrivilege)
{
Pid = pid;
ProcessName = processName;
UserName = userName;
UserSid = userSid;
IsHighPrivilege = isHighPrivilege;
}
public int Pid { get; }
public string ProcessName { get; }
public string UserName { get; }
public string UserSid { get; }
public bool IsHighPrivilege { get; }
}
}
@@ -1291,6 +1291,7 @@
<Compile Include="Info\SystemInfo\GroupPolicy\GroupPolicy.cs" />
<Compile Include="Info\SystemInfo\GroupPolicy\LocalGroupPolicyInfo.cs" />
<Compile Include="Info\SystemInfo\NamedPipes\NamedPipeInfo.cs" />
<Compile Include="Info\SystemInfo\NamedPipes\NamedPipeSecurityAnalyzer.cs" />
<Compile Include="Info\SystemInfo\NamedPipes\NamedPipes.cs" />
<Compile Include="Info\SystemInfo\Ntlm\Ntlm.cs" />
<Compile Include="Info\SystemInfo\Ntlm\NtlmSettingsInfo.cs" />