Arbitrary Code Guard (ACG) mechanism as exemplified by Microsoft Edge
- Transfer
- This publication is a translation of a part of Bypassing Mitigations by Attacking JIT Server in Microsoft Edge by Ivan Fratric (Google Project Zero). The part that describes the ACG mechanism and its application in the Microsoft Edge browser has been translated. Outside of this translation, a more detailed description of the internals of JIT in Chakra (Microsoft Edge JavaScript Engine) and attack vectors on it (with a description of the vulnerabilities found, corrected at the time of publication of the document) remained.
- By the nature of my professional activity, I am neither a technical writer, nor (moreover) a translator. But the contents of the document seemed to me very interesting in terms of studying the insides of Windows. Accordingly, I am open to constructive comments and suggestions for improving the translation.
With the release of Windows 10 Creators Update, Microsoft has begun using the new security mechanism in Microsoft Edge: Arbitrary Code Guard (ACG) . When ACG is applied to a process (in particular, the Microsoft Edge process), it becomes impossible in the target process to allocate new executable memory or to modify existing executable memory. Accordingly, the execution of arbitrary code for an attacker becomes more difficult.
To achieve better performance, modern browsers use JIT (Just-In-Time) compilation of JavaScript code, but this approach is not compatible with ACG. Therefore, the following approach was implemented in Microsoft Edge: JIT was separated into a separate process, relative to the content process (Content Process). The content process sends JavaScript bytecode to the JIT process, and the JIT process compiles it into machine code and projects that machine code back into the content process.
How ACG works in Microsoft Edge
ACG depends on the dynamic process code policy setting. This policy can be set for any Windows process by calling the SetProcessMitigationPolicy function with the ProcessDynamicCodePolicy parameter. In the Microsoft Edge content process, the call is as follows:
00 KERNELBASE!SetProcessMitigationPolicy
01 MicrosoftEdgeCP!SetProcessDynamicCodePolicy+0xc0
02 MicrosoftEdgeCP!StartContentProcess_Exe+0x164
03 MicrosoftEdgeCP!main+0xfe
04 MicrosoftEdgeCP!_main+0xa6
05 MicrosoftEdgeCP!WinMainCRTStartup+0x1b3
06 KERNEL32!BaseThreadInitThunk+0x14
07 ntdll!RtlUserThreadStart+0x21
Each Microsoft Edge content process calls this function shortly after creation. Unfortunately, since one content process can access other content processes that run in the same sandbox ( App Container ), content process A can access content process B before B activates ACG. This allows an attacker to make sure that in process B the ACG mechanism will never be activated (or simply execute arbitrary code in process B until the ACG mechanism is activated). This is an architecture issue that has not been fixed at the time of publication, and is expected to be resolved in future versions of Windows.
A dynamic code policy is not always set by the Microsoft Edge content process. Before deciding to apply this policy, the process accesses several registry entries, as shown in the figure below:

Fortunately, the Microsoft Edge content process does not have write access to any of these registry keys, so a compromised content process cannot simply disable ACG for processes that will be created in the future.
In addition, to ensure backward compatibility, Edge tries to determine if any drivers (such as graphics) that are incompatible with ACG are present before setting the dynamic code policy value. As a Microsoft blog post about ACG:
For compatibility reasons, ACG is currently only used on 64-bit machines whose main GPU is running the WDDM 2.2 driver (driver model released with the Windows 10 Anniversary Update), or when using software rendering. For experimental purposes, software rendering can be forcedly enabled using the Control Panel -> Internet Options -> "Advanced". Currently, Microsoft devices (Surface Book, Surface Pro 4, and Surface Studio) and some other systems whose GPU drivers are exactly compatible with ACG use ACG. We intend to improve the reach and accuracy of the list of supported GPUs as we evaluate telemetry and customer reviews.
This means that on many systems with older GPU drivers, the ACG mechanism will not be enabled, even if the computer is running an updated version of Windows.
To verify that the dynamic code policy is enabled for the process, you can call the PowerShell script Get-ProcessMitigation , as shown in the figure below:

