Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=785

The Adobe Type Manager Font Driver (ATMFD.DLL) responsible for handling PostScript and OpenType fonts in the Windows kernel provides a channel of communication with user-mode applications via an undocumented gdi32!NamedEscape API call. The nature of the channel is similar to IOCTLs [1] of type METHOD_BUFFERED [2], in that it also uses a 32-bit escape code (equivalent to control codes in IOCTL), and input and output buffers employed to pass data to the driver and receive information back. We suspect that this little known interface was originally designed to be more universal, but since ATMFD has remained the only third-party font driver in Windows for the past two decades, in reality it can only be used to interact with this single module.

Considering that there hasn't been any public research into the security of ATMFD's NamedEscape handlers, it is likely that it hasn't been thoroughly audited since the creation of the code. The first public vulnerability disclosed in the interface was a 0-day pool corruption (stemming from a 16-bit signedness issue) discovered in Hacking Team's leaked data dump [3], and subsequently fixed by Microsoft in the MS15-077 bulletin [4]. That security issue motivated us to have a deeper look into the security posture of the code area.

The main hurdle in approaching the NamedEscape attack surface is that Microsoft provides no symbols or other debugging information for the ATMFD.DLL library via the Microsoft Symbol Server, which means that all functionality needs to be reverse-engineered from the ground up, with no hints available whatsoever. At least that was what we believed until recently, when we discovered that the user-mode counterpart of ATMFD is ATMLIB.DLL -- a legacy library rarely used by the operating system nowadays, but which comes with debug symbols and implements many of its features by making NamedEscape calls to the kernel-mode driver. This lead to the further discovery of the "Adobe Type Manager Software API for Windows 95 and Windows NT 4" [5] and "Adobe Type Manager Software API: Windows" [6] documents, which greatly helped us understand the semantics of most of the escape codes, some of the underlying structures and other specifics of the code.

All further analysis presented below is relevant to an ATMFD.DLL file found on Windows 7 32-bit, version 5.1.2.247, md5sum e85bed746bbddcd29ad63f6085e1ce78. The driver currently supports 13 different escape codes in the range of 0x2500 - 0x2514. The bug discussed in this report resides in the handler of code 0x250C, which we have named "ATMGetGlyphName", based on the observation of its behavior and how it is used in the ATMLIB!ATMGetGlyphListW function.

Since the execution flow down the call stack is quite complex before we can reach the vulnerable condition, let's briefly summarize the major stages of execution:

  1) The i/o buffer size is enforced to be exactly 48 bytes.
  2) The ATMGetGlyphName handler function (atmfd+0x1F12) locates the font object based on its kernel-mode address provided at offset 4 of the i/o buffer.
  3) The font is mapped into memory (?) by a function at atmfd+0x5AC6.
  4) More logic follows depending on whether the font is a PostScript or OpenType one. We have found the PostScript-specific logic to be uninteresting, so we'll follow the OpenType one.
  5) A function at atmfd+0xDF10 (we call it "FormatOpenTypeGlyphName") is called with a controlled 16-bit glyph index and a pointer to offset 8 of the i/o buffer (to copy the name there).
  6) In order to retrieve the actual glyph name from the .OTF file, another function at atmfd+0x1A2D6 is invoked, we call it "GetOpenTypeGlyphName".

Here, the interesting functionality begins. If the glyph id is between 0 and 390, the name is obtained from a hard-coded list of names. Otherwise, it is extracted from the .OTF file itself, by reading from the Name INDEX [7]. The core of the function is as follows (in pseudo-code):

--- cut ---
  PushMarkerToStack();

  int glyph_name_offset = ReadCFFEntryOffset(glyph_id);
  int next_glyph_name_offset = ReadCFFEntryOffset(glyph_id + 1);

  *pNameLength = next_glyph_name_offset - glyph_name_offset;

  EnsureBytesAreAvailable(next_glyph_name_offset - glyph_name_offset);

  PopMarkerFromStack();
--- cut ---

The function addresses are as follows:

  +-------------------------+---------------+
  |        Function         |    Address    |
  +-------------------------+---------------+
  | PushMarkerToStack       | inlined       |
  | ReadCFFEntryOffset      | atmfd+0x1994D |
  | EnsureBytesAreAvailable | atmfd+0x18D11 |
  | PopMarkerFromStack      | atmfd+0x18B34 |
  +-------------------------+---------------+

The code construct is consistent with the general Name INDEX structure, which is as follows:

  +---------+------------------+------------------------------------------------+
  |  Type   |       Name       |                  Description                   |
  +---------+------------------+------------------------------------------------+
  | Card16  | count            | Number of object stored in INDEX               |
  | OffSize | offSize          | Offset array element size                      |
  | Offset  | offset [count+1] | Offset array (from byte preceding object data) |
  | Card8   | data[<varies>]   | Object data                                    |
  +---------+------------------+------------------------------------------------+

