/ csharp / SharpLDAPmonitor / Program.cs
Program.cs
  1  using System;
  2  using System.Threading;
  3  using System.Collections.Generic;
  4  using System.Collections;
  5  using System.DirectoryServices;
  6  using System.DirectoryServices.ActiveDirectory;
  7  
  8  namespace SharpLDAPMonitor
  9  {
 10      class Program
 11      {
 12          static void Main(string[] args)
 13          {
 14  
 15              var parsed = ArgumentParser.Parse(args);
 16              Int32 delayInSeconds = 1;
 17              String SearchBase = null;
 18              String username = null;
 19              String password = null;
 20              String connectionString = "LDAP://{0}:{1}";
 21              Int32 PageSize = 5000;
 22              DirectoryEntry ldapConnection;
 23              Dictionary<string, ResultPropertyCollection> results_before = null;
 24              Dictionary<string, ResultPropertyCollection> results_after = null;
 25              Logger logger = null;
 26  
 27              if (parsed.Arguments.ContainsKey("/logfile"))
 28              {
 29                  logger = new Logger(parsed.Arguments["/logfile"], parsed.Arguments.ContainsKey("/debug"));
 30              }
 31              else
 32              {
 33                  logger = new Logger(null, parsed.Arguments.ContainsKey("/debug"));
 34              }
 35  
 36  
 37              logger.WriteLine("[+]======================================================");
 38              logger.WriteLine("[+]  Sharp LDAP live monitor v1.3        @podalirius_    ");
 39              logger.WriteLine("[+]======================================================");
 40              logger.WriteLine("");
 41  
 42              // Display help
 43              if (parsed.Arguments.ContainsKey("/help") || parsed.Arguments.ContainsKey("/h") || parsed.Arguments.Count == 0)
 44              {
 45                  logger.WriteLine("Required");
 46                  logger.WriteLine("   /dcip:<1.1.1.1>    LDAP host to target, most likely the domain controller.");
 47  
 48                  logger.WriteLine("\nOptional");
 49                  logger.WriteLine("   /user:<username>   User to authenticate as.");
 50                  logger.WriteLine("   /pass:<password>   Password of the account.");
 51                  logger.WriteLine("   /ldaps             Use LDAPS instead of LDAP.");
 52                  logger.WriteLine("   /searchbase        Sets the LDAP search base.");
 53                  logger.WriteLine("   /delay:<int>       Delay between two queries in seconds (default: 1).");
 54                  logger.WriteLine("   /randomize         Randomize delay between two queries, between 1 and 5 seconds.");
 55                  logger.WriteLine("   /pagesize          Sets the LDAP page size to use in queries (default: 5000).");
 56                  logger.WriteLine("   /ignoreuserlogons  Ignores user logon events.");
 57                  logger.WriteLine("   /debug             Debug mode.");
 58  
 59                  logger.WriteLine("\nUsage: ldapmonitor.exe /user:DOMAIN\\User /pass:MyP@ssw0rd123! /dcip:192.168.1.1");
 60                  Environment.Exit(-1);
 61              }
 62  
 63              Boolean ignoreuserlogons = parsed.Arguments.ContainsKey("/ignoreuserlogons");
 64  
 65              // Time delay
 66              if (!(parsed.Arguments.ContainsKey("/randomize")))
 67              {
 68                  if (parsed.Arguments.ContainsKey("/delay"))
 69                  {
 70                      delayInSeconds = Int32.Parse(parsed.Arguments["/delay"]);
 71                  }
 72                  else
 73                  {
 74                      delayInSeconds = 1;
 75                  }
 76              }
 77  
 78              // Handle target host
 79              if (!parsed.Arguments.ContainsKey("/dcip"))
 80              {
 81                  logger.WriteLine("[!] /dcip parameter is required.");
 82                  Environment.Exit(-1);
 83              }
 84  
 85              // Handle LDAPS connection switch
 86              if (!parsed.Arguments.ContainsKey("/ldaps"))
 87              {
 88                  connectionString = String.Format(connectionString, parsed.Arguments["/dcip"], "389");
 89              }
 90              else
 91              {
 92                  connectionString = String.Format(connectionString, parsed.Arguments["/dcip"], "636");
 93              }
 94  
 95              // Handle pagesize for LDAP responses
 96              if (parsed.Arguments.ContainsKey("/pagesize"))
 97              {
 98                  PageSize = Int32.Parse(parsed.Arguments["/pagesize"]);
 99              }
100              else
101              {
102                  PageSize = 5000;
103              }
104  
105              // Handle 
106              if (parsed.Arguments.ContainsKey("/searchbase"))
107              {
108                  SearchBase = parsed.Arguments["/searchbase"];
109              }
110  
111              // Use the provided credentials or the current session
112              if (parsed.Arguments.ContainsKey("/user") && parsed.Arguments.ContainsKey("/pass"))
113              {
114                  logger.WriteLine("[+] Using the following credentials:");
115                  logger.WriteLine("  | Target: " + connectionString);
116                  logger.WriteLine("  | User: '" + parsed.Arguments["/user"] + "'");
117                  logger.WriteLine("  | Pass: '" + parsed.Arguments["/pass"] + "'");
118                  username = parsed.Arguments["/user"];
119                  password = parsed.Arguments["/pass"];
120              }
121              else
122              {
123                  logger.WriteLine("[+] Using the current session");
124                  logger.WriteLine("  | Host: " + connectionString);
125              }
126  
127              try
128              {
129                  // Get RootDSE infos (to get list of namingContexts)
130                  DirectoryEntry rootDSE = new System.DirectoryServices.DirectoryEntry(String.Format("{0}/RootDSE", connectionString), username, password, System.DirectoryServices.AuthenticationTypes.Secure);
131                  List<String> namingContexts = new List<string>();
132                  foreach (String nc in rootDSE.Properties["namingContexts"]) { namingContexts.Add(nc); }
133  
134                  // First query 
135                  logger.Debug("Performing initial query ...");
136                  results_before = QueryAllNamingContextsOrSearchBase(namingContexts, connectionString, SearchBase, username, password, PageSize, logger);
137  
138                  logger.WriteLine("\n[>] Listening for LDAP changes ...");
139  
140                  while (true)
141                  {
142                      // Update query
143                      results_after = QueryAllNamingContextsOrSearchBase(namingContexts, connectionString, SearchBase, username, password, PageSize, logger);
144  
145                      // Diff
146                      diff(results_before, results_after, connectionString, logger, ignoreuserlogons);
147                      results_before = results_after;
148  
149                      logger.Debug("Waiting " + delayInSeconds + " second.");
150  
151                      if (parsed.Arguments.ContainsKey("/randomize"))
152                      {
153                          Random rnd = new Random();
154                          delayInSeconds = rnd.Next(1, 5);
155                      }
156                      Thread.Sleep(delayInSeconds * 1000);
157                  }
158              }
159              catch (System.Runtime.InteropServices.COMException e)
160              {
161                  logger.WriteLine("\n");
162                  logger.Warning("Error: (0x" + e.ErrorCode.ToString("X8") + ") " + e.Message);
163              }
164          }
165  
166          static Dictionary<string, ResultPropertyCollection> QueryAllNamingContextsOrSearchBase(List<String> namingContexts, String connectionString, String SearchBase, String Username, String Password, int PageSize, Logger logger)
167          {
168              DirectoryEntry ldapConnection;
169              DirectorySearcher ldapSearcher;
170              Dictionary<string, ResultPropertyCollection> results = new Dictionary<string, ResultPropertyCollection>();
171  
172              if (SearchBase != null)
173              {
174                  logger.Debug(String.Format("Using SearchBase: {0}", SearchBase));
175                  ldapConnection = new System.DirectoryServices.DirectoryEntry(String.Format("{0}/{1}", connectionString, SearchBase), Username, Password, System.DirectoryServices.AuthenticationTypes.Secure);
176                  ldapSearcher = new DirectorySearcher(ldapConnection);
177                  ldapSearcher.Filter = "(objectClass=*)";
178  
179                  foreach (SearchResult item in ldapSearcher.FindAll())
180                  {
181                      if (!(results.ContainsKey(item.Path)))
182                      {
183                          results[item.Path] = item.Properties;
184                      }
185                      else
186                      {
187                          logger.Debug(String.Format("[debug] key already exists: {0} (this shouldn't be possible)", item.Path));
188                      }
189                  }
190  
191                  return results;
192              }
193              else
194              {
195                  foreach (String nc in namingContexts) {
196                      logger.Debug(String.Format("Using namingContext as search base: {0}", SearchBase));
197                      ldapConnection = new System.DirectoryServices.DirectoryEntry(String.Format("{0}/{1}", connectionString, nc), Username, Password, System.DirectoryServices.AuthenticationTypes.Secure);
198                      ldapSearcher = new DirectorySearcher(ldapConnection);
199                      ldapSearcher.Filter = "(objectClass=*)";
200  
201                      foreach(SearchResult item in ldapSearcher.FindAll()) {
202                          if (!(results.ContainsKey(item.Path)))
203                          {
204                              results[item.Path] = item.Properties;
205                          }
206                          else
207                          {
208                              logger.Debug(String.Format("[debug] key already exists: {0} (this shouldn't be possible)", item.Path));
209                          }
210                      }
211                  }
212                  return results;
213              }
214          }
215  
216          /*        static void InitLdapConnection()
217                  {
218                       = new DirectoryEntry(connectionString, username, password, System.DirectoryServices.AuthenticationTypes.Secure);
219                      logger.Debug("Authentication successful!");
220                  }
221          */
222          static void diff(Dictionary<string, ResultPropertyCollection> dict_results_before, Dictionary<string, ResultPropertyCollection> dict_results_after, String connectionString, Logger logger, Boolean ignoreuserlogons)
223          {
224              List<String> ignore_keys = new List<String>();
225              if (ignoreuserlogons)
226              {
227                  ignore_keys.Add("lastlogon");
228                  ignore_keys.Add("logoncount");
229              }
230  
231              String dateprompt = "[" + DateTime.UtcNow.ToString("yyyy/MM/dd hh:mm:ss") + "] ";
232   
233              // Get created and deleted entries, and common_keys
234              List<String> common_keys = new List<String>();
235              foreach (String key in dict_results_before.Keys)
236              {
237                  if (dict_results_after.ContainsKey(key)) { common_keys.Add(key); }
238                  else { logger.WriteLine(dateprompt + "'" + key.Replace(connectionString + "/", "") + "' was deleted."); }
239              }
240              foreach (String key in dict_results_after.Keys)
241              {
242                  if (!dict_results_before.ContainsKey(key)) { logger.WriteLine(dateprompt + "'" + key.Replace(connectionString + "/", "") + "' was added."); }
243              }
244  
245              List<Tuple<string, string, Object, Object>> attrs_diff = new List<Tuple<string, string, Object, Object>>();
246  
247              // Iterate over all the common keys
248              foreach (String path in common_keys)
249              {
250                  attrs_diff.Clear();
251  
252                  // Convert into dictionnaries
253                  Dictionary<String, Object> dict_direntry_before = new Dictionary<String, Object>();
254                  Dictionary<String, Object> dict_direntry_after = new Dictionary<String, Object>();
255  
256                  foreach (DictionaryEntry prop in dict_results_before[path])
257                  {
258                      if (!(ignore_keys.Contains(prop.Key.ToString().ToLower())))
259                      {
260                          dict_direntry_before.Add(prop.Key.ToString(), dict_results_before[path][prop.Key.ToString()][0]);
261                      }
262                  };
263                  foreach (DictionaryEntry prop in dict_results_after[path])
264                  {
265                      if (!(ignore_keys.Contains(prop.Key.ToString().ToLower())))
266                      {
267                          dict_direntry_after.Add(prop.Key.ToString(), dict_results_after[path][prop.Key.ToString()][0]);
268                      }
269                  };
270  
271                  // Store different values
272                  foreach (String pname in dict_direntry_after.Keys)
273                  {
274                      if (dict_direntry_after.ContainsKey(pname) && dict_direntry_before.ContainsKey(pname))
275                      {
276                          if (!(dict_direntry_after[pname].ToString() == dict_direntry_before[pname].ToString()))
277                          {
278                              Tuple<string, string, Object, Object> diff = new Tuple<string, string, Object, Object>(path, pname, dict_direntry_after[pname], dict_direntry_before[pname]);
279                              attrs_diff.Add(diff);
280                          }
281                      }
282                      else if (dict_direntry_after.ContainsKey(pname) && !dict_direntry_before.ContainsKey(pname))
283                      {
284                          Tuple<string, string, Object, Object> diff = new Tuple<string, string, Object, Object>(path, pname, dict_direntry_after[pname], null);
285                          attrs_diff.Add(diff);
286                      }
287                      else if (!dict_direntry_after.ContainsKey(pname) && dict_direntry_before.ContainsKey(pname))
288                      {
289                          Tuple<string, string, Object, Object> diff = new Tuple<string, string, Object, Object>(path, pname, null, dict_direntry_before[pname]);
290                          attrs_diff.Add(diff);
291                      }
292                  }
293  
294                  // Show results
295                  if (attrs_diff.ToArray().Length != 0)
296                  {
297                      logger.WriteLine(dateprompt + path.Replace(connectionString + "/", ""));
298  
299                      foreach (Tuple<string, string, Object, Object> t in attrs_diff)
300                      {
301                          if ((t.Item4 != null) && (t.Item3 != null))
302                          {
303                              logger.WriteLine(" | Attribute " + t.Item2 + " changed from '" + t.Item4 + "' to '" + t.Item3 + "'");
304                          }
305                          else if ((t.Item4 == null) && (t.Item3 != null))
306                          {
307                              logger.WriteLine(" | Attribute " + t.Item2 + " = '" + t.Item3 + "' was created.");
308                          }
309                          else if ((t.Item4 != null) && (t.Item3 == null))
310                          {
311                              logger.WriteLine(" | Attribute " + t.Item2 + " = '" + t.Item4 + "' was deleted.");
312                          }
313                      }
314                  }
315              }
316          }
317      }
318  }