<img height="1" width="1" alt="" style="display:none" src="https://www.facebook.com/tr?id=619966238105738&amp;ev=PixelInitialized">

Documenting the Undocumented: Adding CFG Exceptions

TL;DR Microsoft’s Control Flow Guard (CFG) is a security feature that prevents the abuse of indirect calls from calling addresses that are not marked as safe. CFG can cause problems for anyone trying to execute malicious memory manipulations on Windows. In such cases, this can be bypassed by adding an exception to the CFG bitmap (a mapping of all the “safe” addresses). How can we add such an exception? There are actually two ways: one documented, the other undocumented. In this post, we’ll walk you through both while analyzing the undocumented syscall in depth.

 

What is Microsoft’s Control Flow Guard (CFG)?

A combination of compile and run-time support from CFG implements control flow integrity that tightly restricts where indirect call instructions can execute.

The compiler does the following:

  • Adds a lightweight security check before each indirect call in the compiled code
  • Identifies the set of functions in the application that are valid targets for indirect calls

The runtime support, provided by Windows (both kernel mode and user mode code are involved):

  • Efficiently maintains state that identifies valid indirect call targets
  • Implements the logic that verifies that an indirect call target is valid

Figure-1-CFG-From-Microsoft-Com

For those who would like to get better acquainted with CFG internals I recommend looking into the following reading material:

It Always Starts with a Crash

I was working on a completely different project (a new Windows code injection technique which I will be posting about in the coming weeks so stay tuned!) when I encountered CFG, which meant I had one more hurdle to jump over.

I was able to successfully inject code into various 3rd party applications, such as VLC and Chrome; but when I tried to inject code into mspaint.exe (on Windows 10 mspaint.exe is compiled with CFG support, while VLC and Chrome are not), the application crashed.

Figure-2-Hijacking-Remote-Thread

And this was the result:

Figure-3-MsPaint-Crash

Down the Rabbit Hole We Go

I ran it again with my debugger attached to “mspaint.exe” to see what had happened.

(2e18.3520): Security check failure or stack buffer overrun - code c0000409 (!!! second chance !!!)
eax=00000005 ebx=00547d88 ecx=0000000a edx=773182f0 esi=773182f0 edi=0017cb34
eip=7732b5a0 esp=0017ca70 ebp=0017ca98 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!RtlFailFast2:
7732b5a0 cd29            int     29h

If you’ve never seen “int 29h” I would advise you to run a quick Google search for “int 29h Windows”. You’ll find a few interesting articles that will tell you that “int 29h” leads to nt!KiRaiseSecurityCheckFailure which will not help you very much because it’s very generic.

At any rate, it looks like we failed some kind of security check (maybe stack cookies?). The next step would be to look at the call stack to see if perhaps that would give us a hint as to what was the root cause of the exception.

0:001> k
ChildEBP RetAddr 
0017ca6c 7730c7a2 ntdll!RtlFailFast2
0017ca98 7732aa88 ntdll!RtlpHandleInvalidUserCallTarget+0x73
0017cb20 77318d7b ntdll!LdrpValidateUserCallTargetBitMapRet+0x3b

Looking at this call stack, things are starting to make sense. It looks like a call was made to ntdll!LdrpValidateUserCallTargetBitMapRet.

Let’s take a look at ntdll!LdrpValidateUserCallTargetBitMapRet with IDA:

Figure-4-IDA-Functions-Windows-LdrpValidateUserCallTarget

Looking at the disassembly (after undefining these three functions and redefining them as one) we can probably infer that the call was actually made to ntdll!LdrpValidateUserCallTarget. This can also be verified by further examining the call stack – which we will do in a future post.

Figure-5-IDA-View-LdrpValidateUserCallTarget

The call to ntdll!LdrpValidateUserCallTarget led to the call to ntdll!RtlpHandleInvalidUserCallTarget.

Clearly, we did not pass CFG’s check and were therefore led to ntdll!RtlpHandleInvalidUserCallTarget.

The call to ntdll!RtlpHandleInvalidUserCallTarget led us to ntdll!RtlFailFast2.

