Exploring Control Flow Guard in Windows 10
As operating system developers are always keen on improving exploit mitigation technology, Microsoft has enabled a new mechanism in Windows 10 and in Windows 8.1 Update 3 (released last November) by fault. This technology is called Control Flow Guard (CFG).
Previous mitigation techniques like address space layout randomization (ASLR) and Data Execution Prevention (DEP) have been successful in making exploitation of vulnerabilities more difficult, even if these techniques are not perfect. ASLR causes the development of heap sprays, and DEP results in return-oriented-programming (ROP) techniques showing up in the exploit code.
To explore this particular technology, I used the Windows 10 Technical Preview (build 6.4.9841), with test applications built using the Visual Studio 2015 Preview. Because the CFG implementation of the latest Windows 10 technical preview build (10.0.9926) has a slight change at that same point, I will point out the difference.
To fully implement CFG, both the developer and the operating system must support it properly. As an exploit mitigation mechanism in the system level, the CFG implementation requires cooperation from the compiler, the operating system user mode library, and the kernel mode module. A blog post on MSDN outlined the steps that developers need to do to support CFG.
Microsoft’s implementation of CFG is focused on indirect call protection. Consider the following code in the test program I created:
Figure 1. Code of test application
Let’s take a look at what the encircled code compiles into if CFG is not enabled.
Figure 2. Assembly code of test program
In the above figure, there is one type of indirect call. Its target address is not decided at compilation and is instead decided at runtime. An exploit can abuse this as follows:
Figure 3. How to abuse the indirect call
Microsoft’s implementation of CFG focuses on mitigating problems if the indirect call is exploited and an invalid target is called (in an exploit, this would be to first stage shellcode).
The invalid target has a distinguishing characteristic: in most cases it is not a valid function starting address. Microsoft’s CFG implementation is based on the idea that an indirect call’s target must be the start of a valid function. What is the resulting assembly code if CFG is enabled?
Figure 4. Assembly code, with CFG enabled
Before the indirect call, the target address is passed to the _guard_check_icall function, which is where CFG is actually implemented. In versions of Windows without CFG support, this function does nothing. In Windows 10, which does have CFG support, it points to ntdll!LdrpValidateUserCallTarget. This function takes a target address as argument and does the following:
- Access a bitmap (called CFGBitmap) which represents the starting location of all the functions in the process space. The status of every 8 bytes in the process space corresponds to a bit in CFGBitmap. If there is a function starting address in each group of 8 bytes, the corresponding bit in CFGBitmap is set to 1; otherwise it is set to 0. The figure below is an example of a portion of CFGBitmap:
Figure 5. Representation of CFGBitmap
- Convert the target address to one bit in CFGBitmap. Let’s take 00b01030 as example:
Figure 6. Target address
The highest 3 bytes (the 24 bits encircled in blue) is the offset for CFGBitmap (unit is 4 bytes/32 bits). In this example, the highest three bytes are equal to to 0xb010. Therefore, the pointer to a four byte unit in CFGBitmap is the base address of CFGBitmap plus 0xb010.
Meanwhile, the fourth bit to the eighth bit is (the five bits encircled in red) have the value X. If target address is aligned with 0x10 (target address & 0xf == 0), then X is the bit offset value within the unit. If the target address is not aligned with 0x10 (target address & 0xf != 0), the X | 0x1 is the bit offset value.
In this example, the target address is 0x00b01030. X has the value of six. The formula 0x00b01030 & 0xf has a result of zero; this means the bit offset is also six.
- We look at the bit identified in step #2. If the bit is equal to 1, it means the indirect call target is valid because it is a function’s starting address. If the bit is 0, it means the indirect call target is invalid because it is not one function’s starting address. If the indirect call target is valid, the function will do nothing and let it go. If the indirect call target is invalid, an exception will be raised which should prevent further exploit code from running.
Figure 7. Value in CFGBitmap
The implementation of CFG should be useful in preventing some classes of exploits. This will help reduce the threat from software vulnerabilities in the long run.
We will release a detailed analysis this in an upcoming technical report.