public static Dictionary <string, object> ReadDomainProperties(SearchResultEntry entry) { var props = new Dictionary <string, object> { { "description", entry.GetProperty("description") } }; if (!int.TryParse(entry.GetProperty("msds-behavior-version"), out var level)) { level = -1; } var func = level switch { 0 => "2000 Mixed/Native", 1 => "2003 Interim", 2 => "2003", 3 => "2008", 4 => "2008 R2", 5 => "2012", 6 => "2012 R2", 7 => "2016", _ => "Unknown" }; props.Add("functionallevel", func); return(props); }
public static Dictionary <string, object> ReadGPOProperties(SearchResultEntry entry) { var props = new Dictionary <string, object> { { "description", entry.GetProperty("description") }, { "gpcpath", entry.GetProperty("gpcfilesyspath") } }; return(props); }
/// <summary> /// Attempts to parse all LDAP attributes outside of the ones already collected and converts them to a human readable format using a best guess /// </summary> /// <param name="entry"></param> private static Dictionary <string, object> ParseAllProperties(SearchResultEntry entry) { var flag = IsTextUnicodeFlags.IS_TEXT_UNICODE_STATISTICS; var props = new Dictionary <string, object>(); foreach (var property in entry.Attributes.AttributeNames) { var propName = property.ToString().ToLower(); if (ReservedAttributes.Contains(propName)) { continue; } var collection = entry.Attributes[propName]; if (collection.Count == 0) { continue; } if (collection.Count == 1) { var testBytes = entry.GetPropertyAsBytes(propName); if (testBytes == null || testBytes.Length == 0 || !IsTextUnicode(testBytes, testBytes.Length, ref flag)) { continue; } var testString = entry.GetProperty(propName); if (!string.IsNullOrEmpty(testString)) { if (propName == "badpasswordtime") { props.Add(propName, Helpers.ConvertFileTimeToUnixEpoch(testString)); } else { props.Add(propName, BestGuessConvert(testString)); } } } else { var arrBytes = entry.GetPropertyAsArrayOfBytes(propName); if (arrBytes.Length == 0 || !IsTextUnicode(arrBytes[0], arrBytes[0].Length, ref flag)) { continue; } var arr = entry.GetPropertyAsArray(propName); if (arr.Length > 0) { props.Add(propName, arr.Select(BestGuessConvert).ToArray()); } } } return(props); }
/// <summary> /// Helper function to print attributes of a SearchResultEntry. Not used currently /// </summary> /// <param name="searchResultEntry"></param> public static void PrintEntry(this SearchResultEntry searchResultEntry) { foreach (var propertyName in searchResultEntry.Attributes.AttributeNames) { var property = propertyName.ToString(); Console.WriteLine(property); Console.WriteLine(searchResultEntry.GetProperty(property)); } }
public static Dictionary <string, object> ReadOUProperties(SearchResultEntry entry) { var props = new Dictionary <string, object> { { "description", entry.GetProperty("description") } }; return(props); }
/// <summary> /// Extension method to determine the type of a SearchResultEntry. /// Requires objectsid, samaccounttype, objectclass /// </summary> /// <param name="searchResultEntry"></param> /// <returns></returns> public static LdapTypeEnum GetLdapType(this SearchResultEntry searchResultEntry) { var objectId = searchResultEntry.GetObjectIdentifier(); if (objectId == null) { return(LdapTypeEnum.Unknown); } if (searchResultEntry.GetPropertyAsBytes("msds-groupmsamembership") != null) { return(LdapTypeEnum.User); } var objectType = LdapTypeEnum.Unknown; var samAccountType = searchResultEntry.GetProperty("samaccounttype"); //Its not a common principal. Lets use properties to figure out what it actually is if (samAccountType != null) { if (samAccountType == "805306370") { return(LdapTypeEnum.Unknown); } objectType = Helpers.SamAccountTypeToType(samAccountType); } else { var objectClasses = searchResultEntry.GetPropertyAsArray("objectClass"); if (objectClasses == null) { objectType = LdapTypeEnum.Unknown; } else if (objectClasses.Contains("groupPolicyContainer")) { objectType = LdapTypeEnum.GPO; } else if (objectClasses.Contains("organizationalUnit")) { objectType = LdapTypeEnum.OU; } else if (objectClasses.Contains("domain")) { objectType = LdapTypeEnum.Domain; } } //Override GMSA object type if (searchResultEntry.GetPropertyAsBytes("msds-groupmsamembership") != null) { objectType = LdapTypeEnum.User; } return(objectType); }
/// <summary> /// Helper function to print attributes of a SearchResultEntry /// </summary> /// <param name="searchResultEntry"></param> public static void PrintEntry(this SearchResultEntry searchResultEntry) { var sb = new StringBuilder(); foreach (var propertyName in searchResultEntry.Attributes.AttributeNames) { var property = propertyName.ToString(); sb.Append(property).Append("\t").Append(searchResultEntry.GetProperty(property)).Append("\n"); } Logging.Log(sb.ToString()); }
public static Dictionary <string, object> ReadGroupProperties(SearchResultEntry entry) { var props = new Dictionary <string, object> { { "description", entry.GetProperty("description") } }; var ac = entry.GetProperty("admincount"); if (ac != null) { var a = int.Parse(ac); props.Add("admincount", a != 0); } else { props.Add("admincount", false); } return(props); }
/// <summary> /// Extension method to determine the BloodHound type of a SearchResultEntry using LDAP properties /// Requires ldap properties objectsid, samaccounttype, objectclass /// </summary> /// <param name="entry"></param> /// <returns></returns> public static Label GetLabel(this SearchResultEntry entry) { //Test if we have the msds-groupmsamembership property first. We want to override this as a user object if (entry.GetPropertyAsBytes("msds-groupmsamembership") != null) { return(Label.User); } var objectId = entry.GetObjectIdentifier(); if (objectId.StartsWith("S-1") && WellKnownPrincipal.GetWellKnownPrincipal(objectId, out var commonPrincipal)) { return(commonPrincipal.ObjectType); } var objectType = Label.Unknown; var samAccountType = entry.GetProperty("samaccounttype"); //Its not a common principal. Lets use properties to figure out what it actually is if (samAccountType != null) { objectType = Helpers.SamAccountTypeToType(samAccountType); } else { var objectClasses = entry.GetPropertyAsArray("objectClass"); if (objectClasses == null) { objectType = Label.Unknown; } else if (objectClasses.Contains("groupPolicyContainer")) { objectType = Label.GPO; } else if (objectClasses.Contains("organizationalUnit")) { objectType = Label.OU; } else if (objectClasses.Contains("domain")) { objectType = Label.Domain; } else if (objectClasses.Contains("container")) { objectType = Label.Container; } } return(objectType); }
/// <summary> /// Helper function to print attributes of a SearchResultEntry /// </summary> /// <param name="searchResultEntry"></param> public static string PrintEntry(this SearchResultEntry searchResultEntry) { var sb = new StringBuilder(); if (searchResultEntry.Attributes.AttributeNames == null) { return(sb.ToString()); } foreach (var propertyName in searchResultEntry.Attributes.AttributeNames) { var property = propertyName.ToString(); sb.Append(property).Append("\t").Append(searchResultEntry.GetProperty(property)).Append("\n"); } return(sb.ToString()); }
/// <summary> /// Extension method to determine the type of a SearchResultEntry. /// Requires objectsid, samaccounttype, objectclass /// </summary> /// <param name="searchResultEntry"></param> /// <returns></returns> public static LdapTypeEnum GetLdapType(this SearchResultEntry searchResultEntry) { var objectSid = searchResultEntry.GetSid(); if (CommonPrincipal.GetCommonSid(objectSid, out var commonPrincipal)) { return(commonPrincipal.Type); } var objectType = LdapTypeEnum.Unknown; var samAccountType = searchResultEntry.GetProperty("samaccounttype"); //Its not a common principal. Lets use properties to figure out what it actually is if (samAccountType != null) { if (samAccountType == "805306370") { return(LdapTypeEnum.Unknown); } objectType = Helpers.SamAccountTypeToType(samAccountType); } else { var objectClasses = searchResultEntry.GetPropertyAsArray("objectClass"); if (objectClasses == null) { objectType = LdapTypeEnum.Unknown; } else if (objectClasses.Contains("groupPolicyContainer")) { objectType = LdapTypeEnum.GPO; } else if (objectClasses.Contains("organizationalUnit")) { objectType = LdapTypeEnum.OU; } else if (objectClasses.Contains("domain")) { objectType = LdapTypeEnum.Domain; } } return(objectType); }
/// <summary> /// Reads the primary group info from a user or computer object and massages it into the proper format. /// </summary> /// <param name="entry"></param> /// <returns></returns> public static string GetPrimaryGroupInfo(SearchResultEntry entry) { var primaryGroupId = entry.GetProperty("primarygroupid"); if (primaryGroupId == null) { return(null); } try { var domainSid = new SecurityIdentifier(entry.GetSid()).AccountDomainSid.Value; var primaryGroupSid = $"{domainSid}-{primaryGroupId}"; return(primaryGroupSid); } catch { return(null); } }
public static async IAsyncEnumerable <GPLink> ReadContainerGPLinks(SearchResultEntry entry) { var gpLinkProp = entry.GetProperty("gplink"); if (gpLinkProp == null) { yield break; } foreach (var link in gpLinkProp.Split(']', '[').Where(x => x.StartsWith("LDAP"))) { var s = link.Split(';'); var dn = s[0].Substring(s[0].IndexOf("CN=", StringComparison.OrdinalIgnoreCase)); var status = s[1]; // 1 and 3 represent Disabled, Not Enforced and Disabled, Enforced respectively. if (status is "3" or "1") { continue; } var enforced = status.Equals("2"); var res = await LDAPUtils.ResolveDistinguishedName(dn); if (res == null) { continue; } yield return(new GPLink { GUID = res.ObjectIdentifier, IsEnforced = enforced }); } }
public static bool ReadBlocksInheritance(SearchResultEntry entry) { var opts = entry.GetProperty("gpoptions"); return(opts is "1"); }
public static async Task <ComputerProperties> ReadComputerProperties(SearchResultEntry entry) { var compProps = new ComputerProperties(); var props = new Dictionary <string, object>(); var uac = entry.GetProperty("useraccountcontrol"); bool enabled, unconstrained, trustedToAuth; if (int.TryParse(uac, out var flag)) { var flags = (UAC.UacFlags)flag; enabled = (flags & UAC.UacFlags.AccountDisable) == 0; unconstrained = (flags & UAC.UacFlags.TrustedForDelegation) == UAC.UacFlags.TrustedForDelegation; trustedToAuth = (flags & UAC.UacFlags.TrustedToAuthForDelegation) != 0; } else { unconstrained = false; enabled = true; trustedToAuth = false; } var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName); var comps = new List <TypedPrincipal>(); if (trustedToAuth) { var delegates = entry.GetPropertyAsArray("msds-allowedToDelegateTo"); props.Add("allowedtodelegate", delegates); foreach (var d in delegates) { var hname = d.Contains("/") ? d.Split('/')[1] : d; hname = hname.Split(':')[0]; var resolvedHost = await LDAPUtils.ResolveHostToSid(hname, domain); if (resolvedHost.Contains(".") || resolvedHost.Contains("S-1")) { comps.Add(new TypedPrincipal { ObjectIdentifier = resolvedHost, ObjectType = Label.Computer }); } } } compProps.AllowedToDelegate = comps.ToArray(); var allowedToActPrincipals = new List <TypedPrincipal>(); var rawAllowedToAct = entry.GetPropertyAsBytes("msDS-AllowedToActOnBehalfOfOtherIdentity"); if (rawAllowedToAct != null) { var sd = new ActiveDirectorySecurity(); sd.SetSecurityDescriptorBinaryForm(rawAllowedToAct, AccessControlSections.Access); foreach (ActiveDirectoryAccessRule rule in sd.GetAccessRules(true, true, typeof(SecurityIdentifier))) { var res = LDAPUtils.ResolveIDAndType(rule.IdentityReference.Value, domain); allowedToActPrincipals.Add(res); } } compProps.AllowedToAct = allowedToActPrincipals.ToArray(); props.Add("enabled", enabled); props.Add("unconstraineddelegation", unconstrained); props.Add("lastlogon", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty("lastlogon"))); props.Add("lastlogontimestamp", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty("lastlogontimestamp"))); props.Add("pwdlastset", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty("pwdlastset"))); props.Add("serviceprincipalnames", entry.GetPropertyAsArray("serviceprincipalname")); var os = entry.GetProperty("operatingsystem"); var sp = entry.GetProperty("operatingsystemservicepack"); if (sp != null) { os = $"{os} {sp}"; } props.Add("operatingsystem", os); props.Add("description", entry.GetProperty("description")); var sh = entry.GetPropertyAsArrayOfBytes("sidhistory"); var sidHistoryList = new List <string>(); var sidHistoryPrincipals = new List <TypedPrincipal>(); foreach (var sid in sh) { string sSid; try { sSid = new SecurityIdentifier(sid, 0).Value; } catch { continue; } var res = LDAPUtils.ResolveIDAndType(sSid, domain); sidHistoryPrincipals.Add(res); } compProps.SidHistory = sidHistoryPrincipals.ToArray(); props.Add("sidhistory", sidHistoryList.ToArray()); compProps.Props = props; return(compProps); }
/// <summary> /// Converts a SaerchResultEntry into an LdapWrapper /// </summary> /// <param name="searchResultEntry"></param> /// <returns></returns> internal static LdapWrapper CreateLdapWrapper(SearchResultEntry searchResultEntry) { //Look for a null DN first. Not sure why this would happen. var distinguishedName = searchResultEntry.DistinguishedName; if (distinguishedName == null) { return(null); } var accountName = searchResultEntry.GetProperty("samaccountname"); var samAccountType = searchResultEntry.GetProperty("samaccounttype"); var accountDomain = Helpers.DistinguishedNameToDomain(distinguishedName); var objectSid = searchResultEntry.GetSid(); var objectId = searchResultEntry.GetObjectIdentifier(); //If objectsid/id is null, return if (objectSid == null && objectId == null) { return(null); } var objectType = LdapTypeEnum.Unknown; string objectIdentifier; LdapWrapper wrapper; //Lets see if its a "common" principal if (objectSid != null && CommonPrincipal.GetCommonSid(objectSid, out var commonPrincipal)) { accountName = commonPrincipal.Name; objectType = commonPrincipal.Type; objectIdentifier = Helpers.ConvertCommonSid(objectSid, accountDomain); } else { //Its not a common principal. Lets use properties to figure out what it actually is if (samAccountType != null) { if (samAccountType == "805306370") { return(null); } objectType = Helpers.SamAccountTypeToType(samAccountType); } else { var objectClasses = searchResultEntry.GetPropertyAsArray("objectClass"); if (objectClasses == null) { objectType = LdapTypeEnum.Unknown; } else if (objectClasses.Contains("groupPolicyContainer")) { objectType = LdapTypeEnum.GPO; } else if (objectClasses.Contains("organizationalUnit")) { objectType = LdapTypeEnum.OU; } else if (objectClasses.Contains("domain")) { objectType = LdapTypeEnum.Domain; } } objectIdentifier = objectId; } //Override GMSA object type if (searchResultEntry.GetPropertyAsBytes("msds-groupmsamembership") != null) { objectType = LdapTypeEnum.User; accountName = accountName?.TrimEnd('$'); } //Depending on the object type, create the appropriate wrapper object switch (objectType) { case LdapTypeEnum.Computer: accountName = accountName?.TrimEnd('$'); wrapper = new Computer(searchResultEntry) { DisplayName = $"{accountName}.{accountDomain}".ToUpper(), SamAccountName = accountName }; var hasLaps = searchResultEntry.GetProperty("ms-mcs-admpwdexpirationtime") != null; wrapper.Properties.Add("haslaps", hasLaps); wrapper.Properties.Add("highvalue", false); break; case LdapTypeEnum.User: wrapper = new User(searchResultEntry) { DisplayName = $"{accountName}@{accountDomain}".ToUpper() }; wrapper.Properties.Add("highvalue", false); break; case LdapTypeEnum.Group: wrapper = new Group(searchResultEntry) { DisplayName = $"{accountName}@{accountDomain}".ToUpper() }; if (objectIdentifier.EndsWith("-512") || objectIdentifier.EndsWith("-516") || objectIdentifier.EndsWith("-519") || objectIdentifier.EndsWith("-520") || objectIdentifier.EndsWith("S-1-5-32-544") || objectIdentifier.EndsWith("S-1-5-32-550") || objectIdentifier.EndsWith("S-1-5-32-549") || objectIdentifier.EndsWith("S-1-5-32-551") || objectIdentifier.EndsWith("S-1-5-32-548")) { wrapper.Properties.Add("highvalue", true); } else { wrapper.Properties.Add("highvalue", false); } break; case LdapTypeEnum.GPO: accountName = searchResultEntry.GetProperty("displayname"); wrapper = new GPO(searchResultEntry) { DisplayName = $"{accountName}@{accountDomain}".ToUpper() }; wrapper.Properties.Add("highvalue", false); break; case LdapTypeEnum.OU: accountName = searchResultEntry.GetProperty("name"); wrapper = new OU(searchResultEntry) { DisplayName = $"{accountName}@{accountDomain}".ToUpper() }; wrapper.Properties.Add("highvalue", false); break; case LdapTypeEnum.Domain: wrapper = new Domain(searchResultEntry) { DisplayName = accountDomain.ToUpper() }; wrapper.Properties.Add("highvalue", true); break; case LdapTypeEnum.Unknown: wrapper = null; break; default: throw new ArgumentOutOfRangeException(); } //Null wrappers happen when we cant resolve the object type. Shouldn't ever happen, but just in case, return null here if (wrapper == null) { Console.WriteLine($"Null Wrapper: {distinguishedName}"); return(null); } //Set the DN/SID for the wrapper going forward and a couple other properties wrapper.DistinguishedName = distinguishedName; wrapper.Properties.Add("name", wrapper.DisplayName); wrapper.Properties.Add("domain", wrapper.Domain); wrapper.Properties.Add("objectid", objectIdentifier.ToUpper()); wrapper.Properties.Add("distinguishedname", distinguishedName); wrapper.ObjectIdentifier = objectIdentifier; //Some post processing PostProcessWrapper(wrapper); //Cache the distinguished name from this object Cache.Instance.Add(wrapper.DistinguishedName, new ResolvedPrincipal { ObjectIdentifier = wrapper.ObjectIdentifier, ObjectType = objectType }); //If the objectidentifier is a SID, cache this mapping too if (objectIdentifier.StartsWith("S-1-5")) { Cache.Instance.Add(wrapper.ObjectIdentifier, objectType); } //Return our wrapper for the next step in the pipeline return(wrapper); }
public ResolvedSearchResult ResolveBloodHoundInfo() { var res = new ResolvedSearchResult(); var objectId = GetObjectIdentifier(); if (objectId == null) { _log.LogWarning("ObjectIdentifier is null for {DN}", DistinguishedName); return(null); } var uac = _entry.GetProperty(LDAPProperties.UserAccountControl); if (int.TryParse(uac, out var flag)) { var flags = (UacFlags)flag; if ((flags & UacFlags.ServerTrustAccount) != 0) { _log.LogTrace("Marked {SID} as a domain controller", objectId); res.IsDomainController = true; _utils.AddDomainController(objectId); } } res.ObjectId = objectId; if (IsDeleted()) { res.Deleted = IsDeleted(); _log.LogTrace("{SID} is tombstoned, skipping rest of resolution", objectId); return(res); } //Try to resolve the domain var distinguishedName = DistinguishedName; string itemDomain; if (distinguishedName == null) { if (objectId.StartsWith("S-1-")) { itemDomain = _utils.GetDomainNameFromSid(objectId); } else { _log.LogWarning("Failed to resolve domain for {ObjectID}", objectId); return(null); } } else { itemDomain = Helpers.DistinguishedNameToDomain(distinguishedName); } _log.LogTrace("Resolved domain for {SID} to {Domain}", objectId, itemDomain); res.Domain = itemDomain; if (WellKnownPrincipal.GetWellKnownPrincipal(objectId, out var wkPrincipal)) { res.DomainSid = _utils.GetSidFromDomainName(itemDomain); res.DisplayName = $"{wkPrincipal.ObjectIdentifier}@{itemDomain}"; res.ObjectType = wkPrincipal.ObjectType; res.ObjectId = _utils.ConvertWellKnownPrincipal(objectId, itemDomain); _log.LogTrace("Resolved {DN} to wkp {ObjectID}", DistinguishedName, res.ObjectId); return(res); } if (objectId.StartsWith("S-1-")) { try { res.DomainSid = new SecurityIdentifier(objectId).AccountDomainSid.Value; } catch { res.DomainSid = _utils.GetSidFromDomainName(itemDomain); } } else { res.DomainSid = _utils.GetSidFromDomainName(itemDomain); } var samAccountName = GetProperty(LDAPProperties.SAMAccountName); var itemType = GetLabel(); res.ObjectType = itemType; if (IsGMSA() || IsMSA()) { res.ObjectType = Label.User; itemType = Label.User; } _log.LogTrace("Resolved type for {SID} to {Label}", objectId, itemType); switch (itemType) { case Label.User: case Label.Group: res.DisplayName = $"{samAccountName}@{itemDomain}"; break; case Label.Computer: var shortName = samAccountName?.TrimEnd('$'); var dns = GetProperty(LDAPProperties.DNSHostName); var cn = GetProperty(LDAPProperties.CanonicalName); if (dns != null) { res.DisplayName = dns; } else if (shortName == null && cn == null) { res.DisplayName = $"UNKNOWN.{itemDomain}"; } else if (shortName != null) { res.DisplayName = $"{shortName}.{itemDomain}"; } else { res.DisplayName = $"{cn}.{itemDomain}"; } break; case Label.GPO: res.DisplayName = $"{GetProperty(LDAPProperties.DisplayName)}@{itemDomain}"; break; case Label.Domain: res.DisplayName = itemDomain; break; case Label.OU: case Label.Container: res.DisplayName = $"{GetProperty(LDAPProperties.Name)}@{itemDomain}"; break; case Label.Base: res.DisplayName = $"{samAccountName}@{itemDomain}"; break; default: throw new ArgumentOutOfRangeException(); } return(res); }
public static async Task <UserProperties> ReadUserProperties(SearchResultEntry entry) { var userProps = new UserProperties(); var props = new Dictionary <string, object> { { "description", entry.GetProperty("description") } }; var uac = entry.GetProperty("useraccountcontrol"); bool enabled, trustedToAuth, sensitive, dontReqPreAuth, passwdNotReq, unconstrained, pwdNeverExpires; if (int.TryParse(uac, out var flag)) { var flags = (UAC.UacFlags)flag; enabled = (flags & UAC.UacFlags.AccountDisable) == 0; trustedToAuth = (flags & UAC.UacFlags.TrustedToAuthForDelegation) != 0; sensitive = (flags & UAC.UacFlags.NotDelegated) != 0; dontReqPreAuth = (flags & UAC.UacFlags.DontReqPreauth) != 0; passwdNotReq = (flags & UAC.UacFlags.PasswordNotRequired) != 0; unconstrained = (flags & UAC.UacFlags.TrustedForDelegation) != 0; pwdNeverExpires = (flags & UAC.UacFlags.DontExpirePassword) != 0; } else { trustedToAuth = false; enabled = true; sensitive = false; dontReqPreAuth = false; passwdNotReq = false; unconstrained = false; pwdNeverExpires = false; } props.Add("sensitive", sensitive); props.Add("dontreqpreauth", dontReqPreAuth); props.Add("passwordnotreqd", passwdNotReq); props.Add("unconstraineddelegation", unconstrained); props.Add("pwdneverexpires", pwdNeverExpires); props.Add("enabled", enabled); var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName); var comps = new List <TypedPrincipal>(); if (trustedToAuth) { var delegates = entry.GetPropertyAsArray("msds-allowedToDelegateTo"); props.Add("allowedtodelegate", delegates); foreach (var d in delegates) { var hname = d.Contains("/") ? d.Split('/')[1] : d; hname = hname.Split(':')[0]; var resolvedHost = await LDAPUtils.ResolveHostToSid(hname, domain); if (resolvedHost.Contains(".") || resolvedHost.Contains("S-1")) { comps.Add(new TypedPrincipal { ObjectIdentifier = resolvedHost, ObjectType = Label.Computer }); } } } userProps.AllowedToDelegate = comps.ToArray(); props.Add("lastlogon", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty("lastlogon"))); props.Add("lastlogontimestamp", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty("lastlogontimestamp"))); props.Add("pwdlastset", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty("pwdlastset"))); var spn = entry.GetPropertyAsArray("serviceprincipalname"); props.Add("serviceprincipalnames", spn); props.Add("hasspn", spn.Length > 0); props.Add("displayname", entry.GetProperty("displayname")); props.Add("email", entry.GetProperty("mail")); props.Add("title", entry.GetProperty("title")); props.Add("homedirectory", entry.GetProperty("homedirectory")); props.Add("description", entry.GetProperty("description")); props.Add("userpassword", entry.GetProperty("userpassword")); var ac = entry.GetProperty("admincount"); if (ac != null) { var a = int.Parse(ac); props.Add("admincount", a != 0); } else { props.Add("admincount", false); } var sh = entry.GetPropertyAsArrayOfBytes("sidhistory"); var sidHistoryList = new List <string>(); var sidHistoryPrincipals = new List <TypedPrincipal>(); foreach (var sid in sh) { string sSid; try { sSid = new SecurityIdentifier(sid, 0).Value; } catch { continue; } var res = LDAPUtils.ResolveIDAndType(sSid, domain); sidHistoryPrincipals.Add(res); } userProps.SidHistory = sidHistoryPrincipals.ToArray(); props.Add("sidhistory", sidHistoryList.ToArray()); userProps.Props = props; return(userProps); }