Talos Vulnerability Report

TALOS-2022-1687

JustSystems Corporation Ichitaro Frame stream parser invalid free vulnerability

April 5, 2023
CVE Number

CVE-2023-22291

SUMMARY

An invalid free vulnerability exists in the Frame stream parser functionality of Ichitaro 2022 1.0.1.57600. A specially crafted document can lead to an attempt to free a stack pointer, which causes memory corruption. An attacker can provide a malicious file to trigger this vulnerability.

CONFIRMED VULNERABLE VERSIONS

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

Ichitaro 2022 1.0.1.57600
Versions of relevant binaries:

JSTARO25.OCX
File version: 1.0.1.58105

jsvda.dll
File version: 3.3.321.1

jsmisc32.dll
File version: 2.7.1.0

taro32.exe
File version: 1.0.1.57600

T32com.dll
File version: 1.0.0.200

PRODUCT URLS

Ichitaro - https://www.ichitaro.com/

CVSSv3 SCORE

7.0 - CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H

CWE

CWE-590 - Free of Memory not on the Heap

DETAILS

Ichitaro is a word processor produced by JustSystems that utilizes the ATOK input method system and occupies a large share of the Japanese word processing market. The Ichitaro word processor supports compatibility with many document formats and provides a broad set of features, allowing it to remain competitive with other available word processors.

Other than the typical document and spreadsheet formats that are provided by the Microsoft Office suite, Ichitaro also supports their native document format which uses the file extension .JTD. This file format is based on Microsoft’s Structured Storage format that was developed as part of Microsoft’s Component Object Model (COM). Similar to most document types which utilize this format, the structure of the entire .JTD document is stored within the streams that compose the Structured Storage file format and thus to access them, the application will use Microsoft’s Structured Storage API as exposed via COM.

Once the application has utilized the JSVDA.DLL library to allocate handles for the opened document, the application will proceed to process the streams of the document in order to determine how to display it to the user. One of these streams has the name “Frame” and is processed in a similar way to a number of the streams supported by a .JTD document. In the following code, the application will first use the filename of the document and pass it to a method to begin parsing the document. This method will then pass -1 as a handle along with an address to another method at [1] which will construct an object and write it to a variable on the stack. At [2], the -1 handle is used to refer to the current document handle which will then be passed to a function from the JSVDA.DLL library which will result in constructing an object with the tag “OFRM”. This object will be used throughout the application to access the different parts of the current document. At [3], this object will then be passed to a method to finally begin processing the document.

3c1fe65f: push [ebp+ap_STR_4]
3c1fe662: mov ecx, esi                                                      ; this
3c1fe664: push [ebp+ap_wFilename_0]
3c1fe667: call CCustomFile::method_77a4bc
\\
3c1fa55e: push 0FFFFFFFFh                                                   ; -1 used to refer to handle in current object
3c1fa560: lea eax, [ebp+lp_ofrmObject_14]                                   ; [1] address to store OFRM object
3c1fa563: mov ecx, esi
3c1fa565: push eax
3c1fa566: call CCustomFile::GetObject(OFRM)_3a8b6b                          ; [1] fetch OFRM object for document
\
3be28b83: mov eax, [ebp+av_csIndex_4]
3be28b86: cmp eax, 0FFFFFFFFh
3be28b89: jnz short loc_3BE28B8E
...
3be28b8b: mov eax, [edi+CCustomFile.v_data_64.v_currentDocumentIndex_18]    ; [2] current document handle
3be28b8e: push [ebp+ap_result_0]                                            ; address of OFRM object that gets stored
3be28b91: push 2
3be28b93: push eax
3be28b94: call jsvda::object_OFRM::CreateFromHandle_3cc25                   ; [2] create object for document
/
3c1fa5b6: push [esi+CCustomFile.v_data_64.p_stackObject_2c4]
3c1fa5bc: mov ecx, [esi+CCustomFile.v_stdio_4.p_owner_0]
3c1fa5bf: push [esi+CCustomFile.v_data_64.v_flags_2b4]
3c1fa5c5: push [esi+CCustomFile.v_data_64.v_documentType?_2a4]
3c1fa5cb: mov eax, [ecx]
3c1fa5cd: push [ebp+lp_ofrmObject_14]                                       ; [3] document object
3c1fa5d0: call dword ptr [eax+10h]                                          ; [3] call into method to parse document streams