Figure-6-IDA-View-RtlpHandleInvalidUserCallTarget-RtlFailFast2

ntdll!RtlFailFast2 is a very simple function that looks like this:

Figure-7-IDA-View-RtlFailFast2

If you are not familiar with these functions run a quick search for “LdrpValidateUserCallTarget”. It shouldn’t be too hard to understand that this is CFG’s validator function.

Right before the exception occurred the address, to which an indirect call was attempted to be made to, was stored in ESI.

0:001> u esi
ntdll!NtSetContextThread:
773182f0 b872010000   mov     eax,172h
773182f5 bab0b53277   mov     edx,offset ntdll!Wow64SystemServiceCall (7732b5b0)
773182fa ffd2         call    edx
773182fc c20800       ret     8
773182ff 90           nop

The attempted indirect call address was NtSetContextThread (the syscall behind the beloved API call: SetThreadContext). This function has been used to bypass CFG in the past and has therefore been marked as invalid.

Eureka

Now, if we look at the code surrounding the call to ntdll!RtlFailFast2 we can see a call to another function with a rather interesting name: ntdll!RtlpGuardGrantSuppressedCallAccess.

Figure-8-IDA-View-RtlpHandleInvalidUserCallTarget-RtlpGuardGrantSuppressedCallAccess

This function is a wrapper around the new ntdll!NtSetInformationVirtualMemory syscall.

Figure-9-IDA-View-RtlpGuardGrantSuppressedCallAccess

Which, if we take a quick glance at the MSDN is only partially documented.

NTSTATUS ZwSetInformationVirtualMemory(
  _In_ HANDLE                                ProcessHandle,
  _In_ VIRTUAL_MEMORY_INFORMATION_CLASS      VmInformationClass,
  _In_ ULONG_PTR                             NumberOfEntries,
  _In_ (NumberOfEntries) PMEMORY_RANGE_ENTRY VirtualAddresses,
  _In_ (VmInformationLength) PVOID           VmInformation,
  _In_ ULONG                                 VmInformationLength
);

The only VmInformationClass being referenced in the documentation is VmPrefetchInformation.

If we take a look at the definition of VIRTUAL_MEMORY_INFORMATION_CLASS:

typedef enum _VIRTUAL_MEMORY_INFORMATION_CLASS
{
    VmPrefetchInformation,
    VmPagePriorityInformation,
    VmCfgCallTargetInformation
} VIRTUAL_MEMORY_INFORMATION_CLASS;

We can tell that VmPrefetchInformation is 0x00, while ntdll!RtlpGuardGrantSuppressedCallAccess calls ntdll!NtSetInformationVirtualMemory with VmInformationClass=0x02. Obviously, VmCfgCallTargetInformation is 0x02, which makes a lot of sense.

Now if we take a closer look, the first parameter passed to ntdll!NtSetInformationVirtualMemory is ProcessHandle=0xFFFFFFFF (which is the pseudo-handle of the current process). So if we could call ntdll!NtSetInformationVirtualMemory with the handle of our target process, we should be able to add an exception to the target process’s CFG.

In order to do this we need to understand exactly what’s being passed to ntdll!NtSetInformationVirtualMemory.

Undocumented Code: The Obvious

Let’s start by defining some structs, constants, and prototypes:

#define CFG_CALL_TARGET_VALID (0x00000001)

typedef enum _VIRTUAL_MEMORY_INFORMATION_CLASS
{
       VmPrefetchInformation,
       VmPagePriorityInformation,
       VmCfgCallTargetInformation
} VIRTUAL_MEMORY_INFORMATION_CLASS;

typedef struct _MEMORY_RANGE_ENTRY
{
       PVOID  VirtualAddress;
       SIZE_T NumberOfBytes;
} MEMORY_RANGE_ENTRY, *PMEMORY_RANGE_ENTRY;

typedef struct _CFG_CALL_TARGET_INFO 
{
       ULONG_PTR    Offset;
       ULONG_PTR    Flags;
} CFG_CALL_TARGET_INFO, *PCFG_CALL_TARGET_INFO;

