Skip to main content

Overview

Once the System Service Number (SSN) is resolved, SysWhispers4 needs to actually invoke the syscall. The invocation method determines how the syscall instruction is executed and where the instruction pointer (RIP) appears to be during the call. Different invocation methods offer tradeoffs between:
  • Stealth - Avoiding detection by EDR/AV call stack analysis
  • Simplicity - Code complexity and maintainability
  • Performance - Execution overhead
  • Flexibility - Runtime adaptability
Use the --method flag to select an invocation method.

Method Comparison

MethodStealthRIP LocationSpeedComplexityRuntime Init
Embedded⭐ LowYour stub⚡⚡⚡ FastestLowNo
Indirect⭐⭐ Mediumntdll.dll⚡⚡ FastLowYes
Randomized⭐⭐⭐ HighRandom in ntdll⚡⚡ FastMediumYes
Egg⭐⭐⭐ HighYour stub⚡ Medium (runtime patch)HighYes
Recommended: Use embedded for simplicity, indirect for better stealth, or randomized for maximum call stack evasion.

Embedded (Default)

Description

The embedded method (also called direct syscall) places the syscall instruction directly in your generated stub code. This is the most straightforward approach.

How It Works

  1. Generated assembly includes the syscall instruction
  2. When you call SW4_NtAllocateVirtualMemory(), execution flows:
    • Load SSN into EAX
    • Move arguments into registers
    • Execute syscall instruction in your stub
    • Kernel handles the syscall
    • Return to your code

Usage

python syswhispers.py --preset common --method embedded
This is the default if you don’t specify --method.

Generated Code (x64)

SW4_NtAllocateVirtualMemory PROC
    mov r10, rcx              ; Save RCX (1st arg) to R10
    mov eax, 00000018h        ; SSN = 0x18
    syscall                   ; Execute syscall HERE
    ret
SW4_NtAllocateVirtualMemory ENDP

Call Stack

When EDR inspects the call stack during the syscall:
[kernel32.dll] BaseThreadInitThunk
[Your.exe] main
[Your.exe] SW4_NtAllocateVirtualMemory  <-- RIP is HERE (suspicious!)
[ntoskrnl.exe] NtAllocateVirtualMemory

Advantages

Simplest implementation - Just execute the instruction
No runtime initialization - No need to call SW4Initialize()
Fastest - No indirection overhead
No ntdll dependency - Doesn’t need to find gadgets in ntdll

Disadvantages

Obvious to EDR - RIP is in your module during syscall, not ntdll
Easy to detect - Stack trace shows non-ntdll address
Signature-prone - Syscall instruction in your binary is suspicious

When to Use

  • You need maximum performance
  • You’re not concerned about call stack analysis
  • You want the simplest implementation
  • You’re testing or prototyping

Detection Risk

EDRs can detect direct syscalls by:
  1. Call stack analysis - RIP not in ntdll during syscall
  2. Binary scanning - syscall instruction found in non-ntdll module
  3. Behavioral analysis - Non-ntdll code calling kernel

Indirect

Description

The indirect method jumps to a syscall; ret gadget inside ntdll.dll. This makes the instruction pointer (RIP) appear to be in ntdll during the syscall, mimicking normal behavior.

How It Works

  1. At initialization (SW4Initialize()), scan ntdll.dll for a syscall; ret gadget (opcodes: 0F 05 C3)
  2. Store the address of this gadget
  3. When calling a syscall:
    • Load SSN into EAX
    • Move arguments into registers
    • Jump to the gadget in ntdll
    • ntdll executes syscall; ret
    • Return to your code

Usage

python syswhispers.py --preset common --method indirect

Generated Code (x64)

SW4_NtAllocateVirtualMemory PROC
    mov r10, rcx              ; Save RCX (1st arg) to R10
    mov eax, 00000018h        ; SSN = 0x18
    jmp qword ptr [g_SyscallGadget]  ; Jump to ntdll gadget
