offensive-coding/edr-evasion-dev/SKILL.md
Auth/lab dev: Windows detection-resilience research; syscall dispatch, stack traces, sleep-state, memory permissions, ETW/AMSI telemetry tradeoffs.
npx skillsauth add aeondave/malskill edr-evasion-devInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Field-tested principles for Windows EDR/AV evasion. Verified against S1, Defender, and CrowdStrike (2026). Project-agnostic — applies to any loader, implant, or tool runner targeting Windows 10/11.
EDRs do not evaluate a process at a single point in time. They accumulate a weighted signal score across the entire execution timeline: syscalls, memory allocation patterns, page permission transitions, ETW events, call stack shapes, IAT content, and in-memory byte patterns. Final verdict is a sum.
The loader runs first. Every decision the loader makes — how memory is allocated, whether VirtualProtect is called, whether ETW is emitting events during decryption, whether known loader bytes are present in any page — contributes to the score before the payload executes a single instruction. The payload inherits that accumulated score and works from it, upward.
Donut's loader stub is a well-known shellcode with documented byte signatures. Its default allocation pattern is VirtualAlloc(RWX). Its PE-loader code path matches ML models trained on Donut specifically. The result: by the time the beacon's first instruction ran, the process already had 3–4 strong independent signals. The payload behavior was irrelevant — the verdict was reached at loader stage.
The goal of the loader layer is to reach payload execution with the process's accumulated signal score at or near zero. Concrete requirements derived from a minimal loader execution order:
| Loader decision | Wrong (signal-generating) | Right (zero-signal) |
|---|---|---|
| Anti-debug/sandbox checks | NtQueryInformationProcess, NtSetInformationThread | PEB byte reads + KUSER_SHARED_DATA reads only — no kernel events |
| ETW patch method | VirtualProtect on ntdll page (permission change event) | NtWriteVirtualMemory — kernel COW on SEC_IMAGE, no permission change event |
| ETW patch timing | After startup delay (delay emits ETW events) | Before startup delay so no ETW events during sleep masking or decrypt |
| Payload memory | VirtualAlloc(RWX) or VirtualAlloc then VirtualProtect(RX) | Module stomp (SEC_IMAGE backing), Ghostly Hollow (dual-view), or Mockingjay (existing RWX section) |
| Loader bytes in memory | Donut stub, reflective loader shellcode with known signatures | PIC Rust no_std loader — no recognizable signature; wiped post-decrypt |
| Plaintext payload lifetime | Survives in heap post-exec (EDR periodic scanner finds it) | write_volatile zero after decrypt; stage scrub thread cleans post-handoff |
| Key/nonce material | Static bytes in .rdata | MBA-obfuscated per-build derivation — no raw key bytes in any section |
The execution order in a minimal loader entry point is not arbitrary — it is a signal ordering constraint:
syscall::init() ← indirect/RecycledGate layer live before ANY NT call
spoof::init() ← call stack spoofed from first dispatch
anti_debug (passive) ← PEB read only, zero kernel events
anti_sandbox (passive) ← GetLastInputInfo + GetTickCount64 + GetForegroundWindow
etw::patch() ← ETW silenced BEFORE startup delay and decrypt
startup delay ← NtDelayExecution now runs without ETW-TI telemetry
reassemble + decrypt ← RW allocation only; no RWX at any point
wipe encrypted buffer ← plaintext lifetime minimized
etw::restore() ← ETW restored before handoff
loader::execute() ← payload placed in backed memory via stomp/hollow/mockingjay
stage_scrub ← decode workspace zeroed after payload leaves it
Each step is positioned so that the EDR-observable event it generates either (a) produces zero telemetry, (b) produces telemetry indistinguishable from a benign process, or (c) is masked by the layer initialized above it in the stack.
Implication for payload design: do not open signal surfaces the loader closed. No VirtualProtect on ntdll in the beacon if the loader already patched ETW via NtWriteVirtualMemory. No NtQueryInformationProcess in the payload's anti-debug if the loader's passive checks already covered it. The payload's job is to keep the accumulated score near zero from the handoff point onward — not to rebuild defenses the loader already established.
Field-tested: the strongest evasion configuration is the smallest one. Every evasion feature (VEH, HWBP, indirect syscall, ntdll unhooking, stack spoofing) is itself a detection surface. Feature accumulation produces net negative evasion when each feature generates its own indicator.
A 1,472-line reference beacon (zero VEH, zero DRs, zero unhooking, byte-patch ETW, NtContinue spoof, sleep obfuscation) evades S1/Defender/CS fully. A 6,126-line version with VEH+DR0+DR1+KnownDlls+RecycledGate+build-std is detected.
| Mode | Minimal viable evasion | Rationale | |------|----------------------|-----------| | Beacon (module stomp) | NtContinue spoof + sleep obfuscation + byte-patch ETW | Unbacked needs stack spoof; sleep needs memory encrypt; ETW byte-patch is 1 byte | | Tool (backed EXE) | PEB-resolved direct ntdll stubs + private RWX | Backed .text is legitimate; direct stubs avoid syscall indicators; private RWX is generic | | Tool (unbacked) | indirect_syscall + SilentMoonwalk + E10 HWBP | Unbacked memory needs stack spoof + syscall interception |
Only when: (1) the specific detection is confirmed active against your EDR, (2) the feature's detection cost is measured lower than the vector it eliminates, (3) the feature can be #[cfg] gated so it's physically absent from binaries that don't need it.
Load SSN into EAX, jump to syscall; ret gadget (0F 05 C3) inside ntdll. Return address lands in ntdll → legitimate stack walk. Safe in all thread contexts.
Zw* exports from EAT, sort by address → index = SSN. Works with all Nt* stubs hooked.B8 xx xx 00 00. Fails when EDR inline-hooks.Build a CONTEXT with synthetic RSP (fake BaseThreadInitThunk → RtlUserThreadStart frames) → NtContinue → kernel replays context → syscall with fully spoofed stack.
| Context | Safe dispatch | Unsafe |
|---------|--------------|--------|
| Main thread, no console | spoofed_syscall, masked_syscall, indirect_syscall | — |
| Main thread, console attached | masked_syscall, indirect_syscall | spoofed_syscall (crash) |
| TP worker thread | tp_spoofed_syscall (SMW), indirect_syscall | masked_syscall (RSP corruption), spoofed_syscall (crash) |
| Backed EXE (any thread) | PEB-resolved real ntdll stubs | indirect_syscall (generates MORE detection than hooked stubs) |
Use runtime profile selection instead of one global syscall strategy:
| Profile mode | Primary path | Fallback | Best fit |
|-------------|--------------|----------|----------|
| 1 | indirect_syscall* wrappers | direct/clean stub if wrapper unavailable | unbacked beacon/tooling where stack legitimacy is weak |
| 2 | clean Nt* function pointers from fresh ntdll mapping | indirect_syscall* only for blocked calls | backed EXE where direct stub calls look normal |
Rule: if the binary is disk-backed and has legitimate .text, prefer clean/direct Nt* calls first. Save indirect spoof-heavy paths for unbacked execution contexts.
Some EDR research prototypes correlate kernel handle issuance (pre-NTAPI return) against userland hook telemetry — if a handle exists with no hook observation, HWBP/VEH interception is inferred. Not yet widespread in production S1/CS/Defender (2026); indirect_syscall + tp_spoofed_syscall remain effective. Monitor threat intel for production rollout.
Purpose: spoof TP worker thread call stacks so every frame has a valid .pdata (RUNTIME_FUNCTION) entry. Unwinders (ETW-TI, EDR) verify frame sizes against UNWIND_INFO — mismatched sizes expose synthetic frames.
find_jmp_reg_gadget(ntdll, min_frame): scan ntdll .text for 41 FF E4 (jmp r12); validate: no REX prefix, valid .pdata, frame_size >= min_frame, no frame pointer, past prologselect_chain_frames(ntdll): sample .pdata for 1-2 intermediate functions (frame 0x28–0x80, no FP)init_silentmoonwalk(): populate SMW_GADGET, SMW_CHAIN[4], override BTIT/RUTS frame sizes from .pdatabuild_smw_stack(buf, extra_args): [jmp_r12 gadget] → [intermediates] → [BTIT] → [RUTS] → [0]tp_spoofed_syscall: SMW_READY → jmp r12 (r12=continuation, r13=saved RSP); fallback → E8aFlow: push r12/r13 → lea r12,[continuation] → mov r13,rsp → mov rsp,synth → jmp gadget → syscall;ret → jmp r12 → mov rsp,r13 → pop r13/r12. Unwinder: Nt(leaf) → gadget → intermediate → BTIT → RUTS → 0.
| Constraint | Detail |
|------------|--------|
| min_frame >= 0x58 | NtMapViewOfSection has 10 args; 6 stack args, last at [rsp+0x50]. Smaller frame → arg overflow |
| LLVM rbx issue | List rbx in clobbers; LLVM may allocate it across asm block |
| Lock ordering | E10_LOCK → SPOOF_BUF_LOCK (never reverse). Contention → fallback to indirect_syscall |
| Beacon only | Tool_mode (backed EXE) skips SMW entirely (F9) |
| Init timing | After init_stack_spoof_phase1, before ntdll unhooking |
| Aspect | Backed EXE (tool_mode) | Unbacked (beacon, module stomp) | |--------|----------------------|-------------------------------| | Return address legitimacy | .text is in a known PE on disk → legitimate | Points to private/pagefile memory → suspicious | | Syscall dispatch | Real ntdll stubs (even hooked) | indirect_syscall + gadgets | | Stack spoofing | Unnecessary (adds indicators) | Essential (SilentMoonwalk or NtContinue) | | VEH/HWBP | Counterproductive (detection signal) | Useful for E10 payload interception | | DR registers | Zero DRs | DR0-DR3 for ETW/AMSI/E10 |
Key insight: for backed EXE, each evasion feature generates MORE detection than the attack surface it hides. S1 hooks collect telemetry but don't block; indirect syscall triggers "direct syscall from patched library" indicator.
NtAllocateVirtualMemory)PAGE_EXECUTE_READWRITE — generic, shared with JIT/CLR. Weaker signal than section RWX.NtFreeVirtualMemoryNtCreateSection(PAGE_EXECUTE_READWRITE) → RW view + RX view on same physical pagesSectionCreate.| Scenario | Strategy | Why | |----------|----------|-----| | Beacon (primary) | Module stomp | Image-backed, legitimate VAD entry | | Beacon (fallback) | alloc_rw + nt_protect(RX) | Private, no section kernel signal | | Tool, backed EXE | Private RWX | Generic JIT pattern, one-shot | | Tool, unbacked | Mapped section W^X | Need W^X for Fritter self-modification |
VEH handler catches EXCEPTION_ACCESS_VIOLATION on write to RX view → redirects write to RW view (same physical page). Decodes write instruction at fault RIP, computes RW destination (fault_addr - RX_BASE + RW_BASE), performs write, advances RIP, returns EXCEPTION_CONTINUE_EXECUTION.
Supported write forms: MOV [reg], r8/r32/r64, MOV [mem], imm8/imm32, MOVDQU/MOVQ/MOVUPS [mem], xmm, XOR [mem], reg (read-modify-write via RW view). Full decode table in constraints.md.
XOR: read from RW (has current data) → XOR → write to RW. Never read RX — may have stale cached value.
Technique: NtOpenSection(\KnownDlls\ntdll.dll) → NtMapViewOfSection(readonly) → compare .text → overwrite hooked stubs with clean bytes.
Status: code exists but disabled in all modes. KnownDlls generates 5+ kernel-logged signals (NtOpenSection + NtMapView + NtProtect(RWX on ntdll .text) + bulk write + NtProtect(restore)). The reference beacon evades without unhooking.
Constraint: if ever re-enabled, V6 ordering applies — sandbox/anti-debug checks MUST run before unhooking to avoid generating signals in sandbox.
Inspired by Astral Projection UDRL, cherry-picked without VEH/TrapFlag (detection signals).
DONT_RESOLVE_DLL_REFERENCES leaves IOCs: EntryPoint=NULL, ImageDll/LoadNotificationsSent/ProcessAttachCalled flags unset. PE-sieve and DetectCobaltStomp flag these.
Fix: walk PEB InLoadOrderModuleList by DllBase, patch:
ep_addr → standalone RET gadget (0xC3) in ntdll .textFlags @ offset 0x68 |= 0x4 | 0x8 | 0x40000Zero API calls, pure in-process memory write. Beacon + DLL + CPL modes.
Astral Projection uses VEH + TrapFlag to intercept LoadLibrary's internal NtCreateSection — generates thousands of EXCEPTION_SINGLE_STEP events (ETW-TI visible). Instead: open the DLL file directly after stomp.
peb_get_full_dll_name(h_mod) → PEB FullDllName\??\ → NT path on stack buffer (no heap)NtOpenFile(GENERIC_READ | SYNCHRONIZE) → file_handleNtCreateSection(SECTION_ALL_ACCESS, SEC_IMAGE) → section_handle (via indirect_syscall7)NtClose(file_handle) — section holds its own referenceOne-time init. Beacon EXE only. If any step fails → globals stay 0 → AP3 falls back to legacy.
Replaces XOR + PAGE_NOACCESS with full DLL view unmap during sleep:
NtUnmapViewOfSection(-1, h_mod) — removes DLL view, PEB entry stays intactNtMapViewOfSection(section, -1, &h_mod, SEC_IMAGE) — maps fresh clean DLLNtDelayExecution (spoofed) — scanner sees original unmodified DLLNtUnmapViewOfSection(-1, mapped_base) → NtMapViewOfSection — remap backingNtProtect(RW) → stomp .text → NtProtect(RX)Eliminates: SharedOriginal IOC, modified-.text detection, PAGE_NOACCESS anomaly.
ASLR: after remap, check actual_base == expected. If shifted, update BEACON_MEM and STOMP_MODULE_BASE globals. Fallback: unmap fails → decrypt backup, zero it, fall through to legacy XOR+NOACCESS.
NtProtectVirtualMemory(RX region, PAGE_NOACCESS) — EDR memory scanner sees no executable codeNtProtectVirtualMemory(RX region, PAGE_EXECUTE_READ) — restoreUsed when: AP3 section handle unavailable (DLL/CPL/XLL modes, or init_stomp_section failed), or AP3 unmap step failed (graceful fallback).
Before installing sleep hook trampoline, check first 14 bytes of kernel32!Sleep for known hook signatures:
E9 xx xx xx xx (JMP rel32 — EDR detour)FF 25 xx xx xx xx (JMP [rip+disp] — indirect jump)48 B8 xx xx xx xx xx xx FF E0 (MOV RAX, imm64; JMP RAX)If any detected: skip trampoline, use NtDelayExecution directly. Trampoline copies Sleep's first bytes including the hook → crash/loop.
spoofed_syscall for NtProtect calls (beacon) — indirect_syscall6 exposes loader .textSleep masking is not universally safe in embedded BOF host contexts. Apply this gate:
Sleep prologue is already detoured/hooked, skip custom trampoline and use NtDelayExecution path.This avoids recursive hook loops, host thread corruption, and unstable callback timing in BOF containers.
| Method | Mechanism | Status |
|--------|-----------|--------|
| Byte-patch (beacon) | NtWriteVirtualMemory(-1,self) via indirect syscall writes 33 C0 C3 to NtTraceEvent + EtwEventWrite; immediate restore | Active only if restore happens before long-running handoff |
| HWBP DR0 | Breakpoint on EtwEventWrite, VEH sets EAX=0 + skips | Disabled all modes (B4/F7) |
| Selective DR0 (F6) | RF flag bypass for non-CLR callers | Failed — S1 detects CLR ETW gap regardless |
| No bypass (tool_mode) | All ETW flows normally | Active — one-shot tools finish before EDR processes telemetry |
| Method | Mechanism | Status |
|--------|-----------|--------|
| HWBP DR1 | Breakpoint on AmsiScanBuffer | Disabled in tool_mode (F9) |
| Byte-patch fallback | Write B8 57 00 07 80 C3 to AmsiScanBuffer (return E_INVALIDARG) | Fallback when HWBP fails |
| No bypass (tool_mode) | AMSI runs normally | Active — S1 AMSI provider doesn't flag one-shot execution |
S1 detects BOTH total and selective (CLR-only) DR0 suppression. Safe byte-patching is a different pattern: avoid VirtualProtect entirely, patch via NtWriteVirtualMemory on self, and restore original bytes before execute_shellcode/beacon handoff so the integrity check never sees a dirty ntdll .text.
Problem: HWBP DRs set only on TP worker thread. CLR spawns its own threads (JIT, GC, finalizer) with no DR protection.
Solution: hook NtCreateThreadEx — after creation, SetThreadContext to copy DR0-DR3 to each new thread before resume.
set_hwbp_on_thread. Zero byte modification.MOV RAX; JMP RAX on NtCreateThreadEx stub — detectable by S1 integrity checks.| Constraint | Detail |
|------------|--------|
| Callee-saved registers | All indirect_syscall stubs must push/pop r12/r13/r14. Missing → silent CLR state corruption |
| AMSI lazy re-resolve | Must NOT be inside hook body (CS SharpBlock signature, 60%→90%) — resolve on TP worker instead |
| S1 integrity | ntdll .text compared vs KnownDlls at ~100-500ms intervals; any byte mod detected. HWBP is the only safe path |
Applicability: unbacked memory only. For backed EXE: counterproductive (see §1c).
Payload Nt* calls expose unbacked return addresses in kernel stack walks. HWBP DR2/DR3 intercept them and re-issue via tp_spoofed_syscall with .pdata-coherent stack.
DR6.B2/B3 → acquire E10_LOCK → extract args → tp_spoofed_syscall → set ctx.rax → simulate ret → cycle DR2indirect_syscall (no block; spoof is lost)E10_LOCK → SPOOF_BUF_LOCK (never reverse)Known limitation: kernel-mode observer sees payload return address in EXCEPTION_CONTEXT at [RSP] — captured before VEH runs. No usermode fix.
Status: disabled (V2). S1 ntdll integrity checks detect modified bytes deterministically. E10 HWBP is net better (zero byte modification). Key: CONTEXT.Rip = int3 addr itself (x64 differs from Linux ptrace: no +1).
| Constraint | Detail |
|------------|--------|
| Fritter -g 0 self-modifies | Patches import stubs in-place at runtime. RX-only → ACCESS_VIOLATION → stack overflow. RWX required |
| Fritter -g 1 conflicts with HWBP VEH | Both use VEH; -g 1 guard pages interfere |
| Donut -g 1 same conflict | Same VEH collision |
| CLR hosting via COM fails with Costura.Fody | TypeInitializationException in <Module>.cctor |
| .NET -r v4.0.30319 required for Fritter | Without explicit runtime version, CLR 2.0 may load |
Avoids CreateThread(RWX_addr) — EDR flags thread start addresses in RWX/private memory. Callback-based patterns fire shellcode from within a legitimate DLL:
| API | Fires from | Notes |
|-----|------------|-------|
| timeSetEvent(period, 0, shellcode, 0, TIME_CALLBACK_FUNCTION) | winmm.dll | Async; 100ms delay typical |
| CreateThreadpoolWait(callback, shellcode, NULL) + SetThreadpoolWait | ntdll.dll TP | Fires when wait object signaled |
| QueueUserAPC(shellcode, thread, 0) + alertable wait | target thread | Thread must enter alertable state |
| EnumWindows(shellcode, 0) | user32.dll | Synchronous, blocks caller |
Constraint: callback thread is TP worker context — use indirect_syscall only, not spoofed_syscall or masked_syscall. See references/constraints.md → Indirect / Callback-based Execution.
1. Clear all DRs on TP worker — SetThreadContext(DR7=0)
2. Remove VEH handler — RtlRemoveVectoredExceptionHandler
3. Zero entire RWX buffer — write_bytes(base, 0, size)
4. NtFreeVirtualMemory(base)
Treat staging buffers as disposable evidence:
This reduces post-exec memory forensics recovery of original encrypted/plaintext stages.
1. Clear all DRs
2. Remove VEH handler
3. Clear W^X globals (zero VEH_WX_*)
4. Zero entire RW view — no NtProtect needed
5. NtUnmapViewOfSection(rw_view)
6. NtUnmapViewOfSection(rx_view)
7. Close section handle
Order rationale: DRs before VEH removal (HWBP fire without handler → unhandled exception). Stomp before free/unmap (zero memory while accessible). If payload calls ExitProcess (common with .NET), cleanup never executes — kernel releases everything on termination.
GS:[0x60] → PEB → Ldr → InLoadOrderModuleList. Hash each module's BaseDllName (UTF-16, case-insensitive DJB2). Match → DllBase. Then parse PE Export Directory → hash each export name (ASCII DJB2) → match → address. Reject forwarded exports (address inside export dir range).
read_unaligned mandatory: PE structures make no alignment guarantees. Raw *(ptr as *const u32) panics on misaligned addresses.Compile-time XOR macro (obfcstr!()) over byte arrays — decode on stack at runtime. Never leave API names, DLL paths, or registry keys as string literals. Runtime string building for paths: construct ntdll.dll char-by-char or from segments.
Pipeline: raw → signature obfuscation → zlib deflate(9) → splitmix64 XOR keystream → custom Base64 → ENC_SC
SC_SEED_FRAG[8] / SC_SEED_MASK[8].A-Za-z0-9_.. Entropy 8.0 → ~6.0 bits/byte. Resembles PE symbol/identifier tables. No YARA signatures.#[link_section = ".data1"] — isolates payload from .rdata. NEVER .rsrc (malforms DataDirectory[2]).AES is fingerprinted algorithmically even without static S-box: GF(2^8) 0x1b polynomial, ShiftRows byte permutation, MixColumns structure, 14-round key expansion. Replacing zlib+XOR with AES raised VT 4→8/71.
Decrypt streaming into RW view → XOR re-encrypt per page → NtProtect(PAGE_NOACCESS) on RX → VEH decrypts on access. Eliminates cleartext heap buffer. Constraint: sliding window eviction incompatible with Fritter W^X writes — disable eviction. Tool_mode: bypassed entirely (S3, direct copy — no VEH faults).
map → execute → zero first 0x1000 = CS shellcode loader signature (60%→90%)panic_abort: reintroduces Rust unwinding strings (attempt to divide by zero, library/core/src/...). Always use build-std = ["std", "panic_abort"] + panic = "immediate-abort".ML classifiers use section size ratios. .text must be larger than any single data section (code-dominant). Data-dominant = packer signature.
| opt-level | .text | .data1 (payload) | ratio | Risk | |-----------|-------|---------|-------|------| | 0 | 2,222K | 1,151K | 1.93:1 | OK | | 1 | 876K | 1,151K | 0.76:1 | HIGH | | 2+ | ~400K | 1,151K | 0.35:1 | CRITICAL |
Rule: never change opt-level without verifying resulting section ratios.
| Section | DataDirectory auto-link | Safe for raw data? |
|---------|------------------------|-------------------|
| .rsrc | DD[2] Resource — expects IMAGE_RESOURCE_DIRECTORY | NO — malformation |
| .data1 | None | YES — standard linker overflow name |
| .edata / .idata / .reloc / .debug | Various | No — auto-linked |
Full detail: references/process-injection.md.
Avoid classic CRT (OpenProcess → VirtualAllocEx → WriteProcessMemory → CreateRemoteThread) — fully hooked.
Early Bird APC: CreateProcess(CREATE_SUSPENDED) → alloc RW → write → protect RX → QueueUserAPC → ResumeThread. Fires before EDR DLL loads. Variant — Early Cryo Bird: freeze via NtSetInformationJobObject(JOBOBJECT_FREEZE_INFORMATION) to avoid CREATE_SUSPENDED indicator.
Thread hijacking: SuspendThread → GetThreadContext → set ctx.Rip → SetThreadContext → ResumeThread. Use indirect syscalls (NtGetContextThread/NtSetContextThread). Waiting Thread Hijacking variant: target thread already in NtDelayExecution — no SuspendThread needed.
PPID spoofing: STARTUPINFOEX + UpdateProcThreadAttribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS) → child appears spawned from trusted parent. EDR kernel callback detects PID mismatch — mitigate with token impersonation of spoofed parent.
Full detail: references/anti-analysis.md.
Ordering rule: run ALL anti-analysis checks before any evasion setup. Generating ETW events (NtOpenSection, NtProtect) in a sandbox increases detection even without EDR.
Design principle — near-zero IOC: passive checks are not a stylistic preference; they are a deliberate zero-IOC constraint. Every read-only check adds zero telemetry. Every invasive call (NtQueryInformationProcess, DR mutation, ThreadHideFromDebugger, CPUID ring-trip) generates at least one observable event. The target is a check layer that produces no indicators that distinguish the implant from a normal process.
Passive anti-debug baseline: PEB.BeingDebugged (PEB read, no syscall), parent-process sanity (PPID from PEB), sleep/timing drift (RDTSC), debugger process/window heuristics. No NtQueryInformationProcess, no NtSetInformationThread, no DR read/write in default mode.
Passive anti-sandbox baseline: sleep acceleration (RDTSC — most reliable, zero syscall), low-core/low-RAM heuristic, hostname/username patterns, common analysis process names. CPUID, registry probes, and MAC OUI checks are optional secondary tier — avoid by default.
Decision rule: score multiple weak passive signals; never hard-fail on a single artefact; prefer silent degraded execution over self-termination.
IAT hygiene: PEB walk (§8) removes suspicious IAT entries; fake dead-code imports add benign noise; delay-load via LoadLibrary/GetProcAddress. Combined with §9 string obfuscation → zero readable API strings.
Entropy target: < 7.2 bits/byte per section. Custom Base64 (§10) already achieves 8.0→6.0. Embedding dictionary data and stripping debug info lower further. RC4 lacks AES's GF polynomial fingerprint but splitmix64 XOR remains preferred (zero algorithmic footprint).
BYOVD loads a signed-but-vulnerable kernel driver → Ring 0 → remove EDR callbacks or terminate EDR processes. As of March 2026: 54 active tools targeting 35+ drivers (RTCore64.sys, rwdrv.sys, NSecKrnl.sys); used by RansomHub, BlackByte, Scattered Spider, Qilin/Warlock.
Orthogonality: §1–§11 operate entirely in usermode. BYOVD removes the EDR; §1–§11 evade it. Combining both in one implant is operationally noisy and unnecessary.
Detection surface: NtLoadDriver + driver from TEMP/user-writable path + immediate security-process termination.
Blue team mitigations: HVCI, WDAC driver allowlist, Sysmon EID 6, Microsoft vulnerable driver blocklist.
Do not bundle BYOVD with a usermode-evasion implant — the kernel signal is louder than any benefit from usermode stealth.
Seven disproven assumptions and a full indicator audit table: see references/field-notes.md.
Key lesson: evasion maximalism is self-defeating. A 1,472-line beacon (zero VEH/HWBP/unhooking) evades S1/CS/Defender fully. A 6,126-line version (all features) is detected. Every added feature introduced 1–3 real indicators.
Confirmed reductions: B1 (private RWX), F7 (disable ETW DR0), F9 ph3-5 (eliminate syscall/VEH/HWBP stubs).
Non-reductions: F6 (selective ETW), F2 (4→11 HWBP targets), F9 ph1-2 (call site gating only).
Remaining irreducible: "Malicious shellcode execution" (private RWX + PEB walk + dynamic API).
| File | When to load |
|------|-------------|
| references/constraints.md | Hard constraints with failure modes and crash causes. Load when debugging a crash or unexpected detection. |
| references/field-notes.md | S1/CS/Defender field-tested failures and indicator audit. Load when deciding to add or remove an evasion feature. |
| references/etw-patching.md | Detailed ETW byte-patch pattern: why NtWriteVirtualMemory on self differs from VirtualProtect, required restore window, and patch payload details. |
| references/process-injection.md | Full Early Bird APC / Thread Hijacking / PPID Spoofing details and decision matrix. |
| references/anti-analysis.md | Full anti-debug, anti-VM, IAT hygiene, and entropy management tables. |
data-ai
Scoped routing: Linux operator; hosts, sessions, users, services, packages, logs, containers, SSH, network paths, privilege evidence.
development
Offensive methodology for ICS/OT/SCADA environments in authorized industrial penetration testing and red team operations. Use when assessing PLCs, RTUs, HMIs, engineering workstations, historians, or field devices running Modbus, DNP3, EtherNet/IP, S7comm/S7+, Profinet, IEC 60870-5-104, BACnet, or OPC-UA. Covers passive OT network enumeration, protocol-level device interrogation, PLC coil/register read-write attacks, HMI session exploitation, historian and engineering workstation compromise, and safe escalation rules for critical infrastructure scope. Does not cover: general IT network exploitation (network-technique), physical hardware interfaces UART/JTAG/SPI (hardware-technique), wireless sensor network attacks (wireless-technique), RF/SDR signal analysis (hardware-ctf or wireless-technique), or CTF-framed ICS lab tasks (ics-ctf).
tools
Offensive methodology for authorized game security assessments, game client security research, and game-adjacent penetration testing in real-world engagements. Use when assessing game clients for cheating vulnerabilities, testing anti-cheat effectiveness, auditing game server protocols for score manipulation or economic fraud, reverse engineering game DRM or license validation, analyzing game save file protection, or assessing game mod/plugin security. Covers: process memory scanning and manipulation (Cheat Engine methodology), game binary reversing for license and DRM bypass, game network protocol analysis and packet replay, anti-cheat mechanism analysis, save file format reversing and tampering, speed hack and value injection techniques. Does NOT cover: CTF game challenges (game-ctf), game engine source code auditing (web-exploit-technique or vuln-search-technique for the backend), or general binary exploitation (pwn-ctf or reversing-technique).
development
Auth assessment: hardware/embedded methodology; UART/JTAG/SWD/SPI/I2C, firmware extraction, boot/debug paths, embedded OS evidence.