After the object has been created, it will be passed as a parameter to the following code. This method will be used multiple times by the application to parse the streams within the document in multiple passes. The pass that is chosen by the method is determined by one of the method’s parameters. This method will be called multiple times by the application depending on its 3rd parameter. On the first pass, the code at [4] will check the document type at before extracting a field from a member of the current object and using it along with the “OFRM” document object as parameters to the method call at [5].

3c1f9ebe: push 240h
3c1f9ec3: mov eax, offset byte_3C3C9292
3c1f9ec8: call __EH_prolog3_GS
3c1f9ecd: mov esi, ecx
3c1f9f3f: mov ecx, [ebp+av_documentType_4]                                          ; [4] fetch value from parameter
...
3c1f9f53: cmp ecx, 6                                                                ; [4] ensure value is not larger than 6
3c1f9f56: ja short return(ffffffe0)_779f7e
...
3c1fa1a6: push [ebp+lp_stackobject_234]
3c1fa1ac: mov ecx, [esi+CApollonControl.v_data_4.p_2d4]
3c1fa1b2: push [ebp+av_flags_8]                                                     ; flags
3c1fa1b5: push [ebp+av_documentType_4]                                              ; document type
3c1fa1b8: push [ebp+lp_frameObject_230]                                             ; document object (OFRM)
3c1fa1be: call CApollonControl::field_2d4::processDocumentTextRelatedStuff?_777920  ; [5] call method to process streams
...
3c1f9fcb: or [ebp+var_4], 0FFFFFFFFh
3c1f9fcf: lea ecx, [ebp+var_22C]
3c1f9fd5: call nullsub_48
3c1f9fda: mov eax, ebx
3c1f9fdc: call __EH_epilog3_GS
3c1f9fe1: retn 14h

The method that gets called will use the “OFRM” document object to construct objects responsible for interacting with the different streams within the document. The beginning of the method will first construct an object on the stack using the “OFRM” object for the document, and then call the method at [6] to identify and process the streams within the .JTD document. The entry point of this method sets up and exception handler before initializing an object on the stack at [7] which is 0xE8 bytes in size. Afterwards, this object is used with the method at [8] to read each of the streams available in the document along with their header in order to store the read information into this object. At [9], the “Frame” stream is read from the document and returned as an object with the “OSEG” tag. After the object for reading the stream has been written into a field belonging to the current object, at [10] the method will read the first two 16-bit integers from the “Frame” stream. These will get stored into the current object before returning back to the caller. At [11], the object that was used to pre-read each of the related streams of the document will be used to a method. This method will process the “Frame” stream using the corresponding “OSEG” object that was allocated for it.