SW4_NtAllocateVirtualMemory ENDP

; Initialization finds this gadget in ntdll:
; ntdll.dll+0x12345:
;   syscall
;   ret

Call Stack

When EDR inspects the call stack during the syscall:
[kernel32.dll] BaseThreadInitThunk
[Your.exe] main
[Your.exe] SW4_NtAllocateVirtualMemory
[ntdll.dll] <gadget address>  <-- RIP is in ntdll (looks normal!)
[ntoskrnl.exe] NtAllocateVirtualMemory

Advantages

Better stealth - RIP appears in ntdll, mimicking normal calls
Evades basic call stack checks - Looks like normal ntdll behavior
No syscall instruction in your binary - Harder to detect via scanning
Still fast - Single indirect jump

Disadvantages

⚠️ Requires initialization - Must call SW4Initialize() to find gadget
⚠️ Scanning ntdll - Finding the gadget may trigger EDR heuristics
⚠️ Predictable RIP - Always the same gadget address (can be fingerprinted)

When to Use

  • You need better stealth than embedded
  • You’re okay with runtime initialization
  • You want a good balance of stealth and performance
  • Recommended for most red team engagements

Finding the Gadget

void SW4Initialize() {
    HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
    BYTE* p = (BYTE*)hNtdll;
    SIZE_T size = GetModuleSize(hNtdll);
    
    // Scan for opcodes: 0F 05 C3 (syscall; ret)
    for (SIZE_T i = 0; i < size - 3; i++) {
        if (p[i] == 0x0F && p[i+1] == 0x05 && p[i+2] == 0xC3) {
            g_SyscallGadget = (PVOID)(p + i);
            return;
        }
    }
}

Randomized

Description

The randomized method is an enhancement of indirect that uses a different random syscall gadget for each call. This prevents EDRs from fingerprinting your syscalls by gadget address.

How It Works

  1. At initialization (SW4Initialize()), scan ntdll.dll and collect multiple syscall; ret gadgets
  2. Store all gadget addresses in an array
  3. When calling a syscall:
    • Load SSN into EAX
    • Move arguments into registers
    • Select a random gadget from the array
    • Jump to the random gadget
    • Return to your code

Usage

python syswhispers.py --preset common --method randomized

Generated Code (x64)

SW4_NtAllocateVirtualMemory PROC
    mov r10, rcx                    ; Save RCX (1st arg) to R10
    mov eax, 00000018h              ; SSN = 0x18
    call SW4_GetRandomSyscallGadget ; Get random gadget address
    jmp rax                         ; Jump to random gadget
SW4_NtAllocateVirtualMemory ENDP

Call Stack

When EDR inspects the call stack during the syscall:
[kernel32.dll] BaseThreadInitThunk
[Your.exe] main
[Your.exe] SW4_NtAllocateVirtualMemory
[ntdll.dll] <random gadget address>  <-- RIP varies each call!
[ntoskrnl.exe] NtAllocateVirtualMemory

Advantages

✅✅ Maximum call stack evasion - RIP location varies, hard to fingerprint
Anti-profiling - EDR can’t build a consistent signature
Still appears in ntdll - Maintains the illusion of normal behavior
Harder to detect - No consistent pattern

Disadvantages

⚠️ More complex - Needs gadget management and random selection
⚠️ Slightly slower - Additional function call to get random gadget
⚠️ More initialization overhead - Must collect multiple gadgets

When to Use

  • You need maximum stealth
  • You’re evading advanced EDR with call stack profiling
  • You want to prevent signature-based detection
  • Recommended for stealth configurations

Example Configuration

python syswhispers.py --preset stealth \
  --method randomized \
  --resolve recycled \
  --obfuscate

Gadget Collection

#define MAX_GADGETS 256
PVOID g_SyscallGadgets[MAX_GADGETS];
DWORD g_GadgetCount = 0;

