Last active
July 5, 2024 08:08
-
-
Save rkttu/b94ef5dfebfad6f4312bd0761e8e397d to your computer and use it in GitHub Desktop.
Local Group Policy Enumeration Code Sample
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Query Kind="Program"> | |
<Reference><RuntimeDirectory>\System.DirectoryServices.dll</Reference> | |
<Namespace>Microsoft.Win32</Namespace> | |
<Namespace>Microsoft.Win32.SafeHandles</Namespace> | |
<Namespace>System.ComponentModel</Namespace> | |
<Namespace>System.DirectoryServices</Namespace> | |
<Namespace>System.Runtime.InteropServices</Namespace> | |
<Namespace>System.Security.Principal</Namespace> | |
<Namespace>System.Collections.ObjectModel</Namespace> | |
</Query> | |
internal static class Program | |
{ | |
[STAThread] | |
private static void Main(string[] args) | |
{ | |
bool copyToSystemRegistry = false; | |
ApartmentState currentState = Thread.CurrentThread.GetApartmentState(); | |
if (currentState != ApartmentState.STA) | |
throw new InvalidOperationException(string.Format("Apartment state must be STA. Current state is {0}.", currentState)); | |
using (WindowsIdentity identity = WindowsIdentity.GetCurrent()) | |
{ | |
WindowsPrincipal principal = new WindowsPrincipal(identity); | |
if (!principal.IsInRole(WindowsBuiltInRole.Administrator)) | |
throw new InvalidOperationException("This code must be run as an administrator."); | |
} | |
int hr = 0x0; | |
IntPtr hKey = IntPtr.Zero; | |
GroupPolicySection section; | |
GroupPolicyClass obj = default(GroupPolicyClass); | |
IGroupPolicyObject2 service = default(IGroupPolicyObject2); | |
IReadOnlyList<GroupPolicyEntry> entries; | |
try | |
{ | |
obj = new GroupPolicyClass(); | |
service = (IGroupPolicyObject2)obj; | |
hr = service.OpenLocalMachineGPO((int)GroupPolicyOpenFlags.LoadRegistry); | |
if (hr != 0x0) | |
throw new Win32Exception(hr); | |
section = GroupPolicySection.Machine; | |
hr = service.GetRegistryKey((int)section, out hKey); | |
if (hr != 0x0) | |
throw new Win32Exception(hr); | |
using (SafeRegistryHandle baseKey = new SafeRegistryHandle(hKey, true)) | |
using (RegistryKey registry = RegistryKey.FromHandle(baseKey, RegistryView.Default)) | |
{ | |
entries = registry.CollectEntries(section, null).Dump("Machine Policies"); | |
if (copyToSystemRegistry) | |
entries.CopyToSystemRegistry(); | |
} | |
section = GroupPolicySection.User; | |
hr = service.GetRegistryKey((int)section, out hKey); | |
if (hr != 0x0) | |
throw new Win32Exception(hr); | |
using (SafeRegistryHandle baseKey = new SafeRegistryHandle(hKey, true)) | |
using (RegistryKey registry = RegistryKey.FromHandle(baseKey, RegistryView.Default)) | |
{ | |
entries = registry.CollectEntries(section, null).Dump("User Policies"); | |
if (copyToSystemRegistry) | |
entries.CopyToSystemRegistry(); | |
} | |
} | |
finally | |
{ | |
if (service != null) | |
{ | |
Marshal.ReleaseComObject(service); | |
service = null; | |
} | |
if (obj != null) | |
{ | |
Marshal.ReleaseComObject(obj); | |
obj = null; | |
} | |
} | |
foreach (WindowsAccountInfo eachUserOrGroup in EnumerateSIDs().Dump()) | |
{ | |
try | |
{ | |
obj = new GroupPolicyClass(); | |
service = (IGroupPolicyObject2)obj; | |
hr = service.OpenLocalMachineGPOForPrincipal(eachUserOrGroup.AccountSid, (int)GroupPolicyOpenFlags.LoadRegistry); | |
if (hr != 0x0) | |
throw new Win32Exception(hr); | |
try | |
{ | |
section = GroupPolicySection.User; | |
hr = service.GetRegistryKey((int)section, out hKey); | |
if (hr != 0x0) | |
throw new Win32Exception(hr); | |
using (SafeRegistryHandle baseKey = new SafeRegistryHandle(hKey, true)) | |
using (RegistryKey registry = RegistryKey.FromHandle(baseKey, RegistryView.Default)) | |
{ | |
entries = registry.CollectEntries(section, eachUserOrGroup).Dump($"[{eachUserOrGroup.AccountType}] {eachUserOrGroup.AccountName} User Policies"); | |
if (copyToSystemRegistry) | |
entries.CopyToSystemRegistry(); | |
} | |
} | |
catch { } | |
} | |
catch (Exception ex) | |
{ | |
ex.Dump($"[{eachUserOrGroup.AccountType}] {eachUserOrGroup.AccountName} Failed open GPO - {eachUserOrGroup.AccountSid}"); | |
continue; | |
} | |
finally | |
{ | |
if (service != null) | |
{ | |
Marshal.ReleaseComObject(service); | |
service = null; | |
} | |
if (obj != null) | |
{ | |
Marshal.ReleaseComObject(obj); | |
obj = null; | |
} | |
} | |
} | |
} | |
public static void CopyToSystemRegistry(this IEnumerable<GroupPolicyEntry> entries) | |
{ | |
foreach (GroupPolicyEntry entry in entries) | |
entry.CopyToSystemRegistry(); | |
} | |
public static IReadOnlyList<GroupPolicyEntry> CollectEntries(this RegistryKey registry, GroupPolicySection section, WindowsAccountInfo accountInfo) | |
{ | |
List<GroupPolicyEntry> entries = new List<GroupPolicyEntry>(); | |
CollectPolicyRegistryPathsRecursive(registry, section, accountInfo, entries); | |
return new ReadOnlyCollection<GroupPolicyEntry>(entries); | |
} | |
private static void CollectPolicyRegistryPathsRecursive(RegistryKey registry, GroupPolicySection section, WindowsAccountInfo accountInfo, List<GroupPolicyEntry> entries) | |
{ | |
if (registry == null) | |
return; | |
GroupPolicyEntry currentEntry = new GroupPolicyEntry( | |
section, accountInfo, registry.Name.TrimStart('\\')); | |
foreach (string eachName in registry.GetValueNames()) | |
currentEntry.Values.Add(eachName, registry.GetValue(eachName)); | |
foreach (string eachKey in registry.GetSubKeyNames()) | |
{ | |
using (RegistryKey subKeyRegistry = registry.OpenSubKey(eachKey, false)) | |
{ | |
if (subKeyRegistry != null) | |
CollectPolicyRegistryPathsRecursive(subKeyRegistry, section, accountInfo, entries); | |
} | |
} | |
if (currentEntry.Values.Count > 0) | |
entries.Add(currentEntry); | |
} | |
private static IReadOnlyList<WindowsAccountInfo> EnumerateSIDs(bool skipBuiltInSIDs = true) | |
{ | |
List<WindowsAccountInfo> list = new List<WindowsAccountInfo>(); | |
using (DirectoryEntry computer = new DirectoryEntry($"WinNT://{Environment.MachineName},computer")) | |
{ | |
foreach (DirectoryEntry eachChild in computer.Children) | |
{ | |
try | |
{ | |
byte[] objectSid = default(byte[]); | |
if (string.Equals("User", eachChild.SchemaClassName, StringComparison.OrdinalIgnoreCase)) | |
{ | |
if (!eachChild.Properties.Contains("objectSid")) | |
continue; | |
objectSid = eachChild.Properties["objectSid"].Value as byte[]; | |
} | |
else if (string.Equals("Group", eachChild.SchemaClassName, StringComparison.OrdinalIgnoreCase)) | |
{ | |
if (!eachChild.Properties.Contains("objectSid")) | |
continue; | |
objectSid = eachChild.Properties["objectSid"].Value as byte[]; | |
} | |
else | |
continue; | |
if (objectSid == null || objectSid.Length < 1) | |
continue; | |
IntPtr pSid = IntPtr.Zero; | |
try | |
{ | |
pSid = Marshal.AllocHGlobal(objectSid.Length); | |
Marshal.Copy(objectSid, 0, pSid, objectSid.Length); | |
if (ConvertSidToStringSidW(pSid, out string stringSid)) | |
{ | |
if (stringSid.StartsWith("S-1-5-32", StringComparison.OrdinalIgnoreCase)) | |
continue; | |
list.Add(new WindowsAccountInfo(eachChild.Name, eachChild.SchemaClassName, stringSid)); | |
} | |
} | |
finally | |
{ | |
if (pSid != IntPtr.Zero) | |
{ | |
Marshal.FreeHGlobal(pSid); | |
pSid = IntPtr.Zero; | |
} | |
} | |
} | |
finally { eachChild.Dispose(); } | |
} | |
} | |
return list.AsReadOnly(); | |
} | |
[return: MarshalAs(UnmanagedType.Bool)] | |
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)] | |
private static extern bool ConvertSidToStringSidW(IntPtr pSid, out string strSid); | |
} | |
[ComImport] | |
[Guid("7E37D5E7-263D-45CF-842B-96A95C63E46C")] | |
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | |
internal interface IGroupPolicyObject2 | |
{ | |
[return: MarshalAs(UnmanagedType.U4)] | |
int New( | |
[MarshalAs(UnmanagedType.LPWStr)] string pszDomainName, | |
[MarshalAs(UnmanagedType.LPWStr)] string pszDisplayName, | |
[MarshalAs(UnmanagedType.U4)] int dwFlags); | |
[return: MarshalAs(UnmanagedType.U4)] | |
int OpenDSGPO( | |
[MarshalAs(UnmanagedType.LPWStr)] string pszPath, | |
[MarshalAs(UnmanagedType.U4)] int dwFlags); | |
[return: MarshalAs(UnmanagedType.U4)] | |
int OpenLocalMachineGPO( | |
[MarshalAs(UnmanagedType.U4)] int dwFlags); | |
[return: MarshalAs(UnmanagedType.U4)] | |
int OpenRemoteMachineGPO( | |
[MarshalAs(UnmanagedType.LPWStr)] string pszComputerName, | |
[MarshalAs(UnmanagedType.U4)] int dwFlags); | |
[return: MarshalAs(UnmanagedType.U4)] | |
int Save( | |
[MarshalAs(UnmanagedType.Bool)] bool bMachine, | |
[MarshalAs(UnmanagedType.Bool)] bool bAdd, | |
[MarshalAs(UnmanagedType.LPStruct)] Guid pGuidExtension, | |
[MarshalAs(UnmanagedType.LPStruct)] Guid pGuid); | |
[return: MarshalAs(UnmanagedType.U4)] | |
int Delete(); | |
[return: MarshalAs(UnmanagedType.U4)] | |
int GetName( | |
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, | |
int cchMaxLength); | |
[return: MarshalAs(UnmanagedType.U4)] | |
int GetDisplayName( | |
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, | |
int cchMaxLength); | |
[return: MarshalAs(UnmanagedType.U4)] | |
int SetDisplayName( | |
[MarshalAs(UnmanagedType.LPWStr)] string pszName); | |
[return: MarshalAs(UnmanagedType.U4)] | |
int GetPath( | |
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszPath, | |
int cchMaxPath); | |
[return: MarshalAs(UnmanagedType.U4)] | |
int GetDSPath( | |
[MarshalAs(UnmanagedType.U4)] int dwSection, | |
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszPath, | |
int cchMaxPath); | |
[return: MarshalAs(UnmanagedType.U4)] | |
int GetFileSysPath( | |
[MarshalAs(UnmanagedType.U4)] int dwSection, | |
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszPath, | |
int cchMaxPath); | |
[return: MarshalAs(UnmanagedType.U4)] | |
int GetRegistryKey( | |
[MarshalAs(UnmanagedType.U4)] int dwSection, | |
out IntPtr hKey); | |
[return: MarshalAs(UnmanagedType.U4)] | |
int GetOptions( | |
[MarshalAs(UnmanagedType.U4)] out int dwOptions); | |
[return: MarshalAs(UnmanagedType.U4)] | |
int SetOptions( | |
[MarshalAs(UnmanagedType.U4)] int dwOptions, | |
[MarshalAs(UnmanagedType.U4)] int dwMask); | |
[return: MarshalAs(UnmanagedType.U4)] | |
int GetType( | |
out IntPtr gpoType); | |
[return: MarshalAs(UnmanagedType.U4)] | |
int GetMachineName( | |
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, | |
int cchMaxLength); | |
[return: MarshalAs(UnmanagedType.U4)] | |
int GetPropertySheetPages( | |
out IntPtr hPages, | |
[MarshalAs(UnmanagedType.U4)] out int uPageCount); | |
[return: MarshalAs(UnmanagedType.U4)] | |
int OpenLocalMachineGPOForPrincipal( | |
[MarshalAs(UnmanagedType.LPWStr)] string pszLocalUserOrGroupSID, | |
[MarshalAs(UnmanagedType.U4)] int dwFlags); | |
[return: MarshalAs(UnmanagedType.U4)] | |
int GetRegistryKeyPath( | |
[MarshalAs(UnmanagedType.U4)] int dwSection, | |
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszRegistryKeyPath, | |
int cchMaxLength); | |
} | |
[ComImport] | |
[Guid("EA502722-A23D-11d1-A7D3-0000F87571E3")] | |
internal class GroupPolicyClass { } | |
internal enum GroupPolicyOpenFlags : int | |
{ | |
LoadRegistry = 0x00000001, | |
ReadOnly = 0x00000002, | |
} | |
public enum GroupPolicySection : int | |
{ | |
Root = 0, | |
User = 1, | |
Machine = 2, | |
} | |
public sealed class WindowsAccountInfo | |
{ | |
public WindowsAccountInfo(string accountName, string accountType, string accountSid) | |
{ | |
AccountName = accountName; | |
AccountType = accountType; | |
AccountSid = accountSid; | |
} | |
public string AccountName { get; } | |
public string AccountType { get; } | |
public string AccountSid { get; } | |
public override string ToString() | |
=> $"{AccountName} ({AccountType}): {AccountSid}"; | |
} | |
public sealed class GroupPolicyEntry | |
{ | |
internal GroupPolicyEntry(GroupPolicySection section, WindowsAccountInfo accountInfo, string keyName) | |
{ | |
Section = section; | |
AccountInfo = accountInfo; | |
KeyName = keyName ?? string.Empty; | |
Values = new Dictionary<string, object>(); | |
} | |
public GroupPolicySection Section { get; } | |
public WindowsAccountInfo AccountInfo { get; } | |
public string KeyName { get; } | |
public Dictionary<string, object> Values { get; } | |
public void CopyToSystemRegistry() | |
{ | |
List<IDisposable> disposeTargets = new List<IDisposable>(); | |
try | |
{ | |
RegistryKey targetKey; | |
if (Section == GroupPolicySection.Machine) | |
disposeTargets.Add(targetKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default)); | |
else if (Section == GroupPolicySection.User) | |
{ | |
string userOrGroupSid = AccountInfo?.AccountSid; | |
if (string.IsNullOrWhiteSpace(userOrGroupSid)) | |
disposeTargets.Add(targetKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default)); | |
else | |
{ | |
RegistryKey baseKey = RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default); | |
disposeTargets.Add(baseKey); | |
disposeTargets.Add(targetKey = baseKey.OpenSubKey(userOrGroupSid, true)); | |
} | |
} | |
else | |
throw new NotSupportedException("Cannot copy the root section."); | |
RegistryKey subKey = targetKey.OpenSubKey(KeyName, true); | |
if (subKey != null) | |
{ | |
disposeTargets.Add(subKey); | |
foreach (KeyValuePair<string, object> eachValue in Values) | |
subKey.SetValue(eachValue.Key, eachValue.Value); | |
} | |
} | |
finally | |
{ | |
foreach (IDisposable eachDisposable in disposeTargets) | |
{ | |
if (eachDisposable == null) | |
continue; | |
try { eachDisposable.Dispose(); } | |
catch { } | |
} | |
disposeTargets.Clear(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment