{"id":34704092,"url":"https://github.com/c5hackr/xathook","last_synced_at":"2025-12-24T23:00:43.663Z","repository":{"id":328712803,"uuid":"1116445205","full_name":"C5Hackr/XATHook","owner":"C5Hackr","description":"Lightweight Patchless Hooking Library for Windows","archived":false,"fork":false,"pushed_at":"2025-12-14T21:53:23.000Z","size":17,"stargazers_count":9,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-12-17T10:23:27.519Z","etag":null,"topics":["blueteaming","c","malware-research","redteaming"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/C5Hackr.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-14T21:40:11.000Z","updated_at":"2025-12-16T22:41:32.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/C5Hackr/XATHook","commit_stats":null,"previous_names":["c5hackr/extended-address-table-hooking"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/C5Hackr/XATHook","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/C5Hackr%2FXATHook","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/C5Hackr%2FXATHook/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/C5Hackr%2FXATHook/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/C5Hackr%2FXATHook/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/C5Hackr","download_url":"https://codeload.github.com/C5Hackr/XATHook/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/C5Hackr%2FXATHook/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28012084,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-24T02:00:07.193Z","response_time":83,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["blueteaming","c","malware-research","redteaming"],"created_at":"2025-12-24T23:00:33.839Z","updated_at":"2025-12-24T23:00:43.645Z","avatar_url":"https://github.com/C5Hackr.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# eXtended-Address-Table-Hooking\n\n## Introduction\n\n**XAT** is a lightweight, architecture-aware hooking library focused on **Address Table manipulation**, providing reliable interception of API calls via:\n\n- **IAT (Import Address Table)** patching\n- **Delay-IAT** patching (delay-load imports)\n- **EAT (Export Address Table)** patching (when safe)\n\nUnlike inline hooks or trampoline-based detours, XAT operates at the **linker/loader boundary**. It redirects calls by changing resolver outputs and table entries rather than patching function prologues, making it well-suited for early-stage instrumentation, loader-time hooks, and environments where code integrity, CFG, or instruction integrity checks make inline patching undesirable.\n\n---\n\n## Features\n\n- **Address table hooking coverage**\n  - Normal **IAT** patching (already-resolved imports)\n  - **Delay-IAT** patching (delay-load imports)\n  - **EAT** patching (export RVA redirection via trampoline)\n- **Cross-architecture support**\n  - x86 (32-bit)\n  - x64 (AMD64)\n  - ARM64 (AArch64, Windows)\n- **No inline patching**\n  - No overwritten prologues\n  - No instruction decoding\n  - No detour trampolines (in the inline-hook sense)\n- **Loader-aware**\n  - Works with delay-loaded imports\n  - Compatible with manually mapped PE images (assuming imports/exports are present and mapped normally)\n- **Low footprint**\n  - Minimal executable stubs\n  - No VEH or debug-register-based interception\n  - Deterministic unhooking via recorded patch list + sweeps\n\n---\n\n## Design Philosophy\n\nXAT is built on the premise that **address tables are one of the cleanest interception points when it comes to code integrity checks** in the Windows execution model. By modifying resolver outputs rather than execution flow, XAT avoids many of the detectability issues associated with traditional inline patching techniques.\n\nThe “eXtended” in **XAT** reflects:\n- Support for **multiple table types** (IAT + Delay-IAT + EAT)\n- **Multi-architecture jump stubs**\n- A design intended to scale to additional resolver-level interception patterns\n\n---\n\n## Architecture Notes\n\nXAT emits an absolute jump stub appropriate to the active architecture:\n\n- **x86**  \n  `mov eax, imm32 ; jmp eax`\n\n- **x64**  \n  `mov rax, imm64 ; jmp rax`\n\n- **ARM64**  \n  `ldr x16, #literal ; br x16`\n\nStubs are emitted into allocated executable memory and (on ARM64 especially) require instruction-cache coherence; XAT calls `FlushInstructionCache` after writing stubs.\n\n---\n\n## Typical Use Cases\n\n- API interception without inline hooks\n- Loader-time instrumentation\n- Game, DRM, and anti-cheat research\n- Malware analysis and sandboxing\n- Red-team tooling and evasive monitoring\n\n---\n\n# How it works\n\nThis section explains the **XAT** implementation: how it performs **IAT**, **Delay-IAT**, and **EAT** hooking, how restoration works, and the important caveats, especially around **forwarded exports**.\n\nAt a high level, XAT:\n1. Initializes a hook record and resolves baseline export metadata.\n2. Scans all loaded modules and patches **IAT** entries targeting the chosen module+symbol.\n3. Scans all loaded modules and patches **Delay-IAT** entries as well.\n4. Patches the target module’s **EAT** entry by replacing the export RVA with an RVA to a nearby trampoline stub.\n5. Disables cleanly by restoring recorded **IAT** writes, restoring **EAT**, then sweeping for any remaining pointers.\n\n---\n\n## Core concepts (PE refresher)\n\n### Import Address Table (IAT)\nEach importing module has an IAT containing **resolved function addresses**. Overwriting an IAT slot redirects calls for *that module*.\n\n- ✅ Scope: per importing module\n- ✅ Fast: one pointer write\n- ✅ Stable: no code patching\n- ❌ Doesn’t affect code that uses cached pointers or its own resolver logic\n\n### Delay Import Address Table (Delay-IAT)\nDelay-load imports are resolved lazily and stored in a delay IAT. Many hookers miss this path.\n\n- ✅ Captures delay-load call sites after they’ve been resolved (and some cases even when name tables are missing)\n- ✅ Complements standard IAT patching\n\n### Export Address Table (EAT)\nThe exporting module’s EAT contains **RVAs** for exported functions. Patching the RVA affects *future* resolutions.\n\n- ✅ Scope: future `GetProcAddress`, future binds, delay-load fixups\n- ✅ No inline patching\n- ❌ Doesn’t automatically update already-resolved IATs unless you patch those too (**XAT does**)\n\n---\n\n## Data structures\n\n### `ParsedPEImage`\n```c\ntypedef struct {\n    PVOID ImageBase;\n    PIMAGE_DOS_HEADER Dos;\n    PIMAGE_NT_HEADERS NtHeaders;\n    IMAGE_OPTIONAL_HEADER OptionalHeader;\n    IMAGE_FILE_HEADER FileHeader;\n    PIMAGE_IMPORT_DESCRIPTOR ImportDescriptor;\n    PIMAGE_EXPORT_DIRECTORY ExportDirectory;\n} ParsedPEImage;\n```\n\nA convenience wrapper holding:\n- module base address and validated headers\n- pointers to import/export directories (if present)\n\n### `IatPatch`\n```c\ntypedef struct {\n    ULONG_PTR* IatSlot;\n    ULONG_PTR Original;\n} IatPatch;\n```\n\nA recorded IAT write:\n- `IatSlot` = the exact slot you overwrote\n- `Original` = the old pointer value\n---\n### `XATHook`\n```c\ntypedef struct {\n    IatPatch* IatPatches;\n    DWORD IatPatchCount;\n    DWORD IatPatchCap;\n\n    LPCSTR ModuleName;\n    LPCSTR ProcedureName;\n    PVOID HookFunction;\n\n    DWORD OriginalRva;\n    ParsedPEImage peImage;\n    ParsedPEImage modImage;\n    HookState state;\n\n    PVOID Trampoline;\n} XATHook;\n```\n\nTracks:\n- dynamic list of IAT/Delay-IAT patches\n- target module + procedure\n- hook function pointer\n- original export RVA (for EAT restore)\n- trampoline pointer used for EAT redirection (if enabled)\n- state flags for IAT/EAT status\n\n---\n\n## Memory allocation near the module\n\n### `AllocateJmpNearModule`\nPurpose: allocate executable memory “near” the exporting module so the trampoline RVA is well-behaved and suitable for EAT redirection.\n\nKey properties of the final implementation:\n- Uses system allocation granularity (validated as power-of-two, fallback to 0x10000).\n- Computes a search window around the end of the module image.\n- Bounded scan (`maxSteps` capped) to avoid infinite loops.\n- Tries allocations both upward and downward.\n\nWhy “near” matters:\n- EAT stores **RVAs**, so your trampoline must be representable as:\n  `newRva = tramp - moduleBase`\n- “Near” allocation reduces address-space weirdness and improves reliability across processes/layouts.\n\n---\n\n## Parsing PE images safely\n\n### `ParsePeImage`\nFinal version is defensive:\n- validates DOS and NT signatures\n- only sets Import/Export pointers if the directory exists\n- returns a zeroed `ParsedPEImage` on failure\n\nThis prevents “assume directory exists” crashes while scanning arbitrary modules.\n\n---\n\n## Patch bookkeeping\n\n### `XATHook_ReservePatches`\nMaintains a growable heap array of `IatPatch`:\n- starts at 256\n- doubles until enough capacity\n- uses `HeapAlloc`/`HeapReAlloc`\n\n### `XATHook_FreePatches`\nFrees the list and resets counters.\n\nWhy this matters:\n- disable is deterministic: “recorded restore” can revert exact writes quickly.\n- sweeps remain as a safety net (see below).\n\n---\n\n## Initialization\n\n### `XATHook_Init`\nResponsibilities:\n1. Ensures the target module is loaded.\n2. Initializes the hook record and parses:\n   - current process image (`ParsePeImage(NULL)`)\n   - target module image (`ParsePeImage(ModuleName)`)\n3. Optionally returns the **loader-resolved** export address via:\n   - `GetProcAddress(hMod, ProcedureName)`\n4. Locates and stores the named export RVA in `hook-\u003eOriginalRva` (if found in the name table).\n\nImportant behavioral detail:\n- `GetProcAddress` may return an address that reflects loader behavior (including forwarded exports).\n- `hook-\u003eOriginalRva` only describes what is present in the exporting module’s EAT entry for that name.\n\n---\n\n## Enabling hooks\n\n### Overview: `XATHook_Enable`\nXAT applies hooks in this order:\n1. Patch **IAT** across all loaded modules\n2. Patch **Delay-IAT** across all loaded modules\n3. Patch **EAT** in the exporting module (only if safe and non-forwarded)\n\nReturns `TRUE` if any of these succeed.\n\nIt refuses to run if already enabled (prevents double-patching and duplicate patch records).\n\n---\n\n## How IAT hooking works (normal imports)\n\n### Step 1: Enumerate modules\nXAT enumerates all process modules with `EnumProcessModules`, then for each:\n- validates DOS/NT headers\n- locates Import Directory\n- iterates import descriptors\n\n### Step 2: Filter to import descriptors targeting the chosen module\n```c\nif (GetModuleHandleA(dllName) != (HMODULE)hook-\u003emodImage.ImageBase) {\n    imp++;\n    continue;\n}\n```\n\n### Step 3: Identify the correct import slot\nXAT uses two strategies:\n\n**A) Name-based (preferred)**\nIf INT/OriginalFirstThunk is present and not ordinal:\n- compare `IMAGE_IMPORT_BY_NAME-\u003eName` with `ProcedureName`\n\n**B) Pointer-based fallback**\nIf name info isn’t available:\n- compare the current slot value to either:\n  - `realProc = GetProcAddress(targetModule, ProcedureName)`\n  - `exportAddr = moduleBase + OriginalRva`\n\n### Step 4: Record and patch\nWhen matched:\n- reserve patch capacity\n- `VirtualProtect` the slot\n- record `{ slot, original }`\n- write hook pointer\n- restore protection\n- set `isIatHooked = TRUE`\n\n---\n\n## Delay-IAT hooking\n\n### `XATHook_EnableDelayIAT`\nDelay-load imports are located via `IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT`.\n\nFor each delay import descriptor:\n- walk delay IAT and optionally the delay INT\n- match by name when possible; else fall back to pointer matching (`realProc` / `exportAddr`)\n- record and patch using the same `IatPatch` list\n\nThis closes a major coverage gap: many real targets are imported via delay load.\n\n---\n\n## EAT hooking (export redirection)\n\n### Why a trampoline is required\nEAT entries are **32-bit RVAs**, not pointers. Even on x64, the EAT stores RVAs.\n\nTherefore XAT:\n1. Builds a small, CPU-specific absolute-jump stub.\n2. Allocates executable memory and writes the stub there.\n3. Stores the trampoline’s RVA into `AddressOfFunctions[ordinal]`.\n\nAfter writing the trampoline bytes, XAT calls:\n```c\nFlushInstructionCache(hProc, tramp, sizeof(jmpBytes));\n```\nThis is crucial on ARM64.\n\n### Forwarded export detection (critical)\nBefore patching EAT, XAT checks whether the function RVA points *inside the export directory range*:\n```c\nif (exportDirRva \u0026\u0026 exportDirSize \u0026\u0026\n    rva \u003e= exportDirRva \u0026\u0026 rva \u003c exportDirRva + exportDirSize) {\n    break;\n}\n```\nIf true, it’s treated as a **forwarded export** and EAT patching is skipped.\n\nThis is intentional.\n\n---\n\n## Disabling hooks / restoration\n\n### Overview: `XATHook_Disable`\nDisable is multi-phase:\n\n1. **Restore recorded IAT patches**\n   - `XATHook_RestoreRecordedIAT` reverts every recorded slot and frees the patch list.\n2. **Restore EAT**\n   - `XATHook_RestoreEAT` restores the original RVA if EAT was hooked.\n3. **Sweep restore (normal + delay)**\n   - `XATHook_SweepRestoreAllIAT` and `XATHook_SweepRestoreAllDelayIAT` scan the process and restore any remaining slots still pointing at your hook or trampoline back to `GetProcAddress` truth.\n4. **Free trampoline memory**\n   - `VirtualFree(hook-\u003eTrampoline, 0, MEM_RELEASE)`\n\nWhy sweep restore exists even with recorded patches:\n- other code could have patched the same slots after you\n- you might have missed some entries due to missing metadata, unusual layouts, or name-less matching\n- sweep acts as “return process to loader truth” cleanup\n\n---\n\n## Caveats / deep details\n\n### 1) Forwarded exports: why XAT skips them and why “safe hooking” them isn’t really solvable\n\nA forwarded export isn’t code. The EAT entry points to a **forwarder string** inside the export directory, e.g.:\n- `\"KERNEL32.Sleep\"`\n- `\"NTDLL.RtlSomething\"`\n\nThe loader resolves it by:\n1. reading the string\n2. locating/loading the forwarded-to module\n3. resolving the forwarded-to export\n4. returning the final function address\n\n#### Why you cannot “just hook the forwarded export RVA”\nIf you overwrite that forwarder RVA with a trampoline RVA:\n- you destroy the forwarder semantics and the forwarder string reference\n- anything relying on the export being forwarded (including introspection tooling) can break\n- you convert a forwarded export into a non-forwarded export, which changes PE semantics and can be detectable\n\n#### Why you can’t “safely hook the forwarder target” in a general way\nEven if you parse the forwarder string and try to hook the destination export instead, there are unavoidable problems:\n\n- **No single authoritative target**\n  - API-set mapping, redirections, WOW64 behavior, and OS versioning can change what the forwarder resolves to at runtime.\n- **Timing and loader dependency**\n  - The destination module may not be loaded yet; you’ve turned this into a loader orchestration / race problem.\n- **Semantic mismatch**\n  - Forwarders exist specifically to preserve ABI while moving implementations. Hooking the destination may affect a broader set of callers than the forwarder contract implies.\n- **No “safe EAT patch point”**\n  - You can’t patch “the forwarder” as code because it isn’t code. Converting it to code is inherently a semantic mutation.\n\n**Practical conclusion:**\nForwarded exports are best handled by:\n- hooking the **resolved addresses** via **IAT/Delay-IAT** (what XAT already does), and/or\n- hooking the destination module’s **real** export if and when it’s non-forwarded (as a separate hook instance).\n\nXAT’s policy: skip EAT patching when the export is forwarded, is the correct default.\n\n---\n\n### 2) Pointer-based IAT matching can create false positives\nWhen name tables aren’t available, XAT matches by:\n- `cur == realProc` or `cur == exportAddr`\n\nThis can match more than intended if:\n- multiple imports resolve to the same address\n- another hooker already changed the slot to a value equal to your compare target\n- the INT is missing and the import is by ordinal or otherwise ambiguous\n\nMitigations (optional future ideas):\n- prefer name-based matching whenever possible\n- validate import descriptor identity (module name normalization can help)\n- reduce reliance on pointer matching unless necessary\n\n---\n\n### 3) EAT hooking does not update existing caches\nEAT patching affects future resolutions, but:\n- existing IAT slots won’t change unless you patch them (**XAT does**)\n- copied function pointers stored by the program won’t be updated\n- custom resolvers may bypass both EAT and IAT\n\n---\n\n### 4) Delay-load complexity\nDelay-load machinery varies:\n- name table may be missing\n- resolution happens lazily\n- runtime helpers can rewrite slots\n\nXAT covers both:\n- patching delay IAT entries when found\n- sweeping restore during disable\n\n---\n\n### 5) Instruction cache coherence (especially ARM64)\nAfter writing trampoline bytes into executable memory, instruction cache must be coherent.\n\nXAT calls:\n```c\nFlushInstructionCache(hProc, tramp, sizeof(jmpBytes));\n```\nThis is essential on ARM64 and safe elsewhere.\n\n---\n\n## Minimal usage example\n\n```c\nXATHook hk;\n\nif (!XATHook_Init(\u0026hk, \"user32.dll\", \"MessageBoxA\", MyMessageBoxA, (PVOID*)\u0026OriginalMessageBoxA)) {\n    return;\n}\n\nif (XATHook_Enable(\u0026hk)) {\n    // Hooked via IAT, Delay-IAT, and/or EAT depending on feasibility.\n}\n\n// later...\nif (XATHook_Disable(\u0026hk)) {\n    // ...\n}\n```\n\n---\n\n## Glossary\n\n- **IAT**: Import Address Table - resolved import pointer slots per module.\n- **Delay-IAT**: delay-load import pointer slots (resolved lazily).\n- **EAT**: Export Address Table - exporter’s list of RVAs for exported symbols.\n- **RVA**: Relative Virtual Address - offset from module base.\n- **Forwarded export**: EAT entry that points to a forwarder string inside the export directory.\n- **Trampoline**: small executable stub used to reach an absolute hook address while the table stores only RVAs.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fc5hackr%2Fxathook","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fc5hackr%2Fxathook","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fc5hackr%2Fxathook/lists"}