
Status: In Progress
Author: Danyal Rana
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.
| Field | Value |
|---|---|
| Sample | Renamed for analysis |
| Platform | Windows |
| Architecture | x86 |
| Framework | .NET Framework 2.0 |
| Type | Multi-stage .NET Loader |
| Analysis Status | Ongoing |
MD5
280a26accb8a0b6ea46506785fe22391
SHA1
e8faecc955939d21d2ada0465bd42c8bb6df7946
SHA256
b710e94d81dab6603ad14a45ee82926305bfb35f848d9108d4fc31eaf5161707
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.
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:
Restores the modified bitmap.
Reads ARGB pixel values.
Extracts the payload length.
Reconstructs the embedded assembly.
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[])
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.
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.
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.
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.
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.
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
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.
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
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 throughAssembly.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.
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.