├── .gitattributes ├── .gitignore ├── README.md ├── Sharphound2.sln └── Sharphound2 ├── App.config ├── Cache.cs ├── CollectionMethod.cs ├── DnsManager.cs ├── Enumeration ├── ACLHelpers.cs ├── ContainerHelpers.cs ├── EnumerationRunner.cs ├── GroupHelpers.cs ├── LdapFilter.cs ├── LocalGroupHelpers.cs ├── MappedPrincipal.cs ├── ObjectPropertyHelpers.cs ├── ResolvedEntry.cs ├── SPNHelpers.cs ├── SessionHelpers.cs └── TrustHelpers.cs ├── Extensions.cs ├── FodyWeavers.xml ├── JsonObjects ├── ACL.cs ├── Computer.cs ├── Domain.cs ├── GpLink.cs ├── Gpo.cs ├── GpoMember.cs ├── Group.cs ├── GroupMember.cs ├── JsonBase.cs ├── LocalMember.cs ├── Ou.cs ├── SPNTarget.cs ├── Session.cs ├── Trust.cs └── User.cs ├── PowerShell ├── Out-CompressedDLL.ps1 └── Template.ps1 ├── Properties └── AssemblyInfo.cs ├── Sharphound.cs ├── Sharphound2.csproj ├── Test.cs ├── Utils.cs ├── Wrapper.cs ├── favicon.ico └── packages.config /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignoreable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | node_modules/ 203 | orleans.codegen.cs 204 | 205 | # Since there are multiple workflows, uncomment next line to ignore bower_components 206 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 207 | #bower_components/ 208 | 209 | # RIA/Silverlight projects 210 | Generated_Code/ 211 | 212 | # Backup & report files from converting an old project file 213 | # to a newer Visual Studio version. Backup files are not needed, 214 | # because we have git ;-) 215 | _UpgradeReport_Files/ 216 | Backup*/ 217 | UpgradeLog*.XML 218 | UpgradeLog*.htm 219 | 220 | # SQL Server files 221 | *.mdf 222 | *.ldf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | 238 | # Visual Studio 6 build log 239 | *.plg 240 | 241 | # Visual Studio 6 workspace options file 242 | *.opt 243 | 244 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 245 | *.vbw 246 | 247 | # Visual Studio LightSwitch build output 248 | **/*.HTMLClient/GeneratedArtifacts 249 | **/*.DesktopClient/GeneratedArtifacts 250 | **/*.DesktopClient/ModelManifest.xml 251 | **/*.Server/GeneratedArtifacts 252 | **/*.Server/ModelManifest.xml 253 | _Pvt_Extensions 254 | 255 | # Paket dependency manager 256 | .paket/paket.exe 257 | paket-files/ 258 | 259 | # FAKE - F# Make 260 | .fake/ 261 | 262 | # JetBrains Rider 263 | .idea/ 264 | *.sln.iml 265 | 266 | # CodeRush 267 | .cr/ 268 | 269 | # Python Tools for Visual Studio (PTVS) 270 | __pycache__/ 271 | *.pyc 272 | 273 | # Cake - Uncomment if you are using it 274 | # tools/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # THIS IS NOW DEPRECATED IN FAVOR OF [SHARPHOUND3](https://github.com/BloodHoundAD/SharpHound3). DATA COLLECTED USING THIS METHOD WILL NOT WORK WITH BLOODHOUND 3.0 2 | 3 | 4 | # SharpHound - C# Rewrite of the BloodHound Ingestor 5 | 6 | ## Get SharpHound 7 | The latest build of SharpHound will always be in the BloodHound repository [here](https://github.com/BloodHoundAD/BloodHound/tree/master/Ingestors) 8 | 9 | ## Compile Instructions 10 | Sharphound is written using C# 7.0 features. To easily compile this project, use Visual Studio 2017. 11 | 12 | If you would like to compile on previous versions of Visual Studio, you can install the [Microsoft.Net.Compilers](https://www.nuget.org/packages/Microsoft.Net.Compilers/) nuget package. 13 | 14 | Building the project will generate an executable as well as a PowerShell script that encapsulates the executable. All dependencies are rolled into the binary. 15 | 16 | ## Requirements 17 | Sharphound is designed targetting .Net 3.5. Sharphound must be run from the context of a domain user, either directly through a logon or through another method such as RUNAS. 18 | 19 | ## More Information 20 | 21 | ## Usage 22 | ### Enumeration Options 23 | * **CollectionMethod** - The collection method to use. This parameter accepts a comma separated list of values. Has the following potential values (Default: Default): 24 | * **Default** - Performs group membership collection, domain trust collection, local admin collection, and session collection 25 | * **Group** - Performs group membership collection 26 | * **LocalAdmin** - Performs local admin collection 27 | * **RDP** - Performs Remote Desktop Users collection 28 | * **DCOM** - Performs Distributed COM Users collection 29 | * **GPOLocalGroup** - Performs local admin collection using Group Policy Objects 30 | * **Session** - Performs session collection 31 | * **ComputerOnly** - Performs local admin, RDP, DCOM and session collection 32 | * **LoggedOn** - Performs privileged session collection (requires admin rights on target systems) 33 | * **Trusts** - Performs domain trust enumeration 34 | * **ACL** - Performs collection of ACLs 35 | * **Container** - Performs collection of Containers 36 | * **DcOnly** - Performs collection using LDAP only. Includes Group, Trusts, ACL, ObjectProps, Container, and GPOLocalGroup. 37 | * **All** - Performs all Collection Methods except GPOLocalGroup and LoggedOn 38 | * **SearchForest** - Search all the domains in the forest instead of just your current one 39 | * **Domain** - Search a particular domain. Uses your current domain if null (Default: null) 40 | * **Stealth** - Performs stealth collection methods. All stealth options are single threaded. 41 | * **SkipGCDeconfliction** - Skip Global Catalog deconfliction during session enumeration. This can speed up enumeration, but will result in possible inaccuracies in data. 42 | * **ExcludeDc** - Excludes domain controllers from enumeration (avoids Microsoft ATA flags :) ) 43 | * **ComputerFile** - Specify a file to load computer names/IPs from 44 | * **OU** - Specify which OU to enumerate 45 | 46 | ### Connection Options 47 | * **DomainController** - Specify which Domain Controller to connect to (Default: null) 48 | * **LdapPort** - Specify what port LDAP lives on (Default: 0) 49 | * **SecureLdap** - Connect to AD using Secure LDAP instead of regular LDAP. Will connect to port 636 by default. 50 | * **IgnoreLdapCert** - Ignores LDAP SSL certificate. Use if there's a self-signed certificate for example 51 | * **LDAPUser** - Username to connect to LDAP with. Requires the LDAPPassword parameter as well (Default: null) 52 | * **LDAPPass** - Password for the user to connect to LDAP with. Requires the LDAPUser parameter as well (Default: null) 53 | * **DisableKerbSigning** - Disables LDAP encryption. Not recommended. 54 | 55 | ### Performance Options 56 | * **Threads** - Specify the number of threads to use (Default: 10) 57 | * **PingTimeout** - Specifies the timeout for ping requests in milliseconds (Default: 250) 58 | * **SkipPing** - Instructs Sharphound to skip ping requests to see if systems are up 59 | * **LoopDelay** - The number of seconds in between session loops (Default: 300) 60 | * **MaxLoopTime** - The amount of time to continue session looping. Format is 0d0h0m0s. Null will loop for two hours. (Default: 2h) 61 | * **Throttle** - Adds a delay after each request to a computer. Value is in milliseconds (Default: 0) 62 | * **Jitter** - Adds a percentage jitter to throttle. (Default: 0) 63 | 64 | ### Output Options 65 | * **JSONFolder** - Folder in which to store JSON files (Default: .) 66 | * **JSONPrefix** - Prefix to add to your JSON files (Default: "") 67 | * **NoZip** - Don't compress JSON files to the zip file. Leaves JSON files on disk. (Default: false) 68 | * **EncryptZip** - Add a randomly generated password to the zip file. 69 | * **ZipFileName** - Specify the name of the zip file 70 | * **RandomFilenames** - Randomize output file names 71 | * **PrettyJson** - Outputs JSON with indentation on multiple lines to improve readability. Tradeoff is increased file size. 72 | 73 | ### Cache Options 74 | * **CacheFile** - Filename for the Sharphound cache. (Default: .bin) 75 | * **NoSaveCache** - Don't save the cache file to disk. Without this flag, .bin will be dropped to disk 76 | * **Invalidate** - Invalidate the cache file and build a new cache 77 | 78 | ### Misc Options 79 | * **StatusInterval** - Interval to display progress during enumeration in milliseconds (Default: 30000) 80 | * **Verbose** - Enables verbose output 81 | -------------------------------------------------------------------------------- /Sharphound2.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.15 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sharphound2", "Sharphound2\Sharphound2.csproj", "{69415BF4-E2F3-47E8-A818-93A6DC54811C}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {69415BF4-E2F3-47E8-A818-93A6DC54811C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {69415BF4-E2F3-47E8-A818-93A6DC54811C}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {69415BF4-E2F3-47E8-A818-93A6DC54811C}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {69415BF4-E2F3-47E8-A818-93A6DC54811C}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {5CCF6B18-3152-4D88-A7CA-B550E1F286EC} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Sharphound2/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /Sharphound2/Cache.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.IO; 3 | using ProtoBuf; 4 | using Sharphound2.Enumeration; 5 | 6 | namespace Sharphound2 7 | { 8 | internal class Cache 9 | { 10 | private ConcurrentDictionary _userCache; 11 | private ConcurrentDictionary _groupCache; 12 | private ConcurrentDictionary _computerCache; 13 | private ConcurrentDictionary _domainToSidCache; 14 | private ConcurrentDictionary _globalCatalogMap; 15 | 16 | private readonly string _fileName; 17 | 18 | public static Cache Instance { get; private set; } 19 | 20 | private readonly Sharphound.Options _options; 21 | 22 | public static void CreateInstance(Sharphound.Options opts) 23 | { 24 | Instance = new Cache(opts); 25 | } 26 | 27 | private Cache(Sharphound.Options opts) 28 | { 29 | _options = opts; 30 | _fileName = Path.Combine(_options.JsonFolder, _options.CacheFile); 31 | var oldFilePath = Path.Combine(_options.JsonFolder, "BloodHound.bin"); 32 | if (File.Exists(oldFilePath)) 33 | { 34 | File.Move(oldFilePath, _fileName); 35 | } 36 | 37 | LoadCache(); 38 | } 39 | 40 | private void LoadCache() 41 | { 42 | if (File.Exists(_fileName) && !_options.Invalidate) 43 | { 44 | using (var file = File.OpenRead(_fileName)) 45 | { 46 | _userCache = 47 | Serializer.DeserializeWithLengthPrefix>(file, 48 | PrefixStyle.Base128); 49 | _groupCache = Serializer.DeserializeWithLengthPrefix>(file, 50 | PrefixStyle.Base128); 51 | _computerCache = Serializer.DeserializeWithLengthPrefix>(file, 52 | PrefixStyle.Base128); 53 | _domainToSidCache = Serializer.DeserializeWithLengthPrefix>( 54 | file, 55 | PrefixStyle.Base128); 56 | _globalCatalogMap = Serializer 57 | .DeserializeWithLengthPrefix>(file, 58 | PrefixStyle.Base128); 59 | } 60 | } 61 | else 62 | { 63 | _userCache = new ConcurrentDictionary(); 64 | _groupCache = new ConcurrentDictionary(); 65 | _computerCache = new ConcurrentDictionary(); 66 | _domainToSidCache = new ConcurrentDictionary(); 67 | _globalCatalogMap = new ConcurrentDictionary(); 68 | } 69 | } 70 | 71 | public void SaveCache() 72 | { 73 | if (_options.NoSaveCache) 74 | return; 75 | 76 | using (var file = File.Create(_fileName)) 77 | { 78 | Serializer.SerializeWithLengthPrefix(file,_userCache,PrefixStyle.Base128); 79 | Serializer.SerializeWithLengthPrefix(file, _groupCache, PrefixStyle.Base128); 80 | Serializer.SerializeWithLengthPrefix(file, _computerCache, PrefixStyle.Base128); 81 | Serializer.SerializeWithLengthPrefix(file, _domainToSidCache, PrefixStyle.Base128); 82 | Serializer.SerializeWithLengthPrefix(file, _globalCatalogMap, PrefixStyle.Base128); 83 | } 84 | } 85 | 86 | public bool GetMapValue(string key, string objType, out string resolved) 87 | { 88 | switch (objType) 89 | { 90 | case "group": 91 | return _groupCache.TryGetValue(key, out resolved); 92 | case "user": 93 | return _userCache.TryGetValue(key, out resolved); 94 | case "computer": 95 | return _computerCache.TryGetValue(key, out resolved); 96 | default: 97 | resolved = null; 98 | return false; 99 | } 100 | } 101 | 102 | public bool GetMapValueUnknownType(string key, out MappedPrincipal principal) 103 | { 104 | if (_groupCache.TryGetValue(key, out string resolved)) 105 | { 106 | principal = new MappedPrincipal(resolved, "group"); 107 | return true; 108 | } 109 | if (_userCache.TryGetValue(key, out resolved)) 110 | { 111 | principal = new MappedPrincipal(resolved, "user"); 112 | return true; 113 | } 114 | if (_computerCache.TryGetValue(key, out resolved)) 115 | { 116 | principal = new MappedPrincipal(resolved, "computer"); 117 | return true; 118 | } 119 | principal = null; 120 | return false; 121 | } 122 | 123 | public void AddMapValue(string key, string objType, string resolved) 124 | { 125 | switch (objType) 126 | { 127 | case "group": 128 | _groupCache.TryAdd(key, resolved); 129 | return; 130 | case "user": 131 | _userCache.TryAdd(key, resolved); 132 | return; 133 | case "computer": 134 | _computerCache.TryAdd(key, resolved); 135 | return; 136 | default: 137 | return; 138 | } 139 | } 140 | 141 | public bool GetDomainFromSid(string sid, out string domainName) 142 | { 143 | return _domainToSidCache.TryGetValue(sid, out domainName); 144 | } 145 | 146 | public void AddDomainFromSid(string sid, string domainName) 147 | { 148 | Utils.Debug($"Cached {sid} to {domainName}"); 149 | _domainToSidCache.TryAdd(sid, domainName); 150 | } 151 | 152 | public bool GetGcMap(string username, out string[] domains) 153 | { 154 | return _globalCatalogMap.TryGetValue(username, out domains); 155 | } 156 | 157 | public void AddGcMap(string username, string[] domains) 158 | { 159 | _globalCatalogMap.TryAdd(username, domains); 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /Sharphound2/CollectionMethod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Sharphound2 4 | { 5 | public enum CollectionMethod 6 | { 7 | Group, 8 | ComputerOnly, 9 | LocalGroup, 10 | GPOLocalGroup, 11 | Session, 12 | LoggedOn, 13 | Trusts, 14 | ACL, 15 | SessionLoop, 16 | Default, 17 | ObjectProps, 18 | Container, 19 | LocalAdmin, 20 | RDP, 21 | DCOM, 22 | DcOnly, 23 | SPNTargets, 24 | All 25 | } 26 | 27 | [Flags] 28 | public enum ResolvedCollectionMethod 29 | { 30 | None = 0, 31 | Group = 1, 32 | LocalAdmin = 1 << 1, 33 | GPOLocalGroup = 1 << 2, 34 | Session = 1 << 3, 35 | LoggedOn = 1 << 4, 36 | Trusts = 1 << 5, 37 | ACL = 1 << 6, 38 | Container = 1 << 7, 39 | RDP = 1 << 8, 40 | ObjectProps = 1 << 9, 41 | SessionLoop = 1 << 10, 42 | LoggedOnLoop = 1 << 11, 43 | DCOM = 1 << 12, 44 | SPNTargets = 1 << 13, 45 | DCOnly = 1 << 14 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sharphound2/DnsManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace Sharphound2 7 | { 8 | [SuppressMessage("ReSharper", "InconsistentNaming")] 9 | internal static class DnsManager 10 | { 11 | private static readonly ConcurrentDictionary _dnsCache = new ConcurrentDictionary(); 12 | 13 | /// 14 | /// Resolves a host using DNS and suppresses LLMNR and NBNS 15 | /// 16 | /// The host to resolve 17 | /// The full hostname of the host returned after resolution 18 | /// Boolean representing if the host exists in DNS 19 | internal static bool HostExistsDns(string host, out string name) 20 | { 21 | if (_dnsCache.TryGetValue(host, out name)) 22 | { 23 | return name != null; 24 | } 25 | //We actually dont care about a couple vars, but we need to pass them in for the API call 26 | var zero = IntPtr.Zero; 27 | var zero2 = IntPtr.Zero; 28 | //2176 will disable NBNT and LLMNR 29 | var result = DnsQuery(host, DnsType.TypeA, 2176uL, ref zero, out var results, zero2); 30 | //0 is a successful DNS lookup 31 | if (result == 0) 32 | { 33 | try 34 | { 35 | var record = (TypeADnsRecord) Marshal.PtrToStructure(results, typeof(TypeADnsRecord)); 36 | //Get the name from the record 37 | name = Marshal.PtrToStringUni(record.name); 38 | //Free the memory we grabbed 39 | DnsRecordListFree(results, DnsFreeType.DnsFreeFlat); 40 | _dnsCache.TryAdd(host, name); 41 | return true; 42 | } 43 | catch 44 | { 45 | // ignored 46 | } 47 | } 48 | result = DnsQuery(host, DnsType.TypeAaaa, 2176uL, ref zero, out results, zero2); 49 | if (result == 0) 50 | { 51 | try 52 | { 53 | var record = (TypeADnsRecord) Marshal.PtrToStructure(results, typeof(TypeADnsRecord)); 54 | name = Marshal.PtrToStringUni(record.name); 55 | DnsRecordListFree(results, DnsFreeType.DnsFreeFlat); 56 | _dnsCache.TryAdd(host, name); 57 | return true; 58 | } 59 | catch 60 | { 61 | //ignored 62 | } 63 | 64 | } 65 | //Make sure the memory is freed. Neither ipv4 or ipv6 succeeded so return false 66 | //This host probably doesn't have a matching DNS entry, or at least not one we can find 67 | DnsRecordListFree(results, DnsFreeType.DnsFreeFlat); 68 | name = null; 69 | _dnsCache.TryAdd(host, null); 70 | return false; 71 | } 72 | 73 | 74 | #region PInvoke 75 | #pragma warning disable 169 76 | [DllImport("dnsapi", EntryPoint = "DnsQuery_W", CharSet = CharSet.Unicode)] 77 | private static extern int DnsQuery( 78 | [MarshalAs(UnmanagedType.LPWStr)] string name, 79 | DnsType type, 80 | ulong options, 81 | ref IntPtr extra, 82 | out IntPtr results, 83 | IntPtr reserved 84 | ); 85 | 86 | [DllImport("dnsapi")] 87 | private static extern void DnsRecordListFree( 88 | IntPtr recordList, 89 | DnsFreeType freeType 90 | ); 91 | 92 | #pragma warning disable 649 93 | private struct TypeADnsRecord 94 | { 95 | public IntPtr next; 96 | public IntPtr name; 97 | public ushort type; 98 | public ushort length; 99 | public DnsRecordFlag flags; 100 | public uint ttl; 101 | public uint reserved; 102 | public ARecordType aRecord; 103 | } 104 | #pragma warning restore 649 105 | 106 | private struct ARecordType 107 | { 108 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public byte[] record; 109 | } 110 | 111 | //private struct DnsRecord 112 | //{ 113 | // public IntPtr nextRecord; 114 | // [MarshalAs(UnmanagedType.LPWStr)] public string name; 115 | // public DnsType type; 116 | // public ushort dataLength; 117 | // public DnsRecordFlag flags; 118 | // public uint ttl; 119 | // public uint reserved; 120 | //} 121 | 122 | private struct DnsRecordFlag 123 | { 124 | private ulong section; 125 | private ulong delete; 126 | private ulong charset; 127 | private ulong unused; 128 | private ulong reserved; 129 | } 130 | 131 | [SuppressMessage("ReSharper", "UnusedMember.Local")] 132 | private enum DnsFreeType 133 | { 134 | DnsFreeFlat = 0, 135 | DnsFreeRecordList = 1, 136 | DnsFreeParsedMessagedFields = 2 137 | } 138 | 139 | [Flags] 140 | [SuppressMessage("ReSharper", "UnusedMember.Local")] 141 | private enum DnsType : ushort 142 | { 143 | TypeA = 0x1, 144 | TypeNs = 0x2, 145 | TypeCname = 0x5, 146 | TypeSoa = 0x6, 147 | TypeNull = 0xa, 148 | TypePtr = 0xc, 149 | TypeMx = 0xf, 150 | TypeText = 0x10, 151 | TypeAaaa = 0x1c, 152 | TypeSrc = 0x21, 153 | TypeAxfr = 0xfc, 154 | TypeAll = 0xff, 155 | TypeAny = 0xff, 156 | TypeWins = 0xff01, 157 | Nbstat = 0xff02 158 | } 159 | #pragma warning restore 169 160 | #endregion 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /Sharphound2/Enumeration/ContainerHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.DirectoryServices.Protocols; 5 | using System.Linq; 6 | using Sharphound2.JsonObjects; 7 | 8 | namespace Sharphound2.Enumeration 9 | { 10 | internal static class ContainerHelpers 11 | { 12 | private static Utils _utils; 13 | private static ConcurrentDictionary _gpoCache; 14 | 15 | public static void Init() 16 | { 17 | _utils = Utils.Instance; 18 | _gpoCache = new ConcurrentDictionary(); 19 | } 20 | 21 | internal static void BuildGpoCache(string domain) 22 | { 23 | if (!Utils.IsMethodSet(ResolvedCollectionMethod.Container)) 24 | return; 25 | 26 | var d = _utils.GetDomain(domain); 27 | if (d == null) 28 | return; 29 | 30 | domain = d.Name; 31 | foreach (var entry in _utils.DoSearch("(&(objectCategory=groupPolicyContainer)(name=*)(gpcfilesyspath=*))", 32 | SearchScope.Subtree, new[] { "displayname", "name" }, domain)) 33 | { 34 | var name = entry.GetProp("name").ToUpper(); 35 | var dName = entry.GetProp("displayname")?.ToUpper() ?? name; 36 | name = name.Substring(1, name.Length - 2); 37 | _gpoCache.TryAdd(name, dName); 38 | } 39 | } 40 | 41 | internal static void ResolveContainer(SearchResultEntry entry, ResolvedEntry resolved, ref Ou obj) 42 | { 43 | if (!Utils.IsMethodSet(ResolvedCollectionMethod.Container)) 44 | return; 45 | 46 | var domain = Utils.ConvertDnToDomain(entry.DistinguishedName); 47 | 48 | var opts = entry.GetProp("gpoptions"); 49 | obj.Properties.Add("blocksinheritance", opts != null && opts.Equals("1")); 50 | 51 | //Resolve GPLinks on the ou 52 | var links = new List(); 53 | 54 | var gpLinks = entry.GetProp("gplink"); 55 | if (gpLinks != null) 56 | { 57 | foreach (var l in gpLinks.Split(']', '[').Where(x => x.StartsWith("LDAP"))) 58 | { 59 | var split = l.Split(';'); 60 | var dn = split[0]; 61 | var status = split[1]; 62 | if (status.Equals("3") || status.Equals("1")) 63 | continue; 64 | 65 | var enforced = status.Equals("2"); 66 | var index = dn.IndexOf("CN=", StringComparison.OrdinalIgnoreCase) + 4; 67 | var name = dn.Substring(index, index + 25).ToUpper(); 68 | 69 | if (!_gpoCache.ContainsKey(name)) continue; 70 | 71 | var dName = _gpoCache[name]; 72 | links.Add(new GpLink 73 | { 74 | IsEnforced = enforced, 75 | Name = $"{dName}@{domain}" 76 | }); 77 | } 78 | 79 | obj.Links = links.ToArray(); 80 | } 81 | 82 | var computers = new List(); 83 | var users = new List(); 84 | var ous = new List(); 85 | foreach (var subEntry in _utils.DoSearch( 86 | "(|(samAccountType=805306368)(samAccountType=805306369)(objectclass=organizationalUnit))", 87 | SearchScope.OneLevel, 88 | new[] 89 | { 90 | "samaccountname", "name", "objectguid", "objectclass", "objectsid", "samaccounttype", "dnshostname" 91 | }, domain, entry.DistinguishedName)) 92 | { 93 | 94 | 95 | var subResolved = subEntry.ResolveAdEntry(); 96 | 97 | if (subResolved == null) 98 | continue; 99 | 100 | if (subResolved.ObjectType.Equals("ou")) 101 | { 102 | ous.Add(new Guid(subEntry.GetPropBytes("objectguid")).ToString().ToUpper()); 103 | } 104 | else if (subResolved.ObjectType.Equals("computer")) 105 | { 106 | computers.Add(subResolved.BloodHoundDisplay); 107 | } 108 | else 109 | { 110 | users.Add(subResolved.BloodHoundDisplay); 111 | } 112 | } 113 | 114 | obj.Users = users.ToArray(); 115 | 116 | obj.Computers = computers.ToArray(); 117 | 118 | obj.ChildOus = ous.ToArray(); 119 | } 120 | 121 | internal static void ResolveContainer(SearchResultEntry entry, ResolvedEntry resolved, ref Domain obj) 122 | { 123 | if (!Utils.IsMethodSet(ResolvedCollectionMethod.Container)) 124 | return; 125 | 126 | var domain = Utils.ConvertDnToDomain(entry.DistinguishedName); 127 | 128 | //Resolve GPLinks on the domain 129 | var links = new List(); 130 | 131 | var gpLinks = entry.GetProp("gplink"); 132 | if (gpLinks != null) 133 | { 134 | foreach (var l in gpLinks.Split(']', '[').Where(x => x.StartsWith("LDAP"))) 135 | { 136 | var split = l.Split(';'); 137 | var dn = split[0]; 138 | var status = split[1]; 139 | if (status.Equals("3") || status.Equals("1")) 140 | continue; 141 | 142 | var enforced = status.Equals("2"); 143 | var index = dn.IndexOf("CN=", StringComparison.OrdinalIgnoreCase) + 4; 144 | var name = dn.Substring(index, index + 25).ToUpper(); 145 | if (!_gpoCache.ContainsKey(name)) continue; 146 | 147 | var dName = _gpoCache[name]; 148 | links.Add(new GpLink 149 | { 150 | IsEnforced = enforced, 151 | Name = $"{dName}@{domain}" 152 | }); 153 | } 154 | 155 | obj.Links = links.ToArray(); 156 | } 157 | 158 | var computers = new List(); 159 | var users = new List(); 160 | var ous = new List(); 161 | foreach (var subEntry in _utils.DoSearch( 162 | "(|(samAccountType=805306368)(samAccountType=805306369)(objectclass=organizationalUnit))", 163 | SearchScope.OneLevel, 164 | new[] 165 | { 166 | "samaccountname", "name", "objectguid", "objectclass", "objectsid", "samaccounttype", "dnshostname" 167 | }, domain, entry.DistinguishedName)) 168 | { 169 | var subResolved = subEntry.ResolveAdEntry(); 170 | 171 | if (subResolved == null) 172 | continue; 173 | 174 | if (subResolved.ObjectType.Equals("ou")) 175 | { 176 | ous.Add(new Guid(subEntry.GetPropBytes("objectguid")).ToString().ToUpper()); 177 | } 178 | else if (subResolved.ObjectType.Equals("computer")) 179 | { 180 | computers.Add(subResolved.BloodHoundDisplay); 181 | } 182 | else 183 | { 184 | users.Add(subResolved.BloodHoundDisplay); 185 | } 186 | } 187 | 188 | foreach (var container in _utils.DoSearch("(objectclass=container)", SearchScope.OneLevel, 189 | new[] { "name", "distinguishedname" }, domain)) 190 | { 191 | foreach (var subEntry in _utils.DoSearch("(|(samAccountType=805306368)(samAccountType=805306369))", 192 | SearchScope.Subtree, new[] { "samaccounttype", "samaccountname", "distinguishedname", "dnshostname", "objectsid" }, 193 | domain, container.DistinguishedName)) 194 | { 195 | var subResolved = subEntry.ResolveAdEntry(); 196 | if (subResolved == null) 197 | { 198 | continue; 199 | } 200 | if (subResolved.ObjectType.Equals("computer")) 201 | { 202 | computers.Add(subResolved.BloodHoundDisplay); 203 | } 204 | else 205 | { 206 | users.Add(subResolved.BloodHoundDisplay); 207 | } 208 | } 209 | } 210 | 211 | obj.Users = users.ToArray(); 212 | 213 | obj.Computers = computers.ToArray(); 214 | 215 | obj.ChildOus = ous.ToArray(); 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /Sharphound2/Enumeration/GroupHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.DirectoryServices.Protocols; 4 | using System.Linq; 5 | using System.Reflection; 6 | using Sharphound2.JsonObjects; 7 | using GroupMember = Sharphound2.JsonObjects.GroupMember; 8 | 9 | namespace Sharphound2.Enumeration 10 | { 11 | internal static class GroupHelpers 12 | { 13 | private static Utils _utils; 14 | private static Cache _cache; 15 | private static readonly string[] Props = { "samaccountname", "distinguishedname", "samaccounttype", "dnshostname" }; 16 | public static void Init() 17 | { 18 | _utils = Utils.Instance; 19 | _cache = Cache.Instance; 20 | } 21 | 22 | public static void GetGroupInfo(SearchResultEntry entry, ResolvedEntry resolved, string domainSid, ref Group u) 23 | { 24 | if (!Utils.IsMethodSet(ResolvedCollectionMethod.Group)) 25 | return; 26 | 27 | var fMembers = new List(); 28 | var principalDisplayName = resolved.BloodHoundDisplay; 29 | var principalDomainName = Utils.ConvertDnToDomain(entry.DistinguishedName); 30 | 31 | if (resolved.ObjectType == "group") 32 | _cache.AddMapValue(entry.DistinguishedName, "group", principalDisplayName); 33 | 34 | var members = entry.GetPropArray("member"); 35 | 36 | if (members.Length == 0) 37 | { 38 | var tempMembers = new List(); 39 | var bottom = 0; 40 | 41 | while (true) 42 | { 43 | var top = bottom + 1499; 44 | var range = $"member;range={bottom}-{top}"; 45 | bottom += 1500; 46 | //Try ranged retrieval 47 | var result = _utils.DoSearch("(objectclass=*)", SearchScope.Base, new[] {range}, 48 | principalDomainName, 49 | entry.DistinguishedName).DefaultIfEmpty(null).FirstOrDefault(); 50 | 51 | //We didn't get an object back. Break out of the loop 52 | if (result?.Attributes.AttributeNames == null) 53 | { 54 | break; 55 | } 56 | var en = result.Attributes.AttributeNames.GetEnumerator(); 57 | 58 | //If the enumerator fails, that means theres really no members at all 59 | if (!en.MoveNext()) 60 | { 61 | break; 62 | } 63 | 64 | if (en.Current == null) 65 | { 66 | continue; 67 | } 68 | var attrib = en.Current.ToString(); 69 | if (attrib.EndsWith("-*")) 70 | { 71 | //We're done here, no more members to grab 72 | break; 73 | } 74 | tempMembers.AddRange(result.GetPropArray(attrib)); 75 | 76 | } 77 | 78 | members = tempMembers.ToArray(); 79 | } 80 | 81 | foreach (var dn in members) 82 | { 83 | //Check our cache first 84 | if (!_cache.GetMapValueUnknownType(dn, out var principal)) 85 | { 86 | if (dn.Contains("ForeignSecurityPrincipals")) 87 | { 88 | var sid = dn.Split(',')[0].Substring(3); 89 | if (dn.Contains("CN=S-1-5-21")) 90 | { 91 | var domain = _utils.SidToDomainName(sid); 92 | if (domain == null) 93 | { 94 | Utils.Verbose($"Unable to resolve domain for FSP {dn}"); 95 | continue; 96 | } 97 | 98 | principal = _utils.UnknownSidTypeToDisplay(sid, domain, Props); 99 | } 100 | else 101 | { 102 | if (!MappedPrincipal.GetCommon(sid, out principal)) 103 | { 104 | continue; 105 | } 106 | 107 | principal.PrincipalName = $"{principal.PrincipalName}@{principalDomainName}"; 108 | } 109 | } 110 | else 111 | { 112 | var objEntry = _utils 113 | .DoSearch("(objectclass=*)", SearchScope.Base, Props, Utils.ConvertDnToDomain(dn), dn) 114 | .DefaultIfEmpty(null).FirstOrDefault(); 115 | 116 | if (objEntry == null) 117 | { 118 | principal = null; 119 | } 120 | else 121 | { 122 | var resolvedObj = objEntry.ResolveAdEntry(); 123 | if (resolvedObj == null || resolvedObj.ObjectType == "domain") 124 | principal = null; 125 | else 126 | { 127 | _cache.AddMapValue(dn, resolvedObj.ObjectType, resolvedObj.BloodHoundDisplay); 128 | principal = new MappedPrincipal 129 | ( 130 | resolvedObj.BloodHoundDisplay, 131 | resolvedObj.ObjectType 132 | ); 133 | } 134 | } 135 | } 136 | } 137 | 138 | if (principal != null) 139 | { 140 | fMembers.Add(new GroupMember 141 | { 142 | MemberName = principal.PrincipalName, 143 | MemberType = principal.ObjectType 144 | }); 145 | } 146 | } 147 | 148 | u.Members = fMembers.Distinct().ToArray(); 149 | } 150 | 151 | internal static void GetGroupInfo(SearchResultEntry entry, ResolvedEntry resolved, string domainSid, ref User u) 152 | { 153 | if (!Utils.IsMethodSet(ResolvedCollectionMethod.Group)) 154 | return; 155 | 156 | var pgi = entry.GetProp("primarygroupid"); 157 | if (pgi == null) return; 158 | 159 | var pgsid = $"{domainSid}-{pgi}"; 160 | var primaryGroupName = _utils.SidToDisplay(pgsid, Utils.ConvertDnToDomain(entry.DistinguishedName), Props, "group"); 161 | u.PrimaryGroup = primaryGroupName; 162 | } 163 | 164 | internal static void GetGroupInfo(SearchResultEntry entry, ResolvedEntry resolved, string domainSid, ref Computer u) 165 | { 166 | if (!Utils.IsMethodSet(ResolvedCollectionMethod.Group)) 167 | return; 168 | 169 | var pgi = entry.GetProp("primarygroupid"); 170 | if (pgi == null) return; 171 | 172 | var pgsid = $"{domainSid}-{pgi}"; 173 | var primaryGroupName = _utils.SidToDisplay(pgsid, Utils.ConvertDnToDomain(entry.DistinguishedName), Props, "group"); 174 | u.PrimaryGroup = primaryGroupName; 175 | } 176 | 177 | #region Pinvoke 178 | public enum AdsTypes 179 | { 180 | AdsNameTypeDn = 1, 181 | AdsNameTypeCanonical = 2, 182 | AdsNameTypeNt4 = 3, 183 | AdsNameTypeDisplay = 4, 184 | AdsNameTypeDomainSimple = 5, 185 | AdsNameTypeEnterpriseSimple = 6, 186 | AdsNameTypeGuid = 7, 187 | AdsNameTypeUnknown = 8, 188 | AdsNameTypeUserPrincipalName = 9, 189 | AdsNameTypeCanonicalEx = 10, 190 | AdsNameTypeServicePrincipalName = 11, 191 | AdsNameTypeSidOrSidHistoryName = 12 192 | } 193 | 194 | public static string ConvertAdName(string objectName, AdsTypes inputType, AdsTypes outputType) 195 | { 196 | string domain; 197 | 198 | if (inputType.Equals(AdsTypes.AdsNameTypeNt4)) 199 | { 200 | objectName = objectName.Replace("/", "\\"); 201 | } 202 | 203 | switch (inputType) 204 | { 205 | case AdsTypes.AdsNameTypeNt4: 206 | domain = objectName.Split('\\')[0]; 207 | break; 208 | case AdsTypes.AdsNameTypeDomainSimple: 209 | domain = objectName.Split('@')[1]; 210 | break; 211 | case AdsTypes.AdsNameTypeCanonical: 212 | domain = objectName.Split('/')[0]; 213 | break; 214 | case AdsTypes.AdsNameTypeDn: 215 | domain = objectName.Substring(objectName.IndexOf("DC=", StringComparison.Ordinal)).Replace("DC=", "").Replace(",", "."); 216 | break; 217 | default: 218 | domain = ""; 219 | break; 220 | } 221 | 222 | try 223 | { 224 | var translateName = Type.GetTypeFromProgID("NameTranslate"); 225 | var translateInstance = Activator.CreateInstance(translateName); 226 | 227 | var args = new object[2]; 228 | args[0] = 1; 229 | args[1] = domain; 230 | translateName.InvokeMember("Init", BindingFlags.InvokeMethod, null, translateInstance, args); 231 | 232 | args = new object[2]; 233 | args[0] = (int)inputType; 234 | args[1] = objectName; 235 | translateName.InvokeMember("Set", BindingFlags.InvokeMethod, null, translateInstance, args); 236 | 237 | args = new object[1]; 238 | args[0] = (int)outputType; 239 | 240 | var result = (string)translateName.InvokeMember("Get", BindingFlags.InvokeMethod, null, translateInstance, args); 241 | 242 | return result; 243 | } 244 | catch 245 | { 246 | return null; 247 | } 248 | } 249 | #endregion 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /Sharphound2/Enumeration/LdapFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.Remoting.Channels; 5 | 6 | namespace Sharphound2.Enumeration 7 | { 8 | internal class LdapFilter 9 | { 10 | 11 | internal static LdapData BuildLdapData(ResolvedCollectionMethod methods, bool excludeDc, string ldapFilter) 12 | { 13 | var filterparts = new List(); 14 | var props = new List {"objectsid","distiguishedname"}; 15 | if ((methods & ResolvedCollectionMethod.Group) != 0) 16 | { 17 | filterparts.Add("(|(samaccounttype=268435456)(samaccounttype=268435457)(samaccounttype=536870912)(samaccounttype=536870913)(primarygroupid=*))"); 18 | props.AddRange(new[] 19 | { 20 | "samaccountname", "distinguishedname", "samaccounttype", "member", "cn", "primarygroupid", "dnshostname" 21 | }); 22 | } 23 | 24 | if ((methods & ResolvedCollectionMethod.LocalAdmin) != 0 || 25 | (methods & ResolvedCollectionMethod.Session) != 0 || 26 | (methods & ResolvedCollectionMethod.LoggedOn) != 0 || 27 | (methods & ResolvedCollectionMethod.RDP) != 0 || 28 | (methods & ResolvedCollectionMethod.SessionLoop) != 0 || 29 | (methods & ResolvedCollectionMethod.DCOM) != 0) 30 | { 31 | filterparts.Add("(&(sAMAccountType=805306369)(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))"); 32 | props.AddRange(new[] 33 | { 34 | "samaccountname", "distinguishedname", "dnshostname", "samaccounttype" 35 | }); 36 | } 37 | 38 | if ((methods & ResolvedCollectionMethod.Trusts) != 0) 39 | { 40 | filterparts.Add("(objectclass=domain)"); 41 | props.AddRange(new[] 42 | { 43 | "distinguishedname" 44 | }); 45 | } 46 | 47 | if ((methods & ResolvedCollectionMethod.ACL) != 0) 48 | { 49 | filterparts.Add("(|(samAccountType=805306368)(samAccountType=805306369)(samAccountType=268435456)(samAccountType=268435457)(samAccountType=536870912)(samAccountType=536870913)(objectClass=domain)(objectCategory=groupPolicyContainer))"); 50 | props.AddRange(new[] 51 | { 52 | "samaccountname", "distinguishedname", "dnshostname", "samaccounttype", "ntsecuritydescriptor", "displayname", "objectclass", "objectsid", "name" 53 | }); 54 | } 55 | 56 | if ((methods & ResolvedCollectionMethod.ObjectProps) != 0) 57 | { 58 | filterparts.Add("(|(samaccounttype=268435456)(samaccounttype=268435457)(samaccounttype=536870912)(samaccounttype=536870913)(samaccounttype=805306368)(samaccounttype=805306369)(objectclass=domain)(objectclass=organizationalUnit)(objectcategory=groupPolicyContainer))"); 59 | props.AddRange(new[] 60 | { 61 | "samaccountname", "distinguishedname", "samaccounttype", "pwdlastset", "lastlogon", "lastlogontimestamp", "objectsid", 62 | "sidhistory", "useraccountcontrol", "dnshostname", "operatingsystem", 63 | "operatingsystemservicepack", "serviceprincipalname", "displayname", "mail", "title", 64 | "homedirectory","description","admincount","userpassword","gpcfilesyspath","objectclass", 65 | "msds-behavior-version","objectguid", "name", "gpoptions", "msds-allowedToDelegateTo", "msDS-AllowedToActOnBehalfOfOtherIdentity" 66 | }); 67 | } 68 | 69 | if ((methods & ResolvedCollectionMethod.GPOLocalGroup) != 0) 70 | { 71 | filterparts.Add("(&(objectCategory=groupPolicyContainer)(name=*)(gpcfilesyspath=*))"); 72 | props.AddRange(new[] 73 | { 74 | "displayname", "name", "gpcfilesyspath", "objectclass" 75 | }); 76 | } 77 | 78 | if ((methods & ResolvedCollectionMethod.Container) != 0) 79 | { 80 | filterparts.Add("(|(&(objectCategory=groupPolicyContainer)(name=*)(gpcfilesyspath=*))(objectcategory=organizationalUnit)(objectClass=domain))"); 81 | props.AddRange(new[] 82 | { 83 | "displayname", "name", "objectguid", "gplink", "gpoptions", "objectclass" 84 | }); 85 | } 86 | 87 | if ((methods & ResolvedCollectionMethod.SPNTargets) != 0) 88 | { 89 | filterparts.Add("(|(&(samaccounttype=805306368)(serviceprincipalname=*)))"); 90 | props.AddRange(new[] 91 | { 92 | "serviceprincipalname", "samaccountname", "samaccounttype" 93 | }); 94 | } 95 | 96 | 97 | var filter = string.Join("", filterparts.ToArray()); 98 | filter = filterparts.Count == 1 ? filterparts[0] : $"(|{filter})"; 99 | 100 | if (excludeDc) 101 | { 102 | filter = $"(&({filter})(!(userAccountControl:1.2.840.113556.1.4.803:=8192)))"; 103 | } 104 | 105 | if (ldapFilter != null) 106 | { 107 | filter = $"(&({filter})({ldapFilter}))"; 108 | } 109 | 110 | props.Add("ms-mcs-admpwdexpirationtime"); 111 | 112 | return new LdapData 113 | { 114 | Filter = filter, 115 | Properties = props.Distinct().ToArray() 116 | }; 117 | } 118 | } 119 | 120 | internal class LdapData 121 | { 122 | public string Filter { get; set; } 123 | public string[] Properties { get; set; } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Sharphound2/Enumeration/MappedPrincipal.cs: -------------------------------------------------------------------------------- 1 | namespace Sharphound2.Enumeration 2 | { 3 | internal class MappedPrincipal 4 | { 5 | private string _principalName; 6 | private string _objectType; 7 | 8 | public string PrincipalName 9 | { 10 | get => _principalName.ToUpper(); 11 | set => _principalName = value; 12 | } 13 | 14 | public string ObjectType 15 | { 16 | get => _objectType.ToLower(); 17 | set => _objectType = value; 18 | } 19 | 20 | public MappedPrincipal(string name, string type) 21 | { 22 | PrincipalName = name; 23 | ObjectType = type; 24 | } 25 | 26 | public static bool GetCommon(string sid, out MappedPrincipal result) 27 | { 28 | switch (sid) 29 | { 30 | case "S-1-0": 31 | result = new MappedPrincipal("Null Authority", "USER"); 32 | break; 33 | case "S-1-0-0": 34 | result = new MappedPrincipal("Nobody", "USER"); 35 | break; 36 | case "S-1-1": 37 | result = new MappedPrincipal("World Authority", "USER"); 38 | break; 39 | case "S-1-1-0": 40 | result = new MappedPrincipal("Everyone", "GROUP"); 41 | break; 42 | case "S-1-2": 43 | result = new MappedPrincipal("Local Authority", "USER"); 44 | break; 45 | case "S-1-2-0": 46 | result = new MappedPrincipal("Local", "GROUP"); 47 | break; 48 | case "S-1-2-1": 49 | result = new MappedPrincipal("Console Logon", "GROUP"); 50 | break; 51 | case "S-1-3": 52 | result = new MappedPrincipal("Creator Authority", "USER"); 53 | break; 54 | case "S-1-3-0": 55 | result = new MappedPrincipal("Creator Owner", "USER"); 56 | break; 57 | case "S-1-3-1": 58 | result = new MappedPrincipal("Creator Group", "GROUP"); 59 | break; 60 | case "S-1-3-2": 61 | result = new MappedPrincipal("Creator Owner Server", "COMPUTER"); 62 | break; 63 | case "S-1-3-3": 64 | result = new MappedPrincipal("Creator Group Server", "COMPUTER"); 65 | break; 66 | case "S-1-3-4": 67 | result = new MappedPrincipal("Owner Rights", "GROUP"); 68 | break; 69 | case "S-1-4": 70 | result = new MappedPrincipal("Non-unique Authority", "USER"); 71 | break; 72 | case "S-1-5": 73 | result = new MappedPrincipal("NT Authority", "USER"); 74 | break; 75 | case "S-1-5-1": 76 | result = new MappedPrincipal("Dialup", "GROUP"); 77 | break; 78 | case "S-1-5-2": 79 | result = new MappedPrincipal("Network", "GROUP"); 80 | break; 81 | case "S-1-5-3": 82 | result = new MappedPrincipal("Batch", "GROUP"); 83 | break; 84 | case "S-1-5-4": 85 | result = new MappedPrincipal("Interactive", "GROUP"); 86 | break; 87 | case "S-1-5-6": 88 | result = new MappedPrincipal("Service", "GROUP"); 89 | break; 90 | case "S-1-5-7": 91 | result = new MappedPrincipal("Anonymous", "GROUP"); 92 | break; 93 | case "S-1-5-8": 94 | result = new MappedPrincipal("Proxy", "GROUP"); 95 | break; 96 | case "S-1-5-9": 97 | result = new MappedPrincipal("Enterprise Domain Controllers", "GROUP"); 98 | break; 99 | case "S-1-5-10": 100 | result = new MappedPrincipal("Principal Self", "USER"); 101 | break; 102 | case "S-1-5-11": 103 | result = new MappedPrincipal("Authenticated Users", "GROUP"); 104 | break; 105 | case "S-1-5-12": 106 | result = new MappedPrincipal("Restricted Code", "GROUP"); 107 | break; 108 | case "S-1-5-13": 109 | result = new MappedPrincipal("Terminal Server Users", "GROUP"); 110 | break; 111 | case "S-1-5-14": 112 | result = new MappedPrincipal("Remote Interactive Logon", "GROUP"); 113 | break; 114 | case "S-1-5-15": 115 | result = new MappedPrincipal("This Organization ", "GROUP"); 116 | break; 117 | case "S-1-5-17": 118 | result = new MappedPrincipal("This Organization ", "GROUP"); 119 | break; 120 | case "S-1-5-18": 121 | result = new MappedPrincipal("Local System", "USER"); 122 | break; 123 | case "S-1-5-19": 124 | result = new MappedPrincipal("NT Authority", "USER"); 125 | break; 126 | case "S-1-5-20": 127 | result = new MappedPrincipal("NT Authority", "USER"); 128 | break; 129 | case "S-1-5-80-0": 130 | result = new MappedPrincipal("All Services ", "GROUP"); 131 | break; 132 | case "S-1-5-32-544": 133 | result = new MappedPrincipal("Administrators", "GROUP"); 134 | break; 135 | case "S-1-5-32-545": 136 | result = new MappedPrincipal("Users", "GROUP"); 137 | break; 138 | case "S-1-5-32-546": 139 | result = new MappedPrincipal("Guests", "GROUP"); 140 | break; 141 | case "S-1-5-32-547": 142 | result = new MappedPrincipal("Power Users", "GROUP"); 143 | break; 144 | case "S-1-5-32-548": 145 | result = new MappedPrincipal("Account Operators", "GROUP"); 146 | break; 147 | case "S-1-5-32-549": 148 | result = new MappedPrincipal("Server Operators", "GROUP"); 149 | break; 150 | case "S-1-5-32-550": 151 | result = new MappedPrincipal("Print Operators", "GROUP"); 152 | break; 153 | case "S-1-5-32-551": 154 | result = new MappedPrincipal("Backup Operators", "GROUP"); 155 | break; 156 | case "S-1-5-32-552": 157 | result = new MappedPrincipal("Replicators", "GROUP"); 158 | break; 159 | case "S-1-5-32-554": 160 | result = new MappedPrincipal("Pre-Windows 2000 Compatible Access", "GROUP"); 161 | break; 162 | case "S-1-5-32-555": 163 | result = new MappedPrincipal("Remote Desktop Users", "GROUP"); 164 | break; 165 | case "S-1-5-32-556": 166 | result = new MappedPrincipal("Network Configuration Operators", "GROUP"); 167 | break; 168 | case "S-1-5-32-557": 169 | result = new MappedPrincipal("Incoming Forest Trust Builders", "GROUP"); 170 | break; 171 | case "S-1-5-32-558": 172 | result = new MappedPrincipal("Performance Monitor Users", "GROUP"); 173 | break; 174 | case "S-1-5-32-559": 175 | result = new MappedPrincipal("Performance Log Users", "GROUP"); 176 | break; 177 | case "S-1-5-32-560": 178 | result = new MappedPrincipal("Windows Authorization Access Group", "GROUP"); 179 | break; 180 | case "S-1-5-32-561": 181 | result = new MappedPrincipal("Terminal Server License Servers", "GROUP"); 182 | break; 183 | case "S-1-5-32-562": 184 | result = new MappedPrincipal("Distributed COM Users", "GROUP"); 185 | break; 186 | case "S-1-5-32-568": 187 | result = new MappedPrincipal("IIS_IUSRS", "GROUP"); 188 | break; 189 | case "S-1-5-32-569": 190 | result = new MappedPrincipal("Cryptographic Operators", "GROUP"); 191 | break; 192 | case "S-1-5-32-573": 193 | result = new MappedPrincipal("Event Log Readers", "GROUP"); 194 | break; 195 | case "S-1-5-32-574": 196 | result = new MappedPrincipal("Certificate Service DCOM Access", "GROUP"); 197 | break; 198 | case "S-1-5-32-575": 199 | result = new MappedPrincipal("RDS Remote Access Servers", "GROUP"); 200 | break; 201 | case "S-1-5-32-576": 202 | result = new MappedPrincipal("RDS Endpoint Servers", "GROUP"); 203 | break; 204 | case "S-1-5-32-577": 205 | result = new MappedPrincipal("RDS Management Servers", "GROUP"); 206 | break; 207 | case "S-1-5-32-578": 208 | result = new MappedPrincipal("Hyper-V Administrators", "GROUP"); 209 | break; 210 | case "S-1-5-32-579": 211 | result = new MappedPrincipal("Access Control Assistance Operators", "GROUP"); 212 | break; 213 | case "S-1-5-32-580": 214 | result = new MappedPrincipal("Remote Management Users", "GROUP"); 215 | break; 216 | default: 217 | result = null; 218 | break; 219 | } 220 | return result != null; 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /Sharphound2/Enumeration/ObjectPropertyHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.DirectoryServices; 4 | using System.DirectoryServices.Protocols; 5 | using System.Linq; 6 | using System.Security.AccessControl; 7 | using System.Security.Principal; 8 | using Sharphound2.JsonObjects; 9 | 10 | namespace Sharphound2.Enumeration 11 | { 12 | internal class ObjectPropertyHelpers 13 | { 14 | private static readonly DateTime Subt = new DateTime(1970,1,1); 15 | private static readonly string[] Props = { "distinguishedname", "samaccounttype", "samaccountname", "dnshostname" }; 16 | 17 | internal static void GetProps(SearchResultEntry entry, ResolvedEntry resolved, ref Domain obj) 18 | { 19 | if (!Utils.IsMethodSet(ResolvedCollectionMethod.ObjectProps)) 20 | { 21 | return; 22 | } 23 | 24 | obj.Properties.Add("description",entry.GetProp("description")); 25 | if (!int.TryParse(entry.GetProp("msds-behavior-version"), out var level)) 26 | { 27 | level = -1; 28 | } 29 | string func; 30 | switch (level) 31 | { 32 | case 0: 33 | func = "2000 Mixed/Native"; 34 | break; 35 | case 1: 36 | func = "2003 Interim"; 37 | break; 38 | case 2: 39 | func = "2003"; 40 | break; 41 | case 3: 42 | func = "2008"; 43 | break; 44 | case 4: 45 | func = "2008 R2"; 46 | break; 47 | case 5: 48 | func = "2012"; 49 | break; 50 | case 6: 51 | func = "2012 R2"; 52 | break; 53 | case 7: 54 | func = "2016"; 55 | break; 56 | default: 57 | func = "Unknown"; 58 | break; 59 | } 60 | 61 | obj.Properties.Add("functionallevel", func); 62 | } 63 | 64 | internal static void GetProps(SearchResultEntry entry, ResolvedEntry resolved, ref Ou obj) 65 | { 66 | if (!Utils.IsMethodSet(ResolvedCollectionMethod.ObjectProps)) 67 | { 68 | return; 69 | } 70 | 71 | obj.Properties.Add("description", entry.GetProp("description")); 72 | } 73 | 74 | internal static void GetProps(SearchResultEntry entry, ResolvedEntry resolved, ref Gpo obj) 75 | { 76 | if (!Utils.IsMethodSet(ResolvedCollectionMethod.ObjectProps)) 77 | { 78 | return; 79 | } 80 | 81 | obj.Properties.Add("description", entry.GetProp("description")); 82 | obj.Properties.Add("gpcpath", entry.GetProp("gpcfilesyspath")); 83 | } 84 | 85 | internal static void GetProps(SearchResultEntry entry, ResolvedEntry resolved, ref Group obj) 86 | { 87 | if (!Utils.IsMethodSet(ResolvedCollectionMethod.ObjectProps)) 88 | { 89 | return; 90 | } 91 | var ac = entry.GetProp("admincount"); 92 | if (ac != null) 93 | { 94 | var a = int.Parse(ac); 95 | obj.Properties.Add("admincount", a != 0); 96 | } 97 | else 98 | { 99 | obj.Properties.Add("admincount", false); 100 | } 101 | 102 | obj.Properties.Add("description", entry.GetProp("description")); 103 | } 104 | 105 | internal static void GetProps(SearchResultEntry entry, ResolvedEntry resolved, ref User obj) 106 | { 107 | if (!Utils.IsMethodSet(ResolvedCollectionMethod.ObjectProps)) 108 | { 109 | return; 110 | } 111 | var uac = entry.GetProp("useraccountcontrol"); 112 | bool enabled, trustedToAuth, sensitive, dontReqPreAuth, passwdNotReq, unconstrained; 113 | if (int.TryParse(uac, out var flag)) 114 | { 115 | var flags = (UacFlags)flag; 116 | enabled = (flags & UacFlags.AccountDisable) == 0; 117 | trustedToAuth = (flags & UacFlags.TrustedToAuthForDelegation) != 0; 118 | sensitive = (flags & UacFlags.NotDelegated) != 0; 119 | dontReqPreAuth = (flags & UacFlags.DontReqPreauth) != 0; 120 | passwdNotReq = (flags & UacFlags.PasswordNotRequired) != 0; 121 | unconstrained = (flags & UacFlags.TrustedForDelegation) != 0; 122 | } 123 | else 124 | { 125 | trustedToAuth = false; 126 | enabled = true; 127 | sensitive = false; 128 | dontReqPreAuth = false; 129 | passwdNotReq = false; 130 | unconstrained = false; 131 | } 132 | 133 | var comps = new List(); 134 | 135 | if (trustedToAuth) 136 | { 137 | var delegates = entry.GetPropArray("msds-allowedToDelegateTo"); 138 | obj.Properties.Add("allowedtodelegate", delegates); 139 | 140 | foreach (var d in delegates) 141 | { 142 | var hname = d.Contains("/") ? d.Split('/')[1] : d; 143 | hname = hname.Split(':')[0]; 144 | var resolvedHost = Utils.Instance.ResolveHost(hname, Utils.ConvertDnToDomain(entry.DistinguishedName)); 145 | if (resolvedHost.Contains(".")) 146 | { 147 | comps.Add(resolvedHost.ToUpper()); 148 | } 149 | } 150 | } 151 | obj.AllowedToDelegate = comps.Distinct().ToArray(); 152 | 153 | obj.Properties.Add("enabled", enabled); 154 | //var history = entry.GetPropBytes("sidhistory"); 155 | //obj.SidHistory = history != null ? new SecurityIdentifier(history, 0).Value : ""; 156 | obj.Properties.Add("lastlogon", ConvertToUnixEpoch(entry.GetProp("lastlogon"))); 157 | obj.Properties.Add("lastlogontimestamp", ConvertToUnixEpoch(entry.GetProp("lastlogontimestamp"))); 158 | obj.Properties.Add("pwdlastset", ConvertToUnixEpoch(entry.GetProp("pwdlastset"))); 159 | var spn = entry.GetPropArray("serviceprincipalname"); 160 | obj.Properties.Add("serviceprincipalnames", spn); 161 | obj.Properties.Add("hasspn", spn.Length > 0); 162 | obj.Properties.Add("displayname", entry.GetProp("displayname")); 163 | obj.Properties.Add("email", entry.GetProp("mail")); 164 | obj.Properties.Add("title", entry.GetProp("title")); 165 | obj.Properties.Add("homedirectory", entry.GetProp("homedirectory")); 166 | obj.Properties.Add("description", entry.GetProp("description")); 167 | obj.Properties.Add("userpassword", entry.GetProp("userpassword")); 168 | obj.Properties.Add("sensitive", sensitive); 169 | obj.Properties.Add("dontreqpreauth", dontReqPreAuth); 170 | obj.Properties.Add("passwordnotreqd", passwdNotReq); 171 | obj.Properties.Add("unconstraineddelegation", unconstrained); 172 | var ac = entry.GetProp("admincount"); 173 | if (ac != null) 174 | { 175 | var a = int.Parse(ac); 176 | obj.Properties.Add("admincount", a != 0); 177 | } 178 | else 179 | { 180 | obj.Properties.Add("admincount", false); 181 | } 182 | } 183 | 184 | internal static void GetProps(SearchResultEntry entry, ResolvedEntry resolved, ref Computer obj) 185 | { 186 | if (!Utils.IsMethodSet(ResolvedCollectionMethod.ObjectProps)) 187 | { 188 | return; 189 | } 190 | var uac = entry.GetProp("useraccountcontrol"); 191 | bool enabled, unconstrained, trustedToAuth; 192 | if (int.TryParse(uac, out var flag)) 193 | { 194 | var flags = (UacFlags)flag; 195 | enabled = (flags & UacFlags.AccountDisable) == 0; 196 | unconstrained = (flags & UacFlags.TrustedForDelegation) == UacFlags.TrustedForDelegation; 197 | trustedToAuth = (flags & UacFlags.TrustedToAuthForDelegation) != 0; 198 | } 199 | else 200 | { 201 | unconstrained = false; 202 | enabled = true; 203 | trustedToAuth = false; 204 | } 205 | 206 | var comps = new List(); 207 | 208 | if (trustedToAuth) 209 | { 210 | var delegates = entry.GetPropArray("msds-allowedToDelegateTo"); 211 | obj.Properties.Add("allowedtodelegate", delegates); 212 | 213 | foreach (var d in delegates) 214 | { 215 | var hname = d.Contains("/") ? d.Split('/')[1] : d; 216 | hname = hname.Split(':')[0]; 217 | var resolvedHost = 218 | Utils.Instance.ResolveHost(hname, Utils.ConvertDnToDomain(entry.DistinguishedName)); 219 | if (resolvedHost.Contains(".")) 220 | { 221 | comps.Add(resolvedHost.ToUpper()); 222 | } 223 | } 224 | } 225 | obj.AllowedToDelegate = comps.Distinct().ToArray(); 226 | 227 | var allowedToAct = entry.GetPropBytes("msDS-AllowedToActOnBehalfOfOtherIdentity"); 228 | if (allowedToAct != null) 229 | { 230 | var principals = new List(); 231 | var sd = new ActiveDirectorySecurity(); 232 | sd.SetSecurityDescriptorBinaryForm(allowedToAct, AccessControlSections.Access); 233 | var utils = Utils.Instance; 234 | foreach (ActiveDirectoryAccessRule rule in sd.GetAccessRules(true, true, typeof(SecurityIdentifier))) 235 | { 236 | var sid = rule.IdentityReference.Value; 237 | var domain = utils.SidToDomainName(sid) ?? Utils.ConvertDnToDomain(entry.DistinguishedName); 238 | if (!MappedPrincipal.GetCommon(sid, out var principal)) 239 | { 240 | principal = utils.UnknownSidTypeToDisplay(sid, domain, Props); 241 | } 242 | else 243 | { 244 | if (sid == "S-1-5-9") 245 | { 246 | var dObj = utils.GetForest(domain); 247 | var d = dObj == null ? domain : dObj.RootDomain.Name; 248 | principal.PrincipalName = $"ENTERPRISE DOMAIN CONTROLLERS@{d}".ToUpper(); 249 | } 250 | else 251 | { 252 | principal.PrincipalName = $"{principal.PrincipalName}@{domain}".ToUpper(); 253 | } 254 | } 255 | 256 | if (principal == null) 257 | { 258 | continue; 259 | } 260 | 261 | principals.Add(new LocalMember 262 | { 263 | Name = principal.PrincipalName, 264 | Type = principal.ObjectType 265 | }); 266 | } 267 | 268 | obj.AllowedToAct = principals.ToArray(); 269 | } 270 | 271 | obj.Properties.Add("enabled", enabled); 272 | obj.Properties.Add("unconstraineddelegation", unconstrained); 273 | obj.Properties.Add("lastlogon", ConvertToUnixEpoch(entry.GetProp("lastlogon"))); 274 | obj.Properties.Add("lastlogontimestamp", ConvertToUnixEpoch(entry.GetProp("lastlogontimestamp"))); 275 | obj.Properties.Add("pwdlastset", ConvertToUnixEpoch(entry.GetProp("pwdlastset"))); 276 | obj.Properties.Add("serviceprincipalnames", entry.GetPropArray("serviceprincipalname")); 277 | var os = entry.GetProp("operatingsystem"); 278 | var sp = entry.GetProp("operatingsystemservicepack"); 279 | 280 | if (sp != null) 281 | { 282 | os = $"{os} {sp}"; 283 | } 284 | 285 | obj.Properties.Add("operatingsystem", os); 286 | obj.Properties.Add("description", entry.GetProp("description")); 287 | } 288 | 289 | private static long ConvertToUnixEpoch(string ldapTime) 290 | { 291 | if (ldapTime == null) 292 | return -1; 293 | 294 | var time = long.Parse(ldapTime); 295 | if (time == 0) 296 | return 0; 297 | 298 | long toReturn; 299 | 300 | try 301 | { 302 | toReturn = (long) Math.Floor(DateTime.FromFileTimeUtc(time).Subtract(Subt).TotalSeconds); 303 | } 304 | catch 305 | { 306 | toReturn = -1; 307 | } 308 | 309 | return toReturn; 310 | } 311 | 312 | [Flags] 313 | public enum UacFlags 314 | { 315 | Script = 0x1, 316 | AccountDisable = 0x2, 317 | HomeDirRequired = 0x8, 318 | Lockout = 0x10, 319 | PasswordNotRequired = 0x20, 320 | PasswordCantChange = 0x40, 321 | EncryptedTextPwdAllowed = 0x80, 322 | TempDuplicateAccount = 0x100, 323 | NormalAccount = 0x200, 324 | InterdomainTrustAccount = 0x800, 325 | WorkstationTrustAccount = 0x1000, 326 | ServerTrustAccount = 0x2000, 327 | DontExpirePassword = 0x10000, 328 | MnsLogonAccount = 0x20000, 329 | SmartcardRequired = 0x40000, 330 | TrustedForDelegation = 0x80000, 331 | NotDelegated = 0x100000, 332 | UseDesKeyOnly = 0x200000, 333 | DontReqPreauth = 0x400000, 334 | PasswordExpired = 0x800000, 335 | TrustedToAuthForDelegation = 0x1000000, 336 | PartialSecretsAccount = 0x04000000 337 | } 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /Sharphound2/Enumeration/ResolvedEntry.cs: -------------------------------------------------------------------------------- 1 | namespace Sharphound2.Enumeration 2 | { 3 | internal class ResolvedEntry 4 | { 5 | private string _displayName; 6 | private string _objecttype; 7 | 8 | public string BloodHoundDisplay 9 | { 10 | get => _displayName.ToUpper(); 11 | set => _displayName = value; 12 | } 13 | public string ObjectType 14 | { 15 | get => _objecttype.ToLower(); 16 | set => _objecttype = value; 17 | } 18 | 19 | public string ComputerSamAccountName { get; set; } 20 | 21 | public override string ToString() 22 | { 23 | return $"{BloodHoundDisplay} - {ObjectType}"; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sharphound2/Enumeration/SPNHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.DirectoryServices.Protocols; 4 | using System.Linq; 5 | using System.Text; 6 | using Sharphound2.JsonObjects; 7 | 8 | namespace Sharphound2.Enumeration 9 | { 10 | internal static class SPNHelpers 11 | { 12 | private static Utils _utils; 13 | 14 | internal static void GetSpnTargets(SearchResultEntry entry, ResolvedEntry resolved, ref User obj) 15 | { 16 | if (!Utils.IsMethodSet(ResolvedCollectionMethod.SPNTargets)) 17 | { 18 | return; 19 | } 20 | 21 | var spn = entry.GetPropArray("serviceprincipalname"); 22 | var resolvedTargets = new List(); 23 | var domain = Utils.ConvertDnToDomain(entry.DistinguishedName); 24 | foreach (var sp in spn) 25 | { 26 | if (sp.Contains("@")) 27 | continue; 28 | 29 | if (sp.ToLower().Contains("mssqlsvc")) 30 | { 31 | var initial = sp.Split('/')[1]; 32 | string host; 33 | int port; 34 | if (initial.Contains(':')) 35 | { 36 | var t = initial.Split(':'); 37 | host = t[0]; 38 | if (!int.TryParse(t[1], out port)) 39 | { 40 | port = 1433; 41 | } 42 | } 43 | else 44 | { 45 | host = initial; 46 | port = 1433; 47 | } 48 | 49 | var resolvedHost = Utils.Instance.ResolveHost(host, domain); 50 | if (!resolvedHost.Contains(".")) continue; 51 | 52 | if (Utils.CheckSqlServer(resolvedHost, port)) 53 | { 54 | resolvedTargets.Add(new SPNTarget 55 | { 56 | ComputerName = resolvedHost, 57 | Port = port, 58 | Service = "SQLAdmin" 59 | }); 60 | } 61 | } 62 | } 63 | 64 | obj.SPNTargets = resolvedTargets.Distinct().ToArray(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sharphound2/Enumeration/SessionHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.DirectoryServices.Protocols; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Net; 9 | using System.Runtime.InteropServices; 10 | using System.Text.RegularExpressions; 11 | using System.Threading.Tasks; 12 | using Heijden.DNS; 13 | using Microsoft.Win32; 14 | using Sharphound2.JsonObjects; 15 | 16 | namespace Sharphound2.Enumeration 17 | { 18 | internal static class SessionHelpers 19 | { 20 | private static Cache _cache; 21 | private static Utils _utils; 22 | private static Sharphound.Options _options; 23 | private static readonly string[] RegistryProps = {"samaccounttype", "samaccountname", "distinguishedname"}; 24 | private static readonly Regex SidRegex = new Regex(@"S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$", RegexOptions.Compiled); 25 | private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(10); 26 | 27 | public static void Init(Sharphound.Options opts) 28 | { 29 | _cache = Cache.Instance; 30 | _utils = Utils.Instance; 31 | _options = opts; 32 | } 33 | 34 | public static IEnumerable CollectStealthTargets(string domainName) 35 | { 36 | //Use a dictionary to unique stuff. 37 | var paths = new ConcurrentDictionary(); 38 | //First we want to get all user script paths/home directories/profilepaths 39 | Parallel.ForEach(_utils.DoSearch( 40 | "(&(samAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(|(homedirectory=*)(scriptpath=*)(profilepath=*)))", 41 | SearchScope.Subtree, new[] { "homedirectory", "scriptpath", "profilepath" }, 42 | domainName), x => 43 | { 44 | var result = x; 45 | var poss = new[] 46 | { 47 | result.GetProp("homedirectory"), result.GetProp("scriptpath"), 48 | result.GetProp("profilepath") 49 | }; 50 | 51 | foreach (var s in poss) 52 | { 53 | var split = s?.Split('\\'); 54 | if (!(split?.Length >= 3)) continue; 55 | var path = split[2]; 56 | paths.TryAdd(path, new byte()); 57 | } 58 | }); 59 | 60 | //Lets grab domain controllers as well 61 | if (!_options.ExcludeDC) 62 | { 63 | foreach (var entry in _utils.DoSearch("(userAccountControl:1.2.840.113556.1.4.803:=8192)", 64 | SearchScope.Subtree, 65 | new[] { "dnshostname", "samaccounttype", "samaccountname", "serviceprincipalname" }, 66 | domainName)) 67 | { 68 | var path = entry.ResolveAdEntry(); 69 | paths.TryAdd(path.BloodHoundDisplay, new byte()); 70 | } 71 | } 72 | 73 | foreach (var path in paths.Keys) 74 | { 75 | if (_options.ExcludeDC) 76 | { 77 | if (Directory.Exists($"\\\\{path}\\SYSVOL")) 78 | { 79 | continue; 80 | } 81 | } 82 | yield return new ResolvedEntry 83 | { 84 | BloodHoundDisplay = path, 85 | ObjectType = "computer", 86 | ComputerSamAccountName = "FAKESTRING" 87 | }; 88 | } 89 | } 90 | 91 | public static IEnumerable GetNetSessions(ResolvedEntry target, string computerDomain) 92 | { 93 | if (!Utils.IsMethodSet(ResolvedCollectionMethod.Session) && 94 | !Utils.IsMethodSet(ResolvedCollectionMethod.SessionLoop)) 95 | yield break; 96 | 97 | Utils.Debug($"Starting NetSessionEnum for {target.BloodHoundDisplay}"); 98 | var resumeHandle = IntPtr.Zero; 99 | var si10 = typeof(SESSION_INFO_10); 100 | 101 | var entriesRead = 0; 102 | var ptrInfo = IntPtr.Zero; 103 | 104 | var t = Task.Factory.StartNew(() => NetSessionEnum(target.BloodHoundDisplay, null, null, 10, 105 | out ptrInfo, -1, out entriesRead, 106 | out _, ref resumeHandle)); 107 | 108 | var success = t.Wait(Timeout); 109 | 110 | if (!success) 111 | { 112 | throw new TimeoutException(); 113 | } 114 | 115 | var returnValue = t.Result; 116 | 117 | Utils.Debug($"EntriesRead from NetSessionEnum: {entriesRead}"); 118 | Utils.Debug($"ReturnValue from NetSessionEnum: {returnValue}"); 119 | 120 | //If we don't get a success, just break 121 | if (returnValue != (int)NERR.NERR_Success) yield break; 122 | 123 | var results = new SESSION_INFO_10[entriesRead]; 124 | var iter = ptrInfo; 125 | 126 | //Loop over the data and store it into an array 127 | for (var i = 0; i < entriesRead; i++) 128 | { 129 | results[i] = (SESSION_INFO_10)Marshal.PtrToStructure(iter, si10); 130 | iter = (IntPtr)(iter.ToInt64() + Marshal.SizeOf(si10)); 131 | } 132 | 133 | //Free the IntPtr 134 | NetApiBufferFree(ptrInfo); 135 | foreach (var result in results) 136 | { 137 | var username = result.sesi10_username; 138 | var cname = result.sesi10_cname; 139 | 140 | if (cname == null || username.EndsWith("$") || username.Trim() == "" || username == "$" || 141 | username == _options.CurrentUser || username == "ANONYMOUS LOGON") 142 | continue; 143 | 144 | if (cname.StartsWith("\\", StringComparison.CurrentCulture)) 145 | cname = cname.TrimStart('\\'); 146 | 147 | if (cname.Equals("[::1]") || cname.Equals("127.0.0.1")) 148 | cname = target.BloodHoundDisplay; 149 | 150 | Utils.Debug($"Result Username: {username}"); 151 | 152 | Utils.Debug($"Original cname: {cname}"); 153 | var watch = Stopwatch.StartNew(); 154 | var dnsHostName = _utils.ResolveCname(cname, computerDomain).Replace("\\", ""); 155 | watch.Stop(); 156 | Utils.Debug($"Name resolution took {watch.ElapsedMilliseconds}"); 157 | Utils.Debug($"Result cname: {dnsHostName}"); 158 | 159 | //If we're skipping Global Catalog deconfliction, just return a session 160 | if (_options.SkipGcDeconfliction) 161 | { 162 | yield return new Session { ComputerName = dnsHostName, UserName = username, Weight = 2 }; 163 | } 164 | else 165 | { 166 | //Check our cache first 167 | if (!_cache.GetGcMap(username.ToUpper(), out var possible)) 168 | { 169 | Utils.Debug($"Missed cache hit for {username}"); 170 | //If we didn't get a cache hit, search the global catalog 171 | var temp = new List(); 172 | foreach (var entry in _utils.DoSearch( 173 | $"(&(samAccountType=805306368)(samaccountname={username}))", SearchScope.Subtree, 174 | new[] {"distinguishedname"}, useGc: true)) 175 | { 176 | temp.Add(Utils.ConvertDnToDomain(entry.DistinguishedName).ToUpper()); 177 | } 178 | 179 | possible = temp.ToArray(); 180 | _cache.AddGcMap(username.ToUpper(), possible); 181 | } 182 | else 183 | { 184 | Utils.Debug($"Cache hit for {username}"); 185 | if (possible == null) 186 | { 187 | possible = new string[0]; 188 | } 189 | } 190 | 191 | switch (possible.Length) 192 | { 193 | case 0: 194 | //Object isn't in GC, so we'll default to the computer's domain 195 | yield return new Session 196 | { 197 | UserName = $"{username}@{computerDomain}", 198 | ComputerName = dnsHostName, 199 | Weight = 2 200 | }; 201 | break; 202 | case 1: 203 | //Exactly one instance of this samaccountname, the best scenario 204 | yield return new Session 205 | { 206 | UserName = $"{username}@{possible.First()}", 207 | ComputerName = dnsHostName, 208 | Weight = 2 209 | }; 210 | break; 211 | default: 212 | //Multiple possibilities (whyyyyy) 213 | //Add a weight of 1 for same domain as computer, 2 for others 214 | foreach (var possibility in possible) 215 | { 216 | var weight = possibility.Equals(computerDomain, StringComparison.CurrentCultureIgnoreCase) ? 1 : 2; 217 | 218 | yield return new Session 219 | { 220 | Weight = weight, 221 | ComputerName = dnsHostName.ToUpper(), 222 | UserName = $"{username}@{possibility}" 223 | }; 224 | } 225 | break; 226 | } 227 | } 228 | } 229 | 230 | Utils.DoJitter(); 231 | } 232 | 233 | internal static IEnumerable DoLoggedOnCollection(ResolvedEntry target, string domainName) 234 | { 235 | if (!Utils.IsMethodSet(ResolvedCollectionMethod.LoggedOn) && 236 | !Utils.IsMethodSet(ResolvedCollectionMethod.LoggedOnLoop)) 237 | yield break; 238 | 239 | var t = Task>.Factory.StartNew(() => 240 | { 241 | var users = new List(); 242 | users.AddRange(GetRegistryLoggedOn(target)); 243 | users.AddRange(GetNetLoggedOn(target, domainName)); 244 | 245 | return users; 246 | }); 247 | 248 | var success = t.Wait(Timeout); 249 | 250 | if (!success) 251 | { 252 | throw new TimeoutException(); 253 | } 254 | 255 | var sessions = new List(); 256 | 257 | foreach (var u in t.Result.Distinct()) 258 | { 259 | sessions.Add(new Session 260 | { 261 | ComputerName = target.BloodHoundDisplay, 262 | UserName = u, 263 | Weight = 1 264 | }); 265 | } 266 | 267 | foreach (var x in sessions) 268 | { 269 | yield return x; 270 | } 271 | 272 | Utils.DoJitter(); 273 | } 274 | 275 | //internal static void DoLoggedOnCollection(ResolvedEntry target, string domainName, ref Computer obj) 276 | //{ 277 | // if (!Utils.IsMethodSet(ResolvedCollectionMethod.LoggedOn)) 278 | // return; 279 | 280 | // var users = new List(); 281 | // users.AddRange(GetRegistryLoggedOn(target)); 282 | // users.AddRange(GetNetLoggedOn(target, domainName)); 283 | 284 | // var sessions = new List(); 285 | 286 | // foreach (var u in users.Distinct()) 287 | // { 288 | // sessions.Add(new Session 289 | // { 290 | // ComputerName = target.BloodHoundDisplay, 291 | // UserName = u, 292 | // Weight = 1 293 | // }); 294 | // } 295 | 296 | // if (sessions.Count > 0) 297 | // obj.Sessions = sessions.ToArray(); 298 | //} 299 | 300 | private static IEnumerable GetRegistryLoggedOn(ResolvedEntry target) 301 | { 302 | var users = new List(); 303 | try 304 | { 305 | //Remotely open the registry hive if its not our current one 306 | var key = RegistryKey.OpenRemoteBaseKey(RegistryHive.Users, 307 | Environment.MachineName.Equals(target.ComputerSamAccountName, StringComparison.CurrentCultureIgnoreCase) 308 | ? "" 309 | : target.BloodHoundDisplay); 310 | 311 | //Find all the subkeys that match our regex 312 | var filtered = key.GetSubKeyNames() 313 | .Where(sub => SidRegex.IsMatch(sub)); 314 | 315 | foreach (var subkey in filtered) 316 | { 317 | //Convert our sid to a username 318 | var user = _utils.SidToDisplay(subkey, _utils.SidToDomainName(subkey), 319 | RegistryProps, "user"); 320 | 321 | if (user == null) continue; 322 | users.Add(user.ToUpper()); 323 | } 324 | } 325 | catch (Exception) 326 | { 327 | yield break; 328 | } 329 | 330 | foreach (var user in users.Distinct()) 331 | { 332 | yield return user; 333 | } 334 | } 335 | 336 | private static IEnumerable GetNetLoggedOn(ResolvedEntry entry, string computerDomain) 337 | { 338 | var users = new List(); 339 | 340 | const int queryLevel = 1; 341 | var resumeHandle = 0; 342 | 343 | var tWui1 = typeof(WKSTA_USER_INFO_1); 344 | 345 | //Call the API to get logged on users 346 | var result = NetWkstaUserEnum(entry.BloodHoundDisplay, queryLevel, out IntPtr intPtr, -1, out int entriesRead, out int _, ref resumeHandle); 347 | 348 | //If we don't get 0 or 234 return 349 | if (result != 0 && result != 234) yield break; 350 | var iter = intPtr; 351 | for (var i = 0; i < entriesRead; i++) 352 | { 353 | var data = (WKSTA_USER_INFO_1)Marshal.PtrToStructure(iter, tWui1); 354 | iter = (IntPtr)(iter.ToInt64() + Marshal.SizeOf(tWui1)); 355 | 356 | var domain = data.wkui1_logon_domain; 357 | var username = data.wkui1_username; 358 | 359 | if (domain.Equals(entry.ComputerSamAccountName, StringComparison.CurrentCultureIgnoreCase) || 360 | username.Trim().Equals("") || username.EndsWith("$", StringComparison.Ordinal)) 361 | { 362 | continue; 363 | } 364 | 365 | //Try to convert the domain part to a FQDN, if it doesn't work just use the computer's domain 366 | var domainName = _utils.DomainNetbiosToFqdn(domain) ?? computerDomain; 367 | users.Add($"{username}@{domainName}".ToUpper()); 368 | } 369 | 370 | NetApiBufferFree(intPtr); 371 | 372 | foreach (var user in users.Distinct()) 373 | { 374 | yield return user; 375 | 376 | } 377 | } 378 | 379 | #region PInvoke Imports 380 | [DllImport("NetAPI32.dll", SetLastError = true)] 381 | private static extern int NetSessionEnum( 382 | [MarshalAs(UnmanagedType.LPWStr)] string ServerName, 383 | [MarshalAs(UnmanagedType.LPWStr)] string UncClientName, 384 | [MarshalAs(UnmanagedType.LPWStr)] string UserName, 385 | int Level, 386 | out IntPtr bufptr, 387 | int prefmaxlen, 388 | out int entriesread, 389 | out int totalentries, 390 | ref IntPtr resume_handle); 391 | 392 | [StructLayout(LayoutKind.Sequential)] 393 | public struct SESSION_INFO_10 394 | { 395 | [MarshalAs(UnmanagedType.LPWStr)] 396 | public string sesi10_cname; 397 | [MarshalAs(UnmanagedType.LPWStr)] 398 | public string sesi10_username; 399 | public uint sesi10_time; 400 | public uint sesi10_idle_time; 401 | } 402 | 403 | public enum NERR 404 | { 405 | NERR_Success = 0, 406 | ERROR_MORE_DATA = 234, 407 | ERROR_NO_BROWSER_SERVERS_FOUND = 6118, 408 | ERROR_INVALID_LEVEL = 124, 409 | ERROR_ACCESS_DENIED = 5, 410 | ERROR_INVALID_PARAMETER = 87, 411 | ERROR_NOT_ENOUGH_MEMORY = 8, 412 | ERROR_NETWORK_BUSY = 54, 413 | ERROR_BAD_NETPATH = 53, 414 | ERROR_NO_NETWORK = 1222, 415 | ERROR_INVALID_HANDLE_STATE = 1609, 416 | ERROR_EXTENDED_ERROR = 1208, 417 | NERR_BASE = 2100, 418 | NERR_UnknownDevDir = (NERR_BASE + 16), 419 | NERR_DuplicateShare = (NERR_BASE + 18), 420 | NERR_BufTooSmall = (NERR_BASE + 23) 421 | } 422 | 423 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 424 | public struct WKSTA_USER_INFO_1 425 | { 426 | public string wkui1_username; 427 | public string wkui1_logon_domain; 428 | public string wkui1_oth_domains; 429 | public string wkui1_logon_server; 430 | } 431 | 432 | [DllImport("netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 433 | private static extern int NetWkstaUserEnum( 434 | string servername, 435 | int level, 436 | out IntPtr bufptr, 437 | int prefmaxlen, 438 | out int entriesread, 439 | out int totalentries, 440 | ref int resume_handle); 441 | 442 | [DllImport("netapi32.dll")] 443 | private static extern int NetApiBufferFree( 444 | IntPtr Buff); 445 | #endregion 446 | } 447 | } 448 | -------------------------------------------------------------------------------- /Sharphound2/Enumeration/TrustHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.DirectoryServices.Protocols; 5 | using System.Linq; 6 | using System.Runtime.InteropServices; 7 | using Sharphound2.JsonObjects; 8 | 9 | namespace Sharphound2.Enumeration 10 | { 11 | [SuppressMessage("ReSharper", "UnusedMember.Local")] 12 | internal static class TrustHelpers 13 | { 14 | private static Utils _utils; 15 | 16 | public static void Init() 17 | { 18 | _utils = Utils.Instance; 19 | } 20 | 21 | internal static void DoTrustEnumeration(ResolvedEntry resolved, ref Domain obj) 22 | { 23 | if (!Utils.IsMethodSet(ResolvedCollectionMethod.Trusts)) 24 | return; 25 | 26 | if (resolved == null) 27 | return; 28 | 29 | var trusts = new List(); 30 | var dc = _utils.GetUsableDomainController(_utils.GetDomain(resolved.BloodHoundDisplay)); 31 | //var dc = _utils 32 | // .DoSearch("(userAccountControl:1.2.840.113556.1.4.803:=8192)", SearchScope.Subtree, 33 | // new[] { "dnshostname" }, resolved.BloodHoundDisplay).DefaultIfEmpty(null).FirstOrDefault(); 34 | 35 | if (dc == null) 36 | return; 37 | 38 | 39 | const uint flags = 63; 40 | var ddt = typeof(DsDomainTrusts); 41 | var result = DsEnumerateDomainTrusts(dc, flags, out var ptr, out var domainCount); 42 | 43 | if (result != 0) 44 | return; 45 | 46 | var array = new DsDomainTrusts[domainCount]; 47 | 48 | var iter = ptr; 49 | 50 | //Loop over the data and store it in an array 51 | for (var i = 0; i < domainCount; i++) 52 | { 53 | array[i] = (DsDomainTrusts)Marshal.PtrToStructure(iter, ddt); 54 | iter = (IntPtr)(iter.ToInt64() + Marshal.SizeOf(ddt)); 55 | } 56 | 57 | NetApiBufferFree(ptr); 58 | 59 | for (var i = 0; i < domainCount; i++) 60 | { 61 | var trust = new Trust(); 62 | 63 | var data = array[i]; 64 | var trustFlags = (TrustFlags)data.Flags; 65 | var trustAttribs = (TrustAttrib)data.TrustAttributes; 66 | 67 | // the domain itself 68 | if ((trustFlags & TrustFlags.DsDomainPrimary) == TrustFlags.DsDomainPrimary) 69 | continue; 70 | 71 | if (data.DnsDomainName == null) 72 | continue; 73 | trust.TargetName = data.DnsDomainName; 74 | var inbound = (trustFlags & TrustFlags.DsDomainDirectInbound) == TrustFlags.DsDomainDirectInbound; 75 | var outbound = (trustFlags & TrustFlags.DsDomainDirectOutbound) == TrustFlags.DsDomainDirectOutbound; 76 | 77 | if (inbound && outbound) 78 | { 79 | trust.TrustDirection = (int)TrustDirection.Bidirectional; 80 | } 81 | else if (inbound) 82 | { 83 | trust.TrustDirection = (int)TrustDirection.Inbound; 84 | } 85 | else if (outbound) 86 | { 87 | trust.TrustDirection = (int)TrustDirection.Outbound; 88 | } 89 | else 90 | { 91 | // a trust with no direction is probably not enabled (According to MS documentation) 92 | // see: https://docs.microsoft.com/fr-fr/windows/desktop/api/ntsecapi/ns-ntsecapi-_trusted_domain_information_ex (TrustDirection) 93 | continue; 94 | } 95 | 96 | // parentChild occure only when one of the domain is the forest root 97 | // Check is trusted domain is the current forest root or if trusted domain's parent is current enumerated domain 98 | if (((trustFlags & TrustFlags.DsDomainTreeRoot) == TrustFlags.DsDomainTreeRoot) && ((trustFlags & TrustFlags.DsDomainInForest) == TrustFlags.DsDomainInForest) || array[data.ParentIndex].DnsDomainName?.ToUpper() == resolved.BloodHoundDisplay) 99 | { 100 | trust.TrustType = "ParentChild"; 101 | } 102 | else if ((trustFlags & TrustFlags.DsDomainInForest) == TrustFlags.DsDomainInForest) 103 | { 104 | trust.TrustType = "CrossLink"; 105 | } 106 | else if ((trustAttribs & TrustAttrib.ForestTransitive) == TrustAttrib.ForestTransitive) 107 | { 108 | trust.TrustType = "Forest"; 109 | } 110 | else 111 | { 112 | trust.TrustType = "External"; 113 | } 114 | 115 | if ((trustAttribs & TrustAttrib.NonTransitive) == TrustAttrib.NonTransitive) 116 | { 117 | trust.IsTransitive = false; 118 | } 119 | else 120 | { 121 | trust.IsTransitive = true; 122 | } 123 | 124 | trusts.Add(trust); 125 | } 126 | 127 | obj.Trusts = trusts.ToArray(); 128 | } 129 | 130 | #region PINVOKE 131 | [Flags] 132 | private enum TrustFlags : uint 133 | { 134 | DsDomainInForest = 0x0001, // Domain is a member of the forest 135 | DsDomainDirectOutbound = 0x0002, // Domain is directly trusted 136 | DsDomainTreeRoot = 0x0004, // Domain is root of a tree in the forest 137 | DsDomainPrimary = 0x0008, // Domain is the primary domain of queried server 138 | DsDomainNativeMode = 0x0010, // Primary domain is running in native mode 139 | DsDomainDirectInbound = 0x0020 // Domain is directly trusting 140 | } 141 | 142 | [Flags] 143 | private enum TrustAttrib : uint 144 | { 145 | NonTransitive = 0x0001, 146 | UplevelOnly = 0x0002, 147 | FilterSids = 0x0004, 148 | ForestTransitive = 0x0008, 149 | CrossOrganization = 0x0010, 150 | WithinForest = 0x0020, 151 | TreatAsExternal = 0x0040 152 | } 153 | 154 | [StructLayout(LayoutKind.Sequential)] 155 | [SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")] 156 | private struct DsDomainTrusts 157 | { 158 | [MarshalAs(UnmanagedType.LPTStr)] 159 | private string NetbiosDomainName; 160 | [MarshalAs(UnmanagedType.LPTStr)] 161 | public string DnsDomainName; 162 | public uint Flags; 163 | public uint ParentIndex; 164 | private uint TrustTypeA; 165 | public uint TrustAttributes; 166 | private IntPtr DomainSid; 167 | private Guid DomainGuid; 168 | } 169 | 170 | [DllImport("Netapi32.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true, CharSet = CharSet.Auto)] 171 | private static extern uint DsEnumerateDomainTrusts(string serverName, 172 | uint flags, 173 | out IntPtr domains, 174 | out uint domainCount); 175 | 176 | [DllImport("Netapi32.dll", EntryPoint = "NetApiBufferFree")] 177 | private static extern uint NetApiBufferFree(IntPtr buffer); 178 | 179 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 180 | [SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")] 181 | private struct DomainControllerInfo 182 | { 183 | [MarshalAs(UnmanagedType.LPTStr)] private string DomainControllerName; 184 | [MarshalAs(UnmanagedType.LPTStr)] private string DomainControllerAddress; 185 | private uint DomainControllerAddressType; 186 | private Guid DomainGuid; 187 | [MarshalAs(UnmanagedType.LPTStr)] private string DomainName; 188 | [MarshalAs(UnmanagedType.LPTStr)] private string DnsForestName; 189 | private uint Flags; 190 | [MarshalAs(UnmanagedType.LPTStr)] private string DcSiteName; 191 | [MarshalAs(UnmanagedType.LPTStr)] private string ClientSiteName; 192 | } 193 | 194 | [DllImport("Netapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] 195 | private static extern int DsGetDcName 196 | ( 197 | [MarshalAs(UnmanagedType.LPTStr)] 198 | string computerName, 199 | [MarshalAs(UnmanagedType.LPTStr)] 200 | string domainName, 201 | [In] int domainGuid, 202 | [MarshalAs(UnmanagedType.LPTStr)] 203 | string siteName, 204 | [MarshalAs(UnmanagedType.U4)] 205 | DsGetDcNameFlags flags, 206 | out IntPtr pDomainControllerInfo 207 | ); 208 | 209 | [Flags] 210 | public enum DsGetDcNameFlags : uint 211 | { 212 | DsForceRediscovery = 0x00000001, 213 | DsDirectoryServiceRequired = 0x00000010, 214 | DsDirectoryServicePreferred = 0x00000020, 215 | DsGcServerRequired = 0x00000040, 216 | DsPdcRequired = 0x00000080, 217 | DsBackgroundOnly = 0x00000100, 218 | DsIpRequired = 0x00000200, 219 | DsKdcRequired = 0x00000400, 220 | DsTimeservRequired = 0x00000800, 221 | DsWritableRequired = 0x00001000, 222 | DsGoodTimeservPreferred = 0x00002000, 223 | DsAvoidSelf = 0x00004000, 224 | DsOnlyLdapNeeded = 0x00008000, 225 | DsIsFlatName = 0x00010000, 226 | DsIsDnsName = 0x00020000, 227 | DsReturnDnsName = 0x40000000, 228 | DsReturnFlatName = 0x80000000 229 | } 230 | 231 | [DllImport("advapi32", CharSet = CharSet.Auto, SetLastError = true)] 232 | private static extern bool ConvertSidToStringSid(IntPtr pSid, out string strSid); 233 | #endregion 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /Sharphound2/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.DirectoryServices; 4 | using System.DirectoryServices.Protocols; 5 | using System.Linq; 6 | using System.Security.Principal; 7 | using System.Text; 8 | using Newtonsoft.Json; 9 | using Sharphound2.Enumeration; 10 | 11 | namespace Sharphound2 12 | { 13 | public static class Extensions 14 | { 15 | private static readonly HashSet Groups = new HashSet { "268435456", "268435457", "536870912", "536870913" }; 16 | private static readonly HashSet Computers = new HashSet { "805306369" }; 17 | private static readonly HashSet Users = new HashSet { "805306368" }; 18 | //private static readonly Regex SpnSearch = new Regex(@"HOST\/([A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*)$", RegexOptions.Compiled); 19 | private static string _primaryDomain; 20 | 21 | internal static void SetPrimaryDomain(string domain) 22 | { 23 | _primaryDomain = domain; 24 | } 25 | 26 | public static string ToTitleCase(this string str) 27 | { 28 | return str.Substring(0, 1).ToUpper() + str.Substring(1).ToLower(); 29 | } 30 | 31 | //Helper method for testing stuff 32 | internal static void PrintEntry(this SearchResultEntry result) 33 | { 34 | foreach (var property in result.Attributes.AttributeNames) 35 | { 36 | Console.WriteLine(property); 37 | } 38 | } 39 | 40 | public static bool HasFlag(this Enum self, Enum test) 41 | { 42 | if (self == null || test == null) 43 | { 44 | return false; 45 | } 46 | 47 | try 48 | { 49 | var temp = Convert.ToInt32(self); 50 | var num = Convert.ToInt32(test); 51 | return (temp & num) == num; 52 | } 53 | catch (Exception) 54 | { 55 | return false; 56 | } 57 | 58 | } 59 | 60 | internal static void CloseC(this JsonTextWriter writer, int count, string type) 61 | { 62 | writer.Flush(); 63 | writer.WriteEndArray(); 64 | writer.WritePropertyName("meta"); 65 | writer.WriteStartObject(); 66 | writer.WritePropertyName("count"); 67 | writer.WriteValue(count); 68 | writer.WritePropertyName("type"); 69 | writer.WriteValue(type); 70 | writer.WriteEndObject(); 71 | writer.Close(); 72 | } 73 | 74 | internal static ResolvedEntry ResolveAdEntry(this SearchResultEntry result, bool bypassDns = false) 75 | { 76 | var entry = new ResolvedEntry(); 77 | 78 | var accountName = result.GetProp("samaccountname"); 79 | var distinguishedName = result.DistinguishedName; 80 | var accountType = result.GetProp("samaccounttype"); 81 | 82 | if (distinguishedName == null) 83 | return null; 84 | 85 | var domainName = Utils.ConvertDnToDomain(distinguishedName); 86 | 87 | if (MappedPrincipal.GetCommon(result.GetSid(), out var temp)) 88 | { 89 | return new ResolvedEntry 90 | { 91 | BloodHoundDisplay = $"{temp.PrincipalName}@{domainName}".ToUpper(), 92 | ObjectType = temp.ObjectType 93 | }; 94 | } 95 | 96 | if (Groups.Contains(accountType)) 97 | { 98 | entry.BloodHoundDisplay = $"{accountName}@{domainName}".ToUpper(); 99 | entry.ObjectType = "group"; 100 | return entry; 101 | } 102 | 103 | if (Users.Contains(accountType)) 104 | { 105 | entry.BloodHoundDisplay = $"{accountName}@{domainName}".ToUpper(); 106 | entry.ObjectType = "user"; 107 | return entry; 108 | } 109 | 110 | if (Computers.Contains(accountType)) 111 | { 112 | var shortName = accountName?.TrimEnd('$'); 113 | var dnshostname = result.GetProp("dnshostname"); 114 | if (dnshostname == null) 115 | { 116 | var domain = Utils.ConvertDnToDomain(result.DistinguishedName); 117 | dnshostname = $"{shortName}.{domain}".ToUpper(); 118 | } 119 | 120 | entry.BloodHoundDisplay = dnshostname; 121 | entry.ObjectType = "computer"; 122 | entry.ComputerSamAccountName = shortName; 123 | return entry; 124 | } 125 | 126 | if (accountType == null) 127 | { 128 | var objClass = result.GetPropArray("objectClass"); 129 | if (objClass.Contains("groupPolicyContainer")) 130 | { 131 | entry.BloodHoundDisplay = $"{result.GetProp("displayname")}@{domainName}"; 132 | entry.ObjectType = "gpo"; 133 | return entry; 134 | } 135 | 136 | if (objClass.Contains("organizationalUnit")) 137 | { 138 | entry.BloodHoundDisplay = $"{result.GetProp("name")}@{domainName}"; 139 | entry.ObjectType = "ou"; 140 | return entry; 141 | } 142 | 143 | if (objClass.Contains("container")) 144 | { 145 | entry.BloodHoundDisplay = domainName; 146 | entry.ObjectType = "container"; 147 | return entry; 148 | } 149 | } 150 | else 151 | { 152 | if (accountType.Equals("805306370")) 153 | return null; 154 | } 155 | entry.BloodHoundDisplay = domainName; 156 | entry.ObjectType = "domain"; 157 | return entry; 158 | } 159 | 160 | public static string GetProp(this SearchResultEntry searchResultEntry, string property) 161 | { 162 | if (!searchResultEntry.Attributes.Contains(property)) 163 | return null; 164 | 165 | var collection = searchResultEntry.Attributes[property]; 166 | var lookups = collection.GetValues(typeof(string)); 167 | if (lookups.Length == 0) 168 | return null; 169 | 170 | if (!(lookups[0] is string prop) || prop.Length == 0) 171 | return null; 172 | 173 | return prop; 174 | } 175 | 176 | public static byte[] GetPropBytes(this SearchResultEntry searchResultEntry, string property) 177 | { 178 | if (!searchResultEntry.Attributes.Contains(property)) 179 | return null; 180 | 181 | var collection = searchResultEntry.Attributes[property]; 182 | var lookups = collection.GetValues(typeof(byte[])); 183 | if (lookups.Length == 0) 184 | return null; 185 | 186 | if (!(lookups[0] is byte[] bytes) || bytes.Length == 0) 187 | return null; 188 | 189 | return bytes; 190 | } 191 | 192 | public static string[] GetPropArray(this SearchResultEntry searchResultEntry, string property) 193 | { 194 | if (!searchResultEntry.Attributes.Contains(property)) 195 | return new string[0]; 196 | 197 | var values = searchResultEntry.Attributes[property]; 198 | var strings = values.GetValues(typeof(string)); 199 | 200 | if (!(strings is string[] result)) 201 | return null; 202 | 203 | return result; 204 | } 205 | 206 | 207 | public static string GetSid(this SearchResultEntry searchResultEntry) 208 | { 209 | if (!searchResultEntry.Attributes.Contains("objectsid")) 210 | return null; 211 | 212 | var s = searchResultEntry.Attributes["objectsid"].GetValues(typeof(byte[])); 213 | if (s.Length == 0) 214 | return null; 215 | 216 | if (!(s[0] is byte[] sidBytes) || sidBytes.Length == 0) 217 | return null; 218 | 219 | return new SecurityIdentifier(sidBytes, 0).Value; 220 | } 221 | 222 | public static string GetSid(this DirectoryEntry result) 223 | { 224 | if (!result.Properties.Contains("objectsid")) 225 | return null; 226 | 227 | var s = result.Properties["objectsid"][0]; 228 | switch (s) 229 | { 230 | case byte[] b: 231 | return new SecurityIdentifier(b, 0).ToString(); 232 | case string st: 233 | return new SecurityIdentifier(Encoding.ASCII.GetBytes(st), 0).ToString(); 234 | } 235 | 236 | return null; 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /Sharphound2/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Sharphound2/JsonObjects/ACL.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Sharphound2.JsonObjects 4 | { 5 | internal class ACL : IEquatable 6 | { 7 | public string PrincipalName { get; set; } 8 | public string PrincipalType { get; set; } 9 | public string RightName { get; set; } 10 | 11 | public string AceType { get; set; } 12 | //public string Qualifier { get; set; } 13 | //public bool Inherited { get; set; } 14 | 15 | public bool Equals(ACL other) 16 | { 17 | if (ReferenceEquals(null, other)) return false; 18 | if (ReferenceEquals(this, other)) return true; 19 | return string.Equals(PrincipalName, other.PrincipalName) && 20 | string.Equals(PrincipalType, other.PrincipalType) && string.Equals(RightName, other.RightName) && 21 | string.Equals(AceType, other.AceType); 22 | } 23 | 24 | public override bool Equals(object obj) 25 | { 26 | if (ReferenceEquals(null, obj)) return false; 27 | if (ReferenceEquals(this, obj)) return true; 28 | if (obj.GetType() != GetType()) return false; 29 | return Equals((ACL) obj); 30 | } 31 | 32 | public override int GetHashCode() 33 | { 34 | unchecked 35 | { 36 | var hashCode = PrincipalName != null ? PrincipalName.GetHashCode() : 0; 37 | hashCode = (hashCode * 397) ^ (PrincipalType != null ? PrincipalType.GetHashCode() : 0); 38 | hashCode = (hashCode * 397) ^ (RightName != null ? RightName.GetHashCode() : 0); 39 | hashCode = (hashCode * 397) ^ (AceType != null ? AceType.GetHashCode() : 0); 40 | return hashCode; 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /Sharphound2/JsonObjects/Computer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Sharphound2.JsonObjects 4 | { 5 | internal class Computer : JsonBase 6 | { 7 | private string _name; 8 | public string Name 9 | { 10 | get => _name; 11 | set => _name = value.ToUpper(); 12 | } 13 | public string PrimaryGroup { get; set; } 14 | 15 | public Dictionary Properties = new Dictionary(); 16 | 17 | public LocalMember[] LocalAdmins { get; set; } 18 | public LocalMember[] RemoteDesktopUsers { get; set; } 19 | public LocalMember[] DcomUsers { get; set; } 20 | public string[] AllowedToDelegate { get; set; } 21 | public LocalMember[] AllowedToAct { get; set; } 22 | public ACL[] Aces { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sharphound2/JsonObjects/Domain.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Sharphound2.JsonObjects 4 | { 5 | internal class Domain : JsonBase 6 | { 7 | private string _name; 8 | public string Name 9 | { 10 | get => _name; 11 | set => _name = value.ToUpper(); 12 | } 13 | public Dictionary Properties = new Dictionary(); 14 | 15 | public GpLink[] Links { get; set; } 16 | public Trust[] Trusts { get; set; } 17 | public ACL[] Aces { get; set; } 18 | public string[] ChildOus { get; set; } 19 | public string[] Computers { get; set; } 20 | public string[] Users { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sharphound2/JsonObjects/GpLink.cs: -------------------------------------------------------------------------------- 1 | namespace Sharphound2.JsonObjects 2 | { 3 | internal class GpLink 4 | { 5 | private string _name; 6 | 7 | public bool? IsEnforced { get; set; } 8 | public string Name 9 | { 10 | get => _name; 11 | set => _name = value.ToUpper(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sharphound2/JsonObjects/Gpo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Sharphound2.JsonObjects 4 | { 5 | internal class Gpo : JsonBase 6 | { 7 | private string _name; 8 | public string Name 9 | { 10 | get => _name; 11 | set => _name = value.ToUpper(); 12 | } 13 | public string Guid { get; set; } 14 | 15 | public Dictionary Properties = new Dictionary(); 16 | 17 | public ACL[] Aces { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sharphound2/JsonObjects/GpoMember.cs: -------------------------------------------------------------------------------- 1 | namespace Sharphound2.JsonObjects 2 | { 3 | internal class GpoMember : JsonBase 4 | { 5 | public string[] AffectedComputers { get; set; } 6 | public LocalMember[] RemoteDesktopUsers { get; set; } 7 | public LocalMember[] LocalAdmins { get; set; } 8 | public LocalMember[] DcomUsers { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sharphound2/JsonObjects/Group.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Sharphound2.JsonObjects 4 | { 5 | internal class Group : JsonBase 6 | { 7 | private string _name; 8 | public string Name 9 | { 10 | get => _name; 11 | set => _name = value.ToUpper(); 12 | } 13 | 14 | public Dictionary Properties = new Dictionary(); 15 | 16 | public ACL[] Aces { get; set; } 17 | public GroupMember[] Members { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sharphound2/JsonObjects/GroupMember.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Sharphound2.JsonObjects 4 | { 5 | internal class GroupMember : IEquatable 6 | { 7 | public string MemberName { get; set; } 8 | public string MemberType { get; set; } 9 | 10 | public bool Equals(GroupMember other) 11 | { 12 | if (ReferenceEquals(null, other)) return false; 13 | if (ReferenceEquals(this, other)) return true; 14 | return string.Equals(MemberName, other.MemberName) && string.Equals(MemberType, other.MemberType); 15 | } 16 | 17 | public override bool Equals(object obj) 18 | { 19 | if (ReferenceEquals(null, obj)) return false; 20 | if (ReferenceEquals(this, obj)) return true; 21 | if (obj.GetType() != GetType()) return false; 22 | return Equals((GroupMember) obj); 23 | } 24 | 25 | public override int GetHashCode() 26 | { 27 | unchecked 28 | { 29 | return ((MemberName != null ? MemberName.GetHashCode() : 0) * 397) ^ 30 | (MemberType != null ? MemberType.GetHashCode() : 0); 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Sharphound2/JsonObjects/JsonBase.cs: -------------------------------------------------------------------------------- 1 | namespace Sharphound2.JsonObjects 2 | { 3 | internal class JsonBase 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Sharphound2/JsonObjects/LocalMember.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Sharphound2.JsonObjects 4 | { 5 | internal class LocalMember : IEquatable 6 | { 7 | private string _type; 8 | 9 | public string Name { get; set; } 10 | 11 | public string Type 12 | { 13 | get => _type; 14 | set => _type = value.ToTitleCase(); 15 | } 16 | 17 | public bool Equals(LocalMember other) 18 | { 19 | if (ReferenceEquals(null, other)) return false; 20 | if (ReferenceEquals(this, other)) return true; 21 | return string.Equals(_type, other._type) && string.Equals(Name, other.Name); 22 | } 23 | 24 | public override bool Equals(object obj) 25 | { 26 | if (ReferenceEquals(null, obj)) return false; 27 | if (ReferenceEquals(this, obj)) return true; 28 | if (obj.GetType() != GetType()) return false; 29 | return Equals((LocalMember) obj); 30 | } 31 | 32 | public override int GetHashCode() 33 | { 34 | unchecked 35 | { 36 | return ((_type != null ? _type.GetHashCode() : 0) * 397) ^ (Name != null ? Name.GetHashCode() : 0); 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /Sharphound2/JsonObjects/Ou.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Sharphound2.JsonObjects 4 | { 5 | internal class Ou : JsonBase 6 | { 7 | public string Guid { get; set; } 8 | 9 | public Dictionary Properties = new Dictionary(); 10 | 11 | public string[] ChildOus { get; set; } 12 | public string[] Computers { get; set; } 13 | public string[] Users { get; set; } 14 | public GpLink[] Links { get; set; } 15 | public ACL[] Aces { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sharphound2/JsonObjects/SPNTarget.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Sharphound2.JsonObjects 7 | { 8 | class SPNTarget : IEquatable 9 | { 10 | private string _computerName; 11 | 12 | public string ComputerName 13 | { 14 | get => _computerName; 15 | set => _computerName = value.ToUpper(); 16 | } 17 | 18 | public int Port { get; set; } 19 | 20 | public string Service { get; set; } 21 | 22 | public bool Equals(SPNTarget other) 23 | { 24 | return string.Equals(_computerName, other._computerName) && string.Equals(Service, other.Service) && Port == other.Port; 25 | } 26 | 27 | public override bool Equals(object obj) 28 | { 29 | if (ReferenceEquals(null, obj)) return false; 30 | if (ReferenceEquals(this, obj)) return true; 31 | if (obj.GetType() != this.GetType()) return false; 32 | return Equals((SPNTarget) obj); 33 | } 34 | 35 | public override int GetHashCode() 36 | { 37 | unchecked 38 | { 39 | var hashCode = (_computerName != null ? _computerName.GetHashCode() : 0); 40 | hashCode = (hashCode * 397) ^ (Service != null ? Service.GetHashCode() : 0); 41 | hashCode = (hashCode * 397) ^ Port; 42 | return hashCode; 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sharphound2/JsonObjects/Session.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Sharphound2.JsonObjects 4 | { 5 | internal class Session : JsonBase, IEquatable 6 | { 7 | private string _computerName; 8 | private string _userName; 9 | 10 | public string UserName 11 | { 12 | get => _userName; 13 | set => _userName = value.ToUpper(); 14 | } 15 | 16 | public string ComputerName 17 | { 18 | get => _computerName; 19 | set => _computerName = value.ToUpper(); 20 | } 21 | 22 | public int Weight { get; set; } 23 | 24 | public bool Equals(Session other) 25 | { 26 | if (ReferenceEquals(null, other)) return false; 27 | if (ReferenceEquals(this, other)) return true; 28 | return string.Equals(_userName, other._userName) && string.Equals(_computerName, other._computerName); 29 | } 30 | 31 | public override bool Equals(object obj) 32 | { 33 | if (ReferenceEquals(null, obj)) return false; 34 | if (ReferenceEquals(this, obj)) return true; 35 | if (obj.GetType() != GetType()) return false; 36 | return Equals((Session) obj); 37 | } 38 | 39 | public override int GetHashCode() 40 | { 41 | unchecked 42 | { 43 | return ((_userName != null ? _userName.GetHashCode() : 0) * 397) ^ 44 | (_computerName != null ? _computerName.GetHashCode() : 0); 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /Sharphound2/JsonObjects/Trust.cs: -------------------------------------------------------------------------------- 1 | namespace Sharphound2.JsonObjects 2 | { 3 | internal class Trust 4 | { 5 | private string _targetName; 6 | 7 | public string TargetName 8 | { 9 | get => _targetName; 10 | set => _targetName = value.ToUpper(); 11 | } 12 | 13 | public bool? IsTransitive { get; set; } 14 | public int TrustDirection { get; set; } 15 | public string TrustType { get; set; } 16 | } 17 | 18 | internal enum TrustDirection 19 | { 20 | Inbound = 0, 21 | Outbound = 1, 22 | Bidirectional = 2 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sharphound2/JsonObjects/User.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Sharphound2.JsonObjects 4 | { 5 | internal class User : JsonBase 6 | { 7 | private string _name; 8 | public string Name 9 | { 10 | get => _name; 11 | set => _name = value.ToUpper(); 12 | } 13 | public string PrimaryGroup { get; set; } 14 | 15 | public Dictionary Properties = new Dictionary(); 16 | 17 | public ACL[] Aces { get; set; } 18 | public string[] AllowedToDelegate { get; set; } 19 | public SPNTarget[] SPNTargets { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sharphound2/PowerShell/Out-CompressedDLL.ps1: -------------------------------------------------------------------------------- 1 | function Out-CompressedDll 2 | { 3 | <# 4 | .SYNOPSIS 5 | 6 | Creates the powershell in-memory version of SharpHound. 7 | Based entirely off Out-CompressedDll by Matthew Graeber (@mattifestation) 8 | Original script at https://github.com/PowerShellMafia/PowerSploit/blob/master/ScriptModification/Out-CompressedDll.ps1 9 | #> 10 | 11 | [CmdletBinding()] Param ( 12 | [Parameter(Mandatory = $True)] 13 | [String] 14 | $FilePath, 15 | 16 | [Parameter(Mandatory = $True)] 17 | [String] 18 | $TemplatePath 19 | ) 20 | 21 | $Path = Resolve-Path $FilePath 22 | 23 | if (! [IO.File]::Exists($Path)) 24 | { 25 | Throw "$Path does not exist." 26 | } 27 | 28 | $FileBytes = [System.IO.File]::ReadAllBytes($Path) 29 | 30 | if (($FileBytes[0..1] | % {[Char]$_}) -join '' -cne 'MZ') 31 | { 32 | Throw "$Path is not a valid executable." 33 | } 34 | 35 | $Length = $FileBytes.Length 36 | $CompressedStream = New-Object IO.MemoryStream 37 | $DeflateStream = New-Object IO.Compression.DeflateStream ($CompressedStream, [IO.Compression.CompressionMode]::Compress) 38 | $DeflateStream.Write($FileBytes, 0, $FileBytes.Length) 39 | $DeflateStream.Dispose() 40 | $CompressedFileBytes = $CompressedStream.ToArray() 41 | $CompressedStream.Dispose() 42 | $EncodedCompressedFile = [Convert]::ToBase64String($CompressedFileBytes) 43 | 44 | Write-Verbose "Compression ratio: $(($EncodedCompressedFile.Length/$FileBytes.Length).ToString('#%'))" 45 | 46 | $Output = @" 47 | `$EncodedCompressedFile = '$EncodedCompressedFile`' 48 | `$DeflatedStream = New-Object IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String(`$EncodedCompressedFile),[IO.Compression.CompressionMode]::Decompress) 49 | `$UncompressedFileBytes = New-Object Byte[]($Length) 50 | `$DeflatedStream.Read(`$UncompressedFileBytes, 0, $Length) | Out-Null 51 | `$Assembly = [Reflection.Assembly]::Load(`$UncompressedFileBytes) 52 | `$BindingFlags = [Reflection.BindingFlags] "Public,Static" 53 | `$a = @() 54 | `$Assembly.GetType("Costura.AssemblyLoader", `$false).GetMethod("Attach", `$BindingFlags).Invoke(`$Null, @()) 55 | `$Assembly.GetType("Sharphound2.Sharphound").GetMethod("InvokeBloodHound").Invoke(`$Null, @(,`$passed)) 56 | "@ 57 | 58 | Get-Content $TemplatePath | %{$_ -replace "#ENCODEDCONTENTHERE", $Output} 59 | } 60 | -------------------------------------------------------------------------------- /Sharphound2/PowerShell/Template.ps1: -------------------------------------------------------------------------------- 1 |  2 | function Invoke-BloodHound{ 3 | <# 4 | .SYNOPSIS 5 | 6 | Runs the BloodHound C# Ingestor using reflection. The assembly is stored in this file. 7 | 8 | .DESCRIPTION 9 | 10 | Using reflection and assembly.load, load the compiled BloodHound C# ingestor into memory 11 | and run it without touching disk. Parameters are converted to the equivalent CLI arguments 12 | for the SharpHound executable and passed in via reflection. The appropriate function 13 | calls are made in order to ensure that assembly dependencies are loaded properly. 14 | 15 | .PARAMETER CollectionMethod 16 | 17 | Specifies the CollectionMethod being used. Possible value are: 18 | Group - Collect group membership information 19 | LocalGroup - Collect local group information for computers 20 | LocalAdmin - Collect local admin users for computers 21 | RDP - Collect remote desktop users for computers 22 | DCOM - Collect distributed COM users for computers 23 | Session - Collect session information for computers 24 | SessionLoop - Continuously collect session information until killed 25 | Trusts - Enumerate domain trust data 26 | ACL - Collect ACL (Access Control List) data 27 | Container - Collect GPO/OU Data 28 | ComputerOnly - Collects Local Admin and Session data 29 | GPOLocalGroup - Collects Local Admin information using GPO (Group Policy Objects) 30 | LoggedOn - Collects session information using privileged methods (needs admin!) 31 | ObjectProps - Collects node property information for users and computers 32 | Default - Collects Group Membership, Local Admin, Sessions, and Domain Trusts 33 | DcOnly - Collects Group Membership, ACLs, ObjectProps, Trusts, Containers, and GPO Admins 34 | All - Collect all data except GPOLocalGroup and LoggedOn 35 | 36 | This can be a list of comma seperated valued as well to run multiple collection methods! 37 | 38 | .PARAMETER Domain 39 | 40 | Specifies the domain to enumerate. If not specified, will enumerate the current 41 | domain your user context specifies. 42 | 43 | .PARAMETER SearchForest 44 | 45 | Expands data collection to include all domains in the forest. 46 | 47 | .PARAMETER LdapFilter 48 | 49 | Append this ldap filter to the search filter to further filter the results enumerated 50 | 51 | .PARAMETER Stealth 52 | 53 | Use stealth collection options, will sacrifice data quality in favor of much reduced 54 | network impact 55 | 56 | .PARAMETER SkipGCDeconfliction 57 | 58 | Skip's Global Catalog deconfliction during session enumeration. This option 59 | can result in more inaccuracy in data. 60 | 61 | .PARAMETER ComputerFile 62 | 63 | A file containing a list of computers to enumerate. This option can only be used with the following Collection Methods: 64 | Session, SessionLoop, LocalGroup, ComputerOnly, LoggedOn 65 | 66 | .PARAMETER ExcludeDC 67 | 68 | Exclude domain controllers from session queries. Useful for ATA environments which detect this behavior 69 | 70 | .PARAMETER OU 71 | 72 | Limit enumeration to this OU. Takes a DistinguishedName. 73 | Ex. OU=Domain Controllers,DC=testlab,DC=local 74 | 75 | .PARAMETER DomainController 76 | 77 | Specify which Domain Controller to request data from. Defaults to closest DC using Site Names 78 | 79 | .PARAMETER LdapPort 80 | 81 | Override the port used to connect to LDAP 82 | 83 | .PARAMETER SecureLdap 84 | 85 | Uses LDAPs instead of unencrypted LDAP on port 636 86 | 87 | .PARAMETER IgnoreLdapCert 88 | 89 | Ignores the certificate for LDAP 90 | 91 | .PARAMETER LDAPUser 92 | 93 | User to connect to LDAP with 94 | 95 | .PARAMETER LDAPPass 96 | 97 | Password for user you are connecting to LDAP with 98 | 99 | .PARAMETER DisableKerbSigning 100 | 101 | Disables Kerberos Signing on requests. 102 | 103 | .PARAMETER Threads 104 | 105 | Specifies the number of threads to use during enumeration (Default 20) 106 | 107 | .PARAMETER PingTimeout 108 | 109 | Specifies timeout for ping requests to computers in milliseconds (Default 750) 110 | 111 | .PARAMETER SkipPing 112 | 113 | Skip all ping checks for computers. This option will most likely be slower as 114 | API calls will be made to all computers regardless of being up 115 | Use this option if ping is disabled on the network for some reason 116 | 117 | .PARAMETER LoopDelay 118 | 119 | Amount of time to wait between session enumeration loops in minutes. This option 120 | should be used in conjunction with the SessionLoop enumeration method. 121 | (Default 300 seconds) 122 | 123 | .PARAMETER MaxLoopTime 124 | 125 | Length of time to run looped session collection. Format: 0d0h0m0s or any variation of this format. 126 | Use in conjunction with -c SessionLoop 127 | Default will loop for two hours 128 | 129 | .PARAMETER Throttle 130 | 131 | Time in milliseconds to throttle after each request to a computer 132 | 133 | .Parameter Jitter 134 | 135 | Percentage jitter to apply to throttle 136 | 137 | .PARAMETER JSONFolder 138 | 139 | Folder to export JSONs too (Defaults to current directory) 140 | 141 | .PARAMETER JSONPrefix 142 | 143 | Prefix to add to your JSON Files (Default "") 144 | 145 | .PARAMETER NoZip 146 | 147 | Don't compress JSON files and remove them from disk 148 | 149 | .PARAMETER EncryptZip 150 | 151 | Add a random password to the zip file 152 | 153 | .PARAMETER ZipFileName 154 | 155 | Change the filename for the zip file 156 | 157 | .PARAMETER RandomFilenames 158 | 159 | Randomize output filenames 160 | 161 | .PARAMETER PrettyJson 162 | 163 | Output pretty JSON at the cost of file size 164 | 165 | .PARAMETER CacheFile 166 | 167 | Filename for the cache used by bloodhound. (Default .bin) 168 | 169 | .PARAMETER Invalidate 170 | 171 | Invalidate the cache and build a new one 172 | 173 | .PARAMETER SaveCache 174 | 175 | Whether to save the cache file. Set this to false to disable writing it to disk 176 | 177 | .PARAMETER Interval 178 | 179 | Interval to display progress during enumeration in milliseconds (Default 30000) 180 | 181 | .PARAMETER Verbose 182 | 183 | Enable verbose output mode. Will print a lot! 184 | 185 | .PARAMETER OverrideUser 186 | 187 | Overrides the 'current' user to filter it out of session enumeration. 188 | Useful when you're using runas, as the user will be detected incorrectly 189 | 190 | .EXAMPLE 191 | 192 | PS C:\> Invoke-BloodHound 193 | 194 | Executes the default collection options and exports JSONs to the current directory, compresses the data to a zip file, 195 | and then removes the JSON files from disk 196 | 197 | .EXAMPLE 198 | 199 | PS C:\> Invoke-BloodHound -CollectionMethod SessionLoop -LoopDelay 60 -MaxLoopTime 10 200 | 201 | Executes session collection in a loop. Will wait 1 minute after each run to continue collection 202 | and will continue running for 10 minutes after which the script will exit 203 | 204 | .EXAMPLE 205 | 206 | PS C:\> Invoke-BloodHound -CollectionMethod All 207 | 208 | Runs ACL, ObjectProps, Container, and Default collection methods, compresses the data to a zip file, 209 | and then removes the JSON files from disk 210 | 211 | .EXAMPLE (Opsec!) 212 | 213 | PS C:\> Invoke-BloodHound -CollectionMethod DCOnly --NoSaveCache --RandomFilenames --EncryptZip 214 | 215 | Run LDAP only collection methods (Groups, Trusts, ObjectProps, ACL, Containers, GPO Admins) without outputting the cache file to disk. 216 | Randomizes filenames of the JSON files and the zip file and adds a password to the zip file 217 | #> 218 | 219 | param( 220 | [String[]] 221 | $CollectionMethod = [string[]] @('Default'), 222 | 223 | [Switch] 224 | $SearchForest, 225 | 226 | [String] 227 | $Domain, 228 | 229 | [String] 230 | $LdapFilter, 231 | 232 | [Switch] 233 | $Stealth, 234 | 235 | [Switch] 236 | $SkipGCDeconfliction, 237 | 238 | [Switch] 239 | $ExcludeDC, 240 | 241 | [String] 242 | $ComputerFile, 243 | 244 | [String] 245 | $OU, 246 | 247 | [string] 248 | $DomainController, 249 | 250 | [int] 251 | $LdapPort, 252 | 253 | [Switch] 254 | $SecureLdap, 255 | 256 | [Switch] 257 | $IgnoreLdapCert, 258 | 259 | [String] 260 | $LDAPUser, 261 | 262 | [String] 263 | $LDAPPass, 264 | 265 | [Switch] 266 | $DisableKerbSigning, 267 | 268 | [ValidateRange(1,50)] 269 | [Int] 270 | $Threads = 10, 271 | 272 | [ValidateRange(50,1500)] 273 | [int] 274 | $PingTimeout = 250, 275 | 276 | [Switch] 277 | $SkipPing, 278 | 279 | [ValidateRange(1,50000000)] 280 | [int] 281 | $LoopDelay, 282 | 283 | [ValidatePattern('[0-9]+[smdh]')] 284 | [string] 285 | $MaxLoopTime, 286 | 287 | [ValidateRange(0,100)] 288 | [int] 289 | $Jitter, 290 | 291 | [int] 292 | $Throttle, 293 | 294 | [ValidateScript({ Test-Path -Path $_ })] 295 | [String] 296 | $JSONFolder = $(Get-Location), 297 | 298 | [ValidateNotNullOrEmpty()] 299 | [String] 300 | $JSONPrefix, 301 | 302 | [Switch] 303 | $NoZip, 304 | 305 | [Switch] 306 | $EncryptZip, 307 | 308 | [String] 309 | $ZipFileName, 310 | 311 | [Switch] 312 | $RandomFilenames, 313 | 314 | [Switch] 315 | $PrettyJson, 316 | 317 | 318 | [String] 319 | [ValidateNotNullOrEmpty()] 320 | $CacheFile, 321 | 322 | [Switch] 323 | $Invalidate, 324 | 325 | [Switch] 326 | $NoSaveCache, 327 | 328 | [ValidateRange(500,60000)] 329 | [int] 330 | $StatusInterval, 331 | 332 | [String] 333 | $OverrideUser, 334 | 335 | [Switch] 336 | $Verbose 337 | ) 338 | 339 | $vars = New-Object System.Collections.Generic.List[System.Object] 340 | 341 | $vars.Add("-c") 342 | foreach ($cmethod in $CollectionMethod){ 343 | $vars.Add($cmethod); 344 | } 345 | 346 | if ($Domain){ 347 | $vars.Add("-d"); 348 | $vars.Add($Domain); 349 | } 350 | 351 | if ($SearchForest){ 352 | $vars.Add("-s"); 353 | } 354 | 355 | if ($Stealth){ 356 | $vars.Add("--Stealth") 357 | } 358 | 359 | if ($SkipGCDeconfliction){ 360 | $vars.Add("--SkipGCDeconfliction") 361 | } 362 | 363 | if ($ExcludeDC){ 364 | $vars.Add("--ExcludeDC") 365 | } 366 | 367 | if ($ComputerFile){ 368 | $vars.Add("--ComputerFile"); 369 | $vars.Add($ComputerFile); 370 | } 371 | 372 | if ($OU){ 373 | $vars.Add("--OU"); 374 | $vars.Add($OU); 375 | } 376 | 377 | if ($DomainController){ 378 | $vars.Add("--DomainController"); 379 | $vars.Add($DomainController); 380 | } 381 | 382 | if ($LdapPort){ 383 | $vars.Add("--LdapPort"); 384 | $vars.Add($LdapPort); 385 | } 386 | 387 | if ($SecureLdap){ 388 | $vars.Add("--SecureLdap"); 389 | } 390 | 391 | if ($IgnoreLdapCert){ 392 | $vars.Add("--IgnoreLdapCert"); 393 | } 394 | 395 | if ($LDAPUser){ 396 | $vars.Add("--LDAPUser"); 397 | $vars.Add($LDAPUser); 398 | } 399 | 400 | if ($LDAPPass){ 401 | $vars.Add("--LDAPPass"); 402 | $vars.Add($LDAPPass); 403 | } 404 | 405 | if ($DisableKerbSigning){ 406 | $vars.Add("--DisableKerbSigning"); 407 | } 408 | 409 | if ($Threads){ 410 | $vars.Add("-t") 411 | $vars.Add($Threads) 412 | } 413 | 414 | if ($PingTimeout){ 415 | $vars.Add("--PingTimeout") 416 | $vars.Add($PingTimeout) 417 | } 418 | 419 | if ($SkipPing){ 420 | $vars.Add("--SkipPing"); 421 | } 422 | 423 | if ($LoopDelay){ 424 | $vars.Add("--LoopDelay") 425 | $vars.Add($LoopDelay) 426 | } 427 | 428 | if ($MaxLoopTime){ 429 | $vars.Add("--MaxLoopTime") 430 | $vars.Add($MaxLoopTime) 431 | } 432 | 433 | if ($Throttle){ 434 | $vars.Add("--Throttle"); 435 | $vars.Add($Throttle); 436 | } 437 | 438 | if ($Jitter){ 439 | $vars.Add("--Jitter"); 440 | $vars.Add($Jitter); 441 | } 442 | 443 | if ($JSONFolder){ 444 | $vars.Add("--JSONFolder"); 445 | $vars.Add($JSONFolder); 446 | } 447 | 448 | if ($JSONPrefix){ 449 | $vars.Add("--JSONPrefix"); 450 | $vars.Add($JSONPrefix); 451 | } 452 | 453 | if ($NoZip){ 454 | $vars.Add("--NoZip"); 455 | } 456 | 457 | if ($EncryptZip){ 458 | $vars.Add("--EncryptZip"); 459 | } 460 | 461 | if ($ZipFileName){ 462 | $vars.Add("--ZipFileName"); 463 | $vars.Add($ZipFileName); 464 | } 465 | 466 | if ($RandomFilenames){ 467 | $vars.Add("--RandomFilenames"); 468 | } 469 | 470 | if ($PrettyJson){ 471 | $vars.Add("--PrettyJson"); 472 | } 473 | 474 | if ($CacheFile){ 475 | $vars.Add("--CacheFile"); 476 | $vars.Add($CacheFile); 477 | } 478 | 479 | if ($Invalidate){ 480 | $vars.Add("--Invalidate"); 481 | } 482 | 483 | if ($NoSaveCache){ 484 | $vars.Add("--NoSaveCache"); 485 | } 486 | 487 | if ($LdapFilter){ 488 | $vars.Add("--LdapFilter"); 489 | $vars.Add($LdapFilter); 490 | } 491 | 492 | if ($Verbose){ 493 | $vars.Add("-v") 494 | } 495 | 496 | if ($StatusInterval){ 497 | $vars.Add("--StatusInterval") 498 | $vars.Add($StatusInterval) 499 | } 500 | 501 | if ($OverrideUser){ 502 | $vars.Add("--OverrideUser") 503 | $vars.Add($OverrideUser) 504 | } 505 | 506 | $passed = [string[]]$vars.ToArray() 507 | 508 | #ENCODEDCONTENTHERE 509 | } 510 | -------------------------------------------------------------------------------- /Sharphound2/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("SharpHound")] 8 | [assembly: AssemblyDescription("The BloodHound C# Ingestor")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("SharpHound")] 12 | [assembly: AssemblyCopyright("Copyright © 2017")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("69415bf4-e2f3-47e8-a818-93a6dc54811c")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("2.2.0.0")] 35 | [assembly: AssemblyFileVersion("2.2.0.0")] 36 | -------------------------------------------------------------------------------- /Sharphound2/Sharphound.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using Sharphound2.Enumeration; 3 | using System; 4 | using System.DirectoryServices.Protocols; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Security.Principal; 8 | using System.Text.RegularExpressions; 9 | using CommandLine.Text; 10 | using static Sharphound2.CollectionMethod; 11 | 12 | namespace Sharphound2 13 | { 14 | internal class Sharphound 15 | { 16 | public class Options 17 | { 18 | [OptionArray('c', "CollectionMethod", DefaultValue = new[] { "Default" }, HelpText = "Collection Method (Group, LocalGroup, GPOLocalGroup, Session, LoggedOn, ComputerOnly, Trusts, Stealth, Default, RDP, DCOM")] 19 | public string[] CollectionMethod { get; set; } 20 | 21 | [Option(HelpText = "Use stealth enumeration options", DefaultValue = false)] 22 | public bool Stealth { get; set; } 23 | 24 | [Option('d', "Domain", HelpText = "Domain to enumerate", DefaultValue = null, MutuallyExclusiveSet = "DomainOption")] 25 | public string Domain { get; set; } 26 | 27 | [Option('s', "SearchForest", HelpText = "Search the entire forest", DefaultValue = false, MutuallyExclusiveSet = "DomainOption")] 28 | public bool SearchForest { get; set; } 29 | 30 | [Option(DefaultValue = null)] 31 | public string Ou { get; set; } 32 | 33 | [Option(HelpText = "Custom LDAP filter to control collection", DefaultValue = null)] 34 | public string LdapFilter { get; set; } 35 | 36 | [Option(DefaultValue = null)] 37 | public string ComputerFile { get; set; } 38 | 39 | [Option('t', "Threads", HelpText = "Number of Threads to use", DefaultValue = 10)] 40 | public int Threads { get; set; } 41 | 42 | [Option(HelpText = "Folder to drop Json files", DefaultValue = ".")] 43 | public string JsonFolder { get; set; } 44 | 45 | [Option(HelpText = "Prefix for Json file names", DefaultValue = "")] 46 | public string JsonPrefix { get; set; } 47 | 48 | [Option(DefaultValue = false)] 49 | public bool PrettyJson { get; set; } 50 | 51 | [Option(DefaultValue = 0)] 52 | public int LdapPort { get; set; } 53 | 54 | [Option(HelpText = "Interval to display progress in milliseconds", DefaultValue = 30000)] 55 | public int StatusInterval { get; set; } 56 | 57 | [Option(HelpText = "Skip ping checks for hosts", DefaultValue = false)] 58 | public bool SkipPing { get; set; } 59 | 60 | [Option(HelpText = "Timeout in milliseconds for ping timeout", DefaultValue = 500)] 61 | public int PingTimeout { get; set; } 62 | 63 | [Option(HelpText = "Skip Global Catalog Deconfliction", DefaultValue = false)] 64 | public bool SkipGcDeconfliction { get; set; } 65 | 66 | [Option(HelpText = "Filename for the data cache (defaults to b64 of machine sid)", DefaultValue = null)] 67 | public string CacheFile { get; set; } 68 | 69 | [Option(HelpText = "Filename for the zip file", DefaultValue = null)] 70 | public string ZipFileName { get; set; } 71 | 72 | [Option(HelpText = "Random Filenames", DefaultValue = false)] 73 | public bool RandomFilenames { get; set; } 74 | 75 | [Option(HelpText = "Invalidate and build new cache", DefaultValue = false)] 76 | public bool Invalidate { get; set; } 77 | 78 | [Option(HelpText = "Don't save the cache file to disk", DefaultValue = false)] 79 | public bool NoSaveCache { get; set; } 80 | 81 | [Option(DefaultValue = 300, HelpText = "Time in seconds between each session loop")] 82 | public int LoopDelay { get; set; } 83 | 84 | [Option(DefaultValue = null)] 85 | public string MaxLoopTime { get; set; } 86 | 87 | [Option('v', "Verbose", HelpText = "Enable verbose output", DefaultValue = false)] 88 | public bool Verbose { get; set; } 89 | 90 | [Option(HelpText = "Exclude Domain Controllers from search (useful for ATA environments)", DefaultValue = false)] 91 | public bool ExcludeDC { get; set; } 92 | 93 | [Option(DefaultValue = false)] 94 | public bool SecureLdap { get; set; } 95 | 96 | [Option(DefaultValue = false)] 97 | public bool IgnoreLdapCert { get; set; } 98 | 99 | [Option(DefaultValue = false)] 100 | public bool DisableKerbSigning { get; set; } 101 | 102 | [Option(DefaultValue = false)] 103 | public bool NoZip { get; set; } 104 | 105 | [Option(DefaultValue = false)] 106 | public bool EncryptZip { get; set; } 107 | 108 | [Option(DefaultValue = null)] 109 | public string Test { get; set; } 110 | 111 | [Option(DefaultValue = null)] 112 | public string DomainController { get; set; } 113 | 114 | [Option(DefaultValue = false)] 115 | public bool Debug { get; set; } 116 | 117 | [Option(DefaultValue = 0)] 118 | public int Throttle { get; set; } 119 | 120 | [Option(DefaultValue = 0)] 121 | public int Jitter { get; set; } 122 | 123 | [Option(DefaultValue = null)] 124 | public string LdapUser { get; set; } 125 | 126 | [Option(DefaultValue = null)] 127 | public string LdapPass { get; set; } 128 | 129 | [Option(DefaultValue = null)] 130 | public string OverrideUser { get; set; } 131 | 132 | [ParserState] 133 | public IParserState LastParserState { get; set; } 134 | 135 | [HelpOption] 136 | public string GetUsage() 137 | { 138 | var text = @"SharpHound v2.1.0 139 | Usage: SharpHound.exe 140 | 141 | Enumeration Options: 142 | -c , --CollectionMethod (Default: Default) 143 | Default - Enumerate Trusts, Sessions, Local Admin, and Group Membership 144 | Group - Enumerate Group Membership 145 | LocalGroup - Enumerate the Administrators, Distributed COM Users, and Remote Desktop Users groups 146 | LocalAdmin - Enumerate the Administrators Group 147 | DCOM - Enumerate the Distributed COM Users Group 148 | RDP - Enumerate the Remote Desktop Users Group 149 | Session - Enumerate Sessions 150 | SessionLoop - Continuously Enumerate Sessions 151 | LoggedOn - Enumerate Sessions using Elevation 152 | ComputerOnly - Enumerate Sessions and Local Admin 153 | Trusts - Enumerate Domain Trusts 154 | ACL - Enumerate ACLs 155 | ObjectProps - Enumerate Object Properties for Users/Computers 156 | Container - Collects GPO/OU Structure 157 | DCOnly - Enumerate Group Membership, Trusts, ACLs, ObjectProps, Containers, and GPO Local Admins 158 | All - Performs all enumeration methods except GPOLocalGroup and LoggedOn 159 | 160 | This can be a list of comma seperated valued as well to run multiple collection methods! 161 | 162 | -s , --SearchForest 163 | Search the entire forest instead of just current domain 164 | 165 | -d , --Domain (Default: "") 166 | Search a specific domain 167 | 168 | --SkipGCDeconfliction 169 | Skip Global Catalog deconfliction during session enumeration 170 | This option can result in more inaccuracies! 171 | 172 | --Stealth 173 | Use stealth collection options 174 | 175 | --Ou (Default: null) 176 | Ou to limit computer enumeration too. Requires a DistinguishedName (OU=Domain Controllers,DC=contoso,DC=local) 177 | 178 | --ComputerFile (Default: null) 179 | A file containing a list of computers to enumerate. This option can only be used with the following Collection Methods: 180 | Session, SessionLoop, LocalAdmin, ComputerOnly, LoggedOn 181 | 182 | --ExcludeDC 183 | Exclude domain controllers from session queries. Useful for ATA environments which detect this behavior 184 | 185 | --LdapFilter 186 | Append this to the ldap filter used for querying the directory 187 | 188 | --OverrideUser 189 | Overrides the 'current' user to filter it out of session enumeration. 190 | Useful when you're using runas, as the user will be detected incorrectly 191 | 192 | Connection Options: 193 | --SecureLdap 194 | Uses secure LDAP (LDAPS) instead of regular 195 | 196 | --LdapPort 197 | Override the port used to connect to LDAP 198 | 199 | --IgnoreLdapCert 200 | Ignores the SSL certificate for LDAP. Use for self-signed certs 201 | 202 | --DisableKerbSigning 203 | Disables Kerberos signing on LDAP requests 204 | 205 | --DomainController (Default: null) 206 | Specify which Domain Controller to request data from. Defaults to closest DC using Site Names 207 | 208 | --LdapUser (Default: null) 209 | User to connect to LDAP with 210 | 211 | --LdapPass (Default: null) 212 | Password for the user to connect to LDAP with 213 | 214 | Performance Tuning: 215 | -t , --Threads (Default: 10) 216 | The number of threads to use for Enumeration 217 | 218 | --PingTimeout (Default: 200) 219 | Timeout to use when pinging computers in milliseconds 220 | 221 | --SkipPing 222 | Skip pinging computers (will most likely be slower) 223 | Use this option if ping is disabled on the network 224 | 225 | --LoopDelay 226 | Amount of time to wait in between session enumeration loops 227 | Use in conjunction with -c SessionLoop 228 | 229 | --MaxLoopTime 230 | Time to stop looping. Format is 0d0h0m0s or any variation of this. 231 | Use in conjunction with -c SessionLoop 232 | Default will loop infinitely 233 | 234 | --Throttle (Default: 0) 235 | Time in milliseconds to throttle between requests to computers 236 | 237 | --Jitter (Default: 0) 238 | Percent jitter to apply to throttle 239 | 240 | Output Options 241 | --JsonFolder (Default: .) 242 | The folder in which to store JSON files 243 | 244 | --JsonPrefix (Default: """") 245 | The prefix to add to your JSON files 246 | 247 | --NoZip 248 | Don't compress and remove JSON files 249 | 250 | --EncryptZip 251 | Add a random password to the zip files 252 | 253 | --ZipFileName 254 | Specify the filename for the zip file 255 | 256 | -- RandomFilenames 257 | Randomize output filenames 258 | 259 | --PrettyJson 260 | Output pretty JSON 261 | 262 | Cache Options 263 | --NoSaveCache 264 | Dont save the cache to disk to speed up future runs 265 | 266 | --CacheFile (Default: .bin) 267 | Filename for the BloodHound database to write to disk 268 | 269 | --Invalidate 270 | Invalidate the cache and build a new one 271 | 272 | General Options 273 | --StatusInterval (Default: 30000) 274 | Interval to display progress during enumeration in milliseconds 275 | 276 | -v , --Verbose 277 | Display Verbose Output 278 | "; 279 | 280 | if (LastParserState?.Errors.Any() != true) return text; 281 | var errors = new HelpText().RenderParsingErrorsText(this, 2); 282 | text += errors; 283 | 284 | return text; 285 | } 286 | 287 | public ResolvedCollectionMethod ResolvedCollMethods { get; set; } 288 | public string CurrentUser { get; set; } 289 | 290 | public DateTime LoopEnd { get; set; } 291 | public bool SessionLoopRunning = false; 292 | 293 | //public string GetEncodedUserPass() 294 | //{ 295 | // var plainTextBytes = Encoding.UTF8.GetBytes(UserPass); 296 | // return Convert.ToBase64String(plainTextBytes); 297 | //} 298 | 299 | //public string GetURI() 300 | //{ 301 | // return $"{Uri}/db/data/transaction/commit"; 302 | //} 303 | 304 | //public string GetCheckURI() 305 | //{ 306 | // return $"{Uri}/db/data/"; 307 | //} 308 | } 309 | 310 | public static void Main(string[] args) 311 | { 312 | if (args == null) 313 | throw new ArgumentNullException(nameof(args)); 314 | 315 | var options = new Options(); 316 | 317 | if (!Parser.Default.ParseArguments(args, options)) 318 | { 319 | return; 320 | } 321 | 322 | if (options.CacheFile == null) 323 | { 324 | var sid = Utils.GetLocalMachineSid(); 325 | var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(sid); 326 | options.CacheFile = $"{Convert.ToBase64String(plainTextBytes)}.bin"; 327 | } 328 | 329 | try 330 | { 331 | // ReSharper disable once ReturnValueOfPureMethodIsNotUsed 332 | Path.Combine(options.JsonFolder, options.CacheFile); 333 | } 334 | catch (ArgumentException) 335 | { 336 | Console.WriteLine("Invalid characters in output path. Check for trailing backslashes!"); 337 | return; 338 | } 339 | 340 | if (options.CollectionMethod.Length == 1) 341 | { 342 | options.CollectionMethod = options.CollectionMethod[0].Split(','); 343 | } 344 | 345 | if (options.Jitter > 100 || options.Jitter < 0) 346 | { 347 | Console.WriteLine("Jitter must be a value between 0 and 100!"); 348 | return; 349 | } 350 | 351 | if (options.Throttle < 0) 352 | { 353 | Console.WriteLine("Throttle must be 0 or greater!"); 354 | return; 355 | } 356 | 357 | var resolved = ResolvedCollectionMethod.None; 358 | 359 | foreach (var unparsed in options.CollectionMethod) 360 | { 361 | try 362 | { 363 | var e = (CollectionMethod)Enum.Parse(typeof(CollectionMethod), unparsed, true); 364 | switch (e) 365 | { 366 | case All: 367 | resolved = resolved | ResolvedCollectionMethod.ACL | ResolvedCollectionMethod.Container | 368 | ResolvedCollectionMethod.Group | ResolvedCollectionMethod.LocalAdmin | 369 | ResolvedCollectionMethod.ObjectProps | ResolvedCollectionMethod.RDP | 370 | ResolvedCollectionMethod.Session | ResolvedCollectionMethod.Trusts | 371 | ResolvedCollectionMethod.DCOM | ResolvedCollectionMethod.LoggedOn | 372 | ResolvedCollectionMethod.SPNTargets; 373 | break; 374 | case DcOnly: 375 | resolved = resolved | ResolvedCollectionMethod.ACL | ResolvedCollectionMethod.Container | 376 | ResolvedCollectionMethod.Trusts | ResolvedCollectionMethod.ObjectProps | 377 | ResolvedCollectionMethod.GPOLocalGroup | ResolvedCollectionMethod.Group | ResolvedCollectionMethod.DCOnly; 378 | break; 379 | case CollectionMethod.Group: 380 | resolved = resolved | ResolvedCollectionMethod.Group; 381 | break; 382 | case ComputerOnly: 383 | resolved = resolved | ResolvedCollectionMethod.LocalAdmin | 384 | ResolvedCollectionMethod.Session | ResolvedCollectionMethod.RDP | 385 | ResolvedCollectionMethod.DCOM; 386 | break; 387 | case LocalGroup: 388 | resolved = resolved | ResolvedCollectionMethod.LocalAdmin | ResolvedCollectionMethod.RDP | ResolvedCollectionMethod.DCOM; 389 | break; 390 | case GPOLocalGroup: 391 | resolved = resolved | ResolvedCollectionMethod.GPOLocalGroup; 392 | break; 393 | case Session: 394 | resolved = resolved | ResolvedCollectionMethod.Session; 395 | break; 396 | case LoggedOn: 397 | resolved = resolved | ResolvedCollectionMethod.LoggedOn; 398 | break; 399 | case Trusts: 400 | resolved = resolved | ResolvedCollectionMethod.Trusts; 401 | break; 402 | case ACL: 403 | resolved = resolved | ResolvedCollectionMethod.ACL; 404 | break; 405 | case SessionLoop: 406 | resolved = resolved | ResolvedCollectionMethod.SessionLoop; 407 | break; 408 | case Default: 409 | resolved = resolved | ResolvedCollectionMethod.RDP | ResolvedCollectionMethod.DCOM | ResolvedCollectionMethod.LocalAdmin | ResolvedCollectionMethod.Group | ResolvedCollectionMethod.Session | ResolvedCollectionMethod.Trusts; 410 | break; 411 | case ObjectProps: 412 | resolved = resolved | ResolvedCollectionMethod.ObjectProps; 413 | break; 414 | case Container: 415 | resolved = resolved | ResolvedCollectionMethod.Container; 416 | break; 417 | case LocalAdmin: 418 | resolved = resolved | ResolvedCollectionMethod.LocalAdmin; 419 | break; 420 | case RDP: 421 | resolved = resolved | ResolvedCollectionMethod.RDP; 422 | break; 423 | case DCOM: 424 | resolved = resolved | ResolvedCollectionMethod.DCOM; 425 | break; 426 | case SPNTargets: 427 | resolved = resolved | ResolvedCollectionMethod.SPNTargets; 428 | break; 429 | default: 430 | throw new ArgumentOutOfRangeException(); 431 | } 432 | } 433 | catch 434 | { 435 | Console.WriteLine($"Failed to parse value {unparsed}. Check your values for CollectionMethods!"); 436 | return; 437 | } 438 | } 439 | 440 | if (options.Debug) 441 | { 442 | Console.WriteLine("Debug Mode activated!"); 443 | //options.Threads = 1; 444 | } 445 | 446 | if ((resolved & ResolvedCollectionMethod.SessionLoop) != 0) 447 | { 448 | if (options.MaxLoopTime != null) 449 | { 450 | var regex = new Regex("[0-9]+[smdh]"); 451 | var matches = regex.Matches(options.MaxLoopTime); 452 | var numregex = new Regex("[0-9]+"); 453 | var timeregex = new Regex("[smdh]"); 454 | if (matches.Count == 0) 455 | { 456 | Console.WriteLine("LoopEndTime does not match required format"); 457 | return; 458 | } 459 | 460 | var now = DateTime.Now; 461 | var drift = 0; 462 | foreach (var match in matches) 463 | { 464 | var num = int.Parse(numregex.Match(match.ToString()).Value); 465 | var spec = timeregex.Match(match.ToString()); 466 | 467 | switch (spec.Value) 468 | { 469 | case "s": 470 | now = now.AddSeconds(num); 471 | drift += num; 472 | break; 473 | case "m": 474 | now = now.AddMinutes(num); 475 | drift += num * 60; 476 | break; 477 | case "h": 478 | now = now.AddHours(num); 479 | drift += num * 60 * 60; 480 | break; 481 | case "d": 482 | now = now.AddDays(num); 483 | drift += num * 60 * 60 * 24; 484 | break; 485 | } 486 | } 487 | 488 | options.LoopEnd = now; 489 | 490 | if (drift == 0) 491 | { 492 | Console.WriteLine("LoopEndTime is zero! Specify a real value"); 493 | return; 494 | } 495 | } 496 | else 497 | { 498 | options.LoopEnd = DateTime.Now + TimeSpan.FromHours(2); 499 | } 500 | } 501 | 502 | options.CurrentUser = options.OverrideUser ?? WindowsIdentity.GetCurrent().Name.Split('\\')[1]; 503 | 504 | Cache.CreateInstance(options); 505 | Utils.CreateInstance(options); 506 | 507 | 508 | if (!Utils.CheckWritePrivs()) 509 | { 510 | Console.WriteLine("Unable to write in chosen directory. Please check privs"); 511 | return; 512 | } 513 | 514 | var nowtime = DateTime.Now; 515 | Console.WriteLine($"Initializing BloodHound at {nowtime.ToShortTimeString()} on {nowtime.ToShortDateString()}"); 516 | 517 | if (options.ComputerFile != null) 518 | { 519 | if (options.PingTimeout < 1000) 520 | { 521 | Console.WriteLine("Increasing ping timeout to 1 second for ComputerFile mode"); 522 | options.PingTimeout = 1000; 523 | } 524 | } 525 | 526 | if (Utils.Instance.GetDomainList().Count == 0) 527 | { 528 | Console.WriteLine("Unable to contact domain. Try from a domain context!"); 529 | return; 530 | } 531 | 532 | if (options.DomainController != null) 533 | { 534 | Console.WriteLine("Manually specifying a domain controller will likely result in data loss. Only use this for performance/opsec reasons"); 535 | if (options.SearchForest) 536 | { 537 | Console.WriteLine("SearchForest is not usable with the --DomainController flag"); 538 | options.SearchForest = false; 539 | } 540 | } 541 | else 542 | { 543 | //Build our DC cache 544 | Utils.Instance.GetUsableDomainControllers(); 545 | } 546 | 547 | SessionHelpers.Init(options); 548 | LocalGroupHelpers.Init(options); 549 | GroupHelpers.Init(); 550 | AclHelpers.Init(); 551 | TrustHelpers.Init(); 552 | ContainerHelpers.Init(); 553 | 554 | if (options.Test != null) 555 | { 556 | Test.DoStuff(options.Test); 557 | return; 558 | } 559 | 560 | //Lets test our connection to LDAP before we do anything else 561 | try 562 | { 563 | var conn = Utils.Instance.GetLdapConnection(options.Domain); 564 | if (conn == null) 565 | { 566 | Console.WriteLine("LDAP connection test failed, probably can't contact domain"); 567 | return; 568 | } 569 | conn.Bind(); 570 | } 571 | catch (LdapException) 572 | { 573 | Console.WriteLine("Ldap Connection Failure."); 574 | if (options.LdapPass != null) 575 | { 576 | Console.WriteLine("Check credentials supplied to SharpHound"); 577 | } 578 | Console.WriteLine("Try again with the IgnoreLdapCert option if using SecureLDAP or check your DomainController/LdapPort option"); 579 | return; 580 | } 581 | 582 | //if (options.Uri != null) 583 | //{ 584 | // if (!options.Uri.StartsWith("http",StringComparison.OrdinalIgnoreCase)) 585 | // { 586 | // Console.WriteLine("URI must start with http:// or https://"); 587 | // return; 588 | // } 589 | 590 | // using (var client = new WebClient()) 591 | // { 592 | // client.Headers.Add("content-type", "application/json"); 593 | // client.Headers.Add("Accept", "application/json; charset=UTF-8"); 594 | 595 | // if (options.UserPass != null) 596 | // client.Headers.Add("Authorization", options.GetEncodedUserPass()); 597 | 598 | // try 599 | // { 600 | // client.DownloadData(options.GetCheckURI()); 601 | // Console.WriteLine("Successfully connected to the Neo4j REST endpoint."); 602 | // Console.WriteLine("WARNING: As of BloodHound 1.5, using the REST API is unsupported and will be removed in a future release."); 603 | // Console.WriteLine("WARNING: Container collection will not work with the REST API, and bugs may exist."); 604 | // } 605 | // catch 606 | // { 607 | // Console.WriteLine("Unable to connect to the Neo4j REST endpoint. Check your URI and username/password"); 608 | // Console.WriteLine("WARNING: As of BloodHound 1.5, using the REST API is unsupported and will be removed in a future release."); 609 | // Console.WriteLine("WARNING: Container collection will not work with the REST API, and bugs may exist."); 610 | // return; 611 | // } 612 | // } 613 | //} 614 | 615 | if (options.Stealth) 616 | { 617 | Console.WriteLine("Note: All stealth options are single threaded"); 618 | } 619 | 620 | if (options.Throttle > 0) 621 | { 622 | Console.WriteLine( 623 | $"Adding a delay of {options.Throttle} milliseconds to computer requests with a jitter of {options.Jitter}%"); 624 | } 625 | 626 | //Do some sanity checks 627 | if (options.ComputerFile != null) 628 | { 629 | if (!File.Exists(options.ComputerFile)) 630 | { 631 | Console.WriteLine("Specified ComputerFile does not exist!"); 632 | return; 633 | } 634 | 635 | if (options.Stealth) 636 | { 637 | Console.WriteLine("Switching to one thread for ComputerFile, removing stealth"); 638 | options.Stealth = false; 639 | options.Threads = 1; 640 | } 641 | 642 | Console.WriteLine("ComputerFile detected! Removing non-computer collection methods"); 643 | resolved = resolved & ~ResolvedCollectionMethod.ACL & ~ResolvedCollectionMethod.Group 644 | & ~ResolvedCollectionMethod.GPOLocalGroup & ~ResolvedCollectionMethod.Trusts 645 | & ~ResolvedCollectionMethod.Container & ~ResolvedCollectionMethod.ObjectProps; 646 | } 647 | 648 | if (options.Stealth) 649 | { 650 | if ((resolved & ResolvedCollectionMethod.LocalAdmin) != 0) 651 | { 652 | Console.WriteLine("Note: You specified Stealth and LocalGroup which is equivalent to GPOLocalGroup"); 653 | resolved = resolved & ~ResolvedCollectionMethod.LocalAdmin; 654 | resolved = resolved | ResolvedCollectionMethod.GPOLocalGroup; 655 | } 656 | 657 | if ((resolved & ResolvedCollectionMethod.LoggedOn) != 0) 658 | { 659 | Console.WriteLine("LoggedOn enumeration is not supported with Stealth"); 660 | resolved = resolved & ~ResolvedCollectionMethod.LoggedOn; 661 | } 662 | } 663 | 664 | if ((resolved & ResolvedCollectionMethod.Session) != 0 && 665 | (resolved & ResolvedCollectionMethod.SessionLoop) != 0) 666 | { 667 | resolved = resolved ^ ResolvedCollectionMethod.Session; 668 | } 669 | 670 | if ((resolved & ResolvedCollectionMethod.LoggedOn) != 0 && 671 | (resolved & ResolvedCollectionMethod.SessionLoop) != 0) 672 | { 673 | resolved = resolved ^ ResolvedCollectionMethod.LoggedOn; 674 | resolved = resolved | ResolvedCollectionMethod.LoggedOnLoop; 675 | } 676 | 677 | 678 | if ((resolved & ResolvedCollectionMethod.SessionLoop) != 0) 679 | { 680 | Console.WriteLine(options.MaxLoopTime == null 681 | ? $"Session Loop mode specified without MaxLoopTime, will loop for 2 hours ({options.LoopEnd.ToShortDateString()} at {options.LoopEnd.ToShortTimeString()})" 682 | : $"Session Loop mode specified. Looping will end on {options.LoopEnd.ToShortDateString()} at {options.LoopEnd.ToShortTimeString()}"); 683 | Console.WriteLine("Looping will start after any other collection methods"); 684 | } 685 | 686 | if (resolved.Equals(ResolvedCollectionMethod.None)) 687 | { 688 | Console.WriteLine("No collection methods specified. Exiting"); 689 | return; 690 | } 691 | 692 | Console.WriteLine($"Resolved Collection Methods to {resolved}"); 693 | 694 | if ((resolved & ResolvedCollectionMethod.ACL) != 0) 695 | { 696 | Utils.Verbose("Building GUID Cache"); 697 | AclHelpers.BuildGuidCache(); 698 | } 699 | 700 | options.ResolvedCollMethods = resolved; 701 | 702 | var runner = new EnumerationRunner(options); 703 | 704 | if (options.Stealth) 705 | { 706 | runner.StartStealthEnumeration(); 707 | } 708 | else 709 | { 710 | if (options.ComputerFile == null) 711 | { 712 | runner.StartEnumeration(); 713 | } 714 | else 715 | { 716 | runner.StartCompFileEnumeration(); 717 | } 718 | } 719 | Console.WriteLine(); 720 | 721 | Cache.Instance.SaveCache(); 722 | Utils.Instance.KillConnections(); 723 | 724 | if (!options.NoZip) 725 | { 726 | Utils.CompressFiles(); 727 | } 728 | } 729 | 730 | // Accessor function for the PS1 to work, do not change or remove 731 | public static void InvokeBloodHound(string[] args) 732 | { 733 | Main(args); 734 | } 735 | } 736 | } 737 | -------------------------------------------------------------------------------- /Sharphound2/Sharphound2.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {69415BF4-E2F3-47E8-A818-93A6DC54811C} 8 | Exe 9 | Sharphound2 10 | SharpHound 11 | v3.5 12 | 512 13 | 14 | 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | none 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | favicon.ico 37 | 38 | 39 | 40 | ..\packages\CommandLineParser.1.9.71\lib\net35\CommandLine.dll 41 | 42 | 43 | ..\packages\Costura.Fody.1.6.2\lib\portable-net+sl+win+wpa+wp\Costura.dll 44 | False 45 | 46 | 47 | ..\packages\Heijden.Dns.2.0.0\lib\net35\Heijden.Dns.dll 48 | 49 | 50 | ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll 51 | 52 | 53 | ..\packages\Newtonsoft.Json.11.0.2\lib\net35\Newtonsoft.Json.dll 54 | 55 | 56 | ..\packages\protobuf-net.2.4.0\lib\net35\protobuf-net.dll 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | ..\packages\TaskParallelLibrary.1.0.2856.0\lib\Net35\System.Threading.dll 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 134 | 135 | 136 | 137 | 138 | 139 | 140 | powershell -ep bypass -c ". '$(ProjectDir)\PowerShell\Out-CompressedDLL.ps1';Out-CompressedDll -FilePath '$(TargetPath)' -TemplatePath '$(ProjectDir)\PowerShell\Template.ps1' | Out-File -Encoding ASCII '$(TargetDir)$(TargetName).ps1'" 141 | 142 | 143 | -------------------------------------------------------------------------------- /Sharphound2/Test.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.DirectoryServices; 3 | using System.DirectoryServices.Protocols; 4 | using System.Net; 5 | using System.Runtime.InteropServices; 6 | using System.Security.Principal; 7 | using System.Text; 8 | using Heijden.DNS; 9 | 10 | namespace Sharphound2 11 | { 12 | internal class Test 13 | { 14 | public static void DoStuff(string host) 15 | { 16 | var resolver = new Resolver(); 17 | foreach (var x in resolver.DnsServers) 18 | { 19 | Console.WriteLine(x.Address); 20 | } 21 | } 22 | 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sharphound2/Wrapper.cs: -------------------------------------------------------------------------------- 1 | namespace Sharphound2 2 | { 3 | //This class exists because of a memory leak in BlockingCollection. By setting the reference to Item to null after enumerating it, 4 | //we can force garbage collection of the internal item, while the Wrapper is held by the collection. 5 | //This is highly preferrable because the internal item consumes a lot of memory while the wrapper barely uses any 6 | internal class Wrapper 7 | { 8 | public T Item { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sharphound2/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BloodHoundAD/SharpHound2/41516e778ea186e144e4494f2e070cdb9aa878b9/Sharphound2/favicon.ico -------------------------------------------------------------------------------- /Sharphound2/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | --------------------------------------------------------------------------------