Overview
Using direct syscalls comes with significant security implications. This guide covers critical vulnerabilities to avoid, operational security best practices, and threat model considerations when choosing policies.Critical: NULL vs nullptr on x64
This is the most common and dangerous mistake when using syscalls-cpp on x64 platforms.The Problem
TheNULL macro is typically defined as integer 0, which is a 32-bit value. On x64, pointers are 64-bit. When you pass NULL to a syscall expecting a 64-bit pointer, the compiler may:
- Push only 32 bits onto the stack
- Fail to properly zero-extend to 64 bits
- Cause argument misalignment for all subsequent parameters
- Result in stack corruption and unpredictable crashes
Example of the Bug
The Solution
Always usenullptr on x64:
Why This Matters
Stack Corruption
Misaligned arguments corrupt the stack, causing crashes that are extremely difficult to debug.
Unpredictable Behavior
The syscall may read garbage values from the stack, leading to memory corruption or security vulnerabilities.
Hard to Debug
The crash often occurs far from the actual bug, making it hard to identify the root cause.
Platform-Specific
Works fine on x86 (where NULL is acceptable), fails mysteriously on x64.
Allocation Strategy Trade-offs
Each allocation policy has different security properties. Choose based on your threat model.allocator::section - Maximum Protection
- Uses
SEC_NO_CHANGEflag to make memory immutable - Cannot be modified even by the allocating process
- Resistant to in-memory patching and hook injection
- Visible to kernel-mode inspection
- When protecting against usermode hooking/patching
- When you need immutable code regions
- Defense against tampering
- More complex allocation process
- Slightly higher initialization overhead
- Memory cannot be modified after creation
allocator::heap - Private Executable Heap
- Private heap with
HEAP_CREATE_ENABLE_EXECUTE - Memory can be deallocated without freeing entire region
- Less common allocation pattern (harder to detect)
- Heap metadata may leak information
- When you need to allocate/free stubs dynamically
- When hiding allocation patterns from monitoring tools
- When heap-based allocations are more expected
- Heap metadata exists (potential information leak)
- Can be modified (vulnerable to patching)
- Requires heap management overhead
allocator::memory - Standard Virtual Memory
- Standard
NtAllocateVirtualMemoryallocation - Transitions from RW to RX (W^X principle)
- Most common allocation pattern
- Easily monitored by security products
- When you need standard memory allocation
- When you want predictable behavior
- For testing and development
- Most easily detected allocation pattern
- Can be modified if protection changes
- Standard technique, well-known to defenders
Comparison Matrix
| Property | section | heap | memory |
|---|---|---|---|
| Tamper Resistance | Excellent | Poor | Poor |
| Stealth | Medium | High | Low |
| Performance | Medium | High | High |
| Complexity | High | Medium | Low |
| Modifiable | No | Yes | Yes |
| Detection Risk | Medium | Low | High |
Stub Generation Strategy Trade-offs
Different stub generation approaches have varying detection risks and complexity.generator::direct - Self-Contained Syscall
- Direct
syscallinstruction in allocated memory - Self-contained, no external dependencies
- Easily detected by signature scanning
- Fastest execution
- Static signature:
0F 05(syscall instruction) - Memory scanning will find syscall instructions
- Pattern is well-known to EDR products
- When performance is critical
- When detection is not a concern
- For development and testing
generator::gadget - Indirect Syscall (x64 only)
- No
syscallinstruction in allocated memory - Jumps to legitimate
syscall; retgadget in ntdll - Harder to detect via memory scanning
- Syscall originates from trusted module
- Unusual control flow (jump to gadget)
- Stack analysis may reveal indirect call
- Return address points outside normal call chain
- When avoiding syscall instructions in your memory
- When you want syscalls to appear from ntdll
- Against signature-based detection
generator::exception - VEH-Based Syscall
- Uses
ud2(illegal instruction) exception - Actual syscall performed in exception handler
- No syscall instruction in stub code
- Most complex control flow
- VEH registration is monitored by many EDR products
- Unusual exception-based control flow
- Performance overhead from exception handling
- Exception-based execution is suspicious
- For advanced evasion techniques
- When you need maximum obfuscation
- In research and experimentation
- Significant performance overhead
- Complex debugging
- VEH registration may trigger alerts
Comparison Matrix
| Property | direct | gadget | exception |
|---|---|---|---|
| Performance | Excellent | Good | Poor |
| Stealth | Low | High | Medium |
| Complexity | Low | Medium | High |
| x86 Support | Yes | No | Yes |
| Detection Risk | High | Low | Medium |
| Debugging | Easy | Medium | Hard |
Parsing Strategy Considerations
parser::directory - Exception/Export Directory
Security Properties:- Uses PE metadata (exception directory on x64)
- Resilient to hooks (reads structure, not code)
- Accurate on unpatched systems
- Fast parsing
- On unhooked systems
- When you trust PE metadata
- For maximum performance
- May fail if PE structure is modified
- Doesn’t detect hooks
parser::signature - Prologue Scanning with Hook Detection
Security Properties:- Scans actual function code
- Detects hooks via signature mismatch
- Searches neighboring functions if hook detected
- Resilient to simple hooks
- On systems with usermode hooks (EDR)
- As a fallback parser
- When you need hook detection
jmpinstructions (0xE9, 0xEB)jmp [mem](0xFF 0x25)int3breakpoints (0xCC)ud2(0x0F 0x0B)- Push/return trampolines
Recommended Parser Chain
Combine both for resilience:Operational Security Best Practices
1. Avoid Suspicious Patterns
Suspicious combinations:- VEH registration + syscall allocation in short time window
- Large number of small executable allocations
- Executable heap creation immediately before sensitive operations
2. Minimize Initialization Footprint
Initialize once and reuse:3. Handle Initialization Failures Gracefully
Don’t make your intent obvious:4. Be Aware of Telemetry
Modern Windows has extensive telemetry:Event Tracing for Windows (ETW)
Event Tracing for Windows (ETW)
ETW can log:
- Memory allocations with executable permissions
- VEH registration
- Syscall invocations (via kernel providers)
- Thread creation
Kernel-Mode Callbacks
Kernel-Mode Callbacks
Kernel drivers can register callbacks for:
- Process/thread creation
- Image loading
- Object creation
- Memory allocation
EDR/AV Scanning
EDR/AV Scanning
Security products actively scan:
- Memory for suspicious patterns
- Call stacks for anomalies
- Behavioral patterns
- API call sequences
5. Test Against Real Security Products
Don’t assume a technique works without testing:Test in a Lab Environment
Set up VMs with actual EDR/AV products installed. Never test on production systems without authorization.
Monitor Telemetry
Use tools like Sysmon and Process Monitor to see what telemetry your technique generates.
Threat Model Considerations
Against Usermode Hooks
Threat: EDR/AV products hook Win32 APIs and higher-level Nt* functions. Defense:- Use
parser::signaturewith hook detection - Use
allocator::sectionto prevent stub patching - Consider
generator::gadgetto avoid direct syscalls
Against Memory Scanning
Threat: Security products scan process memory for suspicious patterns. Defense:- Avoid
generator::direct(contains0F 05syscall signature) - Use
generator::gadgetorgenerator::exception - Consider custom generators with polymorphic code (see custom-generator.cpp example)
Against Behavioral Analysis
Threat: EDR products analyze program behavior and call sequences. Defense:- Minimize suspicious behavior patterns
- Mix syscalls with normal API calls
- Avoid unusual timing or sequences
Against Kernel-Mode Inspection
Threat: Kernel drivers inspect memory, threads, and syscalls from ring 0. Defense:- None from usermode - kernel can see everything
- This library cannot bypass kernel-mode detection
- Consider kernel-mode solutions if this is your threat model
Recommended Configurations
For Maximum Stealth
For Maximum Protection (Anti-Tampering)
For Development and Testing
For Research and Experimentation
Legal and Ethical Considerations
Legitimate Use Cases
Security Research
Studying Windows internals and syscall mechanisms for educational purposes.
Red Team Operations
Authorized penetration testing with written permission from the system owner.
Software Protection
Protecting your own software from tampering and reverse engineering.
Malware Analysis
Understanding techniques used by malware to improve detection.
Illegal Use Cases
- Unauthorized access to computer systems
- Bypassing security on systems you don’t own
- Creating malware
- Any use without explicit authorization
Summary
Security is about trade-offs. Choose policies based on your specific threat model, performance requirements, and operational constraints.
- Always use
nullptr, neverNULLon x64 - This prevents critical stack corruption bugs - Choose allocation policies based on your need for tamper resistance vs. stealth
- Choose generation policies based on your need for performance vs. detection avoidance
- Use parser chains with both directory and signature parsers for resilience
- Test against real security products to validate your approach
- Remember: No usermode technique can bypass kernel-mode detection
Next Steps
Basic Usage
Learn the fundamentals of using syscalls-cpp safely
Custom Policies
Create your own policies tailored to your threat model