Talos Vulnerability Report

TALOS-2016-0170

Microsoft Windows PDF API Jpeg2000 csiz Remote Code Execution Vulnerability

August 9, 2016
CVE Number

CVE-2016-3319

Description

An exploitable out of bounds write vulnerability exists in the PDF parsing API in the latest versions of Microsoft Windows. A specially crafted PDF file can cause an out of bounds write resulting in arbitrary code execution. Vulnerability can be triggered via malicious web page or a saved PDF file delivered by other means.

Tested Versions

Microsoft Windows PDF API Windows.Data.Pdf.dll version 10.0.10.586.162

Product URLs

http://www.microsoft.com

CVSSv3 Score

7.5 - CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H

Details

The vulnerability is present in the Microsoft native PDF API which is available since Windows 8.1. In Windows 10, Microsoft Edge is the default application for opening PDF files enabling potential vulnerabilities in native PDF API to be exploited over the Web.

There exists a vulnerability in the way Microsoft PDF API parses jpeg2000 files embedded in the PDF documents. A specially crafted jpeg2000 file can trigger a out of bounds memory overwrite and lead to remote code execution.

Jpeg2000 files consist of a number of containers or boxes. Contiguous Codestream box contains the actual image data in the jp2 file and can have a number of child boxes. Contiguous Codestream box starts with a “jp2c” marker. According to the standard, jp2c box can contain SIZ marker segment (image and tile size info), a COD marker segment (coding style default info), a QCD marker segment (quantization default information) and a number of tile part elements which can in turn contain their own COD, CQD and other child elements. Start markers for SIZ is 0xFF51, COD 0xFF52, QCD is 0xFF5C and tile part is 0xFF90. An example of the file layout can be as follows:

```
+------------------+
|                  |
|  Codestream Box  |
|                  |
+-------+----------+
        |
        |            +---------+
        +------------+  SIZ    |
        |            +---------+
        |            +---------+
        +------------+  COD    |
        |            +---------+
        |            +---------+
        +------------+  CQD    |
        |            +---------+
        |            +---------------+
        +------------+  Tile Parts   |
                     +------+--------+
                            |
                            |      +-----------+
                            +------+ Tile Part |
                            |      +-----+-----+
                            |            |   +---------+
                            |            +---+  COD*   |
                            |            |   +---------+
                            |            |   +---------+
                            |            +---+  CQD*   |
                            |            |   +---------+
                            |            |   +---------+
                            |            +---+  COM*   |
                            |            |   +---------+
                            |            |   +---------+
                            |            +---+  SOT    |
                            |                +---------+
                            |
                            |       +-------------+     *Optional
                            +-------+ Tile Part   |
                                    +-------------+
```

According to the standard, tile part elements can contain only COD, CQD, COM, and SOT elements where COD, CQD and COM are optional. In the supplied testcase triggering the vulnerability a tile part element has an unexpected SIZ element which gets parsed and leads to a vulnerability.

Elements are parsed one by one in a CCodeStreamDecoder::DecodeMarkers method where for each marker type, a suitable decoder is called:

```
.text:6E1EC62D push    esi
.text:6E1EC62E mov     esi, [ebp+var_74]
.text:6E1EC631 mov     ecx, edi        ; _DWORD
.text:6E1EC633 push    esi
.text:6E1EC634 call    ds:___guard_check_icall_fptr ; CType1NoOpReceiver<IType1EncodingReceiver>::Begin(void)
.text:6E1EC63A call    edi             ; calls the decoder for specific marker
.text:6E1EC63C jmp     short loc_6E
```

When parsing a SIZ element, edi in the above code calls the CCodeStreamDecoder::s_SIZMarkerDecoder method. In it, various values are initialized. Amongst other things, SIZ marker specifies the number of components (csiz) as an 16 bit integer. This value is used to resize a vectors holding COD and CQD information:

```
.text:6E1EE747 mov     eax, [edi]
.text:6E1EE749 mov     esi, [eax+10h]
.text:6E1EE74C mov     ecx, esi        ; _DWORD
.text:6E1EE74E call    ds:___guard_check_icall_fptr ; CType1NoOpReceiver<IType1EncodingReceiver>::Begin(void)
.text:6E1EE754 mov     ecx, edi
.text:6E1EE756 call    esi             ;                                                 [1]
.text:6E1EE758 movzx   esi, ax
.text:6E1EE75B lea     ecx, [ebx+78h]
.text:6E1EE75E push    esi
.text:6E1EE75F mov     [esp+0C4h+var_8C], esi
.text:6E1EE763 mov     [ebx+3Ch], esi
.text:6E1EE766 call    std::vector<QCD_MARKER,std::allocator<QCD_MARKER>>::resize(uint)  [2]
.text:6E1EE76B push    esi
.text:6E1EE76C lea     ecx, [ebx+84h]
.text:6E1EE772 call    std::vector<COD_MARKER,std::allocator<COD_MARKER>>::resize(uint)  [3]

```

In the above disassembly, at [1] csiz value is read from the bytestream, at [2], it’s used to resize the QCD_MARKER vector, and at [3] the same for COD_MARKER vector. The pointers to both are stored at ebx+78h and ebx+84h respectively.

