Skip to content

Commit 6116e19

Browse files
authored
Merge pull request #1837 from HackTricks-wiki/research_update_src_windows-hardening_windows-local-privilege-escalation_kernel-race-condition-object-manager-slowdown_20260131_022905
Research Update Enhanced src/windows-hardening/windows-local...
2 parents 9796de5 + 65677b1 commit 6116e19

1 file changed

Lines changed: 21 additions & 1 deletion

File tree

src/windows-hardening/windows-local-privilege-escalation/kernel-race-condition-object-manager-slowdown.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,26 @@ Tips:
5454
* Alternate the character per level (`A/B/C/...`) if the parent directory starts rejecting duplicates.
5555
* Keep a handle array so you can delete the chain cleanly after exploitation to avoid polluting the namespace.
5656

57+
## Slowdown primitive #3 – Shadow directories, hash collisions & symlink reparses (minutes instead of microseconds)
58+
59+
Object directories support **shadow directories** (fallback lookups) and bucketed hash tables for entries. Abuse both plus the 64-component symbolic-link reparse limit to multiply slowdown without exceeding the `UNICODE_STRING` length:
60+
61+
1. Create two directories under `\BaseNamedObjects`, e.g. `A` (shadow) and `A\A` (target). Create the second using the first as the shadow directory (`NtCreateDirectoryObjectEx`), so missing lookups in `A` fall through to `A\A`.
62+
2. Fill each directory with thousands of **colliding names** that land in the same hash bucket (e.g., varying trailing digits while keeping the same `RtlHashUnicodeString` value). Lookups now degrade to O(n) linear scans inside a single directory.
63+
3. Build a chain of ~63 **object manager symbolic links** that repeatedly reparse into the long `A\A\…` suffix, consuming the reparse budget. Each reparse restarts parsing from the top, multiplying the collision cost.
64+
4. Lookup of the final component (`...\\0`) now takes **minutes** on Windows 11 when 16 000 collisions are present per directory, providing a practically guaranteed race win for one-shot kernel LPEs.
65+
66+
```cpp
67+
ScopedHandle shadow = CreateDirectory(L"\\BaseNamedObjects\\A");
68+
ScopedHandle target = CreateDirectoryEx(L"A", shadow.get(), shadow.get());
69+
CreateCollidingEntries(shadow, 16000, dirs);
70+
CreateCollidingEntries(target, 16000, dirs);
71+
CreateSymlinkChain(shadow, LongSuffix(L"\\A", 16000), 63);
72+
printf("%f\n", RunTest(LongSuffix(L"\\A", 16000) + L"\\0", 1));
73+
```
74+
75+
*Why it matters*: A minutes-long slowdown turns one-shot race-based LPEs into deterministic exploits.
76+
5777
## Measuring your race window
5878
5979
Embed a quick harness inside your exploit to measure how large the window becomes on the victim hardware. The snippet below opens the target object `iterations` times and returns the average per-open cost using `QueryPerformanceCounter`.
@@ -94,7 +114,7 @@ The results feed directly into your race orchestration strategy (e.g., number of
94114
## Operational considerations
95115

96116
- **Combine primitives** – You can use a long name *per level* in a directory chain for even higher latency until you exhaust the `UNICODE_STRING` size.
97-
- **One-shot bugs** – The expanded window (tens of microseconds) makes “single trigger” bugs realistic when paired with CPU affinity pinning or hypervisor-assisted preemption.
117+
- **One-shot bugs** – The expanded window (tens of microseconds to minutes) makes “single trigger” bugs realistic when paired with CPU affinity pinning or hypervisor-assisted preemption.
98118
- **Side effects** – The slowdown only affects the malicious path, so overall system performance remains unaffected; defenders will rarely notice unless they monitor namespace growth.
99119
- **Cleanup** – Keep handles to every directory/object you create so you can call `NtMakeTemporaryObject`/`NtClose` afterwards. Unbounded directory chains may persist across reboots otherwise.
100120

0 commit comments

Comments
 (0)