Gaps in CFG coverage were addressed. Many sensitive functions were previously marked as "CFG valid" and could be used to bypass the exploit mitigation. The Anniversary Update made these functions "CFG not valid".

Switched to CFG "dispatch mode" on 64-bit systems by default.

Hardened longjmp. An attacker can use setjmp/longjmp to access stack information and overwrite the return address to bypass CFG. The Anniversary Update includes additional checks to resolve the issue.

Control Flow Guard (CFG) is an exploit mitigation feature that Microsoft introduced in Windows 10 and Windows 8.1 Update 3 that makes it significantly harder for exploits to run code on systems running these operating systems. This year’s major Windows 10 update (called the Anniversary Update) introduced improvements to CFG. The Anniversary Update began its rollout to most users in August 2016 , although it may not be finished deploying to all users until this coming November . No mitigation method is perfect—researchers have found multiple ways to bypass CFG since its initial release in November 2014—but mitigations are designed to reduce the effectivity of these attacks against CFG. The Anniversary Update improved CFG mitigation in three aspects:Let's look at how CFG is implemented. In Microsoft Edge, there are many sensitive APIs that can bypass CFG. The following explains how an exploit would exploit these sensitive APIs:

Figure 1. Steps to exploitation

Use the AAR/W to call VirtualProtect (or VirtualAlloc) out of context , get a page memory location where the protect attribute is PAGE_EXECUTE_READWRITE

Use the AAR/W code to copy shellcode to the page.

Call the page address out of context.

If an attacker already has a vulnerability that can carry out arbitrary address read and write (AAR/W) into memory, he can carry out the following:This is just an example; there are many other sensitive APIs can bypass CFG. So Microsoft did three things to set the sensitive APIs as CFG not valid.When the Microsoft Edge rendering process MicrosoftEdgeCP.exe starts up, it will call MicrosoftEdgeCP!Spartan::util::CFG::SuppressSensitiveAPI functions. The MicrosoftEdgeCP.exe module stores the Sensitive API name strings (see Figure 2), and SuppressSensitiveAPI get the Sensitive API addresses by their name strings. It uses the address as a parameter to call SetProcessValidCallTargets() with the CFG_CALL_TARGET_INFO.Flags parameter set to 0. This will set the Sensitive API CFG as not valid.

Figure 2. Sensitive API Names (Click to enlarge)

The RtlRemoteCall function is CFG valid, so an attacker can call it out of context. RtlRemoteCall will call the LdrControlFlowGuardEnforced function (see Figure 3), which will check the ntdll!LdrSystemDllInitBlock offset 0x60 value, and it restores the CFGBitmap address (see Figure 4). If the value not zero, the module is CFG enabled, RtlRemoteCall will do nothing and return 0xC0000002U.

Figure 3. RtlRemoteCall

Figure 4. CFG enable check

When a module is CFG enabled, its PE header will contain a Load Config structure:

Figure 5. Load Config structure

0x00 RVA (four bytes)

0x04 flag_sensitive (one bytes)

If GuardFlags is 0x13500h, the __guard_fids_table will be an array with each element a Relative Virtual Address(RVA). The Windows 10 Anniversary Update adds a new value for GuardFlags - 0x10013500h.. In this case, the ___guard_fids_table is an array, with each element a five-byte structure:The first 4 bytes is the RVA. The fifth byte is a flag, which we will call flag_sensitive. If this flag is set to zero, the RVA address's corresponding bit in CFGBitmap will be set to 1. If the flag_sensitive is set to 1, the RVA address's corresponding bit in CFGBitmap will be set to 0. This sets the function located in the RVA to be CFG not valid. The following is the Eshims module. The guard flags are at 0x10013500.

Figure 6. New Guard Flags

The __guard_fids_table looks like the following:

Figure 7. Eshims __guard_fids_table (Click to enlarge)

0x100050f0 NS_ACGLockdownTelemetry::APIHook_VirtualAlloc

0x10005160 NS_ACGLockdownTelemetry::APIHook_VirtualAllocEx

0x100051d0 NS_ACGLockdownTelemetry::APIHook_VirtualProtect

0x10005240 NS_ACGLockdownTelemetry::APIHook_VirtualProtectEx

0x100052b0 NS_ACGLockdownTelemetry::APIHook_MapViewOfFile

0x10005320 NS_ACGLockdownTelemetry::APIHook_MapViewOfFileEx

0x10005390 NS_ACGLockdownTelemetry::APIHook_MapViewOfFileExNuma

0x10005400 NS_ACGLockdownTelemetry::APIHook_WriteProcessMemory

