USB Storage Device Monitoring using ELK

USB storage devices are commonly used in almost all organizations to store or transfer data. These devices act as the primary sources of malware or a small USB device can be used to steal or exfiltrate confidential data.
This example explains how ELK can be used to monitor and audit USB operations such USB detection, read, write, modification of files and folders in USB block devices on Windows machines.
A windows service is created and installed on Windows computers using two dotnet classes, ManagementEventWatcher and WqlEventQuery to register a service to listen for any USB events. In the ELK setup, a tcp listener is configured with Logstash on tcp port 9988 and once a USB event is occurred on a PC corresponding message is forwarded to port 9988 on Logstash server. A grok pattern is written for Logstash filter to parse the messages and to extract relevant fields as input for Elasticsearch query engine.
The below code for windows service is just a prototype implementation. It can be enhanced and improved in a better way.

Development of Windows Service

A thread created and started that registers two watchers for USB insert and removal events

    protected override void OnStart(string[] args)
       {
           try
           {
               executeThread = new Thread(new ThreadStart(Execute));
               executeThread.Start();
           }
           catch (Exception ex) {  /*Exception handling part*/}
       }
       public void Execute()
       {
          try
           {
           WqlEventQuery insertQuery = new WqlEventQuery("SELECT * FROM Win32_VolumeChangeEvent WHERE EventType = 2");
           ManagementEventWatcher insertWatcher = new ManagementEventWatcher(insertQuery);
           insertWatcher.EventArrived += new EventArrivedEventHandler(DeviceInsertedEvent);
           insertWatcher.Start();
           insertWatcher.WaitForNextEvent();
           WqlEventQuery removeQuery = new WqlEventQuery("SELECT * FROM __InstanceDeletionEvent WITHIN 2 WHERE TargetInstance ISA 'Win32_USBHub'");
           ManagementEventWatcher removeWatcher = new ManagementEventWatcher(removeQuery);
           removeWatcher.EventArrived += new EventArrivedEventHandler(DeviceRemovedEvent);
           removeWatcher.Start();
           removeWatcher.WaitForNextEvent();
           while (true) ;
           }
           catch (ThreadAbortException tx) { /*Exception handling part*/}
       }

 protected override void OnStop()
       {
           try
           {
               ServiceScan f1 = new ServiceScan();
               f1.monitorThread.Abort();
               executeThread.Abort();
           }
          catch (Exception ex) { /*Exception handling part*/}
       }

Handlers for insert and removal events

     private static void DeviceRemovedEvent(object sender, EventArrivedEventArgs e)

       {
           string messageToSend = " :Operation: removed a storage device :Object:  :: at :Time: " + DateTime.Now.ToString();
           ServiceScan f1 = new ServiceScan();
           f1.sendMessage(messageToSend);
           f1.watcher.Dispose();
           f1.monitorThread.Abort();
       }
       private static void DeviceInsertedEvent(object sender, EventArrivedEventArgs e)
       {
           ServiceScan f1 = new ServiceScan();
           string volumeName = e.NewEvent.Properties["DriveName"].Value.ToString();
           Console.WriteLine("DEVICE IS INSERTED : " + volumeName);
           string messageToSend = " :Operation: has inserted a storage device :Object: " + volumeName + " :: at :Time: " + DateTime.Now.ToString();
           f1.sendMessage(messageToSend);
           f1.monitorThread = new Thread(() => driveActivities(volumeName));
           f1.monitorThread.Start();
       }

