Add Yara File Rule Matcher #131

This commit is contained in:
Namhyeon Go 2024-08-12 12:47:19 +09:00
parent 1a4931e283
commit 58d1829c9c
8 changed files with 269 additions and 30 deletions

View File

@ -0,0 +1,167 @@
// FileEventMonitor.cs
// https://github.com/gnh1201/welsonjs
using System;
using System.Diagnostics.Eventing.Reader;
using System.IO;
using libyaraNET;
using System.Collections.Generic;
using System.ServiceProcess;
namespace WelsonJS.Service
{
public class FileEventMonitor
{
private Rules rules;
private EventLogWatcher eventLogWatcher;
private ServiceMain parent;
private string ruleFolderPath;
public FileEventMonitor(ServiceBase parent, string workingDirectory)
{
this.parent = (ServiceMain)parent;
this.ruleFolderPath = Path.Combine(workingDirectory, "app/assets/yar");
AddYaraRules(new List<string>(Directory.GetFiles(this.ruleFolderPath, "*.yar")));
}
public void AddYaraRulesFromDirectory(string directoryPath)
{
if (!Directory.Exists(directoryPath))
{
Console.WriteLine($"Directory not found: {directoryPath}");
return;
}
var yarFiles = Directory.GetFiles(directoryPath, "*.yar");
AddYaraRules(new List<string>(yarFiles));
}
public void AddYaraRules(List<string> ruleFiles)
{
Dispose();
using (var ctx = new YaraContext())
{
try
{
using (var compiler = new Compiler())
{
foreach (var ruleFile in ruleFiles)
{
if (File.Exists(ruleFile))
{
compiler.AddRuleFile(ruleFile);
parent.Log($"Loaded YARA rule from {ruleFile}");
}
else
{
parent.Log($"File not found: {ruleFile}");
}
}
rules = compiler.GetRules();
}
}
catch (Exception ex)
{
parent.Log($"Error loading YARA rules: {ex.Message}");
}
}
}
public void Start()
{
string query = @"<QueryList>
<Query Id='0' Path='Microsoft-Windows-Sysmon/Operational'>
<Select Path='Microsoft-Windows-Sysmon/Operational'>*[System/EventID=11]</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;
}
public void Stop()
{
if (eventLogWatcher != null)
{
eventLogWatcher.Dispose();
eventLogWatcher = null;
}
Dispose();
}
private void OnEventRecordWritten(object sender, EventRecordWrittenEventArgs e)
{
if (e.EventRecord != null)
{
try
{
string fileName = e.EventRecord.Properties[7]?.Value?.ToString();
if (!string.IsNullOrEmpty(fileName) && File.Exists(fileName))
{
parent.Log($"File created: {fileName}");
parent.DispatchServiceEvent("fileCreated", new string[] { fileName });
ScanFileWithYara(fileName);
}
}
catch (Exception ex)
{
parent.Log($"Error processing event: {ex.Message}");
}
}
else
{
parent.Log("The event instance was null.");
}
}
private void ScanFileWithYara(string filePath)
{
if (rules == null)
{
parent.Log("No YARA rules loaded. Skipping file scan.");
return;
}
using (var ctx = new YaraContext())
{
var scanner = new Scanner();
var results = scanner.ScanFile(filePath, rules);
if (results.Count > 0)
{
parent.Log($"YARA match found in file {filePath}:");
foreach (var result in results)
{
var matches = result.Matches;
foreach (var match in matches)
{
parent.Log($"YARA matched: {match.ToString()}");
parent.DispatchServiceEvent("fileRuleMatched", new string[] { filePath, match.ToString() });
}
}
}
else
{
parent.Log($"No YARA match found in file {filePath}.");
}
}
}
private void Dispose()
{
if (rules != null)
{
rules.Dispose();
rules = null;
}
}
}
}

View File

@ -1,4 +1,6 @@
using System;
// ScreenMatching.cs
// https://github.com/gnh1201/welsonjs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
@ -49,17 +51,17 @@ public class ScreenMatching
public int Bottom;
}
ServiceMain parent;
public List<Bitmap> templateImages;
string templateFolderPath;
int currentTemplateIndex = 0;
private List<Bitmap> templateImages;
private string templateFolderPath;
private int currentTemplateIndex = 0;
private ServiceMain parent;
public ScreenMatching(ServiceBase _parent, string workingDirectory)
public ScreenMatching(ServiceBase parent, string workingDirectory)
{
parent = (ServiceMain)_parent;
this.parent = (ServiceMain)parent;
this.templateFolderPath = Path.Combine(workingDirectory, "app/assets/img/_templates");
this.templateImages = new List<Bitmap>();
templateFolderPath = Path.Combine(workingDirectory, "app/assets/img/_templates");
templateImages = new List<Bitmap>();
LoadTemplateImages();
}

View File

@ -28,8 +28,11 @@
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
//
// ServiceMain
//
this.ServiceName = "ServiceMain";
}
#endregion

View File

