DarkTortilla Malware: How It Works and How to Test Your Defenses

Umut Bayram | 11 MIN READ

| September 11, 2026

Key Takeaways

  • DarkTortilla is a highly configurable .NET crypter and multi-stage loader active since at least August 2015.
  • Logistics-themed phishing emails deliver the loader inside archive and disk-image attachments such as .iso, .zip, and .img.
  • Encrypted configuration hides inside embedded bitmap images, decrypted with Rijndael in ECB mode using a fixed key.
  • Main payloads including AgentTesla, AsyncRat, RedLine, and Cobalt Strike run injected in memory, never touching disk.
  • The Picus Platform simulates DarkTortilla malware attacks so teams can validate security controls against this threat.

DarkTortilla is a highly configurable .NET-based crypter and multi-stage loader active since at least August 2015. It targets Windows systems, spreading through logistics-themed phishing emails.

Unlike typical loaders, DarkTortilla hides its encrypted configuration inside bitmap pixel data and can pull its core processor DLL from public paste sites. It runs payloads only inside injected legitimate processes, applies three interchangeable persistence mechanisms, and pairs a WatchDog executable with the loader so each restarts the other.

In this blog, we will explain how DarkTortilla works, from delivery to payload injection, and show how Picus helps you validate your security controls against this malware.

DarkTortilla Malware at a Glance

Field

Detail

Malware type

Crypter and multi-stage loader

Target platform

Windows, using the .NET runtime

Possible earliest activity

At least August 2015

Components

Initial loader (.NET executable) and core processor (.NET DLL)

Delivery

Malicious email attachments and documents containing an embedded loader executable

Typical main payloads

AgentTesla, AsyncRat, NanoCore, RedLine, Cobalt Strike, Metasploit

Payload execution

Process injection using an embedded .NET DLL

Persistence

Per-user registry autostarts or a .lnk shortcut in the Startup directory

Anti-analysis

Obfuscated code and strings, optional environment checks, fake errors, and execution delays

What Is DarkTortilla Malware?

DarkTortilla is a complex and highly configurable .NET-based crypter that has possibly been active since at least August 2015. A crypter is a delivery wrapper whose job is to hide, unpack, and launch another payload, and DarkTortilla is unusually feature-rich for that role, using two cooperating components, an initial loader executable and a core processor DLL, whose behavior is driven almost entirely by an encrypted runtime configuration [1].

It typically delivers popular information stealers and remote access trojans such as AgentTesla, AsyncRat, NanoCore, and RedLine, but samples have also been observed delivering targeted post-exploitation payloads such as Cobalt Strike and Metasploit. Alongside that primary payload, it can deploy "addon packages" that include additional malicious payloads, benign decoy documents, and legitimate executables.

Beyond delivery, it hides its configuration inside bitmap images, can fetch its core processor from a public paste site, displays an attacker-defined fake error dialog, relocates and installs itself through several routes, sets persistence through three different mechanisms, executes its main payload only inside an injected legitimate process so nothing is written to disk, and runs anti-tamper controls, including a WatchDog executable that restarts the loader.

How Does DarkTortilla Malware Work?

Initial Access and Delivery

Attackers commonly deliver DarkTortilla through logistics-themed malicious email. The attachments use archive or disk-image extensions such as .iso, .zip, .img, .dmg, and .tar.

In one delivery campaign, the attached .iso disk image contains one loader executable with the same base filename and an .exe extension.

Attackers also embed the initial loader executable in a Packager Shell Object inside a malicious document. The malicious document presents the file as protected content and instructs the recipient to double-click an embedded icon [1].

These delivery routes depend on the recipient opening or activating malicious content.

Execution and Configuration Decryption

Some initial loaders first test internet access with HTTP GET requests. The loader uses www.google.com and www.bing.com as connectivity-check domains. Samples implementing this behavior retry failed requests until the required checks succeed.

The loader then retrieves the encoded core processor. In the embedded variant, the data lives in an assembly resource. The loader uses a 16-byte key to decode the core processor, then loads it into memory.

Externally hosted variants retrieve the core processor from a public paste site. One variant uses the pastebin.pl payload-hosting domain and conceals its retrieval URL by attaching unrelated text to both ends. The returned content uses fake XML tags, integer values, and changing letter delimiters. After removing the wrapper and splitting the integers, the loader subtracts a sample-specific constant; in one variant, subtracting 925 from the first values produces 77 and 90, the MZ executable-header bytes.