Handlers for drive activities

       private static void driveActivities(string volumeName)
       {
           ServiceScan f1 = new ServiceScan();
           string path = volumeName;
           f1.watcher = new FileSystemWatcher();
           f1.watcher.NotifyFilter = NotifyFilters.FileName |
                   NotifyFilters.Attributes |
                   NotifyFilters.LastAccess |
                   NotifyFilters.LastWrite |
                   NotifyFilters.Security |
                   NotifyFilters.Size |
                   NotifyFilters.CreationTime |
                   NotifyFilters.DirectoryName;
           try
           {
               f1.watcher.Path = path;//assigning path to be watched
               f1.watcher.EnableRaisingEvents = true;//make sure watcher will raise event in case of change in folder.
               f1.watcher.IncludeSubdirectories = true;//make sure watcher will look into subfolders as well.
               f1.watcher.Filter = "*.*"; //watcher should monitor all types of file.
               f1.watcher.Created += watcher_Created; //register event to be called when a file is created in specified path
               f1.watcher.Changed += watcher_Changed;//register event to be called when a file is updated in specified path
               f1.watcher.Deleted += watcher_Deleted;//register event to be called when a file is deleted in specified path
               f1.watcher.Renamed += watcher_Renamed;
               while (true) ;//run infinite loop so program doesn't terminate until we force it.
           }

           catch {/*Error handling part*/ }
       }
       static void watcher_Renamed(object sender, FileSystemEventArgs e)
       {
           string messageToSend = " :Operation: has renamed a file :Object: " + e.FullPath + " :: at :Time: " + DateTime.Now.ToString();
           ServiceScan f1 = new ServiceScan();
           f1.sendMessage(messageToSend);
       }

       static void watcher_Deleted(object sender, FileSystemEventArgs e)
       {
        string messageToSend = " :Operation: has deleted a file :Object: " + e.FullPath + " :: at :Time: " + DateTime.Now.ToString();
           ServiceScan f1 = new ServiceScan();
           f1.sendMessage(messageToSend);
       }
       static void watcher_Changed(object sender, FileSystemEventArgs e)
       {
           string changeType = e.ChangeType.ToString();
           string messageToSend = " :Operation: has updated a file :Object: " + e.FullPath + " :: at :Time: " + DateTime.Now.ToString();
           ServiceScan f1 = new ServiceScan();
           f1.sendMessage(messageToSend);
       }
      static void watcher_Created(object sender, FileSystemEventArgs e)
       {
       string messageToSend = " :Operation: has created a file :Object: " + e.FullPath + " :: at :Time: " + DateTime.Now.ToString();
           ServiceScan f1 = new ServiceScan();
           f1.sendMessage(messageToSend);
       }

Messaging part to Logstash listener

Logstash server IP : 192.168.10.10, Port : 9988

      private void sendMessage(string message)
       {
           string username = "";
           ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT UserName FROM Win32_ComputerSystem");
           try
           {
               foreach (ManagementObject queryObj in searcher.Get())
               {
                   username = queryObj["username"].ToString();
               }
           }
           catch { username = @"Domain\RDPUser"; }
           IPAddress ip = null;
           string hostName = Dns.GetHostName();
           IPHostEntry hostEntry = Dns.GetHostEntry(hostName);
           if (hostEntry.AddressList.Length == 1)
           { ip = hostEntry.AddressList[0]; }
           else
           {
              foreach (IPAddress var in hostEntry.AddressList)
               {
                  if (var.AddressFamily == AddressFamily.InterNetwork)
                   { ip = var; break; }
               }
           }
           string ipAddress = ip.ToString();
           string fqdn = Dns.GetHostEntry(ip).HostName.ToString();
           message = ":UserName:" + username + message + " :: from IP :IP: " + ipAddress + " :: Host :: " + fqdn + "\r\n";
           IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse("192.168.10.10"), 9988);
           using (TcpClient client = new TcpClient())
           {
               NetworkStream clientStream;
               try
               {
                  client.Connect(serverEndPoint);
                  clientStream = client.GetStream();
                  ASCIIEncoding encoder = new ASCIIEncoding();
                   byte[] buffer;
                   buffer = encoder.GetBytes(message);
                   clientStream.Write(buffer, 0, buffer.Length);
                   clientStream.Dispose();
                   client.Close();
               }
               catch { }
           }
       }

The service can be installed using InstallUtil.exe utility or using GPO to deploy in a domain environment.

Logstash configuration

Listener

 input {

 tcp {
  port => 9988
  type => "USBLOG"
 }
}

Grok pattern

if [type]=="USBLOG"
       {
       grok{
               break_on_match => false
               named_captures_only => true
               match => [
               "message" , ":UserName:\s*%{NOTSPACE:UserID} :Operation: \s*%{GREEDYDATA:Operation} :Object: \s*%{GREEDYDATA:Object} :: at :Time: \s*%{GREEDYDATA:Time} :: from IP :IP: %{GREEDYDATA:FromIP} :: Host :: %{GREEDYDATA:HostName}"
               ]
               }
       mutate { add_field => { "indexType" => "USBLOG-TYPE" }}
       }


Output configuration

if [indexType] == "USBLOG-TYPE"
{
elasticsearch {
hosts => "http://localhost:9200"
manage_template => false
index => "usblog-%{+YYYY.MM.dd}"
       document_type => doc
}

In Kibana, create an  Elasticsearch index for the log type with pattern usblob-*

Log messages in Elasticsearch

And you can generate a number of visualizations,dashboards and reports using the usblog-* index  in Kibana