3c1f7920: push 0A4h
3c1f7925: mov eax, offset byte_3C3C9116
3c1f792a: call __EH_prolog3_catch_GS
...
3c1f7934: mov ebx, [ebp+ap_frameObject_0]                               ; load "OFRM" object into %ebx
3c1f7937: lea ecx, [ebp+lv_object_b0]
3c1f793d: call constructor_1b1e
...
3c1f7d3f: xor eax, eax
3c1f7d41: cmp ecx, 6
3c1f7d44: setz al
3c1f7d47: push eax                                                      ; data format
3c1f7d48: push ebx
3c1f7d49: mov ecx, [edi+CApollonControl::field_2d4.p_frameObject_20]    ; "OFRM" object
3c1f7d4c: call object_10c7df::getStreamObjectsAndProcessFrame_776da1    ; [6] use "OFRM" object to process streams
\
3c1f6da1: push 0F4h
3c1f6da6: mov eax, offset loc_3C3C8FAB
3c1f6dab: call __EH_prolog3_catch                                       ; setup an exception handler (1)
...
3c1f6dc6: mov edi, [ebp+av_dataformat_4]                                ; data format
3c1f6dc9: mov [esi+object_10c7df.v_dataformat_0], edi                   ; assign data format to this object
3c1f6dcb: push ebx
3c1f6dcc: push [ebp+ap_oframe_0]                                        ; "OFRM" object
3c1f6dcf: push [esi+object_10c7df.p_field_38]
3c1f6dd2: lea ecx, [ebp+lv_object_fc]                                   ; 0xE8 size object
3c1f6dd8: call object_1b46bf::constructor_1b315f                        ; [7] construct 0xe8 size object
...
3c1f6ddd: mov byte ptr [ebp+var_4], 1
3c1f6de1: lea ecx, [ebp+lv_object_fc]
3c1f6de7: call object_1b46bf::method_getStreamObjects(OSEG)_1b8ca4      ; [8] method that constructs an "OSEG" object for each stream
\
3bc38ca4: push ebx
3bc38ca5: push esi
3bc38ca6: push edi
3bc38ca7: mov edi, ecx
...
3bc38ca9: lea esi, [edi+object_1b46bf.p_oseg(Frame)_98]                 ; location to write "OSEG" object for stream
3bc38caf: push esi                                                      ; "OSEG" object destination
3bc38cb0: push 10h
3bc38cb2: pop ebx
3bc38cb3: push ebx
3bc38cb4: push offset str.Frame                                         ; stream name - "Frame"
3bc38cb9: push [edi+object_1b46bf.p_ofrm_8c]                            ; "OFRM" object
3bc38cbf: call object_OFRM::get_stream(OSEG)_1321d3                     ; [9] construct "OSEG" object from "OFRM" object
...
3bc38db2: mov eax, [esi]                                                ; "OSEG" object location
...
3bc38db8: lea ecx, [edi+object_1b46bf.vw_word(hi,Frame)_ba]             ; second 16-bits from stream
3bc38dbe: push ecx
3bc38dbf: lea esi, [edi+object_1b46bf.vw_word(lo,Frame)_b8]             ; first 16-bits from stream
3bc38dc5: mov ecx, edi
3bc38dc7: push esi
3bc38dc8: push eax                                                      ; "OSEG" object
3bc38dc9: call object_OSEG::readHeader_1b8f35                           ; [10] read two 16-bit values from stream
/
3c1f6dec: lea eax, [ebp+lv_object_fc]                                   ; 0xe8 size object
3c1f6df2: push eax
3c1f6df3: mov ecx, esi
3c1f6df5: call object_10c7df::process_frame_776bd2                      ; [11] process the "Frame" stream that was read
...
3c1f6dfa: cmp [ebp+lv_object_fc.v_memberCount_d8], ebx
3c1f6dfd: jnz short loc_3C1F6E18

The following method is responsible for processing the contents of the “Frame” stream for the first pass. This method sets up another exception handler before the method call at [12]. This method call will use the object that was allocated on the stack and passed as the first parameter to extract the “OSEG” object that was constructed for the “Frame” stream and store it the function’s frame. Afterwards another variable will be constructed on the stack to store the contents of the “Frame” stream as an array. After it has been allocated, at [13] a method will be used with the “OFRM” document object to search for any other stream names that could be used to influence the frames that will be read from the document. At [14], the method will then read a block from the “Frame” stream which will contain a 32-bit length. This is returned as a result and written to a field belonging to the object passed as the first parameter.

3c1f6bd2: push 28h
3c1f6bd4: mov eax, offset loc_3C3C8F7E
3c1f6bd9: call __EH_prolog3                                 ; set up an exception handler (2)
3c1f6bde: mov ebx, ecx
3c1f6be0: mov [ebp+lp_this_10], ebx
...
3c1f6be3: mov esi, [ebp+ap_object_0]                        ; 0xe8 size object
3c1f6be6: mov ecx, esi
3c1f6be8: call object_1b46bf::getFrameIfCached_1b3335       ; [12] get "OSEG" object for the "Frame" stream
3c1f6bed: mov [ebp+lp_oseg_14], eax                         ; store "OSEG" object
3c1f6bf0: test eax, eax
3c1f6bf2: jz loc_3C1F6CFB
...
3c1f6bf8: lea ecx, [ebp+lv_obArray_34]
3c1f6bfb: call CObArray_1b9060::constructor_1b9060          ; construct an array
3c1f6c00: xor edi, edi
3c1f6c02: mov [ebp+var_4], edi
...
3c1f6c05: lea ecx, [ebp+lv_obArray_34]
3c1f6c08: push [esi+object_1b46bf.p_ofrm_8c]                ; "OFRM" document object
3c1f6c0e: call CObArray_1b9060::read_hint_frames_1b908a     ; [13] read other streams from the document to influence the Frame
...
3c1f6c13: push [ebp+lp_oseg_14]
3c1f6c16: mov ecx, ebx
3c1f6c18: call object_OSEG::read_count_block_776b7b         ; [14] read 32-bit length from the "Frame" stream
3c1f6c1d: mov [ebp+lv_count_1c], eax                        ; store 32-bit length on stack
3c1f6c20: mov [esi+object_1b46bf.v_memberCount_d8], eax     ; store 32-bit length in field of object

