fix(security): LocalAdminAnalyzer inventories disabled accounts and reads Administrators-group membership from the SAM (NetLocalGroupGetMembers on S-1-5-32-544); a dormant /active:no admin backdoor now surfaces as unexpected_admin_members (+40, Error), built-in Administrator enabled-state reported for evidence

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Oliver Kieselbach
2026-08-30 01:00:29 +02:00
parent d294415d29
commit 88dfd331dd
8 changed files with 642 additions and 27 deletions
@@ -0,0 +1,199 @@
#nullable enable
using System.Collections.Generic;
using System.Linq;
using AutopilotMonitor.Agent.V2.Core.Monitoring.Interop;
using AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Analyzers;
using Xunit;
namespace AutopilotMonitor.Agent.V2.Core.Tests.Monitoring.Analyzers
{
/// <summary>
/// LocalAdminAnalyzer.EvaluateAccounts — the account/admin-membership evaluation contract.
/// Pins the two evasions that the old name-only, enabled-only scan missed: a disabled
/// (dormant) backdoor account, and Administrators membership as an independent signal.
/// All names are synthetic; the machine name is a fixture constant.
/// </summary>
public sealed class LocalAdminAnalyzerEvaluateAccountsTests
{
private const string Machine = "DESKTOP-TEST01";
private const string MachineSidPrefix = "S-1-5-21-111-222-333-";
private static readonly List<string> Allowed = new List<string>
{
"Administrator", "Guest", "DefaultAccount", "WDAGUtilityAccount", "defaultuser0", "defaultuser1", "adm-*"
};
private static LocalAdminAnalyzer.LocalAccountInfo Account(string name, bool disabled, int rid) =>
new LocalAdminAnalyzer.LocalAccountInfo { Name = name, Disabled = disabled, Sid = MachineSidPrefix + rid };
private static LocalGroupMember LocalMember(string name, int rid) =>
new LocalGroupMember { DomainAndName = Machine + "\\" + name, Sid = MachineSidPrefix + rid, SidUsage = 1 };
// -------------------------------------------------------------- disabled backdoor
[Fact]
public void Disabled_unexpected_account_is_still_reported()
{
var accounts = new List<LocalAdminAnalyzer.LocalAccountInfo>
{
Account("Administrator", disabled: true, rid: 500),
Account("backdoor", disabled: true, rid: 1001)
};
var result = LocalAdminAnalyzer.EvaluateAccounts(accounts, new List<LocalGroupMember>(), true, Machine, Allowed);
Assert.Equal(new[] { "backdoor" }, result.Unexpected);
Assert.Empty(result.UnexpectedAdminMembers);
var detail = result.AccountDetails.Single(d => (string)d["name"] == "backdoor");
Assert.True((bool)detail["disabled"]);
Assert.False((bool)detail["administrators_member"]);
}
[Fact]
public void Disabled_unexpected_account_with_admin_membership_is_an_unexpected_admin_member()
{
var accounts = new List<LocalAdminAnalyzer.LocalAccountInfo>
{
Account("Administrator", disabled: true, rid: 500),
Account("backdoor", disabled: true, rid: 1001)
};
var members = new List<LocalGroupMember>
{
LocalMember("Administrator", 500),
LocalMember("backdoor", 1001)
};
var result = LocalAdminAnalyzer.EvaluateAccounts(accounts, members, true, Machine, Allowed);
Assert.Equal(new[] { "backdoor" }, result.Unexpected);
Assert.Equal(new[] { "backdoor" }, result.UnexpectedAdminMembers);
var detail = result.AccountDetails.Single(d => (string)d["name"] == "backdoor");
Assert.True((bool)detail["administrators_member"]);
}
// -------------------------------------------------------------- allowed list
[Fact]
public void Allowed_admin_members_are_not_flagged_but_listed()
{
var accounts = new List<LocalAdminAnalyzer.LocalAccountInfo>
{
Account("Administrator", disabled: false, rid: 500),
Account("adm-4711", disabled: false, rid: 1002)
};
var members = new List<LocalGroupMember>
{
LocalMember("Administrator", 500),
LocalMember("adm-4711", 1002)
};
var result = LocalAdminAnalyzer.EvaluateAccounts(accounts, members, true, Machine, Allowed);
Assert.Empty(result.Unexpected);
Assert.Empty(result.UnexpectedAdminMembers);
Assert.Equal(2, result.AdministratorsGroupMembers.Count);
Assert.True(result.AccountDetails.All(d => (bool)d["administrators_member"]));
}
// -------------------------------------------------------------- non-local members
[Fact]
public void Members_outside_the_machine_domain_are_listed_but_never_flagged()
{
var accounts = new List<LocalAdminAnalyzer.LocalAccountInfo>
{
Account("Administrator", disabled: true, rid: 500)
};
var members = new List<LocalGroupMember>
{
LocalMember("Administrator", 500),
// Entra role SIDs on an Entra-joined device resolve to nothing — the API returns the SID
new LocalGroupMember { DomainAndName = "S-1-12-1-123-456-789-1011", Sid = "S-1-12-1-123-456-789-1011", SidUsage = 8 },
// Domain group on a hybrid device
new LocalGroupMember { DomainAndName = "CORP\\Workstation Admins", Sid = "S-1-5-21-9-9-9-1105", SidUsage = 2 }
};
var result = LocalAdminAnalyzer.EvaluateAccounts(accounts, members, true, Machine, Allowed);
Assert.Empty(result.Unexpected);
Assert.Empty(result.UnexpectedAdminMembers);
Assert.Equal(3, result.AdministratorsGroupMembers.Count);
Assert.Contains("S-1-12-1-123-456-789-1011", result.AdministratorsGroupMembers);
}
// -------------------------------------------------------------- WMI gap
[Fact]
public void Local_admin_member_missing_from_the_wmi_inventory_is_still_flagged()
{
// WMI failed or did not list the account — the SAM group membership alone is enough.
var members = new List<LocalGroupMember>
{
LocalMember("Administrator", 500),
LocalMember("backdoor", 1001)
};
var result = LocalAdminAnalyzer.EvaluateAccounts(
new List<LocalAdminAnalyzer.LocalAccountInfo>(), members, true, Machine, Allowed);
Assert.Empty(result.Unexpected);
Assert.Equal(new[] { "backdoor" }, result.UnexpectedAdminMembers);
}
[Fact]
public void Membership_matches_by_sid_when_the_member_name_differs_in_case()
{
var accounts = new List<LocalAdminAnalyzer.LocalAccountInfo> { Account("BackDoor", false, 1001) };
var members = new List<LocalGroupMember> { LocalMember("backdoor", 1001) };
var result = LocalAdminAnalyzer.EvaluateAccounts(accounts, members, true, Machine, Allowed);
Assert.Equal(new[] { "BackDoor" }, result.UnexpectedAdminMembers);
}
// -------------------------------------------------------------- built-in Administrator
[Theory]
[InlineData(true, false)]
[InlineData(false, true)]
public void Builtin_administrator_enabled_state_is_reported_by_rid_500(bool disabled, bool expectedEnabled)
{
var accounts = new List<LocalAdminAnalyzer.LocalAccountInfo>
{
Account("Administrator", disabled, rid: 500),
Account("Guest", disabled: true, rid: 501)
};
var result = LocalAdminAnalyzer.EvaluateAccounts(accounts, new List<LocalGroupMember>(), true, Machine, Allowed);
Assert.Equal(expectedEnabled, result.BuiltInAdministratorEnabled);
Assert.Empty(result.Unexpected);
}
// -------------------------------------------------------------- native path (Windows only, net48)
[Fact]
public void Native_administrators_enumeration_succeeds_and_contains_the_builtin_administrator()
{
// Proves the P/Invoke marshalling end-to-end on the executing machine: the SAM
// always holds the RID-500 Administrator in BUILTIN\Administrators.
var result = LocalGroupNativeMethods.GetAdministratorsMembers();
Assert.True(result.Succeeded, $"NET_API_STATUS={result.ErrorCode}");
Assert.False(string.IsNullOrEmpty(result.GroupName));
Assert.Contains(result.Members, m => m.Sid != null && m.Sid.EndsWith("-500"));
Assert.All(result.Members, m => Assert.False(string.IsNullOrEmpty(m.DomainAndName)));
}
[Fact]
public void Builtin_administrator_state_is_null_when_rid_500_is_absent()
{
var accounts = new List<LocalAdminAnalyzer.LocalAccountInfo> { Account("Guest", true, 501) };
var result = LocalAdminAnalyzer.EvaluateAccounts(accounts, new List<LocalGroupMember>(), false, Machine, Allowed);
Assert.Null(result.BuiltInAdministratorEnabled);
Assert.False(result.AdministratorsGroupEnumerated);
}
}
}
@@ -0,0 +1,178 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security.Principal;
namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Interop
{
/// <summary>
/// One member of a local group as returned by <c>NetLocalGroupGetMembers</c> level 2.
/// <see cref="DomainAndName"/> is <c>DOMAIN\name</c> for resolvable members; for members
/// whose SID cannot be resolved on the device (Entra role SIDs on an Entra-joined device,
/// deleted accounts) it falls back to the SID string so the member is still visible.
/// </summary>
internal sealed class LocalGroupMember
{
public string Sid { get; set; }
public string DomainAndName { get; set; }
public int SidUsage { get; set; }
/// <summary>Domain part of <see cref="DomainAndName"/> (empty when there is none).</summary>
public string Domain
{
get
{
var idx = (DomainAndName ?? string.Empty).IndexOf('\\');
return idx > 0 ? DomainAndName.Substring(0, idx) : string.Empty;
}
}
/// <summary>Account part of <see cref="DomainAndName"/> (the whole string when there is no domain).</summary>
public string Name
{
get
{
var value = DomainAndName ?? string.Empty;
var idx = value.IndexOf('\\');
return idx >= 0 && idx < value.Length - 1 ? value.Substring(idx + 1) : value;
}
}
}
/// <summary>Result of <see cref="LocalGroupNativeMethods.GetAdministratorsMembers"/>.</summary>
internal sealed class LocalGroupMembersResult
{
public string GroupName { get; set; }
public List<LocalGroupMember> Members { get; set; } = new List<LocalGroupMember>();
/// <summary>NET_API_STATUS of the enumeration; 0 = success.</summary>
public int ErrorCode { get; set; }
public bool Succeeded => ErrorCode == 0;
}
/// <summary>
/// Win32 P/Invoke declarations for local group membership queries
/// (<c>NetLocalGroupGetMembers</c>). Used by <c>LocalAdminAnalyzer</c> to enumerate the
/// members of the built-in Administrators group directly from the SAM — independent of
/// WMI and of account enabled/disabled state, so a dormant (disabled) admin account is
/// still reported. The group is addressed by its well-known SID (S-1-5-32-544) so the
/// localized group name ("Administratoren", "Administrateurs") does not matter.
/// </summary>
internal static class LocalGroupNativeMethods
{
private const uint MAX_PREFERRED_LENGTH = 0xFFFFFFFF;
/// <summary>LOCALGROUP_MEMBERS_INFO_2 — SID, SID usage and DOMAIN\name of a member.</summary>
[StructLayout(LayoutKind.Sequential)]
private struct LOCALGROUP_MEMBERS_INFO_2
{
public IntPtr lgrmi2_sid;
public int lgrmi2_sidusage;
[MarshalAs(UnmanagedType.LPWStr)]
public string lgrmi2_domainandname;
}
[DllImport("netapi32.dll", CharSet = CharSet.Unicode)]
private static extern int NetLocalGroupGetMembers(
string servername,
string localgroupname,
int level,
out IntPtr bufptr,
uint prefmaxlen,
out int entriesread,
out int totalentries,
IntPtr resumehandle);
[DllImport("netapi32.dll")]
private static extern int NetApiBufferFree(IntPtr buffer);
/// <summary>
/// Resolves the localized name of the built-in Administrators group from its well-known
/// SID. Falls back to the English name when the translation is unavailable.
/// </summary>
public static string ResolveAdministratorsGroupName()
{
try
{
var sid = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null);
var account = (NTAccount)sid.Translate(typeof(NTAccount));
var value = account.Value ?? string.Empty; // "BUILTIN\Administrators"
var idx = value.IndexOf('\\');
var name = idx >= 0 && idx < value.Length - 1 ? value.Substring(idx + 1) : value;
return string.IsNullOrEmpty(name) ? "Administrators" : name;
}
catch
{
return "Administrators";
}
}
/// <summary>
/// Enumerates the members of the built-in Administrators group on the local machine.
/// Never throws: a failed enumeration returns an empty member list with the native
/// error code in <see cref="LocalGroupMembersResult.ErrorCode"/> so the caller can log it.
/// </summary>
public static LocalGroupMembersResult GetAdministratorsMembers()
{
var result = new LocalGroupMembersResult { GroupName = ResolveAdministratorsGroupName() };
var buffer = IntPtr.Zero;
try
{
var status = NetLocalGroupGetMembers(
null, result.GroupName, 2, out buffer, MAX_PREFERRED_LENGTH,
out var entriesRead, out _, IntPtr.Zero);
if (status != 0)
{
result.ErrorCode = status;
return result;
}
var entrySize = Marshal.SizeOf(typeof(LOCALGROUP_MEMBERS_INFO_2));
for (var i = 0; i < entriesRead; i++)
{
var entryPtr = new IntPtr(buffer.ToInt64() + (long)i * entrySize);
var entry = (LOCALGROUP_MEMBERS_INFO_2)Marshal.PtrToStructure(entryPtr, typeof(LOCALGROUP_MEMBERS_INFO_2));
string sid = null;
try
{
if (entry.lgrmi2_sid != IntPtr.Zero)
sid = new SecurityIdentifier(entry.lgrmi2_sid).Value;
}
catch
{
// Malformed SID — keep the member with whatever name the API returned.
}
var domainAndName = entry.lgrmi2_domainandname;
if (string.IsNullOrEmpty(domainAndName))
domainAndName = sid ?? string.Empty;
result.Members.Add(new LocalGroupMember
{
Sid = sid,
DomainAndName = domainAndName,
SidUsage = entry.lgrmi2_sidusage
});
}
return result;
}
catch (Exception ex)
{
// Marshalling failure or missing export — report as a generic failure code
// (HRESULT when available) so the analyzer logs it instead of reporting an empty group as fact.
result.ErrorCode = ex.HResult != 0 ? ex.HResult : -1;
result.Members.Clear();
return result;
}
finally
{
if (buffer != IntPtr.Zero) NetApiBufferFree(buffer);
}
}
}
}
@@ -19,13 +19,22 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Analyzers
///
/// Checks performed:
/// 1. BypassNRO registry flag (HKLM\...\OOBE\BypassNRO = 1)
/// 2. Unexpected local user accounts (via WMI Win32_UserAccount)
/// 3. Unexpected C:\Users profile directories
/// 2. Unexpected local user accounts (via WMI Win32_UserAccount) — disabled accounts
/// included: a backdoor created with /active:no and re-enabled after enrollment
/// is dormant at both scans and would otherwise never surface
/// 3. Administrators-group membership (NetLocalGroupGetMembers on S-1-5-32-544) —
/// an unexpected account that also holds admin membership is the actual backdoor
/// 4. Unexpected C:\Users profile directories
///
/// Confidence scoring:
/// BypassNRO = 1 → +20 (low indicator)
/// Unexpected local account found → +40 (medium indicator)
/// Account + matching C:\Users profile → +40 (high indicator, profile overlap)
/// BypassNRO = 1 → +20 (low indicator)
/// Unexpected local account found → +40 (medium indicator)
/// Unexpected account is an Administrators member → +40 (high indicator)
/// Account + matching C:\Users profile → +40 (high indicator, profile overlap)
///
/// The enabled-state of the built-in Administrator (RID 500) is reported
/// (<c>builtin_administrator_enabled</c>) but not scored: whether OOBE enables it
/// transiently is not established, so the field first collects evidence.
///
/// Emits a single "local_admin_analysis" event at startup and at shutdown,
/// enabling delta detection between pre- and post-enrollment state.
@@ -146,6 +155,11 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Analyzers
if (accountsResult.Unexpected.Count > 0)
confidenceScore += 40;
// An unexpected account that is also a member of Administrators is the
// actual backdoor (enabled or dormant) — high indicator on its own.
if (accountsResult.UnexpectedAdminMembers.Count > 0)
confidenceScore += 40;
// Profile overlap: unexpected account AND matching C:\Users folder
bool profileOverlap = accountsResult.Unexpected.Any(a =>
profilesResult.Unexpected.Any(p =>
@@ -183,6 +197,9 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Analyzers
$"{Name}: confidence={confidenceScore}, finding={findingLabel}, " +
$"bypassNro={bypassNroResult.Value}, " +
$"unexpectedAccounts={accountsResult.Unexpected.Count}, " +
$"unexpectedAdminMembers={accountsResult.UnexpectedAdminMembers.Count}, " +
$"adminGroupEnumerated={accountsResult.AdministratorsGroupEnumerated}, " +
$"builtinAdministratorEnabled={accountsResult.BuiltInAdministratorEnabled?.ToString() ?? "unknown"}, " +
$"unexpectedProfiles={profilesResult.Unexpected.Count}");
var data = new Dictionary<string, object>
@@ -203,8 +220,18 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Analyzers
}
},
{ "unexpected_accounts", accountsResult.Unexpected },
{ "unexpected_admin_members", accountsResult.UnexpectedAdminMembers },
{ "unexpected_profiles", profilesResult.Unexpected },
{ "accounts_checked", accountsResult.AllChecked },
{ "account_details", accountsResult.AccountDetails },
{ "administrators_group", new Dictionary<string, object>
{
{ "enumerated", accountsResult.AdministratorsGroupEnumerated },
{ "error_code", accountsResult.AdministratorsGroupErrorCode },
{ "members", accountsResult.AdministratorsGroupMembers }
}
},
{ "builtin_administrator_enabled", accountsResult.BuiltInAdministratorEnabled },
{ "profiles_found", profilesResult.AllFound }
}
}
@@ -327,33 +354,23 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Analyzers
private LocalAccountCheckResult CheckLocalAdminAccounts(List<string> allowedAccounts)
{
var allChecked = new List<string>();
var unexpected = new List<string>();
var accounts = new List<LocalAccountInfo>();
try
{
using (var searcher = new ManagementObjectSearcher(
"SELECT Name, Disabled FROM Win32_UserAccount WHERE LocalAccount = True"))
"SELECT Name, Disabled, SID FROM Win32_UserAccount WHERE LocalAccount = True"))
{
foreach (ManagementObject obj in searcher.Get())
{
var name = obj["Name"]?.ToString() ?? string.Empty;
var disabled = obj["Disabled"] != null && Convert.ToBoolean(obj["Disabled"]);
var sid = obj["SID"]?.ToString();
if (string.IsNullOrEmpty(name))
continue;
allChecked.Add(name);
// Skip disabled accounts — they cannot be used to log in
if (disabled)
continue;
if (!IsAllowed(name, allowedAccounts))
{
unexpected.Add(name);
_logger.Debug($"{Name}: Unexpected local account: {name}");
}
accounts.Add(new LocalAccountInfo { Name = name, Disabled = disabled, Sid = sid });
}
}
}
@@ -362,7 +379,100 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Analyzers
_logger.Warning($"{Name}: Failed to enumerate local accounts via WMI: {ex.Message}");
}
return new LocalAccountCheckResult { AllChecked = allChecked, Unexpected = unexpected };
// Administrators-group membership is read from the SAM, independent of WMI and of
// the enabled state — a dormant backdoor with admin rights is still a member.
var group = LocalGroupNativeMethods.GetAdministratorsMembers();
if (!group.Succeeded)
_logger.Warning($"{Name}: Failed to enumerate members of '{group.GroupName}': NET_API_STATUS={group.ErrorCode}");
var result = EvaluateAccounts(accounts, group.Members, group.Succeeded, Environment.MachineName, allowedAccounts);
result.AdministratorsGroupErrorCode = group.ErrorCode;
foreach (var name in result.Unexpected)
_logger.Debug($"{Name}: Unexpected local account: {name}");
foreach (var name in result.UnexpectedAdminMembers)
_logger.Debug($"{Name}: Unexpected Administrators member: {name}");
return result;
}
/// <summary>
/// Pure evaluation of the account inventory against the allowed list — separated from
/// the WMI / SAM reads for unit-testing (InternalsVisibleTo).
///
/// Rules:
/// - every local account is checked, disabled ones included (state is reported);
/// - a local account is an Administrators member when its SID matches a group member,
/// or (SID unavailable) its name matches a member of the local machine domain;
/// - <c>UnexpectedAdminMembers</c> = unexpected accounts holding membership, plus local
/// members not on the allowed list that the WMI inventory did not return at all;
/// - members outside the local machine domain (Entra role SIDs, domain groups) are
/// listed for delta comparison but never flagged — they are expected on joined devices.
/// </summary>
internal static LocalAccountCheckResult EvaluateAccounts(
IList<LocalAccountInfo> accounts,
IList<LocalGroupMember> adminMembers,
bool adminGroupEnumerated,
string machineName,
List<string> allowedAccounts)
{
var result = new LocalAccountCheckResult { AdministratorsGroupEnumerated = adminGroupEnumerated };
var adminSids = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var adminLocalNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var member in adminMembers ?? new List<LocalGroupMember>())
{
result.AdministratorsGroupMembers.Add(member.DomainAndName ?? string.Empty);
if (!string.IsNullOrEmpty(member.Sid))
adminSids.Add(member.Sid);
if (IsLocalMember(member, machineName))
adminLocalNames.Add(member.Name);
}
foreach (var account in accounts ?? new List<LocalAccountInfo>())
{
var isAdmin = (!string.IsNullOrEmpty(account.Sid) && adminSids.Contains(account.Sid))
|| adminLocalNames.Contains(account.Name);
result.AllChecked.Add(account.Name);
result.AccountDetails.Add(new Dictionary<string, object>
{
{ "name", account.Name },
{ "disabled", account.Disabled },
{ "administrators_member", isAdmin }
});
if (account.Sid != null && account.Sid.EndsWith("-500", StringComparison.Ordinal))
result.BuiltInAdministratorEnabled = !account.Disabled;
if (IsAllowed(account.Name, allowedAccounts))
continue;
result.Unexpected.Add(account.Name);
if (isAdmin)
result.UnexpectedAdminMembers.Add(account.Name);
}
// Local members the WMI inventory did not surface (WMI failure, or an account the
// provider does not list) — still an unexpected admin when not on the allowed list.
foreach (var name in adminLocalNames)
{
if (IsAllowed(name, allowedAccounts))
continue;
if (result.UnexpectedAdminMembers.Any(u => string.Equals(u, name, StringComparison.OrdinalIgnoreCase)))
continue;
result.UnexpectedAdminMembers.Add(name);
}
return result;
}
private static bool IsLocalMember(LocalGroupMember member, string machineName)
{
var domain = member.Domain;
if (string.IsNullOrEmpty(domain))
return false; // unresolved SID (Entra role, deleted account) — not a local account
return string.Equals(domain, machineName, StringComparison.OrdinalIgnoreCase);
}
private UserProfileCheckResult CheckUserProfiles(List<string> allowedAccounts)
@@ -494,10 +604,36 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Analyzers
public bool KeyExists { get; set; }
}
private class LocalAccountCheckResult
/// <summary>One local account as returned by Win32_UserAccount.</summary>
internal sealed class LocalAccountInfo
{
public string Name { get; set; }
public bool Disabled { get; set; }
public string Sid { get; set; }
}
internal sealed class LocalAccountCheckResult
{
/// <summary>All local account names (enabled and disabled).</summary>
public List<string> AllChecked { get; set; } = new List<string>();
/// <summary>Account names not on the allowed list, disabled ones included.</summary>
public List<string> Unexpected { get; set; } = new List<string>();
/// <summary>Unexpected accounts that are members of the Administrators group.</summary>
public List<string> UnexpectedAdminMembers { get; set; } = new List<string>();
/// <summary>Per-account state: name, disabled, administrators_member.</summary>
public List<Dictionary<string, object>> AccountDetails { get; set; } = new List<Dictionary<string, object>>();
/// <summary>Every Administrators member (DOMAIN\name or SID), local or not — for delta comparison.</summary>
public List<string> AdministratorsGroupMembers { get; set; } = new List<string>();
public bool AdministratorsGroupEnumerated { get; set; }
public int AdministratorsGroupErrorCode { get; set; }
/// <summary>Enabled state of the built-in Administrator (RID 500); null when not found.</summary>
public bool? BuiltInAdministratorEnabled { get; set; }
}
private class UserProfileCheckResult