typeanalysisfamily9d2ca3confidencemediumcreated2026-08-13updated2026-08-13pemingwloaderc2defense-evasionmalware-family
SHA-256: 022fe01a4fb8855747a4068de2131ceb7486cf846df6c18adbefe7564482adb5

9d2ca3: 022fe01a — MinGW-w64 HTTP downloader with in-memory reflective PE loader

A lightweight MinGW-w64 x64 downloader/loader dropped by the Amadey botnet. Instead of carrying an encrypted payload in .data, it hardcodes an XOR-obfuscated C2 URL, downloads the secondary stage over unencrypted HTTP, and maps it reflectively into fresh RWX memory without touching disk.

What It Is

  • SHA-256: 022fe01a4fb8855747a4068de2131ceb7486cf846df6c18adbefe7564482adb5
  • File: PE32+ executable (GUI) x86-64, 131 kB, 19 sections ^[file.txt]
  • Timestamp: Thu May 28 12:10:32 2026 UTC ^[exiftool.json]
  • Toolchain: MinGW-w64 GCC 15.2.0 (MSYS2 Rev8), GNU C99, -m64 -O2 -std=gnu99 -fno-builtin ^[strings.txt:33], ^[strings.txt:553]
  • Linker: LinkVersion 2.45, ASLR + DEP + High Entropy VA, no Control Flow Guard ^[pefile.txt:73]
  • Signing: Unsigned ^[rabin2-info.txt:27]
  • Family: 9d2ca3 (OpenCTI label, co-tagged dropped-by-amadey) ^[metadata.json]
  • Dynamic: CAPE skipped — no Windows guest available. All behavior derived from static RE.

This sample is the sixth confirmed morph under the 9d2ca3 umbrella. Unlike prior MinGW siblings that embedded encrypted payloads locally, this binary is a pure network-downloader stub.

How It Works

  1. WOW64 check. Resolves IsWow64Process dynamically via GetModuleHandleA + GetProcAddress on kernel32.dll, compares the result against the target PE's machine type (AMD64), and aborts if the environments mismatch ^[r2:fcn.14000145c].
  2. URL decode. If no command-line argument is supplied, main() XOR-decodes a 36-byte hardcoded C2 URL from five movabs qword constants on the stack using key 0xAA ^[r2:sym.main]. The decoded URL is http://89.125.188.171/nah11/file.exe.
  3. Download. DownloadPE() initializes WinHTTP with User-Agent "Lak2oes", cracks the URL, opens a GET request, and streams the response into a heap-allocated buffer ^[r2:sym.DownloadPE_char_const__unsigned_long_].
  4. Reflective load. LoadPERaw() validates MZ/PE/AMD64 magic, allocates RWX memory, copies headers and sections, resolves imports via LoadLibraryA + GetProcAddress, processes relocations, and jumps to the mapped entry point ^[r2:fcn.1400014c5]. A "Starting PE...\n" string is logged just before transfer.
  5. Cleanup. The downloaded buffer is freed after the handoff.

Decompiled Behavior

Entry (main) — 0x140001f6f Zeroes a 224-byte stack buffer, branches on argc. If argc > 1, copies argv[1] into the URL slot with strncpy(127). Otherwise, copies five movabs qword constants to [rbp-0xc0] and XOR-loops (i=0; i<=35; i++) with 0xAA, writing the plaintext URL to [rbp-0x90]. Passes the decrypted URL to DownloadPE(&url, &size), checks the returned pointer, then calls LoadPERaw(ptr, size) and free(ptr) ^[r2:sym.main].

Downloader (DownloadPE) — 0x1400019b5 Allocates a large stack frame, calls WinHttpOpen("Lak2oes", 0, 0, 0, 0), converts the URL to widechar with MultiByteToWideChar(CP_UTF8), cracks it with WinHttpCrackUrl, connects via WinHttpConnect, opens a GET request with WinHttpOpenRequest(NULL, "G", NULL, ...), sends it with WinHttpSendRequest, receives with WinHttpReceiveResponse, queries content length via WinHttpQueryHeaders, reads the payload into a malloc'd buffer via WinHttpReadData, and returns the buffer + size to the caller ^[r2:sym.DownloadPE_char_const__unsigned_long_]. Every failure path closes all WinHTTP handles and returns NULL.