After reading the length from the “Frame” stream, the following loop will be encountered. This loop is responsible for decoding each of the members within the “Frame” stream at [15]. During each iteration of the loop, the object decoded from the stream will be assigned into the array at [16]. When decoding each member from the “Frame” stream, the “OSEG” object is used with the method call at [15]. At the beginning of this method, an exception handler will be setup before decoding the current member. Thus if an exception occurs during the processing of a member, a later pass will be used to decode them properly. Within the “Frame” stream, a block of stream data is a variable-length structure that begins with a 16-bit type, followed by a 16-bit length. The 16-bit length is then used as the size to use when reading the contents of the block. At [17], the “OSEG” object that was passed as a parameter will be used to read such a block from the stream. Once the block has been read from the stream into the stack object that was constructed, the method will the block’s contents at [19] as a 32-bit integer followed by a 16-bit one. The 16-bit integer represents the type of a virtual object that will be returned by the method at [19]. Once the object has been created, the method at [20] will proceed to decode the contents of the block into the object. If there was an issue during this decoding process, an exception will be raised resulting in the three-different exception handlers cleaning up their frames whilst leaving any collected objects within the array of the object owned by the calling method.

3c1f6c2e: push [ebp+lp_oseg_14]                             ; "OSEG" object for stream
3c1f6c31: mov ecx, ebx
3c1f6c33: call object_10c7df::read_memberObject_776aee      ; [15] \ read member object
...
3c1f6c3b: mov esi, eax                                      ; contains member object
...
3c1f6c53: mov ecx, [ebp+lp_this_10]
3c1f6c56: push esi                                          ; member object
3c1f6c57: call object_10c7df::setIntoObArray(64)_7a2364     ; [16] assign into array
3c1f6c5c: mov [ebp+lv_length_18], eax
3c1f6c5f: cmp eax, 0FFFFFFFFh
3c1f6c62: jnz short loc_3C1F6C6C
3c1f6c64: call JSFC::CxxThrowException(AVCMemoryException)_1a64f
...
3c1f6c69: mov eax, [ebp+lv_length_18]
3c1f6c6c: push eax
3c1f6c6d: mov ecx, esi
3c1f6c6f: mov [esi+object_1b46bf.p_oseg(Figure)_b0], ebx
3c1f6c75: call vobject_9e7514::setfield(a8)_1b9219          ; update object member with length
...
3c1f6cd8: push esi
3c1f6cd9: push ebx
3c1f6cda: mov ebx, [ebp+lp_this_10]
3c1f6cdd: lea ecx, [ebx+object_10c7df.v_obArray_64]         ; [16] assign element into array on stack
3c1f6ce0: call JSFC::CObArray::setitem_7de7
...
3c1f6ce5: inc edi
3c1f6ce6: cmp edi, [ebp+lv_count_1c]
3c1f6ce9: jl loop_readframe_776c2e                          ; loop
\
3c1f6aee: push 20h
3c1f6af0: mov eax, offset loc_3C3C8F2A
3c1f6af5: call __EH_prolog3                                 ; setup exception handler (3)
...
3c1f6afc: push 0
3c1f6afe: lea ecx, [ebp+lv_metadata_2c]
3c1f6b01: call object_metadata::constructor_1b3451          ; construct object for decoded data
...
3c1f6b0a: lea eax, [ebp+lv_unusedType_10]
3c1f6b0d: push eax
3c1f6b0e: push [ebp+ap_object_0]                            ; "OSEG" object
3c1f6b11: lea ecx, [ebp+lv_metadata_2c]                     ; object for decoded data
3c1f6b14: call object_metadata::readblock_1b8fad            ; [17] read a block from the stream
...
3c1f6b22: lea eax, [ebp+lv_field_18]                        ; [18] result uint32_t
3c1f6b25: push eax
3c1f6b26: lea ecx, [ebp+lv_metadata_2c]
3c1f6b29: call object_metadata::htonl_1b91fd
3c1f6b2e: lea eax, [ebp+lvw_objectCase_14]                  ; [18] result uint16_t containing member type
3c1f6b31: push eax
3c1f6b32: lea ecx, [ebp+lv_metadata_2c]
3c1f6b35: call object_metadata::htonw_1b903e
...
3c1f6b3a: push [ebp+lvw_objectCase_14]                      ; member type
3c1f6b3d: mov ecx, esi
3c1f6b3f: call object_10c7df::constructVirtualObject_7769a8 ; [19] construct object for member type
...
3c1f6b47: mov esi, eax
...
3c1f6b5a: lea eax, [ebp+lv_metadata_2c]                     ; stream data
3c1f6b5d: mov ecx, esi
3c1f6b5f: push eax
3c1f6b60: call vobject_9e7514::readStuff?_1b9229            ; [20] decode structure out of block that was read
...
3c1f6b69: lea ecx, [ebp+lv_metadata_2c]
3c1f6b6c: call object_metadata::destructor_1b353e           ; destroy intermediary object for stream data
...
3c1f6b71: mov eax, esi
3c1f6b73: call __EH_epilog3
3c1f6b78: retn 4

