typeanalysisfamilyesmk-crypter-loaderconfidencemedium
SHA-256: 3dc65c75a559b15e60f09abfbdb1d4399cda6dc4134a793930d90e21d4ceebe3

esmk-crypter-loader: 3dc65c75 — MinGW-w64 reflective PE loader with nibble-encoded resource payload

Executive Summary

Stripped x64 PE compiled with MinGW-w64 GCC 15.2.0. Internal name "ESMK" via debug string. The binary is a custom crypter/loader: it extracts an encrypted payload from its own .rsrc section (type 0x0a, name 0x65), nibble-decodes it, decrypts it with an algorithm selected from embedded constants, decompresses via LZSS/XZ, and maps the result as a reflective PE image in freshly allocated RWX memory. No C2 or persistence logic lives in the loader stage; the final payload family is unknown statically.

1. Build / RE

Toolchain. MinGW-w64 GCC 15.2.0 (Brecht Sanders build, r7), compiled 2024-04-08 15:44:53 UTC. ^[exiftool.json] ^[rabin2-info.txt:11] ^[strings.txt:280-300] Stripped x64 PE, ASLR/NX-compatible, with external PDB reference. ^[pefile.txt:38-74]

Obfuscation. No commercial packer (UPX, VMProtect, Themida) — the binary is its own loader. Control-flow flattening at fcn.140001f00: a state variable seeded with XOR constant 0x1a83e4a0 drives an 8-case jump table at 0x140001fac. ^[r2:fcn.140001f00] The .rsrc section is 185,856 bytes (raw offset 0x28800) and holds the encrypted secondary payload. ^[pefile.txt:258-264]

Anti-analysis. Byte gate: value at 0x140026043 must equal 0x31; mismatch triggers MessageBoxA("Invalid DOS signature") then aborts. ^[r2:fcn.1400017b0] Anti-debug gate: XORs the VirtualAlloc base with GetCurrentProcessId, GetCurrentThreadId, and GetTickCount, comparing against 0xffff. ^[r2:fcn.1400017b0]

Decryption / Decompression Pipeline. Three-stage transform inferred from strings and decompilation:

  1. Nibble decode"Nibble-decoded: %lu -> %lu bytes" at strings.txt:244.
  2. Algorithm-selectable decryptionalgorithmID derived from constants 0x6a0f51f7 (.data 0x140026028) and 0x3507a1d7 (.data 0x140026024), masked with 0x1f. ^[r2:fcn.140001f00]
  3. LZSS/XZ decompressionfcn.140003f20 uses the magic constant 0xcf1bbcdcbb000000 (a variant of the XZ CRC-64 polynomial) and exhibits classic LZSS hash-table + match-length encode/decode loops with 0x8080808080808081 division shortcut. ^[r2:fcn.140003f20]

Reflective Mapping. fcn.1400017b0 validates the decrypted buffer as a PE (DOS/PE signatures), allocates RWX memory via VirtualAlloc(flAllocationType=0x3000, flProtect=0x4), copies headers and sections with fcn.140002640, processes relocations, resolves imports, calls RtlAddFunctionTable, and transfers control to the payload entry point. ^[r2:fcn.1400017b0] Strings confirm: "ExecuteFile: Starting (reflective)", "Sections mapped: %u", "Calling entry point: 0x%p", "ExecuteFile succeeded, resuming thread...". ^[strings.txt:220-260]

Notable Functions.

  • fcn.140001f00 — CFF resource loader: FindResourceA(0, 0x65, 0x0a)LockResource → nibble-decode → decrypt → decompress → hand off to mapper. ^[r2:fcn.140001f00]
  • fcn.1400017b0 — Reflective PE mapper. ^[r2:fcn.1400017b0]
  • fcn.140003f20 — LZSS/XZ decompressor engine; called via wrapper fcn.140008620 / fcn.140008680. ^[r2:fcn.140003f20]

2. Deploy / ATT&CK

Static-only — no CAPE guest available. All TTPs inferred from decompiled control flow and string evidence.

  • T1055.012 (Process Injection: Process Hollowing) — fcn.1400017b0 performs reflective in-memory PE mapping: VirtualAlloc + section copy + import resolution + RtlAddFunctionTable + entry-point transfer. ^[r2:fcn.1400017b0]
  • T1027.002 (Obfuscated Files or Information: Software Packing) — Custom nibble encoding + algorithm-selectable encryption + LZSS compression over the .rsrc payload. ^[strings.txt:243-257]
  • T1622 (Debugger Evasion) — Anti-debug fingerprint gate before payload execution. ^[r2:fcn.1400017b0]
  • T1497.001 (Virtualization/Sandbox Evasion: System Checks) — Time-based fingerprinting via GetTickCount mixed with PID/TID. ^[r2:fcn.1400017b0]

Persistence / C2. None in the loader. The final payload is unknown statically.

Attribution. Internal string "ESMK allocated new buffer, freeing old one." at strings.txt:249 gives the family name. OpenCTI tags dropped-by-gcleaner and mix5.file place this in the gcleaner multi-payload distribution cluster, but the MinGW-w64 build and custom crypter are distinct from the Delphi/.NET droppers historically associated with that label. ^[metadata.json] Same distribution-vs-family mismatch seen in prior gcleaner-tagged MSVC builds (e.g. unclassified-pe32-clipper). ^[gcleaner]

Provenance. Static analysis only. All dynamic behaviour inferred from decompiled control flow and string evidence. No CAPE detonation performed.