typedef NTSTATUS (NTAPI *_NtSetInformationVirtualMemory)(
       HANDLE                           hProcess,
       VIRTUAL_MEMORY_INFORMATION_CLASS VmInformationClass,
       ULONG_PTR                        NumberOfEntries,
       PMEMORY_RANGE_ENTRY              VirtualAddresses,
       PVOID                            VmInformation,
       ULONG                            VmInformationLength
);

Most of ntdll!NtSetInformationVirtualMemory’s parameters are fairly easy to understand:

//Initialize HANDLE hProcess with a call to OpenProcess with the PID of our target process
//Initialize MEMORY_RANGE_ENTRY tMemoryPageEntry.VirtualAddress with the AllocationBase returned by a call VirtualQuery(AddressToAddCfgExceptionTo)
//Initialize MEMORY_RANGE_ENTRY tMemoryPageEntry.NumberOfBytes with the RegionSize returned by a call VirtualQuery(AddressToAddCfgExceptionTo)

ntdll!NtSetInformationVirtualMemory(
               hProcess,
               VmCfgCallTargetInformation,
               0x1,
               &tMemoryPageEntry,
               ???,
               ???
);

Undocumented Code: The Vague

The only tricky parameter is VmInformation (and since we don’t really know what that is, we also have trouble with its size – VmInformationLength). I took the liberty of reversing the syscall and have reconstructed VM_INFORMATION (this VM_INFORMATION structure is relevant only if you pass VmInformationClass=0x02 – you have been warned).

typedef struct _VM_INFORMATION
{
       DWORD                      dwNumberOfOffsets;
       PVOID                      dwMustBeZero;
       PDWORD                     pdwOutput;
       PCFG_CALL_TARGET_INFO      ptOffsets;
} VM_INFORMATION, *PVM_INFORMATION;

For demonstration purposes we will add an exception to one address even though the syscall does support adding multiple exceptions with one call. Let’s just leave that as an exercise for our readers – it shouldn’t be very difficult.

We’ll set:

  • dwNumberOfOffsets to 0x01
  • dwMustBeZero to 0x00
  • pdwOutput to point to a DWORD on the stack. The syscall will set that DWORD to 0x00
  • ptOffsets to point to CFG_CALL_TARGET_INFO tCfgCallTargetInfo
  • tCfgCallTargetInfo.Offset to (AddressToAddCfgExceptionTo – AllocationBase) which is the same as (AddressToAddCfgExceptionTo – tMemoryPageEntry.VirtualAddress)
  • tCfgCallTargetInfo.Flags to CFG_CALL_TARGET_VALID (0x1)

Once all of our parameters have been set correctly we will make the following call:

ntdll!NtSetInformationVirtualMemory(
               hProcess,
               VmCfgCallTargetInformation,
               0x1,
               &tMemoryPageEntry,
               &tVmInformation,
               sizeof(tVmInformation) // == 0x10
);

This call will add an exception to the target process’s CFG for the target address.

The Bottom Line

To sum up, we were able to document the undocumented part of the new syscall ntdll!NtSetInformationVirtualMemory and demonstrate how one would go about using this syscall to add an exception to CFG.

You can also use the documented “KernelBase!SetProcessValidCallTargets” in order to add exceptions, but where’s the fun in that?

I’ve uploaded an example VS solution to GitHub that will add a CFG exception to “mspaint.exe”. The complete project can be found here: https://github.com/BreakingMalwareResearch/CFGExceptions

As you’ll see, the example code implements two functions:

  1. AddCfgExceptionUndocumentedApi– will add an exception by calling ntdll!NtSetInformationVirtualMemory
  2. AddCfgExceptionDocumentedApi will add an exception by calling KernelBase!SetProcessValidCallTargets

This solution has been tested against the WOW version of mspaint.exe (C:WindowsSysWOW64mspaint.exe) on Windows 10 Pro (Version 1511 Build 10586.420).

CATEGORIES

FEATURED ARTICLES

tag cloud