Skip to content

Instantly share code, notes, and snippets.

@rkttu
Last active July 5, 2024 08:08
Show Gist options
  • Save rkttu/b94ef5dfebfad6f4312bd0761e8e397d to your computer and use it in GitHub Desktop.
Save rkttu/b94ef5dfebfad6f4312bd0761e8e397d to your computer and use it in GitHub Desktop.
Local Group Policy Enumeration Code Sample
<Query Kind="Program">
<Reference>&lt;RuntimeDirectory&gt;\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