Next, COD marker decoder is called, CCodeStreamDecoder::s_CODMarkerDecoder, where the above resized vector is used in it’s elements initialized in a loop:

```
.text:6E1ED9BD mov     ecx, [edi+84h]                            [1]
.text:6E1ED9C3 lea     eax, [esp+0A4h+var_88]
.text:6E1ED9C7 push    eax
.text:6E1ED9C8 add     ecx, esi                                  [2]
.text:6E1ED9CA call    COD_MARKER::operator=(COD_MARKER const &) [3]
.text:6E1ED9CF inc     ebx
.text:6E1ED9D0 add     esi, 48h                                  [4]
.text:6E1ED9D3 cmp     ebx, [edi+3Ch]                            [5]
.text:6E1ED9D6 jb      short loc_6E1
```

At [1] a pointer to the vector is retrieved, at [2] esi is used as an index into the vector values, and is added to ecx, at [3] the current vector element is used with its assignment operator, at [4] index is increased, and at [5] the counter is compared to the previously mentioned csiz value.

A similar codepath is executed while parsing the CQD marker.

With page heap enabled , the supplied testcase crashes with the following:

```
eax=0d4fe801 ebx=00000003 ecx=0d9a1000 edx=00000000 esi=0d4fe8d0 edi=0d9a1000
eip=6e2cc0cb esp=0d4fe8a0 ebp=0d4fe8a8 iopl=0         nv up ei pl nz ac pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010216
Windows_Data_Pdf!COD_MARKER::operator=+0xe:
6e2cc0cb 8807            mov     byte ptr [edi],al          ds:002b:0d9a1000=??
0:014> k 5
 # ChildEBP RetAddr
00 0d4fe8a8 6e2cd9fa Windows_Data_Pdf!COD_MARKER::operator=+0xe
01 0d4fe95c 6e2cc63c Windows_Data_Pdf!CCodeStreamDecoder::s_CODMarkerDecoder+0x1ea
02 0d4fe9fc 6e2c9127 Windows_Data_Pdf!CCodeStreamDecoder::DecodeMarkers+0x91
03 0d4fec0c 6e2c8b47 Windows_Data_Pdf!JPXDecoder::Decode+0x80
04 0d4fec70 6e2c8740 Windows_Data_Pdf!PDF::CJPXDecoderByteStream::_Decode+0xdf

```

It’s crashing in the COD_MARKER assignment operator with a write access violation. A step by step examination leads to the details of the crash. Firstly, COD_MARKER vector is resized to 3 inside CCodeStreamDecoder::s_SIZMarkerDecoder method:

```
Breakpoint 2 hit
eax=00000000 ebx=0d3cee0c ecx=0d3cee90 edx=00000000 esi=00000003 edi=0d3cf03c
eip=6e2ce772 esp=0d3cec8c ebp=0d3ced54 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
Windows_Data_Pdf!CCodeStreamDecoder::s_SIZMarkerDecoder+0x172:
6e2ce772 e8e4ebffff      call    Windows_Data_Pdf!std::vector<COD_MARKER,std::allocator<COD_MARKER> >::resize (6e2cd35b)
0:013> ub
Windows_Data_Pdf!CCodeStreamDecoder::s_SIZMarkerDecoder+0x158:
6e2ce758 0fb7f0          movzx   esi,ax
6e2ce75b 8d4b78          lea     ecx,[ebx+78h]
6e2ce75e 56              push    esi
6e2ce75f 89742438        mov     dword ptr [esp+38h],esi
6e2ce763 89733c          mov     dword ptr [ebx+3Ch],esi
6e2ce766 e884ecffff      call    Windows_Data_Pdf!std::vector<QCD_MARKER,std::allocator<QCD_MARKER> >::resize (6e2cd3ef)
6e2ce76b 56              push    esi
6e2ce76c 8d8b84000000    lea     ecx,[ebx+84h]
0:013> dd ecx
0d3cee90  00000000 00000000 00000000 00000000
0d3ceea0  00000000 00000000 00000000 00000000
0d3ceeb0  00000000 0d3ceec8 00000011 00000000
0d3ceec0  0d7e0f1c 6e087f00 6e30c61f 00000064
0d3ceed0  0000000f e16b33b0 0d00ef58 6e30c298
0d3ceee0  6e2c965e 0d7e0f08 6e2c966a e16b3398
0d3ceef0  0d3cef30 6e2c96c2 6e2c96f3 00000000
0d3cef00  0000001c 00000000 00000000 00000000
0:013> p
Breakpoint 2 hit
eax=00000000 ebx=0d3cee0c ecx=0d3cee90 edx=00000000 esi=00000003 edi=0d3cf03c
eip=6e2ce772 esp=0d3cec8c ebp=0d3ced54 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
Windows_Data_Pdf!CCodeStreamDecoder::s_SIZMarkerDecoder+0x172:
6e2ce772 e8e4ebffff      call    Windows_Data_Pdf!std::vector<COD_MARKER,std::allocator<COD_MARKER> >::resize (6e2cd35b)
0:013> p
eax=000000d8 ebx=0d3cee0c ecx=6e2cd399 edx=00000000 esi=00000003 edi=0d3cf03c
eip=6e2ce777 esp=0d3cec90 ebp=0d3ced54 iopl=0         nv up ei pl nz ac pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216
Windows_Data_Pdf!CCodeStreamDecoder::s_SIZMarkerDecoder+0x177:
6e2ce777 8d4340          lea     eax,[ebx+40h]
0:013> dd 0d3cee90
0d3cee90  0d842f28 0d843000 0d843000 00000000
0d3ceea0  00000000 00000000 00000000 00000000
0d3ceeb0  00000000 0d3ceec8 00000011 00000000
0d3ceec0  0d7e0f1c 6e087f00 6e30c61f 00000064
0d3ceed0  0000000f e16b33b0 0d00ef58 6e30c298
0d3ceee0  6e2c965e 0d7e0f08 6e2c966a e16b3398
0d3ceef0  0d3cef30 6e2c96c2 6e2c96f3 00000000
0d3cef00  0000001c 00000000 00000000 00000000
```