void SW4Initialize() {
    HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
    BYTE* p = (BYTE*)hNtdll;
    SIZE_T size = GetModuleSize(hNtdll);
    
    // Scan for all syscall; ret gadgets
    for (SIZE_T i = 0; i < size - 3 && g_GadgetCount < MAX_GADGETS; i++) {
        if (p[i] == 0x0F && p[i+1] == 0x05 && p[i+2] == 0xC3) {
            g_SyscallGadgets[g_GadgetCount++] = (PVOID)(p + i);
        }
    }
}

PVOID SW4_GetRandomSyscallGadget() {
    DWORD index = (rand() * GetTickCount64()) % g_GadgetCount;
    return g_SyscallGadgets[index];
}

Egg

Description

The egg method embeds an 8-byte “egg” marker (e.g., 0x4141414141414141) in place of the syscall instruction at generation time. At runtime, your code searches for these eggs and patches them with the actual syscall instruction.

How It Works

  1. Generated stubs contain an egg marker instead of syscall:
    mov eax, 0x18
    dq 0x4141414141414141  ; Egg marker (8 bytes)
    ret
    
  2. At runtime, call SW4HatchEggs():
    • Scan your module’s .text section for egg markers
    • Replace each egg with 0x0F05C3 (syscall; ret; nop; nop; nop…)
    • Change memory protection as needed
  3. After hatching, syscalls work like embedded method

Usage

python syswhispers.py --preset common --method egg

Generated Code (x64)

SW4_NtAllocateVirtualMemory PROC
    mov r10, rcx              ; Save RCX (1st arg) to R10
    mov eax, 00000018h        ; SSN = 0x18
    dq 4141414141414141h      ; Egg marker (will be replaced with syscall)
    ret
SW4_NtAllocateVirtualMemory ENDP

Runtime Initialization

int main() {
    // CRITICAL: Hatch eggs before calling any syscalls
    if (!SW4HatchEggs()) {
        printf("Failed to hatch eggs!\n");
        return 1;
    }
    
    // Now syscalls work
    SW4_NtAllocateVirtualMemory(...);
}

Advantages

✅✅ No static syscall instruction - Binary doesn’t contain 0x0F05 at rest
Evades static scanning - AV/EDR can’t find syscall bytes in your binary
Dynamic patching - Syscalls only “appear” at runtime
Good for obfuscation - Combined with other techniques, very stealthy

Disadvantages

Complex initialization - Must scan and patch memory
Memory protection changes - VirtualProtect() may trigger EDR
Slower startup - Scanning and patching takes time
RIP still in your module - Same call stack detection risk as embedded

When to Use

  • You need to evade static binary analysis
  • You’re combining with other obfuscation techniques
  • You’re willing to trade initialization complexity for static stealth
  • Your threat model includes AV signature scanning

Egg Hatching Implementation

#define EGG_MARKER 0x4141414141414141ULL

BOOL SW4HatchEggs() {
    PVOID pBase = GetModuleHandleA(NULL);  // Your executable
    PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBase;
    PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)((BYTE*)pBase + pDos->e_lfanew);
    
    // Find .text section
    PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);
    for (int i = 0; i < pNt->FileHeader.NumberOfSections; i++, pSection++) {
        if (strcmp((char*)pSection->Name, ".text") == 0) {
            BYTE* pText = (BYTE*)pBase + pSection->VirtualAddress;
            SIZE_T size = pSection->Misc.VirtualSize;
            
            // Make .text writable
            DWORD oldProtect;
            VirtualProtect(pText, size, PAGE_EXECUTE_READWRITE, &oldProtect);
            
            // Scan for eggs and replace with syscall
            for (SIZE_T j = 0; j < size - 8; j++) {
                ULONGLONG* p = (ULONGLONG*)(pText + j);
                if (*p == EGG_MARKER) {
                    // Replace with: syscall; ret; nop; nop; nop; nop; nop
                    pText[j+0] = 0x0F;  // syscall
                    pText[j+1] = 0x05;
                    pText[j+2] = 0xC3;  // ret
                    pText[j+3] = 0x90;  // nop (padding)
                    pText[j+4] = 0x90;
                    pText[j+5] = 0x90;
                    pText[j+6] = 0x90;
                    pText[j+7] = 0x90;
                }
            }
            
            // Restore original protection
            VirtualProtect(pText, size, oldProtect, &oldProtect);
            return TRUE;
        }
    }
    return FALSE;
}

