CVE-2022-45115
A buffer overflow vulnerability exists in the Attribute Arena functionality of Ichitaro 2022 1.0.1.57600. A specially crafted document can lead to memory corruption. An attacker can provide a malicious file to trigger this vulnerability.
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
Ichitaro 2022 1.0.1.57600
Versions of relevant binaries:
JSTARO25.OCX
File version: 1.0.1.58105
jsvda.dll
File version: 3.3.321.1
jsmisc32.dll
File version: 2.7.1.0
taro32.exe
File version: 1.0.1.57600
T32com.dll
File version: 1.0.0.200
Ichitaro - https://www.ichitaro.com/
7.8 - CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CWE-122 - Heap-based Buffer Overflow
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 that allow it to remain competitive with other available word processors.
Other than the typical document and spreadsheet formats that are provided by the Microsoft Office suite, Ichitaro also supports their native document format which uses the file extension .JTD. This file format is based on Microsoft’s Structured Storage format that was developed as part of Microsoft’s Component Object Model (COM).
Similar to most document types which utilize this format, the structure of the entire .JTD document is stored within the streams that compose the Structured Storage file format. Thus, to access them, the application will use Microsoft’s Structured Storage API as exposed via COM. When the application first needs to process a newly opened document, the following function located within the T32com.dll library will be used to construct the object that retains the information used to reference a given document.
Once inside the JSVDA.dll function, the following code will be used to allocate 0x58 bytes of space using the tagged allocator. The result of the allocation will then be written to its last parameter so that it can be initialized later in the function.
277d462f: lea eax, [ebp+lp_resultDoc_4]
277d4632: push eax ; result
277d4633: push offset ReleaseObjectTag(DOC)_18fa9 ; destructor
277d4638: push 4
277d463a: push 'COD' ; "DOC\0" tag
277d463f: push size object_34605 ; 0x58 bytes
277d4641: push 1
277d4643: call threadSafeObjectAllocator_170b0 ; [1] tagged allocator
277d4648: mov esi, eax
277d464a: add esp, 18h
277d464d: cmp esi, ebx
277d464f: jl loc_277D4948
Within an object using the “object_34605” class is a field that is located at offset +0x48. This field contains a handle that is used to refer to attributes that will be loaded from the document represented by the object. Immediately after allocating an instance of the “object_34605” class, at [2] the function will initialize the field that contains the arena to -1.
277d4663: mov ecx, [ebp+lp_resultDoc_4]
277d4666: mov eax, [edi+object_78250.v_data_4.p_csecHeader_0]
277d4669: mov esi, [ebp+ap_vfkArray_c]
277d466c: mov [ecx+object_34605.p_refCountHeader?_4], eax
277d466f: mov eax, [ebp+lp_resultDoc_4] ; object_34605
277d4672: or [eax+object_34605.v_arenaField_48.p_arena_0], 0FFFFFFFFh ; [2] initialize field to 0xffffffff
277d4676: cmp esi, ebx
277d4678: jz short loc_277D4681
After the “object_34605” field has been initialized, the JSVDA.dll function will proceed to check if the document needs to be decoded, decrypted or converted. Upon completion, the following code will be executed to initialize another field belonging to the “object_34605” class. This code takes each of the objects associated with the path handle, which include “object_78250” and “object_117c5”, and will combine them with the document object “object_34605” in order to initialize a field which contains a descriptor that represents the attributes of a parsed document. This field will then be stored to one of its parameters when the function at [3] is used.
277d4868: mov eax, [ebp+lp_resultDoc_4]
277d486b: lea edx, [eax+object_34605.v_arenaField_48] ; result storage
277d486e: mov ecx, [eax+object_34605.v_arenaField_48.p_pathObject_8] ; object_78250
277d4871: push edx ; result field
277d4872: cmp [eax+object_34605.p_PtrArray?_44], ebx
277d4875: push [ebp+ap_ownerDoc_4] ; object_34605
277d4878: push [eax+object_34605.v_argFlags_14]
277d487b: push edi ; object_78250
277d487c: push [eax+object_34605.p_vfkObject_38]
277d487f: push [ecx+object_78250.v_data_4.p_streamInfoObject_a8] ; object_117c5
277d4885: push [ecx+object_78250.v_data_4.p_cmsgObject_ac]
277d488b: jz short loc_277D48AA
...
277d48aa: call object_34605::field_48::create_691ce ; [3] create attributes descriptor
277d48af: add esp, 1Ch
The function that is responsible for initializing this field is as follows. The first thing the function does is initialize the attributes descriptor within the field at +0x48 of the “object_34605” class at [4]. Afterwards, some objects will be extracted from the fields of the path object, “object_78250”, and the “object_117c5” so that they can be used as parameters with the function call at [5].
278091ce: push ebp
278091cf: mov ebp, esp
278091d1: push ecx
278091d2: mov eax, [ebp+ap_cmsg_0]
278091d5: or [ebp+lp_arena_4], 0FFFFFFFFh
278091d9: push ebx
278091da: mov ebx, [ebp+ap_result_18]
278091dd: push esi
278091de: or [ebx+object_34605::field_48.p_arena_0], 0FFFFFFFFh ; [4] initialize attributes handle
278091e1: test eax, eax
278091e3: jz short loc_278091EA
...
27809209: lea esi, [ebp+lp_arena_4]
2780920c: push esi ; result attributes handle
2780920d: push eax ; base attributes handle (-1)
2780920e: push [ebp+av_docField_10]
27809211: push [ebp+ap_pathObject_c] ; object_78250
27809214: push [ebp+ap_jsTaroMimeType?_8]
27809217: push ecx ; object_73148 from object_117c5
27809218: push edx
27809219: call summaryinfolayout::create_6924b ; [5] call function to parse document stream
2780921e: mov esi, eax
27809220: add esp, 1Ch
27809223: test esi, esi
27809225: jge short loc_27809240
Similar to the logic used with the path handle to parse the “\5SummaryInformation” stream, this function will initialize a layout within the function’s frame and then use it to parse information out of another stream belonging to the structured storage document. At [6], the layout will first be initialized using the memset
function. Afterwards, the parameters of the function will then be stored into the initialized variable. This includes the “object_78250” representing the “PATH” object along with a reference to an object using the “object_73148” class that was extracted from one of the fields belonging to “object_117c5”.
2780924b: push ebp
2780924c: mov ebp, esp
2780924e: sub esp, 20h
...
27809254: push 20h ; ' '
27809256: lea eax, [ebp+lv_layout_20]
27809259: push 0
2780925b: push eax
2780925c: call memset ; [6] initialize variable on the stack
...
27809261: mov eax, [ebp+av_field_0]
27809264: or [ebp+lv_layout_20.field_14], 0FFFFFFFFh
27809268: mov ecx, [ebp+ap_jsTaroMimeType?_8]
2780926b: mov [ebp+lv_layout_20.p_nullobject_c], eax
2780926e: mov eax, [ebp+ap_mrfObject_4] ; object_73148
27809271: add esp, 0Ch
27809274: mov [ebp+lv_layout_20.p_object_10], eax ; object_73148
27809277: mov eax, [ebp+ap_object_c] ; object_78250
2780927a: test eax, eax
2780927c: mov [ebp+lv_layout_20.p_vfk_18], ecx
2780927f: mov [ebp+lv_layout_20.p_object_8], eax ; object_78250
27809282: jz short loc_2780928A
After initializing the layout, the function will check its parameters in order to examine their fields and determine what type of parsing will be done. These values will be written to both the fields at offset +0 and offset +4 of the layout before executing the following code. At [7], the address of the layout will be passed to a function call along with a pointer that whatever gets parsed will be written to. This function call is directly responsible for allocating a variable-sized block of memory that can contain any number of attributes that are parsed from both the path and document handles. This block of memory is then represented by a handle which will get written to the field at offset +0x48 of the “object_34605” upon completion.
2780939b: mov esi, [ebp+ap_resultArena_18]
2780939e: lea eax, [ebp+lv_layout_20]
278093a1: push eax ; layout from current frame
278093a2: push [ebp+ap_arena_4]
278093a5: push esi ; result attributes handle
278093a6: call global_1c5d0::arena::parseLayoutIntoArena_693df ; [7] parse stream
278093ab: mov edi, eax
278093ad: add esp, 0Ch
278093b0: test edi, edi
278093b2: jl short loc_278093D8
In order to parse the stream and initialize the arena, the function will first create a descriptor for preserving the attributes of the document. This is done by calling the function at [8] which will actually allocate space for two memory locations which are linked together during their initialization. Each location is capable of representing a variable-sized contiguous block of memory which when expanded can allow for the expanded data to be distributed across non-contiguous blocks of memory. Within this document, each location will be referred to as an attribute arena. After the necessary data structures have been updated to maintain the state of the new arenas, the first 0x10 bytes of them are initialized with an 8-byte string followed by a size describing the current arena size. Afterwards, the next 0x30 bytes of each arena is populated with a data structure representing the header. This header contains a number of attributes which include the type, and a pointer that references its sibling that was allocated at the same time. After each arena has been initialized, the first one that is allocated will be written to the first parameter of the function call at [58].
278093df: push ebp
278093e0: mov ebp, esp
278093e2: sub esp, 74h
278093e5: mov eax, [ebp+ap_layout_8] ; layout from caller
278093e8: push ebx
278093e9: push esi
278093ea: mov esi, [ebp+ap_arena_0] ; address to write arena/attributes descriptor
278093ed: push edi
278093ee: xor edi, edi
278093f0: push dword ptr [eax+summaryinfolayout.v_arenaType_0]
...
278093fb: push esi ; result arena descriptor
278093fc: mov [ebp+ap_onethousandData_14], edi
278093ff: call ids_wmm::createArena_69660 ; [8] create two arena descriptors that are linked together
27809404: mov ebx, eax
27809406: pop ecx
27809407: cmp ebx, edi
27809409: pop ecx
2780940a: jl return(@ebx)_69659
Once the initial arena has been initialized, it will then be used with the following function call at [9]. This function call will use the layout that was initialized by the caller to read the contents of one of the streams from the document in order to determine 4 of its parameters, and store a list of attributes from the document into the allocated arena referenced by the first parameter.
2780946e: push gv_alwaysZero_798c8 ; parser type
27809474: lea eax, [ebp+ap_onethousandData_14]
27809477: push eax ; result integer
27809478: lea eax, [ebp+lp_data_4]
2780947b: push eax ; result file type data
2780947c: lea eax, [ebp+lp_summaryInformationData_c]
2780947f: push eax ; result version information data
27809480: lea eax, [ebp+lp_wstring_8]
27809483: push eax ; result summary information string
27809484: push edx ; local variable from caller
27809485: push dword ptr [esi] ; arena handle
27809487: call global_1c5d0::arena::readAttributesFromSummaryInformationStream_698a1 ; [9] read attributes from document
2780948c: add esp, 1Ch
The first action the function will perform is to call the function at [10] which will be used to calculate the size of a buffer used to retain the list of attributes prior to copying them into their respective arena. This is accomplished by using the information at offset +0x4 of the layout in order to determine the type of arena. The buffer is used to determine a value, which is then stored at offset +0x0 of the layout. After determining the type, the rest of the function will calculate the size, which is returned and stored into a local variable at [11]. This size is used at [12] to allocate a buffer for the available attributes.
278098a1: push ebp
278098a2: mov ebp, esp
278098a4: sub esp, 18h
278098a7: push ebx
278098a8: mov ebx, [ebp+ap_layout_4] ; layout from further up the stack
278098ab: push esi
278098ac: push edi
278098ad: xor edi, edi
278098af: push ebx ; layout
278098b0: mov [ebp+lp_buffer_8], edi
278098b3: call summaryinfolayout::determineArenaType_69a1e ; [10] calculate the size needed for the attributes
278098b8: mov esi, eax
278098ba: pop ecx
278098bb: cmp esi, edi
278098bd: jl loc_27809A0E
...
278098c3: lea eax, [ebp+lp_buffer_8]
278098c6: mov [ebp+lv_size_c], esi ; [11] store the size of the attributes
278098c9: push eax
278098ca: push esi
278098cb: call TaggedObjectAllocate(BIN)_18bd6 ; [12] allocate space using size
278098d0: mov esi, eax
278098d2: pop ecx
278098d3: cmp esi, edi
278098d5: pop ecx
278098d6: jl loc_27809A0E
Inside the implementation of the function that is used to calculate the size of the heap buffer is the following code. As described previously, the function will first check the arena type and file information from the layout that was passed as the first parameter. At [13], a function will get called to read the contents of the “\4JSRV_SummaryInformation” stream. As each of its parameters are set to NULL, the function will not store anything that is read from the stream and will instead only return the size that is calculated from its contents. Once the size has been returned, at [14] the function will add 0x80 as a buffer and then use it with the function call at [15] to calculate the total size that should be allocated for the attribute contents. At [16], the adjusted size will be added to a global containing the number of bytes required for the statically declared attributes, which will then be returned to the caller upon completion.
27809a35: mov esi, [ebp+ap_layout_0]
27809a38: mov eax, [esi+summaryinfolayout.v_arenaType_0]
27809a3a: and eax, 0F0000h
27809a3f: cmp eax, 20000h
27809a44: jz loc_27809AE6
...
27809a4a: mov eax, [esi+summaryinfolayout.v_fileInfo_4]
...
27809a61: push edi
27809a62: push 0FFFFFFFFh
27809a64: push edi
27809a65: push esi ; layout
27809a66: push edi
27809a67: push edi
27809a68: call summaryinfolayout::ReadStream(JSRV_SummaryInformation)_51ad6 ; [13] read contents of stream to calculate size
27809a6d: add esp, 18h
27809a70: cmp eax, edi
27809a72: jl short loc_27809A7E
...
27809a74: add eax, 80h ; [14] adjust the return size by +0x80
27809a79: push eax ; use returned size as parameter
27809a7a: push 1 ; attribute class
27809a7c: jmp short loc_27809AC4
...
27809ac4: call global_796f8::getPathAttributesOrEndOfAttributes_69af8 ; [15] \ call function to calculate total size of attributes
27809ac9: pop ecx
27809aca: pop ecx
27809acb: jmp short loc_27809AD2
\
27809af8: cmp [esp+av_class_0], 0
27809afd: mov eax, gv_hvPathAttributesSummationExtra_796f8 ; [16] constant size of static attributes (0x188)
27809b02: jnz short loc_27809B09
27809b04: add eax, 0A00h
27809b09: cmp [esp+av_size_4], 0
27809b0e: jle short locret_27809B14
27809b10: add eax, [esp+av_size_4] ; [16] add the size from the function parameter and return it
27809b14: retn
/
27809ad2: cmp eax, edi ; check that the size is unsigned
27809ad4: jge short loc_27809AF3
...
27809af3: pop edi
27809af4: pop esi
27809af5: pop ebx
27809af6: leave
27809af7: retn
The following function is responsible for reading the attribute size from the header of the “\4JSRV_SummaryInformation” stream. It includes additional functionality that allows it to process its contents and store them to a pointer that is passed as one of the function’s parameters. As the caller has passed NULL for many of the function’s parameters, only a calculation using the sizes from the header will be returned. At [17], the function will start by opening up the stream using its name and write the address of an object used to reference the stream as its first parameter. Afterwards at [18], the function will read 0x40 from the beginning of the stream into a buffer that is located on the stack. Once the contents of the header have been made accessible, at [19], two fields will be read from offset +0x14 and offset +0x24 of the header. These two fields contain the sizes of the different components of the stream and at [20] will be added together before being returned to the caller.
277f1aea: push 10h
277f1aec: push offset str.JSRVSummaryInformation ; stream name
277f1af1: push [ebp+ap_layout_8] ; layout
277f1af4: lea eax, [ebp+lp_iidPointerOleStream_4] ; object_731a8 for referencing stream
277f1af7: push eax
277f1af8: call summaryinfolayout::OpenStream_14174 ; [17] open the designated stream
277f1afd: mov esi, eax
277f1aff: add esp, 10h
277f1b02: cmp esi, ebx
277f1b04: jl return(@esi)_51bde
...
277f1b0a: lea eax, [ebp+lv_buffer(40)_44]
277f1b0d: push eax ; destination buffer
277f1b0e: push [ebp+lp_iidPointerOleStream_4] ; stream
277f1b11: push [ebp+ap_layout_8] ; layout
277f1b14: call summaryinfolayout::ReadHeader(40)_52074 ; [18] read 0x40 bytes from stream
277f1b19: mov esi, eax
277f1b1b: add esp, 0Ch
277f1b1e: cmp esi, ebx
277f1b20: jnz loc_277F1BC3
...
277f1b26: mov eax, [ebp+ap_resultTotalSize_0]
277f1b29: mov edi, dword ptr [ebp+lv_buffer(40)_44+14h] ; [19] field at offset +0x14 of header
277f1b2c: cmp eax, ebx
277f1b2e: jz short loc_277F1B32
...
277f1b32: mov eax, [ebp+ap_resultUnknown_4]
277f1b35: mov ecx, dword ptr [ebp+lv_buffer(40)_44+24h] ; [19] field at offset +0x24 of header
277f1b38: cmp eax, ebx
277f1b3a: jz short loc_277F1B3E
...
277f1b3e: cmp [ebp+av_readSizeOrContents_c], ebx
277f1b41: jl short loc_277F1BBE
277f1b43: mov eax, [ebp+av_leftOverSizeSigned_10]
277f1b46: cmp eax, ebx
277f1b48: jle short loc_277F1BBE
277f1b4a: cmp [ebp+ap_resultBuffer_10], ebx ; parameter set to NULL
277f1b4d: jz short loc_277F1BBE
...
277f1bbe: lea esi, [edi+ecx] ; [20] add both fields and return the result
277f1bc1: jmp short loc_277F1BCA
...
277f1bca: push [ebp+lp_iidPointerOleStream_4]
277f1bcd: push [ebp+ap_layout_8]
277f1bd0: call object_731a8::destroy_1424b ; release object_731a8 for stream
277f1bd5: pop ecx
277f1bd6: pop ecx
277f1bd7: jmp short return(@esi)_51bde
Re-visiting the caller of the function that read the stream header, the following instructions summarize how the fields from the header are used to calculate the size of the buffer that gets allocated. At [21] is the prior-mentioned function which calculated the number of bytes by taking the sum of fields +0x14 and +0x24 of the “\4JSRV_SummaryInformation” header. Upon receiving the resulting sum, the instruction at [21] adds 0x80 and then adds a global constant that is calculated at load-time (0x188) to the result. The result of this will be an allocation that is a minimum of 0x208 bytes in size. Afterwards at [22], the result is then used to allocate a heap buffer and store it on the stack. Once the allocation is successful, the buffer and its size is then passed to the function call at [23] to properly read the attributes from the stream into the buffer. The calculated size that is used to allocate this heap buffer is directly responsible for the vulnerability described within this document, and thus the function call at [23] can be used to overflow said buffer.
278098af: push ebx ; layout
278098b0: mov [ebp+lp_buffer_8], edi
278098b3: call summaryinfolayout::determineArenaType_69a1e ; [21] return size of arena from header
\
...
27809a68: call summaryinfolayout::ReadStream(JSRV_SummaryInformation)_51ad6 ; return size from stream contents
27809a6d: add esp, 18h
27809a70: cmp eax, edi
27809a72: jl short loc_27809A7E
...
27809a74: add eax, 80h ; [21] add 0x80 to the returned size
27809a79: push eax ; size parameter
27809a7a: push 1 ; attribute class
27809a7c: jmp short loc_27809AC4
...
27809ac4: call global_796f8::getPathAttributesOrEndOfAttributes_69af8 ; [21] add 0x188 to the returned size
27809ac9: pop ecx
27809aca: pop ecx
27809acb: jmp short loc_27809AD2
/
278098c3: lea eax, [ebp+lp_buffer_8]
278098c6: mov [ebp+lv_size_c], esi ; [22] use the returned size
278098c9: push eax
278098ca: push esi
278098cb: call TaggedObjectAllocate(BIN)_18bd6 ; [22] allocate space using size
...
278098dc: lea eax, [ebp+lv_arenaType_4]
278098df: push eax ; arena type
278098e0: lea eax, [ebp+lv_resultFromStream_10]
278098e3: push [ebp+lv_size_c] ; [23] size
278098e6: push eax ; results from stream
278098e7: lea eax, [ebp+lv_size_14]
278098ea: push eax ; result size
278098eb: lea eax, [ebp+lv_staticAttributeSize_18]
278098ee: push eax ; result static attribute size
278098ef: push ebx ; layout
278098f0: push [ebp+lp_buffer_8] ; [23] buffer for static attributes
278098f3: call summaryinfolayout::ReadSummaryAttributes_69b15 ; [23] read attributes into allocated buffer
After the heap buffer has been allocated, the following function will be used to store a combination of each of the available attributes to it. At [24] is a function call that is responsible for copying the statically declared attributes to the beginning of the heap buffer and return the number of bytes that were written. At [25], the size of the first attributes that were written is stored to third parameter of the function. Afterwards, the returned size is then used, with the heap buffer size that was passed as the fifth parameter, to calculate a pointer that references the end of the recently written attributes and the number of bytes that can be written. Once the leftover size and pointer to the end of the attributes has been calculated, the function call at [26] will be called again in order to append the attributes found within the “\4JSRV_SummaryInformation” stream to the end of heap buffer. After checking for overflow issues, the function will return. Finally at [27] the same buffer will be passed to a call instruction that will append the rest of the required attributes that are defined statically. This will result in the buffer that was provided as a parameter containing a combination of the statically defined path attributes, followed by attributes read from the “\4JSRV_SummaryInformation” stream, which is then overlaid by the statically declared document attributes.
27809b15: push ebp
27809b16: mov ebp, esp
27809b18: sub esp, 44h
...
27809b2a: xor eax, eax
27809b2c: push 1 ; attribute type
27809b2e: mov [ecx], eax
27809b30: push [ebp+ap_buffer_0] ; allocated heap buffer for attributes
...
27809b39: call struc_6a4f0::copyAllAttributesByType?_69bfd ; [24] copy statically declared attributes (0x188) into buffer
27809b3e: pop ecx
27809b3f: test eax, eax
27809b41: pop ecx
27809b42: jl loc_27809BF8
...
27809b48: mov ecx, [ebp+ap_buffer_0]
27809b4b: mov [edi], eax ; [25] store returned size to parameter
27809b4d: mov edi, [ebp+av_size_14] ; [25] size from header as a parameter
27809b50: sub edi, eax ; [25] subtract header size from static size (0x188)
27809b52: add eax, ecx ; calculate offset into heap buffer for stream contents
27809b54: mov ecx, [ebp+ap_layout_4]
27809b57: mov [ebp+ap_buffer_0], eax ; store calculating to local variable
...
27809b5a: mov edx, [ecx+summaryinfolayout.v_arenaType_0] ; check arena type from layout
...
27809b6a: mov edx, [ecx+summaryinfolayout.v_fileInfo_4] ; check file info from layout
...
27809b7c: push eax ; offset into heap buffer
27809b7d: push edi ; left over size in heap buffer
27809b7e: push 0
27809b80: push ecx ; layout
27809b81: push ebx ; result partial size
27809b82: push esi ; result total size
27809b83: call summaryinfolayout::ReadStream(JSRV_SummaryInformation)_51ad6 ; [27] read contents of arena from stream
27809b88: add esp, 18h
27809b8b: test eax, eax
27809b8d: jle short loc_27809B97
...
27809b97: cmp eax, 800F0000h
27809b9c: jz short loc_27809BA2
27809b9e: test eax, eax
27809ba0: jl short leave_69bf8
...
27809ba2: push 2 ; attribute type
27809ba4: push [ebp+ap_buffer_0] ; offset into heap buffer
27809ba7: jmp short loc_27809BE1
...
27809be1: call struc_6a4f0::copyAllAttributesByType?_69bfd ; [28] append static attributes after buffer
27809be6: pop ecx
27809be7: test eax, eax
27809be9: pop ecx
27809bea: jl short loc_27809BF8
The following function is responsible for overlaying the end of the heap buffer with the rest of the statically declared attributes. Depending on the attribute type that is passed as the second parameter of this function, a different attribute array will be used. As the caller is using an attribute type of 2 when calling this function, the instruction at [29] will load the address of the attribute array at offset +0x734e0 of the library. At [30], the function will enter a loop which will copy the attributes from this address into the heap buffer that was passed as a parameter. For each iteration of this loop, the instructions at [31] will copy 0x60 bytes of the attribute directly to the current position within the heap buffer. As the application does not constrain the size for the heap buffer to a multiple of each of these attributes, when the sum of the fields from the “\4JSRV_SummaryInformation” stream header and the constants for the sizes of the statically declared attributes (0x208) are not a multiple of the attribute size, this copy can write outside the bounds of the allocated heap buffer. This can cause memory corruption, which can lead to code execution under the context of the application.
27809bfd: push ebp
27809bfe: mov ebp, esp
27809c00: push ecx
27809c01: mov ecx, [ebp+av_attributeType_4] ; fetch attribute type (2)
...
27809c08: dec ecx
27809c0a: jz short loc_27809C1F
27809c0c: dec ecx
27809c0d: jz short loc_27809C16
...
27809c16: xor ebx, ebx
27809c18: mov eax, offset gv_hvSummaryAttributes_734e0 ; [29] global containing attributes to overlay
27809c1d: jmp short loc_27809C26
...
27809c26: test ebx, ebx
27809c28: jl short loc_27809C80
27809c2a: cmp word ptr [eax+struc_6a4f0.v_wDescription?_4], 0 ; check for empty description
27809c2f: mov ebx, [ebp+ap_destination_0] ; heap buffer from parameters
27809c32: jz short loc_27809C7D
27809c34: mov [ebp+av_attributeType_4], eax ; [30] start of statically declared attributes
27809c37: mov [ebp+var_4], eax
27809c3a: jmp short loc_27809C3F ; [30] enter loop
...
27809c3c: mov eax, [ebp+av_attributeType_4] ; pointer to current attribute
...
27809c3f: push 18h ; length (0x60)
27809c41: mov esi, eax ; source attribute
27809c43: pop ecx
27809c44: mov edi, ebx ; destination attribute
27809c46: rep movsd ; [31] copy 0x60 bytes
...
27809c48: mov ecx, [eax+struc_6a4f0.v_extra2_54] ; extra field of attribute
27809c4b: add ecx, [eax+struc_6a4f0.v_extra1_50] ; extra field of attribute
27809c4e: lea eax, [ebx+(size struc_6a4f0)]
27809c51: push ecx
27809c52: push 0
27809c54: push eax
27809c55: call memset ; [31] initialize extra space after attribute
...
27809c5a: mov eax, [ebp+av_attributeType_4]
27809c5d: add esp, 0Ch
27809c60: mov ecx, [eax+struc_6a4f0.v_extra2_54]
27809c63: add ecx, [eax+struc_6a4f0.v_extra1_50] ; size of extra field
...
27809c66: mov eax, [ebp+var_4]
27809c69: add eax, size struc_6a4f0 ; move to next source attribute
27809c6c: mov [ebp+var_4], eax
...
27809c6f: mov [ebp+av_attributeType_4], eax ; size of current destination attribute
27809c72: cmp word ptr [eax+struc_6a4f0.v_wDescription?_4], 0 ; check for sentinel string
27809c77: lea ebx, [ebx+ecx+(size struc_6a4f0)] ; move to next destination attribute
27809c7b: jnz short loc_27809C3C
After the document has been modified by the code within proof-of-concept, it may then be opened by the application using a debugger such as WinDbg. In the following steps, the WinDbg debugger is used, and the libraries used by the application are mapped at the following addresses.
Browse full module list
start end module name
00030000 0035d000 taro32 (deferred)
277a0000 27826000 jsvda (deferred)
544d0000 55407000 JSTARO25 (deferred)
213e0000 21402000 jsmisc32 (deferred)
3c7c0000 3eef5000 T32com (deferred)
Once the document has been opened by the application, the address where the handle for a path is allocated can be navigated to. This will call the function at 0x278022ef with the path to the document being opened in order to construct an “object_78250” and write its handle to the last parameter.
0:007> g jsvda+47f06
...
eax=100000a4 ebx=088acaa8 ecx=00000000 edx=4f79f714 esi=15e0de58 edi=08d4d5e8
eip=277e7f06 esp=4f79f9fc ebp=4f79fa38 iopl=0 nv up ei ng nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000282
jsvda!Ordinal253+0x2095:
277e7f06 e8e4a30100 call jsvda!Ordinal20 (278022ef)
0:007> dc @esp L4
4f79f9fc ffffffff 100000a4 163015f8 5012861e ..........0....P
0:007> du poi(@esp+4*2)
163015f8 "C:\path\to\document.jtd"
After stepping over the function, the function will write the handle for the path to its last parameter. In this case, the handle for the path to the document gets written as 0x48.
0:007> dc poi(@esp+4*3) L1
5012861e 302e3331 13.0
0:007> p
...
eax=00000000 ebx=088acaa8 ecx=5012861e edx=e243fa8c esi=15e0de58 edi=08d4d5e8
eip=277e7f0b esp=4f79f9fc ebp=4f79fa38 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
jsvda!Ordinal253+0x209a:
277e7f0b 83c410 add esp,10h
0:007> * output parameter that handle was written to
0:007> dc 5012861e L1
5012861e 00000048 H...
Inside the library is the following code, which is directly responsible for constructing the “object_34605”. This function call takes 7 parameters and writes its constructed object to the 7th parameter (0xd19f60). The third parameter (0x1ea2cf40) contains the “object_78250” that was determined from the path handle (0x48) that was given as a parameter.
0:000> g jsvda+683a9
...
eax=00000005 ebx=00000000 ecx=1ea2cf40 edx=00b35000 esi=00000000 edi=00110005
eip=278083a9 esp=00d19f24 ebp=00d19f64 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
jsvda!Ordinal203+0x127:
278083a9 e857c2fcff call jsvda!Ordinal521+0x42d (277d4605)
0:000> dc @esp L7
00d19f24 00000005 00000000 1ea2cf40 08d4e034 ........@...4...
00d19f34 00000000 00000000 00d19f60 ........`...
Examining the header of the “object_78250” in the third parameter shows that the field at offset +0x10 contains the path handle (0x48).
0:000> !py print(itchi.threadSafeHeader(offset=pykd.expr('poi(@esp+4*2)-0x20').l)
<class itchi.threadSafeHeader> 'unnamed_9b481d8' {unnamed=True}
[1ea2cf20] <instance c(ptype.pointer_t<itchi.threadSafeHeader>) 'p_listEntry_0'> *0x0
[1ea2cf24] <instance itchi.s32 'v_count_4'> +0x00000001 (1)
[1ea2cf28] <instance itchi.u16 'vw_flags_8'> 0x0001 (1)
[1ea2cf2a] <instance itchi.u16 'vw_sizeIndex_a'> 0x0007 (7)
[1ea2cf2c] <instance itchi.u32 'v_tag_c'> 0x48544150 (1213481296)
[1ea2cf30] <instance itchi.u32 'v_bucketIndex_10'> 0x00000048 (72)
[1ea2cf34] <instance itchi.u32 'pf_release_14'> 0x27807f69 (662732649)
[1ea2cf38] <instance itchi.u32 'field_18'> 0x00000000 (0)
[1ea2cf3c] <instance itchi.u32 'v_size_1c'> 0x000000c0 (192)
To create the layout for the document that determines how an arena is populated, the following code is used. This function takes 3 parameters, of which one represents the layout and is used to determine which parser to use when initializing the arena. This layout is initialized by the current function.
0:000> g jsvda+693a6
eax=00d19e98 ebx=50998ba0 ecx=0badad78 edx=00000003 esi=00d19ee4 edi=00000002
eip=278093a6 esp=00d19e80 ebp=00d19eb8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
jsvda!Ordinal929+0xf1a:
278093a6 e834000000 call jsvda!Ordinal929+0xf53 (278093df)
0:000> dc @esp L3
00d19e80 00d19ee4 ffffffff 00d19e98 ............
0:000> ub . L4
jsvda!Ordinal929+0xf15:
278093a1 50 push eax
278093a2 ff751c push dword ptr [ebp+1Ch]
278093a5 56 push esi
278093a6 e834000000 call jsvda!Ordinal929+0xf53 (278093df)
The layout is a 0x20 byte structure that uses the fields at offset +0x0 and +0x4 to choose the parser that initializes an arena.
0:000> * print 0x20 byte layout structure
0:000> dc @ebp-0x20 L8
00d19e98 11211111 00000006 1ea2cf40 00000000 ..!.....@.......
00d19ea8 4e1fea38 ffffffff 0badad78 1ea2cf9c 8..N....x.......
0:000> !py print(itchi.summaryinfolayout(offset=pykd.expr('poi(@esp+4*2)')).l)
<class itchi.summaryinfolayout> 'unnamed_9be6b08' {unnamed=True}
[d19e98] <instance itchi.u32 'v_arenaType_0'> 0x11211111 (287379729)
[d19e9c] <instance itchi.u32 'v_fileInfo_4'> 0x00000006 (6)
[d19ea0] <instance c(ptype.pointer_t<itchi.object_78250>) 'p_object_8'> *0x1ea2cf40
[d19ea4] <instance c(ptype.pointer_t<itchi.object_731a8>) 'p_nullobject_c'> *0x0
[d19ea8] <instance c(ptype.pointer_t<itchi.object_73148>) 'p_object_10'> *0x4e1fea38
[d19eac] <instance itchi.u32 'field_14'> 0xffffffff (4294967295)
[d19eb0] <instance c(ptype.pointer_t<itchi.object_VFK>) 'p_vfk_18'> *0xbadad78
[d19eb4] <instance c(ptype.pointer_t<itchi.object_78250__field_58>) 'p_infoField_1c'> *0x1ea2cf9c
Once inside the function, the layout will be checked by the following call instruction. This function call takes one parameter representing the layout.
0:000> g jsvda+698b3
JSVDA!global_1c5d0::arena::readAttributesFromSummaryInformationStream_698a1{+698b3}
eax=00d19e70 ebx=00d19e98 ecx=00010000 edx=00d19e98 esi=00d19ee4 edi=00000000
eip=278098b3 esp=00d19dac ebp=00d19dd4 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
jsvda!Ordinal929+0x1427:
278098b3 e866010000 call jsvda!Ordinal929+0x1592 (27809a1e)
0:000>
jsvda!Ordinal929+0x1423:
278098af 53 push ebx
278098b0 897df8 mov dword ptr [ebp-8],edi
278098b3 e866010000 call jsvda!Ordinal929+0x1592 (27809a1e)
0:000> * print 0x20 byte layout structure
0:000> dc @ebx L(20/4)
00d19e98 11211111 00000006 1ea2cf40 00000000 ..!.....@.......
00d19ea8 4e1fea38 ffffffff 0badad78 1ea2cf9c 8..N....x.......
0:000> !py print(itchi.summaryinfolayout(offset=pykd.expr('@ebx')).l)
<class itchi.summaryinfolayout> 'unnamed_4e1af28' {unnamed=True}
[d19e98] <instance itchi.u32 'v_arenaType_0'> 0x11211111 (287379729)
[d19e9c] <instance itchi.u32 'v_fileInfo_4'> 0x00000006 (6)
[d19ea0] <instance c(ptype.pointer_t<itchi.object_78250>) 'p_object_8'> *0x1ea2cf40
[d19ea4] <instance c(ptype.pointer_t<itchi.object_731a8>) 'p_nullobject_c'> *0x0
[d19ea8] <instance c(ptype.pointer_t<itchi.object_73148>) 'p_object_10'> *0x4e1fea38
[d19eac] <instance itchi.u32 'field_14'> 0xffffffff (4294967295)
[d19eb0] <instance c(ptype.pointer_t<itchi.object_VFK>) 'p_vfk_18'> *0xbadad78
[d19eb4] <instance c(ptype.pointer_t<itchi.object_78250__field_58>) 'p_infoField_1c'> *0x1ea2cf9c
Inside this function, the first two fields of the layout at offset +0x0 and +0x4 will be used to determine how to read the attributes from the document. These fields are determined earlier when the application parses the “\5SummaryInformation” stream. With the provided proof-of-concept, the field at offset +0x0 is set to 0x11211111 and the field at offset +0x4 is set to the integer 6.
0:000> g jsvda+69a44
JSVDA!summaryinfolayout::determineArenaType_69a1e{+69a44}
eax=00010000 ebx=00d19e98 ecx=00010000 edx=00d19e98 esi=00d19e98 edi=00000000
eip=27809a44 esp=00d19d54 ebp=00d19da4 iopl=0 nv up ei ng nz na pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200287
jsvda!Ordinal929+0x15b8:
27809a44 0f849c000000 je jsvda!Ordinal929+0x165a (27809ae6) [br=0]
0:000> ub . L5
jsvda!Ordinal929+0x15a9:
27809a35 8b7508 mov esi,dword ptr [ebp+8]
27809a38 8b06 mov eax,dword ptr [esi]
27809a3a 2500000f00 and eax,offset taro32+0xc0000 (000f0000)
27809a3f 3d00000200 cmp eax,20000h
27809a44 0f849c000000 je jsvda!Ordinal929+0x165a (27809ae6)
0:000> * Print out two relevant fields from layout
0:000> !py print(itchi.summaryinfolayout(offset=pykd.expr('@esi')).l['v_arenaType_0'])
[d19e98] <instance itchi.u32 'v_arenaType_0'> 0x11211111 (287379729)
0:000> !py print(itchi.summaryinfolayout(offset=pykd.expr('@esi')).l['v_fileInfo_4'])
[d19e9c] <instance itchi.u32 'v_fileInfo_4'> 0x00000006 (6)
When the second field is set to 6, the following instruction will be called. The function this instruction dispatches to is responsible for interacting with the “\4JSRV_SummaryInformation” stream. When its parameters are set to NULL, this function will simply return the size of the content that is stored within the stream. At the current time of execution, the third parameter contains a pointer to the layout with the fifth set to -1. As a result, this function call will simply read from the header of the stream to calculate a size that will be returned.
0:000> g jsvda+69a68
JSVDA!summaryinfolayout::determineArenaType_69a1e{+69a68}
eax=00000006 ebx=11120111 ecx=00010000 edx=00d19e98 esi=00d19e98 edi=00000000
eip=27809a68 esp=00d19d3c ebp=00d19da4 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
jsvda!Ordinal929+0x15dc:
27809a68 e86980feff call jsvda!Ordinal968+0x804 (277f1ad6)
0:000> ub . L7
jsvda!Ordinal929+0x15d5:
27809a61 57 push edi
27809a62 6aff push 0FFFFFFFFh
27809a64 57 push edi
27809a65 56 push esi
27809a66 57 push edi
27809a67 57 push edi
27809a68 e86980feff call jsvda!Ordinal968+0x804 (277f1ad6)
0:000> dc @esp L7
00d19d3c 00000000 00000000 00d19e98 00000000 ................
00d19d4c ffffffff 00000000 00000000 ............
0:000> * Print out address of third parameter containing the layout structure
0:000> ? poi(@esp+4*2)
Evaluate expression: 13737624 = 00d19e98
After the function has opened up the stream, the following instruction will be called. This instruction will read 0x40 bytes from the beginning of the “\4JSRV_SummaryInformation” stream into a buffer that is located on the stack. The second parameter of this function is the object containing the functionality used to read from the stream.
0:000> g jsvda+51b14
JSVDA!summaryinfolayout::ReadStream(JSRV_SummaryInformation)_51ad6{+51b14}
eax=00d19cf0 ebx=00000000 ecx=088acdc8 edx=00b35000 esi=00000000 edi=00000000
eip=277f1b14 esp=00d19cd8 ebp=00d19d34 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
jsvda!Ordinal968+0x842:
277f1b14 e85b050000 call jsvda!Ordinal968+0xda2 (277f2074)
0:000> ub . L5
jsvda!Ordinal968+0x838:
277f1b0a 8d45bc lea eax,[ebp-44h]
277f1b0d 50 push eax
277f1b0e ff75fc push dword ptr [ebp-4]
277f1b11 ff7510 push dword ptr [ebp+10h]
277f1b14 e85b050000 call jsvda!Ordinal968+0xda2 (277f2074)
0:000> dc @esp L3
00d19cd8 00d19e98 0079d160 00d19cf0 ....`.y.....
0:000> * Dump destination buffer that header will be written to
0:000> dc poi(@esp+4*2) L(40/4)
00d19cf0 00000030 00000010 00d19d1c 213f5dd6 0............]?!
00d19d00 0000000e 00000000 00000010 00d19d58 ............X...
00d19d10 00d19ee4 00000030 213fc5b0 00000030 ....0.....?!0...
00d19d20 213f5f0a 27831288 00d19d3c 213f5f16 ._?!...'<...._?!
0:000> * Print out object used to interact with the opened stream
0:000> !py print(itchi.object_731a8(offset=pykd.expr('poi(@esp+4*1)')).l)
<class itchi.object_731a8> 'unnamed_4e27ad8' {unnamed=True}
[79d160] <instance itchi.u32 'p_vtable_0'> 0x278131a8 (662778280)
[79d164] <instance itchi.object_SEG__Data 'v_data_4'> "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x12\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x1d\xc5\x09"
0:000> * Print object that contains the COM API for reading from a Structured Storage document
0:000> !py print(itchi.object_731a8(offset=pykd.expr('poi(@esp+4*1)')).l['v_data_4']['p_fileObject_18']['object_731a8'].d.load(source=pint.uint32_t().source))
<class itchi.object_731a8__field_1c> '*object_731a8'
[9c51d20] <instance itchi.u32 'field_0'> 0x00000000 (0)
[9c51d24] <instance c(ptype.pointer_t<itchi.IStream>) 'p_cExposedStreamInterfaces_4'> *0x17044fb0
[9c51d28] <instance c(ptype.pointer_t<itchi.jsmisc32__object_586>) 'p_hashsigObject_8'> *0x0
[9c51d2c] <instance itchi.u64 'vq_cbSize_c'> 0x0000000000000000 (0)
[9c51d34] <instance c(ptype.pointer_t<__p_cExposedDocFile_14>) 'p_cExposedDocFile_14'> *0x163015f8
[9c51d38] <instance itchi.u32 'v_noHashSig_18'> 0x00000000 (0)
0:000> * Output the interface table for the Structured Storage API
0:000> dds 0x17044fb0 L5
17044fb0 766d1240 coml2!CExposedStream::`vftable'
17044fb4 766d1278 coml2!CExposedStream::`vftable'
17044fb8 766d129c coml2!CExposedStream::`vftable'
17044fbc 766d12dc coml2!CExposedStream::`vftable'
17044fc0 766d1650 coml2!CAsyncConnection::`vftable'
After stepping over the call instruction, we can print the contents of the buffer that was passed as a parameter. This data correlates directly with the first 0x40 header of the “\4JSRV_SummaryInformation” stream from the file.
0:000> p
JSVDA!summaryinfolayout::ReadStream(JSRV_SummaryInformation)_51ad6{+51b19}
eax=00000000 ebx=00000000 ecx=00009030 edx=17044fb0 esi=00000000 edi=00000000
eip=277f1b19 esp=00d19cd8 ebp=00d19d34 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
jsvda!Ordinal968+0x847:
277f1b19 8bf0 mov esi,eax
0:000> dc @ebp-44 L(40/4)
00d19cf0 00009030 00000040 00000000 00000000 0...@...........
00d19d00 0d0e0a0d 00000001 00000000 00000000 ................
00d19d10 00000924 00000000 00000000 00000000 $...............
00d19d20 00000000 00000000 00000000 00000000 ................
0:000> * Print out the header from the file
0:000> !py print(jtd.JSRV_SummaryInformation(offset=pykd.expr('(@ebp-0x44)')).l)
<class jtd.JSRV_SummaryInformation> 'unnamed_4ed3ce8' {unnamed=True}
[d19cf0] <instance jtd.u32 'sig'> 0x00009030 (36912)
[d19cf4] <instance jtd.sub_277F2074_9030 'header'> "\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x0a\x0e\x0d\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x24\x09\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"
0:000> !py print(jtd.JSRV_SummaryInformation(offset=pykd.expr('(@ebp-0x44)')).l['header'])
<class jtd.sub_277F2074_9030> 'header'
[d19cf4] <instance jtd.u32 '_1'> 0x00000040 (64)
[d19cf8] <instance jtd.u32 '2'> 0x00000000 (0)
[d19cfc] <instance jtd.u32 '3'> 0x00000000 (0)
[d19d00] <instance jtd.u32 '4'> 0x0d0e0a0d (219023885)
[d19d04] <instance jtd.u32 'v8_5'> 0x00000001 (1)
[d19d08] <instance jtd.u32 '6'> 0x00000000 (0)
[d19d0c] <instance jtd.u32 '7'> 0x00000000 (0)
[d19d10] <instance jtd.u32 'v11_8'> 0x00000924 (2340)
[d19d14] <instance jtd.u32 'v9_9'> 0x00000000 (0)
[d19d18] <instance jtd.u32 'a'> 0x00000000 (0)
[d19d1c] <instance jtd.u32 'b'> 0x00000000 (0)
[d19d20] <instance jtd.u32 'c'> 0x00000000 (0)
[d19d24] <instance jtd.u32 'd'> 0x00000000 (0)
[d19d28] <instance jtd.u32 'e'> 0x00000000 (0)
[d19d2c] <instance jtd.u32 'f'> 0x00000000 (0)
If we continue execution to the next block of instructions, the function will read from the field at offset +0x14 of the stream header and store this in the %edi
register. The value of this field from the header in the proof-of-concept sets this value to 1.
0:000> g jsvda+51b2e
JSVDA!summaryinfolayout::ReadStream(JSRV_SummaryInformation)_51ad6{+51b2e}
eax=00000000 ebx=00000000 ecx=00009030 edx=17044fb0 esi=00000000 edi=00000001
eip=277f1b2e esp=00d19ce4 ebp=00d19d34 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
jsvda!Ordinal968+0x85c:
277f1b2e 7402 je jsvda!Ordinal968+0x860 (277f1b32) [br=1]
0:000> ub . L4
jsvda!Ordinal968+0x854:
277f1b26 8b4508 mov eax,dword ptr [ebp+8]
277f1b29 8b7dd0 mov edi,dword ptr [ebp-30h]
277f1b2c 3bc3 cmp eax,ebx
277f1b2e 7402 je jsvda!Ordinal968+0x860 (277f1b32)
0:000> * Display the value of the field at +0x14
0:000> r @edi
edi=00000001
0:000> !py print(jtd.JSRV_SummaryInformation(offset=pykd.expr('(@ebp-0x44)')).l.at(pykd.expr('@ebp-0x30')))
[d19d04] <instance jtd.u32 'v8_5'> 0x00000001 (1)
Continuing to next conditional branch will result in reading the next field from stream header. This field is at offset +0x24 of the stream and will be stored in the %ecx
register.
0:000> ph
JSVDA!summaryinfolayout::ReadStream(JSRV_SummaryInformation)_51ad6{+51b3a}
eax=00000000 ebx=00000000 ecx=00000000 edx=17044fb0 esi=00000000 edi=00000001
eip=277f1b3a esp=00d19ce4 ebp=00d19d34 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
jsvda!Ordinal968+0x868:
277f1b3a 7402 je jsvda!Ordinal968+0x86c (277f1b3e) [br=1]
0:000> ub . L5
jsvda!Ordinal968+0x85e:
277f1b30 8938 mov dword ptr [eax],edi
277f1b32 8b450c mov eax,dword ptr [ebp+0Ch]
277f1b35 8b4de0 mov ecx,dword ptr [ebp-20h]
277f1b38 3bc3 cmp eax,ebx
277f1b3a 7402 je jsvda!Ordinal968+0x86c (277f1b3e)
0:000> * Display the value of the field at +0x24
0:000> r @ecx
ecx=00000000
0:000> !py print(jtd.JSRV_SummaryInformation(offset=pykd.expr('(@ebp-0x44)')).l.at(pykd.expr('@ebp-0x20')))
[d19d14] <instance jtd.u32 'v9_9'> 0x00000000 (0)
After the two fields have been read, the function will encounter the following instruction. This instruction takes the sum of both of the fields that were read from the header and returns it back to the caller. As the proof-of-concept associated with this document sets the field at offset +0x14 to 0x0 and the field at offset +0x24 to 0x1, the total length of 0x1 will be returned to the caller.
0:000> g jsvda+51bbe
JSVDA!summaryinfolayout::ReadStream(JSRV_SummaryInformation)_51ad6{+51bbe}
eax=ffffffff ebx=00000000 ecx=00000000 edx=17044fb0 esi=00000000 edi=00000001
eip=277f1bbe esp=00d19ce4 ebp=00d19d34 iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200286
jsvda!Ordinal968+0x8ec:
277f1bbe 8d340f lea esi,[edi+ecx]
0:000> p
JSVDA!summaryinfolayout::ReadStream(JSRV_SummaryInformation)_51ad6{+51bc1}
eax=ffffffff ebx=00000000 ecx=00000000 edx=17044fb0 esi=00000001 edi=00000001
eip=277f1bc1 esp=00d19ce4 ebp=00d19d34 iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200286
jsvda!Ordinal968+0x8ef:
277f1bc1 eb07 jmp jsvda!Ordinal968+0x8f8 (277f1bca)
0:000> r @edi,@ecx,@esi
edi=00000001 ecx=00000000 esi=00000001
After the sum of the two fields from the header are returned, the calling function will add 0x80 bytes to the result as a buffer. This is done by the following instruction. As our result is 0x1, this will adjust the final allocation size by 0x81.
0:000> g jsvda+69a74
JSVDA!summaryinfolayout::determineArenaType_69a1e{+69a74}
eax=00000001 ebx=11120111 ecx=0079d160 edx=00b35000 esi=00d19e98 edi=00000000
eip=27809a74 esp=00d19d54 ebp=00d19da4 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202
jsvda!Ordinal929+0x15e8:
27809a74 0580000000 add eax,80h
0:000> r @eax
eax=00000001
0:000> p
eax=00000081 ebx=11120111 ecx=0079d160 edx=00b35000 esi=00d19e98 edi=00000000
eip=27809a79 esp=00d19d54 ebp=00d19da4 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
jsvda!Ordinal929+0x15ed:
27809a79 50 push eax
0:000> r @eax
eax=00000081
Finally, the final calculation for the size of the heap buffer is done by the following function call. This function takes the adjusted size from the stream header and an integer representing which global to add to the adjusted size.
0:000> g jsvda+69ac4
JSVDA!summaryinfolayout::determineArenaType_69a1e{+69ac4}
eax=00000081 ebx=11120111 ecx=0079d160 edx=00b35000 esi=00d19e98 edi=00000000
eip=27809ac4 esp=00d19d4c ebp=00d19da4 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
jsvda!Ordinal929+0x1638:
27809ac4 e82f000000 call jsvda!Ordinal929+0x166c (27809af8)
0:000> dc @esp L2
00d19d4c 00000001 00000081 ........
0:000> t
JSVDA!global_796f8::getPathAttributesOrEndOfAttributes_69af8{+69af8}
eax=00000081 ebx=11120111 ecx=0079d160 edx=00b35000 esi=00d19e98 edi=00000000
eip=27809af8 esp=00d19d48 ebp=00d19da4 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
jsvda!Ordinal929+0x166c:
27809af8 837c240400 cmp dword ptr [esp+4],0 ss:0023:00d19d4c=00000001
0:000> uf .
jsvda!Ordinal929+0x166c:
27809af8 837c240400 cmp dword ptr [esp+4],0
27809afd a1f8968127 mov eax,dword ptr [jsvda!IID_IVdaVwCallBack3+0x7f78 (278196f8)]
27809b02 7505 jne jsvda!Ordinal929+0x167d (27809b09) Branch
jsvda!Ordinal929+0x1678:
27809b04 05000a0000 add eax,0A00h
jsvda!Ordinal929+0x167d:
27809b09 837c240800 cmp dword ptr [esp+8],0
27809b0e 7e04 jle jsvda!Ordinal929+0x1688 (27809b14) Branch
jsvda!Ordinal929+0x1684:
27809b10 03442408 add eax,dword ptr [esp+8]
jsvda!Ordinal929+0x1688:
27809b14 c3 ret Branch
If we trace through this function, we will see the global at offset +0x796f8 of the JSVDA.dll library being added to the sum that was passed as the second parameter. If using the proof-of-concept provided with this advisory, this will result in the value 0x81 from the adjusted header size being added to 0x188 from the global. After they’ve been added together, the value 0x209 will be returned from this function and used to allocate space on the application’s heap.
0:000> p
eax=00000081 ebx=11120111 ecx=0079d160 edx=00b35000 esi=00d19e98 edi=00000000
eip=27809afd esp=00d19d48 ebp=00d19da4 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202
jsvda!Ordinal929+0x1671:
27809afd a1f8968127 mov eax,dword ptr [jsvda!IID_IVdaVwCallBack3+0x7f78 (278196f8)] ds:0023:278196f8=00000188
0:000> * Display the value of the global that gets added to our size
0:000> dc 278196f8 L2
278196f8 00000188 00000832 ....2...
0:000> p
eax=00000188 ebx=11120111 ecx=0079d160 edx=00b35000 esi=00d19e98 edi=00000000
eip=27809b02 esp=00d19d48 ebp=00d19da4 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202
jsvda!Ordinal929+0x1676:
27809b02 7505 jne jsvda!Ordinal929+0x167d (27809b09) [br=1]
0:000> r @eax
eax=00000188
0:000> ? @eax+81
Evaluate expression: 521 = 00000209
0:000> * Skip ahead to where our second parameter gets added to the global
0:000> g jsvda+69b10
eax=00000188 ebx=11120111 ecx=0079d160 edx=00b35000 esi=00d19e98 edi=00000000
eip=27809b10 esp=00d19d48 ebp=00d19da4 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
jsvda!Ordinal929+0x1684:
27809b10 03442408 add eax,dword ptr [esp+8] ss:0023:00d19d50=00000081
0:000> p
eax=00000209 ebx=11120111 ecx=0079d160 edx=00b35000 esi=00d19e98 edi=00000000
eip=27809b14 esp=00d19d48 ebp=00d19da4 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
jsvda!Ordinal929+0x1688:
27809b14 c3 ret
0:000> r @eax
eax=00000209
The following instruction is responsible for using the sum that was calculated to allocate space on the heap. This call instruction takes two parameters. The first parameter being the size, and the second parameter where the resulting pointer should be written. After stepping over the instruction, a pointer to the heap buffer will be written to the parameter. As the size with the provided proof-of-concept will be 0x209, this will result in the allocated size being 0x229, which includes the size of the allocation’s header (0x20). Afterwards, this buffer will be used to consolidate each of the statically defined attributes with the attributes that were read from the stream.
0:000> g jsvda+698cb
JSVDA!global_1c5d0::arena::readAttributesFromSummaryInformationStream_698a1{+698cb}
eax=00d19dcc ebx=00d19e98 ecx=00d19e98 edx=00b35000 esi=00000209 edi=00000000
eip=278098cb esp=00d19da8 ebp=00d19dd4 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
jsvda!Ordinal929+0x143f:
278098cb e806f3faff call jsvda!Ordinal969+0x837 (277b8bd6)
0:000> ub . L5
jsvda!Ordinal929+0x1437:
278098c3 8d45f8 lea eax,[ebp-8]
278098c6 8975f4 mov dword ptr [ebp-0Ch],esi
278098c9 50 push eax
278098ca 56 push esi
278098cb e806f3faff call jsvda!Ordinal969+0x837 (277b8bd6)
0:000> dc @esp L2
00d19da8 00000209 00d19dcc ........
0:000> * Store the address that the heap pointer gets written to
0:000> r @$t1=poi(@esp+4*1)
0:000> * Step over the allocation
0:000> p
JSVDA!global_1c5d0::arena::readAttributesFromSummaryInformationStream_698a1{+698d0}
eax=00000000 ebx=00d19e98 ecx=00000000 edx=00000000 esi=00000209 edi=00000000
eip=278098d0 esp=00d19da8 ebp=00d19dd4 iopl=0 nv up ei pl zr ac pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200257
jsvda!Ordinal929+0x1444:
278098d0 8bf0 mov esi,eax
0:000> * Display the address of the result
0:000> ? @$t1
Evaluate expression: 13737420 = 00d19dcc
0:000> * Print out the contents of the address
0:000> dc @$t1 L1
00d19dcc 1b9f8df0 ....
0:000> * Print the header of the allocation
0:000> !py print(itchi.threadSafeHeader(offset=pykd.expr('poi(@$t1)-0x20')).l)
<class itchi.threadSafeHeader> 'unnamed_4edfc28' {unnamed=True}
[1b9f8dd0] <instance c(ptype.pointer_t<itchi.threadSafeHeader>) 'p_listEntry_0'> *0x0
[1b9f8dd4] <instance itchi.s32 'v_count_4'> +0x00000001 (1)
[1b9f8dd8] <instance itchi.u16 'vw_flags_8'> 0xc000 (49152)
[1b9f8dda] <instance itchi.u16 'vw_sizeIndex_a'> 0x0007 (7)
[1b9f8ddc] <instance itchi.u32 'v_tag_c'> 0x004e4942 (5130562)
[1b9f8de0] <instance itchi.u32 'v_bucketIndex_10'> 0xffffffff (4294967295)
[1b9f8de4] <instance itchi.u32 'pf_release_14'> 0x277b77eb (662403051)
[1b9f8de8] <instance itchi.u32 'field_18'> 0x00000000 (0)
[1b9f8dec] <instance itchi.u32 'v_size_1c'> 0x00000209 (521)
0:000> * The total size of the allocation is 0x229 (including the header)
0:000> !heap -p -a 1b9f8df0
address 1b9f8df0 found in
_DPH_HEAP_ROOT @ 1d61000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
170935e4: 1b9f8dd0 229 - 1b9f8000 2000
...
0:000> dc 1b9f8df0 L(209/4 + 1)
1b9f8df0 00000000 00000000 00000000 00000000 ................
...
1b9f8ff0 00000000 00000000 d0d0d000 ............
The first time the heap allocation is written to is by the function called by the following instruction. This function call takes two parameters, of which the second is the class of attributes that will be written to its first parameter, which is the buffer that was allocated. The size of these attributes come from the global that was previously added to the sum of the two fields from the stream header. If we step over this instruction, the function will return the total number of bytes that were successfully written for the required attributes.
0:000> g jsvda+69b39
JSVDA!summaryinfolayout::ReadSummaryAttributes_69b15{+69b39}
eax=00000000 ebx=00d19dc4 ecx=00d19dd0 edx=00000000 esi=00d19dc0 edi=00d19dbc
eip=27809b39 esp=00d19d34 ebp=00d19d8c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
jsvda!Ordinal929+0x16ad:
27809b39 e8bf000000 call jsvda!Ordinal929+0x1771 (27809bfd)
0:000> ub . L8
jsvda!Ordinal929+0x169e:
27809b2a 33c0 xor eax,eax
27809b2c 6a01 push 1
27809b2e 8901 mov dword ptr [ecx],eax
27809b30 ff7508 push dword ptr [ebp+8]
27809b33 8903 mov dword ptr [ebx],eax
27809b35 8906 mov dword ptr [esi],eax
27809b37 8907 mov dword ptr [edi],eax
27809b39 e8bf000000 call jsvda!Ordinal929+0x1771 (27809bfd)
0:000> dc @esp L2
00d19d34 1b9f8df0 00000001 ........
0:000> p
eax=00000188 ebx=00d19dc4 ecx=00000002 edx=00000000 esi=00d19dc0 edi=00d19dbc
eip=27809b3e esp=00d19d34 ebp=00d19d8c iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
jsvda!Ordinal929+0x16b2:
27809b3e 59 pop ecx
0:000> * Display the returned size
0:000> r @eax
eax=00000188
0:000> * Display the beginning of the allocated heap buffer
0:000> dc poi(@$t1) L(0x209/4 + 1)
1b9f8df0 31000013 30a130d5 30eb30a4 0000540d ...1.0.0.0.0.T..
1b9f8e00 00000000 00000000 00000000 00000000 ................
1b9f8e10 00000000 00000000 00000000 00000000 ................
...
1b9f8fd0 00000000 00000000 00000000 00000000 ................
1b9f8fe0 00000000 00000000 00000000 00000000 ................
1b9f8ff0 00000000 00000000 d0d0d000 ............
After the size has been returned, it will then be used to calculate the next position attributes should be written to. The first instruction will store the size that was successfully copied. Afterwards, the total size will be read and then the difference will be stored in the %edi
register. This value will then be added to the address that was allocated, resulting in the data from the stream being written at offset +0x188 of the buffer. With the provided proof-of-concept, this leaves up to 0x81 bytes for any other attributes to be written into the buffer.
0:000> g jsvda+69b4b
JSVDA!summaryinfolayout::ReadSummaryAttributes_69b15{+69b4b}
eax=00000188 ebx=00d19dc4 ecx=1b9f8df0 edx=00000000 esi=00d19dc0 edi=00d19dbc
eip=27809b4b esp=00d19d3c ebp=00d19d8c iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
jsvda!Ordinal929+0x16bf:
27809b4b 8907 mov dword ptr [edi],eax ds:0023:00d19dbc=00000000
0:000> p
eax=00000188 ebx=00d19dc4 ecx=1b9f8df0 edx=00000000 esi=00d19dc0 edi=00d19dbc
eip=27809b4d esp=00d19d3c ebp=00d19d8c iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
jsvda!Ordinal929+0x16c1:
27809b4d 8b7d1c mov edi,dword ptr [ebp+1Ch] ss:0023:00d19da8=00000209
0:000> p
eax=00000188 ebx=00d19dc4 ecx=1b9f8df0 edx=00000000 esi=00d19dc0 edi=00000209
eip=27809b50 esp=00d19d3c ebp=00d19d8c iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
jsvda!Ordinal929+0x16c4:
27809b50 2bf8 sub edi,eax
0:000> p
eax=00000188 ebx=00d19dc4 ecx=1b9f8df0 edx=00000000 esi=00d19dc0 edi=00000081
eip=27809b52 esp=00d19d3c ebp=00d19d8c iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
jsvda!Ordinal929+0x16c6:
27809b52 03c1 add eax,ecx
0:000> r @edi
edi=00000081
0:000> p
eax=1b9f8f78 ebx=00d19dc4 ecx=1b9f8df0 edx=00000000 esi=00d19dc0 edi=00000081
eip=27809b54 esp=00d19d3c ebp=00d19d8c iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
jsvda!Ordinal929+0x16c8:
27809b54 8b4d0c mov ecx,dword ptr [ebp+0Ch] ss:0023:00d19d98=00d19e98
0:000> * Compare the offset into the heap buffer matches the value in %eax
0:000> ? poi(@$t1)+188 == @eax
Evaluate expression: 1 = 00000001
The next call instruction is the same as the one that was called before to calculate the size from the 0x40 byte header belonging to the “\4JSRV_SummaryInformation”. This time, however, most of the parameters are initialized with the size being passed as the fifth parameter and a pointer to the current position at offset +0x188 of the heap buffer as the sixth parameter.
0:000> g jsvda+69b83
JSVDA!summaryinfolayout::ReadSummaryAttributes_69b15{+69b83}
eax=1b9f8f78 ebx=00d19dc4 ecx=00d19e98 edx=00000006 esi=00d19dc0 edi=00000081
eip=27809b83 esp=00d19d24 ebp=00d19d8c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
jsvda!Ordinal929+0x16f7:
27809b83 e84e7ffeff call jsvda!Ordinal968+0x804 (277f1ad6)
0:000> ub . L7
jsvda!Ordinal929+0x16f0:
27809b7c 50 push eax
27809b7d 57 push edi
27809b7e 6a00 push 0
27809b80 51 push ecx
27809b81 53 push ebx
27809b82 56 push esi
27809b83 e84e7ffeff call jsvda!Ordinal968+0x804 (277f1ad6)
0:000> dc @esp L6
00d19d24 00d19dc0 00d19dc4 00d19e98 00000000 ................
00d19d34 00000081 1b9f8f78 ....x...
0:000> ddp @esp L5
00d19d24 00d19dc0 00000000
00d19d28 00d19dc4 00000000
00d19d2c 00d19e98 11211111
00d19d30 00000000
00d19d34 00000081
00d19d38 1b9f8f78
Similar to last time, this function will read 0x40 bytes from the header and use them to calculate the number of bytes to be read. This time, however, a different path will be taken, which will read data from the stream into the buffer that was passed as the function’s sixth parameter.
0:000> g jsvda+51b14
JSVDA!summaryinfolayout::ReadStream(JSRV_SummaryInformation)_51ad6{+51b14}
eax=00d19cd8 ebx=00000000 ecx=088acdc8 edx=00b35000 esi=00000000 edi=00000081
eip=277f1b14 esp=00d19cc0 ebp=00d19d1c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
jsvda!Ordinal968+0x842:
277f1b14 e85b050000 call jsvda!Ordinal968+0xda2 (277f2074)
0:000> ub . L5
jsvda!Ordinal968+0x838:
277f1b0a 8d45bc lea eax,[ebp-44h]
277f1b0d 50 push eax
277f1b0e ff75fc push dword ptr [ebp-4]
277f1b11 ff7510 push dword ptr [ebp+10h]
277f1b14 e85b050000 call jsvda!Ordinal968+0xda2 (277f2074)
0:000> p
JSVDA!summaryinfolayout::ReadStream(JSRV_SummaryInformation)_51ad6{+51b19}
eax=00000000 ebx=00000000 ecx=00009030 edx=161fefb0 esi=00000000 edi=00000081
eip=277f1b19 esp=00d19cc0 ebp=00d19d1c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
jsvda!Ordinal968+0x847:
277f1b19 8bf0 mov esi,eax
0:000> * Display the header read from the stream
0:000> dc @ebp-44
00d19cd8 00009030 00000040 00000000 00000000 0...@...........
00d19ce8 0d0e0a0d 00000001 00000000 00000000 ................
00d19cf8 00000924 00000000 00000000 00000000 $...............
00d19d08 00000000 00000000 00000000 00000000 ................
After the contents of the stream are read into the buffer and the function has returned, the following code will be executed in order to append the rest of the attributes to the allocated heap buffer. The call instruction at address 0x27809be1 takes two parameters. The first parameter is the position into the heap buffer to write attributes, and the second parameter is the class of attributes to be copied (0x2). As the application adds 0x80 to the size, this leaves 0x81 bytes of space to copy any of the required statically declared attributes.
0:000> g jsvda+69ba2
JSVDA!summaryinfolayout::ReadSummaryAttributes_69b15{+69ba2}
eax=00000000 ebx=00d19dc4 ecx=0079d160 edx=00b35000 esi=00d19dc0 edi=00000081
eip=27809ba2 esp=00d19d3c ebp=00d19d8c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
jsvda!Ordinal929+0x1716:
27809ba2 6a02 push 2
0:000> p
eax=00000000 ebx=00d19dc4 ecx=0079d160 edx=00b35000 esi=00d19dc0 edi=00000081
eip=27809ba4 esp=00d19d38 ebp=00d19d8c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
jsvda!Ordinal929+0x1718:
27809ba4 ff7508 push dword ptr [ebp+8] ss:0023:00d19d94=1b9f8f78
0:000> p
eax=00000000 ebx=00d19dc4 ecx=0079d160 edx=00b35000 esi=00d19dc0 edi=00000081
eip=27809ba7 esp=00d19d34 ebp=00d19d8c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
jsvda!Ordinal929+0x171b:
27809ba7 eb38 jmp jsvda!Ordinal929+0x1755 (27809be1)
0:000> p
JSVDA!summaryinfolayout::ReadSummaryAttributes_69b15{+69be1}
eax=00000000 ebx=00d19dc4 ecx=0079d160 edx=00b35000 esi=00d19dc0 edi=00000081
eip=27809be1 esp=00d19d34 ebp=00d19d8c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
jsvda!Ordinal929+0x1755:
27809be1 e817000000 call jsvda!Ordinal929+0x1771 (27809bfd)
0:000> * Display the first two parameters
0:000> dc @esp L2
00d19d34 1b9f8f78 00000002 x.......
0:000> ? poi(@$t1)
Evaluate expression: 463441392 = 1b9f8df0
0:000> * The attributes will be written to offset +0x188 of the heap buffer
0:000> ? poi(@esp+4*0)-poi(@$t1)
Evaluate expression: 392 = 00000188
0:000> dc poi(@esp+4*0) L(0x8c/4)
1b9f8f78 00000000 00000000 00000000 00000000 ................
1b9f8f88 00000000 00000000 00000000 00000000 ................
1b9f8f98 00000000 00000000 00000000 00000000 ................
1b9f8fa8 00000000 00000000 00000000 00000000 ................
1b9f8fb8 00000000 00000000 00000000 00000000 ................
1b9f8fc8 00000000 00000000 00000000 00000000 ................
1b9f8fd8 00000000 00000000 00000000 00000000 ................
1b9f8fe8 00000000 00000000 00000000 00000000 ................
1b9f8ff8 d0d0d000 d0d0d0d0 ???????? ........????
When copying attributes into the heap buffer, a loop will be used. This loop terminates by checking the first character of the attribute’s description. Each of these attributes are 0x60 bytes in size with the value in the field being used to determine any extra data that should follow the attribute. As the amount of space in the heap buffer is 0x81, as a result of the specific fields that were set in the proof-of-concept, this will result in this loop iterating twice before running out of space.
0:000> g jsvda+179e
JSVDA!struc_6a4f0::copyAllAttributesByType?_69bfd{+0x69c2a}
eax=278134e0 ebx=00000000 ecx=00000000 edx=00b35000 esi=00d19dc0 edi=00000081
eip=27809c2a esp=00d19d1c ebp=00d19d2c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
jsvda!Ordinal929+0x179e:
27809c2a 6683780400 cmp word ptr [eax+4],0 ds:0023:278134e4=30b3
0:000> * Print out the first attribute
0:000> !py print(itchi.struc_6a4f0(offset=pykd.expr('@esi')).l)
<class itchi.struc_6a4f0> 'unnamed_4ef6988' {unnamed=True}
[278134e0] <instance itchi.u32 'v_identifier_0'> 0x31000001 (822083585)
[278134e4] <instance c(pstr.wstring<wchar_t<utf-16-le>>) 'v_wDescription?_4'> (16) '\u30b3\u30fc\u30c9\u30da\u30fc\u30b8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
[27813504] <instance itchi.u32 'field_24'> 0x00000000 (0)
[27813508] <instance itchi.u32 'field_28'> 0x00000000 (0)
[2781350c] <instance itchi.u32 'field_2c'> 0x00000000 (0)
[27813510] <instance itchi.u32 'field_30'> 0x00000000 (0)
[27813514] <instance itchi.u32 'field_34'> 0x00000000 (0)
[27813518] <instance itchi.u32 'field_38'> 0x00000000 (0)
[2781351c] <instance itchi.u32 'field_3c'> 0x00000000 (0)
[27813520] <instance itchi.u32 'field_40'> 0x00000000 (0)
[27813524] <instance itchi.u32 'v_important_44'> 0x01000000 (16777216)
[27813528] <instance itchi.u32 'v_someType?_48'> 0x00000002 (2)
[2781352c] <instance itchi.u32 'v_someStringIndex_4c'> 0x00000001 (1)
[27813530] <instance itchi.u32 'v_extra1_50'> 0x00000002 (2)
[27813534] <instance itchi.u32 'v_extra2_54'> 0x00000000 (0)
[27813538] <instance itchi.u32 'field_58'> 0x00000000 (0)
[2781353c] <instance itchi.u32 'field_5c'> 0x00000000 (0)
[27813540] <instance dynamic.block(2) 'extra(50)'> "\x02\x00"
[27813542] <instance dynamic.block(0) 'extra(54)'> ""
The following instruction is used to copy the current attribute into the heap buffer for each iteration of the loop. After the first attribute has been copied with its extra data initialized, the next iteration will write outside the bounds of the heap buffer. This is a buffer overflow which can corrupt the heap and can be leveraged to earn code execution within the context of the application.
0:000> g jsvda+69c46
JSVDA!struc_6a4f0::copyAllAttributesByType?_69bfd{+0x69c46}
eax=278134e0 ebx=1b9f8f78 ecx=00000018 edx=00b35000 esi=278134e0 edi=1b9f8f78
eip=27809c46 esp=00d19d1c ebp=00d19d2c iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202
jsvda!Ordinal929+0x17ba:
27809c46 f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
0:000> ? 4*@ecx
Evaluate expression: 96 = 00000060
0:000> * Next iteration
0:000> g @eip
eax=27813540 ebx=1b9f8fda ecx=00000018 edx=00000000 esi=27813540 edi=1b9f8fda
eip=27809c46 esp=00d19d1c ebp=00d19d2c iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200286
jsvda!Ordinal929+0x17ba:
27809c46 f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
0:000> * Show the amount of buffer space that is left
0:000> dc @edi L@ecx
1b9f8fda 00000000 00000000 00000000 00000000 ................
1b9f8fea 00000000 00000000 00000000 d0000000 ................
1b9f8ffa d0d0d0d0 ???????? ???????? ???????? ....????????????
1b9f900a ???????? ???????? ???????? ???????? ????????????????
1b9f901a ???????? ???????? ???????? ???????? ????????????????
1b9f902a ???????? ???????? ???????? ???????? ????????????????
0:000> * Print out the current attribute being written
0:000> !py print(itchi.struc_6a4f0(offset=pykd.expr('@esi')).l)
<class itchi.struc_6a4f0> 'unnamed_4ef6688' {unnamed=True}
[27813540] <instance itchi.u32 'v_identifier_0'> 0x31000002 (822083586)
[27813544] <instance c(pstr.wstring<wchar_t<utf-16-le>>) 'v_wDescription?_4'> (16) '\u898b\u51fa\u3057\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
[27813564] <instance itchi.u32 'field_24'> 0x00000000 (0)
[27813568] <instance itchi.u32 'field_28'> 0x00000000 (0)
[2781356c] <instance itchi.u32 'field_2c'> 0x00000000 (0)
[27813570] <instance itchi.u32 'field_30'> 0x00000000 (0)
[27813574] <instance itchi.u32 'field_34'> 0x00000000 (0)
[27813578] <instance itchi.u32 'field_38'> 0x00000000 (0)
[2781357c] <instance itchi.u32 'field_3c'> 0x00000000 (0)
[27813580] <instance itchi.u32 'field_40'> 0x00000000 (0)
[27813584] <instance itchi.u32 'v_important_44'> 0x01000402 (16778242)
[27813588] <instance itchi.u32 'v_someType?_48'> 0x0000000d (13)
[2781358c] <instance itchi.u32 'v_someStringIndex_4c'> 0x00000001 (1)
[27813590] <instance itchi.u32 'v_extra1_50'> 0x00000002 (2)
[27813594] <instance itchi.u32 'v_extra2_54'> 0x00000000 (0)
[27813598] <instance itchi.u32 'field_58'> 0x00000000 (0)
[2781359c] <instance itchi.u32 'field_5c'> 0x00000000 (0)
[278135a0] <instance dynamic.block(2) 'extra(50)'> "\x04\x00"
[278135a2] <instance dynamic.block(0) 'extra(54)'> ""
0:000> * Step over the copy instruction triggering the heap-based buffer overflow
0:000> p
(10a8.1098): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=27813540 ebx=1b9f8fda ecx=0000000f edx=00000000 esi=27813564 edi=1b9f8ffe
eip=27809c46 esp=00d19d1c ebp=00d19d2c iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210286
jsvda!Ordinal929+0x17ba:
27809c46 f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
To modify a given document with the necessary attributes, run the proof-of-concept with Python against the desired document using the following parameters:
$ python poc.py3.zip modify /path/to/document.jtd
This will update the document in-place with the necessary changes required to trigger the vulnerability. The same proof-of-concept can be used to process a document and scan it for the necessary attributes by using the “read” command instead of “modify”.
The attributes used by the application and described by this document can be found within the “\4JSRV_SummaryInformation” stream. At the beginning of this stream is a 0x40 byte header.
<class __main__.JSRV_SummaryInformationHeader> 'unnamed_7f44afc8c0a0' {unnamed=True}
[0] <instance jtd.u32 'sig'> 0x00009030 (36912)
[4] <instance jtd.sub_277F2074_9030 'header'> "\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x0a\x0e\x0d\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x24\x09\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"
[40] <instance ptype.block 'tail'> ""
<class jtd.sub_277F2074_9030> 'header'
[4] <instance jtd.u32 '_1'> 0x00000040 (64)
[8] <instance jtd.u32 '2'> 0x00000000 (0)
[c] <instance jtd.u32 '3'> 0x00000000 (0)
[10] <instance jtd.u32 '4'> 0x0d0e0a0d (219023885)
[14] <instance jtd.u32 'v8_5'> 0x00000001 (1)
[18] <instance jtd.u32 '6'> 0x00000000 (0)
[1c] <instance jtd.u32 '7'> 0x00000000 (0)
[20] <instance jtd.u32 'v11_8'> 0x00000924 (2340)
[24] <instance jtd.u32 'v9_9'> 0x00000000 (0)
[28] <instance jtd.u32 'a'> 0x00000000 (0)
[2c] <instance jtd.u32 'b'> 0x00000000 (0)
[30] <instance jtd.u32 'c'> 0x00000000 (0)
[34] <instance jtd.u32 'd'> 0x00000000 (0)
[38] <instance jtd.u32 'e'> 0x00000000 (0)
[3c] <instance jtd.u32 'f'> 0x00000000 (0)
[14] <instance jtd.u32 'v8_5'> 0x00000001 (1)
[24] <instance jtd.u32 'v9_9'> 0x00000000 (0)
The fields from this header that are related to this vulnerability reside at offset +0x14 and offset +0x24 of the header. The size of the allocation is calculated by taking the sum of these two fields and adding them to the size of the static attributes (0x188) and the value 0x80. The amount of data that is written into the allocated buffer is 0x832 bytes. Thus, if the sum of all of these values is smaller than the difference of 0x832 and 0x80, then this vulnerability is being triggered.
2022-12-19 - Vendor Disclosure
2023-04-04 - Vendor Patch Release
2023-04-05 - Public Release
Discovered by a member of Cisco Talos.