Inside the Windows Kernel: who updates SharedUserData?
As you might know, the SharedUserData structure is present in all Windows NT+ versions, and is always at the same static offset 0x7FFE0000. Its purpose is to provide a bunch of interesting data to all the processes, without forcing them to do an expensive syscall.
You can find the offset in WinDbg like so:
0:000> dd SharedUserData L1 7ffe0000 00000000
It is the previously mentioned static offset,
Now, you can browse the structure in friendly format with
_KUSER_SHARED_DATA as the type:
0:000> dt _KUSER_SHARED_DATA 7ffe0000 ntdll!_KUSER_SHARED_DATA +0x000 TickCountLowDeprecated : 0 +0x004 TickCountMultiplier : 0xfa00000 +0x008 InterruptTime : _KSYSTEM_TIME +0x014 SystemTime : _KSYSTEM_TIME +0x020 TimeZoneBias : _KSYSTEM_TIME ... +0x3e0 XState : _XSTATE_CONFIGURATION
(fields skipped for brevity)
As you see, there are some fields, such as
SystemTime, which are constantly updated:
0:000> dt _KUSER_SHARED_DATA 7ffe0000 SystemTime ntdll!_KUSER_SHARED_DATA +0x014 SystemTime : _KSYSTEM_TIME 0:000> dx -r1 (*((ntdll!_KSYSTEM_TIME *)0x7ffe0014)) (*((ntdll!_KSYSTEM_TIME *)0x7ffe0014)) [Type: _KSYSTEM_TIME] [+0x000] LowPart : 0xba4a1f7b [Type: unsigned long] ; <------ time ... 0:000> dx -r1 (*((ntdll!_KSYSTEM_TIME *)0x7ffe0014)) (*((ntdll!_KSYSTEM_TIME *)0x7ffe0014)) [Type: _KSYSTEM_TIME] [+0x000] LowPart : 0xbb9757c7 [Type: unsigned long] ; <------ time ...
Now, to see who updates it, we put a hardware breakpoint on write on the
0:000> ba w4 0x7ffe0014 0:000> g *BUSY* Debuggee is running...
However, it never breaks. If we break manually and check the value, it has been updated:
0:001> dx -r1 (*((ntdll!_KSYSTEM_TIME *)0x7ffe0014)) (*((ntdll!_KSYSTEM_TIME *)0x7ffe0014)) [Type: _KSYSTEM_TIME] [+0x000] LowPart : 0x801a52c [Type: unsigned long] [+0x004] High1Time : 30594240 [Type: long] [+0x008] High2Time : 30594240 [Type: long]
Maybe it’s the kernel who updates it, and we just can’t see it from usermode. Let’s quickly switch to kernel debugging:
0: kd> !process 0 1 notepad.exe PROCESS 8d379550 SessionId: 1 Cid: 0640 Peb: 7ffdf000 ParentCid: 0a1c ^^^^^^^^ ... 0: kd> .process /p /r /i 8d379550 You need to continue execution (press 'g' <enter>) for the context to be switched. When the debugger breaks in again, you will be in the new process context. 0: kd> g Break instruction exception - code 80000003 (first chance) nt!RtlpBreakWithStatusInstruction: 82a8c110 cc int 3 0: kd> !process -1 0 PROCESS 8d379550 SessionId: 1 Cid: 0640 Peb: 7ffdf000 ParentCid: 0a1c DirBase: 3fdd1680 ObjectTable: 8b5b9cb8 HandleCount: 74. Image: notepad.exe
We are in the process’ context. Let’s make sure that SharedUserData is still at the same offset:
0: kd> dd SharedUserData L1 7ffe0000 00000000 0: kd> dt _KUSER_SHARED_DATA 7ffe0000 nt!_KUSER_SHARED_DATA +0x000 TickCountLowDeprecated : 0 +0x004 TickCountMultiplier : 0xf99a027 +0x008 InterruptTime : _KSYSTEM_TIME +0x014 SystemTime : _KSYSTEM_TIME ... +0x3e0 XState : _XSTATE_CONFIGURATION
It effectively is. Let’s put a breakpoint and see what happens:
0: kd> dx -r1 ((ntkrpamp!_KSYSTEM_TIME *)0x7ffe0014) ((ntkrpamp!_KSYSTEM_TIME *)0x7ffe0014) : 0x7ffe0014 [Type: _KSYSTEM_TIME *] [+0x000] LowPart : 0x2ffad577 [Type: unsigned long] [+0x004] High1Time : 30594240 [Type: long] [+0x008] High2Time : 30594240 [Type: long] 0: kd> ba w4 0x7ffe0014 0: kd> g
Still nothing, it won’t break, even though the value is continuously updated. What’s up?
What happens is that the value at that address is being updated from another virtual address mapping. That effectively means that the SharedUserData structure is mapped (at least) twice: once at the static offset, and (at least) once for whoever updates it. That means there are two or more virtual addresses that are mapped to the same physical address, so when you update the value of one, the other one updates as well. And the hardware breakpoints never break because data breakpoints are based on the linear address, not the physical address.
So what can we do to find out who is updating
SystemTime? We’ll translate the virtual address to a physical one, and then find out all the virtual addresses
mapped to it. Then we can set a breakpoint on each mapping and see what happens.
Translating a virtual address to a physical one
We’ll refer to the MSDN documentation, and do it the
First, we have to determine the byte index of the address, i.e. the lowest 12 bits. For my case, the address is
SharedUserData.SystemTime), and the
lowest 12 bits are
0111 1111 1111 1110 0000 0000 0001 0100 -> 0x14 ^^^^ ^^^^ ^^^^
Next, we’ll find the page frame number (PFN) for that address with
1: kd> !pte 0x7FFE0014 VA 7ffe0014 PDE at C0601FF8 PTE at C03FFF00 contains 000000000D946867 contains 80000000001DC005 pfn d946 ---DA--UWEV pfn 1dc -------UR-V -------------->^^^<-----------------
In this case, the PFN is
0x1DC. Shift it left 12 bits, and you’ll get the physical address of the beginning of that page:
>>> hex(0x1DC << 12) '0x1dc000'
Now add the byte index (previously found), and you’ll have the physical address:
>>> hex(0x1DC000 + 0x14) '0x1dc014'
Let’s confirm that the physical address points to the same data as the virtual one. The
dd command accepts virtual addresses,
!dd accepts physical ones:
1: kd> dd 7ffe0000 L4 7ffe0000 00000000 0f99a027 9dc50308 00000001 1: kd> !dd 1dc000 L4 # 1dc000 00000000 0f99a027 9dc50308 00000001
They seem to point to the same place!
Finding out the mappings
Now, we have the physical address. To find out which virtual addresses are mapped to it, we’ll use
a full map of all the physical-to-virtual addresses for the current process. The output is very long,
so I’ll grep out the non-interesting parts:
1: kd> !process -1 0 PROCESS 8d379550 SessionId: 1 Cid: 0640 Peb: 7ffdf000 ParentCid: 0a1c DirBase: 3fdd1680 ObjectTable: 8b5b9cb8 HandleCount: 73. Image: notepad.exe 1: kd> !ptov 3fdd1680 X86PtoV: pagedir 3fdd1680, PAE enabled. ... 1dc000 7ffe0000 1dc000 ffdf0000 ...
Since the address starts with
0x1DC0, I just searched for all the physical addresses that start with it. Remember that the physical address
(left column) corresponds to the start of the page, like the virtual address (right column), so you need to add the offset for the structure
member in question, which is
SharedUserData + 0x14:
0: kd> ba w4 ffdf0000+14 0: kd> g Breakpoint 0 hit nt!KeUpdateSystemTime+0x9e: 82a8b9e6 89351800dfff mov dword ptr ds:[0FFDF0018h],esi
Bingo! There you have it. ;-)
Got this far? You might want to check out the discussion at reddit or hacker news.