After an exception is raised, the application will return back to the method at 0x3c1f9ebe and finish its execution. Upon the second pass when calling this method, the following different path will be executed through the method. Prior to the method exiting, the “OFRM” object representing the document along with a boolean value as determined by the document type will be used with the method at [21]. This method will enter a path of execution that will reuse the objects that have already been collected during the first pass.

3c1f9ebe: push 240h
3c1f9ec3: mov eax, offset byte_3C3C9292
3c1f9ec8: call __EH_prolog3_GS
3c1f9ecd: mov esi, ecx
...
3c1f9ef1: mov eax, [ebp+av_flags_8]                 ; check flags
3c1f9ef4: and eax, 0Fh
3c1f9ef7: mov [ebp+lv_flags_23c], eax               ; store their lowest 4 bits
...
3c1f9f3f: mov ecx, [ebp+av_documentType_4]          ; check document type
3c1f9f42: xor edi, edi
3c1f9f44: inc edi
3c1f9f45: cmp ecx, 3
3c1f9f48: jb short return(ffffffe0)_779f7e
3c1f9f4a: cmp ecx, 4
3c1f9f4d: jbe loc_3C1FA36E
3c1f9f53: cmp ecx, 6
3c1f9f56: ja short return(ffffffe0)_779f7e
3c1f9f58: mov eax, [ebp+lv_flags_23c]               ; check flags
3c1f9f5e: xor edx, edx
3c1f9f60: sub eax, edx
3c1f9f62: jz flags(800)_77a165                      ; skip conditional
3c1f9f68: dec eax
3c1f9f69: sub eax, edi
3c1f9f6b: jz useWindowFrameObject_77a121            ; branch to code that uses "OFRM" object
...
3c1fa121: lea eax, [ebp+lp_frameObject_24c]         ; "OFRM" object
3c1fa127: mov [ebp+lv_result_248], edi
3c1fa12d: push eax                                  ; address of "OFRM" object
3c1fa12e: xor eax, eax
3c1fa130: mov [ebp+lv_244], edx
3c1fa136: cmp ecx, 6
3c1fa139: mov [ebp+lv_240], edx
3c1fa13f: mov ecx, [esi+CApollonControl.v_data_4.p_object_324]
3c1fa145: setnz al                                  ; document format from document type
3c1fa148: mov [ebp+lp_frameObject_24c], ebx         ; "OFRM" object
3c1fa14e: inc eax
3c1fa14f: push eax
3c1fa150: call object_ef0fd::method_768e8f          ; [21] call method with objects from prior pass
3c1fa155: mov ebx, [ebp+lv_result_248]
3c1fa15b: test eax, eax
3c1fa15d: cmovnz ebx, edi
3c1fa160: jmp return(@ebx)_779f98

