private static LinkTableRow[] EnumerateLinkTable(JetDb db) { Stopwatch stopwatch = null; if (ShowDebugOutput) { ConsoleEx.WriteDebug($"Called: {nameof(NtdsAudit)}::{nameof(EnumerateLinkTable)}"); stopwatch = new Stopwatch(); stopwatch.Start(); } using (var table = db.OpenJetDbTable(LINKTABLE)) { // Get a dictionary mapping column names to column ids var columnDictionary = table.GetColumnDictionary(); var linktable = new List <LinkTableRow>(); var deletedLinkCount = 0; // Loop over the table table.MoveBeforeFirst(); while (table.TryMoveNext()) { var linkDelTimeColumn = new DateTimeColumnValue { Columnid = columnDictionary["link_deltime"] }; var linkDntColumn = new Int32ColumnValue { Columnid = columnDictionary["link_DNT"] }; var backlinkDnt = new Int32ColumnValue { Columnid = columnDictionary["backlink_DNT"] }; table.RetrieveColumns(linkDelTimeColumn, linkDntColumn, backlinkDnt); // Ignore deleted links if (linkDelTimeColumn.Error == JET_wrn.Success) { deletedLinkCount++; continue; } linktable.Add(new LinkTableRow { LinkDnt = linkDntColumn.Value.Value, BacklinkDnt = backlinkDnt.Value.Value, }); } if (ShowDebugOutput) { ConsoleEx.WriteDebug($" Ignored {deletedLinkCount} deleted backlinks"); ConsoleEx.WriteDebug($" Found {linktable.Count} backlinks"); stopwatch.Stop(); ConsoleEx.WriteDebug($" Completed in {stopwatch.Elapsed}"); } return(linktable.ToArray()); } }
private static MSysObjectsRow[] EnumerateMSysObjects(JetDb db) { Stopwatch stopwatch = null; if (ShowDebugOutput) { ConsoleEx.WriteDebug($"Called: {nameof(NtdsAudit)}::{nameof(EnumerateMSysObjects)}"); stopwatch = new Stopwatch(); stopwatch.Start(); } var mSysObjects = new List <MSysObjectsRow>(); using (var table = db.OpenJetDbTable(MSYSOBJECTS)) { // Get a dictionary mapping column names to column ids var columnDictionary = table.GetColumnDictionary(); // Loop over the table adding attribute ids and column names to the dictionary table.MoveBeforeFirst(); while (table.TryMoveNext()) { var nameColumn = new Utf8StringColumnValue { Columnid = columnDictionary["Name"] }; table.RetrieveColumns(nameColumn); if (nameColumn.Value.StartsWith("ATT", StringComparison.Ordinal)) { mSysObjects.Add(new MSysObjectsRow { AttributeId = int.Parse(Regex.Replace(nameColumn.Value, "[A-Za-z-]", string.Empty, RegexOptions.None), CultureInfo.InvariantCulture), ColumnName = nameColumn.Value, }); } } } if (ShowDebugOutput) { ConsoleEx.WriteDebug($" Found {mSysObjects.Count} datatable column names"); stopwatch.Stop(); ConsoleEx.WriteDebug($" Completed in {stopwatch.Elapsed}"); } return(mSysObjects.ToArray()); }
/// <summary> /// Initializes a new instance of the <see cref="NtdsAudit"/> class. /// </summary> /// <param name="ntdsPath">The path to the NTDS file.</param> /// <param name="dumphashes">A value indicating whether to dump hashes.</param> /// <param name="includeHistoryHashes">A value indicating whether to include history hashes</param> /// <param name="systemHivePath">The path to the System hive.</param> /// <param name="wordlistPath">The path to a wordlist for simple hash cracking.</param> public NtdsAudit(string ntdsPath, bool dumphashes, bool includeHistoryHashes, string systemHivePath, string wordlistPath) { ntdsPath = ntdsPath ?? throw new ArgumentNullException(nameof(ntdsPath)); ProgressBar progress = null; if (!ShowDebugOutput) { progress = new ProgressBar("Performing audit..."); } try { using (var db = new JetDb(ntdsPath)) { _mSysObjects = EnumerateMSysObjects(db); if (!ShowDebugOutput) { progress.Report(8 / (double)100); } _linkTable = EnumerateLinkTable(db); if (!ShowDebugOutput) { progress.Report(16 / (double)100); } _ldapDisplayNameToDatatableColumnNameDictionary = EnumerateDatatableTableLdapDisplayNames(db, _mSysObjects); if (!ShowDebugOutput) { progress.Report(24 / (double)100); } _datatable = EnumerateDatatableTable(db, _ldapDisplayNameToDatatableColumnNameDictionary, dumphashes, includeHistoryHashes); if (!ShowDebugOutput) { progress.Report(32 / (double)100); } } if (dumphashes) { DecryptSecretData(systemHivePath, includeHistoryHashes); if (!ShowDebugOutput) { progress.Report(40 / (double)100); } } CalculateDnsForDatatableRows(); if (!ShowDebugOutput) { progress.Report(48 / (double)100); } CalculateObjectCategoryStringForDatableRows(); if (!ShowDebugOutput) { progress.Report(56 / (double)100); } Domains = CalculateDomainInfo(); if (!ShowDebugOutput) { progress.Report(64 / (double)100); } Users = CalculateUserInfo(); if (!ShowDebugOutput) { progress.Report(72 / (double)100); } if (dumphashes) { if (!string.IsNullOrWhiteSpace(wordlistPath)) { var ntlmHashToPasswordDictionary = PrecomputeHashes(wordlistPath); CheckUsersForWeakPasswords(ntlmHashToPasswordDictionary); } } if (!ShowDebugOutput) { progress.Report(80 / (double)100); } Groups = CalculateSecurityGroupInfo(); if (!ShowDebugOutput) { progress.Report(88 / (double)100); } Computers = CalculateComputerInfo(); if (!ShowDebugOutput) { progress.Report(96 / (double)100); } CalculateGroupMembership(); if (!ShowDebugOutput) { progress.Report(100 / (double)100); } } finally { (progress as IDisposable)?.Dispose(); } }
private static IReadOnlyDictionary <string, string> EnumerateDatatableTableLdapDisplayNames(JetDb db, MSysObjectsRow[] mSysObjects) { Stopwatch stopwatch = null; if (ShowDebugOutput) { ConsoleEx.WriteDebug($"Called: {nameof(NtdsAudit)}::{nameof(EnumerateDatatableTableLdapDisplayNames)}"); stopwatch = new Stopwatch(); stopwatch.Start(); } var ldapDisplayNameToColumnNameDictionary = new Dictionary <string, string>(); var unmatchedCount = 0; using (var table = db.OpenJetDbTable(DATATABLE)) { // Get a dictionary mapping column names to column ids var columnDictionary = table.GetColumnDictionary(); // Loop over the table table.MoveBeforeFirst(); while (table.TryMoveNext()) { var ldapDisplayNameColumn = new StringColumnValue { Columnid = columnDictionary["ATTm131532"] }; var attributeIdColumn = new Int32ColumnValue { Columnid = columnDictionary["ATTc131102"] }; table.RetrieveColumns(attributeIdColumn, ldapDisplayNameColumn); if (attributeIdColumn.Value != null) { if (Array.Find(mSysObjects, x => x.AttributeId == attributeIdColumn.Value) == null) { unmatchedCount++; } else { ldapDisplayNameToColumnNameDictionary.Add(ldapDisplayNameColumn.Value, mSysObjects.First(x => x.AttributeId == attributeIdColumn.Value).ColumnName); } } } } if (ShowDebugOutput) { ConsoleEx.WriteDebug($" Failed to match {unmatchedCount} LDAP display names to datatable column names"); ConsoleEx.WriteDebug($" Matched {ldapDisplayNameToColumnNameDictionary.Count} LDAP display names to datatable column names"); stopwatch.Stop(); ConsoleEx.WriteDebug($" Completed in {stopwatch.Elapsed}"); } return(new ReadOnlyDictionary <string, string>(ldapDisplayNameToColumnNameDictionary)); }
private static DatatableRow[] EnumerateDatatableTable(JetDb db, IReadOnlyDictionary <string, string> ldapDisplayNameToDatatableColumnNameDictionary, bool dumpHashes, bool includeHistoryHashes) { Stopwatch stopwatch = null; if (ShowDebugOutput) { ConsoleEx.WriteDebug($"Called: {nameof(NtdsAudit)}::{nameof(EnumerateDatatableTable)}"); stopwatch = new Stopwatch(); stopwatch.Start(); } var datatable = new List <DatatableRow>(); var deletedCount = 0; using (var table = db.OpenJetDbTable(DATATABLE)) { // Get a dictionary mapping column names to column ids var columnDictionary = table.GetColumnDictionary(); // Loop over the table table.MoveBeforeFirst(); while (table.TryMoveNext()) { var accountExpiresColumn = new BytesColumnValue { Columnid = columnDictionary[ldapDisplayNameToDatatableColumnNameDictionary["accountExpires"]] }; var displayNameColumn = new StringColumnValue { Columnid = columnDictionary[ldapDisplayNameToDatatableColumnNameDictionary["displayName"]] }; var distinguishedNameTagColumn = new Int32ColumnValue { Columnid = columnDictionary["DNT_col"] }; var groupTypeColumn = new Int32ColumnValue { Columnid = columnDictionary[ldapDisplayNameToDatatableColumnNameDictionary["groupType"]] }; var isDeletedColumn = new Int32ColumnValue { Columnid = columnDictionary[ldapDisplayNameToDatatableColumnNameDictionary["isDeleted"]] }; var lastLogonColumn = new LdapDateTimeColumnValue { Columnid = columnDictionary[ldapDisplayNameToDatatableColumnNameDictionary["lastLogonTimestamp"]] }; var lmColumn = new BytesColumnValue { Columnid = columnDictionary[ldapDisplayNameToDatatableColumnNameDictionary["dBCSPwd"]] }; var lmHistoryColumn = new BytesColumnValue { Columnid = columnDictionary[ldapDisplayNameToDatatableColumnNameDictionary["lmPwdHistory"]] }; var nameColumn = new StringColumnValue { Columnid = columnDictionary[ldapDisplayNameToDatatableColumnNameDictionary["name"]] }; var ntColumn = new BytesColumnValue { Columnid = columnDictionary[ldapDisplayNameToDatatableColumnNameDictionary["unicodePwd"]] }; var ntHistoryColumn = new BytesColumnValue { Columnid = columnDictionary[ldapDisplayNameToDatatableColumnNameDictionary["ntPwdHistory"]] }; var objColumn = new BoolColumnValue { Columnid = columnDictionary["OBJ_col"] }; var objectCategoryColumn = new Int32ColumnValue { Columnid = columnDictionary[ldapDisplayNameToDatatableColumnNameDictionary["objectCategory"]] }; var objectSidColumn = new BytesColumnValue { Columnid = columnDictionary[ldapDisplayNameToDatatableColumnNameDictionary["objectSid"]] }; var parentDistinguishedNameTagColumn = new Int32ColumnValue { Columnid = columnDictionary["PDNT_col"] }; var passwordLastSetColumn = new LdapDateTimeColumnValue { Columnid = columnDictionary[ldapDisplayNameToDatatableColumnNameDictionary["pwdLastSet"]] }; var pekListColumn = new BytesColumnValue { Columnid = columnDictionary[ldapDisplayNameToDatatableColumnNameDictionary["pekList"]] }; var primaryGroupIdColumn = new Int32ColumnValue { Columnid = columnDictionary[ldapDisplayNameToDatatableColumnNameDictionary["primaryGroupID"]] }; var rdnTypeColumn = new Int32ColumnValue { Columnid = columnDictionary["RDNtyp_col"] }; // The RDNTyp_col holds the Attribute-ID for the attribute being used as the RDN, such as CN, OU, DC var samAccountNameColumn = new StringColumnValue { Columnid = columnDictionary[ldapDisplayNameToDatatableColumnNameDictionary["sAMAccountName"]] }; var timeColumn = new LdapDateTimeColumnValue { Columnid = columnDictionary["time_col"] }; var userAccountControlColumn = new Int32ColumnValue { Columnid = columnDictionary[ldapDisplayNameToDatatableColumnNameDictionary["userAccountControl"]] }; var supplementalCredentialsColumn = new BytesColumnValue { Columnid = columnDictionary[ldapDisplayNameToDatatableColumnNameDictionary["supplementalCredentials"]] }; var columns = new List <ColumnValue> { accountExpiresColumn, displayNameColumn, distinguishedNameTagColumn, groupTypeColumn, isDeletedColumn, lastLogonColumn, nameColumn, objColumn, objectCategoryColumn, objectSidColumn, parentDistinguishedNameTagColumn, passwordLastSetColumn, primaryGroupIdColumn, rdnTypeColumn, samAccountNameColumn, timeColumn, userAccountControlColumn, }; if (dumpHashes) { columns.Add(pekListColumn); columns.Add(lmColumn); columns.Add(ntColumn); columns.Add(supplementalCredentialsColumn); if (includeHistoryHashes) { columns.Add(lmHistoryColumn); columns.Add(ntHistoryColumn); } } table.RetrieveColumns(columns.ToArray()); // Skip deleted objects if (isDeletedColumn.Value.HasValue && isDeletedColumn.Value != 0) { deletedCount++; continue; } // Some deleted objects do not have the isDeleted flag but do have a string appended to the DN (https://support.microsoft.com/en-us/help/248047/phantoms--tombstones-and-the-infrastructure-master) if (nameColumn.Value?.Contains("\nDEL:") ?? false) { deletedCount++; continue; } SecurityIdentifier sid = null; uint rid = 0; if (objectSidColumn.Error == JET_wrn.Success) { var sidBytes = objectSidColumn.Value; var ridBytes = sidBytes.Skip(sidBytes.Length - sizeof(int)).Take(sizeof(int)).Reverse().ToArray(); sidBytes = sidBytes.Take(sidBytes.Length - sizeof(int)).Concat(ridBytes).ToArray(); rid = BitConverter.ToUInt32(ridBytes, 0); sid = new SecurityIdentifier(sidBytes, 0); } var row = new DatatableRow { AccountExpires = accountExpiresColumn.Value, DisplayName = displayNameColumn.Value, Dnt = distinguishedNameTagColumn.Value, GroupType = groupTypeColumn.Value, LastLogon = lastLogonColumn.Value, Name = nameColumn.Value, ObjectCategoryDnt = objectCategoryColumn.Value, Rid = rid, Sid = sid, ParentDnt = parentDistinguishedNameTagColumn.Value, Phantom = objColumn.Value == false, LastPasswordChange = passwordLastSetColumn.Value, PrimaryGroupDnt = primaryGroupIdColumn.Value, RdnType = rdnTypeColumn.Value, SamAccountName = samAccountNameColumn.Value, UserAccountControlValue = userAccountControlColumn.Value, }; if (dumpHashes) { if (pekListColumn.Value != null) { row.PekList = pekListColumn.Value; } if (lmColumn.Value != null) { row.EncryptedLmHash = lmColumn.Value; } if (ntColumn.Value != null) { row.EncryptedNtHash = ntColumn.Value; } if (includeHistoryHashes) { if (lmHistoryColumn.Value != null) { row.EncryptedLmHistory = lmHistoryColumn.Value; } if (ntHistoryColumn.Value != null) { row.EncryptedNtHistory = ntHistoryColumn.Value; } } if (supplementalCredentialsColumn.Value != null) { row.SupplementalCredentialsBlob = supplementalCredentialsColumn.Value; } } datatable.Add(row); } } if (ShowDebugOutput) { ConsoleEx.WriteDebug($" Skipped {deletedCount} deleted objects"); ConsoleEx.WriteDebug($" Enumerated {datatable.Count} objects"); stopwatch.Stop(); ConsoleEx.WriteDebug($" Completed in {stopwatch.Elapsed}"); } return(datatable.ToArray()); }