Skip to main content

Overview

The generator::exception policy generates minimal stubs that trigger an illegal instruction exception (ud2). A Vectored Exception Handler (VEH) intercepts the exception and performs the syscall, providing maximum call stack obfuscation.

Constants

bRequiresGadget
static constexpr bool
Indicates whether this policy requires syscall gadgets from ntdll.dll
static constexpr bool bRequiresGadget = true;
value
true
Exception-based stubs require a gadget to execute the actual syscall from within the exception handler

Method Signatures

getStubSize
static constexpr size_t
Returns the size of the generated stub in bytes
static constexpr size_t getStubSize() { return 8; }
return
size_t
8 bytes (smallest of all generators)
generate
static void
Generates the exception-triggering stub
static void generate(
    uint8_t* pBuffer,
    uint32_t /*uSyscallNumber*/,
    void* /*pGadgetAddress*/
)

Generated Shellcode

Stub (8 bytes)

From syscall.hpp:296-307:
struct exception
{
    static constexpr bool bRequiresGadget = true;
    static constexpr size_t getStubSize() { return 8; }
    
    static void generate(uint8_t* pBuffer, uint32_t /*uSyscallNumber*/, void* /*pGadgetAddress*/)
    {
        pBuffer[0] = 0x0F;  // ud2 (illegal instruction)
        pBuffer[1] = 0x0B;
        pBuffer[2] = 0xC3;  // ret
        std::fill_n(pBuffer + 3, getStubSize() - 3, 0x90);  // nop padding
    }
};
Assembly breakdown:
  1. ud2 - Trigger an EXCEPTION_ILLEGAL_INSTRUCTION exception
  2. ret - Return instruction (executed after exception handler completes)
  3. nop (x5) - Padding to fill the 8-byte stub size

Exception Handler

The Vectored Exception Handler performs the actual syscall:

Thread-Local Exception Context

From syscall.hpp:49-75:
inline thread_local struct ExceptionContext_t
{
    bool m_bShouldHandle = false;
    const void* m_pExpectedExceptionAddress = nullptr;
    void* m_pSyscallGadget = nullptr;
    uint32_t m_uSyscallNumber = 0;
} pExceptionContext;

class CExceptionContextGuard
{
public:
    CExceptionContextGuard(const void* pExpectedAddress, void* pSyscallGadget, 
                          uint32_t uSyscallNumber)
    {
        pExceptionContext.m_bShouldHandle = true;
        pExceptionContext.m_pExpectedExceptionAddress = pExpectedAddress;
        pExceptionContext.m_pSyscallGadget = pSyscallGadget;
        pExceptionContext.m_uSyscallNumber = uSyscallNumber;
    }

    ~CExceptionContextGuard()
    {
        pExceptionContext.m_bShouldHandle = false;
    }

    CExceptionContextGuard(const CExceptionContextGuard&) = delete;
    CExceptionContextGuard& operator=(const CExceptionContextGuard&) = delete;
};

VEH Implementation

From syscall.hpp:77-109:
static LONG NTAPI VectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo)
{
    if (!pExceptionContext.m_bShouldHandle)
        return EXCEPTION_CONTINUE_SEARCH;

    if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION &&
        pExceptionInfo->ExceptionRecord->ExceptionAddress == pExceptionContext.m_pExpectedExceptionAddress)
    {
        pExceptionContext.m_bShouldHandle = false;
        
#if SYSCALL_PLATFORM_WINDOWS_64
        // Set up registers for syscall
        pExceptionInfo->ContextRecord->R10 = pExceptionInfo->ContextRecord->Rcx;
        pExceptionInfo->ContextRecord->Rax = pExceptionContext.m_uSyscallNumber;
        pExceptionInfo->ContextRecord->Rip = reinterpret_cast<uintptr_t>(pExceptionContext.m_pSyscallGadget);
#else
        // x86 setup
        uintptr_t uReturnAddressAfterSyscall = 
            reinterpret_cast<uintptr_t>(pExceptionInfo->ExceptionRecord->ExceptionAddress) + 2;
        pExceptionInfo->ContextRecord->Edx = pExceptionInfo->ContextRecord->Esp;
        pExceptionInfo->ContextRecord->Esp -= sizeof(uintptr_t);
        *reinterpret_cast<uintptr_t*>(pExceptionInfo->ContextRecord->Esp) = uReturnAddressAfterSyscall;
        pExceptionInfo->ContextRecord->Eip = reinterpret_cast<uintptr_t>(pExceptionContext.m_pSyscallGadget);
        pExceptionInfo->ContextRecord->Eax = pExceptionContext.m_uSyscallNumber;
#endif

        return EXCEPTION_CONTINUE_EXECUTION;
    }

    return EXCEPTION_CONTINUE_SEARCH;
}

Execution Flow

1

Stub invocation

The caller invokes the stub, which executes ud2, triggering an exception.
2

Exception dispatch

Windows dispatches the exception to registered Vectored Exception Handlers.
3

Context validation

The VEH checks if the exception address matches the expected stub address and the exception code is EXCEPTION_ILLEGAL_INSTRUCTION.
4

Register setup

The handler modifies the CPU context: sets EAX/RAX to the syscall number, R10 to RCX (x64), and RIP/EIP to the gadget address.
5

Syscall execution

Execution resumes at the gadget, which executes the syscall and returns.
6

Return to caller

The syscall result propagates back through the exception mechanism to the original caller.

Usage Example

#include <syscalls-cpp/syscall.hpp>

using ExceptionManager = syscall::Manager<
    syscall::policies::allocator::section,
    syscall::policies::generator::exception
>;

ExceptionManager manager;
if (!manager.initialize()) {
    std::cerr << "Failed to initialize\n";
    return 1;
}

// The VEH is automatically registered during initialization
// Invoke syscalls normally
PVOID pAddress = nullptr;
SIZE_T uSize = 0x1000;

manager.invoke<NTSTATUS>(
    SYSCALL_ID("NtAllocateVirtualMemory"),
    NtCurrentProcess(),
    &pAddress, 0, &uSize,
    MEM_COMMIT | MEM_RESERVE,
    PAGE_READWRITE
);

// VEH is automatically unregistered when manager is destroyed

Security Benefits

Call Stack Obfuscation

The call stack shows exception handling frames, masking the true intent of the syscall.

No Syscall Instructions

The stub contains no syscall or call instructions—just ud2; ret; nop.

Minimal Footprint

Only 8 bytes per stub, the smallest of all generators.

Thread Safety

Thread-local context ensures safe concurrent syscall execution.

Trade-offs

AspectDescription
PerformanceSlowest (50-100x overhead from exception handling)
StealthHighest (completely obfuscated execution)
ComplexityMost complex (VEH, context manipulation)
Platform Supportx86 and x64
Use CaseMaximum stealth, research, red team operations
Performance Impact: Exception-based syscalls are significantly slower due to exception dispatching overhead. Use this policy only when maximum stealth is required, not for performance-critical code.

Thread Safety

The exception context is thread_local, ensuring that concurrent syscalls from multiple threads don’t interfere with each other. Each thread maintains its own exception context.

See Also

generator::direct

Direct syscall stub generation

generator::gadget

Gadget-based stub generation

Exception Syscalls Example

Complete working example

Build docs developers (and LLMs) love