Later during execution the following code will be executed to reuse the objects allocated in response to the members from the “Frame” stream. The method being described takes a large number of parameters of which the majority are flags that were determined from the “OFRM” object. At [22], the current method will fetch the “OFRM” object representing the document from a property of the current object. Afterwards,­the “OFRM” object and the numerous parameters will be passed to the method call at [23]. Once inside the method, the application will construct an object on the stack that will be used to contain an object array. This array will be used with the method call at [24]. This method will use the “OFRM” object to read and decode the contents of the “hint_paste_frame”, “hint_paste_frame_ext”, and “hint_paste_frame_ext2” streams from the document if they are available. After extracting the hints, the “OFRM” object will finally be passed to the method at [25] to read from the “Frame” stream again.

3bd1bc51: mov ecx, [esi+object_9d0fd8.v_data_d4.field_2C]
3bd1bc57: lea eax, [ebp+lv_object_34]
3bd1bc5a: push eax                                              ; object on stack
3bd1bc5b: xor eax, eax
3bd1bc5d: cmp ecx, 2
3bd1bc60: setz al
3bd1bc63: push eax                                              ; flag parameter
3bd1bc64: lea eax, [ebp+var_14]
3bd1bc67: push eax
3bd1bc68: movzx eax, [esi+object_9d0fd8.v_data_d4.field_1C8]
...
3bd1bc78: xor eax, eax
3bd1bc7a: cmp [esi+object_9d0fd8.v_data_d4.field_0], 2
3bd1bc81: setz al
3bd1bc84: push eax                                              ; flag parameter (dataformat)
3bd1bc85: push [esi+object_9d0fd8.v_data_4.field_cc]            ; parameter from field
3bd1bc8b: push [esi+object_9d0fd8.v_data_d4.v_size_48]          ; parameter from field
3bd1bc91: push ecx                                              ; parameter from field
3bd1bc92: push [esi+object_9d0fd8.v_data_4.p_oframe_c4]
...
3bd1bc98: mov ecx, esi
3bd1bc9a: call object_9d0fd8::get_frameObject_7b05ce            ; [22] get "OFRM" object from current object
...
3bd1bc9f: mov ecx, eax
3bd1bca1: call object_10c7df::method_86605d                     ; [23] call method with parameters on stack
\
3c2e605d: push 2Ch
3c2e605f: mov eax, offset loc_3C425809
3c2e6064: call __EH_prolog3
...
3c2e6075: mov edi, [ebp+av_dataformat_10]                       ; dataformat from parameter
3c2e6078: pop ecx
3c2e6079: pop ecx
3c2e607a: lea ecx, [ebp+lv_obArray_38]                          ; object on stack
3c2e607d: mov [esi], edi
3c2e607f: call CObArray_1b9060::constructor_1b9060              ; pair of arrays
...
3c2e6088: lea ecx, [ebp+lv_obArray_38]
3c2e608b: push [ebp+ap_frame_0]                                 ; "OFRM" object
3c2e608e: call CObArray_1b9060::read_hint_frames_1b908a         ; [24] read other streams from the document
3c2e6093: test eax, eax
3c2e6095: jnz loc_3C2E6152
...
3c2e609b: lea ecx, [ebp+lv_object_20]
3c2e609e: call constructor_2126dd                               ; constructor for object on stack
...
3c2e60a3: mov byte ptr [ebp+var_4], bl
3c2e60a6: lea ecx, [ebp+lv_object_20]                           ; object on stack
3c2e60a9: push [ebp+ap_frame_0]                                 ; "OFRM" object
3c2e60ac: call object_2126dd::readFrameStream_212731            ; [25] read contents of "Frame" stream

The method that reads the contents of the “Frame” stream again is shown. This method will simply use the “OFRM” object that was passed as a parameter to get an “OSEG” object for the “Frame” stream at [26]. Immediately afterwards at [27], the two 16-bit integers are read from the header using a method belonging to the “OSEG” object. This method will check that the first 16-bit integer is less than or equal to the value 2. Following this check, at [28], a block that contains a 32-bit length is then read from the stream. Similar to other blocks that are read from streams, this block contains a 16-bit integer describing the type and a 16-bit integer describing the size prior to the 32-bit length in the block’s contents. This method will check that the 16-bit type of the length is set to the value 0x0101. Once the length has been read and stored to the current object, the method call at [29] will be used to read each of the members within the stream.

