Talos Vulnerability Report

TALOS-2016-0199

Ichitaro Word Processor PersistDirectory Code Execution Vulnerability

February 24, 2017
CVE Number

CVE-2017-2791

Summary

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.

Tested Versions

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

Product URLs

http://www.ichitaro.com https://www.justsystems.com/jp/download/trial/ichitaro

CVSSv3 Score

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

Details

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

File-Format Details

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.

Crash Information

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

Timeline

2016-08-28 - Vendor Disclosure
2017-02-24 - Public Release

Credit

Discovered by a member of Cisco's Talos team.