├── .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 |
--------------------------------------------------------------------------------