0x10005490 NS_ACGLockdownTelemetry::APIHook_SetProcessValidCallTargets

0x10008ef0 NS_FlashOOPAbandonOnOOM::APIHook_VirtualAlloc

0x10008f40 NS_FlashOOPAbandonOnOOM::APIHook_VirtualAllocEx

0x10008f90 NS_FlashOOPAbandonOnOOM::APIHook_VirtualAllocExNuma

Eshims sets the following API calls as sensitive, making them CFG not valid.In the Windows 10 Anniversary Update, for 64-bit processes the CFG check function now uses ntdll!LdrpDispatchUserCallTarget by default.

Figure 8. Check Mode versus Dispatch Mode (from Data Driven Software Security)

Figure 9. Longjmp exploit steps

Prepare jumpbuf memory.

Use jumpbuf as the parameter, use the AAR/W vulnerability to call setjmp out of context.

Read the jumpbuf -> ebp value. The address of the return address is calculated based on the value of ebp and modifies it to the shellcode address.

Using jumpbuf as parameter, the AAR/W vuln calls longjmp out of context, program will run to jumpbuf->eip. When it returns, it will run the shellcode, bypassing CFG.

Assume that the attacker has AAR/W capabilities, gained via a separate vulnerability. They could then attack CFG by doing the following:In the Anniversary Update, Microsoft addressed this issue in two ways: first, in the msvcrt module’s ___guard_fids_tables set longjmp function is sensitive, specifically in MicrosoftEdgeCP!Spartan::util::CFG::SuppressSensitiveAPI . The function set setjmp CFG is not valid. Secondly, a ___guard_longjmp_table was added to the module PE header. When longjmp is called, it will also call __except_validate_jump_buffer to validate the jmp buffer.To validate the jmp buffer, the module PE headers add the __guard_longjmp_table field. This is an array that records the longjmp target address’s RVA value. The following is the chakra.dll __guard_longjmp_table, which contains only one RVA address. Here, setjmp3 is called once.

Figure 10. guard_longjmp_table

The following is the code call for setjmp3.longjmp. The target address is 0x10124050,the imagebase is 0x10000000, so the RVA is 0x124050.

Figure 11. call setjmp3

The first parameter ofis jmp_buf. However, it will call the __except_validate_jump_buffer function to validate jmp_buf.

Figure 12. longjmp

typedef struct __JUMP_BUFFER { unsigned long Ebp; unsigned long Ebx; unsigned long Edi; unsigned long Esi; unsigned long Esp; unsigned long Eip; unsigned long Registration; unsigned long TryLevel; unsigned long Cookie; unsigned long UnwindFunc; unsigned long UnwindData[6]; } _JUMP_BUFFER;

_except_validate_jump_buffer --> kernelbase!GuardCheckLongJumpTargetImpl --->ntdll!RtlGuardCheckLongJumpTarget

This is the structure of jmp_buf:In __except_validate_jump_buffer, the code first checks the pJumpBuff -> esp, if esp not in thread stack, it throws an exception. It then checks if in the GuardCheckLongJumpTargetImpl in the MicrosoftEdgeCp.exe process, the msvcrt!GuardCheckLongJumpTargetImpl points to kernelbase!GuardCheckLongJumpTargetImpl. This function will then call ntdll!RtlGuardCheckLongJumpTarget to do the real check. _except_validate_jump_buffer will call stack like following:

Figure 13.__except_validate_jump_buffer

RtlGuardCheckLongJumpTarget checks whether the longjmp target address is in the module. If not, it will call RtlQueryProtectedPolicy for further checks. If it is, it will call RtlImageDirectoryEntryToData get the __guard_longjmp_table information, and call bsearch_s to check if the longjmp target RVA is in the __guard_longjmp_table. If it's in the table, it is valid.

Figure 14. RtlGuardCheckLongJumpTarget

In the MicrosotEdgeCp.exe process, RtlpProtectedPolices is null. This means that RtlQueryProtectedPolicy returns 0xC0000225u. This does not pass validation, and an exception is thrown.

Figure 15. RtlpProtectedPolicies is null

Microsoft has done their best to try and improve CFG mitigation, with improvements that will make it harder to execute arbitrary code. Currently, there is no known method for bypassing CFG, so vulnerabilities that attack specific flaws in CFG implementation will become increasingly important. We expect vulnerability researchers to continue to search for new ways to try and bypass any mitigations put in place by Microsoft. For users and system administrators, this highlights an excellent reason to upgrade to the newest versions of software: not only do these versions fix known security flaws, but they also introduce new techniques for hardening code and preventing any vulnerabilities from being exploited in the first place.