Overview
RecycledGate represents the most resilient SSN resolution technique available in SysWhispers4, combining the hook-resistant VA-sorting of FreshyCalls with opcode validation from Hell’s Gate. This dual-verification approach provides maximum confidence even in hostile environments.
Use RecycledGate when you need highest confidence in SSN accuracy without the performance cost of SyscallsFromDisk .
The Core Insight
RecycledGate asks: What if we could get the benefits of both approaches?
FreshyCalls : Works when hooks modify opcodes, but relies on export table accuracy
Hell’s Gate : Works when export table is clean, but fails on hooked opcodes
Solution : Use FreshyCalls as the primary source, Hell’s Gate as validation when possible:
IF stub is clean (no hook detected):
ssn_freshycalls = sorted_index
ssn_opcode = read_mov_eax_instruction()
IF ssn_freshycalls == ssn_opcode:
RETURN ssn_freshycalls // Double-confirmed!
ELSE:
RETURN ssn_freshycalls // Trust VA sort over potentially-tampered opcode
ELSE (stub is hooked):
RETURN ssn_freshycalls // Opcode is garbage, VA sort is authoritative
Algorithm
Step-by-Step Process
Sort by Virtual Address (FreshyCalls)
Parse ntdll.dll export table and sort all Nt* functions by VA: // Collect exports
SW4_EXPORT * ntExports = parse_ntdll_exports (pNtdll);
// Sort ascending by address
qsort (ntExports, ntCount, sizeof (SW4_EXPORT), compare_va);
// Index = candidate SSN
for (i = 0 ; i < ntCount; i ++ ) {
candidate_ssn [ ntExports [i]. Hash ] = i;
}
Check for Hooks (Pattern Detection)
Inspect the first few bytes of each stub for common EDR hook patterns: BOOL is_hooked (PVOID addr ) {
PBYTE code = (PBYTE)addr;
// E9 xx xx xx xx — near JMP
if ( code [ 0 ] == 0x E9 ) return TRUE ;
// FF 25 xx xx xx xx — far JMP [rip+offset]
if ( code [ 0 ] == 0x FF && code [ 1 ] == 0x 25 ) return TRUE ;
// EB xx — short JMP
if ( code [ 0 ] == 0x EB ) return TRUE ;
// CC — INT3 breakpoint
if ( code [ 0 ] == 0x CC ) return TRUE ;
// E8 xx xx xx xx — CALL (rare)
if ( code [ 0 ] == 0x E8 ) return TRUE ;
return FALSE ; // Appears clean
}
Opcode Validation (When Clean)
If the stub appears unhoooked, read the SSN from opcodes: if ( ! is_hooked (funcAddr)) {
PBYTE code = (PBYTE)funcAddr;
// Expected: 4C 8B D1 B8 [SSN_lo] [SSN_hi] 00 00
if ( code [ 0 ] == 0x 4C && code [ 1 ] == 0x 8B && code [ 2 ] == 0x D1 &&
code [ 3 ] == 0x B8 ) {
DWORD ssn_opcode = * (DWORD * )(code + 4 );
// Cross-check with FreshyCalls
if (ssn_opcode == candidate_ssn [hash]) {
SW4_SsnTable [idx] = ssn_opcode; // Verified!
} else {
// Mismatch — trust FreshyCalls
SW4_SsnTable [idx] = candidate_ssn [hash];
}
}
} else {
// Stub is hooked — use FreshyCalls exclusively
SW4_SsnTable [idx] = candidate_ssn [hash];
}
Handle Edge Cases
For stubs that are hooked, rely solely on the VA-sort index: // Even if ALL stubs are hooked, RecycledGate still works
// because FreshyCalls doesn't require clean opcodes
for (i = 0 ; i < ntCount; i ++ ) {
if ( is_hooked ( ntExports [i]. Address )) {
// Trust sorted index
SW4_SsnTable [idx] = i;
}
}
Full Implementation
BOOL SW4_RecycledGate (PVOID pNtdll ) {
// Phase 1: FreshyCalls — sort by VA
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)pNtdll;
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((PBYTE)pNtdll + dos -> e_lfanew );
PIMAGE_EXPORT_DIRECTORY exports =
(PIMAGE_EXPORT_DIRECTORY)((PBYTE)pNtdll +
nt -> OptionalHeader . DataDirectory [IMAGE_DIRECTORY_ENTRY_EXPORT]. VirtualAddress );
PDWORD nameRvas = (PDWORD)((PBYTE)pNtdll + exports -> AddressOfNames );
PDWORD funcRvas = (PDWORD)((PBYTE)pNtdll + exports -> AddressOfFunctions );
PWORD ordinals = (PWORD)((PBYTE)pNtdll + exports -> AddressOfNameOrdinals );
SW4_EXPORT * ntExports = (SW4_EXPORT * ) calloc ( exports -> NumberOfNames , sizeof (SW4_EXPORT));
DWORD ntCount = 0 ;
for (DWORD i = 0 ; i < exports -> NumberOfNames ; i ++ ) {
PCHAR name = (PCHAR)((PBYTE)pNtdll + nameRvas [i]);
if ( name [ 0 ] == 'N' && name [ 1 ] == 't' ) {
DWORD funcRva = funcRvas [ ordinals [i]];
ntExports [ntCount]. Address = (PVOID)((PBYTE)pNtdll + funcRva);
ntExports [ntCount]. Hash = djb2_hash (name);
ntCount ++ ;
}
}
qsort (ntExports, ntCount, sizeof (SW4_EXPORT), compare_va);
// Phase 2: Cross-validation with opcode reading
for (DWORD i = 0 ; i < ntCount; i ++ ) {
PVOID addr = ntExports [i]. Address ;
DWORD hash = ntExports [i]. Hash ;
DWORD ssn_candidate = i; // FreshyCalls SSN
// Find matching function in our table
DWORD funcIdx = 0x FFFFFFFF ;
for (DWORD j = 0 ; j < SW4_FUNC_COUNT; j ++ ) {
if ( SW4_FunctionHashes [j] == hash) {
funcIdx = j;
break ;
}
}
if (funcIdx == 0x FFFFFFFF ) continue ;
// Check if stub is hooked
PBYTE code = (PBYTE)addr;
BOOL hooked = ( code [ 0 ] == 0x E9 || code [ 0 ] == 0x CC || code [ 0 ] == 0x EB ||
( code [ 0 ] == 0x FF && code [ 1 ] == 0x 25 ) || code [ 0 ] == 0x E8 );
if ( ! hooked) {
// Validate with Hell's Gate opcode read
if ( code [ 0 ] == 0x 4C && code [ 1 ] == 0x 8B && code [ 2 ] == 0x D1 &&
code [ 3 ] == 0x B8 ) {
DWORD ssn_opcode = * (DWORD * )(code + 4 );
if (ssn_opcode == ssn_candidate) {
// Perfect match — double-verified!
SW4_SsnTable [funcIdx] = ssn_opcode;
} else {
// Mismatch — VA sort is more reliable
SW4_SsnTable [funcIdx] = ssn_candidate;
}
} else {
// Unexpected opcode pattern — trust VA sort
SW4_SsnTable [funcIdx] = ssn_candidate;
}
} else {
// Stub is hooked — FreshyCalls is authoritative
SW4_SsnTable [funcIdx] = ssn_candidate;
}
}
free (ntExports);
return TRUE ;
}
Advantages
Dual Verification SSNs are cross-validated when possible, maximizing confidence in results
Hook Resistant Works even if 100% of stubs are hooked — FreshyCalls provides fallback
Opcode Anomaly Detection Detects discrepancies between VA-sort and opcodes (EDR tampering indicators)
Fast Minimal overhead vs. FreshyCalls (~3-5ms vs. ~2ms) — much faster than SyscallsFromDisk
Use Cases
Scenario 1: Partially Hooked NTDLL
EDR hooks 20% of Nt* functions:
NtAllocateVirtualMemory: Clean → VA-sort: 0x18, Opcode: 0x18 ✅ Verified
NtCreateThreadEx: Hooked → VA-sort: 0xC2 ✅ Trusted (opcode garbage)
NtWriteVirtualMemory: Clean → VA-sort: 0x3A, Opcode: 0x3A ✅ Verified
NtProtectVirtualMemory: Hooked → VA-sort: 0x50 ✅ Trusted
Result : All SSNs correct, 80% double-verified.
Scenario 2: Sophisticated EDR
EDR modifies export table and hooks stubs:
Export table VA order: [Possibly tampered]
Opcode bytes: [Hooked with E9 JMP]
RecycledGate: VA-sort still reflects true stub layout
Opcode validation skipped (hooks detected)
✅ Correct SSNs from VA-sort
Result : Still works — FreshyCalls component is export-table-independent at the VA level.
Scenario 3: Clean NTDLL
No hooks present (testing, early-stage payload):
All stubs clean → 100% of SSNs double-verified (VA + opcode)
→ Maximum confidence in results
Operation Time Notes Export table parse ~1ms Same as FreshyCalls qsort (VA ordering) ~1ms Same as FreshyCalls Hook detection ~1ms Simple byte checks Opcode validation ~1ms Only for clean stubs Cross-check logic <1ms Comparison arithmetic Total ~4-5ms ~2x FreshyCalls, 3x faster than SyscallsFromDisk
Performance impact is negligible — occurs once during SW4_Initialize(). Subsequent syscalls are full-speed.
Comparison with Alternatives
Feature FreshyCalls RecycledGate SyscallsFromDisk HW Breakpoint Hook Resistance Very High Maximum Maximum Maximum Verification Single (VA) Dual (VA+Opcode) Single (Opcode) Single (Runtime) Speed Fast (~2ms) Fast (~5ms) Slow (~15ms) Slow (~20ms) Complexity Low Medium Medium High Anomaly Detection ❌ ✅ ❌ ❌ Export Tampering ⚠️ Vulnerable ⚠️ Vulnerable ✅ Immune ✅ Immune
Limitations
Export Table Manipulation
If an EDR reorders the export directory RVAs to break VA-sorting (extremely rare — would break legitimate API resolution), RecycledGate is defeated:
// Hypothetical attack: EDR swaps export RVAs
exports -> AddressOfFunctions [NtAlloc_index] = trampoline_rva; // Points elsewhere
exports -> AddressOfFunctions [NtCreate_index] = original_NtAlloc_rva;
Mitigation : Use SyscallsFromDisk , which maps clean ntdll from \KnownDlls\.
Opcode Pattern Changes
Future Windows versions may change the syscall stub prologue:
; Current x64 stub (Win7-Win11):
4C 8B D1 mov r10 , rcx
B8 XX XX 00 00 mov eax , <SSN>
0F 05 syscall
C3 ret
; Hypothetical future change:
XX XX XX XX XX <new prologue>
B8 XX XX 00 00 mov eax , <SSN>
...
Impact : Opcode validation would fail, but FreshyCalls fallback ensures SSNs are still resolved.
When to Use RecycledGate
High-security targets with sophisticated EDRs
Verification-critical operations (privilege escalation, persistence)
Partial hook environments (maximizes validation coverage)
Performance-sensitive but paranoia-required contexts (faster than SyscallsFromDisk)
Anomaly detection desired — mismatch between VA/opcode signals tampering
CTF challenges — FreshyCalls is sufficient
Testing environments without EDR — Static resolution is faster
Sandboxed contexts — if KnownDlls blocked, no advantage over FreshyCalls
Usage in SysWhispers4
Generate with RecycledGate
# Basic usage
python syswhispers.py --preset injection --resolve recycled
# Recommended: combine with obfuscation and indirect invocation
python syswhispers.py --preset stealth \
--resolve recycled \
--method randomized \
--obfuscate \
--encrypt-ssn \
--stack-spoof
# Maximum evasion (all techniques)
python syswhispers.py --preset stealth \
--resolve recycled \
--method randomized \
--obfuscate --encrypt-ssn --stack-spoof \
--etw-bypass --amsi-bypass --unhook-ntdll \
--anti-debug --sleep-encrypt
Integration Example
#include "SW4Syscalls.h"
int main ( void ) {
// Optional: remove hooks first for maximum validation coverage
SW4_UnhookNtdll ();
// Initialize with RecycledGate
if ( ! SW4_Initialize ()) {
fprintf (stderr, "[!] RecycledGate initialization failed \n " );
return 1 ;
}
printf ( "[+] SSNs resolved via RecycledGate (VA-sort + opcode validation) \n " );
// Optional: anti-debugging check
if ( ! SW4_AntiDebugCheck ()) {
fprintf (stderr, "[!] Debugger detected \n " );
return 0 ;
}
// Use syscalls with high confidence in SSN accuracy
PVOID base = NULL ;
SIZE_T size = 0x 1000 ;
NTSTATUS st = SW4_NtAllocateVirtualMemory (
GetCurrentProcess (), & base, 0 , & size,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE
);
if ( NT_SUCCESS (st)) {
printf ( "[+] Memory allocated at 0x %p \n " , base);
// Use sleep encryption during idle
SW4_SleepEncrypt ( 5000 ); // .text encrypted during sleep
SW4_NtFreeVirtualMemory ( GetCurrentProcess (), & base, & size, MEM_RELEASE);
}
return NT_SUCCESS (st) ? 0 : 1 ;
}
Detection Considerations
Observable Behaviors
Export table enumeration — common, not inherently suspicious
Opcode reading — may trigger memory scanning alerts
Hook detection logic — pattern matching via byte checks
EDR Visibility
Action Kernel Visibility User-Mode Visibility Parse export table ❌ ❌ (in-process) qsort VA list ❌ ❌ (in-process) Read stub opcodes ⚠️ (possible) ✅ (if hooked) Syscall execution ✅ (ETW-Ti) ❌ (hooks bypassed)
Best Practices
Combine with indirect invocation
Keep RIP inside ntdll during syscalls: python syswhispers.py --resolve recycled --method randomized
Enable obfuscation
Randomize stub order and inject junk instructions: python syswhispers.py --resolve recycled --obfuscate
Use ntdll unhooking
Remove hooks before RecycledGate runs: SW4_UnhookNtdll (); // Clean ntdll
SW4_Initialize (); // All stubs clean → 100% validated
Further Reading
FreshyCalls The VA-sorting technique at RecycledGate’s core
Hell's Gate Opcode reading technique used for validation
SyscallsFromDisk Alternative when export table tampering suspected
Original Research RecycledGate by thefLink