Overview
SysWhispers4 includes syscall tables for Windows 7 through Windows 11 24H2. When Microsoft releases new Windows builds, syscall numbers can change. This guide shows how to update the tables using the included update_syscall_table.py script.
This is only necessary if you’re using --resolve static (embedded syscall numbers). Dynamic methods like FreshyCalls, Halo’s Gate, etc. automatically adapt to any Windows version.
Why Update Tables?
When Syscall Numbers Change
Microsoft occasionally changes syscall numbers between builds:
// Windows 11 22H2 (build 22621)
NtCreateThreadEx = 0xC6
// Windows 11 23H2 (build 22631)
NtCreateThreadEx = 0xC7 // ← Changed!
If your embedded table has the wrong number, the syscall will:
- Call the wrong kernel function (undefined behavior)
- Trigger an invalid syscall exception
- Return
STATUS_INVALID_SYSTEM_SERVICE
New Windows Builds
Recent builds not in the default table:
- Windows 11 25H2 (build 26200) - Released Q2 2026
- Windows Server 2025 (build 26100) - Released late 2024
- Future builds (use the update script to stay current)
Quick Start
Update x64 Table (Default)
cd SysWhispers4
python scripts/update_syscall_table.py
Output:
[~] Fetching: https://raw.githubusercontent.com/j00ru/windows-syscalls/master/x64/csv/nt.csv
[+] Written 183 functions (x64) → data/syscalls_nt_x64.json
[+] Syscall table update complete.
Update Both x64 and x86
python scripts/update_syscall_table.py --arch x64,x86
Output:
[~] Fetching: https://raw.githubusercontent.com/j00ru/windows-syscalls/master/x64/csv/nt.csv
[+] Written 183 functions (x64) → data/syscalls_nt_x64.json
[~] Fetching: https://raw.githubusercontent.com/j00ru/windows-syscalls/master/x86/csv/nt.csv
[+] Written 231 functions (x86) → data/syscalls_nt_x86.json
[+] Syscall table update complete.
Command-Line Options
Architecture Selection
# x64 only (default)
python scripts/update_syscall_table.py --arch x64
# x86 only
python scripts/update_syscall_table.py --arch x86
# Both
python scripts/update_syscall_table.py --arch x64,x86
Custom Output Path
# Write to custom location instead of data/
python scripts/update_syscall_table.py --out custom_syscalls.json
Filter Specific Functions
# Only update specific functions (useful for testing)
python scripts/update_syscall_table.py \
--functions NtAllocateVirtualMemory,NtCreateThreadEx,NtWriteVirtualMemory
Script Internals
Data Source
The script fetches CSV files from j00ru/windows-syscalls:
CSV_URLS = {
"x64": "https://raw.githubusercontent.com/j00ru/windows-syscalls/master/x64/csv/nt.csv",
"x86": "https://raw.githubusercontent.com/j00ru/windows-syscalls/master/x86/csv/nt.csv",
}
j00ru’s repository is the authoritative source for Windows syscall research, maintained since Windows NT 3.1.
Example x64 CSV header:
Function Name,Windows 7 (SP1),Windows 10 (1507),Windows 10 (1903),Windows 11 and Server (11 24H2),...
NtAllocateVirtualMemory,24,24,24,24,...
NtCreateThreadEx,166,189,199,198,...
Each row = NT function, each column = Windows build.
Parsing Logic
def parse_joru_csv(csv_text: str) -> dict:
reader = csv.reader(io.StringIO(csv_text))
rows = list(reader)
header = rows[0] # Column headers = Windows versions
result = {
"_comment": "NT syscall numbers — generated by SysWhispers4",
"_source": "https://github.com/j00ru/windows-syscalls",
"_windows_builds": {},
}
# Map human-readable version strings to build keys
for col in range(1, len(header)):
version_str = header[col] # e.g., "Windows 11 and Server (11 24H2)"
build_key, display_label = parse_version(version_str) # → ("26100", "Windows 11 24H2")
result["_windows_builds"][build_key] = display_label
# Parse function rows
for row in rows[1:]:
func_name = row[0]
if not func_name.startswith("Nt"):
continue # Skip non-NT functions
func_entry = {}
for col, build_key in enumerate(build_keys, start=1):
ssn_str = row[col].strip()
if ssn_str and ssn_str not in ("", "n/a", "-"):
ssn = int(ssn_str, 16) if ssn_str.startswith("0x") else int(ssn_str)
func_entry[build_key] = ssn
result[func_name] = func_entry
return result
Version Mapping
The script maps j00ru’s human-readable column headers to short build keys:
VER_MAP = {
"Windows 7 (SP1)": ("7_sp1", "Windows 7 SP1"),
"Windows 10 (1507)": ("10240", "Windows 10 1507 (build 10240)"),
"Windows 10 (1903)": ("18362", "Windows 10 1903 (build 18362)"),
"Windows 11 and Server (11 24H2)": ("26100", "Windows 11 24H2 (build 26100)"),
"Windows 11 and Server (Server 2025)": ("26100_srv", "Windows Server 2025 (build 26100)"),
# ... full list in source
}
Why use build numbers instead of version names?
- Consistent across x64/x86
- Unambiguous (“22H2” exists for both Win10 and Win11)
- Easier to match against
RtlGetVersion() output
JSON Structure
{
"_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)",
"18362": "Windows 10 1903 (build 18362)",
"22000": "Windows 11 21H2 (build 22000)",
"26100": "Windows 11 24H2 (build 26100)",
"26100_srv": "Windows Server 2025 (build 26100)"
},
"NtAllocateVirtualMemory": {
"7_sp1": 24,
"10240": 24,
"18362": 24,
"22000": 24,
"26100": 24
},
"NtCreateThreadEx": {
"7_sp1": 166,
"10240": 189,
"18362": 199,
"22000": 197,
"26100": 198
},
...
}
| Key | Purpose |
|---|
_comment | Describes file origin |
_source | Link to j00ru’s repository |
_format | Explains data structure |
_windows_builds | Maps build keys to human-readable labels |
Metadata keys are ignored during generation (filtered by key.startswith("_") check).
How SysWhispers4 Uses the Table
Static Resolution (--resolve static)
# In generator.py
def gen_ssn_table(self):
build_key = self.get_target_build_key() # e.g., "22000" for Win11 21H2
ssn_table = load_syscall_table(self.arch) # Load data/syscalls_nt_x64.json
code = "DWORD SW4_SsnTable[] = {\n"
for func in self.functions:
if func.name in ssn_table and build_key in ssn_table[func.name]:
ssn = ssn_table[func.name][build_key]
code += f" {ssn}, // {func.name}\n"
else:
code += f" 0xFFFFFFFF, // {func.name} (unavailable)\n"
code += "};\n"
return code
Generated code:
DWORD SW4_SsnTable[] = {
24, // NtAllocateVirtualMemory
198, // NtCreateThreadEx
58, // NtWriteVirtualMemory
// ...
};
Runtime Build Detection
To support multiple Windows versions with one binary, use dynamic resolution instead:
# Instead of static:
python syswhispers.py --preset common --resolve static
# Use FreshyCalls (works on any version):
python syswhispers.py --preset common --resolve freshycalls
Why: FreshyCalls sorts exports by virtual address — the sorted index is the syscall number. Works on:
- Windows 7 → 11 (any build)
- Future versions (no table update needed)
Workflow: Updating for New Windows Release
Scenario: Windows 11 25H2 Just Released
Step 1: Wait for j00ru to update his repository
j00ru typically updates within 1-2 weeks of a new build release. Check:
# Visit j00ru's repo
https://github.com/j00ru/windows-syscalls
# Look for new CSV columns
https://github.com/j00ru/windows-syscalls/blob/master/x64/csv/nt.csv
Step 2: Run the update script
cd SysWhispers4
python scripts/update_syscall_table.py --arch x64,x86
Step 3: Verify new build in output
# Check data/syscalls_nt_x64.json
cat data/syscalls_nt_x64.json | jq '._windows_builds'
# Should show new entry:
{
...
"26200": "Windows 11 25H2 (build 26200)"
}
Step 4: Update version mapping (if needed)
If the script doesn’t recognize the new build name, add it to VER_MAP in update_syscall_table.py:
VER_MAP = {
# ... existing entries
"Windows 11 and Server (11 25H2)": ("26200", "Windows 11 25H2 (build 26200)"),
}
Step 5: Test generation
python syswhispers.py --preset common --resolve static
# Verify generated code
grep -A 5 "SW4_SsnTable" SW4Syscalls.c
Step 6: Commit changes
git add data/syscalls_nt_x64.json data/syscalls_nt_x86.json
git commit -m "Update syscall tables for Windows 11 25H2 (build 26200)"
Troubleshooting
Script Fails to Fetch CSV
Error:
[!] Failed to fetch https://raw.githubusercontent.com/...: URLError
Solutions:
- Check internet connection
- Verify j00ru’s repo is accessible (not moved/deleted)
- Check firewall/proxy settings
- Use
--out to write to accessible location
Unknown Build in CSV
Warning:
Unknown version string: "Windows 12 (Beta)"
Solution:
Add to VER_MAP in update_syscall_table.py:
VER_MAP = {
# ...
"Windows 12 (Beta)": ("27000", "Windows 12 Beta (build 27000)"),
}
SSN Mismatch After Update
Symptom: Binary crashes with STATUS_INVALID_SYSTEM_SERVICE after regenerating stubs.
Diagnosis:
# Check what build number the system reports
powershell "[System.Environment]::OSVersion.Version"
# Example output: 10.0.26100.0
# Build number = 26100
# Verify SSN table has entry for 26100
cat data/syscalls_nt_x64.json | jq '.NtAllocateVirtualMemory["26100"]'
Fix:
- If missing, run
update_syscall_table.py again
- If present, verify you’re using the correct build key in generator logic
Alternative: Manual Table Creation
kd> u ntdll!NtAllocateVirtualMemory L5
ntdll!NtAllocateVirtualMemory:
00007ffe`12345678 4c8bd1 mov r10,rcx
00007ffe`1234567b b818000000 mov eax,18h ← SSN = 0x18 (24 decimal)
00007ffe`12345680 f604250803fe7f01 test byte ptr [SharedUserData+0x308],1
00007ffe`12345688 7503 jne ntdll!NtAllocateVirtualMemory+0x15
00007ffe`1234568a 0f05 syscall
Extract for all functions:
kd> .foreach (func {!for_each_function ntdll Nt*}) { u ntdll!${func} L3; .echo ${func} }
Manual JSON Entry
{
"NtAllocateVirtualMemory": {
"26100": 24, // ← Manually add new build
"22000": 24,
"18362": 24
}
}
When to use:
- j00ru hasn’t updated yet
- Testing on preview/insider builds
- Custom Windows builds
Best Practices
1. Use Dynamic Resolution for Production
# Instead of brittle static tables:
python syswhispers.py --preset common --resolve static
# Use version-agnostic methods:
python syswhispers.py --preset common --resolve freshycalls # or recycled, from_disk
Why:
- Works on any Windows version (past, present, future)
- No table updates needed
- More resilient against hooks
2. Update Tables Before Engagements
# Before red team operation, ensure tables are current:
python scripts/update_syscall_table.py --arch x64,x86
3. Version Control Your Tables
# Track changes to syscall numbers over time
git log -p data/syscalls_nt_x64.json
# See when NtCreateThreadEx changed:
git log -p -S "NtCreateThreadEx" data/syscalls_nt_x64.json
4. Automate Updates (CI/CD)
# GitHub Actions example
name: Update Syscall Tables
on:
schedule:
- cron: '0 0 * * 0' # Weekly
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- run: python scripts/update_syscall_table.py --arch x64,x86
- uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: "chore: update syscall tables"
Table Coverage
Current Coverage (Default Tables)
| OS | Builds Included |
|---|
| Windows 7 | SP1 (7601) |
| Windows 8 | 8.0 (9200), 8.1 (9600) |
| Windows 10 | 1507 (10240) → 22H2 (19045) — 14 builds |
| Windows 11 | 21H2 (22000) → 24H2 (26100) — 4 builds |
| Windows Server | 2022 (20348), 2025 (26100) |
x86-Specific (Legacy Coverage)
| OS | Builds Included |
|---|
| Windows NT | 3.1, 3.5, 3.51, 4.0 (SP0-SP6) |
| Windows 2000 | SP0-SP4 |
| Windows XP | SP0-SP3 |
| Windows Server 2003 | RTM, SP1, SP2, R2, R2 SP2 |
| Windows Vista | RTM, SP1, SP2 |
| All modern builds same as x64 | |
Security Considerations
Trust in j00ru’s Data
The update script trusts data from j00ru’s GitHub repository.
Risks:
- GitHub account compromise
- Man-in-the-middle attack (if not using HTTPS)
- Malicious data injection
Mitigations:
-
Verify SSL certificate:
# In update_syscall_table.py
import ssl
context = ssl.create_default_context()
context.check_hostname = True
context.verify_mode = ssl.CERT_REQUIRED
-
Manual verification:
# After update, spot-check a few known SSNs
# NtAllocateVirtualMemory on Win11 22H2 should be 24 (0x18)
cat data/syscalls_nt_x64.json | jq '.NtAllocateVirtualMemory["22621"]'
-
Use local copy:
# Clone j00ru's repo locally
git clone https://github.com/j00ru/windows-syscalls
# Modify script to read from local CSV
python scripts/update_syscall_table.py --local-csv windows-syscalls/x64/csv/nt.csv
FAQ
Do I need to update tables for dynamic resolution methods?
No. FreshyCalls, Halo’s Gate, Tartarus’ Gate, RecycledGate, etc. automatically determine SSNs at runtime. Tables are only needed for --resolve static.
How often does Microsoft change syscall numbers?
Rarely within a major version. Typically only between major releases (Win10 → Win11) or significant feature updates.
Example stability:
NtAllocateVirtualMemory = 24 (0x18) on every Windows 10 build (1507-22H2)
NtCreateThreadEx varies: 189 (1507) → 199 (1903) → 197 (Win11 21H2)
Can I contribute updated tables back to SysWhispers4?
Yes! Submit a PR with updated JSON files:
git checkout -b update-syscall-tables
python scripts/update_syscall_table.py --arch x64,x86
git add data/syscalls_nt_*.json
git commit -m "Update syscall tables for Windows 11 25H2"
git push origin update-syscall-tables
# Open PR on GitHub
What if a function doesn’t exist on an older Windows version?
The JSON will omit that build:
{
"NtAllocateVirtualMemoryEx": {
"19041": 118, // Added in Win10 2004
"22000": 119,
// No entries for Win7, Win8 (function didn't exist)
}
}
Generated code handles this:
if (build_key in table) {
ssn = table[func_name][build_key];
} else {
ssn = 0xFFFFFFFF; // Marker for unavailable
}
Next Steps