Skip to main content
February 23, 2017
PC

Mitigating arbitrary native code execution in Microsoft Edge



Some of the most important security features in modern web browsers are those that you never actually see as you browse the web. These security features work behind the scenes to protect you from browser-based vulnerabilities that could be abused by hackers to compromise your device or personal data.

In previous blog posts and presentations, we described some of the recent improvements that have been made to Windows 10 and Microsoft Edge in this space. Today we’re kicking off a two-part blog post that describes our vulnerability mitigation strategy and provides a technical deep-dive into some of the major security improvements that are coming to Microsoft Edge in the Creators Update of Windows 10.

Framing our Vulnerability Mitigation Strategy

Before we dive in, it may help to start with an overview of how we approach the problem of web browser vulnerabilities. The Microsoft Edge security team employs a layered, data-driven defense strategy that focuses investments at key points along the kill-chain that attackers follow when exploiting vulnerabilities.

Table illustrating the Edge vulnerability mitigation strategy. The Strategy row reads: "Make it difficult & costly to find, exploit, and leverage software vulnerabilities." The "Tactics" read: "Eliminate entire classes of vulnerabilities," "Break exploitation techniques," "Contain damage & prevent persistence," and "Limit hte window of opportunity to exploit."

First and foremost in this strategy, we look for ways to eliminate classes of vulnerabilities by reducing attack surface and by finding or mitigating specific patterns of vulnerabilities (such as use after free issues, see MemGC). In this way, we try to counter the classic asymmetry between attackers and defenders, e.g. where attackers only need to find one good security issue whereas defenders need to ensure there are none.

Still, we assume that we won’t be able to eliminate all vulnerabilities, so we look for ways to break the techniques that attackers can use to exploit them. This helps to spoil the recipes that attackers prefer to use when trying to transform a vulnerability into a way of running code on a device. This further counters the asymmetry by removing the underlying ingredients and primitives that enable vulnerabilities to be exploited.

We assume that we won’t be able to break all exploits, so we look for ways to contain damage and prevent persistence on a device if a vulnerability is exploited. We do this by once again applying the two previous tactics but this time directed at the attack surface that is accessible from code running within Microsoft Edge’s browser sandbox. This helps constrain attacker capabilities and further increases the cost of achieving their objective.

Finally, assuming all else fails, we look to limit the window of opportunity for an attacker to exploit a vulnerability by having effective tools and processes in place. On the processes side, we take advantage of the well-oiled security incident response processes in the Microsoft Security Response Center (MSRC). On the tools side, we have technologies like Windows Defender and SmartScreen which can be used to block malicious URLs that attempt to deliver an exploit and Windows Update to rapidly deploy and install security updates.

While we’re continuing to invest in security improvements along all of these fronts, the remainder of this post will focus on investments we’ve made to break techniques that are used to exploit the most common type of security issue in modern browsers: memory safety vulnerabilities. More specifically, the next section will explore the technologies we’ve built to help mitigate arbitrary native code execution.

Transparency

Browser security is a difficult problem space. Despite the best efforts of all browser vendors, vulnerabilities exist and can potentially be exploited. This is why Microsoft currently offers bug bounties of up to $15,000 USD for vulnerabilities found in Microsoft Edge and up to $200,000 USD for novel mitigation bypasses and defenses as part of our Mitigation Bypass and Defense Bounty. These bounty programs reinforce our commitment to our vulnerability mitigation strategy and help us reward the great work of security researchers around the world.

Mitigating Arbitrary Native Code Execution

Most modern browser exploits attempt to transform a memory safety vulnerability into a method of running arbitrary native code on a target device. This technique is prevalent because it provides the path of least resistance for attackers by enabling them to flexibly and uniformly stage each phase of their attack. For defenders, preventing arbitrary native code execution is desirable because it can substantially limit an attacker’s range of freedom without requiring prior knowledge of a vulnerability. To this end, Microsoft Edge in the Creators Update of Windows 10 leverages Code Integrity Guard (CIG) and Arbitrary Code Guard (ACG) to help break the most universal primitive found in modern web browser exploits: loading malicious code into memory.

Hackers are developers, too

A typical web browser exploit chain consists of three parts:

  1. An exploit for a remote code execution (RCE) vulnerability which is used to get native code running on the target device.
  2. An exploit for elevation of privilege (EOP) vulnerability which is used to increase privileges and escape the sandbox.
  3. A payload that leverages the obtained access to achieve the attacker’s objective (e.g. ransomware, implant, recon, etc).

These parts naturally translate into a modular design for exploits which enables attackers to select different RCE, EOP, and payload combinations based on their target. As a consequence, modern exploits ubiquitously rely on executing arbitrary native code in order to run the 2nd and 3rd stages of their exploit. By breaking this critical link in the chain, we can influence the exploit economics by invalidating the attacker’s software design assumptions and forcing refactoring costs on them.

Preventing the loading of malicious native code

An application can directly load malicious native code into memory by either 1) loading a malicious DLL/EXE from disk or 2) dynamically generating/modifying code in memory. CIG prevents the first method by enabling DLL code signing requirements for Microsoft Edge. This ensures that only properly signed DLLs are allowed to load by a process. ACG then complements this by ensuring that signed code pages are immutable and that new unsigned code pages cannot be created.

CIG: Only allow properly signed images to load