DarkTortilla stores its encrypted configuration inside bitmap images embedded in the loader. The core processor locates these images, combines the bytes from their pixel data in order, and decrypts the result to recover its settings.

The GetPixelData extraction routine uses the Bitmap.LockBits API to access each image's pixel data. It copies the configuration bytes into a shared buffer and returns the combined data for decryption [1]:

internal static byte[] GetPixelData(Image[] image_0)

{

MemoryStream memoryStream = new MemoryStream();

checked {

foreach (Image image in image_0) {

// Convert each input image into a bitmap with accessible pixels.

Rectangle rect = new Rectangle(Point.Empty, image.Size);

MemoryStream stream = new MemoryStream();

image.Save(stream, ImageFormat.Png);

Bitmap bitmap = new Bitmap(stream);

BitmapData bitmapData = bitmap.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

byte[] array = new byte[Marshal.ReadInt32(bitmapData.Scan0) >> 15 - 1 + 1];

IntPtr scan = bitmapData.Scan0;

byte[] array2 = array;

// Repeatedly copy the same region, starting six bytes into the pixels.

for (int j = 0; j < array2.Length; j++) {

Marshal.Copy(new IntPtr((int)scan + 6), array, 0, array.Length);

}

bitmap.UnlockBits(bitmapData);

// Append this image's bytes to the accumulated configuration.

memoryStream.Write(array, 0, array.Length);

}

return memoryStream.ToArray();

}

}

The core processor decrypts the configuration with the RijndaelManaged cryptographic class and a fixed 16-byte key. It uses ECB mode and ISO10126 padding. The byte_0 input parameter supplies the encrypted bytes [1]:

// Read the encrypted configuration bytes from the byte_0 input parameter.

byte[] array = new byte[] {

81, 42, 59, 7, 27, 70, 83, 13, 71, 75, 17, 9, 39, 64, 3, 2

};

byte[] result;

using (RijndaelManaged rijndaelManaged = new RijndaelManaged())

{

// Initialize the cipher with the fixed key, block mode, and padding.

rijndaelManaged.Key = array;

rijndaelManaged.IV = array;

rijndaelManaged.Mode = CipherMode.ECB;

rijndaelManaged.Padding = PaddingMode.ISO10126;

using (ICryptoTransform cryptoTransform = rijndaelManaged.CreateDecryptor(array, rijndaelManaged.IV))

{

using (MemoryStream memoryStream = new MemoryStream())

{

using (CryptoStream cryptoStream = new CryptoStream(memoryStream, cryptoTransform, CryptoStreamMode.Write))

{

cryptoStream.Write(byte_0, 0, byte_0.Length);

cryptoStream.FlushFinalBlock(); // Finish decryption and remove padding.

result = memoryStream.ToArray(); // Keep the decrypted configuration bytes.

}

}

}

}

The decrypted configuration contains the payloads and the settings that control execution. For example, the %Installation% field enables installation and persistence, %MainFile% holds the main payload, and %Compress% indicates whether payloads need decompression. Other settings control addons, environment checks, delays, and recovery.

Defense Evasion and Anti-Analysis

The loader replaces meaningful code names with obscure identifiers and uses switch statements to control execution. The num state variable determines which block runs next. This structure makes the program's execution flow harder to follow [1]:

int num = 6; // Initial dispatch state.

for (;;)

{

switch (num)

{

default:

{

List<object> list = new List<object>();

int num2 = list.Count;

num = 3; // Select the next state after initializing the list.

continue;

}

// Other cases test conditions or perform arithmetic and update num.

}

}

The core processor also makes static analysis harder. Some of its code can cause a decompiler to fail even though the malware still runs.

It also conceals configuration-key names by building them at runtime. For example, the ConfigKeyDecoder.DecodeStrings routine turns encoded character values into readable configuration-key names. Here, it reconstructs %InstallationReg%, the field that specifies the registry path for standard persistence [1]:

// Rebuild the configuration key "%InstallationReg%" and store it as a string.

result = Conversions.ToString(ConfigKeyDecoder.DecodeStrings(

17, // The decoded key contains 17 characters.

new int[]

{

// Each number selects a character from the decoder's lookup array.

// For example, 26 selects "%", 8 selects "i", and 13 selects "n".

26, 8, 13, 18, 19, 0, 11, 11, 0, 19,

8, 14, 13, 17, 4, 6, 26

},

// Capitalize "i" and "r", identified by lookup indexes 8 and 17.

new int[] { 8, 17 }

));