The entry “AllowThreadOptOut: OFF” should be especially noted. In previous versions of Windows, this option was "ON", which allowed to exit from ACG. This, as expected, led to the fact that the mechanism was extremely inefficient.
When the Microsoft Edge content process calls SetProcessMitigationPolicy (), it also sets the AllowRemoteDowngrade flag, which allows non-AppContainer processes to disable the policy at any time. This is used when the display driver changes. Attempting to disable the dynamic code policy from the content process itself, after inclusion, will result in an ERROR_ACCESS_DENIED error .
When the dynamic code policy is enabled, as mentioned above, it becomes impossible:
- allocate new executable memory
- modify existing executable memory
This means that calls to the VirtualAlloc and VirtualProtect functions will fail if the flags in the argument flProtect / flNewProtect are related to the executable memory. This applies to all functions that can cause a similar effect, for example, MapViewOfFile. When the function fails due to ACG, it returns a new error code: 0xc0000604, STATUS_DYNAMIC_CODE_BLOCKED.
When process A tries to allocate executable memory in process B, only the policy of process A matters. That is: if process B has a dynamic code policy (ACG mechanism is enabled), and process A does not, then the call from process A will be successful, if process A will have a descriptor for process B with the appropriate rights.
Thus, the only ways to allocate executable memory in a process with the ACG mechanism enabled are:
- allocation of executable memory by another process for which the ACG mechanism is disabled
- loading dll into the process
To handle the second situation, Microsoft has provided another mechanism - CIG ( Code Integrity Guard ). With the CIG mechanism enabled, a process can only load Microsoft-signed DLL files.
How effective is ACG?
There are several approaches used by attackers in situations where it is impossible to allocate executable memory:
- If the attacker does not need to leave the current process, then a data-only attack can be performed (use only non-executable memory). For a browser, this can mean overwriting the corresponding fields to disable or trick policy checks, which is equivalent to a Universal XSS attack (Note: in Google Chrome with site isolation enabled, the implementation of such an attack is very complicated).
- Otherwise, when scripts are not available to the attacker, the only way is to reuse existing code, such as ROP. It is worth noting that an attacker cannot use ROP only to make the payload memory area executable, and then transfer control there (as is often done in exploits today). Instead, all payload should be written to the ROP. This will be a time-consuming task at present, but the large-scale use of ACG may provide an incentive for the development of automated tools that would facilitate this task.
- If there is a script execution environment, then the attacker has a third (simpler) approach. Instead of writing a payload on ROP, an attacker with a read and write primitive can use a scripting runtime that is already present in this process (for example, JavaScript in Edge). This will create an interface that will allow:
- call an arbitrary native function with arbitrary arguments from the script runtime
- pass the return value back to the script runtime
Having a library of exploitation with such capabilities, the attacker, instead of writing the payload in the native code, writes code in a scripting language, using the library for native calls where necessary. The task is greatly simplified if the exploitation library provides auxiliary methods such as malloc (), memcpy (), etc. In fact, this library is already there: pwn.js .
Instead of being able to call arbitrary native functions, an attacker might have the ability to make arbitrary system calls (syscalls).
Although from the above it may seem that ACG will not be very useful, especially in a web browser, you need to take into account that scenarios 2 and 3 assume an attacker who can capture the control flow. In other words, the attacker needs to bypass CFG (Control Flow Guard).
Currently, with a large number of well-known approaches , bypassing CFG in Windows is not difficult. However, if Microsoft can fix all the known shortcomings of CFG (and Microsoft has already shown its intention to do this), the situation may change in the next couple of years.
Thus, the successful use of ACG directly depends on the successful use of CFG and CIG ( Code Integrity Guard ). All of these mechanisms should work together to prevent malicious code from executing:
- When using CIG and ACG, but without CFG, as described above, the attacker can encode the payload as an ROP chain or abuse the script runtime to execute arbitrary code.
- Using CFG and CIG, but without ACG, an attacker can project executable memory into the current process.
- Using CFG and ACG, but without CIG, an attacker can load a malicious library into the current process.
Chakra (JIT Server)
To implement JIT in Chakra (JavaScript Engine in Microsoft Edge) with the ACG mechanism enabled, Microsoft runs the parts of Chakra that are responsible for compiling the code in a separate process, the JIT server. The main interaction between the content process and the JIT server is shown in the figure below:

With this architecture, the content process still handles all the tasks associated with running JavaScript, except for compiling (JIT'ing) scripts. When Chakra determines that the JavaScript function (or loop) should be compiled into native code (usually this happens after interpreting the same section of the script several times), instead of doing this in the current process, a JIT server is called, which is passed bytecode of the target function. Then, the JIT server compiles the bytecode and writes the resulting executable native code back to the calling process using shared memory (section object). After that, the content process can execute the resulting executable code without violating the dynamic code policy.
From the point of view of running processes, the JIT server looks the same as another content process, and even uses the same exe file: MicrosoftEdgeCP.exe. The significant difference is that the ACG mechanism is not enabled for the JIT process, which allows it to project the executable code back into the content process. In this case, only one JIT server process is launched, which serves all existing content processes.
Conclusion
ACG succeeds in achieving its immediate goal: preventing the allocation and modification of executable memory. However, due to the interdependence of CFG, ACG and CIG on the one hand, as well as the shortcomings of the current CFG implementation in Microsoft Windows on the other, ACG is not a sufficient tool to prevent advanced attacks from exiting the browser sandbox. Therefore, Microsoft must correct all known CFG flaws before ACG becomes a significant obstacle to exploits.