├── .github └── CODEOWNERS ├── .gitignore ├── App.config ├── CODEOWNERS ├── Program.cs ├── Properties └── AssemblyInfo.cs ├── README.md ├── SiteCodeHunter.csproj └── SiteCodeHunter.sln /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @zephrfish 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /.vs 3 | /obj 4 | -------------------------------------------------------------------------------- /App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @zephrfish 2 | -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.DirectoryServices.Protocols; 3 | using System.Net; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text.RegularExpressions; 7 | 8 | public class SCCMSiteCodeHunter 9 | { 10 | private static void FindManagementPointsAndSiteServer(string domain, string username = null, string password = null, bool useLdaps = false, bool debug = false) 11 | { 12 | int ldapPort = useLdaps ? 636 : 389; 13 | string protocol = useLdaps ? "ldaps://" : "ldap://"; 14 | string searchBase = FqdnToBaseDn(domain); 15 | 16 | try 17 | { 18 | if (debug) 19 | { 20 | Console.WriteLine($"[DEBUG] LDAP Protocol: {protocol}"); 21 | Console.WriteLine($"[DEBUG] LDAP Port: {ldapPort}"); 22 | Console.WriteLine($"[DEBUG] Search Base: {searchBase}"); 23 | Console.WriteLine($"[DEBUG] Using credentials: {(!string.IsNullOrEmpty(username) ? username : "Current User Context")}"); 24 | } 25 | 26 | Console.WriteLine($"[+] Connecting to {protocol}{domain}:{ldapPort}..."); 27 | 28 | var ldapConnection = new LdapConnection(new LdapDirectoryIdentifier(domain, ldapPort, useLdaps, false)); 29 | 30 | if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password)) 31 | { 32 | ldapConnection.Credential = new NetworkCredential(username, password, domain); 33 | } 34 | 35 | ldapConnection.AuthType = AuthType.Negotiate; 36 | 37 | string searchFilter = "(objectclass=mSSMSManagementPoint)"; 38 | string[] searchAttributes = { "mSSMSMPName", "distinguishedName", "name", "mSSMSSiteCode" }; 39 | 40 | if (debug) 41 | { 42 | Console.WriteLine($"[DEBUG] LDAP Search Filter: {searchFilter}"); 43 | Console.WriteLine($"[DEBUG] LDAP Search Attributes: {string.Join(", ", searchAttributes)}"); 44 | } 45 | 46 | var searchRequest = new SearchRequest(searchBase, searchFilter, SearchScope.Subtree, searchAttributes); 47 | 48 | var searchResponse = (SearchResponse)ldapConnection.SendRequest(searchRequest); 49 | 50 | if (debug) 51 | { 52 | Console.WriteLine($"[DEBUG] LDAP Search Response: {searchResponse.Entries.Count} entries found."); 53 | } 54 | 55 | string siteCode = null; 56 | string managementPoint = null; 57 | 58 | foreach (SearchResultEntry entry in searchResponse.Entries) 59 | { 60 | managementPoint = entry.Attributes["mSSMSMPName"]?[0]?.ToString() ?? "Unknown"; 61 | Console.WriteLine($"[+] Found Management Point: {managementPoint}"); 62 | 63 | if (debug) 64 | { 65 | Console.WriteLine("[DEBUG] Entry Attributes:"); 66 | foreach (string attr in entry.Attributes.AttributeNames) 67 | { 68 | Console.WriteLine($"[DEBUG] {attr}: {entry.Attributes[attr][0]}"); 69 | } 70 | } 71 | 72 | siteCode = entry.Attributes["mSSMSSiteCode"]?[0]?.ToString() ?? "Unknown"; 73 | Console.WriteLine($" [+] Associated Site Code: {siteCode}"); 74 | } 75 | 76 | if (!string.IsNullOrEmpty(siteCode)) 77 | { 78 | FindSiteServer(ldapConnection, searchBase, siteCode, managementPoint, debug); 79 | } 80 | else 81 | { 82 | Console.WriteLine("[!] Could not determine the Site Code."); 83 | } 84 | } 85 | catch (Exception ex) 86 | { 87 | Console.WriteLine($"[!] Error: {ex.Message}"); 88 | if (debug) 89 | { 90 | Console.WriteLine($"[DEBUG] Stack Trace: {ex.StackTrace}"); 91 | } 92 | } 93 | } 94 | 95 | private static void FindSiteServer(LdapConnection ldapConnection, string searchBase, string siteCode, string managementPoint, bool debug) 96 | { 97 | try 98 | { 99 | string searchFilter = $"(&(objectClass=*)(mSSMSSiteCode={siteCode}))"; 100 | string[] searchAttributes = { "name", "distinguishedName", "mSSMSSiteCode", "mSSMSMPName", "dNSHostName", "mSSMSSiteServer" }; 101 | 102 | if (debug) 103 | { 104 | Console.WriteLine($"[DEBUG] Searching for Site Server with Site Code: {siteCode}"); 105 | Console.WriteLine($"[DEBUG] LDAP Search Filter: {searchFilter}"); 106 | } 107 | 108 | var searchRequest = new SearchRequest(searchBase, searchFilter, SearchScope.Subtree, searchAttributes); 109 | var searchResponse = (SearchResponse)ldapConnection.SendRequest(searchRequest); 110 | 111 | bool validServerFound = false; 112 | 113 | if (searchResponse.Entries.Count > 0) 114 | { 115 | foreach (SearchResultEntry entry in searchResponse.Entries) 116 | { 117 | string siteServer = entry.Attributes["name"]?[0]?.ToString() ?? "Unknown"; 118 | string dnsHostName = entry.Attributes["dNSHostName"]?[0]?.ToString() ?? "Unknown"; 119 | string isSiteServer = entry.Attributes["mSSMSSiteServer"]?[0]?.ToString() ?? "false"; 120 | 121 | if (debug) 122 | { 123 | Console.WriteLine("[DEBUG] Site Server Attributes:"); 124 | foreach (string attr in entry.Attributes.AttributeNames) 125 | { 126 | Console.WriteLine($"[DEBUG] {attr}: {entry.Attributes[attr][0]}"); 127 | } 128 | } 129 | 130 | if (isSiteServer.ToLower() == "true" && SiteServerExists(dnsHostName)) 131 | { 132 | Console.WriteLine($"[+] Valid Site Server: {siteServer} ({dnsHostName})"); 133 | validServerFound = true; 134 | } 135 | else 136 | { 137 | Console.WriteLine($"[!] Invalid Site Server: {siteServer} ({dnsHostName})"); 138 | } 139 | } 140 | } 141 | 142 | if (!validServerFound && !string.IsNullOrEmpty(managementPoint)) 143 | { 144 | Console.WriteLine($"[+] No valid Site Server found. Using Management Point as fallback: {managementPoint}"); 145 | } 146 | else if (!validServerFound) 147 | { 148 | Console.WriteLine("[!] No valid Site Server or fallback Management Point found."); 149 | } 150 | } 151 | catch (Exception ex) 152 | { 153 | Console.WriteLine($"[!] Error while searching for Site Server: {ex.Message}"); 154 | } 155 | } 156 | 157 | private static bool SiteServerExists(string dnsHostName) 158 | { 159 | try 160 | { 161 | var hostEntry = System.Net.Dns.GetHostEntry(dnsHostName); 162 | return hostEntry != null; 163 | } 164 | catch 165 | { 166 | return false; 167 | } 168 | } 169 | 170 | private static string FqdnToBaseDn(string fqdn) 171 | { 172 | if (string.IsNullOrEmpty(fqdn)) 173 | { 174 | throw new ArgumentNullException(nameof(fqdn), "FQDN cannot be null or empty."); 175 | } 176 | 177 | return string.Join(",", fqdn.Split('.').Select(part => $"DC={part}")); 178 | } 179 | 180 | private static void ShowHelp() 181 | { 182 | Console.WriteLine("Usage: SiteCodeHunter.exe --domain [--username ] [--password ] [--ldaps] [--debug]"); 183 | Console.WriteLine("Options:"); 184 | Console.WriteLine(" --domain Specify the domain name to query."); 185 | Console.WriteLine(" --username Specify the username for authentication."); 186 | Console.WriteLine(" --password Specify the password for authentication."); 187 | Console.WriteLine(" --ldaps Use LDAPS (secure LDAP) for the query."); 188 | Console.WriteLine(" --debug Enable debug output."); 189 | Console.WriteLine(" --help Show this help message."); 190 | } 191 | 192 | public static void Main(string[] args) 193 | { 194 | var arguments = new Dictionary(); 195 | for (int i = 0; i < args.Length; i++) 196 | { 197 | if (args[i].StartsWith("--")) 198 | { 199 | string key = args[i]; 200 | string value = (i + 1 < args.Length && !args[i + 1].StartsWith("--")) ? args[++i] : null; 201 | arguments[key] = value; 202 | } 203 | } 204 | 205 | if (arguments.ContainsKey("--help")) 206 | { 207 | ShowHelp(); 208 | return; 209 | } 210 | 211 | if (!arguments.ContainsKey("--domain") || string.IsNullOrEmpty(arguments["--domain"])) 212 | { 213 | Console.WriteLine("[!] Error: --domain is required."); 214 | ShowHelp(); 215 | return; 216 | } 217 | 218 | string domain = arguments["--domain"]; 219 | string username = arguments.ContainsKey("--username") ? arguments["--username"] : null; 220 | string password = arguments.ContainsKey("--password") ? arguments["--password"] : null; 221 | bool useLdaps = arguments.ContainsKey("--ldaps"); 222 | bool debug = arguments.ContainsKey("--debug"); 223 | 224 | if (debug) 225 | { 226 | Console.WriteLine($"[DEBUG] Domain: {domain}"); 227 | Console.WriteLine($"[DEBUG] Username: {username ?? "null"}"); 228 | Console.WriteLine($"[DEBUG] Use LDAPS: {useLdaps}"); 229 | } 230 | 231 | try 232 | { 233 | FindManagementPointsAndSiteServer(domain, username, password, useLdaps, debug); 234 | } 235 | catch (Exception ex) 236 | { 237 | Console.WriteLine($"[!] An error occurred: {ex.Message}"); 238 | if (debug) 239 | { 240 | Console.WriteLine($"[DEBUG] Stack Trace: {ex.StackTrace}"); 241 | } 242 | } 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SiteCodeHunter")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SiteCodeHunter")] 13 | [assembly: AssemblyCopyright("Copyright © 2024")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("e6253dc2-dc70-4952-9309-92729750d5ca")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | [assembly: AssemblyVersion("1.0.0.0")] 33 | [assembly: AssemblyFileVersion("1.0.0.0")] 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SCCMSiteCodeHunter 2 | While porting CMloot to CSharp I started writing a function to enumerate SCCM servers and thus this little utility was born. SiteCodeHunter or SCCMSiteCodeHunter will take a domain and/or credentials and connect to it over LDAP/LDAPS depending on what flags you give it, it'll then perform LDAP queries to get the sitecode and perform a bit of validation to identify if it is a valid sitecode or not. 3 | 4 | It is primarily a utility for querying SCCM (System Center Configuration Manager) management points and site servers using LDAP. This program supports LDAPS for secure queries and includes debugging options for troubleshooting. 5 | 6 | ## Usage 7 | ``` 8 | SiteCodeHunter.exe --domain [--username ] [--password ] [--ldaps] [--debug] 9 | ``` 10 | 11 | - `--domain `: Specify the domain name to query (required) 12 | - `--username `: Specify the username for authentication (optional) 13 | - `--password `: Specify the password for authentication (optional) 14 | - `--ldaps`: Use LDAPS (secure LDAP) for the query (optional) 15 | - `--debug`: Enable debug output for troubleshooting (optional) 16 | - `--help`: Show the help message 17 | 18 | ## Example Usage 19 | Querying with default user context 20 | ``` 21 | SiteCodeHunter.exe --domain polaris.internal 22 | [+] Connecting to ldap://polaris.internal:389... 23 | [+] Found Management Point: SCCM-MGMT.POLARIS.INTERNAL 24 | [+] Associated Site Code: 123 25 | [!] Invalid Site Server: SMS-Site-123 (Unknown) 26 | [!] Invalid Site Server: SMS-MP-123-SCCM-MGMT.POLARIS.INTERNAL (sccm-mgmt.polaris.internal) 27 | [+] No valid Site Server found. Using Management Point as fallback: SCCM-MGMT.POLARIS.INTERNAL 28 | ``` 29 | 30 | Querying with username and password 31 | ``` 32 | SiteCodeHunter.exe --domain polaris.internal --username user --password password 33 | [+] Connecting to ldap://polaris.internal:389... 34 | [+] Found Management Point: SCCM-MGMT.POLARIS.INTERNAL 35 | [+] Associated Site Code: 123 36 | [!] Invalid Site Server: SMS-Site-123 (Unknown) 37 | [!] Invalid Site Server: SMS-MP-123-SCCM-MGMT.POLARIS.INTERNAL (sccm-mgmt.polaris.internal) 38 | [+] No valid Site Server found. Using Management Point as fallback: SCCM-MGMT.POLARIS.INTERNAL 39 | ``` 40 | 41 | Querying using LDAPS with debug enabled 42 | ``` 43 | SiteCodeHunter.exe --domain polaris.internal --ldaps --debug 44 | [DEBUG] Domain: polaris.internal 45 | [DEBUG] Username: null 46 | [DEBUG] Use LDAPS: True 47 | [DEBUG] LDAP Protocol: ldaps:// 48 | [DEBUG] LDAP Port: 636 49 | [DEBUG] Search Base: DC=polaris,DC=internal 50 | [DEBUG] Using credentials: Current User Context 51 | [+] Connecting to ldaps://polaris.internal:636... 52 | [DEBUG] LDAP Search Filter: (objectclass=mSSMSManagementPoint) 53 | [DEBUG] LDAP Search Attributes: mSSMSMPName, distinguishedName, name, mSSMSSiteCode 54 | [!] Error: The LDAP server is unavailable. 55 | ``` -------------------------------------------------------------------------------- /SiteCodeHunter.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {E6253DC2-DC70-4952-9309-92729750D5CA} 8 | Exe 9 | SiteCodeHunter 10 | SiteCodeHunter 11 | v4.7.2 12 | 512 13 | true 14 | true 15 | false 16 | Q:\SCCMSiteCodeHunter\ 17 | true 18 | Disk 19 | false 20 | Foreground 21 | 7 22 | Days 23 | false 24 | false 25 | true 26 | 1 27 | 1.0.0.%2a 28 | false 29 | true 30 | true 31 | 32 | 33 | AnyCPU 34 | true 35 | full 36 | false 37 | bin\Debug\ 38 | DEBUG;TRACE 39 | prompt 40 | 4 41 | 42 | 43 | AnyCPU 44 | pdbonly 45 | true 46 | bin\Release\ 47 | TRACE 48 | prompt 49 | 4 50 | 51 | 52 | DF882D0F65C864452DCCFD9ED7BA7890D15B2B77 53 | 54 | 55 | true 56 | 57 | 58 | true 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | False 81 | Microsoft .NET Framework 4.7.2 %28x86 and x64%29 82 | true 83 | 84 | 85 | False 86 | .NET Framework 3.5 SP1 87 | false 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /SiteCodeHunter.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.11.35431.28 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SiteCodeHunter", "SiteCodeHunter.csproj", "{E6253DC2-DC70-4952-9309-92729750D5CA}" 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 | {E6253DC2-DC70-4952-9309-92729750D5CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {E6253DC2-DC70-4952-9309-92729750D5CA}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {E6253DC2-DC70-4952-9309-92729750D5CA}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {E6253DC2-DC70-4952-9309-92729750D5CA}.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 = {292EADA7-5E1A-41FD-9872-815E1B9A9D37} 24 | EndGlobalSection 25 | EndGlobal 26 | --------------------------------------------------------------------------------