# Evasion Chain ## Architecture Evasion lives in two layers: - **Go binary** — reflective PE loader, sleep obfuscation, cgocall bridge - **Zig DLL** — syscall dispatch, API resolution, AMSI/ETW/EDR evasion, module stomping ## Loading Phase ### Reflective DLL Loading How the Go loader reads PE headers, allocates memory via VirtualAlloc, copies sections, applies relocations, resolves imports, sets section protections, resolves exports, calls DllMain and init_evasion. No file on disk, no LoadLibrary callback. ## Initialization Phase ### init_evasion → syscall.init_syscall - Walks PEB to find ntdll base - Scans ntdll for `0F 05 C3` (syscall;ret) gadgets, stores addresses in pool - Seeds PRNG - Caches exception directory for HAL's Gate fallback - Builds FreshyCalls table: walks ntdll export directory, collects Nt* exports, sorts by RVA, SSN = position. Immune to inline hooks. Reference: https://0xdbgman.github.io/posts/edr-internals-research-and-bypass/ - Scans ntdll for `41 FF E2` (jmp r10) gadget — used as CFG-safe IC neutralizer ### api.ensure() - Resolves ~100 Windows API function pointers by walking PEB → module export tables - kernel32, ntdll, advapi32, winhttp, bcrypt - Used by evasion functions for non-syscall operations (VirtualProtect, LoadLibrary, etc.) ## Evasion Phase ### stomp_evasion - Copies DLL .text into a signed Microsoft DLL (CryptoAPI/dwrite/msvcp_win) - Changes target protection to RWX, copies, restores to RX - Zeros original PE headers (DOS header + NT headers) - Memory scanners see legitimate signed DLL code at execution addresses - Base passed from Go (reflective DLL not in PEB module list) ### patch_etw (three-tier) - Tier 1: NtTraceControl to stop EDR ETW providers (MDE, kernel-process, security-mitigations) - Tier 2: Hardware breakpoint on EtwEventWrite via VEH + debug registers - Tier 3: RET patch fallback — writes 0xC3 to EtwEventWrite's first byte - On Windows 10/11 where NtTraceControl is forwarded to api-ms-win DLLs, extract_ssn skips Method 1 (forward RVA guard) and falls through to HAL's Gate Method 2 ### patch_amsi - Loads amsi.dll, resolves AmsiScanBuffer address - Installs Vectored Exception Handler (VEH) as first-in-chain - Sets DR0 hardware breakpoint to AmsiScanBuffer address - Sets Dr7 to enable DR0 locally - SuspendThread → SetThreadContext → ResumeThread (proper thread context modification per MS docs) - On hit: VEH handler sets RAX=0 (AMSI_RESULT_CLEAN), sets RIP to return address, pops stack - No bytes modified in amsi.dll — invisible to EDR tamper detection ### remove_edr_callbacks - Escalates SeDebugPrivilege (best-effort, silently skips if fails) - Calls NtSetInformationProcess with InfoClass=40 (ProcessInstrumentationCallback) - CFG-aware: on CFG-enabled systems (Win10 1709+), kernel refuses null callback pointer. Uses `jmp r10` gadget address instead — IC fires but immediately returns, neutralizing it. Reference: https://cirosec.de/en/news/windows-instrumentation-callbacks-part-4/ - On non-CFG systems: sets Callback=NULL, removing the EDR's instrumentation callback - Runs LAST in startup chain (the call itself is an IoC — but nothing follows it) - Defanged on Windows 11 23H2+ (restricted to kernel-mode callers) ## Sleep Phase ### EvasionSleep (Go-side) - Gated by dllLoaded flag (prevents protection before DLL fully initialized) - Pre-saved .text bounds from section scan during loading - VirtualProtect(DLL .text, PAGE_NOACCESS) — DLL code unreadable during idle - time.Sleep(duration) — Go runtime, not DLL code - VirtualProtect(restore original protection) — DLL executable again - DLL .text at different address than Go binary .text — Go code executes fine during protected period ## Syscall Dispatch ### FreshyCalls: SSN Extraction - Walks ntdll's export directory, collects all Nt* exports with real code addresses - Sorts by RVA ascending — SSN = position in sorted order - Immune to inline hooking: EDRs can patch stub bytes but cannot change linker RVA order - HAL's Gate fallback: binary search exception directory for forwarded exports not in ntdll's direct export table - Reference: https://0xdbgman.github.io/posts/edr-internals-research-and-bypass/ ### Indirect Syscall Execution - Random gadget from g_syscall_addrs pool (0F 05 C3 in ntdll) - hells_gate assembly: encrypts SSN, stores gadget address globally - hell_descent assembly: decrypts SSN into eax, sets up r10 (first arg), jumps to gadget - Gadget's `syscall` runs with eax=SSN — kernel processes it - Gadget's `ret` returns to hell_descent caller, unwinding through normal call chain - Never calls the hooked export stub — EDR sees a syscall from a random ntdll address ### No Call Stack Spoofing - Go's cgocall requires clean stack returns - NtContinue-based ROP chains corrupt Go's goroutine scheduler - All dispatch is non-spoofed ## Key Fixes Applied - PE struct offsets corrected (SizeOfImage at 56, not 36) - imageImportDescriptor corrected to 20 bytes - @intCast→ntstatus helper (usize to NTSTATUS via @bitCast) - extract_ssn forwarded export guard (prevents @intCast panic) - CONTEXT_DEBUG_REGISTERS = 0x100010 (was 0x10, missing AMD64 flag) - SuspendThread/ResumeThread before SetThreadContext - stomp_evasion(base) parameter from Go - dllLoaded flag guards EvasionSleep