Control Flow Guard in Windows 8.1 and VS2015

kao

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:

  1. Project Properties|Configuration Properties|C/C++|Command Line|Additional Options -> add /d2guard4
  2. 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:
Control Flow Guard on Win 8.1

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