Integer or pointer? CVE-2019-1071

A few months ago I found an info-leak in the win32k driver. It was in the xxxCreateWindowEx (CreateWindow API) and it leaked a kernel pointer to (the desktop heap of) a menu object.

The desktop heap is where all UI objects are allocated in kernel and it’s mapped to user address space too. Meaning that if one finds out the kernel address of an object, and she already got the address in user space, then it reveals the delta between the mappings (which is a secret). And now all objects addresses are known.

Note that in the beginning win32k used to map the desktop heap which revealed kernel pointers (because kernel objects contained kernel pointers in them) and it was recently fixed to harden breaking KASLR. So now such a pointer leak is very helpful for exploitation.

The origin of the bug is in the fact that the same field used for a child-window-ID is also used for a menu pointer (through ‘union’) in the window structure in the kernel. Back in the beginning of the 90’s saving memory space was sacred and this very specific optimization costed MS a few vulnerabilities already.

If you look carefully in MSDN at the CreateWindow API you will notice the HMENU argument, which can also be an ID (or an integer eventually). The bug is a confusion between the time we create the window and the time it queries for a menu.

When we create the window we pass NULL for the HMENU, however the class we use specifies a menu resource through the WNDCLASS.lpszMenuName field. Making the window creation code calling back to user-mode (#1 in snippet) to create a menu for that menu name. While back in user-mode we SetWindowLong() the same window to become a child window (WS_CHILD), meaning that from now on that field should be interpreted as an ID/integer. And now back into the kernel at xxxCreateWindow the following if statement (#2) gets confused.

if (window-not-child &&
    NULL == pmenu &&
    NULL != cls.lpszMenuName)
{
  pmenu = xxxClientLoadMenu(cls.lpszMenuName); ///// #1
}
if (window is child) ///// #2
{
  pwnd->menu = pmenu; ///// #3
  // By now pmenu should be an ID, but it's really a pointer!
}
else
{
  HMAssignmentLock(&pwnd->menu, pmenu);
}

And at last, at #3 our menu is really a pointer and not an ID! Again, the code assumes that if the window is a child then the pmenu variable is always an ID and if the window is not a child, then pmenu is really a pointer to a menu (hence the else with locking the object…).
Now since the window is really a child (as we changed it from user-mode from the xxx callback) and the menu is interpreted as an ID, we managed to leak a kernel pointer to a window-ID.

Finally, it’s accessible just by querying:
GetWindowLongPtr(hWnd, GWL_ID).

Code can be found on my github. Bonus – there’s also a NULL deref bug hidden inside that is exploitable on Windows versions that can map the NULL page.

Yippi

Leave a Reply