Multi-Stage .NET Malware Using Bitmap Steganography, Reflection & Runtime API Resolution

Status: In Progress

Author: Danyal Rana


Summary

This report documents the ongoing reverse engineering of a multi-stage .NET malware loader that conceals its secondary payload inside bitmap resources before executing it entirely from memory.

Unlike conventional loaders that embed encrypted binaries as resources or append payloads to image files, this sample reconstructs a managed PE assembly directly from bitmap ARGB pixel values before loading it through Assembly.Load(byte[]).

Static analysis has currently confirmed:

  • Bitmap-based payload concealment

  • Reflection-based assembly loading

  • Reflection-based method invocation

  • Custom byte stream decoding

  • Runtime AES/Rijndael compatibility logic

  • Embedded resource decryption

  • Dynamic Win32 API resolution

  • Runtime delegate generation

  • Native memory management delegates consistent with in-memory execution

Although the final payload has not yet been recovered, the recovered Stage 2 assembly exhibits characteristics commonly associated with modern fileless malware loaders.


Sample Information

FieldValue
SampleRenamed for analysis
PlatformWindows
Architecturex86
Framework.NET Framework 2.0
TypeMulti-stage .NET Loader
Analysis StatusOngoing

Hashes

MD5

280a26accb8a0b6ea46506785fe22391

SHA1

e8faecc955939d21d2ada0465bd42c8bb6df7946

SHA256

b710e94d81dab6603ad14a45ee82926305bfb35f848d9108d4fc31eaf5161707


Technical Overview

The malware separates its functionality across multiple execution stages.

Rather than embedding executable code directly inside the initial PE, the loader stores an entire managed assembly inside an embedded bitmap resource.

The recovered assembly is reconstructed directly from ARGB pixel values before being loaded into memory using the .NET reflection API.

No secondary executable is written to disk during this stage.


Stage 1 Analysis

The first-stage executable masquerades as a legitimate Windows Forms application while hiding a second-stage assembly inside an embedded bitmap.

Embedded resources include:

  • Map2026.bmp

  • PAxG.bmp

Static analysis showed that Map2026.bmp stores a serialized .NET assembly directly within its pixel data.

Unlike traditional image steganography that relies on LSB encoding or appended overlays, this sample stores the payload sequentially across ARGB values.

The extraction routine:

  1. Restores the modified bitmap.

  2. Reads ARGB pixel values.

  3. Extracts the payload length.

  4. Reconstructs the embedded assembly.

  5. Loads the recovered bytes using Assembly.Load(byte[]).

Recovered execution chain:

Map2026.bmp
        │
        ▼
RestoreOriginalBitmap()
        │
        ▼
ARGB Pixel Extraction
        │
        ▼
Payload Size (DWORD)
        │
        ▼
Embedded .NET Assembly
        │
        ▼
Assembly.Load(byte[])

Reflection-Based Execution

Unlike standard .NET applications that invoke the managed entry point, the recovered assembly is executed through reflection.

Recovered execution path:

Assembly.Load(byte[])
        │
        ▼
Assembly.GetTypes()[20]
        │
        ▼
Type.GetMethods()[29]
        │
        ▼
MethodInfo.Invoke()

This indexed reflection avoids normal execution flow and complicates automated analysis.


Custom Decoder

Prior to reflective loading, the malware applies a lightweight byte-wise decoding routine (Mist()).

Static analysis indicates that the routine:

  • derives an initial key index using the final byte of an embedded key (last_byte XOR 0x70);

  • advances through an internal key schedule;

  • XORs each byte with both key material and an evolving state value;

  • removes a trailing sentinel byte before returning the decoded stream.

Although relatively simple, the routine effectively obscures embedded resources before execution.


Stage 2 Analysis

The recovered second-stage assembly introduces substantially more functionality than Stage 1.

Confirmed capabilities include:

  • Reflection

  • AES/Rijndael cryptography

  • MD5 hashing

  • CryptoStream support

  • Runtime delegate generation

  • Embedded resource processing

  • Dynamic Win32 API resolution

No outbound network communication has yet been identified through static analysis.


Cryptographic Components

Stage 2 dynamically instantiates cryptographic providers using Activator.CreateInstance().

Recovered compatibility logic:

AesCryptoServiceProvider (.NET 3.5)
            │
            ▼
AesCryptoServiceProvider (.NET 4.0)
            │
            ▼
RijndaelManaged

The implementation also includes:

  • CryptoStream

  • CreateDecryptor()

  • CreateEncryptor()

  • MD5 hashing

  • Incremental hashing via HashAlgorithm.TransformBlock()

