Arbitrary Code Guard

Benoît Sevens
4 min readMay 1, 2019

--

Arbitrary Code Guard (ACG) is one of the many exploit mitigations in Windows 10. Basically it prevents a process from doing 2 things:

  • Allocating new executable memory (without an image file backing it)
  • Modifying any existing executable memory by writing to it.

Microsoft Edge for example applies this mitigation to all of its processes to make browser exploitation harder.

Let’s take a look at it.

Enabling ACG

ACG is enabled on a per-process basis, based on the process name. It can be enabled in an administrative PowerShell prompt:

PS C:\WINDOWS\system32> Set-ProcessMitigation -name bash.exe -enable BlockDynamicCode

This command creates the necessary registry keys and values in HKLM (you can verify this with Process Monitor):

We can verify the correct implementation with the following command:

PS C:\WINDOWS\system32> Get-ProcessMitigation -name bash.exe

The field DynamicCode contains the ACG setting. Note that you can also enable ACG in audit mode.

A second possibility (instead of the registry) is that the process calls the SetProcessMitigationPolicy API function during process initialization. We will see an example code snippet further below. This is how Edge enabled ACG.

Process Hacker

Process Hacker can show you per process the mitigation policies. For ACG-enabled processes, this contains “Dynamic code prohibited”.

How does Process Hacker get this information? The answer is in the source file procmtgn.c, where it calls NtQueryInformationProcess with the undocumented value ProcessMitigationPolicy for ProcessInformationClass. Calling this API fills in a structure called PROCESS_MITIGATION_POLICY_INFORMATION with different process mitigation informations.

Windows itself stores this information when the process starts up in the EPROCESS.

Let’s check for ourselves

We can write a little program to test all of this for ourselves and play a bit with ACG:

This program will do the 2 checks mentioned in the introduction to see if ACG is enabled:

  • Try to allocate new executable memory
  • Try to make existing executable memory writable

Based on the output of these tests, it will conclude if ACG is active or not. If it is not active, it will try to activate it with the SetProcessMitigationPolicy() API call. It will then rerun the tests.

You can build this with Visual Studio. If we run this without ACG enabled in the registry, it outputs:

*** Arbitrary Code Guard check ***[*] Check BEFORE SetProcessMitigationPolicy()
[*] TEST 1: Trying to allocate new RX memory...
[-] TEST 1 RESULTS: ACG is inactive. Memory address allocated: 0x008f0000.
[*] TEST 2: Trying to make existing RX memory Writable
[-] TEST 2 RESULTS: ACG is inactive. Memory is now RWX.
[-] ACG is not enabled by the registry.
[*] Calling SetProcessMitigationPolicy()...[*] Check AFTER SetProcessMitigationPolicy()
[*] TEST 1: Trying to allocate new RX memory...
[+] TEST 1 RESULTS: ACG is active. Error code returned: 0x00000677.
[*] TEST 2: Trying to make existing RX memory Writable
[+] TEST 2 RESULTS: ACG is active. Error code returned: 0x00000677.

What is this error code? Let’s check in the header files (you need to have an SDK installed for this):

(1655 is 0x00000677 in decimal)

So this error is basically ERROR_DYNAMIC_CODE_BLOCKED.

Now we activate ACG in the registry:

PS C:\WINDOWS\system32> Set-ProcessMitigation -name ACG.exe -enable BlockDynamicCode

If we run our program again:

*** Arbitrary Code Guard check ***[*] Check BEFORE SetProcessMitigationPolicy()
[*] TEST 1: Trying to allocate new RX memory...
[+] TEST 1 RESULTS: ACG is active. Error code returned: 0x00000677.
[*] TEST 2: Trying to make existing RX memory Writable
[+] TEST 2 RESULTS: ACG is active. Error code returned: 0x00000677.
[+] ACG is enabled by the registry.

Exploit mitigation

Now what does the exploit mitigation precisely do and how does it make life harder (or more interesting) for attackers?

Exploits will often write shellcode somewhere in memory. This memory should of course be writable, otherwise the exploit would not be able to write to it.

Because of DEP (which is the norm nowadays), this memory will often not be executable. Exploits found a solution to this: they ROP. Basically they use existing pieces of executable memory to execute their payload. But ROP’ing is hard, so they will only ROP the strict minimum required. An often applied method is to change the permissions on the memory containing the shellcode (like e.g. with a VirtualProtect API call).

If you now call VirtualProtect on this memory to make it executable, the API call will fail. This makes the exploitation process harder. Now you have to ROP your complete payload.

But what about JIT?

As said before, Edge applies ACG to all of its processes. However, as you may know, modern browsers need JIT’ed code for their JavaScript engines. JIT’ed code is by definition compiled and written on the fly (and should be executable, because it is code). So, JIT is incompatible with ACG.

Edge solves this by creating a separate process for the JIT’ed code (also called MicrosoftEdgeCP.exe). The memory containing the JIT’ed code is mapped as shared memory between the 2 processes with different permissions:

  • The JIT engine maps it as READ_WRITE (it needs to generate the code)
  • The content process maps it as READ_EXECUTE (it needs to execute the code)

You can read more on this here.

Bypasses

Although it does make the exploitation process harder, security researchers have found ways to bypass this mitigation.

Ivan Fratric of Google Project Zero provided extensive research in this domain, which is an amazing read. You can find it here.

--

--