To the recipient, execution may look like an application failure. The core processor can display a fake error asking them to install .NET Framework version 4.8.0. The %Message% configuration field enables this dialog, while %MessageRepetition% determines whether it appears again after installation.

The %VM% configuration field enables virtual-machine checks. The core processor queries system information through WMI and examines running processes and services. If it finds virtual-machine indicators, it terminates the loader.

The core processor also uses the Sleep API to delay persistence and addon processing. The %Delay% configuration field specifies pauses of up to five minutes. These delays can push malicious activity beyond a sandbox's observation window [1].

Persistence, and Process Recovery

The %Melt% configuration field tells the core processor to move the loader into the %TEMP% temporary directory under a configured filename. The processor starts the relocated copy and terminates the original process.

Installation can move the loader again. If the %Installation% configuration field enables installation, the core processor copies the loader to its configured location and starts it through the Process.Start API.

For persistence, the core processor can use a standard registry entry, a hidden registry entry, or a Startup-folder shortcut. There is a small but consequential detail in how it chooses. If both the StartupFolder and isHidden configuration properties are true, the Startup-folder option wins. The routine then waits for any configured delay, applies persistence [1]:

private static void SetPersistence_Parent()

{

try

{

int persistence_type = 1; // Default to standard registry persistence.

if (DecryptedConfigStruct.InstallStruct.isHidden)

{

persistence_type = 2;

}

// This independent check overrides the hidden option when both are true.

if (DecryptedConfigStruct.InstallStruct.StartupFolder)

{

persistence_type = 3;

}

if (DecryptedConfigStruct.Execute.Delay > 0)

{

Imports.Sleep(checked(DecryptedConfigStruct.Execute.Delay * 1000));

}

// Apply the selected mechanism.

Persistence.SetPersistence(persistence_type, DecryptedConfigStruct.InstallStruct.InstalledFilePath);

}

catch (Exception ex)

{

}

}

With standard registry persistence, the core processor adds the installed loader to HKCU\Software\Microsoft\Windows\CurrentVersion\Run, a per-user autostart key. That makes the loader run when the user logs on. The configuration supplies an autostart label such as Updates or Runtime Broker for the entry.

For hidden persistence, the core processor modifies the Shell value under HKCU\Software\Microsoft\Windows NT\CurrentVersion\Winlogon, a per-user shell-configuration key. Normally, that setting starts explorer.exe, the desktop shell that provides the desktop and taskbar. DarkTortilla adds its loader to the same setting, so the desktop opens as usual while the malware starts alongside it [1].

cmd.exe /c REG ADD "HKCU\<configured_reg_key>" /f /v "<configured_reg_val>" /t REG_SZ /d "<installed_darktortilla_exe_path>"

The core processor can also create a .lnk shortcut in the Startup folder. The shortcut points to the installed loader and launches it at logon.

After the main payload starts, the %InjectionPersist% configuration field enables process recovery. The core processor restarts the payload's subprocess and reinjects the payload if that process terminates. It also starts a WatchDog recovery executable that restarts the loader if necessary. The monitoring works in both directions. The core processor checks the WatchDog recovery executable once per second and restarts it if necessary.

When the %StartupPersist% configuration field enables it, the processor also reapplies persistence every 30 seconds. As a result, stopping one component or removing an autostart entry may leave another component able to restore it.

Payload Deployment

Before launching the main payload, the core processor handles any addon packages. It decompresses them if needed and follows their individual execution settings. Attackers use these packages to deliver additional malware or cryptocurrency miners, and can include legitimate executables and decoy documents as well. Each addon has its own settings for when and how it runs.

The main payload takes a more direct route. The core processor reads it from the %MainFile% configuration field, decompresses it if needed, and injects it into a subprocess. The payload itself stays in memory.

The %HostIndex% configuration field determines which process hosts the injected payload. It can select a service-host executable such as svchost.exe, the RegAsm.exe assembly-registration utility, or the InstallUtil.exe installation utility.

That configured choice can change, however. Before selecting the injection target, the core processor checks for the avp security-software process. If it finds that process, it overrides the configuration and uses the loader's own executable name.

