Testing for Truthiness: Exploiting Improper ChecksMarch 09, 2018 | Jasiel Spelman
As I help get the
victims devices ready for this year’s Pwn2Own contest, I recognize how much smoother the preparation is in comparison to years past. If you combine Pwn2Own and Mobile Pwn2Own, this makes the 11th contest I’ve helped organize. While each one is still incredibly involved to plan, we've gotten better. We no longer trip circuit breakers and burn up our equipment because we're in a foreign country.
The first time I was involved at Pwn2Own during CanSecWest was in 2013. At that contest, MWR Labs successfully exploited Google Chrome using a type confusion bug in Webkit. They then took advantage of a kernel pool overflow in win32k.sys to achieve SYSTEM-level code execution. After the contest, they blogged about their efforts on their site. One of the things I love about this kernel bug is that the root issue came down to the difference between how two functions interpreted if a value was truthy. One function considered non-zero values as true, while another only considered whether or not the least significant bit was non-zero. It turns out that improperly checking a value can have serious consequences.
I’m reminded of this bug chain not just because I’m deep in preparation for this year’s Pwn2Own, but because, despite the usual craziness that is Pwn2Own preparation, I submitted a talk about uninitialized data to the BSides Austin conference, which occurs this week. I’ll be covering a couple of bugs during the presentation, including the uninitialized variable VMware bug from last week, as well as an information leak I found in the Windows kernel in 2016.
The kernel vulnerability was in
NtGdiQueryFonts and is identified by CVE-2016-3354/ZDI-16-507. Let’s look at the function in question:
Just based on this screenshot, it looks like it might be okay. A temporary buffer is being allocated, and the return value of
GreQueryFonts is checked before the contents of the buffer are copied back to userland.
Unfortunately, things go dark once we look a little deeper.
GreQueryFonts just calls and returns the result from
PUBLIC_PFTOBJ::QueryFonts. The first thing I looked at inside
QueryFonts was the possible return values. I was hoping for a code path that would result in an early exit that didn’t modify the input buffer but still returned success.
What I found was far easier to trigger. If we just look at the cross references of the returned value, we’ll see the following in IDA:
See the issue?
NtGdiQueryFonts was expecting an error condition to be represented by the value -1, however that value would never get returned by
NtGdiQueryFonts! The buffer was not zero-initialized, so any indices not explicitly initialized by
QueryFonts would contain uninitialized memory, resulting in a potentially very large information leak.
If you look at a patched version of win32k.sys, or win32kfull.sys in Windows 10, you’ll see that they still perform a check of the return value against -1 but afterward will check to make sure they only copy the initialized values back to userland.
I mentioned this particular kernel bug was from 2016, and it was eventually patched by MS16-106. I bring it up not just because I’ll be discussing it (and others) today at BSides Austin, but because we’ve seen bugs leveraging uninitialized values used in Pwn2Own over the years. It will be interesting to see what types of exploits will be used in the contest this year. If history is any guide, the only thing we can really expect is the unexpected. Join us in Vancouver, or stay tuned to this blog and our Twitter account for all the details of the contest as it occurs.