Disclaimer- This publication is a translation of a part of the “Bypassing Mitigations by Attacking JIT Server in Microsoft Edge” document by Ivan Fratric (Google Project Zero). The part that contains the description of the ACG mechanism and its application in the Microsoft Edge browser is translated. Beyond this translation, there is a more detailed description of the internals of JIT in Chakra (Microsoft Edge JavaScript Engine) and attack vectors for it (with a description of the vulnerabilities found by the time of publication of the document).
- By the nature of my professional activity, I am neither a technical writer, nor (even more so) 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 began using a new security mechanism in Microsoft Edge:
Arbitrary Code Guard (ACG) . When ACG is applied to a process (particularly in the Microsoft Edge process), it becomes impossible in the target process to allocate new executable memory or modify existing executable memory. Accordingly, the execution of arbitrary code for the attacker becomes more difficult.
To achieve better performance, modern browsers use JIT compile (Just-In-Time) 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. The content process sends the JIT bytecode to the JIT process, and the JIT process compiles it into machine code and projects this machine code back into the content process.
How does ACG work in Microsoft Edge
ACG depends on the setting of the dynamic process code policy. This policy can be set for any Windows process by calling the
SetProcessMitigationPolicy function with the ProcessDynamicCodePolicy parameter. As part of 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 process of Microsoft Edge content 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 do so in process B, the ACG mechanism will never be activated (or simply execute arbitrary code in process B before the ACG mechanism is activated). This
is an architecture problem that is
not fixed at the time of publication of the document, and it is expected that the problem will be solved in future versions of Windows.
')
The dynamic code policy is not always established by the Microsoft Edge content process. Before deciding to apply this policy, the process refers to several entries in the registry, as shown in the figure below:

Fortunately, the Microsoft Edge content process does not have write access to any of these registry keys, so the compromised content process cannot simply disable ACG for processes that will be created in the future.
In addition, to ensure backward compatibility, before setting the dynamic code policy value, Edge tries to determine if there are any drivers (for example, graphics) that are incompatible with ACG. As
Microsoft ’s ACG
blog says:
For compatibility reasons, ACG is currently used only on 64-bit machines with the main GPU running under the control of the WDDM 2.2 driver (driver model released with 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". At the current time, on Microsoft devices (Surface Book, Surface Pro 4 and Surface Studio) and some other systems, the GPU drivers of which are exactly compatible with ACG, ACG is used. We intend to improve the coverage and accuracy of the list of supported GPUs, since we value telemetry and customer feedback.
This means that on many systems with old 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 a process, you can call the
Get-ProcessMitigation PowerShell script, as shown in the figure below:

Of particular note is the “AllowThreadOptOut: OFF” entry. In previous versions of Windows, this parameter was “ON”, which made it possible to exit from under 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 enabling, 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
- change existing executable memory
This means that calls to
VirtualAlloc and
VirtualProtect will fail with an error if the flags in the flProtect / flNewProtect argument are related to executable memory. This applies to all functions that can cause a similar effect, for example - MapViewOfFile. When the function fails with ACG, it returns a new error code: 0xc0000604, STATUS_DYNAMIC_CODE_BLOCKED.
When process A attempts to allocate executable memory in process B, only policy A. process is important. That is: if process B is set to dynamic code policy (ACG is enabled) and process A is not, then the call from process A will be successful, if process A will have a handle to 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
- DLL loading into process
To handle the second situation, Microsoft has provided another mechanism - CIG (
Code Integrity Guard ). With the CIG mechanism enabled, the process can load only Microsoft-signed .dll files.
How effective is ACG?
There are several approaches used by attackers in a situation where it is impossible to allocate executable memory:
- If the attacker does not need to exit the current process, then you can perform a data-only attack (use only non-executable memory). Applied to the browser, this may mean overwriting the relevant fields to disable or deceive policy checks, which is equivalent to the Universal XSS attack (Note: the implementation of such an attack on Google Chrome with site isolation turned on is very complicated).
- Otherwise, when scripts are not available to an attacker, the only way is to reuse existing code, such as ROP. It should be noted that an attacker cannot use ROP only to make the memory area of the payload executable, and then transfer control there (as is often done in exploits today). Instead, the entire payload must be written to the ROP. At present, this will be a laborious task, but the large-scale use of ACG can serve as an incentive to develop 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 a ROP, an attacker with a read and write primitive can use a script execution environment that is already present in the process (for example, JavaScript in the Edge). This will create an interface that allows you to:
- call an arbitrary native function with arbitrary arguments from the script execution environment
- pass the return value back to the script execution environment
Having an exploitation library with such capabilities, attacking, instead of writing the payload in native code, writes code in a scripting language, using the library for native calls where it is needed. The task is greatly simplified if the operating library provides helper methods such as malloc (), memcpy (), etc. In fact, such a library already exists: pwn.js.
Instead of the ability to call arbitrary native functions, an attacker may be able to make arbitrary system calls (syscalls).
Although it may seem from the foregoing that ACG is not very useful, especially in a web browser, you need to take into account that scenarios 2 and 3 suggest an attacker capable of capturing the flow of control. In other words, an attacker needs to bypass the CFG (Control Flow Guard).
Currently, with a
lot of well-known approaches , bypassing the CFG in Windows is easy. However, if Microsoft can fix all known CFG flaws (and Microsoft has already indicated its intention to do this), the situation may change in the next couple of years.
Thus, the successful application of ACG is directly dependent on the successful application of CFG and CIG (
Code Integrity Guard ). All of these mechanisms must work together to prevent the attacker from executing code:
- When using CIG and ACG, but without CFG, as described above, an attacker can encode the payload in the form of a ROP chain or abuse the script execution environment to execute arbitrary code.
- When using CFG and CIG, but without ACG, the attacker can project the executable memory into the current process.
- When using CFG and ACG, but without a CIG, the attacker can load the malicious library into the current process.
Chakra (JIT server)
To implement JIT in Chakra (JavaScript Engine in Microsoft Edge) with ACG enabled, Microsoft launches 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 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 part of the script several times), instead of doing this by the current process, the JIT server is called, which is passed to byte code of the objective function. The JIT server then compiles the bytecode and writes the resulting executable native code back to the calling process using shared memory (section object). After this, 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 essential 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. This starts only one JIT server process that 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 mutual dependence 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 means of preventing advanced attacks on exiting the browser sandbox. Therefore, Microsoft must correct all known CFG flaws before ACG becomes a significant obstacle for exploits.