Custom Egg Marker

You can customize the egg marker in generated code to avoid signature detection:
// In generator templates
#define SW4_EGG_MARKER 0xDEADBEEFCAFEBABEULL  // Custom marker

Combining Invocation and Resolution

Compatibility Matrix

Resolution MethodEmbeddedIndirectRandomizedEgg
Static
FreshyCalls
Hell’s Gate
Halo’s Gate
Tartarus’ Gate
From Disk
RecycledGate
HW Breakpoint
All combinations are supported! Choose based on your requirements. General Use:
python syswhispers.py --preset common \
  --resolve freshycalls \
  --method embedded
Better Stealth:
python syswhispers.py --preset injection \
  --resolve freshycalls \
  --method indirect
Maximum Stealth:
python syswhispers.py --preset stealth \
  --resolve recycled \
  --method randomized \
  --obfuscate
Evade Static Analysis:
python syswhispers.py --preset stealth \
  --resolve from_disk \
  --method egg \
  --obfuscate

Performance Comparison

Benchmark (1000 syscalls)

MethodAvg Time (µs)Overhead vs EmbeddedInitialization
Embedded1.20% (baseline)None
Indirect1.5+25%~5ms (one-time)
Randomized1.8+50%~15ms (one-time)
Egg1.2 + 200*+0% + hatch time~50ms (one-time)
*Egg method has a one-time patching overhead (~200ms) but then performs like embedded.

Detection Evasion Summary

Call Stack Analysis

MethodRIP LocationEDR Detection Risk
EmbeddedYour .exe⚠️ High - Obvious anomaly
Indirectntdll.dll (fixed)⭐ Medium - Looks normal but predictable
Randomizedntdll.dll (varies)⭐⭐⭐ Low - Unpredictable, hard to profile
EggYour .exe⚠️ High - Same as embedded after hatching

Binary Scanning

MethodStatic Syscall BytesSignature Risk
Embedded✅ Yes (0x0F 0x05)⚠️ High
Indirect❌ No⭐⭐ Low
Randomized❌ No⭐⭐ Low
Egg❌ No (at rest)⭐⭐⭐ Very Low

Choosing the Right Method

Decision Tree

Do you need to evade static binary analysis?
├─ Yes → `egg` (no static syscall bytes)
└─ No ↓

Do you need to evade call stack analysis?
├─ Yes ↓
│   ├─ Maximum stealth? → `randomized`
│   └─ Good balance? → `indirect`
└─ No ↓

Do you prioritize simplicity and speed?
└─ Yes → `embedded` (default)

Quick Recommendations

ScenarioRecommended MethodReason
PrototypingembeddedSimplest, fastest
Red team (general)indirectGood stealth/performance balance
Advanced EDRrandomizedAnti-profiling
Static detectioneggNo syscall bytes at rest
Maximum stealthrandomized + recycled + --obfuscateAll techniques combined

Example Configurations

Development/Testing

python syswhispers.py --preset common --method embedded

Production Red Team

python syswhispers.py --preset injection \
  --method indirect \
  --resolve freshycalls

Maximum Evasion

python syswhispers.py --preset stealth \
  --method randomized \
  --resolve recycled \
  --obfuscate \
  --encrypt-ssn \
  --stack-spoof

Evade AV Scanning

python syswhispers.py --preset stealth \
  --method egg \
  --resolve from_disk \
  --obfuscate

See Also

Build docs developers (and LLMs) love