Windows Kernel Double Fetches

The Windows Kernel suffers from double fetches in win32kfull!xxxImeWindowPosChanged and win32kfull!InternalRebuildHwndListForIMEClass.

MD5 | cda157a63a505b6dc3e57ec6e5981c51

Windows Kernel double fetches in win32kfull!xxxImeWindowPosChanged and win32kfull!InternalRebuildHwndListForIMEClass 


We have noticed the following code in the win32kfull!xxxImeWindowPosChanged function on Windows 10 version 1709 32-bit (listing from the IDA Pro disassembler):

--- cut ---
.text:000485A4 ; __try { // __except at loc_F3502
.text:000485A4 mov [ebp+ms_exc.registration.TryLevel], 0
.text:000485AB mov eax, [ecx]
.text:000485AD mov edx, ds:__imp__MmUserProbeAddress
.text:000485B3 cmp eax, [edx]
.text:000485B5 jnb short loc_485B9
.text:000485B7 mov edx, ecx
.text:000485B9 loc_485B9:
.text:000485B9 mov eax, [edx]
.text:000485BB mov eax, [eax+8]
.text:000485BE mov [ebp+var_24], eax
.text:000485C1 mov [ebp+var_3C], eax
.text:000485C1 ; } // starts at 485A4
--- cut ---

At the start of the code snippet, ECX is set to a user-mode address. This means that the address that is accessed at 0x485BB is fetched from ring-3 twice: first at 0x485AB in order to sanitize it (compare with MmUserProbeAddress), and then at 0x485B9 to actually dereference it. This is a race condition problem known as TOCTTOU (Time of Check to Time of Use), and can allow a malicious program to change the verified address in between the two reads to bypass the security check.

Let's observe (in WinDbg) how the bug could be exploited. First, let's set a breakpoint at the first instruction of the relevant code, at win32kfull!xxxImeWindowPosChanged+0x15b:

--- cut ---
3: kd> ba e 1 win32kfull!xxxImeWindowPosChanged+15b
--- cut ---

Soon enough under normal system runtime the breakpoint will be hit. We can see that ECX points into writeable user-mode memory, and contains the pointer to be sanitized and accessed:

--- cut ---
3: kd> g
Breakpoint 0 hit
a4c386db 8b01 mov eax,dword ptr [ecx]

1: kd> !pte ecx
VA 028e4f10
PDE at C06000A0 PTE at C0014720
contains 0000000090045867 contains 80000000135DC867
pfn 90045 ---DA--UWEV pfn 135dc ---DA--UW-V

1: kd> dd ecx
028e4f10 028bb020 00000000 98e09ad1 8c000104
--- cut ---

Let's proceed to the next instruction, to have the address at [ECX] loaded into EAX:

--- cut ---
1: kd> p
a4c386dd 8b15cc7ee6a4 mov edx,dword ptr [win32kfull!MmUserProbeAddress (a4e67ecc)]
--- cut ---

Now, we can manually simulate the modification of the address under [ECX] by a concurrent user-mode thread. Let's set it to an invalid 0xbbbbbbbb value:

--- cut ---
0: kd> ed ecx bbbbbbbb
--- cut ---

By single-stepping through the next few instructions, we can see that the pointer sanitization passes through correctly:

--- cut ---
1: kd> p
a4c386e3 3b02 cmp eax,dword ptr [edx]
1: kd> p
a4c386e5 7302 jae win32kfull!xxxImeWindowPosChanged+0x169 (a4c386e9)
1: kd> p
a4c386e7 8bd1 mov edx,ecx
1: kd> p
a4c386e9 8b02 mov eax,dword ptr [edx]
1: kd> p
a4c386eb 8b4008 mov eax,dword ptr [eax+8]
--- cut ---

Once the sanitization completes, the address in question is fetched again from user-mode at 0xa01486e9, and now contains the unmapped 0xbbbbbbbb value:

--- cut ---
0: kd> ? eax
Evaluate expression: -1145324613 = bbbbbbbb
--- cut ---

When we let the execution continue, a kernel bugcheck is generated as a result of trying to access the invalid pointer:

--- cut ---
*** Fatal System Error: 0x000000d6

Driver at fault:
*** win32kfull.sys - Address A4C386EB base at A4C00000, DateStamp 262da7cd