Loader (LoadPERaw) — 0x1400014c5 A hand-written PE mapper. Validates e_magic==MZ, Signature==PE, Machine==AMD64, then checks IsWow64() against the target architecture — mismatch causes an early abort. Allocates a destination buffer with VirtualAlloc(..., 0x3000, PAGE_EXECUTE_READWRITE), copies the full raw image, walks section headers copying each section to its VirtualAddress, walks the import directory resolving each DLL with LoadLibraryA and each import by ordinal or name with GetProcAddress, walks the base-relocation directory patching offsets with the image-base delta, and finally calls the mapped entry point via a cast function pointer ^[r2:fcn.1400014c5].

WOW64 resolver — 0x14000145c Standard runtime resolution: GetModuleHandleA("kernel32.dll") → GetProcAddress("IsWow64Process"). Result cached in a stack local ^[r2:fcn.14000145c].

C2 Infrastructure

Indicator Value Provenance
URL http://89.125.188.171/nah11/file.exe ^[r2:sym.main] (XOR-decoded from stack constants)
Method HTTP GET ^[r2:sym.DownloadPE_char_const__unsigned_long_]
User-Agent Lak2oes ^[r2:sym.DownloadPE_char_const__unsigned_long_]
IP 89.125.188.171 (Moldova / AS43289) derived from decoded URL
Path /nah11/file.exe derived from decoded URL

No other network IOCs, domains, or hardcoded IPs were recovered.

Interesting Tidbits

  • Compiler freshness. GCC 15.2.0 is very recent (May 2026 build). The MSYS2 Rev8 toolchain string appears 40 times in .rdata, a hallmark of MinGW-w64 debug-info retention ^[strings.txt:33].
  • XOR key in plain sight. The decompiled main uses xor eax, 0xffffffaa — the high bytes are a disassembly artifact; the effective byte key is 0xAA ^[r2:sym.main].
  • Reflective loader quality. The PE mapper is competent: it handles both import-by-name and import-by-ordinal, processes the full relocation bitmap, and enforces architecture parity before mapping. This is not a lazy dropper; it's a deliberate loader ^[r2:fcn.1400014c5].
  • No opsec on UA. The hardcoded User-Agent "Lak2oes" is unique and trackable across samples. No attempt to masquerade as a browser.
  • Empty Log stub. Log(char const*) is compiled to a no-op (push rbp / mov rbp,rsp / pop rbp / ret) — a placeholder for debugging that survived release ^[r2:sym.Log_char_const_].
  • No persistence, no registry. Unlike the .NET 4.6.2 stager morph (8f288492), this sample has no persistence mechanism. It is a fire-and-forget downloader.

How To Mess With It (Homelab Replication)

Toolchain: MinGW-w64 GCC 15.2.0 via MSYS2 UCRT64 environment.

Goal: Produce a 64-bit PE that downloads a payload over WinHTTP and maps it reflectively.

  1. Write a minimal C program using winhttp.h and windows.h.
  2. Hardcode a URL string XOR-encrypted with key 0xAA as movabs qword constants, or simply store it in .rdata and XOR at runtime.
  3. Call WinHttpOpen, WinHttpCrackUrl, WinHttpConnect, WinHttpOpenRequest with "GET", WinHttpSendRequest, WinHttpReceiveResponse, WinHttpReadData.
  4. Allocate a new buffer with VirtualAlloc(..., MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE).
  5. Implement a minimal PE mapper: validate MZ/PE/AMD64, copy headers and sections, resolve imports via LoadLibraryA + GetProcAddress, apply relocations, and call ((void(*)())entry)().
  6. Compile: gcc -m64 -O2 -std=gnu99 -fno-builtin -o downloader.exe main.c -lwinhttp.
  7. Verification: Run the reproducer against a local HTTP server hosting a tiny x64 PE. The target PE should execute without ever touching disk. Compare against this sample's capa fingerprint once capa signatures are available.