In order to extract any data from an index, it is necessary to read the offset of the interesting entry, and the next one (to calculate the length), which is what the function does. What are the PushMarkerToStack and PopMarkerFromStack functions, though? As it turns out, the font object being operated on has a 16-element stack (each element 32-bit wide). The ATMFD.DLL file contains multiple assertion strings, which show that the stack is internally named "HeldDataKeys", the element counter is "nHeldDataKeys", and a special -1 value pushed on the stack is "MARK":

  "fSetPriv->HeldDataKeys[ fSetPriv->nHeldDataKeys-1] == MARK"
  "fSetPriv->nHeldDataKeys >= 0"
  "fSetPriv->nHeldDataKeys > 0"
  "fSetPriv->nHeldDataKeys < MAXHELDDATAKEYS"

It is generally important for memory safety to never go beyond the bounds of the HeldDataKeys array, as doing otherwise would result in overwriting adjacent fields of the font object structure, or adjacent pool allocations. Therefore, management of the nHeldDataKeys field must be performed very carefully. It appears to be safe in the GetOpenTypeGlyphName function, as only one element is pushed and subsequently popped.

However, if we have a look into the EnsureBytesAreAvailable function, it turns out that if more bytes are requested than are found in the CFF table of the .OTF file, then an exception is generated and handled internally in the routine. One of the actions taken during the handling of the exception is a call to a function at atmfd+0x18C05, which pops all data from the stack until and including the first occurrence of -1. Since another element is also unconditionally popped at the end of GetOpenTypeGlyphName, two elements are popped for just one pushed, which corrupts the state of the nHeldDataKeys field and makes it possible to set it to a negative value.

In this specific case, we fully control the Name INDEX being used. Since it is possible to set the offset size to 4 bytes (through the offSize field mentioned above), we can fully control both 32-bit return values of the ReadCFFEntryOffset calls, and thus also their difference, which is passed as an argument to EnsureBytesAreAvailable.

In the simplest scenario, triggering the vulnerability in ATMGetGlyphName indefinitely will decrement the nHeldDataKeys field one by one, and overwrite earlier and earlier DWORDs on the pool with 0xffffffff (starting with the font object itself, and then moving onto adjacent pool allocations). This is sufficient to demonstrate pool corruption and a system crash; however, it is also possible to maintain a higher degree of control over what is written to the out-of-bounds memory region, by invoking other escape handlers which push more than just the marker, once nHeldDataKeys is already adjusted to where we want to write. This should enable easier and more reliable exploitation.

Another potential obstacle in exploitation could be the fact that the font being operated on must be identified by its kernel-mode address. In practice, however, this is not a problem, as the address can be quickly brute-forced by testing addresses nearby the addresses of other GDI objects (whose locations are available to user-mode programs). This technique was used in the HackingTeam exploit for escape 0x2514. To make it even simpler, the provided proof-of-concept code just brute-forces the entire 32-bit kernel address space, which only takes a few seconds to locate the font object and trigger the bug.

If we start an exploit which triggers the vulnerability 100 times on a system with Special Pools enabled, we should observe the following or similar bugcheck:

