The Page You Cannot Shadow: KUSER_SHARED_DATA
I thought I could shadow any page. I was wrong. KUSER_SHARED_DATA at 0xFFFFF78000000000 is the one page in the Windows kernel that you cannot NPT-shadow with a static copy. I tried. I got an instant MEMORY_MANAGEMENT BSOD. This is the story of the shortest-lived feature in the project.
The plan
KUSER_SHARED_DATA (KUSD) is a special page mapped at a fixed virtual address in every process. It contains system information that both kernel and usermode code read constantly -- system time, tick count, processor features, NtMajorVersion, and a field I cared about: DbgVirtEnabled. Setting this bit would tell certain debugging tools that the system is running under a hypervisor with debugging capabilities.
I already had the shadow page mechanism working perfectly for g_CiOptions. The approach was simple: find KUSD's physical address, create a shadow copy with DbgVirtEnabled set, remap via NPT. Should take twenty minutes.
The crash
I enabled the shadow. The machine BSODed within three seconds. MEMORY_MANAGEMENT -- bugcheck 0x1A. WinDbg showed the crash in the memory manager, deep in MiTimerDpc, trying to read the system time from KUSD.
// KUSER_SHARED_DATA -- what the kernel writes every tick
struct KUSER_SHARED_DATA { // at 0xFFFFF78000000000
// ... many fields ...
TickCountLowDeprecated: u32,
TickCountMultiplier: u32,
InterruptTime: KSYSTEM_TIME,
SystemTime: KSYSTEM_TIME,
TimeZoneBias: KSYSTEM_TIME,
// ... hundreds more fields ...
}
The problem hit me immediately. KUSD isn't a static data page. The kernel writes to it constantly -- every single clock interrupt, roughly every 15.6 milliseconds. It updates InterruptTime, SystemTime, TickCountLow, and several other time-related fields. Every tick.
Why static shadows die
My shadow page was a snapshot taken at the moment I created it. A frozen copy. The real KUSD page kept getting updated by the kernel every 15ms. But the guest was reading my shadow -- which had time values from three seconds ago.
Everything that reads system time from KUSD -- which is nearly everything in the kernel -- saw time standing still. Or worse, saw time jumping backwards when cached values expired and were re-read. The memory manager's timer DPC read a stale tick count, computed a negative time delta, and hit a sanity check that triggered MEMORY_MANAGEMENT.
// what happens with a stale KUSD shadow
// kernel writes to REAL page every 15ms:
real_kusd.SystemTime = now(); // 16:42:03.001
// but guest reads MY SHADOW (frozen at creation time):
shadow_kusd.SystemTime; // 16:42:00.000
// kernel: "time went backwards by 3 seconds???"
// MEMORY_MANAGEMENT (0x1A)
g_CiOptions that gets written once during initialization and then only read -- perfect shadow target. A page that the kernel writes to 64 times per second -- impossible to shadow with a static copy. The shadow goes stale before the next tick fires.Could I fix it?
I considered three approaches and rejected all of them.
Option 1: Write-trap + forward. Intercept writes to the real KUSD page and forward them to the shadow. This would require marking the real page as read-only in the host's page tables, which would #PF on every kernel tick update, which would mean a VMEXIT 64 times per second on every core just for time updates. The overhead would be massive and the timing perturbation would make the system unstable.
Option 2: Periodic sync. Copy the real page to the shadow every N milliseconds. But the copy isn't atomic -- the kernel might be mid-write when I copy, producing a torn read. System time could jump around. And there's still a window where the shadow is stale.
Option 3: Intercept reads instead of writes. Don't shadow the page at all. Instead, intercept every read to KUSD and return modified values. This would be a VMEXIT on every single KUSD access -- which happens thousands of times per second from every process on the system. The performance impact would be catastrophic.
The fix
I deleted the code. The KUSD shadow was a 47-line feature that lasted about 30 minutes in the codebase. Some pages are simply too hot to shadow. The kernel treats KUSD as a high-frequency shared-memory communication channel between the clock interrupt handler and the rest of the system. It's not a configuration variable -- it's a live data feed.
The DbgVirtEnabled bit I wanted to set? I found another way to achieve the same effect through a different mechanism that doesn't involve touching KUSD. Lesson learned: before shadowing a page, check how often it gets written. If the answer is "every tick," walk away.