➠ Local Privilege Escalation in Win32k.sys Through Indexed Color Palettes
This is the second in our series of Top 5 interesting cases from 2019. Each of these bugs has some element that sets them apart from the more than 1,000 advisories released by the program this year. Today’s blog looks a local privilege escalation in the Windows kernel-mode driver submitted to the program by Marcin Wiązowski.
As sandboxing programs for security purposes becomes commonplace, sandbox escapes become more important. As such, we’re always looking for these types of bugs. We were especially excited when Marcin Wiązowski submitted this innovative Windows 7 local privilege escalation (LPE) bug, together with a full exploit. The bug was great, and his write-up of the vulnerability and exploit were even better. This blog goes through his analysis of the bug that would eventually become CVE-2019-1362.
This vulnerability can be exploited by an unprivileged user to execute code in the context of the kernel and gain SYSTEM privileges.
This bug resulted from a lack of validation of parameters to the
win32k.sys!CreateSurfacePal kernel function. This function is involved with handling palette objects. A palette object is a kernel-mode object that serves as an array of available colors. It is used in conjunction with certain graphical output devices (displays and printers) that are configured to operate with a set of pre-defined colors.
In the original design of the Windows NT kernel, printer drivers were modules loaded into the kernel. Starting with Windows Vista, Microsoft made a major architectural change: Printer drivers would run in user mode instead of running as part of the kernel. This change was made as a fundamental security enhancement, and the security benefit it brought was quite clear: once moved into user mode, bugs in printer drivers have a much-reduced security impact.
In a sharply ironic twist, however, this architectural change turned out to have an inverse effect on the security of the remaining graphics code that stayed behind in the kernel. Refactoring printer drivers and moving them into user space created an entire new attack surface, consisting of the new interface created between kernel-mode graphics code and the driver code that had now been relocated to user space. Kernel-mode code that once was secure could now be made to fail in various ways through influence from printer driver code now running in user mode, which is far more open to tampering.
In particular, in regards to the
win32k.sys!CreateSurfacePal kernel function that is the subject of this bug, the user-mode printer driver architecture made it possible for the first time for malicious code running in user mode to pass an invalid parameter. Typically, an attack would proceed by interfering with the functioning of a legitimate printer driver.
Plan of Attack
We start by hooking the user-mode printer driver.
A user-mode print driver is a DLL that is registered in the system Registry and exports some standardized functions.
For example, the default “Microsoft XPS Document Writer” driver resides in:
and exports the following functions:
When the user calls the
gdi32.dll!CreateDCA/W API with “Microsoft XPS Document Writer” as a parameter, the
mxdwdrv.dll driver is loaded. Then a callback from kernel to user mode is made, and the
mxdwdrv.dll!DrvEnableDriver function is called:
After making the call, the
pded parameter returns a pointer to a
c gives a number of items in the
pdrvfn table, and
pdrvfn table consists of
pdrvfn table is located somewhere in the memory of the
mxdwdrv.dll driver, and each
_DRVFN item describes one of the implemented driver’s functions – where
iFunc is one of the values listed in winddi.h header file:
pfn is a pointer to the user-mode code, located somewhere in the
By overwriting the
pdrvfn table, we can redirect any function implemented by the driver to our own piece of code.
Although we used “Microsoft XPS Document Writer” driver as an example, any print driver, installed in the operating system, can be used. This driver hooking algorithm is “Algorithm 1”:
1 - Call the user-mode
winspool.drv!EnumPrintersA/W API with the
PRINTER_ENUM_ LOCAL parameter to find any available printer. For each printer, its name is returned. In our example, it is “Microsoft XPS Document Writer”.
2 - Use the
winspool.drv!GetPrinterDriverA/W APIs to retrieve the path to the driver DLL (i.e. path to mxdwdrv.dll).
3 - Call
kernel32.dll!LoadLibraryExA/W with the
LOAD_WITH_ALTERED_SEARCH_PATH flag to load the driver DLL.
4 - Call the driver’s
DrvEnableDriver exported function in order to obtain the address of the
5 - Use
kernel32!VirtualProtect API to make the
pdrvfn table writable in memory.
6 - Modify the
pdrvfn table as needed. You should save the original contents of the table to be able to call the original function(s).
7 - Call the driver’s
DrvDisableDriver exported function. The print driver DLL remains loaded in memory, in its patched state.
8 - Call the
gdi32.dll!CreateDCA/W API with printer name obtained in step 1. This call internally loads print driver DLL. However, it is already loaded (and patched by us), so only the DLL’s reference counter will be incremented. This way, we forced the kernel to use the in-memory patched print driver, which redirects needed driver functions to our own code.
For further exploitation, we’ll hook the print driver’s
DrvEnablePDEV function. To do this, we’ll modify the
_DRVFN record, having
iFunc == INDEX_DrvEnablePDEV:
Our plan is to call the original
DrvEnablePDEV function from our hooked code and then modify some fields returned in the
hpalDefault field is a handle to a template palette. This palette must be created by calling a
gdi32.dll!EngCreatePalette API in user mode. By default, this palette has 256 entries – where each entry is a 4-byte structure:
flGraphicsCaps field, we are going to set the
GCAPS_PALMANAGED flag, so the palette given above will be used in the “managed” mode. This flag is not set by default.
ulNumColors value gives a number of the reserved entries in the managed palette given above. By default, it is equal to 20, so first 10 and last 10 palette entries are reserved – they can’t be modified by the application; the remaining entries (in the middle) can be modified freely.
ulNumPalReg value gives a number of all entries in the managed palette given above – it is 256 by default.
The Vulnerability in win32k.sys!CreateSurfacePal
A palette object is represented in kernel mode by a
_PALOBJ structure, which is not publicly defined (winddi.h contains an empty declaration). However, some information can be found in a ReactOS documentation (although another structure name is used there):
There is a
ppalThis pointer in the
_PALOBJ structure, which by default points to the structure itself. Palette entries follow the
_PALOBJ structure immediately in memory. Note the first entry still belongs to the
_PALOBJ structure itself, and is declared as
pFirstColor field points to the first palette entry. By default, it just points to the
After making a call to the
gdi32.dll!CreateDCA/W API in user mode, a callback from kernel is made to call our hook , the user-mode
DrvEnablePDEV function. Then the kernel performs various operations on the data returned to it by the
DrvEnablePDEV call. In particular, if the
flGraphicsCaps field has the
GCAPS_PALMANAGED flag set, the palette returned in the
hpalDefault field is used as a template. It is used to create an internal copy of it and this internal copy becomes a device palette. This is performed in a
win32k.sys!CreateSurfacePal function, which uses also our
ulNumPalReg values. In the newly-created device palette, the palette entries (that are going to become reserved entries) are initialized by setting their
peFlags values to 0x30.
The algorithm of the
win32k.sys!CreateSurfacePal function (Algorithm 2) is:
CreateSurfacePal accepts the following parameters:
-- A pointer to the kernel memory of our
hpalDefault palette – i.e. pointer to the palette’s
_PALOBJ structure in kernel memory. Let’s name this pointer
ulNumColors (number of reserved entries)
2 - Call
win32k.sys!PALMEMOBJ::bCreatePalette to create a new palette by using our
palSrc palette as a template. The new palette will become a device palette; let’s name it
3 - Initialize reserved entries in
palDst with 0x30
peFlags value. The pseudocode is as follows:
4 - Call
win32k.sys!XEPALOBJ::vCopyEntriesFrom, to copy all the palette entries back from
palSrc. On x64, this call is inlined, so only
win32k.sys!memmove is called.
5 - Call
win32k.sys!XEPALOBJ::ulTime, also inlined on x64, to copy the
palSrc->ulTime value to:
palDst->ppalThis->ulTime field (only when
palDst->ppalThis != palDst).
Under normal circumstances,
CreateSurfacePal would be called with parameters as follows:
hpalDefault being a handle to a palette with 256 entries
ulNumColors (number of reserved entries) = 20
ulNumPalReg = 256
So the code, described in step 3 sets
peFlags values to 0x30 in the first 10 and last 10 entries of the
The problem with the code described in step 3 is that the algorithm doesn’t validate the
ulNumColors parameter (indicating the number of reserved entries) against the true number of palette entries, which is
palSrc -> cEntries. Having hijacked a printer driver in user mode, an attacker can pass an out-of-range
ulNumColors value, causing out-of-bounds memory writes.
Assuming the default palette size of 256 entries, the attacker should pass
ulNumColors value of 514:
As seen above, one entry below the range and one entry above the range will be modified by setting the highest byte of the
PALETTEENTRY record (which is a DWORD) to 0x30.
One entry below the range contains a
palDst->ppalThis field, which allows us to alter the
ppalThis pointer by setting its highest byte to 0x30.
One entry above the range – fortunately – is always unused. When allocating memory for the palette object, the following calculation is made:
sizeof(_PALOBJ) + sizeof(PALETTEENTRY) * NumberOfEntries
_PALOBJ structure contains one entry (the
apalColors field), so one more entry than needed is always allocated.
Exploitation on x86
palDst->ppalThis is a 4-byte value. By overwriting its highest byte with 0x30, we set it to a value having the form 0x30XXXXXX. This represents a user-mode address. The first usage of the overwritten
palDst->ppalThis pointer occurs is in the
CreateSurfacePal function itself. It is used to write the
ulTime field. This is step 5 of Algorithm 2 described above.
If we want to just crash the operating system, it’s enough to make sure that the entire memory range from 0x30000000 to 0x30ffffff is inaccessible.
We will now consider how this vulnerability can be used to achieve escalation of privilege.
When using a printer driver without any manipulations, the order of operations is as follows:
1 - Application code calls
gdi32.dll!CreateDCA/W, which finds and loads a printer driver DLL.
2 - The printer driver DLL creates a template palette by calling
3 - The kernel makes a callback to user mode and calls the printer driver’s
DrvEnablePDEV function. At this point,
DrvEnablePDEV can specify a handle to the template palette to be used. The kernel will use this handle to obtain a pointer to palette’s kernel memory. In
CreateSurfacePal, this will be
4 - If the
DrvEnablePDEV call returned the
GCAPS_PALMANAGED flag, the kernel creates a driver palette based on the template palette. In
CreateSurfacePal, this will be
palDst. In the template palette
palSrc, the kernel sets the
hSelected field to point to the newly created device palette:
palSrc->hSelected = palDst
5 - Now usual printing actions are performed.
6 - The user calls
7 - The kernel makes a callback to user mode and calls the printer driver’s
DrvDisablePDEV function. The printer driver DLL deletes the template palette by calling
gdi32.dll!EngDeletePalette. Because the template palette references the device palette in its
hSelected field, the device palette (which we also know as
palDst) is deleted automatically.
8 - The print driver DLL is unloaded.
Let’s now focus on the “device palette is deleted automatically” part. In practice, the device palette’s
palDst -> ppalThis field points to a
_PALOBJ record, and it is used as follows:
a: To access the
BaseObject field. This field contains a handle to the
palDst palette at the beginning of the BaseObject structure. The palette’s handle is passed to
win32k.sys!HmgRemoveObject, which requires the palette’s reference counter to be 1. The reference counter is managed internally by the operating system.
b: After the successful call to
ppalThis pointer is passed to a
win32k.sys!FreeObject call, which in turn passes the pointer to
win32k.sys!ExFreePoolWithTag to deallocate the object.
By triggering the vulnerability, we are able to redirect the
ppalThis pointer to user-mode memory. If at that address we can spoof
_PALOBJ structure that is “valid enough”, we can cause this user-mode address to be passed to the
ExFreePoolWithTag, which is our goal for further exploitation.
To be “valid enough”, our fake, user-mode
_PALOBJ structure must be filled with zeroes, with the following exceptions:
BaseObject field must contain a valid handle to a palette that has its reference counter equal to 1.
ppalThis field must point to the beginning of our fake structure to avoid further recursion in the objection destruction algorithm.
There are two problems to overcome. The first is that the overwritten
ppalThis pointer points to some 0x30XXXXXX location, but we don’t know where exactly. Fortunately, it’s enough to fill the entire memory range from 0x30000000 to
0x30ffffff memory range with zeroes in the initial preparation phase. Then, as already described in step 5 of the Algorithm 2, the
CreateSurfacePal function will write a non-zero timestamp to the
ulTime field by referencing the already overwritten
ppalThis pointer. We can then scan the memory range for the non-zero DWORD value. This reveals the exact location of our spoofed user-mode
The second problem is that the
BaseObject field of our fake
_PALOBJ structure must contain a handle a palette having its reference counter equal to 1. Under normal circumstances, this should be just a handle to the kernel-created device palette
palDst, but we don’t know the handle value of this palette. Fortunately, we can use any other palette instead. The question then becomes where to obtain a palette with reference counter equal to 1. Interestingly, such a palette can be obtained by using our hooked printer driver DLL one more time. We can make a second call to
gdi32.dll!CreateDCA/W and pass a newly-created template palette to the kernel, this time without performing any additional manipulations. After returning from the
CreateDCA/W call, our template palette will have its reference counter equal to 1 as long as we don’t call gdi32.dll!DeleteDC. We can now place the handle of this palette in the BaseObject field of our fake
We are now able to pass an address of our user-mode
_PALOBJ structure to the kernel
ExFreePoolWithTag call, which is definitely an unusual situation. Under normal circumstances,
ExFreePoolWithTag handles only blocks of kernel memory. By surrounding our fake user-mode
_PALOBJ structure by two fake pool headers, we can trick the
ExFreePoolWithTag internals into writing a semi-controlled value to a fully controlled kernel memory address. This is, of course, exactly what we want to do.
ExFreePoolWithTag function works on blocks of so-called pool memory. Describing all the details of pool memory exploitation would be far beyond the scope of this blog, and furthermore, it couldn’t be done better than it was already done in the classic work “Kernel Pool Exploitation on Windows 7” by Tarjei Mandt [PDF]. So, only the main points will be highlighted here:
-- Each block of pool memory (with the exception of big blocks, but those are not relevant here) is preceded by a
POOL_HEADER record. We need to spoof a pool header before our fake
-- When freeing a block of memory, the contents of its pool header are verified with contents of the next pool header. Therefore, we need to also create an additional fake pool header above our fake
-- There is a field in the pool header (
PoolIndex field) that contains an index of the pool to which the block of memory belongs.
-- Under normal circumstances, 4 pools are available in the operating system, although up to 16 pools can be theoretically allocated. Each pool is managed by using its descriptor, a
-- We are going to use a pool exploitation technique called “PoolIndex Overwrite”, although in our case, the “overwrite” part is not a trick since our fake pool headers are located in user memory, so we can write to them at will. We’ll set the
PoolIndex field in our fake pool headers to 15. The kernel uses a table to convert
PoolIndex values into addresses of the corresponding
POOL_DESCRIPTOR records. Since only 4 pools are allocated, a
PoolIndex of 15 will be translated into a null pointer.
On a default install of Windows 7 on x86, it’s possible to allocate a null page (memory starting at address 0) by calling the
ntdll.dll!NtAllocateVirtualMemory native API. (As an aside, note that null page allocation may be enabled or disabled by using the
EnableLowVaAccess of registry key
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory.) This allows us to allocate a fake
POOL_DESCRIPTOR structure there. For our purposes, it’s enough to initialize the
ListHeads fields of this structure as described in the aforementioned work “Kernel Pool Exploitation on Windows 7”. This will lead to the ability to overwrite kernel memory of our choice with a semi-controlled value.
Depending on the amount of physically installed RAM memory, the operating system either returns blocks of memory being freed to the pool immediately, or in larger bursts, known as “delayed frees”. Depending on that variable, by using our crafted
POOL_DESCRIPTOR structure, we can either overwrite kernel memory of our choice with an address of the memory block being freed (a DWORD of the form 0x30XXXXXX, in our case), or with an address of some user-mode memory block that was allocated when preparing our crafted
POOL_DESCRIPTOR structure (which would be some value from range roughly from 0x00010000 to 0x7fff0000). Our last non-obvious task is to find a good target in kernel for overwriting with some value that is mostly unknown but guaranteed to be at least 0x10000.
With such constraints, a good choice is a palette object. Palette entries can be written to by using the
gdi32.dll!SetPaletteEntries API. In its kernel implementation, this API enforces a limit of 0xffff on the index requested to write to. This is because larger palettes cannot be created in user mode. We can create some palette (let’s name it PaletteLO), having, for example, 256 entries. Calling
PaletteLO will succeed only if the requested palette entry to write will be in the range from 0 to 255. We can then use the exploitation method described above to overwrite the
cEntries field in the PaletteLO’s
_PALOBJ kernel structure with an unknown value that is at least 0x10000. Once we have done this,
SetPaletteEntries will work on
PaletteLO within the maximum possible range from 0 to 0xffff range. That will allow us to overwrite memory locations of our choice within the 0xffff limit, located above our
PaletteLO, and we will be able to fully control the contents via color parameters we pass when calling
For the next step of the attack, we will want to create some another palette, located a bit above
PaletteLO. Let’s name it
PaletteHI. We will then call
PaletteLO to set the
pFirstColor field in the PaletteHI’s
_PALOBJ kernel structure with a pointer of our choosing. Since
pFirstColor indicates the base address of a palette’s color array, subsequent calls to
SetPaletteEntries on PaletteHI will overwrite memory pointed to by the
pFirstColor value with fully controlled contents. At this point, it’s really game over. We have gained the ability to overwrite any chosen memory location with any chosen contents by calling
Our palettes before manipulations look like:
After overwriting the
cEntries field in
pFirstColor field in PaletteHI:
To find the kernel addresses of our palettes, we can use a global kernel GDI table that holds information about each GDI object and is mapped in read-only mode into the user address space of each running process. The table contains 65536 entries. The entry structure is not publicly documented, but it’s commonly known as GDICELL:
The meaning of the GDICELL fields are as follows:
KernelAddress: pointer to the kernel memory allocated for the object
ObjectOwnerPid: PID of the object’s owning process
Upper: upper 16 bits of the object’s 32-bit handle value
ObjectType: an enumeration representing the object type
Flags: apparently a field containing some flags
UserAddress: pointer to user memory optionally allocated for the object
To find the kernel address of a palette, it’s enough to use 16 lowest bytes of palette’s handle value as an index to the GDI table and then read the
To create the
PaletteHI palettes that are close enough in kernel memory, it’s enough to create palette objects in a loop until the location of any two created palettes meets our requirements. When addressing an entry within the 0xffff limit,
PaletteLO must be able to overwrite the
pFirstColor pointer in the
PaletteHI’s kernel memory. Such pairs of palettes can be created easily in practice.
It’s time to draft the full exploitation algorithm:
1 - Make sure that the entire memory range from 0x30000000 to 0x30ffffff is writable and filled with zeroes.
2 - Create a pair of palettes that are close enough:
3 - Allocate a null page and prepare a fake
POOL_DESCRIPTOR there. Its contents should lead to overwriting the
cEntries field in
_PALOBJ kernel structure. The value written will be unknown but guaranteed to be at least 0x10000.
4 - Find and hook any printer driver DLL using Algorithm 1 above, but without step 8. Hook the driver’s
5 - Calling
gdi32.dll!EngCreatePalette to create a template palette having 256 entries. This will be called
6 - Instruct the hooked
DrvEnablePDEV function to return
PaletteSRC, along with
ulNumColors parameter equal to 514 and
GCAPS_PALMANAGED flag set in the
7 - Call
gdi32.dll!CreateDCA/W to create an internal device palette with its
ppalThis field pointing to some 0x30XXXXXX address. This device palette will be called
PaletteDEVICE. The reference counter of the
PaletteSRC will become 1.
8 - Scan the memory range from 0x30000000 to 0x30ffffff memory range and find the fake, user-mode
_PALOBJ structure there.
9 - Call
gdi32.dll!DeleteDC. The reference counter of
PaletteSRC will become 0. This allow us to delete it later in step 15.
10 - Call
gdi32.dll!EngCreatePalette to create another template palette. This will be called
11 - Instruct the hooked
DrvEnablePDEV function to return
PaletteHELPER. This time, along with an in-range ulNumColors value, the
GCAPS_PALMANAGED flag should be set in the
12 - Call
gdi32.dll!CreateDCA/W. The reference counter of the
PaletteHELPER will become 1, which is exactly what we need.
13 - In our fake, user-mode
_PALOBJ structure, set the handle value contained in the
BaseObject field to
PaletteHELPER. Also set the
ppalThis field to point to the beginning of the user-mode
14 - Prepare a fake pool header before the user-mode
_PALOBJ structure, and also another one above it.
15 - Call
PaletteSRC. Since the
hSelected field in the
_PALOBJ kernel structure references
PaletteDEVICE will be also deleted. Since
ppalThis pointer points to the fake
_PALOBJ structure in user memory, this user address will be passed to the
win32k.sysExFreePoolWithTag. And since we created fake pool headers before and after the fake
_PALOBJ, our fake null-page pool descriptor will be used by the
ExFreePoolWithTag. This way, the
cEntries field in
_PALOBJ kernel structure will be overwritten with a value that is at least 0x10000.
16 - Call
gdi32.dll! SetPaletteEntries on
PaletteLO to overwrite the
pFirstColor pointer in PaletteHI’s
_PALOBJ kernel structure with any chosen address.
17 - Call
gdi32.dll! SetPaletteEntries on
PaletteHI to overwrite any chosen address with any chosen value.
We now have a write-what-where primitive. Choosing a final target for overwriting will be described below.
Choosing the Memory Location to Overwrite
A process access token is a great choice for an overwrite.
An access token is a kernel object that contains the security context of a process or thread. This includes the identity of the user account that started the process or thread and privileges of that user. Currently, 34 possible privileges are defined:
These privileges may be present or absent. They may also be enabled or disabled. A privilege that is present in the token may get disabled and then reenabled again. It may be also removed, so it becomes absent and cannot be made present anymore so that a process cannot elevate its privileges. Making privileges present is possible only during creation of the token, and creating a token is available only for highly privileged system processes that hold
SeSecurityPrivilege. If a process is privileged to create a token, it can create a token with any privileges that it wants. Such privileges can be used later to launch a process as any user.
To exploit the vulnerability, a kernel address of a token structure must be obtained. To achieve this, a handle to the token must be obtained first by calling
advapi32.dll!OpenProcessToken. Then the internal API
ntdll.dll!NtQuerySystemInformation can be used to obtain the kernel address of the token structure. The simplified token’s structure is:
Privileges_Present field is a 64-bit bitfield that determines present and absent privileges. Currently, only privileges from 2 to 35 are defined, so only these bits are in normal use.
Privileges_Enabled field is a 64-bit bitfield that determines which present privileges are enabled and which are disabled.
To elevate privileges, we’ll set the
pFirstColor field of
PaletteHI to point to the
TOKEN.Privileges_Present field. Then we’ll call
PaletteHI to write to four consecutive palette entries with indices from 0 to 3. The new contents of these palette entries should contain all bits set to 1. This will make all privileges present and enabled. Setting undefined privileges doesn’t cause any problems, although they can be removed by using
Now we have all possible privileges, but still some things we cannot do, as we are still a standard user. The next step is to make use of the
SeSecurityPrivilege privileges we now have. Using these privileges, we can create any token we want using the undocumented API
ntdll.dll!NtCreateToken. In fact, we can create a token representing the most powerful SYSTEM user with all privileges present – a more powerful token than usual SYSTEM tokens. The operating system itself uses SYSTEM tokens, that have some privileges absent. Then, having the most powerful token and also the
SeAssignPrimaryTokenPrivilege privilege, we can use
advapi32.dll!CreateProcessAsUserW to create the most powerful process that can exist.
Since we have also a
SeTcbPrivilege, we can change the
SessionId field of the newly-created token. It is set to 0 by default, so a process created by using this token works like system services and cannot interact with a desktop. After setting the token’s
SessionId to the
SessionId of some logged-in user, the newly created process can draw windows on the user’s desktop. This is nice for demonstration purposes, although it is not required to seize the operating system.
On 64-bit systems, the
palDst -> ppalThis pointer is an 8-byte value. By overwriting its highest byte with 0x30, we set it to a value of the form
0x30XXXXXX’XXXXXXXX value. Current hardware implementations of the 64-bit architecture support only 48 bits for addressing, and the most significant 16 bits of the virtual address (bits 48 through 63) must be copies of bit 47. As a consequence, the highest 16 bits must be either all 0s or all 1s. By setting highest byte to 0x30, we break this rule, which causes an exception and crash. This means that on 64-bit systems the vulnerability can be exploited only for DoS.
Potentially, it might be possible to exploit this vulnerability for elevation of privileges on Itanium machines with Windows Server installations.
Changes in Windows 8 and Above
According to disassembled win32k.sys (Windows 8 and 8.1) or win32kbase.sys (Windows 10), the vulnerability in
CreateSurfacePal function doesn’t exist. The value passed from user mode is verified properly.
It’s also worth noting that since Windows 10 Version 1607 (Redstone 1 “Anniversary Update”), the
GDICELL. KernelAddress field no longer holds a direct pointer to kernel memory. You can read more on this in “A Tale Of Bitmaps: Leaking GDI Objects Post Windows 10 Anniversary Edition”.
Microsoft addressed this vulnerability in October and assigned this bug CVE-2019-1362. The accompanying bulletin simply states the problem was fixed by “correcting how the Windows kernel-mode driver handles objects in memory.” In all likelihood, the patch backports the behavior from Windows 8 since that version of the OS is not affected.
Thanks again to Marcin Wiązowski for providing such a thorough and well-documented analysis of this LPE. It’s easy to see why this bug (and its analysis) stood out from others when looking back at 2019.
These types of bugs are often combined with other vulnerabilities to forge a complete exploit chain. A similar LPE was recently seen in the wild alongside a use-after-free in Chrome targeting Korean news sites. Although the LPE itself isn’t thought to have critical severity, the pairing with other remote code execution bugs makes for an effective attack.
Stay tuned for the next Top 5 bug blog, which will be released tomorrow. Until then, follow the team for the latest in exploit techniques and security patches.... ➠ Komplette Nachricht lesen