N bytes of memory was allocated and more than N bytes are being referenced.
This cannot be protected by try-except.
When possible, the guilty driver's name (Unicode string) is printed on
the bugcheck screen and saved in KiBugCheckDriver.
Arg1: bbbbbbc3, memory referenced
Arg2: 00000000, value 0 = read operation, 1 = write operation
Arg3: a4c386eb, if non-zero, the address which referenced memory.
Arg4: 00000000, (reserved)


TRAP_FRAME: d26c79a4 -- (.trap 0xffffffffd26c79a4)
ErrCode = 00000000
eax=bbbbbbbb ebx=b20030d8 ecx=028e4f10 edx=028e4f10 esi=b0ba1038 edi=b20004d0
eip=a4c386eb esp=d26c7a18 ebp=d26c7a80 iopl=0 nv up ei ng nz na po cy
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010283
a4c386eb 8b4008 mov eax,dword ptr [eax+8] ds:0023:bbbbbbc3=????????
Resetting default scope

LAST_CONTROL_TRANSFER: from 81e4b1d2 to 81db4d24

d26c735c 81e4b1d2 00000003 54f0de0b 00000065 nt!RtlpBreakWithStatusInstruction
d26c73b0 81e4ac15 8ba9a340 d26c77cc d26c7840 nt!KiBugCheckDebugBreak+0x1f
d26c77a0 81db383a 00000050 bbbbbbc3 00000000 nt!KeBugCheck2+0x78d
d26c77c4 81db3771 00000050 bbbbbbc3 00000000 nt!KiBugCheck2+0xc6
d26c77e4 81d2fee8 00000050 bbbbbbc3 00000000 nt!KeBugCheckEx+0x19
d26c7840 81d30efe d26c79a4 bbbbbbc3 d26c78a0 nt!MiSystemFault+0x13c8
d26c7908 81dc831c 00000000 bbbbbbc3 00000000 nt!MmAccessFault+0x83e
d26c7908 a4c386eb 00000000 bbbbbbc3 00000000 nt!KiTrap0E+0xec
d26c7a80 a4c37b5d 00000000 a53a8510 a5968008 win32kfull!xxxImeWindowPosChanged+0x16b
d26c7ab0 a4c36abd 00000000 a53a8510 b2006180 win32kfull!xxxSendChangedMsgs+0xef
d26c7b18 a4c364b6 00000097 b2006180 b2006100 win32kfull!xxxEndDeferWindowPosEx+0x349
d26c7b38 a4c36292 00000000 00000000 00000000 win32kfull!xxxSetWindowPosAndBand+0x15e
d26c7b7c a4c6356f 00000000 00000000 00000000 win32kfull!xxxSetWindowPos+0x46
d26c7bdc a4c633de 00010000 000100ce 042bf748 win32kfull!xxxShowWindowEx+0x16f
d26c7c04 81dc4d17 000100ce 00000000 042bf754 win32kfull!NtUserShowWindow+0x90
d26c7c04 76fc1670 000100ce 00000000 042bf754 nt!KiSystemServicePostCall
--- cut ---

The same vulnerable construct was also found in the win32kfull!InternalRebuildHwndListForIMEClass function (EAX points into user-mode at the beginning of the snippet):

--- cut ---
.text:000F43F4 test eax, eax
.text:000F43F6 jz loc_4B617
.text:000F43FC mov edx, [eax]
.text:000F43FE test edx, edx
.text:000F4400 jz loc_4B617
.text:000F4406 mov ecx, ds:__imp__MmUserProbeAddress
.text:000F440C cmp edx, [ecx]
.text:000F440E jnb short loc_F4412
.text:000F4410 mov ecx, eax
.text:000F4412 loc_F4412:
.text:000F4412 mov eax, [ecx]
.text:000F4414 test byte ptr [eax+18h], 10h
--- cut ---

We have noticed that the bugs were most likely introduced in October 2017, as this is the first version of win32kfull.sys that contains the affected code. Proof-of-concept programs are not provided for these issues, but they have been observed and confirmed at normal system runtime, and are quite evident in the code.

Exploitation of the vulnerabilities could allow local authenticated attackers to defeat certain exploit mitigations (kernel ASLR) or read other secrets stored in the kernel address space.

This bug is subject to a 90 day disclosure deadline. After 90 days elapse or a patch has been made broadly available, the bug report will become visible to the public.

Found by: mjurczyk

Related Posts