Figure 1. Code of test applicationLet’s take a look at what the encircled code compiles into if CFG is not enabled.
Figure 2. Assembly code of test programIn 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 callMicrosoft'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 enabledBefore 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 addressThe 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