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, 0x7ffe0000.

Now, you can browse the structure in friendly format with dt, using _KUSER_SHARED_DATA as the type:

0:000> dt _KUSER_SHARED_DATA 7ffe0000
   +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
   +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 LowPart field:

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)
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
   +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 !pte way.

First, we have to determine the byte index of the address, i.e. the lowest 12 bits. For my case, the address is 0x7FFE0014 (SharedUserData.SystemTime), and the lowest 12 bits are 0x14:

0111 1111 1111 1110 0000 0000 0001 0100 -> 0x14
                         ^^^^ ^^^^ ^^^^

Next, we’ll find the page frame number (PFN) for that address with !pte:

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)

Now add the byte index (previously found), and you’ll have the physical address:

>>> hex(0x1DC000 + 0x14)

Let’s confirm that the physical address points to the same data as the virtual one. The dd command accepts virtual addresses, while !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 !ptov: 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 SystemTime -> SharedUserData + 0x14:

0: kd> ba w4 ffdf0000+14
0: kd> g
Breakpoint 0 hit
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.