Talos Vulnerability Report

TALOS-2023-1722

JustSystems Corporation Ichitaro "LayoutBox" stream heap-based buffer overflow vulnerability

April 5, 2023
CVE Number

CVE-2023-22660

SUMMARY

A heap-based buffer overflow vulnerability exists in the way Ichitaro version 2022 1.0.1.57600 processes certain LayoutBox stream record types. A specially crafted document can cause a buffer overflow, leading to memory corruption, which can result in arbitrary code execution.To trigger this vulnerability, the victim would need to open a malicious, attacker-created document.

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-122 - Heap-based Buffer Overflow

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. After the JSVDA library has allocated a handle for the document that was opened, the JSTARO25.OCX library will be responsible for reading and parsing the streams from the document. The following pseudocode shows a method that the JSTARO25.OCX library uses to parse the streams from the document. At [1], a property of the current object is used call a method that will process data from each of the streams within the document.

/* JSTARO25.OCX:0x3c1f7920 */
int __thiscall CApollonControl::field_2d4::processDocumentTextRelatedStuff__777920(CApollonControl::field_2d4 *this, JSVDA::object_OFRM *ap_frameObject_0, int av_documentType_4, int av_flags_8, stackobject_799a4b *ap_stackobject_c)
{
...
  lp_this_64 = this;
  constructor_1b1e4c(&lv_object_b0);
  v58 = 0;
  constructor_3ac15f(v57, this->p_apollon_0);
...
  if ( !object_10c7df::getStreamObjectsAndProcessFrame_776da1(              // [1] parse all the streams
          this->p_frameObject_20,
          ap_frameObject_0,
          v_documentType_4 == 6) )
    goto LABEL_15;
...
}

Once the method has been called, some fields within the method’s object will be initialized and then at [2], an object will be constructed on the stack in order to keep track of all of the streams that wil be parsed from the document. The method call at [3] will construct objects for each stream from the document that will be used to access their contents. At [4], the object on the stack will be used with the current method’s object to process the contents of the “Frame” stream. This method will add each element within the “Frame” stream to an array that is stored as a field within the current method’s object. At [5], each of the elements within that array will be used in order to call a method for each object that was added.

