Lucene search

K
seebugRootSSV:97140
HistoryFeb 24, 2018 - 12:00 a.m.

Windows Kernel double fetches in win32kfull!xxxImeWindowPosChanged and win32kfull!InternalRebuildHwndListForIMEClass( CVE-2018-0809)

2018-02-2400:00:00
Root
www.seebug.org
37

0.001 Low

EPSS

Percentile

33.8%

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

  .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
  .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

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:

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

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:

  3: kd> g
  Breakpoint 0 hit
  win32kfull!xxxImeWindowPosChanged+0x15b:
  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

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

  1: kd> p
  win32kfull!xxxImeWindowPosChanged+0x15d:
  a4c386dd 8b15cc7ee6a4    mov     edx,dword ptr [win32kfull!MmUserProbeAddress (a4e67ecc)]

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:

  0: kd> ed ecx bbbbbbbb

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

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

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

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

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

  *** Fatal System Error: 0x000000d6
                         (0xBBBBBBC3,0x00000000,0xA4C386EB,0x00000000)

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

  [...]

  DRIVER_PAGE_FAULT_BEYOND_END_OF_ALLOCATION (d6)
  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.
  Arguments:
  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
  win32kfull!xxxImeWindowPosChanged+0x16b:
  a4c386eb 8b4008          mov     eax,dword ptr [eax+8] ds:0023:bbbbbbc3=????????
  Resetting default scope

  LAST_CONTROL_TRANSFER:  from 81e4b1d2 to 81db4d24

  STACK_TEXT:  
  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

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

  .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
  .text:000F4412 loc_F4412:
  .text:000F4412                 mov     eax, [ecx]
  .text:000F4414                 test    byte ptr [eax+18h], 10h

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.