CVE-2017-2791
Ichitaro Office contains a vulnerability that exists when trying to open a specially crafted PowerPoint file. Due to the application incorrectly handling the error case for a function’s result, the application will use this result in a pointer calculation for reading file data into. Due to this, the application will read data from the file into an invalid address thus corrupting memory. Under the right conditions this can lead to code execution under the context of the application.
JustSystems Ichitaro 2016 Trial
0:000> lm vm jxxtppt
06bc0000 06c47000 JXXTPPT C (export symbols) JXXTPPT.DLL
Image path: C:\Program Files\JustSystems\JSLIB32\JXXTPPT.DLL
File version: 1.0.3.0
Product version: 1.0.3.0
0:000> lm vm jsvda
277a0000 27826000 jsvda (export symbols) jsvda.dll
Image path: C:\Program Files\JustSystems\JSLIB32\jsvda.dll
File version: 3.3.312.1
Product version: 3.3.312.1
http://www.ichitaro.com https://www.justsystems.com/jp/download/trial/ichitaro
7.5 - CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H
Ichitaro’s word-processor includes the ability to parse data from various Microsoft Office document formats. Each of these document formats are stored using Microsoft’s Structured Storage format. When using these formats, the document contents are organized in the form of a list of streams. Within each stream is a list of records that are used to describe the attributes and contents of various parts of the document. When processing a Powerpoint Document (.ppt), the application will first have to identify the “PowerPoint Document” stream. Once the correct stream has been identified, the application will proceed to read records within the file in order to render its contents to the user. Each of these records are referred to as an Atom which is prefixed with a type and length and followed by its contents.
In order to determine the records of the document that were last edited, the application will first look up the “Current User” stream. This stream is typically a single-record that describes where the PersistDirectory records are at within the “PowerPoint Document” stream. The application begins to do this at address 0x6bd64a1. Once identifying the correct streams and storing them at +0x28 and +0x2c of an object, the application will read 0x14 bytes for the structure “CurrentUserAtom” from the “Current User” stream. This is portrayed in the following code.
JXXTPPT!JXXTPPT_Jsfc_Convert+0x15176:
06bd64a1 68e808c306 push offset JXXTPPT!DRFL_SaveFD3A+0x32e6d (06c308e8) ; "Current User
06bd64a6 8b4590 mov eax,dword ptr [ebp-70h] ; *this
06bd64a9 83c028 add eax,28h
06bd64ac 50 push eax ; *this+0x28
06bd64ad 8b4d08 mov ecx,dword ptr [ebp+8]
06bd64b0 51 push ecx
06bd64b1 8b4d90 mov ecx,dword ptr [ebp-70h]
06bd64b4 e8b8010000 call JXXTPPT!JXXTPPT_Jsfc_Convert+0x15346 (06bd6671)
06bd64b9 85c0 test eax,eax
06bd64bb 7438 je JXXTPPT!JXXTPPT_Jsfc_Convert+0x151ca (06bd64f5)
...
JXXTPPT!JXXTPPT_Jsfc_Convert+0x15259:
06bd6584 6a14 push 14h
06bd6586 8d4da0 lea ecx,[ebp-60h]
06bd6589 51 push ecx ; Target buffer of CurrentUser atom
06bd658a 8b5590 mov edx,dword ptr [ebp-70h]
06bd658d 8b4228 mov eax,dword ptr [edx+28h] ; "Current User" stream
06bd6590 50 push eax
06bd6591 e8deb5ffff call JXXTPPT!JXXTPPT_Jsfc_Convert+0x10849 (06bd1b74) ; Read 0x14 bytes from stream
06bd6596 83c40c add esp,0Ch
After the CurrentUser record’s structure has been filled, the application will use one of its fields, OffsetToCurrentEdit, to determine where the actual UserEditAtom is located within the PowerPoint Document stream. the application does this by seeking into the stream at address 0x6bd65b7. Afterwards, the application will read 0x1c bytes for the UserEditAtom header, and then call into the function at 0x6bd6601. The function at 0x6bd7372 will construct an object for the PersistDirectory within the Powerpoint file and read the number of directory entries as described in the file format specification.
JXXTPPT!JXXTPPT_Jsfc_Convert+0x1527c:
06bd65a7 8b4da8 mov ecx,dword ptr [ebp-58h] ; CurrentUser.OffsetToCurrentEdit
06bd65aa 83c108 add ecx,8
06bd65ad 51 push ecx
06bd65ae 6a00 push 0
06bd65b0 8b5590 mov edx,dword ptr [ebp-70h]
06bd65b3 8b422c mov eax,dword ptr [edx+2Ch] ; "PowerPoint Document" stream
06bd65b6 50 push eax
06bd65b7 e887b5ffff call JXXTPPT!JXXTPPT_Jsfc_Convert+0x10818 (06bd1b43) ; Seek to offset of UserEditAtom
06bd65bc 83c40c add esp,0Ch
...
JXXTPPT!JXXTPPT_Jsfc_Convert+0x152a2:
06bd65cd 6a1c push 1Ch
06bd65cf 8d4dd8 lea ecx,[ebp-28h] ; Target buffer of file data
06bd65d2 51 push ecx
06bd65d3 8b5590 mov edx,dword ptr [ebp-70h]
06bd65d6 8b422c mov eax,dword ptr [edx+2Ch] ; "PowerPoint Document" stream.
06bd65d9 50 push eax
06bd65da e895b5ffff call JXXTPPT!JXXTPPT_Jsfc_Convert+0x10849 (06bd1b74) ; Read 0x1c bytes of data from file
06bd65df 83c40c add esp,0Ch
...
JXXTPPT!JXXTPPT_Jsfc_Convert+0x152c2:
06bd65ed b907000000 mov ecx,7
06bd65f2 8d75d8 lea esi,[ebp-28h] ; 0x1c bytes of data
06bd65f5 8d7db4 lea edi,[ebp-4Ch]
06bd65f8 f3a5 rep movs dword ptr es:[edi],dword ptr [esi] ; Copy to %ebp-4c
06bd65fa 8d4db4 lea ecx,[ebp-4Ch] ; Header data of UserEditAtom
06bd65fd 51 push ecx
06bd65fe 8b4d90 mov ecx,dword ptr [ebp-70h]
06bd6601 e86c0d0000 call JXXTPPT!JXXTPPT_Jsfc_Convert+0x16047 (06bd7372) ; Parse Record
Inside the function at 0x6bd7372, the application will allocate space for a 0x4c object and initialize it with a record-type of 0x1772. This record-type corresponds with the PersistDirectory record described within the Powerpoint specification. The constructor for this 0x4c object is also responsible for initializing the index that is overflown at offset 0x40 of the structure. This index is used to calculate the total number of offsets that are maintained within the PersistDirectory records for the document and is used to determine how much space to allocate for them.
JXXTPPT!JXXTPPT_Jsfc_Convert+0x16081:
06bd73ac 6a4c push 4Ch
06bd73ae e8dbbffeff call JXXTPPT!JXXTPPT_Jsfc_Convert+0x2063 (06bc338e) ; Allocate 0x4c bytes of space
06bd73b3 83c404 add esp,4
...
06bd73c6 6a00 push 0
06bd73c8 8b4dbc mov ecx,dword ptr [ebp-44h]
06bd73cb e850970000 call JXXTPPT!JXXTPPT_Jsfc_Convert+0x1f7f5 (06be0b20) ; \
\
JXXTPPT!JXXTPPT_Jsfc_Convert+0x1f811:
06be0b3c 6872170000 push 1772h ; Enumeration for RT_PersistDirectoryAtom
06be0b41 8b4508 mov eax,dword ptr [ebp+8]
06be0b44 50 push eax
06be0b45 8b4df0 mov ecx,dword ptr [ebp-10h]
06be0b48 e8a387ffff call JXXTPPT!JXXTPPT_Jsfc_Convert+0x17fc5 (06bd92f0) ; atom record object
...
06be0b54 8b4df0 mov ecx,dword ptr [ebp-10h]
06be0b57 83c140 add ecx,40h ; Index at +0x40
06be0b5a e8c1e60000 call JXXTPPT!JXXTPPT_Jsfc_Convert+0x2def5 (06bef220) ; Construct an object
After constructing the 0x4c byte object, the application will return back to the function 0x6bd7372 at the address 0x6bd73d0. This function contains numerous loops in order to properly parse the UserEditAtom and all the PersistDirectory entries within a document. The outer-most loop will read the UserEditAtom, seek to the dword specified in the offsetPersistDirectory field, and then will simply consume all the offsets of the record within the PersistDirectoryAtoms.
JXXTPPT!JXXTPPT_Jsfc_Convert+0x16119:
06bd7444 6a04 push 4
06bd7446 8d55cc lea edx,[ebp-34h]
06bd7449 52 push edx
06bd744a 8b45ac mov eax,dword ptr [ebp-54h]
06bd744d 8b482c mov ecx,dword ptr [eax+2Ch]
06bd7450 51 push ecx
06bd7451 e81ea7ffff call JXXTPPT!JXXTPPT_Jsfc_Convert+0x10849 (06bd1b74) ; Read 4 bytes from the file for the offset
06bd7456 83c40c add esp,0Ch
...
JXXTPPT!JXXTPPT_Jsfc_Convert+0x161b7:
06bd74e2 8b4ddc mov ecx,dword ptr [ebp-24h] ; cPersist field from record
06bd74e5 8b55e0 mov edx,dword ptr [ebp-20h] ; \
06bd74e8 8d048a lea eax,[edx+ecx*4] ; |- Seek current position forward.
06bd74eb 8945e0 mov dword ptr [ebp-20h],eax ; /
06bd74ee 8b4de0 mov ecx,dword ptr [ebp-20h] ; current position
06bd74f1 3b4dd0 cmp ecx,dword ptr [ebp-30h] ; record size
06bd74f4 0f824affffff jb JXXTPPT!JXXTPPT_Jsfc_Convert+0x16119 (06bd7444)
For each iteration of this loop, the application will read a dword that determines the PersistId as well as the number of offsets that are stored as cPersist. Using cPersist as the number of offsets to read, the next loop will then read each offset and update the fields in the object at %ebp-10. This object is responsible for containing the PersistId as well as each offset for every single persist directory entry within the document. The field at 0x40 of this object will be incremented for each offset that is read out of all the records within the document.
JXXTPPT!JXXTPPT_Jsfc_Convert+0x16145:
06bd7470 8b45cc mov eax,dword ptr [ebp-34h]
06bd7473 c1e814 shr eax,14h
06bd7476 25ff0f0000 and eax,0FFFh
06bd747b 8945dc mov dword ptr [ebp-24h],eax ; PersistDirectoryEntry.cPersist
06bd747e 8b4dcc mov ecx,dword ptr [ebp-34h]
06bd7481 81e1ffff0f00 and ecx,0FFFFFh
06bd7487 894dc8 mov dword ptr [ebp-38h],ecx ; PersistDirectoryEntry.PersistId
...
JXXTPPT!JXXTPPT_Jsfc_Convert+0x16168: ; loop
06bd7493 8b55c4 mov edx,dword ptr [ebp-3Ch] ; \
06bd7496 83c201 add edx,1 ; |- cPersist Index
06bd7499 8955c4 mov dword ptr [ebp-3Ch],edx ; /
06bd749c 8b45c4 mov eax,dword ptr [ebp-3Ch]
06bd749f 3b45dc cmp eax,dword ptr [ebp-24h] ; Check if cPersist Index is larger than cPersist
06bd74a2 733e jae JXXTPPT!JXXTPPT_Jsfc_Convert+0x161b7 (06bd74e2)
...
06bd74a4 6a04 push 4
06bd74a6 8d4dd4 lea ecx,[ebp-2Ch] ; offset
06bd74a9 51 push ecx
06bd74aa 8b55ac mov edx,dword ptr [ebp-54h]
06bd74ad 8b422c mov eax,dword ptr [edx+2Ch]
06bd74b0 50 push eax
06bd74b1 e8bea6ffff call JXXTPPT!JXXTPPT_Jsfc_Convert+0x10849 (06bd1b74) ; Read 4 bytes for the offset
06bd74b6 83c40c add esp,0Ch
...
JXXTPPT!JXXTPPT_Jsfc_Convert+0x1619c:
06bd74c7 8b4dd4 mov ecx,dword ptr [ebp-2Ch] ; offset
06bd74ca 51 push ecx
06bd74cb 8b55c8 mov edx,dword ptr [ebp-38h] ; persistId
06bd74ce 52 push edx
06bd74cf 8b4df0 mov ecx,dword ptr [ebp-10h]
06bd74d2 e857970000 call JXXTPPT!JXXTPPT_Jsfc_Convert+0x1f903 (06be0c2e) ; XXX: Store offset/persistId and increase current object+40 count
06bd74d7 8b45c8 mov eax,dword ptr [ebp-38h] ; \
06bd74da 83c001 add eax,1 ; |- Increase persistId
06bd74dd 8945c8 mov dword ptr [ebp-38h],eax ; /
06bd74e0 ebb1 jmp JXXTPPT!JXXTPPT_Jsfc_Convert+0x16168 (06bd7493) ; continue loop
Immediately following this loop, the application will process the rest of the PersistDirectory records linked by the first record. It does this by checking to see if CurrentAtom.offsetLastEdit is 0. If this is true, the loop will terminate. Otherwise, the loop will seek to the specified offset of the document and continue to read offsets out of the record similarly to the first loop described earlier.
JXXTPPT!JXXTPPT_Jsfc_Convert+0x161cf:
06bd74fa 8b5508 mov edx,dword ptr [ebp+8] ; CurrentAtom
06bd74fd 837a0800 cmp dword ptr [edx+8],0 ; Break if CurrentAtom.offsetLastEdit is 0
06bd7501 0f8460010000 je JXXTPPT!JXXTPPT_Jsfc_Convert+0x1633c (06bd7667)
...
JXXTPPT!JXXTPPT_Jsfc_Convert+0x1631f:
06bd764a 8b45dc mov eax,dword ptr [ebp-24h] ; cPersist
06bd764d 8b4de0 mov ecx,dword ptr [ebp-20h] ; \
06bd7650 8d1481 lea edx,[ecx+eax*4] ; |- currentPsition
06bd7653 8955e0 mov dword ptr [ebp-20h],edx ; /
06bd7656 8b45e0 mov eax,dword ptr [ebp-20h] ; currentPosition
06bd7659 3b45d0 cmp eax,dword ptr [ebp-30h] ; check if currentPosition is larger than record size
06bd765c 0f824dffffff jb JXXTPPT!JXXTPPT_Jsfc_Convert+0x16284 (06bd75af)
06bd7662 e993feffff jmp JXXTPPT!JXXTPPT_Jsfc_Convert+0x161cf (06bd74fa)
Inside this loop, the application will seek to the file offset specified in the current record’s OffsetLastEdit field. The root of this vulnerability is due to an aggressor being allowed to specify an OffsetLastEdit field that points to any number of chained records. This allows for an attacker to be able to control when this loop terminates and can set the number of offsets to any value they deem useful. This loop has a similar functionality of reading offsets based on the value of the cPersist and PersistId fields that was described prior. This can be used to corrupt arbitrary memory which will be described later.
JXXTPPT!JXXTPPT_Jsfc_Convert+0x161e3:
06bd750e 8b4508 mov eax,dword ptr [ebp+8] ; Record Header
06bd7511 8b4808 mov ecx,dword ptr [eax+8] ; offsetLastEdit
06bd7514 83c108 add ecx,8 ; seek past record's header
06bd7517 51 push ecx
06bd7518 6a00 push 0
06bd751a 8b55ac mov edx,dword ptr [ebp-54h]
06bd751d 8b422c mov eax,dword ptr [edx+2Ch] ; file object
06bd7520 50 push eax
06bd7521 e81da6ffff call JXXTPPT!JXXTPPT_Jsfc_Convert+0x10818 (06bd1b43) ; seek the file pointer to offsetLastEdit+8
06bd7526 83c40c add esp,0Ch
Before entering the inner-most loop for reading offsets, however, the application will determine the PersistId and the number of offsets that follow by reading a dword and extracting a number from it’s bits. These will be used to read the number of offsets from the document and store them for retrieval later. The function call at 0x6bd763a will be used to store the offset and PersistId for each iteration of the loop.
JXXTPPT!JXXTPPT_Jsfc_Convert+0x162b0:
06bd75db 8b55cc mov edx,dword ptr [ebp-34h]
06bd75de c1ea14 shr edx,14h
06bd75e1 81e2ff0f0000 and edx,0FFFh
06bd75e7 8955dc mov dword ptr [ebp-24h],edx ; cPersist
06bd75ea 8b45cc mov eax,dword ptr [ebp-34h]
06bd75ed 25ffff0f00 and eax,0FFFFFh
06bd75f2 8945c8 mov dword ptr [ebp-38h],eax ; PersistId
...
JXXTPPT!JXXTPPT_Jsfc_Convert+0x162d3: ; loop
06bd75fe 8b4dc0 mov ecx,dword ptr [ebp-40h] ; \
06bd7601 83c101 add ecx,1 ; |- increase current index
06bd7604 894dc0 mov dword ptr [ebp-40h],ecx ; /
06bd7607 8b55c0 mov edx,dword ptr [ebp-40h] ; current index
06bd760a 3b55dc cmp edx,dword ptr [ebp-24h] ; check if index larger than cPersist
06bd760d 733b jae JXXTPPT!JXXTPPT_Jsfc_Convert+0x1631f (06bd764a)
...
JXXTPPT!JXXTPPT_Jsfc_Convert+0x16304:
06bd762f 8b45d4 mov eax,dword ptr [ebp-2Ch] ; offset that was read
06bd7632 50 push eax
06bd7633 8b4dc8 mov ecx,dword ptr [ebp-38h] ; PersistId
06bd7636 51 push ecx
06bd7637 8b4df0 mov ecx,dword ptr [ebp-10h] ; file object
06bd763a e8ef950000 call JXXTPPT!JXXTPPT_Jsfc_Convert+0x1f903 (06be0c2e) ; XXX: Increases Current Offset Index
06bd763f 8b55c8 mov edx,dword ptr [ebp-38h] ; \
06bd7642 83c201 add edx,1 ; |- PersistId
06bd7645 8955c8 mov dword ptr [ebp-38h],edx ; /
06bd7648 ebb4 jmp JXXTPPT!JXXTPPT_Jsfc_Convert+0x162d3 (06bd75fe) ; continue loop
At the function 0x6be0c2e, the application will allocate 8 bytes of space to store the PersistId and the offset. Also within this function, the aplication will update the current index of offsets. This is done to offset 0x40 of the PersistDirectory object that was allocated with the enumeration 0x1772 earlier. This counter represents the number of offsets that have been read and will be increased without any constraints on it’s bounds. Due to a potential attacker being able to control when the linked list of Persist Records terminates as well as the number of offsets that are within a record allows one to set this field to any value that they choose.
JXXTPPT!JXXTPPT_Jsfc_Convert+0x1f90c:
06be0c37 6a08 push 8
06be0c39 e85027feff call JXXTPPT!JXXTPPT_Jsfc_Convert+0x2063 (06bc338e) ; allocate space
06be0c3e 83c404 add esp,4
...
JXXTPPT!JXXTPPT_Jsfc_Convert+0x1f92a:
06be0c55 8b4dfc mov ecx,dword ptr [ebp-4] ; pair
06be0c58 51 push ecx
06be0c59 8b4df8 mov ecx,dword ptr [ebp-8] ; 0x1772 Tagged Object
06be0c5c 83c140 add ecx,40h ; XXX: Points to the current number of offsets that have been read
06be0c5f e83c7bffff call JXXTPPT!JXXTPPT_Jsfc_Convert+0x17475 (06bd87a0) ; Update Index in Tagged Object
The function at 0x6bd87a0 is simply a wrapper that calls 0x6be5ba0. Inside the function at 0x6be5ba0, the application will check to see if there’s enough space for the current number of offsets. If this is not the case then the application will increase the current size by a power of two and then resize it using realloc. It is at address 0x6be5bf6 that the application immediately stores this to *this+8 without checking to see if realloc has returned a failure result (NULL). Due to this oversight, the function call can fail under memory pressure which causes some pointer arithmetic that follows to point outside the bounds of the original allocation.
JXXTPPT!JXXTPPT_Jsfc_Convert+0x1747c:
06bd87a7 8b45fc mov eax,dword ptr [ebp-4] ; XXX: Number of offsets that have been read
06bd87aa 8b08 mov ecx,dword ptr [eax]
06bd87ac 51 push ecx
06bd87ad 8b5508 mov edx,dword ptr [ebp+8] ; Pair
06bd87b0 52 push edx
06bd87b1 8b4dfc mov ecx,dword ptr [ebp-4] ; XXX: Number of offsets that have been read
06bd87b4 e8e7d30000 call JXXTPPT!JXXTPPT_Jsfc_Convert+0x24875 (06be5ba0) \
\
JXXTPPT!JXXTPPT_Jsfc_Convert+0x248a6:
06be5bd1 8b55f8 mov edx,dword ptr [ebp-8] ; Take original size
06be5bd4 d1e2 shl edx,1 ; Increase size by power of two
06be5bd6 8b45fc mov eax,dword ptr [ebp-4] ;
06be5bd9 895004 mov dword ptr [eax+4],edx ; Store it back to object
06be5bdc 8b4dfc mov ecx,dword ptr [ebp-4]
06be5bdf 8b5104 mov edx,dword ptr [ecx+4] ; Read that size back
06be5be2 c1e202 shl edx,2
06be5be5 52 push edx ; new size
06be5be6 8b45fc mov eax,dword ptr [ebp-4]
06be5be9 8b4808 mov ecx,dword ptr [eax+8]
06be5bec 51 push ecx ; old pointer
06be5bed ff15f424c206 call dword ptr [JXXTPPT!DRFL_SaveFD3A+0x24a79 (06c224f4)] ; calls realloc
06be5bf3 83c408 add esp,8
06be5bf6 8b55fc mov edx,dword ptr [ebp-4]
06be5bf9 894208 mov dword ptr [edx+8],eax ; XXX: Store pointer without checking for failure
Immediately after resizing the buffer to the next power of two, the application will proceed to write the user-controlled offset to NULL + Index*4. This actually happens at address 0x6be5c38. Normally a near-NULL write is not exploitable on modern systems due to NULL page constraints, but due to an attacker nearly being able to control the index that’s being added to this pointer, one can specify an index that’s larger than a page. In this situation, utilizing a technique such as a heapspray to force a useful data structure being mapped at an address that’s a power of two, an aggressor could potentially corrupt memory that might allow them to manipulate more of the state of the application.
JXXTPPT!JXXTPPT_Jsfc_Convert+0x24901:
06be5c2c 8b4dfc mov ecx,dword ptr [ebp-4]
06be5c2f 8b5108 mov edx,dword ptr [ecx+8] ; NULL pointer from re-alloc
06be5c32 8b450c mov eax,dword ptr [ebp+0Ch] ; Number of offsets currently read
06be5c35 8b4d08 mov ecx,dword ptr [ebp+8] ; Pair
06be5c38 890c82 mov dword ptr [edx+eax*4],ecx ; XXX: Write offset to user-controlled value
Ichitaro Word Processor is able to open various file formats that have been created by the Microsoft Office Suite of applications. These file formats are encoded in a filesystem of sorts known as the Structured Storage file-format. The Structured Storage file-format has the ability to encode multiple files/streams within the document. It is within these streams that the contents of the document can be located. Each of these streams contain a list of records that are each prefixed with the following header. Within this structure, a record contains a 4-bit version followed by a 12-bit Instance. Following it is a 16-bit type, a 32-bit length, followed by the record’s contents.
<class Header> 'header'
[86b7e] <instance VersionInstance 'Version/Instance'> 0 / 0x000
[86b80] <instance RecordType 'Type'> RT_UserEditAtom(0xff5)
[86b82] <instance uint32_t 'Length'> 0x0000001c (28)
Despite the contents of a Powerpoint document being primarily contained within the “PowerPoint Document” stream, the application starts by reading the contents of the “Current User” stream. The Current User stream consists entirely of just a single record named the CurrentUserAtom. At offset 0 of the stream will be the following structure. Within this structure at offset 0x10 is a dword that represents the offset into the “PowerPoint Document” stream. This offset points to another structure named the UserEditAtom which contains information about the previous edits that were made to the document. The offset in the following structure is based it’s location within the provided sample.
<class RecordGeneral> 'unnamed_547bea0' {unnamed=True}
[57fcc0] <instance Header 'header'> version=0 instance=0x000 type=0x0ff6 length=0x0000001d
[57fcc8] <instance powerpoint.CurrentUserAtom 'data'>
[57fcc8] <instance uint4 'size'> 0x00000014 (20)
[57fccc] <instance dynamic.block(4) 'headerToken'> "\x5f\xc0\x91\xe3"
[57fcd0] <instance pointer_t 'offsetToCurrentEdit'> *0x86b7e
[57fcd4] <instance uint2 'lenUserName'> 0x0005 (5)
[57fcd6] <instance uint2 'docfileversion'> 0x03f4 (1012)
[57fcd8] <instance ubyte1 'majorVersion'> 0x03 (3)
[57fcd9] <instance ubyte1 'minorVersion'> 0x00 (0)
[57fcda] <instance block(2) 'unused'> "\x2e\x0c"
[57fcdc] <instance pstr.string<char_t> 'ansiUserName'> u'xxxxx'
[57fce1] <instance uint4 'relVersion'> 0x00000008 (8)
At offset 0x86b7e of the “PowerPoint Document” stream is the UserEditAtom record. This record has a type of 0xff5 and is where the vulnerability begins. This atom is the record that the application uses in order to locate the PersistDirectory records and where a linked list based on the offsetLastEdit field is located. At offset 0x10 of this record type is a pointer to the next UserEditAtom. If this is set to non-zero, the application will continue onto the next UserEditRecord that is described by this offset. For each of the UserEditAtom records, the application will dereference the field at 0x14 to identify the PersistDirectoryAtom containing the list of offsets. The offsets for all of these UserEditAtom records are then aggregated into the buffer that is inreased by powers of two.
<class RecordGeneral> '58'
[86b7e] <instance Header 'header'> version=0 instance=0x000 type=0x0ff5 length=0x0000001c
[86b86] <instance UserEditAtom 'data'>
[86b86] <instance sint4 'lastSlideIDRef'> 0x000001a9 (425)
[86b8a] <instance uint2 'version'> 0x1fe9 (8169)
[86b8c] <instance ubyte1 'minorVersion'> 0x00 (0)
[86b8d] <instance ubyte1 'majorVersion'> 0x03 (3)
[86b8e] <instance pointer_t<UserEditAtom> 'offsetLastEdit'> *0x836a3
[86b92] <instance pointer_t<PersistDirectoryAtom> 'offsetPersistDirectory'> *0x86b66
[86b96] <instance uint4 'docPersistIdRef'> 0x00000001 (1)
[86b9a] <instance uint4 'persistIdSeed'> 0x000000d8 (216)
[86b9e] <instance sint2 'lastView'> 0x0001 (1)
[86ba0] <instance block(2) 'unused'> "\xc5\x31"
When loading the contents of this record type, the application descends into the offsetPersistDirectory record in order to load the different offsets of the edits that have been made to the document. This record contains an array of elements with the following format. This format is a dword where the ficount and id are encoded. The first 12 bits represent the number of offsets that follow, whereas the other 20 bits representing an unique identifier. Immediately following this dword is the array of offsets. This array of offsets is what the vulnerability writes out-of-bounds. Each of these elements then repeat until they meet the size of the record described in it’s header. As an example within the provided proof-of-concept, the following 2-element array begins at offset 0x86b66 of the stream.
<class RecordGeneral> '*offsetPersistDirectory'
[86b66] <instance Header 'header'> version=0 instance=0x000 type=0x1772 length=0x00000010
[86b6e] <instance PersistDirectoryAtom 'data'>
[86b6e] <instance PersistDirectoryEntry.info 'info[0]'> (0x00100001, 32) info.persistId:0x00001 info.cPersist:0x001
[86b72] <instance PersistDirectoryEntry.offsets 'offsets[0]'> pointer_t[1] [*0x000836c7]
[86b76] <instance PersistDirectoryEntry.info 'info[1]'> (0x001000bb, 32) info.persistId:0x000bb info.cPersist:0x001
[86b7a] <instance PersistDirectoryEntry.offsets 'offsets[1]'> pointer_t[1] [*0x000857b0]
The vulnerability actually occurs due to the application not restricting the number of offsets that it will read out of a list of UserEditAtom records. If an aggressor specifies a list of UserEditAtoms (using the value of offsetLastEdit) that either does not terminate due to it being self-referencing or aggregates a number of offsets (up to a power of 2) in which the target is unable to allocate space for then this will allow an aggressor to corrupt memory at the same power of two relative to the null page. This can be done by using a combination of either multiple PersistDirectory entries with a high count for the number of offsets or multiple UserEdit atoms.
0:000> lm m jxxtppt jsvda
start end module name
06bc0000 06c47000 JXXTPPT C (export symbols) JXXTPPT.DLL
277a0000 27826000 jsvda (export symbols) jsvda.dll
0:004> bl
0 e 06be5c48 0001 (0001) 0:**** JXXTPPT!JXXTPPT_Jsfc_Convert+0x2491d ".printf \"increased to %x\\n\",@eax;gc"
1 d 06bd74fa 0001 (0001) 0:**** JXXTPPT!JXXTPPT_Jsfc_Convert+0x161cf
2 e 06be5bdf 0001 (0001) 0:**** JXXTPPT!JXXTPPT_Jsfc_Convert+0x248b4 "? dwo(@ecx+4);gc"
3 e 06be5bf6 0001 (0001) 0:**** JXXTPPT!JXXTPPT_Jsfc_Convert+0x248cb "? @eax; gc"
0:000> g
Evaluate expression: 67108864 = 04000000
Evaluate expression: 0 = 00000000
(e58.c0c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=02000000 ebx=03f83980 ecx=44976e30 edx=00000000 esi=00259a48 edi=00259a24
eip=06be5c38 esp=00259930 ebp=00259938 iopl=0 nv up ei pl nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210216
JXXTPPT!JXXTPPT_Jsfc_Convert+0x2490d:
06be5c38 890c82 mov dword ptr [edx+eax*4],ecx ds:0023:08000000=d74b0802
0:000> ub .
JXXTPPT!JXXTPPT_Jsfc_Convert+0x248f3:
06be5c1e 8d449104 lea eax,[ecx+edx*4+4]
06be5c22 50 push eax
06be5c23 ff150825c206 call dword ptr [JXXTPPT!DRFL_SaveFD3A+0x24a8d (06c22508)]
06be5c29 83c40c add esp,0Ch
06be5c2c 8b4dfc mov ecx,dword ptr [ebp-4] ; *this
06be5c2f 8b5108 mov edx,dword ptr [ecx+8] ; *this+8
06be5c32 8b450c mov eax,dword ptr [ebp+0Ch]
06be5c35 8b4d08 mov ecx,dword ptr [ebp+8]
0:000> ub jxxtppt+25bfc L10
JXXTPPT!JXXTPPT_Jsfc_Convert+0x2489f:
06be5bca c745f801000000 mov dword ptr [ebp-8],1
06be5bd1 8b55f8 mov edx,dword ptr [ebp-8] ; previous size
06be5bd4 d1e2 shl edx,1
06be5bd6 8b45fc mov eax,dword ptr [ebp-4] ; *this
06be5bd9 895004 mov dword ptr [eax+4],edx ; *this+4
06be5bdc 8b4dfc mov ecx,dword ptr [ebp-4]
06be5bdf 8b5104 mov edx,dword ptr [ecx+4]
06be5be2 c1e202 shl edx,2
06be5be5 52 push edx
06be5be6 8b45fc mov eax,dword ptr [ebp-4]
06be5be9 8b4808 mov ecx,dword ptr [eax+8]
06be5bec 51 push ecx
06be5bed ff15f424c206 call dword ptr [JXXTPPT!DRFL_SaveFD3A+0x24a79 (06c224f4)] ; realloc
06be5bf3 83c408 add esp,8
06be5bf6 8b55fc mov edx,dword ptr [ebp-4] ; *this
06be5bf9 894208 mov dword ptr [edx+8],eax ; *this+8
0:000> ? dwo(@ebp-8) ; previous size
Evaluate expression: 33554432 = 02000000
0:000> ? poi(poi(@ebp-4)+4) ; current size
Evaluate expression: 67108864 = 04000000
0:000> ? poi(poi(@ebp-4)+4) << 2 ; allocation size
Evaluate expression: 268435456 = 10000000
0:000> ? dwo(@ebp+C) ; from first argument
Evaluate expression: 33554432 = 02000000
0:000> ? dwo(poi(@ebp-4)+8) ; returned from realloc
Evaluate expression: 0 = 00000000
2016-08-28 - Vendor Disclosure
2017-02-24 - Public Release
Discovered by a member of Cisco's Talos team.