/* JSTARO25.OCX:0x3c1f6da1 */
int __thiscall object_10c7df::getStreamObjectsAndProcessFrame_776da1(object_10c7df *this, JSVDA::object_OFRM *ap_oframe_0, int av_dataformat_4)
{
  int v_length_4; // eax
  int v6; // edi
  int v7; // ecx
  object_1b46bf lv_object_fc; // [esp+14h] [ebp-FCh] BYREF
  int v9; // [esp+FCh] [ebp-14h]
  int v10; // [esp+10Ch] [ebp-4h]
  int av_boolean_4a; // [esp+11Ch] [ebp+Ch]
...
  lv_object_fc.p_object_e4 = this;
  v10 = 0;
  this->field_60 = 1;
  object_10c7df::clearBitsAndEnterProcessing_776d36(this);
  this->v_dataformat_0 = av_dataformat_4;
  object_1b46bf::constructor_1b315f(&lv_object_fc, this->p_field_38, ap_oframe_0, 0);   // [2] construct object for streams
  LOBYTE(v10) = 1;
  object_1b46bf::method_getStreamObjects_OSEG__1b8ca4(&lv_object_fc);                   // [3] call method to create an object for each stream
  object_10c7df::process_frame_776bd2(this, &lv_object_fc);                             // [4] use the object for the "Frame" stream to process the stream
...
  if ( !av_dataformat_4 )
  {
LABEL_9:
    v_length_4 = this->v_obArray_64.v_data_4.v_length_4;                                // [5] length of an array (CObArray)
    av_boolean_4a = v_length_4;
    v6 = 0;
    v9 = 0;
    while ( v6 < v_length_4 )                                                           // [5] loop through all items in array
    {
      v7 = *(this->v_obArray_64.v_data_4.p_items_0 + 4 * v6);                           // [5] get item at index of the array
      if ( v7 && !(*(*v7 + 308))(v7, &lv_object_fc) )                                   // [5] call method of item
        goto return(0)_776e2e;
      v9 = ++v6;
      v_length_4 = av_boolean_4a;
    }
...
}

The following method shows an object being constructed for each stream from the document. The two streams that are related to the vulnerability referenced by this advisory are the “Frame” stream which is stored to offset +0x98 of the current object at [6], and the “Layout” stream which is stored at offset +0x9c of the current object at [7].

/* JSTARO25.OCX:0x3bc38ca4 */
int __thiscall object_1b46bf::method_getStreamObjects_OSEG__1b8ca4(object_1b46bf *this)
{
  JSVDA::object_OSEG **v2; // esi
  int dataformat__1b33d9; // ebx
  int result; // eax
...
  v2 = &this->p_oseg(Frame)_98;
  object_OFRM::get_stream_OSEG__1321d3(this->p_ofrm_8c, "Frame", 16, &this->p_oseg(Frame)_98);                  // [6] "Frame" stream
  object_OFRM::get_stream_OSEG__1321d3(this->p_ofrm_8c, "LayoutBox", 16, &this->p_oseg(LayoutBox)_9c);          // [7] "LayoutBox" stream
  object_OFRM::get_stream_OSEG__1321d3(this->p_ofrm_8c, "LayoutBoxText", 16, &this->p_oseg(LayoutBoxText)_90);
...
}

After the objects for each stream have been constructed, the method will then pass the objec tcontaining those objects to the following method in order to start processing the contents of the “Frame” stream. At [8], a method will be used in order to get the object referencing the “Frame” stream. This object will then be used at [9] to read the number of elements to read from the contents of the stream. This number will then be used in a loop that reads the content of an individual object at [10], which will then get stored into the array at [11].

/* JSTARO25.OCX 0x3c1f6bd2 */
void __thiscall object_10c7df::process_frame_776bd2(object_10c7df *this, object_1b46bf *ap_object_0)
{
  object_10c7df *p_this; // ebx
  int v_count_1c; // edi
  int v_count_776b7b; // eax
  vobject_9e7514 *p_memberObject_776aee; // esi
  void *v_long_a8; // ebx
  int v7; // eax
  void *v8; // eax
  int lv_index_4c; // [esp-8h] [ebp-4Ch]
  CObArray_1b9060 lv_obArray_34; // [esp+10h] [ebp-34h] BYREF
  int lv_count_1c; // [esp+28h] [ebp-1Ch]
  int v12; // [esp+2Ch] [ebp-18h] BYREF
  JSVDA::object_OSEG *lp_oseg_14; // [esp+30h] [ebp-14h]
  object_10c7df *lp_this_10; // [esp+34h] [ebp-10h]
  JSVDA::object_OSEG *v15; // [esp+40h] [ebp-4h]
...
  lp_oseg_14 = object_1b46bf::getFrameIfCached_1b3335(ap_object_0);                                     // [8] get "Frame" object
  if ( lp_oseg_14 )
  {
    CObArray_1b9060::constructor_1b9060(&lv_obArray_34);
...
    v_count_776b7b = object_OSEG::read_count_block_776b7b(lp_oseg_14);                                  // [9] read count from "Frame" stream
    lv_count_1c = v_count_776b7b;
    ap_object_0->v_memberCount_d8 = v_count_776b7b;                                                     // [9] store count
...
      do
      {
        p_memberObject_776aee = object_10c7df::read_vobject_776aee(p_this, lp_oseg_14);                 // [10] read contents of individual object
        p_memberObject_776aee->v_data_4.v_usageBits__b8 |= 1u;
        v_long_a8 = p_memberObject_776aee->v_data_4.v_long_a8;
...
          lv_index_4c = v_long_a8;
          p_this = lp_this_10;
          JSFC::CObArray::setitem_7de7(&lp_this_10->v_obArray_64, lv_index_4c, p_memberObject_776aee);  // [11] assign item into array
        }
        ++v_count_1c;
      }
      while ( v_count_1c < lv_count_1c );
}

The folowing pseudocode shows the method that is used to read each individual object from the “Frame” stream. This will be done using an object constructed on the stack. At [12], this object will be used to read a block from the stream. At [13], this object will be used to decode two integers from the stream. One of these integers represents the object type and will be passed to the method at [14]. This type will be used to determine which object should be constructed. The vulnerability described by this document only involves objects constructed by cases 2, 5, and 6. After the object has been constructed and returned, the virtual method and regular method at [16] will assign the values that determined which object to construct to the virtual object. At [17], the contents of the “Frame” stream that was related to said object will then be read into the object. After the object has been constructed, the method will return and the resulting object will get assigned into the prior mentioned array.

/* JSTARO25.OCX:0x3c1f6aee */
vobject_9e6e68 *__thiscall object_10c7df::read_vobject_776aee(object_10c7df *this, JSVDA::object_OSEG *ap_object_0)
{
  object_metadata lv_metadata_2c; // [esp+10h] [ebp-2Ch] BYREF
  uint32_t lv_field_18; // [esp+24h] [ebp-18h] BYREF
  int lvw_objectCase_14; // [esp+28h] [ebp-14h] BYREF
  int lv_unusedType_10; // [esp+2Ch] [ebp-10h] BYREF
  int v9; // [esp+38h] [ebp-4h]

  object_metadata::constructor_1b3451(&lv_metadata_2c, 0);
  v9 = 0;
  if ( !object_metadata::readblock_1b8fad(&lv_metadata_2c, ap_object_0, &lv_unusedType_10) )    // [12] read block from stream
    JSFC::CxxThrowException_AVCMemoryException__1a64f();

  object_metadata::htonl_1b91fd(&lv_metadata_2c, &lv_field_18);                                 // [13] read uint32_t from stream
  object_metadata::htonw_1b903e(&lv_metadata_2c, &lvw_objectCase_14);                           // [13] read uint16_t from stream

  p_vobject_3 = object_10c7df::choose_vobject_7769a8(this, lvw_objectCase_14);                  // [14] construct a virtual object

  (*(p_vobject_3->p_vtable_0 + 0x64))(p_vobject_3, lvw_objectCase_14);                          // [16] assign case number
  vobject_9e6e68::setfield_a8__1b9219(p_vobject_3, lv_field_18);                                // [16] assign integer
  vobject_9e6e68::readStuff__1b9229(p_vobject_3, &lv_metadata_2c);                              // [17] read block from stream
  v9 = -1;
  object_metadata::destructor_1b353e(&lv_metadata_2c);
  return p_vobject_3;
}
/* JSTARO25.OCX:0x3c1f69a8 */
vobject_9e7514 *__thiscall object_10c7df::choose_vobject_7769a8(object_10c7df *this, int av_objectCase_0)
{
...
  switch ( av_objectCase_0 )
  {
...
    case 2:
      v9 = JSFC::malloc_181e(sizeof(vobject_1b95c9));                                                   // [15] size is 0x770
      p_result_3 = 0;
      if ( !v9 )
        goto LABEL_22;
      v5 = vobject_1b95c9::constructor_1b95c9(v9, this);                                                // [15] constructor of vobject
      goto LABEL_20;
...
    case 5:
      v6 = JSFC::malloc_181e(sizeof(vobject_9e79d8));                                                   // [15] size is 0x86c
      if ( v6 )
      {
        v5 = vobject_9e79d8::constructor_5430bf(v6, this);                                              // [15] constructor of vobject
        goto LABEL_20;
      }
      break;
    case 6:
      v4 = JSFC::malloc_181e(0x7ECu);                                                                   // [15] size is 0x7ec
      if ( v4 )
      {
        v5 = object_9e7814::constructor_541be0(v4, this, 0xFFFF);                                       // [15] constructor of vobject
LABEL_20:
        p_result_3 = v5;
        goto LABEL_22;
      }
      break;
...
  return p_result_3;
}

The objects that are constructed by the method that was just recently described have the following layout. The field that they all have in common is the structure at offset +0x5e8. It is when this structure is initialized that this vulnerability will be triggered.

// layout of each object that can be constructed.
struct vobject_1b95c9
{
  void *p_vtable_0;
  vobject_9e6e68::data v_data_4;    //   0x4..+0xFC
  struc_1b942e v_data_dc;           // 0x100..+0x4E8
  struc_776812 v_data_5c4;          // 0x5E8..+0x184
  vobject_1b95c9::data v_data_748;  // 0x76c..+0xFC
};

struct vobject_9e79d8
{
  int p_vtable_0;
  vobject_9e6e68::data v_data_4;    //   0x4..+0xFC
  struc_1b942e v_data_dc;           // 0x100..+0x4E8
  struc_776812 v_data_5c4;          // 0x5E8..+0x184
  vobject_1b95c9::data v_data_748;  // 0x76C..+0xFC
  vobject_9e79d8::data v_data_770;  // 0x868..+0xFC
};

After the object has been read from the “Frame” object and assigned into its array, the following method will be returned to. The following loop will then be used to process each object that was constructed and appended to the array. At [16], the length of the array is fetched in order to be used to terminate the loop that follows. At [17], the element at the current array index is fetched, and then a virtual method at [18] is called using the object from the array.

/* JSTARO25.OCX:0x3c1f6da1 */
int __thiscall object_10c7df::getStreamObjectsAndProcessFrame_776da1(object_10c7df *this, JSVDA::object_OFRM *ap_oframe_0, int av_dataformat_4)
{
...
    v_length_4 = this->v_obArray_64.v_data_4.v_length_4;                                // [16] length of an array (CObArray)
    av_boolean_4a = v_length_4;
    v6 = 0;
    v9 = 0;
    while ( v6 < v_length_4 )
    {
      v7 = *(this->v_obArray_64.v_data_4.p_items_0 + 4 * v6);                           // [17] get item at index of the array
      if ( v7 && !(*(*v7 + 308))(v7, &lv_object_fc) )                                   // [18] call method of item
        goto return(0)_776e2e;
      v9 = ++v6;
      v_length_4 = av_boolean_4a;
    }
...
}

The following pseudocode represents the virtual method that is called from the object at the current index of the array. The first thing the virtual method does is to open up the “LayoutBox” stream in order to construct an object and return it at [19]. After the object has been constructed for the stream, the method at [20] will be used to read the header of the stream. Finally the method at [21], will be used to read the contents of the stream by passing the field at offset +0x5c4 as one of its parameters.

/* JSTARO25.OCX:0x3bc39caf */
int __thiscall vobject_1b95c9::method_1b9caf(vobject_1b95c9 *this, object_1b46bf *ap_object_0)
{
  JSVDA::object_OSEG *p_oseg(LayoutBox)_1b9ddc; // edi
  int v_long_ac; // edi
  object_10c7df *p_object_14; // eax
  int v7; // [esp+10h] [ebp-4Ch] BYREF
  int v8[17]; // [esp+14h] [ebp-48h] BYREF
  int v9; // [esp+58h] [ebp-4h]
...
  vobject_9e6e68::method_1b37a3(this);
  p_oseg(LayoutBox)_1b9ddc = object_1b46bf::getStream_LayoutBox__1b9ddc(ap_object_0);               // [19] open up "LayoutBox" stream
  if ( !vobject_9e6e68::method_1b9e29(this, p_oseg(LayoutBox)_1b9ddc)                               // [20] read header of "LayoutBox" stream
    || !stream_LayoutBox_::readStuff__1b9eda(p_oseg(LayoutBox)_1b9ddc, &this->v_data_5c4, 0xFFFF)   // [21] read contents of stream into field at +0x5c4
    || !vobject_1b95c9::read_struc_1b942e__1b9949(this, p_oseg(LayoutBox)_1b9ddc) )
...
}

In order to read the contents of the “LayoutBox” stream, the folllowing pseudocode will be used. At [22], a method will be called to read a uint16_t and a uint32_t from the stream contents. This represents the type and length of the items to be read from the stream. At [23], the type is checked followed by the method call at [24] which will read up to 0x400 bytes into a buffer on the stack. Afterwards, the data that was read from the stream along with the field at offset +0x5c4 of the virtual object will be passed to the method at [25].

/* JSTARO25.OCX:0x3bc39eda */
int __cdecl stream_LayoutBox_::readStuff__1b9eda(JSVDA::object_OSEG *ap_stream(LayoutBox)_0, struc_776812 *ap_destinationStruc_4, int avw_unused_8)
{
  struc_3a896c::construct_3a896c(&lv_struc_438);
  v10 = 0;
  if ( !struc_3a896c::readHeaderFromStream_3a5b36(&lv_struc_438, ap_stream(LayoutBox)_0) )  // [22] read header
    goto return(0)_1b9f9c;
  if ( lv_struc_438.vw_blockType_0 == 0x6666 )                                              // [23] check type from header
...
  else
  {
    if ( (unsigned int)(unsigned __int16)lv_struc_438.vw_blockType_0 - 0x6667 > 1 )         // [23] check type from header
... 
  }
  v_blockSize_4 = lv_struc_438.v_blockSize_4;
  if ( LOWORD(lv_struc_438.v_blockSize_4) > 0x400u )
    goto return(0)_1b9f9c;
  if ( !stream_LayoutBox_::read_850695(                                                     // [24] read contents of buffer into stack
          ap_stream(LayoutBox)_0,
          lv_struc_438.v_blockSize_4,
          lv_buffer_40c__410,
          &lvw_bufferLength_43c) )
    goto return(0)_1b9f9c;
  v5 = lvw_bufferLength_43c + 6;                                                            // [24] add header length to amount read
  if ( !blocklayout_1b9fb5::read_1b9fb5(                                                    // [25] read data after header into field at +0x5c4
          lv_buffer_40c__410,
          ap_destinationStruc_4,
          v_blockSize_4,
          vw_sixsixseven_3,
          &lvw_bufferLength_43c)
    || v_blockSize_4 != lvw_bufferLength_43c )
  {
    goto return(0)_1b9f9c;
  }
return(@esi)_1b9f9e:
  v10 = -1;
  nullsub_55();
  return v5;
}

The data that has just been read from the “LayoutBox” stream, contains an array of elements that are prefixed with two uint16_t representing the type and length of the element. The following pseudocode is responsible for reading individual elements from that array of elements. At [26], the parameter that references the field at +0x5c4 of the virtual object will be initialized prior to entering a loop. For each iteration of the loop, the function at [27] will be called in order to determine the type and length of the element from the stream. This data will be written into a variable on the stack and will then be passed to the function call at [28] in order to determine how the element should be read. The vulnerability described by this document is specifically in regards to how the element of type 0x6003 is handled. At [29], a wrapper is called with the data from the stream and the field at +0x5c4 of the virtual object in order to handle type 0x6003.

/* JSTARO25.OCX:0x3bc39fb5 */
int __cdecl blocklayout_1b9fb5::read_1b9fb5(_BYTE *ap_buffer_0, struc_776812 *ap_destinationStruc_4, unsigned __int16 avw_blocksize_8, unsigned __int16 avw_sixsixseven_10, _WORD *ap_resultLength_10)
{
  *&lv_layout_24.vw_case_0 = 0;
  v5 = 0;
  p_buffer_0 = ap_buffer_0;
  struc_776812::construct_77604c(ap_destinationStruc_4);                            // [26] construct (initialize) parameter object
  v14 = 0;
  while ( v5 < avw_blocksize_8 )
  {
    if ( !blocklayout_1b9fb5::swapHeaderOrder_1b5f56(&lv_layout_24, p_buffer_0) )   // [27] read header of element inside current bock
      JSFC::CxxThrowException_AVCUserException__3471d();
...
    if ( struc_776812::readCases_1ba07e(                                            // [28] use case to read current element from stream
           p_blocklayoutData_0,
           ap_destinationStruc_4,
           lv_layout_24.vw_case_0,
           *&lv_layout_24.vw_wordCounter_2,
           avw_sixsixseven_10,
           &Av_resultLength_8) )
...
    v5 += v_resultLength_8;
    lv_currentOffset_14 = v5;
    p_buffer_0 = &p_blocklayoutData_0[v_resultLength_8];
    lp_blocklayoutdata_18 = p_buffer_0;
  }
  *ap_resultLength_10 = v5;
  return 1;
}

/* JSTARO25.OCX:0x3bc3a07e */
int __cdecl struc_776812::readCases_1ba07e( unsigned __int8 *ap_blocklayoutdata_0, struc_776812 *ap_destinationStruc_4, __int16 avw_case_8, int av_wordCounter_c, unsigned __int16 avw_sixsixseven_10, unsigned __int16 *ap_result_14)
{
 switch ( avw_case_8 )
  {
    case 0x6001:
      v6 = sub_3BC93C2D(ap_blocklayoutdata_0, ap_destinationStruc_4, av_wordCounter_c, 0, ap_result_14);
      goto LABEL_13;
    case 0x6002:
      v6 = sub_3BC93C6F(ap_blocklayoutdata_0, ap_destinationStruc_4, av_wordCounter_c, 0, ap_result_14);
      goto LABEL_13;
    case 0x6003:
      v6 = struc_776812::wrapper_addItemWithKey_6003__213cb1(                       // [29] call function for block type 0x6003
             ap_blocklayoutdata_0,
             ap_destinationStruc_4,
             av_wordCounter_c,
             0,
             ap_result_14);
      goto LABEL_13;
...
}

/* JSTARO25.OCX:0x3bc93cb1 */
int __cdecl struc_776812::wrapper_addItemWithKey_6003__213cb1(_BYTE *ap_source_0, struc_776812 *ap_destinationStruc_4, int av_wordCounter_8, CPtrArray **ap_ptrArray_c, _WORD *ap_result_10)
{
  return sub_3C36B3AF(ap_source_0, ap_destinationStruc_4, 0x6003, av_wordCounter_8, ap_ptrArray_c, ap_result_10);
}

The following pseudocode shows the contents of the function called by the wrapper for element type 0x6003. This function uses the data from the file as its first parameter, the field of the virtual object that was previously allocated as its second parameter, and the length read from the file as its fourth parameter. At [30], the length read from the file is clamped to 16-bits and will be used to terminate a loop that follows. At [31], a pointer will be initialized so that it references the field at offset +0x52 of the parameter which originates from the virtual object that was previously allocated. It is suspected that the field at offset +0x52 is actually only 0x20 bytes. Despite this, at [32], the clamped 16-bit length will be used in a loop that reads from the first parameter at [33], and writes to this pointer at [34]. At this loop is bounded by a 16-bit integer that can be up to 16-bits (0xffff), this loop can write outside the boundaries of the field that was allocated for it resulting in memory corruption and can allow for code execution under the context of the application.

/* JSTARO25.OCX:0x3c36b3af */
int __cdecl sub_3C36B3AF(_BYTE *ap_source_0, struc_776812 *ap_destinationStruc_4, int avw_key_8, int av_wordCounter_c, CPtrArray **ap_resultPtrArray_10, _WORD *ap_result_14)
{
   __int16 vw_wordCounter_c; // cx
  __int16 vw_zero_8; // si
  char *v9; // edi
  int v_counter_c; // ebx
  char v11; // al
  int result; // eax
  __int16 v13; // cx
  __int16 Av_wordCounter_c; // [esp+14h] [ebp+Ch]
...
  vw_wordCounter_c = av_wordCounter_c;                          // [30] length from current element in stream
  vw_zero_8 = 0;
  av_wordCounter_c = (unsigned __int16)av_wordCounter_c;        // [30] clamp length from current element in stream
  v9 = ap_destinationStruc_4->vb_array(20)_52;                  // [31] assign offset +0x52 of +0x5c4 field to pointer for writing
  Av_wordCounter_c = 0;
  if ( (_WORD)av_wordCounter_c )
  {
    vw_zero_8 = av_wordCounter_c;
    v_counter_c = (unsigned __int16)av_wordCounter_c;           // [32] grab uint16_t from parameter
    Av_wordCounter_c = av_wordCounter_c;
    do
    {
      v11 = *ap_source_0++;                                     // [33] read from contents of stream
      *v9++ = v11;                                              // [34] write into field at offset +0x52 of +0x5c4 field
      --v_counter_c;
    }
    while ( v_counter_c );                                      // [32] loop sentinel
  } 
...
}

The layout of the structure being written to has a size of +0x184. The field being written to is at offset +0x52 of this structure. It is suspected that the size of this field is 0x20 bytes.

struct struc_776812
{
  ...
  __int16 field_50;             // 0x50..+0x2
  char vb_array(20)_52[32];     // 0x52..+0x20
  __int16 field_72;             // 0x72..+0x2
  ...
};

Crash Information

First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0000ffcd ebx=0000fea5 ecx=00a6ffff edx=00d38cb0 esi=0000ffff edi=3f886000
eip=513cb3e2 esp=00d389d0 ebp=00d389dc iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00210212
JSTARO25!DllUnregisterServer+0x4b5e1b:
513cb3e2 8807            mov     byte ptr [edi],al          ds:0023:3f886000=??

0:000> r @cx
cx=ffff

0:000> dc @edx
00d38cb0  fffffffe 77274b53 771d3cf5 4551aff8  ....SK'w.<.w..QE
00d38cc0  ba8679de 00000000 03a30000 3f8858ac  .y...........X.?
00d38cd0  771d3cf5 0d21cfc8 ba867a26 00000000  .<.w..!.&z......
00d38ce0  03a30000 0f261f38 01001002 00000010  ....8.&.........
00d38cf0  ba867922 00d38cb0 77137eb8 00d38e38  "y.......~.w8...
00d38d00  772181c0 cd7dda46 fffffffe 77274b53  ..!wF.}.....SK'w
00d38d10  771d3cf5 00000000 ba867a66 00000000  .<.w....fz......
00d38d20  03a30000 00000000 03a30000 00000000  ................

0:000> ub . L2
JSTARO25!DllUnregisterServer+0x4b5e18:
513cb3df 8a02            mov     al,byte ptr [edx]
513cb3e1 42              inc     edx

0:000> u . L4
JSTARO25!DllUnregisterServer+0x4b5e1b:
513cb3e2 8807            mov     byte ptr [edi],al
513cb3e4 47              inc     edi
513cb3e5 83eb01          sub     ebx,1
513cb3e8 75f5            jne     JSTARO25!DllUnregisterServer+0x4b5e18 (513cb3df)

0:000> dc @edi
3f886000  ???????? ???????? ???????? ????????  ????????????????
3f886010  ???????? ???????? ???????? ????????  ????????????????
3f886020  ???????? ???????? ???????? ????????  ????????????????
3f886030  ???????? ???????? ???????? ????????  ????????????????
3f886040  ???????? ???????? ???????? ????????  ????????????????
3f886050  ???????? ???????? ???????? ????????  ????????????????
3f886060  ???????? ???????? ???????? ????????  ????????????????
3f886070  ???????? ???????? ???????? ????????  ????????????????

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 both the “Frame” and “LayoutBox” streams. Within the “Frame” stream is an array of blocks, each block within 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> '0'                                                                                                                            
[10] <instance jtd.ntohs 'type'> 0x0102 (258)                          
[12] <instance jtd.ntohs 'size'> 0x0038 (56)                           
[14] <instance jtd.Frame_member_776aee 'data'> "\x00\x00\x00\x04\x00\x02\x00\x00\x00\x05\x00\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x15\x28\x00\x00\x00\x00\x00\x00\x13\x88\x00\x00\x13\x88\x00\x00\x00\xc8\x00\x01\x00\x00\x00\x00\x00\x02\x0b\x80\x00\x00\x00\x00"                                     
[4c] <instance dynamic.block(0) 'padding'> ""                                                                                                          

Within each block is an array of elements that are prefixed with 32-bit field followed by a 16-bit type. If any of the types within the block are the values 0x0002, 0x0005, or 0x0006, then the “LayoutBox” stream will be consulted.

<class jtd.Frame_member_776aee> 'data'                                 
[14] <instance jtd.ntohl 'lv_field_18'> 0x00000004 (4)                 
[18] <instance jtd.ntohs 'lvw_objectCase_14'> 0x0002 (2)
[1a] <instance jtd.Frame_vobject_unknown_substance 'vobject_7769a8'> "\x00\x00\x00\x05\x00\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x15\x28\x00\x00\x00\x00\x00\x00\x13\x88\x00\x00\x13\x88\x00\x00\x00\xc8\x00\x01\x00\x00\x00\x00\x00\x02\x0b\x80\x00\x00\x00\x00"                                       

The “LayoutBox” stream uses a data structure similar to the “Frame” stream. At the beginning of the “LayoutBox” stream are two instances of the 16-bit type, 16-bit length blocks. After these blocks have been read is a third block.

<class jtd.LayoutBox> 'unnamed_7fec2c256320' {unnamed=True,abated=True}
[0] <instance jtd.Frame_block 'version?'> "\x00\x01\x00\x04\x00\x01\x00\x01"
[8] <instance jtd.Frame_block 'skip?'> "\x02\x01\x00\x08\xff\xff\xff\xff\xff\xff\xff\xff"
[14] <instance jtd.LayoutBox_block 'box'> "\x66\x68\x00\x00\x00\xa6\x60 ..skipped ~179 bytes.. \x00\x00\x00\x00\x00\x00\x00"
[0] <class jtd.LayoutBox_rest> rest ...

This third block has the following structure and contains an array of elements within. Each of the elements contained within this structure are prefixed with a 16-bit type and a 16-bit length.

<class jtd.LayoutBox_block> 'box'
[14] <instance jtd.ntohs 'vw_blockType_0'> 0x6668 (26216)
[16] <instance jtd.ntohl 'v_blockSize_4'> 0x000000a6 (166)
[1a] <instance jtd.LayoutBox_buffer 'lv_buffer_410'> jtd.blocklayout_1b9fb5[4] "\x60\x06\x00\x09\x3c\x00\xc8 ..skipped ~146 bytes.. \x03\x00\x00\xff\xff\x00\x00"
[c0] <instance dynamic.block(27) 'padding'> "\x04\x01\x00\x54\x00\x00\x00\x96\x00\x00\x00\x28\x00\x00\x00\x64\x00\x00\x00\xc8\x00\x00\x00\xc8\x00\x00\x00"

If any of the types are 0x6003, and the length is larger than 0x20, then the application will write outside the bounds of the field. If the length is larger than 0x132, then the application will write outside the bounds of its field. Depending on the object type that was parsed out of the “Frame” stream, if the length is larger than 0x1ac for type 2, 0x2a8 for type 5, and 0x228 for type 6, then a heap overflow will occur.

<class jtd.blocklayout_1b9fb5> '3' {underload=True}
[a3] <instance jtd.ntohs 'vw_case_0'> 0x6003 (24579)
[a5] <instance jtd.ntohs 'vw_wordCounter_2'> 0xffff (65535)
[a7] <instance ptype.block 'block'> ""
[a7] <instance dynamic.block(65535) 'padding'> {underload=True} "\x07\x03\x1f\x0f\x03\x01\xff\x00\x00\x03\xe8\x00\x00\x01\x07\x00\x00\x00\x03\x00\x00\xff\xff\x00\x00"
TIMELINE

2023-02-15 - Vendor Disclosure
2023-04-04 - Vendor Patch Release
2023-04-05 - Public Release

Credit

Discovered by a member of Cisco Talos.