The presence of streamed cryptographic routines strongly suggests that encrypted resources are processed during runtime rather than stored in plaintext.


Runtime API Resolution

Rather than statically importing sensitive Win32 APIs, Stage 2 reconstructs API names dynamically before resolving them through:

  • LoadLibrary()

  • GetProcAddress()

  • Marshal.GetDelegateForFunctionPointer()

Recovered API strings include:

  • VirtualAlloc

  • VirtualProtect

  • WriteProcessMemory

  • OpenProcess

  • CloseHandle

Recovered delegate signatures closely match native Win32 prototypes for memory allocation and process memory manipulation.

This approach significantly reduces static indicators and is commonly observed in malware that performs reflective loading or in-memory execution.


Current Execution Chain

PestWind.exe
        │
        ▼
Map2026.bmp
        │
        ▼
Bitmap Restoration
        │
        ▼
ARGB Payload Extraction
        │
        ▼
Assembly.Load()
        │
        ▼
Reflection
        │
        ▼
Stage 2
        │
        ├─────────────┐
        │             │
        ▼             ▼
Custom Decoder    ResourceA
        │             │
        ▼             ▼
Reflection     AES/Rijndael
        │             │
        └──────┬──────┘
               ▼
Dynamic API Resolution
               │
               ▼
LoadLibrary()
GetProcAddress()
Marshal.GetDelegateForFunctionPointer()
               │
               ▼
VirtualAlloc
VirtualProtect
WriteProcessMemory
OpenProcess

Current Assessment

Static analysis confirms that the recovered DLL functions as an intermediate loader rather than the final payload.

Confirmed observations include:

  • Bitmap-based payload concealment

  • Reflection-based execution

  • Runtime API resolution

  • Delegate generation

  • Embedded resource processing

  • AES/Rijndael cryptographic support

  • Memory management delegate definitions

At the time of writing, the final decrypted payload has not yet been recovered through static analysis alone.

Dynamic execution inside an isolated virtual machine will be required to fully reconstruct the remaining execution stages.


Future Work

The investigation remains ongoing.

Current priorities include:

  • Recovering the decrypted ResourceA payload

  • Identifying AES keys and IV generation

  • Confirming runtime process injection

  • Characterizing the final payload

  • Determining persistence mechanisms

  • Identifying any command-and-control infrastructure

  • Mapping additional ATT&CK techniques as execution continues

Cryptographic Binding to Assembly Metadata

During analysis of the Stage 2 loader, an additional anti-analysis mechanism was identified within the AES initialization routine. Prior to invoking CreateDecryptor(), the loader retrieves its own assembly’s public key token through Assembly.GetName().GetPublicKeyToken(). Selected bytes from this token are XORed into the initialization vector before decryption begins.

By deriving part of the IV from assembly metadata instead of relying solely on static constants, the loader introduces a lightweight integrity dependency that complicates straightforward extraction of cryptographic material through static inspection alone. Although this technique does not prevent runtime recovery of the key or IV, it demonstrates deliberate effort to couple decryption logic with the loader’s identity.

Secondary AES Utility

In addition to the primary loader decryption routine, Stage 2 contains a second AES helper that initializes a SymmetricAlgorithm instance with statically embedded cryptographic material stored in <PrivateImplementationDetails>. Unlike the primary loader which derives part of its IV from the assembly’s public key token before calling CreateDecryptor(key, iv) this helper directly assigns a 32-byte AES key and 16-byte IV using RuntimeHelpers.InitializeArray() before invoking CreateDecryptor().

This suggests the malware employs multiple cryptographic workflows: one tied to assembly metadata for stage loading, and another using embedded key material for decrypting additional resources or configuration data.

Stage 2 contains multiple cryptographic routines, including a metadata-dependent AES decryption path and an additional AES helper utilizing embedded key and IV material stored within <PrivateImplementationDetails>. Static analysis further identifies reflective execution behavior culminating in MethodInfo.Invoke(), strongly supporting the conclusion that decrypted payloads are executed directly in memory rather than written to disk.

Stage 2 employs layered API resolution. Only LoadLibrary is imported conventionally, while subsequent Win32 APIs are resolved dynamically and converted into callable delegates using Marshal.GetDelegateForFunctionPointer(). Additionally, sensitive API names are reconstructed at runtime from fragmented string literals (e.g., "Virtual ".Trim() + "Alloc"), reducing the visibility of recognizable API names during static string inspection.