Control Flow Guard Improvements in Windows 10 Anniversary Update
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:
- 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.
Addressing CFG coverage gaps – Using sensitive APIs to bypass CFG
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
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:
- 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.
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.
MicrosoftEdgeCP!Spartan::util::CFG::SuppressSensitiveAPI
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)
Disable RtlRemoteCall when CFG is enabled
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
New Guard Flags
When a module is CFG enabled, its PE header will contain a Load Config structure:
Figure 5. Load Config structure
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:
- 0x00 RVA (four bytes)
- 0x04 flag_sensitive (one bytes)
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)
Eshims sets the following API calls as sensitive, making them CFG not valid.
- 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
Switching to CFG “dispatch mode” on 64-bit by default
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)
Longjmp hardening – Use setjmp/longjmp to bypass CFG
Figure 9. Longjmp exploit steps
Assume that the attacker has AAR/W capabilities, gained via a separate vulnerability. They could then attack CFG by doing the following:
- 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.
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.
Validate the jmp buffer – __guard_longjmp_table
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 of longjmp is jmp_buf. However, it will call the __except_validate_jump_buffer function to validate jmp_buf.
Figure 12. longjmp
This is the structure of jmp_buf:
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;
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:
- _except_validate_jump_buffer –> kernelbase!GuardCheckLongJumpTargetImpl —>ntdll!RtlGuardCheckLongJumpTarget
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
Summary
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.
Post from: Trendlabs Security Intelligence Blog – by Trend Micro
Control Flow Guard Improvements in Windows 10 Anniversary Update
Read more: Control Flow Guard Improvements in Windows 10 Anniversary Update
Incoming search terms