welsonjs/WelsonJS.Toolkit/WelsonJS.Service/FileEventMonitor.cs

255 lines
10 KiB
C#

// FileEventMonitor.cs
// Namhyeon Go <abuse@catswords.net>
// https://github.com/gnh1201/welsonjs
using ClamAV.Net.Client;
using ClamAV.Net.Client.Results;
using Microsoft.Extensions.Logging;
using System;
using System.Diagnostics.Eventing.Reader;
using System.ServiceProcess;
using System.Threading.Tasks;
namespace WelsonJS.Service
{
public class FileEventMonitor
{
private EventLogWatcher eventLogWatcher;
private ServiceMain parent;
private ILogger logger;
private enum EventType: int
{
FileCreate = 11,
NetworkConnection = 3,
RegistryEvent_1 = 12,
RegistryEvent_2 = 13,
RegistryEvent_3 = 14
};
private enum FileCreateEvent: int {
RuleName,
UtcTime,
ProcessGuid,
ProcessId,
Image,
TargetFilename,
CreationUtcTime,
User
};
private enum NetworkConnectionEvent: int
{
RuleName,
UtcTime,
ProcessGuid,
ProcessId,
Image,
User,
Protocol,
Initiated,
SourceIsIpv6,
SourceIp,
SourceHostname,
SourcePort,
SourcePortName,
DestinationIsIpv6,
DestinationIp,
DestinationHostname,
DestinationPort,
DestinationPortName,
};
private enum RegistryEvent: int
{
RuleName,
EventType,
UtcTime,
ProcessGuid,
ProcessId,
Image,
TargetObject,
Details,
User
}
private string clamAvConenctionString;
private IClamAvClient clamAvClient;
public FileEventMonitor(ServiceBase _parent, string workingDirectory, ILogger _logger)
{
parent = (ServiceMain)_parent;
logger = _logger;
try
{
clamAvConenctionString = parent.ReadSettingsValue("CLAMAV_HOST");
}
catch (Exception ex)
{
clamAvConenctionString = "tcp://127.0.0.1:3310";
logger.LogInformation($"Failed to read the address because of {ex.Message}. Set default: {clamAvConenctionString}");
}
ConnectToClamAv().Start();
}
public void Start()
{
try
{
string query = @"<QueryList>
<Query Id='0' Path='Microsoft-Windows-Sysmon/Operational'>
<Select Path='Microsoft-Windows-Sysmon/Operational'>*[System/EventID=11 or System/EventID=3 or System/EventID=12 or System/EventID=13 or System/EventID=14]</Select>
</Query>
</QueryList>";
EventLogQuery eventLogQuery = new EventLogQuery("Microsoft-Windows-Sysmon/Operational", PathType.LogName, query);
eventLogWatcher = new EventLogWatcher(eventLogQuery);
eventLogWatcher.EventRecordWritten += new EventHandler<EventRecordWrittenEventArgs>(OnEventRecordWritten);
eventLogWatcher.Enabled = true;
}
catch (Exception ex)
{
logger.LogInformation($"Could not reach to the Sysmon service: {ex.Message}");
}
}
public void Stop()
{
if (eventLogWatcher != null)
{
try
{
eventLogWatcher.Dispose();
eventLogWatcher = null;
}
catch (Exception)
{
eventLogWatcher = null;
}
}
}
private void OnEventRecordWritten(object sender, EventRecordWrittenEventArgs e)
{
if (e.EventRecord != null)
{
int eventId = e.EventRecord.Id;
try
{
switch (eventId)
{
case (int)EventType.FileCreate:
{
string ruleName = e.EventRecord.Properties[(int)FileCreateEvent.RuleName]?.Value?.ToString();
string processId = e.EventRecord.Properties[(int)FileCreateEvent.ProcessId]?.Value?.ToString();
string image = e.EventRecord.Properties[(int)FileCreateEvent.Image]?.Value?.ToString();
string fileName = e.EventRecord.Properties[(int)FileCreateEvent.TargetFilename]?.Value?.ToString();
logger.LogInformation($"> Detected the file creation: {fileName}");
logger.LogInformation(parent.DispatchServiceEvent("fileCreated", new string[] {
ruleName,
processId,
image,
fileName
}));
if (clamAvClient != null)
{
logger.LogInformation($"> Starting the ClamAV scan: {fileName}");
Task.Run(async () =>
{
await ScanWithClamAv(fileName);
});
}
break;
}
case (int)EventType.NetworkConnection:
{
string ruleName = e.EventRecord.Properties[(int)NetworkConnectionEvent.RuleName]?.Value?.ToString();
string processId = e.EventRecord.Properties[(int)NetworkConnectionEvent.ProcessId]?.Value?.ToString();
string image = e.EventRecord.Properties[(int)NetworkConnectionEvent.Image]?.Value?.ToString();
string protocol = e.EventRecord.Properties[(int)NetworkConnectionEvent.Protocol]?.Value?.ToString();
string destinationIp = e.EventRecord.Properties[(int)NetworkConnectionEvent.DestinationIp]?.Value?.ToString();
string desinationPort = e.EventRecord.Properties[(int)NetworkConnectionEvent.DestinationPort]?.Value?.ToString();
string dstinationAddress = $"{protocol}://{destinationIp}:{desinationPort}";
logger.LogInformation($"> Detected the network connection: {dstinationAddress}");
logger.LogInformation(parent.DispatchServiceEvent("networkConnected", new string[] {
ruleName,
processId,
image,
dstinationAddress
}));
break;
}
case (int)EventType.RegistryEvent_1:
case (int)EventType.RegistryEvent_2:
case (int)EventType.RegistryEvent_3:
{
string ruleName = e.EventRecord.Properties[(int)RegistryEvent.RuleName]?.Value?.ToString();
string processId = e.EventRecord.Properties[(int)RegistryEvent.ProcessId]?.Value?.ToString();
string image = e.EventRecord.Properties[(int)RegistryEvent.Image]?.Value?.ToString();
string eventType = e.EventRecord.Properties[(int)RegistryEvent.EventType]?.Value?.ToString();
string targetObject = e.EventRecord.Properties[(int)RegistryEvent.TargetObject]?.Value?.ToString();
logger.LogInformation($"> Detected the registry modification: {targetObject}");
logger.LogInformation(parent.DispatchServiceEvent("registryModified", new string[] {
ruleName,
processId,
image,
eventType,
targetObject
}));
break;
}
default:
throw new ArgumentException("Not supported event type");
}
}
catch (Exception ex)
{
logger.LogInformation($"Failed to process the event bacause of {ex.Message}.");
}
}
else
{
logger.LogInformation("The event instance was null.");
}
}
private async Task ConnectToClamAv()
{
try {
// Create a client
clamAvClient = ClamAvClient.Create(new Uri(clamAvConenctionString));
// Send PING command to ClamAV
await clamAvClient.PingAsync().ConfigureAwait(false);
// Get ClamAV engine and virus database version
VersionResult result = await clamAvClient.GetVersionAsync().ConfigureAwait(false);
logger.LogInformation($"ClamAV version {result.ProgramVersion}, Virus database version {result.VirusDbVersion}");
}
catch (Exception ex)
{
logger.LogInformation($"Failed to read the address because of {ex.Message}. {clamAvConenctionString}");
clamAvClient = null;
}
}
private async Task ScanWithClamAv(string remotePath)
{
ScanResult res = await clamAvClient.ScanRemotePathAsync(remotePath).ConfigureAwait(false);
logger.LogInformation($"> Scan result: Infected={res.Infected}, VirusName={res.VirusName}");
logger.LogInformation(parent.DispatchServiceEvent("avScanResult", new string[] {
res.Infected.ToString(),
res.VirusName
}));
}
}
}