-
Notifications
You must be signed in to change notification settings - Fork 0
Getting Started
This guide will help you integrate MLVScan.Core into your application for malware detection in Unity mod assemblies.
dotnet add package MLVScan.CoreInstall-Package MLVScan.Core<ItemGroup>
<PackageReference Include="MLVScan.Core" Version="1.0.0" />
</ItemGroup>The simplest way to scan a mod file:
using MLVScan;
using MLVScan.Services;
// Create scanner with default rules
var rules = RuleFactory.CreateDefaultRules();
var scanner = new AssemblyScanner(rules);
// Scan a file
var findings = scanner.Scan("path/to/mod.dll");
// Process results
foreach (var finding in findings)
{
Console.WriteLine($"[{finding.Severity}] {finding.Description}");
Console.WriteLine($" Location: {finding.Location}");
if (finding.HasCallChain)
{
Console.WriteLine($" Call Chain: {finding.CallChain!.Nodes.Count} nodes");
Console.WriteLine(finding.CallChain!.ToDetailedDescription());
}
else if (!string.IsNullOrEmpty(finding.CodeSnippet))
{
Console.WriteLine($" Code: {finding.CodeSnippet}");
}
}For web applications or in-memory scenarios:
using var stream = File.OpenRead("mod.dll");
var findings = scanner.Scan(stream, "mod.dll");This is particularly useful for:
- Web file uploads
- In-memory assembly analysis
- Network-streamed content
public class ScanFinding
{
public string Description { get; set; } // What was detected
public Severity Severity { get; set; } // Threat level
public string Location { get; set; } // Where in the code
public string CodeSnippet { get; set; } // IL instructions (optional)
public string RuleId { get; set; } // Which rule triggered this
public bool HasCallChain { get; } // Does this have a call chain?
public CallChain? CallChain { get; set; } // Full attack path (if available)
}Note: AssemblyScanner automatically includes:
- Traditional rule-based detection
- Call graph analysis for attack path visibility
- Data flow analysis for multi-step attack pattern recognition
See Call Graph Analysis for details on using call chains.
See Data Flow Analysis for details on data flow tracking.
public enum Severity
{
Low = 0, // Minor suspicious patterns
Medium = 1, // Potentially dangerous
High = 2, // Dangerous behaviors
Critical = 3 // Highly dangerous activities
}var findings = scanner.Scan("suspicious_mod.dll");
// Group by severity
var critical = findings.Where(f => f.Severity == Severity.Critical);
var high = findings.Where(f => f.Severity == Severity.High);
// Decision logic
if (critical.Any())
{
Console.WriteLine("CRITICAL THREAT DETECTED - DO NOT LOAD");
}
else if (high.Count() > 3)
{
Console.WriteLine("Multiple high-severity issues - investigate carefully");
}using MLVScan.Models;
var config = new ScanConfig
{
EnableMultiSignalDetection = true, // Reduce false positives
DetectAssemblyMetadata = true // Check assembly attributes
};
var scanner = new AssemblyScanner(rules, config);Implement IScanLogger for platform-specific logging:
using MLVScan.Abstractions;
public class MyLogger : IScanLogger
{
public void Log(string message)
{
// Your logging implementation
MyLoggingFramework.Info(message);
}
public void LogWarning(string message)
{
MyLoggingFramework.Warn(message);
}
public void LogError(string message)
{
MyLoggingFramework.Error(message);
}
}
// Use with scanner
var scanner = new AssemblyScanner(rules, config, logger: new MyLogger());For platforms that need to resolve game assemblies:
using Mono.Cecil;
using MLVScan.Abstractions;
public class MyResolverProvider : IAssemblyResolverProvider
{
private readonly string _gameDirectory;
public MyResolverProvider(string gameDirectory)
{
_gameDirectory = gameDirectory;
}
public IAssemblyResolver CreateResolver()
{
var resolver = new DefaultAssemblyResolver();
resolver.AddSearchDirectory(Path.Combine(_gameDirectory, "Managed"));
resolver.AddSearchDirectory(Path.Combine(_gameDirectory, "MelonLoader"));
return resolver;
}
}
// Use with scanner
var resolverProvider = new MyResolverProvider(@"C:\Games\MyGame");
var scanner = new AssemblyScanner(rules, config, resolverProvider);public async Task<ScanResult> ScanUploadedFile(IFormFile file)
{
var rules = RuleFactory.CreateDefaultRules();
var scanner = new AssemblyScanner(rules);
using var stream = file.OpenReadStream();
var findings = scanner.Scan(stream, file.FileName);
return new ScanResult
{
FileName = file.FileName,
IsClean = !findings.Any(f => f.Severity >= Severity.High),
Findings = findings.ToList()
};
}public Dictionary<string, List<ScanFinding>> ScanAllMods(string modsDirectory)
{
var rules = RuleFactory.CreateDefaultRules();
var scanner = new AssemblyScanner(rules);
var results = new Dictionary<string, List<ScanFinding>>();
var modFiles = Directory.GetFiles(modsDirectory, "*.dll");
foreach (var modFile in modFiles)
{
try
{
var findings = scanner.Scan(modFile).ToList();
if (findings.Any())
{
results[modFile] = findings;
}
}
catch (Exception ex)
{
Console.WriteLine($"Error scanning {modFile}: {ex.Message}");
}
}
return results;
}// Create custom rule set for specific needs
public List<IScanRule> CreateStrictRules()
{
return new List<IScanRule>
{
new Shell32Rule(),
new ProcessStartRule(),
new LoadFromStreamRule(),
new DataExfiltrationRule(),
new PersistenceRule()
// Only check for critical threats
};
}
var scanner = new AssemblyScanner(CreateStrictRules());You can use .NET's built-in cryptography for hash verification:
using System.Security.Cryptography;
public string CalculateHash(string filePath)
{
using var sha256 = SHA256.Create();
using var stream = File.OpenRead(filePath);
var hash = sha256.ComputeHash(stream);
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
}
// Use for whitelisting
var fileHash = CalculateHash("mod.dll");
var whitelist = new[] { "3918e145...", "8e6dd194..." };
if (whitelist.Contains(fileHash))
{
Console.WriteLine("Mod is whitelisted - skipping scan");
}
else
{
var findings = scanner.Scan("mod.dll");
}Always wrap scanning in try-catch:
try
{
var findings = scanner.Scan("mod.dll");
// Process findings
}
catch (BadImageFormatException)
{
Console.WriteLine("Not a valid .NET assembly");
}
catch (FileNotFoundException)
{
Console.WriteLine("File not found");
}
catch (Exception ex)
{
Console.WriteLine($"Scan error: {ex.Message}");
}MLVScan.Core uses multi-signal detection to reduce false positives. Some patterns are only flagged when combined with others:
// Example: Base64 alone might not trigger
// But Base64 + Process.Start will
var config = new ScanConfig
{
EnableMultiSignalDetection = true // Enable contextual analysis
};This helps avoid flagging legitimate uses of reflection, encoding, etc.
private Dictionary<string, List<ScanFinding>> _scanCache = new();
public List<ScanFinding> GetScanResults(string modPath)
{
if (_scanCache.ContainsKey(modPath))
{
return _scanCache[modPath];
}
var findings = scanner.Scan(modPath).ToList();
_scanCache[modPath] = findings;
return findings;
}For scanning multiple files:
var results = modFiles.AsParallel()
.Select(file => new
{
File = file,
Findings = scanner.Scan(file).ToList()
})
.Where(r => r.Findings.Any())
.ToDictionary(r => r.File, r => r.Findings);- Data Flow Analysis - Learn about data flow tracking and attack pattern recognition
- Call Graph Analysis - Learn about consolidated findings with attack paths
- Detection Rules - Learn about all 17+ detection rules
- API Reference - Detailed API documentation
- Contributing - Add new detection rules
Q: Does MLVScan.Core execute the assembly being scanned?
A: No, it uses static analysis only via Mono.Cecil. Assemblies are never executed.
Q: Can I use this with non-Unity assemblies?
A: Yes, it works with any .NET assembly, though it's optimized for Unity mod detection.
Q: How do I add custom detection rules?
A: See Detection Rules for creating custom rules.
Q: What happens if a scan fails?
A: Exceptions are thrown for invalid assemblies. Always wrap scans in try-catch.
- MLVScan (MelonLoader) - Reference implementation
- MLVScanWeb - Web implementation example
- Mono.Cecil Documentation