Inside EDRs: How Security Products Hook Windows APIs (And How Attackers Bypass Them)
Hook
When you call CreateFile() in Windows, you’re not actually calling CreateFile(). Modern endpoint security products silently redirect that call through their own monitoring code before letting it reach the kernel—and this repository catalogs exactly how eighteen different EDR vendors accomplish that interception.
Context
Windows security has always been a cat-and-mouse game between attackers and defenders. Endpoint Detection and Response (EDR) products emerged in the mid-2010s as traditional antivirus proved inadequate against sophisticated threats. Unlike signature-based AV, EDRs monitor system behavior in real-time, watching for suspicious patterns like credential dumping, process injection, or unusual network connections.
To achieve this visibility, EDRs need to intercept potentially dangerous API calls before they reach the Windows kernel. The most common technique is “userland hooking”—modifying the in-memory version of system DLLs like ntdll.dll to redirect API calls through the EDR’s monitoring engine. When your program calls NtReadVirtualMemory to read another process’s memory, the EDR’s hook intercepts it, logs the attempt, evaluates the context, and either allows or blocks the operation. The Mr-Un1k0d3r/EDRs repository exists at this battleground, providing both intelligence about which APIs different vendors monitor and practical tools for detecting or bypassing these hooks. Created by security researcher Marc-André Leclerc (Mr-Un1k0d3r) with community contributions, it represents years of reverse-engineering work against commercial security products.
Technical Insight
The repository’s core insight revolves around a fundamental Windows architecture detail: ntdll.dll APIs are simply small stub functions that set up syscall arguments and invoke the kernel. When an EDR hooks these functions, it typically overwrites the first few bytes with a jump instruction to its monitoring code. The repository’s tools exploit this by comparing the on-disk version of ntdll.dll (which is unmodified) with the in-memory version (which may be hooked).
The dynamic syscall resolution technique is particularly elegant. Instead of hardcoding syscall numbers (which change between Windows versions), the code maps a clean copy of ntdll.dll from disk and parses it to find syscall IDs at runtime:
// Simplified example from the repository's approach
HANDLE hFile = CreateFileA("C:\\Windows\\System32\\ntdll.dll",
GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL);
HANDLE hMapping = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
LPVOID pCleanNtdll = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
// Parse the clean copy to find the syscall ID
DWORD syscallNumber = GetSyscallNumber(pCleanNtdll, "NtReadVirtualMemory");
// Now invoke directly via syscall, bypassing any hooks
SyscallInvoke(syscallNumber, processHandle, baseAddress, buffer, size, bytesRead);
This technique sidesteps userland hooks entirely because it transitions directly to kernel mode using the syscall instruction, never touching the potentially-hooked API stubs in memory. The GetSyscallNumber function walks the Export Address Table of the clean ntdll.dll, locates the target function, and extracts the syscall ID from the instruction bytes.
The hook detection utilities work by scanning memory for common hook patterns. EDRs typically use one of several hooking methods: inline hooks (JMP instructions at function start), IAT hooks (modifying import tables), or export table hooks. The repository’s detection code searches for these signatures:
// Detect inline hook by comparing first bytes
for (int i = 0; i < numFunctions; i++) {
BYTE* memoryFunc = GetFunctionAddress(inMemoryNtdll, functionNames[i]);
BYTE* diskFunc = GetFunctionAddress(cleanNtdll, functionNames[i]);
if (memcmp(memoryFunc, diskFunc, 5) != 0) {
// First 5 bytes differ - likely a JMP hook
printf("[!] Hook detected on %s\n", functionNames[i]);
printf(" Memory: %02X %02X %02X %02X %02X\n",
memoryFunc[0], memoryFunc[1], memoryFunc[2],
memoryFunc[3], memoryFunc[4]);
}
}
The repository also includes extensive documentation of which specific APIs each EDR vendor hooks. For example, the data shows that older CrowdStrike Falcon versions hooked dozens of ntdll.dll functions including NtCreateThread, NtAllocateVirtualMemory, NtProtectVirtualMemory, and NtWriteVirtualMemory—all common in process injection techniques. Meanwhile, products like Elastic EDR focus on a smaller set of high-value APIs. This intelligence is invaluable for red teams developing evasion tactics, but equally useful for blue teams understanding their product’s visibility gaps.
What makes this particularly interesting from an architecture perspective is the evolution documented in the repository. Newer entries note that modern EDR versions from vendors like CrowdStrike have largely abandoned userland hooks in favor of kernel callbacks—specifically PsSetCreateProcessNotifyRoutineEx, ObRegisterCallbacks, and minifilter drivers. These kernel-mode techniques are fundamentally harder to bypass because they don’t rely on modifying user-mode memory. The repository inadvertently documents the security industry’s shift from userland to kernel-mode monitoring, showing how offensive research drives defensive innovation.
Gotcha
The elephant in the room: these techniques are increasingly obsolete against modern EDR deployments. As the repository’s own documentation acknowledges, vendors like CrowdStrike, Palo Alto Cortex XDR, and Microsoft Defender for Endpoint have moved most monitoring logic to kernel mode. When you unhook ntdll.dll or invoke syscalls directly, you’re bypassing detection mechanisms that many enterprise EDRs no longer rely on as their primary detection layer. You’ll burn development time implementing syscall evasion only to discover the EDR flagged your process through kernel callbacks, ETW telemetry, or behavioral analytics instead.
The hook intelligence also suffers from a shelf-life problem. EDR products update continuously, changing which APIs they monitor and how they implement hooks. The repository’s data represents specific product versions tested at specific times—often months or years old. Using outdated hook information in production red team engagements can lead to false confidence, where you think you’re evading detection but are actually triggering alerts through updated monitoring mechanisms. Additionally, the repository provides no tooling for more sophisticated EDR evasion needs like AMSI bypasses, ETW patching, PPL bypass, or evading user-mode API monitoring implemented through Inline Assembly or hardware breakpoints.
Verdict
Use if: You’re building Windows offensive tooling and need to understand the landscape of EDR userland hooks, conducting security research that requires cataloging vendor detection capabilities, developing direct syscall implementations and want reference data on hook patterns, or training red/blue teams on fundamental EDR evasion and detection mechanisms. The repository excels as both a learning resource and intelligence database. Skip if: You’re facing modern EDR deployments that primarily use kernel callbacks (check the repo’s notes—if your target EDR is listed as using kernel-mode detection, these techniques won’t help), you need a complete evasion framework rather than low-level primitives, you’re working outside Windows environments, or you need current production-ready bypass techniques rather than educational examples. This repository is best viewed as a foundation for understanding EDR architecture and a historical record of the userland hooking era, not as a silver bullet for contemporary evasion.