Evasion Guide
What each technique does, what it defeats, and known limitations
What each evasion technique does, what detection it defeats, and known limitations. Most flags compose freely, but some are mutually exclusive or have precedence rules — see Flag Compatibility & Precedence.
Always-On Techniques
These are compiled into every agent. No build flag needed.
Indirect Syscalls
What it does: Resolves syscall numbers at runtime using HalosGate (read SSN from ntdll, fall back to adjacent syscall if hooked) and executes via a jmp gadget pointing to the syscall; ret instruction inside ntdll. All sensitive operations — memory allocation, thread creation, context manipulation, section mapping — go through this path.
What it defeats:
- Userland API hooking (EDR inline hooks on ntdll)
- API call monitoring via IAT hooks
Limitations:
- Kernel-level ETW telemetry still fires (e.g.,
EtwTiprovider) - Some EDRs hook at the kernel level via callbacks, not userland hooks
- HalosGate may fail if adjacent syscalls are also hooked (TartarusGate handles this)
Ekko Sleep Obfuscation
What it does: During sleep, the implant encrypts its own memory using a ROP chain:
VirtualProtect→ make.textRWSystemFunction032→ RC4 encrypt.text+.datasectionsWaitForSingleObject→ sleep for the configured intervalSystemFunction032→ RC4 decryptVirtualProtect→ restore.textto RX
Thread context is encrypted during the sleep window.
What it defeats:
- Memory scanners that look for implant signatures during idle periods
- Periodic memory integrity scans by EDR
- Pattern matching on code sections
Limitations:
- The ROP gadgets themselves are in memory (but they’re legitimate ntdll/kernel32 addresses)
- Automatically disabled during keylogger operation (keylogger needs active polling)
- Short sleep intervals reduce the window of protection
Sleep Mask
What it does: XOR-encrypts sensitive runtime state during Ekko sleep with a per-cycle random key:
- HTTP/HTTPS config (server URL, paths, headers)
- Child agent table
- SOCKS channel state
- Traffic profile strings
Combined with Ekko (.text RC4), stack encryption, and Schannel transport: zero cleartext IOC surfaces exist during sleep.
What it defeats:
- Data section string extraction during sleep
- Memory forensics recovery of C2 server URLs, User-Agent strings, API paths
Limitations:
- State must be decrypted before use on wake — small window of exposure during active operation
Schannel TLS (No WinHTTP)
What it does: HTTPS transport uses raw Winsock sockets + Windows Schannel SSPI for TLS, with manual HTTP/1.1 request framing. No winhttp.dll dependency.
What it defeats:
- WinHTTP internal heap cache IOC leakage (SSL sessions, URL buffers survive
WinHttpCloseHandle) winhttp.dllimport table detection (legitimate processes rarely import it)- WinHTTP-specific behavioral monitoring
Limitations:
- Schannel SSPI is more complex to maintain than WinHTTP
secur32.dllappears in imports (but this is expected for many legitimate processes)
Secure Free
What it does: Calls SecureZeroMemory before every HeapFree and VirtualFree across 45+ callsites. Freed memory is zeroed before being returned to the heap.
What it defeats:
- Memory forensics recovery of freed allocations
- Volatility-style heap analysis finding old strings, keys, or config data
Limitations:
- Small performance cost (negligible in practice)
- Doesn’t prevent the OS from paging memory to disk before it’s freed
PPID Spoofing
What it does: When spawning sacrificial processes (for migration or execute-assembly), uses PROC_THREAD_ATTRIBUTE_PARENT_PROCESS to set the parent to explorer.exe.
What it defeats:
- Process tree analysis (child of
cmd.exeorpowershell.exeis suspicious) - Parent-child relationship heuristics
Evasion Flags
Hardware Breakpoint AMSI/ETW Bypass (--hwbp-bypass)
What it does: Installs a Vectored Exception Handler (VEH) that uses debug registers (DR0-DR3) to set hardware breakpoints on:
AmsiScanBuffer— returnsS_OK+AMSI_RESULT_CLEAN(assembly declared clean)NtTraceEvent— returnsERROR_SUCCESS(ETW event silently dropped)GetStdHandle— redirects .NET console output to the capture pipeExitProcess— prevents assembly from killing the host process
What it defeats:
- AMSI scanning of .NET assemblies and PowerShell
- ETW telemetry for .NET runtime events, assembly load events
.textsection integrity scans (no bytes are modified — breakpoints are in CPU registers)
Limitations:
- Only 4 hardware breakpoints available (DR0-DR3) — all are used
- A debugger or anti-cheat that uses debug registers will conflict
- Doesn’t suppress kernel-mode callbacks (use
--loadlib-proxyfor that)
HWBP All Threads (--hwbp-all-threads)
What it does: Extends --hwbp-bypass to apply hardware breakpoints to all threads in the process, not just the main thread. Enumerates threads via NtQuerySystemInformation and sets DR0-DR3 on each via NtSetContextThread.
What it defeats:
- EDR spawning worker threads to bypass single-thread HWBP hooks
- CLR threadpool threads calling
AmsiScanBufferoutside the hooked thread
Limitations:
- Thread enumeration adds overhead (noticeable in processes with hundreds of threads)
- New threads created after the hook is set are not automatically covered
Implies --hwbp-bypass.
Stack Spoofing (--stack-spoof)
What it does: During Ekko sleep, fabricates legitimate-looking thread stack frames using return addresses pointing to real ntdll.dll and kernel32.dll gadgets. The sleeping thread’s stack looks like a normal system thread.
What it defeats:
- EDR thread stack-walk analysis
- Stack-based attribution (tracing execution back to injected code)
Limitations:
- Only active during sleep — during execution, the real stack is visible
- Stack frame patterns may not match every EDR’s heuristic (but covers the major ones)
Stack Encryption (--stack-encrypt)
What it does: XOR-encrypts the poll thread’s entire used stack region during Ekko sleep. Prevents recovery of stack-resident strings, return addresses, and local variables.
What it defeats:
- Memory scanners recovering URLs, paths, and config strings from the stack
- Return address analysis during sleep
Limitations:
- Only the poll thread stack is encrypted — other threads are not affected
- Stack must be decrypted on wake before resuming
ETW NOP Call (--etw-nop-call)
What it does: Patches EtwpEventWriteFull in ntdll with xor eax, eax; ret (return success, do nothing). Suppresses all user-mode ETW event generation at the source — no events reach any ETW session.
What it defeats:
- User-mode ETW providers (Microsoft-Windows-DotNETRuntime, Security-Auditing, Threat-Intelligence)
- Process Monitor, ProcMon-style ETW consumers
- EDR user-mode ETW-based detections
Limitations:
- Byte-patches ntdll
.textsection — detectable by.textintegrity scans - Kernel-mode ETW providers (EtwTi) are unaffected
- Breaks legitimate ETW consumers in the same process
Module Stomping (--module-stomp)
What it does: Instead of allocating fresh memory with VirtualAlloc, the PE mapper stub loads a sacrificial system DLL (xpsservices.dll, ~1.4MB) via LoadLibraryExA with DONT_RESOLVE_DLL_REFERENCES (maps the DLL but skips DllMain and dependency loading). The stub then overwrites the DLL’s sections with the loader PE — relocations, imports, and protections applied in-place. The DLL’s PEB EntryPoint is nulled to prevent a crash at process exit.
What it defeats:
- Unbacked memory detection — VAD tree shows
xpsservices.dll(file-backed SEC_IMAGE) at the loader PE address, not a private RWX allocation VirtualAllocbehavioral monitoring — no RWX memory allocation, no MEM_COMMIT/MEM_RESERVE calls for the loader PE- pe-sieve header scanning — PE headers are zeroed after mapping (before thread launch)
Limitations:
- pe-sieve disk-to-memory comparison will detect modified code (accepted as residual risk; Ekko encrypts
.textduring sleep, making comparisons produce garbage) - Loader PE must fit within the DLL’s
SizeOfImage(~1.4MB for xpsservices.dll) - Module-stomped migration requires DLL pre-loading on the stub thread to avoid a loader lock deadlock during Early Bird APC injection (schannel.dll, mswsock.dll, ncryptsslp.dll, bcryptprimitives.dll + Winsock init)
- Falls back to
VirtualAllocif the target DLL is unavailable or too small
Module Overloading (--module-overload)
What it does: Maps a clean DLL as SEC_IMAGE (NtCreateSection + NtMapViewOfSection), then overwrites the section with payload — manual relocation and import resolution. No PEB loader-list entry (unlike LoadLibrary-based stomping).
What it defeats:
- Unbacked memory detection (VAD shows legitimate DLL file backing)
- PEB loader-list analysis (no entry for the overloaded module)
Limitations:
- pe-sieve disk-to-memory comparison still detects modified code
- Mutually exclusive with
--module-stomp
LoadLibrary Proxy (--loadlib-proxy)
What it does: During execute-assembly, hooks LdrLoadDll in ntdll and selectively manual-maps CLR-specific DLLs (clr.dll, clrjit.dll) to suppress kernel-mode PsSetLoadImageNotifyRoutine callbacks. System DLLs load normally. Includes API set resolution (PEB->ApiSetMap), TLS directory processing, and fail-open design.
What it defeats:
- Kernel-mode image-load notifications for CLR DLLs
- EDR callbacks that trigger on clr.dll/clrjit.dll loading
- User-mode ETW DLL-load events for filtered DLLs
Limitations:
- Increases the CLR stub size (~8KB → ~20KB, must fit within mswsock.dll
.text) - Only filters clr.dll and clrjit.dll (system DLLs use original loader)
- Falls back to original
LdrLoadDllon mapping failure (fail-open) - .NET Framework 4.x targeting only
Tampered Syscalls (--tampered-syscalls)
What it does: Instead of executing syscall from user-allocated memory, calls a decoy ntdll function (NtQuerySecurityObject). A VEH handler intercepts at the syscall instruction boundary via DR3 hardware breakpoint, swaps the SSN and register arguments to the real target. The syscall instruction executes from ntdll’s own stub — return address points into ntdll.
What it defeats:
- SSN-based detection (SyscallDetect, InlineWhispers monitoring)
- Return-address analysis (call stack shows ntdll, not injected code)
- User-memory
syscallinstruction scanning
Limitations:
- Uses DR3 (leaves DR0-DR1 for HWBP AMSI/ETW bypass)
- DR3 conflict with any debugger or anti-cheat using that register
- Slight overhead per syscall (VEH dispatch + register swap)
PE Fluctuation (--pe-fluctuation)
What it does: Timer-driven encrypt-at-rest for the mapped PE. VEH handler decrypts on ACCESS_VIOLATION (demand-paging pattern). Only the accessed page is live at any moment. Fresh RC4 key per resume cycle via QPC + PID + TID. Complement to Ekko — even between Ekko cycles, the PE image is encrypted.
What it defeats:
- Continuous memory scanning (PE is encrypted even during active operation)
- Memory dump analysis (only one page decrypted at a time)
Limitations:
- VEH dispatch overhead on every code page access (mitigated by 500ms encrypt timer)
- InterlockedCompareExchange required to prevent double-RC4 corruption
Heap Encryption (--heap-encrypt)
What it does: During Ekko sleep, calls HeapWalk() on all non-default process heaps and XOR-encrypts their contents. Catches arbitrary heap allocations from BOF output, temporary buffers, and leaked data that SleepMaskRegister() doesn’t know about.
What it defeats:
- Heap forensics during sleep (volatility, WinDbg heap analysis)
- Memory scanners finding BOF output strings in heap allocations
Limitations:
- HeapWalk is relatively expensive — adds ~5-15ms to sleep/wake cycle
- Default process heap is excluded (system allocations)
Foliage Sleep Obfuscation (--sleep-method foliage)
What it does: APC-based alternative to Ekko. Creates a suspended thread, builds a 7-step ROP chain via CONTEXT structs (NtWaitForSingleObject → VirtualProtect(RW) → SF032(encrypt) → WaitForSingleObjectEx(sleep) → SF032(decrypt) → VirtualProtect(RX) → ExitThread), queues each as an NtContinue APC.
What it defeats:
- Same as Ekko (memory scanning during sleep) but with a different behavioral fingerprint
- EDRs that specifically signature Ekko’s shellcode-stub pattern
Limitations:
- No RWX shellcode page (that’s the advantage, not a limitation)
- Stack-spoof and stack-encrypt are Ekko-specific (not compatible with Foliage)
Peer Reconnect (--peer-reconnect)
What it does: SMB/TCP child agents fall back to direct HTTPS beaconing if the parent agent dies. After a configurable timeout (default: 5 minutes), the child stops waiting for the parent and begins polling the C2 server directly.
What it defeats:
- Loss of access when a parent agent is killed, migrated, or the parent host goes down
Limitations:
- Child must have network egress to the C2 server (or redirector) for the fallback to work
- Only applies to SMB and TCP child agents — HTTPS agents already connect directly
Migration Techniques
Multiple injection techniques are available for process migration. Each defeats different behavioral detections.
| Technique | Flag | Defeats |
|---|---|---|
| EarlyBird APC | (default) | Classic, well-known |
| Thread context hijack | --context-hijack | EarlyBird APC detection |
| Section-based injection | --section-map | NtWriteVirtualMemory detection |
| Delayed suspend | --delayed-suspend | CREATE_SUSPENDED callback detection |
| Phantom DLL hollowing | --phantom-dll | Unbacked memory detection |
| Remote thread execution | --remote-thread | NtSetContextThread detection (incompatible with --context-hijack — child thread lacks csrss registration, SSPI/TLS unavailable) |
| Process ghosting | --ghost | On-disk AV scanning (file deleted before process runs) |
| Process herpaderping | --herpaderp | On-disk AV scanning (file content changed after mapping) |
Post-injection cleanup (SecureZeroMemory of injection blob + stub + config) is always performed.
Process Ghosting (--ghost)
What it does: Creates a file, writes the payload, marks it delete-pending via NtSetInformationFile(FileDispositionInformation), creates SEC_IMAGE section from the delete-pending file, creates process via NtCreateProcessEx, then closes the file handle (file disappears). The process runs from a file that no longer exists on disk.
What it defeats:
- On-disk AV scanning (file is gone before process runs)
- Forensic file recovery (delete-pending files are unlinked)
Limitations:
- ETW kernel callbacks still fire for process creation
- Some EDRs monitor
NtSetInformationFile+NtCreateSectionsequences
Process Herpaderping (--herpaderp)
What it does: Creates a file, writes the payload, creates SEC_IMAGE section from it, then overwrites the file content with a benign decoy (e.g., legitimate svchost.exe bytes) before creating the process. When AV/EDR reads the file for scanning, it sees the decoy — but the section mapped into memory still contains the original payload.
What it defeats:
- On-disk AV scanning (file reads show benign content after section creation)
- File-hash analysis (on-disk hash doesn’t match in-memory content)
- Forensic file recovery (file on disk is the decoy, not the payload)
Limitations:
- ETW kernel callbacks still fire for process creation
- Mutually exclusive with
--ghost - Requires write access to a temp directory
Flag Compatibility & Precedence
What “All Evasion” Means
--all-evasion (or the “All Evasion” toggle in the Web UI) enables the maximum compatible set of evasion flags. It does not blindly enable every flag — four are excluded to avoid conflicts:
| Excluded Flag | Reason |
|---|---|
--remote-thread | Mutually exclusive with --context-hijack. Creates a thread not registered with csrss.exe — SSPI/TLS is unavailable in the child. |
--module-overload | Redundant with --module-stomp (stomp takes precedence at runtime). |
--ghost | Process-level migration technique — replaces the entire migration flow. Use explicitly when needed. |
--herpaderp | Same category as ghost. Use explicitly. |
The flags included by --all-evasion:
--hwbp-bypass --hwbp-all-threads --stack-spoof --stack-encrypt --module-stomp --foliage-sleep --delayed-suspend --phantom-dll --section-map --context-hijack --tampered-syscalls --peer-reconnect --loadlib-proxy --pe-fluctuation --heap-encrypt --etw-nop-call
Mutual Exclusions
These flag pairs cannot be used together. The build system will error if both are set.
| Flag A | Flag B | Why |
|---|---|---|
--remote-thread | --context-hijack | Remote thread creates via NtCreateThreadEx (no csrss registration → SSPI/TLS broken in child). Context hijack redirects the main thread (properly registered → SSPI works). |
These pairs are not hard errors, but one silently wins:
| Flag A (wins) | Flag B (ignored) | Why |
|---|---|---|
--module-stomp | --module-overload | Both set allocMode in the PE mapper stub. Stomp is checked first. |
--ghost | --herpaderp | Ghost returns early from MigrateToProcess(). Herpaderp never runs. |
--ghost / --herpaderp | All other migration flags | Ghost and herpaderp replace the entire injection pipeline. Section-map, phantom-dll, context-hijack, delayed-suspend are all bypassed. |
Precedence Rules
When multiple flags are set, runtime if/else chains determine which technique runs. Higher in the list wins.
Execution trigger (how the injected code starts running):
| Priority | Flag | Method |
|---|---|---|
| 1 | --remote-thread | NtCreateThreadEx — new thread in target |
| 2 | --context-hijack | NtSetContextThread — hijack main thread RIP |
| 3 | (default) | NtQueueApcThread — EarlyBird APC on main thread |
Memory allocation (where the injection blob lives):
| Priority | Flag | Method |
|---|---|---|
| 1 | --phantom-dll | SEC_IMAGE map of dbghelp.dll → overwrite .text section |
| 2 | --section-map | Anonymous shared section (NtCreateSection + NtMapViewOfSection) |
| 3 | (default) | NtAllocateVirtualMemory (standard RWX allocation) |
Process creation (how the sacrificial process is spawned):
| Priority | Flag | Method |
|---|---|---|
| 1 | --remote-thread | CreateProcess non-suspended, wait 1s, inject via NtCreateThreadEx |
| 2 | --delayed-suspend | CreateProcess non-suspended, wait 1s, suspend main thread |
| 3 | (default) | CreateProcess suspended (CREATE_SUSPENDED) |
Sleep obfuscation:
| Priority | Flag | Method |
|---|---|---|
| 1 | --sleep-method foliage | APC-based NtContinue chain (7-step ROP) |
| 2 | (default) | Ekko shellcode stub (timer-based ROP) |
Both fall back to plain Sleep() on failure.
Dependencies
| Flag | Requires | Notes |
|---|---|---|
--hwbp-all-threads | --hwbp-bypass | All-threads extends the base HWBP hook to cover every thread. The build system auto-enables --hwbp-bypass when --hwbp-all-threads is set. |
--stack-spoof | Ekko sleep (not Foliage) | Stack spoofing is implemented inside the Ekko stub. When --sleep-method foliage is set, --stack-spoof is silently ignored. |
--stack-encrypt | Ekko sleep (not Foliage) | Same as stack-spoof — Ekko-only feature. |
Recommended Combinations
Maximum evasion (recommended): --all-evasion
Uses context-hijack, module-stomp, phantom-dll, section-map, delayed-suspend, foliage, all HWBP hooks, tampered syscalls, PE fluctuation, heap/stack encryption, and ETW suppression. Covers memory, behavioral, and network-level detections.
Stealth with TLS integrity: --hwbp-bypass --hwbp-all-threads --context-hijack --section-map --phantom-dll --tampered-syscalls --pe-fluctuation --heap-encrypt --etw-nop-call
Skips foliage (uses Ekko) to retain stack-spoof and stack-encrypt. Good when you need maximum sleep-time obfuscation.
Minimal footprint: --hwbp-bypass --tampered-syscalls
Just AMSI/ETW bypass and indirect syscall hardening. Smallest behavioral change, lowest detection surface.
Process ghosting (standalone): --ghost
Replaces the standard migration pipeline entirely. Do not combine with other migration flags — they are ignored.
Runtime Interactions
These behaviors happen automatically at runtime regardless of build flags:
| Condition | Effect |
|---|---|
| CLR loaded (execute-assembly) | Ekko and Foliage disabled — background GC/JIT threads crash during .text encryption. Falls back to plain Sleep. |
| Keylogger active | Ekko and Foliage disabled — keylogger polling thread needs active .text. |
| Migrated child process | Ekko always disabled (noEkko=TRUE) — manually-mapped PE has different .text boundaries, ROP gadget chain unreliable. |
| PE fluctuation + Ekko/Foliage | Coordinated automatically — PE fluctuation suspends its timer before Ekko/Foliage encrypts .text, resumes after decryption. Both encrypting .text simultaneously would corrupt memory. |
Traffic & Identity
Traffic Mimicry (--profile)
What it does: Shapes C2 HTTPS traffic to mimic legitimate SaaS API patterns. Each profile sets:
- URI paths (e.g.,
/api/conversations.historyfor Slack) - User-Agent string matching the real application
- Content-Type headers
- Authorization headers (Bearer tokens)
- JSON body wrappers (request payloads embedded as base64 inside realistic JSON)
What it defeats:
- Network traffic analysis and IDS signatures
- SOC alert triage (traffic looks like legitimate SaaS API calls)
- Proxy categorization
Limitations:
- Profile must match on both implant and redirector (see Deployment Guide)
- Deep inspection of JSON payload structure may reveal anomalies
- Certificate mismatch if not using domain fronting or proper TLS termination
Process Masquerade (--cover)
What it does: Spoofs PE version info resources (CompanyName, ProductName, InternalName, OriginalFilename, FileDescription) and PE timestamps to match a legitimate Windows process.
What it defeats:
- Process metadata analysis (task manager, Process Explorer column inspection)
- Automated process ancestry/legitimacy checks
Limitations:
- Binary hash won’t match the real process
- PEB
ImagePathNamestill shows the actual path (unless combined with PPID spoofing + spawn from correct directory)
Auto-selected when --profile is used. --cover overrides.
BOF-Based Techniques
DLL Notification Unhooking (unhook-dllnotif)
What it does: Walks the LdrDllNotificationList in ntdll and removes EDR-registered DLL load notification callbacks.
What it defeats:
- EDR DLL load monitoring (the EDR stops receiving notifications about new DLLs being loaded)
Limitations:
- The EDR may detect that its callback was removed
- Must be run after the EDR has registered its callback
Usage: unhook-dllnotif (BOF command, not a build flag)
Threadless Injection (threadless-inject)
What it does: Hooks a target process’s export function first 5 bytes with a CALL to a “memory hole” (unused region within ±1.75GB of the hook site). The memory hole contains 63 bytes of self-healing shellcode that restores the original 8 bytes before executing the payload.
What it defeats:
- Thread creation monitoring (no new thread)
- APC queue monitoring (no APC)
- Context modification detection (no SetContextThread)
Limitations:
- Payload executes only when target naturally calls the hooked export
- Must find a suitable memory hole near the hook site
Usage: threadless-inject <pid> <dll> <export> <sc_file> (BOF command)
ETW Hijack (etw-hijack)
What it does: Restarts active ETW tracing sessions with a fake log file path, effectively blinding any ETW consumer (EDR, Sysmon, ProcMon) that relies on the hijacked session.
What it defeats:
- ETW-based EDR telemetry (the session is redirected to a dead-end log)
- Sysmon event collection (if the Sysmon ETW session is targeted)
Limitations:
- The original ETW consumer may detect the session was restarted
- Must have sufficient privileges to restart ETW sessions
Usage: etw-hijack (BOF command)
Hook Detection (detect-hooks)
What it does: Compares loaded ntdll/kernel32/kernelbase against clean copies from \KnownDlls to identify inline hooks placed by EDR.
Usage: detect-hooks / detect-hooks -v (BOF command — informational, not evasive)