Microsoft Windows PDF API Jpeg2000 csiz Remote Code Execution Vulnerability(CVE-2016-3319)

2017-10-13T00:00:00
ID SSV:96679
Type seebug
Reporter Root
Modified 2017-10-13T00:00:00

Description

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 QCDMARKER vector, and at [3] the same for CODMARKER vector. The pointers to both are stored atebx+78handebx+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 CODMARKER assignment operator with a write access violation. A step by step examination leads to the details of the crash. Firstly, CODMARKER 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&lt;COD_MARKER,std::allocator&lt;COD_MARKER&gt; &gt;::resize (6e2cd35b) 0:013&gt; 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&lt;QCD_MARKER,std::allocator&lt;QCD_MARKER&gt; &gt;::resize (6e2cd3ef) 6e2ce76b 56 push esi 6e2ce76c 8d8b84000000 lea ecx,[ebx+84h] 0:013&gt; 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&gt; 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&lt;COD_MARKER,std::allocator&lt;COD_MARKER&gt; &gt;::resize (6e2cd35b) 0:013&gt; 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&gt; 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 samethisas previously. Before the call, the same vector pointers are at0d3cee90, but after the call, the vector has been reallocated because of resize. In the above code, the value of theesi`, 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