using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using MLVScan.Abstractions; using MLVScan.Adapters; using MLVScan.MelonLoader; using MLVScan.Models; using MLVScan.Models.CrossAssembly; using MLVScan.Models.DataFlow; using MLVScan.Models.Dto; using MLVScan.Models.Rules; using MLVScan.Models.Rules.Helpers; using MLVScan.Models.ThreatIntel; using MLVScan.Services; using MLVScan.Services.Caching; using MLVScan.Services.Configuration; using MLVScan.Services.DataFlow; using MLVScan.Services.Diagnostics; using MLVScan.Services.Helpers; using MLVScan.Services.Resolution; using MLVScan.Services.Scope; using MLVScan.Services.ThreatIntel; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using Microsoft.Win32.SafeHandles; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Collections.Generic; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(MelonLoaderPlugin), "MLVScan", "2.1.1", "Bars", null)] [assembly: MelonPriority(int.MinValue)] [assembly: MelonColor(255, 139, 0, 0)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("MLVScan.MelonLoader")] [assembly: AssemblyConfiguration("MelonLoader")] [assembly: AssemblyFileVersion("2.1.1")] [assembly: AssemblyInformationalVersion("2.1.1+16c98237552f20efc624e1f89d8969e34db43e19")] [assembly: AssemblyProduct("MLVScan.MelonLoader")] [assembly: AssemblyTitle("MLVScan.MelonLoader")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.1.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace MLVScan { internal static class MLVScanBuildInfo { public const string PlatformVersion = "2.1.1"; } public static class PlatformConstants { public const string PlatformVersion = "2.1.1"; public const string PlatformName = "MLVScan.MelonLoader"; public static string GetVersionString() { return "MLVScan.MelonLoader v2.1.1"; } public static string GetFullVersionInfo() { return "Engine: MLVScan.Core v" + MLVScanVersions.CoreVersion + " Platform: " + GetVersionString(); } } } namespace MLVScan.Adapters { public class MelonScanLogger : IScanLogger { private readonly Instance _logger; public MelonScanLogger(Instance logger) { _logger = logger ?? throw new ArgumentNullException("logger"); } public void Debug(string message) { _logger.Msg("[DEBUG] " + message); } public void Info(string message) { _logger.Msg(message); } public void Warning(string message) { _logger.Warning(message); } public void Error(string message) { _logger.Error(message); } public void Error(string message, Exception exception) { _logger.Error($"{message}: {exception}"); } } public class GameAssemblyResolverProvider : CatalogingAssemblyResolverProviderBase { public GameAssemblyResolverProvider(LoaderScanTelemetryHub telemetry) : base(telemetry) { } protected override IEnumerable GetStableRoots() { List list = new List(); try { string path = Path.Combine(MelonEnvironment.GameRootDirectory, Path.GetFileNameWithoutExtension(MelonEnvironment.GameExecutablePath) + "_Data", "Managed"); if (Directory.Exists(path)) { list.Add(new ResolverRoot(path, 0)); } string path2 = Path.Combine(MelonEnvironment.GameRootDirectory, "MelonLoader", "net35"); if (Directory.Exists(path2)) { list.Add(new ResolverRoot(path2, 5)); } string path3 = Path.Combine(MelonEnvironment.GameRootDirectory, "MelonLoader", "net6"); if (Directory.Exists(path3)) { list.Add(new ResolverRoot(path3, 6)); } } catch (Exception) { return list; } return list; } } } namespace MLVScan.MelonLoader { public class MelonLoaderPlugin : MelonPlugin { private MelonLoaderServiceFactory _serviceFactory; private MelonConfigManager _configManager; private MelonPlatformEnvironment _environment; private MelonPluginScanner _pluginScanner; private MelonPluginDisabler _pluginDisabler; private IlDumpService _ilDumpService; private DeveloperReportGenerator _developerReportGenerator; private ReportUploadService _reportUploadService; private bool _initialized = false; private bool _showUploadConsentPopup; private string _pendingUploadPath = string.Empty; private string _pendingUploadModName = string.Empty; private string _pendingUploadVerdictKind = string.Empty; private bool _pendingUploadWasBlocked = true; private List _pendingUploadFindings; public override void OnEarlyInitializeMelon() { try { ((MelonBase)this).LoggerInstance.Msg("Pre-scanning for malicious mods..."); _serviceFactory = new MelonLoaderServiceFactory(((MelonBase)this).LoggerInstance); _configManager = _serviceFactory.CreateConfigManager(); _environment = _serviceFactory.CreateEnvironment(); _pluginScanner = _serviceFactory.CreatePluginScanner(); _pluginDisabler = _serviceFactory.CreatePluginDisabler(); _ilDumpService = _serviceFactory.CreateIlDumpService(); _developerReportGenerator = _serviceFactory.CreateDeveloperReportGenerator(); _reportUploadService = _serviceFactory.CreateReportUploadService(); _initialized = true; ScanAndDisableMods(force: true); } catch (Exception ex) { ((MelonBase)this).LoggerInstance.Error("Error in pre-mod scan: " + ex.Message); ((MelonBase)this).LoggerInstance.Error(ex.StackTrace); } } public override void OnInitializeMelon() { try { ((MelonBase)this).LoggerInstance.Msg("MLVScan initialization complete"); if (_configManager.Config.WhitelistedHashes.Length != 0) { ((MelonBase)this).LoggerInstance.Msg($"{_configManager.Config.WhitelistedHashes.Length} mod(s) are whitelisted and won't be scanned"); ((MelonBase)this).LoggerInstance.Msg("To manage whitelisted mods, edit MelonPreferences.cfg"); } } catch (Exception ex) { ((MelonBase)this).LoggerInstance.Error("Error initializing MLVScan: " + ex.Message); ((MelonBase)this).LoggerInstance.Error(ex.StackTrace); } } public override void OnGUI() { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) if (_showUploadConsentPopup) { float num = Math.Min(620f, (float)Screen.width - 40f); float num2 = 280f; float num3 = ((float)Screen.width - num) / 2f; float num4 = ((float)Screen.height - num2) / 2f; GUI.Box(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), string.Empty); GUI.Box(new Rect(num3, num4, num, num2), "MLVScan Upload Consent"); GUI.Label(new Rect(num3 + 20f, num4 + 40f, num - 40f, 140f), ConsentMessageHelper.GetUploadConsentMessage(_pendingUploadModName, _pendingUploadVerdictKind, _pendingUploadWasBlocked) + "\n\nWould you like to upload this file to the MLVScan API for human review?\n\nYes: upload this mod now and enable automatic uploads for future detections.\nNo: do not upload and do not show this prompt again."); if (GUI.Button(new Rect(num3 + 20f, num4 + num2 - 60f, (num - 60f) / 2f, 36f), "Yes, upload")) { HandleUploadConsentDecision(approved: true); } if (GUI.Button(new Rect(num3 + 40f + (num - 60f) / 2f, num4 + num2 - 60f, (num - 60f) / 2f, 36f), "No thanks")) { HandleUploadConsentDecision(approved: false); } } } public Dictionary ScanAndDisableMods(bool force = false) { try { if (!_initialized) { ((MelonBase)this).LoggerInstance.Error("Cannot scan mods - MLVScan not properly initialized"); return new Dictionary(); } ((MelonBase)this).LoggerInstance.Msg("Scanning mods for threats..."); Dictionary source = _pluginScanner.ScanAllPlugins(force); Dictionary dictionary = source.Where((KeyValuePair kv) => kv.Value != null && ScanResultFacts.RequiresAttention(kv.Value)).ToDictionary((KeyValuePair kv) => kv.Key, (KeyValuePair kv) => kv.Value); if (dictionary.Count > 0) { List list = _pluginDisabler.DisableSuspiciousPlugins(dictionary, force); int count = list.Count; int num = dictionary.Count - count; if (count > 0) { ((MelonBase)this).LoggerInstance.Msg($"Disabled {count} mod(s) that matched the active blocking policy"); } if (num > 0) { ((MelonBase)this).LoggerInstance.Warning($"{num} mod(s) require manual review but were not blocked by the current configuration"); } GenerateDetailedReports(list, dictionary); ((MelonBase)this).LoggerInstance.Msg("To whitelist any false positives, add their SHA256 hash to the MLVScan → WhitelistedHashes setting in MelonPreferences.cfg"); } else { ((MelonBase)this).LoggerInstance.Msg("No mods requiring action were found"); } return dictionary; } catch (Exception ex) { ((MelonBase)this).LoggerInstance.Error("Error scanning mods: " + ex.Message); return new Dictionary(); } } private void GenerateDetailedReports(List disabledMods, Dictionary scanResults) { bool valueOrDefault = _configManager?.Config?.Scan?.DeveloperMode == true; Dictionary dictionary = (disabledMods ?? new List()).ToDictionary((DisabledPluginInfo info) => info.OriginalPath, StringComparer.OrdinalIgnoreCase); if (valueOrDefault) { ((MelonBase)this).LoggerInstance.Msg("Developer Mode: Enabled"); } ((MelonBase)this).LoggerInstance.Warning("======= DETAILED SCAN REPORT ======="); ((MelonBase)this).LoggerInstance.Msg(PlatformConstants.GetFullVersionInfo()); foreach (KeyValuePair item in scanResults.OrderBy, string>((KeyValuePair kv) => Path.GetFileName(kv.Key), StringComparer.OrdinalIgnoreCase)) { item.Deconstruct(out var key, out var value); string text = key; ScannedPluginResult scannedPluginResult = value; dictionary.TryGetValue(text, out var value2); bool flag = value2 != null; string fileName = Path.GetFileName(text); string text2 = value2?.FileHash ?? scannedPluginResult?.FileHash ?? string.Empty; string text3 = value2?.OriginalPath ?? scannedPluginResult?.FilePath ?? string.Empty; string text4 = ((flag && File.Exists(value2.DisabledPath)) ? value2.DisabledPath : (scannedPluginResult?.FilePath ?? text)); List list = scannedPluginResult?.Findings ?? new List(); ThreatVerdictInfo threatVerdictInfo = value2?.ThreatVerdict ?? scannedPluginResult?.ThreatVerdict ?? new ThreatVerdictInfo(); ScanStatusInfo scanStatusInfo = value2?.ScanStatus ?? scannedPluginResult?.ScanStatus ?? new ScanStatusInfo(); string outcomeLabel = ThreatVerdictTextFormatter.GetOutcomeLabel(scannedPluginResult); string outcomeSummary = ThreatVerdictTextFormatter.GetOutcomeSummary(scannedPluginResult); Dictionary> dictionary2 = (from f in list group f by f.Description).ToDictionary((IGrouping g) => g.Key, (IGrouping g) => g.ToList()); ((MelonBase)this).LoggerInstance.Warning((flag ? "BLOCKED MOD" : "REVIEW REQUIRED") + ": " + fileName); ((MelonBase)this).LoggerInstance.Msg("SHA256 Hash: " + text2); ((MelonBase)this).LoggerInstance.Msg("-------------------------------"); if (list.Count == 0 && threatVerdictInfo.Kind == ThreatVerdictKind.None && scanStatusInfo.Kind == ScanStatusKind.Complete) { ((MelonBase)this).LoggerInstance.Msg("No specific findings were retained."); continue; } if (threatVerdictInfo.Kind != ThreatVerdictKind.None) { QueueConsentPromptIfNeeded(text4, fileName, list, threatVerdictInfo, flag); } ((MelonBase)this).LoggerInstance.Warning($"Total retained findings: {list.Count}"); if (!string.IsNullOrWhiteSpace(outcomeLabel)) { ((MelonBase)this).LoggerInstance.Warning("Outcome: " + outcomeLabel); } if (!string.IsNullOrWhiteSpace(outcomeSummary)) { ((MelonBase)this).LoggerInstance.Msg(outcomeSummary); } if (scanStatusInfo.Kind != ScanStatusKind.Complete) { ((MelonBase)this).LoggerInstance.Msg(flag ? "Action: blocked by current incomplete-scan policy." : "Action: manual review required; not blocked by current config."); } string primaryFamilyLabel = ThreatVerdictTextFormatter.GetPrimaryFamilyLabel(threatVerdictInfo); if (!string.IsNullOrWhiteSpace(primaryFamilyLabel)) { ((MelonBase)this).LoggerInstance.Msg("Family: " + primaryFamilyLabel); } string confidenceLabel = ThreatVerdictTextFormatter.GetConfidenceLabel(threatVerdictInfo); if (!string.IsNullOrWhiteSpace(confidenceLabel)) { ((MelonBase)this).LoggerInstance.Msg("Confidence: " + confidenceLabel); } Dictionary dictionary3 = (from f in list group f by f.Severity into g orderby (int)g.Key descending select g).ToDictionary((IGrouping g) => g.Key, (IGrouping g) => g.Count()); ((MelonBase)this).LoggerInstance.Warning("Severity breakdown:"); foreach (KeyValuePair item2 in dictionary3) { string arg = FormatSeverityLabel(item2.Key); ((MelonBase)this).LoggerInstance.Msg($" {arg}: {item2.Value} issue(s)"); } ((MelonBase)this).LoggerInstance.Msg("-------------------------------"); List topFindingSummaries = ThreatVerdictTextFormatter.GetTopFindingSummaries(list); if (topFindingSummaries.Count > 0) { ((MelonBase)this).LoggerInstance.Warning("Top signals:"); foreach (string item3 in topFindingSummaries) { ((MelonBase)this).LoggerInstance.Msg(" - " + item3); } } if (valueOrDefault) { ((MelonBase)this).LoggerInstance.Msg("Developer mode is enabled. Full remediation guidance is included in the report file."); } ((MelonBase)this).LoggerInstance.Msg("Full technical details were written to the saved report file for human review."); ((MelonBase)this).LoggerInstance.Msg("-------------------------------"); DisplaySecurityNotice(fileName, threatVerdictInfo, scanStatusInfo, flag); try { string text5 = _environment?.ReportsDirectory ?? Path.Combine(MelonEnvironment.UserDataDirectory, "MLVScan", "Reports"); if (!Directory.Exists(text5)) { Directory.CreateDirectory(text5); } string text6 = DateTime.Now.ToString("yyyyMMdd_HHmmss"); string text7 = Path.Combine(text5, fileName + "_" + text6 + ".report.txt"); string text8 = Path.Combine(text5, "Prompts"); if (!Directory.Exists(text8)) { Directory.CreateDirectory(text8); } MelonConfigManager configManager = _configManager; if (configManager != null && configManager.Config?.DumpFullIlReports == true && _ilDumpService != null && scanStatusInfo.Kind == ScanStatusKind.Complete) { string path = Path.Combine(text5, "IL"); string text9 = Path.Combine(path, fileName + "_" + text6 + ".il.txt"); if (_ilDumpService.TryDumpAssembly(text4, text9)) { ((MelonBase)this).LoggerInstance.Msg("Full IL dump saved to: " + text9); } else { ((MelonBase)this).LoggerInstance.Warning("Failed to dump IL for this mod (see logs for details)."); } } else { MelonConfigManager configManager2 = _configManager; if (configManager2 != null && configManager2.Config?.DumpFullIlReports == true && scanStatusInfo.Kind == ScanStatusKind.RequiresReview) { ((MelonBase)this).LoggerInstance.Warning("Skipped full IL dump because this file was not fully analyzed by the loader."); } } PromptGeneratorService promptGeneratorService = ((scanStatusInfo.Kind == ScanStatusKind.Complete) ? _serviceFactory.CreatePromptGenerator() : null); using (StreamWriter streamWriter = new StreamWriter(text7)) { if (valueOrDefault && _developerReportGenerator != null) { string value3 = _developerReportGenerator.GenerateFileReport(fileName, text2, list, threatVerdictInfo, scanStatusInfo); streamWriter.Write(value3); } else { streamWriter.WriteLine("MLVScan Security Report"); streamWriter.WriteLine(PlatformConstants.GetFullVersionInfo()); streamWriter.WriteLine($"Generated: {DateTime.Now}"); streamWriter.WriteLine("Mod File: " + fileName); streamWriter.WriteLine("Outcome: " + outcomeLabel); if (!string.IsNullOrWhiteSpace(outcomeSummary)) { streamWriter.WriteLine("Outcome Summary: " + outcomeSummary); } streamWriter.WriteLine("Action Taken: " + (flag ? "Blocked" : "Manual review required (not blocked by current config)")); streamWriter.WriteLine("SHA256 Hash: " + text2); streamWriter.WriteLine("Original Path: " + text3); streamWriter.WriteLine((flag ? "Disabled Path" : "Current Path") + ": " + text4); streamWriter.WriteLine("Path Used For Analysis: " + text4); streamWriter.WriteLine($"Total Retained Findings: {list.Count}"); streamWriter.WriteLine(); ThreatVerdictTextFormatter.WriteThreatVerdictSection(streamWriter, threatVerdictInfo); ThreatVerdictTextFormatter.WriteScanStatusSection(streamWriter, scanStatusInfo); streamWriter.WriteLine("\nSeverity Breakdown:"); foreach (KeyValuePair item4 in dictionary3) { streamWriter.WriteLine($"- {item4.Key}: {item4.Value} issue(s)"); } streamWriter.WriteLine("=============================================="); foreach (KeyValuePair> item5 in dictionary2) { streamWriter.WriteLine("\n== " + item5.Key + " =="); streamWriter.WriteLine($"Severity: {item5.Value[0].Severity}"); streamWriter.WriteLine($"Instances: {item5.Value.Count}"); streamWriter.WriteLine("\nLocations & Analysis:"); foreach (ScanFinding item6 in item5.Value) { streamWriter.WriteLine("- " + item6.Location); if (item6.HasCallChain && item6.CallChain != null) { streamWriter.WriteLine(" Call Chain Analysis:"); streamWriter.WriteLine(" " + item6.CallChain.Summary); streamWriter.WriteLine(" Attack Path:"); foreach (CallChainNode node in item6.CallChain.Nodes) { CallChainNodeType nodeType = node.NodeType; if (1 == 0) { } key = nodeType switch { CallChainNodeType.EntryPoint => "[ENTRY]", CallChainNodeType.IntermediateCall => "[CALL]", CallChainNodeType.SuspiciousDeclaration => "[DECL]", _ => "[???]", }; if (1 == 0) { } string text10 = key; streamWriter.WriteLine(" " + text10 + " " + node.Location); if (!string.IsNullOrEmpty(node.Description)) { streamWriter.WriteLine(" " + node.Description); } } } if (item6.HasDataFlow && item6.DataFlowChain != null) { streamWriter.WriteLine(" Data Flow Analysis:"); streamWriter.WriteLine($" Pattern: {item6.DataFlowChain.Pattern}"); streamWriter.WriteLine(" " + item6.DataFlowChain.Summary); if (item6.DataFlowChain.IsCrossMethod) { streamWriter.WriteLine($" Cross-method flow through {item6.DataFlowChain.InvolvedMethods.Count} methods"); } streamWriter.WriteLine(" Data Flow Chain:"); foreach (DataFlowNode node2 in item6.DataFlowChain.Nodes) { DataFlowNodeType nodeType2 = node2.NodeType; if (1 == 0) { } key = nodeType2 switch { DataFlowNodeType.Source => "[SOURCE]", DataFlowNodeType.Transform => "[TRANSFORM]", DataFlowNodeType.Sink => "[SINK]", DataFlowNodeType.Intermediate => "[PASS]", _ => "[???]", }; if (1 == 0) { } string text11 = key; streamWriter.WriteLine(" " + text11 + " " + node2.Operation + " (" + node2.DataDescription + ") @ " + node2.Location); } } if (!string.IsNullOrEmpty(item6.CodeSnippet)) { streamWriter.WriteLine(" Code Snippet (IL):"); string[] array = item6.CodeSnippet.Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text12 in array) { streamWriter.WriteLine(" " + text12); } streamWriter.WriteLine(); } } } WriteSecurityNoticeToReport(streamWriter, threatVerdictInfo, scanStatusInfo, flag); } } bool flag2 = false; if (promptGeneratorService != null) { flag2 = promptGeneratorService.SavePromptToFile(text4, list, text8); } else if (scanStatusInfo.Kind == ScanStatusKind.RequiresReview) { ((MelonBase)this).LoggerInstance.Warning("Skipped LLM prompt generation because this file was not fully analyzed by the loader."); } if (flag2) { ((MelonBase)this).LoggerInstance.Msg("Detailed report saved to: " + text7); ((MelonBase)this).LoggerInstance.Msg("LLM analysis prompt saved to: " + Path.Combine(text8, fileName + ".prompt.md")); ((MelonBase)this).LoggerInstance.Msg("You can copy the contents of the prompt file into ChatGPT to help determine if this is malware or a false positive, although don't trust ChatGPT to be 100% accurate."); } else { ((MelonBase)this).LoggerInstance.Msg("Detailed report saved to: " + text7); } MelonConfigManager configManager3 = _configManager; if (configManager3 == null || configManager3.Config?.EnableReportUpload != true || _reportUploadService == null || threatVerdictInfo.Kind == ThreatVerdictKind.None) { continue; } try { string reportUploadApiBaseUrl = _configManager.GetReportUploadApiBaseUrl(); if (!string.IsNullOrWhiteSpace(reportUploadApiBaseUrl) && File.Exists(text4)) { byte[] assemblyBytes = File.ReadAllBytes(text4); SubmissionMetadata metadata = BuildSubmissionMetadata(fileName, list); _reportUploadService.UploadReportNonBlocking(assemblyBytes, fileName, metadata, reportUploadApiBaseUrl); } } catch (Exception ex) { ((MelonBase)this).LoggerInstance.Warning("Report upload skipped for " + fileName + ": " + ex.Message); } } catch (Exception ex2) { ((MelonBase)this).LoggerInstance.Error("Failed to save detailed report: " + ex2.Message); } } ((MelonBase)this).LoggerInstance.Warning("====== END OF SCAN REPORT ======"); } private void QueueConsentPromptIfNeeded(string accessiblePath, string modName, List findings, ThreatVerdictInfo threatVerdict, bool wasBlocked) { if (_configManager != null && !_showUploadConsentPopup) { MLVScanConfig config = _configManager.Config; if (!config.ReportUploadConsentAsked) { _showUploadConsentPopup = true; _pendingUploadPath = accessiblePath; _pendingUploadModName = modName; _pendingUploadVerdictKind = threatVerdict?.Kind.ToString() ?? string.Empty; _pendingUploadWasBlocked = wasBlocked; _pendingUploadFindings = findings; config.ReportUploadConsentPending = true; config.PendingReportUploadPath = accessiblePath ?? string.Empty; config.PendingReportUploadVerdictKind = _pendingUploadVerdictKind; _configManager.SaveConfig(config); ((MelonBase)this).LoggerInstance.Warning("MLVScan is waiting for your upload consent decision in the in-game popup."); } } } private void HandleUploadConsentDecision(bool approved) { _showUploadConsentPopup = false; if (_configManager == null) { return; } MLVScanConfig config = _configManager.Config; config.ReportUploadConsentAsked = true; config.ReportUploadConsentPending = false; config.PendingReportUploadPath = string.Empty; config.PendingReportUploadVerdictKind = string.Empty; config.EnableReportUpload = approved; _configManager.SaveConfig(config); if (!approved) { ((MelonBase)this).LoggerInstance.Msg("MLVScan report upload declined. You will not be prompted again."); _pendingUploadPath = string.Empty; _pendingUploadModName = string.Empty; _pendingUploadVerdictKind = string.Empty; _pendingUploadWasBlocked = true; _pendingUploadFindings = null; return; } ((MelonBase)this).LoggerInstance.Msg("MLVScan report upload enabled. Uploading the flagged mod now."); try { if (_reportUploadService != null && !string.IsNullOrWhiteSpace(_pendingUploadPath) && File.Exists(_pendingUploadPath)) { string reportUploadApiBaseUrl = _configManager.GetReportUploadApiBaseUrl(); if (!string.IsNullOrWhiteSpace(reportUploadApiBaseUrl)) { byte[] assemblyBytes = File.ReadAllBytes(_pendingUploadPath); SubmissionMetadata metadata = BuildSubmissionMetadata(_pendingUploadModName, _pendingUploadFindings ?? new List()); _reportUploadService.UploadReportNonBlocking(assemblyBytes, _pendingUploadModName, metadata, reportUploadApiBaseUrl); } } } catch (Exception ex) { ((MelonBase)this).LoggerInstance.Warning("Report upload skipped for " + _pendingUploadModName + ": " + ex.Message); } finally { _pendingUploadPath = string.Empty; _pendingUploadModName = string.Empty; _pendingUploadVerdictKind = string.Empty; _pendingUploadWasBlocked = true; _pendingUploadFindings = null; } } private static SubmissionMetadata BuildSubmissionMetadata(string modName, List findings) { List findingSummary = (from f in findings.Take(20) select new FindingSummaryItem { RuleId = f.RuleId, Description = f.Description, Severity = f.Severity.ToString(), Location = RedactionHelper.RedactLocation(f.Location) }).ToList(); return new SubmissionMetadata { LoaderType = "MelonLoader", LoaderVersion = null, PluginVersion = "2.1.1", ModName = RedactionHelper.RedactFilename(modName), FindingSummary = findingSummary, ConsentVersion = "1", ConsentTimestamp = DateTime.UtcNow.ToString("o") }; } private static string FormatSeverityLabel(Severity severity) { if (1 == 0) { } string result = severity switch { Severity.Critical => "CRITICAL", Severity.High => "HIGH", Severity.Medium => "MEDIUM", Severity.Low => "LOW", _ => severity.ToString().ToUpper(), }; if (1 == 0) { } return result; } private void DisplaySecurityNotice(string modName, ThreatVerdictInfo threatVerdict, ScanStatusInfo scanStatus, bool wasBlocked) { ((MelonBase)this).LoggerInstance.Warning("IMPORTANT SECURITY NOTICE"); ((MelonBase)this).LoggerInstance.Msg(wasBlocked ? ("MLVScan detected and disabled " + modName + " before it was loaded.") : ("MLVScan flagged " + modName + " for review but did not block it under the current configuration.")); if (IsKnownThreatVerdict(threatVerdict)) { ((MelonBase)this).LoggerInstance.Msg("This mod is likely malware because it matched previously analyzed malware intelligence."); ((MelonBase)this).LoggerInstance.Msg("If this is your first time running the game with this mod, your PC is likely safe."); ((MelonBase)this).LoggerInstance.Msg("However, if you've previously run the game with this mod, your system MAY be infected."); ((MelonBase)this).LoggerInstance.Warning("Recommended security steps:"); ((MelonBase)this).LoggerInstance.Msg("1. Check with the modding community first - no detection is perfect"); ((MelonBase)this).LoggerInstance.Msg(" Join the modding Discord at: https://discord.gg/UD4K4chKak"); ((MelonBase)this).LoggerInstance.Msg(" Ask about this mod in the MLVScan thread of #mod-releases to confirm if it's actually malicious"); ((MelonBase)this).LoggerInstance.Msg("2. Run a full system scan with a trusted antivirus like Malwarebytes"); ((MelonBase)this).LoggerInstance.Msg(" Malwarebytes is recommended as a free and effective antivirus solution"); ((MelonBase)this).LoggerInstance.Msg("3. Use Microsoft Safety Scanner for a secondary scan"); ((MelonBase)this).LoggerInstance.Msg("4. Change important passwords if antivirus shows a threat"); ((MelonBase)this).LoggerInstance.Warning("Resources for malware removal:"); ((MelonBase)this).LoggerInstance.Msg("- Malwarebytes: https://www.malwarebytes.com/cybersecurity/basics/how-to-remove-virus-from-computer"); ((MelonBase)this).LoggerInstance.Msg("- Microsoft Safety Scanner: https://learn.microsoft.com/en-us/defender-endpoint/safety-scanner-download"); } else if (threatVerdict != null && threatVerdict.Kind == ThreatVerdictKind.Suspicious) { ((MelonBase)this).LoggerInstance.Msg("This mod was flagged because it triggered suspicious correlated behavior."); ((MelonBase)this).LoggerInstance.Msg("It may still be a false positive, so review the saved report before assuming infection."); ((MelonBase)this).LoggerInstance.Warning("Recommended review steps:"); ((MelonBase)this).LoggerInstance.Msg("1. Check with the modding community first - no detection is perfect"); ((MelonBase)this).LoggerInstance.Msg(" Join the modding Discord at: https://discord.gg/UD4K4chKak"); ((MelonBase)this).LoggerInstance.Msg(" Ask about this mod in the MLVScan thread of #mod-releases to confirm if it is actually malicious"); ((MelonBase)this).LoggerInstance.Msg("2. Review the saved report for the exact behavior that triggered the block"); ((MelonBase)this).LoggerInstance.Msg("3. Only run a full antivirus scan if you have already executed this mod or still do not trust it"); ((MelonBase)this).LoggerInstance.Msg("4. Whitelist the SHA256 only if you have independently verified the mod is safe"); } else if (scanStatus != null && scanStatus.Kind == ScanStatusKind.RequiresReview) { ((MelonBase)this).LoggerInstance.Msg("This mod could not be fully analyzed by the loader because it exceeded the current in-memory scan limit."); ((MelonBase)this).LoggerInstance.Msg("MLVScan still calculated its SHA-256 hash and checked exact known-malicious sample matches."); ((MelonBase)this).LoggerInstance.Warning("Recommended review steps:"); ((MelonBase)this).LoggerInstance.Msg("1. Review the saved report before assuming the mod is safe"); ((MelonBase)this).LoggerInstance.Msg("2. Validate the mod with trusted community sources or the original author"); ((MelonBase)this).LoggerInstance.Msg("3. Enable BlockIncompleteScans if you want oversized or incomplete scans blocked automatically"); ((MelonBase)this).LoggerInstance.Msg("4. Whitelist the SHA256 only after independent verification"); } else { ((MelonBase)this).LoggerInstance.Msg("Review the saved report before deciding whether to whitelist this mod."); } } private static void WriteSecurityNoticeToReport(StreamWriter writer, ThreatVerdictInfo threatVerdict, ScanStatusInfo scanStatus, bool wasBlocked) { writer.WriteLine("\n\n============== SECURITY NOTICE ==============\n"); writer.WriteLine("IMPORTANT: READ THIS SECURITY INFORMATION\n"); writer.WriteLine(wasBlocked ? "MLVScan detected and disabled this mod before it was loaded." : "MLVScan flagged this mod for review but did not block it under the current configuration."); if (IsKnownThreatVerdict(threatVerdict)) { writer.WriteLine("This mod is likely malware because it matched previously analyzed malware intelligence.\n"); writer.WriteLine("YOUR SYSTEM SECURITY STATUS:"); writer.WriteLine("- If this is your FIRST TIME starting the game with this mod installed:"); writer.WriteLine(" Your PC is likely SAFE as MLVScan prevented the mod from loading."); writer.WriteLine("\n- If you have PREVIOUSLY PLAYED the game with this mod loaded:"); writer.WriteLine(" Your system MAY BE INFECTED with malware. Take action immediately.\n"); writer.WriteLine("RECOMMENDED SECURITY STEPS:"); writer.WriteLine("1. Check with the modding community first - no detection system is perfect"); writer.WriteLine(" Join the modding Discord at: https://discord.gg/UD4K4chKak"); writer.WriteLine(" Ask about this mod in the #MLVScan or #report-mods channels to confirm if it is actually malicious"); writer.WriteLine("\n2. Run a full system scan with a reputable antivirus program"); writer.WriteLine(" Free option: Malwarebytes (https://www.malwarebytes.com/)"); writer.WriteLine(" Malwarebytes is recommended as a free and effective antivirus solution"); writer.WriteLine("\n3. Run Microsoft Safety Scanner as a secondary check"); writer.WriteLine(" Download: https://learn.microsoft.com/en-us/defender-endpoint/safety-scanner-download"); writer.WriteLine("\n4. Update all your software from official sources"); writer.WriteLine("\n5. Change passwords for important accounts (from a clean device if possible)"); writer.WriteLine("\n6. Monitor your accounts for any suspicious activity"); writer.WriteLine("\nDETAILED MALWARE REMOVAL GUIDES:"); writer.WriteLine("- Malwarebytes Guide: https://www.malwarebytes.com/cybersecurity/basics/how-to-remove-virus-from-computer"); writer.WriteLine("- Microsoft Safety Scanner: https://learn.microsoft.com/en-us/defender-endpoint/safety-scanner-download"); writer.WriteLine("- XWorm (Common Modding Malware) Removal Guide: https://www.pcrisk.com/removal-guides/27436-xworm-rat"); } else if (threatVerdict != null && threatVerdict.Kind == ThreatVerdictKind.Suspicious) { writer.WriteLine("This mod was flagged because it triggered suspicious correlated behavior.\n"); writer.WriteLine("IMPORTANT:"); writer.WriteLine("- This may still be a false positive."); writer.WriteLine("- Review the report details and verify the mod with trusted community sources before assuming infection.\n"); writer.WriteLine("RECOMMENDED REVIEW STEPS:"); writer.WriteLine("1. Check with the modding community first - no detection system is perfect"); writer.WriteLine(" Join the modding Discord at: https://discord.gg/UD4K4chKak"); writer.WriteLine(" Ask about this mod in the #MLVScan or #report-mods channels to confirm if it is actually malicious"); writer.WriteLine("\n2. Review the detailed findings and call/data-flow evidence in this report"); writer.WriteLine("\n3. Only run a full antivirus scan if you already executed the mod or still do not trust it"); writer.WriteLine("\n4. Whitelist the SHA256 only after independent verification"); } else if (scanStatus != null && scanStatus.Kind == ScanStatusKind.RequiresReview) { writer.WriteLine("This mod could not be fully analyzed by the loader because it exceeded the current in-memory scan limit.\n"); writer.WriteLine("IMPORTANT:"); writer.WriteLine("- MLVScan still calculated the SHA-256 hash and checked exact known-malicious sample matches."); writer.WriteLine("- No retained malicious verdict was produced, but the file was not fully analyzed."); writer.WriteLine("- Review the report details and verify the mod with trusted community sources before assuming it is safe.\n"); writer.WriteLine("RECOMMENDED REVIEW STEPS:"); writer.WriteLine("1. Review the report details and confirm whether this mod is expected to be unusually large"); writer.WriteLine("\n2. Validate the mod with the original author or trusted community sources"); writer.WriteLine("\n3. Enable BlockIncompleteScans if you want oversized or incomplete scans blocked automatically"); writer.WriteLine("\n4. Whitelist the SHA256 only after independent verification"); } writer.WriteLine("\n============================================="); } private static bool IsKnownThreatVerdict(ThreatVerdictInfo threatVerdict) { return (threatVerdict != null && threatVerdict.Kind == ThreatVerdictKind.KnownMaliciousSample) || (threatVerdict != null && threatVerdict.Kind == ThreatVerdictKind.KnownMalwareFamily); } } public class MelonLoaderServiceFactory { private readonly Instance _melonLogger; private readonly IScanLogger _scanLogger; private readonly IAssemblyResolverProvider _resolverProvider; private readonly MelonConfigManager _configManager; private readonly MelonPlatformEnvironment _environment; private readonly MLVScanConfig _fallbackConfig; private readonly LoaderScanTelemetryHub _telemetry; public MelonLoaderServiceFactory(Instance logger) { _melonLogger = logger ?? throw new ArgumentNullException("logger"); _scanLogger = new MelonScanLogger(logger); _telemetry = new LoaderScanTelemetryHub(); _resolverProvider = new GameAssemblyResolverProvider(_telemetry); _environment = new MelonPlatformEnvironment(); _fallbackConfig = new MLVScanConfig(); try { _configManager = new MelonConfigManager(logger); } catch (Exception ex) { _melonLogger.Error("Failed to create ConfigManager: " + ex.Message); _melonLogger.Msg("Using default configuration values"); } } public MelonConfigManager CreateConfigManager() { if (_configManager == null) { throw new InvalidOperationException("Configuration manager unavailable: failed to initialize during factory construction."); } return _configManager; } public MelonPlatformEnvironment CreateEnvironment() { return _environment; } public AssemblyScanner CreateAssemblyScanner() { MLVScanConfig mLVScanConfig = _configManager?.Config ?? _fallbackConfig; IReadOnlyList rules = RuleFactory.CreateDefaultRules(); return new AssemblyScanner(rules, mLVScanConfig.Scan, _resolverProvider); } public MelonPluginScanner CreatePluginScanner() { MLVScanConfig config = _configManager?.Config ?? _fallbackConfig; return new MelonPluginScanner(_scanLogger, _resolverProvider, config, _configManager, _environment, _telemetry); } public MelonPluginDisabler CreatePluginDisabler() { MLVScanConfig config = _configManager?.Config ?? _fallbackConfig; return new MelonPluginDisabler(_scanLogger, config); } public PromptGeneratorService CreatePromptGenerator() { MLVScanConfig mLVScanConfig = _configManager?.Config ?? _fallbackConfig; return new PromptGeneratorService(mLVScanConfig.Scan, _scanLogger); } public IlDumpService CreateIlDumpService() { return new IlDumpService(_scanLogger, _environment); } public DeveloperReportGenerator CreateDeveloperReportGenerator() { return new DeveloperReportGenerator(_scanLogger); } public ReportUploadService CreateReportUploadService() { return new ReportUploadService(_configManager, delegate(string msg) { _melonLogger.Msg(msg); }, delegate(string msg) { _melonLogger.Warning(msg); }, delegate(string msg) { _melonLogger.Error(msg); }); } } public class MelonConfigManager : IConfigManager { private readonly Instance _logger; private readonly MelonPreferences_Category _category; private readonly MelonPreferences_Entry _enableAutoScan; private readonly MelonPreferences_Entry _enableAutoDisable; private readonly MelonPreferences_Entry _enableScanCache; private readonly MelonPreferences_Entry _blockKnownThreats; private readonly MelonPreferences_Entry _blockSuspicious; private readonly MelonPreferences_Entry _blockIncompleteScans; private readonly MelonPreferences_Entry _scanDirectories; private readonly MelonPreferences_Entry _whitelistedHashes; private readonly MelonPreferences_Entry _dumpFullIlReports; private readonly MelonPreferences_Entry _developerMode; private readonly MelonPreferences_Entry _enableReportUpload; private readonly MelonPreferences_Entry _reportUploadConsentAsked; private readonly MelonPreferences_Entry _reportUploadConsentPending; private readonly MelonPreferences_Entry _pendingReportUploadPath; private readonly MelonPreferences_Entry _pendingReportUploadVerdictKind; private readonly MelonPreferences_Entry _reportUploadApiBaseUrl; private readonly MelonPreferences_Entry _uploadedReportHashes; private readonly MelonPreferences_Entry _includeMods; private readonly MelonPreferences_Entry _includePlugins; private readonly MelonPreferences_Entry _includeUserLibs; private readonly MelonPreferences_Entry _includeThunderstoreProfiles; private readonly MelonPreferences_Entry _additionalTargetRoots; private readonly MelonPreferences_Entry _excludedTargetRoots; public MLVScanConfig Config { get; private set; } public MelonConfigManager(Instance logger) { _logger = logger ?? throw new ArgumentNullException("logger"); try { _category = MelonPreferences.CreateCategory("MLVScan"); _enableAutoScan = _category.CreateEntry("EnableAutoScan", true, (string)null, "Whether to scan mods at startup", false, false, (ValueValidator)null, (string)null); _enableAutoDisable = _category.CreateEntry("EnableAutoDisable", true, (string)null, "Whether to automatically disable mods that meet the active blocking policy", false, false, (ValueValidator)null, (string)null); _enableScanCache = _category.CreateEntry("EnableScanCache", true, (string)null, "Whether to reuse scan results for unchanged files using a local authenticated cache", false, false, (ValueValidator)null, (string)null); _blockKnownThreats = _category.CreateEntry("BlockKnownThreats", true, (string)null, "Whether to block mods that match a known threat family or exact malicious sample", false, false, (ValueValidator)null, (string)null); _blockSuspicious = _category.CreateEntry("BlockSuspicious", true, (string)null, "Whether to block suspicious unknown behavior that may still be a false positive", false, false, (ValueValidator)null, (string)null); _blockIncompleteScans = _category.CreateEntry("BlockIncompleteScans", false, (string)null, "Whether to block mods that could not be fully analyzed and require manual review", false, false, (ValueValidator)null, (string)null); _scanDirectories = _category.CreateEntry("ScanDirectories", new string[2] { "Mods", "Plugins" }, (string)null, "Directories to scan for mods", false, false, (ValueValidator)null, (string)null); _whitelistedHashes = _category.CreateEntry("WhitelistedHashes", Array.Empty(), (string)null, "List of mod SHA256 hashes to skip when scanning", false, false, (ValueValidator)null, (string)null); _dumpFullIlReports = _category.CreateEntry("DumpFullIlReports", false, (string)null, "When enabled, saves full IL dumps for scanned mods next to reports", false, false, (ValueValidator)null, (string)null); _developerMode = _category.CreateEntry("DeveloperMode", false, (string)null, "Developer mode: Shows remediation guidance to help mod developers fix false positives", false, false, (ValueValidator)null, (string)null); _enableReportUpload = _category.CreateEntry("EnableReportUpload", false, (string)null, "When enabled (and consent given), send reports to MLVScan API for false positive analysis", false, false, (ValueValidator)null, (string)null); _reportUploadConsentAsked = _category.CreateEntry("ReportUploadConsentAsked", false, (string)null, "Whether the first-run consent prompt has been shown (internal)", false, false, (ValueValidator)null, (string)null); _reportUploadConsentPending = _category.CreateEntry("ReportUploadConsentPending", false, (string)null, "Whether an upload consent popup is pending (internal)", false, false, (ValueValidator)null, (string)null); _pendingReportUploadPath = _category.CreateEntry("PendingReportUploadPath", string.Empty, (string)null, "Suspicious mod path waiting for upload consent (internal)", false, false, (ValueValidator)null, (string)null); _pendingReportUploadVerdictKind = _category.CreateEntry("PendingReportUploadVerdictKind", string.Empty, (string)null, "Threat verdict kind for the pending upload consent item (internal)", false, false, (ValueValidator)null, (string)null); _reportUploadApiBaseUrl = _category.CreateEntry("ReportUploadApiBaseUrl", "https://api.mlvscan.com", (string)null, "API base URL for report uploads", false, false, (ValueValidator)null, (string)null); _uploadedReportHashes = _category.CreateEntry("UploadedReportHashes", Array.Empty(), (string)null, "List of assembly SHA256 hashes already uploaded to the MLVScan API (internal)", false, false, (ValueValidator)null, (string)null); _includeMods = _category.CreateEntry("IncludeMods", true, (string)null, "Whether to include Mods folders in the target scan scope", false, false, (ValueValidator)null, (string)null); _includePlugins = _category.CreateEntry("IncludePlugins", true, (string)null, "Whether to include Plugins folders in the target scan scope", false, false, (ValueValidator)null, (string)null); _includeUserLibs = _category.CreateEntry("IncludeUserLibs", true, (string)null, "Whether to include UserLibs folders in the target scan scope", false, false, (ValueValidator)null, (string)null); _includeThunderstoreProfiles = _category.CreateEntry("IncludeThunderstoreProfiles", true, (string)null, "Whether to include Thunderstore profile folders in the target scan scope", false, false, (ValueValidator)null, (string)null); _additionalTargetRoots = _category.CreateEntry("AdditionalTargetRoots", Array.Empty(), (string)null, "Additional absolute paths to include in the target scan scope", false, false, (ValueValidator)null, (string)null); _excludedTargetRoots = _category.CreateEntry("ExcludedTargetRoots", Array.Empty(), (string)null, "Absolute paths to exclude from the target scan scope", false, false, (ValueValidator)null, (string)null); ((MelonEventBase>)(object)_enableAutoScan.OnEntryValueChanged).Subscribe((LemonAction)OnConfigChanged, 0, false); ((MelonEventBase>)(object)_enableAutoDisable.OnEntryValueChanged).Subscribe((LemonAction)OnConfigChanged, 0, false); ((MelonEventBase>)(object)_enableScanCache.OnEntryValueChanged).Subscribe((LemonAction)OnConfigChanged, 0, false); ((MelonEventBase>)(object)_blockKnownThreats.OnEntryValueChanged).Subscribe((LemonAction)OnConfigChanged, 0, false); ((MelonEventBase>)(object)_blockSuspicious.OnEntryValueChanged).Subscribe((LemonAction)OnConfigChanged, 0, false); ((MelonEventBase>)(object)_blockIncompleteScans.OnEntryValueChanged).Subscribe((LemonAction)OnConfigChanged, 0, false); ((MelonEventBase>)(object)_scanDirectories.OnEntryValueChanged).Subscribe((LemonAction)OnConfigChanged, 0, false); ((MelonEventBase>)(object)_whitelistedHashes.OnEntryValueChanged).Subscribe((LemonAction)OnConfigChanged, 0, false); ((MelonEventBase>)(object)_dumpFullIlReports.OnEntryValueChanged).Subscribe((LemonAction)OnConfigChanged, 0, false); ((MelonEventBase>)(object)_developerMode.OnEntryValueChanged).Subscribe((LemonAction)OnConfigChanged, 0, false); ((MelonEventBase>)(object)_enableReportUpload.OnEntryValueChanged).Subscribe((LemonAction)OnConfigChanged, 0, false); ((MelonEventBase>)(object)_reportUploadConsentAsked.OnEntryValueChanged).Subscribe((LemonAction)OnConfigChanged, 0, false); ((MelonEventBase>)(object)_reportUploadConsentPending.OnEntryValueChanged).Subscribe((LemonAction)OnConfigChanged, 0, false); ((MelonEventBase>)(object)_pendingReportUploadPath.OnEntryValueChanged).Subscribe((LemonAction)OnConfigChanged, 0, false); ((MelonEventBase>)(object)_pendingReportUploadVerdictKind.OnEntryValueChanged).Subscribe((LemonAction)OnConfigChanged, 0, false); ((MelonEventBase>)(object)_reportUploadApiBaseUrl.OnEntryValueChanged).Subscribe((LemonAction)OnConfigChanged, 0, false); ((MelonEventBase>)(object)_uploadedReportHashes.OnEntryValueChanged).Subscribe((LemonAction)OnConfigChanged, 0, false); ((MelonEventBase>)(object)_includeMods.OnEntryValueChanged).Subscribe((LemonAction)OnConfigChanged, 0, false); ((MelonEventBase>)(object)_includePlugins.OnEntryValueChanged).Subscribe((LemonAction)OnConfigChanged, 0, false); ((MelonEventBase>)(object)_includeUserLibs.OnEntryValueChanged).Subscribe((LemonAction)OnConfigChanged, 0, false); ((MelonEventBase>)(object)_includeThunderstoreProfiles.OnEntryValueChanged).Subscribe((LemonAction)OnConfigChanged, 0, false); ((MelonEventBase>)(object)_additionalTargetRoots.OnEntryValueChanged).Subscribe((LemonAction)OnConfigChanged, 0, false); ((MelonEventBase>)(object)_excludedTargetRoots.OnEntryValueChanged).Subscribe((LemonAction)OnConfigChanged, 0, false); UpdateConfigFromPreferences(); CleanupLegacyPreferenceEntries(); _logger.Msg("Configuration loaded successfully"); } catch (Exception ex) { _logger.Error("Failed to initialize config system: " + ex.Message); _logger.Msg("Using fallback in-memory configuration"); Config = new MLVScanConfig(); } } public MLVScanConfig LoadConfig() { UpdateConfigFromPreferences(); return Config; } private void OnConfigChanged(T oldValue, T newValue) { UpdateConfigFromPreferences(); _logger.Msg("Configuration updated"); } private void UpdateConfigFromPreferences() { Config = new MLVScanConfig { EnableAutoScan = _enableAutoScan.Value, EnableAutoDisable = _enableAutoDisable.Value, EnableScanCache = _enableScanCache.Value, BlockKnownThreats = _blockKnownThreats.Value, BlockSuspicious = _blockSuspicious.Value, BlockIncompleteScans = _blockIncompleteScans.Value, ScanDirectories = _scanDirectories.Value, WhitelistedHashes = _whitelistedHashes.Value, DumpFullIlReports = _dumpFullIlReports.Value, Scan = new ScanConfig { DeveloperMode = _developerMode.Value }, EnableReportUpload = _enableReportUpload.Value, ReportUploadConsentAsked = _reportUploadConsentAsked.Value, ReportUploadConsentPending = _reportUploadConsentPending.Value, PendingReportUploadPath = _pendingReportUploadPath.Value, PendingReportUploadVerdictKind = _pendingReportUploadVerdictKind.Value, ReportUploadApiBaseUrl = _reportUploadApiBaseUrl.Value, UploadedReportHashes = NormalizeHashes(_uploadedReportHashes.Value), IncludeMods = _includeMods.Value, IncludePlugins = _includePlugins.Value, IncludeUserLibs = _includeUserLibs.Value, IncludePatchers = false, IncludeThunderstoreProfiles = _includeThunderstoreProfiles.Value, AdditionalTargetRoots = (_additionalTargetRoots.Value ?? Array.Empty()), ExcludedTargetRoots = (_excludedTargetRoots.Value ?? Array.Empty()) }; } public void SaveConfig(MLVScanConfig newConfig) { try { _enableAutoScan.Value = newConfig.EnableAutoScan; _enableAutoDisable.Value = newConfig.EnableAutoDisable; _enableScanCache.Value = newConfig.EnableScanCache; _blockKnownThreats.Value = newConfig.BlockKnownThreats; _blockSuspicious.Value = newConfig.BlockSuspicious; _blockIncompleteScans.Value = newConfig.BlockIncompleteScans; _scanDirectories.Value = newConfig.ScanDirectories; _whitelistedHashes.Value = newConfig.WhitelistedHashes; _dumpFullIlReports.Value = newConfig.DumpFullIlReports; _developerMode.Value = newConfig.Scan?.DeveloperMode ?? false; _enableReportUpload.Value = newConfig.EnableReportUpload; _reportUploadConsentAsked.Value = newConfig.ReportUploadConsentAsked; _reportUploadConsentPending.Value = newConfig.ReportUploadConsentPending; _pendingReportUploadPath.Value = newConfig.PendingReportUploadPath ?? string.Empty; _pendingReportUploadVerdictKind.Value = newConfig.PendingReportUploadVerdictKind ?? string.Empty; _reportUploadApiBaseUrl.Value = newConfig.ReportUploadApiBaseUrl; _uploadedReportHashes.Value = NormalizeHashes(newConfig.UploadedReportHashes); _includeMods.Value = newConfig.IncludeMods; _includePlugins.Value = newConfig.IncludePlugins; _includeUserLibs.Value = newConfig.IncludeUserLibs; _includeThunderstoreProfiles.Value = newConfig.IncludeThunderstoreProfiles; _additionalTargetRoots.Value = newConfig.AdditionalTargetRoots ?? Array.Empty(); _excludedTargetRoots.Value = newConfig.ExcludedTargetRoots ?? Array.Empty(); PersistPreferences(); _logger.Msg("Configuration saved successfully"); } catch (Exception ex) { _logger.Error("Error saving configuration: " + ex.Message); Config = newConfig; } } public string[] GetWhitelistedHashes() { return _whitelistedHashes.Value; } public void SetWhitelistedHashes(string[] hashes) { if (hashes != null) { string[] array = (from h in hashes where !string.IsNullOrWhiteSpace(h) select h.ToLowerInvariant()).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); _whitelistedHashes.Value = array; PersistPreferences(); UpdateConfigFromPreferences(); _logger.Msg($"Updated whitelist with {array.Length} hash(es)"); } } public bool IsHashWhitelisted(string hash) { if (string.IsNullOrWhiteSpace(hash)) { return false; } return Config.WhitelistedHashes.Contains(hash.ToLowerInvariant(), StringComparer.OrdinalIgnoreCase); } public string GetReportUploadApiBaseUrl() { return _reportUploadApiBaseUrl.Value; } public bool IsReportHashUploaded(string hash) { if (string.IsNullOrWhiteSpace(hash)) { return false; } return NormalizeHashes(_uploadedReportHashes.Value).Contains(hash.ToLowerInvariant(), StringComparer.OrdinalIgnoreCase); } public void MarkReportHashUploaded(string hash) { if (HashUtility.IsValidHash(hash)) { string[] array = NormalizeHashes((_uploadedReportHashes.Value ?? Array.Empty()).Append(hash)); int num = array.Length; string[] value = _uploadedReportHashes.Value; if (num != ((value != null) ? value.Length : 0) || !IsReportHashUploaded(hash)) { _uploadedReportHashes.Value = array; PersistPreferences(); UpdateConfigFromPreferences(); _logger.Msg("Recorded uploaded report hash: " + hash); } } } private void PersistPreferences() { MelonPreferences.Save(); CleanupLegacyPreferenceEntries(); } private void CleanupLegacyPreferenceEntries() { try { string filePath = Path.Combine(MelonEnvironment.UserDataDirectory, "MelonPreferences.cfg"); if (LegacyConfigCleanup.TryRemoveObsoleteIniEntries(filePath, "MLVScan", out var removedKeys)) { _logger.Msg("Removed legacy config keys from MelonPreferences.cfg: " + string.Join(", ", removedKeys)); } } catch { } } private static string[] NormalizeHashes(IEnumerable hashes) { return (from h in hashes ?? Array.Empty() where !string.IsNullOrWhiteSpace(h) select h.ToLowerInvariant()).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); } } public class MelonPlatformEnvironment : IPlatformEnvironment { private readonly string _gameRoot; private readonly string _dataDir; private readonly string _reportsDir; public string GameRootDirectory => _gameRoot; public string[] PluginDirectories => new string[2] { Path.Combine(_gameRoot, "Mods"), Path.Combine(_gameRoot, "Plugins") }; public string DataDirectory { get { EnsureDataDirectory(); return _dataDir; } } public string ReportsDirectory { get { EnsureDataDirectory(); if (!Directory.Exists(_reportsDir)) { Directory.CreateDirectory(_reportsDir); } return _reportsDir; } } public string ManagedDirectory { get { try { string[] directories = Directory.GetDirectories(_gameRoot, "*_Data"); string[] array = directories; foreach (string path in array) { string text = Path.Combine(path, "Managed"); if (Directory.Exists(text)) { return text; } } } catch { } string text2 = Path.Combine(_gameRoot, "MelonLoader", "Managed"); if (Directory.Exists(text2)) { return text2; } return string.Empty; } } public string SelfAssemblyPath { get { try { return typeof(MelonPlatformEnvironment).Assembly.Location; } catch { return string.Empty; } } } public string PlatformName => "MelonLoader"; public MelonPlatformEnvironment() { _gameRoot = MelonEnvironment.GameRootDirectory; _dataDir = Path.Combine(MelonEnvironment.UserDataDirectory, "MLVScan"); _reportsDir = Path.Combine(_dataDir, "Reports"); } private void EnsureDataDirectory() { if (!Directory.Exists(_dataDir)) { Directory.CreateDirectory(_dataDir); } } } public class MelonPluginScanner : PluginScannerBase { private readonly MelonPlatformEnvironment _environment; public MelonPluginScanner(IScanLogger logger, IAssemblyResolverProvider resolverProvider, MLVScanConfig config, IConfigManager configManager, MelonPlatformEnvironment environment, LoaderScanTelemetryHub telemetry) : base(logger, resolverProvider, config, configManager, environment, telemetry) { _environment = environment ?? throw new ArgumentNullException("environment"); } protected override IEnumerable GetScanDirectories() { HashSet emitted = new HashSet(StringComparer.OrdinalIgnoreCase); HashSet builtInRoots = new HashSet(StringComparer.OrdinalIgnoreCase) { Path.GetFullPath(Path.Combine(_environment.GameRootDirectory, "Mods")), Path.GetFullPath(Path.Combine(_environment.GameRootDirectory, "Plugins")), Path.GetFullPath(Path.Combine(_environment.GameRootDirectory, "UserLibs")) }; if (Config.IncludeMods) { AddIfPresent(Path.Combine(_environment.GameRootDirectory, "Mods")); } if (Config.IncludePlugins) { AddIfPresent(Path.Combine(_environment.GameRootDirectory, "Plugins")); } if (Config.IncludeUserLibs) { AddIfPresent(Path.Combine(_environment.GameRootDirectory, "UserLibs")); } string[] array = Config.ScanDirectories ?? Array.Empty(); foreach (string scanDir in array) { AddLegacyIfPresent(scanDir); } if (Config.IncludeThunderstoreProfiles) { foreach (string thunderstoreRoot in GetThunderstoreDirectories()) { AddIfPresent(thunderstoreRoot); } } foreach (string item in emitted) { yield return item; } void AddIfPresent(string path) { if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path)) { emitted.Add(Path.GetFullPath(path)); } } void AddLegacyIfPresent(string text) { if (!string.IsNullOrWhiteSpace(text)) { string fullPath = Path.GetFullPath(Path.IsPathRooted(text) ? text : Path.Combine(_environment.GameRootDirectory, text)); if (!builtInRoots.Contains(fullPath)) { AddIfPresent(fullPath); } } } } protected override bool IsSelfAssembly(string filePath) { try { string selfAssemblyPath = _environment.SelfAssemblyPath; if (string.IsNullOrEmpty(selfAssemblyPath)) { return false; } return Path.GetFullPath(filePath).Equals(Path.GetFullPath(selfAssemblyPath), StringComparison.OrdinalIgnoreCase); } catch { return false; } } protected override IEnumerable GetResolverDirectories() { HashSet emitted = new HashSet(StringComparer.OrdinalIgnoreCase); AddIfPresent(Path.Combine(_environment.GameRootDirectory, "Mods")); AddIfPresent(Path.Combine(_environment.GameRootDirectory, "Plugins")); AddIfPresent(Path.Combine(_environment.GameRootDirectory, "UserLibs")); string[] array = Config.ScanDirectories ?? Array.Empty(); foreach (string text in array) { AddIfPresent(Path.IsPathRooted(text) ? text : Path.Combine(_environment.GameRootDirectory, text)); } if (Config.IncludeThunderstoreProfiles) { foreach (string thunderstoreDirectory in GetThunderstoreDirectories()) { AddIfPresent(thunderstoreDirectory); } } return emitted; void AddIfPresent(string path) { if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path)) { emitted.Add(Path.GetFullPath(path)); } } } private static IEnumerable GetThunderstoreDirectories() { if (!RuntimeInformationHelper.IsWindows) { yield break; } string appDataPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData); string thunderstoreBasePath = Path.Combine(appDataPath, "Thunderstore Mod Manager", "DataFolder"); if (!Directory.Exists(thunderstoreBasePath)) { yield break; } foreach (string gameFolder in SafeGetDirectories(thunderstoreBasePath)) { string profilesPath = Path.Combine(gameFolder, "profiles"); if (!Directory.Exists(profilesPath)) { continue; } foreach (string profileFolder in SafeGetDirectories(profilesPath)) { string modsPath = Path.Combine(profileFolder, "Mods"); if (Directory.Exists(modsPath)) { yield return modsPath; } string pluginsPath = Path.Combine(profileFolder, "Plugins"); if (Directory.Exists(pluginsPath)) { yield return pluginsPath; } } } } private static IEnumerable SafeGetDirectories(string path) { string[] directories; try { directories = Directory.GetDirectories(path); } catch (UnauthorizedAccessException) { yield break; } catch (DirectoryNotFoundException) { yield break; } catch (IOException) { yield break; } string[] array = directories; for (int i = 0; i < array.Length; i++) { yield return array[i]; } } } public class MelonPluginDisabler : PluginDisablerBase { private const string DisabledExtension = ".disabled"; public MelonPluginDisabler(IScanLogger logger, MLVScanConfig config) : base(logger, config) { } protected override string GetDisabledExtension() { return ".disabled"; } protected override string GetDisabledPath(string originalPath) { return Path.ChangeExtension(originalPath, ".disabled"); } } } namespace MLVScan.Services { internal static class ConsentMessageHelper { public static string GetUploadConsentMessage(string modName, string verdictKind, bool wasBlocked = true) { string text = (string.IsNullOrWhiteSpace(modName) ? "this mod" : modName); if (string.Equals(verdictKind, ThreatVerdictKind.KnownMaliciousSample.ToString(), StringComparison.Ordinal) || string.Equals(verdictKind, ThreatVerdictKind.KnownMalwareFamily.ToString(), StringComparison.Ordinal)) { return wasBlocked ? ("MLVScan identified " + text + " as likely malware and disabled it.") : ("MLVScan identified " + text + " as likely malware, but it was not blocked by the current configuration."); } if (string.IsNullOrWhiteSpace(verdictKind) || string.Equals(verdictKind, ThreatVerdictKind.None.ToString(), StringComparison.Ordinal)) { return wasBlocked ? ("MLVScan blocked " + text + " because it could not complete full analysis and manual review is required.") : ("MLVScan could not complete full analysis of " + text + ", so manual review is required before you trust it."); } return wasBlocked ? ("MLVScan blocked " + text + " because it triggered suspicious behavior. It may still be a false positive.") : ("MLVScan flagged " + text + " because it triggered suspicious behavior, but it was not blocked by the current configuration. It may still be a false positive."); } } public abstract class PluginScannerBase { protected readonly IScanLogger Logger; protected readonly IAssemblyResolverProvider ResolverProvider; protected readonly MLVScanConfig Config; protected readonly IConfigManager ConfigManager; protected readonly IPlatformEnvironment Environment; protected readonly AssemblyScanner AssemblyScanner; protected readonly ThreatVerdictBuilder ThreatVerdictBuilder; private const long MaxAssemblyScanBytes = 268435456L; private readonly IFileIdentityProvider _fileIdentityProvider; private readonly IResolverCatalogProvider _resolverCatalogProvider; private readonly LoaderScanTelemetryHub _telemetry; private readonly TargetAssemblyScopeFilter _scopeFilter = new TargetAssemblyScopeFilter(); private readonly string _scannerFingerprint; private readonly string _selfAssemblyHash; private IScanCacheStore _cacheStore; private bool _cacheUnavailable; protected PluginScannerBase(IScanLogger logger, IAssemblyResolverProvider resolverProvider, MLVScanConfig config, IConfigManager configManager, IPlatformEnvironment environment, LoaderScanTelemetryHub telemetry) { Logger = logger ?? throw new ArgumentNullException("logger"); ResolverProvider = resolverProvider ?? throw new ArgumentNullException("resolverProvider"); Config = config ?? throw new ArgumentNullException("config"); ConfigManager = configManager ?? throw new ArgumentNullException("configManager"); Environment = environment ?? throw new ArgumentNullException("environment"); _telemetry = telemetry ?? throw new ArgumentNullException("telemetry"); _fileIdentityProvider = new CrossPlatformFileIdentityProvider(); _resolverCatalogProvider = resolverProvider as IResolverCatalogProvider; IScanRule[] rules = RuleFactory.CreateDefaultRules().ToArray(); AssemblyScanner = new AssemblyScanner(rules, Config.Scan, ResolverProvider); ThreatVerdictBuilder = new ThreatVerdictBuilder(); _scannerFingerprint = ComputeScannerFingerprint(Config.Scan, (IReadOnlyCollection)(object)rules); _selfAssemblyHash = GetSelfAssemblyHash(environment.SelfAssemblyPath); } protected abstract IEnumerable GetScanDirectories(); protected abstract bool IsSelfAssembly(string filePath); protected virtual IEnumerable GetResolverDirectories() { return GetScanDirectories(); } protected virtual void OnScanComplete(Dictionary results) { } public Dictionary ScanAllPlugins(bool forceScanning = false) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); if (!forceScanning && !Config.EnableAutoScan) { Logger.Info("Automatic scanning is disabled in configuration"); return dictionary; } _telemetry.BeginRun($"{Environment.PlatformName}-{DateTime.UtcNow:yyyyMMdd-HHmmss}"); string[] array = GetScanDirectories().ToArray(); foreach (string item in array.Where((string directory) => !Directory.Exists(directory))) { Logger.Warning("Directory not found: " + item); } long startTimestamp = _telemetry.StartTimestamp(); IReadOnlyList readOnlyList = _scopeFilter.BuildEffectiveRoots(array, Config); _telemetry.AddPhaseElapsed("Scope.BuildEffectiveRoots", startTimestamp); StringComparer pathComparer = GetPathComparer(); string[] effectiveRoots = (from root in GetResolverDirectories().Concat(Config.AdditionalTargetRoots) where !string.IsNullOrWhiteSpace(root) select root).Select(Path.GetFullPath).Distinct(pathComparer).ToArray(); string resolverFingerprint = BuildResolverCatalog((IReadOnlyCollection)(object)effectiveRoots); string[] array2 = readOnlyList.SelectMany(EnumerateCandidateFiles).Distinct(pathComparer).ToArray(); HashSet activeCanonicalPaths = new HashSet(pathComparer); HashSet processedCanonicalPaths = new HashSet(pathComparer); string[] array3 = array2; foreach (string text in array3) { try { ScanSingleFile(text, readOnlyList, dictionary, activeCanonicalPaths, processedCanonicalPaths, resolverFingerprint); } catch (Exception ex) { Logger.Error("Error scanning " + Path.GetFileName(text) + ": " + ex.Message); } } GetCacheStore()?.PruneMissingEntries(activeCanonicalPaths); OnScanComplete(dictionary); _telemetry.CompleteRun(readOnlyList.Count, array2.Length, dictionary.Count); string text2 = TryGetDataDirectory(); if (!string.IsNullOrWhiteSpace(text2)) { string text3 = _telemetry.TryWriteArtifact(Path.Combine(text2, "Diagnostics")); if (!string.IsNullOrWhiteSpace(text3)) { Logger.Debug("Wrote loader profile artifact: " + text3); } } return dictionary; } protected virtual IEnumerable EnumerateCandidateFiles(string directoryPath) { Logger.Info("Scanning directory: " + directoryPath); return Directory.EnumerateFiles(directoryPath, "*.dll", SearchOption.AllDirectories); } protected virtual void ScanSingleFile(string filePath, IReadOnlyCollection effectiveRoots, Dictionary results, ISet activeCanonicalPaths, ISet processedCanonicalPaths, string resolverFingerprint) { long startTimestamp = _telemetry.StartTimestamp(); string fileName = Path.GetFileName(filePath); using FileProbe fileProbe = _fileIdentityProvider.OpenProbe(filePath); if (!_scopeFilter.IsTargetAssembly(fileProbe.CanonicalPath, effectiveRoots, Config)) { _telemetry.IncrementCounter("Files.OutOfScope", 1L); return; } activeCanonicalPaths.Add(fileProbe.CanonicalPath); if (!processedCanonicalPaths.Add(fileProbe.CanonicalPath)) { _telemetry.IncrementCounter("Files.DuplicateCanonicalPath", 1L); return; } if (IsSelfAssembly(fileProbe.CanonicalPath)) { Logger.Debug("Skipping self: " + fileName); _telemetry.IncrementCounter("Files.Self", 1L); return; } if (Config.EnableScanCache && TryReuseByPathCache(fileProbe, filePath, resolverFingerprint, results)) { _telemetry.RecordFileSample(filePath, startTimestamp, "cache-hit:path", 0L, 0); return; } bool flag = fileProbe.Stream.CanSeek && fileProbe.Stream.Length > 268435456; long num = 0L; byte[] array = null; string text; if (flag) { Logger.Warning($"Manual review required for {fileName}: it exceeds the loader scan limit of {256L} MB and cannot be fully analyzed in memory."); _telemetry.IncrementCounter("Files.TooLarge", 1L); num = (fileProbe.Stream.CanSeek ? fileProbe.Stream.Length : 0); if (num > 0) { _telemetry.IncrementCounter("Bytes.Read", num); } long startTimestamp2 = _telemetry.StartTimestamp(); text = CalculateStreamHash(fileProbe.Stream); _telemetry.AddPhaseElapsed("Hash.CalculateSha256", startTimestamp2); } else { long startTimestamp3 = _telemetry.StartTimestamp(); array = ReadFileBytes(fileProbe.Stream); _telemetry.AddPhaseElapsed("File.ReadBytes", startTimestamp3); num = array.Length; _telemetry.IncrementCounter("Bytes.Read", num); long startTimestamp4 = _telemetry.StartTimestamp(); text = HashUtility.CalculateBytesHash(array); _telemetry.AddPhaseElapsed("Hash.CalculateSha256", startTimestamp4); } if (IsExactSelfCopy(text)) { Logger.Debug("Skipping self copy: " + fileName); _telemetry.IncrementCounter("Files.SelfCopy", 1L); } else { if (IsHashWhitelisted(fileName, text)) { return; } if (Config.EnableScanCache && TryReuseByHashCache(text, fileProbe, filePath, resolverFingerprint, results)) { _telemetry.RecordFileSample(filePath, startTimestamp, "cache-hit:hash", num, 0); return; } ScannedPluginResult scannedPluginResult = ThreatVerdictBuilder.Build(filePath, text, new List()); if (scannedPluginResult.ThreatVerdict.Kind == ThreatVerdictKind.KnownMaliciousSample) { UpsertCacheEntry(fileProbe, text, resolverFingerprint, scannedPluginResult); RegisterResultIfNeeded(fileName, scannedPluginResult, results); _telemetry.RecordFileSample(filePath, startTimestamp, "hash-only-known-sample", num, 0); return; } if (!flag) { long startTimestamp5 = _telemetry.StartTimestamp(); using MemoryStream assemblyStream = new MemoryStream(array, writable: false); List source = AssemblyScanner.Scan(assemblyStream, filePath).ToList(); _telemetry.AddPhaseElapsed("Scan.Assembly", startTimestamp5); List list = source.Where((ScanFinding finding) => finding.Location != "Assembly scanning").ToList(); ScannedPluginResult scannedPluginResult2 = ThreatVerdictBuilder.Build(filePath, text, list); UpsertCacheEntry(fileProbe, text, resolverFingerprint, scannedPluginResult2); RegisterResultIfNeeded(fileName, scannedPluginResult2, results); _telemetry.RecordFileSample(filePath, startTimestamp, "scan", num, list.Count); return; } ScannedPluginResult scannedPluginResult3 = CreateOversizedAssemblyResult(filePath, text, num); UpsertCacheEntry(fileProbe, text, resolverFingerprint, scannedPluginResult3); RegisterResultIfNeeded(fileName, scannedPluginResult3, results); _telemetry.RecordFileSample(filePath, startTimestamp, "oversized:review-required", num, scannedPluginResult3.Findings.Count); } } private bool TryReuseByPathCache(FileProbe probe, string filePath, string resolverFingerprint, Dictionary results) { IScanCacheStore cacheStore = GetCacheStore(); if (cacheStore == null) { return false; } ScanCacheEntry scanCacheEntry = cacheStore.TryGetByPath(probe.CanonicalPath); if (scanCacheEntry == null) { _telemetry.IncrementCounter("Cache.PathMiss", 1L); return false; } if (!scanCacheEntry.CanReuseStrictly(probe, _scannerFingerprint, resolverFingerprint, cacheStore.CanTrustCleanEntries)) { _telemetry.IncrementCounter("Cache.PathRejected", 1L); return false; } _telemetry.IncrementCounter("Cache.PathHit", 1L); RegisterResultIfNeeded(Path.GetFileName(filePath), scanCacheEntry.CloneResultForPath(filePath), results); return true; } private bool TryReuseByHashCache(string hash, FileProbe probe, string filePath, string resolverFingerprint, Dictionary results) { IScanCacheStore cacheStore = GetCacheStore(); if (cacheStore == null) { return false; } ScanCacheEntry scanCacheEntry = cacheStore.TryGetByHash(hash); if (scanCacheEntry == null) { _telemetry.IncrementCounter("Cache.HashMiss", 1L); return false; } if (!string.Equals(scanCacheEntry.ScannerFingerprint, _scannerFingerprint, StringComparison.Ordinal) || !string.Equals(scanCacheEntry.ResolverFingerprint, resolverFingerprint, StringComparison.Ordinal)) { _telemetry.IncrementCounter("Cache.HashRejected", 1L); return false; } if (!ScanResultFacts.HasThreatVerdict(scanCacheEntry.Result) && !cacheStore.CanTrustCleanEntries) { _telemetry.IncrementCounter("Cache.HashRejected", 1L); return false; } ScannedPluginResult scannedPluginResult = scanCacheEntry.CloneResultForPath(filePath); UpsertCacheEntry(probe, hash, resolverFingerprint, scannedPluginResult); _telemetry.IncrementCounter("Cache.HashHit", 1L); RegisterResultIfNeeded(Path.GetFileName(filePath), scannedPluginResult, results); return true; } private void RegisterResultIfNeeded(string fileName, ScannedPluginResult scannedResult, Dictionary results) { if (scannedResult == null || IsHashWhitelisted(fileName, scannedResult.FileHash) || !ScanResultFacts.RequiresAttention(scannedResult)) { return; } results[scannedResult.FilePath] = scannedResult; if (scannedResult.ThreatVerdict.Kind == ThreatVerdictKind.KnownMaliciousSample || scannedResult.ThreatVerdict.Kind == ThreatVerdictKind.KnownMalwareFamily) { string text = scannedResult.ThreatVerdict.PrimaryFamily?.DisplayName; if (!string.IsNullOrWhiteSpace(text)) { Logger.Warning("Detected likely malware in " + fileName + " - " + scannedResult.ThreatVerdict.Title + ": " + text); } else { Logger.Warning("Detected likely malware in " + fileName + " - " + scannedResult.ThreatVerdict.Title); } } else if (scannedResult.ThreatVerdict.Kind == ThreatVerdictKind.Suspicious) { Logger.Warning("Detected suspicious behavior in " + fileName + " - " + scannedResult.ThreatVerdict.Title); } else { Logger.Warning("Manual review required for " + fileName + " - " + scannedResult.ScanStatus.Title); } } private bool IsHashWhitelisted(string fileName, string fileHash) { if (!ConfigManager.IsHashWhitelisted(fileHash)) { return false; } Logger.Debug("Skipping whitelisted: " + fileName); _telemetry.IncrementCounter("Files.Whitelisted", 1L); return true; } private void UpsertCacheEntry(FileProbe probe, string hash, string resolverFingerprint, ScannedPluginResult result) { GetCacheStore()?.Upsert(new ScanCacheEntry { CanonicalPath = probe.CanonicalPath, RealPath = probe.OriginalPath, FileIdentity = probe.Identity, Sha256 = hash, ScannerFingerprint = _scannerFingerprint, ResolverFingerprint = resolverFingerprint, Result = result }); } private static ScannedPluginResult CreateOversizedAssemblyResult(string filePath, string fileHash, long fileSizeBytes) { long num = 256L; double num2 = ((fileSizeBytes > 0) ? Math.Ceiling((double)fileSizeBytes / 1048576.0) : 0.0); List findings = new List { new ScanFinding("Loader scan preflight", $"Assembly exceeds the loader scan limit ({num2:0.#} MB > {num} MB). SHA-256 and exact known-malicious sample checks still ran, but full IL analysis was skipped and the file requires manual review.", Severity.Medium) { RuleId = "OversizedAssembly" } }; return new ScannedPluginResult { FilePath = (filePath ?? string.Empty), FileHash = (fileHash ?? string.Empty), Findings = findings, ThreatVerdict = new ThreatVerdictInfo { Kind = ThreatVerdictKind.None, Title = "No threat verdict", Summary = "No retained malicious verdict was produced before the loader hit a scan-completeness limit.", Confidence = 0.0, ShouldBypassThreshold = false }, ScanStatus = new ScanStatusInfo { Kind = ScanStatusKind.RequiresReview, Title = "Manual review required", Summary = $"This file exceeds the loader scan size limit ({num} MB). MLVScan calculated its SHA-256 hash and checked exact known-malicious sample matches, but full IL analysis was skipped to avoid loading the entire assembly into memory." } }; } private string BuildResolverCatalog(IReadOnlyCollection effectiveRoots) { if (_resolverCatalogProvider == null) { return "resolver-provider-unavailable"; } long startTimestamp = _telemetry.StartTimestamp(); _resolverCatalogProvider.BuildCatalog(effectiveRoots); _telemetry.AddPhaseElapsed("Resolver.BuildCatalog", startTimestamp); return _resolverCatalogProvider.ContextFingerprint; } private IScanCacheStore GetCacheStore() { if (!Config.EnableScanCache || _cacheUnavailable) { return null; } if (_cacheStore != null) { return _cacheStore; } try { _cacheStore = CreateDefaultCacheStore(Environment); } catch (Exception ex) { _cacheUnavailable = true; Logger.Warning("Scan cache unavailable; continuing without cache reuse: " + ex.Message); } return _cacheStore; } private bool IsExactSelfCopy(string hash) { return !string.IsNullOrWhiteSpace(_selfAssemblyHash) && _selfAssemblyHash.Equals(hash, StringComparison.OrdinalIgnoreCase); } private string TryGetDataDirectory() { try { return Environment.DataDirectory; } catch (Exception ex) { Logger.Warning("Diagnostics output unavailable: " + ex.Message); return null; } } private static string GetSelfAssemblyHash(string selfAssemblyPath) { try { if (string.IsNullOrWhiteSpace(selfAssemblyPath) || !File.Exists(selfAssemblyPath)) { return string.Empty; } return HashUtility.CalculateFileHash(selfAssemblyPath); } catch { return string.Empty; } } private static byte[] ReadFileBytes(FileStream stream) { stream.Position = 0L; using MemoryStream memoryStream = new MemoryStream((int)(stream.CanSeek ? stream.Length : 0)); stream.CopyTo(memoryStream); return memoryStream.ToArray(); } private static string CalculateStreamHash(FileStream stream) { stream.Position = 0L; using SHA256 sHA = SHA256.Create(); byte[] array = sHA.ComputeHash(stream); return BitConverter.ToString(array).Replace("-", string.Empty).ToLowerInvariant(); } private static IScanCacheStore CreateDefaultCacheStore(IPlatformEnvironment environment) { string cacheDirectory = Path.Combine(environment.DataDirectory, "Cache"); return new SecureScanCacheStore(cacheDirectory, new ScanCacheSigner(cacheDirectory)); } private static string ComputeScannerFingerprint(ScanConfig config, IReadOnlyCollection rules) { List list = new List { "MLVScan.MelonLoader", "2.1.1", MLVScanVersions.CoreVersion, "1.3.0".ToString(), $"EnableMultiSignalDetection={config.EnableMultiSignalDetection}", $"AnalyzeExceptionHandlers={config.AnalyzeExceptionHandlers}", $"AnalyzeLocalVariables={config.AnalyzeLocalVariables}", $"AnalyzePropertyAccessors={config.AnalyzePropertyAccessors}", $"DetectAssemblyMetadata={config.DetectAssemblyMetadata}", $"EnableCrossMethodAnalysis={config.EnableCrossMethodAnalysis}", $"MaxCallChainDepth={config.MaxCallChainDepth}", $"EnableReturnValueTracking={config.EnableReturnValueTracking}", $"EnableRecursiveResourceScanning={config.EnableRecursiveResourceScanning}", $"MaxRecursiveResourceSizeMB={config.MaxRecursiveResourceSizeMB}", $"MinimumEncodedStringLength={config.MinimumEncodedStringLength}" }; list.AddRange(from rule in rules.OrderBy((IScanRule rule) => rule.RuleId, StringComparer.Ordinal).ThenBy((IScanRule rule) => rule.GetType().FullName, StringComparer.Ordinal) select $"{rule.RuleId}|{rule.Severity}|{rule.GetType().FullName}"); return HashUtility.CalculateBytesHash(Encoding.UTF8.GetBytes(string.Join("\n", list))); } private static StringComparer GetPathComparer() { return (RuntimeInformationHelper.IsWindows || RuntimeInformationHelper.IsMacOs) ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; } } public class DisabledPluginInfo { public string OriginalPath { get; } public string DisabledPath { get; } public string FileHash { get; } public ThreatVerdictInfo ThreatVerdict { get; } public ScanStatusInfo ScanStatus { get; } public DisabledPluginInfo(string originalPath, string disabledPath, string fileHash, ThreatVerdictInfo threatVerdict, ScanStatusInfo scanStatus) { OriginalPath = originalPath; DisabledPath = disabledPath; FileHash = fileHash; ThreatVerdict = threatVerdict ?? new ThreatVerdictInfo(); ScanStatus = scanStatus ?? new ScanStatusInfo(); } } public abstract class PluginDisablerBase { protected readonly IScanLogger Logger; protected readonly MLVScanConfig Config; protected virtual string GetDisabledExtension() { return ".disabled"; } protected PluginDisablerBase(IScanLogger logger, MLVScanConfig config) { Logger = logger ?? throw new ArgumentNullException("logger"); Config = config ?? throw new ArgumentNullException("config"); } protected virtual string GetDisabledPath(string originalPath) { return Path.ChangeExtension(originalPath, GetDisabledExtension()); } protected virtual void OnPluginDisabled(string originalPath, string disabledPath, string hash) { } public List DisableSuspiciousPlugins(Dictionary scanResults, bool forceDisable = false) { if (!forceDisable && !Config.EnableAutoDisable) { Logger.Info("Automatic disabling is turned off in configuration"); return new List(); } List list = new List(); foreach (var (text2, scannedPluginResult2) in scanResults) { if (!ScanResultFacts.RequiresAttention(scannedPluginResult2)) { continue; } ThreatVerdictInfo threatVerdict = scannedPluginResult2?.ThreatVerdict ?? new ThreatVerdictInfo(); ScanStatusInfo scanStatus = scannedPluginResult2?.ScanStatus ?? new ScanStatusInfo(); if (!ShouldDisable(scannedPluginResult2)) { Logger.Info("Plugin " + Path.GetFileName(text2) + " requires attention (" + GetOutcomeTitle(scannedPluginResult2) + ") but blocking for this outcome is disabled in configuration"); continue; } try { DisabledPluginInfo disabledPluginInfo = DisablePlugin(text2, threatVerdict, scanStatus); if (disabledPluginInfo != null) { list.Add(disabledPluginInfo); OnPluginDisabled(disabledPluginInfo.OriginalPath, disabledPluginInfo.DisabledPath, disabledPluginInfo.FileHash); } } catch (Exception ex) { Logger.Error("Failed to disable " + Path.GetFileName(text2) + ": " + ex.Message); } } return list; } private bool ShouldDisable(ScannedPluginResult scanResult) { ThreatVerdictInfo threatVerdictInfo = scanResult?.ThreatVerdict ?? new ThreatVerdictInfo(); ThreatVerdictKind kind = threatVerdictInfo.Kind; if (1 == 0) { } bool result = kind switch { ThreatVerdictKind.KnownMaliciousSample => Config.BlockKnownThreats, ThreatVerdictKind.KnownMalwareFamily => Config.BlockKnownThreats, ThreatVerdictKind.Suspicious => Config.BlockSuspicious, _ => ShouldDisable(scanResult?.ScanStatus ?? new ScanStatusInfo()), }; if (1 == 0) { } return result; } private bool ShouldDisable(ScanStatusInfo scanStatus) { ScanStatusKind scanStatusKind = scanStatus?.Kind ?? ScanStatusKind.Complete; if (1 == 0) { } bool result = scanStatusKind == ScanStatusKind.RequiresReview && Config.BlockIncompleteScans; if (1 == 0) { } return result; } private static string GetOutcomeTitle(ScannedPluginResult scanResult) { if (ScanResultFacts.HasThreatVerdict(scanResult)) { return scanResult.ThreatVerdict.Title; } if (ScanResultFacts.RequiresManualReview(scanResult)) { return scanResult.ScanStatus.Title; } return "No action required"; } protected virtual DisabledPluginInfo DisablePlugin(string pluginPath, ThreatVerdictInfo threatVerdict, ScanStatusInfo scanStatus) { string fileHash = HashUtility.CalculateFileHash(pluginPath); string disabledPath = GetDisabledPath(pluginPath); if (File.Exists(disabledPath)) { File.Delete(disabledPath); } File.Move(pluginPath, disabledPath); Logger.Warning("BLOCKED: " + Path.GetFileName(pluginPath)); return new DisabledPluginInfo(pluginPath, disabledPath, fileHash, threatVerdict, scanStatus); } } public class DeveloperReportGenerator { private readonly IScanLogger _logger; public DeveloperReportGenerator(IScanLogger logger) { _logger = logger ?? throw new ArgumentNullException("logger"); } public void GenerateConsoleReport(string modName, List findings) { if (findings == null || findings.Count == 0) { return; } _logger.Info("======= DEVELOPER SCAN REPORT ======="); _logger.Info(PlatformConstants.GetFullVersionInfo()); _logger.Info("Mod: " + modName); _logger.Info("--------------------------------------"); _logger.Info($"Total findings: {findings.Count}"); _logger.Info(""); IOrderedEnumerable> orderedEnumerable = from f in findings where f.RuleId != null group f by f.RuleId into g orderby g.Max((ScanFinding f) => f.Severity) descending select g; foreach (IGrouping item in orderedEnumerable) { ScanFinding scanFinding = item.First(); int num = item.Count(); _logger.Info($"[{scanFinding.Severity}] {scanFinding.Description}"); _logger.Info(" Rule: " + scanFinding.RuleId); _logger.Info($" Occurrences: {num}"); if (scanFinding.DeveloperGuidance != null) { _logger.Info(""); _logger.Info(" Developer Guidance:"); _logger.Info(" " + WrapText(scanFinding.DeveloperGuidance.Remediation, 2)); if (!string.IsNullOrEmpty(scanFinding.DeveloperGuidance.DocumentationUrl)) { _logger.Info(" Documentation: " + scanFinding.DeveloperGuidance.DocumentationUrl); } if (scanFinding.DeveloperGuidance.AlternativeApis != null && scanFinding.DeveloperGuidance.AlternativeApis.Length != 0) { _logger.Info(" Suggested APIs: " + string.Join(", ", scanFinding.DeveloperGuidance.AlternativeApis)); } if (!scanFinding.DeveloperGuidance.IsRemediable) { _logger.Warning(" No safe alternative - this pattern should not be used in mods."); } } else { _logger.Info(" (No developer guidance available for this rule)"); } _logger.Info(""); _logger.Info(" Findings:"); foreach (ScanFinding item2 in from f in item orderby f.Severity descending, f.Location select f) { _logger.Info(" - " + item2.Location); if (item2.HasCallChain && item2.CallChain != null) { _logger.Info(" Call Chain:"); foreach (CallChainNode node in item2.CallChain.Nodes) { CallChainNodeType nodeType = node.NodeType; if (1 == 0) { } string text = nodeType switch { CallChainNodeType.EntryPoint => "[ENTRY]", CallChainNodeType.IntermediateCall => "[CALL]", CallChainNodeType.SuspiciousDeclaration => "[DECL]", _ => "[???]", }; if (1 == 0) { } string text2 = text; _logger.Info(" " + text2 + " " + node.Location); if (!string.IsNullOrWhiteSpace(node.Description)) { _logger.Info(" " + node.Description); } } } if (!item2.HasDataFlow || item2.DataFlowChain == null) { continue; } _logger.Info($" Data Flow: {item2.DataFlowChain.Pattern}"); if (item2.DataFlowChain.IsCrossMethod) { _logger.Info($" Cross-method: {item2.DataFlowChain.InvolvedMethods.Count} methods"); } _logger.Info(" Data Flow Chain:"); foreach (DataFlowNode node2 in item2.DataFlowChain.Nodes) { DataFlowNodeType nodeType2 = node2.NodeType; if (1 == 0) { } string text = nodeType2 switch { DataFlowNodeType.Source => "[SOURCE]", DataFlowNodeType.Transform => "[TRANSFORM]", DataFlowNodeType.Sink => "[SINK]", DataFlowNodeType.Intermediate => "[PASS]", _ => "[???]", }; if (1 == 0) { } string text3 = text; _logger.Info(" " + text3 + " " + node2.Operation + " (" + node2.DataDescription + ") @ " + node2.Location); } } _logger.Info("--------------------------------------"); } _logger.Info(""); _logger.Info("For more information, visit: https://discord.gg/UD4K4chKak"); _logger.Info("====================================="); } public string GenerateFileReport(string modName, string hash, List findings, ThreatVerdictInfo threatVerdict = null, ScanStatusInfo scanStatus = null) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("======= MLVScan Developer Report ======="); stringBuilder.AppendLine(PlatformConstants.GetFullVersionInfo()); stringBuilder.AppendLine("Mod: " + modName); stringBuilder.AppendLine("SHA256: " + hash); stringBuilder.AppendLine($"Scan Date: {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); stringBuilder.AppendLine($"Total Findings: {findings.Count}"); stringBuilder.AppendLine(""); using (StringWriter writer = new StringWriter(stringBuilder)) { ThreatVerdictTextFormatter.WriteThreatVerdictSection(writer, threatVerdict); ThreatVerdictTextFormatter.WriteScanStatusSection(writer, scanStatus); } IOrderedEnumerable> orderedEnumerable = from f in findings where f.RuleId != null group f by f.RuleId into g orderby g.Max((ScanFinding f) => f.Severity) descending select g; foreach (IGrouping item in orderedEnumerable) { ScanFinding scanFinding = item.First(); int num = item.Count(); stringBuilder.AppendLine("========================================="); stringBuilder.AppendLine("Rule: " + scanFinding.RuleId); stringBuilder.AppendLine($"Severity: {scanFinding.Severity}"); stringBuilder.AppendLine("Description: " + scanFinding.Description); stringBuilder.AppendLine($"Occurrences: {num}"); stringBuilder.AppendLine(""); if (scanFinding.DeveloperGuidance != null) { stringBuilder.AppendLine("--- DEVELOPER GUIDANCE ---"); stringBuilder.AppendLine("Remediation: " + scanFinding.DeveloperGuidance.Remediation); stringBuilder.AppendLine(""); if (!string.IsNullOrEmpty(scanFinding.DeveloperGuidance.DocumentationUrl)) { stringBuilder.AppendLine("Documentation: " + scanFinding.DeveloperGuidance.DocumentationUrl); } if (scanFinding.DeveloperGuidance.AlternativeApis != null && scanFinding.DeveloperGuidance.AlternativeApis.Length != 0) { stringBuilder.AppendLine("Suggested APIs:"); string[] alternativeApis = scanFinding.DeveloperGuidance.AlternativeApis; foreach (string text in alternativeApis) { stringBuilder.AppendLine(" - " + text); } } if (!scanFinding.DeveloperGuidance.IsRemediable) { stringBuilder.AppendLine(""); stringBuilder.AppendLine("WARNING: This pattern has no safe alternative and should not be used in mods."); } stringBuilder.AppendLine(""); } else { stringBuilder.AppendLine("(No developer guidance available for this rule)"); stringBuilder.AppendLine(""); } stringBuilder.AppendLine("--- FINDINGS ---"); foreach (ScanFinding item2 in item) { stringBuilder.AppendLine("Location: " + item2.Location); if (item2.HasCallChain && item2.CallChain != null) { stringBuilder.AppendLine(); stringBuilder.AppendLine("--- CALL CHAIN ANALYSIS ---"); stringBuilder.AppendLine(item2.CallChain.Summary); stringBuilder.AppendLine(); stringBuilder.AppendLine("Attack Path:"); foreach (CallChainNode node in item2.CallChain.Nodes) { CallChainNodeType nodeType = node.NodeType; if (1 == 0) { } string text2 = nodeType switch { CallChainNodeType.EntryPoint => "[ENTRY]", CallChainNodeType.IntermediateCall => "[CALL]", CallChainNodeType.SuspiciousDeclaration => "[DECL]", _ => "[???]", }; if (1 == 0) { } string text3 = text2; stringBuilder.AppendLine(" " + text3 + " " + node.Location); if (!string.IsNullOrEmpty(node.Description)) { stringBuilder.AppendLine(" " + node.Description); } } } if (item2.HasDataFlow && item2.DataFlowChain != null) { stringBuilder.AppendLine(); stringBuilder.AppendLine("--- DATA FLOW ANALYSIS ---"); stringBuilder.AppendLine($"Pattern: {item2.DataFlowChain.Pattern}"); stringBuilder.AppendLine(item2.DataFlowChain.Summary); if (item2.DataFlowChain.IsCrossMethod) { stringBuilder.AppendLine(); stringBuilder.AppendLine("Cross-Method Flow:"); foreach (string involvedMethod in item2.DataFlowChain.InvolvedMethods) { stringBuilder.AppendLine(" - " + involvedMethod); } } stringBuilder.AppendLine(); stringBuilder.AppendLine("Data Flow Path:"); for (int num3 = 0; num3 < item2.DataFlowChain.Nodes.Count; num3++) { DataFlowNode dataFlowNode = item2.DataFlowChain.Nodes[num3]; string text4 = ((num3 > 0) ? " -> " : " "); DataFlowNodeType nodeType2 = dataFlowNode.NodeType; if (1 == 0) { } string text2 = nodeType2 switch { DataFlowNodeType.Source => "[SOURCE]", DataFlowNodeType.Transform => "[TRANSFORM]", DataFlowNodeType.Sink => "[SINK]", DataFlowNodeType.Intermediate => "[PASS]", _ => "[???]", }; if (1 == 0) { } string text5 = text2; stringBuilder.AppendLine(text4 + text5 + " " + dataFlowNode.Operation + " (" + dataFlowNode.DataDescription + ")"); stringBuilder.AppendLine(new string(' ', text4.Length) + " Location: " + dataFlowNode.Location); } } if (!string.IsNullOrEmpty(item2.CodeSnippet)) { stringBuilder.AppendLine("Code Snippet:"); stringBuilder.AppendLine(item2.CodeSnippet); } stringBuilder.AppendLine(); } } stringBuilder.AppendLine("========================================="); stringBuilder.AppendLine(""); stringBuilder.AppendLine("Need help? Join the community: https://discord.gg/UD4K4chKak"); return stringBuilder.ToString(); } private string WrapText(string text, int indent) { string text2 = new string(' ', indent); int num = 80 - indent; string[] array = text.Split(' '); List list = new List(); string text3 = ""; string[] array2 = array; foreach (string text4 in array2) { if (text3.Length + text4.Length + 1 > num) { if (!string.IsNullOrEmpty(text3)) { list.Add(text3); } text3 = text4; } else { text3 = text3 + ((text3.Length > 0) ? " " : "") + text4; } } if (!string.IsNullOrEmpty(text3)) { list.Add(text3); } return string.Join("\n " + text2, list); } } public class IlDumpService { private readonly IScanLogger _logger; private readonly IPlatformEnvironment _environment; private readonly DefaultAssemblyResolver _assemblyResolver; public IlDumpService(IScanLogger logger, IPlatformEnvironment environment) { _logger = logger ?? throw new ArgumentNullException("logger"); _environment = environment ?? throw new ArgumentNullException("environment"); _assemblyResolver = BuildResolver(); } public bool TryDumpAssembly(string assemblyPath, string outputPath) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(assemblyPath) || string.IsNullOrWhiteSpace(outputPath)) { return false; } try { Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); ReaderParameters val = new ReaderParameters { ReadWrite = false, InMemory = true, ReadSymbols = false, AssemblyResolver = (IAssemblyResolver)(object)_assemblyResolver }; AssemblyDefinition val2 = AssemblyDefinition.ReadAssembly(assemblyPath, val); using StreamWriter streamWriter = new StreamWriter(outputPath); streamWriter.WriteLine("; Full IL dump for " + Path.GetFileName(assemblyPath)); streamWriter.WriteLine($"; Generated: {DateTime.Now}"); streamWriter.WriteLine("; Platform: " + _environment.PlatformName); streamWriter.WriteLine(); Enumerator enumerator = val2.Modules.GetEnumerator(); try { while (enumerator.MoveNext()) { ModuleDefinition current = enumerator.Current; streamWriter.WriteLine(".module " + ((ModuleReference)current).Name); streamWriter.WriteLine(); Enumerator enumerator2 = current.Types.GetEnumerator(); try { while (enumerator2.MoveNext()) { TypeDefinition current2 = enumerator2.Current; WriteType(current2, streamWriter); } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } _logger.Info("Saved IL dump to: " + outputPath); return true; } catch (Exception ex) { _logger.Error("Failed to dump IL for " + Path.GetFileName(assemblyPath) + ": " + ex.Message); return false; } } private DefaultAssemblyResolver BuildResolver() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown DefaultAssemblyResolver val = new DefaultAssemblyResolver(); string gameRootDirectory = _environment.GameRootDirectory; if (Directory.Exists(gameRootDirectory)) { ((BaseAssemblyResolver)val).AddSearchDirectory(gameRootDirectory); } string managedDirectory = _environment.ManagedDirectory; if (!string.IsNullOrEmpty(managedDirectory) && Directory.Exists(managedDirectory)) { ((BaseAssemblyResolver)val).AddSearchDirectory(managedDirectory); } string[] pluginDirectories = _environment.PluginDirectories; foreach (string text in pluginDirectories) { if (Directory.Exists(text)) { ((BaseAssemblyResolver)val).AddSearchDirectory(text); } } return val; } private static void WriteType(TypeDefinition type, StreamWriter writer) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) writer.WriteLine(".class " + ((MemberReference)type).FullName); Enumerator enumerator = type.Methods.GetEnumerator(); try { while (enumerator.MoveNext()) { MethodDefinition current = enumerator.Current; WriteMethod(current, writer); } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } Enumerator enumerator2 = type.NestedTypes.GetEnumerator(); try { while (enumerator2.MoveNext()) { TypeDefinition current2 = enumerator2.Current; WriteType(current2, writer); } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } private static void WriteMethod(MethodDefinition method, StreamWriter writer) { //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) try { string text = string.Join(", ", ((IEnumerable)((MethodReference)method).Parameters).Select((ParameterDefinition p) => ((MemberReference)((ParameterReference)p).ParameterType).FullName + " " + ((ParameterReference)p).Name)); writer.WriteLine(" .method " + ((MemberReference)((MethodReference)method).ReturnType).FullName + " " + ((MemberReference)method).Name + "(" + text + ")"); if (!method.HasBody) { writer.WriteLine(" // No body (abstract / external)"); writer.WriteLine(); return; } writer.WriteLine(" {"); Enumerator enumerator = method.Body.Instructions.GetEnumerator(); try { while (enumerator.MoveNext()) { Instruction current = enumerator.Current; string text2 = FormatOperand(current.Operand); string text3 = $" IL_{current.Offset:X4}: {current.OpCode}"; if (!string.IsNullOrEmpty(text2)) { text3 = text3 + " " + text2; } writer.WriteLine(text3); } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } writer.WriteLine(" }"); writer.WriteLine(); } catch (Exception ex) { writer.WriteLine(" // Failed to dump method " + ((MemberReference)method).Name + ": " + ex.Message); writer.WriteLine(); } } private static string FormatOperand(object operand) { if (1 == 0) { } string result; if (operand != null) { if (!(operand is string text)) { MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null); if (val == null) { FieldReference val2 = (FieldReference)((operand is FieldReference) ? operand : null); if (val2 == null) { TypeReference val3 = (TypeReference)((operand is TypeReference) ? operand : null); if (val3 == null) { Instruction val4 = (Instruction)((operand is Instruction) ? operand : null); result = ((val4 != null) ? $"IL_{val4.Offset:X4}" : ((!(operand is Instruction[] source)) ? (operand.ToString() ?? string.Empty) : string.Join(", ", source.Select((Instruction t) => $"IL_{t.Offset:X4}")))); } else { result = ((MemberReference)val3).FullName; } } else { result = ((MemberReference)val2).FullName; } } else { result = ((MemberReference)val).FullName; } } else { result = "\"" + text + "\""; } } else { result = string.Empty; } if (1 == 0) { } return result; } } public class PromptGeneratorService { private readonly ScanConfig _config; private readonly IScanLogger _logger; public PromptGeneratorService(ScanConfig config, IScanLogger logger) { _config = config ?? throw new ArgumentNullException("config"); _logger = logger ?? throw new ArgumentNullException("logger"); } public string GeneratePrompt(string modPath, List findings) { if (findings == null || !findings.Any()) { return "No suspicious findings to analyze."; } string fileName = Path.GetFileName(modPath); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("# Mod Security Analysis Request"); stringBuilder.AppendLine(); stringBuilder.AppendLine("I need to determine if the following mod is malicious or a false positive. Below is a security scan report generated by MLVScan (a security tool for MelonLoader mods)."); stringBuilder.AppendLine(); stringBuilder.AppendLine("## Mod Information"); stringBuilder.AppendLine("- **Filename**: " + fileName); stringBuilder.AppendLine($"- **Scan Date**: {DateTime.Now}"); stringBuilder.AppendLine($"- **Total Findings**: {findings.Count}"); stringBuilder.AppendLine(); Dictionary dictionary = (from f in findings group f by f.Severity into g orderby (int)g.Key descending select g).ToDictionary((IGrouping g) => g.Key, (IGrouping g) => g.Count()); stringBuilder.AppendLine("## Severity Breakdown"); foreach (KeyValuePair item in dictionary) { stringBuilder.AppendLine($"- **{FormatSeverityLabel(item.Key)}**: {item.Value} issue(s)"); } stringBuilder.AppendLine(); stringBuilder.AppendLine("## Detailed Findings"); Dictionary> dictionary2 = (from f in findings group f by f.Description).ToDictionary((IGrouping g) => g.Key, (IGrouping g) => g.ToList()); var (dictionary5, dictionary6) = ExtractCodeBlocks(modPath, findings); foreach (KeyValuePair> item2 in dictionary2) { stringBuilder.AppendLine("### " + item2.Key); stringBuilder.AppendLine($"- **Severity**: {item2.Value[0].Severity}"); stringBuilder.AppendLine($"- **Occurrences**: {item2.Value.Count}"); stringBuilder.AppendLine("- **Locations & Snippets**:"); foreach (ScanFinding item3 in item2.Value.Take(5)) { stringBuilder.AppendLine(" - **Location**: " + item3.Location); if (!string.IsNullOrEmpty(item3.CodeSnippet)) { stringBuilder.AppendLine(" **IL Snippet (Exact location of suspicious call)**:"); stringBuilder.AppendLine(" ```"); string[] array = item3.CodeSnippet.Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text in array) { stringBuilder.AppendLine(" " + text); } stringBuilder.AppendLine(" ```"); } if (dictionary5.TryGetValue(item3.Location, out var value) && !string.IsNullOrWhiteSpace(value)) { stringBuilder.AppendLine(" **Attempted C# Decompilation (Entire Method Context & Type Info)**:"); stringBuilder.AppendLine(" ```csharp"); string[] array2 = value.Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text2 in array2) { stringBuilder.AppendLine(" " + text2); } stringBuilder.AppendLine(" ```"); } if (dictionary6.TryGetValue(item3.Location, out var value2) && !string.IsNullOrWhiteSpace(value2)) { stringBuilder.AppendLine(" **Surrounding Class Structure (Member Signatures Only)**:"); stringBuilder.AppendLine(" ```csharp"); string[] array3 = value2.Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text3 in array3) { stringBuilder.AppendLine(" " + text3); } stringBuilder.AppendLine(" ```"); } stringBuilder.AppendLine(); } if (item2.Value.Count > 5) { stringBuilder.AppendLine($" - *And {item2.Value.Count - 5} more occurrences (details omitted for brevity)*"); } stringBuilder.AppendLine(); } string value3 = ExtractAssemblyMetadata(modPath); if (!string.IsNullOrEmpty(value3)) { stringBuilder.AppendLine("## Assembly Metadata"); stringBuilder.AppendLine(value3); stringBuilder.AppendLine(); } stringBuilder.AppendLine("## Request"); stringBuilder.AppendLine("Based on this scan report and code analysis, please help me determine:"); stringBuilder.AppendLine("1. Is this mod likely to be malicious or is it a false positive?"); stringBuilder.AppendLine("2. If potentially malicious, what specific security risks does it pose?"); stringBuilder.AppendLine("3. What is the intent of the suspicious code? Is there a benign explanation?"); stringBuilder.AppendLine("4. What further actions should I take? (Whitelist it, delete it, report it, etc.)"); stringBuilder.AppendLine(); stringBuilder.AppendLine("Please explain your reasoning with reference to the specific code patterns and provide context about:"); stringBuilder.AppendLine("- Whether these patterns are common in legitimate mods or game utilities"); stringBuilder.AppendLine("- Alternative explanations for the suspicious patterns"); stringBuilder.AppendLine("- Your confidence level in the assessment"); stringBuilder.AppendLine(); stringBuilder.AppendLine("Important context: This is a mod for a game using MelonLoader (a mod loading framework). Legitimate mods generally don't need to use system-level APIs like shell execution, registry access, etc."); return stringBuilder.ToString(); } private Tuple, Dictionary> ExtractCodeBlocks(string modPath, List findings) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) Dictionary dictionary = new Dictionary(); Dictionary dictionary2 = new Dictionary(); try { if (!File.Exists(modPath)) { return Tuple.Create(dictionary, dictionary2); } ReaderParameters val = new ReaderParameters { ReadWrite = false, InMemory = true, ReadSymbols = false }; AssemblyDefinition val2 = AssemblyDefinition.ReadAssembly(modPath, val); try { Dictionary> dictionary3 = new Dictionary>(); Enumerator enumerator = val2.Modules.GetEnumerator(); try { while (enumerator.MoveNext()) { ModuleDefinition current = enumerator.Current; Enumerator enumerator2 = current.Types.GetEnumerator(); try { while (enumerator2.MoveNext()) { TypeDefinition current2 = enumerator2.Current; CollectSuspiciousStrings(current2, dictionary3); } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } if (dictionary3.Any()) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("// Notable string literals found in the assembly:"); stringBuilder.AppendLine(); foreach (string item in dictionary3.Keys.OrderBy((string k) => k)) { stringBuilder.AppendLine("// Potential " + item + ":"); foreach (string item2 in dictionary3[item].Take(10)) { stringBuilder.AppendLine("// \"" + EscapeStringForCode(item2) + "\""); } if (dictionary3[item].Count > 10) { stringBuilder.AppendLine($"// ... and {dictionary3[item].Count - 10} more"); } stringBuilder.AppendLine(); } dictionary["SuspiciousStrings"] = stringBuilder.ToString(); } foreach (ScanFinding finding in findings) { try { string location = finding.Location; if (location == "Assembly scanning" || !location.Contains(".")) { continue; } string empty = string.Empty; string fullTypeName; string methodNameFromFinding; if (location.Contains(":")) { string[] array = location.Split(':'); string text = array[0]; empty = array[1]; int num = text.LastIndexOf('.'); if (num > 0) { fullTypeName = text.Substring(0, num); methodNameFromFinding = text.Substring(num + 1); goto IL_032f; } } else { int num2 = location.LastIndexOf('.'); if (num2 > 0) { fullTypeName = location.Substring(0, num2); methodNameFromFinding = location.Substring(num2 + 1); goto IL_032f; } } goto end_IL_0244; IL_032f: TypeDefinition val3 = FindType(val2.MainModule, fullTypeName); if (val3 == null) { continue; } if (!dictionary2.ContainsKey(finding.Location) || dictionary2[finding.Location] == string.Empty) { dictionary2[finding.Location] = GenerateClassStructure(val3, methodNameFromFinding); } MethodDefinition val4 = ((IEnumerable)val3.Methods).FirstOrDefault((Func)((MethodDefinition m) => ((MemberReference)m).Name == methodNameFromFinding)); if (val4 == null) { if (!dictionary.ContainsKey(finding.Location)) { dictionary[finding.Location] = "// DllImport or external method: " + finding.Location + ". Primary context is the IL snippet and class structure."; } continue; } if (!val4.HasBody && !val4.IsAbstract) { if (!dictionary.ContainsKey(finding.Location)) { dictionary[finding.Location] = "// Method " + methodNameFromFinding + " has no body or is abstract."; } continue; } string text2 = DecompileMethod(val4); if (string.IsNullOrEmpty(text2)) { continue; } StringBuilder stringBuilder2 = new StringBuilder(); stringBuilder2.AppendLine("// Context: Method is part of " + ((MemberReference)val3).FullName); if (val3.HasCustomAttributes) { stringBuilder2.AppendLine("// Type attributes:"); foreach (CustomAttribute item3 in ((IEnumerable)val3.CustomAttributes).Take(5)) { stringBuilder2.AppendLine("// - " + ((MemberReference)item3.AttributeType).Name); } if (val3.CustomAttributes.Count > 5) { stringBuilder2.AppendLine($"// - ...and {val3.CustomAttributes.Count - 5} more"); } } if (val3.BaseType != null && ((MemberReference)val3.BaseType).FullName != "System.Object") { stringBuilder2.AppendLine("// Inherits from: " + ((MemberReference)val3.BaseType).FullName); } List list = FindRelatedSuspiciousMethods(val3, val4); if (list.Any()) { stringBuilder2.AppendLine("// Other suspicious methods in this class (names only):"); foreach (MethodDefinition item4 in list.Take(3)) { stringBuilder2.AppendLine("// - " + ((MemberReference)item4).Name); } if (list.Count > 3) { stringBuilder2.AppendLine($"// - ...and {list.Count - 3} more"); } } stringBuilder2.AppendLine(); stringBuilder2.AppendLine("// Finding Description: " + finding.Description); stringBuilder2.AppendLine($"// Severity: {finding.Severity}"); stringBuilder2.AppendLine(); dictionary[finding.Location] = stringBuilder2.ToString() + text2; end_IL_0244:; } catch (Exception ex) { _logger.Error("Failed to extract code for " + finding.Location + ": " + ex.Message); dictionary[finding.Location] = "// Error extracting detailed code for " + finding.Location + ": " + ex.Message; } } if (((IEnumerable)val2.MainModule.Resources).Any()) { StringBuilder stringBuilder3 = new StringBuilder(); stringBuilder3.AppendLine("// Assembly Resources (could contain hidden payloads):"); foreach (Resource item5 in ((IEnumerable)val2.MainModule.Resources).Take(20)) { stringBuilder3.AppendLine("// " + item5.Name + " - " + GetResourceTypeName(item5)); } if (val2.MainModule.Resources.Count > 20) { stringBuilder3.AppendLine($"// ...and {val2.MainModule.Resources.Count - 20} more resources"); } dictionary["Assembly.Resources"] = stringBuilder3.ToString(); } } finally { ((IDisposable)val2)?.Dispose(); } } catch (Exception ex2) { _logger.Error("Failed to extract code blocks from " + modPath + ": " + ex2.Message); } return Tuple.Create(dictionary, dictionary2); } private void CollectSuspiciousStrings(TypeDefinition type, Dictionary> suspiciousStrings) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) Enumerator enumerator = type.Methods.GetEnumerator(); try { while (enumerator.MoveNext()) { MethodDefinition current = enumerator.Current; if (!current.HasBody) { continue; } Enumerator enumerator2 = current.Body.Instructions.GetEnumerator(); try { while (enumerator2.MoveNext()) { Instruction current2 = enumerator2.Current; OpCode opCode = current2.OpCode; if (((OpCode)(ref opCode)).Name == "ldstr" && current2.Operand is string text && IsSuspiciousString(text, out var category)) { if (!suspiciousStrings.ContainsKey(category)) { suspiciousStrings[category] = new List(); } if (!suspiciousStrings[category].Contains(text)) { suspiciousStrings[category].Add(text); } } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } Enumerator enumerator3 = type.NestedTypes.GetEnumerator(); try { while (enumerator3.MoveNext()) { TypeDefinition current3 = enumerator3.Current; CollectSuspiciousStrings(current3, suspiciousStrings); } } finally { ((IDisposable)enumerator3/*cast due to .constrained prefix*/).Dispose(); } } private bool IsSuspiciousString(string value, out string category) { if (string.IsNullOrEmpty(value)) { category = string.Empty; return false; } if (value.Length > 20 && IsLikelyBase64(value)) { category = "Base64 encoded data"; return true; } if (value.StartsWith("http://") || value.StartsWith("https://") || value.StartsWith("ftp://") || value.StartsWith("ws://")) { category = "URL"; return true; } if (value.Contains(".exe") || value.Contains(".dll") || value.Contains(".bat") || value.Contains(".cmd") || value.Contains(".ps1") || value.Contains(".vbs")) { category = "executable file reference"; return true; } if (IsLikelyIPAddress(value)) { category = "IP address"; return true; } if (value.StartsWith("HKEY_") || value.Contains("\\Software\\") || value.Contains("\\Microsoft\\") || value.Contains("\\System\\")) { category = "registry path"; return true; } if (value.StartsWith("cmd ") || value.StartsWith("powershell ") || value.Contains(" /c ") || value.Contains(" /k ")) { category = "command line"; return true; } if (value.Contains("encrypt") || value.Contains("decrypt") || value.Contains("aes") || value.Contains("rsa") || value.Contains("md5") || value.Contains("sha") || value.Contains("hash")) { category = "cryptographic reference"; return true; } if ((value.Contains(".") && !value.Contains(" ") && !value.Contains("\\") && !value.EndsWith(".cs") && !value.EndsWith(".txt") && value.Length > 5) || value.Contains("pastebin") || value.Contains("discord") || value.Contains("webhook")) { category = "domain or web service"; return true; } category = string.Empty; return false; } private bool IsLikelyBase64(string value) { if (value.Length % 4 != 0) { return false; } foreach (char c in value) { if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && (c < '0' || c > '9') && c != '+' && c != '/' && c != '=') { return false; } } return value.Length > 20 && value.Any((char c2) => c2 >= 'A' && c2 <= 'Z') && value.Any((char c2) => c2 >= 'a' && c2 <= 'z') && value.Any((char c2) => c2 >= '0' && c2 <= '9'); } private bool IsLikelyIPAddress(string value) { string pattern = "^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"; string pattern2 = "^[0-9a-fA-F:]+"; return Regex.IsMatch(value, pattern) || Regex.IsMatch(value, pattern2); } private string GetResourceTypeName(Resource resource) { //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Unknown result type (might be due to invalid IL or missing references) EmbeddedResource val = (EmbeddedResource)(object)((resource is EmbeddedResource) ? resource : null); if (val != null) { using (Stream stream = val.GetResourceStream()) { if (stream.Length > 0) { byte[] array = new byte[Math.Min(stream.Length, 16L)]; stream.Read(array, 0, array.Length); if (array.Length >= 2 && array[0] == 77 && array[1] == 90) { return "PE File/DLL (MZ header)"; } if (array.Length >= 4 && array[0] == 127 && array[1] == 69 && array[2] == 76 && array[3] == 70) { return "ELF Binary"; } if (array.Length >= 4 && array[0] == 80 && array[1] == 75 && array[2] == 3 && array[3] == 4) { return "ZIP Archive"; } if (array.Length >= 2 && array[0] == byte.MaxValue && array[1] == 216) { return "JPEG Image"; } if (array.Length >= 3 && array[0] == 71 && array[1] == 73 && array[2] == 70) { return "GIF Image"; } if (array.Length >= 4 && ((array[0] == 137 && array[1] == 80 && array[2] == 78 && array[3] == 71) || (array[0] == 66 && array[1] == 77))) { return "PNG or BMP Image"; } return $"Binary data ({stream.Length} bytes)"; } return "Empty resource"; } } return ((object)resource.ResourceType/*cast due to .constrained prefix*/).ToString(); } private List FindRelatedSuspiciousMethods(TypeDefinition type, MethodDefinition currentMethod) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) List list = new List(); Enumerator enumerator = type.Methods.GetEnumerator(); try { while (enumerator.MoveNext()) { MethodDefinition current = enumerator.Current; if (current == currentMethod || !current.HasBody) { continue; } Enumerator enumerator2 = current.Body.Instructions.GetEnumerator(); try { while (enumerator2.MoveNext()) { Instruction current2 = enumerator2.Current; OpCode opCode = current2.OpCode; if (!(((OpCode)(ref opCode)).Name == "call")) { opCode = current2.OpCode; if (!(((OpCode)(ref opCode)).Name == "callvirt")) { opCode = current2.OpCode; if (!(((OpCode)(ref opCode)).Name == "newobj")) { continue; } } } object operand = current2.Operand; MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null); if (val != null && IsSuspiciousMethodCall(val)) { list.Add(current); break; } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } return list; } private TypeDefinition FindType(ModuleDefinition module, string fullTypeName) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) Enumerator enumerator = module.Types.GetEnumerator(); try { while (enumerator.MoveNext()) { TypeDefinition current = enumerator.Current; if (((MemberReference)current).FullName == fullTypeName) { return current; } TypeDefinition val = FindNestedType(current, fullTypeName); if (val != null) { return val; } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } return null; } private TypeDefinition FindNestedType(TypeDefinition parentType, string fullTypeName) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) Enumerator enumerator = parentType.NestedTypes.GetEnumerator(); try { while (enumerator.MoveNext()) { TypeDefinition current = enumerator.Current; if (((MemberReference)current).FullName == fullTypeName) { return current; } TypeDefinition val = FindNestedType(current, fullTypeName); if (val != null) { return val; } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } return null; } private string DecompileMethod(MethodDefinition method) { //IL_038d: Unknown result type (might be due to invalid IL or missing references) //IL_0392: Unknown result type (might be due to invalid IL or missing references) //IL_041e: Unknown result type (might be due to invalid IL or missing references) //IL_0423: Unknown result type (might be due to invalid IL or missing references) if (!method.HasBody) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(GetMethodVisibility(method) + " " + ((MemberReference)((MethodReference)method).ReturnType).Name + " " + ((MemberReference)method).Name + "(" + GetMethodParameters(method) + ")"); stringBuilder.AppendLine("{"); string value = ReconstructCode(method); if (!string.IsNullOrEmpty(value)) { stringBuilder.AppendLine(value); } else { stringBuilder.AppendLine(" // Method body:"); List source = ((IEnumerable)method.Body.ExceptionHandlers).ToList(); HashSet hashSet = source.Select((ExceptionHandler h) => h.HandlerStart.Offset).ToHashSet(); HashSet hashSet2 = source.Select(delegate(ExceptionHandler h) { Instruction handlerEnd = h.HandlerEnd; return (handlerEnd != null) ? handlerEnd.Offset : int.MaxValue; }).ToHashSet(); HashSet hashSet3 = source.Select((ExceptionHandler h) => h.TryStart.Offset).ToHashSet(); HashSet hashSet4 = source.Select(delegate(ExceptionHandler h) { Instruction tryEnd = h.TryEnd; return (tryEnd != null) ? tryEnd.Offset : int.MaxValue; }).ToHashSet(); bool flag = false; bool flag2 = false; List variables = ((IEnumerable)method.Body.Variables).ToList(); List parameters = ((IEnumerable)((MethodReference)method).Parameters).ToList(); List list = ((IEnumerable)method.Body.Instructions).ToList(); for (int num = 0; num < list.Count; num++) { Instruction instruction = list[num]; if (hashSet3.Contains(instruction.Offset) && !flag) { stringBuilder.AppendLine(" try {"); flag = true; } if (hashSet.Contains(instruction.Offset) && !flag2) { ExceptionHandler val = ((IEnumerable)source).FirstOrDefault((Func)((ExceptionHandler h) => h.HandlerStart.Offset == instruction.Offset)); object obj; if (val == null) { obj = null; } else { TypeReference catchType = val.CatchType; obj = ((catchType != null) ? ((MemberReference)catchType).Name : null); } if (obj == null) { obj = "Exception"; } stringBuilder.AppendLine(" } catch (" + (string?)obj + ") {"); flag = false; flag2 = true; } if ((hashSet4.Contains(instruction.Offset) && flag) || (hashSet2.Contains(instruction.Offset) && flag2)) { stringBuilder.AppendLine(" }"); flag = false; flag2 = false; } string text = FormatInstructionWithContext(instruction, num, list, variables, parameters); if (!string.IsNullOrEmpty(text)) { string text2 = " "; if (flag || flag2) { text2 += " "; } stringBuilder.AppendLine(text2 + text); } } if (flag || flag2) { stringBuilder.AppendLine(" }"); } } if (((MethodReference)method).Parameters.Count > 0) { stringBuilder.AppendLine(); stringBuilder.AppendLine(" // Method parameters:"); Enumerator enumerator = ((MethodReference)method).Parameters.GetEnumerator(); try { while (enumerator.MoveNext()) { ParameterDefinition current = enumerator.Current; stringBuilder.AppendLine(" // " + ((MemberReference)((ParameterReference)current).ParameterType).FullName + " " + ((ParameterReference)current).Name); } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } } if (method.Body.Variables.Count > 0) { stringBuilder.AppendLine(); stringBuilder.AppendLine(" // Local variables:"); Enumerator enumerator2 = method.Body.Variables.GetEnumerator(); try { while (enumerator2.MoveNext()) { VariableDefinition current2 = enumerator2.Current; stringBuilder.AppendLine($" // {((MemberReference)((VariableReference)current2).VariableType).FullName} var_{((VariableReference)current2).Index}"); } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } List list2 = FindSuspiciousApiCalls(method); if (list2.Any()) { stringBuilder.AppendLine(); stringBuilder.AppendLine(" // Suspicious API calls:"); foreach (string item in list2) { stringBuilder.AppendLine(" // " + item); } } stringBuilder.AppendLine("}"); return stringBuilder.ToString(); } private string ReconstructCode(MethodDefinition method) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(); try { List list = ((IEnumerable)method.Body.Instructions).ToList(); for (int i = 0; i < list.Count; i++) { Instruction val = list[i]; OpCode opCode = val.OpCode; if (!(((OpCode)(ref opCode)).Name == "call")) { opCode = val.OpCode; if (!(((OpCode)(ref opCode)).Name == "callvirt")) { opCode = val.OpCode; if (((OpCode)(ref opCode)).Name == "newobj") { object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 != null) { stringBuilder.AppendLine(" new " + ((MemberReference)((MemberReference)val2).DeclaringType).Name + "(...);"); continue; } } opCode = val.OpCode; if (((OpCode)(ref opCode)).Name == "ldstr" && val.Operand is string value) { stringBuilder.AppendLine(" // String: \"" + EscapeStringForCode(value) + "\""); } continue; } } object operand2 = val.Operand; MethodReference val3 = (MethodReference)((operand2 is MethodReference) ? operand2 : null); if (val3 != null) { string methodCallRepresentation = GetMethodCallRepresentation(val3, list, i); if (!string.IsNullOrEmpty(methodCallRepresentation)) { stringBuilder.AppendLine(" " + methodCallRepresentation + ";"); } } } if (stringBuilder.Length == 0) { return string.Empty; } return stringBuilder.ToString(); } catch { return string.Empty; } } private string EscapeStringForCode(string value) { if (string.IsNullOrEmpty(value)) { return value; } return value.Replace("\"", "\\\"").Replace("\r", "\\r").Replace("\n", "\\n") .Replace("\t", "\\t"); } private string GetMethodCallRepresentation(MethodReference methodRef, List instructions, int currentIndex) { //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) string name = ((MemberReference)methodRef).Name; string name2 = ((MemberReference)((MemberReference)methodRef).DeclaringType).Name; if (name.Contains("Process") && name.Contains("Start")) { return "System.Diagnostics.Process.Start(...) // Executes external process"; } if (name.Contains("Load") && name2.Contains("Assembly")) { return "Assembly." + name + "(...) // Dynamically loads code"; } if ((name.Contains("FromBase64") || name.Contains("GetString")) && name2.Contains("Convert")) { string text = "..."; for (int num = currentIndex - 1; num >= Math.Max(0, currentIndex - 5); num--) { OpCode opCode = instructions[num].OpCode; if (((OpCode)(ref opCode)).Name == "ldstr" && instructions[num].Operand is string) { text = instructions[num].Operand.ToString(); if (text.Length > 20) { text = text.Substring(0, 17) + "..."; } break; } } return "Convert." + name + "(\"" + text + "\") // Decodes Base64 data"; } if (name.Contains("RegOpenKey") || name.Contains("RegCreateKey") || (name.Contains("Registry") && (name.Contains("Get") || name.Contains("Set")))) { return name2 + "." + name + "(...) // Registry manipulation"; } if (name.Contains("CreateFile") || name.Contains("WriteFile") || name.Contains("ReadFile")) { return name2 + "." + name + "(...) // File system operation"; } if (name.Contains("Socket") || name.Contains("Connect") || name.Contains("Send") || name.Contains("Receive")) { return name2 + "." + name + "(...) // Network communication"; } return name2 + "." + name + "(...)"; } private string FormatInstructionWithContext(Instruction instruction, int index, List allInstructions, List variables, List parameters) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) OpCode opCode = instruction.OpCode; string name = ((OpCode)(ref opCode)).Name; if (name == "nop") { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append($"/* {instruction.Offset:X4} */ "); object operand = instruction.Operand; MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null); if (val != null) { stringBuilder.Append(name + " " + ((MemberReference)((MemberReference)val).DeclaringType).FullName + "." + ((MemberReference)val).Name + "()"); if (val.Parameters.Count > 0) { stringBuilder.Append(" // Takes: " + string.Join(", ", ((IEnumerable)val.Parameters).Select((ParameterDefinition p) => ((MemberReference)((ParameterReference)p).ParameterType).Name))); } if (IsSuspiciousMethodCall(val)) { stringBuilder.Append(" // SUSPICIOUS: " + GetSuspiciousMethodDescription(val)); } } else { object operand2 = instruction.Operand; TypeReference val2 = (TypeReference)((operand2 is TypeReference) ? operand2 : null); if (val2 != null) { stringBuilder.Append(name + " " + ((MemberReference)val2).FullName); } else { object operand3 = instruction.Operand; FieldReference val3 = (FieldReference)((operand3 is FieldReference) ? operand3 : null); if (val3 != null) { stringBuilder.Append(name + " " + ((MemberReference)((MemberReference)val3).DeclaringType).Name + "." + ((MemberReference)val3).Name); } else { object operand4 = instruction.Operand; VariableDefinition val4 = (VariableDefinition)((operand4 is VariableDefinition) ? operand4 : null); if (val4 != null) { string name2 = ((MemberReference)((VariableReference)val4).VariableType).Name; stringBuilder.Append($"{name} V_{((VariableReference)val4).Index} /* {name2} */"); } else { object operand5 = instruction.Operand; ParameterDefinition val5 = (ParameterDefinition)((operand5 is ParameterDefinition) ? operand5 : null); if (val5 != null) { string name3 = ((MemberReference)((ParameterReference)val5).ParameterType).Name; stringBuilder.Append(name + " " + ((ParameterReference)val5).Name + " /* " + name3 + " */"); } else if (instruction.Operand is string text) { string text2 = text; if (text2.Length > 50) { text2 = text2.Substring(0, 47) + "..."; } stringBuilder.Append(name + " \"" + EscapeStringForCode(text2) + "\""); } else { object operand6 = instruction.Operand; Instruction val6 = (Instruction)((operand6 is Instruction) ? operand6 : null); if (val6 != null) { stringBuilder.Append($"{name} IL_{val6.Offset:X4}"); } else { string text3 = instruction.Operand?.ToString() ?? string.Empty; stringBuilder.Append(name + " " + text3); } } } } } } return stringBuilder.ToString(); } private bool IsSuspiciousMethodCall(MethodReference methodRef) { string fullName = ((MemberReference)((MemberReference)methodRef).DeclaringType).FullName; string name = ((MemberReference)methodRef).Name; return (fullName.Contains("Process") && name.Contains("Start")) || (fullName.Contains("Assembly") && (name.Contains("Load") || name.Contains("LoadFrom") || name.Contains("LoadFile"))) || (fullName.Contains("Convert") && name.Contains("FromBase64")) || ((fullName.Contains("Registry") || name.Contains("Reg")) && (name.Contains("CreateKey") || name.Contains("OpenKey") || name.Contains("SetValue") || name.Contains("GetValue"))) || fullName.Contains("Shell32") || name.Contains("ShellExecute") || ((fullName.Contains("Socket") || fullName.Contains("Http") || fullName.Contains("Tcp") || fullName.Contains("Web") || fullName.Contains("Net")) && (name.Contains("Connect") || name.Contains("Send") || name.Contains("Download") || name.Contains("Upload"))); } private string GetSuspiciousMethodDescription(MethodReference methodRef) { string fullName = ((MemberReference)((MemberReference)methodRef).DeclaringType).FullName; string name = ((MemberReference)methodRef).Name; if (fullName.Contains("Process") && name.Contains("Start")) { return "Executes external programs"; } if (fullName.Contains("Assembly") && (name.Contains("Load") || name.Contains("LoadFrom") || name.Contains("LoadFile"))) { return "Dynamically loads code which could be malicious"; } if (fullName.Contains("Convert") && name.Contains("FromBase64")) { return "Decodes potentially obfuscated data"; } if ((fullName.Contains("Registry") || name.Contains("Reg")) && (name.Contains("CreateKey") || name.Contains("OpenKey") || name.Contains("SetValue") || name.Contains("GetValue"))) { return "Manipulates system registry which can persist malware"; } if (fullName.Contains("Shell32") || name.Contains("ShellExecute")) { return "Executes system commands"; } if ((fullName.Contains("Socket") || fullName.Contains("Http") || fullName.Contains("Tcp") || fullName.Contains("Web") || fullName.Contains("Net")) && (name.Contains("Connect") || name.Contains("Send") || name.Contains("Download") || name.Contains("Upload"))) { return "Performs network operations that could exfiltrate data or download malware"; } return "Potentially suspicious behavior"; } private List FindSuspiciousApiCalls(MethodDefinition method) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) List list = new List(); Enumerator enumerator = method.Body.Instructions.GetEnumerator(); try { while (enumerator.MoveNext()) { Instruction current = enumerator.Current; OpCode opCode = current.OpCode; if (!(((OpCode)(ref opCode)).Name == "call")) { opCode = current.OpCode; if (!(((OpCode)(ref opCode)).Name == "callvirt")) { opCode = current.OpCode; if (!(((OpCode)(ref opCode)).Name == "newobj")) { continue; } } } object operand = current.Operand; MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null); if (val != null && IsSuspiciousMethodCall(val)) { list.Add(((MemberReference)((MemberReference)val).DeclaringType).FullName + "." + ((MemberReference)val).Name + "() - " + GetSuspiciousMethodDescription(val)); } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } return list.Distinct().ToList(); } private string GetFieldVisibility(FieldDefinition field) { if (field.IsPublic) { return "public"; } if (field.IsPrivate) { return "private"; } if (field.IsFamily) { return "protected"; } if (field.IsFamilyOrAssembly) { return "protected internal"; } if (field.IsAssembly) { return "internal"; } return "private"; } private string GetPropertyVisibility(PropertyDefinition prop) { MethodDefinition getMethod = prop.GetMethod; MethodDefinition setMethod = prop.SetMethod; if ((getMethod != null && getMethod.IsPublic) || (setMethod != null && setMethod.IsPublic)) { return "public"; } if ((getMethod != null && getMethod.IsFamilyOrAssembly) || (setMethod != null && setMethod.IsFamilyOrAssembly)) { return "protected internal"; } if ((getMethod != null && getMethod.IsFamily) || (setMethod != null && setMethod.IsFamily)) { return "protected"; } if ((getMethod != null && getMethod.IsAssembly) || (setMethod != null && setMethod.IsAssembly)) { return "internal"; } if ((getMethod != null && getMethod.IsPrivate) || (setMethod != null && setMethod.IsPrivate)) { return "private"; } if (getMethod == null && setMethod == null) { return "public"; } return "public"; } private string GenerateClassStructure(TypeDefinition typeDef, string highlightMethodName = null) { //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) if (typeDef == null) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("// Class Outline: " + ((MemberReference)typeDef).FullName); if (typeDef.BaseType != null && ((MemberReference)typeDef.BaseType).FullName != "System.Object") { stringBuilder.AppendLine("// Inherits from: " + ((MemberReference)typeDef.BaseType).FullName); } if (typeDef.HasInterfaces) { Enumerator enumerator = typeDef.Interfaces.GetEnumerator(); try { while (enumerator.MoveNext()) { InterfaceImplementation current = enumerator.Current; stringBuilder.AppendLine("// Implements: " + ((MemberReference)current.InterfaceType).FullName); } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } } string text = "class"; if (typeDef.IsInterface) { text = "interface"; } else if (typeDef.IsEnum) { text = "enum"; } else if (((TypeReference)typeDef).IsValueType) { text = "struct"; } stringBuilder.AppendLine("public " + text + " " + ((MemberReference)typeDef).Name + " // Simplified declaration"); stringBuilder.AppendLine("{"); List list = ((IEnumerable)typeDef.Fields).ToList(); if (list.Any()) { stringBuilder.AppendLine(" // Fields"); foreach (FieldDefinition item in list.Take(10)) { stringBuilder.AppendLine(" " + GetFieldVisibility(item) + " " + (item.IsStatic ? "static " : "") + ((MemberReference)((FieldReference)item).FieldType).Name + " " + ((MemberReference)item).Name + ";"); } if (list.Count > 10) { stringBuilder.AppendLine($" // ... and {list.Count - 10} more fields"); } stringBuilder.AppendLine(); } List list2 = ((IEnumerable)typeDef.Properties).ToList(); if (list2.Any()) { stringBuilder.AppendLine(" // Properties"); foreach (PropertyDefinition item2 in list2.Take(10)) { string text2 = "{ "; if (item2.GetMethod != null) { text2 += "get; "; } if (item2.SetMethod != null) { text2 += "set; "; } text2 += "}"; MethodDefinition getMethod = item2.GetMethod; int num; if (getMethod == null || !getMethod.IsStatic) { MethodDefinition setMethod = item2.SetMethod; num = ((setMethod != null && setMethod.IsStatic) ? 1 : 0); } else { num = 1; } bool flag = (byte)num != 0; stringBuilder.AppendLine(" " + GetPropertyVisibility(item2) + " " + (flag ? "static " : "") + ((MemberReference)((PropertyReference)item2).PropertyType).Name + " " + ((MemberReference)item2).Name + " " + text2); } if (list2.Count > 10) { stringBuilder.AppendLine($" // ... and {list2.Count - 10} more properties"); } stringBuilder.AppendLine(); } List list3 = ((IEnumerable)typeDef.Methods).Where((MethodDefinition m) => !m.IsConstructor && !m.IsSpecialName).ToList(); if (list3.Any()) { stringBuilder.AppendLine(" // Methods (signatures only, excluding constructors/property accessors)"); foreach (MethodDefinition item3 in list3.Take(15)) { string text3 = ((highlightMethodName != null && ((MemberReference)item3).Name == highlightMethodName) ? " // <<< Method with finding" : ""); stringBuilder.AppendLine(" " + GetMethodVisibility(item3) + " " + (item3.IsStatic ? "static " : "") + ((MemberReference)((MethodReference)item3).ReturnType).Name + " " + ((MemberReference)item3).Name + "(" + GetMethodParameters(item3) + ");" + text3); } if (list3.Count > 15) { stringBuilder.AppendLine($" // ... and {list3.Count - 15} more methods"); } } stringBuilder.AppendLine("}"); return stringBuilder.ToString(); } private string GetMethodVisibility(MethodDefinition method) { if (method.IsPublic) { return "public"; } if (method.IsPrivate) { return "private"; } if (method.IsFamily) { return "protected"; } if (method.IsFamilyOrAssembly) { return "protected internal"; } return "internal"; } private string GetMethodParameters(MethodDefinition method) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) List list = new List(); Enumerator enumerator = ((MethodReference)method).Parameters.GetEnumerator(); try { while (enumerator.MoveNext()) { ParameterDefinition current = enumerator.Current; list.Add(((MemberReference)((ParameterReference)current).ParameterType).Name + " " + ((ParameterReference)current).Name); } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } return string.Join(", ", list); } private string FormatInstruction(Instruction instruction) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) OpCode opCode = instruction.OpCode; string name = ((OpCode)(ref opCode)).Name; if (name == "nop") { return string.Empty; } string text = FormatOperand(instruction.Operand); return (name + " " + text).Trim(); } private string FormatOperand(object operand) { if (operand == null) { return string.Empty; } MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null); if (val != null) { return ((MemberReference)((MemberReference)val).DeclaringType).Name + "." + ((MemberReference)val).Name + "()"; } TypeReference val2 = (TypeReference)((operand is TypeReference) ? operand : null); if (val2 != null) { return ((MemberReference)val2).FullName; } FieldReference val3 = (FieldReference)((operand is FieldReference) ? operand : null); if (val3 != null) { return ((MemberReference)((MemberReference)val3).DeclaringType).Name + "." + ((MemberReference)val3).Name; } if (operand is string text) { return "\"" + text + "\""; } return operand.ToString(); } private string ExtractAssemblyMetadata(string modPath) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown try { if (!File.Exists(modPath)) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(); ReaderParameters val = new ReaderParameters { ReadWrite = false, InMemory = true, ReadSymbols = false }; AssemblyDefinition val2 = AssemblyDefinition.ReadAssembly(modPath, val); try { stringBuilder.AppendLine("- **Assembly Name**: " + ((AssemblyNameReference)val2.Name).Name); stringBuilder.AppendLine($"- **Version**: {((AssemblyNameReference)val2.Name).Version}"); if (!string.IsNullOrEmpty(((AssemblyNameReference)val2.Name).Culture)) { stringBuilder.AppendLine("- **Culture**: " + ((AssemblyNameReference)val2.Name).Culture); } stringBuilder.AppendLine("- **Referenced Assemblies**:"); foreach (AssemblyNameReference item in ((IEnumerable)val2.MainModule.AssemblyReferences).Take(10)) { stringBuilder.AppendLine($" - {item.Name} (v{item.Version})"); } if (val2.MainModule.AssemblyReferences.Count > 10) { stringBuilder.AppendLine($" - *and {val2.MainModule.AssemblyReferences.Count - 10} more...*"); } List list = ((IEnumerable)val2.CustomAttributes).Where((CustomAttribute attr) => ((MemberReference)attr.AttributeType).Name.Contains("Security") || ((MemberReference)attr.AttributeType).Name.Contains("Permission") || ((MemberReference)attr.AttributeType).Name.Contains("Unsafe")).ToList(); if (list.Any()) { stringBuilder.AppendLine("- **Security-Related Attributes**:"); foreach (CustomAttribute item2 in list) { stringBuilder.AppendLine(" - " + ((MemberReference)item2.AttributeType).Name); } } return stringBuilder.ToString(); } finally { ((IDisposable)val2)?.Dispose(); } } catch (Exception ex) { _logger.Error("Failed to extract assembly metadata from " + modPath + ": " + ex.Message); return string.Empty; } } public bool SavePromptToFile(string modPath, List findings, string outputDirectory) { try { string contents = GeneratePrompt(modPath, findings); string fileName = Path.GetFileName(modPath); Directory.CreateDirectory(outputDirectory); string path = Path.Combine(outputDirectory, fileName + ".prompt.md"); File.WriteAllText(path, contents); return true; } catch (Exception ex) { _logger.Error("Failed to save prompt to file: " + ex.Message); return false; } } private static string FormatSeverityLabel(Severity severity) { if (1 == 0) { } string result = severity switch { Severity.Critical => "CRITICAL", Severity.High => "HIGH", Severity.Medium => "MEDIUM", Severity.Low => "LOW", _ => severity.ToString().ToUpper(), }; if (1 == 0) { } return result; } } public static class HashUtility { public static string CalculateFileHash(string filePath) { try { if (!File.Exists(filePath)) { return "File not found: " + filePath; } using SHA256 sHA = SHA256.Create(); using FileStream inputStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); byte[] array = sHA.ComputeHash(inputStream); return BitConverter.ToString(array).Replace("-", "").ToLowerInvariant(); } catch (UnauthorizedAccessException) { return "Access denied"; } catch (IOException ex2) { return "IO Error: " + ex2.Message; } catch (Exception ex3) { return "Error: " + ex3.Message; } } public static string CalculateBytesHash(byte[] bytes) { try { if (bytes == null || bytes.Length == 0) { return string.Empty; } using SHA256 sHA = SHA256.Create(); byte[] array = sHA.ComputeHash(bytes); return BitConverter.ToString(array).Replace("-", "").ToLowerInvariant(); } catch { return string.Empty; } } public static bool IsValidHash(string hash) { if (string.IsNullOrWhiteSpace(hash)) { return false; } if (hash.Length != 64) { return false; } foreach (char c in hash) { if ((c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F')) { return false; } } return true; } } public class ReportUploadService { private const int SmallFileLimitBytes = 33554432; private const int DefaultTimeoutSeconds = 30; private const int MaxRetries = 2; private static readonly object InFlightUploadsLock = new object(); private static readonly HashSet InFlightUploads = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly IConfigManager _configManager; private readonly Action _logInfo; private readonly Action _logWarn; private readonly Action _logError; public ReportUploadService(IConfigManager configManager, Action logInfo, Action logWarn, Action logError) { _configManager = configManager; _logInfo = logInfo ?? ((Action)delegate { }); _logWarn = logWarn ?? ((Action)delegate { }); _logError = logError ?? ((Action)delegate { }); } public void UploadReportNonBlocking(byte[] assemblyBytes, string filename, SubmissionMetadata metadata, string apiBaseUrl) { if (assemblyBytes == null || assemblyBytes.Length == 0) { _logWarn("ReportUploadService: No assembly bytes to upload for " + (filename ?? "")); return; } if (string.IsNullOrWhiteSpace(apiBaseUrl)) { _logWarn("ReportUploadService: API base URL not configured"); return; } string baseUrl = apiBaseUrl.TrimEnd('/'); Task.Run(async delegate { try { await UploadReportAsync(assemblyBytes, filename, metadata, baseUrl); } catch (Exception ex) { Exception ex2 = ex; _logError("ReportUploadService: Upload failed: " + ex2.Message); } }); } public async Task UploadReportAsync(byte[] assemblyBytes, string filename, SubmissionMetadata metadata, string apiBaseUrl) { if (assemblyBytes == null || assemblyBytes.Length == 0) { _logWarn("ReportUploadService: No assembly bytes to upload for " + (filename ?? "")); return; } string assemblyHash = HashUtility.CalculateBytesHash(assemblyBytes); if (HashUtility.IsValidHash(assemblyHash)) { if (_configManager?.IsReportHashUploaded(assemblyHash) ?? false) { _logInfo("ReportUploadService: Skipping duplicate upload for " + filename + " (" + assemblyHash + ")"); return; } lock (InFlightUploadsLock) { if (InFlightUploads.Contains(assemblyHash)) { _logInfo("ReportUploadService: Upload already queued for " + filename + " (" + assemblyHash + ")"); return; } InFlightUploads.Add(assemblyHash); } } string baseUrl = apiBaseUrl.TrimEnd('/'); bool useDirectUpload = assemblyBytes.Length <= 33554432; _logInfo(string.Format("ReportUploadService: Uploading {0} ({1} bytes) via {2}", filename, assemblyBytes.Length, useDirectUpload ? "POST /files" : "presigned upload")); try { for (int attempt = 0; attempt <= 2; attempt++) { try { if (!useDirectUpload) { await UploadViaPresignedUrlAsync(assemblyBytes, filename, metadata, baseUrl); } else { await UploadViaPostFilesAsync(assemblyBytes, filename, metadata, baseUrl); } if (HashUtility.IsValidHash(assemblyHash)) { _configManager?.MarkReportHashUploaded(assemblyHash); } _logInfo("ReportUploadService: Successfully uploaded " + filename); break; } catch (Exception ex) { _logWarn($"ReportUploadService: Attempt {attempt + 1} failed: {ex.Message}"); if (attempt == 2) { throw; } await Task.Delay(1000 * (attempt + 1)); } } } finally { if (HashUtility.IsValidHash(assemblyHash)) { lock (InFlightUploadsLock) { InFlightUploads.Remove(assemblyHash); } } } } private async Task UploadViaPostFilesAsync(byte[] assemblyBytes, string filename, SubmissionMetadata metadata, string baseUrl) { using HttpClient client = CreateHttpClient(); using MultipartFormDataContent content = new MultipartFormDataContent(); string safeFilename = RedactionHelper.RedactFilename(filename); ByteArrayContent fileContent = new ByteArrayContent(assemblyBytes); fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "\"file\"", FileName = "\"" + safeFilename + "\"" }; content.Add(fileContent); string metadataJson = SerializeMetadata(metadata); if (!string.IsNullOrEmpty(metadataJson)) { StringContent metaContent = new StringContent(metadataJson, Encoding.UTF8, "application/json"); metaContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "\"metadata\"" }; content.Add(metaContent); } string url = baseUrl + "/files"; HttpResponseMessage response = await client.PostAsync(url, content); if (!response.IsSuccessStatusCode) { throw new IOException(string.Format(arg1: await response.Content.ReadAsStringAsync(), format: "POST /files returned {0}: {1}", arg0: (int)response.StatusCode)); } } private async Task UploadViaPresignedUrlAsync(byte[] assemblyBytes, string filename, SubmissionMetadata metadata, string baseUrl) { using HttpClient client = CreateHttpClient(); string metadataJson = SerializeMetadata(metadata); string metadataBase64Url = (string.IsNullOrEmpty(metadataJson) ? null : Convert.ToBase64String(Encoding.UTF8.GetBytes(metadataJson)).Replace('+', '-').Replace('/', '_') .TrimEnd('=')); List query = new List { "filename=" + Uri.EscapeDataString(filename), "contentType=application/octet-stream" }; if (!string.IsNullOrEmpty(metadataBase64Url)) { query.Add("metadata=" + Uri.EscapeDataString(metadataBase64Url)); } string getUrl = baseUrl + "/files/upload_url?" + string.Join("&", query); HttpResponseMessage getResponse = await client.GetAsync(getUrl); if (!getResponse.IsSuccessStatusCode) { throw new IOException(string.Format(arg1: await getResponse.Content.ReadAsStringAsync(), format: "GET /files/upload_url returned {0}: {1}", arg0: (int)getResponse.StatusCode)); } var (uploadUrl, _) = ParseUploadUrlResponse(await getResponse.Content.ReadAsStringAsync()); if (string.IsNullOrEmpty(uploadUrl)) { throw new IOException("Failed to parse upload URL from response"); } using HttpClient putClient = CreateHttpClient(); using ByteArrayContent putContent = new ByteArrayContent(assemblyBytes); putContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); HttpResponseMessage putResponse = await putClient.PutAsync(uploadUrl, putContent); if (!putResponse.IsSuccessStatusCode) { throw new IOException(string.Format(arg1: await putResponse.Content.ReadAsStringAsync(), format: "PUT to presigned URL returned {0}: {1}", arg0: (int)putResponse.StatusCode)); } } private static HttpClient CreateHttpClient() { try { if ((ServicePointManager.SecurityProtocol & SecurityProtocolType.Tls12) == 0) { ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; } } catch { } return new HttpClient { Timeout = TimeSpan.FromSeconds(30.0) }; } private static string SerializeMetadata(SubmissionMetadata metadata) { if (metadata == null) { return null; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append('{'); List list = new List(); if (!string.IsNullOrEmpty(metadata.LoaderType)) { list.Add("\"loaderType\":\"" + EscapeJson(metadata.LoaderType) + "\""); } if (!string.IsNullOrEmpty(metadata.LoaderVersion)) { list.Add("\"loaderVersion\":\"" + EscapeJson(metadata.LoaderVersion) + "\""); } if (!string.IsNullOrEmpty(metadata.PluginVersion)) { list.Add("\"pluginVersion\":\"" + EscapeJson(metadata.PluginVersion) + "\""); } if (!string.IsNullOrEmpty(metadata.GameVersion)) { list.Add("\"gameVersion\":\"" + EscapeJson(metadata.GameVersion) + "\""); } if (!string.IsNullOrEmpty(metadata.ModName)) { list.Add("\"modName\":\"" + EscapeJson(metadata.ModName) + "\""); } if (!string.IsNullOrEmpty(metadata.SourceHint)) { list.Add("\"sourceHint\":\"" + EscapeJson(metadata.SourceHint) + "\""); } if (!string.IsNullOrEmpty(metadata.ConsentVersion)) { list.Add("\"consentVersion\":\"" + EscapeJson(metadata.ConsentVersion) + "\""); } if (!string.IsNullOrEmpty(metadata.ConsentTimestamp)) { list.Add("\"consentTimestamp\":\"" + EscapeJson(metadata.ConsentTimestamp) + "\""); } if (metadata.FindingSummary != null && metadata.FindingSummary.Count > 0) { string[] value = metadata.FindingSummary.Select((FindingSummaryItem f) => "{" + (string.IsNullOrEmpty(f.RuleId) ? "" : ("\"ruleId\":\"" + EscapeJson(f.RuleId) + "\",")) + (string.IsNullOrEmpty(f.Description) ? "" : ("\"description\":\"" + EscapeJson(f.Description) + "\",")) + (string.IsNullOrEmpty(f.Severity) ? "" : ("\"severity\":\"" + EscapeJson(f.Severity) + "\",")) + (string.IsNullOrEmpty(f.Location) ? "" : ("\"location\":\"" + EscapeJson(f.Location) + "\"")) + "}").ToArray(); list.Add("\"findingSummary\":[" + string.Join(",", value) + "]"); } stringBuilder.Append(string.Join(",", list)); stringBuilder.Append('}'); return stringBuilder.ToString(); } private static string EscapeJson(string s) { if (string.IsNullOrEmpty(s)) { return string.Empty; } return s.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\r", "\\r") .Replace("\n", "\\n") .Replace("\t", "\\t"); } private static (string uploadUrl, string submissionId) ParseUploadUrlResponse(string json) { try { string text = ExtractJsonString(json, "upload_url"); string item = ExtractJsonString(json, "submission_id"); if (string.IsNullOrEmpty(text) && json.Contains("\"data\"")) { int startIndex = json.IndexOf("\"data\"", StringComparison.Ordinal); string json2 = json.Substring(startIndex); text = ExtractJsonString(json2, "upload_url"); item = ExtractJsonString(json2, "submission_id"); } return (uploadUrl: text, submissionId: item); } catch { return (uploadUrl: null, submissionId: null); } } private static string ExtractJsonString(string json, string key) { string value = "\"" + key + "\""; int num = json.IndexOf(value, StringComparison.OrdinalIgnoreCase); if (num < 0) { return null; } num = json.IndexOf(':', num); if (num < 0) { return null; } num = json.IndexOf('"', num); if (num < 0) { return null; } int num2 = num + 1; int i; for (i = num2; i < json.Length && json[i] != '"'; i++) { if (json[i] == '\\') { i++; } } string text = json.Substring(num2, i - num2); return text.Replace("\\\"", "\"").Replace("\\\\", "\\"); } } public static class RedactionHelper { public static string RedactFilename(string pathOrFilename) { if (string.IsNullOrWhiteSpace(pathOrFilename)) { return "unknown.bin"; } string fileName = Path.GetFileName(pathOrFilename); StringBuilder stringBuilder = new StringBuilder(); string text = fileName; foreach (char c in text) { if (char.IsLetterOrDigit(c) || c == '.' || c == '_' || c == '-') { stringBuilder.Append(c); } } return (stringBuilder.Length > 0) ? stringBuilder.ToString() : "unknown.bin"; } public static string RedactLocation(string location) { if (string.IsNullOrWhiteSpace(location)) { return location; } if (LooksLikeAbsolutePath(location)) { string fileName = Path.GetFileName(location); if (!string.IsNullOrEmpty(fileName)) { return fileName + " [path redacted]"; } return "[path redacted]"; } return location; } public static string GetPathFingerprint(string path) { if (string.IsNullOrWhiteSpace(path)) { return null; } try { byte[] bytes = Encoding.UTF8.GetBytes(path.ToLowerInvariant()); using SHA256 sHA = SHA256.Create(); byte[] array = sHA.ComputeHash(bytes); string text = BitConverter.ToString(array).Replace("-", "").ToLowerInvariant(); return (text.Length >= 12) ? text.Substring(0, 12) : text; } catch { return null; } } private static bool LooksLikeAbsolutePath(string s) { if (string.IsNullOrEmpty(s) || s.Length < 3) { return false; } if (s[0] == '/' || s[0] == '\\') { return true; } if (s.Length >= 3 && char.IsLetter(s[0]) && s[1] == ':' && (s[2] == '\\' || s[2] == '/')) { return true; } if (s.Contains(":\\") || s.Contains(":/")) { return true; } return false; } } public class ThreatVerdictBuilder { private sealed class ThreatFamilyPresentation { public string DisplayName { get; set; } public string Summary { get; set; } public string ReferenceUrl { get; set; } } private static class ThreatFamilyPresentationCatalog { private static readonly IReadOnlyDictionary Families = new Dictionary(StringComparer.Ordinal) { ["family-resource-shell32-tempcmd-v2"] = new ThreatFamilyPresentation { DisplayName = "Embedded resource temp CMD dropper", Summary = "Writes an embedded payload into a temporary .cmd file and runs it through ShellExecuteEx or Process.Start.", ReferenceUrl = "https://mlvscan.com/advisories/families/resource-shell32-tempcmd-v2" }, ["family-powershell-iwr-dlbat-v1"] = new ThreatFamilyPresentation { DisplayName = "PowerShell IWR temp batch downloader", Summary = "Uses hidden PowerShell to download a TEMP batch file, run it, then clean it up.", ReferenceUrl = "https://mlvscan.com/advisories/families/powershell-iwr-dlbat-v1" }, ["family-webdownload-stage-exec-v2"] = new ThreatFamilyPresentation { DisplayName = "Web download staged payload executor", Summary = "Downloads a payload into TEMP and immediately executes it via a hidden process chain, regardless of whether the stager uses WebClient or HttpClient.", ReferenceUrl = "https://mlvscan.com/advisories/families/webdownload-stage-exec-v2" }, ["family-webdownload-stage-exec-v3"] = new ThreatFamilyPresentation { DisplayName = "Web download staged payload executor", Summary = "Downloads a payload to TEMP through WebClient, HttpClient, or a similar network API and then executes it via hidden or shell-assisted process launch.", ReferenceUrl = "https://mlvscan.com/advisories/families/webdownload-stage-exec-v3" }, ["family-embedded-resource-script-stager-v1"] = new ThreatFamilyPresentation { DisplayName = "Embedded resource script stager", Summary = "Stores a script payload in a referenced embedded resource, stages it at runtime, and uses hidden shell or PowerShell execution to retrieve or run a payload.", ReferenceUrl = "https://mlvscan.com/advisories/families/embedded-resource-script-stager-v1" }, ["family-remote-script-pipe-shell-v1"] = new ThreatFamilyPresentation { DisplayName = "Remote script piped to shell", Summary = "Launches a command shell that retrieves a remote script and pipes it directly into another shell interpreter.", ReferenceUrl = "https://mlvscan.com/advisories/families/remote-script-pipe-shell-v1" }, ["family-encoded-powershell-tempcmd-stager-v1"] = new ThreatFamilyPresentation { DisplayName = "Encoded PowerShell temp command stager", Summary = "Numeric-encoded strings reconstruct a hidden cmd.exe or PowerShell launcher that downloads a command script into TEMP and starts it hidden.", ReferenceUrl = "https://mlvscan.com/advisories/families/encoded-powershell-tempcmd-stager-v1" }, ["family-hex-remote-config-tempcmd-stager-v1"] = new ThreatFamilyPresentation { DisplayName = "Hex remote config temp CMD stager", Summary = "Hex and byte-array reconstructed strings hide remote command configuration, reflected WebClient retrieval, temporary command-file staging, and hidden cmd.exe execution.", ReferenceUrl = "https://mlvscan.com/advisories/families/hex-remote-config-tempcmd-stager-v1" }, ["family-dynamic-assembly-reflection-loader-v1"] = new ThreatFamilyPresentation { DisplayName = "Dynamic assembly reflection loader", Summary = "Opaque assembly bytes are loaded at runtime and invoked through reflection, separating a visible mod wrapper from the executable payload.", ReferenceUrl = "https://mlvscan.com/advisories/families/dynamic-assembly-reflection-loader-v1" }, ["family-obfuscated-metadata-loader-v1"] = new ThreatFamilyPresentation { DisplayName = "Obfuscated metadata-backed loader", Summary = "Decodes hidden launcher content from numeric strings and assembly metadata at runtime.", ReferenceUrl = "https://mlvscan.com/advisories/families/obfuscated-metadata-loader-v1" }, ["family-obfuscated-metadata-loader-v2"] = new ThreatFamilyPresentation { DisplayName = "Obfuscated metadata-backed loader", Summary = "Decodes hidden launcher content from numeric strings, metadata, or manifest resources and uses reflection to reach the payload.", ReferenceUrl = "https://mlvscan.com/advisories/families/obfuscated-metadata-loader-v2" } }; public static ThreatFamilyPresentation Get(string familyId) { if (!string.IsNullOrWhiteSpace(familyId) && Families.TryGetValue(familyId, out var value)) { return value; } return new ThreatFamilyPresentation { DisplayName = "Known malware family", Summary = "This mod matches a previously analyzed malware family.", ReferenceUrl = string.Empty }; } } private readonly ThreatFamilyClassifier _classifier = new ThreatFamilyClassifier(); private readonly ThreatDispositionClassifier _dispositionClassifier = new ThreatDispositionClassifier(); public ScannedPluginResult Build(string filePath, string fileHash, List findings) { if (findings == null) { findings = new List(); } return new ScannedPluginResult { FilePath = (filePath ?? string.Empty), FileHash = (fileHash ?? string.Empty), Findings = findings, ThreatVerdict = BuildVerdict(findings, fileHash) }; } private ThreatVerdictInfo BuildVerdict(List findings, string fileHash) { List list = _classifier.Classify(findings, fileHash).ToList(); ThreatDispositionResult threatDispositionResult = _dispositionClassifier.Classify(findings, list); List list2 = list.Select(MapFamily).ToList(); ThreatFamilyReference threatFamilyReference = (from f in list2 orderby f.ExactHashMatch descending, f.Confidence descending select f).ThenBy((ThreatFamilyReference f) => f.DisplayName, StringComparer.Ordinal).FirstOrDefault(); if (threatDispositionResult.Classification == ThreatDispositionClassification.KnownThreat && threatFamilyReference != null && threatFamilyReference.ExactHashMatch) { return new ThreatVerdictInfo { Kind = ThreatVerdictKind.KnownMaliciousSample, Title = "Known malicious sample match", Summary = "This mod is likely malware because it exactly matches a previously confirmed malicious sample and was blocked before execution.", Confidence = 1.0, ShouldBypassThreshold = false, PrimaryFamily = threatFamilyReference, Families = list2 }; } if (threatDispositionResult.Classification == ThreatDispositionClassification.KnownThreat && threatFamilyReference != null) { return new ThreatVerdictInfo { Kind = ThreatVerdictKind.KnownMalwareFamily, Title = "Known malware family match", Summary = "This mod is likely malware because it matches the previously analyzed malware family \"" + threatFamilyReference.DisplayName + "\" and was blocked before execution.", Confidence = threatFamilyReference.Confidence, ShouldBypassThreshold = false, PrimaryFamily = threatFamilyReference, Families = list2 }; } if (threatDispositionResult.Classification == ThreatDispositionClassification.Suspicious) { return new ThreatVerdictInfo { Kind = ThreatVerdictKind.Suspicious, Title = "Suspicious mod", Summary = "This mod triggered correlated suspicious behavior and was blocked as a precaution. It may still be a false positive and should be reviewed before assuming infection.", Confidence = 0.0, ShouldBypassThreshold = false, PrimaryFamily = threatFamilyReference, Families = list2 }; } return new ThreatVerdictInfo { Kind = ThreatVerdictKind.None, Title = "No threat verdict", Summary = "No suspicious behavior patterns were retained for this mod.", Confidence = 0.0, ShouldBypassThreshold = false, PrimaryFamily = threatFamilyReference, Families = list2 }; } private static ThreatFamilyReference MapFamily(ThreatFamilyMatch match) { ThreatFamilyPresentation threatFamilyPresentation = ThreatFamilyPresentationCatalog.Get(match.FamilyId); return new ThreatFamilyReference { FamilyId = match.FamilyId, DisplayName = threatFamilyPresentation.DisplayName, Summary = (string.IsNullOrWhiteSpace(match.Summary) ? threatFamilyPresentation.Summary : match.Summary), MatchKind = match.MatchKind.ToString(), TechnicalName = match.DisplayName, ReferenceUrl = threatFamilyPresentation.ReferenceUrl, Confidence = match.Confidence, ExactHashMatch = match.ExactHashMatch, MatchedRules = match.MatchedRules.ToList(), Evidence = match.Evidence.Select((ThreatFamilyEvidence evidence) => string.IsNullOrWhiteSpace(evidence.Kind) ? evidence.Value : (evidence.Kind + ": " + evidence.Value)).ToList() }; } } public static class ThreatVerdictTextFormatter { public static string GetVerdictLabel(ThreatVerdictInfo threatVerdict) { return (threatVerdict?.Title ?? string.Empty).Trim(); } public static string GetScanStatusLabel(ScanStatusInfo scanStatus) { return (scanStatus?.Title ?? string.Empty).Trim(); } public static string GetOutcomeLabel(ScannedPluginResult scanResult) { if (ScanResultFacts.HasThreatVerdict(scanResult)) { return GetVerdictLabel(scanResult.ThreatVerdict); } if (ScanResultFacts.RequiresManualReview(scanResult)) { return GetScanStatusLabel(scanResult.ScanStatus); } return string.Empty; } public static string GetOutcomeSummary(ScannedPluginResult scanResult) { if (ScanResultFacts.HasThreatVerdict(scanResult)) { return scanResult?.ThreatVerdict?.Summary ?? string.Empty; } if (ScanResultFacts.RequiresManualReview(scanResult)) { return scanResult?.ScanStatus?.Summary ?? string.Empty; } return string.Empty; } public static string GetPrimaryFamilyLabel(ThreatVerdictInfo threatVerdict) { return threatVerdict?.PrimaryFamily?.DisplayName ?? string.Empty; } public static string GetConfidenceLabel(ThreatVerdictInfo threatVerdict) { if (threatVerdict == null || threatVerdict.Confidence <= 0.0) { return string.Empty; } return $"{threatVerdict.Confidence * 100.0:F0}%"; } public static List GetTopFindingSummaries(List findings, int maxItems = 3) { if (findings == null || findings.Count == 0) { return new List(); } return (from item in (from f in findings group f by f.Description into @group select new { Description = @group.Key, Severity = @group.Max((ScanFinding f) => f.Severity), Count = @group.Count() } into item orderby (int)item.Severity descending, item.Count descending select item).ThenBy(item => item.Description, StringComparer.Ordinal).Take(maxItems) select string.Format("[{0}] {1} ({2} instance{3})", item.Severity, item.Description, item.Count, (item.Count == 1) ? string.Empty : "s")).ToList(); } public static void WriteThreatVerdictSection(TextWriter writer, ThreatVerdictInfo threatVerdict) { if (writer == null || threatVerdict == null || threatVerdict.Kind == ThreatVerdictKind.None) { return; } writer.WriteLine("Threat Verdict:"); writer.WriteLine("- Verdict: " + threatVerdict.Title); writer.WriteLine("- Summary: " + threatVerdict.Summary); string primaryFamilyLabel = GetPrimaryFamilyLabel(threatVerdict); if (!string.IsNullOrWhiteSpace(primaryFamilyLabel)) { writer.WriteLine("- Family: " + primaryFamilyLabel); } string confidenceLabel = GetConfidenceLabel(threatVerdict); if (!string.IsNullOrWhiteSpace(confidenceLabel)) { writer.WriteLine("- Confidence: " + confidenceLabel); } if (threatVerdict.PrimaryFamily != null) { if (!string.IsNullOrWhiteSpace(threatVerdict.PrimaryFamily.MatchKind)) { writer.WriteLine("- Match Type: " + threatVerdict.PrimaryFamily.MatchKind); } if (!string.IsNullOrWhiteSpace(threatVerdict.PrimaryFamily.TechnicalName)) { writer.WriteLine("- Technical Match: " + threatVerdict.PrimaryFamily.TechnicalName); } if (!string.IsNullOrWhiteSpace(threatVerdict.PrimaryFamily.ReferenceUrl)) { writer.WriteLine("- Family Reference: " + threatVerdict.PrimaryFamily.ReferenceUrl); } if (threatVerdict.PrimaryFamily.MatchedRules.Count > 0) { writer.WriteLine("- Matched Rules: " + string.Join(", ", threatVerdict.PrimaryFamily.MatchedRules)); } if (threatVerdict.PrimaryFamily.Evidence.Count > 0) { writer.WriteLine("- Evidence:"); foreach (string item in threatVerdict.PrimaryFamily.Evidence) { writer.WriteLine(" - " + item); } } } writer.WriteLine(); } public static void WriteScanStatusSection(TextWriter writer, ScanStatusInfo scanStatus) { if (writer != null && scanStatus != null && scanStatus.Kind != ScanStatusKind.Complete) { writer.WriteLine("Scan Status:"); writer.WriteLine("- Status: " + scanStatus.Title); writer.WriteLine("- Summary: " + scanStatus.Summary); writer.WriteLine(); } } } } namespace MLVScan.Services.Configuration { public static class LegacyConfigCleanup { private static readonly string[] ObsoleteConfigKeys = new string[3] { "DisableThreshold", "MinSeverityForDisable", "SuspiciousThreshold" }; public static bool TryRemoveObsoleteIniEntries(string filePath, string sectionName, out string[] removedKeys) { removedKeys = Array.Empty(); if (string.IsNullOrWhiteSpace(filePath) || string.IsNullOrWhiteSpace(sectionName) || !File.Exists(filePath)) { return false; } HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); List list = new List(); bool flag = false; bool flag2 = false; string[] array = File.ReadAllLines(filePath); foreach (string text in array) { string line = text.Trim(); string key; if (TryGetSectionName(line, out var sectionName2)) { flag = string.Equals(sectionName2, sectionName, StringComparison.OrdinalIgnoreCase); list.Add(text); } else if (flag && TryGetIniKey(line, out key) && ObsoleteConfigKeys.Contains(key, StringComparer.OrdinalIgnoreCase)) { hashSet.Add(key); flag2 = true; } else { list.Add(text); } } if (!flag2) { return false; } File.WriteAllLines(filePath, list); removedKeys = hashSet.OrderBy((string result) => result, StringComparer.OrdinalIgnoreCase).ToArray(); return true; } private static bool TryGetSectionName(string line, out string sectionName) { sectionName = string.Empty; if (line.Length >= 3 && line[0] == '[') { if (line[line.Length - 1] == ']') { sectionName = line.Substring(1, line.Length - 1 - 1).Trim(); return !string.IsNullOrWhiteSpace(sectionName); } } return false; } private static bool TryGetIniKey(string line, out string key) { key = string.Empty; if (string.IsNullOrWhiteSpace(line) || line.StartsWith(";") || line.StartsWith("#")) { return false; } int num = line.IndexOf('='); if (num <= 0) { return false; } key = line.Substring(0, num).Trim(); return !string.IsNullOrWhiteSpace(key); } } } namespace MLVScan.Services.Scope { public sealed class TargetAssemblyScopeFilter { private static readonly string[][] ResolverOnlySegmentSequences = new string[9][] { new string[2] { "*_Data", "Managed" }, new string[2] { "MelonLoader", "Managed" }, new string[2] { "MelonLoader", "net35" }, new string[2] { "MelonLoader", "net6" }, new string[2] { "BepInEx", "core" }, new string[2] { "BepInEx", "cache" }, new string[2] { "BepInEx", "interop" }, new string[2] { ".nuget", "packages" }, new string[2] { "dotnet", "shared" } }; public IReadOnlyList BuildEffectiveRoots(IEnumerable candidateRoots, MLVScanConfig config) { HashSet hashSet = new HashSet(GetPathComparer()); IEnumerable first = candidateRoots ?? Array.Empty(); string[] second = config?.AdditionalTargetRoots ?? Array.Empty(); string[] roots = config?.ExcludedTargetRoots ?? Array.Empty(); foreach (string item in first.Concat(second)) { if (!string.IsNullOrWhiteSpace(item)) { string text = Normalize(item); if (Directory.Exists(text) && !IsResolverOnlyRoot(text) && !IsUnderAny(text, roots)) { hashSet.Add(text); } } } return hashSet.OrderBy((string root) => root, GetPathComparer()).ToList(); } public bool IsTargetAssembly(string assemblyPath, IReadOnlyCollection effectiveRoots, MLVScanConfig config) { if (string.IsNullOrWhiteSpace(assemblyPath)) { return false; } if (!assemblyPath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) { return false; } string fullPath = Normalize(assemblyPath); if (!File.Exists(fullPath)) { return false; } string[] roots = config?.ExcludedTargetRoots ?? Array.Empty(); if (IsUnderAny(fullPath, roots)) { return false; } return ((IEnumerable)(((object)effectiveRoots) ?? ((object)Array.Empty()))).Any((string root) => IsTargetAssemblyUnderRoot(fullPath, root)); } private static bool IsResolverOnlyRoot(string path) { if (string.IsNullOrWhiteSpace(path)) { return false; } string[] pathSegments = path.Split(new char[2] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries); return ResolverOnlySegmentSequences.Any((string[] sequence) => EndsWithSegmentSequence(pathSegments, sequence)); } private static bool IsTargetAssemblyUnderRoot(string fullPath, string root) { if (!IsUnderRoot(fullPath, root)) { return false; } string relativePath = Path.GetRelativePath(root, fullPath); string[] relativeSegments = relativePath.Split(new char[2] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries); return !ResolverOnlySegmentSequences.Any((string[] sequence) => StartsWithSegmentSequence(relativeSegments, sequence)); } private static bool StartsWithSegmentSequence(IReadOnlyList pathSegments, IReadOnlyList candidateSegments) { if (pathSegments.Count < candidateSegments.Count) { return false; } for (int i = 0; i < candidateSegments.Count; i++) { if (!SegmentMatches(pathSegments[i], candidateSegments[i])) { return false; } } return true; } private static bool EndsWithSegmentSequence(IReadOnlyList pathSegments, IReadOnlyList candidateSegments) { if (pathSegments.Count < candidateSegments.Count) { return false; } int num = pathSegments.Count - candidateSegments.Count; for (int i = 0; i < candidateSegments.Count; i++) { if (!SegmentMatches(pathSegments[num + i], candidateSegments[i])) { return false; } } return true; } private static bool SegmentMatches(string actualSegment, string candidateSegment) { if (candidateSegment.StartsWith("*", StringComparison.Ordinal)) { return actualSegment.EndsWith(candidateSegment.Substring(1), StringComparison.OrdinalIgnoreCase); } return string.Equals(actualSegment, candidateSegment, StringComparison.OrdinalIgnoreCase); } private static bool IsUnderAny(string fullPath, IEnumerable roots) { foreach (string root in roots) { if (string.IsNullOrWhiteSpace(root) || !IsUnderRoot(fullPath, Normalize(root))) { continue; } return true; } return false; } private static bool IsUnderRoot(string fullPath, string root) { string value = (root.EndsWith(Path.DirectorySeparatorChar) ? root : (root + Path.DirectorySeparatorChar)); StringComparison pathComparison = GetPathComparison(); return fullPath.StartsWith(value, pathComparison) || string.Equals(fullPath, root, pathComparison); } private static string Normalize(string path) { return Path.GetFullPath(path); } private static StringComparer GetPathComparer() { return (RuntimeInformationHelper.IsWindows || RuntimeInformationHelper.IsMacOs) ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; } private static StringComparison GetPathComparison() { return (RuntimeInformationHelper.IsWindows || RuntimeInformationHelper.IsMacOs) ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; } } } namespace MLVScan.Services.Resolution { internal static class AssemblyResolverCatalogBuilder { public static ResolverCatalog Build(IEnumerable roots) { List list = new List(); HashSet hashSet = new HashSet(StringComparer.Ordinal); List list2 = new List(); foreach (ResolverRoot item in (from root in roots where !string.IsNullOrWhiteSpace(root.Path) && Directory.Exists(root.Path) orderby root.Priority select root).ThenBy((ResolverRoot root) => GetComparisonKey(root.Path), StringComparer.Ordinal)) { foreach (string item2 in EnumerateAssemblyPaths(item.Path)) { try { string fullPath = Path.GetFullPath(item2); string comparisonKey = GetComparisonKey(fullPath); if (hashSet.Add(comparisonKey) && TryReadAssemblyIdentity(fullPath, out var candidate)) { candidate.Priority = item.Priority; list.Add(candidate); FileInfo fileInfo = new FileInfo(fullPath); string text = ComputeContentFingerprint(fullPath); list2.Add($"{candidate.SimpleName}|{candidate.Version}|{candidate.PublicKeyToken}|{item.Priority}|{comparisonKey}|{fileInfo.Length}|{fileInfo.LastWriteTimeUtc.Ticks}|{text}"); } } catch (UnauthorizedAccessException) { } catch (FileNotFoundException) { } catch (DirectoryNotFoundException) { } catch (IOException) { } } } Dictionary> candidatesBySimpleName = list.GroupBy((ResolverCatalogCandidate resolverCatalogCandidate) => resolverCatalogCandidate.SimpleName, StringComparer.OrdinalIgnoreCase).ToDictionary, string, IReadOnlyList>((IGrouping group) => group.Key, (IGrouping group) => group.OrderBy((ResolverCatalogCandidate resolverCatalogCandidate) => resolverCatalogCandidate.Priority).ThenBy((ResolverCatalogCandidate resolverCatalogCandidate) => GetComparisonKey(resolverCatalogCandidate.Path), StringComparer.Ordinal).ToArray(), StringComparer.OrdinalIgnoreCase); return ResolverCatalog.Create(ComputeFingerprint(list2), candidatesBySimpleName); } private static bool TryReadAssemblyIdentity(string path, out ResolverCatalogCandidate candidate) { candidate = null; try { AssemblyName assemblyName = AssemblyName.GetAssemblyName(path); candidate = new ResolverCatalogCandidate { SimpleName = (assemblyName.Name ?? string.Empty), FullName = (assemblyName.FullName ?? string.Empty), Version = (assemblyName.Version?.ToString() ?? string.Empty), PublicKeyToken = FormatPublicKeyToken(assemblyName.GetPublicKeyToken()), Path = path }; return !string.IsNullOrWhiteSpace(candidate.SimpleName); } catch { return false; } } private static string ComputeFingerprint(IEnumerable lines) { string s = string.Join("\n", lines.OrderBy((string line) => line, StringComparer.Ordinal)); return HashUtility.CalculateBytesHash(Encoding.UTF8.GetBytes(s)); } private static string ComputeContentFingerprint(string path) { using FileStream inputStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); using SHA256 sHA = SHA256.Create(); byte[] array = sHA.ComputeHash(inputStream); return BitConverter.ToString(array).Replace("-", string.Empty).ToLowerInvariant(); } private static string FormatPublicKeyToken(byte[] token) { if (token == null || token.Length == 0) { return string.Empty; } return BitConverter.ToString(token).Replace("-", string.Empty).ToLowerInvariant(); } private static bool IsAssemblyLike(string path) { string extension = Path.GetExtension(path); return extension.Equals(".dll", StringComparison.OrdinalIgnoreCase) || extension.Equals(".exe", StringComparison.OrdinalIgnoreCase) || extension.Equals(".winmd", StringComparison.OrdinalIgnoreCase); } private static IEnumerable EnumerateAssemblyPaths(string rootPath) { IEnumerator enumerator; try { enumerator = Directory.EnumerateFiles(rootPath, "*", SearchOption.AllDirectories).GetEnumerator(); } catch (UnauthorizedAccessException) { yield break; } catch (FileNotFoundException) { yield break; } catch (DirectoryNotFoundException) { yield break; } catch (IOException) { yield break; } using (enumerator) { while (true) { string current; try { if (!enumerator.MoveNext()) { break; } current = enumerator.Current; } catch (UnauthorizedAccessException) { continue; } catch (FileNotFoundException) { continue; } catch (DirectoryNotFoundException) { continue; } catch (IOException) { continue; } if (IsAssemblyLike(current)) { yield return current; } } } } private static string GetComparisonKey(string path) { string fullPath = Path.GetFullPath(path); return (GetPathComparerForRoot(fullPath) == StringComparer.OrdinalIgnoreCase) ? fullPath.ToUpperInvariant() : fullPath; } private static StringComparer GetPathComparerForRoot(string pathRoot) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return StringComparer.OrdinalIgnoreCase; } string text = FindExistingPathForSensitivityProbe(pathRoot); if (string.IsNullOrWhiteSpace(text)) { return StringComparer.Ordinal; } string alternateCasePath = GetAlternateCasePath(text); if (string.Equals(alternateCasePath, text, StringComparison.Ordinal)) { return StringComparer.Ordinal; } return (File.Exists(alternateCasePath) || Directory.Exists(alternateCasePath)) ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; } private static string FindExistingPathForSensitivityProbe(string path) { string text = Path.GetFullPath(path); while (!string.IsNullOrWhiteSpace(text)) { if (File.Exists(text) || Directory.Exists(text)) { return text; } text = Path.GetDirectoryName(text); } return string.Empty; } private static string GetAlternateCasePath(string path) { char[] array = path.ToCharArray(); for (int i = 0; i < array.Length; i++) { if (char.IsLetter(array[i])) { array[i] = (char.IsUpper(array[i]) ? char.ToLowerInvariant(array[i]) : char.ToUpperInvariant(array[i])); break; } } return new string(array); } } public abstract class CatalogingAssemblyResolverProviderBase : IAssemblyResolverProvider, IResolverCatalogProvider { private readonly object _sync = new object(); private readonly LoaderScanTelemetryHub _telemetry; private ResolverCatalog _catalog; public string ContextFingerprint { get; private set; } protected CatalogingAssemblyResolverProviderBase(LoaderScanTelemetryHub telemetry) { _telemetry = telemetry ?? throw new ArgumentNullException("telemetry"); ContextFingerprint = ResolverCatalog.Empty.Fingerprint; } public void BuildCatalog(IEnumerable targetRoots) { ResolverRoot[] roots = GetStableRoots().Concat(from root in targetRoots ?? Array.Empty() where !string.IsNullOrWhiteSpace(root) select new ResolverRoot(root, 20)).ToArray(); ResolverCatalog resolverCatalog = AssemblyResolverCatalogBuilder.Build(roots); lock (_sync) { _catalog = resolverCatalog; ContextFingerprint = resolverCatalog.Fingerprint; } } public IAssemblyResolver CreateResolver() { ResolverCatalog catalog; lock (_sync) { if (_catalog == null) { _catalog = AssemblyResolverCatalogBuilder.Build(GetStableRoots()); ContextFingerprint = _catalog.Fingerprint; } catalog = _catalog; } return (IAssemblyResolver)(object)new IndexedAssemblyResolver(catalog, _telemetry); } protected abstract IEnumerable GetStableRoots(); } internal interface IResolverCatalogProvider { string ContextFingerprint { get; } void BuildCatalog(IEnumerable targetRoots); } internal sealed class IndexedAssemblyResolver : BaseAssemblyResolver { private readonly ResolverCatalog _catalog; private readonly Dictionary _resolved = new Dictionary(StringComparer.Ordinal); private readonly HashSet _missing = new HashSet(StringComparer.Ordinal); private readonly LoaderScanTelemetryHub _telemetry; public IndexedAssemblyResolver(ResolverCatalog catalog, LoaderScanTelemetryHub telemetry = null) { _catalog = catalog ?? ResolverCatalog.Empty; _telemetry = telemetry; } public override AssemblyDefinition Resolve(AssemblyNameReference name) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown return ((BaseAssemblyResolver)this).Resolve(name, new ReaderParameters { AssemblyResolver = (IAssemblyResolver)(object)this }); } public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) if (name == null) { throw new ArgumentNullException("name"); } string fullName = name.FullName; if (_resolved.TryGetValue(fullName, out var value)) { _telemetry?.IncrementCounter("Resolver.CacheHit", 1L); return value; } if (_missing.Contains(fullName)) { _telemetry?.IncrementCounter("Resolver.NegativeCacheHit", 1L); throw new AssemblyResolutionException(name); } if (!_catalog.CandidatesBySimpleName.TryGetValue(name.Name, out var value2)) { _missing.Add(fullName); _telemetry?.IncrementCounter("Resolver.Miss", 1L); throw new AssemblyResolutionException(name); } foreach (ResolverCatalogCandidate item in OrderCandidates(name, value2)) { try { ReaderParameters val = (ReaderParameters)(((object)parameters) ?? ((object)new ReaderParameters())); val.AssemblyResolver = (IAssemblyResolver)(object)this; AssemblyDefinition val2 = AssemblyDefinition.ReadAssembly(item.Path, val); _resolved[fullName] = val2; _telemetry?.IncrementCounter("Resolver.ResolveHit", 1L); return val2; } catch { } } _missing.Add(fullName); _telemetry?.IncrementCounter("Resolver.Miss", 1L); throw new AssemblyResolutionException(name); } protected override void Dispose(bool disposing) { if (disposing) { foreach (AssemblyDefinition item in _resolved.Values.Distinct()) { item.Dispose(); } _resolved.Clear(); _missing.Clear(); } ((BaseAssemblyResolver)this).Dispose(disposing); } private static IEnumerable OrderCandidates(AssemblyNameReference reference, IEnumerable candidates) { string expectedVersion = reference.Version?.ToString() ?? string.Empty; string expectedToken = FormatPublicKeyToken(reference.PublicKeyToken); return (from candidate in candidates orderby (!CandidateMatches(reference.Name, expectedVersion, expectedToken, candidate)) ? 1 : 0, candidate.Priority select candidate).ThenBy((ResolverCatalogCandidate candidate) => candidate.Path, GetPathComparer()); } private static bool CandidateMatches(string simpleName, string expectedVersion, string expectedToken, ResolverCatalogCandidate candidate) { if (!string.Equals(simpleName, candidate.SimpleName, StringComparison.OrdinalIgnoreCase)) { return false; } if (!string.IsNullOrWhiteSpace(expectedVersion) && !string.Equals(expectedVersion, candidate.Version, StringComparison.OrdinalIgnoreCase)) { return false; } if (!string.IsNullOrWhiteSpace(expectedToken) && !string.Equals(expectedToken, candidate.PublicKeyToken, StringComparison.OrdinalIgnoreCase)) { return false; } return true; } private static string FormatPublicKeyToken(byte[] token) { if (token == null || token.Length == 0) { return string.Empty; } return BitConverter.ToString(token).Replace("-", string.Empty).ToLowerInvariant(); } private static StringComparer GetPathComparer() { return (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; } } internal sealed class ResolverCatalog { private static readonly IReadOnlyDictionary> EmptyCandidates = new ReadOnlyDictionary>(new Dictionary>(StringComparer.OrdinalIgnoreCase)); public static ResolverCatalog Empty { get; } = new ResolverCatalog("empty", EmptyCandidates); public string Fingerprint { get; } public IReadOnlyDictionary> CandidatesBySimpleName { get; } private ResolverCatalog(string fingerprint, IReadOnlyDictionary> candidatesBySimpleName) { Fingerprint = (string.IsNullOrWhiteSpace(fingerprint) ? "empty" : fingerprint); CandidatesBySimpleName = candidatesBySimpleName ?? EmptyCandidates; } public static ResolverCatalog Create(string fingerprint, IReadOnlyDictionary> candidatesBySimpleName) { IReadOnlyDictionary> candidatesBySimpleName2; if (candidatesBySimpleName != null) { IReadOnlyDictionary> readOnlyDictionary = new ReadOnlyDictionary>(new Dictionary>(candidatesBySimpleName, StringComparer.OrdinalIgnoreCase)); candidatesBySimpleName2 = readOnlyDictionary; } else { candidatesBySimpleName2 = EmptyCandidates; } return new ResolverCatalog(fingerprint, candidatesBySimpleName2); } } internal sealed class ResolverCatalogCandidate { public string SimpleName { get; set; } = string.Empty; public string FullName { get; set; } = string.Empty; public string Version { get; set; } = string.Empty; public string PublicKeyToken { get; set; } = string.Empty; public string Path { get; set; } = string.Empty; public int Priority { get; set; } } public readonly struct ResolverRoot { public string Path { get; } public int Priority { get; } public ResolverRoot(string path, int priority) { Path = path; Priority = priority; } } } namespace MLVScan.Services.Diagnostics { public sealed class LoaderScanTelemetryHub { private LoaderScanProfileSnapshot _lastSnapshot; public void BeginRun(string runId) { _lastSnapshot = null; } public long StartTimestamp() { return 0L; } public void AddPhaseElapsed(string phaseName, long startTimestamp) { } public void IncrementCounter(string counterName, long delta = 1L) { } public void RecordFileSample(string filePath, long startTimestamp, string action, long bytesRead, int findingsCount) { } public void CompleteRun(int rootsScanned, int candidateFiles, int flaggedFiles) { } internal LoaderScanProfileSnapshot GetLastSnapshot() { return _lastSnapshot; } public string TryWriteArtifact(string diagnosticsDirectory) { return null; } } internal sealed class LoaderScanProfileSnapshot { public string RunId { get; set; } = string.Empty; public long TotalElapsedTicks { get; set; } public IReadOnlyList Phases { get; set; } = Array.Empty(); public IReadOnlyDictionary Counters { get; set; } = new Dictionary(StringComparer.Ordinal); public IReadOnlyList SlowFiles { get; set; } = Array.Empty(); } internal sealed class LoaderScanPhaseTiming { public string Name { get; set; } = string.Empty; public long ElapsedTicks { get; set; } public int Count { get; set; } } internal sealed class LoaderScanFileSample { public string FilePath { get; set; } = string.Empty; public long ElapsedTicks { get; set; } public string Action { get; set; } = string.Empty; public long BytesRead { get; set; } public int FindingsCount { get; set; } } } namespace MLVScan.Services.Caching { internal static class AtomicFileStorage { public static void WriteAllBytes(string path, byte[] contents) { string directoryName = Path.GetDirectoryName(path); if (!string.IsNullOrWhiteSpace(directoryName)) { Directory.CreateDirectory(directoryName); } string text = path + ".tmp"; using (FileStream fileStream = new FileStream(text, FileMode.Create, FileAccess.Write, FileShare.None)) { fileStream.Write(contents, 0, contents.Length); fileStream.Flush(flushToDisk: true); } try { Replace(text, path); } catch { if (File.Exists(text)) { try { File.Delete(text); } catch { } } throw; } } public static void WriteAllText(string path, string contents) { WriteAllBytes(path, Encoding.UTF8.GetBytes(contents)); } private static void Replace(string tempPath, string path) { if (File.Exists(path)) { File.Replace(tempPath, path, null, ignoreMetadataErrors: true); } else { File.Move(tempPath, path); } } } internal sealed class CrossPlatformFileIdentityProvider : IFileIdentityProvider { private struct ByHandleFileInformation { public uint dwFileAttributes; public FileTime ftCreationTime; public FileTime ftLastAccessTime; public FileTime ftLastWriteTime; public uint dwVolumeSerialNumber; public uint nFileSizeHigh; public uint nFileSizeLow; public uint nNumberOfLinks; public uint nFileIndexHigh; public uint nFileIndexLow; } private struct FileTime { public uint dwLowDateTime; public uint dwHighDateTime; } public FileProbe OpenProbe(string path) { if (string.IsNullOrWhiteSpace(path)) { throw new ArgumentException("Path is required.", "path"); } string fullPath = Path.GetFullPath(path); bool flag = IsSymlinkOrReparsePoint(fullPath); FileStream fileStream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); try { string canonicalPath = GetCanonicalPath(fullPath, fileStream.SafeFileHandle); FileIdentitySnapshot identity = (RuntimeInformationHelper.IsWindows ? CreateWindowsIdentity(fullPath, fileStream.SafeFileHandle, flag) : CreateUnixIdentity(fullPath, fileStream.SafeFileHandle, flag)); return new FileProbe(fullPath, canonicalPath, fileStream, identity, flag); } catch { fileStream.Dispose(); throw; } } private static bool IsSymlinkOrReparsePoint(string path) { try { return File.GetAttributes(path).HasFlag(FileAttributes.ReparsePoint); } catch { return false; } } private static string GetCanonicalPath(string path, SafeFileHandle handle) { if (!RuntimeInformationHelper.IsWindows) { return path; } StringBuilder stringBuilder = new StringBuilder(1024); uint finalPathNameByHandle; while (true) { finalPathNameByHandle = GetFinalPathNameByHandle(handle, stringBuilder, stringBuilder.Capacity, 0u); if (finalPathNameByHandle == 0) { return path; } if (finalPathNameByHandle < stringBuilder.Capacity) { break; } if (finalPathNameByHandle >= int.MaxValue) { return path; } stringBuilder = new StringBuilder((int)(finalPathNameByHandle + 1)); } return NormalizeWindowsDevicePath(stringBuilder.ToString(0, (int)finalPathNameByHandle)); } private static FileIdentitySnapshot CreateWindowsIdentity(string path, SafeFileHandle handle, bool isLink) { if (!GetFileInformationByHandle(handle, out var lpFileInformation)) { throw new IOException("GetFileInformationByHandle failed for " + path); } long ticks = DateTime.FromFileTimeUtc((long)(((ulong)lpFileInformation.ftLastWriteTime.dwHighDateTime << 32) | lpFileInformation.ftLastWriteTime.dwLowDateTime)).Ticks; long size = (long)(((ulong)lpFileInformation.nFileSizeHigh << 32) | lpFileInformation.nFileSizeLow); string identityKey = $"{lpFileInformation.dwVolumeSerialNumber:x8}:{lpFileInformation.nFileIndexHigh:x8}{lpFileInformation.nFileIndexLow:x8}"; return new FileIdentitySnapshot { Platform = "windows", HasStrongIdentity = true, IdentityKey = identityKey, Size = size, LastWriteUtcTicks = ticks, ChangeUtcTicks = ticks, IsSymlinkOrReparsePoint = isLink }; } private static FileIdentitySnapshot CreateUnixIdentity(string path, SafeFileHandle handle, bool isLink) { FileInfo fileInfo = new FileInfo(path); long ticks = fileInfo.LastWriteTimeUtc.Ticks; return new FileIdentitySnapshot { Platform = (RuntimeInformationHelper.IsMacOs ? "macos" : "linux"), HasStrongIdentity = false, IdentityKey = Path.GetFullPath(path), Size = (fileInfo.Exists ? fileInfo.Length : 0), LastWriteUtcTicks = ticks, ChangeUtcTicks = ticks, IsSymlinkOrReparsePoint = isLink }; } private static string NormalizeWindowsDevicePath(string path) { if (path.StartsWith("\\\\?\\UNC\\", StringComparison.OrdinalIgnoreCase)) { return "\\\\" + path.Substring(8); } if (path.StartsWith("\\\\?\\", StringComparison.OrdinalIgnoreCase)) { return path.Substring(4); } return path; } [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern uint GetFinalPathNameByHandle(SafeFileHandle hFile, StringBuilder lpszFilePath, int cchFilePath, uint dwFlags); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool GetFileInformationByHandle(SafeFileHandle hFile, out ByHandleFileInformation lpFileInformation); } internal interface IFileIdentityProvider { FileProbe OpenProbe(string path); } internal sealed class FileProbe : IDisposable { public string OriginalPath { get; } public string CanonicalPath { get; } public FileStream Stream { get; } public FileIdentitySnapshot Identity { get; } public bool IsSymlinkOrReparsePoint { get; } public bool CanReuseByStrongIdentity => !IsSymlinkOrReparsePoint && Identity.HasStrongIdentity; public FileProbe(string originalPath, string canonicalPath, FileStream stream, FileIdentitySnapshot identity, bool isSymlinkOrReparsePoint) { OriginalPath = originalPath; CanonicalPath = canonicalPath; Stream = stream; Identity = identity; IsSymlinkOrReparsePoint = isSymlinkOrReparsePoint; } public void Dispose() { Stream.Dispose(); } } internal sealed class FileIdentitySnapshot { public string Platform { get; set; } = string.Empty; public bool HasStrongIdentity { get; set; } public string IdentityKey { get; set; } = string.Empty; public long Size { get; set; } public long LastWriteUtcTicks { get; set; } public long ChangeUtcTicks { get; set; } public bool IsSymlinkOrReparsePoint { get; set; } public bool MatchesStrongIdentity(FileIdentitySnapshot other) { if (other == null || !HasStrongIdentity || !other.HasStrongIdentity) { return false; } return string.Equals(Platform, other.Platform, StringComparison.Ordinal) && string.Equals(IdentityKey, other.IdentityKey, StringComparison.Ordinal) && Size == other.Size && LastWriteUtcTicks == other.LastWriteUtcTicks && ChangeUtcTicks == other.ChangeUtcTicks && IsSymlinkOrReparsePoint == other.IsSymlinkOrReparsePoint; } } internal interface IScanCacheSigner { bool CanTrustCleanEntries { get; } string Sign(byte[] payloadBytes); bool Verify(byte[] payloadBytes, string signature); } internal interface IScanCacheStore { bool CanTrustCleanEntries { get; } ScanCacheEntry TryGetByPath(string canonicalPath); ScanCacheEntry TryGetByHash(string sha256Hash); void Upsert(ScanCacheEntry entry); void Remove(string canonicalPath); void PruneMissingEntries(IReadOnlyCollection activeCanonicalPaths); } internal static class RuntimeInformationHelper { public static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); public static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); public static bool IsMacOs => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); } internal static class ScanCacheEnvelopeCodec { private const int EnvelopeMagic = 1296848451; private const int EnvelopeVersion = 2; public static byte[] SerializePayload(ScanCacheEntryPayload payload) { using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true); WritePayload(binaryWriter, payload ?? new ScanCacheEntryPayload()); binaryWriter.Flush(); return memoryStream.ToArray(); } public static byte[] SerializeEnvelope(string signature, byte[] payloadBytes) { byte[] array = payloadBytes ?? Array.Empty(); using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true); binaryWriter.Write(1296848451); binaryWriter.Write(2); WriteString(binaryWriter, signature); binaryWriter.Write(array.Length); binaryWriter.Write(array); binaryWriter.Flush(); return memoryStream.ToArray(); } public static bool TryDeserializeEnvelope(byte[] envelopeBytes, out string signature, out byte[] payloadBytes) { signature = string.Empty; payloadBytes = Array.Empty(); if (envelopeBytes == null || envelopeBytes.Length == 0) { return false; } try { using MemoryStream memoryStream = new MemoryStream(envelopeBytes, writable: false); using BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8, leaveOpen: true); if (binaryReader.ReadInt32() != 1296848451 || binaryReader.ReadInt32() != 2) { return false; } signature = ReadString(binaryReader); int num = binaryReader.ReadInt32(); if (num < 0 || num > memoryStream.Length - memoryStream.Position) { return false; } payloadBytes = binaryReader.ReadBytes(num); if (payloadBytes.Length != num) { return false; } return true; } catch { signature = string.Empty; payloadBytes = Array.Empty(); return false; } } public static ScanCacheEntryPayload DeserializePayload(byte[] payloadBytes) { using MemoryStream input = new MemoryStream(payloadBytes, writable: false); using BinaryReader binaryReader = new BinaryReader(input, Encoding.UTF8, leaveOpen: true); return new ScanCacheEntryPayload { CanonicalPath = ReadString(binaryReader), RealPath = ReadString(binaryReader), FileIdentity = ReadFileIdentity(binaryReader), Sha256 = ReadString(binaryReader), ScannerFingerprint = ReadString(binaryReader), ResolverFingerprint = ReadString(binaryReader), CreatedUtc = DateTime.FromBinary(binaryReader.ReadInt64()), VerifiedUtc = DateTime.FromBinary(binaryReader.ReadInt64()), Result = ReadResult(binaryReader) }; } private static void WritePayload(BinaryWriter writer, ScanCacheEntryPayload payload) { WriteString(writer, payload.CanonicalPath); WriteString(writer, payload.RealPath); WriteFileIdentity(writer, payload.FileIdentity); WriteString(writer, payload.Sha256); WriteString(writer, payload.ScannerFingerprint); WriteString(writer, payload.ResolverFingerprint); writer.Write(payload.CreatedUtc.ToBinary()); writer.Write(payload.VerifiedUtc.ToBinary()); WriteResult(writer, payload.Result); } private static void WriteFileIdentity(BinaryWriter writer, FileIdentitySnapshot identity) { FileIdentitySnapshot fileIdentitySnapshot = identity ?? new FileIdentitySnapshot(); WriteString(writer, fileIdentitySnapshot.Platform); writer.Write(fileIdentitySnapshot.HasStrongIdentity); WriteString(writer, fileIdentitySnapshot.IdentityKey); writer.Write(fileIdentitySnapshot.Size); writer.Write(fileIdentitySnapshot.LastWriteUtcTicks); writer.Write(fileIdentitySnapshot.ChangeUtcTicks); writer.Write(fileIdentitySnapshot.IsSymlinkOrReparsePoint); } private static FileIdentitySnapshot ReadFileIdentity(BinaryReader reader) { return new FileIdentitySnapshot { Platform = ReadString(reader), HasStrongIdentity = reader.ReadBoolean(), IdentityKey = ReadString(reader), Size = reader.ReadInt64(), LastWriteUtcTicks = reader.ReadInt64(), ChangeUtcTicks = reader.ReadInt64(), IsSymlinkOrReparsePoint = reader.ReadBoolean() }; } private static void WriteResult(BinaryWriter writer, ScannedPluginResult result) { ScannedPluginResult scannedPluginResult = result ?? new ScannedPluginResult(); WriteString(writer, scannedPluginResult.FilePath); WriteString(writer, scannedPluginResult.FileHash); WriteFindings(writer, scannedPluginResult.Findings); WriteThreatVerdict(writer, scannedPluginResult.ThreatVerdict); WriteScanStatus(writer, scannedPluginResult.ScanStatus); } private static ScannedPluginResult ReadResult(BinaryReader reader) { return new ScannedPluginResult { FilePath = ReadString(reader), FileHash = ReadString(reader), Findings = ReadFindings(reader), ThreatVerdict = (ReadThreatVerdict(reader) ?? new ThreatVerdictInfo()), ScanStatus = (ReadScanStatus(reader) ?? new ScanStatusInfo()) }; } private static void WriteFindings(BinaryWriter writer, List findings) { List list = findings ?? new List(); writer.Write(list.Count); foreach (ScanFinding item in list) { WriteFinding(writer, item); } } private static List ReadFindings(BinaryReader reader) { int num = reader.ReadInt32(); List list = new List(Math.Max(num, 0)); for (int i = 0; i < num; i++) { list.Add(ReadFinding(reader)); } return list; } private static void WriteFinding(BinaryWriter writer, ScanFinding finding) { ScanFinding scanFinding = finding ?? new ScanFinding(string.Empty, string.Empty); WriteString(writer, scanFinding.Location); WriteString(writer, scanFinding.Description); writer.Write((int)scanFinding.Severity); WriteString(writer, scanFinding.CodeSnippet); WriteString(writer, scanFinding.RuleId); WriteDeveloperGuidance(writer, scanFinding.DeveloperGuidance); writer.Write(scanFinding.BypassCompanionCheck); writer.Write(scanFinding.RiskScore.HasValue); if (scanFinding.RiskScore.HasValue) { writer.Write(scanFinding.RiskScore.Value); } WriteCallChain(writer, scanFinding.CallChain); WriteDataFlowChain(writer, scanFinding.DataFlowChain); } private static ScanFinding ReadFinding(BinaryReader reader) { ScanFinding scanFinding = new ScanFinding(ReadString(reader), ReadString(reader), (Severity)reader.ReadInt32(), ReadString(reader)) { RuleId = ReadString(reader), DeveloperGuidance = ReadDeveloperGuidance(reader), BypassCompanionCheck = reader.ReadBoolean() }; if (reader.ReadBoolean()) { scanFinding.RiskScore = reader.ReadInt32(); } scanFinding.CallChain = ReadCallChain(reader); scanFinding.DataFlowChain = ReadDataFlowChain(reader); return scanFinding; } private static void WriteDeveloperGuidance(BinaryWriter writer, IDeveloperGuidance guidance) { writer.Write(guidance != null); if (guidance != null) { WriteString(writer, guidance.Remediation); WriteString(writer, guidance.DocumentationUrl); WriteStringArray(writer, guidance.AlternativeApis); writer.Write(guidance.IsRemediable); } } private static IDeveloperGuidance ReadDeveloperGuidance(BinaryReader reader) { if (!reader.ReadBoolean()) { return null; } return new DeveloperGuidance(ReadString(reader), ReadString(reader), ReadStringArray(reader), reader.ReadBoolean()); } private static void WriteThreatVerdict(BinaryWriter writer, ThreatVerdictInfo verdict) { writer.Write(verdict != null); if (verdict != null) { writer.Write((int)verdict.Kind); WriteString(writer, verdict.Title); WriteString(writer, verdict.Summary); writer.Write(verdict.Confidence); writer.Write(verdict.ShouldBypassThreshold); WriteThreatFamily(writer, verdict.PrimaryFamily); WriteThreatFamilies(writer, verdict.Families); } } private static ThreatVerdictInfo ReadThreatVerdict(BinaryReader reader) { if (!reader.ReadBoolean()) { return null; } return new ThreatVerdictInfo { Kind = (ThreatVerdictKind)reader.ReadInt32(), Title = ReadString(reader), Summary = ReadString(reader), Confidence = reader.ReadDouble(), ShouldBypassThreshold = reader.ReadBoolean(), PrimaryFamily = ReadThreatFamily(reader), Families = ReadThreatFamilies(reader) }; } private static void WriteScanStatus(BinaryWriter writer, ScanStatusInfo scanStatus) { writer.Write(scanStatus != null); if (scanStatus != null) { writer.Write((int)scanStatus.Kind); WriteString(writer, scanStatus.Title); WriteString(writer, scanStatus.Summary); } } private static ScanStatusInfo ReadScanStatus(BinaryReader reader) { if (!reader.ReadBoolean()) { return null; } return new ScanStatusInfo { Kind = (ScanStatusKind)reader.ReadInt32(), Title = ReadString(reader), Summary = ReadString(reader) }; } private static void WriteThreatFamilies(BinaryWriter writer, List families) { List list = families ?? new List(); writer.Write(list.Count); foreach (ThreatFamilyReference item in list) { WriteThreatFamily(writer, item); } } private static List ReadThreatFamilies(BinaryReader reader) { int num = reader.ReadInt32(); List list = new List(Math.Max(num, 0)); for (int i = 0; i < num; i++) { list.Add(ReadThreatFamily(reader)); } return list; } private static void WriteThreatFamily(BinaryWriter writer, ThreatFamilyReference family) { writer.Write(family != null); if (family != null) { WriteString(writer, family.FamilyId); WriteString(writer, family.DisplayName); WriteString(writer, family.Summary); WriteString(writer, family.MatchKind); WriteString(writer, family.TechnicalName); WriteString(writer, family.ReferenceUrl); writer.Write(family.Confidence); writer.Write(family.ExactHashMatch); WriteStringList(writer, family.MatchedRules); WriteStringList(writer, family.Evidence); } } private static ThreatFamilyReference ReadThreatFamily(BinaryReader reader) { if (!reader.ReadBoolean()) { return null; } return new ThreatFamilyReference { FamilyId = ReadString(reader), DisplayName = ReadString(reader), Summary = ReadString(reader), MatchKind = ReadString(reader), TechnicalName = ReadString(reader), ReferenceUrl = ReadString(reader), Confidence = reader.ReadDouble(), ExactHashMatch = reader.ReadBoolean(), MatchedRules = ReadStringList(reader), Evidence = ReadStringList(reader) }; } private static void WriteCallChain(BinaryWriter writer, CallChain callChain) { writer.Write(callChain != null); if (callChain == null) { return; } WriteString(writer, callChain.ChainId); WriteString(writer, callChain.RuleId); writer.Write((int)callChain.Severity); WriteString(writer, callChain.Summary); writer.Write(callChain.Nodes?.Count ?? 0); if (callChain.Nodes == null) { return; } foreach (CallChainNode node in callChain.Nodes) { WriteCallChainNode(writer, node); } } private static CallChain ReadCallChain(BinaryReader reader) { if (!reader.ReadBoolean()) { return null; } CallChain callChain = new CallChain(ReadString(reader), ReadString(reader), (Severity)reader.ReadInt32(), ReadString(reader)); int num = reader.ReadInt32(); for (int i = 0; i < num; i++) { callChain.AppendNode(ReadCallChainNode(reader)); } return callChain; } private static void WriteCallChainNode(BinaryWriter writer, CallChainNode node) { CallChainNode callChainNode = node ?? new CallChainNode(string.Empty, string.Empty, CallChainNodeType.IntermediateCall); WriteString(writer, callChainNode.Location); WriteString(writer, callChainNode.Description); writer.Write((int)callChainNode.NodeType); WriteString(writer, callChainNode.CodeSnippet); } private static CallChainNode ReadCallChainNode(BinaryReader reader) { return new CallChainNode(ReadString(reader), ReadString(reader), (CallChainNodeType)reader.ReadInt32(), ReadString(reader)); } private static void WriteDataFlowChain(BinaryWriter writer, DataFlowChain dataFlowChain) { writer.Write(dataFlowChain != null); if (dataFlowChain == null) { return; } WriteString(writer, dataFlowChain.ChainId); WriteString(writer, dataFlowChain.SourceVariable); writer.Write((int)dataFlowChain.Pattern); writer.Write((int)dataFlowChain.Severity); WriteString(writer, dataFlowChain.Summary); WriteString(writer, dataFlowChain.MethodLocation); writer.Write(dataFlowChain.IsCrossMethod); WriteStringList(writer, dataFlowChain.InvolvedMethods); writer.Write(dataFlowChain.Nodes?.Count ?? 0); if (dataFlowChain.Nodes == null) { return; } foreach (DataFlowNode node in dataFlowChain.Nodes) { WriteDataFlowNode(writer, node); } } private static DataFlowChain ReadDataFlowChain(BinaryReader reader) { if (!reader.ReadBoolean()) { return null; } string chainId = ReadString(reader); string sourceVariable = ReadString(reader); DataFlowPattern pattern = (DataFlowPattern)reader.ReadInt32(); Severity severity = (Severity)reader.ReadInt32(); string summary = ReadString(reader); string methodLocation = ReadString(reader); DataFlowChain dataFlowChain = new DataFlowChain(chainId, pattern, severity, summary, methodLocation) { SourceVariable = sourceVariable, IsCrossMethod = reader.ReadBoolean(), InvolvedMethods = ReadStringList(reader) }; int num = reader.ReadInt32(); for (int i = 0; i < num; i++) { dataFlowChain.AppendNode(ReadDataFlowNode(reader)); } return dataFlowChain; } private static void WriteDataFlowNode(BinaryWriter writer, DataFlowNode node) { DataFlowNode dataFlowNode = node ?? new DataFlowNode(string.Empty, string.Empty, DataFlowNodeType.Intermediate, string.Empty, 0); WriteString(writer, dataFlowNode.Location); WriteString(writer, dataFlowNode.Operation); writer.Write((int)dataFlowNode.NodeType); WriteString(writer, dataFlowNode.DataDescription); writer.Write(dataFlowNode.InstructionOffset); WriteString(writer, dataFlowNode.CodeSnippet); WriteString(writer, dataFlowNode.MethodKey); writer.Write(dataFlowNode.IsMethodBoundary); WriteString(writer, dataFlowNode.TargetMethodKey); } private static DataFlowNode ReadDataFlowNode(BinaryReader reader) { return new DataFlowNode(ReadString(reader), ReadString(reader), (DataFlowNodeType)reader.ReadInt32(), ReadString(reader), reader.ReadInt32(), ReadString(reader), ReadString(reader)) { IsMethodBoundary = reader.ReadBoolean(), TargetMethodKey = ReadString(reader) }; } private static void WriteStringList(BinaryWriter writer, List values) { List list = values ?? new List(); writer.Write(list.Count); foreach (string item in list) { WriteString(writer, item); } } private static List ReadStringList(BinaryReader reader) { int num = reader.ReadInt32(); List list = new List(Math.Max(num, 0)); for (int i = 0; i < num; i++) { list.Add(ReadString(reader)); } return list; } private static void WriteStringArray(BinaryWriter writer, string[] values) { writer.Write(values != null); if (values != null) { writer.Write(values.Length); foreach (string value in values) { WriteString(writer, value); } } } private static string[] ReadStringArray(BinaryReader reader) { if (!reader.ReadBoolean()) { return null; } int num = reader.ReadInt32(); string[] array = new string[Math.Max(num, 0)]; for (int i = 0; i < num; i++) { array[i] = ReadString(reader); } return array; } private static void WriteString(BinaryWriter writer, string value) { writer.Write(value != null); if (value != null) { writer.Write(value); } } private static string ReadString(BinaryReader reader) { return reader.ReadBoolean() ? reader.ReadString() : string.Empty; } } internal sealed class ScanCacheEntry { public string CanonicalPath { get; set; } = string.Empty; public string RealPath { get; set; } = string.Empty; public FileIdentitySnapshot FileIdentity { get; set; } = new FileIdentitySnapshot(); public string Sha256 { get; set; } = string.Empty; public string ScannerFingerprint { get; set; } = string.Empty; public string ResolverFingerprint { get; set; } = string.Empty; public DateTime CreatedUtc { get; set; } public DateTime VerifiedUtc { get; set; } public ScannedPluginResult Result { get; set; } = new ScannedPluginResult(); public bool CanReuseStrictly(FileProbe probe, string scannerFingerprint, string resolverFingerprint, bool canTrustCleanEntries) { if (probe == null) { return false; } if (!string.Equals(ScannerFingerprint, scannerFingerprint, StringComparison.Ordinal) || !string.Equals(ResolverFingerprint, resolverFingerprint, StringComparison.Ordinal)) { return false; } if (!ScanResultFacts.HasThreatVerdict(Result) && !canTrustCleanEntries) { return false; } return probe.CanReuseByStrongIdentity && FileIdentity.MatchesStrongIdentity(probe.Identity); } public ScannedPluginResult CloneResultForPath(string filePath) { List list = new List((Result?.Findings?.Count).GetValueOrDefault()); if (Result?.Findings != null) { foreach (ScanFinding finding in Result.Findings) { ScanFinding item = new ScanFinding(finding.Location, finding.Description, finding.Severity, finding.CodeSnippet) { RuleId = finding.RuleId, DeveloperGuidance = finding.DeveloperGuidance, CallChain = finding.CallChain, DataFlowChain = finding.DataFlowChain, BypassCompanionCheck = finding.BypassCompanionCheck, RiskScore = finding.RiskScore }; list.Add(item); } } ThreatVerdictInfo threatVerdictInfo = null; if (Result?.ThreatVerdict != null) { ThreatVerdictInfo threatVerdictInfo2 = new ThreatVerdictInfo(); threatVerdictInfo2.Kind = Result.ThreatVerdict.Kind; threatVerdictInfo2.Title = Result.ThreatVerdict.Title; threatVerdictInfo2.Summary = Result.ThreatVerdict.Summary; threatVerdictInfo2.Confidence = Result.ThreatVerdict.Confidence; threatVerdictInfo2.ShouldBypassThreshold = Result.ThreatVerdict.ShouldBypassThreshold; threatVerdictInfo2.PrimaryFamily = CloneFamily(Result.ThreatVerdict.PrimaryFamily); threatVerdictInfo2.Families = Result.ThreatVerdict.Families?.Select(CloneFamily).ToList() ?? new List(); threatVerdictInfo = threatVerdictInfo2; } ScanStatusInfo scanStatusInfo = null; if (Result?.ScanStatus != null) { scanStatusInfo = new ScanStatusInfo { Kind = Result.ScanStatus.Kind, Title = Result.ScanStatus.Title, Summary = Result.ScanStatus.Summary }; } return new ScannedPluginResult { FilePath = filePath, FileHash = (Result?.FileHash ?? Sha256), Findings = list, ThreatVerdict = (threatVerdictInfo ?? new ThreatVerdictInfo()), ScanStatus = (scanStatusInfo ?? new ScanStatusInfo()) }; } private static ThreatFamilyReference CloneFamily(ThreatFamilyReference family) { if (family == null) { return null; } return new ThreatFamilyReference { FamilyId = family.FamilyId, DisplayName = family.DisplayName, Summary = family.Summary, MatchKind = family.MatchKind, TechnicalName = family.TechnicalName, ReferenceUrl = family.ReferenceUrl, Confidence = family.Confidence, ExactHashMatch = family.ExactHashMatch, MatchedRules = (family.MatchedRules?.ToList() ?? new List()), Evidence = (family.Evidence?.ToList() ?? new List()) }; } } internal sealed class ScanCacheEnvelope { public int SchemaVersion { get; set; } = 2; public string Signature { get; set; } = string.Empty; public ScanCacheEntryPayload Payload { get; set; } = new ScanCacheEntryPayload(); } internal sealed class ScanCacheEntryPayload { public string CanonicalPath { get; set; } = string.Empty; public string RealPath { get; set; } = string.Empty; public FileIdentitySnapshot FileIdentity { get; set; } = new FileIdentitySnapshot(); public string Sha256 { get; set; } = string.Empty; public string ScannerFingerprint { get; set; } = string.Empty; public string ResolverFingerprint { get; set; } = string.Empty; public DateTime CreatedUtc { get; set; } public DateTime VerifiedUtc { get; set; } public ScannedPluginResult Result { get; set; } = new ScannedPluginResult(); public ScanCacheEntry ToEntry() { return new ScanCacheEntry { CanonicalPath = CanonicalPath, RealPath = RealPath, FileIdentity = FileIdentity, Sha256 = Sha256, ScannerFingerprint = ScannerFingerprint, ResolverFingerprint = ResolverFingerprint, CreatedUtc = CreatedUtc, VerifiedUtc = VerifiedUtc, Result = Result }; } public static ScanCacheEntryPayload FromEntry(ScanCacheEntry entry) { return new ScanCacheEntryPayload { CanonicalPath = entry.CanonicalPath, RealPath = entry.RealPath, FileIdentity = entry.FileIdentity, Sha256 = entry.Sha256, ScannerFingerprint = entry.ScannerFingerprint, ResolverFingerprint = entry.ResolverFingerprint, CreatedUtc = entry.CreatedUtc, VerifiedUtc = entry.VerifiedUtc, Result = entry.Result }; } } internal sealed class ScanCacheSigner : IScanCacheSigner { private struct DataBlob { public int cbData; public IntPtr pbData; } private const int CryptProtectUiForbidden = 1; private readonly byte[] _secret; public bool CanTrustCleanEntries { get; } public ScanCacheSigner(string cacheDirectory) { string secretPath = Path.Combine(cacheDirectory, "secret.bin"); _secret = LoadOrCreateSecret(secretPath, out var canTrustCleanEntries); CanTrustCleanEntries = canTrustCleanEntries && _secret.Length != 0; } public string Sign(byte[] payloadBytes) { if (payloadBytes == null || payloadBytes.Length == 0 || _secret.Length == 0) { return string.Empty; } using HMACSHA256 hMACSHA = new HMACSHA256(_secret); return Convert.ToBase64String(hMACSHA.ComputeHash(payloadBytes)); } public bool Verify(byte[] payloadBytes, string signature) { if (payloadBytes == null || payloadBytes.Length == 0 || string.IsNullOrEmpty(signature) || _secret.Length == 0) { return false; } byte[] bytes = Encoding.UTF8.GetBytes(Sign(payloadBytes)); byte[] bytes2 = Encoding.UTF8.GetBytes(signature); return bytes.Length == bytes2.Length && CryptographicOperations.FixedTimeEquals(bytes, bytes2); } private static byte[] LoadOrCreateSecret(string secretPath, out bool canTrustCleanEntries) { canTrustCleanEntries = false; try { return RuntimeInformationHelper.IsWindows ? LoadOrCreateWindowsSecret(secretPath, out canTrustCleanEntries) : LoadOrCreatePortableSecret(secretPath, out canTrustCleanEntries); } catch { canTrustCleanEntries = false; return Array.Empty(); } } private static byte[] LoadOrCreateWindowsSecret(string secretPath, out bool canTrustCleanEntries) { canTrustCleanEntries = true; if (File.Exists(secretPath)) { byte[] array = File.ReadAllBytes(secretPath); try { byte[] array2 = UnprotectForCurrentUser(array); if (IsValidSecret(array2)) { return array2; } } catch { if (IsValidSecret(array)) { canTrustCleanEntries = false; return array; } } DeleteInvalidSecret(secretPath); } Directory.CreateDirectory(Path.GetDirectoryName(secretPath)); byte[] array3 = CreateRandomSecret(); try { byte[] contents = ProtectForCurrentUser(array3); AtomicFileStorage.WriteAllBytes(secretPath, contents); } catch { canTrustCleanEntries = false; AtomicFileStorage.WriteAllBytes(secretPath, array3); } return array3; } private static byte[] LoadOrCreatePortableSecret(string secretPath, out bool canTrustCleanEntries) { Directory.CreateDirectory(Path.GetDirectoryName(secretPath)); canTrustCleanEntries = false; bool flag = false; if (File.Exists(secretPath)) { byte[] array = File.ReadAllBytes(secretPath); if (IsValidSecret(array)) { canTrustCleanEntries = true; return array; } DeleteInvalidSecret(secretPath); flag = true; } byte[] array2 = CreateRandomSecret(); AtomicFileStorage.WriteAllBytes(secretPath, array2); if (!flag) { canTrustCleanEntries = true; } return array2; } private static byte[] ProtectForCurrentUser(byte[] data) { GCHandle gCHandle = GCHandle.Alloc(data, GCHandleType.Pinned); try { DataBlob pDataIn = new DataBlob { cbData = data.Length, pbData = gCHandle.AddrOfPinnedObject() }; if (!CryptProtectData(ref pDataIn, null, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 1, out var pDataOut)) { throw new CryptographicException(Marshal.GetLastWin32Error()); } try { return CopyOutputBlob(pDataOut); } finally { FreeOutputBlob(pDataOut); } } finally { gCHandle.Free(); } } private static byte[] UnprotectForCurrentUser(byte[] data) { GCHandle gCHandle = GCHandle.Alloc(data, GCHandleType.Pinned); try { DataBlob pDataIn = new DataBlob { cbData = data.Length, pbData = gCHandle.AddrOfPinnedObject() }; if (!CryptUnprotectData(ref pDataIn, out var ppszDataDescr, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 1, out var pDataOut)) { throw new CryptographicException(Marshal.GetLastWin32Error()); } try { return CopyOutputBlob(pDataOut); } finally { if (ppszDataDescr != IntPtr.Zero) { LocalFree(ppszDataDescr); } FreeOutputBlob(pDataOut); } } finally { gCHandle.Free(); } } private static byte[] CopyOutputBlob(DataBlob blob) { if (blob.cbData <= 0 || blob.pbData == IntPtr.Zero) { return Array.Empty(); } byte[] array = new byte[blob.cbData]; Marshal.Copy(blob.pbData, array, 0, blob.cbData); return array; } private static void FreeOutputBlob(DataBlob blob) { if (blob.pbData != IntPtr.Zero) { LocalFree(blob.pbData); } } private static byte[] CreateRandomSecret() { byte[] array = new byte[32]; using RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create(); randomNumberGenerator.GetBytes(array); return array; } private static bool IsValidSecret(byte[] secret) { return secret != null && secret.Length == 32; } private static void DeleteInvalidSecret(string secretPath) { try { if (File.Exists(secretPath)) { File.Delete(secretPath); } } catch { } } [DllImport("crypt32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern bool CryptProtectData(ref DataBlob pDataIn, string szDataDescr, IntPtr pOptionalEntropy, IntPtr pvReserved, IntPtr pPromptStruct, int dwFlags, out DataBlob pDataOut); [DllImport("crypt32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern bool CryptUnprotectData(ref DataBlob pDataIn, out IntPtr ppszDataDescr, IntPtr pOptionalEntropy, IntPtr pvReserved, IntPtr pPromptStruct, int dwFlags, out DataBlob pDataOut); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr LocalFree(IntPtr hMem); } internal sealed class SecureScanCacheStore : IScanCacheStore { private readonly Dictionary _entriesByPath = new Dictionary(GetPathComparer()); private readonly Dictionary _entriesByHash = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly IScanCacheSigner _signer; private readonly string _entriesDirectory; public bool CanTrustCleanEntries => _signer.CanTrustCleanEntries; public SecureScanCacheStore(string cacheDirectory, IScanCacheSigner signer) { _signer = signer ?? throw new ArgumentNullException("signer"); _entriesDirectory = Path.Combine(cacheDirectory ?? throw new ArgumentNullException("cacheDirectory"), "entries"); Directory.CreateDirectory(_entriesDirectory); LoadEntries(); } public ScanCacheEntry TryGetByPath(string canonicalPath) { if (string.IsNullOrWhiteSpace(canonicalPath)) { return null; } _entriesByPath.TryGetValue(NormalizePathKey(canonicalPath), out var value); return value; } public ScanCacheEntry TryGetByHash(string sha256Hash) { if (string.IsNullOrWhiteSpace(sha256Hash)) { return null; } _entriesByHash.TryGetValue(sha256Hash, out var value); return value; } public void Upsert(ScanCacheEntry entry) { if (entry != null && !string.IsNullOrWhiteSpace(entry.CanonicalPath)) { entry.CreatedUtc = ((entry.CreatedUtc == default(DateTime)) ? DateTime.UtcNow : entry.CreatedUtc); entry.VerifiedUtc = DateTime.UtcNow; ScanCacheEntryPayload payload = ScanCacheEntryPayload.FromEntry(entry); byte[] payloadBytes = ScanCacheEnvelopeCodec.SerializePayload(payload); byte[] contents = ScanCacheEnvelopeCodec.SerializeEnvelope(_signer.Sign(payloadBytes), payloadBytes); AtomicFileStorage.WriteAllBytes(GetEntryFilePath(entry.CanonicalPath), contents); string normalizedPath = NormalizePathKey(entry.CanonicalPath); IndexEntry(normalizedPath, entry); } } public void Remove(string canonicalPath) { if (string.IsNullOrWhiteSpace(canonicalPath)) { return; } string key = NormalizePathKey(canonicalPath); if (_entriesByPath.TryGetValue(key, out var value)) { _entriesByPath.Remove(key); if (!string.IsNullOrWhiteSpace(value.Sha256) && _entriesByHash.TryGetValue(value.Sha256, out var value2) && value2 == value) { _entriesByHash.Remove(value.Sha256); } } try { foreach (string entryFilePath in GetEntryFilePaths(canonicalPath)) { if (File.Exists(entryFilePath)) { File.Delete(entryFilePath); } } } catch { } } public void PruneMissingEntries(IReadOnlyCollection activeCanonicalPaths) { HashSet hashSet = new HashSet(activeCanonicalPaths?.Select(NormalizePathKey) ?? Enumerable.Empty(), GetPathComparer()); string[] array = _entriesByPath.Keys.ToArray(); foreach (string text in array) { if (!hashSet.Contains(text)) { Remove(text); } } } private void LoadEntries() { if (!Directory.Exists(_entriesDirectory)) { return; } foreach (string item in EnumerateEntryFiles()) { try { byte[] envelopeBytes = File.ReadAllBytes(item); if (!ScanCacheEnvelopeCodec.TryDeserializeEnvelope(envelopeBytes, out var signature, out var payloadBytes)) { DeleteCorruptEntry(item); continue; } if (!_signer.Verify(payloadBytes, signature)) { DeleteCorruptEntry(item); continue; } ScanCacheEntryPayload scanCacheEntryPayload = ScanCacheEnvelopeCodec.DeserializePayload(payloadBytes); if (scanCacheEntryPayload == null) { DeleteCorruptEntry(item); continue; } ScanCacheEntry scanCacheEntry = scanCacheEntryPayload.ToEntry(); string normalizedPath = NormalizePathKey(scanCacheEntry.CanonicalPath); IndexEntry(normalizedPath, scanCacheEntry); } catch { DeleteCorruptEntry(item); } } } private void DeleteCorruptEntry(string path) { try { File.Delete(path); } catch { } } private void IndexEntry(string normalizedPath, ScanCacheEntry entry) { if (_entriesByPath.TryGetValue(normalizedPath, out var value) && !string.IsNullOrWhiteSpace(value.Sha256) && (string.IsNullOrWhiteSpace(entry.Sha256) || !string.Equals(value.Sha256, entry.Sha256, StringComparison.OrdinalIgnoreCase))) { _entriesByHash.Remove(value.Sha256); } _entriesByPath[normalizedPath] = entry; if (!string.IsNullOrWhiteSpace(entry.Sha256)) { _entriesByHash[entry.Sha256] = entry; } } private string GetEntryFilePath(string canonicalPath) { string text = HashUtility.CalculateBytesHash(Encoding.UTF8.GetBytes(NormalizePathKey(canonicalPath))); return Path.Combine(_entriesDirectory, text + ".cache"); } private IEnumerable GetEntryFilePaths(string canonicalPath) { string key = HashUtility.CalculateBytesHash(Encoding.UTF8.GetBytes(NormalizePathKey(canonicalPath))); yield return Path.Combine(_entriesDirectory, key + ".cache"); yield return Path.Combine(_entriesDirectory, key + ".json"); } private IEnumerable EnumerateEntryFiles() { foreach (string item in Directory.EnumerateFiles(_entriesDirectory, "*.cache", SearchOption.TopDirectoryOnly)) { yield return item; } foreach (string item2 in Directory.EnumerateFiles(_entriesDirectory, "*.json", SearchOption.TopDirectoryOnly)) { yield return item2; } } private static string NormalizePathKey(string path) { string fullPath = Path.GetFullPath(path); return RuntimeInformationHelper.IsWindows ? fullPath.ToLowerInvariant() : fullPath; } private static StringComparer GetPathComparer() { return RuntimeInformationHelper.IsWindows ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; } } } namespace MLVScan.Models { public class MLVScanConfig { public ScanConfig Scan { get; set; } = new ScanConfig(); public bool EnableAutoScan { get; set; } = true; public bool EnableAutoDisable { get; set; } = true; public bool EnableScanCache { get; set; } = true; public bool BlockKnownThreats { get; set; } = true; public bool BlockSuspicious { get; set; } = true; public bool BlockIncompleteScans { get; set; } = false; public string[] ScanDirectories { get; set; } = new string[2] { "Mods", "Plugins" }; public string[] WhitelistedHashes { get; set; } = Array.Empty(); public bool DumpFullIlReports { get; set; } = false; public bool EnableReportUpload { get; set; } = false; public bool ReportUploadConsentAsked { get; set; } = false; public bool ReportUploadConsentPending { get; set; } = false; public string PendingReportUploadPath { get; set; } = string.Empty; public string PendingReportUploadVerdictKind { get; set; } = string.Empty; public string ReportUploadApiBaseUrl { get; set; } = "https://api.mlvscan.com"; public string[] UploadedReportHashes { get; set; } = Array.Empty(); public bool IncludeMods { get; set; } = true; public bool IncludePlugins { get; set; } = true; public bool IncludeUserLibs { get; set; } = true; public bool IncludePatchers { get; set; } = true; public bool IncludeThunderstoreProfiles { get; set; } = true; public string[] AdditionalTargetRoots { get; set; } = Array.Empty(); public string[] ExcludedTargetRoots { get; set; } = Array.Empty(); } public class SubmissionMetadata { public string LoaderType { get; set; } public string LoaderVersion { get; set; } public string PluginVersion { get; set; } public string GameVersion { get; set; } public string ModName { get; set; } public string SourceHint { get; set; } public List FindingSummary { get; set; } public string ConsentVersion { get; set; } public string ConsentTimestamp { get; set; } } public class FindingSummaryItem { public string RuleId { get; set; } public string Description { get; set; } public string Severity { get; set; } public string Location { get; set; } } public enum ThreatVerdictKind { None, Suspicious, KnownMalwareFamily, KnownMaliciousSample } public enum ScanStatusKind { Complete, RequiresReview } public class ThreatFamilyReference { public string FamilyId { get; set; } = string.Empty; public string DisplayName { get; set; } = string.Empty; public string Summary { get; set; } = string.Empty; public string MatchKind { get; set; } = string.Empty; public string TechnicalName { get; set; } = string.Empty; public string ReferenceUrl { get; set; } = string.Empty; public double Confidence { get; set; } public bool ExactHashMatch { get; set; } public List MatchedRules { get; set; } = new List(); public List Evidence { get; set; } = new List(); } public class ThreatVerdictInfo { public ThreatVerdictKind Kind { get; set; } public string Title { get; set; } = string.Empty; public string Summary { get; set; } = string.Empty; public double Confidence { get; set; } public bool ShouldBypassThreshold { get; set; } public ThreatFamilyReference PrimaryFamily { get; set; } public List Families { get; set; } = new List(); } public class ScanStatusInfo { public ScanStatusKind Kind { get; set; } = ScanStatusKind.Complete; public string Title { get; set; } = string.Empty; public string Summary { get; set; } = string.Empty; } public class ScannedPluginResult { public string FilePath { get; set; } = string.Empty; public string FileHash { get; set; } = string.Empty; public List Findings { get; set; } = new List(); public ThreatVerdictInfo ThreatVerdict { get; set; } = new ThreatVerdictInfo(); public ScanStatusInfo ScanStatus { get; set; } = new ScanStatusInfo(); } internal static class ScanResultFacts { public static bool HasThreatVerdict(ScannedPluginResult result) { return (result?.ThreatVerdict?.Kind).GetValueOrDefault() != ThreatVerdictKind.None; } public static bool RequiresManualReview(ScannedPluginResult result) { return (result?.ScanStatus?.Kind).GetValueOrDefault() != ScanStatusKind.Complete; } public static bool RequiresAttention(ScannedPluginResult result) { return HasThreatVerdict(result) || RequiresManualReview(result); } public static bool IsClean(ScannedPluginResult result) { return !RequiresAttention(result); } } } namespace MLVScan.Abstractions { public interface IConfigManager { MLVScanConfig Config { get; } MLVScanConfig LoadConfig(); void SaveConfig(MLVScanConfig config); bool IsHashWhitelisted(string hash); string[] GetWhitelistedHashes(); void SetWhitelistedHashes(string[] hashes); string GetReportUploadApiBaseUrl(); bool IsReportHashUploaded(string hash); void MarkReportHashUploaded(string hash); } public interface IPlatformEnvironment { string GameRootDirectory { get; } string[] PluginDirectories { get; } string DataDirectory { get; } string ReportsDirectory { get; } string ManagedDirectory { get; } string SelfAssemblyPath { get; } string PlatformName { get; } } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace MLVScan { [Obsolete("Use MLVScanVersions instead. Removed in v2.0.")] public static class Constants { [Obsolete("Use MLVScanVersions.CoreVersion instead. Removed in v2.0.")] public const string CoreVersion = "1.7.1"; [Obsolete("Use MLVScanVersions.GetVersionString() instead. Removed in v2.0.")] public static string GetVersionString() { return MLVScanVersions.GetVersionString(); } } public static class MLVScanVersions { internal const string DeclaredCoreVersion = "1.7.1"; public const string SchemaVersion = "1.3.0"; public static string CoreVersion => "1.7.1"; [Obsolete("Use the CoreVersion property instead. Removed in v2.0.")] public static string GetCoreVersion() { return CoreVersion; } public static string GetVersionString() { return "MLVScan.Core v" + CoreVersion; } [Obsolete("Use the SchemaVersion property instead. Removed in v2.0.")] public static string GetSchemaVersion() { return "1.3.0"; } } public static class RuleFactory { public static IReadOnlyList CreateDefaultRules() { return CreateCoreRules().AsReadOnly(); } public static IReadOnlyList CreateDefaultRulesWith(params IScanRule[] additionalRules) { if (additionalRules == null) { throw new ArgumentNullException("additionalRules"); } List list = CreateCoreRules(); foreach (IScanRule scanRule in additionalRules) { if (scanRule == null) { throw new ArgumentNullException("additionalRules", "Additional rules cannot contain null entries."); } list.Add(scanRule); } ValidateRuleIds(list); return list.AsReadOnly(); } private static List CreateCoreRules() { return new List { new Base64Rule(), new ProcessStartRule(), new AssemblyDynamicLoadRule(), new ByteArrayManipulationRule(), new DllImportRule(), new RegistryRule(), new EncodedStringLiteralRule(), new ReflectionRule(), new EncodedStringPipelineRule(), new EncodedBlobSplittingRule(), new COMReflectionAttackRule(), new DataExfiltrationRule(), new DataInfiltrationRule(), new PersistenceRule(), new HexStringRule(), new SuspiciousLocalVariableRule(), new ObfuscatedReflectiveExecutionRule(), new EmbeddedResourceScriptRule(), new SuspiciousAssemblyNameRule() }; } private static void ValidateRuleIds(IEnumerable rules) { HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (IScanRule rule in rules) { if (string.IsNullOrWhiteSpace(rule.RuleId)) { throw new ArgumentException("Rule IDs cannot be null, empty, or whitespace.", "rules"); } if (!hashSet.Add(rule.RuleId)) { throw new ArgumentException("Duplicate rule ID '" + rule.RuleId + "' was registered.", "rules"); } } } } } namespace MLVScan.Services { public class AssemblyScanner : IAssemblyScanner { private static readonly HashSet ExecutionCorrelationRuleIds = new HashSet(StringComparer.Ordinal) { "DllImportRule", "ProcessStartRule", "Shell32Rule" }; private readonly IReadOnlyCollection _rules; private readonly TypeScanner _typeScanner; private readonly MetadataScanner _metadataScanner; private readonly DllImportScanner _dllImportScanner; private readonly CallGraphBuilder _callGraphBuilder; private readonly DataFlowAnalyzer _dataFlowAnalyzer; private readonly IAssemblyResolverProvider _resolverProvider; private readonly ScanConfig _config; private readonly ScanTelemetryHub _telemetry; private readonly IProgress? _progressReporter; public AssemblyScanner(IEnumerable rules, ScanConfig? config = null, IAssemblyResolverProvider? resolverProvider = null, IEntryPointProvider? entryPointProvider = null, IProgress? progressReporter = null) { _config = config ?? new ScanConfig(); _resolverProvider = resolverProvider ?? DefaultAssemblyResolverProvider.Instance; _rules = (rules as IReadOnlyCollection) ?? rules.ToList(); _telemetry = new ScanTelemetryHub(); _progressReporter = progressReporter; CodeSnippetBuilder snippetBuilder = new CodeSnippetBuilder(); SignalTracker signalTracker = new SignalTracker(_config); StringPatternDetector stringPatternDetector = new StringPatternDetector(); _callGraphBuilder = new CallGraphBuilder(rules, snippetBuilder, entryPointProvider); _dataFlowAnalyzer = new DataFlowAnalyzer(rules, snippetBuilder, _telemetry); ReflectionDetector reflectionDetector = new ReflectionDetector(rules, signalTracker, stringPatternDetector, snippetBuilder); InstructionAnalyzer instructionAnalyzer = new InstructionAnalyzer(rules, signalTracker, reflectionDetector, stringPatternDetector, snippetBuilder, _config, _telemetry, _callGraphBuilder); LocalVariableAnalyzer localVariableAnalyzer = new LocalVariableAnalyzer(rules, signalTracker, _config); ExceptionHandlerAnalyzer exceptionHandlerAnalyzer = new ExceptionHandlerAnalyzer(rules, signalTracker, snippetBuilder, _config); MethodScanner methodScanner = new MethodScanner(rules, signalTracker, instructionAnalyzer, snippetBuilder, localVariableAnalyzer, exceptionHandlerAnalyzer, _config, _telemetry); PropertyEventScanner propertyEventScanner = new PropertyEventScanner(methodScanner, _config); _typeScanner = new TypeScanner(methodScanner, signalTracker, reflectionDetector, snippetBuilder, propertyEventScanner, rules, _config, _telemetry); _metadataScanner = new MetadataScanner(rules); _dllImportScanner = new DllImportScanner(rules, _callGraphBuilder); } public IEnumerable Scan(string assemblyPath) { //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Expected O, but got Unknown if (string.IsNullOrWhiteSpace(assemblyPath)) { throw new ArgumentException("Assembly path must be provided", "assemblyPath"); } if (!File.Exists(assemblyPath)) { throw new FileNotFoundException("Assembly file not found", assemblyPath); } string text = CreateAssemblyTelemetryId(assemblyPath); _telemetry.BeginAssembly(text); _telemetry.IncrementCounter("Assemblies.Scanned", 1L); long startTimestamp = _telemetry.StartTimestamp(); List list = new List(); try { _callGraphBuilder.Clear(); _dataFlowAnalyzer.Clear(); ReaderParameters val = new ReaderParameters { ReadWrite = false, InMemory = true, ReadSymbols = false, AssemblyResolver = _resolverProvider.CreateResolver() }; long startTimestamp2 = _telemetry.StartTimestamp(); ReportProgress("ReadAssembly", 0, 1, text); AssemblyDefinition val2 = AssemblyDefinition.ReadAssembly(assemblyPath, val); try { _telemetry.AddPhaseElapsed("AssemblyScanner.ReadAssembly", startTimestamp2); int completedUnits = 1; int totalUnits = 1 + CountScanUnits(val2) + 3; ReportProgress("ReadAssembly", completedUnits, totalUnits, ((AssemblyNameReference)val2.Name).Name); long startTimestamp3 = _telemetry.StartTimestamp(); ScanAssembly(val2, list, ref completedUnits, totalUnits); _telemetry.AddPhaseElapsed("AssemblyScanner.ScanAssembly", startTimestamp3); long startTimestamp4 = _telemetry.StartTimestamp(); IEnumerable collection = _callGraphBuilder.BuildCallChainFindings(); _telemetry.AddPhaseElapsed("AssemblyScanner.BuildCallChainFindings", startTimestamp4); list.AddRange(collection); completedUnits++; ReportProgress("BuildCallChainFindings", completedUnits, totalUnits, ((AssemblyNameReference)val2.Name).Name); long startTimestamp5 = _telemetry.StartTimestamp(); IEnumerable collection2 = _dataFlowAnalyzer.BuildDataFlowFindings(); _telemetry.AddPhaseElapsed("AssemblyScanner.BuildDataFlowFindings", startTimestamp5); list.AddRange(collection2); completedUnits++; ReportProgress("BuildDataFlowFindings", completedUnits, totalUnits, ((AssemblyNameReference)val2.Name).Name); long startTimestamp6 = _telemetry.StartTimestamp(); CorrelateDataFlowIntoExecutionFindings(list); _telemetry.AddPhaseElapsed("AssemblyScanner.CorrelateDataFlowIntoExecutionFindings", startTimestamp6); completedUnits++; ReportProgress("CorrelateFindings", completedUnits, totalUnits, ((AssemblyNameReference)val2.Name).Name); } finally { ((IDisposable)val2)?.Dispose(); } } catch (Exception) { list.Add(new ScanFinding("Assembly scanning", "Warning: Some parts of the assembly could not be scanned. This doesn't necessarily mean the mod is malicious.") { RuleId = "AssemblyScanner" }); } _telemetry.AddPhaseElapsed("AssemblyScanner.Total", startTimestamp); List list2 = FilterEmptyFindings(list).ToList(); _telemetry.CompleteAssembly(list.Count, list2.Count); return list2; } public IEnumerable Scan(Stream assemblyStream, string? virtualPath = null) { //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Expected O, but got Unknown if (assemblyStream == null || !assemblyStream.CanRead) { throw new ArgumentException("Assembly stream must be readable", "assemblyStream"); } if (assemblyStream.CanSeek) { assemblyStream.Position = 0L; } string text = CreateStreamTelemetryId(virtualPath); _telemetry.BeginAssembly(text); _telemetry.IncrementCounter("Assemblies.Scanned", 1L); long startTimestamp = _telemetry.StartTimestamp(); List list = new List(); try { _callGraphBuilder.Clear(); _dataFlowAnalyzer.Clear(); ReaderParameters val = new ReaderParameters { ReadWrite = false, InMemory = true, ReadSymbols = false, AssemblyResolver = _resolverProvider.CreateResolver() }; long startTimestamp2 = _telemetry.StartTimestamp(); ReportProgress("ReadAssembly", 0, 1, text); AssemblyDefinition val2 = AssemblyDefinition.ReadAssembly(assemblyStream, val); try { _telemetry.AddPhaseElapsed("AssemblyScanner.ReadAssembly", startTimestamp2); int completedUnits = 1; int totalUnits = 1 + CountScanUnits(val2) + 3; ReportProgress("ReadAssembly", completedUnits, totalUnits, ((AssemblyNameReference)val2.Name).Name); long startTimestamp3 = _telemetry.StartTimestamp(); ScanAssembly(val2, list, ref completedUnits, totalUnits); _telemetry.AddPhaseElapsed("AssemblyScanner.ScanAssembly", startTimestamp3); long startTimestamp4 = _telemetry.StartTimestamp(); IEnumerable collection = _callGraphBuilder.BuildCallChainFindings(); _telemetry.AddPhaseElapsed("AssemblyScanner.BuildCallChainFindings", startTimestamp4); list.AddRange(collection); completedUnits++; ReportProgress("BuildCallChainFindings", completedUnits, totalUnits, ((AssemblyNameReference)val2.Name).Name); long startTimestamp5 = _telemetry.StartTimestamp(); IEnumerable collection2 = _dataFlowAnalyzer.BuildDataFlowFindings(); _telemetry.AddPhaseElapsed("AssemblyScanner.BuildDataFlowFindings", startTimestamp5); list.AddRange(collection2); completedUnits++; ReportProgress("BuildDataFlowFindings", completedUnits, totalUnits, ((AssemblyNameReference)val2.Name).Name); long startTimestamp6 = _telemetry.StartTimestamp(); CorrelateDataFlowIntoExecutionFindings(list); _telemetry.AddPhaseElapsed("AssemblyScanner.CorrelateDataFlowIntoExecutionFindings", startTimestamp6); completedUnits++; ReportProgress("CorrelateFindings", completedUnits, totalUnits, ((AssemblyNameReference)val2.Name).Name); } finally { ((IDisposable)val2)?.Dispose(); } } catch (Exception) { list.Add(new ScanFinding(virtualPath ?? "Assembly scanning", "Warning: Some parts of the assembly could not be scanned. Please ensure this is a valid managed .NET assembly. This doesn't necessarily mean the assembly is malicious.") { RuleId = "AssemblyScanner" }); } _telemetry.AddPhaseElapsed("AssemblyScanner.Total", startTimestamp); List list2 = FilterEmptyFindings(list).ToList(); _telemetry.CompleteAssembly(list.Count, list2.Count); return list2; } private void ScanAssembly(AssemblyDefinition assembly, List findings, ref int completedUnits, int totalUnits) { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Unknown result type (might be due to invalid IL or missing references) //IL_027d: Unknown result type (might be due to invalid IL or missing references) if (_config.DetectAssemblyMetadata) { long startTimestamp = _telemetry.StartTimestamp(); findings.AddRange(_metadataScanner.ScanAssemblyMetadata(assembly)); _telemetry.AddPhaseElapsed("AssemblyScanner.MetadataScanner", startTimestamp); completedUnits++; ReportProgress("ScanMetadata", completedUnits, totalUnits, ((AssemblyNameReference)assembly.Name).Name); } Enumerator enumerator = assembly.Modules.GetEnumerator(); try { while (enumerator.MoveNext()) { ModuleDefinition current = enumerator.Current; _telemetry.IncrementCounter("Assembly.ModulesScanned", 1L); _telemetry.IncrementCounter("Assembly.TopLevelTypesScanned", current.Types.Count); long startTimestamp2 = _telemetry.StartTimestamp(); _dllImportScanner.ScanForDllImports(current); _telemetry.AddPhaseElapsed("AssemblyScanner.DllImportScanner", startTimestamp2); completedUnits++; ReportProgress("ScanImports", completedUnits, totalUnits, ((ModuleReference)current).Name); Enumerator enumerator2 = current.Types.GetEnumerator(); try { while (enumerator2.MoveNext()) { TypeDefinition current2 = enumerator2.Current; long startTimestamp3 = _telemetry.StartTimestamp(); findings.AddRange(_typeScanner.ScanType(current2)); _telemetry.AddPhaseElapsed("AssemblyScanner.TypeScanner", startTimestamp3); completedUnits++; ReportProgress("ScanType", completedUnits, totalUnits, ((MemberReference)current2).FullName); foreach (MethodDefinition item in EnumerateMethodsRecursively(current2)) { long startTimestamp4 = _telemetry.StartTimestamp(); _dataFlowAnalyzer.AnalyzeMethod(item); _telemetry.AddPhaseElapsed("AssemblyScanner.DataFlowAnalyzeMethod", startTimestamp4); completedUnits++; ReportProgress("AnalyzeMethodDataFlow", completedUnits, totalUnits, ((MemberReference)item).FullName); } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } long startTimestamp5 = _telemetry.StartTimestamp(); _dataFlowAnalyzer.AnalyzeCrossMethodFlows(); _telemetry.AddPhaseElapsed("AssemblyScanner.DataFlowAnalyzeCrossMethodFlows", startTimestamp5); completedUnits++; ReportProgress("AnalyzeCrossMethodDataFlow", completedUnits, totalUnits, ((AssemblyNameReference)assembly.Name).Name); Enumerator enumerator4 = assembly.Modules.GetEnumerator(); try { while (enumerator4.MoveNext()) { ModuleDefinition current4 = enumerator4.Current; foreach (IScanRule rule in _rules) { _telemetry.IncrementCounter("Assembly.PostAnalysisRuleInvocations", 1L); long startTimestamp6 = _telemetry.StartTimestamp(); IEnumerable collection = rule.PostAnalysisRefine(current4, findings); _telemetry.AddPhaseElapsed("AssemblyScanner.PostAnalysisRefine", startTimestamp6); findings.AddRange(collection); completedUnits++; ReportProgress("PostAnalysisRefine", completedUnits, totalUnits, rule.RuleId); } } } finally { ((IDisposable)enumerator4/*cast due to .constrained prefix*/).Dispose(); } } private int CountScanUnits(AssemblyDefinition assembly) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) if (_progressReporter == null) { return 1; } int num = (_config.DetectAssemblyMetadata ? 1 : 0); Enumerator enumerator = assembly.Modules.GetEnumerator(); try { while (enumerator.MoveNext()) { ModuleDefinition current = enumerator.Current; num++; Enumerator enumerator2 = current.Types.GetEnumerator(); try { while (enumerator2.MoveNext()) { TypeDefinition current2 = enumerator2.Current; num++; num += EnumerateMethodsRecursively(current2).Count(); } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } num++; num += assembly.Modules.Count * _rules.Count; return Math.Max(1, num); } private void ReportProgress(string phase, int completedUnits, int totalUnits, string? currentItem = null) { _progressReporter?.Report(new ScanProgress(phase, completedUnits, totalUnits, currentItem)); } private static IEnumerable EnumerateMethodsRecursively(TypeDefinition type) { Enumerator enumerator = type.Methods.GetEnumerator(); try { while (enumerator.MoveNext()) { yield return enumerator.Current; } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } Enumerator enumerator2 = type.NestedTypes.GetEnumerator(); try { while (enumerator2.MoveNext()) { TypeDefinition nestedType = enumerator2.Current; foreach (MethodDefinition item in EnumerateMethodsRecursively(nestedType)) { yield return item; } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } private static IEnumerable FilterEmptyFindings(List findings) { if (findings.Count == 1 && findings[0].Location == "Assembly scanning" && string.IsNullOrEmpty(findings[0].CodeSnippet)) { return new List(); } return findings; } internal static string CreateAssemblyTelemetryId(string assemblyPath) { return GetPathLeaf(assemblyPath); } internal static string CreateStreamTelemetryId(string? virtualPath) { if (string.IsNullOrWhiteSpace(virtualPath)) { return ""; } if (IsAbsoluteLikePath(virtualPath)) { return GetPathLeaf(virtualPath); } return virtualPath; } private static bool IsAbsoluteLikePath(string path) { if (Path.IsPathRooted(path)) { return true; } if (path.Length >= 3 && char.IsLetter(path[0]) && path[1] == ':' && (path[2] == Path.DirectorySeparatorChar || path[2] == Path.AltDirectorySeparatorChar || path[2] == '\\' || path[2] == '/')) { return true; } return path.StartsWith("\\\\", StringComparison.Ordinal) || path.StartsWith("//", StringComparison.Ordinal); } private static string GetPathLeaf(string path) { int num = Math.Max(path.LastIndexOf('/'), path.LastIndexOf('\\')); string result; if (num < 0) { result = path; } else { int num2 = num + 1; result = path.Substring(num2, path.Length - num2); } return result; } internal ScanProfileSnapshot? GetLastProfileSnapshot() { return _telemetry.GetLastSnapshot(); } private static void CorrelateDataFlowIntoExecutionFindings(List findings) { if (findings.Count == 0) { return; } List list = findings.Where((ScanFinding f) => f.RuleId == "DataFlowAnalysis" && f.HasDataFlow).ToList(); if (list.Count == 0) { return; } HashSet mergedDataFlowFindings = new HashSet(); foreach (ScanFinding dataFlowFinding in list) { DataFlowChain dataFlowChain = dataFlowFinding.DataFlowChain; if (dataFlowChain == null) { continue; } DataFlowNode dataFlowNode = dataFlowChain.Nodes.FirstOrDefault((DataFlowNode node) => node.NodeType == DataFlowNodeType.Sink && IsExecutionSinkOperation(node.Operation)); if (dataFlowNode == null) { continue; } string sinkMethodLocation = TrimOffset(dataFlowNode.Location); if (string.IsNullOrWhiteSpace(sinkMethodLocation)) { continue; } int? sinkOffset = ExtractOffset(dataFlowNode.Location); ScanFinding scanFinding = (from f in findings where f != dataFlowFinding && f.RuleId != null && ExecutionCorrelationRuleIds.Contains(f.RuleId) && string.Equals(TrimOffset(f.Location), sinkMethodLocation, StringComparison.Ordinal) orderby sinkOffset.HasValue && ExtractOffset(f.Location).HasValue && ExtractOffset(f.Location) == sinkOffset descending, f.Severity descending select f).FirstOrDefault(); if (scanFinding != null) { scanFinding.DataFlowChain = dataFlowChain; if (dataFlowFinding.Severity > scanFinding.Severity) { scanFinding.Severity = dataFlowFinding.Severity; } if (!scanFinding.Description.Contains("Correlated data flow:", StringComparison.OrdinalIgnoreCase)) { scanFinding.Description = scanFinding.Description + " Correlated data flow: " + dataFlowChain.Summary + "."; } mergedDataFlowFindings.Add(dataFlowFinding); } } if (mergedDataFlowFindings.Count > 0) { findings.RemoveAll((ScanFinding f) => mergedDataFlowFindings.Contains(f)); } } private static bool IsExecutionSinkOperation(string operation) { if (string.IsNullOrWhiteSpace(operation)) { return false; } return operation.Contains("Process.Start", StringComparison.OrdinalIgnoreCase) || operation.Contains("PInvoke.ShellExecute", StringComparison.OrdinalIgnoreCase) || operation.Contains("PInvoke.CreateProcess", StringComparison.OrdinalIgnoreCase) || operation.Contains("PInvoke.WinExec", StringComparison.OrdinalIgnoreCase); } private static string TrimOffset(string location) { if (string.IsNullOrWhiteSpace(location)) { return location; } int num = location.LastIndexOf(':'); if (num < 0 || num == location.Length - 1) { return location; } int result = num + 1; string s = location.Substring(result, location.Length - result); return int.TryParse(s, out result) ? location.Substring(0, num) : location; } private static int? ExtractOffset(string location) { if (string.IsNullOrWhiteSpace(location)) { return null; } int num = location.LastIndexOf(':'); if (num < 0 || num == location.Length - 1) { return null; } int num2 = num + 1; string s = location.Substring(num2, location.Length - num2); int result; return int.TryParse(s, out result) ? new int?(result) : ((int?)null); } } [EditorBrowsable(EditorBrowsableState.Never)] public class CallGraphBuilder { private class SuspiciousDeclaration { public MethodDefinition Method { get; set; } = null; public string MethodKey { get; set; } = null; public string RuleId { get; set; } = null; public Severity RuleSeverity { get; set; } public string RuleDescription { get; set; } = null; public IDeveloperGuidance? DeveloperGuidance { get; set; } public string CodeSnippet { get; set; } = null; public string Description { get; set; } = null; public string Location { get; set; } = null; } private class CallSite { public MethodDefinition CallerMethod { get; set; } = null; public string CallerMethodKey { get; set; } = null; public string CalledMethodKey { get; set; } = null; public int InstructionOffset { get; set; } public string CodeSnippet { get; set; } = null; public string Location { get; set; } = null; public string? ContextDescription { get; set; } } private readonly IEnumerable _rules; private readonly CodeSnippetBuilder _snippetBuilder; private readonly IEntryPointProvider _entryPointProvider; private readonly Dictionary _suspiciousDeclarations = new Dictionary(); private readonly Dictionary> _callSites = new Dictionary>(); public int SuspiciousDeclarationCount => _suspiciousDeclarations.Count; public int CallSiteCount => _callSites.Values.Sum((List list) => list.Count); public CallGraphBuilder(IEnumerable rules, CodeSnippetBuilder snippetBuilder, IEntryPointProvider? entryPointProvider = null) { _rules = rules ?? throw new ArgumentNullException("rules"); _snippetBuilder = snippetBuilder ?? throw new ArgumentNullException("snippetBuilder"); _entryPointProvider = entryPointProvider ?? new GenericEntryPointProvider(); } public void Clear() { _suspiciousDeclarations.Clear(); _callSites.Clear(); } public void RegisterSuspiciousDeclaration(MethodDefinition method, IScanRule triggeringRule, string codeSnippet, string description, Severity? capturedSeverity = null, string? capturedRuleDescription = null, IDeveloperGuidance? capturedDeveloperGuidance = null) { string methodKey = GetMethodKey(method); if (!_suspiciousDeclarations.ContainsKey(methodKey)) { Dictionary suspiciousDeclarations = _suspiciousDeclarations; SuspiciousDeclaration obj = new SuspiciousDeclaration { Method = method, MethodKey = methodKey, RuleId = triggeringRule.RuleId, RuleSeverity = (capturedSeverity ?? triggeringRule.Severity), RuleDescription = (capturedRuleDescription ?? triggeringRule.Description), DeveloperGuidance = (capturedDeveloperGuidance ?? triggeringRule.DeveloperGuidance), CodeSnippet = codeSnippet, Description = description }; TypeDefinition declaringType = method.DeclaringType; obj.Location = ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) + "." + ((MemberReference)method).Name; suspiciousDeclarations[methodKey] = obj; } } public void RegisterCallSite(MethodDefinition callerMethod, MethodReference calledMethod, int instructionOffset, string codeSnippet, string? contextDescription = null) { string methodKey = GetMethodKey(calledMethod); if (!_callSites.TryGetValue(methodKey, out List value)) { value = new List(); _callSites[methodKey] = value; } string callerKey = GetMethodKey(callerMethod); CallSite callSite = value.FirstOrDefault((CallSite s) => s.CallerMethodKey == callerKey && s.InstructionOffset == instructionOffset); if (callSite != null) { if (string.IsNullOrWhiteSpace(callSite.ContextDescription) && !string.IsNullOrWhiteSpace(contextDescription)) { callSite.ContextDescription = contextDescription; } return; } List list = value; CallSite obj = new CallSite { CallerMethod = callerMethod, CallerMethodKey = callerKey, CalledMethodKey = methodKey, InstructionOffset = instructionOffset, CodeSnippet = codeSnippet }; TypeDefinition declaringType = callerMethod.DeclaringType; obj.Location = $"{((declaringType != null) ? ((MemberReference)declaringType).FullName : null)}.{((MemberReference)callerMethod).Name}:{instructionOffset}"; obj.ContextDescription = contextDescription; list.Add(obj); } public bool IsSuspiciousMethod(MethodReference method) { string methodKey = GetMethodKey(method); return _suspiciousDeclarations.ContainsKey(methodKey); } public bool IsMethodSuspiciousByRule(MethodReference method) { return _rules.Any((IScanRule rule) => rule.IsSuspicious(method)); } public IEnumerable BuildCallChainFindings() { List list = new List(); HashSet hashSet = new HashSet(); foreach (var (text2, declaration) in _suspiciousDeclarations) { if (!hashSet.Contains(text2)) { if (_callSites.TryGetValue(text2, out List value) && value.Count > 0) { CallChain callChain = BuildCallChain(declaration, value); ScanFinding item = CreateCallChainFinding(callChain, declaration); list.Add(item); } else { ScanFinding item2 = CreateStandaloneDeclarationFinding(declaration); list.Add(item2); } hashSet.Add(text2); } } return list; } private CallChain BuildCallChain(SuspiciousDeclaration declaration, List callSites) { CallChain callChain = new CallChain(declaration.RuleId + ":" + declaration.MethodKey, declaration.RuleId, declaration.RuleSeverity, BuildChainSummary(declaration, callSites)); foreach (CallSite callSite in callSites) { CallChainNodeType callChainNodeType = ((!_entryPointProvider.IsEntryPoint(callSite.CallerMethod)) ? CallChainNodeType.IntermediateCall : CallChainNodeType.EntryPoint); string text = ((callChainNodeType == CallChainNodeType.EntryPoint) ? ("Entry point calls " + ((MemberReference)declaration.Method).Name) : ("Calls " + ((MemberReference)declaration.Method).Name)); if (!string.IsNullOrWhiteSpace(callSite.ContextDescription)) { text = text + " (" + callSite.ContextDescription + ")"; } callChain.AppendNode(new CallChainNode(callSite.Location, text, callChainNodeType, callSite.CodeSnippet)); } callChain.AppendNode(new CallChainNode(declaration.Location, declaration.Description, CallChainNodeType.SuspiciousDeclaration, declaration.CodeSnippet)); return callChain; } private string BuildChainSummary(SuspiciousDeclaration declaration, List callSites) { IEnumerable values = callSites.Select((CallSite cs) => ((MemberReference)cs.CallerMethod).Name).Distinct().Take(3); string text = string.Join(", ", values); if (callSites.Count > 3) { text += $" (+{callSites.Count - 3} more)"; } string[] obj = new string[7] { declaration.RuleDescription, " - Hidden in ", null, null, null, null, null }; TypeDefinition declaringType = declaration.Method.DeclaringType; obj[2] = ((declaringType != null) ? ((MemberReference)declaringType).Name : null); obj[3] = "."; obj[4] = ((MemberReference)declaration.Method).Name; obj[5] = ", invoked from: "; obj[6] = text; string text2 = string.Concat(obj); List list = (from cs in callSites select cs.ContextDescription into context where !string.IsNullOrWhiteSpace(context) select context).Distinct(StringComparer.Ordinal).Take(2).ToList(); if (list.Count == 1) { text2 = text2 + ". " + list[0]; } else if (list.Count > 1) { text2 = text2 + ". Invocation contexts: " + string.Join(" | ", list); } return text2; } private ScanFinding CreateCallChainFinding(CallChain callChain, SuspiciousDeclaration declaration) { string location = ((callChain.Nodes.Count > 1) ? callChain.Nodes[0].Location : declaration.Location); ScanFinding scanFinding = new ScanFinding(location, callChain.Summary, callChain.Severity, callChain.ToCombinedCodeSnippet()); scanFinding.RuleId = declaration.RuleId; scanFinding.DeveloperGuidance = declaration.DeveloperGuidance; scanFinding.CallChain = callChain; return scanFinding; } private ScanFinding CreateStandaloneDeclarationFinding(SuspiciousDeclaration declaration) { ScanFinding scanFinding = new ScanFinding(declaration.Location, declaration.RuleDescription + " - " + declaration.Description + " (no callers detected - may be dead code or called via reflection)", declaration.RuleSeverity, declaration.CodeSnippet); scanFinding.RuleId = declaration.RuleId; scanFinding.DeveloperGuidance = declaration.DeveloperGuidance; return scanFinding; } private static string GetMethodKey(MethodReference method) { TypeReference declaringType = ((MemberReference)method).DeclaringType; return ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) + "." + ((MemberReference)method).Name; } private static string GetMethodKey(MethodDefinition method) { TypeDefinition declaringType = method.DeclaringType; return ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) + "." + ((MemberReference)method).Name; } } [EditorBrowsable(EditorBrowsableState.Never)] public class CodeSnippetBuilder { public string BuildSnippet(Collection instructions, int index, int contextLines) { StringBuilder stringBuilder = new StringBuilder(); for (int i = Math.Max(0, index - contextLines); i < Math.Min(instructions.Count, index + contextLines + 1); i++) { if (i == index) { stringBuilder.Append(">>> "); } else { stringBuilder.Append(" "); } stringBuilder.AppendLine(((object)instructions[i]).ToString()); } return stringBuilder.ToString().TrimEnd(); } } [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Use ScanConfig to control data flow behavior. DataFlowAnalyzerConfig is an internal pipeline detail and will be removed in v2.0.")] public class DataFlowAnalyzerConfig { public bool EnableCrossMethodAnalysis { get; set; } = true; public int MaxCallChainDepth { get; set; } = 5; public bool EnableReturnValueTracking { get; set; } = true; } [EditorBrowsable(EditorBrowsableState.Never)] public class DataFlowAnalyzer { private readonly DataFlowAnalysisState _state = new DataFlowAnalysisState(); private readonly DataFlowMethodAnalyzer _methodAnalyzer; private readonly CrossMethodDataFlowAnalyzer _crossMethodAnalyzer; private readonly DataFlowPatternEvaluator _patternEvaluator; private readonly ScanTelemetryHub _telemetry; public int DataFlowChainCount => _state.MethodDataFlows.Values.Sum((List list) => list.Count); public int SuspiciousChainCount => _state.MethodDataFlows.Values.SelectMany((List list) => list).Count((DataFlowChain chain) => chain.IsSuspicious); public int CrossMethodChainCount => _state.CrossMethodChains.Count; public int SuspiciousCrossMethodChainCount => _state.CrossMethodChains.Count((DataFlowChain chain) => chain.IsSuspicious); public DataFlowAnalyzer(IEnumerable rules, CodeSnippetBuilder snippetBuilder) : this(rules, snippetBuilder, new DataFlowAnalyzerConfig(), new ScanTelemetryHub()) { } [Obsolete("Use the overloads that rely on ScanConfig-driven behavior. DataFlowAnalyzerConfig is an internal pipeline detail and will be removed in v2.0.")] public DataFlowAnalyzer(IEnumerable rules, CodeSnippetBuilder snippetBuilder, DataFlowAnalyzerConfig config) : this(rules, snippetBuilder, config, new ScanTelemetryHub()) { } internal DataFlowAnalyzer(IEnumerable rules, CodeSnippetBuilder snippetBuilder, ScanTelemetryHub telemetry) : this(rules, snippetBuilder, new DataFlowAnalyzerConfig(), telemetry) { } internal DataFlowAnalyzer(IEnumerable rules, CodeSnippetBuilder snippetBuilder, DataFlowAnalyzerConfig config, ScanTelemetryHub telemetry) { if (rules == null) { throw new ArgumentNullException("rules"); } if (snippetBuilder == null) { throw new ArgumentNullException("snippetBuilder"); } if (config == null) { throw new ArgumentNullException("config"); } _telemetry = telemetry ?? throw new ArgumentNullException("telemetry"); DataFlowOperationClassifier operationClassifier = new DataFlowOperationClassifier(); DataFlowNodeFactory nodeFactory = new DataFlowNodeFactory(snippetBuilder); _patternEvaluator = new DataFlowPatternEvaluator(); _methodAnalyzer = new DataFlowMethodAnalyzer(operationClassifier, _patternEvaluator, nodeFactory); _crossMethodAnalyzer = new CrossMethodDataFlowAnalyzer(_patternEvaluator, nodeFactory, config); } public void Clear() { _state.Clear(); } public List AnalyzeMethod(MethodDefinition method) { if (((method != null) ? method.Body : null) == null || method.Body.Instructions.Count == 0) { _telemetry.IncrementCounter("DataFlowAnalyzer.MethodsSkipped", 1L); return new List(); } long startTimestamp = _telemetry.StartTimestamp(); DataFlowMethodAnalysisResult dataFlowMethodAnalysisResult = _methodAnalyzer.AnalyzeMethod(method); _telemetry.AddPhaseElapsed("DataFlowAnalyzer.AnalyzeMethod", startTimestamp); _state.StoreMethodAnalysis(dataFlowMethodAnalysisResult); _telemetry.IncrementCounter("DataFlowAnalyzer.MethodsAnalyzed", 1L); _telemetry.IncrementCounter("DataFlowAnalyzer.MethodChainsBuilt", dataFlowMethodAnalysisResult.Chains.Count); _telemetry.IncrementCounter("DataFlowAnalyzer.SuspiciousMethodChains", dataFlowMethodAnalysisResult.Chains.Count((DataFlowChain chain) => chain.IsSuspicious)); return dataFlowMethodAnalysisResult.Chains; } public void AnalyzeCrossMethodFlows() { long startTimestamp = _telemetry.StartTimestamp(); IReadOnlyList readOnlyList = _crossMethodAnalyzer.Analyze(_state); _telemetry.AddPhaseElapsed("DataFlowAnalyzer.AnalyzeCrossMethodFlows", startTimestamp); _telemetry.IncrementCounter("DataFlowAnalyzer.CrossMethodChainsBuilt", readOnlyList.Count); foreach (DataFlowChain item in readOnlyList) { if (item.IsSuspicious) { _state.CrossMethodChains.Add(item); } } _telemetry.IncrementCounter("DataFlowAnalyzer.SuspiciousCrossMethodChains", _state.CrossMethodChains.Count((DataFlowChain chain) => chain.IsSuspicious)); } public IEnumerable BuildDataFlowFindings() { long startTimestamp = _telemetry.StartTimestamp(); List list = new List(); foreach (DataFlowChain item in _state.MethodDataFlows.Values.SelectMany((List result) => result)) { if (item.IsSuspicious && _patternEvaluator.ShouldEmitFinding(item.Pattern)) { list.Add(_patternEvaluator.CreateFinding(item)); } } foreach (DataFlowChain crossMethodChain in _state.CrossMethodChains) { if (crossMethodChain.IsSuspicious && _patternEvaluator.ShouldEmitFinding(crossMethodChain.Pattern)) { list.Add(_patternEvaluator.CreateFinding(crossMethodChain)); } } _telemetry.AddPhaseElapsed("DataFlowAnalyzer.BuildDataFlowFindings", startTimestamp); _telemetry.IncrementCounter("DataFlowAnalyzer.FindingsEmitted", list.Count); return list; } } [EditorBrowsable(EditorBrowsableState.Never)] public class DllImportScanner { private readonly IEnumerable _rules; private readonly CallGraphBuilder? _callGraphBuilder; public DllImportScanner(IEnumerable rules, CallGraphBuilder? callGraphBuilder = null) { _rules = rules ?? throw new ArgumentNullException("rules"); _callGraphBuilder = callGraphBuilder; } public IEnumerable ScanForDllImports(ModuleDefinition module) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Invalid comparison between Unknown and I4 List list = new List(); try { foreach (TypeDefinition allType in TypeCollectionHelper.GetAllTypes(module)) { Enumerator enumerator2 = allType.Methods.GetEnumerator(); try { while (enumerator2.MoveNext()) { MethodDefinition method = enumerator2.Current; try { if ((method.Attributes & 0x2000) == 0 || method.PInvokeInfo == null) { continue; } IScanRule scanRule = _rules.FirstOrDefault((IScanRule rule) => rule.IsSuspicious((MethodReference)(object)method)); if (scanRule != null) { Severity severity = scanRule.Severity; string description = scanRule.Description; IDeveloperGuidance developerGuidance = scanRule.DeveloperGuidance; string name = method.PInvokeInfo.Module.Name; string text = method.PInvokeInfo.EntryPoint ?? ((MemberReference)method).Name; string codeSnippet = "[DllImport(\"" + name + "\", EntryPoint = \"" + text + "\")]\n" + ((MemberReference)((MethodReference)method).ReturnType).Name + " " + ((MemberReference)method).Name + "(" + string.Join(", ", ((IEnumerable)((MethodReference)method).Parameters).Select((ParameterDefinition p) => ((MemberReference)((ParameterReference)p).ParameterType).Name + " " + ((ParameterReference)p).Name)) + ");"; string description2 = "P/Invoke declaration imports " + text + " from " + name; if (_callGraphBuilder != null) { _callGraphBuilder.RegisterSuspiciousDeclaration(method, scanRule, codeSnippet, description2, severity, description, developerGuidance); continue; } ScanFinding scanFinding = new ScanFinding(((MemberReference)method.DeclaringType).FullName + "." + ((MemberReference)method).Name, description, severity, codeSnippet).WithRuleMetadata(scanRule); scanFinding.DeveloperGuidance = developerGuidance; list.Add(scanFinding); } } catch (Exception) { } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } } catch (Exception) { } return list; } } public class ExceptionHandlerAnalyzer { private readonly IEnumerable _rules; private readonly SignalTracker _signalTracker; private readonly CodeSnippetBuilder _snippetBuilder; private readonly ScanConfig _config; public ExceptionHandlerAnalyzer(IEnumerable rules, SignalTracker signalTracker, CodeSnippetBuilder snippetBuilder, ScanConfig config) { _rules = rules ?? throw new ArgumentNullException("rules"); _signalTracker = signalTracker ?? throw new ArgumentNullException("signalTracker"); _snippetBuilder = snippetBuilder ?? throw new ArgumentNullException("snippetBuilder"); _config = config ?? new ScanConfig(); } public IEnumerable AnalyzeExceptionHandlers(MethodDefinition method, Collection exceptionHandlers, MethodSignals? methodSignals, string typeFullName) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) List list = new List(); if (!_config.AnalyzeExceptionHandlers) { return list; } try { Enumerator enumerator = exceptionHandlers.GetEnumerator(); try { while (enumerator.MoveNext()) { ExceptionHandler current = enumerator.Current; if (current.HandlerStart != null && current.HandlerEnd != null) { IEnumerable collection = AnalyzeHandlerBlock(method, current, method.Body.Instructions, methodSignals, typeFullName); list.AddRange(collection); } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } } catch (Exception) { } return list; } private IEnumerable AnalyzeHandlerBlock(MethodDefinition method, ExceptionHandler handler, Collection allInstructions, MethodSignals? methodSignals, string typeFullName) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) List list = new List(); try { List instructionsInRange = GetInstructionsInRange(allInstructions, handler.HandlerStart, handler.HandlerEnd); if (instructionsInRange.Count == 0) { return list; } foreach (Instruction item in instructionsInRange) { if (!(item.OpCode == OpCodes.Call) && !(item.OpCode == OpCodes.Callvirt)) { continue; } object operand = item.Operand; MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null); if (val == null) { continue; } foreach (IScanRule rule in _rules) { if (!rule.IsSuspicious(val)) { continue; } int num = allInstructions.IndexOf(item); if (num >= 0 && methodSignals != null && rule.ShouldSuppressFinding(val, allInstructions, num, methodSignals)) { continue; } string codeSnippet = _snippetBuilder.BuildSnippet(allInstructions, num, 2); string handlerTypeDescription = GetHandlerTypeDescription(handler); TypeDefinition declaringType = method.DeclaringType; ScanFinding scanFinding = new ScanFinding($"{((declaringType != null) ? ((MemberReference)declaringType).FullName : null)}.{((MemberReference)method).Name}:{item.Offset}", rule.Description + " (found in exception " + handlerTypeDescription + ")", rule.Severity, codeSnippet) { RuleId = rule.RuleId, DeveloperGuidance = rule.DeveloperGuidance }; list.Add(scanFinding); if (methodSignals != null) { if (!rule.RequiresCompanionFinding || scanFinding.Severity != Severity.Low) { _signalTracker.MarkRuleTriggered(methodSignals, method.DeclaringType, rule.RuleId); } _signalTracker.MarkSuspiciousExceptionHandling(methodSignals, method.DeclaringType); } } } } catch (Exception) { } return list; } private static List GetInstructionsInRange(Collection allInstructions, Instruction start, Instruction? end) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) List list = new List(); if (start == null) { return list; } bool flag = false; Enumerator enumerator = allInstructions.GetEnumerator(); try { while (enumerator.MoveNext()) { Instruction current = enumerator.Current; if (current == start) { flag = true; } if (flag) { list.Add(current); } if (current == end) { break; } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } return list; } private static string GetHandlerTypeDescription(ExceptionHandler handler) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected I4, but got Unknown ExceptionHandlerType handlerType = handler.HandlerType; if (1 == 0) { } string result = (int)handlerType switch { 0 => "catch block", 2 => "finally block", 1 => "filter block", 4 => "fault block", _ => "handler", }; if (1 == 0) { } return result; } } [EditorBrowsable(EditorBrowsableState.Never)] public class InstructionAnalyzer { [EditorBrowsable(EditorBrowsableState.Never)] public class InstructionAnalysisResult { public List Findings { get; set; } = new List(); public List<(MethodDefinition method, Instruction instruction, int index, Collection instructions, MethodSignals? methodSignals)> PendingReflectionFindings { get; set; } = new List<(MethodDefinition, Instruction, int, Collection, MethodSignals)>(); } private static readonly HashSet ReflectionCompanionRuleIds = new HashSet(StringComparer.Ordinal) { "ProcessStartRule", "Shell32Rule", "COMReflectionAttackRule", "AssemblyDynamicLoadRule", "PersistenceRule", "RegistryRule", "DataExfiltrationRule", "DataInfiltrationRule", "Base64Rule", "HexStringRule", "EncodedStringLiteralRule", "EncodedStringPipelineRule", "EncodedBlobSplittingRule", "ByteArrayManipulationRule" }; private readonly IReadOnlyList _rules; private readonly IReadOnlyList _contextualPatternRules; private readonly SignalTracker _signalTracker; private readonly ReflectionDetector _reflectionDetector; private readonly StringPatternDetector _stringPatternDetector; private readonly CodeSnippetBuilder _snippetBuilder; private readonly ScanConfig _config; private readonly CallGraphBuilder? _callGraphBuilder; private readonly IScanRule? _reflectionRule; private readonly ScanTelemetryHub _telemetry; public InstructionAnalyzer(IEnumerable rules, SignalTracker signalTracker, ReflectionDetector reflectionDetector, StringPatternDetector stringPatternDetector, CodeSnippetBuilder snippetBuilder, ScanConfig config, CallGraphBuilder? callGraphBuilder = null) : this(rules, signalTracker, reflectionDetector, stringPatternDetector, snippetBuilder, config, new ScanTelemetryHub(), callGraphBuilder) { } internal InstructionAnalyzer(IEnumerable rules, SignalTracker signalTracker, ReflectionDetector reflectionDetector, StringPatternDetector stringPatternDetector, CodeSnippetBuilder snippetBuilder, ScanConfig config, ScanTelemetryHub telemetry, CallGraphBuilder? callGraphBuilder = null) { if (rules == null) { throw new ArgumentNullException("rules"); } _rules = rules.ToArray(); _contextualPatternRules = _rules.Where((IScanRule rule) => RuleOverrideChecker.OverridesRuleMethod(rule, "AnalyzeContextualPattern", typeof(MethodReference), typeof(Collection), typeof(int), typeof(MethodSignals))).ToArray(); _signalTracker = signalTracker ?? throw new ArgumentNullException("signalTracker"); _reflectionDetector = reflectionDetector ?? throw new ArgumentNullException("reflectionDetector"); _stringPatternDetector = stringPatternDetector ?? throw new ArgumentNullException("stringPatternDetector"); _snippetBuilder = snippetBuilder ?? throw new ArgumentNullException("snippetBuilder"); _config = config ?? new ScanConfig(); _telemetry = telemetry ?? throw new ArgumentNullException("telemetry"); _callGraphBuilder = callGraphBuilder; _reflectionRule = _rules.FirstOrDefault((IScanRule rule) => rule is ReflectionRule); } public InstructionAnalysisResult AnalyzeInstructions(MethodDefinition method, Collection instructions, MethodSignals? methodSignals, string typeFullName) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) InstructionAnalysisResult instructionAnalysisResult = new InstructionAnalysisResult(); MethodSignals methodSignals2 = methodSignals ?? _signalTracker.CreateMethodSignals() ?? new MethodSignals(); long startTimestamp = _telemetry.StartTimestamp(); _telemetry.IncrementCounter("InstructionAnalyzer.MethodsAnalyzed", 1L); _telemetry.IncrementCounter("InstructionAnalyzer.InstructionsVisited", instructions.Count); long startTimestamp2 = _telemetry.StartTimestamp(); HashSet hashSet = BuildExceptionHandlerOffsets(method, instructions); _telemetry.AddPhaseElapsed("InstructionAnalyzer.BuildExceptionHandlerOffsets", startTimestamp2); for (int i = 0; i < instructions.Count; i++) { Instruction val = instructions[i]; try { if (!(val.OpCode == OpCodes.Call) && !(val.OpCode == OpCodes.Callvirt)) { continue; } object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 == null) { continue; } _telemetry.IncrementCounter("InstructionAnalyzer.CallInstructions", 1L); if (methodSignals != null) { long startTimestamp3 = _telemetry.StartTimestamp(); _signalTracker.UpdateMethodSignals(methodSignals, val2, method.DeclaringType); _telemetry.AddPhaseElapsed("InstructionAnalyzer.UpdateMethodSignals", startTimestamp3); TypeReference declaringType = ((MemberReference)val2).DeclaringType; if (((declaringType != null) ? ((MemberReference)declaringType).FullName : null) == "System.Environment" && ((MemberReference)val2).Name == "GetFolderPath") { int? num = InstructionHelper.ExtractFolderPathArgument(instructions, i); if (num.HasValue && PersistenceRule.IsSensitiveFolder(num.Value)) { _signalTracker.MarkSensitiveFolder(methodSignals, method.DeclaringType); } } } bool flag = _callGraphBuilder != null && _callGraphBuilder.IsSuspiciousMethod(val2); if (flag) { _telemetry.IncrementCounter("InstructionAnalyzer.CallGraphTrackedCalls", 1L); string codeSnippet = _snippetBuilder.BuildSnippet(instructions, i, 8); string contextDescription = DllImportInvocationContextExtractor.TryBuildContext(method, val2, instructions, i); _callGraphBuilder.RegisterCallSite(method, val2, val.Offset, codeSnippet, contextDescription); if (methodSignals != null) { IScanRule scanRule = FindFirstSuspiciousRule(val2); if (scanRule != null) { _signalTracker.MarkRuleTriggered(methodSignals, method.DeclaringType, scanRule.RuleId); } } continue; } if (!hashSet.Contains(val.Offset)) { foreach (IScanRule contextualPatternRule in _contextualPatternRules) { _telemetry.IncrementCounter("InstructionAnalyzer.ContextualRuleInvocations", 1L); long startTimestamp4 = _telemetry.StartTimestamp(); IEnumerable enumerable = contextualPatternRule.AnalyzeContextualPattern(val2, instructions, i, methodSignals2); _telemetry.AddPhaseElapsed("InstructionAnalyzer.ContextualRuleDispatch", startTimestamp4); foreach (ScanFinding item in enumerable) { if (contextualPatternRule.RequiresCompanionFinding && item.Severity != Severity.Low && !item.BypassCompanionCheck) { bool flag2 = methodSignals?.HasTriggeredRuleOtherThan(contextualPatternRule.RuleId) ?? false; bool flag3 = false; if (!string.IsNullOrEmpty(typeFullName)) { MethodSignals typeSignals = _signalTracker.GetTypeSignals(typeFullName); if (typeSignals != null) { flag3 = typeSignals.HasTriggeredRuleOtherThan(contextualPatternRule.RuleId); } } if (!flag2 && !flag3) { continue; } } item.WithRuleMetadata(contextualPatternRule); instructionAnalysisResult.Findings.Add(item); _telemetry.IncrementCounter("InstructionAnalyzer.ContextualFindings", 1L); if (methodSignals != null && (!contextualPatternRule.RequiresCompanionFinding || item.Severity != Severity.Low)) { _signalTracker.MarkRuleTriggered(methodSignals, method.DeclaringType, contextualPatternRule.RuleId); } } } } else { _telemetry.IncrementCounter("InstructionAnalyzer.ExceptionHandlerSkippedCalls", 1L); } long startTimestamp5 = _telemetry.StartTimestamp(); bool flag4 = _reflectionDetector.IsReflectionInvokeMethod(val2); _telemetry.AddPhaseElapsed("InstructionAnalyzer.ReflectionInvokeCheck", startTimestamp5); if (flag4) { _telemetry.IncrementCounter("InstructionAnalyzer.ReflectionInvokes", 1L); IScanRule reflectionRule = _reflectionRule; if (reflectionRule != null) { bool flag5 = HasStrongReflectionCompanion(methodSignals, reflectionRule.RuleId); bool flag6 = false; if (!string.IsNullOrEmpty(typeFullName)) { MethodSignals typeSignals2 = _signalTracker.GetTypeSignals(typeFullName); if (typeSignals2 != null) { flag6 = HasStrongReflectionCompanion(typeSignals2, reflectionRule.RuleId); } } if (!flag5 && !flag6) { if (_config.EnableMultiSignalDetection && method.DeclaringType != null) { instructionAnalysisResult.PendingReflectionFindings.Add((method, val, i, instructions, methodSignals)); _telemetry.IncrementCounter("InstructionAnalyzer.PendingReflectionQueued", 1L); } } else { string codeSnippet2 = _snippetBuilder.BuildSnippet(instructions, i, 2); TypeDefinition declaringType2 = method.DeclaringType; ScanFinding scanFinding = new ScanFinding($"{((declaringType2 != null) ? ((MemberReference)declaringType2).FullName : null)}.{((MemberReference)method).Name}:{val.Offset}", reflectionRule.Description + " (combined with other suspicious patterns)", reflectionRule.Severity, codeSnippet2).WithRuleMetadata(reflectionRule); instructionAnalysisResult.Findings.Add(scanFinding); _telemetry.IncrementCounter("InstructionAnalyzer.ReflectionFindings", 1L); if (methodSignals != null && (!reflectionRule.RequiresCompanionFinding || scanFinding.Severity != Severity.Low)) { _signalTracker.MarkRuleTriggered(methodSignals, method.DeclaringType, reflectionRule.RuleId); } } } } if (hashSet.Contains(val.Offset) || flag || flag4) { goto IL_07ff; } long startTimestamp6 = _telemetry.StartTimestamp(); IScanRule scanRule2 = FindFirstSuspiciousRule(val2); _telemetry.AddPhaseElapsed("InstructionAnalyzer.FindFirstSuspiciousRule", startTimestamp6); if (scanRule2 == null) { goto IL_07ff; } _telemetry.IncrementCounter("InstructionAnalyzer.DirectSuspiciousMatches", 1L); MethodSignals typeSignals3 = null; if (!string.IsNullOrEmpty(typeFullName)) { typeSignals3 = _signalTracker.GetTypeSignals(typeFullName); } long startTimestamp7 = _telemetry.StartTimestamp(); if (scanRule2.ShouldSuppressFinding(val2, instructions, i, methodSignals2, typeSignals3)) { _telemetry.AddPhaseElapsed("InstructionAnalyzer.ShouldSuppressFinding", startTimestamp7); _telemetry.IncrementCounter("InstructionAnalyzer.SuppressedFindings", 1L); continue; } _telemetry.AddPhaseElapsed("InstructionAnalyzer.ShouldSuppressFinding", startTimestamp7); string codeSnippet3 = _snippetBuilder.BuildSnippet(instructions, i, 2); string findingDescription = scanRule2.GetFindingDescription(method, val2, instructions, i); TypeDefinition declaringType3 = method.DeclaringType; ScanFinding scanFinding2 = new ScanFinding($"{((declaringType3 != null) ? ((MemberReference)declaringType3).FullName : null)}.{((MemberReference)method).Name}:{val.Offset}", findingDescription, scanRule2.Severity, codeSnippet3).WithRuleMetadata(scanRule2); if (HasEquivalentFinding(instructionAnalysisResult.Findings, scanFinding2)) { continue; } instructionAnalysisResult.Findings.Add(scanFinding2); _telemetry.IncrementCounter("InstructionAnalyzer.DirectFindings", 1L); if (methodSignals != null && (!scanRule2.RequiresCompanionFinding || scanFinding2.Severity != Severity.Low)) { _signalTracker.MarkRuleTriggered(methodSignals, method.DeclaringType, scanRule2.RuleId); } goto IL_07ff; IL_07ff: long startTimestamp8 = _telemetry.StartTimestamp(); IEnumerable enumerable2 = _reflectionDetector.ScanForReflectionInvocation(method, val, val2, i, instructions, methodSignals); _telemetry.AddPhaseElapsed("InstructionAnalyzer.ReflectionDetector", startTimestamp8); foreach (ScanFinding item2 in enumerable2) { instructionAnalysisResult.Findings.Add(item2); _telemetry.IncrementCounter("InstructionAnalyzer.ReflectionBypassFindings", 1L); if (methodSignals != null && _reflectionRule != null && (!_reflectionRule.RequiresCompanionFinding || item2.Severity != Severity.Low)) { _signalTracker.MarkRuleTriggered(methodSignals, method.DeclaringType, _reflectionRule.RuleId); } } } catch (Exception) { } } _telemetry.AddPhaseElapsed("InstructionAnalyzer.AnalyzeInstructions", startTimestamp); return instructionAnalysisResult; } private IScanRule? FindFirstSuspiciousRule(MethodReference calledMethod) { foreach (IScanRule rule in _rules) { if (rule.IsSuspicious(calledMethod)) { return rule; } } return null; } private static bool HasEquivalentFinding(IEnumerable findings, ScanFinding candidate) { return findings.Any((ScanFinding existing) => string.Equals(existing.RuleId, candidate.RuleId, StringComparison.Ordinal) && string.Equals(existing.Location, candidate.Location, StringComparison.Ordinal) && string.Equals(existing.Description, candidate.Description, StringComparison.Ordinal) && existing.Severity == candidate.Severity); } private HashSet BuildExceptionHandlerOffsets(MethodDefinition method, Collection instructions) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) HashSet hashSet = new HashSet(); if (!_config.AnalyzeExceptionHandlers || !method.Body.HasExceptionHandlers) { return hashSet; } Enumerator enumerator = method.Body.ExceptionHandlers.GetEnumerator(); try { while (enumerator.MoveNext()) { ExceptionHandler current = enumerator.Current; if (current.HandlerStart == null) { continue; } int offset = current.HandlerStart.Offset; Instruction handlerEnd = current.HandlerEnd; int num = ((handlerEnd != null) ? handlerEnd.Offset : int.MaxValue); Enumerator enumerator2 = instructions.GetEnumerator(); try { while (enumerator2.MoveNext()) { Instruction current2 = enumerator2.Current; if (current2.Offset >= offset && current2.Offset < num) { hashSet.Add(current2.Offset); } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } return hashSet; } private static bool HasStrongReflectionCompanion(MethodSignals? signals, string reflectionRuleId) { if (signals == null) { return false; } foreach (string triggeredRuleId in signals.GetTriggeredRuleIds()) { if (triggeredRuleId.Equals(reflectionRuleId, StringComparison.Ordinal) || !ReflectionCompanionRuleIds.Contains(triggeredRuleId)) { continue; } return true; } return false; } } [EditorBrowsable(EditorBrowsableState.Never)] public class LocalVariableAnalyzer { private readonly IReadOnlyList _localVariableRules; private readonly SignalTracker _signalTracker; private readonly ScanConfig _config; public LocalVariableAnalyzer(IEnumerable rules, SignalTracker signalTracker, ScanConfig config) { if (rules == null) { throw new ArgumentNullException("rules"); } _signalTracker = signalTracker ?? throw new ArgumentNullException("signalTracker"); _config = config ?? new ScanConfig(); _localVariableRules = rules.Where((IScanRule rule) => rule is SuspiciousLocalVariableRule).ToArray(); } public IEnumerable AnalyzeLocalVariables(MethodDefinition method, Collection variables, MethodSignals? methodSignals) { List list = new List(); MethodSignals methodSignals2 = methodSignals ?? _signalTracker.CreateMethodSignals() ?? new MethodSignals(); if (!_config.AnalyzeLocalVariables) { return list; } try { foreach (IScanRule localVariableRule in _localVariableRules) { IEnumerable enumerable = localVariableRule.AnalyzeInstructions(method, method.Body.Instructions, methodSignals2); foreach (ScanFinding item in enumerable) { if (!localVariableRule.RequiresCompanionFinding || (methodSignals != null && methodSignals.HasTriggeredRuleOtherThan(localVariableRule.RuleId))) { item.WithRuleMetadata(localVariableRule); list.Add(item); if (methodSignals != null && (!localVariableRule.RequiresCompanionFinding || item.Severity != Severity.Low)) { _signalTracker.MarkRuleTriggered(methodSignals, method.DeclaringType, localVariableRule.RuleId); } } } } if (methodSignals2.HasSuspiciousLocalVariables) { _signalTracker.MarkSuspiciousLocalVariables(methodSignals2, method.DeclaringType); } } catch (Exception) { } return list; } internal IReadOnlyCollection GetProcessedRuleIds() { return (IReadOnlyCollection)(object)_localVariableRules.Select((IScanRule rule) => rule.RuleId).ToArray(); } } [EditorBrowsable(EditorBrowsableState.Never)] public class MetadataScanner { private readonly IEnumerable _rules; public MetadataScanner(IEnumerable rules) { _rules = rules ?? throw new ArgumentNullException("rules"); } public IEnumerable ScanAssemblyMetadata(AssemblyDefinition assembly) { List list = new List(); try { foreach (IScanRule rule in _rules) { IEnumerable enumerable = rule.AnalyzeAssemblyMetadata(assembly); foreach (ScanFinding item in enumerable) { item.WithRuleMetadata(rule); list.Add(item); } } } catch (Exception) { } return list; } } [EditorBrowsable(EditorBrowsableState.Never)] public class MethodScanner { [EditorBrowsable(EditorBrowsableState.Never)] public class MethodScanResult { public List Findings { get; set; } = new List(); public List<(MethodDefinition method, Instruction instruction, int index, Collection instructions, MethodSignals? methodSignals)> PendingReflectionFindings { get; set; } = new List<(MethodDefinition, Instruction, int, Collection, MethodSignals)>(); } private readonly IReadOnlyList _rules; private readonly IReadOnlyList _stringLiteralRules; private readonly SignalTracker _signalTracker; private readonly InstructionAnalyzer _instructionAnalyzer; private readonly CodeSnippetBuilder _snippetBuilder; private readonly LocalVariableAnalyzer _localVariableAnalyzer; private readonly ExceptionHandlerAnalyzer _exceptionHandlerAnalyzer; private readonly ScanConfig _config; private readonly ScanTelemetryHub _telemetry; public MethodScanner(IEnumerable rules, SignalTracker signalTracker, InstructionAnalyzer instructionAnalyzer, CodeSnippetBuilder snippetBuilder, LocalVariableAnalyzer localVariableAnalyzer, ExceptionHandlerAnalyzer exceptionHandlerAnalyzer, ScanConfig config) : this(rules, signalTracker, instructionAnalyzer, snippetBuilder, localVariableAnalyzer, exceptionHandlerAnalyzer, config, new ScanTelemetryHub()) { } internal MethodScanner(IEnumerable rules, SignalTracker signalTracker, InstructionAnalyzer instructionAnalyzer, CodeSnippetBuilder snippetBuilder, LocalVariableAnalyzer localVariableAnalyzer, ExceptionHandlerAnalyzer exceptionHandlerAnalyzer, ScanConfig config, ScanTelemetryHub telemetry) { if (rules == null) { throw new ArgumentNullException("rules"); } _rules = rules.ToArray(); _stringLiteralRules = _rules.Where((IScanRule rule) => RuleOverrideChecker.OverridesRuleMethod(rule, "AnalyzeStringLiteral", typeof(string), typeof(MethodDefinition), typeof(int))).ToArray(); _signalTracker = signalTracker ?? throw new ArgumentNullException("signalTracker"); _instructionAnalyzer = instructionAnalyzer ?? throw new ArgumentNullException("instructionAnalyzer"); _snippetBuilder = snippetBuilder ?? throw new ArgumentNullException("snippetBuilder"); _localVariableAnalyzer = localVariableAnalyzer ?? throw new ArgumentNullException("localVariableAnalyzer"); _exceptionHandlerAnalyzer = exceptionHandlerAnalyzer ?? throw new ArgumentNullException("exceptionHandlerAnalyzer"); _config = config ?? new ScanConfig(); _telemetry = telemetry ?? throw new ArgumentNullException("telemetry"); } public MethodScanResult ScanMethod(MethodDefinition method, string typeFullName) { //IL_040e: Unknown result type (might be due to invalid IL or missing references) //IL_0413: Unknown result type (might be due to invalid IL or missing references) MethodScanResult methodScanResult = new MethodScanResult(); TypeDefinition declaringType = method.DeclaringType; string methodName = ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) + "." + ((MemberReference)method).Name; long startTimestamp = _telemetry.StartTimestamp(); int num = (method.HasBody ? method.Body.Instructions.Count : 0); int num2 = ((method.HasBody && method.Body.HasVariables) ? method.Body.Variables.Count : 0); int num3 = ((method.HasBody && method.Body.HasExceptionHandlers) ? method.Body.ExceptionHandlers.Count : 0); try { if (!method.HasBody) { _telemetry.IncrementCounter("MethodScanner.MethodsWithoutBodySkipped", 1L); return methodScanResult; } _telemetry.IncrementCounter("MethodScanner.MethodsScanned", 1L); _telemetry.IncrementCounter("MethodScanner.InstructionsVisited", num); _telemetry.IncrementCounter("MethodScanner.LocalVariablesVisited", num2); _telemetry.IncrementCounter("MethodScanner.ExceptionHandlersVisited", num3); Collection instructions = method.Body.Instructions; HashSet hashSet = new HashSet(StringComparer.Ordinal); MethodSignals methodSignals = _signalTracker.CreateMethodSignals(); MethodSignals methodSignals2 = methodSignals ?? new MethodSignals(); if (method.Body.HasVariables) { long startTimestamp2 = _telemetry.StartTimestamp(); IEnumerable collection = _localVariableAnalyzer.AnalyzeLocalVariables(method, method.Body.Variables, methodSignals); _telemetry.AddPhaseElapsed("MethodScanner.AnalyzeLocalVariables", startTimestamp2); methodScanResult.Findings.AddRange(collection); if (_config.AnalyzeLocalVariables) { hashSet = new HashSet(_localVariableAnalyzer.GetProcessedRuleIds(), StringComparer.Ordinal); } } if (method.Body.HasExceptionHandlers) { long startTimestamp3 = _telemetry.StartTimestamp(); IEnumerable collection2 = _exceptionHandlerAnalyzer.AnalyzeExceptionHandlers(method, method.Body.ExceptionHandlers, methodSignals, typeFullName); _telemetry.AddPhaseElapsed("MethodScanner.AnalyzeExceptionHandlers", startTimestamp3); methodScanResult.Findings.AddRange(collection2); } long startTimestamp4 = _telemetry.StartTimestamp(); foreach (IScanRule rule in _rules) { if (hashSet.Contains(rule.RuleId)) { continue; } _telemetry.IncrementCounter("MethodScanner.RuleInstructionPasses", 1L); IEnumerable enumerable = rule.AnalyzeInstructions(method, instructions, methodSignals2); foreach (ScanFinding item in enumerable) { if (!rule.RequiresCompanionFinding || item.Severity == Severity.Low || item.BypassCompanionCheck || (methodSignals != null && methodSignals.HasTriggeredRuleOtherThan(rule.RuleId))) { item.WithRuleMetadata(rule); methodScanResult.Findings.Add(item); if (methodSignals != null && (!rule.RequiresCompanionFinding || item.Severity != Severity.Low)) { _signalTracker.MarkRuleTriggered(methodSignals, method.DeclaringType, rule.RuleId); } if (methodSignals != null && (rule is EncodedStringLiteralRule || rule is EncodedStringPipelineRule || rule is EncodedBlobSplittingRule)) { _signalTracker.MarkEncodedStrings(methodSignals, method.DeclaringType); } } } } _telemetry.AddPhaseElapsed("MethodScanner.RuleInstructionPasses", startTimestamp4); long startTimestamp5 = _telemetry.StartTimestamp(); for (int i = 0; i < instructions.Count; i++) { Instruction val = instructions[i]; if (!(val.OpCode == OpCodes.Ldstr) || !(val.Operand is string literal)) { continue; } _telemetry.IncrementCounter("MethodScanner.StringLiteralInstructions", 1L); foreach (IScanRule stringLiteralRule in _stringLiteralRules) { _telemetry.IncrementCounter("MethodScanner.StringLiteralRulePasses", 1L); IEnumerable enumerable2 = stringLiteralRule.AnalyzeStringLiteral(literal, method, i); foreach (ScanFinding item2 in enumerable2) { if (!stringLiteralRule.RequiresCompanionFinding || item2.Severity == Severity.Low || item2.BypassCompanionCheck || (methodSignals != null && methodSignals.HasTriggeredRuleOtherThan(stringLiteralRule.RuleId))) { item2.WithRuleMetadata(stringLiteralRule); methodScanResult.Findings.Add(item2); if (methodSignals != null && (!stringLiteralRule.RequiresCompanionFinding || item2.Severity != Severity.Low)) { _signalTracker.MarkRuleTriggered(methodSignals, method.DeclaringType, stringLiteralRule.RuleId); } if (methodSignals != null) { _signalTracker.MarkEncodedStrings(methodSignals, method.DeclaringType); } } } } } _telemetry.AddPhaseElapsed("MethodScanner.StringLiteralPasses", startTimestamp5); long startTimestamp6 = _telemetry.StartTimestamp(); InstructionAnalyzer.InstructionAnalysisResult instructionAnalysisResult = _instructionAnalyzer.AnalyzeInstructions(method, instructions, methodSignals, typeFullName); _telemetry.AddPhaseElapsed("MethodScanner.InstructionAnalyzer", startTimestamp6); methodScanResult.Findings.AddRange(instructionAnalysisResult.Findings); methodScanResult.PendingReflectionFindings.AddRange(instructionAnalysisResult.PendingReflectionFindings); if (methodSignals != null && _config.EnableMultiSignalDetection) { if (methodSignals.IsCriticalCombination()) { List findings = methodScanResult.Findings; TypeDefinition declaringType2 = method.DeclaringType; findings.Add(new ScanFinding(((declaringType2 != null) ? ((MemberReference)declaringType2).FullName : null) + "." + ((MemberReference)method).Name, "Critical: Multiple suspicious patterns detected (" + methodSignals.GetCombinationDescription() + ")", Severity.Critical, $"This method contains {methodSignals.SignalCount} suspicious signals that form a likely malicious pattern.") { RuleId = "MultiSignalDetection" }); } else if (methodSignals.IsHighRiskCombination()) { List findings2 = methodScanResult.Findings; TypeDefinition declaringType3 = method.DeclaringType; findings2.Add(new ScanFinding(((declaringType3 != null) ? ((MemberReference)declaringType3).FullName : null) + "." + ((MemberReference)method).Name, "High risk: Multiple suspicious patterns detected (" + methodSignals.GetCombinationDescription() + ")", Severity.High, $"This method contains {methodSignals.SignalCount} suspicious signals.") { RuleId = "MultiSignalDetection" }); } } } catch (Exception) { } finally { _telemetry.AddPhaseElapsed("MethodScanner.ScanMethod", startTimestamp); _telemetry.RecordMethodSample(methodName, startTimestamp, num, methodScanResult.Findings.Count, num2, num3, methodScanResult.PendingReflectionFindings.Count); } return methodScanResult; } } [EditorBrowsable(EditorBrowsableState.Never)] public class PropertyEventScanner { private readonly MethodScanner _methodScanner; private readonly ScanConfig _config; public PropertyEventScanner(MethodScanner methodScanner, ScanConfig config) { _methodScanner = methodScanner ?? throw new ArgumentNullException("methodScanner"); _config = config ?? new ScanConfig(); } public IReadOnlyDictionary BuildAccessorContexts(TypeDefinition type) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) Dictionary dictionary = new Dictionary(); if (!_config.AnalyzePropertyAccessors) { return dictionary; } try { if (type.HasProperties) { Enumerator enumerator = type.Properties.GetEnumerator(); try { while (enumerator.MoveNext()) { PropertyDefinition current = enumerator.Current; AddContext(dictionary, current.GetMethod, "found in property getter: " + ((MemberReference)current).Name); AddContext(dictionary, current.SetMethod, "found in property setter: " + ((MemberReference)current).Name); } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } } if (type.HasEvents) { Enumerator enumerator2 = type.Events.GetEnumerator(); try { while (enumerator2.MoveNext()) { EventDefinition current2 = enumerator2.Current; AddContext(dictionary, current2.AddMethod, "found in event add: " + ((MemberReference)current2).Name); AddContext(dictionary, current2.RemoveMethod, "found in event remove: " + ((MemberReference)current2).Name); AddContext(dictionary, current2.InvokeMethod, "found in event invoke: " + ((MemberReference)current2).Name); } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } } catch (Exception) { } return dictionary; } public IEnumerable ScanProperties(TypeDefinition type, string typeFullName) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) List list = new List(); if (!_config.AnalyzePropertyAccessors || !type.HasProperties) { return list; } try { Enumerator enumerator = type.Properties.GetEnumerator(); try { while (enumerator.MoveNext()) { PropertyDefinition current = enumerator.Current; MethodDefinition getMethod = current.GetMethod; if (getMethod != null && getMethod.HasBody) { IEnumerable collection = ScanPropertyAccessor(current.GetMethod, ((MemberReference)current).Name, "getter", typeFullName); list.AddRange(collection); } MethodDefinition setMethod = current.SetMethod; if (setMethod != null && setMethod.HasBody) { IEnumerable collection2 = ScanPropertyAccessor(current.SetMethod, ((MemberReference)current).Name, "setter", typeFullName); list.AddRange(collection2); } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } } catch (Exception) { } return list; } public IEnumerable ScanEvents(TypeDefinition type, string typeFullName) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) List list = new List(); if (!_config.AnalyzePropertyAccessors || !type.HasEvents) { return list; } try { Enumerator enumerator = type.Events.GetEnumerator(); try { while (enumerator.MoveNext()) { EventDefinition current = enumerator.Current; MethodDefinition addMethod = current.AddMethod; if (addMethod != null && addMethod.HasBody) { IEnumerable collection = ScanEventHandler(current.AddMethod, ((MemberReference)current).Name, "add", typeFullName); list.AddRange(collection); } MethodDefinition removeMethod = current.RemoveMethod; if (removeMethod != null && removeMethod.HasBody) { IEnumerable collection2 = ScanEventHandler(current.RemoveMethod, ((MemberReference)current).Name, "remove", typeFullName); list.AddRange(collection2); } MethodDefinition invokeMethod = current.InvokeMethod; if (invokeMethod != null && invokeMethod.HasBody) { IEnumerable collection3 = ScanEventHandler(current.InvokeMethod, ((MemberReference)current).Name, "invoke", typeFullName); list.AddRange(collection3); } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } } catch (Exception) { } return list; } private IEnumerable ScanPropertyAccessor(MethodDefinition method, string propertyName, string accessorType, string typeFullName) { List list = new List(); try { MethodScanner.MethodScanResult methodScanResult = _methodScanner.ScanMethod(method, typeFullName); foreach (ScanFinding finding in methodScanResult.Findings) { ScanFinding item = new ScanFinding(finding.Location, finding.Description + " (found in property " + accessorType + ": " + propertyName + ")", finding.Severity, finding.CodeSnippet) { RuleId = finding.RuleId, DeveloperGuidance = finding.DeveloperGuidance, CallChain = finding.CallChain, DataFlowChain = finding.DataFlowChain, BypassCompanionCheck = finding.BypassCompanionCheck, RiskScore = finding.RiskScore }; list.Add(item); } } catch (Exception) { } return list; } private IEnumerable ScanEventHandler(MethodDefinition method, string eventName, string handlerType, string typeFullName) { List list = new List(); try { MethodScanner.MethodScanResult methodScanResult = _methodScanner.ScanMethod(method, typeFullName); foreach (ScanFinding finding in methodScanResult.Findings) { ScanFinding item = new ScanFinding(finding.Location, finding.Description + " (found in event " + handlerType + ": " + eventName + ")", finding.Severity, finding.CodeSnippet) { RuleId = finding.RuleId, DeveloperGuidance = finding.DeveloperGuidance, CallChain = finding.CallChain, DataFlowChain = finding.DataFlowChain, BypassCompanionCheck = finding.BypassCompanionCheck, RiskScore = finding.RiskScore }; list.Add(item); } } catch (Exception) { } return list; } private static void AddContext(Dictionary contexts, MethodDefinition? method, string context) { if (method == null || !method.HasBody) { return; } if (contexts.TryGetValue(method, out string value)) { if (!value.Contains(context, StringComparison.Ordinal)) { contexts[method] = value + "; " + context; } } else { contexts[method] = context; } } } [EditorBrowsable(EditorBrowsableState.Never)] public class ReflectionDetector { private readonly IEnumerable _rules; private readonly SignalTracker _signalTracker; private readonly StringPatternDetector _stringPatternDetector; private readonly CodeSnippetBuilder _snippetBuilder; public ReflectionDetector(IEnumerable rules, SignalTracker signalTracker, StringPatternDetector stringPatternDetector, CodeSnippetBuilder snippetBuilder) { _rules = rules ?? throw new ArgumentNullException("rules"); _signalTracker = signalTracker ?? throw new ArgumentNullException("signalTracker"); _stringPatternDetector = stringPatternDetector ?? throw new ArgumentNullException("stringPatternDetector"); _snippetBuilder = snippetBuilder ?? throw new ArgumentNullException("snippetBuilder"); } public bool IsReflectionInvokeMethod(MethodReference method) { //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) if (((MemberReference)method).DeclaringType == null) { return false; } string fullName = ((MemberReference)((MemberReference)method).DeclaringType).FullName; string name = ((MemberReference)method).Name; if (fullName == "System.Type" && name == "InvokeMember") { return true; } if (fullName == "System.Type" && name == "GetTypeFromProgID") { return true; } if (fullName == "System.Activator" && name == "CreateInstance") { return true; } if ((fullName == "System.Type" && name == "GetTypeFromProgID") || (fullName == "System.Type" && name == "GetTypeFromCLSID")) { Enumerator enumerator = method.Parameters.GetEnumerator(); try { while (enumerator.MoveNext()) { ParameterDefinition current = enumerator.Current; if (((ParameterReference)current).Name.Contains("Shell") || ((ParameterReference)current).Name.Contains("Command") || ((ParameterReference)current).Name.Contains("Process") || ((ParameterReference)current).Name.Contains("Exec")) { return true; } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } } if ((fullName == "System.Reflection.MethodInfo" && name == "Invoke") || (fullName == "System.Reflection.MethodBase" && name == "Invoke")) { return true; } return false; } public IEnumerable ScanForReflectionInvocation(MethodDefinition methodDef, Instruction instruction, MethodReference calledMethod, int index, Collection instructions, MethodSignals? methodSignals) { //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Expected O, but got Unknown //IL_01a2: Expected O, but got Unknown List list = new List(); try { if (!IsReflectionInvokeMethod(calledMethod)) { return list; } string invokedMethodName = ExtractInvokedMethodName(instructions, index); bool flag = (methodSignals != null && HasStrongCompanionSignals(methodSignals)) || _stringPatternDetector.HasAssemblyLoadingInMethod(methodDef, instructions) || _stringPatternDetector.HasSuspiciousStringPatterns(methodDef, instructions, index); bool flag2 = false; if (methodDef.DeclaringType != null) { MethodSignals typeSignals = _signalTracker.GetTypeSignals(((MemberReference)methodDef.DeclaringType).FullName); if (typeSignals != null) { flag2 = HasStrongCompanionSignals(typeSignals); } } if (string.IsNullOrEmpty(invokedMethodName)) { if (!flag && !flag2) { return list; } Severity severity = Severity.High; string codeSnippet = _snippetBuilder.BuildSnippet(instructions, index, 4); TypeDefinition declaringType = methodDef.DeclaringType; list.Add(new ScanFinding($"{((declaringType != null) ? ((MemberReference)declaringType).FullName : null)}.{((MemberReference)methodDef).Name}:{instruction.Offset}", "Reflection invocation with non-literal target method name (cannot determine what is being invoked) - combined with other suspicious patterns", severity, codeSnippet) { RuleId = "ReflectionRule" }); return list; } if (!flag && !flag2) { return list; } MethodReference fakeMethodRef = new MethodReference(invokedMethodName, ((MemberReference)methodDef).Module.TypeSystem.Object) { DeclaringType = new TypeReference("", "ReflectedType", ((MemberReference)methodDef).Module, (IMetadataScope)null) }; if (_rules.Any((IScanRule rule) => rule.IsSuspicious(fakeMethodRef) || WouldRuleMatchMethodName(rule, invokedMethodName))) { IScanRule scanRule = _rules.FirstOrDefault((IScanRule r) => r.IsSuspicious(fakeMethodRef) || WouldRuleMatchMethodName(r, invokedMethodName)); if (scanRule == null) { return list; } string codeSnippet2 = _snippetBuilder.BuildSnippet(instructions, index, 4); TypeDefinition declaringType2 = methodDef.DeclaringType; list.Add(new ScanFinding($"{((declaringType2 != null) ? ((MemberReference)declaringType2).FullName : null)}.{((MemberReference)methodDef).Name}:{instruction.Offset}", "Potential reflection bypass: " + scanRule.Description, (scanRule.Severity == Severity.Low) ? Severity.Medium : scanRule.Severity, codeSnippet2) { RuleId = scanRule.RuleId, DeveloperGuidance = scanRule.DeveloperGuidance }); } } catch (Exception) { } return list; } private string? ExtractInvokedMethodName(Collection instructions, int currentIndex) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0309: Unknown result type (might be due to invalid IL or missing references) //IL_030e: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_0258: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_027c: Unknown result type (might be due to invalid IL or missing references) //IL_0281: Unknown result type (might be due to invalid IL or missing references) int num = -1; string text = null; for (int i = Math.Max(0, currentIndex - 20); i < currentIndex; i++) { Instruction val = instructions[i]; if (val.OpCode == OpCodes.Ldstr && val.Operand is string text2) { string text3 = text2; if (EncodedStringLiteralRule.IsEncodedString(text2)) { string text4 = EncodedStringLiteralRule.DecodeNumericString(text2); if (!string.IsNullOrEmpty(text4)) { text3 = text4; } } if (text3.Contains("Shell.Application") || text3.Contains("shell32")) { return "ShellExecute"; } if (IsSuspiciousMethodName(text3)) { return text3; } text = text3; } if ((val.OpCode == OpCodes.Stloc || val.OpCode == OpCodes.Stloc_0 || val.OpCode == OpCodes.Stloc_1 || val.OpCode == OpCodes.Stloc_2 || val.OpCode == OpCodes.Stloc_3 || val.OpCode == OpCodes.Stloc_S) && text != null && i > 0 && instructions[i - 1].OpCode == OpCodes.Ldstr) { if (val.OpCode == OpCodes.Stloc_0) { num = 0; } else if (val.OpCode == OpCodes.Stloc_1) { num = 1; } else if (val.OpCode == OpCodes.Stloc_2) { num = 2; } else if (val.OpCode == OpCodes.Stloc_3) { num = 3; } else { object operand = val.Operand; VariableDefinition val2 = (VariableDefinition)((operand is VariableDefinition) ? operand : null); if (val2 != null) { num = ((VariableReference)val2).Index; } } } if (num < 0 || text == null) { continue; } bool flag = false; if (val.OpCode == OpCodes.Ldloc_0 && num == 0) { flag = true; } else if (val.OpCode == OpCodes.Ldloc_1 && num == 1) { flag = true; } else if (val.OpCode == OpCodes.Ldloc_2 && num == 2) { flag = true; } else if (val.OpCode == OpCodes.Ldloc_3 && num == 3) { flag = true; } else { object operand2 = val.Operand; VariableDefinition val3 = (VariableDefinition)((operand2 is VariableDefinition) ? operand2 : null); if (val3 != null && ((VariableReference)val3).Index == num) { flag = true; } } if (flag && IsSuspiciousMethodName(text)) { return text; } } for (int j = currentIndex + 1; j < Math.Min(instructions.Count, currentIndex + 10); j++) { Instruction val4 = instructions[j]; if (val4.OpCode == OpCodes.Ldstr && val4.Operand is string text5 && (text5 == "ShellExecute" || text5 == "Execute" || text5 == "Shell")) { return text5; } } return null; } private bool IsSuspiciousMethodName(string str) { if (string.IsNullOrWhiteSpace(str)) { return false; } if (str.Contains("Shell.Application") || str.Contains("shell32")) { return true; } string[] source = new string[18] { "ShellExecute", "Shell", "Execute", "Start", "Process", "Exec", "Run", "Launch", "CreateProcess", "Spawn", "Command", "Eval", "LoadLibrary", "LoadFrom", "cmd.exe", "powershell.exe", "wscript.exe", "cscript.exe" }; return source.Any((string name) => str.Equals(name, StringComparison.OrdinalIgnoreCase) || str.Contains(name, StringComparison.OrdinalIgnoreCase)); } private bool WouldRuleMatchMethodName(IScanRule rule, string methodName) { if (rule.Description.Contains("process") || rule.Description.Contains("Process")) { if (rule is ProcessStartRule) { return methodName.Equals("Start", StringComparison.OrdinalIgnoreCase); } string[] source = new string[4] { "Process", "Exec", "Run", "Launch" }; return source.Any((string name) => methodName.Equals(name, StringComparison.OrdinalIgnoreCase) || methodName.Contains(name, StringComparison.OrdinalIgnoreCase)); } if (rule.Description.Contains("base64") || rule.Description.Contains("Base64")) { return methodName.Equals("FromBase64String", StringComparison.OrdinalIgnoreCase) || methodName.Equals("ToBase64String", StringComparison.OrdinalIgnoreCase); } if (rule.Description.Contains("Registry")) { return methodName.Contains("Registry") || methodName.Contains("GetValue") || methodName.Contains("SetValue"); } if (rule.Description.Contains("assembly") || rule.Description.Contains("Assembly")) { return methodName.Contains("Load") || methodName.Contains("Assembly") || methodName.Contains("Compile"); } return false; } private static bool HasStrongCompanionSignals(MethodSignals signals) { if (signals.HasEncodedStrings || signals.HasBase64) { return true; } if (signals.HasProcessLikeCall) { return true; } if (signals.HasEnvironmentVariableModification) { return true; } if (signals.HasNetworkCall && signals.HasFileWrite) { return true; } if (signals.UsesSensitiveFolder && (signals.HasProcessLikeCall || signals.HasNetworkCall || signals.HasFileWrite || signals.HasEncodedStrings || signals.HasBase64)) { return true; } if (signals.HasPathManipulation && (signals.HasProcessLikeCall || signals.HasNetworkCall || signals.HasEnvironmentVariableModification)) { return true; } return false; } } public static class ScanResultMapper { private sealed class ReferenceEqualityComparer : IEqualityComparer { public static ReferenceEqualityComparer Instance { get; } = new ReferenceEqualityComparer(); public bool Equals(ScanFinding? x, ScanFinding? y) { return x == y; } public int GetHashCode(ScanFinding obj) { return RuntimeHelpers.GetHashCode(obj); } } private static readonly ThreatFamilyClassifier ThreatFamilyClassifier = new ThreatFamilyClassifier(); private static readonly ThreatDispositionClassifier ThreatDispositionClassifier = new ThreatDispositionClassifier(); public static ScanResultDto ToDto(IEnumerable findings, string fileName, byte[] assemblyBytes, ScanResultOptions options) { List list = findings.ToList(); List list2 = (from f in list where f.HasCallChain select f.CallChain).Distinct().ToList(); List list3 = (from f in list where f.HasDataFlow select f.DataFlowChain).Distinct().ToList(); string sha256Hash = ComputeSha256(assemblyBytes); AnalysisCompletenessResult analysisCompletenessResult = BuildAnalysisCompleteness(list); IReadOnlyList readOnlyList = ThreatFamilyClassifier.Classify(list, list2, list3, sha256Hash); ThreatDispositionResult threatDispositionResult = ThreatDispositionClassifier.Classify(list, readOnlyList, analysisCompletenessResult); HashSet relatedFindings = threatDispositionResult.RelatedFindings.ToHashSet(); List list4 = list.Select((ScanFinding finding) => ToFindingDto(finding, options, (!relatedFindings.Contains(finding)) ? FindingVisibility.Advanced : FindingVisibility.Default)).ToList(); Dictionary findingIdsByReference = (from item in list.Zip(list4, (ScanFinding finding, FindingDto dto) => new { finding, dto.Id }) where !string.IsNullOrWhiteSpace(item.Id) select item).ToDictionary(item => item.finding, item => item.Id, ReferenceEqualityComparer.Instance); ScanResultDto scanResultDto = new ScanResultDto { SchemaVersion = options.SchemaVersion, Metadata = new ScanMetadataDto { CoreVersion = options.CoreVersion, PlatformVersion = options.PlatformVersion, ScannerVersion = options.PlatformVersion, Timestamp = DateTime.UtcNow.ToString("o"), ScanMode = options.ScanMode, Platform = options.Platform }, Input = new ScanInputDto { FileName = fileName, SizeBytes = assemblyBytes.Length, Sha256Hash = sha256Hash }, Assembly = ExtractAssemblyMetadata(assemblyBytes), Summary = BuildSummary(list), AnalysisCompleteness = ToAnalysisCompletenessDto(analysisCompletenessResult), Findings = list4, Disposition = ToThreatDispositionDto(threatDispositionResult, findingIdsByReference) }; if (readOnlyList.Count > 0) { scanResultDto.ThreatFamilies = readOnlyList.Select(ToThreatFamilyDto).ToList(); } if (options.IncludeCallChains && list2.Any()) { scanResultDto.CallChains = list2.Select(ToCallChainDto).ToList(); } if (options.IncludeDataFlows && list3.Any()) { scanResultDto.DataFlows = list3.Select(ToDataFlowChainDto).ToList(); } if (options.IncludeDeveloperGuidance) { List list5 = list.Where((ScanFinding f) => f.DeveloperGuidance != null).GroupBy((ScanFinding f) => f.DeveloperGuidance.Remediation, StringComparer.Ordinal).Select(ToDeveloperGuidanceDto) .ToList(); if (list5.Any()) { scanResultDto.DeveloperGuidance = list5; } } return scanResultDto; } public static ScanResultDto ToDto(IEnumerable findings, string fileName, byte[] assemblyBytes, bool developerMode = false) { ScanResultOptions options = new ScanResultOptions { IncludeDeveloperGuidance = developerMode, ScanMode = (developerMode ? "developer" : "detailed") }; return ToDto(findings, fileName, assemblyBytes, options); } private static AssemblyMetadataDto? ExtractAssemblyMetadata(byte[] assemblyBytes) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown if (assemblyBytes.Length == 0) { return null; } try { using MemoryStream memoryStream = new MemoryStream(assemblyBytes, writable: false); AssemblyDefinition val = AssemblyDefinition.ReadAssembly((Stream)memoryStream, new ReaderParameters { ReadSymbols = false }); try { List list = (from reference in (IEnumerable)val.MainModule.AssemblyReferences select reference.Name into value where !string.IsNullOrWhiteSpace(value) select value).Distinct(StringComparer.OrdinalIgnoreCase).OrderBy((string result) => result, StringComparer.OrdinalIgnoreCase).ToList(); AssemblyMetadataDto assemblyMetadataDto = new AssemblyMetadataDto(); AssemblyNameDefinition name = val.Name; assemblyMetadataDto.Name = NullIfWhiteSpace((name != null) ? ((AssemblyNameReference)name).Name : null); AssemblyNameDefinition name2 = val.Name; assemblyMetadataDto.AssemblyVersion = ((name2 == null) ? null : ((AssemblyNameReference)name2).Version?.ToString()); assemblyMetadataDto.FileVersion = GetAssemblyAttributeValue(val, "System.Reflection.AssemblyFileVersionAttribute"); assemblyMetadataDto.InformationalVersion = GetAssemblyAttributeValue(val, "System.Reflection.AssemblyInformationalVersionAttribute"); assemblyMetadataDto.TargetFramework = GetAssemblyAttributeValue(val, "System.Runtime.Versioning.TargetFrameworkAttribute"); assemblyMetadataDto.ModuleRuntimeVersion = NullIfWhiteSpace(val.MainModule.RuntimeVersion); assemblyMetadataDto.ReferencedAssemblies = ((list.Count > 0) ? list : null); return assemblyMetadataDto; } finally { ((IDisposable)val)?.Dispose(); } } catch { return null; } } private static string? GetAssemblyAttributeValue(AssemblyDefinition assembly, string fullAttributeName) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) CustomAttribute val = ((IEnumerable)assembly.CustomAttributes).FirstOrDefault((Func)((CustomAttribute candidate) => string.Equals(((MemberReference)candidate.AttributeType).FullName, fullAttributeName, StringComparison.Ordinal))); if (val == null || val.ConstructorArguments.Count == 0) { return null; } CustomAttributeArgument val2 = val.ConstructorArguments[0]; return NullIfWhiteSpace(((CustomAttributeArgument)(ref val2)).Value?.ToString()); } private static string? NullIfWhiteSpace(string? value) { return string.IsNullOrWhiteSpace(value) ? null : value.Trim(); } private static ScanSummaryDto BuildSummary(List findings) { return new ScanSummaryDto { TotalFindings = findings.Count, CountBySeverity = (from f in findings group f by f.Severity.ToString()).ToDictionary((IGrouping g) => g.Key, (IGrouping g) => g.Count()), TriggeredRules = (from r in (from f in findings where f.RuleId != null select f.RuleId).Distinct() orderby r select r).ToList() }; } private static AnalysisCompletenessResult BuildAnalysisCompleteness(IReadOnlyList findings) { List incompleteFindings = findings.Where(IsIncompleteAnalysisFinding).ToList(); if (incompleteFindings.Count == 0) { return new AnalysisCompletenessResult { Status = AnalysisCompletenessStatus.Complete, IsComplete = true, ReviewRecommended = false }; } List list = findings.Where((ScanFinding finding) => !incompleteFindings.Contains(finding)).ToList(); AnalysisCompletenessResult analysisCompletenessResult = new AnalysisCompletenessResult(); analysisCompletenessResult.Status = ((list.Count > 0) ? AnalysisCompletenessStatus.Partial : AnalysisCompletenessStatus.Incomplete); analysisCompletenessResult.IsComplete = false; analysisCompletenessResult.ReviewRecommended = true; analysisCompletenessResult.RelatedFindings = incompleteFindings; analysisCompletenessResult.Reasons = incompleteFindings.Select(ToAnalysisCompletenessReason).ToList(); return analysisCompletenessResult; } private static bool IsIncompleteAnalysisFinding(ScanFinding finding) { if (!string.IsNullOrWhiteSpace(finding.RuleId) && (finding.RuleId.Contains("incomplete", StringComparison.OrdinalIgnoreCase) || finding.RuleId.Contains("manualreview", StringComparison.OrdinalIgnoreCase) || finding.RuleId.Contains("scanwarning", StringComparison.OrdinalIgnoreCase))) { return true; } if (string.Equals(finding.RuleId, "AssemblyScanner", StringComparison.Ordinal) && finding.Description.Contains("could not be scanned", StringComparison.OrdinalIgnoreCase)) { return true; } return finding.Description.Contains("could not complete full il analysis", StringComparison.OrdinalIgnoreCase) || finding.Description.Contains("full il analysis was skipped", StringComparison.OrdinalIgnoreCase); } private static AnalysisCompletenessReason ToAnalysisCompletenessReason(ScanFinding finding) { return new AnalysisCompletenessReason { ReasonId = "scanner-incomplete", Summary = finding.Description, Phase = ResolveAnalysisCompletenessPhase(finding), RuleId = finding.RuleId, Location = finding.Location }; } private static string ResolveAnalysisCompletenessPhase(ScanFinding finding) { return string.Equals(finding.RuleId, "AssemblyScanner", StringComparison.Ordinal) ? "assembly-scan" : "analysis"; } private static FindingDto ToFindingDto(ScanFinding finding, ScanResultOptions options, FindingVisibility visibility) { FindingDto findingDto = new FindingDto { Id = Guid.NewGuid().ToString("N"), RuleId = finding.RuleId, Description = finding.Description, Severity = finding.Severity.ToString(), Location = finding.Location, CodeSnippet = finding.CodeSnippet, RiskScore = finding.RiskScore, CallChainId = finding.CallChain?.ChainId, DataFlowChainId = finding.DataFlowChain?.ChainId, Visibility = visibility.ToString() }; if (options.IncludeDeveloperGuidance && finding.DeveloperGuidance != null) { findingDto.DeveloperGuidance = ToDeveloperGuidanceDto(finding.DeveloperGuidance, string.IsNullOrWhiteSpace(finding.RuleId) ? null : new List { finding.RuleId }); } if (finding.HasCallChain) { findingDto.CallChain = ToCallChainDto(finding.CallChain); } if (finding.HasDataFlow) { findingDto.DataFlowChain = ToDataFlowChainDto(finding.DataFlowChain); } return findingDto; } private static CallChainDto ToCallChainDto(CallChain callChain) { return new CallChainDto { Id = callChain.ChainId, RuleId = callChain.RuleId, Description = callChain.Summary, Severity = callChain.Severity.ToString(), Nodes = callChain.Nodes.Select((CallChainNode node) => new CallChainNodeDto { NodeType = node.NodeType.ToString(), Location = node.Location, Description = node.Description, CodeSnippet = node.CodeSnippet }).ToList() }; } private static DataFlowChainDto ToDataFlowChainDto(DataFlowChain dataFlow) { return new DataFlowChainDto { Id = dataFlow.ChainId, Description = dataFlow.Summary, Severity = dataFlow.Severity.ToString(), Pattern = dataFlow.Pattern.ToString(), SourceVariable = dataFlow.SourceVariable, MethodLocation = dataFlow.MethodLocation, IsCrossMethod = dataFlow.IsCrossMethod, IsSuspicious = dataFlow.IsSuspicious, CallDepth = dataFlow.CallDepth, InvolvedMethods = ((dataFlow.InvolvedMethods.Count > 0) ? dataFlow.InvolvedMethods : null), Nodes = dataFlow.Nodes.Select((DataFlowNode node) => new DataFlowNodeDto { NodeType = node.NodeType.ToString(), Location = node.Location, Operation = node.Operation, DataDescription = node.DataDescription, InstructionOffset = node.InstructionOffset, MethodKey = node.MethodKey, IsMethodBoundary = node.IsMethodBoundary, TargetMethodKey = node.TargetMethodKey, CodeSnippet = node.CodeSnippet }).ToList() }; } private static DeveloperGuidanceDto ToDeveloperGuidanceDto(IGrouping guidanceGroup) { IDeveloperGuidance developerGuidance = guidanceGroup.First().DeveloperGuidance; List list = (from f in guidanceGroup select f.RuleId into ruleId where !string.IsNullOrWhiteSpace(ruleId) select (ruleId)).Distinct(StringComparer.Ordinal).OrderBy((string ruleId) => ruleId, StringComparer.Ordinal).ToList(); string documentationUrl = guidanceGroup.Select((ScanFinding f) => f.DeveloperGuidance.DocumentationUrl).FirstOrDefault((string url) => !string.IsNullOrWhiteSpace(url)); string[] array = (from api in guidanceGroup.SelectMany((ScanFinding f) => f.DeveloperGuidance.AlternativeApis ?? Array.Empty()) where !string.IsNullOrWhiteSpace(api) select api).Distinct(StringComparer.Ordinal).ToArray(); return new DeveloperGuidanceDto { RuleId = ((list.Count == 1) ? list[0] : null), RuleIds = ((list.Count > 0) ? list : null), Remediation = developerGuidance.Remediation, DocumentationUrl = documentationUrl, AlternativeApis = ((array.Length != 0) ? array : null), IsRemediable = guidanceGroup.All((ScanFinding f) => f.DeveloperGuidance.IsRemediable) }; } private static DeveloperGuidanceDto ToDeveloperGuidanceDto(IDeveloperGuidance guidance, List? ruleIds) { return new DeveloperGuidanceDto { RuleId = ((ruleIds != null && ruleIds.Count == 1) ? ruleIds[0] : null), RuleIds = ((ruleIds != null && ruleIds.Count > 0) ? ruleIds : null), Remediation = guidance.Remediation, DocumentationUrl = guidance.DocumentationUrl, AlternativeApis = guidance.AlternativeApis, IsRemediable = guidance.IsRemediable }; } private static ThreatFamilyDto ToThreatFamilyDto(ThreatFamilyMatch match) { return new ThreatFamilyDto { FamilyId = match.FamilyId, VariantId = match.VariantId, DisplayName = match.DisplayName, Summary = match.Summary, MatchKind = match.MatchKind.ToString(), Confidence = match.Confidence, ExactHashMatch = match.ExactHashMatch, MatchedRules = match.MatchedRules, AdvisorySlugs = match.AdvisorySlugs, Evidence = match.Evidence.Select((ThreatFamilyEvidence e) => new ThreatFamilyEvidenceDto { Kind = e.Kind, Value = e.Value, RuleId = e.RuleId, Location = e.Location, CallChainId = e.CallChainId, DataFlowChainId = e.DataFlowChainId, Pattern = e.Pattern, MethodLocation = e.MethodLocation, Confidence = e.Confidence }).ToList() }; } private static AnalysisCompletenessDto ToAnalysisCompletenessDto(AnalysisCompletenessResult completeness) { return new AnalysisCompletenessDto { Status = completeness.Status.ToString(), IsComplete = completeness.IsComplete, ReviewRecommended = completeness.ReviewRecommended, Reasons = completeness.Reasons.Select((AnalysisCompletenessReason reason) => new AnalysisCompletenessReasonDto { ReasonId = reason.ReasonId, Summary = reason.Summary, Phase = reason.Phase, RuleId = reason.RuleId, Location = reason.Location }).ToList() }; } private static ThreatDispositionDto ToThreatDispositionDto(ThreatDispositionResult disposition, IReadOnlyDictionary findingIdsByReference) { return new ThreatDispositionDto { Classification = disposition.Classification.ToString(), Headline = disposition.Headline, Summary = disposition.Summary, BlockingRecommended = disposition.BlockingRecommended, PrimaryThreatFamilyId = disposition.PrimaryThreatFamilyId, RelatedFindingIds = (from finding in disposition.RelatedFindings.Where(findingIdsByReference.ContainsKey) select findingIdsByReference[finding]).Distinct(StringComparer.Ordinal).ToList() }; } private static string ComputeSha256(byte[] data) { using SHA256 sHA = SHA256.Create(); byte[] array = sHA.ComputeHash(data); return BitConverter.ToString(array).Replace("-", "").ToLowerInvariant(); } } [EditorBrowsable(EditorBrowsableState.Never)] public class SignalTracker { private readonly Dictionary _typeSignals; private readonly ScanConfig _config; public SignalTracker(ScanConfig config) { _config = config ?? new ScanConfig(); _typeSignals = new Dictionary(); } public MethodSignals? CreateMethodSignals() { return _config.EnableMultiSignalDetection ? new MethodSignals() : null; } public MethodSignals? GetOrCreateTypeSignals(string typeFullName) { if (!_config.EnableMultiSignalDetection) { return null; } if (!_typeSignals.TryGetValue(typeFullName, out MethodSignals value)) { value = new MethodSignals(); _typeSignals[typeFullName] = value; } return value; } public MethodSignals? GetTypeSignals(string typeFullName) { MethodSignals value; return _typeSignals.TryGetValue(typeFullName, out value) ? value : null; } public void ClearTypeSignals(string typeFullName) { _typeSignals.Remove(typeFullName); } public void UpdateMethodSignals(MethodSignals signals, MethodReference method, TypeDefinition? declaringType) { if (((method != null) ? ((MemberReference)method).DeclaringType : null) == null || signals == null) { return; } string fullName = ((MemberReference)((MemberReference)method).DeclaringType).FullName; string name = ((MemberReference)method).Name; if (fullName.Contains("Convert") && name.Contains("FromBase64")) { signals.HasBase64 = true; if (declaringType != null) { MethodSignals orCreateTypeSignals = GetOrCreateTypeSignals(((MemberReference)declaringType).FullName); if (orCreateTypeSignals != null) { orCreateTypeSignals.HasBase64 = true; } } } if (fullName.Contains("System.Diagnostics.Process") && name == "Start") { signals.HasProcessLikeCall = true; if (declaringType != null) { MethodSignals orCreateTypeSignals2 = GetOrCreateTypeSignals(((MemberReference)declaringType).FullName); if (orCreateTypeSignals2 != null) { orCreateTypeSignals2.HasProcessLikeCall = true; } } } if ((fullName == "System.Reflection.MethodInfo" && name == "Invoke") || (fullName == "System.Reflection.MethodBase" && name == "Invoke")) { signals.HasSuspiciousReflection = true; } if (fullName.StartsWith("System.Net") || fullName.Contains("WebRequest") || fullName.Contains("HttpClient") || fullName.Contains("WebClient")) { signals.HasNetworkCall = true; if (declaringType != null) { MethodSignals orCreateTypeSignals3 = GetOrCreateTypeSignals(((MemberReference)declaringType).FullName); if (orCreateTypeSignals3 != null) { orCreateTypeSignals3.HasNetworkCall = true; } } } if ((fullName.StartsWith("System.IO.File") && (name.Contains("Write") || name.Contains("Create"))) || (fullName == "System.IO.FileStream" && name == ".ctor") || (fullName.StartsWith("System.IO.Stream") && name.Contains("Write")) || (fullName == "System.IO.StreamWriter" && name.StartsWith("Write")) || (fullName == "System.IO.BinaryWriter" && name.StartsWith("Write"))) { signals.HasFileWrite = true; if (declaringType != null) { MethodSignals orCreateTypeSignals4 = GetOrCreateTypeSignals(((MemberReference)declaringType).FullName); if (orCreateTypeSignals4 != null) { orCreateTypeSignals4.HasFileWrite = true; } } } if (!(fullName == "System.Environment") || !(name == "SetEnvironmentVariable")) { return; } signals.HasEnvironmentVariableModification = true; if (declaringType != null) { MethodSignals orCreateTypeSignals5 = GetOrCreateTypeSignals(((MemberReference)declaringType).FullName); if (orCreateTypeSignals5 != null) { orCreateTypeSignals5.HasEnvironmentVariableModification = true; } } } public void MarkEncodedStrings(MethodSignals? methodSignals, TypeDefinition? declaringType) { if (methodSignals == null) { return; } methodSignals.HasEncodedStrings = true; if (declaringType != null) { MethodSignals orCreateTypeSignals = GetOrCreateTypeSignals(((MemberReference)declaringType).FullName); if (orCreateTypeSignals != null) { orCreateTypeSignals.HasEncodedStrings = true; } } } public void MarkSensitiveFolder(MethodSignals? methodSignals, TypeDefinition? declaringType) { if (methodSignals == null) { return; } methodSignals.UsesSensitiveFolder = true; if (declaringType != null) { MethodSignals orCreateTypeSignals = GetOrCreateTypeSignals(((MemberReference)declaringType).FullName); if (orCreateTypeSignals != null) { orCreateTypeSignals.UsesSensitiveFolder = true; } } } public void MarkSuspiciousLocalVariables(MethodSignals? methodSignals, TypeDefinition? declaringType) { if (methodSignals == null) { return; } methodSignals.HasSuspiciousLocalVariables = true; if (declaringType != null) { MethodSignals orCreateTypeSignals = GetOrCreateTypeSignals(((MemberReference)declaringType).FullName); if (orCreateTypeSignals != null) { orCreateTypeSignals.HasSuspiciousLocalVariables = true; } } } public void MarkSuspiciousExceptionHandling(MethodSignals? methodSignals, TypeDefinition? declaringType) { if (methodSignals == null) { return; } methodSignals.HasSuspiciousExceptionHandling = true; if (declaringType != null) { MethodSignals orCreateTypeSignals = GetOrCreateTypeSignals(((MemberReference)declaringType).FullName); if (orCreateTypeSignals != null) { orCreateTypeSignals.HasSuspiciousExceptionHandling = true; } } } public void MarkRuleTriggered(MethodSignals? methodSignals, TypeDefinition? declaringType, string ruleId) { if (methodSignals != null && !string.IsNullOrEmpty(ruleId)) { methodSignals.MarkRuleTriggered(ruleId); if (declaringType != null) { GetOrCreateTypeSignals(((MemberReference)declaringType).FullName)?.MarkRuleTriggered(ruleId); } } } } [EditorBrowsable(EditorBrowsableState.Never)] public class StringPatternDetector { public bool HasSuspiciousStringPatterns(MethodDefinition methodDef, Collection instructions, int currentIndex) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) int num = Math.Max(0, currentIndex - 20); int num2 = Math.Min(instructions.Count, currentIndex + 20); for (int i = num; i < num2; i++) { Instruction val = instructions[i]; if (val.OpCode == OpCodes.Ldstr && val.Operand is string text) { if (EncodedStringLiteralRule.IsEncodedString(text)) { string text2 = EncodedStringLiteralRule.DecodeNumericString(text); if (text2 != null && EncodedStringLiteralRule.ContainsSuspiciousContent(text2)) { return true; } } if (text.Contains("powershell", StringComparison.OrdinalIgnoreCase) || text.Contains("cmd.exe", StringComparison.OrdinalIgnoreCase) || text.Contains("wscript", StringComparison.OrdinalIgnoreCase) || text.Contains("cscript", StringComparison.OrdinalIgnoreCase) || text.Contains("ShellExecute", StringComparison.OrdinalIgnoreCase) || text.Contains("Startup", StringComparison.OrdinalIgnoreCase) || text.Contains("RunOnce", StringComparison.OrdinalIgnoreCase)) { return true; } } if (!(val.OpCode == OpCodes.Call) && !(val.OpCode == OpCodes.Callvirt)) { continue; } object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 != null && ((MemberReference)val2).DeclaringType != null) { string fullName = ((MemberReference)((MemberReference)val2).DeclaringType).FullName; string name = ((MemberReference)val2).Name; if (fullName.Contains("Convert") && name.Contains("FromBase64")) { return true; } } } return false; } public bool HasAssemblyLoadingInMethod(MethodDefinition methodDef, Collection instructions) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) Enumerator enumerator = instructions.GetEnumerator(); try { while (enumerator.MoveNext()) { Instruction current = enumerator.Current; if (!(current.OpCode == OpCodes.Call) && !(current.OpCode == OpCodes.Callvirt)) { continue; } object operand = current.Operand; MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null); if (val != null && ((MemberReference)val).DeclaringType != null) { string fullName = ((MemberReference)((MemberReference)val).DeclaringType).FullName; string name = ((MemberReference)val).Name; if ((fullName.Contains("Assembly") || fullName.Contains("AssemblyLoadContext")) && (name == "Load" || name.Contains("LoadFrom") || name == "LoadFile" || name == "LoadFromStream" || name == "LoadFromAssemblyPath")) { return true; } } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } return false; } } [EditorBrowsable(EditorBrowsableState.Never)] public class TypeScanner { private static readonly HashSet ReflectionCompanionRuleIds = new HashSet(StringComparer.Ordinal) { "ProcessStartRule", "Shell32Rule", "COMReflectionAttackRule", "AssemblyDynamicLoadRule", "PersistenceRule", "RegistryRule", "DataExfiltrationRule", "DataInfiltrationRule", "Base64Rule", "HexStringRule", "EncodedStringLiteralRule", "EncodedStringPipelineRule", "EncodedBlobSplittingRule", "ByteArrayManipulationRule" }; private readonly MethodScanner _methodScanner; private readonly SignalTracker _signalTracker; private readonly ReflectionDetector _reflectionDetector; private readonly CodeSnippetBuilder _snippetBuilder; private readonly PropertyEventScanner _propertyEventScanner; private readonly IEnumerable _rules; private readonly ScanConfig _config; private readonly ScanTelemetryHub _telemetry; public TypeScanner(MethodScanner methodScanner, SignalTracker signalTracker, ReflectionDetector reflectionDetector, CodeSnippetBuilder snippetBuilder, PropertyEventScanner propertyEventScanner, IEnumerable rules, ScanConfig config) : this(methodScanner, signalTracker, reflectionDetector, snippetBuilder, propertyEventScanner, rules, config, new ScanTelemetryHub()) { } internal TypeScanner(MethodScanner methodScanner, SignalTracker signalTracker, ReflectionDetector reflectionDetector, CodeSnippetBuilder snippetBuilder, PropertyEventScanner propertyEventScanner, IEnumerable rules, ScanConfig config, ScanTelemetryHub telemetry) { _methodScanner = methodScanner ?? throw new ArgumentNullException("methodScanner"); _signalTracker = signalTracker ?? throw new ArgumentNullException("signalTracker"); _reflectionDetector = reflectionDetector ?? throw new ArgumentNullException("reflectionDetector"); _snippetBuilder = snippetBuilder ?? throw new ArgumentNullException("snippetBuilder"); _propertyEventScanner = propertyEventScanner ?? throw new ArgumentNullException("propertyEventScanner"); _rules = rules ?? throw new ArgumentNullException("rules"); _config = config ?? new ScanConfig(); _telemetry = telemetry ?? throw new ArgumentNullException("telemetry"); } public IEnumerable ScanType(TypeDefinition type) { //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) List list = new List(); long startTimestamp = _telemetry.StartTimestamp(); int count = type.Methods.Count; int count2 = type.NestedTypes.Count; int num = 0; string fullName = ((MemberReference)type).FullName; bool flag = false; _telemetry.IncrementCounter("TypeScanner.TypesScanned", 1L); _telemetry.IncrementCounter("TypeScanner.MethodsDiscovered", count); _telemetry.IncrementCounter("TypeScanner.NestedTypesDiscovered", count2); try { IReadOnlyDictionary methodContexts = _propertyEventScanner.BuildAccessorContexts(type); if (_config.EnableMultiSignalDetection) { _signalTracker.GetOrCreateTypeSignals(fullName); flag = true; } List<(MethodDefinition, Instruction, int, Collection, MethodSignals)> list2 = new List<(MethodDefinition, Instruction, int, Collection, MethodSignals)>(); try { Enumerator enumerator = type.Methods.GetEnumerator(); try { while (enumerator.MoveNext()) { MethodDefinition current = enumerator.Current; MethodScanner.MethodScanResult methodScanResult = _methodScanner.ScanMethod(current, fullName); AnnotateFindings(methodScanResult.Findings, current, methodContexts); list.AddRange(methodScanResult.Findings); list2.AddRange(methodScanResult.PendingReflectionFindings); } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } num = list2.Count; _telemetry.IncrementCounter("TypeScanner.PendingReflectionQueued", num); if (_config.EnableMultiSignalDetection) { long startTimestamp2 = _telemetry.StartTimestamp(); ProcessPendingReflectionFindings(list2, fullName, list, methodContexts); _telemetry.AddPhaseElapsed("TypeScanner.ProcessPendingReflectionFindings", startTimestamp2); } } finally { if (flag) { _signalTracker.ClearTypeSignals(fullName); } } Enumerator enumerator2 = type.NestedTypes.GetEnumerator(); try { while (enumerator2.MoveNext()) { TypeDefinition current2 = enumerator2.Current; list.AddRange(ScanType(current2)); } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } catch (Exception) { } finally { _telemetry.AddPhaseElapsed("TypeScanner.ScanType", startTimestamp); _telemetry.RecordTypeSample(((MemberReference)type).FullName, startTimestamp, count, count2, list.Count, num); } return list; } private void ProcessPendingReflectionFindings(List<(MethodDefinition method, Instruction instruction, int index, Collection instructions, MethodSignals? methodSignals)> pendingReflectionFindings, string typeFullName, List findings, IReadOnlyDictionary methodContexts) { if (pendingReflectionFindings.Count == 0) { return; } MethodSignals typeSignals = _signalTracker.GetTypeSignals(typeFullName); if (typeSignals == null) { return; } IScanRule scanRule = _rules.FirstOrDefault((IScanRule r) => r is ReflectionRule); if (scanRule == null || !HasStrongReflectionCompanion(typeSignals, scanRule.RuleId)) { return; } int count = findings.Count; foreach (var pendingReflectionFinding in pendingReflectionFindings) { MethodDefinition item = pendingReflectionFinding.method; Instruction item2 = pendingReflectionFinding.instruction; int item3 = pendingReflectionFinding.index; Collection item4 = pendingReflectionFinding.instructions; MethodSignals item5 = pendingReflectionFinding.methodSignals; string codeSnippet = _snippetBuilder.BuildSnippet(item4, item3, 2); TypeDefinition declaringType = item.DeclaringType; ScanFinding scanFinding = new ScanFinding($"{((declaringType != null) ? ((MemberReference)declaringType).FullName : null)}.{((MemberReference)item).Name}:{item2.Offset}", scanRule.Description + " (combined with other suspicious patterns detected in this type)", scanRule.Severity, codeSnippet) { RuleId = scanRule.RuleId, DeveloperGuidance = scanRule.DeveloperGuidance }; if (methodContexts.TryGetValue(item, out string value)) { scanFinding.Description = scanFinding.Description + " (" + value + ")"; } findings.Add(scanFinding); if (item5 != null) { _signalTracker.MarkRuleTriggered(item5, item.DeclaringType, scanRule.RuleId); } } _telemetry.IncrementCounter("TypeScanner.PendingReflectionFindingsConfirmed", findings.Count - count); } private static bool HasStrongReflectionCompanion(MethodSignals typeSignal, string reflectionRuleId) { foreach (string triggeredRuleId in typeSignal.GetTriggeredRuleIds()) { if (triggeredRuleId.Equals(reflectionRuleId, StringComparison.Ordinal) || !ReflectionCompanionRuleIds.Contains(triggeredRuleId)) { continue; } return true; } return false; } private static void AnnotateFindings(List findings, MethodDefinition method, IReadOnlyDictionary methodContexts) { if (!methodContexts.TryGetValue(method, out string value)) { return; } foreach (ScanFinding finding in findings) { if (!finding.Description.Contains(value, StringComparison.Ordinal)) { finding.Description = finding.Description + " (" + value + ")"; } } } } } namespace MLVScan.Services.ThreatIntel { public sealed class ThreatDispositionClassifier { private static readonly HashSet StrongStandaloneRuleIds = new HashSet(StringComparer.Ordinal) { "DataFlowAnalysis", "EmbeddedResourceScriptRule", "ObfuscatedReflectiveExecutionRule" }; private static readonly HashSet SuspiciousDataFlowPatterns = new HashSet { DataFlowPattern.DownloadAndExecute, DataFlowPattern.DataExfiltration, DataFlowPattern.DynamicCodeLoading, DataFlowPattern.CredentialTheft, DataFlowPattern.RemoteConfigLoad, DataFlowPattern.ObfuscatedPersistence, DataFlowPattern.EmbeddedResourceDropAndExecute }; private static readonly string[] LolbinMarkers = new string[7] { "powershell.exe", "cmd.exe", "wscript.exe", "cscript.exe", "mshta.exe", "rundll32.exe", "regsvr32.exe" }; private static readonly string[] HiddenExecutionMarkers = new string[6] { "CreateNoWindow=true", "CreateNoWindow set", "WindowStyle=Hidden", "WindowStyle Hidden", "UseShellExecute=true", "UseShellExecute set" }; public ThreatDispositionResult Classify(IEnumerable findings, IEnumerable? threatFamilies) { return Classify(findings, threatFamilies, null); } public ThreatDispositionResult Classify(IEnumerable findings, IEnumerable? threatFamilies, AnalysisCompletenessResult? analysisCompleteness) { List findingsList = findings?.ToList() ?? new List(); List source = threatFamilies?.ToList() ?? new List(); ThreatFamilyMatch threatFamilyMatch = (from match in source orderby match.ExactHashMatch descending, match.Confidence descending select match).ThenBy((ThreatFamilyMatch match) => match.FamilyId, StringComparer.Ordinal).FirstOrDefault(); if (threatFamilyMatch != null) { return BuildKnownThreatDisposition(findingsList, threatFamilyMatch); } List list = findingsList.Where((ScanFinding finding) => IsSuspiciousSeedFinding(finding, findingsList)).ToList(); if (list.Count > 0) { return BuildSuspiciousDisposition(findingsList, list); } if (analysisCompleteness != null && analysisCompleteness.ReviewRecommended) { return BuildManualReviewDisposition(analysisCompleteness); } return new ThreatDispositionResult { Classification = ThreatDispositionClassification.Clean, Headline = "No known threats detected", Summary = "No known malware family matches or correlated suspicious behavior were retained.", BlockingRecommended = false }; } private static ThreatDispositionResult BuildKnownThreatDisposition(IReadOnlyList findings, ThreatFamilyMatch primaryFamily) { List relatedFindings = ResolveKnownThreatFindings(findings, primaryFamily); string summary = (primaryFamily.ExactHashMatch ? "This file exactly matches a previously confirmed malicious sample." : ("This file matches the previously analyzed malware family \"" + primaryFamily.DisplayName + "\".")); return new ThreatDispositionResult { Classification = ThreatDispositionClassification.KnownThreat, Headline = "Likely malware detected", Summary = summary, BlockingRecommended = true, PrimaryThreatFamilyId = primaryFamily.FamilyId, PrimaryThreatFamily = primaryFamily, RelatedFindings = relatedFindings }; } private static ThreatDispositionResult BuildSuspiciousDisposition(IReadOnlyList findings, IReadOnlyList suspiciousSeeds) { return new ThreatDispositionResult { Classification = ThreatDispositionClassification.Suspicious, Headline = "Suspicious behavior detected", Summary = "This file shows correlated suspicious behavior. It may be malicious, but it may also be a false positive and should be reviewed.", BlockingRecommended = true, RelatedFindings = ExpandCorrelatedFindings(findings, suspiciousSeeds) }; } private static ThreatDispositionResult BuildManualReviewDisposition(AnalysisCompletenessResult analysisCompleteness) { return new ThreatDispositionResult { Classification = ThreatDispositionClassification.ManualReviewRequired, Headline = "Manual review required", Summary = "Analysis did not complete enough to support a clean verdict. Review the completeness reasons before trusting this result.", BlockingRecommended = true, RelatedFindings = analysisCompleteness.RelatedFindings }; } private static List ResolveKnownThreatFindings(IReadOnlyList findings, ThreatFamilyMatch primaryFamily) { HashSet matchedRules = primaryFamily.MatchedRules.Where((string ruleId) => !string.IsNullOrWhiteSpace(ruleId) && !string.Equals(ruleId, "DataFlowAnalysis", StringComparison.Ordinal)).ToHashSet(StringComparer.Ordinal); HashSet evidenceLocations = (from evidence in primaryFamily.Evidence where !string.IsNullOrWhiteSpace(evidence.Location) select evidence.Location).ToHashSet(StringComparer.Ordinal); HashSet callChainIds = (from evidence in primaryFamily.Evidence where !string.IsNullOrWhiteSpace(evidence.CallChainId) select evidence.CallChainId).ToHashSet(StringComparer.Ordinal); HashSet dataFlowIds = (from evidence in primaryFamily.Evidence where !string.IsNullOrWhiteSpace(evidence.DataFlowChainId) select evidence.DataFlowChainId).ToHashSet(StringComparer.Ordinal); List list = findings.Where((ScanFinding finding) => evidenceLocations.Contains(finding.Location) || (finding.HasCallChain && callChainIds.Contains(finding.CallChain.ChainId)) || (finding.HasDataFlow && dataFlowIds.Contains(finding.DataFlowChain.ChainId))).ToList(); if (list.Count == 0) { list = findings.Where((ScanFinding finding) => !string.IsNullOrWhiteSpace(finding.RuleId) && matchedRules.Contains(finding.RuleId)).ToList(); } else if (matchedRules.Count > 0) { list = ExpandCorrelatedFindings(findings, list.Concat(findings.Where((ScanFinding finding) => !string.IsNullOrWhiteSpace(finding.RuleId) && matchedRules.Contains(finding.RuleId))).ToList()); } return list; } private static List ExpandCorrelatedFindings(IEnumerable findings, IReadOnlyCollection seeds) { HashSet hashSet = new HashSet(seeds); HashSet hashSet2 = (from finding in seeds where finding.HasCallChain select finding.CallChain.ChainId).ToHashSet(StringComparer.Ordinal); HashSet hashSet3 = (from finding in seeds where finding.HasDataFlow select finding.DataFlowChain.ChainId).ToHashSet(StringComparer.Ordinal); HashSet hashSet4 = (from finding in seeds select finding.Location into location where !string.IsNullOrWhiteSpace(location) select location).ToHashSet(StringComparer.Ordinal); foreach (ScanFinding finding in findings) { if (!hashSet.Contains(finding) && ((finding.HasCallChain && hashSet2.Contains(finding.CallChain.ChainId)) || (finding.HasDataFlow && hashSet3.Contains(finding.DataFlowChain.ChainId)) || hashSet4.Contains(finding.Location))) { hashSet.Add(finding); } } return hashSet.OrderByDescending((ScanFinding finding) => finding.Severity).ThenBy((ScanFinding finding) => finding.RuleId, StringComparer.Ordinal).ThenBy((ScanFinding finding) => finding.Location, StringComparer.Ordinal) .ToList(); } private static bool IsSuspiciousSeedFinding(ScanFinding finding, IReadOnlyList allFindings) { if (finding == null || finding.Severity < Severity.High) { return false; } if (string.Equals(finding.RuleId, "DataFlowAnalysis", StringComparison.Ordinal)) { return finding.HasDataFlow && IsSuspiciousDataFlowSeed(finding); } if (StrongStandaloneRuleIds.Contains(finding.RuleId ?? string.Empty)) { return true; } if (IsHiddenLolbinDownloadExecuteSeed(finding)) { return true; } if (IsHiddenLolbinTempScriptSeed(finding)) { return true; } if (IsKnownInfrastructureStagedExecutionSeed(finding, allFindings)) { return true; } if (IsMultiSignalStagedExecutionSeed(finding, allFindings)) { return true; } if (finding.HasCallChain && finding.HasDataFlow) { return true; } List source = ExpandCorrelatedFindings(allFindings, (IReadOnlyCollection)(object)new ScanFinding[1] { finding }); int num = source.Count((ScanFinding relatedFinding) => relatedFinding.Severity >= Severity.High); int num2 = (from relatedFinding in source where relatedFinding.Severity >= Severity.High && !string.IsNullOrWhiteSpace(relatedFinding.RuleId) select relatedFinding.RuleId).Distinct(StringComparer.Ordinal).Count(); if (string.Equals(finding.RuleId, "AssemblyDynamicLoadRule", StringComparison.Ordinal)) { return finding.BypassCompanionCheck && num2 >= 2; } return (finding.HasCallChain || finding.HasDataFlow) && num >= 2 && num2 >= 2; } private static bool IsSuspiciousDataFlowSeed(ScanFinding finding) { DataFlowPattern pattern = finding.DataFlowChain.Pattern; if (pattern == DataFlowPattern.EmbeddedResourceDropAndExecute) { return HasEmbeddedDropperExecutionMarkers(finding); } return SuspiciousDataFlowPatterns.Contains(pattern); } private static bool HasEmbeddedDropperExecutionMarkers(ScanFinding finding) { List haystacks = EnumerateEmbeddedDataFlowTexts(finding).ToList(); return ContainsAny(haystacks, ".cmd", ".bat", "%TEMP%", "ShellExecuteEx", "PInvoke.ShellExecute", "PInvoke.CreateProcess", "PInvoke.WinExec", "temp script dropper pattern", "nShow=0", "WindowStyle=Hidden", "CreateNoWindow=true"); } private static IEnumerable EnumerateEmbeddedDataFlowTexts(ScanFinding finding) { yield return finding.Description; if (!string.IsNullOrWhiteSpace(finding.CodeSnippet)) { yield return finding.CodeSnippet; } if (!finding.HasDataFlow) { yield break; } DataFlowChain dataFlow = finding.DataFlowChain; yield return dataFlow.Summary; yield return dataFlow.MethodLocation; foreach (DataFlowNode node in dataFlow.Nodes) { yield return node.Location; yield return node.Operation; yield return node.DataDescription; if (!string.IsNullOrWhiteSpace(node.CodeSnippet)) { yield return node.CodeSnippet; } } } private static bool IsHiddenLolbinDownloadExecuteSeed(ScanFinding finding) { if (!string.Equals(finding.RuleId, "ProcessStartRule", StringComparison.Ordinal) || !finding.HasDataFlow || !IsSuspiciousDataFlowSeed(finding)) { return false; } List haystacks = EnumerateEmbeddedDataFlowTexts(finding).ToList(); return ContainsAny(haystacks, LolbinMarkers) && ContainsAny(haystacks, HiddenExecutionMarkers); } private static bool IsHiddenLolbinTempScriptSeed(ScanFinding finding) { if (!string.Equals(finding.RuleId, "ProcessStartRule", StringComparison.Ordinal)) { return false; } List haystacks = EnumerateFindingTexts(finding).ToList(); return ContainsAny(haystacks, LolbinMarkers) && ContainsAny(haystacks, HiddenExecutionMarkers) && ContainsAny(haystacks, ".bat", ".cmd", "%TEMP%", "WorkingDirectory=Temp", "GetTempPath"); } private static bool IsKnownInfrastructureStagedExecutionSeed(ScanFinding finding, IReadOnlyList allFindings) { if (IsKnownInfrastructureFinding(finding)) { return allFindings.Any(IsHiddenStagedExecutionFinding); } return IsHiddenStagedExecutionFinding(finding) && allFindings.Any(IsKnownInfrastructureFinding); } private static bool IsKnownInfrastructureFinding(ScanFinding finding) { return string.Equals(finding.RuleId, "DataInfiltrationRule", StringComparison.Ordinal) && ContainsAny(EnumerateFindingTexts(finding), "known malicious domain", "confirmed payload delivery infrastructure"); } private static bool IsMultiSignalStagedExecutionSeed(ScanFinding finding, IReadOnlyList allFindings) { if (!string.Equals(finding.RuleId, "MultiSignalDetection", StringComparison.Ordinal) || !ContainsAny(EnumerateFindingTexts(finding), "process execution + Base64 decoding + file write")) { return false; } string methodLocation = NormalizeMethodLocation(finding.Location); return allFindings.Any((ScanFinding candidate) => IsHiddenStagedExecutionFinding(candidate) && string.Equals(NormalizeMethodLocation(candidate.Location), methodLocation, StringComparison.Ordinal)); } private static bool IsHiddenStagedExecutionFinding(ScanFinding finding) { if (!string.Equals(finding.RuleId, "ProcessStartRule", StringComparison.Ordinal)) { return false; } List haystacks = EnumerateFindingTexts(finding).ToList(); return ContainsAny(haystacks, HiddenExecutionMarkers) && ContainsAny(haystacks, ".bat", ".cmd", ".exe", "%TEMP%", "WorkingDirectory=Temp", "GetTempPath", "Correlated data flow"); } private static IEnumerable EnumerateFindingTexts(ScanFinding finding) { yield return finding.Description; yield return finding.Location; if (!string.IsNullOrWhiteSpace(finding.CodeSnippet)) { yield return finding.CodeSnippet; } } private static string NormalizeMethodLocation(string? location) { if (string.IsNullOrWhiteSpace(location)) { return string.Empty; } int num = location.IndexOf(':'); return (num >= 0) ? location.Substring(0, num) : location; } private static bool ContainsAny(IEnumerable haystacks, params string[] needles) { List haystackList = haystacks.Where((string value) => !string.IsNullOrWhiteSpace(value)).ToList(); return needles.Any((string needle) => haystackList.Any((string value) => value.Contains(needle, StringComparison.OrdinalIgnoreCase))); } } internal sealed class ThreatFamilyAnalysisContext { public IReadOnlyList Findings { get; } public IReadOnlyList CallChains { get; } public IReadOnlyList DataFlows { get; } public ThreatFamilyAnalysisContext(IEnumerable findings, IEnumerable? callChains, IEnumerable? dataFlows) { Findings = findings.ToList(); CallChains = CollectUniqueChains(callChains, from f in Findings where f.HasCallChain select f.CallChain); DataFlows = CollectUniqueFlows(dataFlows, from f in Findings where f.HasDataFlow select f.DataFlowChain); } public bool HasRule(string ruleId) { return Findings.Any((ScanFinding f) => string.Equals(f.RuleId, ruleId, StringComparison.Ordinal)); } public ScanFinding? FindFinding(string ruleId, params string[] needles) { return Findings.FirstOrDefault((ScanFinding f) => string.Equals(f.RuleId, ruleId, StringComparison.Ordinal) && (needles.Length == 0 || TextContainsAll(EnumerateFindingTexts(f), needles))); } public CallChain? FindCallChain(string ruleId, params string[] needles) { return CallChains.FirstOrDefault((CallChain chain) => string.Equals(chain.RuleId, ruleId, StringComparison.Ordinal) && (needles.Length == 0 || TextContainsAll(EnumerateCallChainTexts(chain), needles))); } public DataFlowChain? FindDataFlow(DataFlowPattern pattern, params string[] needles) { return DataFlows.FirstOrDefault((DataFlowChain flow) => flow.Pattern == pattern && (needles.Length == 0 || TextContainsAll(EnumerateDataFlowTexts(flow), needles))); } public bool AnyFindingContainsAll(params string[] needles) { return Findings.Any((ScanFinding f) => TextContainsAll(EnumerateFindingTexts(f), needles)); } public bool AnyCallChainContainsAll(params string[] needles) { return CallChains.Any((CallChain chain) => TextContainsAll(EnumerateCallChainTexts(chain), needles)); } public bool AnyDataFlowContainsAll(params string[] needles) { return DataFlows.Any((DataFlowChain flow) => TextContainsAll(EnumerateDataFlowTexts(flow), needles)); } public bool AnyContextContainsAll(params string[] needles) { return AnyFindingContainsAll(needles) || AnyCallChainContainsAll(needles) || AnyDataFlowContainsAll(needles); } public IReadOnlyList BuildMatchedRules(params string[] rules) { return (from rule in rules.Where((string rule) => !string.IsNullOrWhiteSpace(rule)).Distinct(StringComparer.Ordinal) where rule == "DataFlowAnalysis" || HasRule(rule) || CallChains.Any((CallChain c) => string.Equals(c.RuleId, rule, StringComparison.Ordinal)) select rule).OrderBy((string rule) => rule, StringComparer.Ordinal).ToList(); } public ThreatFamilyEvidence CreateRuleEvidence(string kind, string value, ScanFinding? finding) { return new ThreatFamilyEvidence { Kind = kind, Value = value, RuleId = finding?.RuleId, Location = finding?.Location, CallChainId = finding?.CallChain?.ChainId, DataFlowChainId = finding?.DataFlowChain?.ChainId, Pattern = finding?.DataFlowChain?.Pattern.ToString(), MethodLocation = finding?.DataFlowChain?.MethodLocation }; } public ThreatFamilyEvidence CreateCallChainEvidence(string kind, string value, CallChain? chain) { return new ThreatFamilyEvidence { Kind = kind, Value = value, RuleId = chain?.RuleId, Location = chain?.Nodes.LastOrDefault()?.Location, CallChainId = chain?.ChainId }; } public ThreatFamilyEvidence CreateDataFlowEvidence(string kind, string value, DataFlowChain? flow) { return new ThreatFamilyEvidence { Kind = kind, Value = value, DataFlowChainId = flow?.ChainId, Pattern = flow?.Pattern.ToString(), MethodLocation = flow?.MethodLocation, Location = flow?.Nodes.LastOrDefault()?.Location }; } private static IReadOnlyList CollectUniqueChains(IEnumerable? explicitCallChains, IEnumerable findingCallChains) { HashSet seen = new HashSet(StringComparer.Ordinal); List list = new List(); foreach (CallChain item in explicitCallChains ?? Enumerable.Empty()) { AddChain(item, seen, list); } foreach (CallChain findingCallChain in findingCallChains) { AddChain(findingCallChain, seen, list); } return list; } private static IReadOnlyList CollectUniqueFlows(IEnumerable? explicitDataFlows, IEnumerable findingDataFlows) { HashSet seen = new HashSet(StringComparer.Ordinal); List list = new List(); foreach (DataFlowChain item in explicitDataFlows ?? Enumerable.Empty()) { AddFlow(item, seen, list); } foreach (DataFlowChain findingDataFlow in findingDataFlows) { AddFlow(findingDataFlow, seen, list); } return list; } private static void AddChain(CallChain chain, ISet seen, ICollection chains) { if (seen.Add(chain.ChainId)) { chains.Add(chain); } } private static void AddFlow(DataFlowChain flow, ISet seen, ICollection flows) { if (seen.Add(flow.ChainId)) { flows.Add(flow); } } private static bool TextContainsAll(IEnumerable haystacks, IEnumerable needles) { List haystackList = haystacks.Where((string value) => !string.IsNullOrWhiteSpace(value)).ToList(); return needles.All((string needle) => haystackList.Any((string value) => value.Contains(needle, StringComparison.OrdinalIgnoreCase))); } private static IEnumerable EnumerateFindingTexts(ScanFinding finding) { yield return finding.Location; yield return finding.Description; if (!string.IsNullOrWhiteSpace(finding.CodeSnippet)) { yield return finding.CodeSnippet; } } private static IEnumerable EnumerateCallChainTexts(CallChain chain) { yield return chain.Summary; yield return chain.RuleId; foreach (CallChainNode node in chain.Nodes) { yield return node.Location; yield return node.Description; if (!string.IsNullOrWhiteSpace(node.CodeSnippet)) { yield return node.CodeSnippet; } } } private static IEnumerable EnumerateDataFlowTexts(DataFlowChain flow) { yield return flow.Summary; yield return flow.MethodLocation; yield return flow.Pattern.ToString(); foreach (DataFlowNode node in flow.Nodes) { yield return node.Location; yield return node.Operation; yield return node.DataDescription; if (!string.IsNullOrWhiteSpace(node.MethodKey)) { yield return node.MethodKey; } if (!string.IsNullOrWhiteSpace(node.TargetMethodKey)) { yield return node.TargetMethodKey; } if (!string.IsNullOrWhiteSpace(node.CodeSnippet)) { yield return node.CodeSnippet; } } } } internal static class ThreatFamilyCatalog { public static IReadOnlyList Families { get; } = new <>z__ReadOnlyArray(new ThreatFamilyDefinition[9] { new ThreatFamilyDefinition { FamilyId = "family-resource-shell32-tempcmd-v2", DisplayName = "Embedded resource temp CMD dropper", Summary = "Extracts an embedded resource to a temporary .cmd file and executes it via ShellExecuteEx or Process.Start.", AdvisorySlugs = new <>z__ReadOnlyArray(new string[3] { "2025-12-malware-customtv-il2cpp", "2025-12-malware-nomoretrash", "2025-12-malware-realandwaitingtimeonfire" }), ExactSampleHashes = Array.Empty(), Variants = new <>z__ReadOnlyArray(new ThreatFamilyVariantDefinition[2] { new ThreatFamilyVariantDefinition { VariantId = "resource-shell32-tempcmd-shell32", DisplayName = "Embedded resource -> temp .cmd -> ShellExecuteEx", Summary = "Embedded payload materialized to a temporary .cmd file and launched through ShellExecuteEx.", Confidence = 0.99, Matcher = MatchEmbeddedShellExecuteTempCmd }, new ThreatFamilyVariantDefinition { VariantId = "resource-shell32-tempcmd-process-start", DisplayName = "Embedded resource -> temp .cmd -> Process.Start", Summary = "Embedded payload materialized to a temporary .cmd file and launched through Process.Start.", Confidence = 0.97, Matcher = MatchEmbeddedProcessStartTempCmd } }) }, new ThreatFamilyDefinition { FamilyId = "family-powershell-iwr-dlbat-v1", DisplayName = "PowerShell IWR temp batch downloader", Summary = "Launches hidden PowerShell to download a batch file into TEMP, run it, then remove it.", AdvisorySlugs = new <>z__ReadOnlyArray(new string[2] { "2026-01-malware-endlessgraffiti", "2026-01-malware-fastergrowth" }), ExactSampleHashes = new <>z__ReadOnlyArray(new string[2] { "6c15802426e22e8a0376af1be8bb5caebb5b2e2f4f06a8e7944c80c647a548e6", "5e3bb51b52725c2f0f2a4d9eb4ecbadbd169aec0e0ac474d9127f205da4e3b72" }), Variants = new <>z__ReadOnlySingleElementList(new ThreatFamilyVariantDefinition { VariantId = "powershell-iwr-dlbat-cleanup", DisplayName = "Hidden PowerShell IWR temp batch chain", Summary = "Uses hidden PowerShell with Invoke-WebRequest to stage dl.bat in TEMP, execute it, sleep, and delete it.", Confidence = 0.98, Matcher = MatchPowerShellIwrDlBat }) }, new ThreatFamilyDefinition { FamilyId = "family-webdownload-stage-exec-v3", DisplayName = "Web download staged payload executor", Summary = "Downloads a payload to TEMP through WebClient, HttpClient, or a similar network API and then executes it via hidden or shell-assisted process launch.", AdvisorySlugs = new <>z__ReadOnlyArray(new string[10] { "2026-02-malware-moretrees", "2026-03-malware-customer-search-bar", "2026-03-malware-longlastingfertilizer", "2026-03-malware-nopolice", "2026-03-malware-rentalcars", "2026-03-malware-skitching", "2026-03-malware-storagehub", "2026-03-malware-unlimitedgraffiti", "2026-03-malware-vortex-backuprtilizer", "2026-04-malware-dynamicorders" }), ExactSampleHashes = Array.Empty(), Variants = new <>z__ReadOnlyArray(new ThreatFamilyVariantDefinition[4] { new ThreatFamilyVariantDefinition { VariantId = "webdownload-temp-ps1-hidden-powershell", DisplayName = "Web download -> temp .ps1 -> hidden PowerShell", Summary = "Downloads a script payload into TEMP and launches it through a hidden powershell.exe chain, which helps platforms quickly cluster trojanized mod loaders using the same script-stager tradecraft.", Confidence = 0.98, Matcher = MatchWebDownloadTempPowerShellScript }, new ThreatFamilyVariantDefinition { VariantId = "webdownload-temp-batch-hidden-cmd", DisplayName = "Web download -> temp batch -> hidden cmd.exe", Summary = "Downloads a batch payload into TEMP and pivots through hidden cmd.exe execution, separating command-shell stagers from PowerShell-script variants during triage.", Confidence = 0.97, Matcher = MatchWebDownloadTempBatchCmd }, new ThreatFamilyVariantDefinition { VariantId = "webdownload-temp-exe-direct-launch", DisplayName = "Web download -> temp executable -> direct launch", Summary = "Downloads an executable payload and launches it directly from a temporary working directory, distinguishing direct binary droppers from script-based stagers.", Confidence = 0.97, Matcher = MatchWebDownloadTempExecutable }, new ThreatFamilyVariantDefinition { VariantId = "webdownload-temp-hidden-launch-generic", DisplayName = "Web download staged payload executor", Summary = "Downloads a payload over the network, stages it in TEMP, then executes it through powershell.exe, cmd.exe, or a hidden direct process launch.", Confidence = 0.95, Matcher = MatchWebDownloadStageExecute } }) }, new ThreatFamilyDefinition { FamilyId = "family-embedded-resource-script-stager-v1", DisplayName = "Embedded resource script stager", Summary = "Stores a script payload in a referenced embedded resource, stages it at runtime, and uses hidden shell or PowerShell execution to retrieve or run a payload.", AdvisorySlugs = Array.Empty(), ExactSampleHashes = Array.Empty(), Variants = new <>z__ReadOnlySingleElementList(new ThreatFamilyVariantDefinition { VariantId = "embedded-resource-powershell-download-tempbat", DisplayName = "Embedded resource -> PowerShell download -> temp batch", Summary = "Referenced embedded resource contains a PowerShell/WebClient download command that stages a batch payload under TEMP and executes it.", Confidence = 0.97, Matcher = MatchEmbeddedResourcePowerShellDownloadTempBatch }) }, new ThreatFamilyDefinition { FamilyId = "family-remote-script-pipe-shell-v1", DisplayName = "Remote script piped to shell", Summary = "Launches a command shell that retrieves a remote script and pipes it directly into another shell interpreter without staging a visible payload file.", AdvisorySlugs = Array.Empty(), ExactSampleHashes = Array.Empty(), Variants = new <>z__ReadOnlySingleElementList(new ThreatFamilyVariantDefinition { VariantId = "curl-pipe-cmd-remote-script", DisplayName = "curl URL piped to cmd.exe", Summary = "Runs cmd.exe with a curl command that fetches a remote script and pipes the response directly into cmd.exe.", Confidence = 0.98, Matcher = MatchCurlPipeCmdRemoteScript }) }, new ThreatFamilyDefinition { FamilyId = "family-encoded-powershell-tempcmd-stager-v1", DisplayName = "Encoded hidden PowerShell temp command stager", Summary = "Stores command-line process, PowerShell, and TEMP command script arguments as numeric-encoded strings before reflectively launching a hidden downloader.", AdvisorySlugs = Array.Empty(), ExactSampleHashes = Array.Empty(), Variants = new <>z__ReadOnlySingleElementList(new ThreatFamilyVariantDefinition { VariantId = "numeric-decoded-iwr-tempcmd-hidden-launch", DisplayName = "Numeric decode -> hidden PowerShell -> TEMP command", Summary = "Decodes a hidden cmd.exe or powershell.exe launcher that downloads a command script into TEMP and starts it hidden.", Confidence = 0.97, Matcher = MatchEncodedPowerShellTempCommandStager }) }, new ThreatFamilyDefinition { FamilyId = "family-hex-remote-config-tempcmd-stager-v1", DisplayName = "Hex remote config temp CMD stager", Summary = "Decodes remote command configuration URLs and reflectively stages the fetched command into a temporary script before hidden cmd.exe execution.", AdvisorySlugs = Array.Empty(), ExactSampleHashes = Array.Empty(), Variants = new <>z__ReadOnlySingleElementList(new ThreatFamilyVariantDefinition { VariantId = "hex-config-reflective-tempcmd-hidden-cmd", DisplayName = "Hex config -> reflected WebClient -> temp .cmd -> hidden cmd.exe", Summary = "Uses hex/byte-array string reconstruction to hide config URLs, WebClient.DownloadString, temp command-file staging, and a hidden reflected cmd.exe launch.", Confidence = 0.97, Matcher = MatchHexRemoteConfigReflectiveTempCmd }) }, new ThreatFamilyDefinition { FamilyId = "family-dynamic-assembly-reflection-loader-v1", DisplayName = "Dynamic assembly reflection loader", Summary = "Loads opaque assembly bytes at runtime and invokes code through reflection, often hiding the payload as an embedded or external plugin resource.", AdvisorySlugs = Array.Empty(), ExactSampleHashes = Array.Empty(), Variants = new <>z__ReadOnlyArray(new ThreatFamilyVariantDefinition[2] { new ThreatFamilyVariantDefinition { VariantId = "assembly-load-reflective-invoke", DisplayName = "Assembly.Load bytes with reflective invoke", Summary = "Loads assembly bytes dynamically and invokes an unresolved reflected method, separating the visible host mod from the executable payload.", Confidence = 0.94, Matcher = MatchAssemblyLoadReflectiveInvoke }, new ThreatFamilyVariantDefinition { VariantId = "dynamic-code-loader-hidden-svchost", DisplayName = "Dynamic code loader with hidden svchost launch", Summary = "Combines dynamic code loading or plugin assembly loading with hidden launch of a Windows service binary name.", Confidence = 0.96, Matcher = MatchDynamicLoaderHiddenSystemProcess } }) }, new ThreatFamilyDefinition { FamilyId = "family-obfuscated-metadata-loader-v2", DisplayName = "Obfuscated metadata-backed loader", Summary = "Uses encoded strings, numeric transforms, or assembly metadata to reconstruct a staged hidden command launcher at runtime.", AdvisorySlugs = new <>z__ReadOnlySingleElementList("2025-11-malware-scheduleimorenpcs"), ExactSampleHashes = new <>z__ReadOnlySingleElementList("b6ea902d5eda7bb210c31715f2c90a4b249ce8b6c1747d571028719d025d59db"), Variants = new <>z__ReadOnlyArray(new ThreatFamilyVariantDefinition[4] { new ThreatFamilyVariantDefinition { VariantId = "numeric-metadata-hidden-loader", DisplayName = "Numeric decode + metadata hidden loader", Summary = "Reconstructs hidden cmd.exe/powershell.exe launcher details from numeric-encoded strings and metadata attributes.", Confidence = 0.95, Matcher = MatchObfuscatedMetadataLoader }, new ThreatFamilyVariantDefinition { VariantId = "assembly-description-encoded-hidden-launcher", DisplayName = "Assembly description encoded hidden launcher", Summary = "Stores hidden ProcessStartInfo, cmd.exe, and PowerShell downloader launch arguments as numeric segments in AssemblyDescription metadata.", Confidence = 0.96, Matcher = MatchAssemblyDescriptionEncodedHiddenLauncher }, new ThreatFamilyVariantDefinition { VariantId = "base64-tempbat-hidden-cmd", DisplayName = "Base64 decoded temp batch hidden cmd", Summary = "Decodes an embedded Base64 payload, writes it as a temporary batch or command script, and executes it through hidden cmd.exe.", Confidence = 0.97, Matcher = MatchBase64TempBatchHiddenCmd }, new ThreatFamilyVariantDefinition { VariantId = "obfuscated-reflective-hidden-process-launch", DisplayName = "Obfuscated reflective hidden process launch", Summary = "Correlates Base64 or numeric decode behavior with reflective or native execution bridging and hidden process launch.", Confidence = 0.95, Matcher = MatchObfuscatedReflectiveHiddenProcessLaunch } }) } }); private static ThreatFamilyVariantMatch? MatchHexRemoteConfigReflectiveTempCmd(ThreatFamilyAnalysisContext context) { ScanFinding scanFinding = context.Findings.FirstOrDefault((ScanFinding finding) => string.Equals(finding.RuleId, "ObfuscatedReflectiveExecutionRule", StringComparison.Ordinal) && FindingContainsAny(finding, "hex remote config reflective temp command stager") && FindingContainsAny(finding, "WebClient.DownloadString", "DownloadString") && FindingContainsAny(finding, "GetTempFileName") && FindingContainsAny(finding, ".cmd", ".bat") && FindingContainsAny(finding, "File.WriteAllText", "WriteAllText") && FindingContainsAny(finding, "ProcessStartInfo") && FindingContainsAny(finding, "cmd.exe") && FindingContainsAny(finding, "WindowStyle=Hidden", "WindowStyle Hidden", "Hidden") && FindingContainsAny(finding, "MethodInfo.Invoke", "MethodBase.Invoke")); if (scanFinding == null) { return null; } ThreatFamilyVariantMatch threatFamilyVariantMatch = new ThreatFamilyVariantMatch(); threatFamilyVariantMatch.MatchedRules = context.BuildMatchedRules("ObfuscatedReflectiveExecutionRule"); threatFamilyVariantMatch.Evidence = new <>z__ReadOnlyArray(new ThreatFamilyEvidence[5] { context.CreateRuleEvidence("rule", "ObfuscatedReflectiveExecutionRule", scanFinding), Evidence("config", "hex-encoded remote command configuration URLs"), Evidence("download", "reflected WebClient.DownloadString"), Evidence("staging", "Path.GetTempFileName + .cmd + File.WriteAllText"), Evidence("execution", "reflected hidden cmd.exe ProcessStartInfo launch") }); return threatFamilyVariantMatch; } private static ThreatFamilyVariantMatch? MatchEmbeddedShellExecuteTempCmd(ThreatFamilyAnalysisContext context) { DataFlowChain dataFlowChain = context.FindDataFlow(DataFlowPattern.EmbeddedResourceDropAndExecute); ScanFinding scanFinding = context.FindFinding("DllImportRule", "ShellExecuteEx"); CallChain callChain = context.FindCallChain("DllImportRule", "ShellExecuteEx"); bool flag = scanFinding != null || callChain != null; bool flag2 = context.AnyDataFlowContainsAll(".cmd") || context.AnyFindingContainsAll(".cmd") || context.AnyCallChainContainsAll(".cmd"); if (dataFlowChain == null || !flag || !flag2) { return null; } ThreatFamilyVariantMatch threatFamilyVariantMatch = new ThreatFamilyVariantMatch(); threatFamilyVariantMatch.MatchedRules = context.BuildMatchedRules("DataFlowAnalysis", "DllImportRule"); threatFamilyVariantMatch.Evidence = new <>z__ReadOnlyArray(new ThreatFamilyEvidence[6] { context.CreateDataFlowEvidence("pattern", DataFlowPattern.EmbeddedResourceDropAndExecute.ToString(), dataFlowChain), context.CreateDataFlowEvidence("data-flow-chain", dataFlowChain?.ChainId ?? "not-available", dataFlowChain), context.CreateRuleEvidence("api", "ShellExecuteEx", scanFinding), context.CreateCallChainEvidence("call-chain", callChain?.ChainId ?? "not-available", callChain), Evidence("staging", "embedded resource -> temp .cmd"), Evidence("execution", "ShellExecuteEx via shell32.dll") }); return threatFamilyVariantMatch; } private static ThreatFamilyVariantMatch? MatchEmbeddedProcessStartTempCmd(ThreatFamilyAnalysisContext context) { DataFlowChain dataFlowChain = context.FindDataFlow(DataFlowPattern.EmbeddedResourceDropAndExecute); ScanFinding scanFinding = context.FindFinding("ProcessStartRule"); if (dataFlowChain == null || scanFinding == null) { return null; } if (!context.AnyDataFlowContainsAll(".cmd") && !context.AnyFindingContainsAll(".cmd") && !context.AnyCallChainContainsAll(".cmd") && !context.AnyDataFlowContainsAll(".bat") && !context.AnyFindingContainsAll(".bat") && !context.AnyCallChainContainsAll(".bat")) { return null; } ThreatFamilyVariantMatch threatFamilyVariantMatch = new ThreatFamilyVariantMatch(); threatFamilyVariantMatch.MatchedRules = context.BuildMatchedRules("DataFlowAnalysis", "ProcessStartRule"); threatFamilyVariantMatch.Evidence = new <>z__ReadOnlyArray(new ThreatFamilyEvidence[5] { context.CreateDataFlowEvidence("pattern", DataFlowPattern.EmbeddedResourceDropAndExecute.ToString(), dataFlowChain), context.CreateDataFlowEvidence("data-flow-chain", dataFlowChain.ChainId, dataFlowChain), context.CreateRuleEvidence("rule", "ProcessStartRule", scanFinding), Evidence("staging", "embedded resource -> temp script"), Evidence("execution", "Process.Start script execution") }); return threatFamilyVariantMatch; } private static ThreatFamilyVariantMatch? MatchPowerShellIwrDlBat(ThreatFamilyAnalysisContext context) { DataFlowChain dataFlowChain = context.FindDataFlow(DataFlowPattern.DownloadAndExecute); ScanFinding scanFinding = context.FindFinding("ProcessStartRule", "powershell.exe"); if (scanFinding == null) { return null; } bool flag = context.AnyContextContainsAll("iwr") || context.AnyContextContainsAll("Invoke-WebRequest"); bool flag2 = context.AnyContextContainsAll("dl.bat") || context.AnyContextContainsAll("%TEMP%", ".bat"); bool flag3 = context.AnyContextContainsAll("Start-Sleep") || context.AnyContextContainsAll("Remove-Item"); if (!flag || !flag2 || !flag3) { return null; } if (dataFlowChain == null && context.FindCallChain("ProcessStartRule", "powershell.exe") == null && !context.AnyFindingContainsAll("powershell.exe", "iwr", "dl.bat", "Start-Sleep", "Remove-Item")) { return null; } ThreatFamilyVariantMatch threatFamilyVariantMatch = new ThreatFamilyVariantMatch(); threatFamilyVariantMatch.MatchedRules = context.BuildMatchedRules("DataFlowAnalysis", "ProcessStartRule"); threatFamilyVariantMatch.Evidence = new <>z__ReadOnlyArray(new ThreatFamilyEvidence[7] { context.CreateDataFlowEvidence("pattern", dataFlowChain?.Pattern.ToString() ?? "standalone-process-chain", dataFlowChain), context.CreateDataFlowEvidence("data-flow-chain", dataFlowChain?.ChainId ?? "not-available", dataFlowChain), context.CreateRuleEvidence("rule", "ProcessStartRule", scanFinding), context.CreateRuleEvidence("launcher", "powershell.exe", scanFinding), Evidence("download", "Invoke-WebRequest / iwr"), Evidence("staging", "%TEMP%/dl.bat"), Evidence("cleanup", "sleep then remove temp batch") }); return threatFamilyVariantMatch; } private static ThreatFamilyVariantMatch? MatchObfuscatedMetadataLoader(ThreatFamilyAnalysisContext context) { ScanFinding scanFinding = context.FindFinding("EncodedStringLiteralRule"); ScanFinding scanFinding2 = context.FindFinding("ReflectionRule", "AssemblyMetadataAttribute"); CallChain callChain = context.FindCallChain("ReflectionRule", "AssemblyMetadataAttribute"); ScanFinding scanFinding3 = context.FindFinding("EncodedStringPipelineRule"); DataFlowChain dataFlowChain = context.FindDataFlow(DataFlowPattern.DynamicCodeLoading); if (scanFinding == null || scanFinding2 == null || scanFinding3 == null) { return null; } ThreatFamilyVariantMatch threatFamilyVariantMatch = new ThreatFamilyVariantMatch(); threatFamilyVariantMatch.MatchedRules = context.BuildMatchedRules("EncodedStringLiteralRule", "EncodedStringPipelineRule", "ReflectionRule", (dataFlowChain != null) ? "DataFlowAnalysis" : string.Empty); threatFamilyVariantMatch.Evidence = new <>z__ReadOnlyArray(new ThreatFamilyEvidence[8] { context.CreateRuleEvidence("rule", "EncodedStringLiteralRule", scanFinding), context.CreateRuleEvidence("rule", "EncodedStringPipelineRule", scanFinding3), context.CreateRuleEvidence("rule", "ReflectionRule", scanFinding2), context.CreateCallChainEvidence("call-chain", callChain?.ChainId ?? "not-available", callChain), context.CreateDataFlowEvidence("data-flow-pattern", dataFlowChain?.Pattern.ToString() ?? "not-available", dataFlowChain), Evidence("obfuscation", "numeric string decode pipeline"), Evidence("payload-source", "assembly metadata value"), Evidence("execution", "hidden cmd.exe / powershell.exe loader") }); return threatFamilyVariantMatch; } private static ThreatFamilyVariantMatch? MatchAssemblyDescriptionEncodedHiddenLauncher(ThreatFamilyAnalysisContext context) { ScanFinding scanFinding = context.Findings.FirstOrDefault((ScanFinding finding) => string.Equals(finding.RuleId, "EncodedStringLiteralRule", StringComparison.Ordinal) && FindingContainsAny(finding, "AssemblyDescriptionAttribute") && FindingContainsAny(finding, "ProcessStartInfo") && FindingContainsAny(finding, "cmd.exe") && FindingContainsAny(finding, "Invoke-WebRequest", "DownloadFile", "WebClient") && FindingContainsAny(finding, ".cmd", ".bat", "ProgramData", "%TEMP%", "$env:TEMP") && FindingContainsAny(finding, "WindowStyle Hidden", "WindowStyle", "Hidden", "CreateNoWindow")); if (scanFinding == null) { return null; } ThreatFamilyVariantMatch threatFamilyVariantMatch = new ThreatFamilyVariantMatch(); threatFamilyVariantMatch.MatchedRules = context.BuildMatchedRules("EncodedStringLiteralRule"); threatFamilyVariantMatch.Evidence = new <>z__ReadOnlyArray(new ThreatFamilyEvidence[5] { context.CreateRuleEvidence("rule", "EncodedStringLiteralRule", scanFinding), Evidence("payload-source", "AssemblyDescriptionAttribute"), Evidence("decode", "multi-level numeric encoded launcher metadata"), Evidence("download", "PowerShell web request command"), Evidence("execution", "hidden cmd.exe / powershell launcher") }); return threatFamilyVariantMatch; } private static ThreatFamilyVariantMatch? MatchBase64TempBatchHiddenCmd(ThreatFamilyAnalysisContext context) { ScanFinding scanFinding = context.FindFinding("Base64Rule"); ScanFinding scanFinding2 = context.Findings.FirstOrDefault((ScanFinding finding) => string.Equals(finding.RuleId, "ProcessStartRule", StringComparison.Ordinal) && FindingContainsAll(finding, "Target: \"cmd.exe\"") && HasScriptStagingIndicators(finding) && HasHiddenExecutionIndicators(finding)); ScanFinding scanFinding3 = context.FindFinding("MultiSignalDetection", "process execution", "Base64", "file write"); if (scanFinding == null || scanFinding2 == null || scanFinding3 == null) { return null; } if (!LocationsShareMethod(scanFinding.Location, scanFinding2.Location) || !LocationsShareMethod(scanFinding2.Location, scanFinding3.Location)) { return null; } ThreatFamilyVariantMatch threatFamilyVariantMatch = new ThreatFamilyVariantMatch(); threatFamilyVariantMatch.MatchedRules = context.BuildMatchedRules("Base64Rule", "MultiSignalDetection", "ProcessStartRule"); threatFamilyVariantMatch.Evidence = new <>z__ReadOnlyArray(new ThreatFamilyEvidence[6] { context.CreateRuleEvidence("rule", "Base64Rule", scanFinding), context.CreateRuleEvidence("rule", "MultiSignalDetection", scanFinding3), context.CreateRuleEvidence("rule", "ProcessStartRule", scanFinding2), Evidence("decode", "Base64 payload materialization"), Evidence("staging", "decoded payload -> temporary .bat/.cmd script"), Evidence("execution", "hidden cmd.exe script execution") }); return threatFamilyVariantMatch; } private static ThreatFamilyVariantMatch? MatchEncodedPowerShellTempCommandStager(ThreatFamilyAnalysisContext context) { ScanFinding scanFinding = context.Findings.FirstOrDefault((ScanFinding finding) => string.Equals(finding.RuleId, "EncodedStringLiteralRule", StringComparison.Ordinal) && FindingContainsAny(finding, "powershell.exe", "cmd.exe") && FindingContainsAny(finding, "Invoke-WebRequest", "iwr ", "DownloadFile") && FindingContainsAny(finding, "%TEMP%", "$env:TEMP", "temp.cmd", ".cmd", ".bat") && FindingContainsAny(finding, "WindowStyle Hidden", "-WindowStyle Hidden", "Hidden")); ScanFinding scanFinding2 = context.FindFinding("EncodedStringPipelineRule"); ScanFinding scanFinding3 = context.FindFinding("Base64Rule"); ScanFinding scanFinding4 = context.FindFinding("ObfuscatedReflectiveExecutionRule", "cross-method obfuscated reflection staging cluster"); if (scanFinding == null || (scanFinding2 == null && scanFinding3 == null && scanFinding4 == null)) { return null; } ThreatFamilyVariantMatch threatFamilyVariantMatch = new ThreatFamilyVariantMatch(); threatFamilyVariantMatch.MatchedRules = context.BuildMatchedRules("EncodedStringLiteralRule", "EncodedStringPipelineRule", "Base64Rule", "ObfuscatedReflectiveExecutionRule"); threatFamilyVariantMatch.Evidence = new <>z__ReadOnlyArray(new ThreatFamilyEvidence[8] { context.CreateRuleEvidence("rule", "EncodedStringLiteralRule", scanFinding), context.CreateRuleEvidence("rule", "EncodedStringPipelineRule", scanFinding2), context.CreateRuleEvidence("rule", "Base64Rule", scanFinding3), context.CreateRuleEvidence("rule", "ObfuscatedReflectiveExecutionRule", scanFinding4), Evidence("decode", "numeric encoded command-line payload"), Evidence("download", "PowerShell web request"), Evidence("staging", "TEMP .cmd/.bat payload"), Evidence("execution", "hidden cmd.exe / powershell.exe launcher") }); return threatFamilyVariantMatch; } private static ThreatFamilyVariantMatch? MatchAssemblyLoadReflectiveInvoke(ThreatFamilyAnalysisContext context) { ScanFinding scanFinding = context.FindFinding("AssemblyDynamicLoadRule"); ScanFinding scanFinding2 = context.FindFinding("ReflectionRule", "non-literal target method name") ?? context.FindFinding("ReflectionRule", "without determinable target method"); if (scanFinding == null || scanFinding2 == null) { return null; } if (!LocationsShareMethod(scanFinding.Location, scanFinding2.Location)) { return null; } ThreatFamilyVariantMatch threatFamilyVariantMatch = new ThreatFamilyVariantMatch(); threatFamilyVariantMatch.MatchedRules = context.BuildMatchedRules("AssemblyDynamicLoadRule", "ReflectionRule"); threatFamilyVariantMatch.Evidence = new <>z__ReadOnlyArray(new ThreatFamilyEvidence[4] { context.CreateRuleEvidence("rule", "AssemblyDynamicLoadRule", scanFinding), context.CreateRuleEvidence("rule", "ReflectionRule", scanFinding2), Evidence("payload", "dynamic Assembly.Load byte payload"), Evidence("execution", "unresolved reflected method invocation") }); return threatFamilyVariantMatch; } private static ThreatFamilyVariantMatch? MatchDynamicLoaderHiddenSystemProcess(ThreatFamilyAnalysisContext context) { ScanFinding scanFinding = context.Findings.FirstOrDefault((ScanFinding finding) => string.Equals(finding.RuleId, "ProcessStartRule", StringComparison.Ordinal) && FindingContainsAny(finding, "Target: \"svchost.exe\"", "Target: \"rundll32.exe\"", "Target: \"regsvr32.exe\"") && HasHiddenExecutionIndicators(finding)); ScanFinding scanFinding2 = context.FindFinding("AssemblyDynamicLoadRule"); DataFlowChain dataFlowChain = context.FindDataFlow(DataFlowPattern.DynamicCodeLoading); if (scanFinding == null || (scanFinding2 == null && dataFlowChain == null)) { return null; } ThreatFamilyVariantMatch threatFamilyVariantMatch = new ThreatFamilyVariantMatch(); threatFamilyVariantMatch.MatchedRules = context.BuildMatchedRules("ProcessStartRule", "AssemblyDynamicLoadRule", (dataFlowChain != null) ? "DataFlowAnalysis" : string.Empty); threatFamilyVariantMatch.Evidence = new <>z__ReadOnlyArray(new ThreatFamilyEvidence[5] { context.CreateRuleEvidence("rule", "ProcessStartRule", scanFinding), context.CreateRuleEvidence("rule", "AssemblyDynamicLoadRule", scanFinding2), context.CreateDataFlowEvidence("data-flow-pattern", dataFlowChain?.Pattern.ToString() ?? "not-available", dataFlowChain), Evidence("loader", "dynamic code loading present"), Evidence("execution", "hidden Windows system-binary-name process launch") }); return threatFamilyVariantMatch; } private static ThreatFamilyVariantMatch? MatchObfuscatedReflectiveHiddenProcessLaunch(ThreatFamilyAnalysisContext context) { ScanFinding scanFinding = context.FindFinding("ObfuscatedReflectiveExecutionRule"); ScanFinding scanFinding2 = context.Findings.FirstOrDefault((ScanFinding scanFinding3) => string.Equals(scanFinding3.RuleId, "ProcessStartRule", StringComparison.Ordinal) && HasHiddenExecutionIndicators(scanFinding3)); ScanFinding finding = context.FindFinding("Base64Rule"); ScanFinding finding2 = context.FindFinding("MultiSignalDetection"); if (scanFinding == null || scanFinding2 == null || !FindingContainsAny(scanFinding, "process execution sink", "native execution bridge", "staged execution")) { return null; } ThreatFamilyVariantMatch threatFamilyVariantMatch = new ThreatFamilyVariantMatch(); threatFamilyVariantMatch.MatchedRules = context.BuildMatchedRules("ObfuscatedReflectiveExecutionRule", "ProcessStartRule", "Base64Rule", "MultiSignalDetection"); threatFamilyVariantMatch.Evidence = new <>z__ReadOnlyArray(new ThreatFamilyEvidence[7] { context.CreateRuleEvidence("rule", "ObfuscatedReflectiveExecutionRule", scanFinding), context.CreateRuleEvidence("rule", "ProcessStartRule", scanFinding2), context.CreateRuleEvidence("rule", "Base64Rule", finding), context.CreateRuleEvidence("rule", "MultiSignalDetection", finding2), Evidence("decode", "obfuscated Base64 or numeric reconstruction"), Evidence("bridge", "reflective/native execution bridge"), Evidence("execution", "hidden process launch") }); return threatFamilyVariantMatch; } private static ThreatFamilyVariantMatch? MatchEmbeddedResourcePowerShellDownloadTempBatch(ThreatFamilyAnalysisContext context) { ScanFinding scanFinding = context.FindFinding("EmbeddedResourceScriptRule", "staged script payload"); if (scanFinding == null || !FindingContainsAny(scanFinding, "powershell", "Invoke-WebRequest", "WebClient", "DownloadFile") || !FindingContainsAny(scanFinding, "%TEMP%", "$env:TEMP", ".bat", ".cmd") || !FindingContainsAny(scanFinding, "WindowStyle Hidden", "-WindowStyle Hidden", "ExecutionPolicy Bypass", "Start-Process", "& ")) { return null; } ThreatFamilyVariantMatch threatFamilyVariantMatch = new ThreatFamilyVariantMatch(); threatFamilyVariantMatch.MatchedRules = context.BuildMatchedRules("EmbeddedResourceScriptRule"); threatFamilyVariantMatch.Evidence = new <>z__ReadOnlyArray(new ThreatFamilyEvidence[5] { context.CreateRuleEvidence("rule", "EmbeddedResourceScriptRule", scanFinding), Evidence("source", "referenced embedded script resource"), Evidence("download", "PowerShell/WebClient payload retrieval"), Evidence("staging", "TEMP batch or command script"), Evidence("execution", "hidden script execution") }); return threatFamilyVariantMatch; } private static ThreatFamilyVariantMatch? MatchCurlPipeCmdRemoteScript(ThreatFamilyAnalysisContext context) { ScanFinding scanFinding = context.Findings.FirstOrDefault((ScanFinding finding) => string.Equals(finding.RuleId, "ProcessStartRule", StringComparison.Ordinal) && FindingContainsAll(finding, "Target: \"cmd.exe\"") && FindingContainsAny(finding, "curl ", "curl.exe", "iwr ", "Invoke-WebRequest") && FindingContainsAny(finding, "| cmd", "|cmd", "| powershell", "|powershell", " | ")); if (scanFinding == null || !FindingContainsAny(scanFinding, "http://", "https://")) { return null; } ThreatFamilyVariantMatch threatFamilyVariantMatch = new ThreatFamilyVariantMatch(); threatFamilyVariantMatch.MatchedRules = context.BuildMatchedRules("ProcessStartRule"); threatFamilyVariantMatch.Evidence = new <>z__ReadOnlyArray(new ThreatFamilyEvidence[3] { context.CreateRuleEvidence("rule", "ProcessStartRule", scanFinding), Evidence("download", "remote script fetched by command-line web client"), Evidence("execution", "remote response piped directly to shell") }); return threatFamilyVariantMatch; } private static ThreatFamilyEvidence Evidence(string kind, string value) { return new ThreatFamilyEvidence { Kind = kind, Value = value }; } private static ThreatFamilyEvidence Evidence(string kind, string value, string? pattern, string? methodLocation, double? confidence) { return new ThreatFamilyEvidence { Kind = kind, Value = value, Pattern = pattern, MethodLocation = methodLocation, Confidence = confidence }; } private static bool FindingContainsAny(ScanFinding finding, params string[] needles) { return needles.Any((string needle) => !string.IsNullOrWhiteSpace(needle) && EnumerateFindingTexts(finding).Any((string value) => value.Contains(needle, StringComparison.OrdinalIgnoreCase))); } private static bool HasScriptStagingIndicators(ScanFinding finding) { return FindingContainsAny(finding, ".bat", ".cmd") && FindingContainsAny(finding, "%TEMP%", "WorkingDirectory=Temp", "GetTempPath", "TEMP"); } private static bool LocationsShareMethod(string? left, string? right) { string text = NormalizeMethodLocation(left); string b = NormalizeMethodLocation(right); return !string.IsNullOrWhiteSpace(text) && string.Equals(text, b, StringComparison.Ordinal); } private static string NormalizeMethodLocation(string? location) { if (string.IsNullOrWhiteSpace(location)) { return string.Empty; } int num = location.IndexOf(':'); return (num >= 0) ? location.Substring(0, num) : location; } private static IEnumerable EnumerateFindingTexts(ScanFinding finding) { yield return finding.Location; yield return finding.Description; if (!string.IsNullOrWhiteSpace(finding.CodeSnippet)) { yield return finding.CodeSnippet; } } private static ThreatFamilyVariantMatch? MatchWebDownloadTempPowerShellScript(ThreatFamilyAnalysisContext context) { DataFlowChain dataFlowChain = context.FindDataFlow(DataFlowPattern.DownloadAndExecute); ScanFinding scanFinding = FindDownloadFinding(context, ".ps1"); ScanFinding scanFinding2 = FindProcessStartFinding(context, (ScanFinding finding) => FindingContainsAll(finding, "Target: \"powershell.exe\"", ".ps1")); if (dataFlowChain == null || scanFinding == null || scanFinding2 == null || !HasTempScriptStaging(context, ".ps1") || !HasHiddenExecutionContext(context)) { return null; } string value = ((context.AnyContextContainsAll("where.exe", "powershell") || context.AnyContextContainsAll("SysNative", "powershell.exe") || context.AnyContextContainsAll("System32", "powershell.exe") || context.AnyContextContainsAll("SysWOW64", "powershell.exe")) ? "PowerShell path resolution before execution" : ((FindProcessStartFinding(context, (ScanFinding finding) => FindingContainsAll(finding, "Target: \"cmd.exe\"", ".ps1")) != null) ? "cmd.exe fallback for PowerShell script launch" : ((context.AnyContextContainsAll("UseShellExecute=true") || context.AnyContextContainsAll("UseShellExecute set")) ? "shell-assisted powershell.exe launch" : "hidden powershell.exe launch"))); ThreatFamilyVariantMatch threatFamilyVariantMatch = new ThreatFamilyVariantMatch(); threatFamilyVariantMatch.MatchedRules = context.BuildMatchedRules("DataFlowAnalysis", "DataInfiltrationRule", "ProcessStartRule"); threatFamilyVariantMatch.Evidence = new <>z__ReadOnlyArray(new ThreatFamilyEvidence[8] { context.CreateDataFlowEvidence("pattern", DataFlowPattern.DownloadAndExecute.ToString(), dataFlowChain), context.CreateDataFlowEvidence("data-flow-chain", dataFlowChain.ChainId, dataFlowChain), context.CreateDataFlowEvidence("source", DescribeDownloadSource(context), dataFlowChain), context.CreateRuleEvidence("rule", "DataInfiltrationRule", scanFinding), context.CreateRuleEvidence("rule", "ProcessStartRule", scanFinding2), Evidence("staging", "%TEMP%/*.ps1"), Evidence("launcher", value), Evidence("execution", "hidden powershell.exe script execution") }); return threatFamilyVariantMatch; } private static ThreatFamilyVariantMatch? MatchWebDownloadTempBatchCmd(ThreatFamilyAnalysisContext context) { DataFlowChain dataFlowChain = context.FindDataFlow(DataFlowPattern.DownloadAndExecute); ScanFinding scanFinding = context.FindFinding("DataInfiltrationRule"); ScanFinding scanFinding2 = FindProcessStartFinding(context, (ScanFinding finding) => FindingContainsAll(finding, "Target: \"cmd.exe\"") && (FindingContainsAll(finding, ".bat") || FindingContainsAll(finding, ".cmd"))); if (dataFlowChain == null || scanFinding == null || scanFinding2 == null || !HasTempBatchStaging(context) || !HasHiddenExecutionContext(context)) { return null; } ThreatFamilyVariantMatch threatFamilyVariantMatch = new ThreatFamilyVariantMatch(); threatFamilyVariantMatch.MatchedRules = context.BuildMatchedRules("DataFlowAnalysis", "DataInfiltrationRule", "ProcessStartRule"); threatFamilyVariantMatch.Evidence = new <>z__ReadOnlyArray(new ThreatFamilyEvidence[8] { context.CreateDataFlowEvidence("pattern", DataFlowPattern.DownloadAndExecute.ToString(), dataFlowChain), context.CreateDataFlowEvidence("data-flow-chain", dataFlowChain.ChainId, dataFlowChain), context.CreateDataFlowEvidence("source", DescribeDownloadSource(context), dataFlowChain), context.CreateRuleEvidence("rule", "DataInfiltrationRule", scanFinding), context.CreateRuleEvidence("rule", "ProcessStartRule", scanFinding2), Evidence("staging", "%TEMP%/*.bat or *.cmd"), Evidence("launcher", "hidden cmd.exe launch"), Evidence("execution", "batch payload execution through cmd.exe") }); return threatFamilyVariantMatch; } private static ThreatFamilyVariantMatch? MatchWebDownloadTempExecutable(ThreatFamilyAnalysisContext context) { DataFlowChain dataFlowChain = context.FindDataFlow(DataFlowPattern.DownloadAndExecute); ScanFinding scanFinding = FindDownloadFinding(context, ".exe"); ScanFinding scanFinding2 = FindProcessStartFinding(context, IsDirectExecutableLaunchFinding); if (dataFlowChain == null || scanFinding == null || scanFinding2 == null || !HasTempExecutableStaging(context) || !HasHiddenExecutionContext(context)) { return null; } ThreatFamilyVariantMatch threatFamilyVariantMatch = new ThreatFamilyVariantMatch(); threatFamilyVariantMatch.MatchedRules = context.BuildMatchedRules("DataFlowAnalysis", "DataInfiltrationRule", "ProcessStartRule"); threatFamilyVariantMatch.Evidence = new <>z__ReadOnlyArray(new ThreatFamilyEvidence[8] { context.CreateDataFlowEvidence("pattern", DataFlowPattern.DownloadAndExecute.ToString(), dataFlowChain), context.CreateDataFlowEvidence("data-flow-chain", dataFlowChain.ChainId, dataFlowChain), context.CreateDataFlowEvidence("source", DescribeDownloadSource(context), dataFlowChain), context.CreateRuleEvidence("rule", "DataInfiltrationRule", scanFinding), context.CreateRuleEvidence("rule", "ProcessStartRule", scanFinding2), Evidence("staging", "temporary executable payload"), Evidence("launcher", "direct Process.Start on staged executable"), Evidence("execution", "direct executable launch from TEMP") }); return threatFamilyVariantMatch; } private static ThreatFamilyVariantMatch? MatchWebDownloadStageExecute(ThreatFamilyAnalysisContext context) { DataFlowChain dataFlowChain = context.FindDataFlow(DataFlowPattern.DownloadAndExecute); ScanFinding scanFinding = context.FindFinding("DataInfiltrationRule"); ScanFinding scanFinding2 = FindPrimaryStagedExecutionFinding(context); if (scanFinding == null) { scanFinding = ((scanFinding2 != null && FindingContainsAll(scanFinding2, "Correlated data flow", "Downloads data from network")) ? scanFinding2 : null); } if (dataFlowChain == null || scanFinding == null || scanFinding2 == null || !HasTempStagingContext(context) || !HasHiddenExecutionContext(context)) { return null; } ThreatFamilyVariantMatch threatFamilyVariantMatch = new ThreatFamilyVariantMatch(); threatFamilyVariantMatch.MatchedRules = context.BuildMatchedRules("DataFlowAnalysis", "DataInfiltrationRule", "ProcessStartRule"); threatFamilyVariantMatch.Evidence = new <>z__ReadOnlyArray(new ThreatFamilyEvidence[7] { context.CreateDataFlowEvidence("pattern", DataFlowPattern.DownloadAndExecute.ToString(), dataFlowChain), context.CreateDataFlowEvidence("data-flow-chain", dataFlowChain.ChainId, dataFlowChain), context.CreateDataFlowEvidence("source", DescribeDownloadSource(context), dataFlowChain), context.CreateRuleEvidence("rule", "DataInfiltrationRule", scanFinding), context.CreateRuleEvidence("rule", "ProcessStartRule", scanFinding2), Evidence("staging", DescribeStagedPayload(context)), Evidence("execution", DescribeExecutionStyle(scanFinding2)) }); return threatFamilyVariantMatch; } private static ScanFinding? FindDownloadFinding(ThreatFamilyAnalysisContext context, string extension) { return context.FindFinding("DataInfiltrationRule", extension); } private static ScanFinding? FindProcessStartFinding(ThreatFamilyAnalysisContext context, Func predicate) { return context.Findings.FirstOrDefault((ScanFinding finding) => string.Equals(finding.RuleId, "ProcessStartRule", StringComparison.Ordinal) && predicate(finding)); } private static ScanFinding? FindPrimaryStagedExecutionFinding(ThreatFamilyAnalysisContext context) { return FindProcessStartFinding(context, (ScanFinding finding) => !FindingContainsAll(finding, "Target: \"where.exe\"") && (FindingContainsAll(finding, "Correlated data flow") || HasTempStagingIndicators(finding)) && HasHiddenExecutionIndicators(finding)); } private static bool HasTempScriptStaging(ThreatFamilyAnalysisContext context, string extension) { return context.AnyContextContainsAll("%TEMP%", extension) || context.AnyContextContainsAll("WorkingDirectory=Temp", extension) || context.AnyContextContainsAll("GetTempPath", extension); } private static bool HasTempBatchStaging(ThreatFamilyAnalysisContext context) { return HasTempScriptStaging(context, ".bat") || HasTempScriptStaging(context, ".cmd"); } private static bool HasTempExecutableStaging(ThreatFamilyAnalysisContext context) { return context.AnyContextContainsAll("WorkingDirectory=Temp") || context.AnyContextContainsAll("%TEMP%", ".exe") || context.AnyContextContainsAll("GetTempPath", ".exe"); } private static bool HasTempStagingContext(ThreatFamilyAnalysisContext context) { return context.AnyContextContainsAll("%TEMP%") || context.AnyContextContainsAll("WorkingDirectory=Temp") || context.AnyContextContainsAll("GetTempPath") || context.AnyFindingContainsAll("Correlated data flow", ".bat") || context.AnyFindingContainsAll("Correlated data flow", ".cmd") || context.AnyFindingContainsAll("Correlated data flow", ".exe"); } private static bool HasHiddenExecutionContext(ThreatFamilyAnalysisContext context) { return context.AnyContextContainsAll("CreateNoWindow") || context.AnyContextContainsAll("WindowStyle=Hidden") || context.AnyContextContainsAll("UseShellExecute=true") || context.AnyContextContainsAll("UseShellExecute set"); } private static bool HasTempStagingIndicators(ScanFinding finding) { return FindingContainsAll(finding, "%TEMP%") || FindingContainsAll(finding, "WorkingDirectory=Temp") || FindingContainsAll(finding, "GetTempPath"); } private static bool HasHiddenExecutionIndicators(ScanFinding finding) { return FindingContainsAll(finding, "CreateNoWindow") || FindingContainsAll(finding, "WindowStyle=Hidden") || FindingContainsAll(finding, "UseShellExecute=true") || FindingContainsAll(finding, "UseShellExecute set"); } private static bool IsDirectExecutableLaunchFinding(ScanFinding finding) { if (!HasHiddenExecutionIndicators(finding)) { return false; } if (!FindingContainsAll(finding, ".exe") || FindingContainsAll(finding, "Target: \"powershell.exe\"") || FindingContainsAll(finding, "Target: \"cmd.exe\"") || FindingContainsAll(finding, "Target: \"where.exe\"")) { return false; } return true; } private static string DescribeDownloadSource(ThreatFamilyAnalysisContext context) { if (context.AnyContextContainsAll("DownloadFileTaskAsync") || context.AnyContextContainsAll("WebClient")) { return "WebClient download"; } if (context.AnyContextContainsAll("GetByteArrayAsync") || context.AnyContextContainsAll("HttpClient")) { return "HttpClient download"; } return "network download"; } private static string DescribeStagedPayload(ThreatFamilyAnalysisContext context) { if (HasTempScriptStaging(context, ".ps1")) { return "%TEMP%/*.ps1"; } if (HasTempBatchStaging(context)) { return "%TEMP%/*.bat or *.cmd"; } if (HasTempExecutableStaging(context)) { return "temporary executable payload"; } return "TEMP staged payload"; } private static string DescribeExecutionStyle(ScanFinding executionFinding) { if (FindingContainsAll(executionFinding, "Target: \"powershell.exe\"")) { return "hidden powershell.exe execution"; } if (FindingContainsAll(executionFinding, "Target: \"cmd.exe\"")) { return "hidden cmd.exe execution"; } return "hidden staged payload execution"; } private static bool FindingContainsAll(ScanFinding finding, params string[] needles) { return needles.All(delegate(string needle) { int result; if (!string.IsNullOrWhiteSpace(needle)) { string description = finding.Description; if (description == null || !description.Contains(needle, StringComparison.OrdinalIgnoreCase)) { string location = finding.Location; if (location == null || !location.Contains(needle, StringComparison.OrdinalIgnoreCase)) { result = ((finding.CodeSnippet?.Contains(needle, StringComparison.OrdinalIgnoreCase) ?? false) ? 1 : 0); goto IL_005d; } } result = 1; } else { result = 0; } goto IL_005d; IL_005d: return (byte)result != 0; }); } } public sealed class ThreatFamilyClassifier { public IReadOnlyList Classify(IEnumerable findings, string? sha256Hash) { return Classify(findings, null, null, sha256Hash); } public IReadOnlyList Classify(IEnumerable findings, IEnumerable? callChains, IEnumerable? dataFlows, string? sha256Hash) { ThreatFamilyAnalysisContext threatFamilyAnalysisContext = new ThreatFamilyAnalysisContext(findings, callChains, dataFlows); List list = new List(); foreach (ThreatFamilyDefinition family in ThreatFamilyCatalog.Families) { if (!string.IsNullOrWhiteSpace(sha256Hash) && family.ExactSampleHashes.Contains(sha256Hash, StringComparer.OrdinalIgnoreCase)) { list.Add(new ThreatFamilyMatch { FamilyId = family.FamilyId, VariantId = "exact-known-sample", DisplayName = family.DisplayName, Summary = family.Summary, MatchKind = ThreatMatchKind.ExactSampleHash, Confidence = 1.0, ExactHashMatch = true, AdvisorySlugs = family.AdvisorySlugs.ToList(), MatchedRules = (from f in threatFamilyAnalysisContext.Findings select f.RuleId into ruleId where !string.IsNullOrWhiteSpace(ruleId) select ruleId).Distinct(StringComparer.Ordinal).Cast().OrderBy((string ruleId) => ruleId, StringComparer.Ordinal) .ToList(), Evidence = new List(2) { new ThreatFamilyEvidence { Kind = "hash", Value = sha256Hash }, new ThreatFamilyEvidence { Kind = "match", Value = "Exact sample hash match", Confidence = 1.0 } } }); continue; } foreach (ThreatFamilyVariantDefinition variant in family.Variants) { ThreatFamilyVariantMatch threatFamilyVariantMatch = variant.Matcher(threatFamilyAnalysisContext); if (threatFamilyVariantMatch == null) { continue; } list.Add(new ThreatFamilyMatch { FamilyId = family.FamilyId, VariantId = variant.VariantId, DisplayName = variant.DisplayName, Summary = variant.Summary, MatchKind = ThreatMatchKind.BehaviorVariant, Confidence = variant.Confidence, ExactHashMatch = false, AdvisorySlugs = family.AdvisorySlugs.ToList(), MatchedRules = threatFamilyVariantMatch.MatchedRules.Distinct(StringComparer.Ordinal).OrderBy((string ruleId) => ruleId, StringComparer.Ordinal).ToList(), Evidence = threatFamilyVariantMatch.Evidence.ToList() }); break; } } return (from match in list orderby match.ExactHashMatch descending, match.Confidence descending select match).ThenBy((ThreatFamilyMatch match) => match.FamilyId, StringComparer.Ordinal).ToList(); } } internal sealed class ThreatFamilyDefinition { public string FamilyId { get; set; } = string.Empty; public string DisplayName { get; set; } = string.Empty; public string Summary { get; set; } = string.Empty; public IReadOnlyList AdvisorySlugs { get; set; } = Array.Empty(); public IReadOnlyList ExactSampleHashes { get; set; } = Array.Empty(); public IReadOnlyList Variants { get; set; } = Array.Empty(); } internal sealed class ThreatFamilyVariantDefinition { public string VariantId { get; set; } = string.Empty; public string DisplayName { get; set; } = string.Empty; public string Summary { get; set; } = string.Empty; public double Confidence { get; set; } public Func Matcher { get; set; } = (ThreatFamilyAnalysisContext _) => (ThreatFamilyVariantMatch?)null; } internal sealed class ThreatFamilyVariantMatch { public IReadOnlyList MatchedRules { get; set; } = Array.Empty(); public IReadOnlyList Evidence { get; set; } = Array.Empty(); } } namespace MLVScan.Services.Helpers { internal static class DllImportInvocationContextExtractor { private const int SearchWindow = 240; private static readonly string[] ShellExecuteInfoFieldOrder = new string[5] { "lpVerb", "lpFile", "lpParameters", "lpDirectory", "nShow" }; public static string? TryBuildContext(MethodDefinition callerMethod, MethodReference calledMethod, Collection instructions, int callInstructionIndex) { if (!TryGetPInvokeEntryPoint(calledMethod, out string entryPoint)) { return null; } if (!entryPoint.StartsWith("ShellExecute", StringComparison.OrdinalIgnoreCase)) { return null; } return BuildShellExecuteContext(callerMethod, instructions, callInstructionIndex); } public static bool IsNativeExecutionPInvoke(MethodReference calledMethod) { if (!TryGetPInvokeEntryPoint(calledMethod, out string entryPoint)) { return false; } return DllImportRule.IsNativeExecutionEntryPoint(entryPoint); } private static string? BuildShellExecuteContext(MethodDefinition callerMethod, Collection instructions, int callInstructionIndex) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); if (TryGetCallArgumentAddressLocalIndex(instructions, callInstructionIndex, out var localIndex)) { ExtractShellExecuteInfoFieldValues(callerMethod, instructions, callInstructionIndex, localIndex, dictionary); } StringBuilder stringBuilder = new StringBuilder(); if (dictionary.Count > 0) { stringBuilder.Append("Invocation context: "); List list = new List(); string[] shellExecuteInfoFieldOrder = ShellExecuteInfoFieldOrder; foreach (string text in shellExecuteInfoFieldOrder) { if (dictionary.TryGetValue(text, out var value)) { list.Add(text + "=" + value); } } stringBuilder.Append(string.Join(", ", list)); } if (TryBuildPreCallBehaviorHint(instructions, callInstructionIndex, out string hint)) { if (stringBuilder.Length > 0) { stringBuilder.Append(". "); } stringBuilder.Append(hint); } if (stringBuilder.Length == 0) { return null; } return stringBuilder.ToString(); } private static void ExtractShellExecuteInfoFieldValues(MethodDefinition callerMethod, Collection instructions, int callInstructionIndex, int structLocalIndex, IDictionary fieldValues) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) int num = Math.Max(0, callInstructionIndex - 240); for (int num2 = callInstructionIndex - 1; num2 >= num; num2--) { Instruction val = instructions[num2]; if (!(val.OpCode != OpCodes.Stfld)) { object operand = val.Operand; FieldReference val2 = (FieldReference)((operand is FieldReference) ? operand : null); if (val2 != null && IsTrackedShellExecuteInfoField(((MemberReference)val2).Name) && !fieldValues.ContainsKey(((MemberReference)val2).Name) && IsFieldStoreForStructLocal(instructions, num2, structLocalIndex)) { string valueDisplay; if (((MemberReference)val2).Name.Equals("lpFile", StringComparison.OrdinalIgnoreCase) && TryReconstructTempScriptLpFilePath(instructions, num2, out string path)) { fieldValues[((MemberReference)val2).Name] = NormalizeDisplayValue(path); } else if (InstructionValueResolver.TryResolveStackValueDisplay(callerMethod, instructions, num2 - 1, out valueDisplay)) { fieldValues[((MemberReference)val2).Name] = NormalizeDisplayValue(valueDisplay); } } } } } private static bool TryBuildPreCallBehaviorHint(Collection instructions, int callInstructionIndex, out string hint) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) hint = string.Empty; int num = Math.Max(0, callInstructionIndex - 240); bool flag = false; bool flag2 = false; bool flag3 = false; bool flag4 = false; for (int i = num; i < callInstructionIndex; i++) { Instruction val = instructions[i]; if (val.OpCode == OpCodes.Call || val.OpCode == OpCodes.Callvirt || val.OpCode == OpCodes.Newobj) { object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 != null) { TypeReference declaringType = ((MemberReference)val2).DeclaringType; string text = ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) ?? string.Empty; string name = ((MemberReference)val2).Name; if (text == "System.Reflection.Assembly" && name == "GetManifestResourceStream") { flag = true; } if ((text.StartsWith("System.IO.File", StringComparison.Ordinal) && (name.Contains("Write", StringComparison.Ordinal) || name.Contains("Create", StringComparison.Ordinal))) || (text == "System.IO.FileStream" && name == ".ctor") || (text == "System.IO.Stream" && name == "CopyTo")) { flag2 = true; } if (text == "System.IO.Path" && name == "GetTempPath") { flag3 = true; } } } if (val.OpCode == OpCodes.Ldstr && val.Operand is string text2 && (text2.Contains(".cmd", StringComparison.OrdinalIgnoreCase) || text2.Contains(".bat", StringComparison.OrdinalIgnoreCase) || text2.Contains(".ps1", StringComparison.OrdinalIgnoreCase))) { flag4 = true; } } if (!flag && !flag2) { return false; } if (flag && flag2) { hint = "Pre-call behavior: embedded resource is materialized and written to disk"; if (flag3 || flag4) { hint += " (temp script dropper pattern)"; } return true; } if (flag) { hint = "Pre-call behavior: embedded resource access observed"; return true; } if (flag2) { hint = "Pre-call behavior: file write observed before native execution"; return true; } return false; } private static bool TryGetCallArgumentAddressLocalIndex(Collection instructions, int callInstructionIndex, out int localIndex) { localIndex = -1; if (callInstructionIndex <= 0) { return false; } if (TryGetAddressedLocalIndex(instructions[callInstructionIndex - 1], out localIndex)) { return true; } for (int num = callInstructionIndex - 1; num >= Math.Max(0, callInstructionIndex - 6); num--) { if (TryGetAddressedLocalIndex(instructions[num], out localIndex)) { return true; } } return false; } private static bool IsFieldStoreForStructLocal(Collection instructions, int fieldStoreIndex, int expectedLocalIndex) { int num = Math.Max(0, fieldStoreIndex - 6); for (int num2 = fieldStoreIndex - 1; num2 >= num; num2--) { if (TryGetAddressedLocalIndex(instructions[num2], out var localIndex)) { return localIndex == expectedLocalIndex; } } return false; } private static bool TryGetAddressedLocalIndex(Instruction instruction, out int localIndex) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) localIndex = -1; if (instruction.OpCode == OpCodes.Ldloca_S) { object operand = instruction.Operand; VariableDefinition val = (VariableDefinition)((operand is VariableDefinition) ? operand : null); if (val != null) { localIndex = ((VariableReference)val).Index; return true; } } if (instruction.OpCode == OpCodes.Ldloca) { object operand2 = instruction.Operand; VariableDefinition val2 = (VariableDefinition)((operand2 is VariableDefinition) ? operand2 : null); if (val2 != null) { localIndex = ((VariableReference)val2).Index; return true; } } return false; } private static bool TryReconstructTempScriptLpFilePath(Collection instructions, int lpFileStoreIndex, out string path) { path = string.Empty; if (lpFileStoreIndex < 2) { return false; } if (!instructions[lpFileStoreIndex - 1].TryGetLocalIndex(out var index)) { return false; } int num = Math.Max(0, lpFileStoreIndex - 240); for (int num2 = lpFileStoreIndex - 2; num2 >= num; num2--) { if (instructions[num2].TryGetStoredLocalIndex(out var index2) && index2 == index) { if (!TryBuildTempScriptPathFromStore(instructions, num2, out path)) { return false; } return true; } } return false; } private static bool TryBuildTempScriptPathFromStore(Collection instructions, int localStoreIndex, out string path) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) path = string.Empty; if (localStoreIndex <= 0) { return false; } if (!IsPathCombineCall(instructions[localStoreIndex - 1])) { return false; } int num = Math.Max(0, localStoreIndex - 240); bool flag = false; bool flag2 = false; string text = null; for (int i = num; i < localStoreIndex; i++) { Instruction val = instructions[i]; if (val.OpCode == OpCodes.Call || val.OpCode == OpCodes.Callvirt) { object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 != null) { TypeReference declaringType = ((MemberReference)val2).DeclaringType; string text2 = ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) ?? string.Empty; string name = ((MemberReference)val2).Name; if (text2 == "System.IO.Path" && name == "GetTempPath") { flag = true; } else if (text2 == "System.Guid" && name == "NewGuid") { flag2 = true; } } } if (text == null && val.OpCode == OpCodes.Ldstr && val.Operand is string literal && TryExtractScriptExtensionFromGuidFormatLiteral(literal, out string extension)) { text = extension; } } if (!flag || !flag2 || text == null) { return false; } path = "%TEMP%/" + text; return true; } private static bool IsPathCombineCall(Instruction instruction) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) if (!(instruction.OpCode != OpCodes.Call) || !(instruction.OpCode != OpCodes.Callvirt)) { object operand = instruction.Operand; MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null); if (val != null) { TypeReference declaringType = ((MemberReference)val).DeclaringType; string text = ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) ?? string.Empty; return text == "System.IO.Path" && (((MemberReference)val).Name == "Combine" || ((MemberReference)val).Name == "Join"); } } return false; } private static bool TryExtractScriptExtensionFromGuidFormatLiteral(string literal, out string extension) { extension = string.Empty; if (string.IsNullOrWhiteSpace(literal) || !literal.Contains("{0}", StringComparison.Ordinal)) { return false; } if (literal.EndsWith(".cmd", StringComparison.OrdinalIgnoreCase)) { extension = ".cmd"; return true; } if (literal.EndsWith(".bat", StringComparison.OrdinalIgnoreCase)) { extension = ".bat"; return true; } if (literal.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) { extension = ".ps1"; return true; } return false; } private static bool IsTrackedShellExecuteInfoField(string fieldName) { string[] shellExecuteInfoFieldOrder = ShellExecuteInfoFieldOrder; foreach (string text in shellExecuteInfoFieldOrder) { if (text.Equals(fieldName, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static bool TryGetPInvokeEntryPoint(MethodReference calledMethod, out string entryPoint) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) entryPoint = string.Empty; try { MethodDefinition val = calledMethod.Resolve(); if (val == null) { return false; } if ((val.Attributes & 0x2000) == 0 || val.PInvokeInfo == null) { return false; } entryPoint = val.PInvokeInfo.EntryPoint ?? ((MemberReference)calledMethod).Name; return true; } catch { return false; } } private static string NormalizeDisplayValue(string value) { if (string.IsNullOrWhiteSpace(value)) { return ""; } string text = value.Replace("\r", " ").Replace("\n", " ").Trim(); if (text.Length > 140) { text = text.Substring(0, 140) + "..."; } if (text.StartsWith("<", StringComparison.Ordinal) && text.EndsWith(">", StringComparison.Ordinal)) { return text; } if (int.TryParse(text, out var _)) { return text; } if (text.StartsWith("\"", StringComparison.Ordinal) && text.EndsWith("\"", StringComparison.Ordinal)) { return text; } return "\"" + text + "\""; } } [EditorBrowsable(EditorBrowsableState.Never)] public static class InstructionExtensions { public static int GetPushCount(this Instruction instruction) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Expected I4, but got Unknown if (instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Callvirt) { object operand = instruction.Operand; MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null); if (val != null) { TypeReference returnType = val.ReturnType; return (!(((returnType != null) ? ((MemberReference)returnType).FullName : null) == "System.Void")) ? 1 : 0; } } if (instruction.OpCode == OpCodes.Newobj) { return 1; } OpCode opCode = instruction.OpCode; StackBehaviour stackBehaviourPush = ((OpCode)(ref opCode)).StackBehaviourPush; if (1 == 0) { } int result = (stackBehaviourPush - 19) switch { 0 => 0, 1 => 1, 3 => 1, 4 => 1, 5 => 1, 6 => 1, 7 => 1, 2 => 2, _ => 0, }; if (1 == 0) { } return result; } public static int GetPopCount(this Instruction instruction) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Expected I4, but got Unknown if (instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Callvirt) { object operand = instruction.Operand; MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null); if (val != null) { int num = val.Parameters.Count; if (val.HasThis) { num++; } return num; } } if (instruction.OpCode == OpCodes.Newobj) { object operand2 = instruction.Operand; MethodReference val2 = (MethodReference)((operand2 is MethodReference) ? operand2 : null); if (val2 != null) { return val2.Parameters.Count; } } OpCode opCode = instruction.OpCode; StackBehaviour stackBehaviourPop = ((OpCode)(ref opCode)).StackBehaviourPop; if (1 == 0) { } int result = (int)stackBehaviourPop switch { 0 => 0, 1 => 1, 3 => 1, 10 => 1, 2 => 2, 4 => 2, 5 => 2, 6 => 2, 8 => 2, 9 => 2, 7 => 3, _ => 0, }; if (1 == 0) { } return result; } public static bool TryResolveInt32Literal(this Instruction instruction, out int value) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected I4, but got Unknown //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) OpCode opCode = instruction.OpCode; Code code = ((OpCode)(ref opCode)).Code; if (1 == 0) { } int num = (code - 21) switch { 0 => -1, 1 => 0, 2 => 1, 3 => 2, 4 => 3, 5 => 4, 6 => 5, 7 => 6, 8 => 7, 9 => 8, _ => int.MinValue, }; if (1 == 0) { } value = num; if (value != int.MinValue) { return true; } if (instruction.OpCode == OpCodes.Ldc_I4 && instruction.Operand is int num2) { value = num2; return true; } if (instruction.OpCode == OpCodes.Ldc_I4_S && instruction.Operand is sbyte b) { value = b; return true; } value = 0; return false; } public static bool TryGetLocalIndex(this Instruction instruction, out int index) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected I4, but got Unknown //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Invalid comparison between Unknown and I4 //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Invalid comparison between Unknown and I4 OpCode opCode = instruction.OpCode; Code code = ((OpCode)(ref opCode)).Code; if (1 == 0) { } int num = (code - 6) switch { 0 => 0, 1 => 1, 2 => 2, 3 => 3, _ => -1, }; if (1 == 0) { } index = num; if (index >= 0) { return true; } opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code == 17) { object operand = instruction.Operand; VariableDefinition val = (VariableDefinition)((operand is VariableDefinition) ? operand : null); if (val != null) { index = ((VariableReference)val).Index; return true; } } opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code == 202) { object operand2 = instruction.Operand; VariableDefinition val2 = (VariableDefinition)((operand2 is VariableDefinition) ? operand2 : null); if (val2 != null) { index = ((VariableReference)val2).Index; return true; } } return false; } public static bool TryGetStoredLocalIndex(this Instruction instruction, out int index) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected I4, but got Unknown //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Invalid comparison between Unknown and I4 //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Invalid comparison between Unknown and I4 OpCode opCode = instruction.OpCode; Code code = ((OpCode)(ref opCode)).Code; if (1 == 0) { } int num = (code - 10) switch { 0 => 0, 1 => 1, 2 => 2, 3 => 3, _ => -1, }; if (1 == 0) { } index = num; if (index >= 0) { return true; } opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code == 19) { object operand = instruction.Operand; VariableDefinition val = (VariableDefinition)((operand is VariableDefinition) ? operand : null); if (val != null) { index = ((VariableReference)val).Index; return true; } } opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code == 204) { object operand2 = instruction.Operand; VariableDefinition val2 = (VariableDefinition)((operand2 is VariableDefinition) ? operand2 : null); if (val2 != null) { index = ((VariableReference)val2).Index; return true; } } return false; } public static bool TryGetArgumentIndex(this Instruction instruction, out int index) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected I4, but got Unknown //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Invalid comparison between Unknown and I4 //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Invalid comparison between Unknown and I4 OpCode opCode = instruction.OpCode; Code code = ((OpCode)(ref opCode)).Code; if (1 == 0) { } int num = (code - 2) switch { 0 => 0, 1 => 1, 2 => 2, 3 => 3, _ => -1, }; if (1 == 0) { } index = num; if (index >= 0) { return true; } opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code == 14) { object operand = instruction.Operand; ParameterDefinition val = (ParameterDefinition)((operand is ParameterDefinition) ? operand : null); if (val != null) { index = ((ParameterReference)val).Index; return true; } } opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code == 199) { object operand2 = instruction.Operand; ParameterDefinition val2 = (ParameterDefinition)((operand2 is ParameterDefinition) ? operand2 : null); if (val2 != null) { index = ((ParameterReference)val2).Index; return true; } } return false; } public static bool IsValueProducer(this Instruction instruction) { return instruction.GetPushCount() > 0; } public static bool IsValueConsumer(this Instruction instruction) { return instruction.GetPopCount() > 0; } public static bool IsMethodCall(this Instruction instruction) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) return instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Callvirt || instruction.OpCode == OpCodes.Newobj; } public static bool IsCallOrCallvirt(this Instruction instruction) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) return instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Callvirt; } public static MethodReference? GetMethodReference(this Instruction instruction) { if (instruction.IsMethodCall()) { object operand = instruction.Operand; MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null); if (val != null) { return val; } } return null; } public static bool IsBranch(this Instruction instruction) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Invalid comparison between Unknown and I4 //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Invalid comparison between Unknown and I4 //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Invalid comparison between Unknown and I4 //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Invalid comparison between Unknown and I4 //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Invalid comparison between Unknown and I4 //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Invalid comparison between Unknown and I4 //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Invalid comparison between Unknown and I4 //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Invalid comparison between Unknown and I4 //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Invalid comparison between Unknown and I4 //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Invalid comparison between Unknown and I4 //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Invalid comparison between Unknown and I4 //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Invalid comparison between Unknown and I4 //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Invalid comparison between Unknown and I4 //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Invalid comparison between Unknown and I4 //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Invalid comparison between Unknown and I4 //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Invalid comparison between Unknown and I4 //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Invalid comparison between Unknown and I4 //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Invalid comparison between Unknown and I4 //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Invalid comparison between Unknown and I4 //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Invalid comparison between Unknown and I4 //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Invalid comparison between Unknown and I4 //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Invalid comparison between Unknown and I4 //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Invalid comparison between Unknown and I4 //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Invalid comparison between Unknown and I4 //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_0202: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_020c: Invalid comparison between Unknown and I4 OpCode opCode = instruction.OpCode; int result; if ((int)((OpCode)(ref opCode)).Code != 55) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 42) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 58) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 45) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 63) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 50) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 60) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 47) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 59) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 46) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 62) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 49) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 61) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 48) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 56) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 43) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 57) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 44) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 64) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 51) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 65) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 52) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 66) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 53) { opCode = instruction.OpCode; if ((int)((OpCode)(ref opCode)).Code != 67) { opCode = instruction.OpCode; result = (((int)((OpCode)(ref opCode)).Code == 54) ? 1 : 0); goto IL_0211; } } } } } } } } } } } } } } } } } } } } } } } } } result = 1; goto IL_0211; IL_0211: return (byte)result != 0; } public static bool IsArgumentLoad(this Instruction instruction) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) return instruction.OpCode == OpCodes.Ldarg_0 || instruction.OpCode == OpCodes.Ldarg_1 || instruction.OpCode == OpCodes.Ldarg_2 || instruction.OpCode == OpCodes.Ldarg_3 || instruction.OpCode == OpCodes.Ldarg_S || instruction.OpCode == OpCodes.Ldarg; } public static bool IsSimpleConstantLoad(this Instruction instruction) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) return instruction.OpCode == OpCodes.Ldstr || instruction.OpCode == OpCodes.Ldc_I4 || instruction.OpCode == OpCodes.Ldc_I4_S || instruction.OpCode == OpCodes.Ldnull; } } [EditorBrowsable(EditorBrowsableState.Never)] public static class InstructionHelper { public static int? ExtractFolderPathArgument(Collection instructions, int currentIndex) { int num = Math.Max(0, currentIndex - 5); for (int num2 = currentIndex - 1; num2 >= num; num2--) { Instruction instruction = instructions[num2]; if (instruction.TryResolveInt32Literal(out var value)) { return value; } } return null; } } internal static class MethodReferenceExtensions { public static string GetMethodKey(this MethodReference method) { return ((MemberReference)method).FullName; } public static string GetDisplayName(this MethodReference method) { TypeReference declaringType = ((MemberReference)method).DeclaringType; return ((declaringType != null) ? ((MemberReference)declaringType).Name : null) + "." + ((MemberReference)method).Name; } public static string GetMethodLocation(this MethodDefinition method) { TypeDefinition declaringType = method.DeclaringType; return ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) + "." + ((MemberReference)method).Name; } } internal static class RuleOverrideChecker { public static bool OverridesRuleMethod(IScanRule rule, string methodName, params Type[] parameterTypes) { MethodInfo method = rule.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, parameterTypes, null); return method != null && method.DeclaringType != typeof(IScanRule); } } [EditorBrowsable(EditorBrowsableState.Never)] public static class ScanFindingExtensions { public static ScanFinding WithRuleMetadata(this ScanFinding finding, IScanRule rule) { if (finding == null || rule == null) { return finding; } finding.RuleId = rule.RuleId; finding.DeveloperGuidance = rule.DeveloperGuidance; return finding; } } [EditorBrowsable(EditorBrowsableState.Never)] public static class TypeCollectionHelper { public static IEnumerable GetAllTypes(ModuleDefinition module) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) List list = new List(); try { Enumerator enumerator = module.Types.GetEnumerator(); try { while (enumerator.MoveNext()) { TypeDefinition current = enumerator.Current; list.Add(current); CollectNestedTypes(current, list); } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } } catch (Exception) { } return list; } private static void CollectNestedTypes(TypeDefinition type, List allTypes) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) try { Enumerator enumerator = type.NestedTypes.GetEnumerator(); try { while (enumerator.MoveNext()) { TypeDefinition current = enumerator.Current; allTypes.Add(current); CollectNestedTypes(current, allTypes); } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } } catch (Exception) { } } } } namespace MLVScan.Services.Diagnostics { internal sealed class ScanTelemetryHub { private ScanProfileSnapshot? _lastSnapshot; public void BeginAssembly(string assemblyId) { _lastSnapshot = null; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public long StartTimestamp() { return 0L; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AddPhaseElapsed(string phaseName, long startTimestamp) { } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void IncrementCounter(string counterName, long delta = 1L) { } public void RecordTypeSample(string typeName, long startTimestamp, int methodCount, int nestedTypeCount, int findingsCount, int pendingReflectionCount) { } public void RecordMethodSample(string methodName, long startTimestamp, int instructionCount, int findingsCount, int localVariableCount, int exceptionHandlerCount, int pendingReflectionCount) { } public void CompleteAssembly(int findingsBeforeFilter, int findingsAfterFilter) { _lastSnapshot = null; } public ScanProfileSnapshot? GetLastSnapshot() { return _lastSnapshot; } } internal sealed class ScanProfileSnapshot { public string AssemblyId { get; set; } = string.Empty; public long TotalElapsedTicks { get; set; } public IReadOnlyList Phases { get; set; } = Array.Empty(); public IReadOnlyDictionary Counters { get; set; } = new Dictionary(StringComparer.Ordinal); public IReadOnlyList SlowTypes { get; set; } = Array.Empty(); public IReadOnlyList SlowMethods { get; set; } = Array.Empty(); } internal sealed class ScanProfilePhaseTiming { public string Name { get; set; } = string.Empty; public long ElapsedTicks { get; set; } public int Count { get; set; } } internal sealed class ScanProfileTypeSample { public string TypeName { get; set; } = string.Empty; public long ElapsedTicks { get; set; } public int MethodCount { get; set; } public int NestedTypeCount { get; set; } public int FindingsCount { get; set; } public int PendingReflectionCount { get; set; } } internal sealed class ScanProfileMethodSample { public string MethodName { get; set; } = string.Empty; public long ElapsedTicks { get; set; } public int InstructionCount { get; set; } public int FindingsCount { get; set; } public int LocalVariableCount { get; set; } public int ExceptionHandlerCount { get; set; } public int PendingReflectionCount { get; set; } } } namespace MLVScan.Services.DataFlow { internal sealed class CrossMethodDataFlowAnalyzer { private readonly DataFlowPatternEvaluator _patternEvaluator; private readonly DataFlowNodeFactory _nodeFactory; private readonly DataFlowAnalyzerConfig _config; private readonly DeepCallChainAnalyzer _deepCallChainAnalyzer; public CrossMethodDataFlowAnalyzer(DataFlowPatternEvaluator patternEvaluator, DataFlowNodeFactory nodeFactory, DataFlowAnalyzerConfig config) { _patternEvaluator = patternEvaluator ?? throw new ArgumentNullException("patternEvaluator"); _nodeFactory = nodeFactory ?? throw new ArgumentNullException("nodeFactory"); _config = config ?? throw new ArgumentNullException("config"); _deepCallChainAnalyzer = new DeepCallChainAnalyzer(patternEvaluator, nodeFactory, config.MaxCallChainDepth); } public IReadOnlyList Analyze(DataFlowAnalysisState state) { if (!_config.EnableCrossMethodAnalysis) { return Array.Empty(); } List list = new List(); foreach (DataFlowMethodFlowInfo value2 in state.MethodFlowInfos.Values) { foreach (DataFlowMethodCallSite outgoingCall in value2.OutgoingCalls) { if (!state.MethodFlowInfos.TryGetValue(outgoingCall.TargetMethodKey, out DataFlowMethodFlowInfo value)) { continue; } DataFlowChain dataFlowChain = TryBuildCrossMethodChain(state, value2, outgoingCall, value); if (dataFlowChain != null) { list.Add(dataFlowChain); } if (_config.EnableReturnValueTracking && outgoingCall.CalledMethodReturnsData && outgoingCall.ReturnValueUsed) { DataFlowChain dataFlowChain2 = TryBuildReturnValueChain(state, value2, outgoingCall, value); if (dataFlowChain2 != null) { list.Add(dataFlowChain2); } } } } if (_config.MaxCallChainDepth > 2) { list.AddRange(_deepCallChainAnalyzer.Analyze(state)); } return list; } private DataFlowChain? TryBuildCrossMethodChain(DataFlowAnalysisState state, DataFlowMethodFlowInfo callerInfo, DataFlowMethodCallSite callSite, DataFlowMethodFlowInfo calleeInfo) { if ((!callerInfo.HasSource && !callerInfo.HasTransform) || !calleeInfo.HasSink) { return null; } List callerOpsPassedIntoCall = GetCallerOpsPassedIntoCall(callerInfo, callSite); if (callerOpsPassedIntoCall.Count == 0) { return null; } List operations = (from operation in callerOpsPassedIntoCall.Concat(calleeInfo.Operations.Where((DataFlowInterestingOperation operation) => operation.NodeType == DataFlowNodeType.Sink)) orderby operation.InstructionIndex select operation).ToList(); DataFlowPattern dataFlowPattern = _patternEvaluator.RecognizePattern(operations); if (dataFlowPattern == DataFlowPattern.Legitimate || dataFlowPattern == DataFlowPattern.Unknown) { return null; } DataFlowChain dataFlowChain = new DataFlowChain("cross:" + callerInfo.MethodKey + "->" + calleeInfo.MethodKey, dataFlowPattern, _patternEvaluator.DetermineSeverity(dataFlowPattern), BuildCrossMethodSummary(dataFlowPattern, callerInfo, calleeInfo), callerInfo.MethodKey) { IsCrossMethod = true, InvolvedMethods = new List { callerInfo.MethodKey, calleeInfo.MethodKey } }; foreach (DataFlowInterestingOperation item in callerOpsPassedIntoCall) { dataFlowChain.AppendNode(_nodeFactory.CreateOperationNode(callerInfo.MethodKey, callerInfo.DisplayName, state.GetInstructionsForMethod(callerInfo.MethodKey), item)); } dataFlowChain.AppendNode(_nodeFactory.CreateBoundaryNode(callerInfo.MethodKey, callerInfo.DisplayName, state.GetInstructionsForMethod(callerInfo.MethodKey), callSite.InstructionIndex, callSite.InstructionOffset, "calls " + calleeInfo.DisplayName, "data passed via parameter", calleeInfo.MethodKey)); foreach (DataFlowInterestingOperation item2 in calleeInfo.Operations.Where((DataFlowInterestingOperation operation) => operation.NodeType == DataFlowNodeType.Sink)) { dataFlowChain.AppendNode(_nodeFactory.CreateOperationNode(calleeInfo.MethodKey, calleeInfo.DisplayName, state.GetInstructionsForMethod(calleeInfo.MethodKey), item2)); } return dataFlowChain; } private DataFlowChain? TryBuildReturnValueChain(DataFlowAnalysisState state, DataFlowMethodFlowInfo callerInfo, DataFlowMethodCallSite callSite, DataFlowMethodFlowInfo calleeInfo) { if ((!calleeInfo.ReturnsData && calleeInfo.ReturnProducingOperations.Count == 0) || !callerInfo.Operations.Any((DataFlowInterestingOperation operation) => operation.NodeType == DataFlowNodeType.Sink && operation.InstructionIndex > callSite.InstructionIndex)) { return null; } List list = (from operation in callerInfo.Operations where operation.NodeType == DataFlowNodeType.Sink && operation.InstructionIndex > callSite.InstructionIndex orderby operation.InstructionIndex select operation).ToList(); List operations = calleeInfo.ReturnProducingOperations.OrderBy((DataFlowInterestingOperation operation) => operation.InstructionIndex).Concat(list).ToList(); DataFlowPattern dataFlowPattern = _patternEvaluator.RecognizePattern(operations); if (dataFlowPattern == DataFlowPattern.Legitimate || dataFlowPattern == DataFlowPattern.Unknown) { return null; } DataFlowChain dataFlowChain = new DataFlowChain("return:" + calleeInfo.MethodKey + "->" + callerInfo.MethodKey, dataFlowPattern, _patternEvaluator.DetermineSeverity(dataFlowPattern), BuildReturnValueSummary(dataFlowPattern, callerInfo, calleeInfo), calleeInfo.MethodKey) { IsCrossMethod = true, InvolvedMethods = new List { calleeInfo.MethodKey, callerInfo.MethodKey } }; foreach (DataFlowInterestingOperation returnProducingOperation in calleeInfo.ReturnProducingOperations) { dataFlowChain.AppendNode(_nodeFactory.CreateOperationNode(calleeInfo.MethodKey, calleeInfo.DisplayName, state.GetInstructionsForMethod(calleeInfo.MethodKey), returnProducingOperation, "returns " + returnProducingOperation.DataDescription)); } dataFlowChain.AppendNode(_nodeFactory.CreateBoundaryNode(callerInfo.MethodKey, callerInfo.DisplayName, state.GetInstructionsForMethod(callerInfo.MethodKey), callSite.InstructionIndex, callSite.InstructionOffset, "receives return from " + calleeInfo.DisplayName, "return value passed to caller", calleeInfo.MethodKey)); foreach (DataFlowInterestingOperation item in list) { dataFlowChain.AppendNode(_nodeFactory.CreateOperationNode(callerInfo.MethodKey, callerInfo.DisplayName, state.GetInstructionsForMethod(callerInfo.MethodKey), item)); } return dataFlowChain; } private static string BuildCrossMethodSummary(DataFlowPattern pattern, DataFlowMethodFlowInfo callerInfo, DataFlowMethodFlowInfo calleeInfo) { if (1 == 0) { } string text = pattern switch { DataFlowPattern.EmbeddedResourceDropAndExecute => "Embedded resource drop-and-execute pattern", DataFlowPattern.DownloadAndExecute => "Download and execute pattern", DataFlowPattern.DataExfiltration => "Data exfiltration pattern", DataFlowPattern.DynamicCodeLoading => "Dynamic code loading pattern", DataFlowPattern.CredentialTheft => "Credential theft pattern", DataFlowPattern.ObfuscatedPersistence => "Obfuscated persistence pattern", _ => "Suspicious data flow", }; if (1 == 0) { } string text2 = text; return "Cross-method " + text2 + ": " + callerInfo.DisplayName + " -> " + calleeInfo.DisplayName; } private static string BuildReturnValueSummary(DataFlowPattern pattern, DataFlowMethodFlowInfo callerInfo, DataFlowMethodFlowInfo calleeInfo) { if (1 == 0) { } string text = pattern switch { DataFlowPattern.EmbeddedResourceDropAndExecute => "Return-value embedded resource execution", DataFlowPattern.DownloadAndExecute => "Return-value download and execute", DataFlowPattern.DataExfiltration => "Return-value exfiltration", DataFlowPattern.DynamicCodeLoading => "Return-value code loading", DataFlowPattern.CredentialTheft => "Return-value credential theft", DataFlowPattern.ObfuscatedPersistence => "Return-value persistence", _ => "Return-value suspicious flow", }; if (1 == 0) { } string text2 = text; return text2 + ": " + calleeInfo.DisplayName + " returns -> " + callerInfo.DisplayName; } private static List GetCallerOpsPassedIntoCall(DataFlowMethodFlowInfo callerInfo, DataFlowMethodCallSite callSite) { if (callSite.ParameterMapping.Count == 0) { return new List(); } HashSet passedLocalIndexes = new HashSet(callSite.ParameterMapping.Values); return (from operation in callerInfo.Operations where (operation.NodeType == DataFlowNodeType.Source || operation.NodeType == DataFlowNodeType.Transform) && operation.LocalVariableIndex.HasValue && operation.InstructionIndex < callSite.InstructionIndex && passedLocalIndexes.Contains(operation.LocalVariableIndex.Value) orderby operation.InstructionIndex select operation).ToList(); } } internal sealed class DataFlowAnalysisState { public Dictionary> MethodDataFlows { get; } = new Dictionary>(StringComparer.Ordinal); public Dictionary MethodFlowInfos { get; } = new Dictionary(StringComparer.Ordinal); public List CrossMethodChains { get; } = new List(); public Dictionary> MethodInstructions { get; } = new Dictionary>(StringComparer.Ordinal); public void Clear() { MethodDataFlows.Clear(); MethodFlowInfos.Clear(); CrossMethodChains.Clear(); MethodInstructions.Clear(); } public void StoreMethodAnalysis(DataFlowMethodAnalysisResult analysis) { MethodInstructions[analysis.MethodKey] = analysis.Instructions; MethodFlowInfos[analysis.MethodKey] = analysis.FlowInfo; MethodDataFlows[analysis.MethodKey] = analysis.Chains; } public Collection GetInstructionsForMethod(string methodKey) { if (MethodInstructions.TryGetValue(methodKey, out Collection value)) { return value; } return new Collection(); } } internal static class DataFlowInstructionHelper { public static int? TryGetTargetLocalVariable(Collection instructions, int callIndex) { if (callIndex + 1 >= instructions.Count) { return null; } int index; return instructions[callIndex + 1].TryGetStoredLocalIndex(out index) ? new int?(index) : ((int?)null); } public static Dictionary TryGetParameterMapping(Collection instructions, int callIndex, MethodReference calledMethod) { Dictionary dictionary = new Dictionary(); int count = calledMethod.Parameters.Count; int num = 0; int num2 = callIndex - 1; while (num2 >= 0 && num < count) { Instruction instruction = instructions[num2]; if (instruction.TryGetLocalIndex(out var index)) { dictionary[count - 1 - num] = index; num++; } else if (instruction.IsArgumentLoad() || instruction.IsSimpleConstantLoad()) { num++; } num2--; } return dictionary; } public static bool IsReturnValueUsed(Collection instructions, int callIndex) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) if (callIndex + 1 >= instructions.Count) { return false; } Instruction val = instructions[callIndex + 1]; if (val.TryGetStoredLocalIndex(out var _)) { return true; } return val.OpCode == OpCodes.Call || val.OpCode == OpCodes.Callvirt || val.OpCode == OpCodes.Stfld || val.OpCode == OpCodes.Stsfld; } } internal sealed class DataFlowMethodAnalyzer { private readonly DataFlowOperationClassifier _operationClassifier; private readonly DataFlowPatternEvaluator _patternEvaluator; private readonly DataFlowNodeFactory _nodeFactory; public DataFlowMethodAnalyzer(DataFlowOperationClassifier operationClassifier, DataFlowPatternEvaluator patternEvaluator, DataFlowNodeFactory nodeFactory) { _operationClassifier = operationClassifier ?? throw new ArgumentNullException("operationClassifier"); _patternEvaluator = patternEvaluator ?? throw new ArgumentNullException("patternEvaluator"); _nodeFactory = nodeFactory ?? throw new ArgumentNullException("nodeFactory"); } public DataFlowMethodAnalysisResult AnalyzeMethod(MethodDefinition method) { Collection instructions = method.Body.Instructions; List list = _operationClassifier.IdentifyInterestingOperations(method, instructions); DataFlowMethodFlowInfo flowInfo = BuildMethodFlowInfo(method, instructions, list); List chains = ((list.Count < 2) ? new List() : BuildDataFlowChains(method, instructions, list)); return new DataFlowMethodAnalysisResult { MethodKey = ((MethodReference)(object)method).GetMethodKey(), Instructions = instructions, FlowInfo = flowInfo, Chains = chains }; } private DataFlowMethodFlowInfo BuildMethodFlowInfo(MethodDefinition method, Collection instructions, List operations) { string text = ((((MemberReference)((MethodReference)method).ReturnType).FullName == "System.Void") ? null : ((MemberReference)((MethodReference)method).ReturnType).FullName); bool returnsData = text != null && operations.Any((DataFlowInterestingOperation operation) => operation.NodeType == DataFlowNodeType.Source || operation.NodeType == DataFlowNodeType.Transform); DataFlowMethodFlowInfo obj = new DataFlowMethodFlowInfo { MethodKey = ((MethodReference)(object)method).GetMethodKey() }; TypeDefinition declaringType = method.DeclaringType; obj.DisplayName = ((declaringType != null) ? ((MemberReference)declaringType).Name : null) + "." + ((MemberReference)method).Name; obj.HasSource = operations.Any((DataFlowInterestingOperation operation) => operation.NodeType == DataFlowNodeType.Source); obj.HasSink = operations.Any((DataFlowInterestingOperation operation) => operation.NodeType == DataFlowNodeType.Sink); obj.HasTransform = operations.Any((DataFlowInterestingOperation operation) => operation.NodeType == DataFlowNodeType.Transform); obj.ReturnsData = returnsData; obj.ReturnTypeName = text; obj.Operations = operations; obj.ReturnProducingOperations = operations.Where((DataFlowInterestingOperation operation) => operation.NodeType == DataFlowNodeType.Source || operation.NodeType == DataFlowNodeType.Transform).ToList(); DataFlowMethodFlowInfo dataFlowMethodFlowInfo = obj; for (int num = 0; num < instructions.Count; num++) { Instruction val = instructions[num]; if (val.IsCallOrCallvirt()) { object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 != null) { dataFlowMethodFlowInfo.OutgoingCalls.Add(new DataFlowMethodCallSite { TargetMethodKey = val2.GetMethodKey(), TargetDisplayName = val2.GetDisplayName(), InstructionOffset = val.Offset, InstructionIndex = num, ParameterMapping = DataFlowInstructionHelper.TryGetParameterMapping(instructions, num, val2), ReturnValueUsed = DataFlowInstructionHelper.IsReturnValueUsed(instructions, num), CalledMethodReturnsData = (((MemberReference)val2.ReturnType).FullName != "System.Void") }); } } } return dataFlowMethodFlowInfo; } private List BuildDataFlowChains(MethodDefinition method, Collection instructions, List operations) { List list = new List(); List> list2 = (from operation in operations where operation.LocalVariableIndex.HasValue group operation by operation.LocalVariableIndex.Value into @group where @group.Count() > 1 select @group).ToList(); foreach (IGrouping item in list2) { List list3 = item.OrderBy((DataFlowInterestingOperation operation) => operation.InstructionIndex).ToList(); bool flag = list3.Any((DataFlowInterestingOperation operation) => operation.NodeType == DataFlowNodeType.Source); bool flag2 = list3.Any((DataFlowInterestingOperation operation) => operation.NodeType == DataFlowNodeType.Transform); bool flag3 = list3.Any((DataFlowInterestingOperation operation) => operation.NodeType == DataFlowNodeType.Sink); if ((flag || flag2) && flag3) { list.Add(BuildChain(method, instructions, list3)); } } list.AddRange(BuildSequentialChains(method, instructions, operations)); DataFlowChain dataFlowChain = BuildDirectDownloadToExecuteChain(method, instructions, operations); if (dataFlowChain != null) { list.Add(dataFlowChain); } return list; } private DataFlowChain? BuildDirectDownloadToExecuteChain(MethodDefinition method, Collection instructions, List operations) { DataFlowInterestingOperation downloadSource = operations.FirstOrDefault((DataFlowInterestingOperation operation) => operation.NodeType == DataFlowNodeType.Source && (((MemberReference)operation.MethodReference).Name.Equals("DownloadFile", StringComparison.OrdinalIgnoreCase) || ((MemberReference)operation.MethodReference).Name.Equals("DownloadFileTaskAsync", StringComparison.OrdinalIgnoreCase))); if (downloadSource == null) { return null; } DataFlowInterestingOperation dataFlowInterestingOperation = operations.FirstOrDefault((DataFlowInterestingOperation operation) => operation.NodeType == DataFlowNodeType.Sink && operation.InstructionIndex == downloadSource.InstructionIndex && ((MemberReference)operation.MethodReference).FullName == ((MemberReference)downloadSource.MethodReference).FullName); DataFlowInterestingOperation dataFlowInterestingOperation2 = operations.FirstOrDefault((DataFlowInterestingOperation operation) => operation.NodeType == DataFlowNodeType.Sink && operation.InstructionIndex > downloadSource.InstructionIndex && operation.Operation.Contains("Process.Start", StringComparison.OrdinalIgnoreCase)); if (dataFlowInterestingOperation == null || dataFlowInterestingOperation2 == null) { return null; } return BuildChain(method, instructions, new List { downloadSource, dataFlowInterestingOperation, dataFlowInterestingOperation2 }); } private List BuildSequentialChains(MethodDefinition method, Collection instructions, List operations) { List list = new List(); for (int i = 0; i < operations.Count - 1; i++) { DataFlowInterestingOperation operation = operations[i]; List list2 = (from candidate in operations.Skip(i + 1) where candidate.InstructionIndex - operation.InstructionIndex <= 250 select candidate).ToList(); if (list2.Count == 0) { continue; } List list3 = new List { operation }; list3.AddRange(list2.Take(5)); if (list3.Any((DataFlowInterestingOperation candidate) => candidate.NodeType == DataFlowNodeType.Sink)) { DataFlowPattern dataFlowPattern = _patternEvaluator.RecognizePattern(list3); if (dataFlowPattern != DataFlowPattern.Legitimate && dataFlowPattern != DataFlowPattern.Unknown) { list.Add(BuildChain(method, instructions, list3)); } } } return list; } private DataFlowChain BuildChain(MethodDefinition method, Collection instructions, List operations) { DataFlowPattern pattern = _patternEvaluator.RecognizePattern(operations); Severity severity = _patternEvaluator.DetermineSeverity(pattern); string methodLocation = method.GetMethodLocation(); string chainId = methodLocation + ":" + string.Join("-", operations.Select((DataFlowInterestingOperation operation) => operation.Instruction.Offset)); DataFlowChain dataFlowChain = new DataFlowChain(chainId, pattern, severity, _patternEvaluator.BuildSummary(pattern, operations.Count), methodLocation); foreach (DataFlowInterestingOperation operation in operations) { dataFlowChain.AppendNode(_nodeFactory.CreateOperationNode(methodLocation, methodLocation, instructions, operation)); } return dataFlowChain; } } internal sealed class DataFlowNodeFactory { private readonly CodeSnippetBuilder _snippetBuilder; public DataFlowNodeFactory(CodeSnippetBuilder snippetBuilder) { _snippetBuilder = snippetBuilder ?? throw new ArgumentNullException("snippetBuilder"); } public DataFlowNode CreateOperationNode(string methodKey, string displayName, Collection instructions, DataFlowInterestingOperation operation, string? descriptionOverride = null) { string codeSnippet = _snippetBuilder.BuildSnippet(instructions, operation.InstructionIndex, 1); return new DataFlowNode($"{displayName}:{operation.Instruction.Offset}", operation.Operation, operation.NodeType, descriptionOverride ?? operation.DataDescription, operation.Instruction.Offset, codeSnippet, methodKey); } public DataFlowNode CreateBoundaryNode(string methodKey, string displayName, Collection instructions, int instructionIndex, int instructionOffset, string operation, string description, string targetMethodKey) { string codeSnippet = _snippetBuilder.BuildSnippet(instructions, instructionIndex, 1); return new DataFlowNode($"{displayName}:{instructionOffset}", operation, DataFlowNodeType.Intermediate, description, instructionOffset, codeSnippet, methodKey) { IsMethodBoundary = true, TargetMethodKey = targetMethodKey }; } } internal sealed class DataFlowOperationClassifier { public List IdentifyInterestingOperations(MethodDefinition method, Collection instructions) { List list = new List(); for (int i = 0; i < instructions.Count; i++) { Instruction val = instructions[i]; if (!val.IsCallOrCallvirt()) { continue; } object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 != null) { (DataFlowNodeType, string, string)? tuple = ClassifyOperation(val2); if (tuple.HasValue) { list.Add(new DataFlowInterestingOperation { Instruction = val, InstructionIndex = i, MethodReference = val2, NodeType = tuple.Value.Item1, Operation = tuple.Value.Item2, DataDescription = tuple.Value.Item3, LocalVariableIndex = DataFlowInstructionHelper.TryGetTargetLocalVariable(instructions, i) }); } TypeReference declaringType = ((MemberReference)val2).DeclaringType; if (IsDirectDownloadToDisk(((declaringType != null) ? ((MemberReference)declaringType).FullName : null) ?? string.Empty, ((MemberReference)val2).Name)) { DataFlowInterestingOperation obj = new DataFlowInterestingOperation { Instruction = val, InstructionIndex = i, MethodReference = val2, NodeType = DataFlowNodeType.Sink }; TypeReference declaringType2 = ((MemberReference)val2).DeclaringType; obj.Operation = ((declaringType2 != null) ? ((MemberReference)declaringType2).Name : null) + "." + ((MemberReference)val2).Name; obj.DataDescription = "Writes downloaded data to file"; list.Add(obj); } } } return list; } private (DataFlowNodeType NodeType, string Operation, string DataDescription)? ClassifyOperation(MethodReference method) { TypeReference declaringType = ((MemberReference)method).DeclaringType; string declaringType2 = ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) ?? string.Empty; string name = ((MemberReference)method).Name; TypeReference declaringType3 = ((MemberReference)method).DeclaringType; string item = ((declaringType3 != null) ? ((MemberReference)declaringType3).Name : null) + "." + name; if (IsNetworkSource(declaringType2, name)) { return (DataFlowNodeType.Source, item, "byte[]/string (network data)"); } if (IsFileSource(declaringType2, name)) { return (DataFlowNodeType.Source, item, "byte[]/string (file data)"); } if (IsRegistrySource(declaringType2, name)) { return (DataFlowNodeType.Source, item, "string (registry data)"); } if (IsResourceSource(declaringType2, name)) { return (DataFlowNodeType.Source, item, "stream/byte[] (embedded resource)"); } if (IsBase64Decode(declaringType2, name)) { return (DataFlowNodeType.Transform, "Convert.FromBase64String", "byte[] (decoded)"); } if (IsEncoding(declaringType2, name)) { return (DataFlowNodeType.Transform, item, "byte[]/string (encoded)"); } if (IsCryptoOperation(declaringType2, name)) { return (DataFlowNodeType.Transform, item, "byte[] (crypto operation)"); } if (IsCompressionOperation(declaringType2, name)) { return (DataFlowNodeType.Transform, item, "byte[]/stream (decompressed)"); } if (IsStreamMaterialization(declaringType2, name)) { return (DataFlowNodeType.Transform, item, "byte[] (materialized from stream)"); } if (IsAssemblyLoad(declaringType2, name)) { return (DataFlowNodeType.Sink, item, "Assembly (dynamic code loaded)"); } if (IsNativeExecutionSink(method)) { return (DataFlowNodeType.Sink, GetNativeExecutionOperationName(method), "Executes native shell/process"); } if (IsProcessStart(declaringType2, name)) { return (DataFlowNodeType.Sink, "Process.Start", "Executes process"); } if (IsFileSink(declaringType2, name) || IsFileStreamSink(declaringType2, name)) { return (DataFlowNodeType.Sink, item, "Writes to file"); } if (IsNetworkSink(declaringType2, name)) { return (DataFlowNodeType.Sink, item, "Sends to network"); } if (IsRegistrySink(declaringType2, name)) { return (DataFlowNodeType.Sink, item, "Writes to registry"); } return null; } private static bool IsNetworkSource(string declaringType, string methodName) { return (declaringType.StartsWith("System.Net", StringComparison.Ordinal) || declaringType.Contains("HttpClient", StringComparison.Ordinal) || declaringType.Contains("WebClient", StringComparison.Ordinal) || declaringType.Contains("UnityWebRequest", StringComparison.Ordinal)) && (methodName.Contains("Get", StringComparison.Ordinal) || methodName.Contains("Download", StringComparison.Ordinal) || methodName.Contains("Read", StringComparison.Ordinal) || methodName.Contains("Receive", StringComparison.Ordinal)); } private static bool IsDirectDownloadToDisk(string declaringType, string methodName) { return (declaringType.StartsWith("System.Net", StringComparison.Ordinal) || declaringType.Contains("WebClient", StringComparison.Ordinal) || declaringType.Contains("UnityWebRequest", StringComparison.Ordinal)) && (methodName.Equals("DownloadFile", StringComparison.OrdinalIgnoreCase) || methodName.Equals("DownloadFileTaskAsync", StringComparison.OrdinalIgnoreCase)); } private static bool IsFileSource(string declaringType, string methodName) { return declaringType.StartsWith("System.IO.File", StringComparison.Ordinal) && (methodName.Contains("Read", StringComparison.Ordinal) || methodName == "ReadAllBytes" || methodName == "ReadAllText"); } private static bool IsRegistrySource(string declaringType, string methodName) { return declaringType.Contains("Microsoft.Win32.Registry", StringComparison.Ordinal) && methodName.Contains("GetValue", StringComparison.Ordinal); } private static bool IsResourceSource(string declaringType, string methodName) { return (declaringType == "System.Reflection.Assembly" && methodName == "GetManifestResourceStream") || (declaringType.Contains("ResourceManager", StringComparison.Ordinal) && (methodName == "GetObject" || methodName == "GetStream")); } private static bool IsBase64Decode(string declaringType, string methodName) { return declaringType == "System.Convert" && methodName == "FromBase64String"; } private static bool IsEncoding(string declaringType, string methodName) { return declaringType.Contains("System.Text.Encoding", StringComparison.Ordinal) || (declaringType == "System.Convert" && methodName == "ToBase64String"); } private static bool IsCryptoOperation(string declaringType, string methodName) { if (declaringType.Contains("System.Security.Cryptography", StringComparison.Ordinal)) { switch (methodName) { case "Create": case "CreateDecryptor": case "CreateEncryptor": case "TransformFinalBlock": case "TransformBlock": goto IL_00e8; } } if ((declaringType == "System.Security.Cryptography.CryptoStream" && methodName == ".ctor") || (declaringType.Contains("RijndaelManaged", StringComparison.Ordinal) && methodName == ".ctor") || (declaringType.Contains("DESCryptoServiceProvider", StringComparison.Ordinal) && methodName == ".ctor") || (declaringType.Contains("TripleDESCryptoServiceProvider", StringComparison.Ordinal) && methodName == ".ctor")) { goto IL_00e8; } int result = ((declaringType.Contains("RC2CryptoServiceProvider", StringComparison.Ordinal) && methodName == ".ctor") ? 1 : 0); goto IL_00e9; IL_00e8: result = 1; goto IL_00e9; IL_00e9: return (byte)result != 0; } private static bool IsCompressionOperation(string declaringType, string methodName) { return (declaringType == "System.IO.Compression.GZipStream" && methodName == ".ctor") || (declaringType == "System.IO.Compression.DeflateStream" && methodName == ".ctor") || (declaringType == "System.IO.Compression.BrotliStream" && methodName == ".ctor") || (declaringType.Contains("System.IO.Compression", StringComparison.Ordinal) && methodName == "CopyTo"); } private static bool IsStreamMaterialization(string declaringType, string methodName) { return (declaringType == "System.IO.MemoryStream" && methodName == "ToArray") || (declaringType == "System.IO.MemoryStream" && methodName == "GetBuffer") || (declaringType == "System.IO.Stream" && methodName == "CopyTo"); } private static bool IsAssemblyLoad(string declaringType, string methodName) { if (declaringType == "System.Reflection.Assembly") { switch (methodName) { case "Load": case "LoadFrom": case "LoadFile": goto IL_0063; } } int result = ((declaringType.Contains("AssemblyLoadContext", StringComparison.Ordinal) && (methodName == "LoadFromStream" || methodName == "LoadFromAssemblyPath")) ? 1 : 0); goto IL_0064; IL_0064: return (byte)result != 0; IL_0063: result = 1; goto IL_0064; } private static bool IsProcessStart(string declaringType, string methodName) { return declaringType.Contains("System.Diagnostics.Process", StringComparison.Ordinal) && methodName == "Start"; } private static bool IsNativeExecutionSink(MethodReference method) { return DllImportInvocationContextExtractor.IsNativeExecutionPInvoke(method); } private static string GetNativeExecutionOperationName(MethodReference method) { try { MethodDefinition val = method.Resolve(); if (val == null || val.PInvokeInfo == null) { return "PInvoke." + ((MemberReference)method).Name; } string text = val.PInvokeInfo.EntryPoint ?? ((MemberReference)method).Name; return "PInvoke." + text; } catch { return "PInvoke." + ((MemberReference)method).Name; } } private static bool IsFileSink(string declaringType, string methodName) { return declaringType.StartsWith("System.IO.File", StringComparison.Ordinal) && (methodName.Contains("Write", StringComparison.Ordinal) || methodName.Contains("Create", StringComparison.Ordinal)); } private static bool IsFileStreamSink(string declaringType, string methodName) { return declaringType == "System.IO.FileStream" && methodName == ".ctor"; } private static bool IsNetworkSink(string declaringType, string methodName) { return (declaringType.StartsWith("System.Net", StringComparison.Ordinal) || declaringType.Contains("HttpClient", StringComparison.Ordinal) || declaringType.Contains("WebClient", StringComparison.Ordinal)) && (methodName.Contains("Post", StringComparison.Ordinal) || methodName.Contains("Send", StringComparison.Ordinal) || methodName.Contains("Upload", StringComparison.Ordinal)); } private static bool IsRegistrySink(string declaringType, string methodName) { return declaringType.Contains("Microsoft.Win32.Registry", StringComparison.Ordinal) && (methodName.Contains("SetValue", StringComparison.Ordinal) || methodName.Contains("CreateSubKey", StringComparison.Ordinal)); } } internal sealed class DataFlowPatternEvaluator { public DataFlowPattern RecognizePattern(IReadOnlyList operations) { if (HasResourceSource(operations) && HasProcessStart(operations) && (HasFileWrite(operations) || HasTransform(operations))) { return DataFlowPattern.EmbeddedResourceDropAndExecute; } if (HasNetworkSource(operations) && HasFileWrite(operations) && HasProcessStart(operations)) { return DataFlowPattern.DownloadAndExecute; } if ((HasFileSource(operations) || HasRegistrySource(operations)) && HasNetworkSink(operations)) { return DataFlowPattern.DataExfiltration; } if ((HasNetworkSource(operations) || HasFileSource(operations)) && HasAssemblyLoad(operations)) { return DataFlowPattern.DynamicCodeLoading; } if (HasFileSource(operations) && HasNetworkSink(operations)) { return DataFlowPattern.CredentialTheft; } if (HasTransform(operations) && HasRegistrySink(operations)) { return DataFlowPattern.ObfuscatedPersistence; } if (HasNetworkSource(operations) && !HasDangerousSink(operations)) { return DataFlowPattern.RemoteConfigLoad; } return DataFlowPattern.Unknown; } public Severity DetermineSeverity(DataFlowPattern pattern) { if (1 == 0) { } Severity result = pattern switch { DataFlowPattern.EmbeddedResourceDropAndExecute => Severity.Critical, DataFlowPattern.DownloadAndExecute => Severity.Critical, DataFlowPattern.DataExfiltration => Severity.Critical, DataFlowPattern.DynamicCodeLoading => Severity.Critical, DataFlowPattern.CredentialTheft => Severity.Critical, DataFlowPattern.ObfuscatedPersistence => Severity.High, DataFlowPattern.RemoteConfigLoad => Severity.Medium, _ => Severity.Low, }; if (1 == 0) { } return result; } public string BuildSummary(DataFlowPattern pattern, int operationCount) { if (1 == 0) { } string text = pattern switch { DataFlowPattern.EmbeddedResourceDropAndExecute => "Suspicious data flow: Extracts embedded resource to disk and executes it via native shell API", DataFlowPattern.DownloadAndExecute => "Suspicious data flow: Downloads data from network, processes it, and executes as a program", DataFlowPattern.DataExfiltration => "Suspicious data flow: Reads sensitive data and sends it over the network", DataFlowPattern.DynamicCodeLoading => "Suspicious data flow: Loads and executes code dynamically at runtime", DataFlowPattern.CredentialTheft => "Suspicious data flow: Accesses files and sends data to network (potential credential theft)", DataFlowPattern.ObfuscatedPersistence => "Suspicious data flow: Encodes data before writing to registry (persistence with obfuscation)", DataFlowPattern.RemoteConfigLoad => "Data flow: Downloads configuration from network", _ => "Suspicious data flow detected", }; if (1 == 0) { } string arg = text; return $"{arg} ({operationCount} operations)"; } public ScanFinding CreateFinding(DataFlowChain chain) { Severity severity = DetermineFindingSeverity(chain); return new ScanFinding(chain.MethodLocation, chain.ToDetailedDescription(), severity, chain.ToCombinedCodeSnippet()) { RuleId = "DataFlowAnalysis", DataFlowChain = chain }; } public bool ShouldEmitFinding(DataFlowPattern pattern) { return pattern == DataFlowPattern.EmbeddedResourceDropAndExecute || pattern == DataFlowPattern.DownloadAndExecute || pattern == DataFlowPattern.DynamicCodeLoading || pattern == DataFlowPattern.ObfuscatedPersistence; } private static Severity DetermineFindingSeverity(DataFlowChain chain) { if (chain.Pattern != DataFlowPattern.EmbeddedResourceDropAndExecute) { return chain.Severity; } return HasEmbeddedDropperMarkers(chain) ? chain.Severity : Severity.Medium; } private static bool HasEmbeddedDropperMarkers(DataFlowChain chain) { List source = (from value in EnumerateChainTexts(chain) where !string.IsNullOrWhiteSpace(value) select value).ToList(); return source.Any((string value) => value.Contains(".cmd", StringComparison.OrdinalIgnoreCase) || value.Contains(".bat", StringComparison.OrdinalIgnoreCase) || value.Contains("%TEMP%", StringComparison.OrdinalIgnoreCase) || value.Contains("ShellExecuteEx", StringComparison.OrdinalIgnoreCase) || value.Contains("PInvoke.ShellExecute", StringComparison.OrdinalIgnoreCase) || value.Contains("PInvoke.CreateProcess", StringComparison.OrdinalIgnoreCase) || value.Contains("PInvoke.WinExec", StringComparison.OrdinalIgnoreCase) || value.Contains("temp script dropper pattern", StringComparison.OrdinalIgnoreCase) || value.Contains("nShow=0", StringComparison.OrdinalIgnoreCase) || value.Contains("WindowStyle=Hidden", StringComparison.OrdinalIgnoreCase) || value.Contains("CreateNoWindow=true", StringComparison.OrdinalIgnoreCase)); } private static IEnumerable EnumerateChainTexts(DataFlowChain chain) { yield return chain.Summary; yield return chain.MethodLocation; foreach (DataFlowNode node in chain.Nodes) { yield return node.Location; yield return node.Operation; yield return node.DataDescription; if (!string.IsNullOrWhiteSpace(node.CodeSnippet)) { yield return node.CodeSnippet; } } } private static bool HasNetworkSource(IEnumerable operations) { return operations.Any((DataFlowInterestingOperation operation) => operation.NodeType == DataFlowNodeType.Source && (operation.Operation.Contains("Http", StringComparison.OrdinalIgnoreCase) || operation.Operation.Contains("Web", StringComparison.OrdinalIgnoreCase) || operation.Operation.Contains("Network", StringComparison.OrdinalIgnoreCase) || operation.Operation.Contains("Socket", StringComparison.OrdinalIgnoreCase) || operation.Operation.Contains("TcpClient", StringComparison.OrdinalIgnoreCase) || operation.Operation.Contains("UdpClient", StringComparison.OrdinalIgnoreCase) || operation.Operation.Contains("Receive", StringComparison.OrdinalIgnoreCase))); } private static bool HasFileSource(IEnumerable operations) { return operations.Any((DataFlowInterestingOperation operation) => operation.NodeType == DataFlowNodeType.Source && operation.Operation.Contains("File", StringComparison.OrdinalIgnoreCase)); } private static bool HasRegistrySource(IEnumerable operations) { return operations.Any((DataFlowInterestingOperation operation) => operation.NodeType == DataFlowNodeType.Source && operation.Operation.Contains("Registry", StringComparison.OrdinalIgnoreCase)); } private static bool HasResourceSource(IEnumerable operations) { return operations.Any((DataFlowInterestingOperation operation) => operation.NodeType == DataFlowNodeType.Source && (operation.Operation.Contains("GetManifestResourceStream", StringComparison.OrdinalIgnoreCase) || operation.DataDescription.Contains("embedded resource", StringComparison.OrdinalIgnoreCase))); } private static bool HasTransform(IEnumerable operations) { return operations.Any((DataFlowInterestingOperation operation) => operation.NodeType == DataFlowNodeType.Transform); } private static bool HasFileWrite(IEnumerable operations) { return operations.Any((DataFlowInterestingOperation operation) => operation.NodeType == DataFlowNodeType.Sink && (((operation.Operation.Contains("Write", StringComparison.OrdinalIgnoreCase) || operation.Operation.Contains("Create", StringComparison.OrdinalIgnoreCase)) && operation.Operation.Contains("File", StringComparison.OrdinalIgnoreCase)) || operation.Operation.Contains("FileStream", StringComparison.OrdinalIgnoreCase) || operation.Operation.Contains("DownloadFile", StringComparison.OrdinalIgnoreCase))); } private static bool HasProcessStart(IEnumerable operations) { return operations.Any((DataFlowInterestingOperation operation) => operation.NodeType == DataFlowNodeType.Sink && (operation.Operation.Contains("Process.Start", StringComparison.OrdinalIgnoreCase) || operation.Operation.Contains("PInvoke.ShellExecute", StringComparison.OrdinalIgnoreCase) || operation.Operation.Contains("PInvoke.CreateProcess", StringComparison.OrdinalIgnoreCase) || operation.Operation.Contains("PInvoke.WinExec", StringComparison.OrdinalIgnoreCase))); } private static bool HasNetworkSink(IEnumerable operations) { return operations.Any((DataFlowInterestingOperation operation) => operation.NodeType == DataFlowNodeType.Sink && (operation.Operation.Contains("Http", StringComparison.OrdinalIgnoreCase) || operation.Operation.Contains("Web", StringComparison.OrdinalIgnoreCase) || operation.Operation.Contains("Network", StringComparison.OrdinalIgnoreCase))); } private static bool HasRegistrySink(IEnumerable operations) { return operations.Any((DataFlowInterestingOperation operation) => operation.NodeType == DataFlowNodeType.Sink && operation.Operation.Contains("Registry", StringComparison.OrdinalIgnoreCase)); } private static bool HasAssemblyLoad(IEnumerable operations) { return operations.Any((DataFlowInterestingOperation operation) => operation.Operation.Contains("Assembly", StringComparison.OrdinalIgnoreCase) && operation.Operation.Contains("Load", StringComparison.OrdinalIgnoreCase)); } private static bool HasDangerousSink(IReadOnlyList operations) { return HasProcessStart(operations) || HasFileWrite(operations) || HasRegistrySink(operations); } } internal sealed class DeepCallChainAnalyzer { private readonly DataFlowPatternEvaluator _patternEvaluator; private readonly DataFlowNodeFactory _nodeFactory; private readonly int _maxCallChainDepth; public DeepCallChainAnalyzer(DataFlowPatternEvaluator patternEvaluator, DataFlowNodeFactory nodeFactory, int maxCallChainDepth) { _patternEvaluator = patternEvaluator ?? throw new ArgumentNullException("patternEvaluator"); _nodeFactory = nodeFactory ?? throw new ArgumentNullException("nodeFactory"); _maxCallChainDepth = maxCallChainDepth; } public IReadOnlyList Analyze(DataFlowAnalysisState state) { List list = new List(); foreach (KeyValuePair methodFlowInfo in state.MethodFlowInfos) { foreach (DataFlowMethodCallSite outgoingCall in methodFlowInfo.Value.OutgoingCalls) { if (state.MethodFlowInfos.TryGetValue(outgoingCall.TargetMethodKey, out DataFlowMethodFlowInfo value)) { DataFlowCallChainNode dataFlowCallChainNode = new DataFlowCallChainNode { MethodInfo = methodFlowInfo.Value, VisitedMethods = new HashSet(StringComparer.Ordinal) { methodFlowInfo.Key, outgoingCall.TargetMethodKey } }; DataFlowCallChainNode dataFlowCallChainNode2 = new DataFlowCallChainNode { MethodInfo = value, IncomingCallSite = outgoingCall, VisitedMethods = dataFlowCallChainNode.VisitedMethods }; dataFlowCallChainNode.ChildNodes.Add(dataFlowCallChainNode2); ExploreCallChain(state, dataFlowCallChainNode2, value, 2); DataFlowChain dataFlowChain = TryBuildDeepCallChain(state, dataFlowCallChainNode, dataFlowCallChainNode2); if (dataFlowChain != null) { list.Add(dataFlowChain); } } } } return list; } private void ExploreCallChain(DataFlowAnalysisState state, DataFlowCallChainNode currentNode, DataFlowMethodFlowInfo currentInfo, int currentDepth) { if (currentDepth >= _maxCallChainDepth) { return; } foreach (DataFlowMethodCallSite outgoingCall in currentInfo.OutgoingCalls) { if (!currentNode.VisitedMethods.Contains(outgoingCall.TargetMethodKey) && state.MethodFlowInfos.TryGetValue(outgoingCall.TargetMethodKey, out DataFlowMethodFlowInfo value)) { DataFlowCallChainNode dataFlowCallChainNode = new DataFlowCallChainNode { MethodInfo = value, IncomingCallSite = outgoingCall, VisitedMethods = new HashSet(currentNode.VisitedMethods, StringComparer.Ordinal) { outgoingCall.TargetMethodKey } }; currentNode.ChildNodes.Add(dataFlowCallChainNode); ExploreCallChain(state, dataFlowCallChainNode, value, currentDepth + 1); } } } private DataFlowChain? TryBuildDeepCallChain(DataFlowAnalysisState state, DataFlowCallChainNode rootNode, DataFlowCallChainNode targetNode) { if (targetNode.IncomingCallSite == null) { return null; } List list = (from dataFlowInterestingOperation in rootNode.MethodInfo.Operations where (dataFlowInterestingOperation.NodeType == DataFlowNodeType.Source || dataFlowInterestingOperation.NodeType == DataFlowNodeType.Transform) && dataFlowInterestingOperation.LocalVariableIndex.HasValue && targetNode.IncomingCallSite.ParameterMapping.Values.Contains(dataFlowInterestingOperation.LocalVariableIndex.Value) orderby dataFlowInterestingOperation.InstructionIndex select dataFlowInterestingOperation).ToList(); if (list.Count == 0) { return null; } List<(DataFlowMethodFlowInfo, DataFlowInterestingOperation)> list2 = new List<(DataFlowMethodFlowInfo, DataFlowInterestingOperation)>(); List<(DataFlowMethodFlowInfo, DataFlowInterestingOperation)> list3 = new List<(DataFlowMethodFlowInfo, DataFlowInterestingOperation)>(); List<(DataFlowMethodFlowInfo, DataFlowInterestingOperation)> list4 = new List<(DataFlowMethodFlowInfo, DataFlowInterestingOperation)>(); List<(DataFlowMethodFlowInfo, DataFlowMethodCallSite)> list5 = new List<(DataFlowMethodFlowInfo, DataFlowMethodCallSite)>(); CollectChainOperations(rootNode, list2, list3, list4, list5); if (list2.Count == 0 || list3.Count == 0) { return null; } List list6 = new List(); list6.AddRange(list); list6.AddRange(list4.Select<(DataFlowMethodFlowInfo, DataFlowInterestingOperation), DataFlowInterestingOperation>(((DataFlowMethodFlowInfo Info, DataFlowInterestingOperation Operation) entry) => entry.Operation)); list6.AddRange(list3.Select<(DataFlowMethodFlowInfo, DataFlowInterestingOperation), DataFlowInterestingOperation>(((DataFlowMethodFlowInfo Info, DataFlowInterestingOperation Operation) entry) => entry.Operation)); DataFlowPattern dataFlowPattern = _patternEvaluator.RecognizePattern(list6); if (dataFlowPattern == DataFlowPattern.Legitimate || dataFlowPattern == DataFlowPattern.Unknown) { return null; } List list7 = rootNode.VisitedMethods.ToList(); DataFlowChain dataFlowChain = new DataFlowChain("deep:" + string.Join("->", list7), dataFlowPattern, _patternEvaluator.DetermineSeverity(dataFlowPattern), BuildDeepChainSummary(dataFlowPattern, list7), rootNode.MethodInfo.MethodKey) { IsCrossMethod = true, InvolvedMethods = list7 }; foreach (var (dataFlowMethodFlowInfo, operation) in list2) { dataFlowChain.AppendNode(_nodeFactory.CreateOperationNode(dataFlowMethodFlowInfo.MethodKey, dataFlowMethodFlowInfo.DisplayName, state.GetInstructionsForMethod(dataFlowMethodFlowInfo.MethodKey), operation)); } foreach (var (dataFlowMethodFlowInfo2, dataFlowMethodCallSite) in list5) { if (state.MethodFlowInfos.TryGetValue(dataFlowMethodCallSite.TargetMethodKey, out DataFlowMethodFlowInfo value)) { dataFlowChain.AppendNode(_nodeFactory.CreateBoundaryNode(dataFlowMethodFlowInfo2.MethodKey, dataFlowMethodFlowInfo2.DisplayName, state.GetInstructionsForMethod(dataFlowMethodFlowInfo2.MethodKey), dataFlowMethodCallSite.InstructionIndex, dataFlowMethodCallSite.InstructionOffset, "calls " + value.DisplayName, "data passed via parameter", value.MethodKey)); } } foreach (var (dataFlowMethodFlowInfo3, operation2) in list3) { dataFlowChain.AppendNode(_nodeFactory.CreateOperationNode(dataFlowMethodFlowInfo3.MethodKey, dataFlowMethodFlowInfo3.DisplayName, state.GetInstructionsForMethod(dataFlowMethodFlowInfo3.MethodKey), operation2)); } return dataFlowChain; } private static void CollectChainOperations(DataFlowCallChainNode node, List<(DataFlowMethodFlowInfo Info, DataFlowInterestingOperation Operation)> sources, List<(DataFlowMethodFlowInfo Info, DataFlowInterestingOperation Operation)> sinks, List<(DataFlowMethodFlowInfo Info, DataFlowInterestingOperation Operation)> transforms, List<(DataFlowMethodFlowInfo Caller, DataFlowMethodCallSite Site)> callSites) { foreach (DataFlowInterestingOperation operation in node.MethodInfo.Operations) { switch (operation.NodeType) { case DataFlowNodeType.Source: sources.Add((node.MethodInfo, operation)); break; case DataFlowNodeType.Sink: sinks.Add((node.MethodInfo, operation)); break; case DataFlowNodeType.Transform: transforms.Add((node.MethodInfo, operation)); break; } } if (node.IncomingCallSite != null) { callSites.Add((node.MethodInfo, node.IncomingCallSite)); } foreach (DataFlowCallChainNode childNode in node.ChildNodes) { CollectChainOperations(childNode, sources, sinks, transforms, callSites); } } private static string BuildDeepChainSummary(DataFlowPattern pattern, IReadOnlyList involvedMethods) { if (1 == 0) { } string text = pattern switch { DataFlowPattern.EmbeddedResourceDropAndExecute => "Multi-method embedded resource drop-and-execute pattern", DataFlowPattern.DownloadAndExecute => "Multi-method download and execute pattern", DataFlowPattern.DataExfiltration => "Multi-method data exfiltration pattern", DataFlowPattern.DynamicCodeLoading => "Multi-method dynamic code loading pattern", DataFlowPattern.CredentialTheft => "Multi-method credential theft pattern", DataFlowPattern.ObfuscatedPersistence => "Multi-method obfuscated persistence pattern", _ => "Multi-method suspicious data flow", }; if (1 == 0) { } string text2 = text; string text3 = string.Join(" -> ", involvedMethods.Select(delegate(string method) { int num = method.LastIndexOf('.'); string result; if (num <= 0) { result = method; } else { int num2 = num + 1; result = method.Substring(num2, method.Length - num2); } return result; })); return text2 + ": " + text3; } } } namespace MLVScan.Services.CrossAssembly { public sealed class CrossAssemblyGraphBuilder { public AssemblyDependencyGraph Build(IEnumerable<(string path, AssemblyDefinition assembly, AssemblyArtifactRole role)> targets) { //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) List<(string, AssemblyDefinition, AssemblyArtifactRole)> list = targets.ToList(); List list2 = new List(list.Count); List list3 = new List(); Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var item6 in list) { string item = item6.Item1; AssemblyDefinition item2 = item6.Item2; AssemblyArtifactRole item3 = item6.Item3; string text = Normalize(item); AssemblyNameDefinition name = item2.Name; string text2 = ((name != null) ? ((AssemblyNameReference)name).Name : null) ?? Path.GetFileNameWithoutExtension(text); list2.Add(new AssemblyGraphNode { Path = text, AssemblyName = text2, Role = item3 }); if (!dictionary.ContainsKey(text2)) { dictionary[text2] = text; } } foreach (var item7 in list) { string item4 = item7.Item1; AssemblyDefinition item5 = item7.Item2; string text3 = Normalize(item4); Enumerator enumerator3 = item5.MainModule.AssemblyReferences.GetEnumerator(); try { while (enumerator3.MoveNext()) { AssemblyNameReference current3 = enumerator3.Current; if (dictionary.TryGetValue(current3.Name, out var value) && !string.Equals(text3, value, StringComparison.OrdinalIgnoreCase)) { list3.Add(new AssemblyGraphEdge { SourcePath = text3, TargetPath = value, EdgeType = AssemblyEdgeType.Reference, Evidence = current3.FullName }); } } } finally { ((IDisposable)enumerator3/*cast due to .constrained prefix*/).Dispose(); } } return new AssemblyDependencyGraph { Nodes = list2, Edges = DeduplicateEdges(list3) }; } private static string Normalize(string path) { return Path.GetFullPath(path); } private static IReadOnlyList DeduplicateEdges(IEnumerable edges) { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); List list = new List(); foreach (AssemblyGraphEdge edge in edges) { string item = $"{edge.SourcePath}|{edge.TargetPath}|{(int)edge.EdgeType}"; if (hashSet.Add(item)) { list.Add(edge); } } return list; } } public sealed class CrossAssemblyRiskPropagator { public IEnumerable BuildCorrelatedFindings(AssemblyDependencyGraph graph, IReadOnlyDictionary> findingsByAssemblyPath, QuarantinePolicy policy = QuarantinePolicy.CallerAndCallee) { if (graph.Nodes.Count == 0 || graph.Edges.Count == 0) { return Enumerable.Empty(); } HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var (path, source) in findingsByAssemblyPath) { if (source.Any((ScanFinding finding) => finding.Severity >= Severity.High)) { hashSet.Add(Path.GetFullPath(path)); } } if (hashSet.Count == 0) { return Enumerable.Empty(); } Dictionary pathCache = new Dictionary(StringComparer.OrdinalIgnoreCase); List list2 = new List(); foreach (string suspiciousPath in hashSet) { List list3 = graph.Edges.Where((AssemblyGraphEdge edge) => string.Equals(NormalizePath(edge.TargetPath), suspiciousPath, StringComparison.OrdinalIgnoreCase)).ToList(); List list4 = list3.Select((AssemblyGraphEdge edge) => edge.SourcePath).Distinct(StringComparer.OrdinalIgnoreCase).ToList(); string location = ((list3.Count > 0) ? list3[0].TargetPath : suspiciousPath); foreach (string item in list4) { list2.Add(new ScanFinding(item, "Cross-assembly correlation: assembly calls into a high-risk sidecar dependency.", Severity.High) { RuleId = "CrossAssemblyDependency" }); } if (policy == QuarantinePolicy.CallerAndCallee || policy == QuarantinePolicy.DependencyCluster) { list2.Add(new ScanFinding(location, "Cross-assembly correlation: high-risk dependency is actively referenced by local assemblies.", Severity.High) { RuleId = "CrossAssemblyDependency" }); } if (policy == QuarantinePolicy.DependencyCluster) { list2.AddRange(BuildClusterFindings(graph, suspiciousPath, NormalizePath)); } } return (from finding in list2 group finding by finding.Location + "|" + finding.Description into @group select @group.First()).ToList(); string NormalizePath(string text2) { if (!pathCache.TryGetValue(text2, out string value)) { value = Path.GetFullPath(text2); pathCache[text2] = value; } return value; } } private static IEnumerable BuildClusterFindings(AssemblyDependencyGraph graph, string seedPath, Func normalizePath) { List list = new List(); HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase) { normalizePath(seedPath) }; Queue queue = new Queue(); queue.Enqueue(normalizePath(seedPath)); while (queue.Count > 0) { string current = queue.Dequeue(); foreach (AssemblyGraphEdge item2 in graph.Edges.Where((AssemblyGraphEdge edge) => string.Equals(normalizePath(edge.SourcePath), current, StringComparison.OrdinalIgnoreCase) || string.Equals(normalizePath(edge.TargetPath), current, StringComparison.OrdinalIgnoreCase))) { string item = (string.Equals(normalizePath(item2.SourcePath), current, StringComparison.OrdinalIgnoreCase) ? normalizePath(item2.TargetPath) : normalizePath(item2.SourcePath)); string location = (string.Equals(normalizePath(item2.SourcePath), current, StringComparison.OrdinalIgnoreCase) ? item2.TargetPath : item2.SourcePath); if (hashSet.Add(item)) { queue.Enqueue(item); list.Add(new ScanFinding(location, "Cross-assembly correlation: assembly belongs to a suspicious dependency cluster.", Severity.Medium) { RuleId = "CrossAssemblyDependency" }); } } } return list; } } } namespace MLVScan.Models { public enum AnalysisCompletenessStatus { Complete, Partial, Incomplete } public class CallChainNode { public string Location { get; set; } public string? CodeSnippet { get; set; } public string Description { get; set; } public CallChainNodeType NodeType { get; set; } public CallChainNode(string location, string description, CallChainNodeType nodeType, string? codeSnippet = null) { Location = location; Description = description; NodeType = nodeType; CodeSnippet = codeSnippet; } public override string ToString() { CallChainNodeType nodeType = NodeType; if (1 == 0) { } string text = nodeType switch { CallChainNodeType.EntryPoint => "[ENTRY]", CallChainNodeType.IntermediateCall => "[CALL]", CallChainNodeType.SuspiciousDeclaration => "[DECL]", _ => "[???]", }; if (1 == 0) { } string text2 = text; return text2 + " " + Location + ": " + Description; } } public enum CallChainNodeType { EntryPoint, IntermediateCall, SuspiciousDeclaration } public class CallChain { public string ChainId { get; set; } public string RuleId { get; set; } public List Nodes { get; set; } = new List(); public Severity Severity { get; set; } public string Summary { get; set; } public CallChain(string chainId, string ruleId, Severity severity, string summary) { ChainId = chainId; RuleId = ruleId; Severity = severity; Summary = summary; } public void PrependNode(CallChainNode node) { Nodes.Insert(0, node); } public void AppendNode(CallChainNode node) { Nodes.Add(node); } public string ToDetailedDescription() { if (Nodes.Count == 0) { return Summary; } List list = new List { Summary, "", "Call chain:" }; for (int i = 0; i < Nodes.Count; i++) { CallChainNode arg = Nodes[i]; string arg2 = new string(' ', i * 2); string arg3 = ((i > 0) ? "-> " : ""); list.Add($"{arg2}{arg3}{arg}"); } return string.Join("\n", list); } public string? ToCombinedCodeSnippet() { List list = (from n in Nodes where !string.IsNullOrEmpty(n.CodeSnippet) select "// " + n.Location + "\n" + n.CodeSnippet).ToList(); return (list.Count > 0) ? string.Join("\n\n", list) : null; } } public class DataFlowNode { public string Location { get; set; } public string Operation { get; set; } public DataFlowNodeType NodeType { get; set; } public string DataDescription { get; set; } public int InstructionOffset { get; set; } public string? CodeSnippet { get; set; } public string? MethodKey { get; set; } public bool IsMethodBoundary { get; set; } public string? TargetMethodKey { get; set; } public DataFlowNode(string location, string operation, DataFlowNodeType nodeType, string dataDescription, int instructionOffset, string? codeSnippet = null, string? methodKey = null) { Location = location; Operation = operation; NodeType = nodeType; DataDescription = dataDescription; InstructionOffset = instructionOffset; CodeSnippet = codeSnippet; MethodKey = methodKey; } public override string ToString() { DataFlowNodeType nodeType = NodeType; if (1 == 0) { } string text = nodeType switch { DataFlowNodeType.Source => "[SOURCE]", DataFlowNodeType.Transform => "[TRANSFORM]", DataFlowNodeType.Sink => "[SINK]", DataFlowNodeType.Intermediate => "[PASS]", _ => "[???]", }; if (1 == 0) { } string text2 = text; string text3 = ((IsMethodBoundary && TargetMethodKey != null) ? (" → calls " + TargetMethodKey) : ""); return text2 + " " + Operation + " → " + DataDescription + text3; } } public enum DataFlowNodeType { Source, Transform, Sink, Intermediate } public class DataFlowChain { public string ChainId { get; set; } public string? SourceVariable { get; set; } public List Nodes { get; set; } = new List(); public DataFlowPattern Pattern { get; set; } public Severity Severity { get; set; } public string Summary { get; set; } public string MethodLocation { get; set; } public bool IsSuspicious => Pattern != DataFlowPattern.Legitimate && Pattern != DataFlowPattern.Unknown; public bool IsCrossMethod { get; set; } public List InvolvedMethods { get; set; } = new List(); public int CallDepth => (InvolvedMethods.Count <= 0) ? 1 : InvolvedMethods.Count; public DataFlowChain(string chainId, DataFlowPattern pattern, Severity severity, string summary, string methodLocation) { ChainId = chainId; Pattern = pattern; Severity = severity; Summary = summary; MethodLocation = methodLocation; } public void AppendNode(DataFlowNode node) { Nodes.Add(node); } public void PrependNode(DataFlowNode node) { Nodes.Insert(0, node); } public string ToDetailedDescription() { if (Nodes.Count == 0) { return Summary; } List list = new List { Summary, "", "Data Flow Chain:" }; for (int i = 0; i < Nodes.Count; i++) { DataFlowNode dataFlowNode = Nodes[i]; string arg = ((i > 0) ? " → " : " "); list.Add($"{arg}{dataFlowNode}"); list.Add(" Location: " + dataFlowNode.Location); } return string.Join("\n", list); } public string? ToCombinedCodeSnippet() { List list = (from n in Nodes where !string.IsNullOrEmpty(n.CodeSnippet) select "// " + n.Location + " - " + n.Operation + "\n" + n.CodeSnippet).ToList(); return (list.Count > 0) ? string.Join("\n\n", list) : null; } public DataFlowNode? GetSource() { return Nodes.FirstOrDefault((DataFlowNode n) => n.NodeType == DataFlowNodeType.Source); } public IEnumerable GetSinks() { return Nodes.Where((DataFlowNode n) => n.NodeType == DataFlowNodeType.Sink); } public IEnumerable GetTransforms() { return Nodes.Where((DataFlowNode n) => n.NodeType == DataFlowNodeType.Transform); } } public enum DataFlowPattern { Legitimate, DownloadAndExecute, DataExfiltration, DynamicCodeLoading, CredentialTheft, RemoteConfigLoad, ObfuscatedPersistence, EmbeddedResourceDropAndExecute, Unknown } public class DeveloperGuidance : IDeveloperGuidance { public string Remediation { get; } public string? DocumentationUrl { get; } public string[]? AlternativeApis { get; } public bool IsRemediable { get; } public DeveloperGuidance(string remediation, string? documentationUrl = null, string[]? alternativeApis = null, bool isRemediable = true) { Remediation = remediation ?? throw new ArgumentNullException("remediation"); DocumentationUrl = documentationUrl; AlternativeApis = alternativeApis; IsRemediable = isRemediable; } } public class MethodSignals { private HashSet _triggeredRuleIds = new HashSet(); public bool HasEncodedStrings { get; set; } public bool HasSuspiciousReflection { get; set; } public bool UsesSensitiveFolder { get; set; } public bool HasProcessLikeCall { get; set; } public bool HasBase64 { get; set; } public bool HasNetworkCall { get; set; } public bool HasFileWrite { get; set; } public bool HasSuspiciousLocalVariables { get; set; } public bool HasSuspiciousExceptionHandling { get; set; } public bool HasPathManipulation { get; set; } public bool HasEnvironmentVariableModification { get; set; } public int SignalCount { get { int num = 0; if (HasEncodedStrings) { num++; } if (HasSuspiciousReflection) { num++; } if (UsesSensitiveFolder) { num++; } if (HasProcessLikeCall) { num++; } if (HasBase64) { num++; } if (HasNetworkCall) { num++; } if (HasFileWrite) { num++; } if (HasSuspiciousLocalVariables) { num++; } if (HasSuspiciousExceptionHandling) { num++; } return num; } } public void MarkRuleTriggered(string ruleId) { if (!string.IsNullOrEmpty(ruleId)) { _triggeredRuleIds.Add(ruleId); } } public bool HasTriggeredRuleOtherThan(string ruleId) { if (string.IsNullOrEmpty(ruleId)) { return _triggeredRuleIds.Count > 0; } return _triggeredRuleIds.Count > 0 && !_triggeredRuleIds.All((string id) => id == ruleId); } public bool HasAnyTriggeredRule() { return _triggeredRuleIds.Count > 0; } public IEnumerable GetTriggeredRuleIds() { return _triggeredRuleIds.ToList(); } public bool IsCriticalCombination() { if (HasSuspiciousReflection && HasEncodedStrings) { return true; } if (HasSuspiciousReflection && UsesSensitiveFolder) { return true; } if (HasEncodedStrings && HasProcessLikeCall) { return true; } if (UsesSensitiveFolder && HasFileWrite && HasProcessLikeCall) { return true; } if (HasNetworkCall && UsesSensitiveFolder && HasFileWrite) { return true; } return false; } public bool IsHighRiskCombination() { if (UsesSensitiveFolder && HasNetworkCall) { return true; } if (UsesSensitiveFolder && HasProcessLikeCall) { return true; } if (HasBase64 && HasProcessLikeCall) { return true; } if (HasEncodedStrings && (HasProcessLikeCall || HasNetworkCall)) { return true; } return false; } public string GetCombinationDescription() { List list = new List(); if (HasEncodedStrings) { list.Add("encoded strings"); } if (HasSuspiciousReflection) { list.Add("suspicious reflection"); } if (UsesSensitiveFolder) { list.Add("sensitive folder access"); } if (HasProcessLikeCall) { list.Add("process execution"); } if (HasBase64) { list.Add("Base64 decoding"); } if (HasNetworkCall) { list.Add("network call"); } if (HasFileWrite) { list.Add("file write"); } if (HasSuspiciousLocalVariables) { list.Add("suspicious variable types"); } if (HasSuspiciousExceptionHandling) { list.Add("exception handler patterns"); } return string.Join(" + ", list); } } public class ScanConfig { public bool EnableMultiSignalDetection { get; set; } = true; public bool AnalyzeExceptionHandlers { get; set; } = true; public bool AnalyzeLocalVariables { get; set; } = true; public bool AnalyzePropertyAccessors { get; set; } = true; public bool DetectAssemblyMetadata { get; set; } = true; public bool EnableCrossMethodAnalysis { get; set; } = true; public int MaxCallChainDepth { get; set; } = 5; public bool EnableReturnValueTracking { get; set; } = true; public bool EnableRecursiveResourceScanning { get; set; } = true; public int MaxRecursiveResourceSizeMB { get; set; } = 10; public int MinimumEncodedStringLength { get; set; } = 10; public bool DeveloperMode { get; set; } = false; } public class ScanFinding(string location, string description, Severity severity = Severity.Low, string? codeSnippet = null) { public string Location { get; set; } = location; public string Description { get; set; } = description; public Severity Severity { get; set; } = severity; public string? CodeSnippet { get; set; } = codeSnippet; public string? RuleId { get; set; } public IDeveloperGuidance? DeveloperGuidance { get; set; } public CallChain? CallChain { get; set; } public bool HasCallChain => CallChain != null && CallChain.Nodes.Count > 0; public DataFlowChain? DataFlowChain { get; set; } public bool HasDataFlow => DataFlowChain != null && DataFlowChain.Nodes.Count > 0; public bool BypassCompanionCheck { get; set; } = false; public int? RiskScore { get; set; } public override string ToString() { string text = $"[{Severity}] {Description} at {Location}"; if (!string.IsNullOrEmpty(CodeSnippet)) { text = text + "\n Snippet: " + CodeSnippet; } return text; } } public sealed class ScanProgress { public string Phase { get; } public int CompletedUnits { get; } public int TotalUnits { get; } public int Percentage => (int)Math.Round((double)CompletedUnits / (double)TotalUnits * 100.0); public string? CurrentItem { get; } public ScanProgress(string phase, int completedUnits, int totalUnits, string? currentItem = null) { Phase = (string.IsNullOrWhiteSpace(phase) ? "Scanning" : phase); TotalUnits = Math.Max(1, totalUnits); CompletedUnits = Math.Min(Math.Max(0, completedUnits), TotalUnits); CurrentItem = currentItem; } } public enum Severity { Low = 1, Medium, High, Critical } } namespace MLVScan.Models.ThreatIntel { public sealed class AnalysisCompletenessReason { public string ReasonId { get; set; } = string.Empty; public string Summary { get; set; } = string.Empty; public string? Phase { get; set; } public string? RuleId { get; set; } public string? Location { get; set; } } public sealed class AnalysisCompletenessResult { public AnalysisCompletenessStatus Status { get; set; } = AnalysisCompletenessStatus.Complete; public bool IsComplete { get; set; } = true; public bool ReviewRecommended { get; set; } public List RelatedFindings { get; set; } = new List(); public List Reasons { get; set; } = new List(); } public enum ThreatDispositionClassification { Clean, ManualReviewRequired, Suspicious, KnownThreat } public sealed class ThreatDispositionResult { public ThreatDispositionClassification Classification { get; set; } public string Headline { get; set; } = string.Empty; public string Summary { get; set; } = string.Empty; public bool BlockingRecommended { get; set; } public string? PrimaryThreatFamilyId { get; set; } public ThreatFamilyMatch? PrimaryThreatFamily { get; set; } public List RelatedFindings { get; set; } = new List(); } public sealed class ThreatFamilyEvidence { public string Kind { get; set; } = string.Empty; public string Value { get; set; } = string.Empty; public string? RuleId { get; set; } public string? Location { get; set; } public string? CallChainId { get; set; } public string? DataFlowChainId { get; set; } public string? Pattern { get; set; } public string? MethodLocation { get; set; } public double? Confidence { get; set; } } public sealed class ThreatFamilyMatch { public string FamilyId { get; set; } = string.Empty; public string VariantId { get; set; } = string.Empty; public string DisplayName { get; set; } = string.Empty; public string Summary { get; set; } = string.Empty; public ThreatMatchKind MatchKind { get; set; } public double Confidence { get; set; } public bool ExactHashMatch { get; set; } public List MatchedRules { get; set; } = new List(); public List AdvisorySlugs { get; set; } = new List(); public List Evidence { get; set; } = new List(); } public enum ThreatMatchKind { ExactSampleHash, BehaviorVariant } } namespace MLVScan.Models.Rules { public class AssemblyDynamicLoadRule : IScanRule { private enum LoadOverload { LoadBytes, LoadBytesWithPdb, LoadString, LoadAssemblyName, LoadFrom, LoadFile, ALCLoadFromStream, ALCLoadFromStreamPdb, ALCLoadFromPath, Unknown } private class ProvenanceResult { public int Score { get; set; } public bool HasNetworkSource { get; set; } public bool HasBase64 { get; set; } public bool HasCrypto { get; set; } public bool HasCompression { get; set; } public bool HasResourceSource { get; set; } public bool HasTempPath { get; set; } public bool HasSensitivePath { get; set; } public bool HasWriteThenLoad { get; set; } public string? ResourceName { get; set; } public string GetSummary() { List list = new List(); if (HasNetworkSource) { list.Add("network"); } if (HasBase64) { list.Add("base64"); } if (HasCrypto) { list.Add("crypto"); } if (HasCompression) { list.Add("compression"); } if (HasResourceSource) { list.Add("resource"); } if (HasTempPath) { list.Add("temp-path"); } if (HasSensitivePath) { list.Add("sensitive-path"); } if (HasWriteThenLoad) { list.Add("write-then-load"); } return (list.Count > 0) ? string.Join(" -> ", list) : "unknown"; } } private class PendingLoadFinding { public ScanFinding Finding { get; set; } = null; public string? ResourceName { get; set; } public int InstructionIndex { get; set; } public LoadOverload Overload { get; set; } public int TotalScore { get; set; } } private readonly List _pendingFindings = new List(); private static readonly HashSet SafeAssemblyPrefixes = new HashSet(StringComparer.OrdinalIgnoreCase) { "Il2Cpp", "Il2CppInterop", "0Harmony", "Harmony", "HarmonyLib", "Newtonsoft.Json", "UnityEngine", "Assembly-CSharp", "MelonLoader", "BepInEx", "MonoMod", "Mono.Cecil", "System", "Microsoft", "mscorlib", "netstandard", "NuGet", "Ionic.Zip", "DotNetZip", "LitJson", "YamlDotNet", "Steamworks", "Facepunch", "Sirenix", "UniTask" }; private static readonly Regex SimpleAssemblyNamePattern = new Regex("^[A-Za-z][A-Za-z0-9._\\-]*$", RegexOptions.Compiled); public string Description => "Detected dynamic assembly loading with risk indicators."; public Severity Severity => Severity.High; public string RuleId => "AssemblyDynamicLoadRule"; public bool RequiresCompanionFinding => true; public IDeveloperGuidance? DeveloperGuidance => new DeveloperGuidance("Avoid runtime assembly loading when possible. If necessary, detect the runtime (IL2CPP vs Mono) using your framework's utilities. Ship dependencies as separate assemblies in the appropriate framework directory (e.g., Mods/ folder for MelonLoader, plugin folder for BepInEx). Reference assemblies at compile time instead of loading dynamically.", null, new string[3] { "MelonUtils.IsGameIl2Cpp() (MelonLoader)", "MelonMod.MelonAssembly (MelonLoader)", "IL2CPPUtils.IsGameIl2Cpp() (BepInEx 6.x)" }); public bool IsSuspicious(MethodReference method) { if (((method != null) ? ((MemberReference)method).DeclaringType : null) == null) { return false; } string fullName = ((MemberReference)((MemberReference)method).DeclaringType).FullName; string name = ((MemberReference)method).Name; return IsAssemblyLoadMethod(fullName, name); } public IEnumerable AnalyzeContextualPattern(MethodReference method, Collection instructions, int instructionIndex, MethodSignals methodSignals) { if (((method != null) ? ((MemberReference)method).DeclaringType : null) == null) { yield break; } string typeName = ((MemberReference)((MemberReference)method).DeclaringType).FullName; string methodName = ((MemberReference)method).Name; if (!IsAssemblyLoadMethod(typeName, methodName)) { yield break; } LoadOverload overload = ClassifyOverload(method); int baseScore = GetBaseScore(overload); if (overload == LoadOverload.LoadString || overload == LoadOverload.LoadAssemblyName) { string argName = ExtractStringArgument(instructions, instructionIndex); if (argName != null && IsSafeAssemblyName(argName)) { yield break; } } ProvenanceResult provenance = AnalyzeProvenance(instructions, instructionIndex); int provenanceScore = provenance.Score; int postLoadScore = AnalyzePostLoadBehavior(instructions, instructionIndex); int evasionScore = 0; int resolveScore = 0; int correlationScore = ComputeCorrelationScore(methodSignals); if (!ShouldReportDirectLoad(overload, provenance, postLoadScore, correlationScore)) { yield break; } int totalScore = baseScore + provenanceScore + postLoadScore + evasionScore + resolveScore + correlationScore; Severity? severity = MapScoreToSeverity(totalScore); if (severity.HasValue) { string desc = BuildDescription(overload, totalScore, provenance, severity.Value); StringBuilder snippetBuilder = new StringBuilder(); int contextLines = 2; for (int j = Math.Max(0, instructionIndex - contextLines); j < Math.Min(instructions.Count, instructionIndex + contextLines + 1); j++) { snippetBuilder.Append((j == instructionIndex) ? ">>> " : " "); snippetBuilder.AppendLine(((object)instructions[j]).ToString()); } TypeReference declaringType = ((MemberReference)method).DeclaringType; ScanFinding finding = new ScanFinding(string.Format("{0}.{1}:{2}", ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) ?? "Unknown", ((MemberReference)method).Name, instructions[instructionIndex].Offset), desc, severity.Value, snippetBuilder.ToString().TrimEnd()) { RiskScore = totalScore, BypassCompanionCheck = (totalScore >= 75) }; if (provenance.HasResourceSource) { _pendingFindings.Add(new PendingLoadFinding { Finding = finding, ResourceName = provenance.ResourceName, InstructionIndex = instructionIndex, Overload = overload, TotalScore = totalScore }); } yield return finding; } } public IEnumerable AnalyzeInstructions(MethodDefinition method, Collection instructions, MethodSignals methodSignals) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) List list = new List(); for (int i = 0; i < instructions.Count; i++) { Instruction val = instructions[i]; if (!(val.OpCode == OpCodes.Call) && !(val.OpCode == OpCodes.Callvirt)) { continue; } object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 == null || ((MemberReference)val2).DeclaringType == null) { continue; } string fullName = ((MemberReference)((MemberReference)val2).DeclaringType).FullName; string name = ((MemberReference)val2).Name; if ((!(fullName == "System.AppDomain") || !(name == "add_AssemblyResolve")) && (!fullName.Contains("AssemblyLoadContext") || !(name == "add_Resolving")) && (!fullName.Contains("AssemblyLoadContext") || !(name == "add_ResolvingUnmanagedDll"))) { continue; } MethodDefinition val3 = FindDelegateTarget(instructions, i); int num = 15; if (val3 != null) { if (IsLookupOnlyResolveHandler(val3)) { continue; } num += AnalyzeResolveHandler(val3); } Severity severity = ((num >= 50) ? Severity.High : ((num < 25) ? Severity.Low : Severity.Medium)); StringBuilder stringBuilder = new StringBuilder(); for (int j = Math.Max(0, i - 2); j < Math.Min(instructions.Count, i + 3); j++) { stringBuilder.Append((j == i) ? ">>> " : " "); stringBuilder.AppendLine(((object)instructions[j]).ToString()); } object obj; if (val3 == null) { obj = ""; } else { TypeDefinition declaringType = val3.DeclaringType; obj = " Handler: " + ((declaringType != null) ? ((MemberReference)declaringType).Name : null) + "." + ((MemberReference)val3).Name; } string arg = (string)obj; TypeDefinition declaringType2 = method.DeclaringType; list.Add(new ScanFinding($"{((declaringType2 != null) ? ((MemberReference)declaringType2).FullName : null)}.{((MemberReference)method).Name}:{val.Offset}", $"AssemblyResolve/Resolving event subscription detected (score {num}).{arg}", severity, stringBuilder.ToString().TrimEnd()) { RiskScore = num, BypassCompanionCheck = (num >= 50) }); } list.AddRange(DetectReflectiveLoadInvocation(method, instructions, methodSignals)); return list; } public bool ShouldSuppressFinding(MethodReference method, Collection instructions, int instructionIndex, MethodSignals methodSignals, MethodSignals? typeSignals = null) { if (((method != null) ? ((MemberReference)method).DeclaringType : null) == null) { return false; } LoadOverload loadOverload = ClassifyOverload(method); if (loadOverload != LoadOverload.LoadString && loadOverload != LoadOverload.LoadAssemblyName && !IsPathBasedDirectLoad(loadOverload)) { return false; } string text = ExtractStringArgument(instructions, instructionIndex); if ((loadOverload == LoadOverload.LoadString || loadOverload == LoadOverload.LoadAssemblyName) && text != null && IsSafeAssemblyName(text)) { return true; } ProvenanceResult provenance = AnalyzeProvenance(instructions, instructionIndex); int postLoadScore = AnalyzePostLoadBehavior(instructions, instructionIndex); int num = ComputeCorrelationScore(methodSignals); if (typeSignals != null) { num = Math.Max(num, ComputeCorrelationScore(typeSignals)); } return !ShouldReportDirectLoad(loadOverload, provenance, postLoadScore, num); } public IEnumerable PostAnalysisRefine(ModuleDefinition module, IEnumerable existingFindings) { List list = new List(); foreach (PendingLoadFinding pendingFinding in _pendingFindings) { if (string.IsNullOrEmpty(pendingFinding.ResourceName)) { continue; } List list2 = ScanEmbeddedResource(module, pendingFinding.ResourceName); if (list2.Count > 0) { int num = (list2.Any((ScanFinding f) => f.Severity >= Severity.Critical) ? 50 : (list2.Any((ScanFinding f) => f.Severity >= Severity.High) ? 30 : (list2.Any((ScanFinding f) => f.Severity >= Severity.Medium) ? 15 : 5))); int num2 = pendingFinding.TotalScore + num; Severity severity = MapScoreToSeverity(num2) ?? Severity.High; string text = string.Join("; ", from f in list2.Take(3) select $"[{f.Severity}] {f.Description}"); if (list2.Count > 3) { text += $" (+{list2.Count - 3} more)"; } list.Add(new ScanFinding(pendingFinding.Finding.Location, $"Embedded assembly '{pendingFinding.ResourceName}' loaded from resources contains suspicious code: {text} (combined score {num2})", severity, pendingFinding.Finding.CodeSnippet) { RiskScore = num2, BypassCompanionCheck = (num2 >= 50) }); } } _pendingFindings.Clear(); return list; } private static LoadOverload ClassifyOverload(MethodReference method) { TypeReference declaringType = ((MemberReference)method).DeclaringType; string text = ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) ?? ""; string name = ((MemberReference)method).Name; int count = method.Parameters.Count; if (text == "System.Reflection.Assembly") { if (name == "Load") { if (count == 0) { return LoadOverload.Unknown; } switch (((MemberReference)((ParameterReference)method.Parameters[0]).ParameterType).FullName) { case "System.Byte[]": return (count != 1) ? LoadOverload.LoadBytesWithPdb : LoadOverload.LoadBytes; case "System.String": return LoadOverload.LoadString; case "System.Reflection.AssemblyName": return LoadOverload.LoadAssemblyName; } } if (name == "LoadFrom") { return LoadOverload.LoadFrom; } if (name == "LoadFile") { return LoadOverload.LoadFile; } } if (text.Contains("AssemblyLoadContext")) { if (name == "LoadFromStream") { return (count <= 1) ? LoadOverload.ALCLoadFromStream : LoadOverload.ALCLoadFromStreamPdb; } if (name == "LoadFromAssemblyPath") { return LoadOverload.ALCLoadFromPath; } } return LoadOverload.Unknown; } private static int GetBaseScore(LoadOverload overload) { if (1 == 0) { } int result = overload switch { LoadOverload.LoadBytesWithPdb => 50, LoadOverload.LoadBytes => 45, LoadOverload.ALCLoadFromStream => 45, LoadOverload.ALCLoadFromStreamPdb => 50, LoadOverload.LoadFile => 35, LoadOverload.LoadFrom => 30, LoadOverload.ALCLoadFromPath => 30, LoadOverload.LoadString => 10, LoadOverload.LoadAssemblyName => 10, _ => 20, }; if (1 == 0) { } return result; } private static bool IsPathBasedDirectLoad(LoadOverload overload) { return overload == LoadOverload.LoadFrom || overload == LoadOverload.LoadFile || overload == LoadOverload.ALCLoadFromPath; } private static bool IsSafeAssemblyName(string name) { if (string.IsNullOrWhiteSpace(name)) { return false; } foreach (string safeAssemblyPrefix in SafeAssemblyPrefixes) { if (name.StartsWith(safeAssemblyPrefix, StringComparison.OrdinalIgnoreCase)) { return true; } } if (!SimpleAssemblyNamePattern.IsMatch(name)) { return false; } return name.Length < 200 && !name.Contains("://") && !name.Contains("\\") && !name.Contains("/"); } private static bool ShouldReportDirectLoad(LoadOverload overload, ProvenanceResult provenance, int postLoadScore, int correlationScore) { if (overload == LoadOverload.LoadBytes || overload == LoadOverload.LoadBytesWithPdb || overload == LoadOverload.ALCLoadFromStream || overload == LoadOverload.ALCLoadFromStreamPdb) { return true; } if (provenance.HasNetworkSource || provenance.HasBase64 || provenance.HasCrypto || provenance.HasCompression || provenance.HasResourceSource || provenance.HasTempPath || provenance.HasSensitivePath || provenance.HasWriteThenLoad) { return true; } return postLoadScore > 0 || correlationScore > 0; } private static ProvenanceResult AnalyzeProvenance(Collection instructions, int loadCallIndex) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_042c: Unknown result type (might be due to invalid IL or missing references) //IL_0431: Unknown result type (might be due to invalid IL or missing references) //IL_02be: Unknown result type (might be due to invalid IL or missing references) //IL_02c3: Unknown result type (might be due to invalid IL or missing references) ProvenanceResult provenanceResult = new ProvenanceResult(); int num = Math.Max(0, loadCallIndex - 200); for (int i = num; i < loadCallIndex; i++) { Instruction val = instructions[i]; if (val.OpCode == OpCodes.Call || val.OpCode == OpCodes.Callvirt) { object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 != null && ((MemberReference)val2).DeclaringType != null) { string fullName = ((MemberReference)((MemberReference)val2).DeclaringType).FullName; string name = ((MemberReference)val2).Name; if ((fullName.StartsWith("System.Net") || fullName.Contains("HttpClient") || fullName.Contains("WebClient") || fullName.Contains("UnityWebRequest")) && (name.Contains("Get") || name.Contains("Download") || name.Contains("Receive"))) { provenanceResult.HasNetworkSource = true; provenanceResult.Score += 25; } if (fullName == "System.Convert" && name == "FromBase64String") { provenanceResult.HasBase64 = true; provenanceResult.Score += 15; } if (fullName.Contains("System.Security.Cryptography")) { switch (name) { default: if (!(name == "TransformBlock")) { break; } goto case "Create"; case "Create": case "CreateDecryptor": case "TransformFinalBlock": provenanceResult.HasCrypto = true; provenanceResult.Score += 25; break; } } if ((fullName.Contains("RijndaelManaged") || fullName.Contains("DESCryptoServiceProvider") || fullName.Contains("TripleDES") || fullName.Contains("RC2")) && name == ".ctor") { provenanceResult.HasCrypto = true; provenanceResult.Score += 25; } if ((fullName.Contains("GZipStream") || fullName.Contains("DeflateStream") || fullName.Contains("BrotliStream")) && name == ".ctor") { provenanceResult.HasCompression = true; provenanceResult.Score += 15; } if (fullName.Contains("Assembly") && name == "GetManifestResourceStream") { provenanceResult.HasResourceSource = true; provenanceResult.Score += 10; if (i > 0 && instructions[i - 1].OpCode == OpCodes.Ldstr && instructions[i - 1].Operand is string resourceName) { provenanceResult.ResourceName = resourceName; } } if (fullName.StartsWith("System.IO.File") && (name.Contains("Read") || name == "ReadAllBytes")) { provenanceResult.Score += 5; } if ((fullName == "System.IO.Path" && name == "GetTempPath") || (fullName == "System.IO.Path" && name == "GetTempFileName")) { provenanceResult.HasTempPath = true; provenanceResult.Score += 10; } if (fullName == "System.Environment" && name == "GetFolderPath") { provenanceResult.HasSensitivePath = true; provenanceResult.Score += 20; } if (fullName.StartsWith("System.IO.File") && (name.Contains("Write") || name.Contains("Create"))) { provenanceResult.HasWriteThenLoad = true; provenanceResult.Score += 15; } } } if (val.OpCode == OpCodes.Ldstr && val.Operand is string text) { if ((text.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || text.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) && !provenanceResult.HasNetworkSource) { provenanceResult.HasNetworkSource = true; provenanceResult.Score += 25; } if ((text.Contains("Temp", StringComparison.OrdinalIgnoreCase) || text.Contains("AppData", StringComparison.OrdinalIgnoreCase) || text.Contains("ProgramData", StringComparison.OrdinalIgnoreCase) || text.Contains("Startup", StringComparison.OrdinalIgnoreCase)) && !provenanceResult.HasSensitivePath) { provenanceResult.HasSensitivePath = true; provenanceResult.Score += 15; } } } provenanceResult.Score = Math.Min(provenanceResult.Score, 80); return provenanceResult; } private static int AnalyzePostLoadBehavior(Collection instructions, int loadCallIndex) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) int num = 0; int num2 = Math.Min(instructions.Count, loadCallIndex + 100); bool flag = false; bool flag2 = false; bool flag3 = false; bool flag4 = false; for (int i = loadCallIndex + 1; i < num2; i++) { Instruction val = instructions[i]; if (val.OpCode != OpCodes.Call && val.OpCode != OpCodes.Callvirt) { continue; } object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 != null && ((MemberReference)val2).DeclaringType != null) { string fullName = ((MemberReference)((MemberReference)val2).DeclaringType).FullName; string name = ((MemberReference)val2).Name; if (fullName == "System.Reflection.Assembly" && name == "get_EntryPoint" && !flag) { flag = true; num += 10; } if ((fullName == "System.Reflection.MethodInfo" || fullName == "System.Reflection.MethodBase") && name == "Invoke" && !flag2) { flag2 = true; num += 15; } if (fullName == "System.Reflection.Assembly" && (name == "GetType" || name == "GetTypes") && !flag3) { flag3 = true; num += 5; } if (fullName == "System.Activator" && name == "CreateInstance" && !flag4) { flag4 = true; num += 10; } } } return Math.Min(num, 30); } private static int ComputeCorrelationScore(MethodSignals? signals) { if (signals == null) { return 0; } int num = 0; if (signals.HasProcessLikeCall) { num += 30; } if (signals.HasNetworkCall) { num += 20; } if (signals.HasFileWrite && signals.UsesSensitiveFolder) { num += 25; } if (signals.HasEncodedStrings) { num += 10; } if (signals.HasBase64) { num += 10; } return Math.Min(num, 50); } private static MethodDefinition? FindDelegateTarget(Collection instructions, int subscribeCallIndex) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) for (int num = subscribeCallIndex - 1; num >= Math.Max(0, subscribeCallIndex - 10); num--) { Instruction val = instructions[num]; if (val.OpCode == OpCodes.Ldftn) { object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 != null) { try { return val2.Resolve(); } catch { return null; } } } } return null; } private static int AnalyzeResolveHandler(MethodDefinition handler) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_027d: Unknown result type (might be due to invalid IL or missing references) //IL_0282: Unknown result type (might be due to invalid IL or missing references) //IL_0292: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Unknown result type (might be due to invalid IL or missing references) if (!handler.HasBody) { return 0; } int num = 0; Collection instructions = handler.Body.Instructions; bool flag = false; bool flag2 = false; bool flag3 = false; bool flag4 = false; bool flag5 = false; bool flag6 = false; Enumerator enumerator = instructions.GetEnumerator(); try { while (enumerator.MoveNext()) { Instruction current = enumerator.Current; if (current.OpCode != OpCodes.Call && current.OpCode != OpCodes.Callvirt && current.OpCode != OpCodes.Newobj) { continue; } object operand = current.Operand; MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null); if (val != null && ((MemberReference)val).DeclaringType != null) { string fullName = ((MemberReference)((MemberReference)val).DeclaringType).FullName; string name = ((MemberReference)val).Name; if (fullName == "System.Reflection.Assembly" && name == "Load" && val.Parameters.Count > 0 && ((MemberReference)((ParameterReference)val.Parameters[0]).ParameterType).FullName == "System.Byte[]") { flag = true; } if (fullName.Contains("System.Security.Cryptography")) { flag2 = true; } if (fullName.StartsWith("System.Net") || fullName.Contains("HttpClient") || fullName.Contains("WebClient")) { flag3 = true; } if (fullName.Contains("Assembly") && name == "GetManifestResourceStream") { flag4 = true; } if (fullName.Contains("GZipStream") || fullName.Contains("DeflateStream")) { flag5 = true; } } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } if (flag4 && flag && !flag2 && !flag3) { TypeDefinition declaringType = handler.DeclaringType; if (declaringType == null || ((MemberReference)declaringType).FullName?.Contains("Costura") != true) { TypeDefinition declaringType2 = handler.DeclaringType; if (declaringType2 == null || ((TypeReference)declaringType2).Namespace?.Contains("Costura") != true) { goto IL_027b; } } flag6 = true; goto IL_027b; } goto IL_02ed; IL_027b: Enumerator enumerator2 = instructions.GetEnumerator(); try { while (enumerator2.MoveNext()) { Instruction current2 = enumerator2.Current; if (current2.OpCode == OpCodes.Ldstr && current2.Operand is string text && text.StartsWith("costura.", StringComparison.OrdinalIgnoreCase)) { flag6 = true; break; } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } goto IL_02ed; IL_02ed: if (flag6) { return 0; } if (flag) { num += 15; } if (flag2) { num += 25; } if (flag3) { num += 25; } if (flag5) { num += 10; } if (flag4 && flag && !flag2 && !flag3) { num = Math.Min(num, 10); } return num; } private static bool IsLookupOnlyResolveHandler(MethodDefinition handler) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) if (!handler.HasBody) { return false; } bool flag = false; bool flag2 = false; Enumerator enumerator = handler.Body.Instructions.GetEnumerator(); try { while (enumerator.MoveNext()) { Instruction current = enumerator.Current; if (current.OpCode != OpCodes.Call && current.OpCode != OpCodes.Callvirt && current.OpCode != OpCodes.Newobj) { continue; } object operand = current.Operand; MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null); if (val != null && ((MemberReference)val).DeclaringType != null) { string fullName = ((MemberReference)((MemberReference)val).DeclaringType).FullName; string name = ((MemberReference)val).Name; if (fullName == "System.ResolveEventArgs" && name == "get_Name") { flag = true; } else if ((fullName == "System.AppDomain" && name == "GetAssemblies") || (fullName.Contains("AssemblyLoadContext", StringComparison.Ordinal) && name == "get_Assemblies")) { flag2 = true; } else if (IsAssemblyLoadMethod(fullName, name) || fullName.StartsWith("System.IO.File", StringComparison.Ordinal) || fullName.StartsWith("System.IO.Directory", StringComparison.Ordinal) || fullName == "System.IO.Path" || fullName.StartsWith("System.Net", StringComparison.Ordinal) || fullName.Contains("HttpClient", StringComparison.Ordinal) || fullName.Contains("WebClient", StringComparison.Ordinal) || fullName.Contains("System.Security.Cryptography", StringComparison.Ordinal) || (fullName.Contains("Assembly", StringComparison.Ordinal) && name == "GetManifestResourceStream") || fullName.Contains("GZipStream", StringComparison.Ordinal) || fullName.Contains("DeflateStream", StringComparison.Ordinal)) { return false; } } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } if (!flag || !flag2) { return false; } return !DetectReflectiveLoadInvocation(handler, handler.Body.Instructions, null).Any(); } private static IEnumerable DetectReflectiveLoadInvocation(MethodDefinition method, Collection instructions, MethodSignals? methodSignals) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) List list = new List(); for (int i = 0; i < instructions.Count; i++) { Instruction val = instructions[i]; if (val.OpCode != OpCodes.Call && val.OpCode != OpCodes.Callvirt) { continue; } object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 == null || ((MemberReference)val2).DeclaringType == null) { continue; } string fullName = ((MemberReference)((MemberReference)val2).DeclaringType).FullName; string name = ((MemberReference)val2).Name; if (!(fullName == "System.Type") || !(name == "GetMethod")) { continue; } string text = null; for (int num = i - 1; num >= Math.Max(0, i - 5); num--) { if (instructions[num].OpCode == OpCodes.Ldstr && instructions[num].Operand is string text2) { switch (text2) { default: if (!(text2 == "LoadFromAssemblyPath")) { break; } goto case "Load"; case "Load": case "LoadFrom": case "LoadFile": case "LoadFromStream": text = text2; break; } break; } } if (text == null) { continue; } bool flag = false; for (int j = Math.Max(0, i - 15); j < Math.Min(instructions.Count, i + 15); j++) { if (instructions[j].OpCode == OpCodes.Ldstr && instructions[j].Operand is string text3 && (text3.Contains("System.Reflection.Assembly") || text3.Contains("AssemblyLoadContext"))) { flag = true; break; } if (instructions[j].OpCode == OpCodes.Ldtoken) { object operand2 = instructions[j].Operand; TypeReference val3 = (TypeReference)((operand2 is TypeReference) ? operand2 : null); if (val3 != null && (((MemberReference)val3).FullName == "System.Reflection.Assembly" || ((MemberReference)val3).FullName.Contains("AssemblyLoadContext"))) { flag = true; break; } } } if (flag) { int num2 = 20 + GetBaseScore(LoadOverload.Unknown); StringBuilder stringBuilder = new StringBuilder(); for (int k = Math.Max(0, i - 3); k < Math.Min(instructions.Count, i + 4); k++) { stringBuilder.Append((k == i) ? ">>> " : " "); stringBuilder.AppendLine(((object)instructions[k]).ToString()); } TypeDefinition declaringType = method.DeclaringType; list.Add(new ScanFinding($"{((declaringType != null) ? ((MemberReference)declaringType).FullName : null)}.{((MemberReference)method).Name}:{val.Offset}", $"Reflective invocation of Assembly.{text} detected via GetMethod (evasion technique, score {num2})", (num2 >= 50) ? Severity.High : Severity.Medium, stringBuilder.ToString().TrimEnd()) { RiskScore = num2, BypassCompanionCheck = (num2 >= 50) }); } } return list; } private static List ScanEmbeddedResource(ModuleDefinition module, string resourceName) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Expected O, but got Unknown List list = new List(); try { if (!module.HasResources) { return list; } EmbeddedResource val = null; Enumerator enumerator = module.Resources.GetEnumerator(); try { while (enumerator.MoveNext()) { Resource current = enumerator.Current; EmbeddedResource val2 = (EmbeddedResource)(object)((current is EmbeddedResource) ? current : null); if (val2 != null && current.Name == resourceName) { val = val2; break; } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } if (val == null) { return list; } byte[] array = val.GetResourceData(); if (array == null || array.Length == 0) { return list; } if (array.Length > 10485760) { return list; } if (array.Length < 2 || array[0] != 77 || array[1] != 90) { if (array.Length <= 2 || array[0] != 31 || array[1] != 139) { return list; } try { using MemoryStream stream = new MemoryStream(array); using GZipStream gZipStream = new GZipStream(stream, CompressionMode.Decompress); using MemoryStream memoryStream = new MemoryStream(); gZipStream.CopyTo(memoryStream); array = memoryStream.ToArray(); if (array.Length < 2 || array[0] != 77 || array[1] != 90) { return list; } } catch { return list; } } try { using MemoryStream memoryStream2 = new MemoryStream(array); ReaderParameters val3 = new ReaderParameters { ReadWrite = false, InMemory = true, ReadSymbols = false }; AssemblyDefinition val4 = AssemblyDefinition.ReadAssembly((Stream)memoryStream2, val3); List rules = (from r in RuleFactory.CreateDefaultRules() where !(r is AssemblyDynamicLoadRule) select r).ToList(); AssemblyScanner assemblyScanner = new AssemblyScanner(rules); using MemoryStream assemblyStream = new MemoryStream(array); List source = assemblyScanner.Scan(assemblyStream, "embedded:" + resourceName).ToList(); list.AddRange(source.Where((ScanFinding f) => f.Severity >= Severity.Medium)); } catch { } } catch { } return list; } private static bool IsAssemblyLoadMethod(string typeName, string methodName) { if (typeName == "System.Reflection.Assembly" && (methodName == "Load" || methodName == "LoadFrom" || methodName == "LoadFile")) { return true; } if (typeName.Contains("AssemblyLoadContext") && (methodName == "LoadFromStream" || methodName == "LoadFromAssemblyPath")) { return true; } return false; } private static string? ExtractStringArgument(Collection instructions, int callIndex) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) for (int num = callIndex - 1; num >= Math.Max(0, callIndex - 10); num--) { Instruction val = instructions[num]; if (val.OpCode == OpCodes.Ldstr && val.Operand is string result) { return result; } if (val.OpCode == OpCodes.Call || val.OpCode == OpCodes.Callvirt) { return null; } } return null; } private static Severity? MapScoreToSeverity(int score) { if (score < 15) { return null; } if (score < 25) { return Severity.Low; } if (score < 50) { return Severity.Medium; } if (score < 75) { return Severity.High; } return Severity.Critical; } private static string BuildDescription(LoadOverload overload, int totalScore, ProvenanceResult provenance, Severity severity) { if (1 == 0) { } string text = overload switch { LoadOverload.LoadBytes => "Assembly.Load(byte[])", LoadOverload.LoadBytesWithPdb => "Assembly.Load(byte[], byte[])", LoadOverload.LoadString => "Assembly.Load(string)", LoadOverload.LoadAssemblyName => "Assembly.Load(AssemblyName)", LoadOverload.LoadFrom => "Assembly.LoadFrom(string)", LoadOverload.LoadFile => "Assembly.LoadFile(string)", LoadOverload.ALCLoadFromStream => "AssemblyLoadContext.LoadFromStream", LoadOverload.ALCLoadFromStreamPdb => "AssemblyLoadContext.LoadFromStream (with PDB)", LoadOverload.ALCLoadFromPath => "AssemblyLoadContext.LoadFromAssemblyPath", _ => "Assembly.Load (unknown overload)", }; if (1 == 0) { } string arg = text; string summary = provenance.GetSummary(); if (summary == "unknown") { return $"Dynamic assembly load detected ({arg}, score {totalScore})"; } return $"Dynamic assembly load detected ({arg}, score {totalScore}): provenance: {summary}"; } } public class Base64Rule : IScanRule { public string Description => "Detected FromBase64String call which decodes base64 encrypted strings."; public Severity Severity => Severity.Low; public string RuleId => "Base64Rule"; public bool RequiresCompanionFinding => true; public IDeveloperGuidance? DeveloperGuidance => new DeveloperGuidance("If decoding configuration data, store it in plain text or use your mod framework's configuration system. For MelonLoader: use MelonPreferences. For BepInEx: use Config.Bind(). If decoding assets, embed them directly in your mod or load from standard resources.", null, new string[3] { "MelonPreferences.CreateEntry (MelonLoader)", "MelonPreferences.GetEntry (MelonLoader)", "Config.Bind (BepInEx)" }); public bool IsSuspicious(MethodReference method) { if (((method != null) ? ((MemberReference)method).DeclaringType : null) == null) { return false; } string fullName = ((MemberReference)((MemberReference)method).DeclaringType).FullName; string name = ((MemberReference)method).Name; return fullName.Contains("Convert") && name.Contains("FromBase64"); } } public class ByteArrayManipulationRule : IScanRule { public IDeveloperGuidance? DeveloperGuidance => new DeveloperGuidance("If processing images or audio, use Unity's asset loading APIs (AudioClip.LoadWAVData, Texture2D.LoadImage). For legitimate binary data, document the purpose clearly.", null, new string[2] { "UnityEngine.AudioClip.LoadWAVData", "UnityEngine.Texture2D.LoadImage" }); public string Description => "Detected byte array manipulation. Often legitimate (e.g., WAV/PCM audio processing), but can also be used to hide or load malicious payloads."; public Severity Severity => Severity.Low; public string RuleId => "ByteArrayManipulationRule"; public bool RequiresCompanionFinding => true; public bool IsSuspicious(MethodReference method) { if (((method != null) ? ((MemberReference)method).DeclaringType : null) == null) { return false; } string fullName = ((MemberReference)((MemberReference)method).DeclaringType).FullName; string name = ((MemberReference)method).Name; string text = fullName; string text2 = text; if (!(text2 == "System.Convert")) { if (text2 == "System.IO.MemoryStream" && name == ".ctor") { goto IL_0089; } } else if ((name == "FromBase64String" || name == "FromBase64CharArray") ? true : false) { goto IL_0089; } return false; IL_0089: return true; } } public class COMReflectionAttackRule : IScanRule { private enum ProgIDRiskLevel { Unknown, High, Critical } private class SignalCollection { public List AllStrings { get; } = new List(); public bool HasGetTypeFromProgID { get; set; } public bool HasGetTypeFromCLSID { get; set; } public bool HasActivatorCreateInstance { get; set; } public bool HasTypeInvokeMember { get; set; } public bool HasMarshalGetActiveObject { get; set; } public string? ProgIDValue { get; set; } } private static readonly HashSet CriticalProgIDs = new HashSet(StringComparer.OrdinalIgnoreCase) { "Shell.Application", "WScript.Shell", "Schedule.Service", "MMC20.Application" }; private static readonly HashSet HighRiskProgIDs = new HashSet(StringComparer.OrdinalIgnoreCase) { "Scripting.FileSystemObject", "ADODB.Stream", "MSXML2.XMLHTTP", "WinHttp.WinHttpRequest.5.1", "Microsoft.XMLHTTP", "WScript.Network" }; private static readonly string[] CommandStrings = new string[9] { "cmd.exe", "powershell", "pwsh", "/c ", "/k ", "wscript", "cscript", "mshta", "regsvr32" }; private static readonly string[] ShellIndicators = new string[4] { "ShellExecute", "shell32", "Run", "Exec" }; public string Description => "Detected reflective shell execution via COM (GetTypeFromProgID + InvokeMember pattern)."; public Severity Severity => Severity.Critical; public string RuleId => "COMReflectionAttackRule"; public bool RequiresCompanionFinding => false; public bool IsSuspicious(MethodReference method) { return false; } public IEnumerable AnalyzeInstructions(MethodDefinition methodDef, Collection instructions, MethodSignals methodSignals) { if (methodDef == null || instructions == null || instructions.Count == 0) { yield break; } SignalCollection signals = CollectSignals(instructions); if (!signals.HasGetTypeFromProgID && !signals.HasGetTypeFromCLSID && !signals.HasMarshalGetActiveObject) { yield break; } ProgIDRiskLevel progIDRisk = ClassifyProgID(signals.ProgIDValue); bool hasCommandStrings = signals.AllStrings.Any((string s) => CommandStrings.Any((string cmd) => s.Contains(cmd, StringComparison.OrdinalIgnoreCase))); bool hasShellIndicators = signals.AllStrings.Any((string s) => ShellIndicators.Any((string ind) => s.Contains(ind, StringComparison.OrdinalIgnoreCase))); TypeDefinition declaringType = methodDef.DeclaringType; string location = ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) + "." + ((MemberReference)methodDef).Name; if ((signals.HasGetTypeFromProgID || signals.HasGetTypeFromCLSID) && signals.HasTypeInvokeMember) { yield return CreateFinding(location, "COM reflection attack pattern: GetTypeFromProgID + Type.InvokeMember", Severity.Critical, signals, instructions); } else if (signals.HasGetTypeFromProgID && progIDRisk == ProgIDRiskLevel.Critical) { yield return CreateFinding(location, "Access to dangerous COM object: " + signals.ProgIDValue, Severity.Critical, signals, instructions); } else if (signals.HasGetTypeFromProgID && hasCommandStrings) { yield return CreateFinding(location, "COM access with command execution strings detected", Severity.Critical, signals, instructions); } else if (signals.HasGetTypeFromProgID && progIDRisk == ProgIDRiskLevel.High) { yield return CreateFinding(location, "Access to risky COM object: " + signals.ProgIDValue, Severity.High, signals, instructions); } else if ((signals.HasGetTypeFromProgID || signals.HasGetTypeFromCLSID) && signals.HasActivatorCreateInstance) { yield return CreateFinding(location, "Dynamic COM object instantiation detected", Severity.High, signals, instructions); } else if (signals.HasMarshalGetActiveObject && hasShellIndicators) { yield return CreateFinding(location, "Accessing running COM instance with shell execution indicators", Severity.High, signals, instructions); } } private static SignalCollection CollectSignals(Collection instructions) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) SignalCollection signalCollection = new SignalCollection(); Enumerator enumerator = instructions.GetEnumerator(); try { while (enumerator.MoveNext()) { Instruction current = enumerator.Current; if (current.OpCode == OpCodes.Ldstr && current.Operand is string item) { signalCollection.AllStrings.Add(item); } if (current.OpCode != OpCodes.Call && current.OpCode != OpCodes.Callvirt) { continue; } object operand = current.Operand; MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null); if (val != null && ((MemberReference)val).DeclaringType != null) { string fullName = ((MemberReference)((MemberReference)val).DeclaringType).FullName; string name = ((MemberReference)val).Name; if (fullName == "System.Type" && name == "GetTypeFromProgID") { signalCollection.HasGetTypeFromProgID = true; signalCollection.ProgIDValue = ExtractPrecedingString(instructions, current); } if (fullName == "System.Type" && name == "GetTypeFromCLSID") { signalCollection.HasGetTypeFromCLSID = true; } if (fullName == "System.Activator" && name == "CreateInstance") { signalCollection.HasActivatorCreateInstance = true; } if (fullName == "System.Type" && name == "InvokeMember") { signalCollection.HasTypeInvokeMember = true; } if (fullName == "System.Runtime.InteropServices.Marshal" && name == "GetActiveObject") { signalCollection.HasMarshalGetActiveObject = true; } } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } return signalCollection; } private static string? ExtractPrecedingString(Collection instructions, Instruction targetInstruction) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) int num = instructions.IndexOf(targetInstruction); for (int i = Math.Max(0, num - 5); i < num; i++) { if (instructions[i].OpCode == OpCodes.Ldstr && instructions[i].Operand is string result) { return result; } } return null; } private static ProgIDRiskLevel ClassifyProgID(string? progID) { if (string.IsNullOrEmpty(progID)) { return ProgIDRiskLevel.Unknown; } if (CriticalProgIDs.Contains(progID)) { return ProgIDRiskLevel.Critical; } if (HighRiskProgIDs.Contains(progID)) { return ProgIDRiskLevel.High; } if (progID.Contains("Shell", StringComparison.OrdinalIgnoreCase) || progID.Contains("WScript", StringComparison.OrdinalIgnoreCase)) { return ProgIDRiskLevel.Critical; } return ProgIDRiskLevel.Unknown; } private static ScanFinding CreateFinding(string location, string description, Severity severity, SignalCollection signals, Collection instructions) { StringBuilder stringBuilder = new StringBuilder(); if (signals.ProgIDValue != null) { stringBuilder.AppendLine("ProgID: " + signals.ProgIDValue); } if (signals.HasTypeInvokeMember) { stringBuilder.AppendLine("Uses: Type.InvokeMember (late-bound COM invocation)"); } if (signals.HasActivatorCreateInstance) { stringBuilder.AppendLine("Uses: Activator.CreateInstance"); } List list = signals.AllStrings.Where((string s) => CommandStrings.Any((string cmd) => s.Contains(cmd, StringComparison.OrdinalIgnoreCase)) || ShellIndicators.Any((string ind) => s.Contains(ind, StringComparison.OrdinalIgnoreCase))).Take(5).ToList(); if (list.Count > 0) { stringBuilder.AppendLine("Suspicious strings: " + string.Join(", ", list.Select((string s) => "\"" + s + "\""))); } return new ScanFinding(location, description, severity, stringBuilder.ToString().TrimEnd()); } } public class DataExfiltrationRule : IScanRule { public string Description => "Detected potential data exfiltration endpoints (Discord webhooks, raw paste sites, IP URLs)."; public Severity Severity => Severity.Critical; public string RuleId => "DataExfiltrationRule"; public bool RequiresCompanionFinding => false; public bool IsSuspicious(MethodReference method) { return false; } public IEnumerable AnalyzeContextualPattern(MethodReference method, Collection instructions, int instructionIndex, MethodSignals methodSignals) { if (((method != null) ? ((MemberReference)method).DeclaringType : null) == null) { yield break; } string declaringTypeFullName = ((MemberReference)((MemberReference)method).DeclaringType).FullName ?? string.Empty; string calledMethodName = ((MemberReference)method).Name ?? string.Empty; if (!declaringTypeFullName.StartsWith("System.Net", StringComparison.OrdinalIgnoreCase) && !declaringTypeFullName.Contains("UnityEngine.Networking.UnityWebRequest", StringComparison.OrdinalIgnoreCase) && !declaringTypeFullName.Contains("HttpClient", StringComparison.OrdinalIgnoreCase) && !declaringTypeFullName.Contains("WebClient", StringComparison.OrdinalIgnoreCase) && !declaringTypeFullName.Contains("WebRequest", StringComparison.OrdinalIgnoreCase) && !declaringTypeFullName.Contains("Sockets", StringComparison.OrdinalIgnoreCase) && !declaringTypeFullName.Contains("TcpClient", StringComparison.OrdinalIgnoreCase) && !declaringTypeFullName.Contains("UdpClient", StringComparison.OrdinalIgnoreCase)) { yield break; } int windowStart = Math.Max(0, instructionIndex - 10); int windowEnd = Math.Min(instructions.Count, instructionIndex + 11); IReadOnlyList literals = UrlLiteralCollector.CollectCandidates(instructions, windowStart, windowEnd); if (literals.Count == 0) { yield break; } bool isReadOnlyOperation = calledMethodName.Contains("GetStringAsync", StringComparison.OrdinalIgnoreCase) || calledMethodName.Contains("GetAsync", StringComparison.OrdinalIgnoreCase) || calledMethodName.Contains("GetByteArrayAsync", StringComparison.OrdinalIgnoreCase) || calledMethodName.Contains("DownloadString", StringComparison.OrdinalIgnoreCase) || calledMethodName.Contains("DownloadData", StringComparison.OrdinalIgnoreCase); bool isDataSendingOperation = calledMethodName.Equals("Post", StringComparison.OrdinalIgnoreCase) || calledMethodName.Equals("Put", StringComparison.OrdinalIgnoreCase) || calledMethodName.Contains("PostAsync", StringComparison.OrdinalIgnoreCase) || calledMethodName.Contains("PutAsync", StringComparison.OrdinalIgnoreCase) || calledMethodName.Contains("SendAsync", StringComparison.OrdinalIgnoreCase) || calledMethodName.Contains("GetRequestStream", StringComparison.OrdinalIgnoreCase) || calledMethodName.Contains("UploadString", StringComparison.OrdinalIgnoreCase) || calledMethodName.Contains("UploadData", StringComparison.OrdinalIgnoreCase) || calledMethodName.Contains("UploadFile", StringComparison.OrdinalIgnoreCase) || calledMethodName.Contains("UploadValues", StringComparison.OrdinalIgnoreCase); if (isReadOnlyOperation) { yield break; } bool hasDiscordWebhook = literals.Any(IsDiscordWebhookEndpoint); bool hasWebhookCollectionEndpoint = literals.Any(IsWebhookCollectionEndpoint); bool hasRawPaste = literals.Any((string s) => s.Contains("pastebin.com/raw", StringComparison.OrdinalIgnoreCase) || s.Contains("hastebin.com/raw", StringComparison.OrdinalIgnoreCase)); bool hasBareIpUrl = literals.Any((string s) => Regex.IsMatch(s, "https?://\\d{1,3}(?:\\.\\d{1,3}){3}", RegexOptions.IgnoreCase)); bool mentionsNgrokOrTelegram = literals.Any((string s) => s.Contains("ngrok", StringComparison.OrdinalIgnoreCase) || s.Contains("telegram", StringComparison.OrdinalIgnoreCase)); bool isLegitimateSource = literals.Any(IsLegitimateSourceLiteral); bool isGitHubSource = literals.Any(IsGitHubSourceLiteral); bool isModHostingSource = literals.Any(IsModHostingSourceLiteral); bool isCDNSource = literals.Any(IsCdnSourceLiteral); List urls = new List(); foreach (string literal in literals) { if (literal.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || literal.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) { Match match = Regex.Match(literal, "(https?://[^\\s\"'<>]+)", RegexOptions.IgnoreCase); if (match.Success) { urls.Add(match.Groups[1].Value); } } else { if (!Regex.IsMatch(literal, "https?://", RegexOptions.IgnoreCase)) { continue; } MatchCollection matches = Regex.Matches(literal, "(https?://[^\\s\"'<>]+)", RegexOptions.IgnoreCase); foreach (Match match2 in matches) { urls.Add(match2.Groups[1].Value); } } } urls = urls.Distinct().ToList(); string urlList = ((urls.Count > 0) ? (" URL(s): " + string.Join(", ", urls)) : string.Empty); StringBuilder snippetBuilder = new StringBuilder(); int contextLines = 2; for (int j = Math.Max(0, instructionIndex - contextLines); j < Math.Min(instructions.Count, instructionIndex + contextLines + 1); j++) { if (j == instructionIndex) { snippetBuilder.Append(">>> "); } else { snippetBuilder.Append(" "); } snippetBuilder.AppendLine(((object)instructions[j]).ToString()); } if (hasDiscordWebhook) { TypeReference declaringType = ((MemberReference)method).DeclaringType; yield return new ScanFinding(string.Format("{0}.{1}:{2}", ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) ?? "Unknown", ((MemberReference)method).Name, instructions[instructionIndex].Offset), "Discord webhook endpoint near network call (potential data exfiltration)." + urlList, Severity.Critical, snippetBuilder.ToString().TrimEnd()); } else if (isDataSendingOperation && hasWebhookCollectionEndpoint) { TypeReference declaringType2 = ((MemberReference)method).DeclaringType; yield return new ScanFinding(string.Format("{0}.{1}:{2}", ((declaringType2 != null) ? ((MemberReference)declaringType2).FullName : null) ?? "Unknown", ((MemberReference)method).Name, instructions[instructionIndex].Offset), "Webhook collection endpoint near data-sending operation (potential data exfiltration)." + urlList, Severity.Critical, snippetBuilder.ToString().TrimEnd()); } else if (isDataSendingOperation && (hasRawPaste || hasBareIpUrl || mentionsNgrokOrTelegram)) { TypeReference declaringType3 = ((MemberReference)method).DeclaringType; yield return new ScanFinding(string.Format("{0}.{1}:{2}", ((declaringType3 != null) ? ((MemberReference)declaringType3).FullName : null) ?? "Unknown", ((MemberReference)method).Name, instructions[instructionIndex].Offset), "Data-sending operation (POST/PUT) to suspicious endpoint (potential data exfiltration)." + urlList, Severity.Critical, snippetBuilder.ToString().TrimEnd()); } else if (!isReadOnlyOperation && !isDataSendingOperation && (hasRawPaste || hasBareIpUrl || mentionsNgrokOrTelegram)) { TypeReference declaringType4 = ((MemberReference)method).DeclaringType; yield return new ScanFinding(string.Format("{0}.{1}:{2}", ((declaringType4 != null) ? ((MemberReference)declaringType4).FullName : null) ?? "Unknown", ((MemberReference)method).Name, instructions[instructionIndex].Offset), "Potential payload download endpoint near network call (raw paste/code host/IP)." + urlList, Severity.Medium, snippetBuilder.ToString().TrimEnd()); } else if (isDataSendingOperation && isLegitimateSource) { string sourceType = (isGitHubSource ? "GitHub" : (isModHostingSource ? "mod hosting site" : (isCDNSource ? "CDN" : "unknown source"))); TypeReference declaringType5 = ((MemberReference)method).DeclaringType; yield return new ScanFinding(string.Format("{0}.{1}:{2}", ((declaringType5 != null) ? ((MemberReference)declaringType5).FullName : null) ?? "Unknown", ((MemberReference)method).Name, instructions[instructionIndex].Offset), "Data-sending operation to " + sourceType + " (unusual but potentially legitimate - API interaction)." + urlList, Severity.Low, snippetBuilder.ToString().TrimEnd()); } } private static bool IsDiscordWebhookEndpoint(string literal) { return ExtractUrls(literal).Any(delegate(string url) { if (!Uri.TryCreate(url, UriKind.Absolute, out Uri result)) { return false; } string host = result.Host; return (host.Equals("discord.com", StringComparison.OrdinalIgnoreCase) || host.EndsWith(".discord.com", StringComparison.OrdinalIgnoreCase) || host.Equals("discordapp.com", StringComparison.OrdinalIgnoreCase) || host.EndsWith(".discordapp.com", StringComparison.OrdinalIgnoreCase)) && result.AbsolutePath.StartsWith("/api/webhooks", StringComparison.OrdinalIgnoreCase); }); } private static bool IsWebhookCollectionEndpoint(string literal) { Uri result; return ExtractUrls(literal).Any((string url) => Uri.TryCreate(url, UriKind.Absolute, out result) && (result.Host.Equals("webhook.site", StringComparison.OrdinalIgnoreCase) || result.Host.EndsWith(".webhook.site", StringComparison.OrdinalIgnoreCase))); } private static IEnumerable ExtractUrls(string literal) { foreach (Match match in Regex.Matches(literal, "(https?://[^\\s\"'<>]+)", RegexOptions.IgnoreCase)) { yield return match.Groups[1].Value; } } private static bool IsLegitimateSourceLiteral(string literal) { return ExtractUrls(literal).Any(IsLegitimateSourceUrl); } private static bool IsGitHubSourceLiteral(string literal) { Uri result; return ExtractUrls(literal).Any((string url) => Uri.TryCreate(url, UriKind.Absolute, out result) && IsGitHubSource(result)); } private static bool IsModHostingSourceLiteral(string literal) { Uri result; return ExtractUrls(literal).Any((string url) => Uri.TryCreate(url, UriKind.Absolute, out result) && (HostMatches(result.Host, "modrinth.com") || HostMatches(result.Host, "curseforge.com") || HostMatches(result.Host, "nexusmods.com"))); } private static bool IsCdnSourceLiteral(string literal) { Uri result; return ExtractUrls(literal).Any((string url) => Uri.TryCreate(url, UriKind.Absolute, out result) && IsCdnSource(result)); } private static bool IsLegitimateSourceUrl(string url) { if (!Uri.TryCreate(url, UriKind.Absolute, out Uri result)) { return false; } if (HostMatches(result.Host, "discord.com") || HostMatches(result.Host, "discordapp.com")) { return false; } return IsGitHubSource(result) || HostMatches(result.Host, "modrinth.com") || HostMatches(result.Host, "curseforge.com") || HostMatches(result.Host, "nexusmods.com") || IsCdnSource(result); } private static bool IsGitHubSource(Uri uri) { return (HostMatches(uri.Host, "github.com") && (uri.AbsolutePath.Contains("/releases", StringComparison.OrdinalIgnoreCase) || uri.AbsolutePath.Contains("/release", StringComparison.OrdinalIgnoreCase))) || (HostMatches(uri.Host, "api.github.com") && uri.AbsolutePath.StartsWith("/repos", StringComparison.OrdinalIgnoreCase)) || HostMatches(uri.Host, "raw.githubusercontent.com") || HostMatches(uri.Host, "githubusercontent.com") || HostMatches(uri.Host, "github.io"); } private static bool IsCdnSource(Uri uri) { return HostMatches(uri.Host, "cdn.jsdelivr.net") || HostMatches(uri.Host, "unpkg.com") || HostMatches(uri.Host, "cdnjs.cloudflare.com") || HostMatches(uri.Host, "gstatic.com") || HostMatches(uri.Host, "googleapis.com"); } private static bool HostMatches(string host, string domain) { return host.Equals(domain, StringComparison.OrdinalIgnoreCase) || host.EndsWith("." + domain, StringComparison.OrdinalIgnoreCase); } } public class DataInfiltrationRule : IScanRule { private static readonly HashSet KnownMaliciousDomains = new HashSet(StringComparer.OrdinalIgnoreCase) { "minecraftmods.xyz", "recipiestocook.com", "fingercakes4sale.store", "stardewcookies.xyz", "enardio.com", "hohol4521.xyz", "dvach7.store", "eunexusmodshost.store" }; private static readonly HashSet UrlShortenerDomains = new HashSet(StringComparer.OrdinalIgnoreCase) { "bit.ly", "tinyurl.com", "t.co", "goo.gl", "is.gd", "cutt.ly", "rebrand.ly", "shorturl.at" }; private static readonly HashSet SuspiciousPayloadExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) { ".exe", ".dll", ".bat", ".cmd", ".ps1", ".vbs", ".js", ".hta", ".scr", ".com" }; private static readonly HashSet CommonHostingPayloadExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) { ".exe", ".dll", ".bat", ".cmd", ".ps1", ".vbs", ".hta", ".scr", ".com" }; public string Description => "Detected data download from suspicious endpoint (potential payload infiltration)."; public Severity Severity => Severity.High; public string RuleId => "DataInfiltrationRule"; public bool RequiresCompanionFinding => true; public IDeveloperGuidance? DeveloperGuidance => new DeveloperGuidance("For update checking, use GitHub Releases API (api.github.com/repos/...) or raw.githubusercontent.com.", null, new string[2] { "HttpClient.GetStringAsync", "UnityWebRequest.Get" }); public bool IsSuspicious(MethodReference method) { return false; } public IEnumerable AnalyzeContextualPattern(MethodReference method, Collection instructions, int instructionIndex, MethodSignals methodSignals) { if (((method != null) ? ((MemberReference)method).DeclaringType : null) == null) { yield break; } string declaringTypeFullName = ((MemberReference)((MemberReference)method).DeclaringType).FullName ?? string.Empty; string calledMethodName = ((MemberReference)method).Name ?? string.Empty; if ((!declaringTypeFullName.StartsWith("System.Net", StringComparison.OrdinalIgnoreCase) && !declaringTypeFullName.Contains("UnityEngine.Networking.UnityWebRequest", StringComparison.OrdinalIgnoreCase) && !declaringTypeFullName.Contains("HttpClient", StringComparison.OrdinalIgnoreCase) && !declaringTypeFullName.Contains("WebClient", StringComparison.OrdinalIgnoreCase) && !declaringTypeFullName.Contains("WebRequest", StringComparison.OrdinalIgnoreCase) && !declaringTypeFullName.Contains("Sockets", StringComparison.OrdinalIgnoreCase) && !declaringTypeFullName.Contains("TcpClient", StringComparison.OrdinalIgnoreCase) && !declaringTypeFullName.Contains("UdpClient", StringComparison.OrdinalIgnoreCase)) || (!calledMethodName.Contains("GetStringAsync", StringComparison.OrdinalIgnoreCase) && !calledMethodName.Contains("GetAsync", StringComparison.OrdinalIgnoreCase) && !calledMethodName.Contains("GetByteArrayAsync", StringComparison.OrdinalIgnoreCase) && !calledMethodName.Contains("GetStreamAsync", StringComparison.OrdinalIgnoreCase) && !calledMethodName.Contains("GetResponse", StringComparison.OrdinalIgnoreCase) && !calledMethodName.Contains("OpenRead", StringComparison.OrdinalIgnoreCase) && !calledMethodName.Contains("DownloadString", StringComparison.OrdinalIgnoreCase) && !calledMethodName.Contains("DownloadData", StringComparison.OrdinalIgnoreCase) && !calledMethodName.Contains("DownloadFile", StringComparison.OrdinalIgnoreCase) && !calledMethodName.Equals("Get", StringComparison.OrdinalIgnoreCase))) { yield break; } int windowStart = Math.Max(0, instructionIndex - 25); int windowEnd = Math.Min(instructions.Count, instructionIndex + 26); IReadOnlyList literals = UrlLiteralCollector.CollectCandidates(instructions, windowStart, windowEnd); if (literals.Count == 0) { yield break; } bool hasRawPaste = literals.Any((string s) => s.Contains("pastebin.com/raw", StringComparison.OrdinalIgnoreCase) || s.Contains("hastebin.com/raw", StringComparison.OrdinalIgnoreCase)); bool hasBareIpUrl = literals.Any((string s) => Regex.IsMatch(s, "https?://\\d{1,3}(?:\\.\\d{1,3}){3}", RegexOptions.IgnoreCase)); bool mentionsNgrokOrTelegram = literals.Any((string s) => s.Contains("ngrok", StringComparison.OrdinalIgnoreCase) || s.Contains("telegram", StringComparison.OrdinalIgnoreCase)); bool isLegitimateSource = literals.Any(IsLegitimateSourceLiteral); bool isGitHubSource = literals.Any(IsGitHubSourceLiteral); bool isModHostingSource = literals.Any(IsModHostingSourceLiteral); bool isCDNSource = literals.Any(IsCdnSourceLiteral); List urls = new List(); foreach (string literal in literals) { if (literal.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || literal.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) { Match match = Regex.Match(literal, "(https?://[^\\s\"'<>]+)", RegexOptions.IgnoreCase); if (match.Success) { urls.Add(match.Groups[1].Value); } } else { if (!Regex.IsMatch(literal, "https?://", RegexOptions.IgnoreCase)) { continue; } MatchCollection matches = Regex.Matches(literal, "(https?://[^\\s\"'<>]+)", RegexOptions.IgnoreCase); foreach (Match match2 in matches) { urls.Add(match2.Groups[1].Value); } } } urls = urls.Distinct().ToList(); bool targetsKnownMaliciousDomain = urls.Any(IsKnownMaliciousDomain); bool targetsDirectPayload = urls.Any(IsDirectPayloadUrl); bool targetsCommonHostingPayload = urls.Any(IsCommonHostingPayloadUrl); bool targetsUrlShortener = urls.Any(IsUrlShortenerDomain); string urlList = ((urls.Count > 0) ? (" URL(s): " + string.Join(", ", urls)) : string.Empty); StringBuilder snippetBuilder = new StringBuilder(); int contextLines = 2; for (int j = Math.Max(0, instructionIndex - contextLines); j < Math.Min(instructions.Count, instructionIndex + contextLines + 1); j++) { if (j == instructionIndex) { snippetBuilder.Append(">>> "); } else { snippetBuilder.Append(" "); } snippetBuilder.AppendLine(((object)instructions[j]).ToString()); } if (targetsCommonHostingPayload && isLegitimateSource) { TypeReference declaringType = ((MemberReference)method).DeclaringType; yield return new ScanFinding(string.Format("{0}.{1}:{2}", ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) ?? "Unknown", ((MemberReference)method).Name, instructions[instructionIndex].Offset), "Read-only operation downloads executable or script payload from common hosting source." + urlList, Severity.Medium, snippetBuilder.ToString().TrimEnd()); } else if (isLegitimateSource) { string sourceType = (isGitHubSource ? "GitHub" : (isModHostingSource ? "mod hosting site" : (isCDNSource ? "CDN" : "unknown source"))); TypeReference declaringType2 = ((MemberReference)method).DeclaringType; yield return new ScanFinding(string.Format("{0}.{1}:{2}", ((declaringType2 != null) ? ((MemberReference)declaringType2).FullName : null) ?? "Unknown", ((MemberReference)method).Name, instructions[instructionIndex].Offset), "Read-only network operation to " + sourceType + " (likely legitimate - version check or resource download)." + urlList, Severity.Low, snippetBuilder.ToString().TrimEnd()); } else if (targetsKnownMaliciousDomain) { TypeReference declaringType3 = ((MemberReference)method).DeclaringType; yield return new ScanFinding(string.Format("{0}.{1}:{2}", ((declaringType3 != null) ? ((MemberReference)declaringType3).FullName : null) ?? "Unknown", ((MemberReference)method).Name, instructions[instructionIndex].Offset), "Read-only operation to known malicious domain (confirmed payload delivery infrastructure)." + urlList, Severity.High, snippetBuilder.ToString().TrimEnd()) { BypassCompanionCheck = true }; } else if (targetsUrlShortener) { TypeReference declaringType4 = ((MemberReference)method).DeclaringType; yield return new ScanFinding(string.Format("{0}.{1}:{2}", ((declaringType4 != null) ? ((MemberReference)declaringType4).FullName : null) ?? "Unknown", ((MemberReference)method).Name, instructions[instructionIndex].Offset), "Read-only operation uses URL shortener for download endpoint (destination hidden from static scan)." + urlList, Severity.High, snippetBuilder.ToString().TrimEnd()); } else if (targetsDirectPayload) { TypeReference declaringType5 = ((MemberReference)method).DeclaringType; yield return new ScanFinding(string.Format("{0}.{1}:{2}", ((declaringType5 != null) ? ((MemberReference)declaringType5).FullName : null) ?? "Unknown", ((MemberReference)method).Name, instructions[instructionIndex].Offset), "Read-only operation downloads executable or script payload from non-allowlisted domain." + urlList, Severity.High, snippetBuilder.ToString().TrimEnd()) { BypassCompanionCheck = true }; } else if (hasRawPaste || hasBareIpUrl || mentionsNgrokOrTelegram) { TypeReference declaringType6 = ((MemberReference)method).DeclaringType; yield return new ScanFinding(string.Format("{0}.{1}:{2}", ((declaringType6 != null) ? ((MemberReference)declaringType6).FullName : null) ?? "Unknown", ((MemberReference)method).Name, instructions[instructionIndex].Offset), "Read-only operation to suspicious endpoint (potential payload download)." + urlList, Severity.High, snippetBuilder.ToString().TrimEnd()); } } private static bool IsKnownMaliciousDomain(string url) { if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri) || string.IsNullOrWhiteSpace(uri.Host)) { return false; } return KnownMaliciousDomains.Contains(uri.Host) || KnownMaliciousDomains.Any((string domain) => uri.Host.EndsWith("." + domain, StringComparison.OrdinalIgnoreCase)); } private static bool IsUrlShortenerDomain(string url) { if (!Uri.TryCreate(url, UriKind.Absolute, out Uri result) || string.IsNullOrWhiteSpace(result.Host)) { return false; } return UrlShortenerDomains.Contains(result.Host); } private static bool IsLegitimateSourceLiteral(string literal) { return ExtractUrls(literal).Any(IsLegitimateSourceUrl); } private static bool IsGitHubSourceLiteral(string literal) { Uri result; return ExtractUrls(literal).Any((string url) => Uri.TryCreate(url, UriKind.Absolute, out result) && IsGitHubSource(result)); } private static bool IsModHostingSourceLiteral(string literal) { Uri result; return ExtractUrls(literal).Any((string url) => Uri.TryCreate(url, UriKind.Absolute, out result) && (HostMatches(result.Host, "modrinth.com") || HostMatches(result.Host, "curseforge.com") || HostMatches(result.Host, "nexusmods.com"))); } private static bool IsCdnSourceLiteral(string literal) { Uri result; return ExtractUrls(literal).Any((string url) => Uri.TryCreate(url, UriKind.Absolute, out result) && IsCdnSource(result)); } private static bool IsLegitimateSourceUrl(string url) { if (!Uri.TryCreate(url, UriKind.Absolute, out Uri result)) { return false; } if (HostMatches(result.Host, "discord.com") || HostMatches(result.Host, "discordapp.com")) { return false; } return IsGitHubSource(result) || HostMatches(result.Host, "modrinth.com") || HostMatches(result.Host, "curseforge.com") || HostMatches(result.Host, "nexusmods.com") || IsCdnSource(result); } private static bool IsGitHubSource(Uri uri) { return (HostMatches(uri.Host, "github.com") && (uri.AbsolutePath.Contains("/releases", StringComparison.OrdinalIgnoreCase) || uri.AbsolutePath.Contains("/release", StringComparison.OrdinalIgnoreCase))) || (HostMatches(uri.Host, "api.github.com") && uri.AbsolutePath.StartsWith("/repos", StringComparison.OrdinalIgnoreCase)) || HostMatches(uri.Host, "raw.githubusercontent.com") || HostMatches(uri.Host, "githubusercontent.com") || HostMatches(uri.Host, "github.io"); } private static bool IsCdnSource(Uri uri) { return HostMatches(uri.Host, "cdn.jsdelivr.net") || HostMatches(uri.Host, "unpkg.com") || HostMatches(uri.Host, "cdnjs.cloudflare.com") || HostMatches(uri.Host, "gstatic.com") || HostMatches(uri.Host, "googleapis.com"); } private static bool HostMatches(string host, string domain) { return host.Equals(domain, StringComparison.OrdinalIgnoreCase) || host.EndsWith("." + domain, StringComparison.OrdinalIgnoreCase); } private static IEnumerable ExtractUrls(string literal) { foreach (Match match in Regex.Matches(literal, "(https?://[^\\s\"'<>]+)", RegexOptions.IgnoreCase)) { yield return match.Groups[1].Value; } } private static bool IsDirectPayloadUrl(string url) { if (!Uri.TryCreate(url, UriKind.Absolute, out Uri result)) { return false; } if (IsKnownMaliciousDomain(url)) { return true; } string path = result.AbsolutePath; return SuspiciousPayloadExtensions.Any((string ext) => path.EndsWith(ext, StringComparison.OrdinalIgnoreCase)); } private static bool IsCommonHostingPayloadUrl(string url) { if (!Uri.TryCreate(url, UriKind.Absolute, out Uri result)) { return false; } string path = result.AbsolutePath; return CommonHostingPayloadExtensions.Any((string ext) => path.EndsWith(ext, StringComparison.OrdinalIgnoreCase)); } } public class DllImportRule : IScanRule { private Severity _severity = Severity.Medium; private string _description = "Detected DLL import"; private static readonly string[] ElevatedRiskDlls = new string[6] { "advapi32.dll", "wininet.dll", "urlmon.dll", "winsock.dll", "ws2_32.dll", "shell32.dll" }; private static readonly string[] CommonNativeRuntimeDlls = new string[5] { "kernel32.dll", "user32.dll", "ntdll.dll", "psapi.dll", "dbghelp.dll" }; private static readonly string[] MediumRiskDlls = new string[7] { "gdi32.dll", "ole32.dll", "oleaut32.dll", "comctl32.dll", "comdlg32.dll", "version.dll", "winmm.dll" }; private static readonly string[] LowRiskAudioMediaDlls = new string[26] { "avrt.dll", "bass.dll", "bass_fx.dll", "bassenc.dll", "bassmix.dll", "basswasapi.dll", "bassasio.dll", "basscd.dll", "bassflac.dll", "bassmidi.dll", "bassopus.dll", "basswma.dll", "basswv.dll", "bassape.dll", "fmod.dll", "fmodex.dll", "fmodex64.dll", "fmodstudio.dll", "fmodstudio64.dll", "xaudio2_9.dll", "xaudio2_8.dll", "xaudio2_7.dll", "dsound.dll", "msacm32.dll", "winmm.dll", "mci.dll" }; private static readonly string[] CriticalFunctions = new string[12] { "createprocess", "virtualallocex", "writeprocessmemory", "readprocessmemory", "createremotethread", "internetopen", "internetconnect", "internetreadfile", "httpopen", "urldownload", "inject", "shellexecute" }; private static readonly string[] HighRiskFunctions = new string[7] { "virtualalloc", "virtualprotect", "openprocess", "createthread", "openthread", "suspendthread", "resumethread" }; private static readonly string[] NativeExecutionFunctions = new string[3] { "shellexecute", "createprocess", "winexec" }; private static readonly string[] DiagnosticFunctions = new string[11] { "getmodulehandle", "getcurrentprocess", "getcurrentprocessid", "getcurrentthreadid", "closehandle", "createtoolhelp32snapshot", "process32first", "process32next", "rtlgetversion", "ntqueryinformationprocess", "minidumpwritedump" }; private static readonly string[] BenignUserInteractionFunctions = new string[2] { "messagebox", "getasynckeystate" }; public string Description => _description; public Severity Severity => _severity; public string RuleId => "DllImportRule"; public bool RequiresCompanionFinding => false; public IDeveloperGuidance? DeveloperGuidance => new DeveloperGuidance("Native DLL imports are flagged as high risk. If essential for your mod's functionality, clearly document the purpose and consider using managed alternatives where possible.", null, null, isRemediable: false); public bool IsSuspicious(MethodReference method) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Invalid comparison between Unknown and I4 if (((method != null) ? ((MemberReference)method).DeclaringType : null) == null) { return false; } MethodDefinition val = method.Resolve(); if (val == null) { return false; } if ((val.Attributes & 0x2000) == 0) { return false; } if (val.PInvokeInfo == null) { return false; } string name = val.PInvokeInfo.Module.Name; string text = val.PInvokeInfo.EntryPoint ?? ((MemberReference)method).Name; if (IsKnownIl2CppInteropBridge(val, name, text)) { return false; } string lowerDllName = name.ToLower(); string entryPointLower = text.ToLower(); if (MatchesKnownApi(entryPointLower, DiagnosticFunctions)) { _severity = Severity.Low; _description = "Detected diagnostic DllImport of " + name + " (" + text + ")"; return true; } if (IsNativeExecutionEntryPoint(entryPointLower)) { _severity = Severity.Critical; _description = "Detected high-risk native execution function " + text + " in DllImport from " + name; return true; } if (CriticalFunctions.Any((string func) => entryPointLower.Contains(func))) { _severity = Severity.Critical; _description = "Detected high-risk function " + text + " in DllImport from " + name; return true; } if (HighRiskFunctions.Any((string func) => entryPointLower.Contains(func))) { _severity = Severity.High; _description = "Detected elevated-risk function " + text + " in DllImport from " + name; return true; } if (IsBenignUserInteractionApi(val, lowerDllName, entryPointLower)) { return false; } if (IsLowRiskAudioMediaDll(lowerDllName)) { return false; } if (MatchesKnownDll(lowerDllName, ElevatedRiskDlls)) { _severity = Severity.High; _description = "Detected high-risk DllImport of " + name; return true; } if (MatchesKnownDll(lowerDllName, CommonNativeRuntimeDlls)) { _severity = Severity.Medium; _description = "Detected native runtime DllImport of " + name + " (" + text + ")"; return true; } if (MatchesKnownDll(lowerDllName, MediumRiskDlls)) { _severity = Severity.Medium; _description = "Detected medium-risk DllImport of " + name; return true; } _severity = Severity.Medium; _description = "Detected DllImport of " + name; return true; } private static bool MatchesKnownApi(string entryPointLower, IEnumerable knownApis) { foreach (string knownApi in knownApis) { if (entryPointLower.Equals(knownApi, StringComparison.OrdinalIgnoreCase) || entryPointLower.Equals(knownApi + "a", StringComparison.OrdinalIgnoreCase) || entryPointLower.Equals(knownApi + "w", StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static bool IsKnownIl2CppInteropBridge(MethodDefinition methodDef, string dllName, string entryPoint) { if (methodDef.DeclaringType == null) { return false; } if (!((MemberReference)methodDef.DeclaringType).FullName.Equals("Il2CppInterop.Runtime.IL2CPP", StringComparison.Ordinal)) { return false; } if (!dllName.Equals("GameAssembly", StringComparison.OrdinalIgnoreCase) && !dllName.Equals("GameAssembly.dll", StringComparison.OrdinalIgnoreCase)) { return false; } return entryPoint.StartsWith("il2cpp_", StringComparison.OrdinalIgnoreCase); } public static bool IsNativeExecutionEntryPoint(string entryPointLower) { if (string.IsNullOrWhiteSpace(entryPointLower)) { return false; } return NativeExecutionFunctions.Any((string func) => entryPointLower.Contains(func, StringComparison.OrdinalIgnoreCase)); } private static bool IsLowRiskAudioMediaDll(string lowerDllName) { string text = NormalizeDllName(lowerDllName); string[] lowRiskAudioMediaDlls = LowRiskAudioMediaDlls; foreach (string dllName in lowRiskAudioMediaDlls) { string text2 = NormalizeDllName(dllName); if (text == text2) { return true; } } return false; } private static bool MatchesKnownDll(string lowerDllName, IEnumerable knownDlls) { string text = NormalizeDllName(lowerDllName); foreach (string knownDll in knownDlls) { string text2 = NormalizeDllName(knownDll); if (text.Equals(text2, StringComparison.Ordinal) || text.EndsWith("\\" + text2, StringComparison.Ordinal) || text.EndsWith("/" + text2, StringComparison.Ordinal)) { return true; } } return false; } private static bool IsBenignUserInteractionApi(MethodDefinition methodDef, string lowerDllName, string entryPointLower) { if (!NormalizeDllName(lowerDllName).Equals("user32", StringComparison.Ordinal)) { return false; } if (!MatchesKnownApi(entryPointLower, BenignUserInteractionFunctions)) { return false; } if (MatchesKnownApi(entryPointLower, new string[1] { "messagebox" })) { return HasStandardMessageBoxSignature(methodDef); } if (MatchesKnownApi(entryPointLower, new string[1] { "getasynckeystate" })) { return HasStandardGetAsyncKeyStateSignature(methodDef); } return false; } private static bool HasStandardMessageBoxSignature(MethodDefinition methodDef) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Invalid comparison between Unknown and I4 //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Invalid comparison between Unknown and I4 //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Invalid comparison between Unknown and I4 if ((int)((MethodReference)methodDef).ReturnType.MetadataType != 8) { return false; } if (((MethodReference)methodDef).Parameters.Count != 4) { return false; } if ((int)((ParameterReference)((MethodReference)methodDef).Parameters[0]).ParameterType.MetadataType != 24) { return false; } if ((int)((ParameterReference)((MethodReference)methodDef).Parameters[1]).ParameterType.MetadataType != 14 || (int)((ParameterReference)((MethodReference)methodDef).Parameters[2]).ParameterType.MetadataType != 14) { return false; } return IsMessageBoxOptionsType(((ParameterReference)((MethodReference)methodDef).Parameters[3]).ParameterType); } private static bool HasStandardGetAsyncKeyStateSignature(MethodDefinition methodDef) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Invalid comparison between Unknown and I4 if ((int)((MethodReference)methodDef).ReturnType.MetadataType != 6) { return false; } if (((MethodReference)methodDef).Parameters.Count != 1) { return false; } return (int)((ParameterReference)((MethodReference)methodDef).Parameters[0]).ParameterType.MetadataType == 8; } private static bool IsMessageBoxOptionsType(TypeReference parameterType) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Invalid comparison between Unknown and I4 //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Invalid comparison between Unknown and I4 if ((int)parameterType.MetadataType == 8 || (int)parameterType.MetadataType == 9) { return true; } TypeDefinition val = parameterType.Resolve(); if (val == null || !val.IsEnum) { return false; } FieldDefinition val2 = ((IEnumerable)val.Fields).FirstOrDefault((Func)((FieldDefinition field) => ((MemberReference)field).Name == "value__")); return (val2 != null && (int)((FieldReference)val2).FieldType.MetadataType == 8) || (val2 != null && (int)((FieldReference)val2).FieldType.MetadataType == 9); } private static string NormalizeDllName(string dllName) { string text = (dllName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) ? dllName.Substring(0, dllName.Length - 4) : dllName); return text.ToLowerInvariant(); } } public class EmbeddedResourceScriptRule : IScanRule { private const int MaxResourceBytes = 2097152; private static readonly Regex SuspiciousExecutionPattern = new Regex("(?i)(powershell|pwsh|cmd\\.exe|start-process|shellexecute|invoke-webrequest|\\biwr\\b|invoke-expression|\\biex\\b|net\\.webclient|download(file|string)|executionpolicy|windowstyle\\s+hidden|-noprofile|-nolog[o]?)", RegexOptions.Compiled | RegexOptions.CultureInvariant); private static readonly Regex AntiAnalysisPattern = new Regex("(?i)(get-ciminstance|win32_videocontroller|win32_computersystem|remote display adapter|totalphysicalmemory|virtualbox|vmware|sandbox|analysis)", RegexOptions.Compiled | RegexOptions.CultureInvariant); private static readonly Regex StagedPayloadPattern = new Regex("(?i)(%temp%|\\\\temp\\\\|\\.(cmd|bat|ps1|vbs|js|hta)\\b|out-file|set-content|add-content|start-sleep|remove-item)", RegexOptions.Compiled | RegexOptions.CultureInvariant); public string Description => "Detected referenced embedded script resource with command execution or anti-analysis indicators."; public Severity Severity => Severity.High; public string RuleId => "EmbeddedResourceScriptRule"; public bool RequiresCompanionFinding => false; public bool IsSuspicious(MethodReference method) { return false; } public IEnumerable PostAnalysisRefine(ModuleDefinition module, IEnumerable existingFindings) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) if (module == null || module.Resources.Count == 0) { return Enumerable.Empty(); } HashSet hashSet = CollectReferencedResourceNames(module); if (hashSet.Count == 0) { return Enumerable.Empty(); } List list = new List(); Enumerator enumerator = module.Resources.GetEnumerator(); try { while (enumerator.MoveNext()) { Resource current = enumerator.Current; EmbeddedResource val = (EmbeddedResource)(object)((current is EmbeddedResource) ? current : null); if (val != null && hashSet.Contains(((Resource)val).Name) && TryReadTextResource(val, out string text)) { bool flag = SuspiciousExecutionPattern.IsMatch(text); bool flag2 = AntiAnalysisPattern.IsMatch(text); bool flag3 = StagedPayloadPattern.IsMatch(text); if (flag && (flag2 || flag3)) { string text2 = (flag2 ? "anti-analysis command markers" : "staged script payload markers"); list.Add(new ScanFinding("Embedded resource: " + ((Resource)val).Name, "Referenced embedded resource '" + ((Resource)val).Name + "' contains script execution with " + text2 + ".", Severity.High, BuildSnippet(text)) { RuleId = RuleId, BypassCompanionCheck = true, RiskScore = ((flag2 && flag3) ? 82 : 76) }); } } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } return list; } private static HashSet CollectReferencedResourceNames(ModuleDefinition module) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (TypeDefinition item in EnumerateTypes(module)) { Enumerator enumerator2 = item.Methods.GetEnumerator(); try { while (enumerator2.MoveNext()) { MethodDefinition current2 = enumerator2.Current; if (!current2.HasBody) { continue; } Collection instructions = current2.Body.Instructions; for (int i = 0; i < instructions.Count; i++) { Instruction val = instructions[i]; if (val.OpCode != OpCodes.Call && val.OpCode != OpCodes.Callvirt) { continue; } object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 == null) { continue; } TypeReference declaringType = ((MemberReference)val2).DeclaringType; if (((declaringType != null) ? ((MemberReference)declaringType).FullName : null) != "System.Reflection.Assembly" || ((MemberReference)val2).Name != "GetManifestResourceStream") { continue; } for (int num = i - 1; num >= Math.Max(0, i - 8); num--) { if (instructions[num].OpCode == OpCodes.Ldstr && instructions[num].Operand is string text && !string.IsNullOrWhiteSpace(text)) { hashSet.Add(text); break; } } } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } return hashSet; } private static bool TryReadTextResource(EmbeddedResource resource, out string text) { text = string.Empty; byte[] resourceData; try { resourceData = resource.GetResourceData(); } catch { return false; } if (resourceData.Length == 0 || resourceData.Length > 2097152 || !LooksTextual(resourceData)) { return false; } text = Encoding.UTF8.GetString(resourceData); return !string.IsNullOrWhiteSpace(text); } private static bool LooksTextual(byte[] data) { int num = Math.Min(data.Length, 4096); int num2 = 0; for (int i = 0; i < num; i++) { byte b = data[i]; switch (b) { default: if (b < 128) { continue; } break; case 9: case 10: case 13: case 32: case 33: case 34: case 35: case 36: case 37: case 38: case 39: case 40: case 41: case 42: case 43: case 44: case 45: case 46: case 47: case 48: case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: case 58: case 59: case 60: case 61: case 62: case 63: case 64: case 65: case 66: case 67: case 68: case 69: case 70: case 71: case 72: case 73: case 74: case 75: case 76: case 77: case 78: case 79: case 80: case 81: case 82: case 83: case 84: case 85: case 86: case 87: case 88: case 89: case 90: case 91: case 92: case 93: case 94: case 95: case 96: case 97: case 98: case 99: case 100: case 101: case 102: case 103: case 104: case 105: case 106: case 107: case 108: case 109: case 110: case 111: case 112: case 113: case 114: case 115: case 116: case 117: case 118: case 119: case 120: case 121: case 122: case 123: case 124: case 125: case 126: break; } num2++; } return num == 0 || (double)num2 >= (double)num * 0.85; } private static IEnumerable EnumerateTypes(ModuleDefinition module) { Enumerator enumerator = module.Types.GetEnumerator(); try { while (enumerator.MoveNext()) { TypeDefinition type = enumerator.Current; foreach (TypeDefinition item in EnumerateTypes(type)) { yield return item; } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } } private static IEnumerable EnumerateTypes(TypeDefinition type) { yield return type; Enumerator enumerator = type.NestedTypes.GetEnumerator(); try { while (enumerator.MoveNext()) { TypeDefinition nestedType = enumerator.Current; foreach (TypeDefinition item in EnumerateTypes(nestedType)) { yield return item; } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } } private static string BuildSnippet(string text) { string text2 = text.Replace("\r", " ").Replace("\n", " ").Trim(); int num = FindFirstMarkerIndex(text2); if (num > 120) { string text3 = text2; int num2 = num - 120; text2 = text3.Substring(num2, text3.Length - num2); } if (text2.Length > 420) { text2 = text2.Substring(0, 420) + "..."; } return text2; } private static int FindFirstMarkerIndex(string text) { int num = -1; Regex[] array = new Regex[3] { AntiAnalysisPattern, SuspiciousExecutionPattern, StagedPayloadPattern }; foreach (Regex regex in array) { Match match = regex.Match(text); if (match.Success && (num < 0 || match.Index < num)) { num = match.Index; } } return num; } } public class EncodedBlobSplittingRule : IScanRule { public string Description => "Detected structured encoded blob splitting pattern (backtick/dash separator in loop)."; public Severity Severity => Severity.High; public string RuleId => "EncodedBlobSplittingRule"; public bool RequiresCompanionFinding => false; public bool IsSuspicious(MethodReference method) { return false; } public IEnumerable AnalyzeInstructions(MethodDefinition methodDef, Collection instructions, MethodSignals methodSignals) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0213: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_02cf: Unknown result type (might be due to invalid IL or missing references) //IL_02d4: Unknown result type (might be due to invalid IL or missing references) //IL_02e2: Unknown result type (might be due to invalid IL or missing references) //IL_02e7: Unknown result type (might be due to invalid IL or missing references) //IL_02f5: Unknown result type (might be due to invalid IL or missing references) //IL_02fa: Unknown result type (might be due to invalid IL or missing references) //IL_0308: Unknown result type (might be due to invalid IL or missing references) //IL_030d: Unknown result type (might be due to invalid IL or missing references) //IL_031b: Unknown result type (might be due to invalid IL or missing references) //IL_0320: Unknown result type (might be due to invalid IL or missing references) //IL_032e: Unknown result type (might be due to invalid IL or missing references) //IL_0333: Unknown result type (might be due to invalid IL or missing references) List list = new List(); try { bool flag = false; int num = -1; char c = '\0'; HashSet hashSet = new HashSet { 96, 45, 46 }; for (int i = 0; i < instructions.Count; i++) { Instruction val = instructions[i]; if (!(val.OpCode == OpCodes.Callvirt)) { continue; } object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 == null || ((MemberReference)val2).DeclaringType == null || !(((MemberReference)((MemberReference)val2).DeclaringType).FullName == "System.String") || !(((MemberReference)val2).Name == "Split") || val2.Parameters.Count < 1 || val2.Parameters.Count > 2) { continue; } for (int j = Math.Max(0, i - 10); j < i; j++) { Instruction val3 = instructions[j]; if (val3.OpCode == OpCodes.Ldc_I4_S && val3.Operand is sbyte b) { if (hashSet.Contains(b)) { flag = true; num = i; c = (char)b; break; } } else if (val3.OpCode == OpCodes.Ldc_I4 && val3.Operand is int num2 && true && hashSet.Contains(num2)) { flag = true; num = i; c = (char)num2; break; } } } if (!flag || num < 0) { return list; } bool flag2 = false; int num3 = -1; int num4 = -1; for (int k = num + 1; k < instructions.Count - 1; k++) { Instruction val4 = instructions[k]; if (val4.OpCode == OpCodes.Clt) { for (int l = k + 1; l < Math.Min(instructions.Count, k + 5); l++) { Instruction val5 = instructions[l]; if (!(val5.OpCode == OpCodes.Brtrue) && !(val5.OpCode == OpCodes.Brtrue_S)) { continue; } object operand2 = val5.Operand; Instruction val6 = (Instruction)((operand2 is Instruction) ? operand2 : null); if (val6 == null) { continue; } int num5 = instructions.IndexOf(val6); if (num5 < 0 || num5 >= k) { continue; } bool flag3 = false; for (int m = Math.Max(0, k - 10); m < k; m++) { Instruction val7 = instructions[m]; if (val7.OpCode == OpCodes.Ldloc || val7.OpCode == OpCodes.Ldloc_0 || val7.OpCode == OpCodes.Ldloc_1 || val7.OpCode == OpCodes.Ldloc_2 || val7.OpCode == OpCodes.Ldloc_3 || val7.OpCode == OpCodes.Ldloc_S) { flag3 = true; break; } } if (flag3) { flag2 = true; num3 = num5; num4 = l; break; } } } if (flag2) { break; } } if (flag2 && num3 >= 0) { StringBuilder stringBuilder = new StringBuilder(); int num6 = Math.Max(0, num - 3); int num7 = Math.Min(instructions.Count, num4 + 3); for (int n = num6; n < num7; n++) { if (n == num || (n >= num3 && n <= num4)) { stringBuilder.Append(">>> "); } else { stringBuilder.Append(" "); } stringBuilder.AppendLine(((object)instructions[n]).ToString()); } if (1 == 0) { } string text = c switch { '`' => "backtick (`)", '.' => "dot (.)", _ => "dash (-)", }; if (1 == 0) { } string text2 = text; list.Add(new ScanFinding(((MemberReference)methodDef.DeclaringType).FullName + "." + ((MemberReference)methodDef).Name, "Detected structured encoded blob splitting pattern (" + text2 + " separator in loop)", Severity.High, stringBuilder.ToString().TrimEnd())); } } catch { } return list; } } public class EncodedStringLiteralRule : IScanRule { private static readonly Regex DashSeparatedPattern = new Regex("^\\d{2,3}(-\\d{2,3}){10,}$", RegexOptions.Compiled); private static readonly Regex DotSeparatedPattern = new Regex("^\\d{2,3}(\\.\\d{2,3}){10,}$", RegexOptions.Compiled); private static readonly Regex BacktickSeparatedPattern = new Regex("^\\d{2,3}(`\\d{2,3}){10,}$", RegexOptions.Compiled); private static readonly Regex UnderscoreSeparatedPattern = new Regex("^\\d{2,3}(_\\d{2,3}){10,}$", RegexOptions.Compiled); private static readonly string[] SuspiciousKeywords = new string[26] { "Process", "ProcessStartInfo", "powershell", "cmd.exe", "Start", "Execute", "Shell", ".ps1", ".bat", ".exe", "WindowStyle", "Hidden", "ExecutionPolicy", "Invoke-WebRequest", "DownloadFile", "FromBase64String", "Assembly.Load", "Reflection", "GetMethod", "CreateInstance", "Activator", "AppData", "Startup", "Registry", "RunOnce", "CurrentVersion\\Run" }; public string Description => "Detected numeric-encoded string literals (potential obfuscated payload)."; public Severity Severity => Severity.High; public string RuleId => "EncodedStringLiteralRule"; public bool RequiresCompanionFinding => false; public bool IsSuspicious(MethodReference method) { return false; } public IEnumerable AnalyzeStringLiteral(string literal, MethodDefinition method, int instructionIndex) { if (string.IsNullOrWhiteSpace(literal)) { yield break; } InvisibleUnicodeAnalyzer.InvisibleUnicodeAnalysis invisibleUnicodeAnalysis = InvisibleUnicodeAnalyzer.Analyze(literal); if (invisibleUnicodeAnalysis.HasVariationSelectorPayload && !string.IsNullOrWhiteSpace(invisibleUnicodeAnalysis.DecodedText) && ContainsSuspiciousContent(invisibleUnicodeAnalysis.DecodedText)) { yield return new ScanFinding($"{((MemberReference)method.DeclaringType).FullName}.{((MemberReference)method).Name}:{instructionIndex}", "Invisible Unicode payload string with suspicious decoded content detected. Decoded: " + invisibleUnicodeAnalysis.DecodedText, Severity.Critical, $"Variation selectors: {invisibleUnicodeAnalysis.VariationSelectorCount}\n" + "Decoded: " + invisibleUnicodeAnalysis.DecodedText); yield break; } if (IsEncodedString(literal)) { string decoded = DecodeNumericString(literal); if (decoded != null && ContainsSuspiciousContent(decoded)) { yield return new ScanFinding($"{((MemberReference)method.DeclaringType).FullName}.{((MemberReference)method).Name}:{instructionIndex}", "Numeric-encoded string with suspicious content detected. Decoded: " + decoded, Severity.High, "Encoded: " + literal + "\nDecoded: " + decoded); yield break; } } string multiLevelResult = TryDecodeMultiLevelString(literal); if (multiLevelResult != null && ContainsSuspiciousContent(multiLevelResult)) { string truncatedEncoded = ((literal.Length > 200) ? (literal.Substring(0, 200) + "...") : literal); string truncatedDecoded = ((multiLevelResult.Length > 500) ? (multiLevelResult.Substring(0, 500) + "...") : multiLevelResult); yield return new ScanFinding($"{((MemberReference)method.DeclaringType).FullName}.{((MemberReference)method).Name}:{instructionIndex}", "Multi-level numeric-encoded string with suspicious content detected. Decoded segments: " + truncatedDecoded, Severity.Critical, "Encoded: " + truncatedEncoded + "\nDecoded: " + truncatedDecoded); } } public IEnumerable AnalyzeAssemblyMetadata(AssemblyDefinition assembly) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) List list = new List(); try { Enumerator enumerator = assembly.CustomAttributes.GetEnumerator(); try { while (enumerator.MoveNext()) { CustomAttribute current = enumerator.Current; if (!current.HasConstructorArguments) { continue; } Enumerator enumerator2 = current.ConstructorArguments.GetEnumerator(); try { while (enumerator2.MoveNext()) { CustomAttributeArgument current2 = enumerator2.Current; if (((CustomAttributeArgument)(ref current2)).Value is string value && !string.IsNullOrWhiteSpace(value)) { AnalyzeMetadataString(((MemberReference)current.AttributeType).Name, value, list); } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } } catch { } return list; } private static void AnalyzeMetadataString(string attributeName, string value, List findings) { InvisibleUnicodeAnalyzer.InvisibleUnicodeAnalysis invisibleUnicodeAnalysis = InvisibleUnicodeAnalyzer.Analyze(value); if (invisibleUnicodeAnalysis.HasVariationSelectorPayload && !string.IsNullOrWhiteSpace(invisibleUnicodeAnalysis.DecodedText) && ContainsSuspiciousContent(invisibleUnicodeAnalysis.DecodedText)) { findings.Add(new ScanFinding("Assembly Metadata: " + attributeName, "Hidden invisible Unicode payload in assembly metadata. Decoded content: " + invisibleUnicodeAnalysis.DecodedText, Severity.Critical, $"Variation selectors: {invisibleUnicodeAnalysis.VariationSelectorCount}\n" + "Decoded: " + invisibleUnicodeAnalysis.DecodedText)); return; } if (IsEncodedString(value)) { string text = DecodeNumericString(value); if (text != null && ContainsSuspiciousContent(text)) { findings.Add(new ScanFinding("Assembly Metadata: " + attributeName, "Hidden payload in assembly metadata attribute. Decoded content: " + text, Severity.Critical, "Encoded: " + Truncate(value, 500) + "\nDecoded: " + Truncate(text, 500))); } return; } if (value.Contains('.') && value.Split('.').Length >= 10) { string text2 = DecodeNumericString(value); if (text2 != null && ContainsSuspiciousContent(text2)) { findings.Add(new ScanFinding("Assembly Metadata: " + attributeName, "Hidden payload in assembly metadata attribute. Decoded content: " + text2, Severity.Critical, "Encoded: " + Truncate(value, 500) + "\nDecoded: " + Truncate(text2, 500))); } return; } string text3 = TryDecodeMultiLevelString(value); if (text3 != null && ContainsSuspiciousContent(text3)) { string text4 = Truncate(text3, 500); findings.Add(new ScanFinding("Assembly Metadata: " + attributeName, "Hidden multi-level encoded payload in assembly metadata. Decoded: " + text4, Severity.Critical, $"Encoded length: {value.Length}\nDecoded: {text4}")); } } private static string Truncate(string value, int maxLength) { return (value.Length > maxLength) ? (value.Substring(0, maxLength) + "...") : value; } public static bool IsEncodedString(string literal) { if (string.IsNullOrWhiteSpace(literal)) { return false; } return DashSeparatedPattern.IsMatch(literal) || DotSeparatedPattern.IsMatch(literal) || BacktickSeparatedPattern.IsMatch(literal) || UnderscoreSeparatedPattern.IsMatch(literal); } public static string DecodeNumericString(string encoded) { try { char separator = '-'; if (encoded.Contains('.')) { separator = '.'; } else if (encoded.Contains('`')) { separator = '`'; } else if (encoded.Contains('_')) { separator = '_'; } string[] array = encoded.Split(separator); char[] array2 = new char[array.Length]; for (int i = 0; i < array.Length; i++) { if (int.TryParse(array[i], out var result) && result >= 0 && result <= 127) { array2[i] = (char)result; continue; } return null; } return new string(array2); } catch { return null; } } public static string? TryDecodeMultiLevelString(string literal) { if (string.IsNullOrWhiteSpace(literal) || literal.Length < 20) { return null; } char[] array = new char[4] { '`', '|', ';', '\n' }; char[] array2 = new char[4] { '-', '.', ',', '_' }; char[] array3 = array; foreach (char c in array3) { if (!literal.Contains(c)) { continue; } string[] array4 = literal.Split(c); if (array4.Length < 2) { continue; } char[] array5 = array2; foreach (char secondary in array5) { string text = TryDecodeSegments(array4, secondary); if (text != null) { return text; } } } return null; } private static string? TryDecodeSegments(string[] segments, char secondary) { List list = new List(); int num = 0; foreach (string text in segments) { string text2 = text.Trim(); if (string.IsNullOrEmpty(text2)) { continue; } string[] array = text2.Split(secondary); if (array.Length < 2) { if (int.TryParse(text2, out var result) && result >= 32 && result <= 126) { list.Add(((char)result).ToString()); num++; continue; } return null; } char[] array2 = new char[array.Length]; bool flag = true; for (int j = 0; j < array.Length; j++) { if (int.TryParse(array[j].Trim(), out var result2) && result2 >= 0 && result2 <= 127) { array2[j] = (char)result2; continue; } flag = false; break; } if (!flag) { return null; } list.Add(new string(array2)); num++; } if (num < 2) { return null; } return string.Join(" ", list); } public static bool ContainsSuspiciousContent(string decodedText) { if (string.IsNullOrWhiteSpace(decodedText)) { return false; } string[] suspiciousKeywords = SuspiciousKeywords; foreach (string value in suspiciousKeywords) { if (decodedText.Contains(value, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } } public class EncodedStringPipelineRule : IScanRule { public string Description => "Detected encoded string to char decoding pipeline (ASCII number or invisible Unicode pattern)."; public Severity Severity => Severity.High; public string RuleId => "EncodedStringPipelineRule"; public bool RequiresCompanionFinding => false; public bool IsSuspicious(MethodReference method) { return false; } public IEnumerable AnalyzeInstructions(MethodDefinition methodDef, Collection instructions, MethodSignals methodSignals) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_03cb: Unknown result type (might be due to invalid IL or missing references) //IL_03d0: Unknown result type (might be due to invalid IL or missing references) //IL_0480: Unknown result type (might be due to invalid IL or missing references) //IL_0485: Unknown result type (might be due to invalid IL or missing references) List list = new List(); try { bool flag = false; bool flag2 = false; bool flag3 = false; bool flag4 = false; bool flag5 = false; bool flag6 = false; bool flag7 = false; bool flag8 = false; bool flag9 = false; bool flag10 = false; bool flag11 = false; int num = -1; int num2 = -1; int num3 = -1; int num4 = -1; int num5 = -1; int num6 = -1; int num7 = -1; int num8 = -1; int num9 = -1; int num10 = -1; int num11 = -1; for (int i = 0; i < instructions.Count; i++) { Instruction val = instructions[i]; if (val.OpCode == OpCodes.Call || val.OpCode == OpCodes.Callvirt) { object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 != null && ((MemberReference)val2).DeclaringType != null) { string fullName = ((MemberReference)((MemberReference)val2).DeclaringType).FullName; string name = ((MemberReference)val2).Name; if (fullName == "System.Int32" && name == "Parse" && val2.Parameters.Count == 1 && ((MemberReference)((ParameterReference)val2.Parameters[0]).ParameterType).FullName == "System.String") { flag = true; num = i; } if (fullName == "System.Linq.Enumerable" && name == "Select") { GenericInstanceMethod val3 = (GenericInstanceMethod)(object)((val2 is GenericInstanceMethod) ? val2 : null); if (val3 != null && val3.GenericArguments.Count == 2) { string fullName2 = ((MemberReference)val3.GenericArguments[0]).FullName; string fullName3 = ((MemberReference)val3.GenericArguments[1]).FullName; if (fullName2 == "System.String" && fullName3 == "System.Char") { flag3 = true; num3 = i; } } } if (fullName == "System.String" && name == "Concat") { GenericInstanceMethod val4 = (GenericInstanceMethod)(object)((val2 is GenericInstanceMethod) ? val2 : null); if (val4 != null && val4.GenericArguments.Count == 1 && ((MemberReference)val4.GenericArguments[0]).FullName == "System.Char") { flag4 = true; num4 = i; } } if (fullName == "System.Array" && name == "ConvertAll") { GenericInstanceMethod val5 = (GenericInstanceMethod)(object)((val2 is GenericInstanceMethod) ? val2 : null); if (val5 != null && val5.GenericArguments.Count == 2) { string fullName4 = ((MemberReference)val5.GenericArguments[0]).FullName; string fullName5 = ((MemberReference)val5.GenericArguments[1]).FullName; if (fullName4 == "System.String" && fullName5 == "System.Char") { flag5 = true; num5 = i; } } } if (fullName == "System.Char" && name == "ConvertToUtf32") { flag7 = true; num7 = i; } if (fullName == "System.Char" && name == "IsSurrogatePair") { flag8 = true; num8 = i; } if (fullName == "System.Text.Encoding" && name == "GetString") { flag9 = true; num9 = i; } if ((fullName == "System.Collections.Generic.List`1" || fullName.StartsWith("System.Collections.Generic.List`1", StringComparison.Ordinal)) && name == "Add" && val2.Parameters.Count == 1 && ((MemberReference)((ParameterReference)val2.Parameters[0]).ParameterType).FullName == "System.Byte") { flag10 = true; num10 = i; } } } if (val.OpCode == OpCodes.Newobj) { object operand2 = val.Operand; MethodReference val6 = (MethodReference)((operand2 is MethodReference) ? operand2 : null); if (val6 != null) { TypeReference declaringType = ((MemberReference)val6).DeclaringType; if (((declaringType != null) ? ((MemberReference)declaringType).FullName : null) == "System.String" && val6.Parameters.Count == 1 && ((MemberReference)((ParameterReference)val6.Parameters[0]).ParameterType).FullName == "System.Char[]") { flag6 = true; num6 = i; } } } if (flag && num >= 0 && i > num && i <= num + 3 && val.OpCode == OpCodes.Conv_U2) { flag2 = true; num2 = i; } if (!flag11 && TryResolveInt32Literal(val, out var value) && IsVariationSelectorBoundary(value)) { flag11 = true; num11 = i; } } if (flag3 && flag4 && num3 < num4) { bool flag12 = flag && flag2 && num < num2; StringBuilder stringBuilder = new StringBuilder(); int num12 = Math.Max(0, Math.Min(flag12 ? num : num3, num3) - 2); int num13 = Math.Min(instructions.Count, num4 + 3); for (int j = num12; j < num13; j++) { if (j == num3 || j == num4 || (flag12 && (j == num || j == num2))) { stringBuilder.Append(">>> "); } else { stringBuilder.Append(" "); } stringBuilder.AppendLine(((object)instructions[j]).ToString()); } list.Add(new ScanFinding(((MemberReference)methodDef.DeclaringType).FullName + "." + ((MemberReference)methodDef).Name, "Detected encoded string to char decoding pipeline (Select → Concat)", Severity.High, stringBuilder.ToString().TrimEnd())); } if (flag5 && flag6 && num5 < num6) { StringBuilder stringBuilder2 = new StringBuilder(); int num14 = Math.Max(0, num5 - 3); int num15 = Math.Min(instructions.Count, num6 + 3); for (int k = num14; k < num15; k++) { if (k == num5 || k == num6) { stringBuilder2.Append(">>> "); } else { stringBuilder2.Append(" "); } stringBuilder2.AppendLine(((object)instructions[k]).ToString()); } list.Add(new ScanFinding(((MemberReference)methodDef.DeclaringType).FullName + "." + ((MemberReference)methodDef).Name, "Detected encoded string to char decoding pipeline (Array.ConvertAll → new String(Char[]))", Severity.High, stringBuilder2.ToString().TrimEnd())); } if (flag7 && flag9 && flag10 && flag11) { List list2 = (from index in new int[5] { num7, num8, num11, num10, num9 }.Where((int index) => index >= 0).Distinct() orderby index select index).ToList(); int num16 = Math.Max(0, list2.First() - 2); int num17 = Math.Min(instructions.Count, list2.Last() + 3); StringBuilder stringBuilder3 = new StringBuilder(); for (int num18 = num16; num18 < num17; num18++) { stringBuilder3.Append(list2.Contains(num18) ? ">>> " : " "); stringBuilder3.AppendLine(((object)instructions[num18]).ToString()); } string text = (flag8 ? "variation-selector Unicode decode pipeline with surrogate-pair handling" : "variation-selector Unicode decode pipeline"); list.Add(new ScanFinding(((MemberReference)methodDef.DeclaringType).FullName + "." + ((MemberReference)methodDef).Name, "Detected encoded string to char decoding pipeline (" + text + ")", Severity.Critical, stringBuilder3.ToString().TrimEnd())); } } catch { } return list; } private static bool IsVariationSelectorBoundary(int value) { return value == 65024 || value == 65039 || value == 917760 || value == 917999; } private static bool TryResolveInt32Literal(Instruction instruction, out int value) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) if (instruction.OpCode == OpCodes.Ldc_I4 && instruction.Operand is int num) { value = num; return true; } if (instruction.OpCode == OpCodes.Ldc_I4_S && instruction.Operand is sbyte b) { value = b; return true; } if (instruction.OpCode == OpCodes.Ldc_I4_M1) { value = -1; return true; } if (instruction.OpCode == OpCodes.Ldc_I4_0) { value = 0; return true; } if (instruction.OpCode == OpCodes.Ldc_I4_1) { value = 1; return true; } if (instruction.OpCode == OpCodes.Ldc_I4_2) { value = 2; return true; } if (instruction.OpCode == OpCodes.Ldc_I4_3) { value = 3; return true; } if (instruction.OpCode == OpCodes.Ldc_I4_4) { value = 4; return true; } if (instruction.OpCode == OpCodes.Ldc_I4_5) { value = 5; return true; } if (instruction.OpCode == OpCodes.Ldc_I4_6) { value = 6; return true; } if (instruction.OpCode == OpCodes.Ldc_I4_7) { value = 7; return true; } if (instruction.OpCode == OpCodes.Ldc_I4_8) { value = 8; return true; } value = 0; return false; } } public class HexStringRule : IScanRule { private static readonly Regex HexPattern = new Regex("^[0-9A-Fa-f]{12,}$", RegexOptions.Compiled); public string Description => "Detected hexadecimal encoded string (potential obfuscated payload)."; public Severity Severity => Severity.Medium; public string RuleId => "HexStringRule"; public bool RequiresCompanionFinding => false; public IDeveloperGuidance? DeveloperGuidance => new DeveloperGuidance("If storing assets, consider embedding them as an embedded resource file.", null, new string[2] { "Assembly.GetManifestResourceStream", "File.ReadAllBytes" }); public bool IsSuspicious(MethodReference method) { if (((method != null) ? ((MemberReference)method).DeclaringType : null) == null) { return false; } if (((MemberReference)method).Name == "FromHexString" && ((MemberReference)((MemberReference)method).DeclaringType).Name == "Convert") { return true; } return false; } public IEnumerable AnalyzeStringLiteral(string literal, MethodDefinition method, int instructionIndex) { if (!string.IsNullOrWhiteSpace(literal) && literal.Length % 2 == 0 && HexPattern.IsMatch(literal)) { string decoded = DecodeHexString(literal); if (decoded != null && EncodedStringLiteralRule.ContainsSuspiciousContent(decoded)) { yield return new ScanFinding($"{((MemberReference)method.DeclaringType).FullName}.{((MemberReference)method).Name}:{instructionIndex}", "Hex-encoded string with suspicious content detected. Decoded: " + decoded, Severity.High, "Encoded: " + literal + "\nDecoded: " + decoded); } } } public IEnumerable AnalyzeContextualPattern(MethodReference calledMethod, Collection instructions, int index, MethodSignals signals) { yield break; } private string DecodeHexString(string hex) { try { byte[] array = new byte[hex.Length / 2]; for (int i = 0; i < hex.Length; i += 2) { array[i / 2] = byte.Parse(hex.Substring(i, 2), NumberStyles.HexNumber); } return Encoding.UTF8.GetString(array); } catch { return null; } } } public class ObfuscatedReflectiveExecutionRule : IScanRule { private sealed class CrossMethodReflectionClusterEvidence { public bool HasNumericStringReconstruction { get; set; } public bool HasRuntimeTypeEnumeration { get; set; } public bool HasActivatorStaging { get; set; } public bool HasReflectedPropertyAssignment { get; set; } public bool HasReflectionInvoke { get; set; } public string? NumericDecodeLocation { get; set; } public string? TypeEnumerationLocation { get; set; } public string? ActivatorLocation { get; set; } public string? PropertyAssignmentLocation { get; set; } public string? ReflectionInvokeLocation { get; set; } public bool ShouldReport => HasNumericStringReconstruction && HasRuntimeTypeEnumeration && HasActivatorStaging && HasReflectedPropertyAssignment && HasReflectionInvoke; } private sealed class RemoteConfigTempCmdStagerEvidence { public bool HasRemoteConfigUrl { get; set; } public bool HasHexDecode { get; set; } public bool HasStringReconstruction { get; set; } public bool HasReflectedTypeResolution { get; set; } public bool HasReflectedMethodLookup { get; set; } public bool HasReflectedPropertyAssignment { get; set; } public bool HasReflectionInvoke { get; set; } public bool HasActivatorStaging { get; set; } public bool HasReflectedNetworkDownload { get; set; } public bool HasTempScriptStaging { get; set; } public bool HasHiddenCmdProcessLaunch { get; set; } public string? RemoteConfigLocation { get; set; } public string? HexDecodeLocation { get; set; } public string? ByteStringDecodeLocation { get; set; } public string? PropertyAssignmentLocation { get; set; } public string? ReflectionInvokeLocation { get; set; } public bool ShouldReport => HasRemoteConfigUrl && HasHexDecode && HasStringReconstruction && HasReflectedTypeResolution && HasReflectedMethodLookup && HasReflectedPropertyAssignment && HasReflectionInvoke && HasActivatorStaging && HasReflectedNetworkDownload && HasTempScriptStaging && HasHiddenCmdProcessLaunch; } private const int MinimumDecodeScore = 25; private const int MinimumSinkScore = 35; private const int MinimumTotalScore = 70; private const int ReflectionOnlyDangerFloor = 10; private const int ReflectionOnlyDecodeFloor = 45; public string Description => "Detected correlated obfuscation/decode behavior reaching reflective or staged execution sinks."; public Severity Severity => Severity.High; public string RuleId => "ObfuscatedReflectiveExecutionRule"; public bool RequiresCompanionFinding => false; public bool IsSuspicious(MethodReference method) { return false; } public IEnumerable AnalyzeInstructions(MethodDefinition methodDef, Collection instructions, MethodSignals methodSignals) { if (methodDef == null || instructions == null || instructions.Count == 0) { return Enumerable.Empty(); } ObfuscatedExecutionEvidence obfuscatedExecutionEvidence = ObfuscatedExecutionHeuristics.CollectEvidence(instructions); if (!ShouldReport(obfuscatedExecutionEvidence)) { return Enumerable.Empty(); } Severity severity = DetermineSeverity(obfuscatedExecutionEvidence); if (severity < Severity.High) { return Enumerable.Empty(); } int num = obfuscatedExecutionEvidence.AnchorInstructionIndex; if (num < 0 || num >= instructions.Count) { num = 0; } int offset = instructions[num].Offset; string codeSnippet = BuildSnippet(instructions, num, 2); TypeDefinition declaringType = methodDef.DeclaringType; ScanFinding scanFinding = new ScanFinding($"{((declaringType != null) ? ((MemberReference)declaringType).FullName : null)}.{((MemberReference)methodDef).Name}:{offset}", BuildDescription(obfuscatedExecutionEvidence), severity, codeSnippet) { RiskScore = obfuscatedExecutionEvidence.TotalScore, BypassCompanionCheck = (obfuscatedExecutionEvidence.TotalScore >= 85) }; return new ScanFinding[1] { scanFinding }; } public IEnumerable PostAnalysisRefine(ModuleDefinition module, IEnumerable existingFindings) { if (module == null) { return Enumerable.Empty(); } List findings = existingFindings?.ToList() ?? new List(); List list = new List(); IReadOnlyList moduleDecodedStrings = CollectDecodedStaticArrayStrings(module); foreach (IGrouping item in (from type in EnumerateTypes(module) where !string.IsNullOrWhiteSpace(((TypeReference)type).Namespace) select type).GroupBy((TypeDefinition type) => ((TypeReference)type).Namespace, StringComparer.Ordinal)) { RemoteConfigTempCmdStagerEvidence remoteConfigTempCmdStagerEvidence = CollectRemoteConfigTempCmdStagerEvidence(item, moduleDecodedStrings); if (remoteConfigTempCmdStagerEvidence.ShouldReport) { list.Add(new ScanFinding(item.Key, "Detected cross-method hex remote config reflective temp command stager: hex-encoded remote command config URLs, reflected WebClient.DownloadString, temporary .cmd script staging, File.WriteAllText, reflected ProcessStartInfo cmd.exe launch, hidden window settings, and MethodInfo.Invoke are split across helper methods.", Severity.Critical, BuildRemoteConfigTempCmdSnippet(remoteConfigTempCmdStagerEvidence)) { RuleId = RuleId, RiskScore = 96, BypassCompanionCheck = true }); } if (HasDecodedExecutionFinding(item.Key, findings)) { CrossMethodReflectionClusterEvidence crossMethodReflectionClusterEvidence = CollectCrossMethodEvidence(item); if (crossMethodReflectionClusterEvidence.ShouldReport) { list.Add(new ScanFinding(item.Key, "Detected cross-method obfuscated reflection staging cluster: numeric string reconstruction, runtime assembly/type enumeration, Activator.CreateInstance object staging, reflected property assignment, and MethodInfo.Invoke are split across helper methods around a decoded execution payload.", Severity.Critical, BuildCrossMethodSnippet(crossMethodReflectionClusterEvidence)) { RuleId = RuleId, RiskScore = 92, BypassCompanionCheck = true }); } } } return list; } private static bool ShouldReport(ObfuscatedExecutionEvidence evidence) { if (evidence.SinkScore < 35 || evidence.DecodeScore < 25) { return false; } if (!evidence.HasStrongDecodePrimitive) { return false; } if (evidence.TotalScore < 70) { return false; } bool flag = evidence.HasReflectionInvokeSink && !evidence.HasProcessLikeSink && !evidence.HasAssemblyLoadSink && !evidence.HasNativeSink; if (flag && evidence.DangerScore < 10 && evidence.DecodeScore < 45) { return false; } if (flag && !evidence.HasEncodedLiteral && !evidence.HasDangerousLiteral && !evidence.HasNetworkCall && !evidence.HasFileWriteCall && !evidence.HasSensitivePathAccess) { return false; } return true; } private static CrossMethodReflectionClusterEvidence CollectCrossMethodEvidence(IEnumerable namespaceTypes) { //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) CrossMethodReflectionClusterEvidence crossMethodReflectionClusterEvidence = new CrossMethodReflectionClusterEvidence(); foreach (TypeDefinition namespaceType in namespaceTypes) { foreach (MethodDefinition item in EnumerateMethods(namespaceType)) { if (!item.HasBody || item.Body.Instructions.Count == 0) { continue; } ObfuscatedExecutionEvidence obfuscatedExecutionEvidence = ObfuscatedExecutionHeuristics.CollectEvidence(item.Body.Instructions); if (obfuscatedExecutionEvidence.HasStrongDecodePrimitive && obfuscatedExecutionEvidence.DecodeScore >= 18) { crossMethodReflectionClusterEvidence.HasNumericStringReconstruction = true; CrossMethodReflectionClusterEvidence crossMethodReflectionClusterEvidence2 = crossMethodReflectionClusterEvidence; if (crossMethodReflectionClusterEvidence2.NumericDecodeLocation == null) { string text = (crossMethodReflectionClusterEvidence2.NumericDecodeLocation = ((MemberReference)namespaceType).FullName + "." + ((MemberReference)item).Name); } } bool flag = false; bool flag2 = false; bool flag3 = false; bool flag4 = false; bool flag5 = false; bool flag6 = false; bool flag7 = false; Enumerator enumerator3 = item.Body.Instructions.GetEnumerator(); try { while (enumerator3.MoveNext()) { Instruction current3 = enumerator3.Current; if (current3.OpCode != OpCodes.Call && current3.OpCode != OpCodes.Callvirt) { continue; } object operand = current3.Operand; MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null); if (val == null) { continue; } TypeReference declaringType = ((MemberReference)val).DeclaringType; string text3 = ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) ?? string.Empty; string text4 = ((MemberReference)val).Name ?? string.Empty; if (text3 == "System.AppDomain" && (text4 == "get_CurrentDomain" || text4 == "GetAssemblies")) { flag = true; } if (text3 == "System.Reflection.Assembly" && text4 == "GetTypes") { flag2 = true; } if (text3 == "System.Linq.Enumerable" && (text4 == "SelectMany" || text4 == "FirstOrDefault")) { flag3 = true; } if (text3 == "System.Activator" && text4 == "CreateInstance") { crossMethodReflectionClusterEvidence.HasActivatorStaging = true; CrossMethodReflectionClusterEvidence crossMethodReflectionClusterEvidence2 = crossMethodReflectionClusterEvidence; if (crossMethodReflectionClusterEvidence2.ActivatorLocation == null) { string text = (crossMethodReflectionClusterEvidence2.ActivatorLocation = ((MemberReference)namespaceType).FullName + "." + ((MemberReference)item).Name); } } if (text3 == "System.Type" && text4 == "GetProperty") { flag4 = true; } if (text3 == "System.Reflection.PropertyInfo" && text4 == "SetValue") { flag5 = true; } if (text3 == "System.Type" && text4 == "GetMethod") { flag6 = true; } if (ObfuscatedSinkMatcher.IsReflectionInvokeSink(text3, text4)) { flag7 = true; } } } finally { ((IDisposable)enumerator3/*cast due to .constrained prefix*/).Dispose(); } if (flag && (flag2 || flag3)) { crossMethodReflectionClusterEvidence.HasRuntimeTypeEnumeration = true; CrossMethodReflectionClusterEvidence crossMethodReflectionClusterEvidence2 = crossMethodReflectionClusterEvidence; if (crossMethodReflectionClusterEvidence2.TypeEnumerationLocation == null) { string text = (crossMethodReflectionClusterEvidence2.TypeEnumerationLocation = ((MemberReference)namespaceType).FullName + "." + ((MemberReference)item).Name); } } if (flag4 && flag5) { crossMethodReflectionClusterEvidence.HasReflectedPropertyAssignment = true; CrossMethodReflectionClusterEvidence crossMethodReflectionClusterEvidence2 = crossMethodReflectionClusterEvidence; if (crossMethodReflectionClusterEvidence2.PropertyAssignmentLocation == null) { string text = (crossMethodReflectionClusterEvidence2.PropertyAssignmentLocation = ((MemberReference)namespaceType).FullName + "." + ((MemberReference)item).Name); } } if (flag6 && flag7) { crossMethodReflectionClusterEvidence.HasReflectionInvoke = true; CrossMethodReflectionClusterEvidence crossMethodReflectionClusterEvidence2 = crossMethodReflectionClusterEvidence; if (crossMethodReflectionClusterEvidence2.ReflectionInvokeLocation == null) { string text = (crossMethodReflectionClusterEvidence2.ReflectionInvokeLocation = ((MemberReference)namespaceType).FullName + "." + ((MemberReference)item).Name); } } } } return crossMethodReflectionClusterEvidence; } private static RemoteConfigTempCmdStagerEvidence CollectRemoteConfigTempCmdStagerEvidence(IEnumerable namespaceTypes, IReadOnlyList moduleDecodedStrings) { //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) RemoteConfigTempCmdStagerEvidence remoteConfigTempCmdStagerEvidence = new RemoteConfigTempCmdStagerEvidence(); HashSet hashSet = new HashSet(moduleDecodedStrings, StringComparer.OrdinalIgnoreCase); foreach (TypeDefinition namespaceType in namespaceTypes) { foreach (MethodDefinition item in EnumerateMethods(namespaceType)) { if (!item.HasBody || item.Body.Instructions.Count == 0) { continue; } string text = ((MemberReference)namespaceType).FullName + "." + ((MemberReference)item).Name; List list = new List(); bool flag = false; bool flag2 = false; bool flag3 = false; bool flag4 = false; bool flag5 = false; bool flag6 = false; bool flag7 = false; bool flag8 = false; Collection instructions = item.Body.Instructions; for (int i = 0; i < instructions.Count; i++) { Instruction val = instructions[i]; if (val.OpCode == OpCodes.Ldstr && val.Operand is string text2) { list.Add(text2); hashSet.Add(text2); if (TryDecodeHexString(text2, out string decoded)) { list.Add(decoded); hashSet.Add(decoded); } } if (val.OpCode != OpCodes.Call && val.OpCode != OpCodes.Callvirt) { continue; } object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 == null) { continue; } TypeReference declaringType = ((MemberReference)val2).DeclaringType; string text3 = ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) ?? string.Empty; string text4 = ((MemberReference)val2).Name ?? string.Empty; if (text3 == "System.Text.Encoding" && text4 == "GetString") { flag = true; RemoteConfigTempCmdStagerEvidence remoteConfigTempCmdStagerEvidence2 = remoteConfigTempCmdStagerEvidence; if (remoteConfigTempCmdStagerEvidence2.ByteStringDecodeLocation == null) { string text5 = (remoteConfigTempCmdStagerEvidence2.ByteStringDecodeLocation = text); } } if (text3 == "System.Convert" && text4 == "ToByte" && HasBase16Argument(instructions, i)) { flag2 = true; RemoteConfigTempCmdStagerEvidence remoteConfigTempCmdStagerEvidence2 = remoteConfigTempCmdStagerEvidence; if (remoteConfigTempCmdStagerEvidence2.HexDecodeLocation == null) { string text5 = (remoteConfigTempCmdStagerEvidence2.HexDecodeLocation = text); } } if (text3 == "System.Type" && text4 == "GetType") { flag3 = true; } if (text3 == "System.Type" && text4 == "GetMethod") { flag4 = true; } if (text3 == "System.Type" && text4 == "GetProperty") { flag5 = true; } if (text3 == "System.Reflection.PropertyInfo" && text4 == "SetValue") { flag6 = true; } if (ObfuscatedSinkMatcher.IsReflectionInvokeSink(text3, text4)) { flag7 = true; } if (text3 == "System.Activator" && text4 == "CreateInstance") { flag8 = true; } } if (list.Any(IsRemoteConfigUrl)) { remoteConfigTempCmdStagerEvidence.HasRemoteConfigUrl = true; RemoteConfigTempCmdStagerEvidence remoteConfigTempCmdStagerEvidence2 = remoteConfigTempCmdStagerEvidence; if (remoteConfigTempCmdStagerEvidence2.RemoteConfigLocation == null) { string text5 = (remoteConfigTempCmdStagerEvidence2.RemoteConfigLocation = text); } } if (flag || flag2) { remoteConfigTempCmdStagerEvidence.HasStringReconstruction = true; } if (flag2) { remoteConfigTempCmdStagerEvidence.HasHexDecode = true; } if (flag3) { remoteConfigTempCmdStagerEvidence.HasReflectedTypeResolution = true; } if (flag4) { remoteConfigTempCmdStagerEvidence.HasReflectedMethodLookup = true; } if (flag5 && flag6) { remoteConfigTempCmdStagerEvidence.HasReflectedPropertyAssignment = true; RemoteConfigTempCmdStagerEvidence remoteConfigTempCmdStagerEvidence2 = remoteConfigTempCmdStagerEvidence; if (remoteConfigTempCmdStagerEvidence2.PropertyAssignmentLocation == null) { string text5 = (remoteConfigTempCmdStagerEvidence2.PropertyAssignmentLocation = text); } } if (flag7) { remoteConfigTempCmdStagerEvidence.HasReflectionInvoke = true; RemoteConfigTempCmdStagerEvidence remoteConfigTempCmdStagerEvidence2 = remoteConfigTempCmdStagerEvidence; if (remoteConfigTempCmdStagerEvidence2.ReflectionInvokeLocation == null) { string text5 = (remoteConfigTempCmdStagerEvidence2.ReflectionInvokeLocation = text); } } if (flag8) { remoteConfigTempCmdStagerEvidence.HasActivatorStaging = true; } } } if (hashSet.Any((string value) => ContainsMarker(value, "System.Net.WebClient", "WebClient")) && hashSet.Any((string value) => ContainsMarker(value, "DownloadString", "DownloadFile"))) { remoteConfigTempCmdStagerEvidence.HasReflectedNetworkDownload = true; } if (hashSet.Any((string value) => ContainsMarker(value, "System.IO.Path", "Path")) && hashSet.Any((string value) => ContainsMarker(value, "GetTempFileName")) && hashSet.Any((string value) => ContainsMarker(value, ".cmd", ".bat")) && hashSet.Any((string value) => ContainsMarker(value, "System.IO.File", "File")) && hashSet.Any((string value) => ContainsMarker(value, "WriteAllText", "WriteAllBytes"))) { remoteConfigTempCmdStagerEvidence.HasTempScriptStaging = true; } if (hashSet.Any((string value) => ContainsMarker(value, "System.Diagnostics.ProcessStartInfo", "ProcessStartInfo")) && hashSet.Any((string value) => ContainsMarker(value, "System.Diagnostics.Process", "Process")) && hashSet.Any((string value) => ContainsMarker(value, "cmd.exe")) && hashSet.Any((string value) => ContainsMarker(value, "/c")) && hashSet.Any((string value) => ContainsMarker(value, "WindowStyle")) && hashSet.Any((string value) => ContainsMarker(value, "Hidden")) && hashSet.Any((string value) => ContainsMarker(value, "UseShellExecute", "CreateNoWindow"))) { remoteConfigTempCmdStagerEvidence.HasHiddenCmdProcessLaunch = true; } return remoteConfigTempCmdStagerEvidence; } private static IReadOnlyList CollectDecodedStaticArrayStrings(ModuleDefinition module) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (TypeDefinition item in EnumerateTypes(module)) { Enumerator enumerator2 = item.Fields.GetEnumerator(); try { while (enumerator2.MoveNext()) { FieldDefinition current2 = enumerator2.Current; if (current2.HasFieldRVA && current2.InitialValue != null && current2.InitialValue.Length >= 3 && TryDecodePrintableBytes(current2.InitialValue, out string decoded)) { hashSet.Add(decoded); } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } return hashSet.ToList(); } private static bool TryDecodePrintableBytes(byte[] bytes, out string decoded) { decoded = string.Empty; if (bytes.Length < 3 || bytes.Length > 4096) { return false; } string text = Encoding.ASCII.GetString(bytes).TrimEnd('\0'); if (text.Length < 3) { return false; } int num = text.Count((char c) => (c >= ' ' && c <= '~') || c == '\t' || c == '\r' || c == '\n'); if ((double)num / (double)text.Length < 0.85) { return false; } decoded = text; return true; } private static bool TryDecodeHexString(string literal, out string decoded) { decoded = string.Empty; if (!ObfuscatedDecodeMatcher.IsHexLikeLiteral(literal)) { return false; } string text = literal.Replace("0x", string.Empty, StringComparison.OrdinalIgnoreCase).Replace("-", string.Empty, StringComparison.Ordinal).Replace(":", string.Empty, StringComparison.Ordinal) .Replace(" ", string.Empty, StringComparison.Ordinal); try { byte[] array = new byte[text.Length / 2]; for (int i = 0; i < text.Length; i += 2) { array[i / 2] = byte.Parse(text.Substring(i, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); } return TryDecodePrintableBytes(array, out decoded); } catch { return false; } } private static bool HasBase16Argument(Collection instructions, int callIndex) { int num = Math.Max(0, callIndex - 6); for (int num2 = callIndex - 1; num2 >= num; num2--) { if (instructions[num2].TryResolveInt32Literal(out var value) && value == 16) { return true; } } return false; } private static bool IsRemoteConfigUrl(string value) { return (value.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || value.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) && (value.Contains("paste", StringComparison.OrdinalIgnoreCase) || value.Contains("gist", StringComparison.OrdinalIgnoreCase) || value.Contains("raw", StringComparison.OrdinalIgnoreCase) || value.Contains("file.io", StringComparison.OrdinalIgnoreCase) || value.Contains("transfer", StringComparison.OrdinalIgnoreCase) || value.Contains("config", StringComparison.OrdinalIgnoreCase)); } private static bool ContainsMarker(string value, params string[] markers) { return markers.Any((string marker) => value.IndexOf(marker, StringComparison.OrdinalIgnoreCase) >= 0); } private static bool HasDecodedExecutionFinding(string namespaceName, IReadOnlyCollection findings) { return findings.Any((ScanFinding finding) => finding.Severity >= Severity.High && string.Equals(finding.RuleId, "EncodedStringLiteralRule", StringComparison.Ordinal) && finding.Location.StartsWith(namespaceName + ".", StringComparison.Ordinal) && ContainsExecutionMarker(finding.Description + " " + finding.CodeSnippet)); } private static bool ContainsExecutionMarker(string? text) { if (string.IsNullOrWhiteSpace(text)) { return false; } string[] source = new string[7] { "ProcessStartInfo", "Process.Start", "powershell", "cmd.exe", "MethodInfo.Invoke", "CreateNoWindow", "WindowStyle" }; return source.Any((string marker) => text.IndexOf(marker, StringComparison.OrdinalIgnoreCase) >= 0); } private static IEnumerable EnumerateTypes(ModuleDefinition module) { Enumerator enumerator = module.Types.GetEnumerator(); try { while (enumerator.MoveNext()) { TypeDefinition type = enumerator.Current; foreach (TypeDefinition item in EnumerateTypes(type)) { yield return item; } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } } private static IEnumerable EnumerateTypes(TypeDefinition type) { yield return type; Enumerator enumerator = type.NestedTypes.GetEnumerator(); try { while (enumerator.MoveNext()) { TypeDefinition nestedType = enumerator.Current; foreach (TypeDefinition item in EnumerateTypes(nestedType)) { yield return item; } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } } private static IEnumerable EnumerateMethods(TypeDefinition type) { Enumerator enumerator = type.Methods.GetEnumerator(); try { while (enumerator.MoveNext()) { yield return enumerator.Current; } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } } private static string BuildCrossMethodSnippet(CrossMethodReflectionClusterEvidence evidence) { return string.Join(Environment.NewLine, "numeric decode: " + evidence.NumericDecodeLocation, "type enumeration: " + evidence.TypeEnumerationLocation, "activator staging: " + evidence.ActivatorLocation, "property assignment: " + evidence.PropertyAssignmentLocation, "reflection invoke: " + evidence.ReflectionInvokeLocation); } private static string BuildRemoteConfigTempCmdSnippet(RemoteConfigTempCmdStagerEvidence evidence) { return string.Join(Environment.NewLine, "remote config: " + evidence.RemoteConfigLocation, "hex decode: " + evidence.HexDecodeLocation, "byte string decode: " + evidence.ByteStringDecodeLocation, "property assignment: " + evidence.PropertyAssignmentLocation, "reflection invoke: " + evidence.ReflectionInvokeLocation, "staging: WebClient.DownloadString -> GetTempFileName + .cmd -> File.WriteAllText", "execution: ProcessStartInfo FileName=cmd.exe Arguments=/c WindowStyle=Hidden UseShellExecute=True"); } private static Severity DetermineSeverity(ObfuscatedExecutionEvidence evidence) { bool flag = evidence.HasProcessLikeSink || evidence.HasAssemblyLoadSink || evidence.HasNativeSink; bool flag2 = evidence.HasDangerousLiteral || evidence.DangerScore >= 15 || (evidence.HasNetworkCall && evidence.HasFileWriteCall); if (evidence.TotalScore >= 90 && flag && flag2) { return Severity.Critical; } return Severity.High; } private static string BuildDescription(ObfuscatedExecutionEvidence evidence) { string text = BuildReasonSegment(evidence.DecodeReasons, "decode evidence"); string text2 = BuildReasonSegment(evidence.SinkReasons, "sink evidence"); string text3 = BuildReasonSegment(evidence.DangerReasons, "context evidence"); return $"Detected correlated obfuscation/decode behavior that reaches reflective or staged execution (score {evidence.TotalScore}): {text}; {text2}; {text3}."; } private static string BuildReasonSegment(IReadOnlyList reasons, string fallback) { if (reasons.Count == 0) { return fallback; } return string.Join(", ", reasons.Take(3)); } private static string BuildSnippet(Collection instructions, int centerIndex, int context) { StringBuilder stringBuilder = new StringBuilder(); int num = Math.Max(0, centerIndex - context); int num2 = Math.Min(instructions.Count - 1, centerIndex + context); for (int i = num; i <= num2; i++) { stringBuilder.Append((i == centerIndex) ? ">>> " : " "); stringBuilder.AppendLine(((object)instructions[i]).ToString()); } return stringBuilder.ToString().TrimEnd(); } } public class PersistenceRule : IScanRule { private static readonly HashSet SuspiciousPayloadExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) { ".exe", ".dll", ".bat", ".cmd", ".ps1", ".vbs", ".js", ".hta", ".scr", ".com" }; public string Description => "Detected file write to %TEMP% folder (companion finding)."; public Severity Severity => Severity.Medium; public string RuleId => "PersistenceRule"; public bool RequiresCompanionFinding => true; public IDeveloperGuidance? DeveloperGuidance => new DeveloperGuidance("Use your mod framework's configuration system instead of direct file I/O. For MelonLoader: use MelonPreferences. For BepInEx: use Config.Bind(). For save data, use the game's official persistence APIs if available. Do not write executable files to TEMP or other system folders.", null, new string[3] { "MelonPreferences.CreateEntry (MelonLoader)", "Config.Bind (BepInEx)", "UnityEngine.PlayerPrefs (Unity)" }); public static bool IsSensitiveFolder(int folderValue) { switch (folderValue) { case 7: case 24: case 36: case 37: case 38: case 42: return true; default: return false; } } public static string GetFolderName(int folderValue) { if (1 == 0) { } string result = folderValue switch { 7 => "Startup", 24 => "CommonStartup", 26 => "ApplicationData", 28 => "LocalApplicationData", 35 => "CommonApplicationData", 36 => "Windows", 37 => "System", 38 => "ProgramFiles", 42 => "ProgramFilesX86", _ => $"Folder({folderValue})", }; if (1 == 0) { } return result; } private static bool IsModFrameworkType(string typeName) { if (string.IsNullOrEmpty(typeName)) { return false; } return typeName.Contains("MelonEnvironment") || typeName.Contains("MelonLoader") || typeName.Contains("BepInEx") || typeName.Contains("BepInEx.Paths"); } public bool IsSuspicious(MethodReference method) { return false; } public IEnumerable AnalyzeContextualPattern(MethodReference method, Collection instructions, int instructionIndex, MethodSignals methodSignals) { if (((method != null) ? ((MemberReference)method).DeclaringType : null) == null) { yield break; } string typeName = ((MemberReference)((MemberReference)method).DeclaringType).FullName; string methodName = ((MemberReference)method).Name; if (!IsFileWriteOperation(typeName, methodName, instructions, instructionIndex)) { yield break; } Enumerator enumerator = instructions.GetEnumerator(); try { MethodReference calledMethod = default(MethodReference); while (enumerator.MoveNext()) { Instruction instr = enumerator.Current; int num; if (instr.OpCode == OpCodes.Call || instr.OpCode == OpCodes.Callvirt) { object operand = instr.Operand; calledMethod = (MethodReference)((operand is MethodReference) ? operand : null); num = ((calledMethod != null) ? 1 : 0); } else { num = 0; } if (num != 0) { TypeReference declaringType = ((MemberReference)calledMethod).DeclaringType; string declaringType2 = ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) ?? ""; if (IsModFrameworkType(declaringType2)) { yield break; } } calledMethod = null; } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } bool foundTempPath = false; string sensitiveFolderName = null; MethodReference pathMethod = default(MethodReference); for (int i = 0; i < instructions.Count; i++) { Instruction instr2 = instructions[i]; int num2; if (instr2.OpCode == OpCodes.Call || instr2.OpCode == OpCodes.Callvirt) { object operand = instr2.Operand; pathMethod = (MethodReference)((operand is MethodReference) ? operand : null); num2 = ((pathMethod != null) ? 1 : 0); } else { num2 = 0; } if (num2 != 0) { TypeReference declaringType3 = ((MemberReference)pathMethod).DeclaringType; string pathDeclaringType = ((declaringType3 != null) ? ((MemberReference)declaringType3).FullName : null) ?? ""; if (pathDeclaringType == "System.IO.Path" && ((MemberReference)pathMethod).Name == "GetTempPath") { foundTempPath = true; break; } if (pathDeclaringType == "System.Environment" && ((MemberReference)pathMethod).Name == "GetFolderPath") { int? folderValue = InstructionHelper.ExtractFolderPathArgument(instructions, i); if (folderValue.HasValue && IsPayloadStagingFolder(folderValue.Value)) { sensitiveFolderName = GetFolderName(folderValue.Value); } } } pathMethod = null; } bool foundSensitivePayloadWrite = sensitiveFolderName != null && HasSuspiciousPayloadPath(instructions, instructionIndex); if (foundTempPath || foundSensitivePayloadWrite) { StringBuilder snippetBuilder = new StringBuilder(); int contextLines = 2; for (int j = Math.Max(0, instructionIndex - contextLines); j < Math.Min(instructions.Count, instructionIndex + contextLines + 1); j++) { snippetBuilder.Append((j == instructionIndex) ? ">>> " : " "); snippetBuilder.AppendLine(((object)instructions[j]).ToString()); } yield return new ScanFinding(description: foundTempPath ? "Potential payload drop: Writing to TEMP folder (companion finding)" : ("Potential payload drop: Writing executable/script payload to sensitive folder " + sensitiveFolderName + " (companion finding)"), location: $"{((MemberReference)((MemberReference)method).DeclaringType).FullName}.{((MemberReference)method).Name}:{instructions[instructionIndex].Offset}", severity: Severity.Medium, codeSnippet: snippetBuilder.ToString().TrimEnd()); } } private static bool IsPayloadStagingFolder(int folderValue) { bool flag = IsSensitiveFolder(folderValue); bool flag2 = flag; if (!flag2) { bool flag3 = ((folderValue == 26 || folderValue == 28 || folderValue == 35) ? true : false); flag2 = flag3; } return flag2; } private static bool HasSuspiciousPayloadPath(Collection instructions, int instructionIndex) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) int num = Math.Max(0, instructionIndex - 12); int num2 = Math.Min(instructions.Count, instructionIndex + 3); for (int i = num; i < num2; i++) { if (!(instructions[i].OpCode != OpCodes.Ldstr)) { object operand = instructions[i].Operand; string literal = operand as string; if (literal != null && SuspiciousPayloadExtensions.Any((string ext) => literal.EndsWith(ext, StringComparison.OrdinalIgnoreCase) || literal.Contains(ext + "\"", StringComparison.OrdinalIgnoreCase) || literal.Contains(ext + "'", StringComparison.OrdinalIgnoreCase))) { return true; } } } return false; } private static bool IsFileWriteOperation(string typeName, string methodName, Collection instructions, int instructionIndex) { if (typeName.Equals("System.IO.File", StringComparison.OrdinalIgnoreCase) && (methodName.Contains("Write", StringComparison.OrdinalIgnoreCase) || methodName.Contains("Create", StringComparison.OrdinalIgnoreCase) || methodName.Contains("Append", StringComparison.OrdinalIgnoreCase))) { return true; } if (typeName.Equals("System.IO.FileStream", StringComparison.OrdinalIgnoreCase) && methodName.Equals(".ctor", StringComparison.OrdinalIgnoreCase)) { return HasWritableFileStreamArguments(instructions, instructionIndex); } if ((typeName.Equals("System.IO.StreamWriter", StringComparison.OrdinalIgnoreCase) || typeName.Equals("System.IO.BinaryWriter", StringComparison.OrdinalIgnoreCase)) && methodName.StartsWith("Write", StringComparison.OrdinalIgnoreCase)) { return true; } return false; } private static bool HasWritableFileStreamArguments(Collection instructions, int instructionIndex) { int num = Math.Max(0, instructionIndex - 8); List list = new List(); for (int i = num; i < instructionIndex; i++) { Instruction instruction = instructions[i]; if (TryGetInt32(instruction, out var value)) { list.Add(value); } } if (list.Count == 0) { return false; } if (list.Count >= 2) { int num2 = list[list.Count - 1]; if (num2 == 2 || num2 == 3) { return true; } if (num2 == 1) { return false; } } int num3 = list[list.Count - 1]; return num3 == 1 || num3 == 2 || num3 == 4 || num3 == 5 || num3 == 6; } private static bool TryGetInt32(Instruction instruction, out int value) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected I4, but got Unknown OpCode opCode = instruction.OpCode; Code code = ((OpCode)(ref opCode)).Code; Code val = code; switch (val - 21) { case 0: value = -1; return true; case 1: value = 0; return true; case 2: value = 1; return true; case 3: value = 2; return true; case 4: value = 3; return true; case 5: value = 4; return true; case 6: value = 5; return true; case 7: value = 6; return true; case 8: value = 7; return true; case 9: value = 8; return true; case 10: value = (sbyte)instruction.Operand; return true; case 11: value = (int)instruction.Operand; return true; default: value = 0; return false; } } } public class ProcessStartRule : IScanRule { private Severity _severity = Severity.Critical; private static readonly HashSet LolBinExecutables = new HashSet(StringComparer.OrdinalIgnoreCase) { "powershell.exe", "powershell", "pwsh.exe", "pwsh", "cmd.exe", "cmd", "mshta.exe", "mshta", "wscript.exe", "wscript", "cscript.exe", "cscript", "regsvr32.exe", "regsvr32", "rundll32.exe", "rundll32", "certutil.exe", "certutil", "bitsadmin.exe", "bitsadmin", "msiexec.exe", "msiexec", "svchost.exe", "svchost", "sc.exe", "sc", "schtasks.exe", "schtasks", "wmic.exe", "wmic" }; private static readonly HashSet KnownSafeTools = new HashSet(StringComparer.OrdinalIgnoreCase) { "yt-dlp.exe", "yt-dlp", "ffmpeg.exe", "ffmpeg", "ffprobe.exe", "ffprobe", "git.exe", "git", "node.exe", "node", "npm.exe", "npm", "python.exe", "python", "dotnet.exe", "dotnet" }; private static readonly Regex SuspiciousArgumentPattern = new Regex("(?i)((-|/)ep\\s+bypass|(-|/)executionpolicy\\s+bypass|(-|/)enc(odedcommand)?\\s+[A-Za-z0-9+/=]|(-|/)nop(rofile)?\\b|iex|invoke-(expression|webrequest|restmethod)|iwr\\s+|irm\\s+|\\biwx\\b|\\biwe\\b|downloadstring|downloadfile|start-bitstransfer|hidden|windowstyle\\s+hidden|(-|/)w\\s+hidden|createnowindow|net\\.webclient|system\\.net\\.webclient|curl|wget|\\bwget\\b|\\bcurl\\b|out-file|set-content|add-content|>\\s*[\\w\\\\]|out-string|base64|frombase64string)", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex TempPathPattern = new Regex("(?i)(%temp%|%tmp%|\\\\temp\\\\|\\\\tmp\\\\|gettemppath|gettempfile)", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex DownloadPattern = new Regex("https?://[^\\s\"'<>]+", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex ScriptDropExtensionPattern = new Regex("(?i)\\.(bat|cmd|ps1|vbs|js|hta)(\\b|\\s|\\\"|'|$)", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex StagedLoaderPivotPattern = new Regex("(?i)(\\s/c\\s|\\s/k\\s|start-process|\\bstart\\b|\\bcall\\b|&&|\\|)", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex StagedLoaderDownloadCommandPattern = new Regex("(?i)(\\biwr\\b|invoke-webrequest|\\birm\\b|invoke-restmethod|\\biex\\b|invoke-expression|\\biwx\\b|\\biwe\\b|downloadstring|downloadfile|start-bitstransfer|new-object\\s+net\\.webclient|\\bcurl\\b|\\bwget\\b)", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly HashSet SystemAssemblies = new HashSet(StringComparer.OrdinalIgnoreCase) { "mscorlib", "System", "System.Core", "netstandard", "System.Runtime", "System.Diagnostics.Process" }; public string Description => "Detected Process.Start call which could execute arbitrary programs."; public Severity Severity => _severity; public string RuleId => "ProcessStartRule"; public bool RequiresCompanionFinding => false; public IDeveloperGuidance? DeveloperGuidance => new DeveloperGuidance("Game mods should not start external processes as this can be used to execute malware. For legitimate use cases like opening folders, use Process.Start(\"explorer.exe\", path). For restart functionality, use Process.GetCurrentProcess().MainModule.FileName.", null, new string[2] { "Process.Start(\"explorer.exe\", folderPath) - opens folder in Explorer", "Process.GetCurrentProcess().MainModule.FileName - gets current executable for restart" }); private static bool IsSystemAssembly(string assemblyName) { if (string.IsNullOrEmpty(assemblyName)) { return false; } if (SystemAssemblies.Contains(assemblyName)) { return true; } if (assemblyName.StartsWith("System.", StringComparison.OrdinalIgnoreCase) || assemblyName.StartsWith("Microsoft.", StringComparison.OrdinalIgnoreCase)) { return true; } if (assemblyName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) { string item = assemblyName.Substring(0, assemblyName.Length - 4); if (SystemAssemblies.Contains(item)) { return true; } } return false; } public bool IsSuspicious(MethodReference method) { if (((method != null) ? ((MemberReference)method).DeclaringType : null) == null) { return false; } string fullName = ((MemberReference)((MemberReference)method).DeclaringType).FullName; string name = ((MemberReference)method).Name; return (fullName.Contains("System.Diagnostics.Process") && name == "Start") || (fullName.Contains("Process") && name == "Start"); } public IEnumerable AnalyzeContextualPattern(MethodReference method, Collection instructions, int instructionIndex, MethodSignals methodSignals) { if (((method != null) ? ((MemberReference)method).DeclaringType : null) == null || !IsSuspicious(method)) { yield break; } TypeReference declaringType = ((MemberReference)method).DeclaringType; if (((declaringType != null) ? declaringType.Scope : null) != null) { string assemblyName = declaringType.Scope.Name; if (IsSystemAssembly(assemblyName)) { yield break; } } string target = ExtractProcessTarget(null, method, instructions, instructionIndex); string arguments = ExtractProcessArguments(null, instructions, instructionIndex); bool? useShell; bool hasUseShell = InstructionValueResolver.TryResolveUseShellExecute(null, instructions, instructionIndex, out useShell); bool? createNoWin; bool hasCreateNoWin = InstructionValueResolver.TryResolveCreateNoWindow(null, instructions, instructionIndex, out createNoWin); int? windowStyle; bool hasWindowStyle = InstructionValueResolver.TryResolveWindowStyle(null, instructions, instructionIndex, out windowStyle); string workingDir; bool hasWorkingDir = InstructionValueResolver.TryResolveWorkingDirectory(null, instructions, instructionIndex, out workingDir); bool useShellExecute = hasUseShell && useShell == true; bool createNoWindow = hasCreateNoWin && createNoWin == true; bool windowStyleHidden = hasWindowStyle && windowStyle == 1; bool workingDirectoryIsTemp = hasWorkingDir && workingDir != null && TempPathPattern.IsMatch(workingDir); bool hasUseShellExecuteIndicator = hasUseShell || HasProcessStartInfoSetter(instructions, instructionIndex, "set_UseShellExecute"); bool hasCreateNoWindowIndicator = hasCreateNoWin || HasProcessStartInfoSetter(instructions, instructionIndex, "set_CreateNoWindow"); bool hasWindowStyleIndicator = hasWindowStyle || HasProcessStartInfoSetter(instructions, instructionIndex, "set_WindowStyle"); bool hasWorkingDirectoryIndicator = hasWorkingDir || HasProcessStartInfoSetter(instructions, instructionIndex, "set_WorkingDirectory"); bool hasRedirectStandardInputIndicator = HasProcessStartInfoSetter(instructions, instructionIndex, "set_RedirectStandardInput"); bool hasRedirectStandardOutputIndicator = HasProcessStartInfoSetter(instructions, instructionIndex, "set_RedirectStandardOutput"); bool hasRedirectStandardErrorIndicator = HasProcessStartInfoSetter(instructions, instructionIndex, "set_RedirectStandardError"); string targetLower = target.ToLowerInvariant().Trim('"'); string argumentsLower = arguments.ToLowerInvariant(); var (severity, riskReason) = DetermineSeverity(targetLower, argumentsLower, target, arguments, useShellExecute, createNoWindow, windowStyleHidden, workingDirectoryIsTemp, hasUseShellExecuteIndicator, hasCreateNoWindowIndicator, hasWindowStyleIndicator, hasWorkingDirectoryIndicator, hasRedirectStandardInputIndicator, hasRedirectStandardOutputIndicator, hasRedirectStandardErrorIndicator, methodSignals?.HasNetworkCall ?? false, methodSignals?.HasFileWrite ?? false); if (severity.HasValue) { string description = "Detected Process.Start call which could execute arbitrary programs. Target: " + target + ". Arguments: " + arguments; List evasionIndicators = new List(); AddProcessStartInfoIndicators(evasionIndicators, useShellExecute, createNoWindow, windowStyleHidden, workingDirectoryIsTemp, hasUseShellExecuteIndicator, hasCreateNoWindowIndicator, hasWindowStyleIndicator, hasWorkingDirectoryIndicator); if (evasionIndicators.Count > 0) { description = description + " [Evasion: " + string.Join(", ", evasionIndicators) + "]"; } if (!string.IsNullOrEmpty(riskReason)) { description = description + " [" + riskReason + "]"; } StringBuilder snippetBuilder = new StringBuilder(); int contextLines = 2; for (int j = Math.Max(0, instructionIndex - contextLines); j < Math.Min(instructions.Count, instructionIndex + contextLines + 1); j++) { snippetBuilder.Append((j == instructionIndex) ? ">>> " : " "); snippetBuilder.AppendLine(((object)instructions[j]).ToString()); } yield return new ScanFinding($"{((MemberReference)((MemberReference)method).DeclaringType).FullName}.{((MemberReference)method).Name}:{instructions[instructionIndex].Offset}", description, severity.Value, snippetBuilder.ToString().TrimEnd()); } } private (Severity? severity, string? reason) DetermineSeverity(string targetLower, string argumentsLower, string targetDisplay, string argumentsDisplay, bool useShellExecute = false, bool createNoWindow = false, bool windowStyleHidden = false, bool workingDirectoryIsTemp = false, bool hasUseShellExecuteIndicator = false, bool hasCreateNoWindowIndicator = false, bool hasWindowStyleIndicator = false, bool hasWorkingDirectoryIndicator = false, bool hasRedirectStandardInputIndicator = false, bool hasRedirectStandardOutputIndicator = false, bool hasRedirectStandardErrorIndicator = false, bool hasNetworkCallSignal = false, bool hasFileWriteSignal = false) { bool flag = LolBinExecutables.Contains(targetLower) || LolBinExecutables.Any((string lol) => targetLower.EndsWith("\\" + lol) || targetLower.EndsWith("/" + lol)); bool flag2 = KnownSafeTools.Contains(targetLower) || KnownSafeTools.Any((string safe) => targetLower.EndsWith("\\" + safe) || targetLower.EndsWith("/" + safe)); bool flag3 = !string.IsNullOrEmpty(argumentsLower) && argumentsLower != "" && SuspiciousArgumentPattern.IsMatch(argumentsLower); if (argumentsLower.Contains(" temp drop -> execute)"); } if (flag11) { return (severity: Severity.Critical, reason: "Staged loader chain with ProcessStartInfo execution indicators"); } return (severity: Severity.High, reason: "Potential staged loader chain (download -> temp drop -> execute)"); } if (flag2) { if ((flag4 && flag5 && flag12) || (flag12 && hasFileWriteSignal && hasNetworkCallSignal)) { return (severity: Severity.High, reason: "Known tool with suspicious download-and-execute chain"); } if (flag3 && flag12) { return (severity: Severity.High, reason: "Known tool with suspicious hidden execution arguments"); } if (flag12 || flag3 || flag4 || flag5) { return (severity: Severity.Medium, reason: "Known external tool with elevated execution context"); } return (severity: Severity.Low, reason: "Known external tool"); } if (flag14) { return (severity: Severity.Medium, reason: "Controlled child process with redirected I/O"); } if (flag10 && flag) { List list = new List(); if (useShellExecute) { list.Add("UseShellExecute"); } if (createNoWindow) { list.Add("CreateNoWindow"); } if (windowStyleHidden) { list.Add("WindowStyle.Hidden"); } if (workingDirectoryIsTemp) { list.Add("WorkingDirectory=Temp"); } return (severity: Severity.Critical, reason: "LOLBin with hidden execution (" + string.Join(", ", list) + ")"); } if (flag11 && flag) { return (severity: Severity.Critical, reason: "LOLBin with ProcessStartInfo execution indicators"); } if (flag10 && flag3) { return (severity: Severity.Critical, reason: "Process with evasion and suspicious arguments"); } if (flag11 && flag3) { return (severity: Severity.Critical, reason: "ProcessStartInfo execution with suspicious arguments"); } if (flag10 && flag4) { return (severity: Severity.Critical, reason: "Process with evasion and download URL"); } if (flag11 && flag4) { return (severity: Severity.Critical, reason: "ProcessStartInfo execution with download URL"); } if (flag10 && flag5) { return (severity: Severity.Critical, reason: "Process with evasion and temp path execution"); } if (flag11 && flag5) { return (severity: Severity.Critical, reason: "ProcessStartInfo execution with temp path indicator"); } if (flag10) { List list2 = new List(); if (useShellExecute) { list2.Add("UseShellExecute"); } if (createNoWindow) { list2.Add("CreateNoWindow"); } if (windowStyleHidden) { list2.Add("WindowStyle.Hidden"); } if (workingDirectoryIsTemp) { list2.Add("WorkingDirectory=Temp"); } return (severity: Severity.High, reason: "Hidden process execution (" + string.Join(", ", list2) + ")"); } if (flag11) { List list3 = new List(); if (hasUseShellExecuteIndicator) { list3.Add("UseShellExecute"); } if (hasCreateNoWindowIndicator) { list3.Add("CreateNoWindow"); } if (hasWindowStyleIndicator) { list3.Add("WindowStyle"); } if (hasWorkingDirectoryIndicator) { list3.Add("WorkingDirectory"); } return (severity: Severity.High, reason: "ProcessStartInfo execution indicators (" + string.Join(", ", list3) + ")"); } if (flag && flag3) { return (severity: Severity.Critical, reason: "LOLBin with suspicious arguments"); } if (flag && flag4) { return (severity: Severity.Critical, reason: "LOLBin with URL in arguments"); } if (flag && flag5) { return (severity: Severity.Critical, reason: "LOLBin with temp path execution"); } if (flag) { return (severity: Severity.High, reason: "LOLBin execution"); } if (flag3 && flag4) { if (flag12 && flag5) { return (severity: Severity.Critical, reason: "Process with hidden suspicious download-to-temp execution"); } return (severity: Severity.High, reason: "Process with suspicious download arguments"); } if (flag3) { return (severity: Severity.High, reason: "Process with suspicious arguments"); } if (flag9 && !string.IsNullOrEmpty(argumentsLower) && argumentsLower != "") { return (severity: Severity.Medium, reason: "Unknown target with arguments"); } if (flag2) { return (severity: Severity.Low, reason: "Known external tool"); } if (flag9) { return (severity: Severity.Medium, reason: "Unknown process target"); } return (severity: Severity.Medium, reason: "External process execution"); } public string GetFindingDescription(MethodReference method, Collection instructions, int instructionIndex) { return BuildFindingDescription(null, method, instructions, instructionIndex); } public string GetFindingDescription(MethodDefinition containingMethod, MethodReference method, Collection instructions, int instructionIndex) { return BuildFindingDescription(containingMethod, method, instructions, instructionIndex); } private string BuildFindingDescription(MethodDefinition? containingMethod, MethodReference method, Collection instructions, int instructionIndex) { string text = ExtractProcessTarget(containingMethod, method, instructions, instructionIndex); string text2 = ExtractProcessArguments(containingMethod, instructions, instructionIndex); bool? useShellExecute; bool flag = InstructionValueResolver.TryResolveUseShellExecute(containingMethod, instructions, instructionIndex, out useShellExecute); bool? createNoWindow; bool flag2 = InstructionValueResolver.TryResolveCreateNoWindow(containingMethod, instructions, instructionIndex, out createNoWindow); int? windowStyle; bool flag3 = InstructionValueResolver.TryResolveWindowStyle(containingMethod, instructions, instructionIndex, out windowStyle); string workingDirectory; bool flag4 = InstructionValueResolver.TryResolveWorkingDirectory(containingMethod, instructions, instructionIndex, out workingDirectory); bool useShellExecute2 = flag && useShellExecute == true; bool createNoWindow2 = flag2 && createNoWindow == true; bool windowStyleHidden = flag3 && windowStyle == 1; bool workingDirectoryIsTemp = flag4 && workingDirectory != null && TempPathPattern.IsMatch(workingDirectory); bool hasUseShellExecuteIndicator = flag || HasProcessStartInfoSetter(instructions, instructionIndex, "set_UseShellExecute"); bool hasCreateNoWindowIndicator = flag2 || HasProcessStartInfoSetter(instructions, instructionIndex, "set_CreateNoWindow"); bool hasWindowStyleIndicator = flag3 || HasProcessStartInfoSetter(instructions, instructionIndex, "set_WindowStyle"); bool hasWorkingDirectoryIndicator = flag4 || HasProcessStartInfoSetter(instructions, instructionIndex, "set_WorkingDirectory"); bool hasRedirectStandardInputIndicator = HasProcessStartInfoSetter(instructions, instructionIndex, "set_RedirectStandardInput"); bool hasRedirectStandardOutputIndicator = HasProcessStartInfoSetter(instructions, instructionIndex, "set_RedirectStandardOutput"); bool hasRedirectStandardErrorIndicator = HasProcessStartInfoSetter(instructions, instructionIndex, "set_RedirectStandardError"); string text3 = Description + " Target: " + text + ". Arguments: " + text2; string targetLower = text.ToLowerInvariant().Trim('"'); string argumentsLower = text2.ToLowerInvariant(); (Severity? severity, string? reason) tuple = DetermineSeverity(targetLower, argumentsLower, text, text2, useShellExecute2, createNoWindow2, windowStyleHidden, workingDirectoryIsTemp, hasUseShellExecuteIndicator, hasCreateNoWindowIndicator, hasWindowStyleIndicator, hasWorkingDirectoryIndicator, hasRedirectStandardInputIndicator, hasRedirectStandardOutputIndicator, hasRedirectStandardErrorIndicator); Severity? item = tuple.severity; string item2 = tuple.reason; _severity = item ?? Severity.Medium; List list = new List(); AddProcessStartInfoIndicators(list, useShellExecute2, createNoWindow2, windowStyleHidden, workingDirectoryIsTemp, hasUseShellExecuteIndicator, hasCreateNoWindowIndicator, hasWindowStyleIndicator, hasWorkingDirectoryIndicator); if (list.Count > 0) { text3 = text3 + " [Evasion: " + string.Join(", ", list) + "]"; } if (!string.IsNullOrEmpty(item2)) { text3 = text3 + " [" + item2 + "]"; } return text3; } public bool ShouldSuppressFinding(MethodReference method, Collection instructions, int instructionIndex, MethodSignals methodSignals, MethodSignals? typeSignals = null) { if ((methodSignals != null && methodSignals.HasEnvironmentVariableModification) || (typeSignals != null && typeSignals.HasEnvironmentVariableModification)) { return false; } if ((methodSignals != null && methodSignals.HasFileWrite) || (typeSignals != null && typeSignals.HasFileWrite)) { return false; } if (IsSafeBareExplorerLaunch(instructions, instructionIndex)) { return true; } if (IsSafeShellFolderLaunch(instructions, instructionIndex)) { return true; } if (IsCurrentProcessRestart(instructions, instructionIndex)) { return true; } return false; } private static bool IsSafeBareExplorerLaunch(Collection instructions, int processStartIndex) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) for (int i = Math.Max(0, processStartIndex - 10); i < processStartIndex; i++) { Instruction val = instructions[i]; if (val.OpCode == OpCodes.Ldstr && val.Operand is string text && text.Equals("explorer.exe", StringComparison.OrdinalIgnoreCase)) { if (text.Contains("\\") || text.Contains("/") || text.Contains(":")) { return false; } if (HasPathManipulation(instructions, i, processStartIndex)) { return false; } return true; } } return false; } private static bool IsSafeShellFolderLaunch(Collection instructions, int processStartIndex) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) if (!InstructionValueResolver.TryResolveUseShellExecute(null, instructions, processStartIndex, out var useShellExecute) || useShellExecute != true) { return false; } bool flag = false; bool flag2 = false; bool flag3 = false; int num = Math.Max(0, processStartIndex - 40); for (int i = num; i < processStartIndex; i++) { Instruction val = instructions[i]; if (val.OpCode != OpCodes.Call && val.OpCode != OpCodes.Callvirt) { continue; } object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 == null) { continue; } TypeReference declaringType = ((MemberReference)val2).DeclaringType; if (((declaringType != null) ? ((MemberReference)declaringType).FullName : null) == "System.IO.Directory" && (((MemberReference)val2).Name == "Exists" || ((MemberReference)val2).Name == "CreateDirectory")) { flag = true; continue; } TypeReference declaringType2 = ((MemberReference)val2).DeclaringType; if (((declaringType2 != null) ? ((MemberReference)declaringType2).FullName : null) == "System.Diagnostics.ProcessStartInfo" && ((MemberReference)val2).Name == "set_FileName") { flag2 = true; continue; } TypeReference declaringType3 = ((MemberReference)val2).DeclaringType; if (((declaringType3 != null) ? ((MemberReference)declaringType3).FullName : null) == "System.Diagnostics.ProcessStartInfo" && ((MemberReference)val2).Name == "set_Arguments") { flag3 = true; } } return flag && flag2 && !flag3; } private static bool HasPathManipulation(Collection instructions, int strIndex, int callIndex) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) for (int i = strIndex + 1; i < callIndex; i++) { Instruction val = instructions[i]; if (!(val.OpCode == OpCodes.Call) && !(val.OpCode == OpCodes.Callvirt)) { continue; } object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 == null) { continue; } TypeReference declaringType = ((MemberReference)val2).DeclaringType; string text = ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) ?? ""; string text2 = ((MemberReference)val2).Name ?? ""; if (text == "System.String") { switch (text2) { case "Concat": case "Format": case "Replace": goto IL_0105; } } if (!(text == "System.IO.Path") || (!(text2 == "Combine") && !(text2 == "Join") && !(text2 == "GetFullPath"))) { continue; } goto IL_0105; IL_0105: return true; } return false; } private static bool IsCurrentProcessRestart(Collection instructions, int processStartIndex) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) bool flag = false; bool flag2 = false; bool flag3 = false; int num = -1; int num2 = -1; int num3 = -1; int num4 = Math.Max(0, processStartIndex - 40); for (int i = num4; i < processStartIndex; i++) { Instruction val = instructions[i]; if (val.OpCode != OpCodes.Call && val.OpCode != OpCodes.Callvirt) { continue; } object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 == null) { continue; } TypeReference declaringType = ((MemberReference)val2).DeclaringType; string text = ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) ?? ""; string text2 = ((MemberReference)val2).Name ?? ""; if (text == "System.Diagnostics.Process" && text2 == "GetCurrentProcess") { flag = true; num = i; } else if (text == "System.Diagnostics.Process" && text2 == "get_MainModule") { if (flag && i > num) { flag2 = true; num2 = i; } } else if (text == "System.Diagnostics.ProcessModule" && text2 == "get_FileName" && flag2 && i > num2) { flag3 = true; num3 = i; } } if (flag && flag2 && flag3) { if (num3 > 0) { for (int j = num3 + 1; j < processStartIndex; j++) { Instruction val3 = instructions[j]; if (val3.OpCode != OpCodes.Call && val3.OpCode != OpCodes.Callvirt) { continue; } object operand2 = val3.Operand; MethodReference val4 = (MethodReference)((operand2 is MethodReference) ? operand2 : null); if (val4 == null) { continue; } TypeReference declaringType2 = ((MemberReference)val4).DeclaringType; string text3 = ((declaringType2 != null) ? ((MemberReference)declaringType2).FullName : null) ?? ""; string text4 = ((MemberReference)val4).Name ?? ""; if (text3 == "System.String") { switch (text4) { case "Concat": case "Format": case "Replace": goto IL_0294; } } if (!(text3 == "System.IO.Path") || (!(text4 == "Combine") && !(text4 == "Join"))) { continue; } goto IL_0294; IL_0294: return false; } } return true; } return false; } private static string ExtractProcessTarget(MethodDefinition? containingMethod, MethodReference method, Collection instructions, int processStartIndex) { if (InstructionValueResolver.TryResolveProcessTarget(containingMethod, method, instructions, processStartIndex, out string target)) { return target; } return ""; } private static string ExtractProcessArguments(MethodDefinition? containingMethod, Collection instructions, int processStartIndex) { if (InstructionValueResolver.TryResolveProcessArguments(containingMethod, instructions, processStartIndex, out string arguments)) { return arguments; } return ""; } private static bool HasProcessStartInfoSetter(Collection instructions, int processStartIndex, string setterName) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) int num = Math.Max(0, processStartIndex - 400); for (int num2 = processStartIndex - 1; num2 >= num; num2--) { Instruction val = instructions[num2]; if (!(val.OpCode != OpCodes.Call) || !(val.OpCode != OpCodes.Callvirt)) { object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 != null) { TypeReference declaringType = ((MemberReference)val2).DeclaringType; if (((declaringType != null) ? ((MemberReference)declaringType).FullName : null) == "System.Diagnostics.ProcessStartInfo" && ((MemberReference)val2).Name == setterName) { return true; } } } } return false; } private static void AddProcessStartInfoIndicators(List indicators, bool useShellExecute, bool createNoWindow, bool windowStyleHidden, bool workingDirectoryIsTemp, bool hasUseShellExecuteIndicator, bool hasCreateNoWindowIndicator, bool hasWindowStyleIndicator, bool hasWorkingDirectoryIndicator) { if (useShellExecute) { indicators.Add("UseShellExecute=true"); } else if (hasUseShellExecuteIndicator) { indicators.Add("UseShellExecute set"); } if (createNoWindow) { indicators.Add("CreateNoWindow=true"); } else if (hasCreateNoWindowIndicator) { indicators.Add("CreateNoWindow set"); } if (windowStyleHidden) { indicators.Add("WindowStyle=Hidden"); } else if (hasWindowStyleIndicator) { indicators.Add("WindowStyle set"); } if (workingDirectoryIsTemp) { indicators.Add("WorkingDirectory=Temp"); } else if (hasWorkingDirectoryIndicator) { indicators.Add("WorkingDirectory set"); } } } public class ReflectionRule : IScanRule { private static readonly HashSet SystemAssemblies = new HashSet(StringComparer.OrdinalIgnoreCase) { "mscorlib", "System", "System.Core", "netstandard", "System.Runtime", "System.Reflection" }; public string Description => "Detected reflection invocation without determinable target method (potential bypass)."; public Severity Severity => Severity.High; public string RuleId => "ReflectionRule"; public bool RequiresCompanionFinding => true; public bool IsSuspicious(MethodReference method) { if (((method != null) ? ((MemberReference)method).DeclaringType : null) == null) { return false; } string fullName = ((MemberReference)((MemberReference)method).DeclaringType).FullName; string name = ((MemberReference)method).Name; if ((fullName == "System.Reflection.MethodInfo" && name == "Invoke") || (fullName == "System.Reflection.MethodBase" && name == "Invoke")) { return true; } return false; } public IEnumerable AnalyzeInstructions(MethodDefinition method, Collection instructions, MethodSignals methodSignals) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_0486: Unknown result type (might be due to invalid IL or missing references) //IL_048b: Unknown result type (might be due to invalid IL or missing references) //IL_0432: Unknown result type (might be due to invalid IL or missing references) //IL_0437: Unknown result type (might be due to invalid IL or missing references) List list = new List(); if (!method.HasBody) { return list; } if (method.Body.HasVariables && methodSignals != null && methodSignals.HasTriggeredRuleOtherThan(RuleId)) { List list2 = new List(); Enumerator enumerator = method.Body.Variables.GetEnumerator(); try { while (enumerator.MoveNext()) { VariableDefinition current = enumerator.Current; string fullName = ((MemberReference)((VariableReference)current).VariableType).FullName; if (IsReflectionInvocationType(fullName)) { list2.Add($"{fullName} (var_{((VariableReference)current).Index})"); } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } if (list2.Count > 0) { TypeDefinition declaringType = method.DeclaringType; list.Add(new ScanFinding(((declaringType != null) ? ((MemberReference)declaringType).FullName : null) + "." + ((MemberReference)method).Name, Description + " - uses reflection types: " + string.Join(", ", list2.Take(3)) + ((list2.Count > 3) ? $" and {list2.Count - 3} more" : ""), Severity, "Reflection variable types detected: " + string.Join(", ", list2))); } } bool flag = false; bool flag2 = false; bool flag3 = false; bool flag4 = false; bool flag5 = false; bool flag6 = false; int num = -1; int num2 = -1; int num3 = -1; int num4 = -1; for (int i = 0; i < instructions.Count; i++) { Instruction val = instructions[i]; if (val.OpCode != OpCodes.Call && val.OpCode != OpCodes.Callvirt) { continue; } object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 != null && ((MemberReference)val2).DeclaringType != null) { string fullName2 = ((MemberReference)((MemberReference)val2).DeclaringType).FullName; string name = ((MemberReference)val2).Name; if (fullName2 == "System.Type" && name == "GetMethod") { flag = true; num = i; } if ((fullName2 == "System.Reflection.MethodBase" || fullName2 == "System.Reflection.MethodInfo") && name == "Invoke") { flag2 = true; num2 = i; } if (fullName2 == "System.AppDomain" && (name == "GetAssemblies" || name == "get_CurrentDomain")) { flag3 = true; num3 = i; } if (fullName2 == "System.Linq.Enumerable" && name == "SelectMany") { flag4 = true; } if (fullName2 == "System.Linq.Enumerable" && name == "FirstOrDefault") { flag5 = true; } if (name.StartsWith("GetCustomAttribute")) { flag6 = true; num4 = i; } } } if (flag && flag2 && num < num2) { TypeDefinition declaringType2 = method.DeclaringType; list.Add(new ScanFinding(((declaringType2 != null) ? ((MemberReference)declaringType2).FullName : null) + "." + ((MemberReference)method).Name, "Detected reflection execution chain: Type.GetMethod → MethodBase.Invoke (dynamic method invocation)", Severity.High, BuildSnippet(instructions, num, num2))); } if (flag3 && flag4 && flag5) { TypeDefinition declaringType3 = method.DeclaringType; list.Add(new ScanFinding(((declaringType3 != null) ? ((MemberReference)declaringType3).FullName : null) + "." + ((MemberReference)method).Name, "Detected runtime type scanning: AppDomain.GetAssemblies → SelectMany(GetTypes) → FirstOrDefault (type enumeration to resolve APIs dynamically)", Severity.High, "Method enumerates all loaded assemblies and types to find specific types at runtime, bypassing compile-time references.")); } if (flag6 && num4 >= 0) { Instruction val3 = instructions[num4]; bool flag7 = false; object operand2 = val3.Operand; MethodReference val4 = (MethodReference)((operand2 is MethodReference) ? operand2 : null); if (val4 != null) { GenericInstanceMethod val5 = (GenericInstanceMethod)(object)((val4 is GenericInstanceMethod) ? val4 : null); if (val5 != null) { Enumerator enumerator2 = val5.GenericArguments.GetEnumerator(); try { while (enumerator2.MoveNext()) { TypeReference current2 = enumerator2.Current; if (((MemberReference)current2).FullName.Contains("AssemblyMetadataAttribute")) { flag7 = true; break; } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } Enumerator enumerator3 = val4.Parameters.GetEnumerator(); try { while (enumerator3.MoveNext()) { ParameterDefinition current3 = enumerator3.Current; if (((MemberReference)((ParameterReference)current3).ParameterType).FullName.Contains("AssemblyMetadataAttribute")) { flag7 = true; break; } } } finally { ((IDisposable)enumerator3/*cast due to .constrained prefix*/).Dispose(); } } if (flag7) { TypeDefinition declaringType4 = method.DeclaringType; list.Add(new ScanFinding(((declaringType4 != null) ? ((MemberReference)declaringType4).FullName : null) + "." + ((MemberReference)method).Name, "Detected runtime access to AssemblyMetadataAttribute (potential hidden payload retrieval)", Severity.High, "Method reads AssemblyMetadataAttribute at runtime, which can be used to store and retrieve encoded payloads hidden in assembly metadata.")); } } return list; } private static string BuildSnippet(Collection instructions, int startIdx, int endIdx) { StringBuilder stringBuilder = new StringBuilder(); int num = Math.Max(0, startIdx - 2); int num2 = Math.Min(instructions.Count, endIdx + 3); for (int i = num; i < num2; i++) { stringBuilder.Append((i == startIdx || i == endIdx) ? ">>> " : " "); stringBuilder.AppendLine(((object)instructions[i]).ToString()); } return stringBuilder.ToString().TrimEnd(); } public IEnumerable AnalyzeContextualPattern(MethodReference method, Collection instructions, int instructionIndex, MethodSignals methodSignals) { if (((method != null) ? ((MemberReference)method).DeclaringType : null) == null) { yield break; } TypeReference declaringType = ((MemberReference)method).DeclaringType; if (((declaringType != null) ? declaringType.Scope : null) != null) { string assemblyName = declaringType.Scope.Name; if (IsSystemAssembly(assemblyName)) { yield break; } } if ((!(((MemberReference)((MemberReference)method).DeclaringType).FullName == "System.Reflection.MethodInfo") || !(((MemberReference)method).Name == "Invoke")) && (!(((MemberReference)((MemberReference)method).DeclaringType).FullName == "System.Reflection.MethodBase") || !(((MemberReference)method).Name == "Invoke")) && (!(((MemberReference)((MemberReference)method).DeclaringType).FullName == "System.Activator") || !((MemberReference)method).Name.StartsWith("CreateInstance")) && (!((MemberReference)((MemberReference)method).DeclaringType).FullName.Contains("Delegate") || !(((MemberReference)method).Name == "DynamicInvoke"))) { yield break; } int ldc4Count = 0; int windowStart = Math.Max(0, instructionIndex - 20); for (int i = instructionIndex - 1; i >= windowStart; i--) { Instruction instr = instructions[i]; if ((instr.OpCode == OpCodes.Call || instr.OpCode == OpCodes.Callvirt) && i != instructionIndex) { break; } if (IsLdcI4Instruction(instr)) { ldc4Count++; } else if (!(instr.OpCode != OpCodes.Nop) || !(instr.OpCode != OpCodes.Dup) || !(instr.OpCode != OpCodes.Pop) || IsLocalVariableLoad(instr)) { } } if (ldc4Count >= 3) { StringBuilder snippetBuilder = new StringBuilder(); int contextLines = 3; for (int j = Math.Max(0, instructionIndex - contextLines); j < Math.Min(instructions.Count, instructionIndex + contextLines + 1); j++) { snippetBuilder.Append((j == instructionIndex) ? ">>> " : " "); snippetBuilder.AppendLine(((object)instructions[j]).ToString()); } yield return new ScanFinding($"{((MemberReference)((MemberReference)method).DeclaringType).FullName}.{((MemberReference)method).Name}:{instructions[instructionIndex].Offset}", $"Detected obfuscated reflection: {ldc4Count} sequential integer constants loaded before dynamic invocation (potential API resolution obfuscation)", Severity.High, snippetBuilder.ToString().TrimEnd()); } } private static bool IsLdcI4Instruction(Instruction instruction) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) return instruction.OpCode == OpCodes.Ldc_I4 || instruction.OpCode == OpCodes.Ldc_I4_0 || instruction.OpCode == OpCodes.Ldc_I4_1 || instruction.OpCode == OpCodes.Ldc_I4_2 || instruction.OpCode == OpCodes.Ldc_I4_3 || instruction.OpCode == OpCodes.Ldc_I4_4 || instruction.OpCode == OpCodes.Ldc_I4_5 || instruction.OpCode == OpCodes.Ldc_I4_6 || instruction.OpCode == OpCodes.Ldc_I4_7 || instruction.OpCode == OpCodes.Ldc_I4_8 || instruction.OpCode == OpCodes.Ldc_I4_M1 || instruction.OpCode == OpCodes.Ldc_I4_S; } private static bool IsLocalVariableLoad(Instruction instruction) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) return instruction.OpCode == OpCodes.Ldloc || instruction.OpCode == OpCodes.Ldloc_0 || instruction.OpCode == OpCodes.Ldloc_1 || instruction.OpCode == OpCodes.Ldloc_2 || instruction.OpCode == OpCodes.Ldloc_3 || instruction.OpCode == OpCodes.Ldloc_S; } private static bool IsSystemAssembly(string assemblyName) { if (string.IsNullOrEmpty(assemblyName)) { return false; } if (SystemAssemblies.Contains(assemblyName)) { return true; } if (assemblyName.StartsWith("System.", StringComparison.OrdinalIgnoreCase) || assemblyName.StartsWith("Microsoft.", StringComparison.OrdinalIgnoreCase)) { return true; } if (assemblyName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) { string item = assemblyName.Substring(0, assemblyName.Length - 4); if (SystemAssemblies.Contains(item)) { return true; } } return false; } private static bool IsReflectionInvocationType(string typeName) { int result; switch (typeName) { default: result = ((typeName == "System.Reflection.PropertyInfo") ? 1 : 0); break; case "System.Reflection.MethodInfo": case "System.Reflection.MethodBase": case "System.Reflection.ConstructorInfo": result = 1; break; } return (byte)result != 0; } } public class RegistryRule : IScanRule { private Severity _severity = Severity.Critical; private string _description = "Detected Windows Registry manipulation, which is suspicious for a game mod. Registry access could be used to persist malware or modify system settings."; private static readonly string[] RegistryFunctions = new string[26] { "regcreatekeyex", "regopenkey", "regopenkeya", "regopenkeyex", "regopenkeyexa", "regopenkeyexw", "regsetvalue", "regsetvaluea", "regsetvaluew", "regsetvalueex", "regsetvalueexa", "regsetvalueexw", "reggetvalue", "reggetvaluea", "reggetvaluew", "regdeletekey", "regdeletevalue", "regenumkey", "regenumvalue", "regqueryvalue", "regqueryvalueex", "regcreatekey", "regsetkeysecurity", "regloadkey", "regsavekey", "regnotifychangekeyvalue" }; private static readonly string[] RegistryWriteFunctions = new string[14] { "regcreatekeyex", "regcreatekey", "regsetvalue", "regsetvaluea", "regsetvaluew", "regsetvalueex", "regsetvalueexa", "regsetvalueexw", "regdeletekey", "regdeletevalue", "regsetkeysecurity", "regloadkey", "regsavekey", "regnotifychangekeyvalue" }; private static readonly string[] RegistryReadFunctions = new string[12] { "regopenkey", "regopenkeya", "regopenkeyex", "regopenkeyexa", "regopenkeyexw", "reggetvalue", "reggetvaluea", "reggetvaluew", "regenumkey", "regenumvalue", "regqueryvalue", "regqueryvalueex" }; public string Description => _description; public Severity Severity => _severity; public string RuleId => "RegistryRule"; public bool RequiresCompanionFinding => false; public IDeveloperGuidance? DeveloperGuidance => new DeveloperGuidance("Game mods should not modify the Windows Registry. For persistent settings, use your mod framework's configuration system: MelonPreferences for MelonLoader, Config.Bind() for BepInEx, or Unity's PlayerPrefs for simple settings.", null, new string[3] { "MelonPreferences.CreateEntry (MelonLoader)", "Config.Bind (BepInEx)", "UnityEngine.PlayerPrefs (Unity)" }, isRemediable: false); public bool IsSuspicious(MethodReference method) { //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Invalid comparison between Unknown and I4 if (((method != null) ? ((MemberReference)method).DeclaringType : null) == null) { return false; } string fullName = ((MemberReference)((MemberReference)method).DeclaringType).FullName; string methodName = ((MemberReference)method).Name.ToLowerInvariant(); if (fullName.Contains("Microsoft.Win32.Registry", StringComparison.Ordinal) || fullName.Contains("RegistryKey", StringComparison.Ordinal) || fullName.Contains("RegistryHive", StringComparison.Ordinal)) { return ClassifyManagedRegistryCall(methodName); } if (RegistryWriteFunctions.Any((string regFunction) => methodName.Contains(regFunction, StringComparison.Ordinal))) { _severity = Severity.Critical; _description = "Detected Windows Registry write operation, which could be used for persistence or system tampering."; return true; } if (RegistryReadFunctions.Any((string regFunction) => methodName.Contains(regFunction, StringComparison.Ordinal))) { _severity = Severity.Low; _description = "Detected Windows Registry read access. This can be legitimate for configuration discovery, but should not be used to modify the system."; return true; } MethodDefinition val = method.Resolve(); if (val == null) { return false; } if ((val.Attributes & 0x2000) == 0) { return false; } if (val.PInvokeInfo == null) { return false; } string name = val.PInvokeInfo.Module.Name; string text = val.PInvokeInfo.EntryPoint ?? ((MemberReference)method).Name; if (!name.ToLowerInvariant().Contains("advapi32")) { return false; } if (RegistryWriteFunctions.Any((string func) => methodName.Contains(func, StringComparison.Ordinal))) { _severity = Severity.Critical; _description = "Detected native Windows Registry write API " + text + " from " + name + ", which could be used for persistence or system tampering."; return true; } string entryPointLower = text.ToLowerInvariant(); if (RegistryWriteFunctions.Any((string func) => entryPointLower.Contains(func, StringComparison.Ordinal))) { _severity = Severity.Critical; _description = "Detected native Windows Registry write API " + text + " from " + name + ", which could be used for persistence or system tampering."; return true; } if (RegistryReadFunctions.Any((string func) => methodName.Contains(func, StringComparison.Ordinal)) || RegistryReadFunctions.Any((string func) => entryPointLower.Contains(func, StringComparison.Ordinal))) { _severity = Severity.Low; _description = "Detected native Windows Registry read API " + text + " from " + name + ". This can be legitimate for configuration discovery, but should not modify the system."; return true; } return false; } private bool ClassifyManagedRegistryCall(string methodName) { if (methodName.Contains("setvalue", StringComparison.Ordinal) || methodName.Contains("create", StringComparison.Ordinal) || methodName.Contains("delete", StringComparison.Ordinal) || methodName.Contains("save", StringComparison.Ordinal) || methodName.Contains("flush", StringComparison.Ordinal)) { _severity = Severity.Critical; _description = "Detected Windows Registry write operation, which could be used for persistence or system tampering."; return true; } if (methodName.Contains("getvalue", StringComparison.Ordinal) || methodName.Contains("open", StringComparison.Ordinal) || methodName.Contains("query", StringComparison.Ordinal) || methodName.Contains("enum", StringComparison.Ordinal)) { _severity = Severity.Low; _description = "Detected Windows Registry read access. This can be legitimate for configuration discovery, but should not be used to modify the system."; return true; } _severity = Severity.Medium; _description = "Detected Windows Registry access. Registry interaction is uncommon in mods and should be reviewed carefully."; return true; } } public class SuspiciousAssemblyNameRule : IScanRule { private static readonly Regex MelonLoaderModPattern = new Regex("^MelonLoaderMod\\d+$", RegexOptions.IgnoreCase | RegexOptions.Compiled); public string Description => "Detected suspicious generic assembly naming often associated with throwaway malware samples."; public Severity Severity => Severity.Medium; public string RuleId => "SuspiciousAssemblyNameRule"; public bool RequiresCompanionFinding => false; public bool IsSuspicious(MethodReference method) { return false; } public IEnumerable AnalyzeAssemblyMetadata(AssemblyDefinition assembly) { object obj; if (assembly == null) { obj = null; } else { AssemblyNameDefinition name = assembly.Name; obj = ((name != null) ? ((AssemblyNameReference)name).Name : null); } string assemblyName = (string)obj; if (!string.IsNullOrWhiteSpace(assemblyName) && MelonLoaderModPattern.IsMatch(assemblyName)) { yield return new ScanFinding("Assembly: " + assemblyName, "Assembly uses the generic name pattern 'MelonLoaderMod##', which is commonly seen in disposable malware-laced fake mods.", Severity, "Assembly name: " + assemblyName); } } } public class SuspiciousLocalVariableRule : IScanRule { public string Description => "Supporting signal: method uses variable types commonly seen in malicious code"; public Severity Severity => Severity.Low; public string RuleId => "SuspiciousLocalVariableRule"; public bool RequiresCompanionFinding => true; public bool IsSuspicious(MethodReference method) { return false; } public IEnumerable AnalyzeInstructions(MethodDefinition method, Collection instructions, MethodSignals methodSignals) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) List result = new List(); if (!method.HasBody || !method.Body.HasVariables) { return result; } List list = new List(); bool flag = ShouldSuppressControlledProcessPattern(instructions, methodSignals); Enumerator enumerator = method.Body.Variables.GetEnumerator(); try { while (enumerator.MoveNext()) { VariableDefinition current = enumerator.Current; string fullName = ((MemberReference)((VariableReference)current).VariableType).FullName; if (IsSuspiciousVariableType(fullName) && (!flag || !IsProcessVariableType(fullName))) { list.Add($"{fullName} (var_{((VariableReference)current).Index})"); } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } if (list.Count > 0 && methodSignals != null) { methodSignals.HasSuspiciousLocalVariables = true; methodSignals.MarkRuleTriggered(RuleId); } return result; } private static bool ShouldSuppressControlledProcessPattern(Collection instructions, MethodSignals methodSignals) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) if (methodSignals != null && methodSignals.HasEnvironmentVariableModification) { return false; } bool flag = false; bool flag2 = false; bool flag3 = false; bool flag4 = false; bool flag5 = false; for (int i = 0; i < instructions.Count; i++) { Instruction val = instructions[i]; if (val.OpCode == OpCodes.Ldstr && val.Operand is string literal && IsDangerousProcessCommandLiteral(literal)) { flag5 = true; } if (val.OpCode != OpCodes.Call && val.OpCode != OpCodes.Callvirt) { continue; } object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 == null) { continue; } TypeReference declaringType = ((MemberReference)val2).DeclaringType; string text = ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) ?? string.Empty; string text2 = ((MemberReference)val2).Name ?? string.Empty; if (text == "System.Diagnostics.Process") { if (text2 == "Start") { flag = true; } else if (text2 == "WaitForExit") { flag4 = true; } } else { if (text != "System.Diagnostics.ProcessStartInfo") { continue; } if (text2 == "set_UseShellExecute") { if (TryGetBooleanArgument(instructions, i) == false) { flag2 = true; } } else if ((text2 == "set_RedirectStandardOutput" || text2 == "set_RedirectStandardError") && TryGetBooleanArgument(instructions, i) == true) { flag3 = true; } } } return flag && flag2 && flag3 && flag4 && !flag5; } private static bool? TryGetBooleanArgument(Collection instructions, int callInstructionIndex) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) if (callInstructionIndex <= 0) { return null; } Instruction val = instructions[callInstructionIndex - 1]; if (val.OpCode == OpCodes.Ldc_I4_0) { return false; } if (val.OpCode == OpCodes.Ldc_I4_1) { return true; } if (val.OpCode == OpCodes.Ldc_I4_S && val.Operand is sbyte b) { switch (b) { case 0: return false; case 1: return true; } } if (val.OpCode == OpCodes.Ldc_I4 && val.Operand is int num) { switch (num) { case 0: return false; case 1: return true; } } return null; } private static bool IsDangerousProcessCommandLiteral(string literal) { if (string.IsNullOrWhiteSpace(literal)) { return false; } string text = literal.Trim().ToLowerInvariant(); return text.Contains("cmd.exe") || text.Contains("powershell") || text.Contains("pwsh") || text.Contains("wscript") || text.Contains("cscript") || text.Contains("mshta") || text.Contains("rundll32") || text.Contains("regsvr32") || text.Contains("certutil") || text.Contains("bitsadmin"); } private static bool IsProcessVariableType(string typeName) { return typeName.StartsWith("System.Diagnostics.Process", StringComparison.Ordinal); } private static bool IsSuspiciousVariableType(string typeName) { if (typeName.StartsWith("System.Diagnostics.Process")) { return true; } if (typeName.StartsWith("System.Runtime.InteropServices.") && (typeName.Contains("Marshal") || typeName.Contains("DllImport"))) { return true; } if (typeName.Contains("System.Net.WebClient") || typeName.Contains("System.Net.Http.HttpClient")) { return true; } return false; } } } namespace MLVScan.Models.Rules.Helpers { internal static class InstructionValueResolver { private sealed class ResolverContext { public ModuleDefinition? Module { get; } public HashSet VisitedFields { get; } = new HashSet(StringComparer.Ordinal); public HashSet VisitedMethods { get; } = new HashSet(StringComparer.Ordinal); public ResolverContext(ModuleDefinition? module) { Module = module; } } private struct ResolvedValue { public string Display { get; } public string? ExecutableName { get; } public bool IsConcrete { get; } public ResolvedValue(string display, string? executableName, bool isConcrete) { Display = display; ExecutableName = executableName; IsConcrete = isConcrete; } public static ResolvedValue FromLiteral(string literal) { return new ResolvedValue(literal, ExtractExecutableName(literal), isConcrete: true); } } private const int MaxDepth = 16; private static readonly Regex ExecutableNameRegex = new Regex("([A-Za-z0-9._-]+\\.(?:exe|bat|cmd|com|ps1|msi))", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); private static readonly Regex FormatItemRegex = new Regex("\\{(\\d+)(?:[^}]*)\\}", RegexOptions.CultureInvariant); public static bool TryResolveProcessTarget(MethodDefinition? containingMethod, MethodReference calledMethod, Collection instructions, int processStartIndex, out string target) { ResolverContext context = new ResolverContext((containingMethod != null) ? ((MemberReference)containingMethod).Module : null); if (TryResolveFromStartInfoSetter(context, containingMethod, instructions, processStartIndex, out var value) || TryResolveFromProcessStartArguments(context, containingMethod, calledMethod, instructions, processStartIndex, out value)) { target = BuildTargetDisplay(value); return true; } target = ""; return false; } public static bool TryResolveProcessArguments(MethodDefinition? containingMethod, Collection instructions, int processStartIndex, out string arguments) { ResolverContext context = new ResolverContext((containingMethod != null) ? ((MemberReference)containingMethod).Module : null); if (TryResolveFromStartInfoArgumentsSetter(context, containingMethod, instructions, processStartIndex, out var value)) { arguments = value.Display; return true; } arguments = ""; return false; } public static bool TryResolveStackValueDisplay(MethodDefinition? containingMethod, Collection instructions, int beforeIndex, out string valueDisplay) { ResolverContext context = new ResolverContext((containingMethod != null) ? ((MemberReference)containingMethod).Module : null); if (TryResolveTopStackValue(context, containingMethod, instructions, beforeIndex, null, 0, out var value, out var _)) { valueDisplay = value.Display; return true; } valueDisplay = ""; return false; } public static bool TryResolveUseShellExecute(MethodDefinition? containingMethod, Collection instructions, int processStartIndex, out bool? useShellExecute) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) useShellExecute = null; int num = Math.Max(0, processStartIndex - 400); ResolverContext context = new ResolverContext((containingMethod != null) ? ((MemberReference)containingMethod).Module : null); for (int num2 = processStartIndex - 1; num2 >= num; num2--) { Instruction val = instructions[num2]; if (!(val.OpCode != OpCodes.Call) || !(val.OpCode != OpCodes.Callvirt)) { object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 != null) { TypeReference declaringType = ((MemberReference)val2).DeclaringType; if (!(((declaringType != null) ? ((MemberReference)declaringType).FullName : null) != "System.Diagnostics.ProcessStartInfo") && !(((MemberReference)val2).Name != "set_UseShellExecute") && TryResolveTopStackValue(context, containingMethod, instructions, num2 - 1, null, 0, out var value, out var _)) { bool flag; switch (value.Display) { case "True": case "true": case "1": flag = true; break; default: flag = false; break; } if (flag) { useShellExecute = true; return true; } switch (value.Display) { case "False": case "false": case "0": flag = true; break; default: flag = false; break; } if (flag) { useShellExecute = false; return true; } } } } } return false; } public static bool TryResolveCreateNoWindow(MethodDefinition? containingMethod, Collection instructions, int processStartIndex, out bool? createNoWindow) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) createNoWindow = null; int num = Math.Max(0, processStartIndex - 400); ResolverContext context = new ResolverContext((containingMethod != null) ? ((MemberReference)containingMethod).Module : null); for (int num2 = processStartIndex - 1; num2 >= num; num2--) { Instruction val = instructions[num2]; if (!(val.OpCode != OpCodes.Call) || !(val.OpCode != OpCodes.Callvirt)) { object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 != null) { TypeReference declaringType = ((MemberReference)val2).DeclaringType; if (!(((declaringType != null) ? ((MemberReference)declaringType).FullName : null) != "System.Diagnostics.ProcessStartInfo") && !(((MemberReference)val2).Name != "set_CreateNoWindow") && TryResolveTopStackValue(context, containingMethod, instructions, num2 - 1, null, 0, out var value, out var _)) { bool flag; switch (value.Display) { case "True": case "true": case "1": flag = true; break; default: flag = false; break; } if (flag) { createNoWindow = true; return true; } switch (value.Display) { case "False": case "false": case "0": flag = true; break; default: flag = false; break; } if (flag) { createNoWindow = false; return true; } } } } } return false; } public static bool TryResolveWindowStyle(MethodDefinition? containingMethod, Collection instructions, int processStartIndex, out int? windowStyle) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) windowStyle = null; int num = Math.Max(0, processStartIndex - 400); ResolverContext context = new ResolverContext((containingMethod != null) ? ((MemberReference)containingMethod).Module : null); int num2 = -1; for (int num3 = processStartIndex - 1; num3 >= num; num3--) { Instruction val = instructions[num3]; if (!(val.OpCode != OpCodes.Call) || !(val.OpCode != OpCodes.Callvirt)) { object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 != null) { TypeReference declaringType = ((MemberReference)val2).DeclaringType; if (!(((declaringType != null) ? ((MemberReference)declaringType).FullName : null) != "System.Diagnostics.ProcessStartInfo") && !(((MemberReference)val2).Name != "set_WindowStyle")) { num2 = num3; break; } } } } if (num2 < 0) { return false; } for (int num4 = num2 - 1; num4 >= num; num4--) { Instruction val3 = instructions[num4]; if (!(val3.OpCode == OpCodes.Call) && !(val3.OpCode == OpCodes.Callvirt) && !(val3.OpCode == OpCodes.Pop) && !(val3.OpCode == OpCodes.Stloc) && !(val3.OpCode == OpCodes.Stloc_0) && !(val3.OpCode == OpCodes.Stloc_1) && !(val3.OpCode == OpCodes.Stloc_2) && !(val3.OpCode == OpCodes.Stloc_3) && !(val3.OpCode == OpCodes.Stloc_S) && !(val3.OpCode == OpCodes.Stfld) && !(val3.OpCode == OpCodes.Stsfld) && !(val3.OpCode == OpCodes.Dup)) { if (val3.TryResolveInt32Literal(out var value)) { windowStyle = value; return true; } if ((val3.OpCode == OpCodes.Ldfld || val3.OpCode == OpCodes.Ldsfld) && TryResolveTopStackValue(context, containingMethod, instructions, num4, null, 0, out var value2, out var _) && int.TryParse(value2.Display, out var result)) { windowStyle = result; return true; } break; } } return false; } public static bool TryResolveWorkingDirectory(MethodDefinition? containingMethod, Collection instructions, int processStartIndex, out string? workingDirectory) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) workingDirectory = null; int num = Math.Max(0, processStartIndex - 400); ResolverContext context = new ResolverContext((containingMethod != null) ? ((MemberReference)containingMethod).Module : null); for (int num2 = processStartIndex - 1; num2 >= num; num2--) { Instruction val = instructions[num2]; if (!(val.OpCode != OpCodes.Call) || !(val.OpCode != OpCodes.Callvirt)) { object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 != null) { TypeReference declaringType = ((MemberReference)val2).DeclaringType; if (!(((declaringType != null) ? ((MemberReference)declaringType).FullName : null) != "System.Diagnostics.ProcessStartInfo") && !(((MemberReference)val2).Name != "set_WorkingDirectory") && TryResolveTopStackValue(context, containingMethod, instructions, num2 - 1, null, 0, out var value, out var _)) { workingDirectory = value.Display?.ToLowerInvariant(); return true; } } } } return false; } private static bool TryResolveFromStartInfoArgumentsSetter(ResolverContext context, MethodDefinition? containingMethod, Collection instructions, int processStartIndex, out ResolvedValue value) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) value = default(ResolvedValue); int num = Math.Max(0, processStartIndex - 400); for (int num2 = processStartIndex - 1; num2 >= num; num2--) { Instruction val = instructions[num2]; if (!(val.OpCode != OpCodes.Call) || !(val.OpCode != OpCodes.Callvirt)) { object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 != null) { TypeReference declaringType = ((MemberReference)val2).DeclaringType; if (!(((declaringType != null) ? ((MemberReference)declaringType).FullName : null) != "System.Diagnostics.ProcessStartInfo") && !(((MemberReference)val2).Name != "set_Arguments")) { int producerIndex; return TryResolveTopStackValue(context, containingMethod, instructions, num2 - 1, null, 0, out value, out producerIndex); } } } } return false; } private static bool TryResolveFromStartInfoSetter(ResolverContext context, MethodDefinition? containingMethod, Collection instructions, int processStartIndex, out ResolvedValue value) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) value = default(ResolvedValue); int num = Math.Max(0, processStartIndex - 400); for (int num2 = processStartIndex - 1; num2 >= num; num2--) { Instruction val = instructions[num2]; if (!(val.OpCode != OpCodes.Call) || !(val.OpCode != OpCodes.Callvirt)) { object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 != null) { TypeReference declaringType = ((MemberReference)val2).DeclaringType; if (!(((declaringType != null) ? ((MemberReference)declaringType).FullName : null) != "System.Diagnostics.ProcessStartInfo") && !(((MemberReference)val2).Name != "set_FileName")) { int producerIndex; return TryResolveTopStackValue(context, containingMethod, instructions, num2 - 1, null, 0, out value, out producerIndex); } } } } return false; } private static bool TryResolveFromProcessStartArguments(ResolverContext context, MethodDefinition? containingMethod, MethodReference calledMethod, Collection instructions, int processStartIndex, out ResolvedValue value) { value = default(ResolvedValue); if (!string.Equals(((MemberReference)calledMethod).Name, "Start", StringComparison.Ordinal) || calledMethod.Parameters.Count == 0) { return false; } if (!TryResolveCallArguments(context, containingMethod, instructions, processStartIndex, calledMethod.Parameters.Count, null, 0, out List arguments)) { return false; } if (arguments.Count == 0) { return false; } value = arguments[0]; return true; } private static bool TryResolveCallArguments(ResolverContext context, MethodDefinition? containingMethod, Collection instructions, int callIndex, int parameterCount, Dictionary? argumentMap, int depth, out List arguments) { arguments = new List(parameterCount); int beforeIndex = callIndex - 1; for (int num = parameterCount - 1; num >= 0; num--) { if (!TryResolveTopStackValue(context, containingMethod, instructions, beforeIndex, argumentMap, depth + 1, out var value, out var producerIndex)) { return false; } arguments.Insert(0, value); beforeIndex = producerIndex - 1; } return true; } private static bool TryResolveTopStackValue(ResolverContext context, MethodDefinition? containingMethod, Collection instructions, int beforeIndex, Dictionary? argumentMap, int depth, out ResolvedValue value, out int producerIndex) { value = default(ResolvedValue); producerIndex = -1; if (depth > 16 || beforeIndex < 0) { return false; } producerIndex = FindTopValueProducerIndex(instructions, beforeIndex); if (producerIndex < 0) { return false; } return TryResolveValueFromProducer(context, containingMethod, instructions, producerIndex, argumentMap, depth + 1, out value); } private static int FindTopValueProducerIndex(Collection instructions, int beforeIndex) { int num = 1; for (int num2 = beforeIndex; num2 >= 0; num2--) { Instruction instruction = instructions[num2]; num -= instruction.GetPushCount(); if (num <= 0) { return num2; } num += instruction.GetPopCount(); } return -1; } private static bool TryResolveValueFromProducer(ResolverContext context, MethodDefinition? containingMethod, Collection instructions, int producerIndex, Dictionary? argumentMap, int depth, out ResolvedValue value) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_0262: Unknown result type (might be due to invalid IL or missing references) //IL_0267: Unknown result type (might be due to invalid IL or missing references) //IL_02f3: Unknown result type (might be due to invalid IL or missing references) //IL_02f8: Unknown result type (might be due to invalid IL or missing references) //IL_0305: Unknown result type (might be due to invalid IL or missing references) //IL_030a: Unknown result type (might be due to invalid IL or missing references) //IL_0317: Unknown result type (might be due to invalid IL or missing references) //IL_031c: Unknown result type (might be due to invalid IL or missing references) //IL_0391: Unknown result type (might be due to invalid IL or missing references) //IL_0396: Unknown result type (might be due to invalid IL or missing references) //IL_039a: Unknown result type (might be due to invalid IL or missing references) value = default(ResolvedValue); if (depth > 16) { return false; } Instruction val = instructions[producerIndex]; if (val.OpCode == OpCodes.Ldstr && val.Operand is string literal) { value = ResolvedValue.FromLiteral(literal); return true; } if (val.OpCode == OpCodes.Ldnull) { value = new ResolvedValue("", null, isConcrete: false); return true; } if (val.TryResolveInt32Literal(out var value2)) { value = new ResolvedValue(value2.ToString(), null, isConcrete: true); return true; } if (val.OpCode == OpCodes.Box) { if (TryResolveTopStackValue(context, containingMethod, instructions, producerIndex - 1, argumentMap, depth + 1, out value, out var _)) { return true; } value = new ResolvedValue("", null, isConcrete: false); return true; } if (val.TryGetLocalIndex(out var index)) { if (TryResolveLocalValue(context, containingMethod, instructions, producerIndex - 1, index, argumentMap, depth + 1, out value)) { return true; } value = new ResolvedValue($"", null, isConcrete: false); return true; } if (val.TryGetArgumentIndex(out var index2)) { if (argumentMap != null && argumentMap.TryGetValue(index2, out var value3)) { value = value3; return true; } value = new ResolvedValue($"", null, isConcrete: false); return true; } if (val.OpCode == OpCodes.Ldfld) { object operand = val.Operand; FieldReference val2 = (FieldReference)((operand is FieldReference) ? operand : null); if (val2 != null) { if (TryResolveFieldValueInMethod(context, containingMethod, instructions, producerIndex - 1, val2, isStatic: false, depth + 1, out value) || TryResolveFieldValueAcrossModule(context, val2, isStatic: false, depth + 1, out value)) { return true; } value = new ResolvedValue("", null, isConcrete: false); return true; } } if (val.OpCode == OpCodes.Ldsfld) { object operand2 = val.Operand; FieldReference val3 = (FieldReference)((operand2 is FieldReference) ? operand2 : null); if (val3 != null) { if (TryResolveFieldValueInMethod(context, containingMethod, instructions, producerIndex - 1, val3, isStatic: true, depth + 1, out value) || TryResolveFieldValueAcrossModule(context, val3, isStatic: true, depth + 1, out value)) { return true; } value = new ResolvedValue("", null, isConcrete: false); return true; } } if (val.OpCode == OpCodes.Call || val.OpCode == OpCodes.Callvirt || val.OpCode == OpCodes.Newobj) { object operand3 = val.Operand; MethodReference val4 = (MethodReference)((operand3 is MethodReference) ? operand3 : null); if (val4 != null) { if (TryResolveMethodCallValue(context, containingMethod, instructions, producerIndex, val4, depth + 1, out value)) { return true; } value = new ResolvedValue("", null, isConcrete: false); return true; } } OpCode opCode = val.OpCode; value = new ResolvedValue($"", null, isConcrete: false); return true; } private static bool TryResolveLocalValue(ResolverContext context, MethodDefinition? containingMethod, Collection instructions, int beforeIndex, int localIndex, Dictionary? argumentMap, int depth, out ResolvedValue value) { value = default(ResolvedValue); for (int num = beforeIndex; num >= 0; num--) { Instruction instruction = instructions[num]; if (instruction.TryGetStoredLocalIndex(out var index) && index == localIndex) { int producerIndex; return TryResolveTopStackValue(context, containingMethod, instructions, num - 1, argumentMap, depth + 1, out value, out producerIndex); } } return false; } private static bool TryResolveFieldValueInMethod(ResolverContext context, MethodDefinition? containingMethod, Collection instructions, int beforeIndex, FieldReference field, bool isStatic, int depth, out ResolvedValue value) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) value = default(ResolvedValue); for (int num = beforeIndex; num >= 0; num--) { Instruction val = instructions[num]; if (isStatic ? (val.OpCode == OpCodes.Stsfld) : (val.OpCode == OpCodes.Stfld)) { object operand = val.Operand; FieldReference val2 = (FieldReference)((operand is FieldReference) ? operand : null); if (val2 != null && string.Equals(((MemberReference)val2).FullName, ((MemberReference)field).FullName, StringComparison.Ordinal)) { int producerIndex; return TryResolveTopStackValue(context, containingMethod, instructions, num - 1, null, depth + 1, out value, out producerIndex); } } } return false; } private static bool TryResolveFieldValueAcrossModule(ResolverContext context, FieldReference field, bool isStatic, int depth, out ResolvedValue value) { //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) value = default(ResolvedValue); if (context.Module == null || depth > 16) { return false; } string item = ((MemberReference)field).FullName + (isStatic ? "|S" : "|I"); if (!context.VisitedFields.Add(item)) { return false; } try { bool flag = false; ResolvedValue resolvedValue = default(ResolvedValue); foreach (TypeDefinition type in context.Module.GetTypes()) { Enumerator enumerator2 = type.Methods.GetEnumerator(); try { while (enumerator2.MoveNext()) { MethodDefinition current2 = enumerator2.Current; if (!current2.HasBody) { continue; } Collection instructions = current2.Body.Instructions; for (int num = instructions.Count - 1; num >= 0; num--) { Instruction val = instructions[num]; if (isStatic ? (val.OpCode == OpCodes.Stsfld) : (val.OpCode == OpCodes.Stfld)) { object operand = val.Operand; FieldReference val2 = (FieldReference)((operand is FieldReference) ? operand : null); if (val2 != null && string.Equals(((MemberReference)val2).FullName, ((MemberReference)field).FullName, StringComparison.Ordinal) && TryResolveTopStackValue(context, current2, instructions, num - 1, null, depth + 1, out var value2, out var _)) { if (!flag || IsBetterCandidate(value2, resolvedValue)) { resolvedValue = value2; flag = true; } if (HasHighConfidence(resolvedValue)) { value = resolvedValue; return true; } } } } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } if (flag) { value = resolvedValue; return true; } return false; } finally { context.VisitedFields.Remove(item); } } private static bool TryResolveMethodCallValue(ResolverContext context, MethodDefinition? containingMethod, Collection instructions, int producerIndex, MethodReference method, int depth, out ResolvedValue value) { value = default(ResolvedValue); if (depth > 16) { return false; } int count = method.Parameters.Count; if (!TryResolveCallArguments(context, containingMethod, instructions, producerIndex, count, null, depth + 1, out List arguments)) { arguments = new List(); } TypeReference declaringType = ((MemberReference)method).DeclaringType; string text = ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) ?? string.Empty; if (text == "System.IO.Path") { if (((MemberReference)method).Name == "Combine" || ((MemberReference)method).Name == "Join" || ((MemberReference)method).Name == "GetFullPath" || ((MemberReference)method).Name == "GetFileName") { string text2 = ((arguments.Count > 0) ? CombinePathLikeArguments(arguments) : ("")); bool isConcrete = arguments.Count > 0 && arguments.All((ResolvedValue a) => a.IsConcrete); value = new ResolvedValue(text2, ExtractExecutableName(text2), isConcrete); return true; } if (((MemberReference)method).Name == "GetTempPath") { value = new ResolvedValue("%TEMP%", null, isConcrete: true); return true; } } if (text == "System.Guid" && ((MemberReference)method).Name == "NewGuid") { value = new ResolvedValue("", null, isConcrete: true); return true; } if (text == "System.String") { if (((MemberReference)method).Name == "Concat") { string text3 = string.Concat(arguments.Select((ResolvedValue a) => a.Display)); value = new ResolvedValue(text3, ExtractExecutableName(text3), arguments.All((ResolvedValue a) => a.IsConcrete)); return true; } if (((MemberReference)method).Name == "Format") { if (TryApplySimpleStringFormat(arguments, out string formatted)) { bool isConcrete2 = arguments.Skip(1).All((ResolvedValue a) => a.IsConcrete); value = new ResolvedValue(formatted, ExtractExecutableName(formatted), isConcrete2); return true; } string text4 = string.Concat(arguments.Select((ResolvedValue a) => a.Display)); value = new ResolvedValue(text4, ExtractExecutableName(text4), isConcrete: false); return true; } } if (text == "System.Diagnostics.ProcessStartInfo" && ((MemberReference)method).Name == ".ctor" && arguments.Count > 0) { value = arguments[0]; return true; } MethodDefinition val = method.Resolve(); if (val != null && val.HasBody && context.Module != null && ((MemberReference)val).Module == context.Module && TryResolveMethodReturnValue(context, val, arguments, depth + 1, out value)) { return true; } value = new ResolvedValue("", null, isConcrete: false); return true; } private static bool TryResolveMethodReturnValue(ResolverContext context, MethodDefinition method, IReadOnlyList callArgs, int depth, out ResolvedValue value) { //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) value = default(ResolvedValue); if (depth > 16) { return false; } if (!context.VisitedMethods.Add(((MemberReference)method).FullName)) { return false; } try { Dictionary dictionary = new Dictionary(); if (((MethodReference)method).HasThis) { dictionary[0] = new ResolvedValue("", null, isConcrete: false); } for (int i = 0; i < callArgs.Count; i++) { int key = (((MethodReference)method).HasThis ? (i + 1) : i); dictionary[key] = callArgs[i]; } Collection instructions = method.Body.Instructions; bool flag = false; ResolvedValue resolvedValue = default(ResolvedValue); for (int j = 0; j < instructions.Count; j++) { if (!(instructions[j].OpCode != OpCodes.Ret) && j != 0 && TryResolveTopStackValue(context, method, instructions, j - 1, dictionary, depth + 1, out var value2, out var _)) { if (!flag || IsBetterCandidate(value2, resolvedValue)) { resolvedValue = value2; flag = true; } if (HasHighConfidence(resolvedValue)) { break; } } } if (flag) { value = resolvedValue; return true; } return false; } finally { context.VisitedMethods.Remove(((MemberReference)method).FullName); } } private static bool IsBetterCandidate(ResolvedValue candidate, ResolvedValue currentBest) { return ScoreCandidate(candidate) > ScoreCandidate(currentBest); } private static bool TryApplySimpleStringFormat(IReadOnlyList callArgs, out string formatted) { formatted = string.Empty; if (callArgs.Count == 0) { return false; } string display = callArgs[0].Display; if (string.IsNullOrWhiteSpace(display) || display.StartsWith("<", StringComparison.Ordinal)) { return false; } List formatArgs = (from a in callArgs.Skip(1) select a.Display).ToList(); bool replacedAny = false; formatted = FormatItemRegex.Replace(display, delegate(Match match) { if (!int.TryParse(match.Groups[1].Value, out var result)) { return match.Value; } if (result < 0 || result >= formatArgs.Count) { return match.Value; } replacedAny = true; return formatArgs[result]; }); if (replacedAny) { formatted = formatted.Replace("{{", "{").Replace("}}", "}"); return true; } return false; } private static string CombinePathLikeArguments(IReadOnlyList callArgs) { List list = (from arg in callArgs select arg.Display into display where !string.IsNullOrWhiteSpace(display) select display).ToList(); if (list.Count == 0) { return ""; } string text = list[0]; for (int num = 1; num < list.Count; num++) { string text2 = text.TrimEnd('/', '\\'); string text3 = list[num].TrimStart('/', '\\'); text = (string.IsNullOrEmpty(text2) ? text3 : (text2 + "/" + text3)); } return text; } private static int ScoreCandidate(ResolvedValue value) { int num = 0; if (!string.IsNullOrEmpty(value.ExecutableName)) { num += 6; } if (value.IsConcrete) { num += 2; } if (!value.Display.StartsWith("<", StringComparison.Ordinal)) { num++; } return num; } private static bool HasHighConfidence(ResolvedValue value) { return !string.IsNullOrEmpty(value.ExecutableName) && value.IsConcrete; } private static string BuildTargetDisplay(ResolvedValue value) { if (!string.IsNullOrEmpty(value.ExecutableName)) { return Quote(value.ExecutableName); } if (value.IsConcrete && IsLikelyProcessTargetLiteral(value.Display)) { return Quote(value.Display); } if (value.Display.StartsWith("<", StringComparison.Ordinal)) { return value.Display; } string value2 = ExtractExecutableName(value.Display); if (!string.IsNullOrEmpty(value2)) { return Quote(value2); } return ""; } private static string Quote(string value) { return "\"" + value + "\""; } private static string? ExtractExecutableName(string value) { if (string.IsNullOrWhiteSpace(value)) { return null; } Match match = ExecutableNameRegex.Match(value); if (!match.Success) { return null; } return match.Groups[1].Value; } private static bool IsLikelyProcessTargetLiteral(string literal) { if (string.IsNullOrWhiteSpace(literal)) { return false; } string text = literal.Trim(); return text.Contains(".exe", StringComparison.OrdinalIgnoreCase) || text.Contains(".bat", StringComparison.OrdinalIgnoreCase) || text.Contains(".cmd", StringComparison.OrdinalIgnoreCase) || text.Contains(".ps1", StringComparison.OrdinalIgnoreCase) || text.Contains(".msi", StringComparison.OrdinalIgnoreCase) || text.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || text.StartsWith("https://", StringComparison.OrdinalIgnoreCase) || text.Contains("\\") || text.Contains("/"); } } internal static class InvisibleUnicodeAnalyzer { internal readonly struct InvisibleUnicodeAnalysis { public static InvisibleUnicodeAnalysis Empty => new InvisibleUnicodeAnalysis(hasVariationSelectorPayload: false, 0, 0, null); public bool HasVariationSelectorPayload { get; } public int VariationSelectorCount { get; } public int NonWhitespaceVisibleCount { get; } public string? DecodedText { get; } public InvisibleUnicodeAnalysis(bool hasVariationSelectorPayload, int variationSelectorCount, int nonWhitespaceVisibleCount, string? decodedText) { HasVariationSelectorPayload = hasVariationSelectorPayload; VariationSelectorCount = variationSelectorCount; NonWhitespaceVisibleCount = nonWhitespaceVisibleCount; DecodedText = decodedText; } } private const int VariationSelectorStart = 65024; private const int VariationSelectorEnd = 65039; private const int SupplementaryVariationSelectorStart = 917760; private const int SupplementaryVariationSelectorEnd = 917999; private const int MinimumVariationSelectorCount = 8; public static InvisibleUnicodeAnalysis Analyze(string literal) { if (string.IsNullOrEmpty(literal)) { return InvisibleUnicodeAnalysis.Empty; } int num = 0; int num2 = 0; List list = new List(); for (int i = 0; i < literal.Length; i++) { int num3 = char.ConvertToUtf32(literal, i); if (char.IsSurrogatePair(literal, i)) { i++; } if (TryDecodeVariationSelectorByte(num3, out var decodedByte)) { num++; list.Add(decodedByte); } else if (num3 <= 65535 && !char.IsWhiteSpace((char)num3) && !char.IsControl((char)num3)) { num2++; } } if (num < 8) { return new InvisibleUnicodeAnalysis(hasVariationSelectorPayload: false, num, num2, null); } string decodedText = TryDecodeUtf8(list); return new InvisibleUnicodeAnalysis(hasVariationSelectorPayload: true, num, num2, decodedText); } public static bool TryDecodeVariationSelectorByte(int codePoint, out byte decodedByte) { if (codePoint >= 65024 && codePoint <= 65039) { decodedByte = (byte)(codePoint - 65024); return true; } if (codePoint >= 917760 && codePoint <= 917999) { decodedByte = (byte)(codePoint - 917760 + 16); return true; } decodedByte = 0; return false; } private static string? TryDecodeUtf8(IReadOnlyList decodedBytes) { if (decodedBytes.Count == 0) { return null; } try { return new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetString(decodedBytes.ToArray()); } catch { return null; } } } internal static class ObfuscatedDecodeMatcher { private static readonly char[] TokenSeparators = new char[6] { '-', '`', ':', ',', '|', ' ' }; public static bool TryGetDecodeCallScore(MethodReference calledMethod, string typeName, string methodName, out int score, out string? reason, out bool isStrongDecodePrimitive) { score = 0; reason = null; isStrongDecodePrimitive = false; if (typeName == "System.Int32" && methodName == "Parse" && calledMethod.Parameters.Count == 1 && ((MemberReference)((ParameterReference)calledMethod.Parameters[0]).ParameterType).FullName == "System.String") { score = 10; reason = "integer parsing transform"; isStrongDecodePrimitive = true; return true; } if (typeName == "System.Byte" && methodName == "Parse") { score = 10; reason = "byte parsing transform"; isStrongDecodePrimitive = true; return true; } if (typeName == "System.Convert") { switch (methodName) { default: if (!(methodName == "ToByte")) { break; } goto case "FromBase64String"; case "FromBase64String": case "FromHexString": case "ToInt32": score = 11; reason = "convert." + methodName + " transform"; isStrongDecodePrimitive = true; return true; } } if (typeName == "System.Text.Encoding" && methodName == "GetString") { score = 9; reason = "byte-to-string reconstruction"; isStrongDecodePrimitive = true; return true; } if (typeName == "System.Char" && methodName == "ConvertToUtf32") { score = 12; reason = "Unicode code-point extraction"; isStrongDecodePrimitive = true; return true; } if (typeName == "System.Char" && methodName == "IsSurrogatePair") { score = 6; reason = "surrogate-pair aware Unicode walking"; isStrongDecodePrimitive = true; return true; } if (typeName == "System.Linq.Enumerable" && methodName == "Select") { GenericInstanceMethod val = (GenericInstanceMethod)(object)((calledMethod is GenericInstanceMethod) ? calledMethod : null); if (val != null && val.GenericArguments.Count == 2) { string fullName = ((MemberReference)val.GenericArguments[1]).FullName; if (fullName == "System.Char" || fullName == "System.Byte") { score = 10; reason = "sequence remapping pipeline"; isStrongDecodePrimitive = true; return true; } } } if (typeName == "System.Array" && methodName == "ConvertAll") { GenericInstanceMethod val2 = (GenericInstanceMethod)(object)((calledMethod is GenericInstanceMethod) ? calledMethod : null); if (val2 != null && val2.GenericArguments.Count == 2) { string fullName2 = ((MemberReference)val2.GenericArguments[1]).FullName; if (fullName2 == "System.Char" || fullName2 == "System.Byte") { score = 10; reason = "array conversion pipeline (ConvertAll)"; isStrongDecodePrimitive = true; return true; } } } if (typeName == "System.String" && methodName == "Split") { score = 5; reason = "string.Split pipeline step"; return true; } if (typeName == "System.String" && methodName == "Concat") { GenericInstanceMethod val3 = (GenericInstanceMethod)(object)((calledMethod is GenericInstanceMethod) ? calledMethod : null); if (val3 != null && val3.GenericArguments.Count == 1 && ((MemberReference)val3.GenericArguments[0]).FullName == "System.Char") { score = 7; reason = "char concatenation reconstruction"; isStrongDecodePrimitive = true; return true; } } if (typeName == "System.String" && methodName == "Concat" && calledMethod.Parameters.Count >= 3) { score = 6; reason = "multi-string concatenation chain"; isStrongDecodePrimitive = true; return true; } if (methodName.IndexOf("reverse", StringComparison.OrdinalIgnoreCase) >= 0 && (((MemberReference)calledMethod.ReturnType).FullName == "System.String" || ((MemberReference)calledMethod.ReturnType).FullName == "System.Char[]")) { score = 8; reason = "string reversal transform"; isStrongDecodePrimitive = true; return true; } if (typeName == "System.Array" && methodName == "Reverse") { score = 7; reason = "array reversal primitive"; isStrongDecodePrimitive = true; return true; } if (typeName == "System.Linq.Enumerable" && methodName == "Reverse") { score = 7; reason = "sequence reversal primitive"; isStrongDecodePrimitive = true; return true; } if ((methodName.IndexOf("decode", StringComparison.OrdinalIgnoreCase) >= 0 || methodName.IndexOf("decrypt", StringComparison.OrdinalIgnoreCase) >= 0 || methodName.IndexOf("deobfusc", StringComparison.OrdinalIgnoreCase) >= 0) && (((MemberReference)calledMethod.ReturnType).FullName == "System.String" || ((MemberReference)calledMethod.ReturnType).FullName == "System.Byte[]")) { score = 8; reason = "custom " + methodName + " helper"; isStrongDecodePrimitive = true; return true; } return false; } public static bool IsTokenizedNumericLiteral(string literal) { if (literal.Length < 12) { return false; } string[] array = literal.Split(TokenSeparators, StringSplitOptions.RemoveEmptyEntries); if (array.Length < 4) { return false; } int num = 0; int num2 = 0; string[] array2 = array; foreach (string s in array2) { if (int.TryParse(s, out var result)) { num++; if (result >= 32 && result <= 126) { num2++; } } } if (num < 4) { return false; } double num3 = (double)num / (double)array.Length; return num3 >= 0.7 && num2 >= 3; } public static bool IsBase64LikeLiteral(string literal) { if (literal.Length < 24 || literal.Length % 4 != 0) { return false; } int num = 0; foreach (char c in literal) { if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '+' || c == '/' || c == '=') { num++; } } return num >= literal.Length - 2; } public static bool IsHexLikeLiteral(string literal) { if (literal.Length < 12) { return false; } string text = literal.Replace("0x", string.Empty, StringComparison.OrdinalIgnoreCase).Replace("-", string.Empty, StringComparison.Ordinal).Replace(":", string.Empty, StringComparison.Ordinal) .Replace(" ", string.Empty, StringComparison.Ordinal); if (text.Length < 12 || text.Length % 2 != 0) { return false; } string text2 = text; foreach (char c in text2) { if ((c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F')) { return false; } } return true; } public static bool TryGetDangerLiteralMarker(string literal, out string marker) { string[] array = new string[14] { "powershell", "cmd.exe", "wscript", "cscript", "mshta", "rundll32", "regsvr32", "http://", "https://", "%temp%", "\\temp\\", "appdata", "startup", "shell32.dll" }; string[] array2 = array; foreach (string text in array2) { if (literal.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0) { marker = text; return true; } } string text2 = ReverseString(literal); string[] array3 = array; foreach (string text3 in array3) { if (text2.IndexOf(text3, StringComparison.OrdinalIgnoreCase) >= 0) { marker = text3 + " (reversed: " + literal + ")"; return true; } } marker = string.Empty; return false; } public static bool TryGetInvisibleUnicodeLiteralReason(string literal, out string reason, out bool hasSuspiciousDecodedContent) { InvisibleUnicodeAnalyzer.InvisibleUnicodeAnalysis invisibleUnicodeAnalysis = InvisibleUnicodeAnalyzer.Analyze(literal); if (!invisibleUnicodeAnalysis.HasVariationSelectorPayload) { reason = string.Empty; hasSuspiciousDecodedContent = false; return false; } hasSuspiciousDecodedContent = !string.IsNullOrWhiteSpace(invisibleUnicodeAnalysis.DecodedText) && EncodedStringLiteralRule.ContainsSuspiciousContent(invisibleUnicodeAnalysis.DecodedText); if (hasSuspiciousDecodedContent) { reason = "invisible Unicode payload decodes to '" + invisibleUnicodeAnalysis.DecodedText + "'"; return true; } reason = $"invisible Unicode payload ({invisibleUnicodeAnalysis.VariationSelectorCount} variation selectors)"; return true; } private static string ReverseString(string input) { if (string.IsNullOrEmpty(input)) { return input; } char[] array = input.ToCharArray(); Array.Reverse(array); return new string(array); } } internal sealed class ObfuscatedExecutionEvidence { private const int MaxDecodeScore = 55; private const int MaxSinkScore = 55; private const int MaxDangerScore = 35; public int DecodeScore { get; private set; } public int SinkScore { get; private set; } public int DangerScore { get; private set; } public int TotalScore { get; set; } public int AnchorInstructionIndex { get; private set; } = -1; public bool HasEncodedLiteral { get; set; } public bool HasStrongDecodePrimitive { get; set; } public bool HasReflectionInvokeSink { get; set; } public bool HasAssemblyLoadSink { get; set; } public bool HasProcessLikeSink { get; set; } public bool HasNativeSink { get; set; } public bool HasDynamicTargetResolution { get; set; } public bool HasNetworkCall { get; set; } public bool HasFileWriteCall { get; set; } public bool HasDangerousLiteral { get; set; } public bool HasSensitivePathAccess { get; set; } public List DecodeReasons { get; } = new List(); public List SinkReasons { get; } = new List(); public List DangerReasons { get; } = new List(); public void AddDecode(int points, string reason, int instructionIndex) { if (AddReason(DecodeReasons, reason)) { DecodeScore = Math.Min(55, DecodeScore + points); UpdateAnchor(instructionIndex); } } public void AddSink(int points, string reason, int instructionIndex) { if (AddReason(SinkReasons, reason)) { SinkScore = Math.Min(55, SinkScore + points); UpdateAnchor(instructionIndex); } } public void AddDanger(int points, string reason, int instructionIndex) { if (AddReason(DangerReasons, reason)) { DangerScore = Math.Min(35, DangerScore + points); UpdateAnchor(instructionIndex); } } private void UpdateAnchor(int instructionIndex) { if (AnchorInstructionIndex < 0) { AnchorInstructionIndex = instructionIndex; } } private static bool AddReason(List reasons, string reason) { if (reasons.Count >= 5 || reasons.Contains(reason)) { return false; } reasons.Add(reason); return true; } } internal static class ObfuscatedExecutionHeuristics { public static ObfuscatedExecutionEvidence CollectEvidence(Collection instructions) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) ObfuscatedExecutionEvidence obfuscatedExecutionEvidence = new ObfuscatedExecutionEvidence(); for (int i = 0; i < instructions.Count; i++) { Instruction val = instructions[i]; if (val.OpCode == OpCodes.Ldstr && val.Operand is string literal) { ObfuscatedExecutionPatternMatcher.AnalyzeLiteral(literal, i, obfuscatedExecutionEvidence); } if (val.OpCode == OpCodes.Call || val.OpCode == OpCodes.Callvirt) { object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 != null) { ObfuscatedExecutionPatternMatcher.AnalyzeCall(instructions, i, val2, obfuscatedExecutionEvidence); DetectCharConcatenationChain(instructions, i, val2, obfuscatedExecutionEvidence); } } if (val.OpCode == OpCodes.Conv_U1 || val.OpCode == OpCodes.Conv_U2 || val.OpCode == OpCodes.Conv_I1 || val.OpCode == OpCodes.Conv_I2) { obfuscatedExecutionEvidence.AddDecode(3, "numeric-to-character conversion", i); } if (!(val.OpCode == OpCodes.Newarr)) { continue; } object operand2 = val.Operand; TypeReference val3 = (TypeReference)((operand2 is TypeReference) ? operand2 : null); if (val3 != null) { string fullName = ((MemberReference)val3).FullName; if (fullName == "System.Byte" || fullName == "System.Char") { obfuscatedExecutionEvidence.AddDecode(5, fullName + " array materialization", i); } } } obfuscatedExecutionEvidence.TotalScore = ComputeTotalScore(obfuscatedExecutionEvidence); return obfuscatedExecutionEvidence; } private static void DetectCharConcatenationChain(Collection instructions, int callIndex, MethodReference calledMethod, ObfuscatedExecutionEvidence evidence) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) TypeReference declaringType = ((MemberReference)calledMethod).DeclaringType; string text = ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) ?? string.Empty; string text2 = ((MemberReference)calledMethod).Name ?? string.Empty; if (text != "System.String" || text2 != "Concat") { return; } int num = 0; int num2 = 0; int num3 = Math.Min(15, callIndex); for (int num4 = callIndex - 1; num4 >= callIndex - num3; num4--) { Instruction val = instructions[num4]; if (val.OpCode == OpCodes.Call || val.OpCode == OpCodes.Callvirt) { break; } if (val.OpCode == OpCodes.Ldstr && val.Operand is string text3) { if (text3.Length == 1) { num++; } else if (text3.Length <= 3) { num2++; } } } if (num >= 4) { evidence.HasStrongDecodePrimitive = true; evidence.AddDecode(12, $"single-char concatenation chain ({num} chars)", callIndex); } else if (num + num2 >= 5) { evidence.HasStrongDecodePrimitive = true; evidence.AddDecode(10, "short-string concatenation chain", callIndex); } } private static int ComputeTotalScore(ObfuscatedExecutionEvidence evidence) { int num = evidence.DecodeScore + evidence.SinkScore + evidence.DangerScore; if (evidence.HasEncodedLiteral && evidence.HasDynamicTargetResolution) { num += 8; } if (evidence.HasReflectionInvokeSink && (evidence.HasProcessLikeSink || evidence.HasAssemblyLoadSink || evidence.HasNativeSink)) { num += 10; } if (evidence.HasNetworkCall && evidence.HasFileWriteCall) { num += 8; } if (evidence.HasDangerousLiteral && (evidence.HasProcessLikeSink || evidence.HasNativeSink)) { num += 10; } if (evidence.HasSensitivePathAccess && (evidence.HasFileWriteCall || evidence.HasProcessLikeSink)) { num += 6; } return Math.Min(num, 100); } } internal static class ObfuscatedExecutionPatternMatcher { public static void AnalyzeLiteral(string literal, int index, ObfuscatedExecutionEvidence evidence) { if (string.IsNullOrWhiteSpace(literal)) { return; } if (ObfuscatedDecodeMatcher.IsTokenizedNumericLiteral(literal)) { evidence.HasEncodedLiteral = true; evidence.HasStrongDecodePrimitive = true; evidence.AddDecode(18, "tokenized numeric literal", index); } if (ObfuscatedDecodeMatcher.IsBase64LikeLiteral(literal)) { evidence.HasEncodedLiteral = true; evidence.HasStrongDecodePrimitive = true; evidence.AddDecode(10, "base64-like literal", index); } if (ObfuscatedDecodeMatcher.IsHexLikeLiteral(literal)) { evidence.HasEncodedLiteral = true; evidence.HasStrongDecodePrimitive = true; evidence.AddDecode(8, "hex-like literal", index); } if (ObfuscatedDecodeMatcher.TryGetInvisibleUnicodeLiteralReason(literal, out string reason, out bool hasSuspiciousDecodedContent)) { evidence.HasEncodedLiteral = true; evidence.HasStrongDecodePrimitive = true; evidence.AddDecode(hasSuspiciousDecodedContent ? 20 : 14, reason, index); if (hasSuspiciousDecodedContent) { evidence.HasDangerousLiteral = true; evidence.AddDanger(14, "suspicious content recovered from invisible Unicode payload", index); } } if (ObfuscatedDecodeMatcher.TryGetDangerLiteralMarker(literal, out string marker)) { evidence.HasDangerousLiteral = true; evidence.AddDanger(12, "suspicious literal '" + marker + "'", index); } } public static void AnalyzeCall(Collection instructions, int index, MethodReference calledMethod, ObfuscatedExecutionEvidence evidence) { TypeReference declaringType = ((MemberReference)calledMethod).DeclaringType; string text = ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) ?? string.Empty; string text2 = ((MemberReference)calledMethod).Name ?? string.Empty; if (ObfuscatedDecodeMatcher.TryGetDecodeCallScore(calledMethod, text, text2, out int score, out string reason, out bool isStrongDecodePrimitive)) { evidence.AddDecode(score, reason, index); if (isStrongDecodePrimitive) { evidence.HasStrongDecodePrimitive = true; } } if (ObfuscatedSinkMatcher.IsReflectionInvokeSink(text, text2)) { evidence.HasReflectionInvokeSink = true; evidence.AddSink(40, "reflection invoke sink", index); } if (ObfuscatedSinkMatcher.IsAssemblyLoadSink(text, text2)) { evidence.HasAssemblyLoadSink = true; evidence.AddSink(42, "dynamic assembly loading sink", index); } if (ObfuscatedSinkMatcher.IsProcessSink(text, text2)) { evidence.HasProcessLikeSink = true; evidence.AddSink(45, "process execution sink", index); } if (ObfuscatedSinkMatcher.IsPotentialNativeExecutionSink(calledMethod, text, text2)) { evidence.HasNativeSink = true; evidence.AddSink(35, "native execution bridge", index); } if (ObfuscatedSinkMatcher.IsDynamicTargetResolution(text, text2)) { evidence.HasDynamicTargetResolution = true; evidence.AddDecode(4, "dynamic target resolution", index); } if (ObfuscatedSinkMatcher.IsNetworkCall(text, text2)) { evidence.HasNetworkCall = true; evidence.AddDanger(12, "network transfer primitive", index); } if (ObfuscatedSinkMatcher.IsFileWriteCall(text, text2)) { evidence.HasFileWriteCall = true; evidence.AddDanger(12, "file write primitive", index); } if (text == "System.Environment" && text2 == "GetFolderPath") { int? num = ObfuscatedSinkMatcher.ExtractFolderPathArgument(instructions, index); if (num.HasValue && PersistenceRule.IsSensitiveFolder(num.Value)) { evidence.HasSensitivePathAccess = true; string folderName = PersistenceRule.GetFolderName(num.Value); evidence.AddDanger(8, "sensitive folder access (" + folderName + ")", index); } } } } internal static class ObfuscatedSinkMatcher { public static bool IsReflectionInvokeSink(string typeName, string methodName) { return ((typeName == "System.Reflection.MethodInfo" || typeName == "System.Reflection.MethodBase") && methodName == "Invoke") || (typeName == "System.Delegate" && methodName == "DynamicInvoke"); } public static bool IsAssemblyLoadSink(string typeName, string methodName) { if (typeName == "System.Reflection.Assembly") { switch (methodName) { default: if (!(methodName == "UnsafeLoadFrom")) { break; } goto case "Load"; case "Load": case "LoadFrom": case "LoadFile": return true; } } if (typeName == "System.AppDomain" && methodName == "Load") { return true; } return typeName.Contains("AssemblyLoadContext", StringComparison.Ordinal) && (methodName == "LoadFromAssemblyPath" || methodName == "LoadFromStream"); } public static bool IsProcessSink(string typeName, string methodName) { return typeName == "System.Diagnostics.Process" && methodName == "Start"; } public static bool IsPotentialNativeExecutionSink(MethodReference calledMethod, string typeName, string methodName) { if (methodName.IndexOf("ShellExecute", StringComparison.OrdinalIgnoreCase) >= 0 || methodName.IndexOf("CreateProcess", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } try { MethodDefinition val = calledMethod.Resolve(); if (val != null && val.IsPInvokeImpl) { return true; } } catch { } return typeName == "System.Runtime.InteropServices.Marshal" && (methodName == "GetDelegateForFunctionPointer" || methodName == "GetFunctionPointerForDelegate"); } public static bool IsDynamicTargetResolution(string typeName, string methodName) { if (typeName == "System.Type") { switch (methodName) { default: if (!(methodName == "InvokeMember")) { break; } goto case "GetType"; case "GetType": case "GetMethod": case "GetProperty": case "GetField": return true; } } if (typeName == "System.Reflection.Assembly" && (methodName == "GetType" || methodName == "CreateInstance")) { return true; } return typeName == "System.Activator" && methodName == "CreateInstance"; } public static bool IsNetworkCall(string typeName, string methodName) { if (typeName == "System.Net.WebClient" && (methodName.StartsWith("Download", StringComparison.Ordinal) || methodName.StartsWith("Upload", StringComparison.Ordinal) || methodName == "OpenRead")) { return true; } if (typeName == "System.Net.Http.HttpClient" && (methodName.StartsWith("Get", StringComparison.Ordinal) || methodName.StartsWith("Post", StringComparison.Ordinal) || methodName == "SendAsync")) { return true; } if (typeName == "System.Net.WebRequest" && (methodName == "Create" || methodName == "GetRequestStream" || methodName == "GetResponse")) { return true; } return typeName == "System.Net.Sockets.Socket" && methodName == "Connect"; } public static bool IsFileWriteCall(string typeName, string methodName) { if (typeName == "System.IO.File" && (methodName.StartsWith("Write", StringComparison.Ordinal) || methodName.StartsWith("Append", StringComparison.Ordinal) || methodName == "Create")) { return true; } if (typeName == "System.IO.FileStream" && methodName == "Write") { return true; } if (typeName == "System.IO.StreamWriter" && methodName.StartsWith("Write", StringComparison.Ordinal)) { return true; } return typeName == "System.IO.BinaryWriter" && methodName.StartsWith("Write", StringComparison.Ordinal); } public static int? ExtractFolderPathArgument(Collection instructions, int currentIndex) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) int num = Math.Max(0, currentIndex - 5); for (int num2 = currentIndex - 1; num2 >= num; num2--) { Instruction val = instructions[num2]; if (val.OpCode == OpCodes.Ldc_I4) { return (int)val.Operand; } if (val.OpCode == OpCodes.Ldc_I4_S) { return (sbyte)val.Operand; } if (val.OpCode == OpCodes.Ldc_I4_0) { return 0; } if (val.OpCode == OpCodes.Ldc_I4_1) { return 1; } if (val.OpCode == OpCodes.Ldc_I4_2) { return 2; } if (val.OpCode == OpCodes.Ldc_I4_3) { return 3; } if (val.OpCode == OpCodes.Ldc_I4_4) { return 4; } if (val.OpCode == OpCodes.Ldc_I4_5) { return 5; } if (val.OpCode == OpCodes.Ldc_I4_6) { return 6; } if (val.OpCode == OpCodes.Ldc_I4_7) { return 7; } if (val.OpCode == OpCodes.Ldc_I4_8) { return 8; } } return null; } } internal static class UrlLiteralCollector { public static IReadOnlyList CollectCandidates(Collection instructions, int windowStart, int windowEnd) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) List list = new List(); List list2 = new List(); for (int i = windowStart; i < windowEnd; i++) { Instruction val = instructions[i]; if (val.OpCode == OpCodes.Ldstr && val.Operand is string text && !string.IsNullOrWhiteSpace(text)) { list.Add(text); list2.Add(text); AddConcatenatedRunCandidates(list2, list); } else { list2.Clear(); } } return list.Distinct(StringComparer.Ordinal).ToList(); } private static void AddConcatenatedRunCandidates(List literalRun, List candidates) { if (literalRun.Count >= 2) { int count = Math.Max(0, literalRun.Count - 6); string text = string.Concat(literalRun.Skip(count)); if (!string.IsNullOrWhiteSpace(text)) { candidates.Add(text); } } } } } namespace MLVScan.Models.Dto { public class AnalysisCompletenessDto { public string Status { get; set; } = AnalysisCompletenessStatus.Complete.ToString(); public bool IsComplete { get; set; } = true; public bool ReviewRecommended { get; set; } public List Reasons { get; set; } = new List(); } public class AnalysisCompletenessReasonDto { public string ReasonId { get; set; } = string.Empty; public string Summary { get; set; } = string.Empty; public string? Phase { get; set; } public string? RuleId { get; set; } public string? Location { get; set; } } public class AssemblyMetadataDto { public string? Name { get; set; } public string? AssemblyVersion { get; set; } public string? FileVersion { get; set; } public string? InformationalVersion { get; set; } public string? TargetFramework { get; set; } public string? ModuleRuntimeVersion { get; set; } public List? ReferencedAssemblies { get; set; } } public class CallChainDto { public string? Id { get; set; } public string? RuleId { get; set; } public string Description { get; set; } = string.Empty; public string Severity { get; set; } = "Low"; public List Nodes { get; set; } = new List(); } public class CallChainNodeDto { public string NodeType { get; set; } = string.Empty; public string Location { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public string? CodeSnippet { get; set; } } public class DataFlowChainDto { public string? Id { get; set; } public string Description { get; set; } = string.Empty; public string Severity { get; set; } = "Low"; public string Pattern { get; set; } = "Unknown"; public string? SourceVariable { get; set; } public string? MethodLocation { get; set; } public bool IsCrossMethod { get; set; } public bool IsSuspicious { get; set; } public int CallDepth { get; set; } public List? InvolvedMethods { get; set; } public List Nodes { get; set; } = new List(); } public class DataFlowNodeDto { public string NodeType { get; set; } = string.Empty; public string Location { get; set; } = string.Empty; public string Operation { get; set; } = string.Empty; public string DataDescription { get; set; } = string.Empty; public int InstructionOffset { get; set; } public string? MethodKey { get; set; } public bool IsMethodBoundary { get; set; } public string? TargetMethodKey { get; set; } public string? CodeSnippet { get; set; } } public class DeveloperGuidanceDto { public string? RuleId { get; set; } public List? RuleIds { get; set; } public string Remediation { get; set; } = string.Empty; public string? DocumentationUrl { get; set; } public string[]? AlternativeApis { get; set; } public bool IsRemediable { get; set; } } public class FindingDto { public string? Id { get; set; } public string? RuleId { get; set; } public string Description { get; set; } = string.Empty; public string Severity { get; set; } = "Low"; public string Location { get; set; } = string.Empty; public string? CodeSnippet { get; set; } public int? RiskScore { get; set; } public string? CallChainId { get; set; } public string? DataFlowChainId { get; set; } public DeveloperGuidanceDto? DeveloperGuidance { get; set; } public CallChainDto? CallChain { get; set; } public DataFlowChainDto? DataFlowChain { get; set; } public string? Visibility { get; set; } } public enum FindingVisibility { Default, Advanced } public class ScanInputDto { public string FileName { get; set; } = string.Empty; public long SizeBytes { get; set; } public string? Sha256Hash { get; set; } } public class ScanMetadataDto { public string CoreVersion { get; set; } = MLVScanVersions.CoreVersion; public string PlatformVersion { get; set; } = "0.0.0"; public string Timestamp { get; set; } = DateTime.UtcNow.ToString("o"); public string ScanMode { get; set; } = "detailed"; public string Platform { get; set; } = "core"; public string ScannerVersion { get; set; } = "0.0.0"; } public class ScanResultDto { public string SchemaVersion { get; set; } = "1.3.0"; public ScanMetadataDto Metadata { get; set; } = new ScanMetadataDto(); public ScanInputDto Input { get; set; } = new ScanInputDto(); public AssemblyMetadataDto? Assembly { get; set; } public ScanSummaryDto Summary { get; set; } = new ScanSummaryDto(); public AnalysisCompletenessDto AnalysisCompleteness { get; set; } = new AnalysisCompletenessDto(); public List Findings { get; set; } = new List(); public List? CallChains { get; set; } public List? DataFlows { get; set; } public List? DeveloperGuidance { get; set; } public List? ThreatFamilies { get; set; } public ThreatDispositionDto? Disposition { get; set; } } public class ScanResultOptions { public string Platform { get; set; } = "core"; public string ScanMode { get; set; } = "detailed"; public string CoreVersion { get; set; } = MLVScanVersions.CoreVersion; public string PlatformVersion { get; set; } = "0.0.0"; public string SchemaVersion { get; set; } = "1.3.0"; public bool IncludeDeveloperGuidance { get; set; } = false; public bool IncludeCallChains { get; set; } = true; public bool IncludeDataFlows { get; set; } = true; public static ScanResultOptions ForWasm(bool developerMode = false) { return new ScanResultOptions { Platform = "wasm", ScanMode = (developerMode ? "developer" : "detailed"), IncludeDeveloperGuidance = developerMode }; } public static ScanResultOptions ForCli(bool developerMode = false) { return new ScanResultOptions { Platform = "cli", ScanMode = (developerMode ? "developer" : "detailed"), IncludeDeveloperGuidance = developerMode }; } public static ScanResultOptions ForServer(bool developerMode = false) { return new ScanResultOptions { Platform = "server", ScanMode = (developerMode ? "developer" : "detailed"), IncludeDeveloperGuidance = developerMode }; } public static ScanResultOptions ForDesktop(bool developerMode = false) { return new ScanResultOptions { Platform = "desktop", ScanMode = (developerMode ? "developer" : "detailed"), IncludeDeveloperGuidance = developerMode }; } } public class ScanSummaryDto { public int TotalFindings { get; set; } public Dictionary CountBySeverity { get; set; } = new Dictionary(); public List TriggeredRules { get; set; } = new List(); } public class ThreatDispositionDto { public string Classification { get; set; } = string.Empty; public string Headline { get; set; } = string.Empty; public string Summary { get; set; } = string.Empty; public bool BlockingRecommended { get; set; } public string? PrimaryThreatFamilyId { get; set; } public List RelatedFindingIds { get; set; } = new List(); } public class ThreatFamilyDto { public string FamilyId { get; set; } = string.Empty; public string VariantId { get; set; } = string.Empty; public string DisplayName { get; set; } = string.Empty; public string Summary { get; set; } = string.Empty; public string MatchKind { get; set; } = string.Empty; public double Confidence { get; set; } public bool ExactHashMatch { get; set; } public List MatchedRules { get; set; } = new List(); public List AdvisorySlugs { get; set; } = new List(); public List Evidence { get; set; } = new List(); } public class ThreatFamilyEvidenceDto { public string Kind { get; set; } = string.Empty; public string Value { get; set; } = string.Empty; public string? RuleId { get; set; } public string? Location { get; set; } public string? CallChainId { get; set; } public string? DataFlowChainId { get; set; } public string? Pattern { get; set; } public string? MethodLocation { get; set; } public double? Confidence { get; set; } } } namespace MLVScan.Models.DataFlow { internal sealed class DataFlowCallChainNode { public DataFlowMethodFlowInfo MethodInfo { get; set; } = null; public DataFlowMethodCallSite? IncomingCallSite { get; set; } public List ChildNodes { get; } = new List(); public HashSet VisitedMethods { get; set; } = new HashSet(StringComparer.Ordinal); } internal sealed class DataFlowInterestingOperation { public Instruction Instruction { get; set; } = null; public int InstructionIndex { get; set; } public MethodReference MethodReference { get; set; } = null; public DataFlowNodeType NodeType { get; set; } public string Operation { get; set; } = string.Empty; public string DataDescription { get; set; } = string.Empty; public int? LocalVariableIndex { get; set; } } internal sealed class DataFlowMethodAnalysisResult { public string MethodKey { get; set; } = string.Empty; public Collection Instructions { get; set; } = new Collection(); public DataFlowMethodFlowInfo FlowInfo { get; set; } = new DataFlowMethodFlowInfo(); public List Chains { get; set; } = new List(); } internal sealed class DataFlowMethodCallSite { public string TargetMethodKey { get; set; } = string.Empty; public string TargetDisplayName { get; set; } = string.Empty; public int InstructionOffset { get; set; } public int InstructionIndex { get; set; } public Dictionary ParameterMapping { get; set; } = new Dictionary(); public bool ReturnValueUsed { get; set; } public bool CalledMethodReturnsData { get; set; } } internal sealed class DataFlowMethodFlowInfo { public string MethodKey { get; set; } = string.Empty; public string DisplayName { get; set; } = string.Empty; public bool HasSource { get; set; } public bool HasSink { get; set; } public bool HasTransform { get; set; } public bool ReturnsData { get; set; } public string? ReturnTypeName { get; set; } public List Operations { get; set; } = new List(); public List OutgoingCalls { get; } = new List(); public List ReturnProducingOperations { get; set; } = new List(); } } namespace MLVScan.Models.CrossAssembly { public enum AssemblyArtifactRole { Unknown, Mod, Plugin, UserLib, Patcher, ExternalReference } public sealed class AssemblyDependencyGraph { public IReadOnlyList Nodes { get; set; } = Array.Empty(); public IReadOnlyList Edges { get; set; } = Array.Empty(); public IReadOnlyDictionary> OutgoingBySource => (from edge in Edges group edge by edge.SourcePath).ToDictionary((IGrouping group) => group.Key, (IGrouping group) => group.ToList()); public IReadOnlyDictionary> IncomingByTarget => (from edge in Edges group edge by edge.TargetPath).ToDictionary((IGrouping group) => group.Key, (IGrouping group) => group.ToList()); } public enum AssemblyEdgeType { Reference, CallEvidence, ResourceLoad } public sealed class AssemblyGraphEdge { public string SourcePath { get; set; } = string.Empty; public string TargetPath { get; set; } = string.Empty; public AssemblyEdgeType EdgeType { get; set; } = AssemblyEdgeType.Reference; public string? Evidence { get; set; } } public sealed class AssemblyGraphNode { public string Path { get; set; } = string.Empty; public string AssemblyName { get; set; } = string.Empty; public string? Hash { get; set; } public AssemblyArtifactRole Role { get; set; } = AssemblyArtifactRole.Unknown; } public enum QuarantinePolicy { CallerOnly, CallerAndCallee, DependencyCluster } } namespace MLVScan.Abstractions { [EditorBrowsable(EditorBrowsableState.Never)] public class ConsoleScanLogger : IScanLogger { public static readonly ConsoleScanLogger Instance = new ConsoleScanLogger(); public void Debug(string message) { Console.WriteLine("[MLVScan DEBUG] " + message); } public void Info(string message) { Console.WriteLine("[MLVScan INFO] " + message); } public void Warning(string message) { Console.WriteLine("[MLVScan WARN] " + message); } public void Error(string message) { Console.WriteLine("[MLVScan ERROR] " + message); } public void Error(string message, Exception exception) { Console.WriteLine($"[MLVScan ERROR] {message}: {exception}"); } } public interface IAssemblyResolverProvider { IAssemblyResolver CreateResolver(); } [EditorBrowsable(EditorBrowsableState.Never)] public sealed class DefaultAssemblyResolverProvider : IAssemblyResolverProvider { public static readonly DefaultAssemblyResolverProvider Instance = new DefaultAssemblyResolverProvider(); private DefaultAssemblyResolverProvider() { } public IAssemblyResolver CreateResolver() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown return (IAssemblyResolver)new DefaultAssemblyResolver(); } } public interface IAssemblyScanner { IEnumerable Scan(string assemblyPath); IEnumerable Scan(Stream assemblyStream, string? virtualPath = null); } public interface IDeveloperGuidance { string Remediation { get; } string? DocumentationUrl { get; } string[]? AlternativeApis { get; } bool IsRemediable { get; } } public interface IEntryPointProvider { bool IsEntryPoint(MethodDefinition method); IEnumerable GetKnownEntryPointNames(); } [EditorBrowsable(EditorBrowsableState.Never)] public class GenericEntryPointProvider : IEntryPointProvider { private static readonly HashSet CommonEntryPoints = new HashSet(StringComparer.OrdinalIgnoreCase) { "Awake", "Start", "Update", "LateUpdate", "FixedUpdate", "OnEnable", "OnDisable", "OnDestroy", "OnApplicationQuit", "OnApplicationPause", "OnApplicationFocus", "Initialize", "Init", "Setup", ".cctor" }; private static readonly string[] EntryPointPrefixes = new string[1] { "On" }; public bool IsEntryPoint(MethodDefinition method) { string name = ((MemberReference)method).Name; if (CommonEntryPoints.Contains(name)) { return true; } string[] entryPointPrefixes = EntryPointPrefixes; foreach (string value in entryPointPrefixes) { if (name.StartsWith(value, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } public IEnumerable GetKnownEntryPointNames() { return CommonEntryPoints.OrderBy((string n) => n); } } public interface IScanLogger { void Debug(string message); void Info(string message); void Warning(string message); void Error(string message); void Error(string message, Exception exception); } public interface IScanRule { string Description { get; } Severity Severity { get; } string RuleId { get; } bool RequiresCompanionFinding { get; } IDeveloperGuidance? DeveloperGuidance => null; bool IsSuspicious(MethodReference method); IEnumerable AnalyzeInstructions(MethodDefinition method, Collection instructions, MethodSignals methodSignals) { return Enumerable.Empty(); } IEnumerable AnalyzeStringLiteral(string literal, MethodDefinition method, int instructionIndex) { return Enumerable.Empty(); } IEnumerable AnalyzeAssemblyMetadata(AssemblyDefinition assembly) { return Enumerable.Empty(); } IEnumerable AnalyzeContextualPattern(MethodReference method, Collection instructions, int instructionIndex, MethodSignals methodSignals) { return Enumerable.Empty(); } bool ShouldSuppressFinding(MethodReference method, Collection instructions, int instructionIndex, MethodSignals methodSignals, MethodSignals? typeSignals = null) { return false; } string GetFindingDescription(MethodReference method, Collection instructions, int instructionIndex) { return Description; } string GetFindingDescription(MethodDefinition containingMethod, MethodReference method, Collection instructions, int instructionIndex) { return GetFindingDescription(method, instructions, instructionIndex); } IEnumerable PostAnalysisRefine(ModuleDefinition module, IEnumerable existingFindings) { return Enumerable.Empty(); } } public sealed class NullScanLogger : IScanLogger { public static readonly NullScanLogger Instance = new NullScanLogger(); private NullScanLogger() { } public void Debug(string message) { } public void Info(string message) { } public void Warning(string message) { } public void Error(string message) { } public void Error(string message, Exception exception) { } } } [CompilerGenerated] internal sealed class <>z__ReadOnlyArray : IEnumerable, ICollection, IList, IEnumerable, IReadOnlyCollection, IReadOnlyList, ICollection, IList { int ICollection.Count => _items.Length; bool ICollection.IsSynchronized => false; object ICollection.SyncRoot => this; object IList.this[int index] { get { return _items[index]; } set { throw new NotSupportedException(); } } bool IList.IsFixedSize => true; bool IList.IsReadOnly => true; int IReadOnlyCollection.Count => _items.Length; T IReadOnlyList.this[int index] => _items[index]; int ICollection.Count => _items.Length; bool ICollection.IsReadOnly => true; T IList.this[int index] { get { return _items[index]; } set { throw new NotSupportedException(); } } public <>z__ReadOnlyArray(T[] items) { _items = items; } IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)_items).GetEnumerator(); } void ICollection.CopyTo(Array array, int index) { ((ICollection)_items).CopyTo(array, index); } int IList.Add(object value) { throw new NotSupportedException(); } void IList.Clear() { throw new NotSupportedException(); } bool IList.Contains(object value) { return ((IList)_items).Contains(value); } int IList.IndexOf(object value) { return ((IList)_items).IndexOf(value); } void IList.Insert(int index, object value) { throw new NotSupportedException(); } void IList.Remove(object value) { throw new NotSupportedException(); } void IList.RemoveAt(int index) { throw new NotSupportedException(); } IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)_items).GetEnumerator(); } void ICollection.Add(T item) { throw new NotSupportedException(); } void ICollection.Clear() { throw new NotSupportedException(); } bool ICollection.Contains(T item) { return ((ICollection)_items).Contains(item); } void ICollection.CopyTo(T[] array, int arrayIndex) { ((ICollection)_items).CopyTo(array, arrayIndex); } bool ICollection.Remove(T item) { throw new NotSupportedException(); } int IList.IndexOf(T item) { return ((IList)_items).IndexOf(item); } void IList.Insert(int index, T item) { throw new NotSupportedException(); } void IList.RemoveAt(int index) { throw new NotSupportedException(); } } [CompilerGenerated] internal sealed class <>z__ReadOnlySingleElementList : IEnumerable, ICollection, IList, IEnumerable, IReadOnlyCollection, IReadOnlyList, ICollection, IList { private sealed class Enumerator : IDisposable, IEnumerator, IEnumerator { object IEnumerator.Current => _item; T IEnumerator.Current => _item; public Enumerator(T item) { _item = item; } bool IEnumerator.MoveNext() { return !_moveNextCalled && (_moveNextCalled = true); } void IEnumerator.Reset() { _moveNextCalled = false; } void IDisposable.Dispose() { } } int ICollection.Count => 1; bool ICollection.IsSynchronized => false; object ICollection.SyncRoot => this; object IList.this[int index] { get { if (index != 0) { throw new IndexOutOfRangeException(); } return _item; } set { throw new NotSupportedException(); } } bool IList.IsFixedSize => true; bool IList.IsReadOnly => true; int IReadOnlyCollection.Count => 1; T IReadOnlyList.this[int index] { get { if (index != 0) { throw new IndexOutOfRangeException(); } return _item; } } int ICollection.Count => 1; bool ICollection.IsReadOnly => true; T IList.this[int index] { get { if (index != 0) { throw new IndexOutOfRangeException(); } return _item; } set { throw new NotSupportedException(); } } public <>z__ReadOnlySingleElementList(T item) { _item = item; } IEnumerator IEnumerable.GetEnumerator() { return new Enumerator(_item); } void ICollection.CopyTo(Array array, int index) { array.SetValue(_item, index); } int IList.Add(object value) { throw new NotSupportedException(); } void IList.Clear() { throw new NotSupportedException(); } bool IList.Contains(object value) { return EqualityComparer.Default.Equals(_item, (T)value); } int IList.IndexOf(object value) { return (!EqualityComparer.Default.Equals(_item, (T)value)) ? (-1) : 0; } void IList.Insert(int index, object value) { throw new NotSupportedException(); } void IList.Remove(object value) { throw new NotSupportedException(); } void IList.RemoveAt(int index) { throw new NotSupportedException(); } IEnumerator IEnumerable.GetEnumerator() { return new Enumerator(_item); } void ICollection.Add(T item) { throw new NotSupportedException(); } void ICollection.Clear() { throw new NotSupportedException(); } bool ICollection.Contains(T item) { return EqualityComparer.Default.Equals(_item, item); } void ICollection.CopyTo(T[] array, int arrayIndex) { array[arrayIndex] = _item; } bool ICollection.Remove(T item) { throw new NotSupportedException(); } int IList.IndexOf(T item) { return (!EqualityComparer.Default.Equals(_item, item)) ? (-1) : 0; } void IList.Insert(int index, T item) { throw new NotSupportedException(); } void IList.RemoveAt(int index) { throw new NotSupportedException(); } }