CIG was first enabled for Microsoft Edge starting with the Windows 10 1511 update. In a previous blog post, we explained how a kernel-enforced User Mode Code Integrity (UMCI) policy has been enabled for Microsoft Edge content processes that requires DLLs to be Microsoft, Windows Store, or WHQL-signed. With this policy in place, the kernel will fail attempts to load a DLL that is not properly signed. In practice, exploits do not typically rely on loading a DLL from disk, but it has been used by some exploits and it must be addressed to achieve our objective and to have a comprehensive solution. Since the Windows 10 1511 release, we’ve made additional improvements to help strengthen CIG:

  1. Preventing child process creation (Windows 10 1607): As the UMCI policy is applied per-process, it is also important to prevent an attacker from spawning a new process with a weaker or non-existent UMCI policy. In Windows 10 1607, Microsoft Edge enabled the no child process mitigation policy for content processes which ensures that a child process cannot be created. This policy is currently enforced as a property of the token for a content process which ensures both direct (e.g. calling WinExec) and indirect (e.g. out-of-process COM server) process launches are blocked.
  1. Enabling the CIG policy sooner (Windows 10 Creators Update): The enablement of the UMCI policy has been moved to process creation time rather than during process initialization. This was done to further improve reliability by eliminating a process launch time gap where local injection of improperly signed DLLs into a content process could occur. This was achieved by taking advantage of the UpdateProcThreadAttribute API to specify the code signing policy for the process being launched.

ACG: Code cannot be dynamically generated or modified

While CIG provides strong guarantees that only properly signed DLLs can be loaded from disk, it does not provide any guarantees about the state of image code pages after they are mapped into memory or dynamically generated code pages. This means an attacker can load malicious code by creating new code pages or modifying existing ones even when CIG is enabled. In practice, most modern web browser exploits eventually rely on invoking APIs like VirtualAlloc or VirtualProtect to do just this. Once an attacker has created new code pages, they then copy their native code payload into memory and execute it.

With ACG enabled, the Windows kernel prevents a content process from creating and modifying code pages in memory by enforcing the following policy:

  1. Code pages are immutable. Existing code pages cannot be made writable and therefore always have their intended content. This is enforced with additional checks in the memory manager that prevent code pages from becoming writable or otherwise being modified by the process itself. For example, it is no longer possible to use VirtualProtect to make an image code page become PAGE_EXECUTE_READWRITE.
  1. New, unsigned code pages cannot be created. For example, it is no longer possible to use VirtualAlloc to create a new PAGE_EXECUTE_READWRITE code page.

When combined, the restrictions imposed by ACG and CIG ensure that a process can only directly map signed code pages into memory. Although this is great for security, ACG introduces a serious complication: modern web browsers rely on Just-in-Time (JIT) compilers for best performance. How can we satisfy both needs?

Supporting Just-in-Time (JIT) Compilers

Modern web browsers achieve great performance by transforming JavaScript and other higher-level languages into native code. As a result, they inherently rely on the ability to generate some amount of unsigned native code in a content process. Enabling JIT compilers to work with ACG enabled is a non-trivial engineering task, but it is an investment that we’ve made for Microsoft Edge in the Windows 10 Creators Update. To support this, we moved the JIT functionality of Chakra into a separate process that runs in its own isolated sandbox. The JIT process is responsible for compiling JavaScript to native code and mapping it into the requesting content process. In this way, the content process itself is never allowed to directly map or modify its own JIT code pages.

Impact on attackers

Together, CIG and ACG provide strong protection against a fundamental primitive that is ubiquitously used when exploiting web browser vulnerabilities. This means attackers must develop new methods for chaining the stages of their exploits.

In the Windows 10 Creators Update, CIG is enabled by default for Microsoft Edge, except for scenarios where certain incompatible extensions are present (such as IMEs) – in these scenarios, both CIG and ACG are currently disabled by default.

For compatibility reasons, ACG is currently only enforced on 64-bit desktop devices with a primary GPU running a WDDM 2.2 driver (the driver model released with the Windows 10 Anniversary Update), or when software rendering is use. For experimental purposes, software rendering can be forced via Control Panel ->Internet Options -> ”Advanced”. Current Microsoft devices (Surface Book, Surface Pro 4, and Surface Studio) as well as a few other existing desktop systems with GPU drivers known to be compatible with ACG are opted into ACG enforcement. We intend to improve the coverage and accuracy of the ACG GPU opt-in list as we evaluate the telemetry and feedback from customers.

One of the limitations of CIG and ACG is that they don’t prevent an attacker from leveraging valid signed code pages in an unintended way. For Example, this means attackers could still use well-known techniques like return-oriented programming (ROP) to construct a full payload that doesn’t rely on loading malicious code into memory. In order to help keep signed code “on the rails” as it executes, Microsoft Edge takes advantage of Control Flow Guard (CFG) which applies a control-flow integrity policy to indirect calls. In the future, we hope to further mitigate control-flow hijacking such as by taking advantage of Intel’s Control-flow Enforcement Technology (CET) to protect return addresses on the stack.

Finally, it should be noted that the use of CIG and ACG in Edge is not intended to fully prevent privileged code running on the system from injecting unsigned native code into a content process.  Rather, these features are intended to prevent the scenario of the content process itself attempting to load malicious native code.

Up Next

In an upcoming post, we’ll shift gears to focus on some of the major improvements that have been made to strengthen containment and isolation for Microsoft Edge in the Creators Update of Windows 10. These improvements provide the next line of defense in our efforts to help prevent web browser vulnerabilities from being used to compromise your device or personal data.

Matt Miller, Principal Security Software Engineer, MSRC