update readme
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user