From cf59335bc3bd9e40a7dfc3314634e78b0e79c5a4 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Mon, 18 Jul 2022 15:22:39 -0400 Subject: [PATCH 01/26] wip: more updates --- SharpHoundCommon.sln | 8 +- .../Processors/LDAPPropertyProcessor.cs | 3 +- .../Processors/LocalGroupProcessor.cs | 111 ++++++++++ src/CommonLib/ResolvedSearchResult.cs | 2 +- src/CommonLib/SharpHoundCommonLib.csproj | 13 +- src/SharpHoundRPC/Extensions.cs | 12 ++ src/SharpHoundRPC/Handles/BasePointer.cs | 68 ++++++ src/SharpHoundRPC/Handles/LSAHandle.cs | 33 +++ src/SharpHoundRPC/Handles/LSAPointer.cs | 26 +++ src/SharpHoundRPC/Handles/SAMHandle.cs | 34 +++ src/SharpHoundRPC/Handles/SAMPointer.cs | 25 +++ src/SharpHoundRPC/Handles/SAMSidArray.cs | 32 +++ src/SharpHoundRPC/LSANative/LSAEnums.cs | 45 ++++ src/SharpHoundRPC/LSANative/LSAMethods.cs | 126 +++++++++++ src/SharpHoundRPC/LSANative/LSAStructs.cs | 57 +++++ src/SharpHoundRPC/LSANative/UserRights.cs | 61 ++++++ src/SharpHoundRPC/NTStatus.cs | 17 ++ src/SharpHoundRPC/RPCException.cs | 39 ++++ src/SharpHoundRPC/SAMRPCNative/SAMEnums.cs | 75 +++++++ src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs | 203 ++++++++++++++++++ src/SharpHoundRPC/SAMRPCNative/SAMStructs.cs | 35 +++ src/SharpHoundRPC/Shared/SharedEnums.cs | 20 ++ src/SharpHoundRPC/Shared/SharedStructs.cs | 38 ++++ src/SharpHoundRPC/SharpHoundRPC.csproj | 25 +++ src/SharpHoundRPC/Wrappers/LSABase.cs | 33 +++ src/SharpHoundRPC/Wrappers/LSAPolicy.cs | 53 +++++ src/SharpHoundRPC/Wrappers/SAMAlias.cs | 22 ++ src/SharpHoundRPC/Wrappers/SAMBase.cs | 33 +++ src/SharpHoundRPC/Wrappers/SAMDomain.cs | 39 ++++ src/SharpHoundRPC/Wrappers/SAMServer.cs | 90 ++++++++ 30 files changed, 1369 insertions(+), 9 deletions(-) create mode 100644 src/CommonLib/Processors/LocalGroupProcessor.cs create mode 100644 src/SharpHoundRPC/Extensions.cs create mode 100644 src/SharpHoundRPC/Handles/BasePointer.cs create mode 100644 src/SharpHoundRPC/Handles/LSAHandle.cs create mode 100644 src/SharpHoundRPC/Handles/LSAPointer.cs create mode 100644 src/SharpHoundRPC/Handles/SAMHandle.cs create mode 100644 src/SharpHoundRPC/Handles/SAMPointer.cs create mode 100644 src/SharpHoundRPC/Handles/SAMSidArray.cs create mode 100644 src/SharpHoundRPC/LSANative/LSAEnums.cs create mode 100644 src/SharpHoundRPC/LSANative/LSAMethods.cs create mode 100644 src/SharpHoundRPC/LSANative/LSAStructs.cs create mode 100644 src/SharpHoundRPC/LSANative/UserRights.cs create mode 100644 src/SharpHoundRPC/NTStatus.cs create mode 100644 src/SharpHoundRPC/RPCException.cs create mode 100644 src/SharpHoundRPC/SAMRPCNative/SAMEnums.cs create mode 100644 src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs create mode 100644 src/SharpHoundRPC/SAMRPCNative/SAMStructs.cs create mode 100644 src/SharpHoundRPC/Shared/SharedEnums.cs create mode 100644 src/SharpHoundRPC/Shared/SharedStructs.cs create mode 100644 src/SharpHoundRPC/SharpHoundRPC.csproj create mode 100644 src/SharpHoundRPC/Wrappers/LSABase.cs create mode 100644 src/SharpHoundRPC/Wrappers/LSAPolicy.cs create mode 100644 src/SharpHoundRPC/Wrappers/SAMAlias.cs create mode 100644 src/SharpHoundRPC/Wrappers/SAMBase.cs create mode 100644 src/SharpHoundRPC/Wrappers/SAMDomain.cs create mode 100644 src/SharpHoundRPC/Wrappers/SAMServer.cs diff --git a/SharpHoundCommon.sln b/SharpHoundCommon.sln index 73a7798c..8d0c35a8 100644 --- a/SharpHoundCommon.sln +++ b/SharpHoundCommon.sln @@ -1,12 +1,14 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# +# Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpHoundCommonLib", "src\CommonLib\SharpHoundCommonLib.csproj", "{88EB8B09-EB8A-4E59-BBF7-CA5374DFA9EB}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommonLibTest", "test\unit\CommonLibTest.csproj", "{F1E060CB-58D0-42A7-9BBC-E08C6FD5DD43}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Docfx", "docfx\Docfx.csproj", "{BD8C7EB8-F357-4499-8C08-76B42F600076}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpHoundRPC", "src\SharpHoundRPC\SharpHoundRPC.csproj", "{4F06116D-88A7-4601-AB28-B48F2857D458}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -25,5 +27,9 @@ Global {BD8C7EB8-F357-4499-8C08-76B42F600076}.Debug|Any CPU.Build.0 = Debug|Any CPU {BD8C7EB8-F357-4499-8C08-76B42F600076}.Release|Any CPU.ActiveCfg = Release|Any CPU {BD8C7EB8-F357-4499-8C08-76B42F600076}.Release|Any CPU.Build.0 = Release|Any CPU + {4F06116D-88A7-4601-AB28-B48F2857D458}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4F06116D-88A7-4601-AB28-B48F2857D458}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4F06116D-88A7-4601-AB28-B48F2857D458}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4F06116D-88A7-4601-AB28-B48F2857D458}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/src/CommonLib/Processors/LDAPPropertyProcessor.cs b/src/CommonLib/Processors/LDAPPropertyProcessor.cs index 4fc0ab93..928b59e9 100644 --- a/src/CommonLib/Processors/LDAPPropertyProcessor.cs +++ b/src/CommonLib/Processors/LDAPPropertyProcessor.cs @@ -21,8 +21,7 @@ public class LDAPPropertyProcessor "homedirectory", "description", "admincount", "userpassword", "gpcfilesyspath", "objectclass", "msds-behavior-version", "objectguid", "name", "gpoptions", "msds-allowedtodelegateto", "msDS-allowedtoactonbehalfofotheridentity", "displayname", - "sidhistory", "samaccountname", "samaccounttype", "objectsid", "objectguid", "objectclass", - "samaccountname", "msds-groupmsamembership", + "sidhistory", "samaccountname", "samaccounttype", "objectsid", "objectguid", "objectclass", "msds-groupmsamembership", "distinguishedname", "memberof", "logonhours", "ntsecuritydescriptor", "dsasignature", "repluptodatevector", "member", "whenCreated" }; diff --git a/src/CommonLib/Processors/LocalGroupProcessor.cs b/src/CommonLib/Processors/LocalGroupProcessor.cs new file mode 100644 index 00000000..81d77bd6 --- /dev/null +++ b/src/CommonLib/Processors/LocalGroupProcessor.cs @@ -0,0 +1,111 @@ +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Security.Principal; +using Microsoft.Extensions.Logging; +using SharpHoundCommonLib.Enums; +using SharpHoundCommonLib.OutputTypes; +using SharpHoundRPC; +using SharpHoundRPC.Wrappers; + +namespace SharpHoundCommonLib.Processors +{ + public class LocalGroupProcessor + { + private readonly ILogger _log; + private readonly ILDAPUtils _utils; + private readonly string[] _filteredSids = + { + "S-1-5-2", "S-1-5-2", "S-1-5-3", "S-1-5-4", "S-1-5-6", "S-1-5-7", "S-1-2", "S-1-2-0", "S-1-5-18", + "S-1-5-19", "S-1-5-20" + }; + + public LocalGroupProcessor(ILDAPUtils utils, ILogger log = null) + { + _utils = utils; + _log = log ?? Logging.LogProvider.CreateLogger("LocalGroupProcessor"); + } + + public IEnumerable GetLocalGroups(SAMServer server, string computerDomainSid, string computerDomain) + { + var groupCache = new ConcurrentBag(); + var typeCache = new ConcurrentDictionary(); + var computerSid = new SecurityIdentifier(computerDomainSid); + + var machineSid = server.GetMachineSid(); + foreach (var domainResult in server.GetDomains()) + { + var ret = new LocalGroupAPIResult + { + Name = domainResult.Name, + GroupRID = domainResult.Rid, + }; + + try + { + var domain = server.OpenDomain(domainResult.Name); + foreach (var alias in domain.GetAliases()) + { + var results = new List(); + var localGroup = domain.OpenAlias(alias.Rid); + foreach (var securityIdentifier in localGroup.GetMembers()) + { + if (IsSidFiltered(securityIdentifier)) + continue; + + if (server.IsDomainController(computerSid)) + { + if (_utils.GetWellKnownPrincipal(securityIdentifier.Value, computerDomain, + out var wellKnown)) + { + results.Add(wellKnown); + } + else + { + results.Add(_utils.ResolveIDAndType(securityIdentifier.Value, computerDomain)); + } + } + else + { + if (WellKnownPrincipal.GetWellKnownPrincipal(securityIdentifier.Value, + out var wellKnown)) + { + wellKnown.ObjectIdentifier = $"{machineSid.Value}-{securityIdentifier.Rid()}"; + if (wellKnown.ObjectType == Label.User) + wellKnown.ObjectType = Label.LocalUser; + else if (wellKnown.ObjectType == Label.Group) + wellKnown.ObjectType = Label.LocalGroup; + results.Add(wellKnown); + } else + { + + } + } + } + } + } + catch (RPCException e) + { + ret.Collected = false; + ret.FailureReason = e.ToString(); + } + yield return ret; + } + } + + private bool IsSidFiltered(SecurityIdentifier identifier) + { + var value = identifier.Value; + + if (value.StartsWith("S-1-5-80") || value.StartsWith("S-1-5-82") || + value.StartsWith("S-1-5-90") || value.StartsWith("S-1-5-96")) + return true; + + if (_filteredSids.Contains(value)) + return true; + + return false; + } + } +} \ No newline at end of file diff --git a/src/CommonLib/ResolvedSearchResult.cs b/src/CommonLib/ResolvedSearchResult.cs index 2287c8bd..adb40a3c 100644 --- a/src/CommonLib/ResolvedSearchResult.cs +++ b/src/CommonLib/ResolvedSearchResult.cs @@ -9,7 +9,7 @@ public class ResolvedSearchResult public string DisplayName { - get => _displayName.ToUpper(); + get => _displayName?.ToUpper(); set => _displayName = value; } diff --git a/src/CommonLib/SharpHoundCommonLib.csproj b/src/CommonLib/SharpHoundCommonLib.csproj index 88e6acc6..4b2e0f41 100644 --- a/src/CommonLib/SharpHoundCommonLib.csproj +++ b/src/CommonLib/SharpHoundCommonLib.csproj @@ -18,14 +18,17 @@ full - - + + - - + + - + + + + diff --git a/src/SharpHoundRPC/Extensions.cs b/src/SharpHoundRPC/Extensions.cs new file mode 100644 index 00000000..31f21428 --- /dev/null +++ b/src/SharpHoundRPC/Extensions.cs @@ -0,0 +1,12 @@ +namespace SharpHoundRPC +{ + public static class Extensions + { + public static void CheckError(this NtStatus status, string apiCall) + { + if (status != NtStatus.StatusSuccess && status != NtStatus.StatusMoreEntries && + status != NtStatus.StatusSomeMapped && status != NtStatus.StatusNoMoreEntries) + throw new RPCException(apiCall, status); + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/Handles/BasePointer.cs b/src/SharpHoundRPC/Handles/BasePointer.cs new file mode 100644 index 00000000..92b63c47 --- /dev/null +++ b/src/SharpHoundRPC/Handles/BasePointer.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Security.Principal; +using Microsoft.Win32.SafeHandles; + +namespace SharpHoundRPC.Handles +{ + public abstract class BasePointer : SafeHandleZeroOrMinusOneIsInvalid + { + protected BasePointer() : base(true) + { + } + + protected BasePointer(bool ownsHandle) : base(ownsHandle) + { + } + + protected BasePointer(IntPtr handle) : base(true) + { + SetHandle(handle); + } + + protected BasePointer(IntPtr handle, bool ownsHandle) : base(ownsHandle) + { + SetHandle(handle); + } + + public IEnumerable GetEnumerable(int count) + { + for (var i = 0; i < count; i++) + if (typeof(T) == typeof(int)) + yield return (T) (object) ReadInt32(i); + else if (typeof(T) == typeof(long)) + yield return (T) (object) ReadInt64(i); + else if (typeof(T) == typeof(SecurityIdentifier)) + yield return (T) (object) new SecurityIdentifier(ReadIntPtr(i)); + else + yield return Marshal.PtrToStructure(handle + Marshal.SizeOf() * i); + } + + public T GetData() + { + if (typeof(T) == typeof(int)) return (T) (object) ReadInt32(); + + if (typeof(T) == typeof(long)) return (T) (object) ReadInt64(); + + if (typeof(T) == typeof(SecurityIdentifier)) return (T) (object) new SecurityIdentifier(handle); + + return Marshal.PtrToStructure(handle); + } + + private int ReadInt32(int offset = 0) + { + return Marshal.ReadInt32(handle + offset * Marshal.SizeOf()); + } + + private long ReadInt64(int offset = 0) + { + return Marshal.ReadInt64(handle + offset * Marshal.SizeOf()); + } + + private IntPtr ReadIntPtr(int offset = 0) + { + return Marshal.ReadIntPtr(handle + offset * Marshal.SizeOf()); + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/Handles/LSAHandle.cs b/src/SharpHoundRPC/Handles/LSAHandle.cs new file mode 100644 index 00000000..0bd941ca --- /dev/null +++ b/src/SharpHoundRPC/Handles/LSAHandle.cs @@ -0,0 +1,33 @@ +using System; +using Microsoft.Win32.SafeHandles; +using SharpHoundRPC.LSANative; + +namespace SharpHoundRPC.Handles +{ + public class LSAHandle : SafeHandleZeroOrMinusOneIsInvalid + { + public LSAHandle() : base(true) + { + } + + public LSAHandle(IntPtr handle, bool ownsHandle) : base(ownsHandle) + { + SetHandle(handle); + } + + public LSAHandle(bool ownsHandle) : base(true) + { + } + + protected override bool ReleaseHandle() + { + if (handle == IntPtr.Zero) return true; + return LSAMethods.LsaClose(handle) == NtStatus.StatusSuccess; + } + + ~LSAHandle() + { + Dispose(); + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/Handles/LSAPointer.cs b/src/SharpHoundRPC/Handles/LSAPointer.cs new file mode 100644 index 00000000..d36a52ce --- /dev/null +++ b/src/SharpHoundRPC/Handles/LSAPointer.cs @@ -0,0 +1,26 @@ +using System; +using SharpHoundRPC.LSANative; + +namespace SharpHoundRPC.Handles +{ + public class LSAPointer : BasePointer + { + public LSAPointer() : base(true) + { + } + + public LSAPointer(IntPtr handle) : base(handle, true) + { + } + + public LSAPointer(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle) + { + SetHandle(handle); + } + + protected override bool ReleaseHandle() + { + return LSAMethods.LsaFreeMemory(handle) == NtStatus.StatusSuccess; + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/Handles/SAMHandle.cs b/src/SharpHoundRPC/Handles/SAMHandle.cs new file mode 100644 index 00000000..101dd3a9 --- /dev/null +++ b/src/SharpHoundRPC/Handles/SAMHandle.cs @@ -0,0 +1,34 @@ +using System; +using Microsoft.Win32.SafeHandles; +using SharpHoundRPC.SAMRPCNative; + +namespace SharpHoundRPC.Handles +{ + public class SAMHandle : SafeHandleZeroOrMinusOneIsInvalid + { + public SAMHandle() : base(true) + { + } + + public SAMHandle(IntPtr handle) : base(true) + { + SetHandle(handle); + } + + public SAMHandle(IntPtr handle, bool ownsHandle) : base(ownsHandle) + { + SetHandle(handle); + } + + protected override bool ReleaseHandle() + { + if (handle == IntPtr.Zero) return true; + return SAMMethods.SamCloseHandle(handle) == NtStatus.StatusSuccess; + } + + ~SAMHandle() + { + Dispose(); + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/Handles/SAMPointer.cs b/src/SharpHoundRPC/Handles/SAMPointer.cs new file mode 100644 index 00000000..932b1376 --- /dev/null +++ b/src/SharpHoundRPC/Handles/SAMPointer.cs @@ -0,0 +1,25 @@ +using System; +using SharpHoundRPC.SAMRPCNative; + +namespace SharpHoundRPC.Handles +{ + public class SAMPointer : BasePointer + { + public SAMPointer() : base(true) + { + } + + public SAMPointer(IntPtr handle) : base(handle, true) + { + } + + public SAMPointer(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle) + { + } + + protected override bool ReleaseHandle() + { + return SAMMethods.SamFreeMemory(handle) == NtStatus.StatusSuccess; + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/Handles/SAMSidArray.cs b/src/SharpHoundRPC/Handles/SAMSidArray.cs new file mode 100644 index 00000000..492fe3fb --- /dev/null +++ b/src/SharpHoundRPC/Handles/SAMSidArray.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Security.Principal; + +namespace SharpHoundRPC.Handles +{ + public class SAMSidArray : SAMPointer + { + public SAMSidArray() + { + } + + public SAMSidArray(IntPtr handle) : base(handle) + { + } + + public SAMSidArray(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle) + { + } + + public IEnumerable GetData(int count) + { + for (var i = 0; i < count; i++) + { + var rawPtr = Marshal.ReadIntPtr(handle, Marshal.SizeOf() * i); + var sid = new SecurityIdentifier(rawPtr); + yield return sid; + } + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/LSANative/LSAEnums.cs b/src/SharpHoundRPC/LSANative/LSAEnums.cs new file mode 100644 index 00000000..25616adc --- /dev/null +++ b/src/SharpHoundRPC/LSANative/LSAEnums.cs @@ -0,0 +1,45 @@ +using System; + +namespace SharpHoundRPC.LSANative +{ + public class LSAEnums + { + [Flags] + public enum LsaOpenMask + { + ViewLocalInfo = 0x1, + ViewAuditInfo = 0x2, + GetPrivateInfo = 0x4, + TrustAdmin = 0x8, + CreateAccount = 0x10, + CreateSecret = 0x20, + CreatePrivilege = 0x40, + SetDefaultQuotaLimits = 0x80, + SetAuditRequirements = 0x100, + AuditLogAdmin = 0x200, + ServerAdmin = 0x400, + LookupNames = 0x800, + Notification = 0x1000, + POLICY_READ = 0x20006, + POLICY_ALL_ACCESS = 0x00F0FFF, + POLICY_EXECUTE = 0X20801, + POLICY_WRITE = 0X207F8 + } + + public enum LSAPolicyInformation + { + PolicyAuditLogInformation = 1, + PolicyAuditEventsInformation, + PolicyPrimaryDomainInformation, + PolicyPdAccountInformation, + PolicyAccountDomainInformation, + PolicyLsaServerRoleInformation, + PolicyReplicaSourceInformation, + PolicyDefaultQuotaInformation, + PolicyModificationInformation, + PolicyAuditFullSetInformation, + PolicyAuditFullQueryInformation, + PolicyDnsDomainInformation + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/LSANative/LSAMethods.cs b/src/SharpHoundRPC/LSANative/LSAMethods.cs new file mode 100644 index 00000000..2edc4ff2 --- /dev/null +++ b/src/SharpHoundRPC/LSANative/LSAMethods.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Principal; +using SharpHoundRPC.Handles; +using SharpHoundRPC.Shared; + +namespace SharpHoundRPC.LSANative +{ + [SuppressUnmanagedCodeSecurity] + public class LSAMethods + { + internal static LSAHandle LsaOpenPolicy(string computerName, LSAEnums.LsaOpenMask desiredAccess) + { + var us = new SharedStructs.UnicodeString(computerName); + var objectAttributes = default(LSAStructs.ObjectAttributes); + var status = LsaOpenPolicy(ref us, ref objectAttributes, desiredAccess, out var handle); + status.CheckError("LsaOpenPolicy"); + + return handle; + } + + [DllImport("advapi32.dll")] + private static extern NtStatus LsaOpenPolicy( + ref SharedStructs.UnicodeString server, + ref LSAStructs.ObjectAttributes objectAttributes, + LSAEnums.LsaOpenMask desiredAccess, + out LSAHandle policyHandle + ); + + [DllImport("advapi32.dll")] + internal static extern NtStatus LsaClose( + IntPtr buffer + ); + + [DllImport("advapi32.dll")] + internal static extern NtStatus LsaFreeMemory( + IntPtr buffer + ); + + internal static LSAPointer LsaQueryInformationPolicy(LSAHandle policyHandle, + LSAEnums.LSAPolicyInformation policyInformation) + { + var status = LsaQueryInformationPolicy(policyHandle, policyInformation, out var pointer); + status.CheckError("LSAQueryInformationPolicy"); + + return pointer; + } + + [DllImport("advapi32.dll")] + private static extern NtStatus LsaQueryInformationPolicy( + LSAHandle policyHandle, + LSAEnums.LSAPolicyInformation policyInformation, + out LSAPointer buffer + ); + + internal static IEnumerable LsaEnumerateAccountsWithUserRight(LSAHandle policyHandle, + string userRight) + { + var arr = new SharedStructs.UnicodeString[1]; + arr[0] = new SharedStructs.UnicodeString(userRight); + + var status = LsaEnumerateAccountsWithUserRight(policyHandle, arr, out var sids, out var count); + status.CheckError("LsaEnumerateAccountsWithUserRight"); + + return sids.GetEnumerable(count); + } + + [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern NtStatus LsaEnumerateAccountsWithUserRight( + LSAHandle policyHandle, + SharedStructs.UnicodeString[] userRight, + out LSAPointer sids, + out int count + ); + + internal static IEnumerable<(SecurityIdentifier SID, string Name, SharedEnums.SidNameUse Use, string Domain)> + LsaLookupSids(LSAHandle policyHandle, + SecurityIdentifier[] sids) + { + var count = sids.Length; + var gcHandles = new GCHandle[count]; + var pSids = new IntPtr[count]; + + for (var i = 0; i < count; i++) + { + var sid = sids[i]; + var b = new byte[sid.BinaryLength]; + sid.GetBinaryForm(b, 0); + gcHandles[i] = GCHandle.Alloc(b, GCHandleType.Pinned); + pSids[i] = gcHandles[i].AddrOfPinnedObject(); + } + + try + { + var status = LsaLookupSids(policyHandle, count, pSids, out var referencedDomains, out var names); + status.CheckError("LsaLookupSids"); + + var translatedNames = names.GetEnumerable(count).ToArray(); + var domainList = referencedDomains.GetData(); + var safeDomains = new LSAPointer(domainList.Domains); + var domains = safeDomains.GetEnumerable(domainList.Entries).ToArray(); + for (var i = 0; i < count; i++) + yield return (sids[i], translatedNames[i].Name.ToString(), translatedNames[i].Use, + domains[translatedNames[i].DomainIndex].Name.ToString()); + } + finally + { + foreach (var handle in gcHandles) + if (handle.IsAllocated) + handle.Free(); + } + } + + [DllImport("advapi32.dll")] + private static extern NtStatus LsaLookupSids( + LSAHandle policyHandle, + int count, + [MarshalAs(UnmanagedType.LPArray)] IntPtr[] sidArray, + out LSAPointer referencedDomains, + out LSAPointer names + ); + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/LSANative/LSAStructs.cs b/src/SharpHoundRPC/LSANative/LSAStructs.cs new file mode 100644 index 00000000..dd00e1b9 --- /dev/null +++ b/src/SharpHoundRPC/LSANative/LSAStructs.cs @@ -0,0 +1,57 @@ +using System; +using System.Runtime.InteropServices; +using SharpHoundRPC.Shared; + +namespace SharpHoundRPC.LSANative +{ + public class LSAStructs + { + [StructLayout(LayoutKind.Sequential)] + public struct ObjectAttributes + { + public int Length; + public IntPtr RootDirectory; + public IntPtr ObjectName; + public int Attributes; + public IntPtr SecurityDescriptor; + public IntPtr SecurityQualityOfService; + + public void Dispose() + { + if (ObjectName == IntPtr.Zero) return; + Marshal.DestroyStructure(ObjectName, typeof(SharedStructs.UnicodeString)); + Marshal.FreeHGlobal(ObjectName); + ObjectName = IntPtr.Zero; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct PolicyAccountDomainInfo + { + public SharedStructs.UnicodeString DomainName; + public IntPtr DomainSid; + } + + [StructLayout(LayoutKind.Sequential)] + public struct LSATranslatedNames + { + public SharedEnums.SidNameUse Use; + public SharedStructs.UnicodeString Name; + public int DomainIndex; + } + + [StructLayout(LayoutKind.Sequential)] + public struct LSAReferencedDomains + { + public int Entries; + public IntPtr Domains; + } + + [StructLayout(LayoutKind.Sequential)] + public struct LSATrustInformation + { + public SharedStructs.UnicodeString Name; + public IntPtr Sid; + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/LSANative/UserRights.cs b/src/SharpHoundRPC/LSANative/UserRights.cs new file mode 100644 index 00000000..fe29dcff --- /dev/null +++ b/src/SharpHoundRPC/LSANative/UserRights.cs @@ -0,0 +1,61 @@ +namespace SharpHoundRPC.LSANative +{ + public class UserRights + { + public const string AssignPrimaryToken = "SeAssignPrimaryTokenPrivilege"; + public const string Audit = "SeAuditPrivilege"; + public const string Backup = "SeBackupPrivilege"; + public const string BatchLogon = "SeBatchLogonRight"; + public const string ChangeNotify = "SeChangeNotifyPrivilege"; + public const string CreateGlobal = "SeCreateGlobalPrivilege"; + public const string CreatePagefile = "SeCreatePagefilePrivilege"; + public const string CreatePermanent = "SeCreatePermanentPrivilege"; + public const string CreateSymbolicLink = "SeCreateSymbolicLinkPrivilege"; + public const string CreateToken = "SeCreateTokenPrivilege"; + public const string Debug = "SeDebugPrivilege"; + public const string DenyBatchLogon = "SeDenyBatchLogonRight"; + public const string DenyInteractiveLogon = "SeDenyInteractiveLogonRight"; + public const string DenyNetworkLogon = "SeDenyNetworkLogonRight"; + public const string DenyRemoteInteractiveLogon = "SeDenyRemoteInteractiveLogonRight"; + public const string DenyServiceLogon = "SeDenyServiceLogonRight"; + public const string EnableDelegation = "SeEnableDelegationPrivilege"; + public const string Impersonate = "SeImpersonatePrivilege"; + public const string IncreaseBasePriority = "SeIncreaseBasePriorityPrivilege"; + public const string IncreaseQuota = "SeIncreaseQuotaPrivilege"; + public const string IncreaseWorkingSet = "SeIncreaseWorkingSetPrivilege"; + public const string InteractiveLogon = "SeInteractiveLogonRight"; + public const string LoadDriver = "SeLoadDriverPrivilege"; + public const string LockMemory = "SeLockMemoryPrivilege"; + public const string MachineAccount = "SeMachineAccountPrivilege"; + public const string ManageVolume = "SeManageVolumePrivilege"; + public const string NetworkLogon = "SeNetworkLogonRight"; + public const string ProfileSingleProcess = "SeProfileSingleProcessPrivilege"; + public const string Relabel = "SeRelabelPrivilege"; + public const string RemoteInteractiveLogon = "SeRemoteInteractiveLogonRight"; + public const string RemoteShutdown = "SeRemoteShutdownPrivilege"; + public const string Restore = "SeRestorePrivilege"; + public const string Security = "SeSecurityPrivilege"; + public const string ServiceLogon = "SeServiceLogonRight"; + public const string Shutdown = "SeShutdownPrivilege"; + public const string SyncAgent = "SeSyncAgentPrivilege"; + public const string SystemEnvironment = "SeSystemEnvironmentPrivilege"; + public const string SystemProfile = "SeSystemProfilePrivilege"; + public const string SystemTime = "SeSystemtimePrivilege"; + public const string TakeOwnership = "SeTakeOwnershipPrivilege"; + public const string Tcb = "SeTcbPrivilege"; + public const string TimeZone = "SeTimeZonePrivilege"; + public const string TrustedCredManAccess = "SeTrustedCredManAccessPrivilege"; + public const string Undock = "SeUndockPrivilege"; + + public static readonly string[] AllPrivileges = + { + AssignPrimaryToken, Audit, Backup, BatchLogon, ChangeNotify, CreateGlobal, CreatePagefile, CreatePermanent, + CreateSymbolicLink, CreateToken, Debug, DenyBatchLogon, DenyInteractiveLogon, DenyNetworkLogon, + DenyRemoteInteractiveLogon, DenyServiceLogon, EnableDelegation, Impersonate, IncreaseBasePriority, + IncreaseQuota, IncreaseWorkingSet, InteractiveLogon, LoadDriver, LockMemory, MachineAccount, ManageVolume, + NetworkLogon, ProfileSingleProcess, Relabel, RemoteInteractiveLogon, RemoteShutdown, Restore, Security, + ServiceLogon, Shutdown, SyncAgent, SystemEnvironment, SystemProfile, SystemTime, TakeOwnership, Tcb, + TimeZone, TrustedCredManAccess, Undock + }; + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/NTStatus.cs b/src/SharpHoundRPC/NTStatus.cs new file mode 100644 index 00000000..f5391bd3 --- /dev/null +++ b/src/SharpHoundRPC/NTStatus.cs @@ -0,0 +1,17 @@ +namespace SharpHoundRPC +{ + public enum NtStatus + { + StatusSuccess = 0x0, + StatusMoreEntries = 0x105, + StatusSomeMapped = 0x107, + StatusInvalidHandle = unchecked((int) 0xC0000008), + StatusInvalidParameter = unchecked((int) 0xC000000D), + StatusAccessDenied = unchecked((int) 0xC0000022), + StatusObjectTypeMismatch = unchecked((int) 0xC0000024), + StatusNoSuchDomain = unchecked((int) 0xC00000DF), + StatusRpcServerUnavailable = unchecked((int) 0xC0020017), + StatusNoSuchAlias = unchecked((int) 0xC0000151), + StatusNoMoreEntries = unchecked((int) 0x8000001A) + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/RPCException.cs b/src/SharpHoundRPC/RPCException.cs new file mode 100644 index 00000000..10571580 --- /dev/null +++ b/src/SharpHoundRPC/RPCException.cs @@ -0,0 +1,39 @@ +using System; + +namespace SharpHoundRPC +{ + public class RPCException : Exception + { + public const string Connect = "SamConnect"; + public const string EnumerateDomains = "SamEnumerateDomainsInSamServer"; + public const string ServerNotInitialized = "Server Not Initialized"; + public const string OpenAlias = "SamOpenAlias"; + public const string OpenDomain = "SamOpenDomain"; + public const string AliasNotFound = "Alias Not Found"; + public const string DomainNotFound = "Domain Not Found"; + public const string LookupIds = "SamLookupIdsInDomain"; + public const string EnumerateAliases = "SamEnumerateAliasesInDomain"; + public const string GetAliasMembers = "SamGetMembersinAlias"; + public const string LookupDomain = "SamLookupDomainInSamServer"; + public const string GetMachineSid = "GetMachineSid"; + private readonly string APICall; + private readonly string Status; + + public RPCException(string apiCall, NtStatus status) + { + APICall = apiCall; + Status = status.ToString(); + } + + public RPCException(string apiCall, string status) + { + APICall = apiCall; + Status = status; + } + + public override string ToString() + { + return $"Call to {APICall} returned {Status}"; + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/SAMRPCNative/SAMEnums.cs b/src/SharpHoundRPC/SAMRPCNative/SAMEnums.cs new file mode 100644 index 00000000..572da112 --- /dev/null +++ b/src/SharpHoundRPC/SAMRPCNative/SAMEnums.cs @@ -0,0 +1,75 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace SharpHoundRPC.SAMRPCNative +{ + public class SAMEnums + { + [Flags] + [SuppressMessage("ReSharper", "UnusedMember.Local")] + public enum DomainAccessMask + { + ReadPasswordParameters = 0x1, + WritePasswordParameters = 0x2, + ReadOtherParameters = 0x4, + WriteOtherParameters = 0x8, + CreateUser = 0x10, + CreateGroup = 0x20, + CreateAlias = 0x40, + GetAliasMembership = 0x80, + ListAccounts = 0x100, + Lookup = 0x200, + AdministerServer = 0x400, + AllAccess = 0xf07ff, + Read = 0x20084, + Write = 0x2047A, + Execute = 0x20301 + } + + [Flags] + [SuppressMessage("ReSharper", "UnusedMember.Local")] + public enum SamAccessMasks + { + SamServerConnect = 0x1, + SamServerShutdown = 0x2, + SamServerInitialize = 0x4, + SamServerCreateDomains = 0x8, + SamServerEnumerateDomains = 0x10, + SamServerLookupDomain = 0x20, + SamServerAllAccess = 0xf003f, + SamServerRead = 0x20010, + SamServerWrite = 0x2000e, + SamServerExecute = 0x20021 + } + + [Flags] + [SuppressMessage("ReSharper", "UnusedMember.Local")] + internal enum AliasOpenFlags + { + AddMember = 0x1, + RemoveMember = 0x2, + ListMembers = 0x4, + ReadInfo = 0x8, + WriteAccount = 0x10, + AllAccess = 0xf001f, + Read = 0x20004, + Write = 0x20013, + Execute = 0x20008 + } + + [Flags] + [SuppressMessage("ReSharper", "UnusedMember.Local")] + internal enum SamAliasFlags + { + AddMembers = 0x1, + RemoveMembers = 0x2, + ListMembers = 0x4, + ReadInfo = 0x8, + WriteAccount = 0x10, + AllAccess = 0xf001f, + Read = 0x20004, + Write = 0x20013, + Execute = 0x20008 + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs b/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs new file mode 100644 index 00000000..47e5b5c2 --- /dev/null +++ b/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Principal; +using SharpHoundRPC.Handles; +using SharpHoundRPC.Shared; +using SharpHoundRPC.Wrappers; + +namespace SharpHoundRPC.SAMRPCNative +{ + [SuppressUnmanagedCodeSecurity] + public static class SAMMethods + { + internal static SAMHandle SamConnect(string serverName, SAMEnums.SamAccessMasks requestedConnectAccess) + { + var us = new SharedStructs.UnicodeString(serverName); + var objectAttributes = default(SAMStructs.ObjectAttributes); + + var status = SamConnect(ref us, out var handle, requestedConnectAccess, ref objectAttributes); + + status.CheckError(RPCException.Connect); + objectAttributes.Dispose(); + + return handle; + } + + [DllImport("samlib.dll", CharSet = CharSet.Unicode)] + private static extern NtStatus SamConnect( + ref SharedStructs.UnicodeString serverName, + out SAMHandle serverHandle, + SAMEnums.SamAccessMasks desiredAccess, + ref SAMStructs.ObjectAttributes objectAttributes + ); + + internal static IEnumerable SamEnumerateDomainsInSamServer(SAMHandle serverHandle) + { + var enumerationContext = 0; + var status = + SamEnumerateDomainsInSamServer(serverHandle, ref enumerationContext, out var domains, -1, + out var count); + + status.CheckError(RPCException.EnumerateDomains); + + return domains.GetEnumerable(count); + } + + [DllImport("samlib.dll", CharSet = CharSet.Unicode)] + private static extern NtStatus SamEnumerateDomainsInSamServer( + SAMHandle serverHandle, + ref int enumerationContext, + out SAMPointer buffer, + int prefMaxLen, + out int count + ); + + internal static SecurityIdentifier SamLookupDomainInSamServer(SAMHandle serverHandle, string name) + { + var us = new SharedStructs.UnicodeString(name); + var status = SamLookupDomainInSamServer(serverHandle, ref us, out var sid); + + if (status == NtStatus.StatusNoSuchDomain) + throw new RPCException(RPCException.LookupDomain, RPCException.DomainNotFound); + status.CheckError(RPCException.LookupDomain); + + return sid.GetData(); + } + + [DllImport("samlib.dll", CharSet = CharSet.Unicode)] + private static extern NtStatus SamLookupDomainInSamServer( + SAMHandle serverHandle, + ref SharedStructs.UnicodeString name, + out SAMPointer sid); + + internal static SAMDomain SamOpenDomain(SAMHandle serverHandle, SecurityIdentifier securityIdentifier, + SAMEnums.DomainAccessMask desiredAccess) + { + var bytes = new byte[securityIdentifier.BinaryLength]; + securityIdentifier.GetBinaryForm(bytes, 0); + + var status = SamOpenDomain(serverHandle, desiredAccess, bytes, out var handle); + + if (status == NtStatus.StatusNoSuchDomain) + throw new RPCException(RPCException.OpenDomain, RPCException.DomainNotFound); + + status.CheckError(RPCException.OpenDomain); + + return new SAMDomain(handle); + } + + [DllImport("samlib.dll", CharSet = CharSet.Unicode)] + private static extern NtStatus SamOpenDomain( + SAMHandle serverHandle, + SAMEnums.DomainAccessMask desiredAccess, + [MarshalAs(UnmanagedType.LPArray)] byte[] domainSid, + out SAMHandle domainHandle + ); + + internal static IEnumerable SamGetMembersInAlias(SAMHandle aliasHandle) + { + var status = SamGetMembersInAlias(aliasHandle, out var members, out var count); + status.CheckError(RPCException.GetAliasMembers); + + return members.GetData(count); + } + + [DllImport("samlib.dll", CharSet = CharSet.Unicode)] + private static extern NtStatus SamGetMembersInAlias( + SAMHandle aliasHandle, + out SAMSidArray members, + out int count + ); + + internal static SAMAlias SamOpenAlias(SAMHandle domainHandle, int aliasId, + SAMEnums.AliasOpenFlags desiredAccess = SAMEnums.AliasOpenFlags.ListMembers) + { + var status = SamOpenAlias(domainHandle, desiredAccess, aliasId, out var aliasHandle); + if (status == NtStatus.StatusNoSuchAlias) + throw new RPCException(RPCException.OpenAlias, RPCException.AliasNotFound); + + status.CheckError(RPCException.OpenAlias); + + return new SAMAlias(aliasHandle); + } + + [DllImport("samlib.dll", CharSet = CharSet.Unicode)] + private static extern NtStatus SamOpenAlias( + SAMHandle domainHandle, + SAMEnums.AliasOpenFlags desiredAccess, + int aliasId, + out SAMHandle aliasHandle + ); + + internal static IEnumerable<(string Name, int Rid)> SamEnumerateAliasesInDomain(SAMHandle domainHandle) + { + var enumerationContext = 0; + var status = + SamEnumerateAliasesInDomain(domainHandle, ref enumerationContext, out var ridPointer, -1, + out var count); + + status.CheckError(RPCException.EnumerateAliases); + + foreach (var ridEnum in ridPointer.GetEnumerable(count)) + yield return (ridEnum.Name.ToString(), ridEnum.Rid); + } + + [DllImport("samlib.dll", CharSet = CharSet.Unicode)] + private static extern NtStatus SamEnumerateAliasesInDomain( + SAMHandle domainHandle, + ref int enumerationContext, + out SAMPointer buffer, + int prefMaxLen, + out int count + ); + + internal static (string Name, SharedEnums.SidNameUse Type) SamLookupIdsInDomain(SAMHandle domainHandle, int rid) + { + var ridArray = new[] {rid}; + var status = SamLookupIdsInDomain(domainHandle, 1, ridArray, out var namePointer, out var usePointer); + + status.CheckError(RPCException.LookupIds); + + return (namePointer.GetData().ToString(), + (SharedEnums.SidNameUse) usePointer.GetData()); + } + + internal static void SamLookupIdsInDomain(SAMHandle domainHandle, int[] rids, out string[] names, + out SharedEnums.SidNameUse[] types) + { + var count = rids.Length; + var status = SamLookupIdsInDomain(domainHandle, count, rids, out var namePointer, out var usePointer); + + status.CheckError(RPCException.LookupIds); + + names = namePointer.GetEnumerable(count).Select(x => x.ToString()).ToArray(); + types = new SharedEnums.SidNameUse[count]; + + Marshal.Copy(usePointer.DangerousGetHandle(), (int[]) (object) types, 0, count); + } + + [DllImport("samlib.dll", CharSet = CharSet.Unicode)] + private static extern NtStatus SamLookupIdsInDomain(SAMHandle domainHandle, + int count, + int[] rids, + out SAMPointer names, + out SAMPointer use); + + #region Cleanup + + [DllImport("samlib.dll")] + internal static extern NtStatus SamFreeMemory( + IntPtr handle + ); + + [DllImport("samlib.dll")] + internal static extern NtStatus SamCloseHandle( + IntPtr handle + ); + + #endregion + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/SAMRPCNative/SAMStructs.cs b/src/SharpHoundRPC/SAMRPCNative/SAMStructs.cs new file mode 100644 index 00000000..0dd3f471 --- /dev/null +++ b/src/SharpHoundRPC/SAMRPCNative/SAMStructs.cs @@ -0,0 +1,35 @@ +using System; +using System.Runtime.InteropServices; +using SharpHoundRPC.Shared; + +namespace SharpHoundRPC.SAMRPCNative +{ + public static class SAMStructs + { + public struct ObjectAttributes : IDisposable + { + public void Dispose() + { + if (objectName == IntPtr.Zero) return; + Marshal.DestroyStructure(objectName, typeof(SharedStructs.UnicodeString)); + Marshal.FreeHGlobal(objectName); + objectName = IntPtr.Zero; + } + + public int len; + public IntPtr rootDirectory; + public uint attribs; + public IntPtr sid; + public IntPtr qos; + private IntPtr objectName; + public SharedStructs.UnicodeString ObjectName; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SamRidEnumeration + { + public int Rid; + public SharedStructs.UnicodeString Name; + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/Shared/SharedEnums.cs b/src/SharpHoundRPC/Shared/SharedEnums.cs new file mode 100644 index 00000000..5b752f1d --- /dev/null +++ b/src/SharpHoundRPC/Shared/SharedEnums.cs @@ -0,0 +1,20 @@ +namespace SharpHoundRPC.Shared +{ + public class SharedEnums + { + public enum SidNameUse + { + User = 1, + Group, + Domain, + Alias, + WellKnownGroup, + DeletedAccount, + Invalid, + Unknown, + Computer, + Label, + LogonSession + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/Shared/SharedStructs.cs b/src/SharpHoundRPC/Shared/SharedStructs.cs new file mode 100644 index 00000000..0ee9a085 --- /dev/null +++ b/src/SharpHoundRPC/Shared/SharedStructs.cs @@ -0,0 +1,38 @@ +using System; +using System.Runtime.InteropServices; + +namespace SharpHoundRPC.Shared +{ + public class SharedStructs + { + [StructLayout(LayoutKind.Sequential)] + public struct UnicodeString : IDisposable + { + private readonly ushort Length; + private readonly ushort MaximumLength; + private IntPtr Buffer; + + public UnicodeString(string s) + : this() + { + if (s == null) return; + Length = (ushort) (s.Length * 2); + MaximumLength = (ushort) (Length + 2); + Buffer = Marshal.StringToHGlobalUni(s); + } + + public void Dispose() + { + if (Buffer == IntPtr.Zero) return; + Marshal.FreeHGlobal(Buffer); + Buffer = IntPtr.Zero; + } + + public override string ToString() + { + return (Buffer != IntPtr.Zero ? Marshal.PtrToStringUni(Buffer, Length / 2) : null) ?? + throw new InvalidOperationException(); + } + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/SharpHoundRPC.csproj b/src/SharpHoundRPC/SharpHoundRPC.csproj new file mode 100644 index 00000000..98e82a66 --- /dev/null +++ b/src/SharpHoundRPC/SharpHoundRPC.csproj @@ -0,0 +1,25 @@ + + + net462 + library + SharpHoundRPC + latest + Rohan Vazarkar + SpecterOps + SAM/LSA Wrapper for C# BloodHound tasks + GPL-3.0-only + 1.0.0 + SharpHoundRPC + SharpHoundRPC + + + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + full + + + + + + + + \ No newline at end of file diff --git a/src/SharpHoundRPC/Wrappers/LSABase.cs b/src/SharpHoundRPC/Wrappers/LSABase.cs new file mode 100644 index 00000000..47e451ef --- /dev/null +++ b/src/SharpHoundRPC/Wrappers/LSABase.cs @@ -0,0 +1,33 @@ +using System; +using SharpHoundRPC.Handles; + +namespace SharpHoundRPC.Wrappers +{ + public class LSABase : IDisposable + { + protected LSAHandle Handle; + + protected LSABase(LSAHandle handle) + { + Handle = handle; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) ReleaseHandle(); + } + + protected virtual void ReleaseHandle() + { + Handle?.Dispose(); + Handle = null; + //Call suppressfinalize to prevent finalization, since we've already cleaned up our own stuff + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/Wrappers/LSAPolicy.cs b/src/SharpHoundRPC/Wrappers/LSAPolicy.cs new file mode 100644 index 00000000..b118e632 --- /dev/null +++ b/src/SharpHoundRPC/Wrappers/LSAPolicy.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Linq; +using System.Security.Principal; +using SharpHoundRPC.Handles; +using SharpHoundRPC.LSANative; +using SharpHoundRPC.Shared; + +namespace SharpHoundRPC.Wrappers +{ + public class LSAPolicy : LSABase + { + private string _computerName; + + public LSAPolicy(string computerName, LSAHandle handle) : base(handle) + { + _computerName = computerName; + } + + public static LSAPolicy OpenPolicy(string computerName, LSAEnums.LsaOpenMask desiredAccess = + LSAEnums.LsaOpenMask.LookupNames | LSAEnums.LsaOpenMask.ViewLocalInfo) + { + var handle = LSAMethods.LsaOpenPolicy(computerName, desiredAccess); + return new LSAPolicy(computerName, handle); + } + + public (string Name, string Sid) GetLocalDomainInformation() + { + var result = LSAMethods.LsaQueryInformationPolicy(Handle, + LSAEnums.LSAPolicyInformation.PolicyAccountDomainInformation); + + var domainInfo = result.GetData(); + var domainSid = new SecurityIdentifier(domainInfo.DomainSid); + return (domainInfo.DomainName.ToString(), domainSid.Value.ToUpper()); + } + + public IEnumerable GetPrincipalsWithPrivilege(string userRight) + { + return LSAMethods.LsaEnumerateAccountsWithUserRight(Handle, userRight); + } + + public (string Name, SharedEnums.SidNameUse Use, string Domains) LookupSid(SecurityIdentifier sid) + { + var result = LSAMethods.LsaLookupSids(Handle, new[] {sid}).First(); + return (result.Name, result.Use, result.Domain); + } + + public IEnumerable<(SecurityIdentifier Sid, string Name, SharedEnums.SidNameUse Use, string Domain)> LookupSids( + SecurityIdentifier[] sids) + { + return LSAMethods.LsaLookupSids(Handle, sids); + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/Wrappers/SAMAlias.cs b/src/SharpHoundRPC/Wrappers/SAMAlias.cs new file mode 100644 index 00000000..49b41ec4 --- /dev/null +++ b/src/SharpHoundRPC/Wrappers/SAMAlias.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Security.Principal; +using SharpHoundRPC.Handles; +using SharpHoundRPC.SAMRPCNative; + +namespace SharpHoundRPC.Wrappers +{ + public class SAMAlias : SAMBase + { + public SAMAlias(SAMHandle handle) : base(handle) + { + } + + public string Name { get; set; } + public int Rid { get; set; } + + public IEnumerable GetMembers() + { + return SAMMethods.SamGetMembersInAlias(Handle); + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/Wrappers/SAMBase.cs b/src/SharpHoundRPC/Wrappers/SAMBase.cs new file mode 100644 index 00000000..d0dd1166 --- /dev/null +++ b/src/SharpHoundRPC/Wrappers/SAMBase.cs @@ -0,0 +1,33 @@ +using System; +using SharpHoundRPC.Handles; + +namespace SharpHoundRPC.Wrappers +{ + public class SAMBase : IDisposable + { + protected SAMHandle Handle; + + protected SAMBase(SAMHandle handle) + { + Handle = handle; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) ReleaseHandle(); + } + + protected virtual void ReleaseHandle() + { + Handle?.Dispose(); + Handle = null; + //Call suppressfinalize to prevent finalization, since we've already cleaned up our own stuff + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/Wrappers/SAMDomain.cs b/src/SharpHoundRPC/Wrappers/SAMDomain.cs new file mode 100644 index 00000000..ec68c8c0 --- /dev/null +++ b/src/SharpHoundRPC/Wrappers/SAMDomain.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using SharpHoundRPC.Handles; +using SharpHoundRPC.SAMRPCNative; +using SharpHoundRPC.Shared; + +namespace SharpHoundRPC.Wrappers +{ + public class SAMDomain : SAMBase + { + public SAMDomain(SAMHandle handle) : base(handle) + { + } + + public (string Name, SharedEnums.SidNameUse Type) LookupUserByRid(int rid) + { + return SAMMethods.SamLookupIdsInDomain(Handle, rid); + } + + public IEnumerable<(string Name, int Rid)> GetAliases() + { + return SAMMethods.SamEnumerateAliasesInDomain(Handle); + } + + public SAMAlias OpenAlias(int rid) + { + return SAMMethods.SamOpenAlias(Handle, rid); + } + + public SAMAlias OpenAlias(string name) + { + foreach (var alias in GetAliases()) + if (alias.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) + return OpenAlias(alias.Rid); + + throw new RPCException(RPCException.OpenAlias, RPCException.AliasNotFound); + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/Wrappers/SAMServer.cs b/src/SharpHoundRPC/Wrappers/SAMServer.cs new file mode 100644 index 00000000..86815fdf --- /dev/null +++ b/src/SharpHoundRPC/Wrappers/SAMServer.cs @@ -0,0 +1,90 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Security.Principal; +using SharpHoundRPC.Handles; +using SharpHoundRPC.SAMRPCNative; + +namespace SharpHoundRPC.Wrappers +{ + public class SAMServer : SAMBase + { + private readonly string _computerName; + private SecurityIdentifier _cachedMachineSid; + private readonly ConcurrentDictionary _domainHandleCache; + + public SAMServer(string computerName, SAMHandle handle) : base(handle) + { + _computerName = computerName; + _domainHandleCache = new ConcurrentDictionary(); + } + + public static SAMServer OpenServer(string computerName, SAMEnums.SamAccessMasks requestedConnectAccess = + SAMEnums.SamAccessMasks.SamServerConnect | + SAMEnums.SamAccessMasks + .SamServerEnumerateDomains | + SAMEnums.SamAccessMasks.SamServerLookupDomain) + { + var handle = SAMMethods.SamConnect(computerName, requestedConnectAccess); + return new SAMServer(computerName, handle); + } + + public IEnumerable<(string Name, int Rid)> GetDomains() + { + return SAMMethods.SamEnumerateDomainsInSamServer(Handle) + .Select(result => (result.Name.ToString(), result.Rid)); + } + + public SecurityIdentifier LookupDomain(string name) + { + return SAMMethods.SamLookupDomainInSamServer(Handle, name); + } + + public SecurityIdentifier GetMachineSid(string testName = null) + { + if (_cachedMachineSid != null) + return _cachedMachineSid; + + SecurityIdentifier sid; + + if (testName != null) + try + { + sid = LookupDomain(testName); + _cachedMachineSid = sid; + return sid; + } + catch + { + // ignored + } + + + var domain = GetDomains().FirstOrDefault(); + sid = LookupDomain(domain.Name); + _cachedMachineSid = sid; + return sid; + } + + public bool IsDomainController(SecurityIdentifier domainMachineSid) + { + return domainMachineSid.AccountDomainSid.Equals(GetMachineSid().AccountDomainSid); + } + + public SAMDomain OpenDomain(string domainName, SAMEnums.DomainAccessMask requestedDomainAccess = + SAMEnums.DomainAccessMask.Lookup | + SAMEnums.DomainAccessMask.ListAccounts) + { + var sid = LookupDomain(domainName); + return SAMMethods.SamOpenDomain(Handle, sid, requestedDomainAccess); + } + + public SAMDomain OpenDomain(SecurityIdentifier securityIdentifier, + SAMEnums.DomainAccessMask requestedDomainAccess = + SAMEnums.DomainAccessMask.Lookup | + SAMEnums.DomainAccessMask.ListAccounts) + { + return SAMMethods.SamOpenDomain(Handle, securityIdentifier, requestedDomainAccess); + } + } +} \ No newline at end of file From 4549ec6be5332033b04607828d5996c06b9d2785 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Mon, 18 Jul 2022 15:31:23 -0400 Subject: [PATCH 02/26] feat: add scriptpath to users as logonscript Closes: https://github.com/BloodHoundAD/BloodHound/issues/553 --- src/CommonLib/LDAPProperties.cs | 1 + src/CommonLib/Processors/LDAPPropertyProcessor.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/CommonLib/LDAPProperties.cs b/src/CommonLib/LDAPProperties.cs index ea09e266..27a894b5 100644 --- a/src/CommonLib/LDAPProperties.cs +++ b/src/CommonLib/LDAPProperties.cs @@ -45,5 +45,6 @@ public class LDAPProperties public const string UnixUserPassword = "unixuserpassword"; public const string UnicodePassword = "unicodepwd"; public const string MsSFU30Password = "msSFU30Password"; + public const string ScriptPath = "scriptpath"; } } \ No newline at end of file diff --git a/src/CommonLib/Processors/LDAPPropertyProcessor.cs b/src/CommonLib/Processors/LDAPPropertyProcessor.cs index 5ce3c10f..ab1b1827 100644 --- a/src/CommonLib/Processors/LDAPPropertyProcessor.cs +++ b/src/CommonLib/Processors/LDAPPropertyProcessor.cs @@ -224,6 +224,7 @@ public async Task ReadUserProperties(ISearchResultEntry entry) props.Add("unixpassword", entry.GetProperty(LDAPProperties.UnixUserPassword)); props.Add("unicodepassword", entry.GetProperty(LDAPProperties.UnicodePassword)); props.Add("sfupassword", entry.GetProperty(LDAPProperties.MsSFU30Password)); + props.Add("logonscript", entry.GetProperty(LDAPProperties.ScriptPath)); var ac = entry.GetProperty(LDAPProperties.AdminCount); if (ac != null) From 4e656e6893ce01a9b54f05ddfaeac42341d83318 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Tue, 19 Jul 2022 10:46:36 -0400 Subject: [PATCH 03/26] fix: make ParseAllProperties public --- src/CommonLib/Processors/LDAPPropertyProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommonLib/Processors/LDAPPropertyProcessor.cs b/src/CommonLib/Processors/LDAPPropertyProcessor.cs index ab1b1827..bdb0028d 100644 --- a/src/CommonLib/Processors/LDAPPropertyProcessor.cs +++ b/src/CommonLib/Processors/LDAPPropertyProcessor.cs @@ -386,7 +386,7 @@ public async Task ReadComputerProperties(ISearchResultEntry /// format using a best guess /// /// - private static Dictionary ParseAllProperties(ISearchResultEntry entry) + public Dictionary ParseAllProperties(ISearchResultEntry entry) { var flag = IsTextUnicodeFlags.IS_TEXT_UNICODE_STATISTICS; var props = new Dictionary(); From e519d87f99da7f254e6b19e2af1fca2ef2a566c5 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Tue, 19 Jul 2022 10:50:42 -0400 Subject: [PATCH 04/26] fix: make cache members public with private set to allow serialization (workaround for json.net) --- src/CommonLib/Cache.cs | 76 +++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/src/CommonLib/Cache.cs b/src/CommonLib/Cache.cs index 5a185437..722ed031 100644 --- a/src/CommonLib/Cache.cs +++ b/src/CommonLib/Cache.cs @@ -4,29 +4,29 @@ namespace SharpHoundCommonLib { + [DataContract] public class Cache { - [DataMember]private ConcurrentDictionary _globalCatalogCache; + private Cache() + { + ValueToIdCache = new ConcurrentDictionary(); + IdToTypeCache = new ConcurrentDictionary(); + GlobalCatalogCache = new ConcurrentDictionary(); + MachineSidCache = new ConcurrentDictionary(); + SIDToDomainCache = new ConcurrentDictionary(); + } - [DataMember]private ConcurrentDictionary _idToTypeCache; + [DataMember] public ConcurrentDictionary GlobalCatalogCache { get; private set; } - [DataMember]private ConcurrentDictionary _machineSidCache; + [DataMember] public ConcurrentDictionary IdToTypeCache { get; private set; } - [DataMember]private ConcurrentDictionary _sidToDomainCache; + [DataMember] public ConcurrentDictionary MachineSidCache { get; private set; } - [DataMember]private ConcurrentDictionary _valueToIDCache; + [DataMember] public ConcurrentDictionary SIDToDomainCache { get; private set; } - private Cache() - { - _valueToIDCache = new ConcurrentDictionary(); - _idToTypeCache = new ConcurrentDictionary(); - _globalCatalogCache = new ConcurrentDictionary(); - _machineSidCache = new ConcurrentDictionary(); - _sidToDomainCache = new ConcurrentDictionary(); - } + [DataMember] public ConcurrentDictionary ValueToIdCache { get; private set; } - [IgnoreDataMember] - private static Cache CacheInstance { get; set; } + [IgnoreDataMember] private static Cache CacheInstance { get; set; } /// /// Add a SID to/from Domain mapping to the cache @@ -35,7 +35,7 @@ private Cache() /// internal static void AddSidToDomain(string key, string value) { - CacheInstance?._sidToDomainCache.TryAdd(key, value); + CacheInstance?.SIDToDomainCache.TryAdd(key, value); } /// @@ -46,7 +46,7 @@ internal static void AddSidToDomain(string key, string value) /// internal static bool GetDomainSidMapping(string key, out string value) { - if (CacheInstance != null) return CacheInstance._machineSidCache.TryGetValue(key, out value); + if (CacheInstance != null) return CacheInstance.MachineSidCache.TryGetValue(key, out value); value = null; return false; } @@ -58,46 +58,46 @@ internal static bool GetDomainSidMapping(string key, out string value) /// internal static void AddMachineSid(string key, string value) { - CacheInstance?._machineSidCache.TryAdd(key, value); + CacheInstance?.MachineSidCache.TryAdd(key, value); } internal static bool GetMachineSid(string key, out string value) { - if (CacheInstance != null) return CacheInstance._machineSidCache.TryGetValue(key, out value); + if (CacheInstance != null) return CacheInstance.MachineSidCache.TryGetValue(key, out value); value = null; return false; } internal static void AddConvertedValue(string key, string value) { - CacheInstance?._valueToIDCache.TryAdd(key, value); + CacheInstance?.ValueToIdCache.TryAdd(key, value); } internal static void AddPrefixedValue(string key, string domain, string value) { - CacheInstance?._valueToIDCache.TryAdd(GetPrefixKey(key, domain), value); + CacheInstance?.ValueToIdCache.TryAdd(GetPrefixKey(key, domain), value); } internal static void AddType(string key, Label value) { - CacheInstance?._idToTypeCache.TryAdd(key, value); + CacheInstance?.IdToTypeCache.TryAdd(key, value); } internal static void AddGCCache(string key, string[] value) { - CacheInstance?._globalCatalogCache?.TryAdd(key, value); + CacheInstance?.GlobalCatalogCache?.TryAdd(key, value); } internal static bool GetGCCache(string key, out string[] value) { - if (CacheInstance != null) return CacheInstance._globalCatalogCache.TryGetValue(key, out value); + if (CacheInstance != null) return CacheInstance.GlobalCatalogCache.TryGetValue(key, out value); value = null; return false; } internal static bool GetConvertedValue(string key, out string value) { - if (CacheInstance != null) return CacheInstance._valueToIDCache.TryGetValue(key, out value); + if (CacheInstance != null) return CacheInstance.ValueToIdCache.TryGetValue(key, out value); value = null; return false; } @@ -105,14 +105,14 @@ internal static bool GetConvertedValue(string key, out string value) internal static bool GetPrefixedValue(string key, string domain, out string value) { if (CacheInstance != null) - return CacheInstance._valueToIDCache.TryGetValue(GetPrefixKey(key, domain), out value); + return CacheInstance.ValueToIdCache.TryGetValue(GetPrefixKey(key, domain), out value); value = null; return false; } internal static bool GetIDType(string key, out Label value) { - if (CacheInstance != null) return CacheInstance._idToTypeCache.TryGetValue(key, out value); + if (CacheInstance != null) return CacheInstance.IdToTypeCache.TryGetValue(key, out value); value = Label.Base; return false; } @@ -123,7 +123,7 @@ private static string GetPrefixKey(string key, string domain) } /// - /// Creates a new empty cache instance + /// Creates a new empty cache instance /// /// public static Cache CreateNewCache() @@ -132,7 +132,7 @@ public static Cache CreateNewCache() } /// - /// Sets the cache instance being used by the common library + /// Sets the cache instance being used by the common library /// /// public static void SetCacheInstance(Cache cache) @@ -142,7 +142,7 @@ public static void SetCacheInstance(Cache cache) } /// - /// Gets stats from the currently loaded cache + /// Gets stats from the currently loaded cache /// /// public string GetCacheStats() @@ -150,16 +150,16 @@ public string GetCacheStats() try { return - $"{_idToTypeCache.Count} ID to type mappings.\n {_valueToIDCache.Count} name to SID mappings.\n {_machineSidCache.Count} machine sid mappings.\n {_sidToDomainCache.Count} sid to domain mappings.\n {_globalCatalogCache.Count} global catalog mappings."; + $"{IdToTypeCache.Count} ID to type mappings.\n {ValueToIdCache.Count} name to SID mappings.\n {MachineSidCache.Count} machine sid mappings.\n {SIDToDomainCache.Count} sid to domain mappings.\n {GlobalCatalogCache.Count} global catalog mappings."; } catch { return ""; } } - + /// - /// Returns the currently loaded cache instance + /// Returns the currently loaded cache instance /// /// public static Cache GetCacheInstance() @@ -170,11 +170,11 @@ public static Cache GetCacheInstance() private static void CreateMissingDictionaries() { CacheInstance ??= new Cache(); - CacheInstance._idToTypeCache ??= new ConcurrentDictionary(); - CacheInstance._globalCatalogCache ??= new ConcurrentDictionary(); - CacheInstance._machineSidCache ??= new ConcurrentDictionary(); - CacheInstance._sidToDomainCache ??= new ConcurrentDictionary(); - CacheInstance._valueToIDCache ??= new ConcurrentDictionary(); + CacheInstance.IdToTypeCache ??= new ConcurrentDictionary(); + CacheInstance.GlobalCatalogCache ??= new ConcurrentDictionary(); + CacheInstance.MachineSidCache ??= new ConcurrentDictionary(); + CacheInstance.SIDToDomainCache ??= new ConcurrentDictionary(); + CacheInstance.ValueToIdCache ??= new ConcurrentDictionary(); } } } \ No newline at end of file From 396b98b828d1e7b590c7fc654530dae601afcac1 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Wed, 20 Jul 2022 11:01:26 -0400 Subject: [PATCH 05/26] fix: add DataContract to MetaTag --- src/CommonLib/OutputTypes/MetaTag.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CommonLib/OutputTypes/MetaTag.cs b/src/CommonLib/OutputTypes/MetaTag.cs index c243c3fd..5541a1cd 100644 --- a/src/CommonLib/OutputTypes/MetaTag.cs +++ b/src/CommonLib/OutputTypes/MetaTag.cs @@ -2,6 +2,7 @@ namespace SharpHoundCommonLib.OutputTypes { + [DataContract] public class MetaTag { [DataMember(Name="methods")] public long CollectionMethods { get; set; } From 033f4bb08d8b666c33507a44dd59fb8315762fa5 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Mon, 1 Aug 2022 10:29:00 -0400 Subject: [PATCH 06/26] wip: commit for wip --- .../Processors/LocalGroupProcessor.cs | 77 +++++++++++++--- src/SharpHoundRPC/Extensions.cs | 34 +++++++- src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs | 21 ++--- src/SharpHoundRPC/SharpHoundRPC.csproj | 5 +- src/SharpHoundRPC/Wrappers/SAMServer.cs | 87 ++++++++++++++----- 5 files changed, 172 insertions(+), 52 deletions(-) diff --git a/src/CommonLib/Processors/LocalGroupProcessor.cs b/src/CommonLib/Processors/LocalGroupProcessor.cs index 81d77bd6..095e69a7 100644 --- a/src/CommonLib/Processors/LocalGroupProcessor.cs +++ b/src/CommonLib/Processors/LocalGroupProcessor.cs @@ -1,12 +1,15 @@ -using System.Collections; +using System; +using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Security.Principal; using Microsoft.Extensions.Logging; using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.OutputTypes; using SharpHoundRPC; +using SharpHoundRPC.Shared; using SharpHoundRPC.Wrappers; namespace SharpHoundCommonLib.Processors @@ -33,6 +36,11 @@ public IEnumerable GetLocalGroups(SAMServer server, string var typeCache = new ConcurrentDictionary(); var computerSid = new SecurityIdentifier(computerDomainSid); + try + { + + } + var machineSid = server.GetMachineSid(); foreach (var domainResult in server.GetDomains()) { @@ -44,42 +52,89 @@ public IEnumerable GetLocalGroups(SAMServer server, string try { - var domain = server.OpenDomain(domainResult.Name); + var domain = server.OpenDomain(domainResult.Name);; foreach (var alias in domain.GetAliases()) { var results = new List(); - var localGroup = domain.OpenAlias(alias.Rid); + var names = new List(); + using var localGroup = domain.OpenAlias(alias.Rid); foreach (var securityIdentifier in localGroup.GetMembers()) { if (IsSidFiltered(securityIdentifier)) continue; + var sidValue = securityIdentifier.Value; + if (server.IsDomainController(computerSid)) { - if (_utils.GetWellKnownPrincipal(securityIdentifier.Value, computerDomain, + if (_utils.GetWellKnownPrincipal(sidValue, computerDomain, out var wellKnown)) { results.Add(wellKnown); } else { - results.Add(_utils.ResolveIDAndType(securityIdentifier.Value, computerDomain)); + results.Add(_utils.ResolveIDAndType(sidValue, computerDomain)); } } else { - if (WellKnownPrincipal.GetWellKnownPrincipal(securityIdentifier.Value, + if (WellKnownPrincipal.GetWellKnownPrincipal(sidValue, out var wellKnown)) { wellKnown.ObjectIdentifier = $"{machineSid.Value}-{securityIdentifier.Rid()}"; - if (wellKnown.ObjectType == Label.User) - wellKnown.ObjectType = Label.LocalUser; - else if (wellKnown.ObjectType == Label.Group) - wellKnown.ObjectType = Label.LocalGroup; + wellKnown.ObjectType = wellKnown.ObjectType switch + { + Label.User => Label.LocalUser, + Label.Group => Label.LocalGroup, + _ => wellKnown.ObjectType + }; results.Add(wellKnown); } else { - + if (securityIdentifier.IsEqualDomainSid(computerSid)) + { + results.Add(_utils.ResolveIDAndType(sidValue, computerDomain)); + } + else + { + if (typeCache.TryGetValue(sidValue, out var item)) + { + _log.LogTrace("ResolveLocalSid - Cache hit for {ID}", sidValue); + results.Add(new TypedPrincipal + { + ObjectIdentifier = sidValue, + ObjectType = item.Type + }); + + names.Add(new NamedPrincipal + { + ObjectId = sidValue, + PrincipalName = item.Name + }); + } + + try + { + var (name, use) = server.LookupUserBySid(securityIdentifier); + var objectType = use switch + { + SharedEnums.SidNameUse.User => Label.LocalUser, + SharedEnums.SidNameUse.Group => Label.LocalGroup, + SharedEnums.SidNameUse.Alias => Label.LocalGroup, + _ => Label.Base + }; + + results.Add(new TypedPrincipal + { + + }); + } + catch (Exception e) + { + _log.LogTrace(e, "Unable to resolve local sid {SID}", securityIdentifier.Value); + } + } } } } diff --git a/src/SharpHoundRPC/Extensions.cs b/src/SharpHoundRPC/Extensions.cs index 31f21428..ce4e6a60 100644 --- a/src/SharpHoundRPC/Extensions.cs +++ b/src/SharpHoundRPC/Extensions.cs @@ -1,12 +1,40 @@ -namespace SharpHoundRPC +using System; +using System.Security.Principal; +using FluentResults; + +namespace SharpHoundRPC { public static class Extensions { - public static void CheckError(this NtStatus status, string apiCall) + public static bool IsError(this NtStatus status) { if (status != NtStatus.StatusSuccess && status != NtStatus.StatusMoreEntries && status != NtStatus.StatusSomeMapped && status != NtStatus.StatusNoMoreEntries) - throw new RPCException(apiCall, status); + return true; + + return false; + } + + public static Result ResultValue(this Result result, string failureMessage, T value) + { + if (result.IsSuccess) + { + return Result.Ok(value); + } + + return Result.Fail(failureMessage); + } + + /// + /// Gets the relative identifier for a SID + /// + /// + /// + public static int Rid(this SecurityIdentifier securityIdentifier) + { + var value = securityIdentifier.Value; + var rid = int.Parse(value.Substring(value.LastIndexOf("-", StringComparison.Ordinal) + 1)); + return rid; } } } \ No newline at end of file diff --git a/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs b/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs index 47e5b5c2..2d640c50 100644 --- a/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs +++ b/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs @@ -4,6 +4,7 @@ using System.Runtime.InteropServices; using System.Security; using System.Security.Principal; +using FluentResults; using SharpHoundRPC.Handles; using SharpHoundRPC.Shared; using SharpHoundRPC.Wrappers; @@ -13,17 +14,15 @@ namespace SharpHoundRPC.SAMRPCNative [SuppressUnmanagedCodeSecurity] public static class SAMMethods { - internal static SAMHandle SamConnect(string serverName, SAMEnums.SamAccessMasks requestedConnectAccess) + internal static (NtStatus status, SAMHandle handle) SamConnect(string serverName, SAMEnums.SamAccessMasks requestedConnectAccess) { var us = new SharedStructs.UnicodeString(serverName); var objectAttributes = default(SAMStructs.ObjectAttributes); var status = SamConnect(ref us, out var handle, requestedConnectAccess, ref objectAttributes); - - status.CheckError(RPCException.Connect); objectAttributes.Dispose(); - return handle; + return (status, handle); } [DllImport("samlib.dll", CharSet = CharSet.Unicode)] @@ -34,16 +33,14 @@ private static extern NtStatus SamConnect( ref SAMStructs.ObjectAttributes objectAttributes ); - internal static IEnumerable SamEnumerateDomainsInSamServer(SAMHandle serverHandle) + internal static (NtStatus status, IEnumerable domainRids) SamEnumerateDomainsInSamServer(SAMHandle serverHandle) { var enumerationContext = 0; var status = SamEnumerateDomainsInSamServer(serverHandle, ref enumerationContext, out var domains, -1, out var count); - status.CheckError(RPCException.EnumerateDomains); - - return domains.GetEnumerable(count); + return (status, domains.GetEnumerable(count)); } [DllImport("samlib.dll", CharSet = CharSet.Unicode)] @@ -55,16 +52,12 @@ private static extern NtStatus SamEnumerateDomainsInSamServer( out int count ); - internal static SecurityIdentifier SamLookupDomainInSamServer(SAMHandle serverHandle, string name) + internal static (NtStatus status, SecurityIdentifier securityIdentifier) SamLookupDomainInSamServer(SAMHandle serverHandle, string name) { var us = new SharedStructs.UnicodeString(name); var status = SamLookupDomainInSamServer(serverHandle, ref us, out var sid); - if (status == NtStatus.StatusNoSuchDomain) - throw new RPCException(RPCException.LookupDomain, RPCException.DomainNotFound); - status.CheckError(RPCException.LookupDomain); - - return sid.GetData(); + return (status, sid.GetData()); } [DllImport("samlib.dll", CharSet = CharSet.Unicode)] diff --git a/src/SharpHoundRPC/SharpHoundRPC.csproj b/src/SharpHoundRPC/SharpHoundRPC.csproj index 98e82a66..4c1cd7c0 100644 --- a/src/SharpHoundRPC/SharpHoundRPC.csproj +++ b/src/SharpHoundRPC/SharpHoundRPC.csproj @@ -17,9 +17,10 @@ full - + + - + \ No newline at end of file diff --git a/src/SharpHoundRPC/Wrappers/SAMServer.cs b/src/SharpHoundRPC/Wrappers/SAMServer.cs index 86815fdf..cbb89717 100644 --- a/src/SharpHoundRPC/Wrappers/SAMServer.cs +++ b/src/SharpHoundRPC/Wrappers/SAMServer.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.Linq; using System.Security.Principal; +using FluentResults; using SharpHoundRPC.Handles; using SharpHoundRPC.SAMRPCNative; +using SharpHoundRPC.Shared; namespace SharpHoundRPC.Wrappers { @@ -19,64 +21,90 @@ public SAMServer(string computerName, SAMHandle handle) : base(handle) _domainHandleCache = new ConcurrentDictionary(); } - public static SAMServer OpenServer(string computerName, SAMEnums.SamAccessMasks requestedConnectAccess = + public static Result OpenServer(string computerName, SAMEnums.SamAccessMasks requestedConnectAccess = SAMEnums.SamAccessMasks.SamServerConnect | SAMEnums.SamAccessMasks .SamServerEnumerateDomains | SAMEnums.SamAccessMasks.SamServerLookupDomain) { - var handle = SAMMethods.SamConnect(computerName, requestedConnectAccess); - return new SAMServer(computerName, handle); + var (status, handle) = SAMMethods.SamConnect(computerName, requestedConnectAccess); + + return status.IsError() + ? Result.Fail($"SAMConnect returned {status}") + : Result.Ok(new SAMServer(computerName, handle)); } - public IEnumerable<(string Name, int Rid)> GetDomains() + public Result> GetDomains() { - return SAMMethods.SamEnumerateDomainsInSamServer(Handle) - .Select(result => (result.Name.ToString(), result.Rid)); + var (status, rids) = SAMMethods.SamEnumerateDomainsInSamServer(Handle); + return status.IsError() ? Result.Fail($"SamEnumerateDomainsInSamServer returned {status}") : Result.Ok(rids.Select(x => (x.Name.ToString(), x.Rid))); } - public SecurityIdentifier LookupDomain(string name) + public Result LookupDomain(string name) { - return SAMMethods.SamLookupDomainInSamServer(Handle, name); + var (status, sid) = SAMMethods.SamLookupDomainInSamServer(Handle, name); + return status.IsError() ? Result.Fail($"SamLookupDomainInSamServer returned {status}") : Result.Ok(sid); } - public SecurityIdentifier GetMachineSid(string testName = null) + public Result GetMachineSid(string testName = null) { if (_cachedMachineSid != null) return _cachedMachineSid; - SecurityIdentifier sid; + SecurityIdentifier sid = null; if (testName != null) - try + { + var result = LookupDomain(testName); + if (result.IsSuccess) { - sid = LookupDomain(testName); - _cachedMachineSid = sid; - return sid; + sid = result.Value; } - catch + } + + if (sid == null) + { + var domainResult = GetDomains(); + if (domainResult.IsSuccess) { - // ignored + var result = LookupDomain(domainResult.Value.FirstOrDefault().Name); + if (result.IsSuccess) + { + sid = result.Value; + } } + } - - var domain = GetDomains().FirstOrDefault(); - sid = LookupDomain(domain.Name); + if (sid == null) return Result.Fail("Unable to get machine sid"); _cachedMachineSid = sid; - return sid; + return Result.Ok(sid); + } public bool IsDomainController(SecurityIdentifier domainMachineSid) { return domainMachineSid.AccountDomainSid.Equals(GetMachineSid().AccountDomainSid); } + + public (string Name, SharedEnums.SidNameUse Type) LookupUserBySid(SecurityIdentifier securityIdentifier) + { + var domain = OpenDomain(securityIdentifier); + return domain.LookupUserByRid(securityIdentifier.Rid()); + } public SAMDomain OpenDomain(string domainName, SAMEnums.DomainAccessMask requestedDomainAccess = SAMEnums.DomainAccessMask.Lookup | SAMEnums.DomainAccessMask.ListAccounts) { var sid = LookupDomain(domainName); - return SAMMethods.SamOpenDomain(Handle, sid, requestedDomainAccess); + if (_domainHandleCache.TryGetValue(sid.Value, out var domain)) + { + return domain; + } + + domain = SAMMethods.SamOpenDomain(Handle, sid, requestedDomainAccess); + _domainHandleCache.TryAdd(sid.Value, domain); + return domain; } public SAMDomain OpenDomain(SecurityIdentifier securityIdentifier, @@ -84,7 +112,22 @@ public SAMDomain OpenDomain(SecurityIdentifier securityIdentifier, SAMEnums.DomainAccessMask.Lookup | SAMEnums.DomainAccessMask.ListAccounts) { - return SAMMethods.SamOpenDomain(Handle, securityIdentifier, requestedDomainAccess); + if (_domainHandleCache.TryGetValue(securityIdentifier.Value, out var domain)) + { + return domain; + } + domain = SAMMethods.SamOpenDomain(Handle, securityIdentifier, requestedDomainAccess); + _domainHandleCache.TryAdd(securityIdentifier.Value, domain); + return domain; + } + + protected override void Dispose(bool disposing) + { + foreach (var domainHandle in _domainHandleCache.Values) + { + domainHandle.Dispose(); + } + base.Dispose(disposing); } } } \ No newline at end of file From f4e0ee7e8baea75d07ae18eb23063a0239755bac Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Mon, 15 Aug 2022 11:45:54 -0400 Subject: [PATCH 07/26] wip: commit for wip --- .../Processors/LocalGroupProcessor.cs | 24 ++++- src/SharpHoundRPC/Extensions.cs | 7 ++ src/SharpHoundRPC/Result.cs | 38 ++++++++ src/SharpHoundRPC/SAMRPCNative/SAMEnums.cs | 2 +- src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs | 97 ++++--------------- src/SharpHoundRPC/SharpHoundRPC.csproj | 1 - src/SharpHoundRPC/Wrappers/SAMAlias.cs | 11 ++- src/SharpHoundRPC/Wrappers/SAMDomain.cs | 47 +++++++-- src/SharpHoundRPC/Wrappers/SAMServer.cs | 58 ++++++++--- 9 files changed, 174 insertions(+), 111 deletions(-) create mode 100644 src/SharpHoundRPC/Result.cs diff --git a/src/CommonLib/Processors/LocalGroupProcessor.cs b/src/CommonLib/Processors/LocalGroupProcessor.cs index 095e69a7..aacea4cb 100644 --- a/src/CommonLib/Processors/LocalGroupProcessor.cs +++ b/src/CommonLib/Processors/LocalGroupProcessor.cs @@ -23,7 +23,10 @@ public class LocalGroupProcessor "S-1-5-2", "S-1-5-2", "S-1-5-3", "S-1-5-4", "S-1-5-6", "S-1-5-7", "S-1-2", "S-1-2-0", "S-1-5-18", "S-1-5-19", "S-1-5-20" }; - + + public delegate void ComputerStatusDelegate(CSVComputerStatus status); + public event ComputerStatusDelegate ComputerStatusEvent; + public LocalGroupProcessor(ILDAPUtils utils, ILogger log = null) { _utils = utils; @@ -36,12 +39,18 @@ public IEnumerable GetLocalGroups(SAMServer server, string var typeCache = new ConcurrentDictionary(); var computerSid = new SecurityIdentifier(computerDomainSid); - try + var machineSid = server.GetMachineSid().ValueOrDefault; + var getDomainsResult = server.GetDomains(); + if (getDomainsResult.IsFailed) { - + SendComputerStatus(new CSVComputerStatus + { + Task = "GetDomains", + ComputerName = server.ComputerName, + Status = getDomainsResult. + }); + yield break; } - - var machineSid = server.GetMachineSid(); foreach (var domainResult in server.GetDomains()) { var ret = new LocalGroupAPIResult @@ -162,5 +171,10 @@ private bool IsSidFiltered(SecurityIdentifier identifier) return false; } + + private void SendComputerStatus(CSVComputerStatus status) + { + ComputerStatusEvent?.Invoke(status); + } } } \ No newline at end of file diff --git a/src/SharpHoundRPC/Extensions.cs b/src/SharpHoundRPC/Extensions.cs index ce4e6a60..50a153ab 100644 --- a/src/SharpHoundRPC/Extensions.cs +++ b/src/SharpHoundRPC/Extensions.cs @@ -36,5 +36,12 @@ public static int Rid(this SecurityIdentifier securityIdentifier) var rid = int.Parse(value.Substring(value.LastIndexOf("-", StringComparison.Ordinal) + 1)); return rid; } + + public static byte[] GetBytes(this SecurityIdentifier identifier) + { + var bytes = new byte[identifier.BinaryLength]; + identifier.GetBinaryForm(bytes, 0); + return bytes; + } } } \ No newline at end of file diff --git a/src/SharpHoundRPC/Result.cs b/src/SharpHoundRPC/Result.cs new file mode 100644 index 00000000..8fea07cc --- /dev/null +++ b/src/SharpHoundRPC/Result.cs @@ -0,0 +1,38 @@ +namespace SharpHoundRPC +{ + public class Result + { + private NtStatus Status { get; set; } = NtStatus.StatusSuccess; + public T Value { get; private set; } + + public bool IsFailed => Status.IsError(); + + public static Result Fail(NtStatus status) + { + var result = new Result + { + Status = status + }; + return result; + } + + public static Result Ok(T value) + { + var result = new Result + { + Value = value + }; + return result; + } + + public static implicit operator Result(T input) + { + return Ok(input); + } + + public static implicit operator Result(NtStatus status) + { + return Fail(status); + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/SAMRPCNative/SAMEnums.cs b/src/SharpHoundRPC/SAMRPCNative/SAMEnums.cs index 572da112..9aacc53b 100644 --- a/src/SharpHoundRPC/SAMRPCNative/SAMEnums.cs +++ b/src/SharpHoundRPC/SAMRPCNative/SAMEnums.cs @@ -44,7 +44,7 @@ public enum SamAccessMasks [Flags] [SuppressMessage("ReSharper", "UnusedMember.Local")] - internal enum AliasOpenFlags + public enum AliasOpenFlags { AddMember = 0x1, RemoveMember = 0x2, diff --git a/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs b/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs index 2d640c50..d3e361cd 100644 --- a/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs +++ b/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs @@ -66,80 +66,31 @@ private static extern NtStatus SamLookupDomainInSamServer( ref SharedStructs.UnicodeString name, out SAMPointer sid); - internal static SAMDomain SamOpenDomain(SAMHandle serverHandle, SecurityIdentifier securityIdentifier, - SAMEnums.DomainAccessMask desiredAccess) - { - var bytes = new byte[securityIdentifier.BinaryLength]; - securityIdentifier.GetBinaryForm(bytes, 0); - - var status = SamOpenDomain(serverHandle, desiredAccess, bytes, out var handle); - - if (status == NtStatus.StatusNoSuchDomain) - throw new RPCException(RPCException.OpenDomain, RPCException.DomainNotFound); - - status.CheckError(RPCException.OpenDomain); - - return new SAMDomain(handle); - } - [DllImport("samlib.dll", CharSet = CharSet.Unicode)] - private static extern NtStatus SamOpenDomain( + internal static extern NtStatus SamOpenDomain( SAMHandle serverHandle, SAMEnums.DomainAccessMask desiredAccess, [MarshalAs(UnmanagedType.LPArray)] byte[] domainSid, out SAMHandle domainHandle ); - internal static IEnumerable SamGetMembersInAlias(SAMHandle aliasHandle) - { - var status = SamGetMembersInAlias(aliasHandle, out var members, out var count); - status.CheckError(RPCException.GetAliasMembers); - - return members.GetData(count); - } - [DllImport("samlib.dll", CharSet = CharSet.Unicode)] - private static extern NtStatus SamGetMembersInAlias( + internal static extern NtStatus SamGetMembersInAlias( SAMHandle aliasHandle, out SAMSidArray members, out int count ); - internal static SAMAlias SamOpenAlias(SAMHandle domainHandle, int aliasId, - SAMEnums.AliasOpenFlags desiredAccess = SAMEnums.AliasOpenFlags.ListMembers) - { - var status = SamOpenAlias(domainHandle, desiredAccess, aliasId, out var aliasHandle); - if (status == NtStatus.StatusNoSuchAlias) - throw new RPCException(RPCException.OpenAlias, RPCException.AliasNotFound); - - status.CheckError(RPCException.OpenAlias); - - return new SAMAlias(aliasHandle); - } - [DllImport("samlib.dll", CharSet = CharSet.Unicode)] - private static extern NtStatus SamOpenAlias( + internal static extern NtStatus SamOpenAlias( SAMHandle domainHandle, SAMEnums.AliasOpenFlags desiredAccess, int aliasId, out SAMHandle aliasHandle ); - internal static IEnumerable<(string Name, int Rid)> SamEnumerateAliasesInDomain(SAMHandle domainHandle) - { - var enumerationContext = 0; - var status = - SamEnumerateAliasesInDomain(domainHandle, ref enumerationContext, out var ridPointer, -1, - out var count); - - status.CheckError(RPCException.EnumerateAliases); - - foreach (var ridEnum in ridPointer.GetEnumerable(count)) - yield return (ridEnum.Name.ToString(), ridEnum.Rid); - } - [DllImport("samlib.dll", CharSet = CharSet.Unicode)] - private static extern NtStatus SamEnumerateAliasesInDomain( + internal static extern NtStatus SamEnumerateAliasesInDomain( SAMHandle domainHandle, ref int enumerationContext, out SAMPointer buffer, @@ -147,33 +98,23 @@ private static extern NtStatus SamEnumerateAliasesInDomain( out int count ); - internal static (string Name, SharedEnums.SidNameUse Type) SamLookupIdsInDomain(SAMHandle domainHandle, int rid) - { - var ridArray = new[] {rid}; - var status = SamLookupIdsInDomain(domainHandle, 1, ridArray, out var namePointer, out var usePointer); - - status.CheckError(RPCException.LookupIds); - - return (namePointer.GetData().ToString(), - (SharedEnums.SidNameUse) usePointer.GetData()); - } - - internal static void SamLookupIdsInDomain(SAMHandle domainHandle, int[] rids, out string[] names, - out SharedEnums.SidNameUse[] types) - { - var count = rids.Length; - var status = SamLookupIdsInDomain(domainHandle, count, rids, out var namePointer, out var usePointer); - - status.CheckError(RPCException.LookupIds); - - names = namePointer.GetEnumerable(count).Select(x => x.ToString()).ToArray(); - types = new SharedEnums.SidNameUse[count]; - - Marshal.Copy(usePointer.DangerousGetHandle(), (int[]) (object) types, 0, count); - } + // + // internal static void SamLookupIdsInDomain(SAMHandle domainHandle, int[] rids, out string[] names, + // out SharedEnums.SidNameUse[] types) + // { + // var count = rids.Length; + // var status = SamLookupIdsInDomain(domainHandle, count, rids, out var namePointer, out var usePointer); + // + // status.CheckError(RPCException.LookupIds); + // + // names = namePointer.GetEnumerable(count).Select(x => x.ToString()).ToArray(); + // types = new SharedEnums.SidNameUse[count]; + // + // Marshal.Copy(usePointer.DangerousGetHandle(), (int[]) (object) types, 0, count); + // } [DllImport("samlib.dll", CharSet = CharSet.Unicode)] - private static extern NtStatus SamLookupIdsInDomain(SAMHandle domainHandle, + internal static extern NtStatus SamLookupIdsInDomain(SAMHandle domainHandle, int count, int[] rids, out SAMPointer names, diff --git a/src/SharpHoundRPC/SharpHoundRPC.csproj b/src/SharpHoundRPC/SharpHoundRPC.csproj index 4c1cd7c0..7c1f96bd 100644 --- a/src/SharpHoundRPC/SharpHoundRPC.csproj +++ b/src/SharpHoundRPC/SharpHoundRPC.csproj @@ -17,7 +17,6 @@ full - diff --git a/src/SharpHoundRPC/Wrappers/SAMAlias.cs b/src/SharpHoundRPC/Wrappers/SAMAlias.cs index 49b41ec4..6c13be7b 100644 --- a/src/SharpHoundRPC/Wrappers/SAMAlias.cs +++ b/src/SharpHoundRPC/Wrappers/SAMAlias.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Security.Principal; +using FluentResults; using SharpHoundRPC.Handles; using SharpHoundRPC.SAMRPCNative; @@ -14,9 +15,15 @@ public SAMAlias(SAMHandle handle) : base(handle) public string Name { get; set; } public int Rid { get; set; } - public IEnumerable GetMembers() + public Result> GetMembers() { - return SAMMethods.SamGetMembersInAlias(Handle); + var status = SAMMethods.SamGetMembersInAlias(Handle, out var members, out var count); + if (status.IsError()) + { + return Result.Fail($"SAMGetMembersInAlias returned {status}"); + } + + return Result.Ok(members.GetData(count)); } } } \ No newline at end of file diff --git a/src/SharpHoundRPC/Wrappers/SAMDomain.cs b/src/SharpHoundRPC/Wrappers/SAMDomain.cs index ec68c8c0..0edf0f05 100644 --- a/src/SharpHoundRPC/Wrappers/SAMDomain.cs +++ b/src/SharpHoundRPC/Wrappers/SAMDomain.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using FluentResults; using SharpHoundRPC.Handles; using SharpHoundRPC.SAMRPCNative; using SharpHoundRPC.Shared; @@ -12,28 +14,55 @@ public SAMDomain(SAMHandle handle) : base(handle) { } - public (string Name, SharedEnums.SidNameUse Type) LookupUserByRid(int rid) + public Result<(string Name, SharedEnums.SidNameUse Type)> LookupUserByRid(int rid) { - return SAMMethods.SamLookupIdsInDomain(Handle, rid); + var ridArray = new[] {rid}; + var status = SAMMethods.SamLookupIdsInDomain(Handle, 1, ridArray, out var namePointer, out var usePointer); + if (status.IsError()) + { + return Result.Fail($"SAMLookupIdsInDomain returned {status}"); + } + + return (namePointer.GetData().ToString(), (SharedEnums.SidNameUse)usePointer.GetData()); } - public IEnumerable<(string Name, int Rid)> GetAliases() + public Result> GetAliases() { - return SAMMethods.SamEnumerateAliasesInDomain(Handle); + var enumerationContext = 0; + var status = SAMMethods.SamEnumerateAliasesInDomain(Handle, ref enumerationContext, out var ridPointer ,-1, + out var count); + if (status.IsError()) + { + return Result.Fail($"SAMEnumerateAliasesInDomain returned {status}"); + } + + return Result.Ok(ridPointer.GetEnumerable(count) + .Select(x => (x.Name.ToString(), x.Rid))); } - public SAMAlias OpenAlias(int rid) + public Result OpenAlias(int rid, SAMEnums.AliasOpenFlags desiredAccess = SAMEnums.AliasOpenFlags.ListMembers) { - return SAMMethods.SamOpenAlias(Handle, rid); + var status = SAMMethods.SamOpenAlias(Handle, desiredAccess, rid, out var aliasHandle); + if (status.IsError()) + { + return Result.Fail($"SAMOpenAlias returned {status}"); + } + + return new SAMAlias(aliasHandle); } - public SAMAlias OpenAlias(string name) + public Result OpenAlias(string name) { - foreach (var alias in GetAliases()) + var getAliasesResult = GetAliases(); + if (getAliasesResult.IsFailed) + { + return Result.Fail(getAliasesResult.Errors.First()); + } + foreach (var alias in getAliasesResult.Value) if (alias.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) return OpenAlias(alias.Rid); - throw new RPCException(RPCException.OpenAlias, RPCException.AliasNotFound); + return Result.Fail($"Alias {name} was not found"); } } } \ No newline at end of file diff --git a/src/SharpHoundRPC/Wrappers/SAMServer.cs b/src/SharpHoundRPC/Wrappers/SAMServer.cs index cbb89717..da67c7b9 100644 --- a/src/SharpHoundRPC/Wrappers/SAMServer.cs +++ b/src/SharpHoundRPC/Wrappers/SAMServer.cs @@ -11,14 +11,14 @@ namespace SharpHoundRPC.Wrappers { public class SAMServer : SAMBase { - private readonly string _computerName; private SecurityIdentifier _cachedMachineSid; private readonly ConcurrentDictionary _domainHandleCache; + public string ComputerName { get; private set; } - public SAMServer(string computerName, SAMHandle handle) : base(handle) + public SAMServer(SAMHandle handle, string computerName) : base(handle) { - _computerName = computerName; _domainHandleCache = new ConcurrentDictionary(); + ComputerName = computerName; } public static Result OpenServer(string computerName, SAMEnums.SamAccessMasks requestedConnectAccess = @@ -30,20 +30,20 @@ public static Result OpenServer(string computerName, SAMEnums.SamAcce var (status, handle) = SAMMethods.SamConnect(computerName, requestedConnectAccess); return status.IsError() - ? Result.Fail($"SAMConnect returned {status}") - : Result.Ok(new SAMServer(computerName, handle)); + ? Result.Fail(status) + : Result.Ok(new SAMServer(handle, computerName)); } public Result> GetDomains() { var (status, rids) = SAMMethods.SamEnumerateDomainsInSamServer(Handle); - return status.IsError() ? Result.Fail($"SamEnumerateDomainsInSamServer returned {status}") : Result.Ok(rids.Select(x => (x.Name.ToString(), x.Rid))); + return status.IsError() ? Result>.Fail(status) : Result>.Ok(rids.Select(x => (x.Name.ToString(), x.Rid))); } public Result LookupDomain(string name) { var (status, sid) = SAMMethods.SamLookupDomainInSamServer(Handle, name); - return status.IsError() ? Result.Fail($"SamLookupDomainInSamServer returned {status}") : Result.Ok(sid); + return status.IsError() ? Result.Fail(status.ToString()) : Result.Ok(sid); } public Result GetMachineSid(string testName = null) @@ -83,31 +83,52 @@ public Result GetMachineSid(string testName = null) public bool IsDomainController(SecurityIdentifier domainMachineSid) { - return domainMachineSid.AccountDomainSid.Equals(GetMachineSid().AccountDomainSid); + return domainMachineSid.AccountDomainSid.Equals(GetMachineSid().Value.AccountDomainSid); } - public (string Name, SharedEnums.SidNameUse Type) LookupUserBySid(SecurityIdentifier securityIdentifier) + public Result<(string Name, SharedEnums.SidNameUse Type)> LookupUserBySid(SecurityIdentifier securityIdentifier) { - var domain = OpenDomain(securityIdentifier); + var openDomainResult = OpenDomain(securityIdentifier); + if (openDomainResult.IsFailed) + { + return Result.Fail(openDomainResult.Errors.First()); + } + + var domain = openDomainResult.Value; + return domain.LookupUserByRid(securityIdentifier.Rid()); } - public SAMDomain OpenDomain(string domainName, SAMEnums.DomainAccessMask requestedDomainAccess = + public Result OpenDomain(string domainName, SAMEnums.DomainAccessMask requestedDomainAccess = SAMEnums.DomainAccessMask.Lookup | SAMEnums.DomainAccessMask.ListAccounts) { - var sid = LookupDomain(domainName); + var lookupResult = LookupDomain(domainName); + if (lookupResult.IsFailed) + { + return Result.Fail(lookupResult.Errors.First()); + } + + var sid = lookupResult.Value; + if (_domainHandleCache.TryGetValue(sid.Value, out var domain)) { return domain; } + + var status = SAMMethods.SamOpenDomain(Handle, requestedDomainAccess, sid.GetBytes(), out var domainHandle); + if (status.IsError()) + { + return Result.Fail(status.ToString()); + } + + domain = new SAMDomain(domainHandle); - domain = SAMMethods.SamOpenDomain(Handle, sid, requestedDomainAccess); _domainHandleCache.TryAdd(sid.Value, domain); return domain; } - public SAMDomain OpenDomain(SecurityIdentifier securityIdentifier, + public Result OpenDomain(SecurityIdentifier securityIdentifier, SAMEnums.DomainAccessMask requestedDomainAccess = SAMEnums.DomainAccessMask.Lookup | SAMEnums.DomainAccessMask.ListAccounts) @@ -116,7 +137,14 @@ public SAMDomain OpenDomain(SecurityIdentifier securityIdentifier, { return domain; } - domain = SAMMethods.SamOpenDomain(Handle, securityIdentifier, requestedDomainAccess); + + var status = SAMMethods.SamOpenDomain(Handle, requestedDomainAccess, securityIdentifier.GetBytes(), out var domainHandle); + if (status.IsError()) + { + return Result.Fail(status.ToString()); + } + + domain = new SAMDomain(domainHandle); _domainHandleCache.TryAdd(securityIdentifier.Value, domain); return domain; } From 9056cecd35674f3a75028d062e0ee7fdae5b6004 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Mon, 22 Aug 2022 15:43:30 -0400 Subject: [PATCH 08/26] wip: commit for wip --- src/CommonLib/LDAPQueries/CommonProperties.cs | 2 +- src/CommonLib/LDAPUtils.cs | 7 +- src/CommonLib/Processors/LSAServer.cs | 111 -------- .../Processors/LocalGroupProcessor.cs | 240 ++++++++++++------ .../UserRightsAssignmentProcessor.cs | 148 +++++++++++ src/SharpHoundRPC/Extensions.cs | 11 - src/SharpHoundRPC/LSANative/LSAMethods.cs | 35 +-- src/SharpHoundRPC/Result.cs | 31 +-- src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs | 50 +++- src/SharpHoundRPC/Wrappers/LSAPolicy.cs | 71 +++++- src/SharpHoundRPC/Wrappers/SAMAlias.cs | 7 +- src/SharpHoundRPC/Wrappers/SAMDomain.cs | 24 +- src/SharpHoundRPC/Wrappers/SAMServer.cs | 30 +-- 13 files changed, 469 insertions(+), 298 deletions(-) delete mode 100644 src/CommonLib/Processors/LSAServer.cs create mode 100644 src/CommonLib/Processors/UserRightsAssignmentProcessor.cs diff --git a/src/CommonLib/LDAPQueries/CommonProperties.cs b/src/CommonLib/LDAPQueries/CommonProperties.cs index 5cc9c57e..c8c7d1fd 100644 --- a/src/CommonLib/LDAPQueries/CommonProperties.cs +++ b/src/CommonLib/LDAPQueries/CommonProperties.cs @@ -11,7 +11,7 @@ public static class CommonProperties public static readonly string[] BaseQueryProps = { - "objectsid", "distiguishedname", "objectguid", "ms-mcs-admpwdexpirationtime", "isDeleted", + "objectsid", "distinguishedname", "objectguid", "ms-mcs-admpwdexpirationtime", "isDeleted", "useraccountcontrol" }; diff --git a/src/CommonLib/LDAPUtils.cs b/src/CommonLib/LDAPUtils.cs index 4553c2a8..b2325817 100644 --- a/src/CommonLib/LDAPUtils.cs +++ b/src/CommonLib/LDAPUtils.cs @@ -686,15 +686,16 @@ public IEnumerable QueryLDAP(string ldapFilter, SearchScope { conn = task.ConfigureAwait(false).GetAwaiter().GetResult(); } - catch + catch (Exception e) { + _log.LogTrace(e,"Error getting LDAP connection for filter {Filter} and domain {Domain}", ldapFilter, domainName ?? "null"); yield break; } if (conn == null) { _log.LogTrace("LDAP connection is null for filter {Filter} and domain {Domain}", ldapFilter, - domainName); + domainName ?? "null"); yield break; } @@ -702,7 +703,7 @@ public IEnumerable QueryLDAP(string ldapFilter, SearchScope if (request == null) { - _log.LogTrace("Search request is null for filter {Filter} and domain {Domain}", ldapFilter, domainName); + _log.LogTrace("Search request is null for filter {Filter} and domain {Domain}", ldapFilter, domainName ?? "null"); yield break; } diff --git a/src/CommonLib/Processors/LSAServer.cs b/src/CommonLib/Processors/LSAServer.cs deleted file mode 100644 index f55b2007..00000000 --- a/src/CommonLib/Processors/LSAServer.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Security.Principal; -using Microsoft.Extensions.Logging; -using SharpHoundCommonLib.Enums; -using SharpHoundCommonLib.OutputTypes; - -namespace SharpHoundCommonLib.Processors -{ - public class LSAServer : IDisposable - { - private readonly string _computerName; - private readonly ILogger _log; - private readonly NativeMethods _nativeMethods; - private readonly NativeMethods.LSA_OBJECT_ATTRIBUTES _obj; - - private readonly IntPtr _policyHandle; - private readonly ILDAPUtils _utils; - - /// - /// Creates an instance of an RPCServer which is used for making SharpHound specific LSA API calls for computers - /// - /// - /// - /// - /// - public LSAServer(string computerName, ILDAPUtils utils = null, NativeMethods methods = null, ILogger log = null) - { - _computerName = computerName; - _nativeMethods = methods ?? new NativeMethods(); - _utils = utils ?? new LDAPUtils(); - _log = log ?? Logging.LogProvider.CreateLogger("SAMRPCServer"); - _log.LogTrace($"Opening LSA server for {computerName}"); - - var us = new NativeMethods.LSA_UNICODE_STRING(computerName); - var status = _nativeMethods.CallLSAOpenPolicy(ref us, ref _obj, - NativeMethods.LsaOpenMask.LookupNames | NativeMethods.LsaOpenMask.ViewLocalInfo, - out var policyHandle); - _log.LogTrace($"LSAOpenPolicy returned {status} for {computerName}"); - if (status != NativeMethods.NtStatus.StatusSuccess) - { - _nativeMethods.CallLSAClose(policyHandle); - - throw new APIException - { - Status = status.ToString(), - APICall = "LSAOpenPolicy" - }; - } - - _policyHandle = policyHandle; - } - - public void Dispose() - { - if (_policyHandle != IntPtr.Zero) _nativeMethods.CallLSAClose(_policyHandle); - - _obj.Dispose(); - } - - /// - /// Reads principals granted the specified privilege. Refer to for a list of relevant - /// privileges. - /// - /// - /// - public IEnumerable ReadLSAPrivilege(string privilege) - { - var result = new LSAPrivilegeAPIResult(); - var status = - _nativeMethods.CallLSAEnumerateAccountsWithUserRight(_policyHandle, privilege, out var accounts, - out var count); - - _log.LogTrace($"LSAEnumerateAccountsWithUserRight returned {status} for {privilege} on {_computerName}"); - if (status != NativeMethods.NtStatus.StatusSuccess) - { - _nativeMethods.CallLSAClose(accounts); - result.FailureReason = $"LSAEnumerateAccountsWithUserRight returned {status}"; - yield break; - } - - _log.LogTrace( - $"LSAEnumerateAccountsWithUserRight returned {count} objects for privilege {privilege} on {_computerName}"); - - if (count == 0) - { - _nativeMethods.CallLSAClose(accounts); - result.Collected = true; - yield break; - } - - var sids = new List(); - for (var i = 0; i < count; i++) - try - { - var raw = Marshal.ReadIntPtr(accounts, Marshal.SizeOf() * i); - var sid = new SecurityIdentifier(raw).Value; - sids.Add(sid); - } - catch (Exception e) - { - _log.LogTrace($"Exception converting sid: {e}"); - } - - _nativeMethods.CallLSAClose(accounts); - - foreach (var sid in sids) yield return sid; - } - } -} \ No newline at end of file diff --git a/src/CommonLib/Processors/LocalGroupProcessor.cs b/src/CommonLib/Processors/LocalGroupProcessor.cs index aacea4cb..e71a79e3 100644 --- a/src/CommonLib/Processors/LocalGroupProcessor.cs +++ b/src/CommonLib/Processors/LocalGroupProcessor.cs @@ -1,14 +1,10 @@ -using System; -using System.Collections; -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Security.Principal; using Microsoft.Extensions.Logging; using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.OutputTypes; -using SharpHoundRPC; using SharpHoundRPC.Shared; using SharpHoundRPC.Wrappers; @@ -33,25 +29,51 @@ public LocalGroupProcessor(ILDAPUtils utils, ILogger log = null) _log = log ?? Logging.LogProvider.CreateLogger("LocalGroupProcessor"); } - public IEnumerable GetLocalGroups(SAMServer server, string computerDomainSid, string computerDomain) + public IEnumerable GetLocalGroups(string computerName, string computerDomainSid, string computerDomain) { - var groupCache = new ConcurrentBag(); + var openServerResult = SAMServer.OpenServer(computerName); + if (openServerResult.IsFailed) + { + SendComputerStatus(new CSVComputerStatus + { + Task = "SamConnect", + ComputerName = computerName, + Status = openServerResult.Status.ToString() + }); + yield break; + } + + var server = openServerResult.Value; var typeCache = new ConcurrentDictionary(); var computerSid = new SecurityIdentifier(computerDomainSid); + + if (!Cache.GetMachineSid(computerDomainSid, out var machineSid)) + { + var getMachineSidResult = server.GetMachineSid(); + if (getMachineSidResult.IsFailed) + { + machineSid = "UNKNOWN"; + } + else + { + machineSid = getMachineSidResult.Value.Value; + Cache.AddMachineSid(computerDomainSid, machineSid); + } + } - var machineSid = server.GetMachineSid().ValueOrDefault; + var getDomainsResult = server.GetDomains(); if (getDomainsResult.IsFailed) { SendComputerStatus(new CSVComputerStatus { Task = "GetDomains", - ComputerName = server.ComputerName, - Status = getDomainsResult. + ComputerName = computerName, + Status = getDomainsResult.Status.ToString() }); yield break; } - foreach (var domainResult in server.GetDomains()) + foreach (var domainResult in getDomainsResult.Value) { var ret = new LocalGroupAPIResult { @@ -59,102 +81,160 @@ public IEnumerable GetLocalGroups(SAMServer server, string GroupRID = domainResult.Rid, }; - try + var openDomainResult = server.OpenDomain(domainResult.Name); + if (openDomainResult.IsFailed) { - var domain = server.OpenDomain(domainResult.Name);; - foreach (var alias in domain.GetAliases()) + SendComputerStatus(new CSVComputerStatus + { + Task = $"OpenDomain - {domainResult.Name}", + ComputerName = computerName, + Status = openDomainResult.Status.ToString() + }); + continue; + } + + var domain = openDomainResult.Value; + + var getAliasesResult = domain.GetAliases(); + + if (getAliasesResult.IsFailed) + { + SendComputerStatus(new CSVComputerStatus + { + Task = $"GetAliases - {domainResult.Name}", + ComputerName = computerName, + Status = getAliasesResult.Status.ToString() + }); + continue; + } + + foreach (var alias in getAliasesResult.Value) + { + var openAliasResult = domain.OpenAlias(alias.Rid); + if (openAliasResult.IsFailed) + { + SendComputerStatus(new CSVComputerStatus + { + Task = $"OpenAlias - {alias.Name}", + ComputerName = computerName, + Status = openAliasResult.Status.ToString() + }); + ret.Collected = false; + ret.FailureReason = $"SamOpenAliasInDomain failed with status {openAliasResult.Status}"; + yield return ret; + } + + var results = new List(); + var names = new List(); + + var localGroup = openAliasResult.Value; + var getMembersResult = localGroup.GetMembers(); + if (getMembersResult.IsFailed) { - var results = new List(); - var names = new List(); - using var localGroup = domain.OpenAlias(alias.Rid); - foreach (var securityIdentifier in localGroup.GetMembers()) + SendComputerStatus(new CSVComputerStatus { - if (IsSidFiltered(securityIdentifier)) - continue; + Task = $"GetMembersInAlias - {alias.Name}", + ComputerName = computerName, + Status = getMembersResult.Status.ToString() + }); + ret.Collected = false; + ret.FailureReason = $"SamGetMembersInAlias failed with status {getMembersResult.Status}"; + yield return ret; + } + + foreach (var securityIdentifier in getMembersResult.Value) + { + if (IsSidFiltered(securityIdentifier)) + continue; - var sidValue = securityIdentifier.Value; + var sidValue = securityIdentifier.Value; - if (server.IsDomainController(computerSid)) + if (server.IsDomainController(computerSid)) + { + if (_utils.GetWellKnownPrincipal(sidValue, computerDomain, out var wellKnown)) + results.Add(wellKnown); + else + results.Add(_utils.ResolveIDAndType(sidValue, computerDomain)); + } + else + { + if (WellKnownPrincipal.GetWellKnownPrincipal(sidValue, out var wellKnown)) { - if (_utils.GetWellKnownPrincipal(sidValue, computerDomain, - out var wellKnown)) - { - results.Add(wellKnown); - } - else + if (machineSid == "UNKNOWN") + continue; + wellKnown.ObjectIdentifier = $"{machineSid}-{securityIdentifier.Rid()}"; + wellKnown.ObjectType = wellKnown.ObjectType switch { - results.Add(_utils.ResolveIDAndType(sidValue, computerDomain)); - } + Label.User => Label.LocalUser, + Label.Group => Label.LocalGroup, + _ => wellKnown.ObjectType + }; + results.Add(wellKnown); } else { - if (WellKnownPrincipal.GetWellKnownPrincipal(sidValue, - out var wellKnown)) + if (securityIdentifier.IsEqualDomainSid(computerSid)) { - wellKnown.ObjectIdentifier = $"{machineSid.Value}-{securityIdentifier.Rid()}"; - wellKnown.ObjectType = wellKnown.ObjectType switch - { - Label.User => Label.LocalUser, - Label.Group => Label.LocalGroup, - _ => wellKnown.ObjectType - }; - results.Add(wellKnown); - } else + results.Add(_utils.ResolveIDAndType(sidValue, computerDomain)); + } + else { - if (securityIdentifier.IsEqualDomainSid(computerSid)) + if (typeCache.TryGetValue(sidValue, out var item)) { - results.Add(_utils.ResolveIDAndType(sidValue, computerDomain)); + results.Add(new TypedPrincipal + { + ObjectIdentifier = sidValue, + ObjectType = item.Type + }); + + names.Add(new NamedPrincipal + { + ObjectId = sidValue, + PrincipalName = item.Name + }); } else { - if (typeCache.TryGetValue(sidValue, out var item)) + var lookupUserResult = server.LookupPrincipalBySid(securityIdentifier); + if (lookupUserResult.IsFailed) { - _log.LogTrace("ResolveLocalSid - Cache hit for {ID}", sidValue); - results.Add(new TypedPrincipal - { - ObjectIdentifier = sidValue, - ObjectType = item.Type - }); - - names.Add(new NamedPrincipal - { - ObjectId = sidValue, - PrincipalName = item.Name - }); + _log.LogTrace("Unable to resolve local sid {SID}", sidValue); + continue; } - try + var (name, use) = lookupUserResult.Value; + var objectType = use switch { - var (name, use) = server.LookupUserBySid(securityIdentifier); - var objectType = use switch - { - SharedEnums.SidNameUse.User => Label.LocalUser, - SharedEnums.SidNameUse.Group => Label.LocalGroup, - SharedEnums.SidNameUse.Alias => Label.LocalGroup, - _ => Label.Base - }; - - results.Add(new TypedPrincipal - { - - }); - } - catch (Exception e) + SharedEnums.SidNameUse.User => Label.LocalUser, + SharedEnums.SidNameUse.Group => Label.LocalGroup, + SharedEnums.SidNameUse.Alias => Label.LocalGroup, + _ => Label.Base + }; + + typeCache.TryAdd(sidValue, new CachedLocalItem(name, objectType)); + + results.Add(new TypedPrincipal { - _log.LogTrace(e, "Unable to resolve local sid {SID}", securityIdentifier.Value); - } + ObjectIdentifier = sidValue, + ObjectType = objectType + }); + + names.Add(new NamedPrincipal + { + PrincipalName = name, + ObjectId = sidValue + }); } + } } } } + + ret.LocalNames = names.ToArray(); + ret.Results = results.ToArray(); + yield return ret; } - catch (RPCException e) - { - ret.Collected = false; - ret.FailureReason = e.ToString(); - } - yield return ret; } } diff --git a/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs b/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs new file mode 100644 index 00000000..a589e2b3 --- /dev/null +++ b/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs @@ -0,0 +1,148 @@ +using System.Collections.Generic; +using System.Linq; +using System.Security.Principal; +using Microsoft.Extensions.Logging; +using SharpHoundCommonLib.Enums; +using SharpHoundCommonLib.OutputTypes; +using SharpHoundRPC.Wrappers; + +namespace SharpHoundCommonLib.Processors +{ + public class UserRightsAssignmentProcessor + { + private readonly ILogger _log; + private readonly ILDAPUtils _utils; + private readonly string[] _filteredSids = + { + "S-1-5-2", "S-1-5-2", "S-1-5-3", "S-1-5-4", "S-1-5-6", "S-1-5-7", "S-1-2", "S-1-2-0", "S-1-5-18", + "S-1-5-19", "S-1-5-20" + }; + + public delegate void ComputerStatusDelegate(CSVComputerStatus status); + public event ComputerStatusDelegate ComputerStatusEvent; + + public UserRightsAssignmentProcessor(ILDAPUtils utils, ILogger log = null) + { + _utils = utils; + _log = log ?? Logging.LogProvider.CreateLogger("LocalGroupProcessor"); + } + + public IEnumerable GetUserRightsAssignments(string computerName, string computerDomainSid, string computerDomain, string[] desiredPrivileges = null) + { + var computerSid = new SecurityIdentifier(computerDomainSid); + var policyOpenResult = LSAPolicy.OpenPolicy(computerName); + if (policyOpenResult.IsFailed) + { + SendComputerStatus(new CSVComputerStatus + { + Task = "SamConnect", + ComputerName = computerName, + Status = policyOpenResult.Status.ToString() + }); + yield break; + } + + var server = policyOpenResult.Value; + desiredPrivileges ??= LSAPrivileges.DesiredPrivileges; + foreach (var privilege in desiredPrivileges) + { + var result = new UserRightsAssignmentAPIResult + { + Collected = false, + Privilege = privilege + }; + + var enumerateAccountsResult = server.GetPrincipalsWithPrivilege(privilege); + if (enumerateAccountsResult.IsFailed) + { + SendComputerStatus(new CSVComputerStatus + { + ComputerName = computerName, + Status = enumerateAccountsResult.Status.ToString(), + Task = "LSAEnumerateAccountsWithUserRight" + }); + result.FailureReason = + $"LSAEnumerateAccountsWithUserRights returned {enumerateAccountsResult.Status}"; + yield return result; + } + + if (!Cache.GetMachineSid(computerDomainSid, out var machineSid)) + { + var getMachineSidResult = server.GetLocalDomainInformation(); + if (getMachineSidResult.IsFailed) + { + machineSid = "UNKNOWN"; + } + else + { + machineSid = getMachineSidResult.Value.Sid; + Cache.AddMachineSid(computerDomainSid, machineSid); + } + } + + var isDc = computerSid.IsEqualDomainSid(new SecurityIdentifier(machineSid)); + + var resolved = new List(); + var toResolve = new List(); + + foreach (var sid in enumerateAccountsResult.Value) + { + if (IsSidFiltered(sid)) + continue; + + if (isDc) + { + if (_utils.GetWellKnownPrincipal(sid.Value, computerDomain, out var principal)) + { + resolved.Add(principal); + } + else + { + var res = _utils.ResolveIDAndType(sid.Value, computerDomain); + resolved.Add(res); + } + } + else + { + if (WellKnownPrincipal.GetWellKnownPrincipal(sid.Value, out var common)) + { + if (machineSid == "UNKNOWN") + continue; + common.ObjectIdentifier = $"{machineSid}-{sid.Rid()}"; + common.ObjectType = common.ObjectType switch + { + Label.User => Label.LocalUser, + Label.Group => Label.LocalGroup, + _ => common.ObjectType + }; + resolved.Add(common); + } + else + { + + } + } + } + } + } + + private bool IsSidFiltered(SecurityIdentifier identifier) + { + var value = identifier.Value; + + if (value.StartsWith("S-1-5-80") || value.StartsWith("S-1-5-82") || + value.StartsWith("S-1-5-90") || value.StartsWith("S-1-5-96")) + return true; + + if (_filteredSids.Contains(value)) + return true; + + return false; + } + + private void SendComputerStatus(CSVComputerStatus status) + { + ComputerStatusEvent?.Invoke(status); + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/Extensions.cs b/src/SharpHoundRPC/Extensions.cs index 50a153ab..2e4f8bf5 100644 --- a/src/SharpHoundRPC/Extensions.cs +++ b/src/SharpHoundRPC/Extensions.cs @@ -1,6 +1,5 @@ using System; using System.Security.Principal; -using FluentResults; namespace SharpHoundRPC { @@ -15,16 +14,6 @@ public static bool IsError(this NtStatus status) return false; } - public static Result ResultValue(this Result result, string failureMessage, T value) - { - if (result.IsSuccess) - { - return Result.Ok(value); - } - - return Result.Fail(failureMessage); - } - /// /// Gets the relative identifier for a SID /// diff --git a/src/SharpHoundRPC/LSANative/LSAMethods.cs b/src/SharpHoundRPC/LSANative/LSAMethods.cs index 2edc4ff2..30b83ce0 100644 --- a/src/SharpHoundRPC/LSANative/LSAMethods.cs +++ b/src/SharpHoundRPC/LSANative/LSAMethods.cs @@ -12,14 +12,13 @@ namespace SharpHoundRPC.LSANative [SuppressUnmanagedCodeSecurity] public class LSAMethods { - internal static LSAHandle LsaOpenPolicy(string computerName, LSAEnums.LsaOpenMask desiredAccess) + internal static (NtStatus status, LSAHandle policyHandle) LsaOpenPolicy(string computerName, LSAEnums.LsaOpenMask desiredAccess) { var us = new SharedStructs.UnicodeString(computerName); var objectAttributes = default(LSAStructs.ObjectAttributes); - var status = LsaOpenPolicy(ref us, ref objectAttributes, desiredAccess, out var handle); - status.CheckError("LsaOpenPolicy"); + var status = LsaOpenPolicy(ref us, ref objectAttributes, desiredAccess, out var policyHandle); - return handle; + return (status, policyHandle); } [DllImport("advapi32.dll")] @@ -32,7 +31,7 @@ out LSAHandle policyHandle [DllImport("advapi32.dll")] internal static extern NtStatus LsaClose( - IntPtr buffer + IntPtr handle ); [DllImport("advapi32.dll")] @@ -40,13 +39,12 @@ internal static extern NtStatus LsaFreeMemory( IntPtr buffer ); - internal static LSAPointer LsaQueryInformationPolicy(LSAHandle policyHandle, + internal static (NtStatus status, LSAPointer pointer) LsaQueryInformationPolicy(LSAHandle policyHandle, LSAEnums.LSAPolicyInformation policyInformation) { var status = LsaQueryInformationPolicy(policyHandle, policyInformation, out var pointer); - status.CheckError("LSAQueryInformationPolicy"); - return pointer; + return (status, pointer); } [DllImport("advapi32.dll")] @@ -56,16 +54,15 @@ private static extern NtStatus LsaQueryInformationPolicy( out LSAPointer buffer ); - internal static IEnumerable LsaEnumerateAccountsWithUserRight(LSAHandle policyHandle, + internal static (NtStatus status, LSAPointer sids, int count) LsaEnumerateAccountsWithUserRight(LSAHandle policyHandle, string userRight) { var arr = new SharedStructs.UnicodeString[1]; arr[0] = new SharedStructs.UnicodeString(userRight); var status = LsaEnumerateAccountsWithUserRight(policyHandle, arr, out var sids, out var count); - status.CheckError("LsaEnumerateAccountsWithUserRight"); - return sids.GetEnumerable(count); + return (status, sids, count); } [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)] @@ -76,11 +73,15 @@ private static extern NtStatus LsaEnumerateAccountsWithUserRight( out int count ); - internal static IEnumerable<(SecurityIdentifier SID, string Name, SharedEnums.SidNameUse Use, string Domain)> + internal static (NtStatus status, LSAPointer referencedDomains, LSAPointer names, int count) LsaLookupSids(LSAHandle policyHandle, SecurityIdentifier[] sids) { + var count = sids.Length; + if (count == 0) + return (NtStatus.StatusInvalidParameter, null, null, 0); + var gcHandles = new GCHandle[count]; var pSids = new IntPtr[count]; @@ -96,15 +97,7 @@ out int count try { var status = LsaLookupSids(policyHandle, count, pSids, out var referencedDomains, out var names); - status.CheckError("LsaLookupSids"); - - var translatedNames = names.GetEnumerable(count).ToArray(); - var domainList = referencedDomains.GetData(); - var safeDomains = new LSAPointer(domainList.Domains); - var domains = safeDomains.GetEnumerable(domainList.Entries).ToArray(); - for (var i = 0; i < count; i++) - yield return (sids[i], translatedNames[i].Name.ToString(), translatedNames[i].Use, - domains[translatedNames[i].DomainIndex].Name.ToString()); + return (status, referencedDomains, names, count); } finally { diff --git a/src/SharpHoundRPC/Result.cs b/src/SharpHoundRPC/Result.cs index 8fea07cc..f6f4b68f 100644 --- a/src/SharpHoundRPC/Result.cs +++ b/src/SharpHoundRPC/Result.cs @@ -2,28 +2,16 @@ { public class Result { - private NtStatus Status { get; set; } = NtStatus.StatusSuccess; + public bool IsSuccess { get; private set; } + public NtStatus Status { get; private set; } public T Value { get; private set; } + public string Error { get; private set; } + public bool IsFailed => !IsSuccess; - public bool IsFailed => Status.IsError(); + public static Result Ok(T value) => new() {IsSuccess = true, Value = value}; - public static Result Fail(NtStatus status) - { - var result = new Result - { - Status = status - }; - return result; - } - - public static Result Ok(T value) - { - var result = new Result - { - Value = value - }; - return result; - } + public static Result Fail(NtStatus status) => new() {Status = status}; + public static Result Fail(string error) => new() {Error = error}; public static implicit operator Result(T input) { @@ -34,5 +22,10 @@ public static implicit operator Result(NtStatus status) { return Fail(status); } + + public static implicit operator Result(string error) + { + return Fail(error); + } } } \ No newline at end of file diff --git a/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs b/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs index d3e361cd..77ec4948 100644 --- a/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs +++ b/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs @@ -1,13 +1,10 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Runtime.InteropServices; using System.Security; using System.Security.Principal; -using FluentResults; using SharpHoundRPC.Handles; using SharpHoundRPC.Shared; -using SharpHoundRPC.Wrappers; namespace SharpHoundRPC.SAMRPCNative { @@ -66,31 +63,60 @@ private static extern NtStatus SamLookupDomainInSamServer( ref SharedStructs.UnicodeString name, out SAMPointer sid); + internal static (NtStatus status, SAMHandle domainHandle) SamOpenDomain(SAMHandle serverHandle, + SAMEnums.DomainAccessMask desiredAccess, byte[] domainSid) + { + var status = SamOpenDomain(serverHandle, desiredAccess, domainSid, out var domainHandle); + return (status, domainHandle); + } + [DllImport("samlib.dll", CharSet = CharSet.Unicode)] - internal static extern NtStatus SamOpenDomain( + private static extern NtStatus SamOpenDomain( SAMHandle serverHandle, SAMEnums.DomainAccessMask desiredAccess, [MarshalAs(UnmanagedType.LPArray)] byte[] domainSid, out SAMHandle domainHandle ); + internal static (NtStatus status, SAMSidArray members, int count) SamGetMembersInAlias(SAMHandle aliasHandle) + { + var status = SamGetMembersInAlias(aliasHandle, out var members, out var count); + return (status, members, count); + } + [DllImport("samlib.dll", CharSet = CharSet.Unicode)] - internal static extern NtStatus SamGetMembersInAlias( + private static extern NtStatus SamGetMembersInAlias( SAMHandle aliasHandle, out SAMSidArray members, out int count ); + internal static (NtStatus status, SAMHandle aliasHandle) SamOpenAlias(SAMHandle domainHandle, + SAMEnums.AliasOpenFlags desiredAccess, int aliasId) + { + var status = SamOpenAlias(domainHandle, desiredAccess, aliasId, out var aliasHandle); + return (status, aliasHandle); + } + [DllImport("samlib.dll", CharSet = CharSet.Unicode)] - internal static extern NtStatus SamOpenAlias( + private static extern NtStatus SamOpenAlias( SAMHandle domainHandle, SAMEnums.AliasOpenFlags desiredAccess, int aliasId, out SAMHandle aliasHandle ); + internal static (NtStatus status, SAMPointer pointer, int count) SamEnumerateAliasesInDomain( + SAMHandle domainHandle) + { + var enumerationContext = 0; + var status = SamEnumerateAliasesInDomain(domainHandle, ref enumerationContext, out var buffer, -1, + out var count); + return (status, buffer, count); + } + [DllImport("samlib.dll", CharSet = CharSet.Unicode)] - internal static extern NtStatus SamEnumerateAliasesInDomain( + private static extern NtStatus SamEnumerateAliasesInDomain( SAMHandle domainHandle, ref int enumerationContext, out SAMPointer buffer, @@ -113,8 +139,16 @@ out int count // Marshal.Copy(usePointer.DangerousGetHandle(), (int[]) (object) types, 0, count); // } + internal static (NtStatus status, SAMPointer names, SAMPointer use) SamLookupIdsInDomain(SAMHandle domainHandle, + int rid) + { + var rids = new[] {rid}; + var status = SamLookupIdsInDomain(domainHandle, 1, rids, out var names, out var use); + return (status, names, use); + } + [DllImport("samlib.dll", CharSet = CharSet.Unicode)] - internal static extern NtStatus SamLookupIdsInDomain(SAMHandle domainHandle, + private static extern NtStatus SamLookupIdsInDomain(SAMHandle domainHandle, int count, int[] rids, out SAMPointer names, diff --git a/src/SharpHoundRPC/Wrappers/LSAPolicy.cs b/src/SharpHoundRPC/Wrappers/LSAPolicy.cs index b118e632..14888ba8 100644 --- a/src/SharpHoundRPC/Wrappers/LSAPolicy.cs +++ b/src/SharpHoundRPC/Wrappers/LSAPolicy.cs @@ -16,38 +16,87 @@ public LSAPolicy(string computerName, LSAHandle handle) : base(handle) _computerName = computerName; } - public static LSAPolicy OpenPolicy(string computerName, LSAEnums.LsaOpenMask desiredAccess = + public static Result OpenPolicy(string computerName, LSAEnums.LsaOpenMask desiredAccess = LSAEnums.LsaOpenMask.LookupNames | LSAEnums.LsaOpenMask.ViewLocalInfo) { - var handle = LSAMethods.LsaOpenPolicy(computerName, desiredAccess); + var (status, handle) = LSAMethods.LsaOpenPolicy(computerName, desiredAccess); + if (status.IsError()) + { + return status; + } return new LSAPolicy(computerName, handle); } - public (string Name, string Sid) GetLocalDomainInformation() + public Result<(string Name, string Sid)> GetLocalDomainInformation() { var result = LSAMethods.LsaQueryInformationPolicy(Handle, LSAEnums.LSAPolicyInformation.PolicyAccountDomainInformation); - var domainInfo = result.GetData(); + if (result.status.IsError()) + { + return result.status; + } + + var domainInfo = result.pointer.GetData(); var domainSid = new SecurityIdentifier(domainInfo.DomainSid); return (domainInfo.DomainName.ToString(), domainSid.Value.ToUpper()); } - public IEnumerable GetPrincipalsWithPrivilege(string userRight) + public Result> GetPrincipalsWithPrivilege(string userRight) { - return LSAMethods.LsaEnumerateAccountsWithUserRight(Handle, userRight); + var (status, sids, count) = LSAMethods.LsaEnumerateAccountsWithUserRight(Handle, userRight); + if (status.IsError()) + { + return status; + } + + return Result>.Ok(sids.GetEnumerable(count)); } - public (string Name, SharedEnums.SidNameUse Use, string Domains) LookupSid(SecurityIdentifier sid) + public Result<(string Name, SharedEnums.SidNameUse Use, string Domains)> LookupSid(SecurityIdentifier sid) { - var result = LSAMethods.LsaLookupSids(Handle, new[] {sid}).First(); - return (result.Name, result.Use, result.Domain); + if (sid == null) + return "SID cannot be null"; + + var (status, referencedDomains, names, count) = LSAMethods.LsaLookupSids(Handle, new[] {sid}); + if (status.IsError()) + { + return status; + } + + var translatedNames = names.GetEnumerable(count).ToArray(); + var domainList = referencedDomains.GetData(); + var safeDomains = new LSAPointer(domainList.Domains); + var domains = safeDomains.GetEnumerable(domainList.Entries).ToArray(); + return (translatedNames[0].Name.ToString(), translatedNames[0].Use, + domains[translatedNames[0].DomainIndex].Name.ToString()); } - public IEnumerable<(SecurityIdentifier Sid, string Name, SharedEnums.SidNameUse Use, string Domain)> LookupSids( + public Result> LookupSids( SecurityIdentifier[] sids) { - return LSAMethods.LsaLookupSids(Handle, sids); + sids = sids.Where(x => x != null).ToArray(); + if (sids.Length == 0) + return "No non-null SIDs specified"; + + var (status, referencedDomains, names, count) = LSAMethods.LsaLookupSids(Handle, sids); + if (status.IsError()) + { + return status; + } + + var translatedNames = names.GetEnumerable(count).ToArray(); + var domainList = referencedDomains.GetData(); + var safeDomains = new LSAPointer(domainList.Domains); + var domains = safeDomains.GetEnumerable(domainList.Entries).ToArray(); + + var ret = new List<(SecurityIdentifier Sid, string Name, SharedEnums.SidNameUse Use, string Domain)>(); + for (var i = 0; i < count; i++) + { + ret.Add((sids[i], translatedNames[i].Name.ToString(), translatedNames[i].Use, domains[translatedNames[i].DomainIndex].Name.ToString())); + } + + return ret.ToArray(); } } } \ No newline at end of file diff --git a/src/SharpHoundRPC/Wrappers/SAMAlias.cs b/src/SharpHoundRPC/Wrappers/SAMAlias.cs index 6c13be7b..424aa6cb 100644 --- a/src/SharpHoundRPC/Wrappers/SAMAlias.cs +++ b/src/SharpHoundRPC/Wrappers/SAMAlias.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Security.Principal; -using FluentResults; using SharpHoundRPC.Handles; using SharpHoundRPC.SAMRPCNative; @@ -17,13 +16,13 @@ public SAMAlias(SAMHandle handle) : base(handle) public Result> GetMembers() { - var status = SAMMethods.SamGetMembersInAlias(Handle, out var members, out var count); + var (status, members, count) = SAMMethods.SamGetMembersInAlias(Handle); if (status.IsError()) { - return Result.Fail($"SAMGetMembersInAlias returned {status}"); + return status; } - return Result.Ok(members.GetData(count)); + return Result>.Ok(members.GetData(count)); } } } \ No newline at end of file diff --git a/src/SharpHoundRPC/Wrappers/SAMDomain.cs b/src/SharpHoundRPC/Wrappers/SAMDomain.cs index 0edf0f05..e3ec5ee2 100644 --- a/src/SharpHoundRPC/Wrappers/SAMDomain.cs +++ b/src/SharpHoundRPC/Wrappers/SAMDomain.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using FluentResults; using SharpHoundRPC.Handles; using SharpHoundRPC.SAMRPCNative; using SharpHoundRPC.Shared; @@ -14,13 +13,12 @@ public SAMDomain(SAMHandle handle) : base(handle) { } - public Result<(string Name, SharedEnums.SidNameUse Type)> LookupUserByRid(int rid) + public Result<(string Name, SharedEnums.SidNameUse Type)> LookupPrincipalByRid(int rid) { - var ridArray = new[] {rid}; - var status = SAMMethods.SamLookupIdsInDomain(Handle, 1, ridArray, out var namePointer, out var usePointer); + var (status, namePointer, usePointer) = SAMMethods.SamLookupIdsInDomain(Handle, rid); if (status.IsError()) { - return Result.Fail($"SAMLookupIdsInDomain returned {status}"); + return status; } return (namePointer.GetData().ToString(), (SharedEnums.SidNameUse)usePointer.GetData()); @@ -29,23 +27,22 @@ public SAMDomain(SAMHandle handle) : base(handle) public Result> GetAliases() { var enumerationContext = 0; - var status = SAMMethods.SamEnumerateAliasesInDomain(Handle, ref enumerationContext, out var ridPointer ,-1, - out var count); + var (status, ridPointer, count) = SAMMethods.SamEnumerateAliasesInDomain(Handle); if (status.IsError()) { - return Result.Fail($"SAMEnumerateAliasesInDomain returned {status}"); + return status; } - return Result.Ok(ridPointer.GetEnumerable(count) + return Result>.Ok(ridPointer.GetEnumerable(count) .Select(x => (x.Name.ToString(), x.Rid))); } public Result OpenAlias(int rid, SAMEnums.AliasOpenFlags desiredAccess = SAMEnums.AliasOpenFlags.ListMembers) { - var status = SAMMethods.SamOpenAlias(Handle, desiredAccess, rid, out var aliasHandle); + var (status, aliasHandle) = SAMMethods.SamOpenAlias(Handle, desiredAccess, rid); if (status.IsError()) { - return Result.Fail($"SAMOpenAlias returned {status}"); + return status; } return new SAMAlias(aliasHandle); @@ -56,13 +53,14 @@ public Result OpenAlias(string name) var getAliasesResult = GetAliases(); if (getAliasesResult.IsFailed) { - return Result.Fail(getAliasesResult.Errors.First()); + return getAliasesResult.Status; } + foreach (var alias in getAliasesResult.Value) if (alias.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) return OpenAlias(alias.Rid); - return Result.Fail($"Alias {name} was not found"); + return $"Alias {name} was not found"; } } } \ No newline at end of file diff --git a/src/SharpHoundRPC/Wrappers/SAMServer.cs b/src/SharpHoundRPC/Wrappers/SAMServer.cs index da67c7b9..88d6f867 100644 --- a/src/SharpHoundRPC/Wrappers/SAMServer.cs +++ b/src/SharpHoundRPC/Wrappers/SAMServer.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Security.Principal; -using FluentResults; using SharpHoundRPC.Handles; using SharpHoundRPC.SAMRPCNative; using SharpHoundRPC.Shared; @@ -30,20 +29,20 @@ public static Result OpenServer(string computerName, SAMEnums.SamAcce var (status, handle) = SAMMethods.SamConnect(computerName, requestedConnectAccess); return status.IsError() - ? Result.Fail(status) - : Result.Ok(new SAMServer(handle, computerName)); + ? status + : new SAMServer(handle, computerName); } public Result> GetDomains() { var (status, rids) = SAMMethods.SamEnumerateDomainsInSamServer(Handle); - return status.IsError() ? Result>.Fail(status) : Result>.Ok(rids.Select(x => (x.Name.ToString(), x.Rid))); + return status.IsError() ? status : Result>.Ok(rids.Select(x => (x.Name.ToString(), x.Rid))); } public Result LookupDomain(string name) { var (status, sid) = SAMMethods.SamLookupDomainInSamServer(Handle, name); - return status.IsError() ? Result.Fail(status.ToString()) : Result.Ok(sid); + return status.IsError() ? status : sid; } public Result GetMachineSid(string testName = null) @@ -75,10 +74,9 @@ public Result GetMachineSid(string testName = null) } } - if (sid == null) return Result.Fail("Unable to get machine sid"); + if (sid == null) return "Unable to get machine sid"; _cachedMachineSid = sid; - return Result.Ok(sid); - + return sid; } public bool IsDomainController(SecurityIdentifier domainMachineSid) @@ -86,17 +84,17 @@ public bool IsDomainController(SecurityIdentifier domainMachineSid) return domainMachineSid.AccountDomainSid.Equals(GetMachineSid().Value.AccountDomainSid); } - public Result<(string Name, SharedEnums.SidNameUse Type)> LookupUserBySid(SecurityIdentifier securityIdentifier) + public Result<(string Name, SharedEnums.SidNameUse Type)> LookupPrincipalBySid(SecurityIdentifier securityIdentifier) { var openDomainResult = OpenDomain(securityIdentifier); if (openDomainResult.IsFailed) { - return Result.Fail(openDomainResult.Errors.First()); + return $"OpenDomain returned {openDomainResult.Status}"; } var domain = openDomainResult.Value; - return domain.LookupUserByRid(securityIdentifier.Rid()); + return domain.LookupPrincipalByRid(securityIdentifier.Rid()); } public Result OpenDomain(string domainName, SAMEnums.DomainAccessMask requestedDomainAccess = @@ -106,7 +104,7 @@ public Result OpenDomain(string domainName, SAMEnums.DomainAccessMask var lookupResult = LookupDomain(domainName); if (lookupResult.IsFailed) { - return Result.Fail(lookupResult.Errors.First()); + return $"LookupDomain returned {lookupResult.Error}"; } var sid = lookupResult.Value; @@ -116,10 +114,10 @@ public Result OpenDomain(string domainName, SAMEnums.DomainAccessMask return domain; } - var status = SAMMethods.SamOpenDomain(Handle, requestedDomainAccess, sid.GetBytes(), out var domainHandle); + var (status, domainHandle) = SAMMethods.SamOpenDomain(Handle, requestedDomainAccess, sid.GetBytes()); if (status.IsError()) { - return Result.Fail(status.ToString()); + return status; } domain = new SAMDomain(domainHandle); @@ -138,10 +136,10 @@ public Result OpenDomain(SecurityIdentifier securityIdentifier, return domain; } - var status = SAMMethods.SamOpenDomain(Handle, requestedDomainAccess, securityIdentifier.GetBytes(), out var domainHandle); + var (status, domainHandle) = SAMMethods.SamOpenDomain(Handle, requestedDomainAccess, securityIdentifier.GetBytes()); if (status.IsError()) { - return Result.Fail(status.ToString()); + return status.ToString(); } domain = new SAMDomain(domainHandle); From 799c057f5a4bed1bdd1934b9420080a6e8cafc43 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Thu, 25 Aug 2022 12:16:39 -0400 Subject: [PATCH 09/26] feat: finish new URA processor + integrate RPC lib --- .../UserRightsAssignmentProcessor.cs | 46 +++++++++++-- src/SharpHoundRPC/LSANative/LSAMethods.cs | 28 ++++++-- src/SharpHoundRPC/Wrappers/LSAPolicy.cs | 65 ++++++++++--------- 3 files changed, 98 insertions(+), 41 deletions(-) diff --git a/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs b/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs index a589e2b3..f9a0e5a7 100644 --- a/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs +++ b/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging; using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.OutputTypes; +using SharpHoundRPC.Shared; using SharpHoundRPC.Wrappers; namespace SharpHoundCommonLib.Processors @@ -52,7 +53,7 @@ public IEnumerable GetUserRightsAssignments(strin Privilege = privilege }; - var enumerateAccountsResult = server.GetPrincipalsWithPrivilege(privilege); + var enumerateAccountsResult = server.GetResolvedPrincipalsWithPrivilege(privilege); if (enumerateAccountsResult.IsFailed) { SendComputerStatus(new CSVComputerStatus @@ -83,10 +84,11 @@ public IEnumerable GetUserRightsAssignments(strin var isDc = computerSid.IsEqualDomainSid(new SecurityIdentifier(machineSid)); var resolved = new List(); - var toResolve = new List(); + var names = new List(); - foreach (var sid in enumerateAccountsResult.Value) + foreach (var value in enumerateAccountsResult.Value) { + var (sid, name, use, domain) = value; if (IsSidFiltered(sid)) continue; @@ -108,21 +110,53 @@ public IEnumerable GetUserRightsAssignments(strin { if (machineSid == "UNKNOWN") continue; - common.ObjectIdentifier = $"{machineSid}-{sid.Rid()}"; - common.ObjectType = common.ObjectType switch + var convertedId = $"{machineSid}-{sid.Rid()}"; + names.Add(new NamedPrincipal + { + ObjectId = convertedId, + PrincipalName = common.ObjectIdentifier + }); + + var objectType = common.ObjectType switch { Label.User => Label.LocalUser, Label.Group => Label.LocalGroup, _ => common.ObjectType }; - resolved.Add(common); + resolved.Add(new TypedPrincipal + { + ObjectIdentifier = convertedId, + ObjectType = objectType + }); } else { + var objectType = use switch + { + SharedEnums.SidNameUse.User => Label.LocalUser, + SharedEnums.SidNameUse.Group => Label.LocalGroup, + SharedEnums.SidNameUse.Alias => Label.LocalGroup, + _ => Label.Base + }; + names.Add(new NamedPrincipal + { + ObjectId = sid.ToString(), + PrincipalName = name + }); + + resolved.Add(new TypedPrincipal + { + ObjectIdentifier = sid.ToString(), + ObjectType = objectType + }); } } } + + result.LocalNames = names.ToArray(); + result.Results = resolved.ToArray(); + yield return result; } } diff --git a/src/SharpHoundRPC/LSANative/LSAMethods.cs b/src/SharpHoundRPC/LSANative/LSAMethods.cs index 30b83ce0..f01602cc 100644 --- a/src/SharpHoundRPC/LSANative/LSAMethods.cs +++ b/src/SharpHoundRPC/LSANative/LSAMethods.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.InteropServices; using System.Security; using System.Security.Principal; @@ -12,7 +10,8 @@ namespace SharpHoundRPC.LSANative [SuppressUnmanagedCodeSecurity] public class LSAMethods { - internal static (NtStatus status, LSAHandle policyHandle) LsaOpenPolicy(string computerName, LSAEnums.LsaOpenMask desiredAccess) + internal static (NtStatus status, LSAHandle policyHandle) LsaOpenPolicy(string computerName, + LSAEnums.LsaOpenMask desiredAccess) { var us = new SharedStructs.UnicodeString(computerName); var objectAttributes = default(LSAStructs.ObjectAttributes); @@ -54,7 +53,8 @@ private static extern NtStatus LsaQueryInformationPolicy( out LSAPointer buffer ); - internal static (NtStatus status, LSAPointer sids, int count) LsaEnumerateAccountsWithUserRight(LSAHandle policyHandle, + internal static (NtStatus status, LSAPointer sids, int count) LsaEnumerateAccountsWithUserRight( + LSAHandle policyHandle, string userRight) { var arr = new SharedStructs.UnicodeString[1]; @@ -73,15 +73,22 @@ private static extern NtStatus LsaEnumerateAccountsWithUserRight( out int count ); + internal static (NtStatus status, LSAPointer referencedDomains, LSAPointer names, int count) + LsaLookupSids(LSAHandle policyHandle, + LSAPointer sids, int count) + { + var status = LsaLookupSids(policyHandle, count, sids, out var referencedDomains, out var names); + return (status, referencedDomains, names, count); + } + internal static (NtStatus status, LSAPointer referencedDomains, LSAPointer names, int count) LsaLookupSids(LSAHandle policyHandle, SecurityIdentifier[] sids) { - var count = sids.Length; if (count == 0) return (NtStatus.StatusInvalidParameter, null, null, 0); - + var gcHandles = new GCHandle[count]; var pSids = new IntPtr[count]; @@ -107,6 +114,15 @@ internal static (NtStatus status, LSAPointer referencedDomains, LSAPointer names } } + [DllImport("advapi32.dll")] + private static extern NtStatus LsaLookupSids( + LSAHandle policyHandle, + int count, + LSAPointer sidArray, + out LSAPointer referencedDomains, + out LSAPointer names + ); + [DllImport("advapi32.dll")] private static extern NtStatus LsaLookupSids( LSAHandle policyHandle, diff --git a/src/SharpHoundRPC/Wrappers/LSAPolicy.cs b/src/SharpHoundRPC/Wrappers/LSAPolicy.cs index 14888ba8..81be2148 100644 --- a/src/SharpHoundRPC/Wrappers/LSAPolicy.cs +++ b/src/SharpHoundRPC/Wrappers/LSAPolicy.cs @@ -20,10 +20,8 @@ public static Result OpenPolicy(string computerName, LSAEnums.LsaOpen LSAEnums.LsaOpenMask.LookupNames | LSAEnums.LsaOpenMask.ViewLocalInfo) { var (status, handle) = LSAMethods.LsaOpenPolicy(computerName, desiredAccess); - if (status.IsError()) - { - return status; - } + if (status.IsError()) return status; + return new LSAPolicy(computerName, handle); } @@ -32,10 +30,7 @@ public static Result OpenPolicy(string computerName, LSAEnums.LsaOpen var result = LSAMethods.LsaQueryInformationPolicy(Handle, LSAEnums.LSAPolicyInformation.PolicyAccountDomainInformation); - if (result.status.IsError()) - { - return result.status; - } + if (result.status.IsError()) return result.status; var domainInfo = result.pointer.GetData(); var domainSid = new SecurityIdentifier(domainInfo.DomainSid); @@ -45,25 +40,40 @@ public static Result OpenPolicy(string computerName, LSAEnums.LsaOpen public Result> GetPrincipalsWithPrivilege(string userRight) { var (status, sids, count) = LSAMethods.LsaEnumerateAccountsWithUserRight(Handle, userRight); - if (status.IsError()) - { - return status; - } - + if (status.IsError()) return status; + return Result>.Ok(sids.GetEnumerable(count)); } + public Result> + GetResolvedPrincipalsWithPrivilege(string userRight) + { + var (status, sids, count) = LSAMethods.LsaEnumerateAccountsWithUserRight(Handle, userRight); + if (status.IsError()) return status; + + var (lookupStatus, referencedDomains, names, lookupCount) = LSAMethods.LsaLookupSids(Handle, sids, count); + var translatedNames = names.GetEnumerable(count).ToArray(); + var domainList = referencedDomains.GetData(); + var safeDomains = new LSAPointer(domainList.Domains); + var domains = safeDomains.GetEnumerable(domainList.Entries).ToArray(); + var convertedSids = sids.GetEnumerable(count).ToArray(); + + var ret = new List<(SecurityIdentifier sid, string Name, SharedEnums.SidNameUse Use, string Domain)>(); + for (var i = 0; i < count; i++) + ret.Add((convertedSids[i], translatedNames[i].Name.ToString(), translatedNames[i].Use, + domains[translatedNames[i].DomainIndex].Name.ToString())); + + return ret; + } + public Result<(string Name, SharedEnums.SidNameUse Use, string Domains)> LookupSid(SecurityIdentifier sid) { if (sid == null) return "SID cannot be null"; var (status, referencedDomains, names, count) = LSAMethods.LsaLookupSids(Handle, new[] {sid}); - if (status.IsError()) - { - return status; - } - + if (status.IsError()) return status; + var translatedNames = names.GetEnumerable(count).ToArray(); var domainList = referencedDomains.GetData(); var safeDomains = new LSAPointer(domainList.Domains); @@ -72,19 +82,17 @@ public Result> GetPrincipalsWithPrivilege(string domains[translatedNames[0].DomainIndex].Name.ToString()); } - public Result> LookupSids( - SecurityIdentifier[] sids) + public Result> + LookupSids( + SecurityIdentifier[] sids) { sids = sids.Where(x => x != null).ToArray(); if (sids.Length == 0) return "No non-null SIDs specified"; - + var (status, referencedDomains, names, count) = LSAMethods.LsaLookupSids(Handle, sids); - if (status.IsError()) - { - return status; - } - + if (status.IsError()) return status; + var translatedNames = names.GetEnumerable(count).ToArray(); var domainList = referencedDomains.GetData(); var safeDomains = new LSAPointer(domainList.Domains); @@ -92,9 +100,8 @@ public Result> GetPrincipalsWithPrivilege(string var ret = new List<(SecurityIdentifier Sid, string Name, SharedEnums.SidNameUse Use, string Domain)>(); for (var i = 0; i < count; i++) - { - ret.Add((sids[i], translatedNames[i].Name.ToString(), translatedNames[i].Use, domains[translatedNames[i].DomainIndex].Name.ToString())); - } + ret.Add((sids[i], translatedNames[i].Name.ToString(), translatedNames[i].Use, + domains[translatedNames[i].DomainIndex].Name.ToString())); return ret.ToArray(); } From fd716fcb0d815ddfb7a3baefca66a858962eb7a6 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Fri, 26 Aug 2022 12:47:21 -0400 Subject: [PATCH 10/26] chore: add event delegate to SessionProcessor --- src/CommonLib/Processors/ComputerSessionProcessor.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/CommonLib/Processors/ComputerSessionProcessor.cs b/src/CommonLib/Processors/ComputerSessionProcessor.cs index 13b34a4b..862738f6 100644 --- a/src/CommonLib/Processors/ComputerSessionProcessor.cs +++ b/src/CommonLib/Processors/ComputerSessionProcessor.cs @@ -17,6 +17,9 @@ public class ComputerSessionProcessor private readonly ILogger _log; private readonly NativeMethods _nativeMethods; private readonly ILDAPUtils _utils; + + public delegate void ComputerStatusDelegate(CSVComputerStatus status); + public event ComputerStatusDelegate ComputerStatusEvent; public ComputerSessionProcessor(ILDAPUtils utils, string currentUserName = null, NativeMethods nativeMethods = null, ILogger log = null) @@ -236,5 +239,10 @@ public SessionAPIResult ReadUserSessionsRegistry(string computerName, string com key?.Dispose(); } } + + private void SendComputerStatus(CSVComputerStatus status) + { + ComputerStatusEvent?.Invoke(status); + } } } \ No newline at end of file From 1571e09a80b036a8c08bbc5daea9f7fcdccc3696 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Fri, 30 Sep 2022 12:00:22 -0400 Subject: [PATCH 11/26] chore: some small fixes --- src/CommonLib/Cache.cs | 19 ++++++++++--------- .../UserRightsAssignmentProcessor.cs | 2 +- src/SharpHoundRPC/Wrappers/LSAPolicy.cs | 6 +++++- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/CommonLib/Cache.cs b/src/CommonLib/Cache.cs index d9a4f2c5..bf311aeb 100644 --- a/src/CommonLib/Cache.cs +++ b/src/CommonLib/Cache.cs @@ -7,15 +7,16 @@ namespace SharpHoundCommonLib [DataContract] public class Cache { - [DataMember]private ConcurrentDictionary _globalCatalogCache; - - [DataMember]private ConcurrentDictionary _idToTypeCache; - - [DataMember]private ConcurrentDictionary _machineSidCache; - - [DataMember]private ConcurrentDictionary _sidToDomainCache; - - [DataMember]private ConcurrentDictionary _valueToIDCache; + //Leave these here until we switch back to Newtonsoft which doesn't suck + // [DataMember]private ConcurrentDictionary _globalCatalogCache; + // + // [DataMember]private ConcurrentDictionary _idToTypeCache; + // + // [DataMember]private ConcurrentDictionary _machineSidCache; + // + // [DataMember]private ConcurrentDictionary _sidToDomainCache; + // + // [DataMember]private ConcurrentDictionary _valueToIDCache; private Cache() { diff --git a/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs b/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs index f9a0e5a7..3ffbd1de 100644 --- a/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs +++ b/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs @@ -36,7 +36,7 @@ public IEnumerable GetUserRightsAssignments(strin { SendComputerStatus(new CSVComputerStatus { - Task = "SamConnect", + Task = "LSAOpenPolicy", ComputerName = computerName, Status = policyOpenResult.Status.ToString() }); diff --git a/src/SharpHoundRPC/Wrappers/LSAPolicy.cs b/src/SharpHoundRPC/Wrappers/LSAPolicy.cs index 81be2148..4a9c5bc2 100644 --- a/src/SharpHoundRPC/Wrappers/LSAPolicy.cs +++ b/src/SharpHoundRPC/Wrappers/LSAPolicy.cs @@ -52,11 +52,15 @@ public Result> GetPrincipalsWithPrivilege(string if (status.IsError()) return status; var (lookupStatus, referencedDomains, names, lookupCount) = LSAMethods.LsaLookupSids(Handle, sids, count); + if (lookupStatus.IsError()) + { + return lookupStatus; + } var translatedNames = names.GetEnumerable(count).ToArray(); var domainList = referencedDomains.GetData(); var safeDomains = new LSAPointer(domainList.Domains); var domains = safeDomains.GetEnumerable(domainList.Entries).ToArray(); - var convertedSids = sids.GetEnumerable(count).ToArray(); + var convertedSids = sids.GetEnumerable(lookupCount).ToArray(); var ret = new List<(SecurityIdentifier sid, string Name, SharedEnums.SidNameUse Use, string Domain)>(); for (var i = 0; i < count; i++) From 4559291383b1354f74f5ce955cee36ecfc94d17c Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Fri, 30 Sep 2022 12:08:49 -0400 Subject: [PATCH 12/26] chore: remove RPCServer and associated native methods in favor of RPC lib --- .../Exceptions/ComputerAPIException.cs | 25 + ...mmonException.cs => LDAPQueryException.cs} | 0 src/CommonLib/NativeMethods.cs | 504 +------- src/CommonLib/Processors/CachedLocalItem.cs | 16 + .../Processors/ComputerSessionProcessor.cs | 5 +- .../Processors/GPOLocalGroupProcessor.cs | 9 + src/CommonLib/Processors/RPCServer.cs | 1122 ----------------- test/unit/ComputerSessionProcessorTest.cs | 5 +- 8 files changed, 60 insertions(+), 1626 deletions(-) create mode 100644 src/CommonLib/Exceptions/ComputerAPIException.cs rename src/CommonLib/Exceptions/{SharpHoundCommonException.cs => LDAPQueryException.cs} (100%) create mode 100644 src/CommonLib/Processors/CachedLocalItem.cs delete mode 100644 src/CommonLib/Processors/RPCServer.cs diff --git a/src/CommonLib/Exceptions/ComputerAPIException.cs b/src/CommonLib/Exceptions/ComputerAPIException.cs new file mode 100644 index 00000000..bb719645 --- /dev/null +++ b/src/CommonLib/Exceptions/ComputerAPIException.cs @@ -0,0 +1,25 @@ +using System; + +namespace SharpHoundCommonLib.Exceptions +{ + public class ComputerAPIException : Exception + { + public ComputerAPIException() + { + } + + public ComputerAPIException(string apiCall, NativeMethods.NtStatus status) + { + Status = status.ToString(); + APICall = apiCall; + } + + public string Status { get; set; } + public string APICall { get; set; } + + public override string ToString() + { + return $"Call to {APICall} returned {Status}"; + } + } +} \ No newline at end of file diff --git a/src/CommonLib/Exceptions/SharpHoundCommonException.cs b/src/CommonLib/Exceptions/LDAPQueryException.cs similarity index 100% rename from src/CommonLib/Exceptions/SharpHoundCommonException.cs rename to src/CommonLib/Exceptions/LDAPQueryException.cs diff --git a/src/CommonLib/NativeMethods.cs b/src/CommonLib/NativeMethods.cs index 80e6a58e..4ff83437 100644 --- a/src/CommonLib/NativeMethods.cs +++ b/src/CommonLib/NativeMethods.cs @@ -5,6 +5,7 @@ using System.Security; using System.Security.Principal; using Microsoft.Extensions.Logging; +using SharpHoundCommonLib.Exceptions; using SharpHoundCommonLib.Processors; namespace SharpHoundCommonLib @@ -53,7 +54,7 @@ public virtual WorkstationInfo100 CallNetWkstaGetInfo(string serverName) { var result = NetWkstaGetInfo(serverName, NetWkstaGetInfoQueryLevel, out ptr); if (result != NERR.NERR_Success) - throw new APIException + throw new ComputerAPIException { Status = result.ToString(), APICall = NetWkstaGetInfoQueryName @@ -84,7 +85,7 @@ public virtual IEnumerable CallNetSessionEnum(string serverName _log.LogTrace("Result of NetSessionEnum for {ServerName} is {Result}", serverName, result); if (result != NERR.NERR_Success) - throw new APIException + throw new ComputerAPIException { APICall = NetSessionEnumQueryName, Status = result.ToString() @@ -120,7 +121,7 @@ public virtual IEnumerable CallNetWkstaUserEnum(string server _log.LogTrace("Result of NetWkstaUserEnum for computer {ServerName} is {Result}", servername, result); if (result != NERR.NERR_Success && result != NERR.ERROR_MORE_DATA) - throw new APIException + throw new ComputerAPIException { APICall = NetWkstaUserEnumQueryName, Status = result.ToString() @@ -160,163 +161,6 @@ public virtual IEnumerable CallNetWkstaUserEnum(string server } } - public virtual NtStatus CallSamConnect(ref UNICODE_STRING serverName, out IntPtr serverHandle, - SamAccessMasks desiredAccess, ref OBJECT_ATTRIBUTES objectAttributes) - { - return SamConnect(ref serverName, out serverHandle, desiredAccess, ref objectAttributes); - } - - internal virtual NtStatus CallSamOpenDomain(IntPtr serverHandle, DomainAccessMask desiredAccess, - byte[] domainSid, out IntPtr domainHandle) - { - return SamOpenDomain(serverHandle, desiredAccess, domainSid, out domainHandle); - } - - internal virtual NtStatus CallSamOpenAlias(IntPtr domainHandle, AliasOpenFlags desiredAccess, int aliasId, - out IntPtr aliasHandle) - { - return SamOpenAlias(domainHandle, desiredAccess, aliasId, out aliasHandle); - } - - internal virtual NtStatus CallSamGetMembersInAlias(IntPtr aliasHandle, out IntPtr members, out int count) - { - return SamGetMembersInAlias(aliasHandle, out members, out count); - } - - internal virtual NtStatus CallSamLookupDomainInSamServer(IntPtr serverHandle, ref UNICODE_STRING name, - out IntPtr sid) - { - return SamLookupDomainInSamServer(serverHandle, ref name, out sid); - } - - internal virtual NtStatus CallSamEnumerateAliasesInDomain(IntPtr domainHandle, out IntPtr rids, out int count) - { - var enumContext = 0; - return SamEnumerateAliasesInDomain(domainHandle, ref enumContext, out rids, -1, out count); - } - - - internal virtual NtStatus CallSamFreeMemory(IntPtr handle) - { - return SamFreeMemory(handle); - } - - internal virtual NtStatus CallSamCloseHandle(IntPtr handle) - { - return SamCloseHandle(handle); - } - - internal virtual NtStatus CallSamEnumerateDomainsInSamServer(IntPtr serverHandle, out IntPtr domains, - out int count) - { - var enumContext = 0; - return SamEnumerateDomainsInSamServer(serverHandle, ref enumContext, out domains, -1, out count); - } - - internal virtual NtStatus CallLSAOpenPolicy(ref LSA_UNICODE_STRING serverName, - ref LSA_OBJECT_ATTRIBUTES lsaObjectAttributes, - LsaOpenMask openMask, out IntPtr policyHandle) - { - return LsaOpenPolicy(ref serverName, ref lsaObjectAttributes, openMask, out policyHandle); - } - - internal virtual NtStatus CallLSAEnumerateAccountsWithUserRight(IntPtr policyHandle, string privilege, - out IntPtr buffer, - out int count) - { - var privileges = new LSA_UNICODE_STRING[1]; - privileges[0] = new LSA_UNICODE_STRING(privilege); - return LsaEnumerateAccountsWithUserRight(policyHandle, privileges, out buffer, out count); - } - - internal virtual NtStatus CallSamLookupIdsInDomain(IntPtr domainHandle, int[] rids, out IntPtr names, - out IntPtr use) - { - var count = rids.Length; - - return SamLookupIdsInDomain(domainHandle, count, rids, out names, out use); - } - - public virtual NtStatus CallLSAClose(IntPtr handle) - { - return LsaClose(handle); - } - - public virtual NtStatus CallLSAFreeMemory(IntPtr handle) - { - return LsaFreeMemory(handle); - } - - public virtual NtStatus CallLSALookupSids(IntPtr policyHandle, SecurityIdentifier[] sids, - out IntPtr referencedDomains, out IntPtr names) - { - var count = sids.Length; - var gcHandles = new GCHandle[count]; - var pSids = new IntPtr[count]; - - for (var i = 0; i < count; i++) - { - var sid = sids[i]; - var b = new byte[sid.BinaryLength]; - sid.GetBinaryForm(b, 0); - gcHandles[i] = GCHandle.Alloc(b, GCHandleType.Pinned); - pSids[i] = gcHandles[i].AddrOfPinnedObject(); - } - - try - { - return LsaLookupSids(policyHandle, (uint) count, pSids, out referencedDomains, out names); - } - finally - { - foreach (var handle in gcHandles) - if (handle.IsAllocated) - handle.Free(); - } - } - - public virtual NtStatus CallLSALookupSids2(IntPtr policyHandle, LsaLookupOptions lookupOptions, - SecurityIdentifier[] sids, out IntPtr referencedDomains, out IntPtr names) - { - var count = sids.Length; - var gcHandles = new GCHandle[count]; - var pSids = new IntPtr[count]; - - for (var i = 0; i < count; i++) - { - var sid = sids[i]; - var b = new byte[sid.BinaryLength]; - sid.GetBinaryForm(b, 0); - gcHandles[i] = GCHandle.Alloc(b, GCHandleType.Pinned); - pSids[i] = gcHandles[i].AddrOfPinnedObject(); - } - - try - { - return LsaLookupSids2(policyHandle, lookupOptions, (uint) count, pSids, out referencedDomains, - out names); - } - finally - { - foreach (var handle in gcHandles) - if (handle.IsAllocated) - handle.Free(); - } - } - - public virtual NtStatus CallLsaQueryInformationPolicy(IntPtr policyHandle, - LSAPolicyInformation policyInformation, out IntPtr buffer) - { - return LsaQueryInformationPolicy(policyHandle, policyInformation, out buffer); - } - - [StructLayout(LayoutKind.Sequential)] - public struct POLICY_ACCOUNT_DOMAIN_INFO - { - public LSA_UNICODE_STRING DomainName; - public IntPtr DomainSid; - } - public struct OBJECT_ATTRIBUTES : IDisposable { public void Dispose() @@ -366,169 +210,6 @@ public override string ToString() } } - #region SAMR Imports - - [DllImport("samlib.dll")] - private static extern NtStatus SamCloseHandle( - IntPtr handle - ); - - [DllImport("samlib.dll")] - private static extern NtStatus SamFreeMemory( - IntPtr handle - ); - - [DllImport("samlib.dll", CharSet = CharSet.Unicode)] - private static extern NtStatus SamLookupDomainInSamServer( - IntPtr serverHandle, - ref UNICODE_STRING name, - out IntPtr sid); - - [DllImport("samlib.dll", CharSet = CharSet.Unicode)] - private static extern NtStatus SamGetMembersInAlias( - IntPtr aliasHandle, - out IntPtr members, - out int count); - - [DllImport("samlib.dll", CharSet = CharSet.Unicode)] - private static extern NtStatus SamOpenAlias( - IntPtr domainHandle, - AliasOpenFlags desiredAccess, - int aliasId, - out IntPtr aliasHandle - ); - - [DllImport("samlib.dll", CharSet = CharSet.Unicode)] - private static extern NtStatus SamConnect( - ref UNICODE_STRING serverName, - out IntPtr serverHandle, - SamAccessMasks desiredAccess, - ref OBJECT_ATTRIBUTES objectAttributes - ); - - [DllImport("samlib.dll", CharSet = CharSet.Unicode)] - private static extern NtStatus SamOpenDomain( - IntPtr serverHandle, - DomainAccessMask desiredAccess, - [MarshalAs(UnmanagedType.LPArray)] byte[] domainSid, - out IntPtr domainHandle - ); - - [DllImport("samlib.dll", CharSet = CharSet.Unicode)] - private static extern NtStatus SamLookupIdsInDomain(IntPtr domainHandle, - int count, - int[] rids, - out IntPtr names, - out IntPtr use); - - [DllImport("samlib.dll", CharSet = CharSet.Unicode)] - private static extern NtStatus SamEnumerateAliasesInDomain( - IntPtr domainHandle, - ref int enumerationContext, - out IntPtr buffer, - int prefMaxLen, - out int count); - - [DllImport("samlib.dll", CharSet = CharSet.Unicode)] - private static extern NtStatus SamEnumerateDomainsInSamServer( - IntPtr serverHandle, - ref int enumerationContext, - out IntPtr buffer, - int prefMaxLen, - out int count); - - [Flags] - [SuppressMessage("ReSharper", "UnusedMember.Local")] - internal enum AliasOpenFlags - { - AddMember = 0x1, - RemoveMember = 0x2, - ListMembers = 0x4, - ReadInfo = 0x8, - WriteAccount = 0x10, - AllAccess = 0xf001f, - Read = 0x20004, - Write = 0x20013, - Execute = 0x20008 - } - - [Flags] - [SuppressMessage("ReSharper", "UnusedMember.Local")] - public enum DomainAccessMask - { - ReadPasswordParameters = 0x1, - WritePasswordParameters = 0x2, - ReadOtherParameters = 0x4, - WriteOtherParameters = 0x8, - CreateUser = 0x10, - CreateGroup = 0x20, - CreateAlias = 0x40, - GetAliasMembership = 0x80, - ListAccounts = 0x100, - Lookup = 0x200, - AdministerServer = 0x400, - AllAccess = 0xf07ff, - Read = 0x20084, - Write = 0x2047A, - Execute = 0x20301 - } - - [Flags] - [SuppressMessage("ReSharper", "UnusedMember.Local")] - internal enum SamAliasFlags - { - AddMembers = 0x1, - RemoveMembers = 0x2, - ListMembers = 0x4, - ReadInfo = 0x8, - WriteAccount = 0x10, - AllAccess = 0xf001f, - Read = 0x20004, - Write = 0x20013, - Execute = 0x20008 - } - - [Flags] - [SuppressMessage("ReSharper", "UnusedMember.Local")] - public enum SamAccessMasks - { - SamServerConnect = 0x1, - SamServerShutdown = 0x2, - SamServerInitialize = 0x4, - SamServerCreateDomains = 0x8, - SamServerEnumerateDomains = 0x10, - SamServerLookupDomain = 0x20, - SamServerAllAccess = 0xf003f, - SamServerRead = 0x20010, - SamServerWrite = 0x2000e, - SamServerExecute = 0x20021 - } - - [StructLayout(LayoutKind.Sequential)] - public struct SamRidEnumeration - { - internal static readonly int SizeOf = Marshal.SizeOf(); - public int Rid; - public UNICODE_STRING Name; - } - - public enum SidNameUse - { - User = 1, - Group, - Domain, - Alias, - WellKnownGroup, - DeletedAccount, - Invalid, - Unknown, - Computer, - Label, - LogonSession - } - - #endregion - #region Session Enum Imports [DllImport("NetAPI32.dll", SetLastError = true)] @@ -672,182 +353,5 @@ public struct DOMAIN_CONTROLLER_INFO } #endregion - - #region LSA Imports - - [DllImport("advapi32.dll")] - private static extern NtStatus LsaOpenPolicy( - ref LSA_UNICODE_STRING server, - ref LSA_OBJECT_ATTRIBUTES objectAttributes, - LsaOpenMask desiredAccess, - out IntPtr policyHandle - ); - - [DllImport("advapi32.dll")] - private static extern NtStatus LsaEnumerateAccountRights( - IntPtr policyHandle, - IntPtr accountSid, - IntPtr accountRights, - out int count); - - [DllImport("advapi32.dll")] - private static extern NtStatus LsaLookupSids( - IntPtr policyHandle, - uint count, - [MarshalAs(UnmanagedType.LPArray)] IntPtr[] sidArray, - out IntPtr referencedDomains, - out IntPtr names - ); - - [DllImport("advapi32.dll")] - private static extern NtStatus LsaLookupSids2( - IntPtr policyHandle, - LsaLookupOptions lookupOptions, - uint count, - [MarshalAs(UnmanagedType.LPArray)] IntPtr[] sidArray, - out IntPtr referencedDomains, - out IntPtr names - ); - - [DllImport("advapi32.dll")] - private static extern NtStatus LsaQueryInformationPolicy( - IntPtr policyHandle, LSAPolicyInformation policyInformation, out IntPtr buffer); - - [DllImport("advapi32.dll")] - private static extern NtStatus LsaClose( - IntPtr buffer - ); - - [DllImport("advapi32.dll")] - public static extern NtStatus LsaFreeMemory( - IntPtr buffer - ); - - [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)] - [SuppressUnmanagedCodeSecurity] - public static extern NtStatus LsaEnumerateAccountsWithUserRight( - IntPtr PolicyHandle, - LSA_UNICODE_STRING[] UserRights, - out IntPtr EnumerationBuffer, - out int CountReturned - ); - - [Flags] - public enum LsaLookupOptions : uint - { - ReturnLocalNames = 0, - PreferInternetNames = 0x40000000, - DisallowsConnectedAccountInternetSid = 0x80000000 - } - - [Flags] - public enum LSAPolicyInformation - { - PolicyAuditLogInformation = 1, - PolicyAuditEventsInformation, - PolicyPrimaryDomainInformation, - PolicyPdAccountInformation, - PolicyAccountDomainInformation, - PolicyLsaServerRoleInformation, - PolicyReplicaSourceInformation, - PolicyDefaultQuotaInformation, - PolicyModificationInformation, - PolicyAuditFullSetInformation, - PolicyAuditFullQueryInformation, - PolicyDnsDomainInformation - } - - [Flags] - [SuppressMessage("ReSharper", "UnusedMember.Local")] - public enum LsaOpenMask - { - ViewLocalInfo = 0x1, - ViewAuditInfo = 0x2, - GetPrivateInfo = 0x4, - TrustAdmin = 0x8, - CreateAccount = 0x10, - CreateSecret = 0x20, - CreatePrivilege = 0x40, - SetDefaultQuotaLimits = 0x80, - SetAuditRequirements = 0x100, - AuditLogAdmin = 0x200, - ServerAdmin = 0x400, - LookupNames = 0x800, - Notification = 0x1000, - POLICY_READ = 0x20006, - POLICY_ALL_ACCESS = 0x00F0FFF, - POLICY_EXECUTE = 0X20801, - POLICY_WRITE = 0X207F8 - } - - [StructLayout(LayoutKind.Sequential)] - public struct LSA_UNICODE_STRING : IDisposable - { - private readonly ushort Length; - private readonly ushort MaximumLength; - private IntPtr Buffer; - - public LSA_UNICODE_STRING(string s) - : this() - { - if (s == null) return; - Length = (ushort) (s.Length * 2); - MaximumLength = (ushort) (Length + 2); - Buffer = Marshal.StringToHGlobalUni(s); - } - - public void Dispose() - { - if (Buffer == IntPtr.Zero) return; - Marshal.FreeHGlobal(Buffer); - Buffer = IntPtr.Zero; - } - - public override string ToString() - { - return (Buffer != IntPtr.Zero ? Marshal.PtrToStringUni(Buffer, Length / 2) : null) ?? - throw new InvalidOperationException(); - } - } - - [StructLayout(LayoutKind.Sequential)] - public struct LSA_OBJECT_ATTRIBUTES - { - public int Length; - public IntPtr RootDirectory; - public IntPtr ObjectName; - public int Attributes; - public IntPtr SecurityDescriptor; - public IntPtr SecurityQualityOfService; - - public void Dispose() - { - if (ObjectName == IntPtr.Zero) return; - Marshal.DestroyStructure(ObjectName, typeof(LSA_UNICODE_STRING)); - Marshal.FreeHGlobal(ObjectName); - ObjectName = IntPtr.Zero; - } - } - - [StructLayout(LayoutKind.Sequential)] - public struct LSATranslatedNames - { - public SidNameUse Use; - public LSA_UNICODE_STRING Name; - public int DomainIndex; - } - - public class SidPointer - { - private readonly byte[] data; - - public SidPointer(SecurityIdentifier identifier) - { - data = new byte[identifier.BinaryLength]; - identifier.GetBinaryForm(data, 0); - } - } - - #endregion } } \ No newline at end of file diff --git a/src/CommonLib/Processors/CachedLocalItem.cs b/src/CommonLib/Processors/CachedLocalItem.cs new file mode 100644 index 00000000..7cc91500 --- /dev/null +++ b/src/CommonLib/Processors/CachedLocalItem.cs @@ -0,0 +1,16 @@ +using SharpHoundCommonLib.Enums; + +namespace SharpHoundCommonLib.Processors +{ + internal class CachedLocalItem + { + public CachedLocalItem(string name, Label type) + { + Name = name; + Type = type; + } + + public string Name { get; set; } + public Label Type { get; set; } + } +} \ No newline at end of file diff --git a/src/CommonLib/Processors/ComputerSessionProcessor.cs b/src/CommonLib/Processors/ComputerSessionProcessor.cs index 862738f6..1c69da23 100644 --- a/src/CommonLib/Processors/ComputerSessionProcessor.cs +++ b/src/CommonLib/Processors/ComputerSessionProcessor.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Win32; +using SharpHoundCommonLib.Exceptions; using SharpHoundCommonLib.OutputTypes; namespace SharpHoundCommonLib.Processors @@ -48,7 +49,7 @@ public async Task ReadUserSessions(string computerName, string { apiResult = _nativeMethods.CallNetSessionEnum(computerName).ToArray(); } - catch (APIException e) + catch (ComputerAPIException e) { _log.LogDebug("NetSessionEnum failed on {ComputerName}: {Status}", computerName, e.Status); ret.Collected = false; @@ -144,7 +145,7 @@ public SessionAPIResult ReadUserSessionsPrivileged(string computerName, { apiResult = _nativeMethods.CallNetWkstaUserEnum(computerName).ToArray(); } - catch (APIException e) + catch (ComputerAPIException e) { _log.LogTrace("NetWkstaUserEnum failed on {ComputerName}: {Status}", computerName, e.Status); ret.Collected = false; diff --git a/src/CommonLib/Processors/GPOLocalGroupProcessor.cs b/src/CommonLib/Processors/GPOLocalGroupProcessor.cs index c51dc3c8..9ebe74b4 100644 --- a/src/CommonLib/Processors/GPOLocalGroupProcessor.cs +++ b/src/CommonLib/Processors/GPOLocalGroupProcessor.cs @@ -604,5 +604,14 @@ internal enum GroupActionTarget RestrictedMember, LocalGroup } + + internal enum LocalGroupRids + { + None = 0, + Administrators = 544, + RemoteDesktopUsers = 555, + DcomUsers = 562, + PSRemote = 580 + } } } \ No newline at end of file diff --git a/src/CommonLib/Processors/RPCServer.cs b/src/CommonLib/Processors/RPCServer.cs deleted file mode 100644 index 89c600ea..00000000 --- a/src/CommonLib/Processors/RPCServer.cs +++ /dev/null @@ -1,1122 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Security.Principal; -using System.Threading; -using Microsoft.Extensions.Logging; -using SharpHoundCommonLib.Enums; -using SharpHoundCommonLib.OutputTypes; - -namespace SharpHoundCommonLib.Processors -{ - public class RPCServer : IDisposable - { - private const string DummyMachineSid = "DUMMYSTRING"; - - private static readonly Lazy WellKnownSidBytes = new(() => - { - var sid = new SecurityIdentifier("S-1-5-32"); - var sidBytes = new byte[sid.BinaryLength]; - sid.GetBinaryForm(sidBytes, 0); - return sidBytes; - }, LazyThreadSafetyMode.PublicationOnly); - - private readonly string _computerDomain; - private readonly string _computerDomainSid; - - private readonly string _computerName; - private readonly string _computerSAMAccountName; - private readonly SecurityIdentifier _computerSID; - - private readonly DomainHandleManager _domainHandleManager; - - private readonly string[] _filteredSids = - { - "S-1-5-2", "S-1-5-2", "S-1-5-3", "S-1-5-4", "S-1-5-6", "S-1-5-7", "S-1-2", "S-1-2-0", "S-1-5-18", - "S-1-5-19", "S-1-5-20" - }; - - private readonly ILogger _log; - - private readonly NativeMethods _nativeMethods; - private readonly ConcurrentDictionary _typeCache = new(); - private readonly ILDAPUtils _utils; - private string _cachedMachineSid; - private NativeMethods.LSA_OBJECT_ATTRIBUTES _lsaObj; - private IntPtr _lsaPolicyHandle; - - private bool _lsaServerOpen; - private NativeMethods.OBJECT_ATTRIBUTES _obj; - private IntPtr _samServerHandle; - private bool _samServerOpen; - - /// - /// Creates an instance of an RPCServer which is used for making SharpHound specific SAMRPC/LSA API calls for - /// computers. - /// OpenSAMServer should be called before any other operations - /// - /// The name of the computer to connect too. This should be the network name of the computer - /// The samaccountname of the computer - /// The security identifier for the computer - /// The domain of the computer - /// LDAPUtils instance - /// NativeMethods instance - /// ILogger instance - public RPCServer(string computerName, string samAccountName, string computerSid, string computerDomain, - ILDAPUtils utils = null, - NativeMethods methods = null, ILogger log = null) - { - //Remove the trailing '$' from the SAMAccountName - _computerSAMAccountName = samAccountName.Remove(samAccountName.Length - 1, 1); - _computerSID = new SecurityIdentifier(computerSid); - _computerDomainSid = _computerSID.AccountDomainSid.Value; - _computerName = computerName; - _computerDomain = computerDomain; - _utils = utils; - _nativeMethods = methods ?? new NativeMethods(); - _utils = utils ?? new LDAPUtils(); - _domainHandleManager = new DomainHandleManager(_nativeMethods); - _log = log ?? Logging.LogProvider.CreateLogger("RPCServer"); - } - - public void Dispose() - { - if (_samServerHandle != IntPtr.Zero) - { - _nativeMethods.CallSamCloseHandle(_samServerHandle); - _samServerHandle = IntPtr.Zero; - } - - if (_lsaPolicyHandle != IntPtr.Zero) - { - _nativeMethods.CallLSAClose(_lsaPolicyHandle); - _lsaPolicyHandle = IntPtr.Zero; - } - - _domainHandleManager.Dispose(); - - _obj.Dispose(); - _lsaObj.Dispose(); - } - - /// - /// Opens the SAMRPC server for further use. This needs to be called before any other operations. - /// Refer to https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/87bacbd0-7b8b-429f-abc6-4b3d895d4e90 - /// for access masks - /// - /// - /// - /// - /// An exception indicates that we failed to open a connection, as well as the reason - /// - public void OpenSAMServer( - NativeMethods.SamAccessMasks requestedConnectAccess = NativeMethods.SamAccessMasks.SamServerConnect | - NativeMethods.SamAccessMasks - .SamServerEnumerateDomains | - NativeMethods.SamAccessMasks.SamServerLookupDomain) - { - _log.LogTrace("Opening SAM Server for {ComputerName}", _computerName); - - var us = new NativeMethods.UNICODE_STRING(_computerName); - //Every API call we make relies on both SamConnect - //Make this call immediately and save the handle. If either fails, nothing else is going to work - var status = _nativeMethods.CallSamConnect(ref us, out _samServerHandle, - requestedConnectAccess, - ref _obj); - _log.LogTrace("SamConnect returned {Status} for {ComputerName}", status, _computerName); - if (status != NativeMethods.NtStatus.StatusSuccess) - { - _nativeMethods.CallSamCloseHandle(_samServerHandle); - throw new APIException - { - Status = status.ToString(), - APICall = "SamConnect" - }; - } - - _samServerOpen = true; - } - - /// - /// Opens the LSA policy for further use. - /// - /// - /// - /// An exception indicates a failure to open the LSA policy - /// - public void OpenLSAServer( - NativeMethods.LsaOpenMask desiredAccess = - NativeMethods.LsaOpenMask.LookupNames | NativeMethods.LsaOpenMask.ViewLocalInfo) - { - var us = new NativeMethods.LSA_UNICODE_STRING(_computerName); - var status = _nativeMethods.CallLSAOpenPolicy(ref us, ref _lsaObj, desiredAccess, - out _lsaPolicyHandle); - _log.LogTrace($"LSAOpenPolicy returned {status} for {_computerName}"); - if (status != NativeMethods.NtStatus.StatusSuccess) - throw new APIException - { - Status = status.ToString(), - APICall = "LSAOpenPolicy" - }; - - _lsaServerOpen = true; - } - - /// - /// Gets principals with specified User Rights. If no rights are provided, defaults to the list in - /// LSAPrivileges.DesiredPrivileges - /// - /// - /// - public IEnumerable GetUserRightsAssignments(string[] desiredPrivileges = null) - { - if (!_lsaServerOpen) OpenLSAServer(); - - desiredPrivileges ??= LSAPrivileges.DesiredPrivileges; - - foreach (var privilege in desiredPrivileges) - { - var result = new UserRightsAssignmentAPIResult - { - Collected = false, - Privilege = privilege - }; - - var status = _nativeMethods.CallLSAEnumerateAccountsWithUserRight(_lsaPolicyHandle, privilege, - out var buffer, out var count); - - if (status != NativeMethods.NtStatus.StatusSuccess) - { - if (buffer != IntPtr.Zero) _nativeMethods.CallLSAFreeMemory(buffer); - - result.FailureReason = $"LSAEnumerateAccountsWithUserRight returned {status}"; - yield return result; - } - - result.Collected = true; - - var sids = new List(); - for (var i = 0; i < count; i++) - try - { - var raw = Marshal.ReadIntPtr(buffer, Marshal.SizeOf() * i); - var sid = new SecurityIdentifier(raw); - _log.LogTrace("Got sid {sid} for {ura}", sid.Value, privilege); - sids.Add(sid); - } - catch (Exception e) - { - _log.LogTrace(e, "Exception converting sid"); - } - - GetMachineSidLSA(out var machineSid); - - var resolved = new List(); - var toResolve = new List(); - var isDc = IsDomainController(); - foreach (var sid in sids) - { - var value = sid.Value; - - if (value.StartsWith("S-1-5-80") || value.StartsWith("S-1-5-82") || - value.StartsWith("S-1-5-90") || value.StartsWith("S-1-5-96")) continue; - - if (_filteredSids.Contains(value)) continue; - - if (isDc) - { - if (_utils.GetWellKnownPrincipal(sid.Value, _computerDomain, out var principal)) - { - _log.LogInformation("Found WKP {sid} on DC", sid.Value); - resolved.Add(principal); - } - else - { - var res = _utils.ResolveIDAndType(sid.Value, _computerDomain); - _log.LogInformation("Resolved {sid} on DC", res.ObjectIdentifier); - resolved.Add(res); - } - } - else - { - if (WellKnownPrincipal.GetWellKnownPrincipal(sid.Value, out var common)) - { - common.ObjectIdentifier = $"{machineSid}-{sid.Rid()}"; - if (common.ObjectType == Label.User) - common.ObjectType = Label.LocalUser; - else if (common.ObjectType == Label.Group) common.ObjectType = Label.LocalGroup; - resolved.Add(common); - } - else - { - toResolve.Add(sid); - } - } - } - - if (toResolve.Count == 0) - { - result.Results = resolved.ToArray(); - yield return result; - } - - var resolvedNames = new List(); - status = _nativeMethods.CallLSALookupSids(_lsaPolicyHandle, toResolve.ToArray(), out var domains, - out var names); - - if (status != NativeMethods.NtStatus.StatusSuccess && status != NativeMethods.NtStatus.StatusSomeMapped) - { - _log.LogError("LSALookupSids returned {status} for {computer}, unable to resolve local sids", - status, _computerName); - resolved.AddRange(toResolve.Select(x => new TypedPrincipal(x.Value, Label.Base))); - result.Results = resolved.ToArray(); - yield return result; - } - - for (var i = 0; i < toResolve.Count; i++) - { - var translated = Marshal.PtrToStructure(names + - Marshal.SizeOf() * i); - - Label objectType; - switch (translated.Use) - { - case NativeMethods.SidNameUse.User: - objectType = Label.LocalUser; - break; - case NativeMethods.SidNameUse.Group: - objectType = Label.LocalGroup; - break; - case NativeMethods.SidNameUse.Alias: - objectType = Label.LocalGroup; - break; - case NativeMethods.SidNameUse.Domain: - case NativeMethods.SidNameUse.WellKnownGroup: - case NativeMethods.SidNameUse.DeletedAccount: - case NativeMethods.SidNameUse.Invalid: - case NativeMethods.SidNameUse.Unknown: - case NativeMethods.SidNameUse.Computer: - case NativeMethods.SidNameUse.Label: - case NativeMethods.SidNameUse.LogonSession: - default: - objectType = Label.Base; - break; - } - - resolved.Add(new TypedPrincipal(toResolve[i].Value, objectType)); - try - { - resolvedNames.Add(new NamedPrincipal(translated.Name.ToString(), toResolve[i].Value)); - } - catch - { - } - } - - _nativeMethods.CallLSAFreeMemory(names); - _nativeMethods.CallLSAFreeMemory(domains); - - result.Results = resolved.ToArray(); - result.LocalNames = resolvedNames.ToArray(); - - yield return result; - } - } - - private bool IsMachineAccount(SecurityIdentifier sid) - { - var stringSid = sid.Value; - return IsMachineAccount(stringSid); - } - - private bool IsDomainController() - { - string machineSid; - if (_lsaServerOpen) - { - GetMachineSidLSA(out machineSid); - } - else if (_samServerOpen) - { - GetMachineSid(out machineSid); - } - else - { - OpenSAMServer(); - GetMachineSid(out machineSid); - } - - return machineSid == _computerDomainSid; - } - - private bool IsMachineAccount(string sid) - { - if (sid.StartsWith(_computerDomainSid)) - return false; - - string machineSid; - - if (_lsaServerOpen) - { - GetMachineSidLSA(out machineSid); - } - else if (_samServerOpen) - { - GetMachineSid(out machineSid); - } - else - { - OpenSAMServer(); - GetMachineSid(out machineSid); - } - - if (machineSid.StartsWith(_computerDomainSid)) - return false; - - return true; - } - - /// - /// Gets all local groups and their members - /// - /// - public IEnumerable GetGroupsAndMembers() - { - //First open the SAM server if it hasn't already been - if (!_samServerOpen) OpenSAMServer(); - - var groupCache = new ConcurrentBag(); - - GetMachineSid(out var machineSid); - - foreach (var domainName in ListDomainsInServer()) - if (OpenDomainHandle(domainName, out var domainHandle)) - foreach (var group in GetGroupsFromDomain(domainHandle, - domainName.Equals("builtin", StringComparison.OrdinalIgnoreCase))) - { - _typeCache.TryAdd(group.ObjectID, new CachedLocalItem(group.Name, Label.LocalGroup)); - var result = GetLocalGroupMembers(domainHandle, group); - groupCache.Add(result); - } - - _log.LogTrace("Beginning resolution of local types"); - // We've got all our local groups, now we need to resolve unresolved types - foreach (var group in groupCache) - { - if (!group.Collected) - continue; - - var names = new List(); - var resolvedPrincipals = new List(); - - foreach (var result in group.Results) - { - if (result.ObjectType != Label.Base || !result.ObjectIdentifier.StartsWith(machineSid) || - result.ObjectIdentifier.StartsWith(_computerSID.AccountDomainSid.Value)) - { - resolvedPrincipals.Add(result); - continue; - } - - var sid = new SecurityIdentifier(result.ObjectIdentifier); - _log.LogTrace("Resolving {Sid} in local", sid.Value); - if (!ResolveLocalSid(sid, out var name, out var type)) - { - resolvedPrincipals.Add(result); - continue; - } - - names.Add(new NamedPrincipal(name, result.ObjectIdentifier)); - resolvedPrincipals.Add(new TypedPrincipal(result.ObjectIdentifier, type)); - } - - group.Results = resolvedPrincipals.ToArray(); - group.LocalNames = names.ToArray(); - - yield return group; - } - } - - /// - /// Opens a domain handle in the SAM server - /// - /// - /// - /// - /// - public bool OpenDomainHandle(string domainName, out IntPtr domainHandle, - NativeMethods.DomainAccessMask requestedDomainAccess = NativeMethods.DomainAccessMask.Lookup | - NativeMethods.DomainAccessMask.ListAccounts) - { - if (_domainHandleManager.GetHandleByName(domainName, out domainHandle)) - return true; - - domainHandle = IntPtr.Zero; - if (!LookupDomainSid(domainName, out var sid)) return false; - - var bytes = new byte[sid.BinaryLength]; - sid.GetBinaryForm(bytes, 0); - var status = - _nativeMethods.CallSamOpenDomain(_samServerHandle, requestedDomainAccess, bytes, out domainHandle); - if (status != NativeMethods.NtStatus.StatusSuccess) - { - if (domainHandle != IntPtr.Zero) - _nativeMethods.CallSamFreeMemory(domainHandle); - return false; - } - - _log.LogTrace("Adding domain to manager: {domain}, {sid}", domainName, sid.ToString()); - _domainHandleManager.AddMappedDomain(sid, domainName, domainHandle); - - return true; - } - - /// - /// Opens the built in domain - /// - /// - /// - /// - public bool OpenBuiltInDomain(out IntPtr domainHandle, - NativeMethods.DomainAccessMask requestedDomainAccess = NativeMethods.DomainAccessMask.Lookup | - NativeMethods.DomainAccessMask.ListAccounts) - { - if (_domainHandleManager.GetHandleByName("BUILTIN", out domainHandle)) - return true; - - var status = - _nativeMethods.CallSamOpenDomain(_samServerHandle, requestedDomainAccess, WellKnownSidBytes.Value, - out domainHandle); - if (status != NativeMethods.NtStatus.StatusSuccess) - { - if (domainHandle != IntPtr.Zero) - _nativeMethods.CallSamFreeMemory(domainHandle); - return false; - } - - _domainHandleManager.AddMappedDomain("S-1-5-32", "BUILTIN", domainHandle); - - return true; - } - - ~RPCServer() - { - Dispose(); - } - - /// - /// Gets the list of aliases in a domain - /// - /// - /// - /// - public IEnumerable GetGroupsFromDomain(IntPtr domainHandle, bool isBuiltIn) - { - if (!_samServerOpen) - { - _log.LogError("SAM Server is not open. Call OpenSamServer before calling GetLocalGroups"); - yield break; - } - - if (!GetMachineSid(out var machineSid)) - { - _log.LogError("Failed to get machine sid for {ComputerName}, cannot get local groups", _computerName); - yield break; - } - - var status = _nativeMethods.CallSamEnumerateAliasesInDomain(domainHandle, out var rids, out var count); - _log.LogTrace("SamEnumerateAliasesInDomain returned {Status} on {ComputerName}", status, - _computerName); - if (status != NativeMethods.NtStatus.StatusSuccess) - throw new APIException("SamEnumerateAliasesInDomain", status); - - if (count == 0) - yield break; - - for (var i = 0; i < count; i++) - { - var data = Marshal.PtrToStructure(rids + - i * NativeMethods.SamRidEnumeration.SizeOf); - _log.LogTrace("Got entry {Name} with RID {Rid}", data.Name, data.Rid); - LocalGroup group; - if (IsDomainController()) - { - if (isBuiltIn) - { - var sid = $"S-1-5-32-{data.Rid}"; - _utils.GetWellKnownPrincipal(sid, _computerDomain, out var principal); - group = new LocalGroup - { - ObjectID = principal.ObjectIdentifier, - Rid = data.Rid, - Name = "IGNOREME" - }; - } - else - { - group = new LocalGroup - { - Name = "IGNOREME", - Rid = data.Rid, - ObjectID = $"{machineSid}-{data.Rid}" - }; - } - } - else - { - group = new LocalGroup - { - Name = $"{data.Name.ToString()}@{_computerName}".ToUpper(), - Rid = data.Rid, - ObjectID = $"{machineSid}-{data.Rid}" - }; - } - - yield return group; - } - } - - /// - /// Lists the domains in the SAM Server - /// - /// - /// - public IEnumerable ListDomainsInServer() - { - if (!_samServerOpen) - { - _log.LogError("SAM Server is not open. Call OpenSamServer before calling ListDomainsInServer"); - yield break; - } - - var domains = IntPtr.Zero; - try - { - var status = - _nativeMethods.CallSamEnumerateDomainsInSamServer(_samServerHandle, out domains, out var count); - _log.LogTrace("SamEnumerateDomainsInSamServer returned {Status} on {ComputerName}", status, - _computerName); - if (status != NativeMethods.NtStatus.StatusSuccess || count == 0) - throw new APIException - { - Status = status.ToString(), - APICall = "SamEnumerateDomainsInSamServer" - }; - - for (var i = 0; i < count; i++) - { - var data = Marshal.PtrToStructure(domains + - i * NativeMethods.SamRidEnumeration.SizeOf); - yield return data.Name.ToString(); - } - } - finally - { - if (domains != IntPtr.Zero) - _nativeMethods.CallSamFreeMemory(domains); - } - } - - - /// - /// Reads the members in a specified local group from the open domain. The group is referenced by its RID (Relative - /// Identifier). - /// Groups current used by SharpHound can be found in LocalGroupRids - /// - /// - /// - /// - public LocalGroupAPIResult GetLocalGroupMembers(IntPtr domainHandle, LocalGroup group) - { - if (!_samServerOpen) - { - _log.LogError("SAM Server is not open. Call OpenSamServer before calling GetLocalGroupMembers"); - throw new ServerNotOpenException("SAM Server is not open"); - } - - var result = new LocalGroupAPIResult - { - GroupRID = group.Rid, - ObjectIdentifier = group.ObjectID, - Name = group.Name - }; - - var status = _nativeMethods.CallSamOpenAlias(domainHandle, NativeMethods.AliasOpenFlags.ListMembers, - group.Rid, out var aliasHandle); - _log.LogTrace("SamOpenAlias returned {Status} for RID {GroupRID} on {ComputerName}", status, group.Rid, - _computerName); - if (status != NativeMethods.NtStatus.StatusSuccess) - { - _nativeMethods.CallSamCloseHandle(aliasHandle); - result.FailureReason = $"SamOpenAlias returned {status.ToString()}"; - return result; - } - - status = _nativeMethods.CallSamGetMembersInAlias(aliasHandle, out var members, out var count); - _log.LogTrace("SamGetMembersInAlias returned {Status} for RID {GroupRID} on {ComputerName}", status, - group.Rid, _computerName); - _nativeMethods.CallSamCloseHandle(aliasHandle); - - if (status != NativeMethods.NtStatus.StatusSuccess) - { - _nativeMethods.CallSamFreeMemory(members); - result.FailureReason = $"SamGetMembersInAlias returned {status.ToString()}"; - return result; - } - - _log.LogTrace("SamGetMembersInAlias returned {Count} items for RID {GroupRID} on {ComputerName}", count, - group.Rid, _computerName); - - if (count == 0) - { - _nativeMethods.CallSamFreeMemory(members); - result.Collected = true; - return result; - } - - var sids = new List(); - for (var i = 0; i < count; i++) - try - { - var raw = Marshal.ReadIntPtr(members, Marshal.SizeOf() * i); - var sid = new SecurityIdentifier(raw); - var value = sid.Value; - - //Filter out sids we dont care about/explicitly ignore - if (value.StartsWith("S-1-5-80") || value.StartsWith("S-1-5-82") || - value.StartsWith("S-1-5-90") || value.StartsWith("S-1-5-96")) continue; - - if (_filteredSids.Contains(value)) continue; - - sids.Add(sid); - } - catch (Exception e) - { - _log.LogTrace(e, "Exception converting sid"); - } - - _nativeMethods.CallSamFreeMemory(members); - - GetMachineSid(out var machineSid); - - var isDc = IsDomainController(); - - var converted = sids.Select(x => - { - var sid = x.Value.ToUpper(); - if (isDc || !sid.StartsWith(machineSid) || sid.StartsWith(_computerSID.AccountDomainSid.Value)) - { - _log.LogTrace("Resolving {Sid} in domain", sid); - return _utils.ResolveIDAndType(sid, _computerDomain); - } - - return new TypedPrincipal - { - ObjectIdentifier = sid, - ObjectType = Label.Base - }; - }).Where(x => x != null); - - result.Collected = true; - result.Results = converted.ToArray(); - - return result; - } - - private bool ResolveLocalSid(SecurityIdentifier identifier, out string name, out Label objectType) - { - if (_typeCache.TryGetValue(identifier.Value, out var item)) - { - _log.LogTrace("ResolveLocalSid - Cache hit for {ID}", identifier.Value); - name = item.Name; - objectType = item.Type; - return true; - } - - var domainSid = identifier.AccountDomainSid.Value; - var rid = identifier.Rid(); - - _log.LogTrace("ResolveLocalSid - Starting resolution for {ID} in domain {Domain} with RID {rid}", - identifier.Value, domainSid, rid); - - name = null; - objectType = Label.Base; - - if (!_domainHandleManager.GetHandleBySid(domainSid, out var handle)) - { - _log.LogTrace("ResolveLocalSid - Failed to get handle for {SID}", domainSid); - return false; - } - - var ridArray = new int[1]; - ridArray[0] = rid; - var status = - _nativeMethods.CallSamLookupIdsInDomain(handle, ridArray, out var names, out var use); - try - { - if (status != NativeMethods.NtStatus.StatusSuccess) - { - _log.LogTrace("SamLookupIdsInDomain returned {Status} for {Rid} in domain {Domain}", status, rid, - domainSid); - name = null; - objectType = Label.Base; - return false; - } - - var convertedName = Marshal.PtrToStructure(names); - name = convertedName.ToString(); - - var snu = (NativeMethods.SidNameUse) Marshal.ReadInt32(use, 0); - - switch (snu) - { - case NativeMethods.SidNameUse.User: - objectType = Label.LocalUser; - break; - case NativeMethods.SidNameUse.Group: - objectType = Label.LocalGroup; - break; - case NativeMethods.SidNameUse.Alias: - objectType = Label.LocalGroup; - break; - case NativeMethods.SidNameUse.Domain: - case NativeMethods.SidNameUse.WellKnownGroup: - case NativeMethods.SidNameUse.DeletedAccount: - case NativeMethods.SidNameUse.Invalid: - case NativeMethods.SidNameUse.Unknown: - case NativeMethods.SidNameUse.Computer: - case NativeMethods.SidNameUse.Label: - case NativeMethods.SidNameUse.LogonSession: - default: - objectType = Label.Base; - break; - } - - return true; - } - finally - { - if (names != IntPtr.Zero) _nativeMethods.CallSamFreeMemory(names); - - if (use != IntPtr.Zero) _nativeMethods.CallSamFreeMemory(use); - } - } - - /// - /// Gets the machine SID using LSA calls - /// - /// - /// - public bool GetMachineSidLSA(out string machineSid) - { - if (Cache.GetMachineSid(_computerSID.Value, out machineSid)) - return true; - - if (_cachedMachineSid != null) - { - machineSid = _cachedMachineSid; - return true; - } - - if (!_lsaServerOpen) OpenLSAServer(); - - var status = _nativeMethods.CallLsaQueryInformationPolicy(_lsaPolicyHandle, - NativeMethods.LSAPolicyInformation.PolicyAccountDomainInformation, out var buffer); - if (status != NativeMethods.NtStatus.StatusSuccess) - { - machineSid = DummyMachineSid; - return false; - } - - var data = Marshal.PtrToStructure(buffer); - _nativeMethods.CallLSAFreeMemory(buffer); - - var sid = new SecurityIdentifier(data.DomainSid); - machineSid = sid.Value; - _cachedMachineSid = machineSid; - Cache.AddMachineSid(_computerSID.Value, machineSid); - return true; - } - - /// - /// Uses API calls and caching to attempt to get the local SID of a computer. - /// The local SID of a computer will not match its domain SID, and is used to denote local machine accounts - /// - /// - public bool GetMachineSid(out string machineSid) - { - if (_cachedMachineSid != null) - { - machineSid = _cachedMachineSid; - return machineSid != DummyMachineSid; - } - - if (Cache.GetMachineSid(_computerSID.Value, out machineSid)) - { - _cachedMachineSid = machineSid; - return true; - } - - if (LookupDomainSid(_computerSAMAccountName, out var tempSid)) - { - machineSid = tempSid.Value; - Cache.AddMachineSid(_computerSID.Value, tempSid.Value); - _cachedMachineSid = tempSid.Value; - return true; - } - - var domain = ListDomainsInServer().DefaultIfEmpty(null).FirstOrDefault(); - if (domain != null && LookupDomainSid(domain, out tempSid)) - { - machineSid = tempSid.Value; - Cache.AddMachineSid(_computerSID.Value, tempSid.Value); - _cachedMachineSid = tempSid.Value; - return true; - } - - machineSid = DummyMachineSid; - _cachedMachineSid = machineSid; - - if (!OpenBuiltInDomain(out var domainHandle)) return false; - - //As a fallback, try and retrieve the local administrators group and get the first account with a rid of 500 - //If at any time we encounter a failure, just return a dummy sid that wont match anything - var status = _nativeMethods.CallSamOpenAlias(domainHandle, NativeMethods.AliasOpenFlags.ListMembers, - (int) LocalGroupRids.Administrators, out var aliasHandle); - _log.LogTrace("SamOpenAlias returned {Status} for Administrators on {ComputerName}", status, _computerName); - if (status != NativeMethods.NtStatus.StatusSuccess) - { - _nativeMethods.CallSamCloseHandle(aliasHandle); - return false; - } - - status = _nativeMethods.CallSamGetMembersInAlias(aliasHandle, out var members, out var count); - _log.LogTrace("SamGetMembersInAlias returned {Status} for Administrators on {ComputerName}", status, - _computerName); - if (status != NativeMethods.NtStatus.StatusSuccess) - { - _nativeMethods.CallSamCloseHandle(aliasHandle); - return false; - } - - _nativeMethods.CallSamCloseHandle(aliasHandle); - - if (count == 0) - { - _nativeMethods.CallSamFreeMemory(members); - return false; - } - - var sids = new List(); - for (var i = 0; i < count; i++) - try - { - var ptr = Marshal.ReadIntPtr(members, Marshal.SizeOf() * i); - var sid = new SecurityIdentifier(ptr).Value; - sids.Add(sid); - } - catch (Exception e) - { - _log.LogDebug(e, "GetMachineSid - Exception converting sid"); - } - - _nativeMethods.CallSamFreeMemory(members); - - var domainSid = _computerSID.AccountDomainSid.Value.ToUpper(); - - machineSid = sids.Select(x => - { - try - { - return new SecurityIdentifier(x).Value; - } - catch - { - return null; - } - }).Where(x => x != null).DefaultIfEmpty(null) - .FirstOrDefault(x => x.EndsWith("-500") && !x.ToUpper().StartsWith(domainSid)); - - if (machineSid == null) - { - _log.LogTrace("Did not get a machine SID for {ComputerName}", _computerName); - return false; - } - - machineSid = new SecurityIdentifier(machineSid).AccountDomainSid.Value; - - Cache.AddMachineSid(_computerSID.Value, machineSid); - _cachedMachineSid = machineSid; - return true; - } - - private bool LookupDomainSid(string domainName, out SecurityIdentifier sid) - { - var unicodeString = new NativeMethods.UNICODE_STRING(domainName); - sid = null; - try - { - var status = - _nativeMethods.CallSamLookupDomainInSamServer(_samServerHandle, ref unicodeString, out var ptrSid); - _log.LogTrace("SamLookupDomainInSamServer returned {Status} on {ComputerName} for {Domain}", status, - _computerName, domainName); - if (status == NativeMethods.NtStatus.StatusSuccess) - { - sid = new SecurityIdentifier(ptrSid); - _nativeMethods.CallSamFreeMemory(ptrSid); - return true; - } - else - { - return false; - } - } - catch - { - return false; - } - finally - { - unicodeString.Dispose(); - } - } - } - - public class ServerNotOpenException : Exception - { - public ServerNotOpenException(string message) : base(message) - { - } - } - - public class APIException : Exception - { - public APIException() - { - } - - public APIException(string apiCall, NativeMethods.NtStatus status) - { - Status = status.ToString(); - APICall = apiCall; - } - - public string Status { get; set; } - public string APICall { get; set; } - - public override string ToString() - { - return $"Call to {APICall} returned {Status}"; - } - } - - public class LocalItem - { - public int RelativeID { get; set; } - public Label ObjectType { get; set; } - public string ObjectID { get; set; } - public string Domain { get; set; } - } - - public enum LocalGroupRids - { - None = 0, - Administrators = 544, - RemoteDesktopUsers = 555, - DcomUsers = 562, - PSRemote = 580 - } - - internal class DomainHandleManager - { - private readonly Dictionary _nameToHandleMap = new(); - private readonly NativeMethods _nativeMethods; - private readonly Dictionary _sidToDomainMap = new(); - - internal DomainHandleManager(NativeMethods nativeMethods = null) - { - _nativeMethods = nativeMethods ?? new NativeMethods(); - } - - internal void AddMappedDomain(SecurityIdentifier sid, string name, IntPtr handle) - { - if (name.Equals("Builtin", StringComparison.CurrentCultureIgnoreCase)) - { - _sidToDomainMap.Add(sid.Value.ToUpper(), name.ToUpper()); - _sidToDomainMap.Add(name.ToUpper(), sid.Value.ToUpper()); - } - else - { - _sidToDomainMap.Add(sid.AccountDomainSid.Value.ToUpper(), name.ToUpper()); - _sidToDomainMap.Add(name.ToUpper(), sid.AccountDomainSid.Value.ToUpper()); - } - - - _nameToHandleMap.Add(name.ToUpper(), handle); - } - - internal void AddMappedDomain(string sid, string name, IntPtr handle) - { - _sidToDomainMap.Add(sid.ToUpper(), name.ToUpper()); - _sidToDomainMap.Add(name.ToUpper(), sid.ToUpper()); - _nameToHandleMap.Add(name.ToUpper(), handle); - } - - internal bool GetHandleByName(string name, out IntPtr domainHandle) - { - return _nameToHandleMap.TryGetValue(name.ToUpper(), out domainHandle); - } - - internal bool GetHandleBySid(string sid, out IntPtr domainHandle) - { - if (_sidToDomainMap.TryGetValue(sid.ToUpper(), out var domainName)) - return _nameToHandleMap.TryGetValue(domainName, out domainHandle); - - domainHandle = IntPtr.Zero; - return false; - } - - internal bool GetHandleBySid(SecurityIdentifier sid, out IntPtr domainHandle) - { - var sidString = sid.AccountDomainSid.Value.ToUpper(); - if (_sidToDomainMap.TryGetValue(sidString, out var domainName)) - return _nameToHandleMap.TryGetValue(domainName, out domainHandle); - - domainHandle = IntPtr.Zero; - return false; - } - - internal void Dispose() - { - foreach (var kv in _nameToHandleMap.ToList()) - { - if (kv.Value == IntPtr.Zero) continue; - _nativeMethods.CallSamCloseHandle(kv.Value); - _nameToHandleMap[kv.Key] = IntPtr.Zero; - } - - foreach (var kv in _nameToHandleMap) - { - } - } - - ~DomainHandleManager() - { - Dispose(); - } - } - - internal class CachedLocalItem - { - public CachedLocalItem(string name, Label type) - { - Name = name; - Type = type; - } - - public string Name { get; set; } - public Label Type { get; set; } - } -} \ No newline at end of file diff --git a/test/unit/ComputerSessionProcessorTest.cs b/test/unit/ComputerSessionProcessorTest.cs index be509a00..cea1b88a 100644 --- a/test/unit/ComputerSessionProcessorTest.cs +++ b/test/unit/ComputerSessionProcessorTest.cs @@ -4,6 +4,7 @@ using Moq; using Newtonsoft.Json; using SharpHoundCommonLib; +using SharpHoundCommonLib.Exceptions; using SharpHoundCommonLib.OutputTypes; using SharpHoundCommonLib.Processors; using Xunit; @@ -189,7 +190,7 @@ public async Task ComputerSessionProcessor_ReadUserSessions_ComputerAccessDenied { var mockNativeMethods = new Mock(); //mockNativeMethods.Setup(x => x.CallSamConnect(ref It.Ref.IsAny, out It.Ref.IsAny, It.IsAny(), ref It.Ref.IsAny)).Returns(NativeMethods.NtStatus.StatusAccessDenied); - var ex = new APIException + var ex = new ComputerAPIException { Status = NativeMethods.NERR.ERROR_ACCESS_DENIED.ToString() }; @@ -205,7 +206,7 @@ public async Task ComputerSessionProcessor_ReadUserSessionsPrivileged_ComputerAc { var mockNativeMethods = new Mock(); //mockNativeMethods.Setup(x => x.CallSamConnect(ref It.Ref.IsAny, out It.Ref.IsAny, It.IsAny(), ref It.Ref.IsAny)).Returns(NativeMethods.NtStatus.StatusAccessDenied); - var ex = new APIException + var ex = new ComputerAPIException { Status = NativeMethods.NERR.ERROR_ACCESS_DENIED.ToString() }; From c233c433410c1f44b283d3f717f08bf79694d722 Mon Sep 17 00:00:00 2001 From: Alex Holms Date: Fri, 30 Sep 2022 10:57:26 -0600 Subject: [PATCH 13/26] chore: fixed consistency for logwarning yield break --- src/CommonLib/LDAPUtils.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/CommonLib/LDAPUtils.cs b/src/CommonLib/LDAPUtils.cs index 97ffb575..32f59585 100644 --- a/src/CommonLib/LDAPUtils.cs +++ b/src/CommonLib/LDAPUtils.cs @@ -876,11 +876,9 @@ public IEnumerable QueryLDAP(string ldapFilter, SearchScope { throw new LDAPQueryException("Failed to setup LDAP Query Filter", error); } - else - { - _log.LogWarning(error, "Failed to setup LDAP Query Filter"); - yield break; - } + + _log.LogWarning(error, "Failed to setup LDAP Query Filter"); + yield break; } PageResultResponseControl pageResponse = null; From 73664ae5d4f91fd22a897bfc53ad09706c2b65a5 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Mon, 3 Oct 2022 12:58:28 -0400 Subject: [PATCH 14/26] feat: refactor remaining API calls into RPCLib. Update tests. --- .../Exceptions/ComputerAPIException.cs | 25 -- src/CommonLib/LDAPUtils.cs | 26 +- src/CommonLib/NativeMethods.cs | 336 +----------------- .../Processors/ComputerSessionProcessor.cs | 37 +- .../Processors/LocalGroupProcessor.cs | 1 - src/SharpHoundRPC/Handles/LSAPointer.cs | 2 +- src/SharpHoundRPC/Handles/NetAPIPointer.cs | 26 ++ src/SharpHoundRPC/NetAPINative/NetAPIEnums.cs | 89 +++++ .../NetAPINative/NetAPIMethods.cs | 128 +++++++ .../NetAPINative/NetAPIResult.cs | 31 ++ .../NetAPINative/NetAPIReturns.cs | 28 ++ .../NetAPINative/NetAPIStructs.cs | 56 +++ src/SharpHoundRPC/Result.cs | 4 +- src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs | 21 +- src/SharpHoundRPC/SAMRPCNative/SAMStructs.cs | 20 +- test/unit/ComputerSessionProcessorTest.cs | 166 ++------- 16 files changed, 449 insertions(+), 547 deletions(-) delete mode 100644 src/CommonLib/Exceptions/ComputerAPIException.cs create mode 100644 src/SharpHoundRPC/Handles/NetAPIPointer.cs create mode 100644 src/SharpHoundRPC/NetAPINative/NetAPIEnums.cs create mode 100644 src/SharpHoundRPC/NetAPINative/NetAPIMethods.cs create mode 100644 src/SharpHoundRPC/NetAPINative/NetAPIResult.cs create mode 100644 src/SharpHoundRPC/NetAPINative/NetAPIReturns.cs create mode 100644 src/SharpHoundRPC/NetAPINative/NetAPIStructs.cs diff --git a/src/CommonLib/Exceptions/ComputerAPIException.cs b/src/CommonLib/Exceptions/ComputerAPIException.cs deleted file mode 100644 index bb719645..00000000 --- a/src/CommonLib/Exceptions/ComputerAPIException.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; - -namespace SharpHoundCommonLib.Exceptions -{ - public class ComputerAPIException : Exception - { - public ComputerAPIException() - { - } - - public ComputerAPIException(string apiCall, NativeMethods.NtStatus status) - { - Status = status.ToString(); - APICall = apiCall; - } - - public string Status { get; set; } - public string APICall { get; set; } - - public override string ToString() - { - return $"Call to {APICall} returned {Status}"; - } - } -} \ No newline at end of file diff --git a/src/CommonLib/LDAPUtils.cs b/src/CommonLib/LDAPUtils.cs index 97ffb575..fe280b4e 100644 --- a/src/CommonLib/LDAPUtils.cs +++ b/src/CommonLib/LDAPUtils.cs @@ -17,6 +17,7 @@ using SharpHoundCommonLib.OutputTypes; using SharpHoundCommonLib.Processors; using SharpHoundCommonLib.Exceptions; +using SharpHoundRPC.NetAPINative; using Domain = System.DirectoryServices.ActiveDirectory.Domain; using SearchScope = System.DirectoryServices.Protocols.SearchScope; using SecurityMasks = System.DirectoryServices.Protocols.SecurityMasks; @@ -454,11 +455,11 @@ public async Task ResolveHostToSid(string hostname, string domain) //Step 2: Try NetWkstaGetInfo //Next we'll try calling NetWkstaGetInfo in hopes of getting the NETBIOS name directly from the computer //We'll use the hostname that we started with instead of the one from our previous step - var workstationInfo = await CallNetWkstaGetInfo(strippedHost); + var workstationInfo = await GetWorkstationInfo(strippedHost); if (workstationInfo.HasValue) { - tempName = workstationInfo.Value.computer_name; - tempDomain = workstationInfo.Value.lan_group; + tempName = workstationInfo.Value.ComputerName; + tempDomain = workstationInfo.Value.LanGroup; if (string.IsNullOrEmpty(tempDomain)) tempDomain = normalDomain; @@ -470,8 +471,8 @@ public async Task ResolveHostToSid(string hostname, string domain) var principal = ResolveAccountName(tempName, tempDomain); if (principal != null) { - _hostResolutionMap.TryAdd(strippedHost, sid); - return sid; + _hostResolutionMap.TryAdd(strippedHost, principal.ObjectIdentifier); + return principal.ObjectIdentifier; } } } @@ -1162,19 +1163,18 @@ private static bool RequestNETBIOSNameFromComputer(string server, string domain, /// /// /// - private async Task CallNetWkstaGetInfo(string hostname) + private async Task GetWorkstationInfo(string hostname) { if (!await _portScanner.CheckPort(hostname)) return null; - try + var result = NetAPIMethods.NetWkstaGetInfo(hostname); + if (result.IsSuccess) { - return _nativeMethods.CallNetWkstaGetInfo(hostname); - } - catch - { - return null; + return result.Value; } + + return null; } /// @@ -1396,7 +1396,7 @@ internal string ResolveDomainNetbiosToDns(string domainName) var computerName = _ldapConfig.Server; var dci = _nativeMethods.CallDsGetDcName(computerName, domainName); - if (dci.HasValue) + if (dci.IsSuccess) { flatName = dci.Value.DomainName; _netbiosCache.TryAdd(key, flatName); diff --git a/src/CommonLib/NativeMethods.cs b/src/CommonLib/NativeMethods.cs index 4ff83437..3ec78ded 100644 --- a/src/CommonLib/NativeMethods.cs +++ b/src/CommonLib/NativeMethods.cs @@ -1,39 +1,14 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; -using System.Security; -using System.Security.Principal; using Microsoft.Extensions.Logging; -using SharpHoundCommonLib.Exceptions; -using SharpHoundCommonLib.Processors; +using SharpHoundRPC.NetAPINative; namespace SharpHoundCommonLib { [ExcludeFromCodeCoverage] + // This class exists entirely as a shim for testing public class NativeMethods { - public enum NtStatus - { - StatusSuccess = 0x0, - StatusMoreEntries = 0x105, - StatusSomeMapped = 0x107, - StatusInvalidHandle = unchecked((int) 0xC0000008), - StatusInvalidParameter = unchecked((int) 0xC000000D), - StatusAccessDenied = unchecked((int) 0xC0000022), - StatusObjectTypeMismatch = unchecked((int) 0xC0000024), - StatusNoSuchDomain = unchecked((int) 0xC00000DF), - StatusRpcServerUnavailable = unchecked((int) 0xC0020017), - StatusNoSuchAlias = unchecked((int) 0xC0000151) - } - - private const string NetWkstaUserEnumQueryName = "NetWkstaUserEnum"; - private const string NetSessionEnumQueryName = "NetSessionEnum"; - private const string NetWkstaGetInfoQueryName = "NetWkstaGetInfo"; - - private const int NetWkstaUserEnumQueryLevel = 1; - private const int NetSessionEnumLevel = 10; - private const int NetWkstaGetInfoQueryLevel = 100; private readonly ILogger _log; public NativeMethods(ILogger log = null) @@ -46,312 +21,19 @@ public NativeMethods() _log = Logging.LogProvider.CreateLogger("NativeMethods"); } - public virtual WorkstationInfo100 CallNetWkstaGetInfo(string serverName) - { - var ptr = IntPtr.Zero; - - try - { - var result = NetWkstaGetInfo(serverName, NetWkstaGetInfoQueryLevel, out ptr); - if (result != NERR.NERR_Success) - throw new ComputerAPIException - { - Status = result.ToString(), - APICall = NetWkstaGetInfoQueryName - }; - - var wkstaInfo = Marshal.PtrToStructure(ptr); - return wkstaInfo; - } - finally - { - if (ptr != IntPtr.Zero) - NetApiBufferFree(ptr); - } - } - - public virtual IEnumerable CallNetSessionEnum(string serverName) - { - var ptr = IntPtr.Zero; - - _log.LogTrace("Beginning NetSessionEnum for {ServerName}", serverName); - try - { - var resumeHandle = 0; - var result = NetSessionEnum(serverName, null, null, NetSessionEnumLevel, out ptr, -1, - out var entriesread, - out _, ref resumeHandle); - - _log.LogTrace("Result of NetSessionEnum for {ServerName} is {Result}", serverName, result); - - if (result != NERR.NERR_Success) - throw new ComputerAPIException - { - APICall = NetSessionEnumQueryName, - Status = result.ToString() - }; - - var iter = ptr; - for (var i = 0; i < entriesread; i++) - { - var data = Marshal.PtrToStructure(iter); - iter = (IntPtr) (iter.ToInt64() + Marshal.SizeOf()); - - yield return data; - } - } - finally - { - if (ptr != IntPtr.Zero) - NetApiBufferFree(ptr); - } - } - - public virtual IEnumerable CallNetWkstaUserEnum(string servername) - { - var ptr = IntPtr.Zero; - try - { - var resumeHandle = 0; - _log.LogTrace("Beginning NetWkstaUserEnum for {ServerName}", servername); - var result = NetWkstaUserEnum(servername, NetWkstaUserEnumQueryLevel, out ptr, -1, out var entriesread, - out _, - ref resumeHandle); - - _log.LogTrace("Result of NetWkstaUserEnum for computer {ServerName} is {Result}", servername, result); - - if (result != NERR.NERR_Success && result != NERR.ERROR_MORE_DATA) - throw new ComputerAPIException - { - APICall = NetWkstaUserEnumQueryName, - Status = result.ToString() - }; - - var iter = ptr; - for (var i = 0; i < entriesread; i++) - { - var data = Marshal.PtrToStructure(iter); - iter = (IntPtr) (iter.ToInt64() + Marshal.SizeOf()); - yield return data; - } - } - finally - { - if (ptr != IntPtr.Zero) - NetApiBufferFree(ptr); - } - } - - public virtual DOMAIN_CONTROLLER_INFO? CallDsGetDcName(string computerName, string domainName) - { - var ptr = IntPtr.Zero; - try - { - var result = DsGetDcName(computerName, domainName, null, null, - (uint) (DSGETDCNAME_FLAGS.DS_IS_FLAT_NAME | DSGETDCNAME_FLAGS.DS_RETURN_DNS_NAME), out ptr); - - if (result != 0) return null; - var info = Marshal.PtrToStructure(ptr); - return info; - } - finally - { - if (ptr != IntPtr.Zero) - NetApiBufferFree(ptr); - } - } - - public struct OBJECT_ATTRIBUTES : IDisposable - { - public void Dispose() - { - if (objectName == IntPtr.Zero) return; - Marshal.DestroyStructure(objectName, typeof(UNICODE_STRING)); - Marshal.FreeHGlobal(objectName); - objectName = IntPtr.Zero; - } - - public int len; - public IntPtr rootDirectory; - public uint attribs; - public IntPtr sid; - public IntPtr qos; - private IntPtr objectName; - public UNICODE_STRING ObjectName; - } - - [StructLayout(LayoutKind.Sequential)] - public struct UNICODE_STRING : IDisposable - { - private readonly ushort Length; - private readonly ushort MaximumLength; - private IntPtr Buffer; - - public UNICODE_STRING(string s) - : this() - { - if (s == null) return; - Length = (ushort) (s.Length * 2); - MaximumLength = (ushort) (Length + 2); - Buffer = Marshal.StringToHGlobalUni(s); - } - - public void Dispose() - { - if (Buffer == IntPtr.Zero) return; - Marshal.FreeHGlobal(Buffer); - Buffer = IntPtr.Zero; - } - - public override string ToString() - { - return (Buffer != IntPtr.Zero ? Marshal.PtrToStringUni(Buffer, Length / 2) : null) ?? - throw new InvalidOperationException(); - } - } - - #region Session Enum Imports - - [DllImport("NetAPI32.dll", SetLastError = true)] - private static extern NERR NetSessionEnum( - [MarshalAs(UnmanagedType.LPWStr)] string ServerName, - [MarshalAs(UnmanagedType.LPWStr)] string UncClientName, - [MarshalAs(UnmanagedType.LPWStr)] string UserName, - int Level, - out IntPtr bufptr, - int prefmaxlen, - out int entriesread, - out int totalentries, - ref int resume_handle); - - [StructLayout(LayoutKind.Sequential)] - public struct SESSION_INFO_10 - { - [MarshalAs(UnmanagedType.LPWStr)] public string sesi10_cname; - [MarshalAs(UnmanagedType.LPWStr)] public string sesi10_username; - public uint sesi10_time; - public uint sesi10_idle_time; - } - - public enum NERR - { - NERR_Success = 0, - ERROR_MORE_DATA = 234, - ERROR_NO_BROWSER_SERVERS_FOUND = 6118, - ERROR_INVALID_LEVEL = 124, - ERROR_ACCESS_DENIED = 5, - ERROR_INVALID_PARAMETER = 87, - ERROR_NOT_ENOUGH_MEMORY = 8, - ERROR_NETWORK_BUSY = 54, - ERROR_BAD_NETPATH = 53, - ERROR_NO_NETWORK = 1222, - ERROR_INVALID_HANDLE_STATE = 1609, - ERROR_EXTENDED_ERROR = 1208, - NERR_BASE = 2100, - NERR_UnknownDevDir = NERR_BASE + 16, - NERR_DuplicateShare = NERR_BASE + 18, - NERR_BufTooSmall = NERR_BASE + 23 - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - public struct WKSTA_USER_INFO_1 + public virtual NetAPIResult> NetSessionEnum(string serverName) { - public string wkui1_username; - public string wkui1_logon_domain; - public string wkui1_oth_domains; - public string wkui1_logon_server; + return NetAPIMethods.NetSessionEnum(serverName); } - [DllImport("netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - private static extern NERR NetWkstaUserEnum( - string servername, - int level, - out IntPtr bufptr, - int prefmaxlen, - out int entriesread, - out int totalentries, - ref int resume_handle); - - [DllImport("netapi32.dll")] - private static extern int NetApiBufferFree( - IntPtr Buff); - - #endregion - - #region NetAPI PInvoke Calls - - [DllImport("netapi32.dll", SetLastError = true)] - private static extern NERR NetWkstaGetInfo( - [MarshalAs(UnmanagedType.LPWStr)] string serverName, - uint level, - out IntPtr bufPtr); - - public struct WorkstationInfo100 + public virtual NetAPIResult> NetWkstaUserEnum(string servername) { - public int platform_id; - [MarshalAs(UnmanagedType.LPWStr)] public string computer_name; - [MarshalAs(UnmanagedType.LPWStr)] public string lan_group; - public int ver_major; - public int ver_minor; + return NetAPIMethods.NetWkstaUserEnum(servername); } - #endregion - - #region DSGetDcName Imports - - [DllImport("Netapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] - internal static extern int DsGetDcName - ( - [MarshalAs(UnmanagedType.LPTStr)] string ComputerName, - [MarshalAs(UnmanagedType.LPTStr)] string DomainName, - [In] GuidClass DomainGuid, - [MarshalAs(UnmanagedType.LPTStr)] string SiteName, - uint Flags, - out IntPtr pDOMAIN_CONTROLLER_INFO - ); - - [StructLayout(LayoutKind.Sequential)] - public class GuidClass + public virtual NetAPIResult CallDsGetDcName(string computerName, string domainName) { - public Guid TheGuid; + return NetAPIMethods.DsGetDcName(computerName, domainName); } - - [Flags] - public enum DSGETDCNAME_FLAGS : uint - { - DS_FORCE_REDISCOVERY = 0x00000001, - DS_DIRECTORY_SERVICE_REQUIRED = 0x00000010, - DS_DIRECTORY_SERVICE_PREFERRED = 0x00000020, - DS_GC_SERVER_REQUIRED = 0x00000040, - DS_PDC_REQUIRED = 0x00000080, - DS_BACKGROUND_ONLY = 0x00000100, - DS_IP_REQUIRED = 0x00000200, - DS_KDC_REQUIRED = 0x00000400, - DS_TIMESERV_REQUIRED = 0x00000800, - DS_WRITABLE_REQUIRED = 0x00001000, - DS_GOOD_TIMESERV_PREFERRED = 0x00002000, - DS_AVOID_SELF = 0x00004000, - DS_ONLY_LDAP_NEEDED = 0x00008000, - DS_IS_FLAT_NAME = 0x00010000, - DS_IS_DNS_NAME = 0x00020000, - DS_RETURN_DNS_NAME = 0x40000000, - DS_RETURN_FLAT_NAME = 0x80000000 - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - public struct DOMAIN_CONTROLLER_INFO - { - [MarshalAs(UnmanagedType.LPTStr)] public string DomainControllerName; - [MarshalAs(UnmanagedType.LPTStr)] public string DomainControllerAddress; - public uint DomainControllerAddressType; - public Guid DomainGuid; - [MarshalAs(UnmanagedType.LPTStr)] public string DomainName; - [MarshalAs(UnmanagedType.LPTStr)] public string DnsForestName; - public uint Flags; - [MarshalAs(UnmanagedType.LPTStr)] public string DcSiteName; - [MarshalAs(UnmanagedType.LPTStr)] public string ClientSiteName; - } - - #endregion } } \ No newline at end of file diff --git a/src/CommonLib/Processors/ComputerSessionProcessor.cs b/src/CommonLib/Processors/ComputerSessionProcessor.cs index 1c69da23..4cba3f79 100644 --- a/src/CommonLib/Processors/ComputerSessionProcessor.cs +++ b/src/CommonLib/Processors/ComputerSessionProcessor.cs @@ -8,6 +8,7 @@ using Microsoft.Win32; using SharpHoundCommonLib.Exceptions; using SharpHoundCommonLib.OutputTypes; +using SharpHoundRPC.NetAPINative; namespace SharpHoundCommonLib.Processors { @@ -43,27 +44,23 @@ public async Task ReadUserSessions(string computerName, string string computerDomain) { var ret = new SessionAPIResult(); - NativeMethods.SESSION_INFO_10[] apiResult; - try - { - apiResult = _nativeMethods.CallNetSessionEnum(computerName).ToArray(); - } - catch (ComputerAPIException e) + var result = _nativeMethods.NetSessionEnum(computerName); + if (result.IsFailed) { - _log.LogDebug("NetSessionEnum failed on {ComputerName}: {Status}", computerName, e.Status); + _log.LogDebug("NetSessionEnum failed on {ComputerName}: {Status}", computerName, result.Status); ret.Collected = false; - ret.FailureReason = e.Status; + ret.FailureReason = result.Status.ToString(); return ret; } ret.Collected = true; var results = new List(); - foreach (var sesInfo in apiResult) + foreach (var sesInfo in result.Value) { - var username = sesInfo.sesi10_username; - var computerSessionName = sesInfo.sesi10_cname; + var username = sesInfo.Username; + var computerSessionName = sesInfo.ComputerName; _log.LogTrace("NetSessionEnum Entry: {Username}@{ComputerSessionName} from {ComputerName}", username, computerSessionName, computerName); @@ -139,27 +136,23 @@ public SessionAPIResult ReadUserSessionsPrivileged(string computerName, string computerSamAccountName, string computerSid) { var ret = new SessionAPIResult(); - NativeMethods.WKSTA_USER_INFO_1[] apiResult; + var result = _nativeMethods.NetWkstaUserEnum(computerName); - try - { - apiResult = _nativeMethods.CallNetWkstaUserEnum(computerName).ToArray(); - } - catch (ComputerAPIException e) + if (result.IsFailed) { - _log.LogTrace("NetWkstaUserEnum failed on {ComputerName}: {Status}", computerName, e.Status); + _log.LogTrace("NetWkstaUserEnum failed on {ComputerName}: {Status}", computerName, result.Status); ret.Collected = false; - ret.FailureReason = e.Status; + ret.FailureReason = result.Status.ToString(); return ret; } ret.Collected = true; var results = new List(); - foreach (var wkstaUserInfo in apiResult) + foreach (var wkstaUserInfo in result.Value) { - var domain = wkstaUserInfo.wkui1_logon_domain; - var username = wkstaUserInfo.wkui1_username; + var domain = wkstaUserInfo.LogonDomain; + var username = wkstaUserInfo.Username; _log.LogTrace("NetWkstaUserEnum entry: {Username}@{Domain} from {ComputerName}", username, domain, computerName); diff --git a/src/CommonLib/Processors/LocalGroupProcessor.cs b/src/CommonLib/Processors/LocalGroupProcessor.cs index e71a79e3..a8007225 100644 --- a/src/CommonLib/Processors/LocalGroupProcessor.cs +++ b/src/CommonLib/Processors/LocalGroupProcessor.cs @@ -225,7 +225,6 @@ public IEnumerable GetLocalGroups(string computerName, stri ObjectId = sidValue }); } - } } } diff --git a/src/SharpHoundRPC/Handles/LSAPointer.cs b/src/SharpHoundRPC/Handles/LSAPointer.cs index d36a52ce..499541b0 100644 --- a/src/SharpHoundRPC/Handles/LSAPointer.cs +++ b/src/SharpHoundRPC/Handles/LSAPointer.cs @@ -15,11 +15,11 @@ public LSAPointer(IntPtr handle) : base(handle, true) public LSAPointer(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle) { - SetHandle(handle); } protected override bool ReleaseHandle() { + if (handle == IntPtr.Zero) return true; return LSAMethods.LsaFreeMemory(handle) == NtStatus.StatusSuccess; } } diff --git a/src/SharpHoundRPC/Handles/NetAPIPointer.cs b/src/SharpHoundRPC/Handles/NetAPIPointer.cs new file mode 100644 index 00000000..1a498114 --- /dev/null +++ b/src/SharpHoundRPC/Handles/NetAPIPointer.cs @@ -0,0 +1,26 @@ +using System; +using SharpHoundRPC.NetAPINative; + +namespace SharpHoundRPC.Handles +{ + public class NetAPIPointer : BasePointer + { + public NetAPIPointer() : base(true) + { + } + + public NetAPIPointer(IntPtr handle) : base(handle, true) + { + } + + public NetAPIPointer(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle) + { + } + + protected override bool ReleaseHandle() + { + if (handle == IntPtr.Zero) return true; + return NetAPIMethods.NetApiBufferFree(handle) == NetAPIEnums.NetAPIStatus.Success; + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/NetAPINative/NetAPIEnums.cs b/src/SharpHoundRPC/NetAPINative/NetAPIEnums.cs new file mode 100644 index 00000000..7c8cbca9 --- /dev/null +++ b/src/SharpHoundRPC/NetAPINative/NetAPIEnums.cs @@ -0,0 +1,89 @@ +using System; + +namespace SharpHoundRPC.NetAPINative +{ + public class NetAPIEnums + { + public enum NetAPIStatus : uint + { + Success = 0, + + /// + /// This computer name is invalid. + /// + InvalidComputer = 2351, + + /// + /// This operation is only allowed on the primary domain controller of the domain. + /// + NotPrimary = 2226, + + /// + /// This operation is not allowed on this special group. + /// + SpeGroupOp = 2234, + + /// + /// This operation is not allowed on the last administrative account. + /// + LastAdmin = 2452, + + /// + /// The password parameter is invalid. + /// + BadPassword = 2203, + + /// + /// The password does not meet the password policy requirements. + /// Check the minimum password length, password complexity and password history requirements. + /// + PasswordTooShort = 2245, + + /// + /// The user name could not be found. + /// + UserNotFound = 2221, + ErrorAccessDenied = 5, + ErrorNotEnoughMemory = 8, + ErrorInvalidParameter = 87, + ErrorInvalidName = 123, + ErrorInvalidLevel = 124, + ErrorMoreData = 234, + ErrorSessionCredentialConflict = 1219, + + /// + /// The RPC server is not available. This error is returned if a remote computer was specified in + /// the lpServer parameter and the RPC server is not available. + /// + RpcSServerUnavailable = 2147944122, // 0x800706BA + + /// + /// Remote calls are not allowed for this process. This error is returned if a remote computer was + /// specified in the lpServer parameter and remote calls are not allowed for this process. + /// + RpcERemoteDisabled = 2147549468 // 0x8001011C + } + + [Flags] + public enum DSGETDCNAME_FLAGS : uint + { + DS_FORCE_REDISCOVERY = 0x00000001, + DS_DIRECTORY_SERVICE_REQUIRED = 0x00000010, + DS_DIRECTORY_SERVICE_PREFERRED = 0x00000020, + DS_GC_SERVER_REQUIRED = 0x00000040, + DS_PDC_REQUIRED = 0x00000080, + DS_BACKGROUND_ONLY = 0x00000100, + DS_IP_REQUIRED = 0x00000200, + DS_KDC_REQUIRED = 0x00000400, + DS_TIMESERV_REQUIRED = 0x00000800, + DS_WRITABLE_REQUIRED = 0x00001000, + DS_GOOD_TIMESERV_PREFERRED = 0x00002000, + DS_AVOID_SELF = 0x00004000, + DS_ONLY_LDAP_NEEDED = 0x00008000, + DS_IS_FLAT_NAME = 0x00010000, + DS_IS_DNS_NAME = 0x00020000, + DS_RETURN_DNS_NAME = 0x40000000, + DS_RETURN_FLAT_NAME = 0x80000000 + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/NetAPINative/NetAPIMethods.cs b/src/SharpHoundRPC/NetAPINative/NetAPIMethods.cs new file mode 100644 index 00000000..d24d5f73 --- /dev/null +++ b/src/SharpHoundRPC/NetAPINative/NetAPIMethods.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using SharpHoundRPC.Handles; + +namespace SharpHoundRPC.NetAPINative +{ + public static class NetAPIMethods + { + private const int NetWkstaUserEnumQueryLevel = 1; + private const int NetSessionEnumLevel = 10; + private const int NetWkstaGetInfoQueryLevel = 100; + + [DllImport("netapi32.dll")] + internal static extern NetAPIEnums.NetAPIStatus NetApiBufferFree( + IntPtr buffer); + + public static NetAPIResult> NetWkstaUserEnum(string computerName) + { + var resumeHandle = 0; + var result = NetWkstaUserEnum(computerName, NetWkstaUserEnumQueryLevel, out var buffer, -1, + out var entriesRead, out _, ref resumeHandle); + + using (buffer) + { + if (result != NetAPIEnums.NetAPIStatus.Success && result != NetAPIEnums.NetAPIStatus.ErrorMoreData) + { + return result; + } + + return NetAPIResult>.Ok(buffer.GetEnumerable(entriesRead) + .Select(x => new NetWkstaUserEnumResults(x.Username, x.LogonDomain))); + } + } + + [DllImport("netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern NetAPIEnums.NetAPIStatus NetWkstaUserEnum( + string servername, + int level, + out NetAPIPointer buffer, + int preferredMaxLength, + out int entriesRead, + out int totalEntries, + ref int resumeHandle); + + public static NetAPIResult> NetSessionEnum(string computerName) + { + var resumeHandle = 0; + var result = NetSessionEnum(computerName, null, null, NetSessionEnumLevel, out var buffer, -1, + out var entriesRead, out _, ref resumeHandle); + + using (buffer) + { + if (result != NetAPIEnums.NetAPIStatus.Success && result != NetAPIEnums.NetAPIStatus.ErrorMoreData) + { + return result; + } + + return NetAPIResult>.Ok(buffer.GetEnumerable(entriesRead) + .Select(x => new NetSessionEnumResults(x.Username, x.CName))); + } + } + + [DllImport("NetAPI32.dll", SetLastError = true)] + private static extern NetAPIEnums.NetAPIStatus NetSessionEnum( + [MarshalAs(UnmanagedType.LPWStr)] string serverName, + [MarshalAs(UnmanagedType.LPWStr)] string uncClientName, + [MarshalAs(UnmanagedType.LPWStr)] string userName, + int level, + out NetAPIPointer buffer, + int preferredMaxLength, + out int entriesRead, + out int totalEntries, + ref int resumeHandle); + + public static NetAPIResult NetWkstaGetInfo(string computerName) + { + var result = NetWkstaGetInfo(computerName, NetWkstaGetInfoQueryLevel, out var buffer); + + using (buffer) + { + if (result != NetAPIEnums.NetAPIStatus.Success) + { + return result; + } + + return buffer.GetData(); + } + } + + [DllImport("netapi32.dll", SetLastError = true)] + private static extern NetAPIEnums.NetAPIStatus NetWkstaGetInfo( + [MarshalAs(UnmanagedType.LPWStr)] string serverName, + uint level, + out NetAPIPointer bufPtr); + + public static NetAPIResult DsGetDcName(string computerName, + string domainName) + { + var result = DsGetDcName(computerName, domainName, null, null, + (uint) (NetAPIEnums.DSGETDCNAME_FLAGS.DS_IS_FLAT_NAME | + NetAPIEnums.DSGETDCNAME_FLAGS.DS_RETURN_DNS_NAME), out var buffer); + using (buffer) + { + if (result != NetAPIEnums.NetAPIStatus.Success) + { + return result; + } + + return buffer.GetData(); + } + + } + + [DllImport("Netapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern NetAPIEnums.NetAPIStatus DsGetDcName + ( + [MarshalAs(UnmanagedType.LPTStr)] string computerName, + [MarshalAs(UnmanagedType.LPTStr)] string domainName, + [In] NetAPIStructs.GuidClass domainGuid, + [MarshalAs(UnmanagedType.LPTStr)] string siteName, + uint flags, + out NetAPIPointer pDomainControllerInfo + ); + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/NetAPINative/NetAPIResult.cs b/src/SharpHoundRPC/NetAPINative/NetAPIResult.cs new file mode 100644 index 00000000..1c68d639 --- /dev/null +++ b/src/SharpHoundRPC/NetAPINative/NetAPIResult.cs @@ -0,0 +1,31 @@ +namespace SharpHoundRPC.NetAPINative +{ + public class NetAPIResult + { + public bool IsSuccess { get; private set; } + public NetAPIEnums.NetAPIStatus Status { get; private set; } + public T Value { get; private set; } + public string Error { get; private set; } + public bool IsFailed => !IsSuccess; + + public static NetAPIResult Ok(T value) => new() {IsSuccess = true, Value = value}; + + public static NetAPIResult Fail(NetAPIEnums.NetAPIStatus status) => new() {Status = status}; + public static NetAPIResult Fail(string error) => new() {Error = error}; + + public static implicit operator NetAPIResult(T input) + { + return Ok(input); + } + + public static implicit operator NetAPIResult(NetAPIEnums.NetAPIStatus status) + { + return Fail(status); + } + + public static implicit operator NetAPIResult(string error) + { + return Fail(error); + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/NetAPINative/NetAPIReturns.cs b/src/SharpHoundRPC/NetAPINative/NetAPIReturns.cs new file mode 100644 index 00000000..d000fc0a --- /dev/null +++ b/src/SharpHoundRPC/NetAPINative/NetAPIReturns.cs @@ -0,0 +1,28 @@ +using System.Diagnostics.CodeAnalysis; + +namespace SharpHoundRPC.NetAPINative +{ + public class NetWkstaUserEnumResults + { + public NetWkstaUserEnumResults(string username, string domain) + { + LogonDomain = domain; + Username = username; + } + + public string LogonDomain { get; } + public string Username { get; } + } + + public class NetSessionEnumResults + { + public NetSessionEnumResults(string username, string cname) + { + Username = username; + ComputerName = cname; + } + + public string Username { get; } + public string ComputerName { get; } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/NetAPINative/NetAPIStructs.cs b/src/SharpHoundRPC/NetAPINative/NetAPIStructs.cs new file mode 100644 index 00000000..4357ecda --- /dev/null +++ b/src/SharpHoundRPC/NetAPINative/NetAPIStructs.cs @@ -0,0 +1,56 @@ +using System; +using System.Runtime.InteropServices; + +namespace SharpHoundRPC.NetAPINative +{ + public class NetAPIStructs + { + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct WkstaUserInfo1 + { + public string Username; + public string LogonDomain; + public string OtherDomains; + public string LogonServer; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SessionInfo10 + { + [MarshalAs(UnmanagedType.LPWStr)] public string CName; + [MarshalAs(UnmanagedType.LPWStr)] public string Username; + public uint Time; + public uint IdleTIme; + } + + [StructLayout(LayoutKind.Sequential)] + public struct WorkstationInfo100 + { + public int PlatformId; + [MarshalAs(UnmanagedType.LPWStr)] public string ComputerName; + [MarshalAs(UnmanagedType.LPWStr)] public string LanGroup; + public int MajorVersion; + public int MinorVersion; + } + + [StructLayout(LayoutKind.Sequential)] + public class GuidClass + { + public Guid TheGuid; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct DomainControllerInfo + { + [MarshalAs(UnmanagedType.LPTStr)] public string DomainControllerName; + [MarshalAs(UnmanagedType.LPTStr)] public string DomainControllerAddress; + public uint DomainControllerAddressType; + public Guid DomainGuid; + [MarshalAs(UnmanagedType.LPTStr)] public string DomainName; + [MarshalAs(UnmanagedType.LPTStr)] public string DnsForestName; + public uint Flags; + [MarshalAs(UnmanagedType.LPTStr)] public string DcSiteName; + [MarshalAs(UnmanagedType.LPTStr)] public string ClientSiteName; + } + } +} \ No newline at end of file diff --git a/src/SharpHoundRPC/Result.cs b/src/SharpHoundRPC/Result.cs index f6f4b68f..a85843f3 100644 --- a/src/SharpHoundRPC/Result.cs +++ b/src/SharpHoundRPC/Result.cs @@ -1,4 +1,6 @@ -namespace SharpHoundRPC +using SharpHoundRPC.NetAPINative; + +namespace SharpHoundRPC { public class Result { diff --git a/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs b/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs index 77ec4948..51a6f4a3 100644 --- a/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs +++ b/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs @@ -37,7 +37,8 @@ internal static (NtStatus status, IEnumerable doma SamEnumerateDomainsInSamServer(serverHandle, ref enumerationContext, out var domains, -1, out var count); - return (status, domains.GetEnumerable(count)); + using (domains) + return (status, domains.GetEnumerable(count)); } [DllImport("samlib.dll", CharSet = CharSet.Unicode)] @@ -54,7 +55,8 @@ internal static (NtStatus status, SecurityIdentifier securityIdentifier) SamLook var us = new SharedStructs.UnicodeString(name); var status = SamLookupDomainInSamServer(serverHandle, ref us, out var sid); - return (status, sid.GetData()); + using (sid) + return (status, sid.GetData()); } [DllImport("samlib.dll", CharSet = CharSet.Unicode)] @@ -124,21 +126,6 @@ private static extern NtStatus SamEnumerateAliasesInDomain( out int count ); - // - // internal static void SamLookupIdsInDomain(SAMHandle domainHandle, int[] rids, out string[] names, - // out SharedEnums.SidNameUse[] types) - // { - // var count = rids.Length; - // var status = SamLookupIdsInDomain(domainHandle, count, rids, out var namePointer, out var usePointer); - // - // status.CheckError(RPCException.LookupIds); - // - // names = namePointer.GetEnumerable(count).Select(x => x.ToString()).ToArray(); - // types = new SharedEnums.SidNameUse[count]; - // - // Marshal.Copy(usePointer.DangerousGetHandle(), (int[]) (object) types, 0, count); - // } - internal static (NtStatus status, SAMPointer names, SAMPointer use) SamLookupIdsInDomain(SAMHandle domainHandle, int rid) { diff --git a/src/SharpHoundRPC/SAMRPCNative/SAMStructs.cs b/src/SharpHoundRPC/SAMRPCNative/SAMStructs.cs index 0dd3f471..0bbc643f 100644 --- a/src/SharpHoundRPC/SAMRPCNative/SAMStructs.cs +++ b/src/SharpHoundRPC/SAMRPCNative/SAMStructs.cs @@ -10,18 +10,18 @@ public struct ObjectAttributes : IDisposable { public void Dispose() { - if (objectName == IntPtr.Zero) return; - Marshal.DestroyStructure(objectName, typeof(SharedStructs.UnicodeString)); - Marshal.FreeHGlobal(objectName); - objectName = IntPtr.Zero; + if (_objectName == IntPtr.Zero) return; + Marshal.DestroyStructure(_objectName, typeof(SharedStructs.UnicodeString)); + Marshal.FreeHGlobal(_objectName); + _objectName = IntPtr.Zero; } - public int len; - public IntPtr rootDirectory; - public uint attribs; - public IntPtr sid; - public IntPtr qos; - private IntPtr objectName; + public int Length; + public IntPtr RootDirectory; + public uint Attributes; + public IntPtr SID; + public IntPtr Qos; + private IntPtr _objectName; public SharedStructs.UnicodeString ObjectName; } diff --git a/test/unit/ComputerSessionProcessorTest.cs b/test/unit/ComputerSessionProcessorTest.cs index cea1b88a..6bd981a4 100644 --- a/test/unit/ComputerSessionProcessorTest.cs +++ b/test/unit/ComputerSessionProcessorTest.cs @@ -7,6 +7,7 @@ using SharpHoundCommonLib.Exceptions; using SharpHoundCommonLib.OutputTypes; using SharpHoundCommonLib.Processors; +using SharpHoundRPC.NetAPINative; using Xunit; using Xunit.Abstractions; @@ -38,25 +39,14 @@ public void Dispose() public async Task ComputerSessionProcessor_ReadUserSessions_FilteringWorks() { var mockNativeMethods = new Mock(); - var apiResult = new NativeMethods.SESSION_INFO_10[] + + var apiResult = new NetSessionEnumResults[] { - new() - { - sesi10_username = "dfm", - sesi10_cname = "\\\\192.168.92.110" - }, - new() - { - sesi10_cname = "", - sesi10_username = "admin" - }, - new() - { - sesi10_username = "admin", - sesi10_cname = "\\\\192.168.92.110" - } + new("dfm", "\\\\192.168.92.110"), + new("admin", ""), + new("admin", "\\\\192.168.92.110") }; - mockNativeMethods.Setup(x => x.CallNetSessionEnum(It.IsAny())).Returns(apiResult); + mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); @@ -68,15 +58,11 @@ public async Task ComputerSessionProcessor_ReadUserSessions_FilteringWorks() public async Task ComputerSessionProcessor_ReadUserSessions_ResolvesHost() { var mockNativeMethods = new Mock(); - var apiResult = new NativeMethods.SESSION_INFO_10[] + var apiResult = new NetSessionEnumResults[] { - new() - { - sesi10_username = "admin", - sesi10_cname = "\\\\192.168.1.1" - } + new("admin", "\\\\192.168.1.1") }; - mockNativeMethods.Setup(x => x.CallNetSessionEnum(It.IsAny())).Returns(apiResult); + mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); var expected = new Session[] { @@ -97,15 +83,11 @@ public async Task ComputerSessionProcessor_ReadUserSessions_ResolvesHost() public async Task ComputerSessionProcessor_ReadUserSessions_ResolvesLocalHostEquivalent() { var mockNativeMethods = new Mock(); - var apiResult = new NativeMethods.SESSION_INFO_10[] + var apiResult = new NetSessionEnumResults[] { - new() - { - sesi10_username = "admin", - sesi10_cname = "\\\\127.0.0.1" - } + new("admin", "\\\\127.0.0.1") }; - mockNativeMethods.Setup(x => x.CallNetSessionEnum(It.IsAny())).Returns(apiResult); + mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); var expected = new Session[] { @@ -126,15 +108,11 @@ public async Task ComputerSessionProcessor_ReadUserSessions_ResolvesLocalHostEqu public async Task ComputerSessionProcessor_ReadUserSessions_MultipleMatches_AddsAll() { var mockNativeMethods = new Mock(); - var apiResult = new NativeMethods.SESSION_INFO_10[] + var apiResult = new NetSessionEnumResults[] { - new() - { - sesi10_username = "administrator", - sesi10_cname = "\\\\127.0.0.1" - } + new("administrator", "\\\\127.0.0.1") }; - mockNativeMethods.Setup(x => x.CallNetSessionEnum(It.IsAny())).Returns(apiResult); + mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); var expected = new Session[] { @@ -160,15 +138,11 @@ public async Task ComputerSessionProcessor_ReadUserSessions_MultipleMatches_Adds public async Task ComputerSessionProcessor_ReadUserSessions_NoGCMatch_TriesResolve() { var mockNativeMethods = new Mock(); - var apiResult = new NativeMethods.SESSION_INFO_10[] + var apiResult = new NetSessionEnumResults[] { - new() - { - sesi10_username = "test", - sesi10_cname = "\\\\127.0.0.1" - } + new("test", "\\\\127.0.0.1") }; - mockNativeMethods.Setup(x => x.CallNetSessionEnum(It.IsAny())).Returns(apiResult); + mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); var expected = new Session[] { @@ -186,19 +160,15 @@ public async Task ComputerSessionProcessor_ReadUserSessions_NoGCMatch_TriesResol } [WindowsOnlyFact] - public async Task ComputerSessionProcessor_ReadUserSessions_ComputerAccessDenied_ExceptionCaught() + public async Task ComputerSessionProcessor_ReadUserSessions_ComputerAccessDenied_Handled() { var mockNativeMethods = new Mock(); //mockNativeMethods.Setup(x => x.CallSamConnect(ref It.Ref.IsAny, out It.Ref.IsAny, It.IsAny(), ref It.Ref.IsAny)).Returns(NativeMethods.NtStatus.StatusAccessDenied); - var ex = new ComputerAPIException - { - Status = NativeMethods.NERR.ERROR_ACCESS_DENIED.ToString() - }; - mockNativeMethods.Setup(x => x.CallNetSessionEnum(It.IsAny())).Throws(ex); + mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(NetAPIEnums.NetAPIStatus.ErrorAccessDenied); var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); var test = await processor.ReadUserSessions("test", "test", "test"); Assert.False(test.Collected); - Assert.Equal(NativeMethods.NERR.ERROR_ACCESS_DENIED.ToString(), test.FailureReason); + Assert.Equal(NetAPIEnums.NetAPIStatus.ErrorAccessDenied.ToString(), test.FailureReason); } [WindowsOnlyFact] @@ -206,15 +176,11 @@ public async Task ComputerSessionProcessor_ReadUserSessionsPrivileged_ComputerAc { var mockNativeMethods = new Mock(); //mockNativeMethods.Setup(x => x.CallSamConnect(ref It.Ref.IsAny, out It.Ref.IsAny, It.IsAny(), ref It.Ref.IsAny)).Returns(NativeMethods.NtStatus.StatusAccessDenied); - var ex = new ComputerAPIException - { - Status = NativeMethods.NERR.ERROR_ACCESS_DENIED.ToString() - }; - mockNativeMethods.Setup(x => x.CallNetWkstaUserEnum(It.IsAny())).Throws(ex); + mockNativeMethods.Setup(x => x.NetWkstaUserEnum(It.IsAny())).Returns(NetAPIEnums.NetAPIStatus.ErrorAccessDenied); var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); var test = processor.ReadUserSessionsPrivileged("test", "test", "test"); Assert.False(test.Collected); - Assert.Equal(NativeMethods.NERR.ERROR_ACCESS_DENIED.ToString(), test.FailureReason); + Assert.Equal(NetAPIEnums.NetAPIStatus.ErrorAccessDenied.ToString(), test.FailureReason); } [WindowsOnlyFact] @@ -224,80 +190,20 @@ public async Task ComputerSessionProcessor_ReadUserSessionsPrivileged_FilteringW const string samAccountName = "WIN10"; //This is a sample response from a computer in a test environment. The duplicates are intentional - var apiResults = new NativeMethods.WKSTA_USER_INFO_1[] + var apiResults = new NetWkstaUserEnumResults[] { - new() - { - wkui1_logon_domain = "TESTLAB", - wkui1_logon_server = "PRIMARY", - wkui1_oth_domains = "", - wkui1_username = "dfm" - }, - new() - { - wkui1_logon_domain = "PRIMARY", - wkui1_logon_server = "", - wkui1_oth_domains = "", - wkui1_username = "Administrator" - }, - new() - { - wkui1_logon_domain = "", - wkui1_logon_server = "PRIMARY", - wkui1_oth_domains = "", - wkui1_username = "Administrator" - }, - new() - { - wkui1_logon_domain = "TESTLAB", - wkui1_logon_server = "", - wkui1_oth_domains = "", - wkui1_username = "WIN10$" - }, - new() - { - wkui1_logon_domain = "TESTLAB", - wkui1_logon_server = "", - wkui1_oth_domains = "", - wkui1_username = "WIN10$" - }, - new() - { - wkui1_logon_domain = "TESTLAB", - wkui1_logon_server = "", - wkui1_oth_domains = "", - wkui1_username = "WIN10$" - }, - new() - { - wkui1_logon_domain = "TESTLAB", - wkui1_logon_server = "", - wkui1_oth_domains = "", - wkui1_username = "WIN10$" - }, - new() - { - wkui1_logon_domain = "WIN10", - wkui1_logon_server = "", - wkui1_oth_domains = "", - wkui1_username = "JOHN" - }, - new() - { - wkui1_logon_domain = "NT AUTHORITY", - wkui1_logon_server = "", - wkui1_oth_domains = "", - wkui1_username = "SYSTEM" - }, - new() - { - wkui1_logon_domain = "TESTLAB", - wkui1_logon_server = "", - wkui1_oth_domains = "", - wkui1_username = "ABC" - } + new("dfm", "TESTLAB"), + new("Administrator", "PRIMARY"), + new("Administrator", ""), + new("WIN10$", "TESTLAB"), + new("WIN10$", "TESTLAB"), + new("WIN10$", "TESTLAB"), + new("WIN10$", "TESTLAB"), + new("JOHN", "WIN10"), + new("SYSTEM", "NT AUTHORITY"), + new("ABC", "TESTLAB") }; - mockNativeMethods.Setup(x => x.CallNetWkstaUserEnum(It.IsAny())).Returns(apiResults); + mockNativeMethods.Setup(x => x.NetWkstaUserEnum(It.IsAny())).Returns(apiResults); var expected = new Session[] { From 9e342236d8b021db15c23893ee9b04f449711500 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Mon, 3 Oct 2022 13:51:00 -0400 Subject: [PATCH 15/26] chore: clean up handle earlier --- src/SharpHoundRPC/Wrappers/LSAPolicy.cs | 71 +++++++++++++++++-------- src/SharpHoundRPC/Wrappers/SAMAlias.cs | 11 ++-- src/SharpHoundRPC/Wrappers/SAMDomain.cs | 28 ++++++---- 3 files changed, 75 insertions(+), 35 deletions(-) diff --git a/src/SharpHoundRPC/Wrappers/LSAPolicy.cs b/src/SharpHoundRPC/Wrappers/LSAPolicy.cs index 4a9c5bc2..005159c8 100644 --- a/src/SharpHoundRPC/Wrappers/LSAPolicy.cs +++ b/src/SharpHoundRPC/Wrappers/LSAPolicy.cs @@ -40,34 +40,46 @@ public static Result OpenPolicy(string computerName, LSAEnums.LsaOpen public Result> GetPrincipalsWithPrivilege(string userRight) { var (status, sids, count) = LSAMethods.LsaEnumerateAccountsWithUserRight(Handle, userRight); - if (status.IsError()) return status; + using (sids) + { + if (status.IsError()) return status; - return Result>.Ok(sids.GetEnumerable(count)); + return Result>.Ok(sids.GetEnumerable(count)); + } } public Result> GetResolvedPrincipalsWithPrivilege(string userRight) { var (status, sids, count) = LSAMethods.LsaEnumerateAccountsWithUserRight(Handle, userRight); - if (status.IsError()) return status; - - var (lookupStatus, referencedDomains, names, lookupCount) = LSAMethods.LsaLookupSids(Handle, sids, count); - if (lookupStatus.IsError()) + using (sids) { - return lookupStatus; + if (status.IsError()) return status; + + var (lookupStatus, referencedDomains, names, lookupCount) = LSAMethods.LsaLookupSids(Handle, sids, count); + if (lookupStatus.IsError()) + { + referencedDomains.Dispose(); + names.Dispose(); + return lookupStatus; + } + var translatedNames = names.GetEnumerable(count).ToArray(); + var domainList = referencedDomains.GetData(); + var safeDomains = new LSAPointer(domainList.Domains); + var domains = safeDomains.GetEnumerable(domainList.Entries).ToArray(); + var convertedSids = sids.GetEnumerable(lookupCount).ToArray(); + + var ret = new List<(SecurityIdentifier sid, string Name, SharedEnums.SidNameUse Use, string Domain)>(); + for (var i = 0; i < count; i++) + ret.Add((convertedSids[i], translatedNames[i].Name.ToString(), translatedNames[i].Use, + domains[translatedNames[i].DomainIndex].Name.ToString())); + + referencedDomains.Dispose(); + names.Dispose(); + safeDomains.Dispose(); + + return ret; } - var translatedNames = names.GetEnumerable(count).ToArray(); - var domainList = referencedDomains.GetData(); - var safeDomains = new LSAPointer(domainList.Domains); - var domains = safeDomains.GetEnumerable(domainList.Entries).ToArray(); - var convertedSids = sids.GetEnumerable(lookupCount).ToArray(); - - var ret = new List<(SecurityIdentifier sid, string Name, SharedEnums.SidNameUse Use, string Domain)>(); - for (var i = 0; i < count; i++) - ret.Add((convertedSids[i], translatedNames[i].Name.ToString(), translatedNames[i].Use, - domains[translatedNames[i].DomainIndex].Name.ToString())); - - return ret; } public Result<(string Name, SharedEnums.SidNameUse Use, string Domains)> LookupSid(SecurityIdentifier sid) @@ -76,12 +88,20 @@ public Result> GetPrincipalsWithPrivilege(string return "SID cannot be null"; var (status, referencedDomains, names, count) = LSAMethods.LsaLookupSids(Handle, new[] {sid}); - if (status.IsError()) return status; + if (status.IsError()) + { + names.Dispose(); + referencedDomains.Dispose(); + return status; + } var translatedNames = names.GetEnumerable(count).ToArray(); var domainList = referencedDomains.GetData(); var safeDomains = new LSAPointer(domainList.Domains); var domains = safeDomains.GetEnumerable(domainList.Entries).ToArray(); + names.Dispose(); + referencedDomains.Dispose(); + safeDomains.Dispose(); return (translatedNames[0].Name.ToString(), translatedNames[0].Use, domains[translatedNames[0].DomainIndex].Name.ToString()); } @@ -95,7 +115,12 @@ public Result> GetPrincipalsWithPrivilege(string return "No non-null SIDs specified"; var (status, referencedDomains, names, count) = LSAMethods.LsaLookupSids(Handle, sids); - if (status.IsError()) return status; + if (status.IsError()) + { + referencedDomains.Dispose(); + names.Dispose(); + return status; + } var translatedNames = names.GetEnumerable(count).ToArray(); var domainList = referencedDomains.GetData(); @@ -107,6 +132,10 @@ public Result> GetPrincipalsWithPrivilege(string ret.Add((sids[i], translatedNames[i].Name.ToString(), translatedNames[i].Use, domains[translatedNames[i].DomainIndex].Name.ToString())); + referencedDomains.Dispose(); + names.Dispose(); + safeDomains.Dispose(); + return ret.ToArray(); } } diff --git a/src/SharpHoundRPC/Wrappers/SAMAlias.cs b/src/SharpHoundRPC/Wrappers/SAMAlias.cs index 424aa6cb..7a71de27 100644 --- a/src/SharpHoundRPC/Wrappers/SAMAlias.cs +++ b/src/SharpHoundRPC/Wrappers/SAMAlias.cs @@ -17,12 +17,15 @@ public SAMAlias(SAMHandle handle) : base(handle) public Result> GetMembers() { var (status, members, count) = SAMMethods.SamGetMembersInAlias(Handle); - if (status.IsError()) + using (members) { - return status; - } + if (status.IsError()) + { + return status; + } - return Result>.Ok(members.GetData(count)); + return Result>.Ok(members.GetData(count)); + } } } } \ No newline at end of file diff --git a/src/SharpHoundRPC/Wrappers/SAMDomain.cs b/src/SharpHoundRPC/Wrappers/SAMDomain.cs index e3ec5ee2..2b311929 100644 --- a/src/SharpHoundRPC/Wrappers/SAMDomain.cs +++ b/src/SharpHoundRPC/Wrappers/SAMDomain.cs @@ -16,25 +16,33 @@ public SAMDomain(SAMHandle handle) : base(handle) public Result<(string Name, SharedEnums.SidNameUse Type)> LookupPrincipalByRid(int rid) { var (status, namePointer, usePointer) = SAMMethods.SamLookupIdsInDomain(Handle, rid); - if (status.IsError()) + using (namePointer) { - return status; - } + using (usePointer) + { + if (status.IsError()) + { + return status; + } - return (namePointer.GetData().ToString(), (SharedEnums.SidNameUse)usePointer.GetData()); + return (namePointer.GetData().ToString(), (SharedEnums.SidNameUse)usePointer.GetData()); + } + } } public Result> GetAliases() { - var enumerationContext = 0; var (status, ridPointer, count) = SAMMethods.SamEnumerateAliasesInDomain(Handle); - if (status.IsError()) + using (ridPointer) { - return status; - } + if (status.IsError()) + { + return status; + } - return Result>.Ok(ridPointer.GetEnumerable(count) - .Select(x => (x.Name.ToString(), x.Rid))); + return Result>.Ok(ridPointer.GetEnumerable(count) + .Select(x => (x.Name.ToString(), x.Rid))); + } } public Result OpenAlias(int rid, SAMEnums.AliasOpenFlags desiredAccess = SAMEnums.AliasOpenFlags.ListMembers) From b751b8e0e12c5a6ab458775a6feb88d9ef896619 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Mon, 3 Oct 2022 13:56:22 -0400 Subject: [PATCH 16/26] chore: cleanup test project --- test/unit/ACLProcessorTest.cs | 52 +++++++------- test/unit/CommonLibTest.csproj | 82 +++++++++++------------ test/unit/ComputerSessionProcessorTest.cs | 9 +-- test/unit/Facades/MockLDAPUtils.cs | 12 ++-- test/unit/GPOLocalGroupProcessorTest.cs | 14 ++-- test/unit/LDAPUtilsTest.cs | 15 +++-- test/unit/SPNProcessorsTest.cs | 9 ++- 7 files changed, 99 insertions(+), 94 deletions(-) diff --git a/test/unit/ACLProcessorTest.cs b/test/unit/ACLProcessorTest.cs index d095a3c1..f5654234 100644 --- a/test/unit/ACLProcessorTest.cs +++ b/test/unit/ACLProcessorTest.cs @@ -55,7 +55,7 @@ public void SanityCheck() public void ACLProcessor_IsACLProtected_NullNTSD_ReturnsFalse() { var processor = new ACLProcessor(new MockLDAPUtils(), true); - var result = processor.IsACLProtected((byte[])null); + var result = processor.IsACLProtected((byte[]) null); Assert.False(result); } @@ -206,7 +206,7 @@ public void ACLProcessor_ProcessGMSAReaders_Null_PrincipalID() var collection = new List(); mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IdentityReference()).Returns((string)null); + mockRule.Setup(x => x.IdentityReference()).Returns((string) null); collection.Add(mockRule.Object); mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) @@ -267,7 +267,7 @@ public void ACLProcessor_ProcessACL_Null_SID() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); var processor = new ACLProcessor(mockLDAPUtils.Object, true); @@ -287,7 +287,7 @@ public void ACLProcessor_ProcessACL_Null_ACE() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); var processor = new ACLProcessor(mockLDAPUtils.Object, true); @@ -309,7 +309,7 @@ public void ACLProcessor_ProcessACL_Deny_ACE() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); var processor = new ACLProcessor(mockLDAPUtils.Object, true); @@ -332,7 +332,7 @@ public void ACLProcessor_ProcessACL_Unmatched_Inheritance_ACE() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); var processor = new ACLProcessor(mockLDAPUtils.Object, true); @@ -351,12 +351,12 @@ public void ACLProcessor_ProcessACL_Null_SID_ACE() var collection = new List(); mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns((string)null); + mockRule.Setup(x => x.IdentityReference()).Returns((string) null); collection.Add(mockRule.Object); mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); var processor = new ACLProcessor(mockLDAPUtils.Object, true); @@ -386,7 +386,7 @@ public void ACLProcessor_ProcessACL_GenericAll_Unmatched_Guid() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -417,7 +417,7 @@ public void ACLProcessor_ProcessACL_GenericAll() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -454,7 +454,7 @@ public void ACLProcessor_ProcessACL_WriteDacl() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -491,7 +491,7 @@ public void ACLProcessor_ProcessACL_WriteOwner() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -528,7 +528,7 @@ public void ACLProcessor_ProcessACL_Self() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -565,7 +565,7 @@ public void ACLProcessor_ProcessACL_ExtendedRight_Domain_Unmatched() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -597,7 +597,7 @@ public void ACLProcessor_ProcessACL_ExtendedRight_Domain_DSReplicationGetChanges mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -634,7 +634,7 @@ public void ACLProcessor_ProcessACL_ExtendedRight_Domain_All() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -671,7 +671,7 @@ public void ACLProcessor_ProcessACL_ExtendedRight_Domain_DSReplicationGetChanges mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -709,7 +709,7 @@ public void ACLProcessor_ProcessACL_ExtendedRight_User_Unmatched() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -741,7 +741,7 @@ public void ACLProcessor_ProcessACL_ExtendedRight_User_UserForceChangePassword() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -778,7 +778,7 @@ public void ACLProcessor_ProcessACL_ExtendedRight_User_All() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -815,7 +815,7 @@ public void ACLProcessor_ProcessACL_ExtendedRight_Computer_NoLAPS() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -847,7 +847,7 @@ public void ACLProcessor_ProcessACL_ExtendedRight_Computer_All() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -889,7 +889,7 @@ public void ACLProcessor_ProcessACL_GenericWrite_Unmatched() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -921,7 +921,7 @@ public void ACLProcessor_ProcessACL_GenericWrite_User_All() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -958,7 +958,7 @@ public void ACLProcessor_ProcessACL_GenericWrite_User_WriteMember() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -997,7 +997,7 @@ public void ACLProcessor_ProcessACL_GenericWrite_Computer_WriteAllowedToAct() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); diff --git a/test/unit/CommonLibTest.csproj b/test/unit/CommonLibTest.csproj index abbbd657..62cd2f78 100644 --- a/test/unit/CommonLibTest.csproj +++ b/test/unit/CommonLibTest.csproj @@ -1,47 +1,47 @@ - - net5.0 - false - true - ..\..\docfx\coverage\ - OpenCover - + + net5.0 + false + true + ..\..\docfx\coverage\ + OpenCover + - - - - + + + + - - - - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + - - - - - - - + + + + + + + diff --git a/test/unit/ComputerSessionProcessorTest.cs b/test/unit/ComputerSessionProcessorTest.cs index 6bd981a4..c120610c 100644 --- a/test/unit/ComputerSessionProcessorTest.cs +++ b/test/unit/ComputerSessionProcessorTest.cs @@ -4,7 +4,6 @@ using Moq; using Newtonsoft.Json; using SharpHoundCommonLib; -using SharpHoundCommonLib.Exceptions; using SharpHoundCommonLib.OutputTypes; using SharpHoundCommonLib.Processors; using SharpHoundRPC.NetAPINative; @@ -39,7 +38,7 @@ public void Dispose() public async Task ComputerSessionProcessor_ReadUserSessions_FilteringWorks() { var mockNativeMethods = new Mock(); - + var apiResult = new NetSessionEnumResults[] { new("dfm", "\\\\192.168.92.110"), @@ -164,7 +163,8 @@ public async Task ComputerSessionProcessor_ReadUserSessions_ComputerAccessDenied { var mockNativeMethods = new Mock(); //mockNativeMethods.Setup(x => x.CallSamConnect(ref It.Ref.IsAny, out It.Ref.IsAny, It.IsAny(), ref It.Ref.IsAny)).Returns(NativeMethods.NtStatus.StatusAccessDenied); - mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(NetAPIEnums.NetAPIStatus.ErrorAccessDenied); + mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())) + .Returns(NetAPIEnums.NetAPIStatus.ErrorAccessDenied); var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); var test = await processor.ReadUserSessions("test", "test", "test"); Assert.False(test.Collected); @@ -176,7 +176,8 @@ public async Task ComputerSessionProcessor_ReadUserSessionsPrivileged_ComputerAc { var mockNativeMethods = new Mock(); //mockNativeMethods.Setup(x => x.CallSamConnect(ref It.Ref.IsAny, out It.Ref.IsAny, It.IsAny(), ref It.Ref.IsAny)).Returns(NativeMethods.NtStatus.StatusAccessDenied); - mockNativeMethods.Setup(x => x.NetWkstaUserEnum(It.IsAny())).Returns(NetAPIEnums.NetAPIStatus.ErrorAccessDenied); + mockNativeMethods.Setup(x => x.NetWkstaUserEnum(It.IsAny())) + .Returns(NetAPIEnums.NetAPIStatus.ErrorAccessDenied); var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); var test = processor.ReadUserSessionsPrivileged("test", "test", "test"); Assert.False(test.Collected); diff --git a/test/unit/Facades/MockLDAPUtils.cs b/test/unit/Facades/MockLDAPUtils.cs index 8d92e58a..7f184371 100644 --- a/test/unit/Facades/MockLDAPUtils.cs +++ b/test/unit/Facades/MockLDAPUtils.cs @@ -10,7 +10,7 @@ using SharpHoundCommonLib; using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.OutputTypes; -using Domain = SharpHoundCommonLib.OutputTypes.Domain; +using Domain = System.DirectoryServices.ActiveDirectory.Domain; namespace CommonLibTest.Facades { @@ -40,10 +40,10 @@ public string[] GetUserGlobalCatalogMatches(string name) name = name.ToLower(); return name switch { - "dfm" => new[] { "S-1-5-21-3130019616-2776909439-2417379446-1105" }, + "dfm" => new[] {"S-1-5-21-3130019616-2776909439-2417379446-1105"}, "administrator" => new[] {"S-1-5-21-3130019616-2776909439-2417379446-500", "S-1-5-21-3084884204-958224920-2707782874-500"}, - "admin" => new[] { "S-1-5-21-3130019616-2776909439-2417379446-2116" }, + "admin" => new[] {"S-1-5-21-3130019616-2776909439-2417379446-2116"}, _ => Array.Empty() }; } @@ -711,7 +711,7 @@ public void AddDomainController(string domainControllerSID) _domainControllers.TryAdd(domainControllerSID, new byte()); } - public System.DirectoryServices.ActiveDirectory.Domain GetDomain(string domainName = null) + public Domain GetDomain(string domainName = null) { throw new NotImplementedException(); } @@ -727,7 +727,7 @@ public IEnumerable GetWellKnownPrincipalOutput(string domain) Label.Computer => new Computer(), Label.Group => new Group(), Label.GPO => new GPO(), - Label.Domain => new Domain(), + Label.Domain => new SharpHoundCommonLib.OutputTypes.Domain(), Label.OU => new OU(), Label.Container => new Container(), _ => throw new ArgumentOutOfRangeException() @@ -1049,7 +1049,7 @@ public ActiveDirectorySecurityDescriptor MakeSecurityDescriptor() private Group GetBaseEnterpriseDC() { - var g = new Group { ObjectIdentifier = "TESTLAB.LOCAL-S-1-5-9".ToUpper() }; + var g = new Group {ObjectIdentifier = "TESTLAB.LOCAL-S-1-5-9".ToUpper()}; g.Properties.Add("name", "ENTERPRISE DOMAIN CONTROLLERS@TESTLAB.LOCAL".ToUpper()); return g; } diff --git a/test/unit/GPOLocalGroupProcessorTest.cs b/test/unit/GPOLocalGroupProcessorTest.cs index ef9e0496..9cf1f6d6 100644 --- a/test/unit/GPOLocalGroupProcessorTest.cs +++ b/test/unit/GPOLocalGroupProcessorTest.cs @@ -145,13 +145,13 @@ public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups_Null_Gpcfilesyspath( var mockSearchResults = new List(); mockSearchResults.Add(mockSearchResultEntry.Object); mockLDAPUtils.Setup(x => x.QueryLDAP( - new LDAPQueryOptions - { - Filter = "(samaccounttype=805306369)", - Scope = SearchScope.Subtree, - Properties = CommonProperties.ObjectSID, - AdsPath = null - })) + new LDAPQueryOptions + { + Filter = "(samaccounttype=805306369)", + Scope = SearchScope.Subtree, + Properties = CommonProperties.ObjectSID, + AdsPath = null + })) .Returns(mockSearchResults.ToArray()); var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); diff --git a/test/unit/LDAPUtilsTest.cs b/test/unit/LDAPUtilsTest.cs index 5e39cf4e..c68cc132 100644 --- a/test/unit/LDAPUtilsTest.cs +++ b/test/unit/LDAPUtilsTest.cs @@ -1,11 +1,11 @@ using System; +using System.DirectoryServices.Protocols; +using System.Threading; using CommonLibTest.Facades; using Moq; using SharpHoundCommonLib; using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.Exceptions; -using System.DirectoryServices.Protocols; -using System.Threading; using Xunit; using Xunit.Abstractions; @@ -120,10 +120,11 @@ public void QueryLDAP_With_Exception() Assert.Throws( () => { - foreach (var sre in _utils.QueryLDAP(null, new SearchScope(), null, new CancellationToken(), null, false, false, null, false, false, true)) + foreach (var sre in _utils.QueryLDAP(null, new SearchScope(), null, new CancellationToken(), null, + false, false, null, false, false, true)) { // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling - }; + } }); Assert.Throws( @@ -132,7 +133,7 @@ public void QueryLDAP_With_Exception() foreach (var sre in _utils.QueryLDAP(options)) { // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling - }; + } }); } @@ -152,7 +153,7 @@ public void QueryLDAP_Without_Exception() foreach (var sre in _utils.QueryLDAP(null, new SearchScope(), null, new CancellationToken())) { // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling - }; + } }); Assert.Null(exception); @@ -162,7 +163,7 @@ public void QueryLDAP_Without_Exception() foreach (var sre in _utils.QueryLDAP(options)) { // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling - }; + } }); Assert.Null(exception); } diff --git a/test/unit/SPNProcessorsTest.cs b/test/unit/SPNProcessorsTest.cs index 2c6dc1cb..0ba7411b 100644 --- a/test/unit/SPNProcessorsTest.cs +++ b/test/unit/SPNProcessorsTest.cs @@ -29,7 +29,8 @@ public async Task ReadSPNTargets_NoPortSupplied_ParsedCorrectly() var expected = new SPNPrivilege { - ComputerSID = "S-1-5-21-3130019616-2776909439-2417379446-1001", Port = 1433, Service = EdgeNames.SQLAdmin + ComputerSID = "S-1-5-21-3130019616-2776909439-2417379446-1001", Port = 1433, + Service = EdgeNames.SQLAdmin }; await foreach (var actual in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) @@ -49,7 +50,8 @@ public async Task ReadSPNTargets_BadPortSupplied_ParsedCorrectly() var expected = new SPNPrivilege { - ComputerSID = "S-1-5-21-3130019616-2776909439-2417379446-1001", Port = 1433, Service = EdgeNames.SQLAdmin + ComputerSID = "S-1-5-21-3130019616-2776909439-2417379446-1001", Port = 1433, + Service = EdgeNames.SQLAdmin }; await foreach (var actual in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) @@ -69,7 +71,8 @@ public async void ReadSPNTargets_SuppliedPort_ParsedCorrectly() var expected = new SPNPrivilege { - ComputerSID = "S-1-5-21-3130019616-2776909439-2417379446-1001", Port = 2345, Service = EdgeNames.SQLAdmin + ComputerSID = "S-1-5-21-3130019616-2776909439-2417379446-1001", Port = 2345, + Service = EdgeNames.SQLAdmin }; await foreach (var actual in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) From 5c4e173df303b1d7416c8a327decbd2a7492fde9 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Mon, 3 Oct 2022 14:00:03 -0400 Subject: [PATCH 17/26] chore: code cleanup RPC Lib --- src/SharpHoundRPC/Extensions.cs | 2 +- src/SharpHoundRPC/NetAPINative/NetAPIEnums.cs | 44 ++++++------ .../NetAPINative/NetAPIMethods.cs | 30 +++----- .../NetAPINative/NetAPIResult.cs | 16 ++++- .../NetAPINative/NetAPIReturns.cs | 6 +- .../NetAPINative/NetAPIStructs.cs | 8 +-- src/SharpHoundRPC/Result.cs | 20 ++++-- src/SharpHoundRPC/SAMRPCNative/SAMEnums.cs | 30 ++++---- src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs | 15 ++-- src/SharpHoundRPC/SharpHoundRPC.csproj | 4 +- src/SharpHoundRPC/Wrappers/LSAPolicy.cs | 8 ++- src/SharpHoundRPC/Wrappers/SAMAlias.cs | 7 +- src/SharpHoundRPC/Wrappers/SAMDomain.cs | 31 +++----- src/SharpHoundRPC/Wrappers/SAMServer.cs | 70 +++++++------------ 14 files changed, 136 insertions(+), 155 deletions(-) diff --git a/src/SharpHoundRPC/Extensions.cs b/src/SharpHoundRPC/Extensions.cs index 2e4f8bf5..50656bb1 100644 --- a/src/SharpHoundRPC/Extensions.cs +++ b/src/SharpHoundRPC/Extensions.cs @@ -31,6 +31,6 @@ public static byte[] GetBytes(this SecurityIdentifier identifier) var bytes = new byte[identifier.BinaryLength]; identifier.GetBinaryForm(bytes, 0); return bytes; - } + } } } \ No newline at end of file diff --git a/src/SharpHoundRPC/NetAPINative/NetAPIEnums.cs b/src/SharpHoundRPC/NetAPINative/NetAPIEnums.cs index 7c8cbca9..7410bd69 100644 --- a/src/SharpHoundRPC/NetAPINative/NetAPIEnums.cs +++ b/src/SharpHoundRPC/NetAPINative/NetAPIEnums.cs @@ -4,6 +4,28 @@ namespace SharpHoundRPC.NetAPINative { public class NetAPIEnums { + [Flags] + public enum DSGETDCNAME_FLAGS : uint + { + DS_FORCE_REDISCOVERY = 0x00000001, + DS_DIRECTORY_SERVICE_REQUIRED = 0x00000010, + DS_DIRECTORY_SERVICE_PREFERRED = 0x00000020, + DS_GC_SERVER_REQUIRED = 0x00000040, + DS_PDC_REQUIRED = 0x00000080, + DS_BACKGROUND_ONLY = 0x00000100, + DS_IP_REQUIRED = 0x00000200, + DS_KDC_REQUIRED = 0x00000400, + DS_TIMESERV_REQUIRED = 0x00000800, + DS_WRITABLE_REQUIRED = 0x00001000, + DS_GOOD_TIMESERV_PREFERRED = 0x00002000, + DS_AVOID_SELF = 0x00004000, + DS_ONLY_LDAP_NEEDED = 0x00008000, + DS_IS_FLAT_NAME = 0x00010000, + DS_IS_DNS_NAME = 0x00020000, + DS_RETURN_DNS_NAME = 0x40000000, + DS_RETURN_FLAT_NAME = 0x80000000 + } + public enum NetAPIStatus : uint { Success = 0, @@ -63,27 +85,5 @@ public enum NetAPIStatus : uint /// RpcERemoteDisabled = 2147549468 // 0x8001011C } - - [Flags] - public enum DSGETDCNAME_FLAGS : uint - { - DS_FORCE_REDISCOVERY = 0x00000001, - DS_DIRECTORY_SERVICE_REQUIRED = 0x00000010, - DS_DIRECTORY_SERVICE_PREFERRED = 0x00000020, - DS_GC_SERVER_REQUIRED = 0x00000040, - DS_PDC_REQUIRED = 0x00000080, - DS_BACKGROUND_ONLY = 0x00000100, - DS_IP_REQUIRED = 0x00000200, - DS_KDC_REQUIRED = 0x00000400, - DS_TIMESERV_REQUIRED = 0x00000800, - DS_WRITABLE_REQUIRED = 0x00001000, - DS_GOOD_TIMESERV_PREFERRED = 0x00002000, - DS_AVOID_SELF = 0x00004000, - DS_ONLY_LDAP_NEEDED = 0x00008000, - DS_IS_FLAT_NAME = 0x00010000, - DS_IS_DNS_NAME = 0x00020000, - DS_RETURN_DNS_NAME = 0x40000000, - DS_RETURN_FLAT_NAME = 0x80000000 - } } } \ No newline at end of file diff --git a/src/SharpHoundRPC/NetAPINative/NetAPIMethods.cs b/src/SharpHoundRPC/NetAPINative/NetAPIMethods.cs index d24d5f73..c26eeca0 100644 --- a/src/SharpHoundRPC/NetAPINative/NetAPIMethods.cs +++ b/src/SharpHoundRPC/NetAPINative/NetAPIMethods.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; @@ -12,7 +11,7 @@ public static class NetAPIMethods private const int NetWkstaUserEnumQueryLevel = 1; private const int NetSessionEnumLevel = 10; private const int NetWkstaGetInfoQueryLevel = 100; - + [DllImport("netapi32.dll")] internal static extern NetAPIEnums.NetAPIStatus NetApiBufferFree( IntPtr buffer); @@ -26,15 +25,14 @@ public static NetAPIResult> NetWkstaUserEnu using (buffer) { if (result != NetAPIEnums.NetAPIStatus.Success && result != NetAPIEnums.NetAPIStatus.ErrorMoreData) - { return result; - } - - return NetAPIResult>.Ok(buffer.GetEnumerable(entriesRead) + + return NetAPIResult>.Ok(buffer + .GetEnumerable(entriesRead) .Select(x => new NetWkstaUserEnumResults(x.Username, x.LogonDomain))); } } - + [DllImport("netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern NetAPIEnums.NetAPIStatus NetWkstaUserEnum( string servername, @@ -54,11 +52,10 @@ public static NetAPIResult> NetSessionEnum(st using (buffer) { if (result != NetAPIEnums.NetAPIStatus.Success && result != NetAPIEnums.NetAPIStatus.ErrorMoreData) - { return result; - } - - return NetAPIResult>.Ok(buffer.GetEnumerable(entriesRead) + + return NetAPIResult>.Ok(buffer + .GetEnumerable(entriesRead) .Select(x => new NetSessionEnumResults(x.Username, x.CName))); } } @@ -81,10 +78,7 @@ private static extern NetAPIEnums.NetAPIStatus NetSessionEnum( using (buffer) { - if (result != NetAPIEnums.NetAPIStatus.Success) - { - return result; - } + if (result != NetAPIEnums.NetAPIStatus.Success) return result; return buffer.GetData(); } @@ -104,14 +98,10 @@ private static extern NetAPIEnums.NetAPIStatus NetWkstaGetInfo( NetAPIEnums.DSGETDCNAME_FLAGS.DS_RETURN_DNS_NAME), out var buffer); using (buffer) { - if (result != NetAPIEnums.NetAPIStatus.Success) - { - return result; - } + if (result != NetAPIEnums.NetAPIStatus.Success) return result; return buffer.GetData(); } - } [DllImport("Netapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] diff --git a/src/SharpHoundRPC/NetAPINative/NetAPIResult.cs b/src/SharpHoundRPC/NetAPINative/NetAPIResult.cs index 1c68d639..d5d1f461 100644 --- a/src/SharpHoundRPC/NetAPINative/NetAPIResult.cs +++ b/src/SharpHoundRPC/NetAPINative/NetAPIResult.cs @@ -8,10 +8,20 @@ public class NetAPIResult public string Error { get; private set; } public bool IsFailed => !IsSuccess; - public static NetAPIResult Ok(T value) => new() {IsSuccess = true, Value = value}; + public static NetAPIResult Ok(T value) + { + return new NetAPIResult {IsSuccess = true, Value = value}; + } - public static NetAPIResult Fail(NetAPIEnums.NetAPIStatus status) => new() {Status = status}; - public static NetAPIResult Fail(string error) => new() {Error = error}; + public static NetAPIResult Fail(NetAPIEnums.NetAPIStatus status) + { + return new NetAPIResult {Status = status}; + } + + public static NetAPIResult Fail(string error) + { + return new NetAPIResult {Error = error}; + } public static implicit operator NetAPIResult(T input) { diff --git a/src/SharpHoundRPC/NetAPINative/NetAPIReturns.cs b/src/SharpHoundRPC/NetAPINative/NetAPIReturns.cs index d000fc0a..9ccf117d 100644 --- a/src/SharpHoundRPC/NetAPINative/NetAPIReturns.cs +++ b/src/SharpHoundRPC/NetAPINative/NetAPIReturns.cs @@ -1,6 +1,4 @@ -using System.Diagnostics.CodeAnalysis; - -namespace SharpHoundRPC.NetAPINative +namespace SharpHoundRPC.NetAPINative { public class NetWkstaUserEnumResults { @@ -21,7 +19,7 @@ public NetSessionEnumResults(string username, string cname) Username = username; ComputerName = cname; } - + public string Username { get; } public string ComputerName { get; } } diff --git a/src/SharpHoundRPC/NetAPINative/NetAPIStructs.cs b/src/SharpHoundRPC/NetAPINative/NetAPIStructs.cs index 4357ecda..adf5b277 100644 --- a/src/SharpHoundRPC/NetAPINative/NetAPIStructs.cs +++ b/src/SharpHoundRPC/NetAPINative/NetAPIStructs.cs @@ -13,7 +13,7 @@ public struct WkstaUserInfo1 public string OtherDomains; public string LogonServer; } - + [StructLayout(LayoutKind.Sequential)] public struct SessionInfo10 { @@ -22,7 +22,7 @@ public struct SessionInfo10 public uint Time; public uint IdleTIme; } - + [StructLayout(LayoutKind.Sequential)] public struct WorkstationInfo100 { @@ -32,13 +32,13 @@ public struct WorkstationInfo100 public int MajorVersion; public int MinorVersion; } - + [StructLayout(LayoutKind.Sequential)] public class GuidClass { public Guid TheGuid; } - + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct DomainControllerInfo { diff --git a/src/SharpHoundRPC/Result.cs b/src/SharpHoundRPC/Result.cs index a85843f3..ca92045a 100644 --- a/src/SharpHoundRPC/Result.cs +++ b/src/SharpHoundRPC/Result.cs @@ -1,6 +1,4 @@ -using SharpHoundRPC.NetAPINative; - -namespace SharpHoundRPC +namespace SharpHoundRPC { public class Result { @@ -10,10 +8,20 @@ public class Result public string Error { get; private set; } public bool IsFailed => !IsSuccess; - public static Result Ok(T value) => new() {IsSuccess = true, Value = value}; + public static Result Ok(T value) + { + return new() {IsSuccess = true, Value = value}; + } + + public static Result Fail(NtStatus status) + { + return new() {Status = status}; + } - public static Result Fail(NtStatus status) => new() {Status = status}; - public static Result Fail(string error) => new() {Error = error}; + public static Result Fail(string error) + { + return new() {Error = error}; + } public static implicit operator Result(T input) { diff --git a/src/SharpHoundRPC/SAMRPCNative/SAMEnums.cs b/src/SharpHoundRPC/SAMRPCNative/SAMEnums.cs index 9aacc53b..c6e60290 100644 --- a/src/SharpHoundRPC/SAMRPCNative/SAMEnums.cs +++ b/src/SharpHoundRPC/SAMRPCNative/SAMEnums.cs @@ -5,6 +5,21 @@ namespace SharpHoundRPC.SAMRPCNative { public class SAMEnums { + [Flags] + [SuppressMessage("ReSharper", "UnusedMember.Local")] + public enum AliasOpenFlags + { + AddMember = 0x1, + RemoveMember = 0x2, + ListMembers = 0x4, + ReadInfo = 0x8, + WriteAccount = 0x10, + AllAccess = 0xf001f, + Read = 0x20004, + Write = 0x20013, + Execute = 0x20008 + } + [Flags] [SuppressMessage("ReSharper", "UnusedMember.Local")] public enum DomainAccessMask @@ -42,21 +57,6 @@ public enum SamAccessMasks SamServerExecute = 0x20021 } - [Flags] - [SuppressMessage("ReSharper", "UnusedMember.Local")] - public enum AliasOpenFlags - { - AddMember = 0x1, - RemoveMember = 0x2, - ListMembers = 0x4, - ReadInfo = 0x8, - WriteAccount = 0x10, - AllAccess = 0xf001f, - Read = 0x20004, - Write = 0x20013, - Execute = 0x20008 - } - [Flags] [SuppressMessage("ReSharper", "UnusedMember.Local")] internal enum SamAliasFlags diff --git a/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs b/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs index 51a6f4a3..45f37164 100644 --- a/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs +++ b/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs @@ -11,7 +11,8 @@ namespace SharpHoundRPC.SAMRPCNative [SuppressUnmanagedCodeSecurity] public static class SAMMethods { - internal static (NtStatus status, SAMHandle handle) SamConnect(string serverName, SAMEnums.SamAccessMasks requestedConnectAccess) + internal static (NtStatus status, SAMHandle handle) SamConnect(string serverName, + SAMEnums.SamAccessMasks requestedConnectAccess) { var us = new SharedStructs.UnicodeString(serverName); var objectAttributes = default(SAMStructs.ObjectAttributes); @@ -30,7 +31,8 @@ private static extern NtStatus SamConnect( ref SAMStructs.ObjectAttributes objectAttributes ); - internal static (NtStatus status, IEnumerable domainRids) SamEnumerateDomainsInSamServer(SAMHandle serverHandle) + internal static (NtStatus status, IEnumerable domainRids) + SamEnumerateDomainsInSamServer(SAMHandle serverHandle) { var enumerationContext = 0; var status = @@ -38,7 +40,9 @@ internal static (NtStatus status, IEnumerable doma out var count); using (domains) + { return (status, domains.GetEnumerable(count)); + } } [DllImport("samlib.dll", CharSet = CharSet.Unicode)] @@ -50,13 +54,16 @@ private static extern NtStatus SamEnumerateDomainsInSamServer( out int count ); - internal static (NtStatus status, SecurityIdentifier securityIdentifier) SamLookupDomainInSamServer(SAMHandle serverHandle, string name) + internal static (NtStatus status, SecurityIdentifier securityIdentifier) SamLookupDomainInSamServer( + SAMHandle serverHandle, string name) { var us = new SharedStructs.UnicodeString(name); var status = SamLookupDomainInSamServer(serverHandle, ref us, out var sid); using (sid) + { return (status, sid.GetData()); + } } [DllImport("samlib.dll", CharSet = CharSet.Unicode)] @@ -116,7 +123,7 @@ internal static (NtStatus status, SAMPointer pointer, int count) SamEnumerateAli out var count); return (status, buffer, count); } - + [DllImport("samlib.dll", CharSet = CharSet.Unicode)] private static extern NtStatus SamEnumerateAliasesInDomain( SAMHandle domainHandle, diff --git a/src/SharpHoundRPC/SharpHoundRPC.csproj b/src/SharpHoundRPC/SharpHoundRPC.csproj index 7c1f96bd..98e82a66 100644 --- a/src/SharpHoundRPC/SharpHoundRPC.csproj +++ b/src/SharpHoundRPC/SharpHoundRPC.csproj @@ -17,9 +17,9 @@ full - + - + \ No newline at end of file diff --git a/src/SharpHoundRPC/Wrappers/LSAPolicy.cs b/src/SharpHoundRPC/Wrappers/LSAPolicy.cs index 005159c8..8ed41311 100644 --- a/src/SharpHoundRPC/Wrappers/LSAPolicy.cs +++ b/src/SharpHoundRPC/Wrappers/LSAPolicy.cs @@ -56,13 +56,15 @@ public Result> GetPrincipalsWithPrivilege(string { if (status.IsError()) return status; - var (lookupStatus, referencedDomains, names, lookupCount) = LSAMethods.LsaLookupSids(Handle, sids, count); + var (lookupStatus, referencedDomains, names, lookupCount) = + LSAMethods.LsaLookupSids(Handle, sids, count); if (lookupStatus.IsError()) { referencedDomains.Dispose(); names.Dispose(); return lookupStatus; } + var translatedNames = names.GetEnumerable(count).ToArray(); var domainList = referencedDomains.GetData(); var safeDomains = new LSAPointer(domainList.Domains); @@ -77,7 +79,7 @@ public Result> GetPrincipalsWithPrivilege(string referencedDomains.Dispose(); names.Dispose(); safeDomains.Dispose(); - + return ret; } } @@ -135,7 +137,7 @@ public Result> GetPrincipalsWithPrivilege(string referencedDomains.Dispose(); names.Dispose(); safeDomains.Dispose(); - + return ret.ToArray(); } } diff --git a/src/SharpHoundRPC/Wrappers/SAMAlias.cs b/src/SharpHoundRPC/Wrappers/SAMAlias.cs index 7a71de27..c41de0df 100644 --- a/src/SharpHoundRPC/Wrappers/SAMAlias.cs +++ b/src/SharpHoundRPC/Wrappers/SAMAlias.cs @@ -19,11 +19,8 @@ public Result> GetMembers() var (status, members, count) = SAMMethods.SamGetMembersInAlias(Handle); using (members) { - if (status.IsError()) - { - return status; - } - + if (status.IsError()) return status; + return Result>.Ok(members.GetData(count)); } } diff --git a/src/SharpHoundRPC/Wrappers/SAMDomain.cs b/src/SharpHoundRPC/Wrappers/SAMDomain.cs index 2b311929..449eb5ed 100644 --- a/src/SharpHoundRPC/Wrappers/SAMDomain.cs +++ b/src/SharpHoundRPC/Wrappers/SAMDomain.cs @@ -20,12 +20,10 @@ public SAMDomain(SAMHandle handle) : base(handle) { using (usePointer) { - if (status.IsError()) - { - return status; - } + if (status.IsError()) return status; - return (namePointer.GetData().ToString(), (SharedEnums.SidNameUse)usePointer.GetData()); + return (namePointer.GetData().ToString(), + (SharedEnums.SidNameUse) usePointer.GetData()); } } } @@ -35,23 +33,19 @@ public SAMDomain(SAMHandle handle) : base(handle) var (status, ridPointer, count) = SAMMethods.SamEnumerateAliasesInDomain(Handle); using (ridPointer) { - if (status.IsError()) - { - return status; - } + if (status.IsError()) return status; - return Result>.Ok(ridPointer.GetEnumerable(count) + return Result>.Ok(ridPointer + .GetEnumerable(count) .Select(x => (x.Name.ToString(), x.Rid))); } } - public Result OpenAlias(int rid, SAMEnums.AliasOpenFlags desiredAccess = SAMEnums.AliasOpenFlags.ListMembers) + public Result OpenAlias(int rid, + SAMEnums.AliasOpenFlags desiredAccess = SAMEnums.AliasOpenFlags.ListMembers) { var (status, aliasHandle) = SAMMethods.SamOpenAlias(Handle, desiredAccess, rid); - if (status.IsError()) - { - return status; - } + if (status.IsError()) return status; return new SAMAlias(aliasHandle); } @@ -59,11 +53,8 @@ public Result OpenAlias(int rid, SAMEnums.AliasOpenFlags desiredAccess public Result OpenAlias(string name) { var getAliasesResult = GetAliases(); - if (getAliasesResult.IsFailed) - { - return getAliasesResult.Status; - } - + if (getAliasesResult.IsFailed) return getAliasesResult.Status; + foreach (var alias in getAliasesResult.Value) if (alias.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) return OpenAlias(alias.Rid); diff --git a/src/SharpHoundRPC/Wrappers/SAMServer.cs b/src/SharpHoundRPC/Wrappers/SAMServer.cs index 88d6f867..ef5d6a63 100644 --- a/src/SharpHoundRPC/Wrappers/SAMServer.cs +++ b/src/SharpHoundRPC/Wrappers/SAMServer.cs @@ -10,9 +10,8 @@ namespace SharpHoundRPC.Wrappers { public class SAMServer : SAMBase { - private SecurityIdentifier _cachedMachineSid; private readonly ConcurrentDictionary _domainHandleCache; - public string ComputerName { get; private set; } + private SecurityIdentifier _cachedMachineSid; public SAMServer(SAMHandle handle, string computerName) : base(handle) { @@ -20,6 +19,8 @@ public SAMServer(SAMHandle handle, string computerName) : base(handle) ComputerName = computerName; } + public string ComputerName { get; } + public static Result OpenServer(string computerName, SAMEnums.SamAccessMasks requestedConnectAccess = SAMEnums.SamAccessMasks.SamServerConnect | SAMEnums.SamAccessMasks @@ -36,7 +37,9 @@ public static Result OpenServer(string computerName, SAMEnums.SamAcce public Result> GetDomains() { var (status, rids) = SAMMethods.SamEnumerateDomainsInSamServer(Handle); - return status.IsError() ? status : Result>.Ok(rids.Select(x => (x.Name.ToString(), x.Rid))); + return status.IsError() + ? status + : Result>.Ok(rids.Select(x => (x.Name.ToString(), x.Rid))); } public Result LookupDomain(string name) @@ -55,10 +58,7 @@ public Result GetMachineSid(string testName = null) if (testName != null) { var result = LookupDomain(testName); - if (result.IsSuccess) - { - sid = result.Value; - } + if (result.IsSuccess) sid = result.Value; } if (sid == null) @@ -67,10 +67,7 @@ public Result GetMachineSid(string testName = null) if (domainResult.IsSuccess) { var result = LookupDomain(domainResult.Value.FirstOrDefault().Name); - if (result.IsSuccess) - { - sid = result.Value; - } + if (result.IsSuccess) sid = result.Value; } } @@ -83,17 +80,15 @@ public bool IsDomainController(SecurityIdentifier domainMachineSid) { return domainMachineSid.AccountDomainSid.Equals(GetMachineSid().Value.AccountDomainSid); } - - public Result<(string Name, SharedEnums.SidNameUse Type)> LookupPrincipalBySid(SecurityIdentifier securityIdentifier) + + public Result<(string Name, SharedEnums.SidNameUse Type)> LookupPrincipalBySid( + SecurityIdentifier securityIdentifier) { var openDomainResult = OpenDomain(securityIdentifier); - if (openDomainResult.IsFailed) - { - return $"OpenDomain returned {openDomainResult.Status}"; - } + if (openDomainResult.IsFailed) return $"OpenDomain returned {openDomainResult.Status}"; var domain = openDomainResult.Value; - + return domain.LookupPrincipalByRid(securityIdentifier.Rid()); } @@ -102,26 +97,17 @@ public Result OpenDomain(string domainName, SAMEnums.DomainAccessMask SAMEnums.DomainAccessMask.ListAccounts) { var lookupResult = LookupDomain(domainName); - if (lookupResult.IsFailed) - { - return $"LookupDomain returned {lookupResult.Error}"; - } + if (lookupResult.IsFailed) return $"LookupDomain returned {lookupResult.Error}"; var sid = lookupResult.Value; - - if (_domainHandleCache.TryGetValue(sid.Value, out var domain)) - { - return domain; - } + + if (_domainHandleCache.TryGetValue(sid.Value, out var domain)) return domain; var (status, domainHandle) = SAMMethods.SamOpenDomain(Handle, requestedDomainAccess, sid.GetBytes()); - if (status.IsError()) - { - return status; - } + if (status.IsError()) return status; domain = new SAMDomain(domainHandle); - + _domainHandleCache.TryAdd(sid.Value, domain); return domain; } @@ -131,16 +117,11 @@ public Result OpenDomain(SecurityIdentifier securityIdentifier, SAMEnums.DomainAccessMask.Lookup | SAMEnums.DomainAccessMask.ListAccounts) { - if (_domainHandleCache.TryGetValue(securityIdentifier.Value, out var domain)) - { - return domain; - } - - var (status, domainHandle) = SAMMethods.SamOpenDomain(Handle, requestedDomainAccess, securityIdentifier.GetBytes()); - if (status.IsError()) - { - return status.ToString(); - } + if (_domainHandleCache.TryGetValue(securityIdentifier.Value, out var domain)) return domain; + + var (status, domainHandle) = + SAMMethods.SamOpenDomain(Handle, requestedDomainAccess, securityIdentifier.GetBytes()); + if (status.IsError()) return status.ToString(); domain = new SAMDomain(domainHandle); _domainHandleCache.TryAdd(securityIdentifier.Value, domain); @@ -149,10 +130,7 @@ public Result OpenDomain(SecurityIdentifier securityIdentifier, protected override void Dispose(bool disposing) { - foreach (var domainHandle in _domainHandleCache.Values) - { - domainHandle.Dispose(); - } + foreach (var domainHandle in _domainHandleCache.Values) domainHandle.Dispose(); base.Dispose(disposing); } } From 414aa53abe7a21a13b7cbe54299ec0db81881e4a Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Mon, 3 Oct 2022 14:01:19 -0400 Subject: [PATCH 18/26] chore: code cleanup commonlib --- .../Exceptions/LDAPQueryException.cs | 16 +- src/CommonLib/LDAPUtils.cs | 213 ++++++++---------- src/CommonLib/NativeMethods.cs | 3 +- .../Processors/ComputerSessionProcessor.cs | 11 +- .../Processors/GPOLocalGroupProcessor.cs | 2 +- .../Processors/LDAPPropertyProcessor.cs | 3 +- .../Processors/LocalGroupProcessor.cs | 32 +-- .../UserRightsAssignmentProcessor.cs | 29 +-- src/CommonLib/SharpHoundCommonLib.csproj | 12 +- 9 files changed, 160 insertions(+), 161 deletions(-) diff --git a/src/CommonLib/Exceptions/LDAPQueryException.cs b/src/CommonLib/Exceptions/LDAPQueryException.cs index 2e66668e..b463a51e 100644 --- a/src/CommonLib/Exceptions/LDAPQueryException.cs +++ b/src/CommonLib/Exceptions/LDAPQueryException.cs @@ -4,8 +4,16 @@ namespace SharpHoundCommonLib.Exceptions { public class LDAPQueryException : Exception { - public LDAPQueryException() { } - public LDAPQueryException(string message) : base(message) { } - public LDAPQueryException(string message, Exception inner) : base(message, inner) { } + public LDAPQueryException() + { + } + + public LDAPQueryException(string message) : base(message) + { + } + + public LDAPQueryException(string message, Exception inner) : base(message, inner) + { + } } -} +} \ No newline at end of file diff --git a/src/CommonLib/LDAPUtils.cs b/src/CommonLib/LDAPUtils.cs index 6da9aef9..01f55b29 100644 --- a/src/CommonLib/LDAPUtils.cs +++ b/src/CommonLib/LDAPUtils.cs @@ -13,10 +13,10 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using SharpHoundCommonLib.Enums; +using SharpHoundCommonLib.Exceptions; using SharpHoundCommonLib.LDAPQueries; using SharpHoundCommonLib.OutputTypes; using SharpHoundCommonLib.Processors; -using SharpHoundCommonLib.Exceptions; using SharpHoundRPC.NetAPINative; using Domain = System.DirectoryServices.ActiveDirectory.Domain; using SearchScope = System.DirectoryServices.Protocols.SearchScope; @@ -184,7 +184,7 @@ public string[] GetUserGlobalCatalogMatches(string name) return sids; var query = new LDAPFilter().AddUsers($"samaccountname={tempName}").GetFilter(); - var results = QueryLDAP(query, SearchScope.Subtree, new[] { "objectsid" }, globalCatalog: true) + var results = QueryLDAP(query, SearchScope.Subtree, new[] {"objectsid"}, globalCatalog: true) .Select(x => x.GetSid()).Where(x => x != null).ToArray(); Cache.AddGCCache(tempName, results); return results; @@ -360,7 +360,7 @@ public IEnumerable DoRangedRetrieval(string distinguishedName, string at var currentRange = $"{baseString};range={index}-*"; var complete = false; - var searchRequest = CreateSearchRequest($"{attributeName}=*", SearchScope.Base, new[] { currentRange }, + var searchRequest = CreateSearchRequest($"{attributeName}=*", SearchScope.Base, new[] {currentRange}, domainName, distinguishedName); if (searchRequest == null) @@ -370,7 +370,7 @@ public IEnumerable DoRangedRetrieval(string distinguishedName, string at { SearchResponse response; - response = (SearchResponse)conn.SendRequest(searchRequest); + response = (SearchResponse) conn.SendRequest(searchRequest); //If we ever get more than one response from here, something is horribly wrong if (response?.Entries.Count == 1) @@ -634,75 +634,6 @@ public TypedPrincipal ResolveDistinguishedName(string dn) }; } - /// - /// Setup LDAP query for filter - /// - /// LDAP filter - /// SearchScope to query - /// LDAP properties to fetch for each object - /// Include the DACL and Owner values in the NTSecurityDescriptor - /// Domain to query - /// Include deleted objects - /// ADS path to limit the query too - /// Use the global catalog instead of the regular LDAP server - /// - /// Skip the connection cache and force a new connection. You must dispose of this connection - /// yourself. - /// - /// Tuple of LdapConnection, SearchRequest, PageResultRequestControl and LDAPQueryException - public Tuple SetupLDAPQueryFilter(string ldapFilter, - SearchScope scope, string[] props, bool includeAcl = false, string domainName = null, bool showDeleted = false, - string adsPath = null, bool globalCatalog = false, bool skipCache = false) - { - _log.LogTrace("Creating ldap connection for {Target} with filter {Filter}", - globalCatalog ? "Global Catalog" : "DC", ldapFilter); - var task = globalCatalog - ? Task.Run(() => CreateGlobalCatalogConnection(domainName, _ldapConfig.AuthType)) - : Task.Run(() => CreateLDAPConnection(domainName, skipCache, _ldapConfig.AuthType)); - - LdapConnection conn; - try - { - conn = task.ConfigureAwait(false).GetAwaiter().GetResult(); - } - catch (Exception e) - { - var errorString = String.Format("Exception getting LDAP connection for {0} and domain {1}", ldapFilter, - domainName ?? "Default Domain"); - return Tuple.Create( - null, null, null, new LDAPQueryException(errorString, e)); - } - - if (conn == null) - { - var errorString = String.Format("LDAP connection is null for filter {0} and domain {1}", ldapFilter, - domainName ?? "Default Domain"); - return Tuple.Create( - null, null, null, new LDAPQueryException(errorString)); - } - - var request = CreateSearchRequest(ldapFilter, scope, props, domainName, adsPath, showDeleted); - - if (request == null) - { - var errorString = String.Format("Search request is null for filter {0} and domain {1}", ldapFilter, - domainName ?? "Default Domain"); - return Tuple.Create( - null, null, null, new LDAPQueryException(errorString)); - } - - var pageControl = new PageResultRequestControl(500); - request.Controls.Add(pageControl); - - if (includeAcl) - request.Controls.Add(new SecurityDescriptorFlagControl - { - SecurityMasks = SecurityMasks.Dacl | SecurityMasks.Owner - }); - - return Tuple.Create(conn, request, pageControl, null); - } - /// /// Queries LDAP using LDAPQueryOptions /// @@ -761,10 +692,7 @@ public IEnumerable QueryLDAP(string ldapFilter, SearchScope if (error != null) { - if (throwException) - { - throw new LDAPQueryException("Failed to setup LDAP Query Filter", error); - } + if (throwException) throw new LDAPQueryException("Failed to setup LDAP Query Filter", error); _log.LogWarning(error, "Failed to setup LDAP Query Filter"); yield break; @@ -780,26 +708,22 @@ public IEnumerable QueryLDAP(string ldapFilter, SearchScope try { _log.LogTrace("Sending LDAP request for {Filter}", ldapFilter); - response = (SearchResponse)conn.SendRequest(request); + response = (SearchResponse) conn.SendRequest(request); if (response != null) - pageResponse = (PageResultResponseControl)response.Controls + pageResponse = (PageResultResponseControl) response.Controls .Where(x => x is PageResultResponseControl).DefaultIfEmpty(null).FirstOrDefault(); } catch (LdapException le) { if (le.ErrorCode != 82) if (throwException) - { - throw new LDAPQueryException(String.Format( + throw new LDAPQueryException(string.Format( "LDAP Exception in Loop: {0}. {1}. {2}. Filter: {3}. Domain: {4}.", le.ErrorCode, le.ServerErrorMessage, le.Message, ldapFilter, domainName), le); - } else - { _log.LogWarning(le, "LDAP Exception in Loop: {ErrorCode}. {ServerErrorMessage}. {Message}. Filter: {Filter}. Domain: {Domain}", le.ErrorCode, le.ServerErrorMessage, le.Message, ldapFilter, domainName); - } yield break; } @@ -807,14 +731,12 @@ public IEnumerable QueryLDAP(string ldapFilter, SearchScope { if (throwException) { - throw new LDAPQueryException(String.Format("Exception in LDAP loop for {0} and {1}", + throw new LDAPQueryException(string.Format("Exception in LDAP loop for {0} and {1}", ldapFilter, domainName)); } - else - { - _log.LogWarning(e, "Exception in LDAP loop for {Filter} and {Domain}", ldapFilter, domainName); - yield break; - } + + _log.LogWarning(e, "Exception in LDAP loop for {Filter} and {Domain}", ldapFilter, domainName); + yield break; } if (cancellationToken.IsCancellationRequested) @@ -873,10 +795,7 @@ public IEnumerable QueryLDAP(string ldapFilter, SearchScope if (error != null) { - if (throwException) - { - throw new LDAPQueryException("Failed to setup LDAP Query Filter", error); - } + if (throwException) throw new LDAPQueryException("Failed to setup LDAP Query Filter", error); _log.LogWarning(error, "Failed to setup LDAP Query Filter"); yield break; @@ -889,40 +808,35 @@ public IEnumerable QueryLDAP(string ldapFilter, SearchScope try { _log.LogTrace("Sending LDAP request for {Filter}", ldapFilter); - response = (SearchResponse)conn.SendRequest(request); + response = (SearchResponse) conn.SendRequest(request); if (response != null) - pageResponse = (PageResultResponseControl)response.Controls + pageResponse = (PageResultResponseControl) response.Controls .Where(x => x is PageResultResponseControl).DefaultIfEmpty(null).FirstOrDefault(); } catch (LdapException le) { if (le.ErrorCode != 82) if (throwException) - { - throw new LDAPQueryException(String.Format( + throw new LDAPQueryException(string.Format( "LDAP Exception in Loop: {0}. {1}. {2}. Filter: {3}. Domain: {4}", le.ErrorCode, le.ServerErrorMessage, le.Message, ldapFilter, domainName), le); - } else - { _log.LogWarning(le, "LDAP Exception in Loop: {ErrorCode}. {ServerErrorMessage}. {Message}. Filter: {Filter}. Domain: {Domain}", le.ErrorCode, le.ServerErrorMessage, le.Message, ldapFilter, domainName); - } yield break; } catch (Exception e) { if (throwException) { - throw new LDAPQueryException(String.Format( + throw new LDAPQueryException(string.Format( "Exception in LDAP loop for {0} and {1}", ldapFilter, domainName ?? "Default Domain"), e); } - else - { - _log.LogWarning(e, "Exception in LDAP loop for {Filter} and {Domain}", ldapFilter, domainName ?? "Default Domain"); - yield break; - } + + _log.LogWarning(e, "Exception in LDAP loop for {Filter} and {Domain}", ldapFilter, + domainName ?? "Default Domain"); + yield break; } if (response == null || pageResponse == null) continue; @@ -1028,11 +942,83 @@ public Domain GetDomain(string domainName = null) return domain; } + /// + /// Setup LDAP query for filter + /// + /// LDAP filter + /// SearchScope to query + /// LDAP properties to fetch for each object + /// Include the DACL and Owner values in the NTSecurityDescriptor + /// Domain to query + /// Include deleted objects + /// ADS path to limit the query too + /// Use the global catalog instead of the regular LDAP server + /// + /// Skip the connection cache and force a new connection. You must dispose of this connection + /// yourself. + /// + /// Tuple of LdapConnection, SearchRequest, PageResultRequestControl and LDAPQueryException + public Tuple SetupLDAPQueryFilter( + string ldapFilter, + SearchScope scope, string[] props, bool includeAcl = false, string domainName = null, + bool showDeleted = false, + string adsPath = null, bool globalCatalog = false, bool skipCache = false) + { + _log.LogTrace("Creating ldap connection for {Target} with filter {Filter}", + globalCatalog ? "Global Catalog" : "DC", ldapFilter); + var task = globalCatalog + ? Task.Run(() => CreateGlobalCatalogConnection(domainName, _ldapConfig.AuthType)) + : Task.Run(() => CreateLDAPConnection(domainName, skipCache, _ldapConfig.AuthType)); + + LdapConnection conn; + try + { + conn = task.ConfigureAwait(false).GetAwaiter().GetResult(); + } + catch (Exception e) + { + var errorString = string.Format("Exception getting LDAP connection for {0} and domain {1}", ldapFilter, + domainName ?? "Default Domain"); + return Tuple.Create( + null, null, null, new LDAPQueryException(errorString, e)); + } + + if (conn == null) + { + var errorString = string.Format("LDAP connection is null for filter {0} and domain {1}", ldapFilter, + domainName ?? "Default Domain"); + return Tuple.Create( + null, null, null, new LDAPQueryException(errorString)); + } + + var request = CreateSearchRequest(ldapFilter, scope, props, domainName, adsPath, showDeleted); + + if (request == null) + { + var errorString = string.Format("Search request is null for filter {0} and domain {1}", ldapFilter, + domainName ?? "Default Domain"); + return Tuple.Create( + null, null, null, new LDAPQueryException(errorString)); + } + + var pageControl = new PageResultRequestControl(500); + request.Controls.Add(pageControl); + + if (includeAcl) + request.Controls.Add(new SecurityDescriptorFlagControl + { + SecurityMasks = SecurityMasks.Dacl | SecurityMasks.Owner + }); + + return Tuple.Create(conn, + request, pageControl, null); + } + private Group GetBaseEnterpriseDC(string domain) { var forest = GetForest(domain)?.Name; if (forest == null) _log.LogWarning("Error getting forest, ENTDC sid is likely incorrect"); - var g = new Group { ObjectIdentifier = $"{forest}-S-1-5-9".ToUpper() }; + var g = new Group {ObjectIdentifier = $"{forest}-S-1-5-9".ToUpper()}; g.Properties.Add("name", $"ENTERPRISE DOMAIN CONTROLLERS@{forest ?? "UNKNOWN"}".ToUpper()); g.Properties.Add("domainsid", GetSidFromDomainName(forest)); g.Properties.Add("domain", forest); @@ -1058,7 +1044,7 @@ private string GetDomainNameFromSidLdap(string sid) //Search using objectsid first var result = QueryLDAP($"(&(objectclass=domain)(objectsid={hexSid}))", SearchScope.Subtree, - new[] { "distinguishedname" }, globalCatalog: true).DefaultIfEmpty(null).FirstOrDefault(); + new[] {"distinguishedname"}, globalCatalog: true).DefaultIfEmpty(null).FirstOrDefault(); if (result != null) { @@ -1069,7 +1055,7 @@ private string GetDomainNameFromSidLdap(string sid) //Try trusteddomain objects with the securityidentifier attribute result = QueryLDAP($"(&(objectclass=trusteddomain)(securityidentifier={sid}))", SearchScope.Subtree, - new[] { "cn" }, globalCatalog: true).DefaultIfEmpty(null).FirstOrDefault(); + new[] {"cn"}, globalCatalog: true).DefaultIfEmpty(null).FirstOrDefault(); if (result != null) { @@ -1167,10 +1153,7 @@ private static bool RequestNETBIOSNameFromComputer(string server, string domain, return null; var result = NetAPIMethods.NetWkstaGetInfo(hostname); - if (result.IsSuccess) - { - return result.Value; - } + if (result.IsSuccess) return result.Value; return null; } @@ -1295,7 +1278,7 @@ private async Task CreateLDAPConnection(string domainName = null var port = _ldapConfig.GetPort(); var ident = new LdapDirectoryIdentifier(targetServer, port, false, false); - var connection = new LdapConnection(ident) { Timeout = new TimeSpan(0, 0, 5, 0) }; + var connection = new LdapConnection(ident) {Timeout = new TimeSpan(0, 0, 5, 0)}; if (_ldapConfig.Username != null) { var cred = new NetworkCredential(_ldapConfig.Username, _ldapConfig.Password); diff --git a/src/CommonLib/NativeMethods.cs b/src/CommonLib/NativeMethods.cs index 3ec78ded..fb0c4c69 100644 --- a/src/CommonLib/NativeMethods.cs +++ b/src/CommonLib/NativeMethods.cs @@ -31,7 +31,8 @@ public virtual NetAPIResult> NetWkstaUserEn return NetAPIMethods.NetWkstaUserEnum(servername); } - public virtual NetAPIResult CallDsGetDcName(string computerName, string domainName) + public virtual NetAPIResult CallDsGetDcName(string computerName, + string domainName) { return NetAPIMethods.DsGetDcName(computerName, domainName); } diff --git a/src/CommonLib/Processors/ComputerSessionProcessor.cs b/src/CommonLib/Processors/ComputerSessionProcessor.cs index 4cba3f79..952558a7 100644 --- a/src/CommonLib/Processors/ComputerSessionProcessor.cs +++ b/src/CommonLib/Processors/ComputerSessionProcessor.cs @@ -6,22 +6,19 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Win32; -using SharpHoundCommonLib.Exceptions; using SharpHoundCommonLib.OutputTypes; -using SharpHoundRPC.NetAPINative; namespace SharpHoundCommonLib.Processors { public class ComputerSessionProcessor { + public delegate void ComputerStatusDelegate(CSVComputerStatus status); + private static readonly Regex SidRegex = new(@"S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$", RegexOptions.Compiled); private readonly string _currentUserName; private readonly ILogger _log; private readonly NativeMethods _nativeMethods; private readonly ILDAPUtils _utils; - - public delegate void ComputerStatusDelegate(CSVComputerStatus status); - public event ComputerStatusDelegate ComputerStatusEvent; public ComputerSessionProcessor(ILDAPUtils utils, string currentUserName = null, NativeMethods nativeMethods = null, ILogger log = null) @@ -32,6 +29,8 @@ public ComputerSessionProcessor(ILDAPUtils utils, string currentUserName = null, _log = log ?? Logging.LogProvider.CreateLogger("CompSessions"); } + public event ComputerStatusDelegate ComputerStatusEvent; + /// /// Uses the NetSessionEnum Win32 API call to get network sessions from a remote computer. /// These are usually from SMB share accesses or other network sessions of the sort @@ -233,7 +232,7 @@ public SessionAPIResult ReadUserSessionsRegistry(string computerName, string com key?.Dispose(); } } - + private void SendComputerStatus(CSVComputerStatus status) { ComputerStatusEvent?.Invoke(status); diff --git a/src/CommonLib/Processors/GPOLocalGroupProcessor.cs b/src/CommonLib/Processors/GPOLocalGroupProcessor.cs index 9ebe74b4..e133eaaa 100644 --- a/src/CommonLib/Processors/GPOLocalGroupProcessor.cs +++ b/src/CommonLib/Processors/GPOLocalGroupProcessor.cs @@ -604,7 +604,7 @@ internal enum GroupActionTarget RestrictedMember, LocalGroup } - + internal enum LocalGroupRids { None = 0, diff --git a/src/CommonLib/Processors/LDAPPropertyProcessor.cs b/src/CommonLib/Processors/LDAPPropertyProcessor.cs index 261ea273..919551c8 100644 --- a/src/CommonLib/Processors/LDAPPropertyProcessor.cs +++ b/src/CommonLib/Processors/LDAPPropertyProcessor.cs @@ -21,7 +21,8 @@ public class LDAPPropertyProcessor "homedirectory", "description", "admincount", "userpassword", "gpcfilesyspath", "objectclass", "msds-behavior-version", "objectguid", "name", "gpoptions", "msds-allowedtodelegateto", "msDS-allowedtoactonbehalfofotheridentity", "displayname", - "sidhistory", "samaccountname", "samaccounttype", "objectsid", "objectguid", "objectclass", "msds-groupmsamembership", + "sidhistory", "samaccountname", "samaccounttype", "objectsid", "objectguid", "objectclass", + "msds-groupmsamembership", "distinguishedname", "memberof", "logonhours", "ntsecuritydescriptor", "dsasignature", "repluptodatevector", "member", "whenCreated" }; diff --git a/src/CommonLib/Processors/LocalGroupProcessor.cs b/src/CommonLib/Processors/LocalGroupProcessor.cs index a8007225..8da6d6e5 100644 --- a/src/CommonLib/Processors/LocalGroupProcessor.cs +++ b/src/CommonLib/Processors/LocalGroupProcessor.cs @@ -12,16 +12,16 @@ namespace SharpHoundCommonLib.Processors { public class LocalGroupProcessor { - private readonly ILogger _log; - private readonly ILDAPUtils _utils; + public delegate void ComputerStatusDelegate(CSVComputerStatus status); + private readonly string[] _filteredSids = { "S-1-5-2", "S-1-5-2", "S-1-5-3", "S-1-5-4", "S-1-5-6", "S-1-5-7", "S-1-2", "S-1-2-0", "S-1-5-18", "S-1-5-19", "S-1-5-20" }; - public delegate void ComputerStatusDelegate(CSVComputerStatus status); - public event ComputerStatusDelegate ComputerStatusEvent; + private readonly ILogger _log; + private readonly ILDAPUtils _utils; public LocalGroupProcessor(ILDAPUtils utils, ILogger log = null) { @@ -29,7 +29,10 @@ public LocalGroupProcessor(ILDAPUtils utils, ILogger log = null) _log = log ?? Logging.LogProvider.CreateLogger("LocalGroupProcessor"); } - public IEnumerable GetLocalGroups(string computerName, string computerDomainSid, string computerDomain) + public event ComputerStatusDelegate ComputerStatusEvent; + + public IEnumerable GetLocalGroups(string computerName, string computerDomainSid, + string computerDomain) { var openServerResult = SAMServer.OpenServer(computerName); if (openServerResult.IsFailed) @@ -46,7 +49,7 @@ public IEnumerable GetLocalGroups(string computerName, stri var server = openServerResult.Value; var typeCache = new ConcurrentDictionary(); var computerSid = new SecurityIdentifier(computerDomainSid); - + if (!Cache.GetMachineSid(computerDomainSid, out var machineSid)) { var getMachineSidResult = server.GetMachineSid(); @@ -61,7 +64,7 @@ public IEnumerable GetLocalGroups(string computerName, stri } } - + var getDomainsResult = server.GetDomains(); if (getDomainsResult.IsFailed) { @@ -73,14 +76,15 @@ public IEnumerable GetLocalGroups(string computerName, stri }); yield break; } + foreach (var domainResult in getDomainsResult.Value) { var ret = new LocalGroupAPIResult { Name = domainResult.Name, - GroupRID = domainResult.Rid, + GroupRID = domainResult.Rid }; - + var openDomainResult = server.OpenDomain(domainResult.Name); if (openDomainResult.IsFailed) { @@ -107,7 +111,7 @@ public IEnumerable GetLocalGroups(string computerName, stri }); continue; } - + foreach (var alias in getAliasesResult.Value) { var openAliasResult = domain.OpenAlias(alias.Rid); @@ -123,7 +127,7 @@ public IEnumerable GetLocalGroups(string computerName, stri ret.FailureReason = $"SamOpenAliasInDomain failed with status {openAliasResult.Status}"; yield return ret; } - + var results = new List(); var names = new List(); @@ -186,7 +190,7 @@ public IEnumerable GetLocalGroups(string computerName, stri ObjectIdentifier = sidValue, ObjectType = item.Type }); - + names.Add(new NamedPrincipal { ObjectId = sidValue, @@ -212,13 +216,13 @@ public IEnumerable GetLocalGroups(string computerName, stri }; typeCache.TryAdd(sidValue, new CachedLocalItem(name, objectType)); - + results.Add(new TypedPrincipal { ObjectIdentifier = sidValue, ObjectType = objectType }); - + names.Add(new NamedPrincipal { PrincipalName = name, diff --git a/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs b/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs index 3ffbd1de..3a3fcdd7 100644 --- a/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs +++ b/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs @@ -11,24 +11,27 @@ namespace SharpHoundCommonLib.Processors { public class UserRightsAssignmentProcessor { - private readonly ILogger _log; - private readonly ILDAPUtils _utils; + public delegate void ComputerStatusDelegate(CSVComputerStatus status); + private readonly string[] _filteredSids = { "S-1-5-2", "S-1-5-2", "S-1-5-3", "S-1-5-4", "S-1-5-6", "S-1-5-7", "S-1-2", "S-1-2-0", "S-1-5-18", "S-1-5-19", "S-1-5-20" }; - - public delegate void ComputerStatusDelegate(CSVComputerStatus status); - public event ComputerStatusDelegate ComputerStatusEvent; - + + private readonly ILogger _log; + private readonly ILDAPUtils _utils; + public UserRightsAssignmentProcessor(ILDAPUtils utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("LocalGroupProcessor"); } - public IEnumerable GetUserRightsAssignments(string computerName, string computerDomainSid, string computerDomain, string[] desiredPrivileges = null) + public event ComputerStatusDelegate ComputerStatusEvent; + + public IEnumerable GetUserRightsAssignments(string computerName, + string computerDomainSid, string computerDomain, string[] desiredPrivileges = null) { var computerSid = new SecurityIdentifier(computerDomainSid); var policyOpenResult = LSAPolicy.OpenPolicy(computerName); @@ -82,7 +85,7 @@ public IEnumerable GetUserRightsAssignments(strin } var isDc = computerSid.IsEqualDomainSid(new SecurityIdentifier(machineSid)); - + var resolved = new List(); var names = new List(); @@ -116,7 +119,7 @@ public IEnumerable GetUserRightsAssignments(strin ObjectId = convertedId, PrincipalName = common.ObjectIdentifier }); - + var objectType = common.ObjectType switch { Label.User => Label.LocalUser, @@ -138,13 +141,13 @@ public IEnumerable GetUserRightsAssignments(strin SharedEnums.SidNameUse.Alias => Label.LocalGroup, _ => Label.Base }; - + names.Add(new NamedPrincipal { ObjectId = sid.ToString(), PrincipalName = name }); - + resolved.Add(new TypedPrincipal { ObjectIdentifier = sid.ToString(), @@ -159,7 +162,7 @@ public IEnumerable GetUserRightsAssignments(strin yield return result; } } - + private bool IsSidFiltered(SecurityIdentifier identifier) { var value = identifier.Value; @@ -173,7 +176,7 @@ private bool IsSidFiltered(SecurityIdentifier identifier) return false; } - + private void SendComputerStatus(CSVComputerStatus status) { ComputerStatusEvent?.Invoke(status); diff --git a/src/CommonLib/SharpHoundCommonLib.csproj b/src/CommonLib/SharpHoundCommonLib.csproj index 4b2e0f41..e6fa9fe8 100644 --- a/src/CommonLib/SharpHoundCommonLib.csproj +++ b/src/CommonLib/SharpHoundCommonLib.csproj @@ -18,17 +18,17 @@ full - - + + - - + + - + - + From fb221bcf1f4591261f297d6432e9257c8890107c Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Mon, 3 Oct 2022 14:05:04 -0400 Subject: [PATCH 19/26] fix: add project reference --- test/unit/CommonLibTest.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/test/unit/CommonLibTest.csproj b/test/unit/CommonLibTest.csproj index 62cd2f78..be39b0c0 100644 --- a/test/unit/CommonLibTest.csproj +++ b/test/unit/CommonLibTest.csproj @@ -11,6 +11,7 @@ + From 42c7dd3e14e09569a824cce33009b71310593363 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Tue, 4 Oct 2022 17:21:28 -0400 Subject: [PATCH 20/26] chore: add UserRights collection method --- src/CommonLib/Enums/CollectionMethods.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/CommonLib/Enums/CollectionMethods.cs b/src/CommonLib/Enums/CollectionMethods.cs index d5c111c7..3c9bdb2c 100644 --- a/src/CommonLib/Enums/CollectionMethods.cs +++ b/src/CommonLib/Enums/CollectionMethods.cs @@ -21,10 +21,11 @@ public enum ResolvedCollectionMethod DCOM = 1 << 12, SPNTargets = 1 << 13, PSRemote = 1 << 14, + UserRights = 1 << 15, LocalGroups = DCOM | RDP | LocalAdmin | PSRemote, - ComputerOnly = LocalGroups | Session, + ComputerOnly = LocalGroups | Session | UserRights, DCOnly = ACL | Container | Group | ObjectProps | Trusts | GPOLocalGroup, Default = Group | Session | Trusts | ACL | ObjectProps | LocalGroups | SPNTargets | Container, - All = Default | LoggedOn | GPOLocalGroup + All = Default | LoggedOn | GPOLocalGroup | UserRights } } \ No newline at end of file From fd9ea808ee5f146b9b528f5592a9cd61170073b1 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Tue, 4 Oct 2022 17:21:41 -0400 Subject: [PATCH 21/26] chore: remove GroupRid from API result --- src/CommonLib/OutputTypes/LocalGroupAPIResult.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CommonLib/OutputTypes/LocalGroupAPIResult.cs b/src/CommonLib/OutputTypes/LocalGroupAPIResult.cs index ceb0da01..e80dea1a 100644 --- a/src/CommonLib/OutputTypes/LocalGroupAPIResult.cs +++ b/src/CommonLib/OutputTypes/LocalGroupAPIResult.cs @@ -5,7 +5,6 @@ namespace SharpHoundCommonLib.OutputTypes { public class LocalGroupAPIResult : APIResult { - public int GroupRID { get; set; } public string ObjectIdentifier { get; set; } public string Name { get; set; } public TypedPrincipal[] Results { get; set; } = Array.Empty(); From 5497793176f14e476c1578e9a50cfcc7ad863be7 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Tue, 4 Oct 2022 17:22:11 -0400 Subject: [PATCH 22/26] fix: fix output for local groups, and name/sid resolution --- .../Processors/LocalGroupProcessor.cs | 46 ++++++++++++++++--- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/src/CommonLib/Processors/LocalGroupProcessor.cs b/src/CommonLib/Processors/LocalGroupProcessor.cs index 8da6d6e5..be0ec9f5 100644 --- a/src/CommonLib/Processors/LocalGroupProcessor.cs +++ b/src/CommonLib/Processors/LocalGroupProcessor.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Security.Principal; @@ -77,14 +78,10 @@ public IEnumerable GetLocalGroups(string computerName, stri yield break; } + var isDc = server.IsDomainController(computerSid); + foreach (var domainResult in getDomainsResult.Value) { - var ret = new LocalGroupAPIResult - { - Name = domainResult.Name, - GroupRID = domainResult.Rid - }; - var openDomainResult = server.OpenDomain(domainResult.Name); if (openDomainResult.IsFailed) { @@ -114,6 +111,13 @@ public IEnumerable GetLocalGroups(string computerName, stri foreach (var alias in getAliasesResult.Value) { + var resolvedName = ResolveGroupName(alias.Name, computerName, machineSid, computerDomain, alias.Rid, isDc, + domainResult.Name.Equals("builtin", StringComparison.OrdinalIgnoreCase)); + var ret = new LocalGroupAPIResult + { + Name = resolvedName.PrincipalName, + ObjectIdentifier = resolvedName.ObjectId + }; var openAliasResult = domain.OpenAlias(alias.Rid); if (openAliasResult.IsFailed) { @@ -241,6 +245,34 @@ public IEnumerable GetLocalGroups(string computerName, stri } } + private NamedPrincipal ResolveGroupName(string baseName, string computerName, string machineSid, string domainName, int groupRid, bool isDc, bool isBuiltIn) + { + if (isDc) + { + if (isBuiltIn) + { + _utils.GetWellKnownPrincipal($"S-1-5-32-{groupRid}".ToUpper(), domainName, out var principal); + return new NamedPrincipal + { + ObjectId = principal.ObjectIdentifier, + PrincipalName = "IGNOREME" + }; + } + + return new NamedPrincipal + { + ObjectId = $"{machineSid}-{groupRid}".ToUpper(), + PrincipalName = "IGNOREME" + }; + } + + return new NamedPrincipal + { + ObjectId = $"{machineSid}-{groupRid}", + PrincipalName = $"{baseName}@{computerName}".ToUpper() + }; + } + private bool IsSidFiltered(SecurityIdentifier identifier) { var value = identifier.Value; From 1afe482dcc460caa7ebace766ee593e96a53d8dd Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Tue, 4 Oct 2022 17:22:39 -0400 Subject: [PATCH 23/26] fix: remove look-ahead memory frees that were breaking everything --- .../NetAPINative/NetAPIMethods.cs | 44 +++++++------------ src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs | 18 +++----- src/SharpHoundRPC/Wrappers/LSAPolicy.cs | 8 ++-- src/SharpHoundRPC/Wrappers/SAMAlias.cs | 12 ++--- src/SharpHoundRPC/Wrappers/SAMDomain.cs | 15 ++++--- src/SharpHoundRPC/Wrappers/SAMServer.cs | 18 +++++--- 6 files changed, 53 insertions(+), 62 deletions(-) diff --git a/src/SharpHoundRPC/NetAPINative/NetAPIMethods.cs b/src/SharpHoundRPC/NetAPINative/NetAPIMethods.cs index c26eeca0..a2dafc8f 100644 --- a/src/SharpHoundRPC/NetAPINative/NetAPIMethods.cs +++ b/src/SharpHoundRPC/NetAPINative/NetAPIMethods.cs @@ -22,15 +22,12 @@ public static NetAPIResult> NetWkstaUserEnu var result = NetWkstaUserEnum(computerName, NetWkstaUserEnumQueryLevel, out var buffer, -1, out var entriesRead, out _, ref resumeHandle); - using (buffer) - { - if (result != NetAPIEnums.NetAPIStatus.Success && result != NetAPIEnums.NetAPIStatus.ErrorMoreData) - return result; - - return NetAPIResult>.Ok(buffer - .GetEnumerable(entriesRead) - .Select(x => new NetWkstaUserEnumResults(x.Username, x.LogonDomain))); - } + if (result != NetAPIEnums.NetAPIStatus.Success && result != NetAPIEnums.NetAPIStatus.ErrorMoreData) + return result; + + return NetAPIResult>.Ok(buffer + .GetEnumerable(entriesRead) + .Select(x => new NetWkstaUserEnumResults(x.Username, x.LogonDomain))); } [DllImport("netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] @@ -48,16 +45,13 @@ public static NetAPIResult> NetSessionEnum(st var resumeHandle = 0; var result = NetSessionEnum(computerName, null, null, NetSessionEnumLevel, out var buffer, -1, out var entriesRead, out _, ref resumeHandle); + + if (result != NetAPIEnums.NetAPIStatus.Success && result != NetAPIEnums.NetAPIStatus.ErrorMoreData) + return result; - using (buffer) - { - if (result != NetAPIEnums.NetAPIStatus.Success && result != NetAPIEnums.NetAPIStatus.ErrorMoreData) - return result; - - return NetAPIResult>.Ok(buffer - .GetEnumerable(entriesRead) - .Select(x => new NetSessionEnumResults(x.Username, x.CName))); - } + return NetAPIResult>.Ok(buffer + .GetEnumerable(entriesRead) + .Select(x => new NetSessionEnumResults(x.Username, x.CName))); } [DllImport("NetAPI32.dll", SetLastError = true)] @@ -76,12 +70,9 @@ private static extern NetAPIEnums.NetAPIStatus NetSessionEnum( { var result = NetWkstaGetInfo(computerName, NetWkstaGetInfoQueryLevel, out var buffer); - using (buffer) - { - if (result != NetAPIEnums.NetAPIStatus.Success) return result; + if (result != NetAPIEnums.NetAPIStatus.Success) return result; - return buffer.GetData(); - } + return buffer.GetData(); } [DllImport("netapi32.dll", SetLastError = true)] @@ -96,12 +87,9 @@ private static extern NetAPIEnums.NetAPIStatus NetWkstaGetInfo( var result = DsGetDcName(computerName, domainName, null, null, (uint) (NetAPIEnums.DSGETDCNAME_FLAGS.DS_IS_FLAT_NAME | NetAPIEnums.DSGETDCNAME_FLAGS.DS_RETURN_DNS_NAME), out var buffer); - using (buffer) - { - if (result != NetAPIEnums.NetAPIStatus.Success) return result; + if (result != NetAPIEnums.NetAPIStatus.Success) return result; - return buffer.GetData(); - } + return buffer.GetData(); } [DllImport("Netapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] diff --git a/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs b/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs index 45f37164..e17de5b6 100644 --- a/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs +++ b/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs @@ -31,18 +31,15 @@ private static extern NtStatus SamConnect( ref SAMStructs.ObjectAttributes objectAttributes ); - internal static (NtStatus status, IEnumerable domainRids) + internal static (NtStatus status, SAMPointer domainRids, int count) SamEnumerateDomainsInSamServer(SAMHandle serverHandle) { var enumerationContext = 0; var status = SamEnumerateDomainsInSamServer(serverHandle, ref enumerationContext, out var domains, -1, out var count); - - using (domains) - { - return (status, domains.GetEnumerable(count)); - } + + return (status, domains, count); } [DllImport("samlib.dll", CharSet = CharSet.Unicode)] @@ -54,16 +51,13 @@ private static extern NtStatus SamEnumerateDomainsInSamServer( out int count ); - internal static (NtStatus status, SecurityIdentifier securityIdentifier) SamLookupDomainInSamServer( + internal static (NtStatus status, SAMPointer securityIdentifier) SamLookupDomainInSamServer( SAMHandle serverHandle, string name) { var us = new SharedStructs.UnicodeString(name); var status = SamLookupDomainInSamServer(serverHandle, ref us, out var sid); - - using (sid) - { - return (status, sid.GetData()); - } + + return (status, sid); } [DllImport("samlib.dll", CharSet = CharSet.Unicode)] diff --git a/src/SharpHoundRPC/Wrappers/LSAPolicy.cs b/src/SharpHoundRPC/Wrappers/LSAPolicy.cs index 8ed41311..5f3d98a4 100644 --- a/src/SharpHoundRPC/Wrappers/LSAPolicy.cs +++ b/src/SharpHoundRPC/Wrappers/LSAPolicy.cs @@ -40,12 +40,10 @@ public static Result OpenPolicy(string computerName, LSAEnums.LsaOpen public Result> GetPrincipalsWithPrivilege(string userRight) { var (status, sids, count) = LSAMethods.LsaEnumerateAccountsWithUserRight(Handle, userRight); - using (sids) - { - if (status.IsError()) return status; + + if (status.IsError()) return status; - return Result>.Ok(sids.GetEnumerable(count)); - } + return Result>.Ok(sids.GetEnumerable(count)); } public Result> diff --git a/src/SharpHoundRPC/Wrappers/SAMAlias.cs b/src/SharpHoundRPC/Wrappers/SAMAlias.cs index c41de0df..1c70f5b2 100644 --- a/src/SharpHoundRPC/Wrappers/SAMAlias.cs +++ b/src/SharpHoundRPC/Wrappers/SAMAlias.cs @@ -17,12 +17,14 @@ public SAMAlias(SAMHandle handle) : base(handle) public Result> GetMembers() { var (status, members, count) = SAMMethods.SamGetMembersInAlias(Handle); - using (members) - { - if (status.IsError()) return status; - return Result>.Ok(members.GetData(count)); - } + if (status.IsError()) + { + return status; + } + + return Result>.Ok(members.GetData(count)); + } } } \ No newline at end of file diff --git a/src/SharpHoundRPC/Wrappers/SAMDomain.cs b/src/SharpHoundRPC/Wrappers/SAMDomain.cs index 449eb5ed..3a3a0be8 100644 --- a/src/SharpHoundRPC/Wrappers/SAMDomain.cs +++ b/src/SharpHoundRPC/Wrappers/SAMDomain.cs @@ -31,14 +31,17 @@ public SAMDomain(SAMHandle handle) : base(handle) public Result> GetAliases() { var (status, ridPointer, count) = SAMMethods.SamEnumerateAliasesInDomain(Handle); - using (ridPointer) - { - if (status.IsError()) return status; - return Result>.Ok(ridPointer - .GetEnumerable(count) - .Select(x => (x.Name.ToString(), x.Rid))); + if (status.IsError()) + { + return status; } + + var ret = Result>.Ok(ridPointer + .GetEnumerable(count) + .Select(x => (x.Name.ToString(), x.Rid))); + + return ret; } public Result OpenAlias(int rid, diff --git a/src/SharpHoundRPC/Wrappers/SAMServer.cs b/src/SharpHoundRPC/Wrappers/SAMServer.cs index ef5d6a63..23a15121 100644 --- a/src/SharpHoundRPC/Wrappers/SAMServer.cs +++ b/src/SharpHoundRPC/Wrappers/SAMServer.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Security.Principal; @@ -36,16 +37,21 @@ public static Result OpenServer(string computerName, SAMEnums.SamAcce public Result> GetDomains() { - var (status, rids) = SAMMethods.SamEnumerateDomainsInSamServer(Handle); - return status.IsError() - ? status - : Result>.Ok(rids.Select(x => (x.Name.ToString(), x.Rid))); + var (status, rids, count) = SAMMethods.SamEnumerateDomainsInSamServer(Handle); + if (status.IsError()) + { + return status; + } + + var ret = Result>.Ok(rids.GetEnumerable(count).Select(x => (x.Name.ToString(), x.Rid))); + return ret; } public Result LookupDomain(string name) { var (status, sid) = SAMMethods.SamLookupDomainInSamServer(Handle, name); - return status.IsError() ? status : sid; + using (sid) + return status.IsError() ? status : sid.GetData(); } public Result GetMachineSid(string testName = null) From 10d4c20d24df9e212681c8b3b856d47dd3194a87 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Tue, 4 Oct 2022 17:23:14 -0400 Subject: [PATCH 24/26] fix: break control flow in URA processor when privilege fails --- src/CommonLib/Processors/UserRightsAssignmentProcessor.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs b/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs index 3a3fcdd7..6fa1b45c 100644 --- a/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs +++ b/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs @@ -25,7 +25,7 @@ public class UserRightsAssignmentProcessor public UserRightsAssignmentProcessor(ILDAPUtils utils, ILogger log = null) { _utils = utils; - _log = log ?? Logging.LogProvider.CreateLogger("LocalGroupProcessor"); + _log = log ?? Logging.LogProvider.CreateLogger("UserRightsAssignmentProcessor"); } public event ComputerStatusDelegate ComputerStatusEvent; @@ -68,6 +68,7 @@ public IEnumerable GetUserRightsAssignments(strin result.FailureReason = $"LSAEnumerateAccountsWithUserRights returned {enumerateAccountsResult.Status}"; yield return result; + continue; } if (!Cache.GetMachineSid(computerDomainSid, out var machineSid)) From 9086c65904b438b1bac066a0efb077d0892e2874 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Tue, 4 Oct 2022 17:38:26 -0400 Subject: [PATCH 25/26] chore: use helper func for LookupSidType --- src/CommonLib/LDAPUtils.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/CommonLib/LDAPUtils.cs b/src/CommonLib/LDAPUtils.cs index 01f55b29..cb8e5351 100644 --- a/src/CommonLib/LDAPUtils.cs +++ b/src/CommonLib/LDAPUtils.cs @@ -220,15 +220,12 @@ public Label LookupSidType(string sid, string domain) { if (Cache.GetIDType(sid, out var type)) return type; - - var hex = Helpers.ConvertSidToHexSid(sid); - if (hex == null) - return Label.Base; - + var rDomain = GetDomainNameFromSid(sid) ?? domain; + _log.LogInformation("looking up sid {sid} in domain {domain}", sid, domain); var result = - QueryLDAP($"(objectsid={hex})", SearchScope.Subtree, CommonProperties.TypeResolutionProps, rDomain) + QueryLDAP(CommonFilters.SpecificSID(sid), SearchScope.Subtree, CommonProperties.TypeResolutionProps, rDomain) .DefaultIfEmpty(null).FirstOrDefault(); type = result?.GetLabel() ?? Label.Base; From 30723dbedd50fe4b2523910d58e0174cd4aadc48 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Wed, 5 Oct 2022 11:37:19 -0400 Subject: [PATCH 26/26] chore: remove log entry --- src/CommonLib/LDAPUtils.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CommonLib/LDAPUtils.cs b/src/CommonLib/LDAPUtils.cs index cb8e5351..b01e18ac 100644 --- a/src/CommonLib/LDAPUtils.cs +++ b/src/CommonLib/LDAPUtils.cs @@ -222,7 +222,6 @@ public Label LookupSidType(string sid, string domain) return type; var rDomain = GetDomainNameFromSid(sid) ?? domain; - _log.LogInformation("looking up sid {sid} in domain {domain}", sid, domain); var result = QueryLDAP(CommonFilters.SpecificSID(sid), SearchScope.Subtree, CommonProperties.TypeResolutionProps, rDomain)