3bc92731: push ebp
3bc92732: mov ebp, esp
...
3bc92734: push esi
3bc92735: push [ebp+ap_frame_0]
3bc92738: mov esi, ecx
3bc9273a: call object_2126dd::getStream(Frame)_212770           ; [26] use "OFRM" object to get "OSEG" object for stream
3bc9273f: test eax, eax
3bc92741: jz short loc_3BC92769
..
3bc92743: mov ecx, esi
3bc92745: call object_2126dd::readStreamHeader?_212792          ; [27] read the header of the stream
3bc9274a: test eax, eax
3bc9274c: jz short loc_3BC92769
...
3bc9274e: mov ecx, esi
3bc92750: call object_2126dd::readCountBlock_212812             ; [28] read first block that follows header containing length
3bc92755: test eax, eax
3bc92757: jz short loc_3BC92769
...
3bc92759: mov ecx, esi
3bc9275b: call object_2126dd::readMemberBlocks_21286d           ; [29] read blocks for each member following the length block
3bc92760: test eax, eax
3bc92762: jz short loc_3BC92769

The following code will be used for processing the number of available members that are within the “Frame” stream. This code will use the count that was decoded earlier in a loop to read and decode each individual member. At [30], two different objects will be constructed for processing each member. The second object that gets constructed is used to cache the contents of a block from the stream. The constructor for this object also allows one to provide a buffer to use for caching the data read from the stream. This instance of the constructor passes the address of a buffer allocated on the stack to it, resulting in the object using a stack buffer during the decoding process. After construction, the method will use the call at [31] to decode the block from the stream. Similar to other blocks, at [32] the two 16-bit integers are read from the beginning of the block within the stream. At [33], the method will then check the size read from the stream and compare it against the object that was allocated to assist with decoding the stream. If the buffer object already has a buffer attached to it and the size of the block is larger than the size within the buffer object, the application will free the buffer object at [34] and then use the size to allocate memory and update the buffer within the block at [35]. Due to the application having allocated the buffer for the object on the stack, this will result in releasing memory that does not belong to the heap.

3bc9286d: push ebp
3bc9286e: mov ebp, esp
3bc92870: push 0FFFFFFFFh
3bc92872: push offset SEH_3BC9286D
3bc92877: mov eax, large fs:0
3bc9287d: push eax
3bc9287e: mov eax, 1100h
3bc92883: call __alloca_probe
3bc92888: mov eax, ___security_cookie
3bc9288d: xor eax, ebp
3bc9288f: mov [ebp+var_10], eax
...
3bc928a3: lea ecx, [ebp+lv_object_10ec]
3bc928a9: push edi
3bc928aa: call object_9e6e68::constructor_1a9b19            ; [30] construct object for member
...
3bc928af: mov [ebp+var_4], edi
3bc928b2: mov [ebp+lv_object_10ec.p_vtable_0], offset gvt_object_9e7168
3bc928bc: mov [ebp+var_4], 1
3bc928c3: lea eax, [ebp+lv_buffer(1000)_10ec]
3bc928c9: push eax
3bc928ca: push 1000h
3bc928cf: lea ecx, [ebp+lv_metadata_1108]
3bc928d5: call object_metadata::attach_212b2f               ; [30] construct object using stack buffer
...
3bc928e9: lea eax, [ebp+lvw_blockId_10f0]
3bc928ef: push eax                                          ; destination of block type
3bc928f0: push [esi+object_metadata.p_memory_10]            ; pointer inside object cache
3bc928f3: lea ecx, [ebp+lv_metadata_1108]                   ; object for caching stream contents
3bc928f9: call object_metadata::readblock_1b8fad            ; [31] read stream into caching object
\
3bc38fad: push ebp
3bc38fae: mov ebp, esp
3bc38fb0: push esi
3bc38fb1: push [ebp+ap_resultShort_4]
3bc38fb4: mov esi, ecx
...
3bc38fb6: push [ebp+ap_object_0]
3bc38fb9: call object_OSEG::readshort_3a61ec                ; [32] decode 16-bit big-endian integer
3bc38fbe: pop ecx
3bc38fbf: pop ecx
3bc38fc0: test eax, eax
3bc38fc2: jz short return(0)_1b9037
...
3bc38fc4: lea eax, [ebp+ap_resultShort_4]
3bc38fc7: push eax
3bc38fc8: push [ebp+ap_object_0]
3bc38fcb: call object_OSEG::readshort_3a61ec                ; [32] decode 16-bit big-endian integer
3bc38fd0: pop ecx
3bc38fd1: pop ecx
3bc38fd2: test eax, eax
3bc38fd4: jz short return(0)_1b9037
...
3bc38fd6: mov cx, word ptr [ebp+ap_resultShort_4]           ; [33] check that size is non-zero
3bc38fda: test cx, cx
3bc38fdd: jnz short try_free_1b8fe4
...
3bc38fe4: movzx eax, cx
3bc38fe7: cmp [esi+object_metadata.v_availableSize_4], eax  ; [33] check size of buffer in object
3bc38fea: jnb short loc_3BC39010
...
3bc38fec: cmp [esi+object_metadata.p_memory_10], 0
3bc38ff0: jz short loc_3BC38FFF                             ; [33] check if buffer is initialized
...
3bc38ff2: push [esi+object_metadata.p_memory_10]
3bc38ff5: call free_8e6a9e                                  ; [34] free buffer if initialized
...
3bc38ffb: mov cx, word ptr [ebp+ap_resultShort_4]           ; 16-bit length of block
3bc38fff: movzx eax, cx
3bc39002: push eax
3bc39003: call malloc_8e6a95                                ; [35] allocate new buffer size
...
3bc39009: mov cx, word ptr [ebp+ap_resultShort_4]           ; 16-bit length of block
3bc3900d: mov [esi+object_metadata.p_memory_10], eax        ; [35] update buffer with pointer returned from allocation

