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