Skip to content

Instantly share code, notes, and snippets.

@ARISTODE
Created November 4, 2025 05:10
Show Gist options
  • Select an option

  • Save ARISTODE/2d6f17fbed1f4e3a29e7166a5a4bf017 to your computer and use it in GitHub Desktop.

Select an option

Save ARISTODE/2d6f17fbed1f4e3a29e7166a5a4bf017 to your computer and use it in GitHub Desktop.

In-memory loader decrypts and manually maps an embedded payload before handing execution to its DllMain.

Investigation Log

  • Step 1: Confirmed IDA attachment to pikabot.exe and enumerated entry points (start at 0x49f3a2, TLS callback at 0x519630) to understand initial execution vectors.
  • Step 2: Reviewed CRT startup at 0x49f22b and wWinMain_ManualLoaderEntry (0x519b8f) to see the real work funneled into ManualMapEmbeddedDll.
  • Step 3: Checked TlsCallback_ProcessDetach (0x519630) and its thunk TlsCallback_CleanupThunk → TlsContextCleanup for context; noted it only frees loader state.
  • Step 4: Disassembled ManualMapEmbeddedDll with Capstone to decode its prologue at 0x519bc9 building XOR key %NkCus1$1f@7tWUF9 and the kernel32.dll string needed for dynamic API lookup.
  • Step 5: Decompiled helpers (FindLoadedModuleByName, ResolveExportByHash, HashExportName) and verified hash constants (0xe3142, 0xd5786, 0x348bfa) by scripting, mapping them to VirtualAlloc, LoadLibraryA, and GetProcAddress.
  • Step 6: Traced the decode loop at 0x519db3–0x519e13 pulling bytes from table 0x704520 and XORing with the stack key to reconstruct an embedded PE image into the first VirtualAlloc buffer.
  • Step 7: Followed the manual loader pipeline: second allocation sized via GetEmbeddedImageSize, section copies with CopyBytes, relocation fix-ups (0x519fb3–0x51a08a), import resolution via runtime LoadLibraryA/GetProcAddress, and TLS callback dispatch.
  • Step 8: Observed the final hand-off at 0x51a1e7–0x51a204 invoking the mapped payload’s DllMain (parameters 1 / 0) and concluded the sample is a staged loader.

Function Mapping Table

  • _wWinMain@16 → wWinMain_ManualLoaderEntry (0x519b8f) – trampoline that immediately invokes the loader.
  • sub_519BBF → ManualMapEmbeddedDll (0x519bbf) – decrypts and maps the embedded payload.
  • TlsCallback_0 → TlsCallback_ProcessDetach (0x519630) – triggers cleanup on detach.
  • sub_5184A0 → TlsCallback_CleanupThunk (0x5184a0) – thunk into TLS cleanup routine.
  • sub_519360 → TlsContextCleanup (0x519360) – frees loader TLS structures.
  • sub_51A2EF → GetPeb (0x51a2ef) – retrieves the PEB for module walking.
  • sub_51A30F → WideStringLength (0x51a30f) – helper for Unicode lengths.
  • sub_51A34F → CompareModuleName (0x51a34f) – case-insensitive Unicode compare.
  • sub_51A42F → HashExportName (0x51a42f) – export-name hash used for API lookup.
  • sub_51A46F → FindLoadedModuleByName (0x51a46f) – walks the PEB loader list.
  • sub_51A4CF → ResolveExportByHash (0x51a4cf) – locates exports by hash.
  • sub_51A21F → GetEmbeddedImageSize (0x51a21f) – reads SizeOfImage from decoded PE.
  • sub_51A29F → CopyBytes (0x51a29f) – minimal memcpy wrapper.
  • sub_51A24F → HasTlsDirectory (0x51a24f) – checks TLS directory presence.

Behavioral Analysis

  • Capabilities: decrypts an embedded PE blob, maps it into executable memory, rebuilds relocations/ imports/TLS, and transfers control to the payload without touching disk.
  • Implementation: dynamic API discovery via PEB walking and HashExportName; custom decoder mixing the stack key %NkCus1$1f@7tWUF9 with table 0x704520; relocation fix-ups using CopyBytes; import resolution loop that repeatedly calls runtime LoadLibraryA/GetProcAddress; TLS callback enumeration followed by a DllMain call with (base, DLL_PROCESS_ATTACH, 0).
  • Execution Flow: wWinMain_ManualLoaderEntry → ManualMapEmbeddedDll sets up key data → resolves VirtualAlloc/LoadLibraryA/GetProcAddress by hash → decodes payload into staging buffer → allocates final image → copies headers/sections & applies relocations → resolves imports/TLS → invokes payload DllMain → returns to CRT exit path.