In the above debugging output, it can be seen that the resize argument is 3 and that this is pointing to 0x0d3cee90. Also, after the call, the location at 0d3cee90 is initialized with pointers to vector start and end, so initially the COD_MARKER vector starts at 0d842f28. Placing a breakpoint at above mentioned COD vector assignment code inside CCodeStreamDecoder::s_CODMarkerDecoder gives:

```
Breakpoint 3 hit
eax=0d3cecc8 ebx=00000000 ecx=0d842f28 edx=000000bb esi=00000000 edi=0d3cee0c
eip=6e2cd9ca esp=0d3ceca8 ebp=0d3ced54 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
Windows_Data_Pdf!CCodeStreamDecoder::s_CODMarkerDecoder+0x1ba:
6e2cd9ca e8eee6ffff      call    Windows_Data_Pdf!COD_MARKER::operator= (6e2cc0bd)
0:013> ub
Windows_Data_Pdf!CCodeStreamDecoder::s_CODMarkerDecoder+0x1a3:
6e2cd9b3 c60701          mov     byte ptr [edi],1
6e2cd9b6 395f3c          cmp     dword ptr [edi+3Ch],ebx
6e2cd9b9 7655            jbe     Windows_Data_Pdf!CCodeStreamDecoder::s_CODMarkerDecoder+0x200 (6e2cda10)
6e2cd9bb 8bf3            mov     esi,ebx
6e2cd9bd 8b8f84000000    mov     ecx,dword ptr [edi+84h]
6e2cd9c3 8d44241c        lea     eax,[esp+1Ch]
6e2cd9c7 50              push    eax
6e2cd9c8 03ce            add     ecx,esi
0:013> dd ecx
0d842f28  c0c0c0c0 00000000 00000000 00000000
0d842f38  00000000 00000000 00000000 c0c0c0c0
0d842f48  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
0d842f58  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
0d842f68  c0c0c0c0 c0c0c0c0 c0c0c0c0 00000000
0d842f78  00000000 00000000 00000000 00000000
0d842f88  00000000 c0c0c0c0 c0c0c0c0 c0c0c0c0
0d842f98  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0

```

In the above debugging output, we hit a breakpoint on a call to Windows_Data_Pdf!COD_MARKER::operator= and can see that ecx points to the beginning of the resized vector from the previous disassembly. It continues to loop 3 times as specified by csiz value.

As mentioned before, the supplied testcase has a tile part that contains an extra SIZ marker along with COD marker, so resuming the execution breaks again in CCodeStreamDecoder::s_SIZMarkerDecoder during COD_MARKER vector resize:

```
Breakpoint 2 hit
eax=00000003 ebx=0d3cee0c ecx=0d3cee90 edx=00000000 esi=00000004 edi=0d3cf03c
eip=6e2ce772 esp=0d3cec8c ebp=0d3ced54 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
Windows_Data_Pdf!CCodeStreamDecoder::s_SIZMarkerDecoder+0x172:
6e2ce772 e8e4ebffff      call    Windows_Data_Pdf!std::vector<COD_MARKER,std::allocator<COD_MARKER> >::resize (6e2cd35b)
0:013> bu
breakpoint 2 redefined
0:013> ub
Windows_Data_Pdf!CCodeStreamDecoder::s_SIZMarkerDecoder+0x158:
6e2ce758 0fb7f0          movzx   esi,ax
6e2ce75b 8d4b78          lea     ecx,[ebx+78h]
6e2ce75e 56              push    esi
6e2ce75f 89742438        mov     dword ptr [esp+38h],esi
6e2ce763 89733c          mov     dword ptr [ebx+3Ch],esi
6e2ce766 e884ecffff      call    Windows_Data_Pdf!std::vector<QCD_MARKER,std::allocator<QCD_MARKER> >::resize (6e2cd3ef)
6e2ce76b 56              push    esi
6e2ce76c 8d8b84000000    lea     ecx,[ebx+84h]
0:013> dd ecx
0d3cee90  0d842f28 0d843000 0d843000 00000000
0d3ceea0  00000000 00000000 00000000 00000000
0d3ceeb0  00000000 00000008 00000008 00000000
0d3ceec0  0d7e0f1c 6e087f01 00000004 00000000
0d3ceed0  01001002 e16b3348 0d00eea0 00000000
0d3ceee0  000001af 00000146 00000000 00000000
0d3ceef0  00000100 00000100 00000000 00000000
0d3cef00  00000003 0d85cff0 0d85cffc 0d85cffc
0:013> p
eax=00000048 ebx=0d3cee0c ecx=6e2cd399 edx=00000000 esi=00000004 edi=0d3cf03c
eip=6e2ce777 esp=0d3cec90 ebp=0d3ced54 iopl=0         nv up ei pl nz ac pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216
Windows_Data_Pdf!CCodeStreamDecoder::s_SIZMarkerDecoder+0x177:
6e2ce777 8d4340          lea     eax,[ebx+40h]
0:013> dd 0d3cee90
0d3cee90  0dab8ee0 0dab9000 0dab9000 00000000
0d3ceea0  00000000 00000000 00000000 00000000
0d3ceeb0  00000000 00000008 00000008 00000000
0d3ceec0  0d7e0f1c 6e087f01 00000004 00000000
0d3ceed0  01001002 e16b3348 0d00eea0 00000000
0d3ceee0  000001af 00000146 00000000 00000000
0d3ceef0  00000100 00000100 00000000 00000000
0d3cef00  00000003 0d85cff0 0d85cffc 0d85cffc

```

In the above debugging output, a call to resize is made with the same this as previously. Before the call, the same vector pointers are at 0d3cee90, but after the call, the vector has been reallocated because of resize. In the above code, the value of the esi, or the argument to resize, is 4 which is the csize value specified in the second SIZ element in the file. In short, the COD_MARKER vector had to be resized to 4 which ended up reallocating it, meaning that previous saved pointers are invalidated. Continuing execution leads us again to COD element decoder, but this time a different path is taken. Instead of new reference to a resized vector, an old one is used but with a new counter:

```
eax=0d3cecc8 ebx=00000000 ecx=0d872f28 edx=00001ec6 esi=00000000 edi=0d3cee0c
eip=6e2cd9f5 esp=0d3ceca8 ebp=0d3ced54 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
Windows_Data_Pdf!CCodeStreamDecoder::s_CODMarkerDecoder+0x1e5:
6e2cd9f5 e8c3e6ffff      call    Windows_Data_Pdf!COD_MARKER::operator= (6e2cc0bd)
0:013> ub
Windows_Data_Pdf!CCodeStreamDecoder::s_CODMarkerDecoder+0x1d2:
6e2cd9e2 5f              pop     edi
6e2cd9e3 3c76            cmp     al,76h
6e2cd9e5 1d8bf38b8f      sbb     eax,8F8BF38Bh
6e2cd9ea 3c01            cmp     al,1
6e2cd9ec 0000            add     byte ptr [eax],al
6e2cd9ee 8d44241c        lea     eax,[esp+1Ch]
6e2cd9f2 50              push    eax
6e2cd9f3 03ce            add     ecx,esi
0:013> dd ecx
0d872f28  c0c0c001 00000000 00000000 00000000
0d872f38  00000000 00000000 00000000 00000000
0d872f48  00000000 00000005 00000001 00000001
0d872f58  00000020 00000020 00000000 c0c00000
0d872f68  00000001 00000001 c0c0c001 00000000
0d872f78  00000000 00000000 00000000 00000000
0d872f88  00000000 00000000 00000000 00000005
0d872f98  00000001 00000001 00000020 00000020
0:013> u
Windows_Data_Pdf!CCodeStreamDecoder::s_CODMarkerDecoder+0x1e5:
6e2cd9f5 e8c3e6ffff      call    Windows_Data_Pdf!COD_MARKER::operator= (6e2cc0bd)
6e2cd9fa 43              inc     ebx
6e2cd9fb 83c648          add     esi,48h
6e2cd9fe 3b5f3c          cmp     ebx,dword ptr [edi+3Ch]
6e2cda01 72e5            jb      Windows_Data_Pdf!CCodeStreamDecoder::s_CODMarkerDecoder+0x1d8 (6e2cd9e8)
6e2cda03 6afe            push    0FFFFFFFEh
6e2cda05 58              pop     eax
6e2cda06 2b442414        sub     eax,dword ptr [esp+14h]
0:013> dd edi+3c L1
0d3cee48  00000004

```

In the above debugging output, we can see that the max counter value is 4 (as specified by new csiz value) but the vector being used is still the same as before, 0d872f28. In the fourth iteration of this loop, the index into the vector elements will be increased past the allocated heap chunk resulting in an out of bound memory access:

```
Breakpoint 4 hit
eax=0d3cecc8 ebx=00000003 ecx=0d873000 edx=00000000 esi=000000d8 edi=0d3cee0c
eip=6e2cd9f5 esp=0d3ceca8 ebp=0d3ced54 iopl=0         nv up ei pl nz ac pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216
Windows_Data_Pdf!CCodeStreamDecoder::s_CODMarkerDecoder+0x1e5:
6e2cd9f5 e8c3e6ffff      call    Windows_Data_Pdf!COD_MARKER::operator= (6e2cc0bd)
0:013> dd ecx
0d873000  ???????? ???????? ???????? ????????
0d873010  ???????? ???????? ???????? ????????
0d873020  ???????? ???????? ???????? ????????
0d873030  ???????? ???????? ???????? ????????
0d873040  ???????? ???????? ???????? ????????
0d873050  ???????? ???????? ???????? ????????
0d873060  ???????? ???????? ???????? ????????
0d873070  ???????? ???????? ???????? ????????

```

This ultimately leads to a crash due to invalid memory write.

With page heap turned off, memory past the end of the heap chunk above will be readable and writable so the process wouldn’t crash there. By carefully controlling the contents of the memory past the adjacent chunk further memory corruption can be achieved possibly leading to arbitrary code execution. Similarly to COD marker, CQD marker parsing is affected with the same out of bounds access/write issue. A stale reference is being reused there too leading to other interesting memory overwrite primitives:

```
.text:6E2CE03F mov     ecx, [ebx+130h]
.text:6E2CE045 lea     eax, [esp+0ACh+var_8C]
.text:6E2CE049 push    eax
.text:6E2CE04A add     ecx, edi
.text:6E2CE04C call    QCD_MARKER::operator=(QCD_MARKER const &)
.text:6E2CE051 inc     esi
.text:6E2CE052 add     edi, 20h
.text:6E2CE055 cmp     esi, [ebx+3Ch]
.text:6E2CE058 jb      short loc_6E2CE0
```

Above disassembly is from CCodeStreamDecoder::s_QCDMarkerDecoder method call and in it, the stale CQD vector pointer is used to iterate through its elements with an assignment operator. The same out of bounds issue occurs and leads to a different crash inside QCD_MARKER::operator= where a call to memmove is passed an pointer from invalid memory.

Finally, without page heap, the following crash occurs:

```
(218.fa0): Windows Runtime Originate Error - code 40080201 (first chance)
(218.fa0): C++ EH exception - code e06d7363 (first chance)
(218.fa0): Windows Runtime Originate Error - code 40080201 (first chance)
(218.fa0): C++ EH exception - code e06d7363 (first chance)
(218.13cc): C++ EH exception - code e06d7363 (first chance)
HEAP[mspdf.exe]: Invalid address specified to RtlFreeHeap( 00A90000, 033B4D40 )
(218.13cc): Break instruction exception - code 80000003 (first chance)
eax=00258000 ebx=033b4d38 ecx=033b4d38 edx=0000003f esi=00a90000 edi=00000000
eip=7778ee8a esp=0426da84 ebp=0426da9c iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
ntdll!RtlpBreakPointHeap+0x19:
7778ee8a cc              int     3
0:014> k 10
 # ChildEBP RetAddr
00 0426da80 7773cce7 ntdll!RtlpBreakPointHeap+0x19
01 0426da9c 7778e0a8 ntdll!RtlpValidateHeapEntry+0x6c924
02 0426daf4 776ecc3e ntdll!RtlDebugFreeHeap+0xbf
03 0426dbf8 776eb4c8 ntdll!RtlpFreeHeap+0xc3e
04 0426dc24 770577a5 ntdll!RtlFreeHeap+0x268
05 0426dc70 6e029505 msvcrt!free+0x65
06 0426dc80 6e1f9481 Windows_Data_Pdf!std::vector<double,std::allocator<double> >::~vector<double,std::allocator<double> >+0x1a
07 0426dc98 6e1f8e56 Windows_Data_Pdf!std::vector<std::unique_ptr<CTile,std::default_delete<CTile> >,std::allocator<std::unique_ptr<CTile,std::default_delete<CTile> > > >::_Tidy+0x2f
08 0426dca0 77050ea7 Windows_Data_Pdf!CCodeStreamDecoder::~CCodeStreamDecoder+0x1b
09 0426ed2c 6e1f8b47 msvcrt!_NLG_Return
0a 0426ed90 6e1f8740 Windows_Data_Pdf!PDF::CJPXDecoderByteStream::_Decode+0xdf
0b 0426ed98 6e05660f Windows_Data_Pdf!PDF::CJPXDecoderByteStream::DecodeData+0x10
0c 0426eddc 6e28c7e0 Windows_Data_Pdf!Infra::CByteStreamDecorator::Initialize+0x5f
0d 0426ee08 6e0e6a3f Windows_Data_Pdf!PDF::CPDFFactory::CreateByteStreamJPXDecoder+0x50
0e 0426ef8c 6e0550b9 Windows_Data_Pdf!PDF::CStreamObject::_Decompress+0x9196b
0f 0426efb4 6e1b91ed Windows_Data_Pdf!PDF::CStreamObject::DecodeByteStream+0x49

``` The above crash occurs after the out of bound memory write in COD marker decoder corrupts heap metadata and an invalid pointer gets used during the CQD decoder.

In order to successfully exploit this vulnerability, a high control over the heap contents is needed which can possibly be achieved with calculated placement of tile information and other boxes in the jp2 file. It is also possible that the vulnerability can be triggered multiple times while parsing the same file, giving the attacker even greater control over the overwrites.

In summary, the vulnerability is due to the fact that SIZ element present inside tile data element (which seems to violate the standard) resizes the COD and CQD vectors, but a stale pointer gets reused leading to out of bounds write. A detection of malicious files of this nature can be based on a fact that a one or more tile parts have a SIZ element that specifies csiz greater than the initial, global SIZ element.

Vulnerability analysis is done on a custom simple sample application that utilizes PDF API, but the supplied testcase also crashes in Microsoft Edge browser.

Crash Information

With application verifier and page heap enabled, output of “analyze -v”:

 (20.90): Windows Runtime Originate Error - code 40080201 (first chance)
 (20.90): C++ EH exception - code e06d7363 (first chance)
 (20.90): Windows Runtime Originate Error - code 40080201 (first chance)
 (20.90): C++ EH exception - code e06d7363 (first chance)
 (20.f3c): C++ EH exception - code e06d7363 (first chance)


 ===========================================================
 VERIFIER STOP 0000000F: pid 0x20: corrupted suffix pattern

      063C1000 : Heap handle
      094EFCF0 : Heap block
      000000D8 : Block size
      094EFDC8 : corruption address
 ===========================================================
 This verifier stop is not continuable. Process will be terminated
 when you use the `go' debugger command.
 ===========================================================

 (20.f3c): Break instruction exception - code 80000003 (first chance)
 eax=060af260 ebx=00000000 ecx=060af260 edx=0000000f esi=094efcf0 edi=72f411a0
 eip=72f3cbfe esp=0a4ad8dc ebp=0a4ad900 iopl=0         nv up ei pl nz na po nc
 cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
 verifier!VerifierStopMessage+0x27e:
 72f3cbfe cc              int     3
 0:014> !analyze -v
 *******************************************************************************
 *                                                                             *
 *                        Exception Analysis                                   *
 *                                                                             *
 *******************************************************************************

 APPLICATION_VERIFIER_HEAPS_CORRUPTED_HEAP_BLOCK_SUFFIX (f)
 Corrupted suffix pattern for heap block.
 Most typically this happens for buffer overrun errors. Sometimes the application
 verifier places non-accessible pages at the end of the allocation and buffer
 overruns will cause an access violation and sometimes the heap block is
 followed by a magic pattern. If this pattern is changed when the block gets
 freed you will get this break. These breaks can be quite difficult to debug
 because you do not have the actual moment when corruption happened.
 You just have access to the free moment (stop happened here) and the
 allocation stack trace (!heap -p -a HEAP_BLOCK_ADDRESS)
 Arguments:
 Arg1: 063c1000, Heap handle used in the call.
 Arg2: 094efcf0, Heap block involved in the operation.
 Arg3: 000000d8, Size of the heap block.
 Arg4: 094efdc8, Corruption address.

 DUMP_CLASS: 2

 DUMP_QUALIFIER: 0

 FAULTING_IP:
 verifier!VerifierStopMessage+27e
 72f3cbfe cc              int     3

 EXCEPTION_RECORD:  (.exr -1)
 ExceptionAddress: 72f3cbfe (verifier!VerifierStopMessage+0x0000027e)
    ExceptionCode: 80000003 (Break instruction exception)
   ExceptionFlags: 00000000
 NumberParameters: 1
    Parameter[0]: 00000000

 FAULTING_THREAD:  00000f3c

 DEFAULT_BUCKET_ID:  STATUS_BREAKPOINT_AVRF

 PROCESS_NAME:  mspdf.exe

 ERROR_CODE: (NTSTATUS) 0x80000003 - {EXCEPTION}  Breakpoint  A breakpoint has been reached.

 EXCEPTION_CODE: (HRESULT) 0x80000003 (2147483651) - One or more arguments are invalid

 EXCEPTION_CODE_STR:  80000003

 EXCEPTION_PARAMETER1:  00000000

 WATSON_BKT_PROCSTAMP:  5706a632

 WATSON_BKT_MODULE:  verifier.dll

 WATSON_BKT_MODSTAMP:  5632d7df

 WATSON_BKT_MODOFFSET:  cbfe

 WATSON_BKT_MODVER:  10.0.10586.0

 MODULE_VER_PRODUCT:  Microsoft® Windows® Operating System

 BUILD_VERSION_STRING:  10.0.10586.162 (th2_release_sec.160223-1728)

 MODLIST_WITH_TSCHKSUM_HASH:  6648028320ab5cbba7b6c72455d4e5c1de630a24

 MODLIST_SHA1_HASH:  5c045408b1c06db5d658dde68dc1f6871a92acac

 NTGLOBALFLAG:  2000100

 APPLICATION_VERIFIER_FLAGS:  48004

 PRODUCT_TYPE:  1

 SUITE_MASK:  272

 APPLICATION_VERIFIER_LOADED: 1

 APP:  mspdf.exe

 ANALYSIS_SESSION_HOST:  DESKTOP-G0NTBS7

 ANALYSIS_SESSION_TIME:  04-24-2016 20:00:13.0430

 ANALYSIS_VERSION: 10.0.10586.567 x86fre

 THREAD_ATTRIBUTES:
 OS_LOCALE:  ENU

 PROBLEM_CLASSES:




     Tid    [0x0]
     Frame  [0x00]
     String [STATUS_BREAKPOINT]
     Data Bucketing



 AVRF
     Tid    [0xf3c]
     Frame  [0x00]: verifier!VerifierStopMessage
     Failure Bucketing


 BUGCHECK_STR:  STATUS_BREAKPOINT_AVRF

 STACK_TEXT:
 0a4ad900 72f3aa52 0000000f 72f31b80 063c1000 verifier!VerifierStopMessage+0x27e
 0a4ad964 72f3ae8a 063c1000 00000000 094efcf0 verifier!AVrfpDphReportCorruptedBlock+0x1c2
 0a4ad9c0 72f3bc3b 063c1000 094efcf0 00000000 verifier!AVrfpDphCheckNormalHeapBlock+0x11a
 0a4ad9e0 72f411b2 063c0000 094efcf0 0a4ada50 verifier!AvrfpDphCheckPageHeapAllocation+0x6b
 0a4ad9f0 72f51def 063c0000 094efcf0 638dce2d verifier!VerifierCheckPageHeapAllocation+0x12
 0a4ada50 770577a5 063c0000 00000000 094efcf0 verifier!AVrfpRtlFreeHeap+0x5f
 0a4ada9c 72f52dd5 094efcf0 638dcea9 094efdc8 msvcrt!free+0x65
 0a4adad4 6e2c934e 094efcf0 e06d7363 19930522 verifier!AVrfp_delete+0x45
 0a4adae8 6e2c8f8b 0a4ae96c 6e2c8e61 00000000 Windows_Data_Pdf!std::vector<COD_MARKER,std::allocator<COD_MARKER> >::_Tidy+0x38
 0a4adaf0 6e2c8e61 00000000 77050ea7 e06d7363 Windows_Data_Pdf!JPXMetadata::~JPXMetadata+0x26
 0a4adaf8 77050ea7 e06d7363 00000000 0a4adb18 Windows_Data_Pdf!CCodeStreamDecoder::~CCodeStreamDecoder+0x26
 0a4aeb64 6e2c8b47 094e9de4 00000800 aaa50673 msvcrt!_NLG_Return
 0a4aebc8 6e2c8740 094e9dcc 6e12660f 094e9dcc Windows_Data_Pdf!PDF::CJPXDecoderByteStream::_Decode+0xdf
 0a4aebd0 6e12660f 094e9dcc 6e1265b0 0a4aec34 Windows_Data_Pdf!PDF::CJPXDecoderByteStream::DecodeData+0x10
 0a4aec14 6e35c7e0 aaa5014b 09456e34 6e35c790 Windows_Data_Pdf!Infra::CByteStreamDecorator::Initialize+0x5f
 0a4aec40 6e1b6a3f 0a4aec6c 0a4aee4c 094e9d40 Windows_Data_Pdf!PDF::CPDFFactory::CreateByteStreamJPXDecoder+0x50
 0a4aedc4 6e1250b9 0a4aee4c 094e9d40 6e125070 Windows_Data_Pdf!PDF::CStreamObject::_Decompress+0x9196b
 0a4aedec 6e2891ed 0a4aee4c 094e9d40 aaa51d87 Windows_Data_Pdf!PDF::CStreamObject::DecodeByteStream+0x49
 0a4af08c 6e2874e6 0a4af2d8 094e88f8 094e8900 Windows_Data_Pdf!Builder::CImageHandler::_LoadImageObject+0x198
 0a4af0d4 6e25a50c 0a4af2d8 094e88f8 094e8900 Windows_Data_Pdf!Builder::CImageHandler::LoadImageObject+0x41
 0a4af158 6e353406 0a4af2d8 0a4af3b8 094cb464 Windows_Data_Pdf!Builder::CResourceFactory::LoadImageSource+0xbc
 0a4af5f4 6e3101ba 094e943c 6e310190 094e9430 Windows_Data_Pdf!PageElements::GraphicsCommandUpdater::UpdateGraphicsCommand+0xee6
 0a4af60c 6e1282da 6e12b880 094e93d0 aaa51b2b Windows_Data_Pdf!std::_Func_impl<std::_Callable_obj<<lambda_267d1e4465265120ffca50182e13906e>,0>,std::allocator<std::_Func_class<void,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil> >,void,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil>::_Do_call+0x2a
 0a4af620 6e12b888 6e127fbf 094e91f8 094e9208 Windows_Data_Pdf!std::_Func_class<void,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil>::operator()+0x5a
 0a4af624 6e127fbf 094e91f8 094e9208 aaa51b33 Windows_Data_Pdf!std::_Func_impl<std::_Callable_obj<Infra::GlobalTask,0>,std::allocator<std::_Func_class<bool,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil> >,bool,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil>::_Do_call+0x8
 0a4af638 6e127d2b 6e1dc410 094e9740 00000001 Windows_Data_Pdf!std::_Func_class<bool,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil>::operator()+0x49
 0a4af648 6e1dc42f aaa51b73 6e1dc410 094e9850 Windows_Data_Pdf!Infra::CTask::Execute+0x33
 0a4af678 6d8353df 094e9740 094e9850 0a4af894 Windows_Data_Pdf!Infra::CAsyncTpWorker::CTpWorkItem::Invoke+0x1f
 0a4af6a4 6d834d20 5ab46f05 0a4af894 08b03ff0 threadpoolwinrt!Windows::System::Threading::CThreadPoolWorkItem::CommonWorkCallback+0xaf
 0a4af6d4 776dde13 0a4af894 094e9850 08b03ff0 threadpoolwinrt!Windows::System::Threading::CThreadPoolWorkItem::BatchedCallback+0x60
 0a4af7c4 776dcc25 0a4af894 08b04060 738bf041 ntdll!TppWorkpExecuteCallback+0x153
 0a4af974 750238f4 08abe0a8 750238d0 f814e18b ntdll!TppWorkerThread+0x555
 0a4af988 77715de3 08abe0a8 738bf0e5 00000000 KERNEL32!BaseThreadInitThunk+0x24
 0a4af9d0 77715dae ffffffff 7773b7db 00000000 ntdll!__RtlUserThreadStart+0x2f
 0a4af9e0 00000000 776dc6d0 08abe0a8 00000000 ntdll!_RtlUserThreadStart+0x1b


 THREAD_SHA1_HASH_MOD_FUNC:  6315750fc53d807b4155ffc0842ee6a03c9f40f3

 THREAD_SHA1_HASH_MOD_FUNC_OFFSET:  f4a0d5cef213b9229c67ca969977acae24171ac9

 THREAD_SHA1_HASH_MOD:  7f0f6cd60042c923021fe565fe770b7a413be340

 FOLLOWUP_IP:
 verifier!VerifierStopMessage+27e
 72f3cbfe cc              int     3

 FAULT_INSTR_CODE:  f87d83cc

 SYMBOL_STACK_INDEX:  0

 SYMBOL_NAME:  verifier!VerifierStopMessage+27e

 FOLLOWUP_NAME:  MachineOwner

 MODULE_NAME: verifier

 IMAGE_NAME:  verifier.dll

 DEBUG_FLR_IMAGE_TIMESTAMP:  5632d7df

 STACK_COMMAND:  ~14s ; kb

 BUCKET_ID:  STATUS_BREAKPOINT_AVRF_verifier!VerifierStopMessage+27e

 PRIMARY_PROBLEM_CLASS:  STATUS_BREAKPOINT_AVRF_verifier!VerifierStopMessage+27e

 BUCKET_ID_OFFSET:  27e

 BUCKET_ID_MODULE_STR:  verifier

 BUCKET_ID_MODTIMEDATESTAMP:  5632d7df

 BUCKET_ID_MODCHECKSUM:  5c097

 BUCKET_ID_MODVER_STR:  10.0.10586.0

 BUCKET_ID_PREFIX_STR:  STATUS_BREAKPOINT_AVRF_

 FAILURE_PROBLEM_CLASS:  STATUS_BREAKPOINT_AVRF

 FAILURE_EXCEPTION_CODE:  80000003

 FAILURE_IMAGE_NAME:  verifier.dll

 FAILURE_FUNCTION_NAME:  VerifierStopMessage

 BUCKET_ID_FUNCTION_STR:  VerifierStopMessage

 FAILURE_SYMBOL_NAME:  verifier.dll!VerifierStopMessage

 FAILURE_BUCKET_ID:  STATUS_BREAKPOINT_AVRF_80000003_verifier.dll!VerifierStopMessage

 TARGET_TIME:  2016-04-24T18:00:19.000Z

 OSBUILD:  10586

 OSSERVICEPACK:  0

 SERVICEPACK_NUMBER: 0

 OS_REVISION: 0

 OSPLATFORM_TYPE:  x86

 OSNAME:  Windows 10

 OSEDITION:  Windows 10 WinNt SingleUserTS

 USER_LCID:  0

 OSBUILD_TIMESTAMP:  2015-10-30 03:46:21

 BUILDDATESTAMP_STR:  160223-1728

 BUILDLAB_STR:  th2_release_sec

 BUILDOSVER_STR:  10.0.10586.162

 ANALYSIS_SESSION_ELAPSED_TIME: 176f

 ANALYSIS_SOURCE:  UM

 FAILURE_ID_HASH_STRING:  um:status_breakpoint_avrf_80000003_verifier.dll!verifierstopmessage

 FAILURE_ID_HASH:  {bedb7089-3b9b-ca23-9c37-a0231a6648d3}

 Followup:     MachineOwner
 ---------

Timeline

2016-04-28 - Vendor Disclosure
2016-08-09 - Public Release
Credit

Discovered by Aleksandar Nikolic of Cisco Talos.