.. /blog
2026-02-14 // 7 min

The Page You Cannot Shadow: KUSER_SHARED_DATA

nptbsoddebugging

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)
The fundamental issue: NPT shadow pages work for static data. A global variable like 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.