Evidence Summary

  • ManualMapEmbeddedDll:0x519bc9-0x519c0d writes the XOR key %NkCus1$1f@7tWUF9; 0x519c1f-0x519cb1 constructs kernel32.dll for module lookup.
  • ManualMapEmbeddedDll:0x519d4e-0x519d87 calls ResolveExportByHash with hashes 0xe3142 / 0xd5786 / 0x348bfa to retrieve VirtualAlloc, LoadLibraryA, GetProcAddress.
  • ManualMapEmbeddedDll:0x519d8a-0x519da1 allocates 0x30E00 bytes for the decoded payload.
  • ManualMapEmbeddedDll:0x519db3-0x519e13 iterates over payload bytes, indexing table 0x704520 and XORing with the key before writing into the staging buffer.
  • ManualMapEmbeddedDll:0x519e27-0x519e4c queries SizeOfImage via GetEmbeddedImageSize and allocates executable memory for the final image.
  • ManualMapEmbeddedDll:0x519ef3-0x519f9c copies section data into the mapped region using CopyBytes.
  • ManualMapEmbeddedDll:0x519fb3-0x51a08a parses relocation blocks, adjusts pointers by the allocation delta, and writes them back.
  • ManualMapEmbeddedDll:0x51a094-0x51a15f walks import descriptors, dynamically calling LoadLibraryA/ GetProcAddress to patch the IAT in memory.
  • ManualMapEmbeddedDll:0x51a179-0x51a1d6 checks HasTlsDirectory, iterates TLS callbacks, and invokes each with (module_base, DLL_PROCESS_ATTACH, 0).
  • ManualMapEmbeddedDll:0x51a1e7-0x51a204 makes the final call to the payload’s DllMain with the process’s instance handle.

Verdict

  • Malicious. The program is a purpose-built in-memory loader that decrypts an embedded PE, manually maps it, resolves imports via hashed lookups, executes TLS callbacks, and transfers execution to the payload’s DllMain. This behavior is characteristic of multi-stage malware such as Pikabot loaders and has no benign application.

Suggested next steps:

  1. Dump the mapped payload after ManualMapEmbeddedDll completes to analyze stage two.
  2. Monitor runtime network activity while executing under instrumentation to profile command-and- control behavior.

Agent reasoning explained by itself: I approached it like I would any opaque loader: start at the true entry, map the call graph, validate assumptions, and dive deeper only where evidence pointed.

  • First I used standard triage heuristics—look for TLS callbacks and CRT stubs—to pin down the real execution path (start → _tmainCRTStartup → wWinMain). Years of reversing loaders taught me that samples often bypass the obvious entry, so I always check for TLS first.
  • Once wWinMain jumped straight into a large helper, that function became my focus. Knowing big prologues with byte-by-byte stores usually indicate embedded data, I disassembled the prologue to confirm it was seeding a key and module strings.
  • From there, prior experience with packed droppers led me to watch for hash-based API resolution. The code patterns (PEB walk, XOR loops, VirtualAlloc) matched common manual-mapper blueprints, so I verified the hashes against known exports using a quick Python script. That confirmed the hypothesis.
  • With the structure established—decode, allocate, copy, relocate, import fix—I walked each stage in order, because manual loaders typically follow the PE layout. When the decompiler truncated, I grabbed raw bytes and used Capstone to view the missing segments, guided by the call flow I’d already sketched out.
  • Throughout, I logged each observation and renamed functions as soon as I understood them. Capturing evidence (addresses, API calls) while it was fresh ensured the final report stayed traceable.
@pehyinchin
Copy link

pehyinchin commented Nov 6, 2025


Years of reversing loaders taught me that samples often bypass the obvious entry, so I always check for TLS first.

😅

update: c.f. https://gist.github.com/ARISTODE/75ab5af813136fb9feb46c2be0a187ea

You are an expert reverse engineer specializing in malware analysis using IDA Pro.

no wonder … haha

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment