Clement Lecigne, Google Threat Analysis Group

The Basics

Disclosure or Patch Date: September, 12, 2023

Product: Adobe Acrobat Reader on Windows and MacOS

Advisory: https://helpx.adobe.com/security/products/acrobat/apsb23-34.html

Affected Versions: 23.003.20284 and earlier versions

First Patched Version: 23.006.20320

Issue/Bug Report: N/A

Patch CL: N/A (closed source)

Bug-Introducing CL: N/A

Reporter(s): Anonymous

The Code

Proof-of-concept:

TTF font with the following bitmap tables.

EBLC :
     version : 0x20000
     numSizes : 0x1
     [-] 0th bitmapSizeTable
         indexSubTableArrayOffset : 0x38
         indexTablesSize : 0x100
         numberOfIndexSubTables : 0x2
         colorRef : 0x0
         hori : 0b fe 08 01 00 00 00 00 0b fe 00 00
         vert : 00 00 00 00 00 00 00 00 00 00 00 00
         startGlyphIndex : 0x4d
         endGlyphIndex : 0x4e
         ppemX : 0xfa
         ppemY : 0xfa
         bitDepth : 0x1
         flags : 0x1
         [-] 0th subArray
             firstGlyphIndex : 0x4d
             lastGlyphIndex : 0x4d
             additionalOffsetToIndexSubtable : 0x10
             indexFormat : 0x1
             imageFormat : 0x8
             imageDataOffset : 0x4
             offsetArray0 : 0x0
             offsetArray1 : 0x0
         [-] 1th subArray
             firstGlyphIndex : 0x4e
             lastGlyphIndex : 0x4e
             additionalOffsetToIndexSubtable : 0x20
             indexFormat : 0x1
             imageFormat : 0x1
             imageDataOffset : 0x4
             offsetArray0 : 0x0
             offsetArray1 : 0xc
EBDT :
     version : 0x20000
     [-] 0th metrics
         height : 0xa2
         width : 0x61
         BearingX : 0x4
         BearingY : 0x9
         Advance : 0x8
         pad : 0x0
         numComponents : 0x1
         [-] 0th ComponentArray
             glyphCode : 0x4e
             xOffset : 0x20
             yOffset : 0xa3
     [-] 1th metrics
         height : 0xa2
         width : 0x61
         BearingX : 0x4
         BearingY : 0x9
         Advance : 0x8
         pad : 0x0
         numComponents : 0x1
         [-] 0th ComponentArray
             glyphCode : 0x4e
             xOffset : 0x20
             yOffset : 0xa3

Note that the font has the following EBSC table which prevents the font from being loaded in many font parsing libraries except libCoolType from Adobe.

2. 'EBSC' - checksum = 0x00000000, offset = 0xffffffff, len = 4294967295

Exploit sample: Not public.

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

The Vulnerability

Bug class: Heap buffer out-of-bounds write

Vulnerability details:

An out of bound (OOB) write vulnerability exists in sfac_GetSbitBitmap when parsing a malformed TrueType font in Adobe libCoolType.

By enabling PageHeap for Adobe Reader, we get the following crash in CoolType!CTCleanup+0x3ed54:

rax=00000000000000ff rbx=0000000000000001 rcx=fffffffff8a29014
rdx=0000000000000004 rsi=000000000000ffff rdi=0000000000000004
rip=00007ffc84736144 rsp=000000ab75f56fa0 rbp=0000000000000000
 r8=0000000000000010  r9=0000027bc9557000 r10=0000027bc1f80010
r11=0000000000000004 r12=000000000000fff3 r13=0000000000000001
r14=0000027b76539ba0 r15=0000000000000001
iopl=0         nv up ei ng nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010286
CoolType!CTCleanup+0x3ed54:
00007ffc`84736144 42080409        or      byte ptr [rcx+r9],al ds:0000027b`c1f80014=??

The code is attempting to bitwise OR the byte located at r9[rcx] with 0xFF (al) and r9[rcx] is out of bound.

By tracing the code, we noticed that r9 is a bitmap buffer which was previously allocated in sbit_GetMetrics using the following formula and data from the font file.

bitmap_size
 = (((EBDT[0].width + 0x1F) >> 3) & 0xFFFC) * EBDT[0].height
 = (((0x61+0x1f)>>3)&0xfffc)*0xa2 = 0xA20