Before loading the RunPe6 injection DLL, the processor reverses its XOR encoding. The encoded_byte input holds one byte of the encoded DLL, and idx identifies its position [1]:

// Use the fixed key and seed to reverse the DLL's byte encoding.

int[] xor_key = { 45, 89, 125, 69, 250, 222, 111 };

int seed = 99;

// Combine one encoded byte with the key byte selected for its position.

int decoded_byte = encoded_byte ^ xor_key[(idx * seed) % xor_key.Length];

After decoding RunPe6 injection DLL and the processor calls its Runn method to execute the payload inside the selected subprocess.

How Picus Simulates DarkTortilla Malware Attacks?

We strongly suggest simulating DarkTortilla Malware Attacks to test the effectiveness of your security controls against real-life cyber attacks using the Picus Platform. You can also test your defenses against hundreds of other malware variants, such as BRICKSTORM, VenomRAT, Chinotto, and Rustonotto, within minutes with a 14-day free trial of the Picus Platform.

Picus Threat Library includes the following threats for the DarkTortilla Malware Attacks:

Threat ID

Threat Name

Attack Module

65900

DarkTortilla Crypter Download Threat

Network Infiltration

45066

DarkTortilla Crypter Email Threat - 1

E-mail Infiltration

88956

DarkTortilla Crypter Download Threat - 1

Network Infiltration

24775

DarkTortilla Crypter Email Threat

E-mail Infiltration

76580

DarkTortilla Malware Dropper Download Threat

Network Infiltration

99517

DarkTortilla Malware Dropper Email Threat

E-mail Infiltration

78307

DarkTortilla Loader Download Threat

Network Infiltration

43893

DarkTortilla Loader Email Threat

E-mail Infiltration

57833

DarkTortilla RAT Download Threat

Network Infiltration

85019

DarkTortilla RAT Email Threat

E-mail Infiltration

Start simulating emerging threats today and get actionable mitigation insights with a 14-day free trial of the Picus Platform.

References

[1] “DarkTortilla Malware Analysis,” Aug. 17, 2022. Available: https://www.sophos.com/en-us/research/darktortilla-malware-analysis. [Accessed: Sept. 09, 2026]

 
 
DarkTortilla is a highly configurable .NET-based crypter and multi-stage loader for Windows. A crypter is a delivery wrapper that hides, unpacks, and launches another payload. DarkTortilla uses two cooperating components, an initial loader executable and a core processor DLL, whose behavior is driven almost entirely by an encrypted runtime configuration.
DarkTortilla arrives through logistics-themed malicious email. Attachments use archive or disk-image extensions such as .iso, .zip, .img, .dmg, and .tar. In one campaign, the attached .iso disk image held a single loader executable. Attackers also embed the loader in a Packager Shell Object inside a document that presents itself as protected content requiring a double-click.
DarkTortilla typically delivers popular information stealers and remote access trojans including AgentTesla, AsyncRat, NanoCore, and RedLine. Samples have also been observed delivering targeted post-exploitation payloads such as Cobalt Strike and Metasploit. Alongside the main payload, addon packages can carry extra malware, cryptocurrency miners, benign decoy documents, and legitimate executables.
DarkTortilla stores its encrypted configuration inside bitmap images embedded in the loader. The core processor locates these images, combines bytes from their pixel data using the Bitmap.LockBits API, then decrypts the result with the RijndaelManaged class, a fixed 16-byte key, ECB mode, and ISO10126 padding to recover its settings.
DarkTortilla uses one of three mechanisms. Standard persistence adds the installed loader to the HKCU Run key. Hidden persistence appends the loader to the Winlogon Shell value so the desktop still loads normally. The third option drops a .lnk shortcut in the Startup folder. When StartupFolder and isHidden are both set, the Startup folder wins.
Block or inspect archive and disk-image email attachments, and treat documents that instruct users to double-click embedded content as suspicious. Monitor the HKCU Run key, the Winlogon Shell value, and the Startup folder for new autostart entries, and watch for injection into svchost.exe, RegAsm.exe, and InstallUtil.exe. Remove all components together, since each can restore the others.
The Picus Platform simulates DarkTortilla malware attacks so security teams can test how effectively existing controls detect and prevent them. The Picus Threat Library also covers hundreds of other malware variants.

Table of Contents

Ready to start? Request a demo