In this article, we are going to look at Control Flow Guards, how they work and what are the common weaknesses.
Control Flow Guard (CFG) is a new layer of defense against memory corruption vulnerabilities such as buffer/heap overflows. It works by blocking execution flow redirection to unauthorized memory locations.
It was first introduced in Microsoft Visual Studio 2015 and runs only on "CFG-Aware" versions of Windows - the x86 and x64 releases for Desktop and Server of Windows 10 and Windows 8.1 with KB3000850 update. Which means CFG must be supported by both the compiler and the operating system in order for it to work.
The Windows Defender Exploit Guard only enables CFG for images compiled and linked with
/guard:cf flag (which is off by default). It's important to note that, just like with ASLR, only the code compiled with the
/guard:cf has CFG protection. If the software uses modules compiled wihout CFG, those modules are not protected.
/guard:cf is set, the compiler and linker insert extra runtime security checks to detect attempts to compromise your code.
During compiling and linking, all indirect calls in the code are analyzed to find all locations that the code can reach when it runs correctly. This information is stored in extra structures in the headers of the EXEs and DLLs. The compiler also injects runtime checks before every indirect call in the code that ensures the target function is allowed to be called. If the check fails at runtime on a CFG-aware operating system, the operating system closes the program.
Let's say we are trying to execute the following indirect call.
mov esi, [esi] call esi
esi contains the address of our shellcode, so we load the pointer to the shellcode back into
esi and call it.
The goal of Microsoft's CFGs is to prevent this construct from being exploited by calling a malicious target. Specifically, if
[esi] contains some intended program functionality instead of a shellcode, it should be allowed to execute, but not otherwise.
If we look at the same code compiled with CFG, the compiler will append a couple of extra instructions.
mov esi, [esi] mov esx, esi push 1 call @_guard_check_icall@4 call esi
Before the indirect call is executed, its address is passed to
_guard_check_icall function. If the operating system doesn't support CFG, nothing will happen, however, if it does, NT loader will replce the
_guard_check_icall placeholder to redirect the execution to
ntdll!LdrpValidateUserCallTarget , which will perform CFG-related check.
At the compile time, the compiler generated a whitelist of valid CFG functions within the given binary. This list is stored in the Load Configuration data directory of the PE header, specifically:
ULONGLONG GuardCFCheckFunctionPointer; ULONGLONG GuardCFDispatchFunctionPointer; ULONGLONG GuardCFFunctionTable; ULONGLONG GuardCFFunctionCount; DWORD GuardFlags; ULONGLONG GuardAddressTakenIatEntryTable; ULONGLONG GuardAddressTakenIatEntryCount; ULONGLONG GuardLongJumpTargetTable; ULONGLONG GuardLongJumpTargetCount;
There are 3 tables, each containing an entry to the relative virtual address (RVA) concatinated with optional flags (see below).
GuardCFFunctionTable was the first mitigation implemented. It contains the pointer to list of functions’ RVAs which the application’s code contains. It protects all the indirect calls in userland as well as in kernel mode. If the flag is set to 0, then each entry in the table will be a
SuppressedCall. These are legitimate functions that will cause the application to crash if called indirectly from a not allowed source. Here is the sample of this list:
GuardCFFunctionCount the list count of function’s RVA.
GuardAddressTakenIatEntryTable is user by drivers for kernel-side protection.
CFGuardLongJumpTarget contains security checks for the MS CRT (
libcmt, etc) non-local goto functionality using
longjmp. Instead, on a longjmp call, the process will call
kernelbase!GuardCheckLongJumpTargetImpl which check that the target stack pointer
(*sp) is actually located in the thread stack space.
GuardCFCheckFunctionPointer is a pointer to the address of
GuardFlags are defined as follows:
Module performs control flow integrity checks using system-supplied support
Module performs control flow and write integrity checks
Module contains valid control flow target metadata
Module does not make use of the /GS security cookie
Module supports read only delay load IAT
Delayload import table in its own
.didatsection (with nothing else in it) that can be freely reprotected
Module contains suppressed export information
Module enables suppression of exports
Module contains longjmp target information
When a function is being called,
ntdll!LdrpValidateUserCallTarget looks into the tables (also called
CFGBitmaps in MicroTrend article). This table contains the starting locations of all the functions in the process space with the 8byte to a bit mapping. For example, if
0x00f0f030 as an argument, the corresponding bit mapping will be calculated as follows:
00000000 11110000 11110000 00110000 in binary. The highest 3 bytes
00000000 11110000 11110000 (
0x7878) is the offset for CFGBitmap. Therefore, the pointer to a four byte unit in CFGBitmap is the base address of CFGBitmap plus
The offset within the table is calculated by looking at the high 5 bits of the last byte. In our case, they are
00110 , let's call this value
target address & 0xf == 0, then the offset is
target address & 0xf != 0, then the offset is
X | 0x1
Since in our example
0x00f0f030 & 0xf == 0, then the bit is
00110b = 0x6 = 6.
If our value in CFG looks like this:
00000000 00000000 00100000 01000000, hence the 6th lowest bit will be one and the function will be allowed to execute:
Bypasses of CFG
The CFGBitmap space’s base address is stored in a fixed address which can be retrieved from user mode code. This was described in the implementation of CFG. This is important, security data but however, it can be easily gotten.
Non-CFG modules are a problem
If the main executable is not enabled for CFG, the process is not protected by CFG even if it loaded a CFG-enabled module.
If a process’s main executable has disabled DEP, it will bypass the CFG violation handle, even if the indirect call target address is invalid.
Every bit in the CFGBitmap represents eight bytes in the process space. So if an invalid target call address has less than eight bytes from the valid function address, the CFG will think the target call address is “valid.”
JIT generated code is not protected by CFG. This is because
NtAllocVirtualMemory will set all “1” in CFGBitmap for allocated executable virtual memory space. It’s possible that customizing the CFGBitmap via
MiCfgMarkValidEntries can address this issue.
You can get a pretty good idea of CFG's shortcomings by looking at the Microsoft's bug bounty schedule. The following CFG attacks are out of scope:
- Hijacking control flow viare turn address corruption
- Bypasses related to limitations of coarse-grained CFI (e.g. calling functions out of context)
- Leveraging non-CFG images
- Bypasses that rely on modifying or corrupting read-only memory
- Bypasses that rely on CONTEXT record corruption
- Bypasses that rely on race conditions or exception handling
- Bypasses that rely on thread suspension
- Instances of missing CFG instrumentation prior to an indirect call
- Code replacement attacks
And only this one is in scope:
Techniques that make it possible to gain control of the instruction pointer through an indirect call in a process that has enabled CFG.
This means the general trend to bypassing CFG is not engage it at all. Quoting Sun Tzu: "The supreme art of war is to subdue the enemy without fighting."
Just like with ASLR+DEP, the CFG measures are making the life of exploit writers more complicated, and, at the same time, motivating to evolve. ASLR+DEP have us ROPs and Heap Spraying, CFG will bring something of its own. And since CFG relies on both the compiler and the OS to operate, it makes it very challenging to port CFG onto Unix environment, which gives Microsoft some competitive advantage (you can learn more on CFG in Unix from Hanno Böck's presentation
Part 1 - Windows Defender Exploit Guard for Pentesters - Validate Exception Chains (SEHOP)
Part 2 - Windows Defender Exploit Guard for Pentesters - ASLR
Part 3 - Windows Defender Exploit Guard for Pentesters - DEP
Part 4 - Windows Defender Exploit Guard for Pentesters - CFG
Part 5 - Windows Defender Exploit Guard for Pentesters - CIG & ACG