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