Introduction
Control Flow Guard (CFG) is less-known security feature in Microsoft Windows 8.1+. It is designed to prevent exploitation of indirect calls in executables.
For CFG protection to be effective, it must be enabled in both compiler/linker and operating system.
As for operating systems, Windows 8.1 Update 3 (released in November) and Windows 10 have enabled CFG. Older versions of Win8.1 had support for CFG, but it was disabled.
Currently, none of officially released versions of Visual Studio have support for CFG. In the preview version of VS2015, you can enable it manually by:
- Project Properties|Configuration Properties|C/C++|Command Line|Additional Options -> add /d2guard4
- Project Properties|Configuration Properties|Linker|Command Line|Additional Options -> add /guard:cf
Then build your project and you're done.
How it works
When compiler is about to emit indirect call like
call esi
it checks if Control Flow Guard is enabled. If it is, the following sequence is emitted instead:
mov ecx, esi ; Target call @_guard_check_icall@4 ; _guard_check_icall(x) call esi
When linker generates executable with enabled Control Flow Guard, it creates special entries in LoadConfig table.
.rdata:00418B70 __load_config_used .rdata:00418B70 dd 5Ch ; Size .rdata:00418B74 dd 0 ; Time stamp .rdata:00418B78 dw 2 dup(0) ; Version: 0.0 .rdata:00418B7C dd 0 ; GlobalFlagsClear .rdata:00418B80 dd 0 ; GlobalFlagsSet .rdata:00418B84 dd 0 ; CriticalSectionDefaultTimeout .rdata:00418B88 dd 0 ; DeCommitFreeBlockThreshold .rdata:00418B8C dd 0 ; DeCommitTotalFreeThreshold .rdata:00418B90 dd 0 ; LockPrefixTable .rdata:00418B94 dd 0 ; MaximumAllocationSize .rdata:00418B98 dd 0 ; VirtualMemoryThreshold .rdata:00418B9C dd 0 ; ProcessAffinityMask .rdata:00418BA0 dd 0 ; ProcessHeapFlags .rdata:00418BA4 dw 0 ; CSDVersion .rdata:00418BA6 dw 0 ; Reserved1 .rdata:00418BA8 dd 0 ; EditList .rdata:00418BAC dd offset ___security_cookie ; SecurityCookie .rdata:00418BB0 dd offset ___safe_se_handler_table ; SEHandlerTable .rdata:00418BB4 dd 3 ; SEHandlerCount .rdata:00418BB8 dd offset ___guard_check_icall_fptr ; GuardCFCheckFunctionPointer .rdata:00418BBC dd 0 ; Reserved2 .rdata:00418BC0 dd offset ___guard_fids_table ; GuardCFFunctionTable .rdata:00418BC4 dd 2Bh ; GuardCFFunctionCount .rdata:00418BC8 dd 3500h ; GuardFlags
BTW, IDA 6.6+ correctly analyzes LoadConfig tables. 🙂
___guard_fids_table is array of all functions defined in the EXE:
.rdata:0041214C ___guard_fids_table .rdata:0041214C dd rva _dynamic_initializer_for____badioinfo__ ; DATA XREF: .rdata:00418BC0o .rdata:00412150 dd rva ?foo@@YAXH@Z ; foo(int) .rdata:00412154 dd rva ?bar@@YAXH@Z ; bar(int) .rdata:00412158 dd rva sub_401100 .rdata:0041215C dd rva sub_401190 .rdata:00412160 dd rva _mainCRTStartup ...
___guard_check_icall_fptr is a pointer to GuardCFCheckFunction. By default it points to simple "retn" instruction.
.rdata:00412104 ___guard_check_icall_fptr dd offset nullsub
So, this executable will run without any problems on older OS.
But when a newer OS loads such executable, it will overwrite pointer to GuardCFCheckFunction with pointer to ntdll!LdrpValidateUserCallTarget:
Now every indirect call from this module will be checked by LdrpValidateUserCallTarget before execution. If called address is not present in GuardCFFunctionTable, OS will terminate process with:
Exception C0000409 (STACK_BUFFER_OVERRUN) - application was unable to process exception
What does it mean for reversers
As mj0011 said on Twitter
Control Flow Guard is awesome! Wish it could completely enable in RTM.@NTarakanov @JohnLaTwC @j00ru @WTFuzz
Maybe I'm not that excited but still think that CFG is a very useful feature.
List of all procedures in EXE
You don't need to guess anymore if this is code or data, EXE comes with a nice list of all functions.
One breakpoint to rule them all
No more guessing which bloody indirect call is the one you're interested in. Just put breakpoint on GuardCFCheckFunction and any indirect call will break. You even have an option to break on all indirect calls in EXE (putting breakpoint in EXE) or all modules (putting breakpoint in NTDLL). Hell yeah!
Packing your executable can break CFG
If your protector does not support CFG properly, your EXE will not work at all. Or it will work, but will not benefit from CFG. For example, packing my test EXE with standard UPX 3.91 produces an EXE file that will work in all OS until Win7, but will not run on Win8/Win10.
Simple workaround is to zero out LoadConfiguration directory RVA/Size in PE header, or to remove IMAGE_DLL_CHARACTERISTICS_GUARD_CF flag (value: 0x4000) from DllCharacteristics field in PE Optional Header.
Currently I'm not aware of any common protector that would correctly support CFG.
Pay more attention with patching EXE
If you're hooking some functions by overwriting entries in vtable (quite a few game cheats do that), you must pay special attention to CFG. You can either update GuardCFFunctionTable or remove CFG support from the patched module using one of the methods mentioned above.
Further reading
Visual Studio 2015 Preview: Work-in-Progress Security Feature
Exploring Control Flow Guard in Windows 10
Windows New Security Features - Control Flow Guard
How Control Flow Guard Drastically Caused Windows 8.1 Address Space and Behavior Changes
http://deroko.phearless.org/cfgicall.txt
Control Flow Guard enabled file for testing: https://www.mediafire.com/?3dbg7wyihbyn8pj