From 3268701ed6cba3320e40f191974497bdaabb2f8e Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Wed, 17 Dec 2025 02:00:18 +0000 Subject: [PATCH] Add winpeas privilege escalation checks from: The Windows Registry Adventure, Part 8: Exploitation of Hive-based Memory Corrup --- winPEAS/winPEASexe/README.md | 1 + winPEAS/winPEASexe/winPEAS/Checks/Checks.cs | 1 + .../winPEASexe/winPEAS/Checks/RegistryInfo.cs | 141 +++++++++++ .../winPEASexe/winPEAS/Helpers/Beaprint.cs | 1 + .../Helpers/Registry/RegistryAclScanner.cs | 221 ++++++++++++++++++ winPEAS/winPEASexe/winPEAS/winPEAS.csproj | 2 + 6 files changed, 367 insertions(+) create mode 100644 winPEAS/winPEASexe/winPEAS/Checks/RegistryInfo.cs create mode 100644 winPEAS/winPEASexe/winPEAS/Helpers/Registry/RegistryAclScanner.cs diff --git a/winPEAS/winPEASexe/README.md b/winPEAS/winPEASexe/README.md index 8dd211c..61298d1 100755 --- a/winPEAS/winPEASexe/README.md +++ b/winPEAS/winPEASexe/README.md @@ -76,6 +76,7 @@ The goal of this project is to search for possible **Privilege Escalation Paths* 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 HKLM/HKU registry keys that grant write access to low-privileged groups (including the cross-user TypingInsights key) so you can spot the hive corruption primitives documented by Project Zero. 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). diff --git a/winPEAS/winPEASexe/winPEAS/Checks/Checks.cs b/winPEAS/winPEASexe/winPEAS/Checks/Checks.cs index 8e0a8d1..d80529d 100644 --- a/winPEAS/winPEASexe/winPEAS/Checks/Checks.cs +++ b/winPEAS/winPEASexe/winPEAS/Checks/Checks.cs @@ -93,6 +93,7 @@ namespace winPEAS.Checks new SystemCheck("activedirectoryinfo", new ActiveDirectoryInfo()), new SystemCheck("cloudinfo", new CloudInfo()), new SystemCheck("windowscreds", new WindowsCreds()), + new SystemCheck("registryinfo", new RegistryInfo()), new SystemCheck("browserinfo", new BrowserInfo()), new SystemCheck("filesinfo", new FilesInfo()), new SystemCheck("fileanalysis", new FileAnalysis()), diff --git a/winPEAS/winPEASexe/winPEAS/Checks/RegistryInfo.cs b/winPEAS/winPEASexe/winPEAS/Checks/RegistryInfo.cs new file mode 100644 index 0000000..51761e3 --- /dev/null +++ b/winPEAS/winPEASexe/winPEAS/Checks/RegistryInfo.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using winPEAS.Helpers; +using winPEAS.Helpers.Registry; + +namespace winPEAS.Checks +{ + internal class RegistryInfo : ISystemCheck + { + private const string TypingInsightsRelativePath = @"Software\Microsoft\Input\TypingInsights"; + + private static readonly string[] KnownWritableSystemKeyCandidates = new[] + { + @"SOFTWARE\Microsoft\CoreShell", + @"SOFTWARE\Microsoft\DRM", + @"SOFTWARE\Microsoft\Input\Locales", + @"SOFTWARE\Microsoft\Input\Settings", + @"SOFTWARE\Microsoft\Shell\Oobe", + @"SOFTWARE\Microsoft\Shell\Session", + @"SOFTWARE\Microsoft\Tracing", + @"SOFTWARE\Microsoft\Windows\UpdateApi", + @"SOFTWARE\Microsoft\WindowsUpdate\UX", + @"SOFTWARE\WOW6432Node\Microsoft\DRM", + @"SOFTWARE\WOW6432Node\Microsoft\Tracing", + @"SYSTEM\Software\Microsoft\TIP", + @"SYSTEM\ControlSet001\Control\Cryptography\WebSignIn\Navigation", + @"SYSTEM\ControlSet001\Control\MUI\StringCacheSettings", + @"SYSTEM\ControlSet001\Control\USB\AutomaticSurpriseRemoval", + @"SYSTEM\ControlSet001\Services\BTAGService\Parameters\Settings", + }; + + private static readonly string[] ScanBasePaths = new[] + { + @"SOFTWARE\Microsoft", + @"SOFTWARE\WOW6432Node\Microsoft", + @"SYSTEM\CurrentControlSet\Services", + @"SYSTEM\CurrentControlSet\Control", + @"SYSTEM\ControlSet001\Control", + }; + + public void PrintInfo(bool isDebug) + { + Beaprint.GreatPrint("Registry permissions for hive exploitation"); + + new List + { + PrintTypingInsightsPermissions, + PrintKnownSystemWritableKeys, + PrintHeuristicWritableKeys, + }.ForEach(action => CheckRunner.Run(action, isDebug)); + } + + private void PrintTypingInsightsPermissions() + { + Beaprint.MainPrint("Cross-user TypingInsights key (HKCU/HKU)"); + + var matches = new List(); + var seen = new HashSet(StringComparer.OrdinalIgnoreCase); + + if (RegistryAclScanner.TryGetWritableKey("HKCU", TypingInsightsRelativePath, out var currentUserKey)) + { + if (seen.Add(currentUserKey.FullPath)) + { + matches.Add(currentUserKey); + } + } + + foreach (var sid in RegistryHelper.GetUserSIDs()) + { + if (string.IsNullOrEmpty(sid) || sid.Equals(".DEFAULT", StringComparison.OrdinalIgnoreCase) || sid.EndsWith("_Classes", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + string relativePath = $"{sid}\\{TypingInsightsRelativePath}"; + if (RegistryAclScanner.TryGetWritableKey("HKU", relativePath, out var info) && seen.Add(info.FullPath)) + { + matches.Add(info); + } + } + + if (matches.Count == 0) + { + Beaprint.GrayPrint(" [-] TypingInsights key does not grant write access to low-privileged groups."); + return; + } + + PrintEntries(matches); + Beaprint.LinkPrint("https://projectzero.google/2025/05/the-windows-registry-adventure-8-exploitation.html", "Writable TypingInsights enables cross-user hive tampering and DoS."); + } + + private void PrintKnownSystemWritableKeys() + { + Beaprint.MainPrint("Known HKLM descendants writable by standard users"); + + var matches = new List(); + foreach (var path in KnownWritableSystemKeyCandidates) + { + if (RegistryAclScanner.TryGetWritableKey("HKLM", path, out var info)) + { + matches.Add(info); + } + } + + if (matches.Count == 0) + { + Beaprint.GrayPrint(" [-] None of the tracked HKLM keys are writable by low-privileged groups."); + return; + } + + PrintEntries(matches); + } + + private void PrintHeuristicWritableKeys() + { + Beaprint.MainPrint("Sample of additional writable HKLM keys (depth-limited scan)"); + + var matches = RegistryAclScanner.ScanWritableKeys("HKLM", ScanBasePaths, maxDepth: 3, maxResults: 25); + if (matches.Count == 0) + { + Beaprint.GrayPrint(" [-] No additional writable HKLM keys were found within the sampled paths."); + return; + } + + PrintEntries(matches); + Beaprint.GrayPrint(" [*] Showing up to 25 entries from the sampled paths to avoid noisy output."); + } + + private static void PrintEntries(IEnumerable entries) + { + foreach (var entry in entries) + { + var principals = string.Join(", ", entry.Principals); + var rights = entry.Rights.Count > 0 ? string.Join(", ", entry.Rights.Distinct(StringComparer.OrdinalIgnoreCase)) : "Write access"; + var displayPath = string.IsNullOrEmpty(entry.FullPath) ? $"{entry.Hive}\\{entry.RelativePath}" : entry.FullPath; + Beaprint.BadPrint($" [!] {displayPath} -> {principals} ({rights})"); + } + } + } +} diff --git a/winPEAS/winPEASexe/winPEAS/Helpers/Beaprint.cs b/winPEAS/winPEASexe/winPEAS/Helpers/Beaprint.cs index 2746f2e..1d017d7 100644 --- a/winPEAS/winPEASexe/winPEAS/Helpers/Beaprint.cs +++ b/winPEAS/winPEASexe/winPEAS/Helpers/Beaprint.cs @@ -132,6 +132,7 @@ namespace winPEAS.Helpers Console.WriteLine(LCYAN + " activedirectoryinfo" + GRAY + " Quick AD checks (gMSA readable passwords, AD CS template rights)" + NOCOLOR); Console.WriteLine(LCYAN + " cloudinfo" + GRAY + " Enumerate cloud information" + NOCOLOR); Console.WriteLine(LCYAN + " windowscreds" + GRAY + " Search windows credentials" + NOCOLOR); + Console.WriteLine(LCYAN + " registryinfo" + GRAY + " Flag writable HKLM/HKU keys that enable hive tampering" + NOCOLOR); Console.WriteLine(LCYAN + " browserinfo" + GRAY + " Search browser information" + NOCOLOR); Console.WriteLine(LCYAN + " filesinfo" + GRAY + " Search generic files that can contains credentials" + NOCOLOR); Console.WriteLine(LCYAN + " fileanalysis" + GRAY + " [NOT RUN BY DEFAULT] Search specific files that can contains credentials and for regexes inside files. Might take several minutes." + NOCOLOR); diff --git a/winPEAS/winPEASexe/winPEAS/Helpers/Registry/RegistryAclScanner.cs b/winPEAS/winPEASexe/winPEAS/Helpers/Registry/RegistryAclScanner.cs new file mode 100644 index 0000000..fbfe3f1 --- /dev/null +++ b/winPEAS/winPEASexe/winPEAS/Helpers/Registry/RegistryAclScanner.cs @@ -0,0 +1,221 @@ +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.AccessControl; +using System.Security.Principal; +using winPEAS.Helpers; + +namespace winPEAS.Helpers.Registry +{ + internal class RegistryWritableKeyInfo + { + public string Hive { get; set; } + public string RelativePath { get; set; } + public string FullPath { get; set; } + public List Principals { get; set; } = new List(); + public List Rights { get; set; } = new List(); + } + + internal static class RegistryAclScanner + { + private static readonly Dictionary LowPrivSidMap = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null).Value, "BUILTIN\\Users" }, + { new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null).Value, "Authenticated Users" }, + { new SecurityIdentifier(WellKnownSidType.WorldSid, null).Value, "Everyone" }, + { new SecurityIdentifier(WellKnownSidType.InteractiveSid, null).Value, "Interactive" }, + { new SecurityIdentifier(WellKnownSidType.BuiltinGuestsSid, null).Value, "BUILTIN\\Guests" }, + }; + + public static bool TryGetWritableKey(string hive, string relativePath, out RegistryWritableKeyInfo info) + { + info = null; + using (var key = OpenKey(hive, relativePath)) + { + if (key == null) + { + return false; + } + + return TryCollectWritableInfo(hive, relativePath, key, out info); + } + } + + public static List ScanWritableKeys(string hive, IEnumerable basePaths, int maxDepth, int maxResults) + { + var results = new List(); + var seenPaths = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var basePath in basePaths ?? Enumerable.Empty()) + { + if (results.Count >= maxResults) + { + break; + } + + using (var key = OpenKey(hive, basePath)) + { + if (key == null) + { + continue; + } + + Traverse(hive, key, basePath, 0, maxDepth, maxResults, seenPaths, results); + } + } + + return results; + } + + private static void Traverse(string hive, RegistryKey currentKey, string currentPath, int depth, int maxDepth, int maxResults, HashSet seenPaths, List results) + { + if (currentKey == null || results.Count >= maxResults) + { + return; + } + + if (TryCollectWritableInfo(hive, currentPath, currentKey, out var info)) + { + if (seenPaths.Add(info.FullPath)) + { + results.Add(info); + } + + if (results.Count >= maxResults) + { + return; + } + } + + if (depth >= maxDepth) + { + return; + } + + string[] subKeys; + try + { + subKeys = currentKey.GetSubKeyNames(); + } + catch + { + return; + } + + foreach (var subKeyName in subKeys) + { + if (results.Count >= maxResults) + { + break; + } + + try + { + using (var childKey = currentKey.OpenSubKey(subKeyName)) + { + if (childKey == null) + { + continue; + } + + string childPath = string.IsNullOrEmpty(currentPath) ? subKeyName : $"{currentPath}\\{subKeyName}"; + Traverse(hive, childKey, childPath, depth + 1, maxDepth, maxResults, seenPaths, results); + } + } + catch + { + // Ignore keys we cannot open + } + } + } + + private static bool TryCollectWritableInfo(string hive, string relativePath, RegistryKey key, out RegistryWritableKeyInfo info) + { + info = null; + + try + { + var acl = key.GetAccessControl(AccessControlSections.Access); + + var principals = new HashSet(StringComparer.OrdinalIgnoreCase); + var rights = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (RegistryAccessRule rule in acl.GetAccessRules(true, true, typeof(SecurityIdentifier))) + { + if (rule.AccessControlType != AccessControlType.Allow) + { + continue; + } + + var sid = rule.IdentityReference as SecurityIdentifier ?? rule.IdentityReference.Translate(typeof(SecurityIdentifier)) as SecurityIdentifier; + if (sid == null) + { + continue; + } + + if (!LowPrivSidMap.TryGetValue(sid.Value, out var label)) + { + continue; + } + + string interestingRight = PermissionsHelper.PermInt2Str((int)rule.RegistryRights, PermissionType.WRITEABLE_OR_EQUIVALENT_REG); + if (string.IsNullOrEmpty(interestingRight)) + { + continue; + } + + principals.Add($"{label} ({sid.Value})"); + rights.Add(interestingRight); + } + + if (principals.Count == 0) + { + return false; + } + + string normalizedRelativePath = relativePath ?? string.Empty; + string fullPath = string.IsNullOrEmpty(normalizedRelativePath) ? key.Name : $"{hive}\\{normalizedRelativePath}"; + + info = new RegistryWritableKeyInfo + { + Hive = hive, + RelativePath = normalizedRelativePath, + FullPath = fullPath, + Principals = principals.ToList(), + Rights = rights.ToList(), + }; + return true; + } + catch + { + return false; + } + } + + private static RegistryKey OpenKey(string hive, string path) + { + if (string.IsNullOrEmpty(path)) + { + return null; + } + + try + { + RegistryKey baseKey = hive switch + { + "HKLM" => Registry.LocalMachine, + "HKCU" => Registry.CurrentUser, + "HKU" => Registry.Users, + _ => null, + }; + + return baseKey?.OpenSubKey(path); + } + catch + { + return null; + } + } + } +} diff --git a/winPEAS/winPEASexe/winPEAS/winPEAS.csproj b/winPEAS/winPEASexe/winPEAS/winPEAS.csproj index 4259210..a5f7b4f 100644 --- a/winPEAS/winPEASexe/winPEAS/winPEAS.csproj +++ b/winPEAS/winPEASexe/winPEAS/winPEAS.csproj @@ -1200,6 +1200,7 @@ + @@ -1464,6 +1465,7 @@ +