@ -31,7 +31,6 @@ using System.Runtime.InteropServices;
using MSScriptControl;
using System.IO;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace WelsonJS.Service
{
@ -47,7 +46,9 @@ namespace WelsonJS.Service
private readonly string appName = "WelsonJS";
private string[] _args;
private bool disabledScreenTime = false;
private bool disabledFileMonitor = false;
private ScreenMatching screenMatcher;
private FileEventMonitor fileEventMonitor;
[DllImport("user32.dll")]
public static extern int GetSystemMetrics(int nIndex);
@ -77,6 +78,10 @@ namespace WelsonJS.Service
case "disable-screen-time":
disabledScreenTime = true;
break;
case "disable-file-monitor":
disabledFileMonitor = true;
break;
}
}
@ -115,6 +120,15 @@ namespace WelsonJS.Service
defaultTimer.Elapsed += OnElapsedTime;
timers.Add(defaultTimer);
// Trace an event of file creation
if (!disabledFileMonitor)
{
fileEventMonitor = new FileEventMonitor(this, workingDirectory);
fileEventMonitor.Start();
Log("File Event Monitor started.");
}
// check this session is the user interactive mode
if (Environment.UserInteractive) {
this.OnUserInteractiveEnvironment();
@ -170,15 +184,20 @@ namespace WelsonJS.Service
Log($"Script file not found: {scriptFilePath}");
}
timers.ForEach(timer => timer.Start()); // start
timers.ForEach(timer => timer?.Start()); // start
Log(appName + " Service Started");
}
protected override void OnStop()
{
timers.ForEach(timer => timer.Stop()); // stop
// stop timers
timers.ForEach(timer => timer?.Stop());
// stop the File Event Monitor
fileEventMonitor?.Stop();
// dispatch stop callback
try
{
Log(DispatchServiceEvent("stop"));
@ -261,19 +280,6 @@ namespace WelsonJS.Service
}
}
private string DispatchServiceEvent(string eventType, string[] args = null)
{
if (args == null)
{
return InvokeScriptMethod("dispatchServiceEvent", scriptName, eventType, "");
}
else
{
return InvokeScriptMethod("dispatchServiceEvent", scriptName, eventType, String.Join("; ", args));
}
}
private string InvokeScriptMethod(string methodName, params object[] parameters)
{
if (scriptControl != null)
@ -314,6 +320,19 @@ namespace WelsonJS.Service
return arguments;
}
public string DispatchServiceEvent(string eventType, string[] args = null)
{
if (args == null)
{
return InvokeScriptMethod("dispatchServiceEvent", scriptName, eventType, "");
}
else
{
return InvokeScriptMethod("dispatchServiceEvent", scriptName, eventType, String.Join("; ", args));
}
}
public void Log(string message)
{
string _message = $"{DateTime.Now}: {message}";

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\Microsoft.O365.Security.Native.libyara.NET.4.5.1\build\net462\Microsoft.O365.Security.Native.libyara.NET.props" Condition="Exists('..\packages\Microsoft.O365.Security.Native.libyara.NET.4.5.1\build\net462\Microsoft.O365.Security.Native.libyara.NET.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@ -12,6 +13,8 @@
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@ -90,6 +93,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="FileEventMonitor.cs" />
<Compile Include="ServiceMain.cs">
<SubType>Component</SubType>
</Compile>
@ -126,6 +130,13 @@
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>이 프로젝트는 이 컴퓨터에 없는 NuGet 패키지를 참조합니다. 해당 패키지를 다운로드하려면 NuGet 패키지 복원을 사용하십시오. 자세한 내용은 http://go.microsoft.com/fwlink/?LinkID=322105를 참조하십시오. 누락된 파일은 {0}입니다.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Microsoft.O365.Security.Native.libyara.NET.4.5.1\build\net462\Microsoft.O365.Security.Native.libyara.NET.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.O365.Security.Native.libyara.NET.4.5.1\build\net462\Microsoft.O365.Security.Native.libyara.NET.props'))" />
</Target>
</Project>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.O365.Security.Native.libyara.NET" version="4.5.1" targetFramework="net48" />
</packages>

19
app.js
View File

@ -595,6 +595,15 @@ function dispatchServiceEvent(name, eventType, _args) {
// load the service
if (app) {
var bind = function(eventType) {
var event_callback_name = "on" + eventType;
if (event_callback_name in app && typeof app[event_callback_name] === "function")
return app[event_callback_name];
return null;
};
return (function(action) {
if (eventType in action) {
try {
@ -606,10 +615,12 @@ function dispatchServiceEvent(name, eventType, _args) {
}
}
})({
start: app.onServiceStart,
stop: app.onServiceStop,
elapsedTime: app.onServiceElapsedTime,
screenTime: app.onServiceScreenTime
start: bind("ServiceStart"),
stop: bind("ServiceStop"),
elapsedTime: bind("ServiceElapsedTime"),
screenTime: bind("ServiceScreenTime"),
fileCreated: bind("FileCreated"),
fileRuleMatched: bind("FileRuleMatched")
});
} else {
console.error("Could not find", name + ".js");

View File

@ -0,0 +1,22 @@
rule malw_eicar {
meta:
description = "Rule to detect the EICAR pattern"
author = "Marc Rivero | McAfee ATR Team"
reference = "https://www.eicar.org/"
rule_version = "v1"
malware_type = "eicar"
malware_family = "W32/Eicar"
actor_type = "Unknown"
actor_group = "Unknown"
hash = "275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f"
strings:
$s1 = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" fullword ascii
condition:
any of them
}