15. Advanced Evasion Techniques
15.1 Sleep Obfuscation (In-Memory Encryption During Sleep)
Core idea: when the Beacon is idle it encrypts its own memory (RW state) → EDR memory scanning only sees encrypted data → a timer/APC triggers decryption (RWX) → the task executes → re-encrypts. EDR always sees encrypted memory during scans.
Comparison of the three mainstream implementations:
| Technique | Trigger mechanism | Core API | Principle |
|---|---|---|---|
| Ekko | Timer Queue | CreateTimerQueueTimer + NtContinue | Chained Timer callbacks: Timer1 → change memory to RW + encrypt → Sleep → Timer2 → decrypt + change to RX + resume execution. All callbacks pass a forged thread context via NtContinue |
| Foliage | APC | NtQueueApcThread + NtContinue | Queues a series of APCs on a worker thread: APC1 → encrypt memory + RW → APC2 → Sleep → APC3 → decrypt + RX + resume execution. Each APC passes a different thread context |
| Zilean | Wait Object | RegisterWait + NtContinue | An Ekko variant that uses a RegisterWait callback instead of a Timer Queue |
| Hypnus | Multi-mode | TpSetTimer/TpSetWait/NtQueueApcThread | Implemented in Rust, three modes + call-stack spoofing + heap encryption. Dynamically registers CFG (Control Flow Guard) targets |
Actual attack flow (Ekko example):
1. CreateTimerQueueTimer registers the callback → points to NtContinue (with forged CONTEXT)
2. Callback fires: VirtualProtect(payload, RW) → SystemFunction032 (RC4 encryption)
3. Sleep (wait interval)
4. Second timer fires: SystemFunction032 (RC4 decryption) → VirtualProtect(payload, RX)
5. NtContinue restores the original thread context → Beacon resumes execution
6. Loop
Detection and countermeasures:
- Hunt-Sleeping-Beacons (HSB): enumerates timers and analyzes whether callback addresses point to NtContinue → detects Sleep Obfuscation
- EkkoMod bypasses HSB: the Timer callback pointer points to the 8 bytes before NtContinue (nop instruction 0F 1F 84 00 00 00 00 00) → at execution nop → NtContinue → HSB does not recognize it as an NtContinue callback
- Stack Duplication: duplicate the thread's registers + stack (including return addresses) → during sleep the call stack looks like a normal callback thread → avoids the stack containing the NtSignalAndWaitForSingleObject IOC
- Module Stomping combined: load the payload into a legitimate DLL's memory region → memory appears to belong to a legitimate module → avoids unbacked-memory detection
- Memory state transitions: the safe approach is RW↔RX (never going through RWX) → Havoc's Sleep Mask uses RWX and is easily flagged by EDR
Linux Sleep Obfuscation (SilentPulse): - A Linux version appeared in 2025: uses POSIX timer_create + SIGEV_THREAD → encrypt/decrypt in the callback - Faces similar challenges: detectable by stack analysis → requires evasion similar to Stack Duplication - Tools: Ekko (archived) / Hypnus (Rust, recommended)
15.2 Evolution of Indirect Syscalls
The evolution from direct to indirect syscalls:
- Direct Syscall: hardcode the syscall instruction in your own code → EDR detects via call-stack analysis that the syscall is not inside ntdll → flagged
- Indirect Syscall: find the address of a syscall; ret instruction in ntdll → jmp to that address and execute → the call stack shows the return address inside ntdll → looks legitimate
- Tartarus' Gate: randomize the syscall stub address at runtime → a different stub each execution → avoids hardcoded signatures
- HalosGate/TartarusGate: when ntdll is hooked (the first few instructions replaced with jmp) → search for adjacent syscall stubs → skip the hook and find a clean stub
- Mockingjay: doesn't need ntdll! Searches loaded legitimate DLLs for a syscall; ret instruction sequence → no involvement with ntdll at all → no ntdll hook to detect
- RecycledGate: searches loaded DLLs for syscall instruction sequences → a more generalized Mockingjay
API Hashing (tool level): - Avoid GetModuleHandle+GetProcAddress (monitored) → manually implement module traversal + function-name hash matching → fully evade API monitoring - In practice: toxoglosser uses GetModuleHandle+GetProcAddress via hashing + Tartarus' Gate + no LazyDLL
15.3 Evolution of ETW/AMSI Bypasses
ETW Patching (event tracing):
- Method 1: modify the EtwEventWrite entry to ret 0 (return immediately) → all ETW events are silently dropped
- Method 2: lower-level patch → modify the first few bytes of ntdll!EtwEventWrite → does not trigger memory protection
- Detection: EDR checks ntdll memory integrity → must restore after patching or use hardware breakpoints instead
AMSI Bypass (Anti-Malware Scan Interface):
- Method 1: patch the AmsiScanBuffer/AmsiScanString entry to return immediately → all content scans return "clean"
- Method 2: hardware breakpoint hooking → set a breakpoint on AmsiScanBuffer via DR registers → no memory modification → stealthier
- Method 3: CLM (Constrained Language Mode) bypass → bypass the execution policy in PowerShell restricted mode
- Evolution: from highly signatured scripts like [Ref].Assembly.GetType('System.Management.Automation.AmsiUtils') → to C# inline compilation or direct syscall-level patches
15.4 Evolution of Injection Techniques
Module Stomping: - Principle: load a legitimate DLL → overwrite its memory contents with the malicious payload → memory appears to belong to a legitimate DLL - Advantage: avoids unbacked memory (memory with no backing file) → common EDR memory-scan IOCs become ineffective - In practice: choose an uncommon system DLL to stomp → reduces the chance of inspection
Map Injection: - Principle: manually parse the PE → map it section-by-section into the target process → no LoadLibrary → no module-load events - Advantage: does not trigger DllMain notifications → does not appear in the module list
Early Bird APC Injection: - Principle: create a suspended process → inject via QueueUserAPC → ResumeThread → the APC executes before the main thread initializes - Advantage: completes injection before EDR installs its hooks → bypasses process-creation monitoring
Hardware Breakpoint Injection: - Principle: set hardware breakpoints using the DR0-DR3 registers → no code memory modified → execute malicious logic when the breakpoint fires - Advantage: no memory modification → EDR integrity checks cannot detect it