The base address for the JSTARO25.OCX module described by this document is at 0x3ba80000.

Crash Information

To enable heap verification, use gflags.exe with the +hpa +htc +hfc +hpc prior to running the application.

(13b4.1190): Break instruction exception - code 80000003 (first chance)
eax=00728000 ebx=00000000 ecx=777c7760 edx=777c7760 esi=777c7760 edi=777c7760
eip=77792240 esp=522fff0c ebp=522fff38 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!DbgBreakPoint:
77792240 cc              int     3
0:007> g

After opening the document, the following error will be displayed in the debugger.

===========================================================
VERIFIER STOP 00000010: pid 0x13B4: corrupted start stamp 

    03641000 : Heap handle
    00937F94 : Heap block
    FFFFFFFF : Block size
    00000000 : Corrupted stamp

Browse full module list
start    end        module name
3b430000 3c367000   JSTARO25   (deferred)             
277a0000 27826000   jsvda      (deferred)             
5f800000 5f8b1000   JSFC       (deferred)             
3c7c0000 3eef5000   T32com     (deferred)   

Exploit Proof of Concept

To modify a given document with the necessary attributes, run the proof-of-concept with Python against the desired document using the following parameters:

$ python poc.py3.zip modify /path/to/document.jtd

This will update the document in-place with the necessary changes required to trigger the vulnerability. The same proof-of-concept can be used to process a document and scan it for the necessary attributes by using the “read” command instead of “modify”.

The attributes used by the application and described by this document can be found within the “Frame” stream. Each block in this stream is a 16-bit type, followed by a 16-bit length, followed by the data contained within the block. This has the following format:

<class jtd.Frame_block> 'header'                                            
[0] <instance jtd.ntohs 'type'> 0x0001 (1)
[2] <instance jtd.ntohs 'size'> 0x0004 (4)                                  
[4] <instance jtd.Frame_header 'data'> "\x00\x02\x00\x01"
[8] <instance dynamic.block(0) 'padding'> ""

After the blocks for the header and the length will be an array of members within the stream. If the size field of any of these is larger than the size of a page, then this denial of service is being triggered.

<class jtd.Frame_block> '11'
[2a4] <instance jtd.ntohs 'type'> 0x0102 (258)       
[2a6] <instance jtd.ntohs 'size'> 0x1001 (4097)                                                                                                             
[2a8] <instance jtd.Frame_member_776aee 'data'> "\x00\x00\x00\x0b\x00\x04\x00\x00\x00\x09\x00\x00\x00\x01..."                                          
TIMELINE

2023-01-11 - Vendor Disclosure
2023-04-04 - Vendor Patch Release
2023-04-05 - Public Release

Credit

Discovered by a member of Cisco Talos.