Fun facts: Windows kernel and guard pages

It has been a while since I last posted here, so I guess it’s high time to get back to work and share some more interesting Windows kernel internals goodies. Before we get to that, however, let’s start with a few announcements. First of all, there is a number of great infosec conferences coming up and it just so happens that I have been accepted to speak at a few of them. The list includes:

  • SyScan in Singapore (22-26th of April) with Gynvael Coldwind: “Bochspwn: Exploiting Kernel Race Conditions Found via Memory Access Patterns”. We will be discussing a personal project of ours that led to the discovery of around 50 local vulnerabilities in the Windows kernel, some of them already patched in the February and April security bulletins (ms13-016, ms13-017, ms13-031, ms13-036).
  • NoSuchCon in Paris (15-17th of May): “Abusing the Windows Kernel: How to Crash an Operating System With Two Instructions”
  • SEConference in Cracow (24-25th of May): “Bezpieczeństwo jądra Windows, lub jak zabić system dwoma instrukcjami”
  • CONFidence in Cracow (28-29th of May) with Gynvael Coldwind: “Beyond MOV ADD XOR – the unusual and unexpected in x86”
  • BlackHat US in Las Vegas (27th of July – 1st of August) with Gynvael Coldwind: TBA (not yet accepted, but going there either way)

If you are planning to attend any of the above, feel free to ping me so that we can have a beer or two. Otherwise, I will be posting the slides / whitepapers on the blog directly following my presentation at each event, in case you’re interested in the materials.

In other news, last Tuesday Microsoft unexpectedly patched the NTFS.sys device driver vulnerability that we described several months ago in the “Introducing the USB Stick of Death” blog post, despite making a prior claim that vulnerabilities which require Administrative and/or physical access to the victim machine are out of scope and are not considered security issues. While this is surprising in itself, the ms13-036 bulletin containing the fix has apparently broken a lot of Windows-driven platforms, to the point where the vendor removed the specific 2823324 update from the download center entirely. Such situations are uncommon to see, and this particular one is most likely evidence of how third-party software can rely on unsupported or internal OS behavior. I would be really interested to read a post-mortem, but I guess a binary diff of the old and new (if one comes out) patch will have to do. Stay tuned for the analysis.

Now, let’s get to the merit of this post.

Guard pages

The Windows operating system supports all page access rights made available by the X86 and X86-64 architectures: by using functions such as VirtualAlloc or VirtualProtect, one can specify any combination of the R/W/E flags for a group of adjacent user-mode memory pages, or none of them (PAGE_NOACCESS). In addition to these standard rights, Windows additionally implements several custom memory page characteristics, used to facilitate various common tasks performed during normal system execution, such as PAGE_GUARD, PAGE_NOCACHE or PAGE_WRITECOMBINE (all of them described in the “Memory Protection Constants” MSDN article). The PAGE_GUARD flag is particularly interesting here, as it works as a one-time PAGE_NOACCESS marker; i.e. after setting the modifier on a single memory page, the first operation against the page results in triggering a STATUS_GUARD_PAGE_VIOLATION exception and automatically restoring its original access rights.

Among other things, the mechanism is extensively used to implement stack expansion in both ring-3 and ring-0 modes: instead of backing the overall stack region with actual memory, operating systems initially commit only few pages and expand the stack when explicitly required – that is, when a guard page placed at the bottom of the currently commited region is touched by executable code for the first time. The concept is better illustrated by the figure below:

Stack expansion is the most typical use-case for guard pages; however, it turns out that they can also be used for different purposes. Imagine the following snippet of C code as part of an IOCTL handler implementation:

__try {
  CONST DWORD dwSecrets[] = {0xBBADBEEF, 0xDDEFACED};
  BOOLEAN bCheckPassed = TRUE;
  UINT i;

  for (i = 0; i < 2; i++) {
    if (RtlCompareMemory(&lpUserPointer[i], &dwSecrets[i], sizeof(DWORD)) != sizeof(DWORD)) {
      bCheckPassed = FALSE;
      RtlZeroMemory(&lpUserPointer[i], sizeof(DWORD));
      break;
    }
  }
} except(EXCEPTION_EXECUTE_HANDLER) {
  return STATUS_UNSUCCESSFUL;
}

While the total length of the secret value compared against is 64 bits, it is rather obvious that line 9 makes it possible to determine the secret as two 32-bit numbers. This is caused by the fact that zeroing out a user-mode memory region containing an invalid value gives us feedback on how far it has gone in comparing the overall 64-bit bitstream. By observing if the first DWORD in the user-mode buffer changes to 0, we can deduce whether it was guessed correctly and proceed to the second part of the secret. However, if the ninth line was removed from the implementation, things would become more complicated. Thanks to the explicit break, we could try to infer information about matched parts of the password by performing precise time measurements in order to extract subtle differences in the number of instructions executed. While effective in many scenarios, timing attacks tend to be costly (due to the need for multiple attempts to obtain reliable results) and sometimes even not possible at all. In that case, we can make use of another simple observation (the subject of this post): ring-0 code triggers guard page exceptions in the same way ring-3 code does. This essentially means that an attempt to access a guarded page will trigger an exception and clear the guard bit as normal, therefore providing the user-mode with information of whether a memory region was accessed in kernel-mode, or not.

If we aim to disclose the information about how far a kernel routine has reached in its execution and it includes multiple user-mode memory fetches (from different addresses), we can accurately determine the code coverage of that function with the granularity of the input data reads. This is particularly useful if the routine in question does not provide any feedback in that regard by itself, e.g. it always returns the same error code regardless of the actual problem (as in the example above). Applications can test the PAGE_GUARD bit of a local memory page by using the VirtualQuery API. One practical usage of this simple idea against a 0-day low-severity information disclosure vulnerability in the Windows kernel will be discussed in detail in a blog post next week. Another theoretical scenario where it could prove useful is when a vulnerable driver passes a user-mode pointer as a parameter to the memcmp or RtlCompareMemory function (which is wrong for other reasons, too). Given that both functions compare words of native CPU size (four bytes on X86, eight bytes on X86-64), passing a ring-3 pointer to either routine would make it easily possible to recover the overall expected input in chunks of 32 or 64 bits, depending on the architecture, as shown below.

Obviously, the technique can be only potentially useful in local scenarios, for exploitation of specific (uncommon) types of information disclosure bugs, possibly as an alternative to timing attacks. It’s still quite fun, and will hopefully make a little more sense provided a real-world example which will be described in a separate blog post soon. Cheers!

4 thoughts on “Fun facts: Windows kernel and guard pages”

  1. Well, Microsoft may have told us to uninstall the relative update (Security Update for Windows 7 for x64-based Systems (KB2823324):

    Download size: 1.1 MB

    You may need to restart your computer for this update to take effect.

    Update type: Important

    A security issue has been identified in a Microsoft software product that could affect your system. You can help protect your system by installing this update from Microsoft. For a complete listing of the issues that are included in this update, see the associated Microsoft Knowledge Base article. After you install this update, you may have to restart your system.

    More information:
    http://go.microsoft.com/fwlink/?LinkId=281927

    Help and Support:
    http://support.microsoft.com

    and taken it out of the Download section, but they haven’t done anything about it showing up in the important updates on Windows Update. So, it sits there wanting to be downloaded and installed again. That is not any help. Somebody needs to get Microsoft’s attention about this, too!

Comments are closed.