Skip to content

Instantly share code, notes, and snippets.

@stevehansen
Last active February 4, 2026 10:25
Show Gist options
  • Select an option

  • Save stevehansen/b75865b836000d2639486dca8daa0f4c to your computer and use it in GitHub Desktop.

Select an option

Save stevehansen/b75865b836000d2639486dca8daa0f4c to your computer and use it in GitHub Desktop.
Chrysalis IOC Scanner
#!/usr/bin/env dotnet run
// =============================================================================
// Chrysalis IOC Scanner
// Scans for known Indicators of Compromise (file hashes, services, registry, network)
// =============================================================================
//
// REQUIREMENTS: .NET 10 SDK (https://dot.net/download)
//
// RUN DIRECTLY FROM GIST:
// dotnet run https://gist.githubusercontent.com/stevehansen/b75865b836000d2639486dca8daa0f4c/raw/IOCScanner.cs
//
// OR DOWNLOAD AND RUN:
// curl -O https://gist.githubusercontent.com/stevehansen/b75865b836000d2639486dca8daa0f4c/raw/IOCScanner.cs
// dotnet run IOCScanner.cs
//
// SCAN SPECIFIC PATHS:
// dotnet run IOCScanner.cs C:\ D:\Temp
// dotnet run IOCScanner.cs /home /var/tmp
//
// EXIT CODES:
// 0 = No threats detected
// 1 = Threats detected
// 2 = Scan cancelled by user
//
// =============================================================================
#:package Spectre.Console@0.50.*
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using Microsoft.Win32;
using Spectre.Console;
// --- IOC DEFINITIONS ---
var targetIOCs = new Dictionary<string, string>
{
["update.exe"] = "a511be5164dc1122fb5a7daa3eef9467e43d8458425b15a640235796006590c9",
["update.exe_sha1_1"] = "8e6e505438c21f3d281e1cc257abdbf7223b7f5a",
["update.exe_sha1_2"] = "90e677d7ff5844407b9c073e3b7e896e078e11cd",
["update.exe_sha1_3"] = "573549869e84544e3ef253bdba79851dcde4963a",
["update.exe_sha1_4"] = "13179c8f19fbf3d8473c49983a199e6cb4f318f0",
["update.exe_sha1_5"] = "4c9aac447bf732acc97992290aa7a187b967ee2c",
["update.exe_sha1_6"] = "821c0cafb2aab0f063ef7e313f64313fc81d46cd",
["load"] = "06a6a5a39193075734a32e0235bde0e979c27228",
["load_2"] = "9c3ba38890ed984a25abb6a094b5dbf052f22fa7",
["alien.ini"] = "ca4b6fe0c69472cd3d63b212eb805b7f65710d33",
["alien.ini_alt1"] = "0d0f315fd8cf408a483f8e2dd1e69422629ed9fd",
["alien.ini_alt2"] = "2a476cfb85fbf012fdbe63a37642c11afa5cf020",
["BluetoothService.exe"] = "2da00de67720f5f13b17e9d985fe70f10f153da60c9ab1086fe58f069a156924",
["BluetoothService"] = "77bfea78def679aa1117f569a35e8fd1542df21f7e00e27f192c907e61d63a2e",
["log.dll"] = "3bdc4c0637591533f1d4198a72a33426c01f69bd2e15ceee547866f65e26b7ad",
["u.bat"] = "9276594e73cda1c69b7d265b3f08dc8fa84bf2d6599086b9acc0bb3745146600",
["conf.c"] = "f4d829739f2d6ba7e3ede83dad428a0ced1a703ec582fc73a4eee3df3704629a",
["libtcc.dll"] = "4a52570eeaf9d27722377865df312e295a7a23c3b6eb991944c2ecd707cc9906",
["admin"] = "831e1ea13a1bd405f5bda2b9d8f2265f7b1db6c668dd2165ccc8a9c4c15ea7dd",
["loader1"] = "0a9b8df968df41920b6ff07785cbfebe8bda29e6b512c94a3b2a83d10014d2fd",
["loader2"] = "e7cd605568c38bd6e0aba31045e1633205d0598c607a855e2e1bca4cca1c6eda",
["system"] = "7add554a98d3a99b319f2127688356c1283ed073a084805f14e33b4f6a6126fd",
["s047t5g.exe"] = "fcc2765305bcd213b7558025b2039df2265c3e0b6401e4833123c461df2de51a",
["[NSIS.nsi]"] = "8ea8b83645fba6e23d48075a0d3fc73ad2ba515b4536710cda4f1f232718f53e",
["ConsoleApplication2.exe"] = "b4169a831292e245ebdffedd5820584d73b129411546e7d3eccf4663d5fc5be3",
["uffhxpSy"] = "4c2ea8193f4a5db63b897a2d3ce127cc5d89687f380b97a1d91e0c8db542e4f8",
["3yzr31vk"] = "078a9e5c6c787e5532a7e728720cbafee9021bfec4a30e3c2be110748d7c43c5",
["install.exe"] = "951792130",
["AutoUpdater.exe"] = "951792130"
};
var maliciousIPs = new[] { "45.76.155.202", "45.32.144.255", "95.179.213.0", "45.77.31.210", "59.110.7.32", "124.222.137.114" };
var maliciousDomains = new[] { "self-dns.it.com", "cdncheck.it.com", "safe-dns.it.com", "api.skycloudcenter.com", "api.wiresguard.com", "update.notepads.top" };
var maliciousIdentifiers = new[] { "BluetoothService", "UpdateService", "WindowsUpdater" };
// --- PRE-SCAN INFO & CONFIRMATION ---
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var isElevated = IsRunningElevated();
var scanPaths = args.Length > 0 ? args : GetDefaultScanPaths();
var uniqueFileNames = targetIOCs.Keys
.Select(k => k.Split('_')[0]) // Remove suffixes like _sha1_1, _alt1
.Distinct()
.ToArray();
AnsiConsole.Write(new FigletText("IOC Scanner").Color(Color.Cyan1));
AnsiConsole.Write(new Rule("[bold]Chrysalis IOC Scanner - Pre-Scan Summary[/]").RuleStyle("cyan"));
AnsiConsole.WriteLine();
// Platform & elevation status
var platformPanel = new Panel(
new Markup(isWindows
? $"[bold]Platform:[/] Windows\n[bold]Elevated:[/] {(isElevated ? "[green]Yes (Administrator)[/]" : "[yellow]No (Limited access)[/]")}"
: $"[bold]Platform:[/] {RuntimeInformation.OSDescription}\n[bold]Elevated:[/] {(isElevated ? "[green]Yes (root)[/]" : "[yellow]No (Limited access)[/]")}")
).Header("[bold]System Info[/]").Border(BoxBorder.Rounded);
AnsiConsole.Write(platformPanel);
AnsiConsole.WriteLine();
// Elevation warning
if (!isElevated)
{
AnsiConsole.Write(new Panel(
new Markup(isWindows
? "[yellow]⚠ Running without Administrator privileges.\nSome registry keys, services, and protected files may not be accessible.\nFor a complete scan, right-click and select 'Run as Administrator'.[/]"
: "[yellow]⚠ Running without root privileges.\nSome system directories and files may not be accessible.\nFor a complete scan, run with: sudo dotnet run IOCScanner.cs[/]")
).Header("[bold yellow]Warning[/]").Border(BoxBorder.Rounded).BorderColor(Color.Yellow));
AnsiConsole.WriteLine();
}
// Actions table
var actionsTable = new Table()
.Border(TableBorder.Rounded)
.AddColumn("[bold]Action[/]")
.AddColumn("[bold]What it reads[/]")
.AddColumn("[bold]Why[/]");
if (isWindows)
{
actionsTable.AddRow(
"[cyan]Registry Check[/]",
"HKCU & HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\nHKLM\\SYSTEM\\CurrentControlSet\\Services\\{name}",
$"Detect persistence mechanisms for: {string.Join(", ", maliciousIdentifiers)}"
);
actionsTable.AddRow(
"[cyan]Network Check[/]",
"Active TCP connections (via .NET API)\nDNS cache (via 'ipconfig /displaydns')",
$"Detect active C2 connections to {maliciousIPs.Length} known malicious IPs\nand DNS lookups to {maliciousDomains.Length} malicious domains"
);
}
actionsTable.AddRow(
"[cyan]Filesystem Scan[/]",
$"Paths: {string.Join(", ", scanPaths)}",
$"Scan all files, compute SHA256/SHA1 hash for {uniqueFileNames.Length} suspicious filenames:\n{string.Join(", ", uniqueFileNames.Take(10))}{(uniqueFileNames.Length > 10 ? "..." : "")}"
);
AnsiConsole.Write(new Panel(actionsTable).Header("[bold]Planned Actions (READ-ONLY)[/]").Border(BoxBorder.Rounded));
AnsiConsole.WriteLine();
// IOC summary
AnsiConsole.MarkupLine($"[dim]IOC Database: {targetIOCs.Count} hashes, {maliciousIPs.Length} IPs, {maliciousDomains.Length} domains, {maliciousIdentifiers.Length} service identifiers[/]");
AnsiConsole.WriteLine();
// Confirmation
if (!AnsiConsole.Confirm("Proceed with scan?", defaultValue: true))
{
AnsiConsole.MarkupLine("[yellow]Scan cancelled by user.[/]");
return 2;
}
AnsiConsole.WriteLine();
AnsiConsole.Write(new Rule("[bold cyan on darkblue] STARTING FULL CHRYSALIS IOC SCAN [/]").RuleStyle("cyan"));
AnsiConsole.WriteLine();
var totalMatches = 0;
// Only run Windows-specific checks on Windows
if (isWindows)
{
totalMatches += ScanPersistence();
totalMatches += ScanNetwork();
}
else
{
AnsiConsole.MarkupLine("[yellow]⚠ Skipping Windows-specific persistence and network checks (not running on Windows)[/]");
AnsiConsole.WriteLine();
}
// Filesystem scan works on all platforms
totalMatches += ScanFilesystem(scanPaths);
// --- RESULTS ---
AnsiConsole.WriteLine();
var resultColor = totalMatches > 0 ? "red" : "green";
var resultText = totalMatches > 0 ? $"⚠ THREATS DETECTED: {totalMatches}" : "✓ NO THREATS DETECTED";
AnsiConsole.Write(new Rule($"[bold {resultColor}] SCAN COMPLETE - {resultText} [/]").RuleStyle("cyan"));
return totalMatches > 0 ? 1 : 0;
// --- FUNCTIONS ---
bool IsRunningElevated()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
using var identity = System.Security.Principal.WindowsIdentity.GetCurrent();
var principal = new System.Security.Principal.WindowsPrincipal(identity);
return principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator);
}
else
{
// Unix: check if running as root (UID 0)
try
{
return Environment.GetEnvironmentVariable("EUID") == "0"
|| Environment.GetEnvironmentVariable("UID") == "0"
|| (Environment.UserName == "root");
}
catch
{
return false;
}
}
}
string[] GetDefaultScanPaths()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return DriveInfo.GetDrives()
.Where(d => d.IsReady && d.DriveType is DriveType.Fixed or DriveType.Removable or DriveType.Network)
.Select(d => d.RootDirectory.FullName)
.ToArray();
}
// Linux/macOS - scan common paths
return ["/home", "/tmp", "/var", "/opt"];
}
int ScanPersistence()
{
var matches = 0;
AnsiConsole.MarkupLine("[cyan]▶ Scanning Persistence (Registry & Services)...[/]");
foreach (var id in maliciousIdentifiers)
{
// Service check via registry
var serviceExists = CheckServiceExists(id);
if (serviceExists == true)
{
AnsiConsole.MarkupLine($" [bold red on yellow]⚠ SERVICE MATCH: '{id}' installed![/]");
matches++;
}
else if (serviceExists is null)
{
AnsiConsole.MarkupLine($" [yellow]⚠ Access denied checking service: {id} (run as Administrator)[/]");
}
// Registry checks
var registryPaths = new[]
{
@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run",
@"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run"
};
foreach (var regPath in registryPaths)
{
foreach (var hive in new[] { Registry.CurrentUser, Registry.LocalMachine })
{
var hiveName = hive == Registry.CurrentUser ? "HKCU" : "HKLM";
try
{
using var key = hive.OpenSubKey(regPath);
if (key is null)
continue; // Key doesn't exist, that's fine
if (key.GetValue(id) is not null)
{
AnsiConsole.MarkupLine($" [bold red on yellow]⚠ REGISTRY MATCH: '{id}' in {hiveName}\\{regPath}[/]");
matches++;
}
}
catch (System.Security.SecurityException)
{
AnsiConsole.MarkupLine($" [yellow]⚠ Access denied: {hiveName}\\{regPath} (run as Administrator)[/]");
}
}
}
}
if (matches == 0)
AnsiConsole.MarkupLine(" [green]✓ No persistence mechanisms found[/]");
AnsiConsole.WriteLine();
return matches;
}
bool? CheckServiceExists(string serviceName)
{
try
{
using var key = Registry.LocalMachine.OpenSubKey($@"SYSTEM\CurrentControlSet\Services\{serviceName}");
return key is not null;
}
catch (System.Security.SecurityException)
{
return null; // Access denied
}
}
int ScanNetwork()
{
var matches = 0;
AnsiConsole.MarkupLine("[cyan]▶ Scanning Network & DNS Cache...[/]");
// Check active connections
try
{
var connections = IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections();
foreach (var ip in maliciousIPs)
{
var match = connections.FirstOrDefault(c => c.RemoteEndPoint.Address.ToString() == ip);
if (match is not null)
{
AnsiConsole.MarkupLine($" [bold red on yellow]⚠ ACTIVE C2 CONNECTION: To {ip}[/]");
matches++;
}
}
}
catch (Exception ex)
{
AnsiConsole.MarkupLine($" [yellow]⚠ Could not check network connections: {ex.Message}[/]");
}
// DNS cache check - Windows only via ipconfig /displaydns
try
{
var psi = new System.Diagnostics.ProcessStartInfo("ipconfig", "/displaydns")
{
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
using var process = System.Diagnostics.Process.Start(psi);
if (process is not null)
{
var output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
foreach (var domain in maliciousDomains)
{
if (output.Contains(domain, StringComparison.OrdinalIgnoreCase))
{
AnsiConsole.MarkupLine($" [bold red on yellow]⚠ DNS CACHE TRACE: Found {domain}[/]");
matches++;
}
}
}
}
catch (Exception ex)
{
AnsiConsole.MarkupLine($" [yellow]⚠ Could not check DNS cache: {ex.Message}[/]");
}
if (matches == 0)
AnsiConsole.MarkupLine(" [green]✓ No malicious network activity found[/]");
AnsiConsole.WriteLine();
return matches;
}
int ScanFilesystem(string[] scanPaths)
{
var matches = 0;
AnsiConsole.MarkupLine("[cyan]▶ Scanning Filesystem...[/]");
AnsiConsole.WriteLine();
foreach (var rootPath in scanPaths)
{
if (!Directory.Exists(rootPath))
{
AnsiConsole.MarkupLine($" [yellow]⚠ Path not accessible: {rootPath}[/]");
continue;
}
AnsiConsole.MarkupLine($"[white on grey]▶ Scanning: {rootPath}[/]");
var queue = new Queue<string>();
queue.Enqueue(rootPath);
var filesScanned = 0;
var pathMatches = 0;
var accessDeniedDirs = 0;
var accessDeniedFiles = 0;
AnsiConsole.Status()
.Spinner(Spinner.Known.Dots)
.Start($"Scanning {rootPath}...", ctx =>
{
while (queue.Count > 0)
{
var currentFolder = queue.Dequeue();
try
{
foreach (var filePath in Directory.GetFiles(currentFolder))
{
filesScanned++;
if (filesScanned % 1000 == 0)
ctx.Status($"Scanned {filesScanned:N0} files in {rootPath}...");
var fileName = Path.GetFileName(filePath);
var matchKeys = targetIOCs.Keys
.Where(k => k.Equals(fileName, StringComparison.OrdinalIgnoreCase) ||
k.StartsWith(fileName, StringComparison.OrdinalIgnoreCase))
.ToList();
if (matchKeys.Count > 0)
{
ctx.Status($"Checking hash: {filePath}");
var maliciousFound = false;
var couldNotRead = false;
foreach (var key in matchKeys)
{
var expectedHash = targetIOCs[key];
// Skip dummy/name-only entries
if (expectedHash == "951792130")
continue;
try
{
using var stream = File.OpenRead(filePath);
var actualHash = expectedHash.Length == 40
? ComputeSha1(stream)
: ComputeSha256(stream);
if (actualHash.Equals(expectedHash, StringComparison.OrdinalIgnoreCase))
{
maliciousFound = true;
break;
}
}
catch (UnauthorizedAccessException)
{
couldNotRead = true;
accessDeniedFiles++;
}
catch (IOException)
{
couldNotRead = true; // File locked
}
}
// Only output if there's something worth reporting
if (maliciousFound)
{
AnsiConsole.MarkupLine($" [bold red on yellow]⚠ MALWARE CONFIRMED: {Markup.Escape(filePath)}[/]");
pathMatches++;
}
else if (couldNotRead)
{
AnsiConsole.MarkupLine($" [yellow]? Suspicious filename, but could not verify: {Markup.Escape(filePath)}[/]");
}
// Hash clean = no output, just continue
}
}
foreach (var dir in Directory.GetDirectories(currentFolder))
{
queue.Enqueue(dir);
}
}
catch (UnauthorizedAccessException)
{
accessDeniedDirs++;
}
catch (IOException) { /* Other I/O error, skip */ }
}
});
matches += pathMatches;
var summary = $"Scanned {filesScanned:N0} files, found {pathMatches} matches";
if (accessDeniedDirs > 0 || accessDeniedFiles > 0)
summary += $" [yellow](skipped: {accessDeniedDirs} dirs, {accessDeniedFiles} files - access denied)[/]";
AnsiConsole.MarkupLine($" [dim]{summary}[/]");
AnsiConsole.WriteLine();
}
return matches;
}
string ComputeSha256(Stream stream)
{
var hash = SHA256.HashData(stream);
return Convert.ToHexStringLower(hash);
}
string ComputeSha1(Stream stream)
{
var hash = SHA1.HashData(stream);
return Convert.ToHexStringLower(hash);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment