Threat Research

Root Cause Analysis of Windows Kernel UAF Vulnerability lead to CVE-2016-3310

By Wayne Chin Yick Low | August 16, 2016

In the first quarter of 2016, we realized that there were tons of windows kernel use-after-free (UAF) vulnerability patches in Microsoft bulletins where most of the vulnerabilities came from Google Project Zero, which is favourable to us because we can easily access those proof-of-concepts (POC). While doing a root cause analysis of one of the UAF vulnerabilities stated in CVE-2015-6100, we discovered that there is an alternative way to trigger the same UAF vulnerability, even after the specified patch has been applied due to weak security fixes. In this blog post, we will discuss the journey of unveiling CVE-2016-3310 as specified in MS16-098


Root cause analysis of CVE-2015-6100

Before we started analysing the POC, we first enabled special pool on the target machine using verifier.exe to could help us to identify the exact faulty code that triggered the UAF. After special pool was set accordingly, we easily spotted the UAF by running the POC (please note that this analysis was performed on a Windows 7 x86 platform):


eax=000013ec ebx=90a20402 ecx=fb864da8 edx=000019c8 esi=fab18728 edi=00000000

eip=90a30e64 esp=92cebcf8 ebp=92cebd08 iopl=0         nv up ei ng nz na po nc

cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00010282


90a30e64 394120          cmp     dword ptr [ecx+20h],eax ds:0023:fb864dc8=????????

Resetting default scope

LAST_CONTROL_TRANSFER:  from 82931ce7 to 828cd308


92ceb7d4 82931ce7 00000003 12cf79fe 00000065 nt!RtlpBreakWithStatusInstruction

92ceb824 829327e5 00000003 00000000 000fb7f2 nt!KiBugCheckDebugBreak+0x1c

92cebbe8 828e03c1 00000050 fb864dc8 00000000 nt!KeBugCheck2+0x68b

92cebc6c 82892be8 00000000 fb864dc8 00000000 nt!MmAccessFault+0x104

92cebc6c 90a30e64 00000000 fb864dc8 00000000 nt!KiTrap0E+0xdc

92cebd08 90a20427 00000000 0027fb98 fab18728 win32k!DC::bMakeInfoDC+0xf5

92cebd24 8288fa06 1d210408 00000000 0027fbc0 win32k!NtGdiMakeInfoDC+0x25

92cebd24 76ec71b4 1d210408 00000000 0027fbc0 nt!KiSystemServicePostCall

0027fb84 75376f81 75367e2b 1d210408 00000000 ntdll!KiFastSystemCallRet

0027fb88 75367e2b 1d210408 00000000 00010000 GDI32!NtGdiMakeInfoDC+0xc

0027fbc0 75357a04 1d210408 0027fc28 010272f4 GDI32!MFP_StartPage+0x84

0027fbd8 00ff11e3 1d210408 000000f6 070c0ad7 GDI32!ExtFloodFill+0x93


Listing 1: Faulty code that trigger use-after-free vulnerability

After some back-tracing, the offending code was found at win32k!DC::bMakeInfoDC. We were then able to determine that the freed object is a surface object (also known as a bitmap object in user-mode context), which is located at the HDCOBJ+1F8. Next, we tried to find out the type of the freed object. Note that at this point we disabled the special pool in the following WinDBG output in order to find out the object type of the pointer stored at HDCOBJ+1F8:


91440e42 8d8ef8010000    lea     ecx,[esi+1F8h]

kd> r

eax=fe8b5ff0 ebx=91430402 ecx=00000030 edx=fe8bd170 esi=fe8b5728 edi=00000000

eip=91440e42 esp=96e43cf8 ebp=96e43d08 iopl=0         nv up ei pl nz na pe nc

cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00000206


91440e42 8d8ef8010000    lea     ecx,[esi+1F8h]

kd> .load pykd.pyd

kd> dc fe8b5728 l1

fe8b5728  1a210433                             3.!.

kd> !py E:\_Scripts\_PyKd\ -v 0x1a210433

PEB: 0x7ffde000

TEB: 0x7ffdf000

Process name: C:\Users\analyst\Desktop\cve-2015-6100-poc.exe

GdiShareHandleTable (KM): 0xffffffffff810000

GdiShareHandleTable (UM): 0x520000

       ======== Requested GDI object with handle (1a210433) ========


          pKernelAddress: 0xfe8b5728

          wProcessId: 0x988

          wCount: 0x0

          wUpper: 0x1a21

          wType: 0x4401 (DC)

kd> p


91440e48 8911            mov     dword ptr [ecx],edx

kd> p


91440e4a 8bca            mov     ecx,edx

kd> !pool fe8bd170

Pool page fe8bd170 region is Paged session pool

fe8bd000 is not a valid large pool allocation, checking large session pool...

 fe8bd158 size:    8 previous size:    0  (Allocated)  Frag

 fe8bd160 size:    8 previous size:    8  (Free)       Free

*fe8bd168 size:  260 previous size:    8  (Allocated) *Gla5

              Pooltag Gla5 : GDITAG_HMGR_LOOKASIDE_SURF_TYPE, Binary : win32k.sys

 fe8bd3c8 size:  620 previous size:  260  (Allocated)  Gh14

 fe8bd9e8 size:   30 previous size:  620  (Free)       Geto

 fe8bda18 size:  5e8 previous size:   30  (Free )  Ussc Process: 86527918


Listing 2: Determine the freed object type

One of the important questions that needed to be answered when it came to our vulnerability analysis is how did the UAF occur in the first place? If we look at the POC, we can see the gdi32!NtGdiStartPage call is wrapped around by a try-catch block. As commented by the POC, a user-mode exception occurs when the function gdi32!NtGdiStartPage is called. The following diagram depicts its call sequences:


Figure 1: Call-sequences lead to user-mode exception


In a nutshell, when we looked at the code where the user-mode exception occurred, we were able to determine that the UAF happened due to an invalid GDI object handle being passed to gdi32!NtGdiStartPage. Our findings were confirmed when we discovered that the CVE-2015-6100 patch contains additional code validating the GDI object handle value on the affected GDI functions after diffing against win32k.sys binaries (See Figure 2). Based on the diffing result, we knew that some of the affected GDI functions that can lead to the same user-mode exception include:

  • win32k!NtGdiStartPage
  • win32k!NtGdiEndPage
  • win32k!GreStartDocInternal
  • win32k!bEndDocInternal

Figure 2: Unpatched win32k.sys 6.1.7601.18985 (left) VS patched win32k.sys 6.1.7601.19054 (right)


Figure 3: win32k!NtGdiStartPage epilog


Our next question was “when did the surface object pointed at HDCOBJ+1F8” get freed? In the event of the crash being handled by POC, it would continue to execute and return to the caller of the user-mode callback function user32!__ClientPrinterThunk. As demonstrated by the code block in Figure 3, we can clearly show that if the user-mode callback function has failed to execute properly and returns NULL, it will proceed to the branch loc_BF99F796 in an attempt to disable and then free the surface object. It is important to note that by disabling the surface object, it synonymously decreases the reference count of the target object stored at GDIOBJ+0x4, as seen in this example:


Listing 3: Demonstrate the reference count and exclusive lock of a GDI object


Some GDI objects can only be freed when the object reference count and its exclusive lock become NULL. This is exactly the routine carried out by win32k!bEndDocInternal upon returning from win32k!UMPDDrvStartPage. The following pseudo-code briefly illustrates the operations of win32k!bEndDocInternal:


Listing 4: Pseudocode to show the operations of win32k!bEndDocInternal

In short, the code figure above instructs another user-mode callback function to disable the surface object through win32k!UMPDDrvDisableSurface, which eventually triggers the user-mode callback to free the target surface object, as can be seen in the following call-stack:


       a1377c50 915ed154 76050364 00000000 00000001 win32k!HmgRemoveObject+0x7c

       a1377cf4 915ed000 00000000 00000000 0014d048 win32k!SURFACE::bDeleteSurface+0x143

       a1377d08 915d7446 00000000 0014d048 fb1d8da8 win32k!SURFREF::bDeleteSurface+0x14

       a1377d1c 916e3c8b 76050364 a1377d34 82892a06 win32k!bDeleteSurface+0x20

       a1377d28 82892a06 76050364 0014d058 777871b4 win32k!NtGdiEngDeleteSurface+0x19

       a1377d28 777871b4 76050364 0014d058 777871b4 nt!KiSystemServicePostCall

       0014d038 779872a1 6d7ac504 76050364 00000000 ntdll!KiFastSystemCallRet

       0014d03c 6d7ac504 76050364 00000000 76050364 GDI32!NtGdiEngDeleteSurface+0xc

       0014d058 779519a2 002f2508 00000000 0014d8f4 mxdwdrv!DrvDisableSurface+0x6c

       0014d08c 75a714bc 0014d8f4 0014d0a4 00000000 GDI32!GdiPrinterThunk+0x252

       0014d8dc 777870ee 0014d8f4 00000018 0014d934 USER32!__ClientPrinterThunk+0x28

       0014d908 7778555c 777870b8 00000000 00000000 ntdll!KiUserCallbackDispatcher+0x2e


Listing 5: Call-stack when the surface object is freed upon calling win32k!UMPDDrvDisableSurface

In summary, we drew the following conclusion on how and when the UAF occurred:

  • When an invalid GDI object handle passed to GDI printing functions, it could cause user-mode code exception
  • If the exception was handled by the program, one could free and delete the surface object specified in the device context via win32k!bEndDocInternal
  • Afterwards, any GDI printing function call that dereferences the freed surface object will also lead to a use-after-free vulnerability 

Abusing user-mode callback leads to arbitrary free of GDI object – CVE-2016-3310

After some reverse-engineering, we discovered that one can free a surface object under a device context (DC) by calling either one of the following win32k GDI functions:

  1. win32k!NtGdiAbortDoc
  2. win32k!NtGdiEndDoc
  3. win32k!NtGdiStartPage

To recap, the surface object is freed under win32k!NtGdiStartPage upon failing to execute the user-mode callback routine because of the access violation triggered in user32!__ClientPrinterThunk when an invalid printer DC was passed to the function. If the exception is caught by the program, then the UAFvulnerability can be triggered.

In the response from MSRC, the affected GDI functions were patched in such a way that the input DC handle is now being validated, and only printer DC is allowed to execute the identified routines. This seemed to be an easy and straight forward fix to prevent the surface object from being freed on demand. However there is alternative way to free the surface object.

It’s worth mentioning here that every surface object stores a reference count and exclusive lock in order to prevent the object from being freed a user mode API call, like the gdi32!DeleteObject function. However, one can first disable the surface object and then free the surface object using user-mode API gdi32!DeleteObject. While there didn’t seem to be any apparent way to disable the surface object, we discovered that we could take advantage of one of the GDI functions highlighted in Figure 4 that would eventually call win32k!PDEVOBJ::vDisableSurface to disable the target surface object.

Figure 4: Xrefs of win32k!bEndDocInternal

Either win32k!NtGdiAbortDoc or win32k!NtGdiEndDoc appeared to be a good candidate for us. However, there are some requirements that need to be met in order to properly lead us to the code path that will execute the win32k!PDEVOBJ::vDisableSurface function:

  1. Printer DC is needed
  2. The surface object pointer at HDC+1F8 cannot be NULL; it will be scrutinized in win32k!XDCOBJ::bValidSurf

The first requirement can be easily achieved by creating a printer DC using “winspool” as a driver name in the CreateDC function. After further reverse-engineering to better understand the highlighted function in Listing 1, we realized that it plays a crucial role that could greatly help us achieve our second objective. In general, the steps can be summarized as follows:

  1. Get a printer DC and then start the print job.
  2. Call win32k!NtGdiMakeInfoDC to store the surface object pointer at HDC+0x8C8 to HDC+0x1F8
  3. Hook user32!__ClientPrinterThunk. The hook handler will be triggered when win32k!NtGdiEndDoc is executed, and it then executes the win32k!NtGdiMakeInfoDC function to restore the surface object pointer from HDC+0x1F8 to HDC+0x8C8.
  4. At the epilogue of win32k!bEndDocInternal, the surface object reference count will then be disabled by decreasing the reference count to 0.
  5. From our controlled program, we were now able to free the disabled surface object simply by calling the gdi32!DeleteObject API function.

Here is the result:




945d2889 394120          cmp     dword ptr [ecx+20h],eax


SYMBOL_NAME:  win32k!DC::bMakeInfoDC+f5

FOLLOWUP_NAME:  MachineOwner

IMAGE_VERSION:  6.1.7601.23452

FAILURE_BUCKET_ID:  0xD5_VRF_win32k!DC::bMakeInfoDC+f5

BUCKET_ID:  0xD5_VRF_win32k!DC::bMakeInfoDC+f5


FAILURE_ID_HASH_STRING:  km:0xd5_vrf_win32k!dc::bmakeinfodc+f5

FAILURE_ID_HASH:  {0a3b4edd-f3e1-fb09-5501-23e8947579df}

Followup: MachineOwner


kd> lmvm win32k.sys

start    end        module name

kd> lmvm win32k.

start    end        module name

kd> lmvm win32k

start    end        module name

94400000 9465d000   win32k     (pdb symbols)          e:\symbols\win32k.pdb\3B7E088E7D3E4382B161AA168A7C93872\win32k.pdb

    Loaded symbol image file: win32k.sys

    Image path: \SystemRoot\System32\win32k.sys

    Image name: win32k.sys

    Timestamp:        Thu May 12 22:54:44 2016 (57349934)

    CheckSum:         002502B1

    ImageSize:        0025D000

    File version:     6.1.7601.23452

    Product version:  6.1.7601.23452

    File flags:       0 (Mask 3F)

    File OS:          40004 NT Win32

    File type:        3.7 Driver

    File date:        00000000.00000000

    Translations:     0409.04b0

    CompanyName:      Microsoft Corporation

    ProductName:      Microsoft® Windows® Operating System

    InternalName:     win32k.sys

    OriginalFilename: win32k.sys

    ProductVersion:   6.1.7601.23452

    FileVersion:      6.1.7601.23452 (win7sp1_ldr.160512-0600)

    FileDescription:  Multi-User Win32 Driver

    LegalCopyright:   © Microsoft Corporation. All rights reserved.    

In summary, the use-after-free (UAF) vulnerability in CVE-2016-3310 is somewhat related to the previously patched CVE-2015-6100  vulnerability, which is a low-hanging fruit opportunity that can be spotted by understanding the root cause of the original vulnerability through manual analysis, binary diffing, and code auditing. However, engineering bug-free software, especially for robust software, remains highly challenging, so we should not point the finger at any software vendors who puts software security as their first priority. At Fortinet, weactively participate in coordinated vulnerability disclosures with major software vendors to proactively protect end-users.

Signing off

-= FortiGuard Lion Team =-


Join the Discussion