Author: puffert (independent security researcher) · License: MIT · Paper: paper_hotpatch_thread_hijack.md
Security research on a bounded class of process-injection primitives whose defining property is that the immediate NtCreateThreadEx caller is Microsoft-signed ntdll.dll code — so the thread's birth is attributed to operating-system infrastructure, not user code. We call this thread-origin camouflage.
The contribution sits on top of known substrates, which are acknowledged as prior art: the subsystem-abuse injection family (MITRE T1055), Windows hotpatching internals (Signal Labs 2022; MSRC 2013; hasherezade), the remote-helper-thread debug behavior of RtlQueryProcessDebugInformation (Checkpoint; Evilcodecave), and debug-register/VEH patchless interception as a mechanism (CrowdStrike patchless AMSI bypass; 0xflux). What this work adds: the thread-origin provenance framing, a static ntdll direct-caller scanner, and two controllability case studies. Detailed in paper_hotpatch_thread_hijack.md.
Positioning. Not a new family and not a new interception mechanism. The novel kernel is the combination: using DR/VEH to steer
LdrHotPatchNotifyinto its internalNtCreateThreadExpath without modifying ntdll bytes, plus formalizing ntdll-born thread creation as an enumerable surface. Orthogonal to memory-axis work (Process Parameter Poisoning); composable, not superior.
| HTH (primary case study) | RQPDI (second case study) | |
|---|---|---|
| ntdll function | LdrHotPatchNotify |
RtlQueryProcessDebugInformation |
| Substrate status | hotpatch internals = prior art; instantiation new | remote-thread debug behavior = prior art; controllability shown |
| Invoked via | LdrHotPatchNotify() + NtManageHotPatch(class 9) |
documented debug API, PID argument |
| Scope | in-process (self) | cross-process (remote by PID) |
| Start routine | arbitrary (own address space) | target-resident (ntdll shared) |
| Technique | patchless — 3 DR breakpoints + VEH, zero ntdll edits | 1 DR breakpoint at +0x558 |
| HVCI | unaffected (no code-page writes) | unaffected |
| Validated | callback executes, calc pops | control proven (injected start addr crashes victim) |
HTH is the headline (the instantiation absent from public sources). RQPDI reuses a known debug substrate to show the start-routine controllability recurs. Both issue NtCreateThreadEx from within ntdll; neither uses CreateThread, CreateRemoteThread, QueueUserAPC, or NtSetContextThread.
paper_hotpatch_thread_hijack.md— the paper (narrowed HTH claim, prior-art survey, patchless technique, second candidate, detection guidance)explanation.md— iteration history of the HTH discovery (10 test iterations → the working technique)NtManageHotPatch_analysis.md,NtManageHotPatch_deep_analysis.md— syscall surface analysisdisasm_*.txt— kernel-function disassembly references (MiFindProcessImageHotPatchRecord, MiOpenHotPatchFile, SeCompareSigningLevels, …)
poc/ntdll_thread_creators.c— enumerates every ntdll function that internally callsNtCreateThreadEx/NtCreateThread/RtlCreateUserThread. PE.textparse + export enum + E8/E9 target scan. A reusable analytical lens that surfaced the second candidate (RQPDI); not a claim of a new class.
poc/Hotpatch_Hijack_patchless.c— the patchless PoC (DR0/DR1/DR2 + VEH). Zero ntdll byte modifications. Pops calc.poc/Hotpatch_Hijack_v1_scan.c— version-resilient in-function pattern scanner (locates decision sites across ntdll builds 26200/22631)poc/Hotpatch_Hijack.c— the earlier byte-patch variant (superseded by patchless; kept for lineage)
poc/probe_RQPDI2.c— reachability probe (finds that a PID, not a handle, reaches+0x5CF; self = local path, remote = thread injection)poc/probe_RQPDI3.c— control proof (injecting[rbp+0x40]crashes the victim; default does not)poc/disasm_RtlQueryProcessDebugInformation.py— Capstone disassembly of theNtCreateThreadExargument setup
poc/hp_*.c,poc/ntmhp_*.c— privilege-model and class-fuzzing probes
The original three-stage loader (mutex delivery → section dual-map / module stomp → execution). Still present and working, but it is the demo substrate, not the contribution. The novel pieces are the thread-origin instances above.
HOWTO.md— full operational guide for the loader (build, run, custom shellcode)readme_comparison_v1-v4.md— loader version comparison (v1–v6)poc/parent_v2.c,poc/child_v2.c…poc/child_v6.c,poc/common.h— loader versions
Requires Visual Studio 2022 (MSVC x64) and Windows 11 25H2+ (build 26200). No admin rights needed for any of the headline tools. From a Developer Command Prompt, cd poc.
cl.exe /nologo /W4 /D_CRT_SECURE_NO_WARNINGS /Fe:ntdll_thread_creators.exe ntdll_thread_creators.c
ntdll_thread_creators.exeExpect three direct callers. The scanner reports the second as ntdll+0xA7DD0 (internal) (a boundary-heuristic limitation); disassembly confirms that call site is RtlCreateUserThread+0x1B5 — i.e. RtlQueryProcessDebugInformation, RtlCreateUserThread, LdrHotPatchNotify.
cl.exe /nologo /W4 /D_CRT_SECURE_NO_WARNINGS /Fe:Hotpatch_Hijack_patchless.exe Hotpatch_Hijack_patchless.c /link ntdll.lib advapi32.lib
Hotpatch_Hijack_patchless.execl.exe /nologo /W4 /D_CRT_SECURE_NO_WARNINGS /Fe:probe_RQPDI3.exe probe_RQPDI3.c /link ntdll.lib
probe_RQPDI3.exeSpawns a sacrificial notepad; the control case leaves it alive, the injected case terminates it (0xC0000409).
Each tool has a matching build_*.bat that sources vcvars64.bat first (e.g. build_patchless.bat, build_thread_creators.bat, build_probe_RQPDI3.bat).
- The thread-origin provenance framing. "The immediate
NtCreateThreadExcaller is signed ntdll code" treated as an enumerable detection input — the class lens. - A static ntdll direct-caller scanner. Enumerates the surface; reusable across builds.
- HTH (primary).
LdrHotPatchNotifycoerced into its internalNtCreateThreadExpath — the instantiation absent from public sources — done patchlessly (3 DR breakpoints + VEH, zero ntdll byte writes), so HVCI has nothing to gate and the highest-fidelity memory signal (NtProtectVirtualMemoryon ntdll) is absent. - RQPDI (second case study). Reuses the known remote-thread debug substrate to show start-routine controllability recurs.
- Honest negative results.
NtManageHotPatchclass 0 is proven CI-gated across 30+ configurations — a documented dead end that motivates the usermode technique.
Substrates (subsystem-abuse family, hotpatch internals, RQPDI debug behavior, DR/VEH interception as a mechanism) are prior art — see paper §1.1 substrate/contribution table.
- Bounded novelty. The framing, the scanner, and the HTH instantiation are the claim; substrates are prior art (paper §1.1, §2). RQPDI's remote-thread behavior is prior art; only its controllability is shown.
- Evidence gap (next). Provenance is argued from disassembly + behavioral probes; ETW/call-stack captures of the created thread are the planned tightening (paper §9.1).
- HTH is self-process; the RQPDI case study is cross-process with a target-resident start-routine constraint (arbitrary code needs prior injection, re-introducing memory-axis signals).
- HVCI claim is precise: the patchless technique does not depend on writing ntdll code pages, validated on an HVCI-configured host. Not tested under a WDAC policy enforcing UMCI on ntdll specifically.
- Static enumeration covers direct callers; runtime cross-validation is future rigor work.
- Validated on build 26200; HTH's in-function scanner handles 22631 as well.
All research conducted on author-owned systems for defensive purposes. Detection recommendations are in paper §8.
This repository is published for defensive security research and education. The proof-of-concept code demonstrates thread-creation primitives against processes you own, on systems you own. Do not use it against systems you are not authorized to test. No vulnerability is disclosed here — this is a technique study; no coordinated disclosure was required, but detection guidance (paper §8) is provided so defenders can treat the ntdll-origin property as a detection input.
Licensed under the MIT License. © 2026 puffert.