Arbitrary Code Guard vs. Kernel Code Injections

21 May Read more blog posts

Arbitrary Code Blocked

A common means of attack used by the WannaCry and Slingshot malware, among others, is through kernel code injections. The recently released Windows 10 Creators Update introduces new techniques for mitigating against remote code execution, of which, one to pay attention to is the Arbitrary Code Guard – an update on the Dynamic Code Restrictions mitigation. In this blog post, we will show how the Arbitrary Code Guard works and test its effectiveness against kernel code injections.

Arbitrary Code Guard

The Arbitrary Code Guard (ACG) is an optional mitigation added to the Windows OS that tries to detect and prevent:

1- Existing code from being modified (code pages cannot become writable). 2- Writing code on a data segment and executing it (data cannot become code).

To achieve these objectives, the ACG enforces the following rule:

• Memory cannot have both Write (W) and Execute (X) attributes (this will henceforth be referred to as the W^X prohibition).

By combining the ACG with Code Integrity Guard (only properly signed images can be loaded), Microsoft tries to prevent threat actors from loading unsafe or unreliable code into memory.

Below is an example code injection which changes a process’s memory to the state that the ACG tries to avoid:

Click to enlarge image

As we can see, there are pages with both execute and write attributes where the code has been injected.

Arbitrary Code Guard workflow:

When a mitigation is created on a specific process a registry key is added to the following registry path:

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\
CurrentVersion\Image File Execution Options

Then, Windows mitigations are set during process creation. This is the call stack for new process creation:

nt!PspAllocateProcess+0xb4b
nt!NtCreateUserProcess+0x723
nt!KiSystemServiceCopyEnd+0x13
ntdll!NtCreateUserProcess+0x14
KERNELBASE!CreateProcessInternalW+0x1b3f
KERNELBASE!CreateProcessW

Let’s look at some code from the PspAllocateProcess function:

Psp Allocate Process

In the above code there are some functions in charge of loading mitigation options:

PspReadIFEOMitigationOptions
PspReadIFEOMitigationAuditOptions

These functions read the following key from the registry:

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\
CurrentVersion\Image File Execution Options

We can see this action in the Process Monitor:

Process monitor

Next, the MitigationsFlagsValues are saved in the EPROCESS structure (in our example, the ACG has been activated):

eprocess

Where does the ACG check and block dynamic code?

As has been pointed out, the ACG monitors memory allocations to enforce the W^X prohibition, so let’s see the call stack when we want to allocate virtual memory:

nt!MiArbitraryCodeBlocked+0x30
nt!MiAllocateVirtualMemory+0x96d
nt!NtAllocateVirtualMemory+0x44
nt!KiSystemServiceCopyEnd+0x13
ntdll+0xa05c4

A function called MiArbitraryCodeBlocked gets called – let’s see what it does:

Mi Arbitrary Code Blocked

As can be seen, this function is in charge of checking the mitigation options in the EPROCESS structure and either allowing or disallowing virtual memory allocations. Let’s see a flow diagram to better understand this function:

Mi Arbitrary Code Clocked flow

This code does the following three things:

1-Get EPROCESS and check if mitigation options are enabled. MitigationsFlags are located at the 0x828 offset in EPROCESS.

2-Check CrossThreadFlags on ETHREAD located at 0x6d0 in order to know if the thread has a special permission to allocate the memory and bypass the mitigation.

3-Trace the result of the mitigation and return status 0xC0000604 (STATUS_DYNAMIC_CODE_BLOCKED).

Kernel code injection

Now let’s see how the ACG behaves when we try to inject code from the kernel. We are going to use two kinds of kernel code injections commonly used by malware:

1-Create a new thread and load a dynamic-link library (DLL).
2-Use an Asynchronous Procedure Call (APC) to load a DLL in an existing thread.

In order to understand how the ACG works with these code injections, let’s review the necessary steps for injecting code into a user-land process for both injection methods:

1-Attach to the target process.
2-Get the Ntdll address.
3-Get the LdrLoadDll address.
4-Allocate virtual memory for the shellcode.
5-Call LdrLoadDll on the shellcode.

When injecting the shellcode using a new thread we call LdrLoadDll on the shellcode using NtCreateThreadEx. On the other hand, if we want to inject the shellcode using an APC we need to call LdrLoadDll on the shellcode using APC functions on a suitable thread.

In both injection methods we need to allocate virtual memory with both write and execute attributes to execute the shellcode. As mentioned before, the ACG shouldn’t allow execute this code injection. Let’s see what the result will be:

This is the code for allocating virtual memory:

Allocate Virtual Memory

If we set a breakpoint on MiArbitraryCodeBlocked after the call to ZwAllocateVirtualMemory, we can see the following return value on windbg:

Mi Arbitrary Code Bl

The return value is STATUS_DYNAMIC_CODE_BLOCKED. Neither of the aforementioned two injection code techniques can bypass the mitigation, since both try to allocate both executable and writable memory (in violation of the W^X prohibition) - exactly what the ACG blocks.

Conclusion

The ACG is a good mitigation technique for avoiding code injections from userland or the kernel. The problem is that this mitigation is optional and malware will be able to perform code injections if the ACG hasn’t been enabled on the target process.

Thank you for taking the time to read this post!

References

Windows 10 Mitigation Improvements
Matt Miller and David Weston’s presentation at Black Hat USA 2016.

The “Bird” That Killed Arbitrary Code Guard
Alex Ionescu’s presentation at Ekoparty Security Conference 2017.

Author: Alonso Candado, Security Software Engineer at CounterCraft

Like Jim Morrison said, this is the end. But you can...