Deployable Signatures

YARA

rule nine_d2_ca3_mingw_reflective_downloader
{
    meta:
        description = "MinGW-w64 reflective PE loader / HTTP downloader used by 9d2ca3 cluster"
        author = "PacketPursuit"
        date = "2026-08-13"
        reference = "022fe01a4fb8855747a4068de2131ceb7486cf846df6c18adbefe7564482adb5"
    strings:
        $pe_msg = "Starting PE...\n" ascii
        $ua = "Lak2oes" ascii wide
        $mingw_fail = "Mingw-w64 runtime failure:" ascii
        $gcc = "GCC: (Rev8, Built by MSYS2 project) 15.2.0" ascii
        $xor_decode = { c7 45 fc 00 00 00 00 eb ?? 8b 45 fc 48 98 0f b6 84 05 ?? ?? ff ff 83 f0 aa }
        $winhttp_get = { 48 8d 15 ?? ?? ?? ?? 41 b8 0d 00 00 00 4c 89 c1 e8 ?? ?? ?? ?? }
    condition:
        uint16(0) == 0x5A4D and
        pe.is_pe and
        pe.machine == pe.MACHINE_AMD64 and
        2 of ($pe_msg, $ua, $mingw_fail) and
        1 of ($xor_decode, $winhttp_get)
}

Behavioral Hunt Query (KQL)

let suspicious_ua = dynamic(["Lak2oes"]);
NetworkConnection
| where InitiatingProcessLoadedDll contains "winhttp.dll"
| where Url matches regex @"^http://\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/.*\.exe$"
| where not(InitiatingProcessImageName in~ ("iexplore.exe", "chrome.exe", "firefox.exe", "edge.exe", "msedge.exe"))
| summarize count() by InitiatingProcessId, InitiatingProcessImageName, Url, UserAgent
| where count_ > 1 or UserAgent in (suspicious_ua)

IOC List

Type Value
SHA-256 022fe01a4fb8855747a4068de2131ceb7486cf846df6c18adbefe7564482adb5
URL http://89.125.188.171/nah11/file.exe
User-Agent Lak2oes
IP 89.125.188.171
Compile time 2026-05-28 12:10:32 UTC

Behavioral Fingerprint

This 64-bit MinGW-w64 binary initializes the WinHTTP stack with a hardcoded User-Agent "Lak2oes", performs an unencrypted HTTP GET to an IP-based URL ending in .exe, downloads the response body into a heap buffer, then manually maps the buffer as a PE image into freshly allocated RWX memory — resolving imports, applying relocations, and transferring execution without ever writing the payload to disk.

Detection Signatures

ATT&CK Technique ID Evidence
Command-Line Argument Handling T1059 strncpy from argv[1] into URL buffer when argc > 1 ^[r2:sym.main]
Application Layer Protocol: Web Protocols T1071.001 WinHTTP GET to 89.125.188.171 ^[r2:sym.DownloadPE_char_const__unsigned_long_]
Process Injection T1055 Reflective in-memory PE mapping with RWX allocation ^[r2:fcn.1400014c5]
Reflective Code Loading T1620 LoadPERaw maps downloaded payload without disk write ^[r2:fcn.1400014c5]
Sandbox Evasion: System Checks T1497.001 IsWow64Process runtime resolution for architecture parity ^[r2:fcn.14000145c]
Obfuscated Files or Information: Software Packing T1027.002 C2 URL XOR-encrypted with key 0xAA in code ^[r2:sym.main]

References

Provenance

Static inputs: file.txt, exiftool.json, pefile.txt, strings.txt, rabin2-info.txt, metadata.json, triage.json. Reverse engineering: radare2 analysis level 3, sym.main, sym.DownloadPE_char_const__unsigned_long_, fcn.1400014c5, fcn.14000145c decompiled via radare2 MCP. No dynamic detonation available (CAPE skipped — no Windows guest). capa and floss tooling not operational at time of analysis.