--- cut ---
  SPECIAL_POOL_DETECTED_MEMORY_CORRUPTION (c1)
  Special pool has detected memory corruption.  Typically the current thread's
  stack backtrace will reveal the guilty party.
  Arguments:
  Arg1: fe67ef50, address trying to free
  Arg2: fe67ee28, address where bits are corrupted
  Arg3: 006fa0b0, (reserved)
  Arg4: 00000023, caller is freeing an address where nearby bytes within the same page have been corrupted

  Debugging Details:
  ------------------

  [...]

  BUGCHECK_STR:  0xC1_23

  SPECIAL_POOL_CORRUPTION_TYPE:  23

  DEFAULT_BUCKET_ID:  VISTA_DRIVER_FAULT

  PROCESS_NAME:  csrss.exe

  CURRENT_IRQL:  2

  LAST_CONTROL_TRANSFER:  from 82930dd7 to 828cc318

  STACK_TEXT:
  9f4963e4 82930dd7 00000003 c453df12 00000065 nt!RtlpBreakWithStatusInstruction
  9f496434 829318d5 00000003 fe67e000 fe67ee28 nt!KiBugCheckDebugBreak+0x1c
  9f4967f8 82930c74 000000c1 fe67ef50 fe67ee28 nt!KeBugCheck2+0x68b
  9f496818 82938b57 000000c1 fe67ef50 fe67ee28 nt!KeBugCheckEx+0x1e
  9f49683c 8293963d fe67ef50 fe67e000 fe67ef50 nt!MiCheckSpecialPoolSlop+0x6e
  9f49691c 82973b90 fe67ef50 00000000 fe67ef50 nt!MmFreeSpecialPool+0x15b
  9f496984 96a609cc fe67ef50 00000000 fe67ef60 nt!ExFreePoolWithTag+0xd6
  9f496998 96b44ec1 fe67ef60 09fe969f 00000000 win32k!VerifierEngFreeMem+0x5b
  WARNING: Stack unwind information not available. Following frames may be wrong.
  9f4969cc 96b43850 fe67ef68 09fe9553 00000000 ATMFD+0x14ec1
  9f496a00 96b329ab 9f496a24 96b4a736 96b6f024 ATMFD+0x13850
  9f496a08 96b4a736 96b6f024 fe744fc0 fe63ccf8 ATMFD+0x29ab
  9f496a24 96b41516 fe744fb0 09fe952f fe63ccf8 ATMFD+0x1a736
  9f496a7c 96b377e0 09fe95e7 96a60c8e 9f496b40 ATMFD+0x11516
  9f496ab4 96b34196 09fe95b7 96a60c8e 9f496b40 ATMFD+0x77e0
  9f496ae4 969ce0a1 fde3a898 fde3a898 9f496b80 ATMFD+0x4196
  9f496b1c 969ce2c4 fde3a898 fde3a898 00000000 win32k!PDEVOBJ::DestroyFont+0x67
  9f496b4c 96954607 00000000 00000000 00000001 win32k!RFONTOBJ::vDeleteRFONT+0x33
  9f496b74 969561fe 9f496b98 fde3a898 00000000 win32k!PUBLIC_PFTOBJ::bLoadFonts+0x6fb
  9f496ba4 96a1fcc4 00000001 ffbbc234 89a3f7f0 win32k!PFTOBJ::bUnloadWorkhorse+0x114
  9f496bcc 96a29ae9 9f496c58 0000002b 00000001 win32k!GreRemoveFontResourceW+0xa0
  9f496d14 8288ea16 00319768 0000002b 00000001 win32k!NtGdiRemoveFontResourceW+0x111
  9f496d14 76dd70d4 00319768 0000002b 00000001 nt!KiSystemServicePostCall
  0022fca4 76de6113 76de5e20 00000020 00000028 ntdll!KiFastSystemCallRet
  0022fd84 76dd6078 00000000 00000000 00000090 ntdll!RtlpAllocateHeap+0xe68
  0022fe14 76de60e4 76de6113 76ec93f1 75957040 ntdll!ZwQueryInformationProcess+0xc
  003b0108 00000000 00000000 00000000 00000000 ntdll!RtlpAllocateHeap+0xab2
--- cut ---

One could wonder if this issue could be triggered directly from within Internet Explorer, via an embedded .OTF font file and an .EMF image containing EMR_NAMEDESCAPE records. One obvious problem is that the font object needs to be identified by its kernel-mode address, which neither the EMF file or even the JavaScript code running in the browser knows. On 64-bit platforms, this address would have to be leaked into JS, which is not a trivial task since the value is not typically stored in the heap, and therefore impossible without using another vulnerability (e.g. an arbitrary read from the GDI handle table). On 32-bit platforms, it could actually be feasible to simply brute-force the address, by including an EMR_NAMEDESCAPE-based exploit chain for every location possible. This, while theoretically feasible, would blow the size of the EMF up to the orders of hundreds of megabytes, making a practical attack unrealistic.

The other obstacle is some obscure reference counting problem with ATMFD. In order for the same object (which contains the HeldDataKeys stack) to persist between multiple calls to NamedEscape (which is what makes it possible to underflow the stack by more than 4 bytes), it is necessary to reference the font after loading it in the system, e.g. with functions such as TextOut() or GetTextMetrics(). However, Internet Explorer does not seem to interact with the font object in any way after loading it in the system, and since the loading itself takes place via a AddFontMemResourceEx API call, the font is private and non-enumerable, meaning that it is impossible to reference it except for the returned handle itself. Until now, we haven't found a way to trigger a large pool corruption from the context of a website, but it could still be possible.

Attached you can find a proof of concept program, which together with the specially crafted .OTF font demonstrates a local pool corruption.

This bug is subject to a 90 day disclosure deadline. If 90 days elapse without a broadly available patch, then the bug report will automatically become visible to the public.

References:
[1] https://msdn.microsoft.com/pl-pl/library/windows/desktop/aa363219%28v=vs.85%29.aspx
[2] https://msdn.microsoft.com/pl-pl/library/windows/hardware/ff565356%28v=vs.85%29.aspx
[3] https://bugs.chromium.org/p/project-zero/issues/detail?id=473
[4] https://technet.microsoft.com/library/security/ms15-077
[5] https://partners.adobe.com/public/developer/en/atm/5642.ATMWin95andNT.pdf
[6] https://partners.adobe.com/public/developer/en/atm/5073.ATM.API_Win.pdf
[7] https://partners.adobe.com/public/developer/en/font/5176.CFF.pdf


Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/39991.zip