The OOB write happens in sfac_GetSbitBitmap when glyphs are merged together in the bitmap buffer. To properly position the glyphs, each glyph has layout information located in the EBDT table, including height, width, advance, X and Y offsets. GetSbitComponent makes recursive calls to sfac_GetSbitBitmap to handle the glyphs one by one. The problem with sfac_GetSbitBitmap is that it merges glyphs without first checking if the X and Y offsets are within the bounds of the bitmap buffer. For example with the font above, the OOB happens when positioning the second glyph on the vertical axis (Y offset).

index = glyph[curr].yOffset * (glyph[curr].Advance+glyph[curr-1].Advance)
  = 0xa3 * (0x8 + 0x8)
  = 0xa30

0xa30 is outside of the allocated bitmap buffer.

Patch analysis:

The vulnerability was patched by adding multiple bound-checks [0] [1] before entering the loop transforming the bitmaps in sfac_GetSbitBitmap. The size allocated for the bitmap buffer by sbit_GetMetrics is also now passed as a new parameter (max_sz) to sfac_GetSbitBitmap.

Pseudo-code:

int sfac_GetSbitBitmap(..., uint16 a12_yOffset, uint16 curr_advance, ..., uint32 max_sz)
...
    v25 = a12_yOffset * curr_advance;           // size
    if ( v25 >= max_sz )                        // [0] bound check
    {
        // err
        return 5120;
    }
...
    if (height) n_iter = a12_yOffset + height;
    max_idx = a13_somesize * n_iter;
    if ( max_idx >= lower_bound && max_idx < max_sz )   // [1] bound check
    {
...
        do
        {
...
            do
            {
                *((_BYTE *)data + idx++) |= *(_BYTE *)data;     // OOB before
                data = (unsigned __int16 *)((char *)data + 1);
                n_iter--;
            } while (n_iter)
...
            idx += curr_advance;
            --height;
        }
        while (height);
    }

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

3 possible options:

  1. Found via smart TTF fuzzing against the libCoolType shipped with Adobe Reader.
  2. Binary diffing various library forks (e.g. both TrueType from MSFT and CoolType from Adobe share the sbit/sfac functions) to spot discrepancies. The vulnerability is very similar to CVE-2011-3402 affecting the TrueType font parsing engine in win32k and exploited in the wild by Duqu.
  3. Code auditing around bitmap size calculation and usage.

Out of these options, I consider 1) or 2) to be the most likely ones.

(Historical/present/future) context of bug: N/A

The Exploit

Exploit strategy (or strategies):

Through the vulnerability to achieve out-of-bounds write, and corrupting adjacent EScript objects previously allocated from the PDF.

Exploit flow:

  1. Activate LFH allocator on various bucket sizes by allocating multiple objects of specific size from Javascript.
  2. Spray a pair of ArrayBuffer and Array objects on the heap.
  3. Load the malicious font triggering the vulnerability by corrupting one byte of the length field of the ArrayBuffer.
  4. Find corrupted ArrayBuffer, allowing limited R/W.
  5. Override length of the adjacent Array using the corrupted ArrayBuffer to have complete arbitrary R/W and setup the addrof primitive.
  6. Use the arbitrary R/W and addrof primitive to compute the ROP chain and write the shellcode in memory.
  7. Without CFG, the exploit simply overrides the TextSize VTable of a TextBox object and trigger its call to hijack the execution flow.
  8. With CFG on, the exploit calls RtlCaptureContext to leak the stack address and then overrides return pointers on the stack to redirect the execution flow to their ROP chain.

Known cases of the same exploit flow:

Most exploits abusing memory corruption vulnerability in Adobe Reader such as CVE-2020-9715 have a similar exploit flow.

Part of an exploit chain?

Yes, this bug was used by government backed actors in North Korea as a way to get RCE within Adobe Reader, we didn't manage to retrieve or get access to the other stages of the chain like the sandbox escape.

The Next Steps

Variant analysis

Areas/approach for variant analysis (and why):

  1. Run extensive fuzzing of the libCoolType TTF parsers.
  2. Code auditing around bitmap usage and size computation in TTF parsers.
  3. Analyze differences between Microsoft and Adobe libCoolType to spot any discrepancies (e.g. missing bound checks).

Found variants:

N/A

Structural improvements

Ideas to kill the bug class:

Rewrite font parsing code in a memory safe language.

Ideas to mitigate the exploit flow:

  • Isolate all or some specific ESOBjects into their own dedicated heap/partition.
  • Sandbox/isolate font parsing code.
  • Disable javascript processing in Adobe Reader.

0-day detection methods

What are potential detection methods for similar 0-days? Meaning are there any ideas of how this exploit or similar exploits could be detected as a 0-day?

N/A

Other References