In the most recent blog post (“Fun facts: Windows kernel and guard pages”), we have learned how the code coverage of kernel routines referencing user-mode memory can be determined by taking advantage of the fact that kernel-mode code triggers guard page exceptions in the same way as user-mode does. Today, I will present how the trick can be used in a practical attack against an actual 0-day vulnerability in the Windows kernel. Don’t get too excited though – the bug is a very peculiar type of an information disclosure class, not particularly useful in any sort of real-life attack. Despite being of minimal severity due to the extent of information the bug makes it possible to leak, it makes a great example of how the misuse of the UNICODE_STRING structure and related kernel routines can lead to opening up a security loophole in the operating system.
Microsoft has been aware of the issue for over 4 months, but due to its low severity, I believe it is rather unlikely it will be fixed any time soon.
Unicode string security
All of the most recent versions of the Windows operating system (starting with Windows 2000) internally handle textual strings using the UTF-16 encoding and a UNICODE_STRING structure defined as follows:
typedef struct _LSA_UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } LSA_UNICODE_STRING, *PLSA_UNICODE_STRING, UNICODE_STRING, *PUNICODE_STRING;
Together with the structure definition, both user- and kernel-mode API interfaces provide a set of functions designed to initialize, examine, compare and otherwise operate on unicode strings, e.g. RtlInitUnicodeString, RtlAnsiStringToUnicodeString or RtlAppendUnicodeStringToString. In the above listing, the Length field represents the current length of the string in bytes (it must be a multiplicity of two due to the encoding used), MaximumLength indicates the total capacity of the buffer in bytes and Buffer points to the actual data. Note that both Length and MaximumLength fields are only 16-bits long, which is by far enough to accommodate the size of any string used during normal operation of the system. Perhaps contrary to intuition, the relatively limited ranges of the integers (making it possible to easily craft a string of a maximum size) do not pose any serious threat with regards to integer arithmetic, because overflowing either fields doesn’t give the attacker much edge. If you think about it, getting Length to be smaller than the actual length of the string can only lead to overwriting the existing buffer contents, but will never result in writing past the pool allocation. Similarly, setting MaximumLength to an overly small number only puts a more strict limitation on how many bytes can be stored in the corresponding buffer, or causes all subsequent calls to fail due to an invalid Length > MaximumLength condition. As a consequence, integer overflows are not of much interest in this context.