Documentation Index
Fetch the complete documentation index at: https://mintlify.com/JoasASantos/SysWhispers4/llms.txt
Use this file to discover all available pages before exploring further.
Overview
SysWhispers4 is a Python-based code generator that produces C/ASM syscall stubs. Understanding the project structure helps you:
- Extend functionality (add new syscalls, resolution methods, etc.)
- Debug generation issues
- Contribute to the project
- Customize for specific needs
Directory Layout
SysWhispers4/
├── syswhispers.py # CLI entry point
├── core/ # Core generation logic
│ ├── __init__.py # Package initialization
│ ├── models.py # Enums, dataclasses, type definitions
│ ├── generator.py # Main code generation engine (~1900 lines)
│ ├── obfuscator.py # Obfuscation: junk code, eggs, XOR
│ └── utils.py # Hashing, data loading, helpers
├── data/ # Static data files
│ ├── prototypes.json # 64 NT function signatures
│ ├── presets.json # 8 function presets (common, injection, etc.)
│ ├── syscalls_nt_x64.json # x64 syscall number table
│ └── syscalls_nt_x86.json # x86 syscall number table
├── scripts/ # Maintenance scripts
│ └── update_syscall_table.py # Fetch latest syscall numbers from j00ru
├── examples/ # Integration examples
│ └── example_injection.c # Reference injection code
├── .gitignore # Git ignore rules
└── README.md # Main documentation
Core Modules
syswhispers.py - CLI Entry Point
Purpose: Command-line interface, argument parsing, orchestration
Key responsibilities:
- Parse command-line arguments (
--preset, --method, --resolve, etc.)
- Validate function names against
prototypes.json
- Load preset configurations from
presets.json
- Instantiate
Generator class
- Write output files (
.h, .c, .asm/.c stubs)
Main flow:
def main():
args = parse_arguments() # Parse CLI args
functions = resolve_functions(args) # Expand presets → function list
ssn_table = load_syscall_table(args) # Load data/syscalls_nt_*.json
gen = Generator(
functions=functions,
arch=args.arch,
method=args.method,
resolve=args.resolve,
# ... evasion flags
)
gen.generate() # Generate all code
gen.write_files(args.out_dir) # Write to disk
When to modify:
- Adding new CLI flags
- Changing default behaviors
- Adding new output file types
core/models.py - Type Definitions
Purpose: Enums and dataclasses representing configuration options
Key types:
class Architecture(Enum):
X64 = "x64" # 64-bit Windows (syscall)
X86 = "x86" # 32-bit Windows (sysenter)
WOW64 = "wow64" # 32-bit on 64-bit (Heaven's Gate)
ARM64 = "arm64" # Windows on ARM (SVC #0)
class ResolveMethod(Enum):
STATIC = "static" # Embedded j00ru table
HELLS_GATE = "hells_gate" # Read mov eax, <SSN> opcode
HALOS_GATE = "halos_gate" # Neighbor scan ±8
TARTARUS = "tartarus" # Detect JMP/CALL hooks, ±16
FRESHYCALLS = "freshycalls" # Sort exports by VA
FROM_DISK = "from_disk" # Map clean ntdll from \KnownDlls
RECYCLED = "recycled" # FreshyCalls + opcode validation
HW_BREAKPOINT = "hw_breakpoint" # DR registers + VEH
class InvocationMethod(Enum):
EMBEDDED = "embedded" # Direct syscall in your code
INDIRECT = "indirect" # JMP to ntdll gadget
RANDOMIZED = "randomized" # Random gadget per call (pool of 64)
EGG = "egg" # Egg hunt (no syscall on disk)
class Compiler(Enum):
MSVC = "msvc" # Microsoft Visual C++ (MASM)
MINGW = "mingw" # MinGW GCC (GAS inline)
CLANG = "clang" # Clang (GAS inline)
@dataclass
class SyscallFunction:
name: str # e.g., "NtAllocateVirtualMemory"
return_type: str # e.g., "NTSTATUS"
parameters: List[Parameter] # List of (type, name) tuples
ssn: Optional[int] # Syscall number (if using static resolution)
When to modify:
- Adding new architectures (e.g., RISC-V)
- Adding new resolution methods
- Adding new invocation techniques
core/generator.py - Code Generation Engine
Purpose: Core logic for generating C/ASM code
Size: ~1900 lines (largest module)
Key classes:
class Generator:
def __init__(self, functions, arch, method, resolve, compiler, **evasion_flags):
self.functions = functions # List[SyscallFunction]
self.arch = Architecture(arch)
self.method = InvocationMethod(method)
self.resolve = ResolveMethod(resolve)
self.compiler = Compiler(compiler)
self.evasion = evasion_flags # obfuscate, encrypt_ssn, etc.
def generate(self):
self.gen_types_header() # SW4Syscalls_Types.h
self.gen_header() # SW4Syscalls.h
self.gen_c_file() # SW4Syscalls.c
if self.compiler == Compiler.MSVC:
self.gen_asm_file() # SW4Syscalls.asm (MASM)
else:
self.gen_stubs_file() # SW4Syscalls_stubs.c (GAS inline)
Key methods:
| Method | Generates | Example |
|---|
gen_types_header() | NT type definitions | typedef struct _UNICODE_STRING {...} |
gen_header() | Function prototypes | NTSTATUS SW4_NtAllocateVirtualMemory(...) |
gen_c_file() | Runtime SSN resolution | BOOL SW4_Initialize(void) {...} |
gen_asm_file() | MASM syscall stubs | SW4_NtAllocateVirtualMemory PROC |
gen_stubs_file() | GAS inline stubs | __asm__ volatile ("mov r10, rcx...") |
gen_ssn_table() | Static SSN array | DWORD SW4_SsnTable[] = {0x18, 0x50, ...} |
gen_gadget_pool() | Indirect gadget array | PVOID SW4_GadgetPool[64] = {...} |
gen_unhook_function() | ntdll unhooking code | SW4_UnhookNtdll() |
gen_etw_bypass() | ETW patch function | SW4_PatchEtw() |
gen_amsi_bypass() | AMSI patch function | SW4_PatchAmsi() |
gen_sleep_encrypt() | Ekko sleep encryption | SW4_SleepEncrypt(DWORD ms) |
Architecture-specific code paths:
def gen_asm_stub(self, func: SyscallFunction):
if self.arch == Architecture.X64:
return self._gen_x64_stub(func)
elif self.arch == Architecture.X86:
return self._gen_x86_stub(func)
elif self.arch == Architecture.WOW64:
return self._gen_wow64_stub(func) # Heaven's Gate
elif self.arch == Architecture.ARM64:
return self._gen_arm64_stub(func) # SVC #0
When to modify:
- Adding new NT functions (update
data/prototypes.json first)
- Implementing new evasion techniques
- Supporting new architectures
- Changing stub format/structure
core/obfuscator.py - Obfuscation Module
Purpose: Code obfuscation, junk injection, egg hunt, SSN encryption
Key functions:
def inject_junk_instructions(asm_code: str, intensity: int = 3) -> str:
"""
Inject harmless junk instructions between real opcodes.
Variants (14 total):
- nop
- xchg ax, ax
- lea r11, [r11]
- push 0x42; pop r11
- test r11d, 0xABh
- fnop
- ... and more
"""
junk_variants = [
"nop",
"xchg ax, ax",
"lea r11, [r11]",
# ... 11 more
]
# Randomly insert junk between lines
return obfuscated_code
def generate_egg_marker() -> bytes:
"""
Generate random 8-byte egg marker (replaces syscall opcode).
Must not collide with valid opcodes.
"""
return os.urandom(8)
def encrypt_ssn_table(ssn_table: List[int], xor_key: int) -> List[int]:
"""
XOR-encrypt SSN values with compile-time random key.
"""
return [ssn ^ xor_key for ssn in ssn_table]
def randomize_stub_order(functions: List[SyscallFunction]) -> List[SyscallFunction]:
"""
Shuffle function order to break signature matching.
Uses secure random.shuffle().
"""
import random
shuffled = functions.copy()
random.shuffle(shuffled)
return shuffled
When to modify:
- Adding new junk instruction variants
- Implementing new obfuscation techniques (control flow flattening, string encryption, etc.)
- Changing egg marker generation algorithm
core/utils.py - Helper Functions
Purpose: Hash functions, file I/O, data loading utilities
Key functions:
def djb2_hash(s: str) -> int:
"""DJB2 string hash (used for function name hashing)."""
h = 5381
for c in s:
h = ((h << 5) + h) + ord(c) # h * 33 + c
return h & 0xFFFFFFFF
def crc32_hash(s: str) -> int:
"""CRC32 hash."""
import zlib
return zlib.crc32(s.encode()) & 0xFFFFFFFF
def fnv1a_hash(s: str) -> int:
"""FNV-1a hash."""
h = 2166136261
for c in s:
h ^= ord(c)
h = (h * 16777619) & 0xFFFFFFFF
return h
def load_json(path: Path) -> dict:
"""Load JSON file with error handling."""
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
def write_file(path: Path, content: str) -> None:
"""Write file with UTF-8 encoding."""
with open(path, 'w', encoding='utf-8', newline='\n') as f:
f.write(content)
When to modify:
- Adding new hash algorithms
- Changing file encoding/format
- Adding data validation logic
Data Files
data/prototypes.json - Function Signatures
Format:
{
"NtAllocateVirtualMemory": {
"return_type": "NTSTATUS",
"parameters": [
{"type": "HANDLE", "name": "ProcessHandle"},
{"type": "PVOID*", "name": "BaseAddress"},
{"type": "ULONG_PTR", "name": "ZeroBits"},
{"type": "PSIZE_T", "name": "RegionSize"},
{"type": "ULONG", "name": "AllocationType"},
{"type": "ULONG", "name": "Protect"}
]
},
...
}
Total functions: 64
Categories:
- Memory (8 functions)
- Section/Mapping (4)
- Process (10)
- Thread (13)
- Handle/Sync (10)
- File (5)
- Token (6)
- Transaction (3)
- Misc (5)
When to modify:
- Adding new NT functions
- Fixing incorrect signatures (check MSDN or ntdll headers)
data/presets.json - Function Presets
Format:
{
"common": {
"description": "Common functions for process/thread/memory operations",
"functions": [
"NtAllocateVirtualMemory",
"NtFreeVirtualMemory",
...
]
},
"injection": { ... },
"evasion": { ... },
"token": { ... },
"stealth": { ... },
"file_ops": { ... },
"transaction": { ... },
"all": { ... }
}
Presets:
common (25 functions)
injection (20)
evasion (15)
token (6)
stealth (32)
file_ops (7)
transaction (7)
all (64)
When to modify:
- Creating custom presets for specific use cases
- Adjusting existing preset function lists
data/syscalls_nt_x64.json - x64 Syscall Table
Format:
{
"_comment": "NT syscall numbers — generated by SysWhispers4/scripts/update_syscall_table.py",
"_source": "https://github.com/j00ru/windows-syscalls",
"_format": "FunctionName -> { build_key -> decimal_ssn }",
"_windows_builds": {
"7_sp1": "Windows 7 SP1",
"10240": "Windows 10 1507 (build 10240)",
"22000": "Windows 11 21H2 (build 22000)",
"26100": "Windows 11 24H2 (build 26100)",
...
},
"NtAllocateVirtualMemory": {
"7_sp1": 24,
"10240": 24,
"14393": 24,
"22000": 24,
"26100": 24
},
...
}
Coverage:
When to update:
- New Windows build released (use
scripts/update_syscall_table.py)
- SSN changes detected
data/syscalls_nt_x86.json - x86 Syscall Table
Same format as x64, but for 32-bit Windows.
Key differences:
- Uses
sysenter instead of syscall
- SSN values differ from x64
- Covers legacy Windows (NT 4.0, 2000, XP, etc.)
Scripts
scripts/update_syscall_table.py - Table Updater
Purpose: Fetch latest syscall numbers from j00ru’s repository
Usage:
# Update x64 table (default)
python scripts/update_syscall_table.py
# Update both x64 and x86
python scripts/update_syscall_table.py --arch x64,x86
# Update specific functions only
python scripts/update_syscall_table.py --functions NtAllocateVirtualMemory,NtCreateThreadEx
# Custom output path
python scripts/update_syscall_table.py --out custom_table.json
How it works:
- Fetches CSV from
https://raw.githubusercontent.com/j00ru/windows-syscalls/master/x64/csv/nt.csv
- Parses CSV columns (each column = Windows build)
- Maps human-readable build names to short keys (
"Windows 10 1909" → "18363")
- Converts hex SSN values to decimal
- Filters to
Nt* functions only
- Writes JSON to
data/syscalls_nt_x64.json
When to run:
- After new Windows build release (e.g., 25H2)
- When j00ru updates his repository
- Before generating stubs for new OS version
See the Syscall Table Updates page for detailed workflow.
Generated Output Files
MSVC (Default)
SW4Syscalls_Types.h # NT type definitions (structures, enums)
SW4Syscalls.h # Function prototypes + SW4_Initialize() + evasion API
SW4Syscalls.c # Runtime SSN resolution + helper functions
SW4Syscalls.asm # MASM syscall stubs (x64/x86/WoW64)
MinGW / Clang
SW4Syscalls_Types.h # NT type definitions
SW4Syscalls.h # Function prototypes
SW4Syscalls.c # Runtime SSN resolution
SW4Syscalls_stubs.c # GAS inline assembly stubs
Key difference: GAS uses inline assembly in .c files instead of separate .asm
Code Flow Example
User runs:
python syswhispers.py --preset common --method indirect --resolve freshycalls
Execution flow:
-
syswhispers.py
- Parse args:
preset="common", method="indirect", resolve="freshycalls"
- Load
data/presets.json → expand "common" to 25 function names
- Load
data/prototypes.json → get signatures for those 25 functions
- Load
data/syscalls_nt_x64.json → get SSN values (not used for FreshyCalls, but loaded for static fallback)
-
core/generator.py
- Instantiate
Generator(functions=25, arch=X64, method=INDIRECT, resolve=FRESHYCALLS, compiler=MSVC)
- Call
gen.generate():
gen_types_header() → Create SW4Syscalls_Types.h
gen_header() → Create SW4Syscalls.h with 25 function prototypes
gen_c_file() → Create SW4Syscalls.c:
- Generate
SW4_Initialize() with FreshyCalls logic (sort exports by VA)
- Generate
SW4_FindSyscallGadgets() for indirect method (scan ntdll for syscall;ret)
gen_asm_file() → Create SW4Syscalls.asm:
- For each function, generate indirect stub:
SW4_NtAllocateVirtualMemory PROC
mov r10, rcx
mov eax, DWORD PTR [SW4_SsnTable + 0*4] ; Load SSN from table
jmp QWORD PTR [SW4_SyscallGadget] ; Jump to ntdll gadget
SW4_NtAllocateVirtualMemory ENDP
-
syswhispers.py (file output)
- Write 4 files to disk
- Print summary:
[+] Generated 25 syscall stubs (x64, MSVC)
[+] Resolution: FreshyCalls (sort-by-VA)
[+] Invocation: Indirect (ntdll gadget)
[+] Files:
SW4Syscalls_Types.h
SW4Syscalls.h
SW4Syscalls.c
SW4Syscalls.asm
Extension Points
Adding a New Resolution Method
-
Add enum to
core/models.py:
class ResolveMethod(Enum):
# ... existing methods
MY_NEW_METHOD = "my_new_method"
-
Implement logic in
core/generator.py:
def gen_c_file(self):
# ...
if self.resolve == ResolveMethod.MY_NEW_METHOD:
init_code += self._gen_my_new_method_code()
def _gen_my_new_method_code(self):
return """
// Your SSN resolution logic here
for (int i = 0; i < num_functions; i++) {
SW4_SsnTable[i] = resolve_ssn_via_my_method(function_names[i]);
}
"""
-
Update CLI in
syswhispers.py:
parser.add_argument(
"-r", "--resolve",
choices=["static", "hells_gate", ..., "my_new_method"],
default="freshycalls"
)
Adding a New Invocation Method
Similar process:
- Add to
InvocationMethod enum
- Implement stub generation in
_gen_x64_stub() (or other arch methods)
- Update CLI
Adding a New Architecture
- Add to
Architecture enum
- Implement
_gen_<arch>_stub() method
- Update syscall instruction/register mappings
- Add syscall table JSON (if SSNs differ)
Testing Your Changes
Unit Tests (Future)
Currently no formal test suite. Recommended approach:
# tests/test_generator.py
import pytest
from core.generator import Generator
from core.models import Architecture, InvocationMethod, ResolveMethod
def test_freshycalls_x64_embedded():
gen = Generator(
functions=["NtAllocateVirtualMemory"],
arch=Architecture.X64,
method=InvocationMethod.EMBEDDED,
resolve=ResolveMethod.FRESHYCALLS,
compiler=Compiler.MSVC,
)
gen.generate()
# Verify output
assert "SW4_NtAllocateVirtualMemory PROC" in gen.asm_code
assert "mov r10, rcx" in gen.asm_code
assert "syscall" in gen.asm_code
Integration Tests
# Generate stubs
python syswhispers.py --preset common -o test_output/
# Compile with MSVC
cd test_output
cl /c test.c SW4Syscalls.c SW4Syscalls.asm
link test.obj SW4Syscalls.obj /OUT:test.exe
# Run
test.exe
Contributing Guidelines
-
Follow existing code style:
- 4-space indentation
- Type hints for all functions
- Docstrings for public APIs
-
Update
prototypes.json when adding functions:
- Verify signature against MSDN or ntdll headers
- Test on multiple Windows versions
-
Run
update_syscall_table.py before release:
- Ensure SSN tables are current
-
Test on multiple configurations:
- MSVC, MinGW, Clang
- x64, x86, WoW64, ARM64 (if applicable)
- All resolution methods
- All invocation methods
-
Document new features in README.md
Debugging Tips
Enable Verbose Output
python syswhispers.py --preset common --verbose
Shows detailed generation steps, loaded data, etc.
Inspect Generated Code
Before compiling, review the generated .c and .asm files:
# Check initialization logic
cat SW4Syscalls.c | grep -A 50 "SW4_Initialize"
# Verify stub format
cat SW4Syscalls.asm | grep -A 10 "NtAllocateVirtualMemory PROC"
Use Debugger
Set breakpoints in your compiled binary:
WinDbg> bp SW4_Initialize
WinDbg> g
WinDbg> k # Check call stack
WinDbg> dt SW4_SsnTable # Inspect SSN table
Measure Initialization Time
#include <windows.h>
#include "SW4Syscalls.h"
int main() {
LARGE_INTEGER freq, start, end;
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&start);
SW4_Initialize();
QueryPerformanceCounter(&end);
double ms = (double)(end.QuadPart - start.QuadPart) * 1000.0 / freq.QuadPart;
printf("SW4_Initialize() took %.3f ms\n", ms);
}
Typical results:
- Static: ~0.001 ms (just loads from table)
- FreshyCalls: ~0.2 ms (sorts ~500 exports)
- From disk: ~5 ms (maps section, parses PE)
Further Reading