Skip to main content

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

1

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;
}
2

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] == 0xE9) return TRUE;
    
    // FF 25 xx xx xx xx — far JMP [rip+offset]
    if (code[0] == 0xFF && code[1] == 0x25) return TRUE;
    
    // EB xx — short JMP
    if (code[0] == 0xEB) return TRUE;
    
    // CC — INT3 breakpoint
    if (code[0] == 0xCC) return TRUE;
    
    // E8 xx xx xx xx — CALL (rare)
    if (code[0] == 0xE8) return TRUE;
    
    return FALSE;  // Appears clean
}
3

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] == 0x4C && code[1] == 0x8B && code[2] == 0xD1 &&
        code[3] == 0xB8) {
        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];
}
4

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 = 0xFFFFFFFF;
        for (DWORD j = 0; j < SW4_FUNC_COUNT; j++) {
            if (SW4_FunctionHashes[j] == hash) {
                funcIdx = j;
                break;
            }
        }
        if (funcIdx == 0xFFFFFFFF) continue;

        // Check if stub is hooked
        PBYTE code = (PBYTE)addr;
        BOOL hooked = (code[0] == 0xE9 || code[0] == 0xCC || code[0] == 0xEB ||
                       (code[0] == 0xFF && code[1] == 0x25) || code[0] == 0xE8);

        if (!hooked) {
            // Validate with Hell's Gate opcode read
            if (code[0] == 0x4C && code[1] == 0x8B && code[2] == 0xD1 &&
                code[3] == 0xB8) {
                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

Performance Analysis

OperationTimeNotes
Export table parse~1msSame as FreshyCalls
qsort (VA ordering)~1msSame as FreshyCalls
Hook detection~1msSimple byte checks
Opcode validation~1msOnly for clean stubs
Cross-check logic<1msComparison 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

FeatureFreshyCallsRecycledGateSyscallsFromDiskHW Breakpoint
Hook ResistanceVery HighMaximumMaximumMaximum
VerificationSingle (VA)Dual (VA+Opcode)Single (Opcode)Single (Runtime)
SpeedFast (~2ms)Fast (~5ms)Slow (~15ms)Slow (~20ms)
ComplexityLowMediumMediumHigh
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 = 0x1000;
    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

  1. Export table enumeration — common, not inherently suspicious
  2. Opcode reading — may trigger memory scanning alerts
  3. Hook detection logic — pattern matching via byte checks

EDR Visibility

ActionKernel VisibilityUser-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

1

Combine with indirect invocation

Keep RIP inside ntdll during syscalls:
python syswhispers.py --resolve recycled --method randomized
2

Enable obfuscation

Randomize stub order and inject junk instructions:
python syswhispers.py --resolve recycled --obfuscate
3

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

Build docs developers (and LLMs) love