Shadow pages and quiet hooks
A compact model for thinking about EPT shadow pages, execute-only mappings, and where a monitor can observe without patching guest bytes.
Problem statement
Inline hooks are easy to explain and hard to hide. They change the bytes that a guest reads, which makes them noisy under integrity checks, memory scanners, and routine forensic tooling.
An EPT-backed design gives the monitor a different primitive: split what the guest can read from what the CPU can execute.
Mental model
Think of the target page as two views of the same address:
| View | Guest action | Backing page |
|---|---|---|
| Read/write | Inspect bytes or copy code | Original page |
| Execute | Fetch instructions | Shadow page |
The key property is that the guest virtual address does not change. Only the second-level translation changes.
Flow
Locate the target
Resolve the guest physical page that backs the function or basic block you want to observe. Keep this part boring: validate the module range, verify page permissions, and avoid assumptions about build-specific offsets.
Split the mapping
Large pages are convenient until you need per-page permissions. Split a 2 MiB mapping into 4 KiB entries before changing execute permissions on a single page.
guest VA -> guest PA -> EPT entry
|
+-- R/W: original page
+-- X: shadow page
Install the shadow
Clone the original page, place the trampoline or trap sequence in the copy, and revoke execute access from the original mapping. Instruction fetches now leave a signal without changing what the guest sees during normal reads.
Cost model
The cheapest useful estimate is the exit rate multiplied by the average handling cost:
For inline reasoning, I usually treat as the first variable to reduce.
Notes worth keeping
- TLB invalidation is part of the feature, not cleanup.
- Per-core state makes debugging harder but can reduce global side effects.
- Treat every VM exit path as hostile input.
- Log the exact OS build, CPU vendor path, and relevant control bits.
Closing thought
The interesting part is not the hook. The interesting part is the boundary between observation and mutation.