CVE-2017-2789
A vulnerability was discovered within the Ichitaro word processor. Ichitaro is published by JustSystems and is considered one of the more popular word processors used within Japan. Ichitaro’s proprietary file format is a Compound Document similar to .doc for Microsoft Word called .jtd. When processing a Figure stream from a .jtd, the application will allocate space when parsing a Figure. When copying filedata into this buffer, the application will calculate two values to determine how much data to copy from the document. If both of these values are larger than the size of the buffer, the application will choose the smaller of the two and trust it to copy data from the file. This value is larger than the buffer size, which leads to a heap-based buffer overflow. This overflow corrupts an offset in the heap used in pointer arithmetic for writing data and can lead to code-execution under the context of the application.
JustSystems Ichitaro
http://www.ichitaro.com/
8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
Ichitaro uses the Structured Storage documentation format to read and process a .jtd file. Although there are many streams within a .jtd, the two streams that affect this bug are Figure and FigureData. Figure is the metadata for Figures and FigureData is contents representing the Figure.
The modules involved in the vulnerability are below (as described by lm vm
in windbg):
start end module name
21430000 21479000 jsfdm C (export symbols) C:\Program Files (x86)\JustSystems\JSLIB32\jsfdm.dll
File version: 4.5.9.0
Product version: 4.5.9.0
start end module name
213e0000 21402000 jsmisc32 C (export symbols) C:\Program Files (x86)\JustSystems\JSLIB32\jsmisc32.dll
File version: 2.7.1.0
Product version: 2.7.1.0
start end module name
14b80000 14bd1000 jsdrfl C (export symbols) C:\Program Files (x86)\JustSystems\JSLIB32\jsdrfl.dll
File version: 1.1.2.0
Product version: 1.1.2.0
In the following code, the application isolates a 0x3004
byte chunk out of a previously allocated 0x36b00
chunk (esi
). This chunk is used to store FigureData which is processed later. The reason this occurs is to write the size of this chunk after the isolation. This leaves the chunk with the size at the end of the object and a pointer to the end of the object.
jsfdm!FDM_inquirePageNumber+0x2a:
2144980e push 3004h // Amount to allocate
21449813 push esi // Chunk to place allocation
21449814 call jsfdm!Ordinal159+0x1440 (21431440) // Calls into following function
\
jsfdm!Ordinal159+0x1475:
21431458 mov ecx,dword ptr [esp+10h] // Extract 0x3004 from arg[1]
21431463 lea edi,[ecx+2] // edi = 0x3004 + 2
21431468 movzx esi,di // esi = 0x3006
...
21431475 add dword ptr [eax+550Eh],esi // Add 0x3006 to our base pointer
2143147b mov eax,dword ptr [eax+550Eh] // Store the base pointer used for writing in eax
...
21431485 mov word ptr [eax-2],di // Store 0x3006 two behind our new pointer
Pseudocode: base_ptr = esi curr_ptr = base_ptr + 0x3006 // 2 additional bytes needed to store the size *(curr_ptr - 2) = 0x3006
Later in the code, the application will reset the writing pointer to point back to the beginning of the chunk using the aforementioned saved size.
jsfdm!Ordinal159+0x1492:
21431492 mov edx,dword ptr [eax+550Eh] // Extract our saved pointer (see 0x21431475 above)
21431498 lea ecx,[eax+550Eh] // Extract the pointer to our saved pointer
2143149e movzx edx,word ptr [edx-2] // Extract the size of the old chunk (0x3006)
214314a2 sub dword ptr [ecx],edx // Rewind the chunk using the extracted chunk size
180b14a4 mov ecx,dword ptr [ecx] // Read pointer from rewound pointer (new_addr)
This new_addr
is then saved for later use by Ichitaro.
The application can now read the size of a given Figure from the Figure header. This value is multiplied by 6 since each of the elements that will be read later in the program are 6 bytes in length.
jsfdm!FDM_inquirePageNumber+0x3b:
2144981f mov ebx,dword ptr [ebp+10h] // Extract the size from Figure header (0xe00)
...
21449828 movsx esi,bx // Sign extend
...
2144982c lea eax,[esi+esi*2]
2144982f lea eax,[eax+eax+0Ah] // size = original_size * 6 + 0xa
From this code, one suspect value is saved and will be returned to later (value_1
). There is also a second value calculated based on the FigureData itself.
The application reads the subelement lengths from the FigureData header by reading the second byte of each element representing a number of subelements.
To calculate the possible figure length from this header, an accumulation value starts at 0. The application starts by adding 0x4b4
to the accumulation value and 0x4b0
for each subsequent element, as shown below:
jsfdm!FDM_initHWMM+0xd1:
continue:
180bfa0b mov eax,dword ptr [ebp+0Ch] // Set eax to current accumulation
loop:
180bfa0e lea edx,[eax+4B4h]
180bfa14 lea ecx,[ebx+4]
180bfa17 cmp ecx,edx // Check if accumulated value will be over threshold
180bfa19 jbe jsfdm!FDM_initHWMM+0xef (last_loop)
180bfa1b test eax,eax // First loop eax isn't set
180bfa1d mov edi,4B4h // Set edi to 4b4 for first loop
180bfa22 je jsfdm!FDM_initHWMM+0xfa (first_loop)
180bfa24 add edi,0FFFFFFFCh // Get 4b0 by truncating 0x4b4 + 0xfffffffc
180bfa27 jmp jsfdm!FDM_initHWMM+0xf6
last_loop:
180bfa29 mov edi,ebx
180bfa2b sub edi,eax
180bfa2d add edi,4 // Set edi to the remainder necessary to reach threshold
180bfa30 test eax,eax
180bfa32 jne jsfdm!FDM_initHWMM+0x106 (every_other_loop)
first_loop:
180bfa34 lea eax,[ebp-4B4h]
180bfa3a push eax
180bfa3b push edi // Push 4b4 to be accumulated
180bfa3c push 0 // Starting accumulation value
180bfa3e jmp jsfdm!FDM_initHWMM+0x10f (body)
every_other_loop:
180bfa40 lea ecx,[ebp-4B0h]
180bfa46 push ecx
180bfa47 push edi // Push 4b0 to be accumulated
180bfa48 push eax // Current accumulation value
body:
180bfa49 push dword ptr [esi] // Push address to store accumulation
180bfa4b call jsfdm!FDM_convWTEXTtoTEXT+0xbe4c
...
180bfa5e add dword ptr [ebp+0Ch],eax // Add to local accumulation
180bfa61 lea eax,[ebx+4]
180bfa64 cmp dword ptr [ebp+0Ch],eax // Check if parsed enough
180bfa67 je jsfdm!FDM_initHWMM+0x131 (return(1))
180bfa69 jb jsfdm!FDM_initHWMM+0xd1 (continue)
return(1)
180bfa6b push 1
180bfa6d pop eax
180bfa6e pop edi
180bfa6f pop esi
180bfa70 pop ebx
180bfa71 leave
After saving the possible length using elements, the application also must take into account subelements. Thus, 0x16
is added to the accumulation value for each of the subelements. This final value will be refered to as value_2
.
jsdrfl!CFigureFileCtrl::LoadFDM+0x22c:
14b83a51 8b459c mov eax,dword ptr [ebp-64h] // Current figure
14b83a54 8b4808 mov ecx,dword ptr [eax+8]
14b83a57 e8e46b0000 call jsdrfl!CFigureFileCtrl::GetCompressBitmap+0x134 (14b8a640)
14b83a5c 8945e0 mov dword ptr [ebp-20h],eax // eax now holds the number of subelements
...
14b83a6b c745dc00000000 mov dword ptr [ebp-24h],0 // Initialize loop counter to zero
14b83a72 e903000000 jmp jsdrfl!CFigureFileCtrl::LoadFDM+0x255 (loop)
increment_loop:
14b83a77 ff45dc inc dword ptr [ebp-24h]
loop:
14b83a7a 8b45e0 mov eax,dword ptr [ebp-20h]
14b83a7d 3945dc cmp dword ptr [ebp-24h],eax // Check if parsed all subelements
...
14b83c9a 8b4d9c mov ecx,dword ptr [ebp-64h] // Current figure
14b83c9d e86a370000 call jsdrfl!CFigureFileCtrl::putFig (14b8740c)
\
jsdrfl!CFigureFileCtrl::putFig+0x21:
14b8742d 898de8feffff mov dword ptr [ebp-118h],ecx // Locally save current figure
...
14b8758f 8b85e8feffff mov eax,dword ptr [ebp-118h] // Retrieve current figure
14b87595 8b4014 mov eax,dword ptr [eax+14h] // Retrieve subelements
14b87598 50 push eax
14b87599 e8aec20300 call jsdrfl!DRFL_ExtSaveFDM+0xcec2 (14bc384c)
\
jsfdm!FDM_putFig:
180c0adc 8b442404 mov eax,dword ptr [esp+4] // Get our subelements
...
180c0aed 50 push eax
180c0aee e83a000000 call jsfdm!FDM_putFigEx+0x37 (180c0b2d)
\
jsfdm!FDM_putFigEx+0x37:
180c0b2e 8b742408 mov esi,dword ptr [esp+8] // Retrieve subelements
...
180c0b5f 56 push esi
180c0b60 e824000000 call jsfdm!FDM_insertFig+0x1f (180c0b89)
\
jsfdm!FDM_insertFig+0x292:
180c0df8 lea eax,[ebx+0Ch] // Subelement data
180c0dfb push eax
180c0dfc push 16h // Push 16 to be accumulated
180c0dfe push dword ptr [ebp-1Ch] // Current accumulation value
180c0e01 lea edi,[ebx+7Ah]
...
180c0e09 push dword ptr [edi] // Push address to store accumulation
180c0e0b call jsfdm!FDM_convWTEXTtoTEXT+0xbe4c (180df252)
Before copying the FigureData into the allocated region, a min(value_1, value_2)
occurs to determine the size to copy.
jsmisc32!Ordinal501+0xbd:
213f5d8a lea eax,[edi+ebx] // edi = 0; move value_1 into eax
213f5d8d cmp eax,ecx // ecx is value_2; compare value_1 and value_2
213f5d8f jbe jsmisc32!Ordinal501+0xc8 (213f5d95) // Select value_1 if it is lower
213f5d91 sub ecx,edi
213f5d93 mov ebx,ecx // Select value_2 if it is lower
The application will choose the smaller of the two values, regardless if it is actually greater than the original 0x3006
chunk size. Using this value, the application will overwrite the saved chunk size (offset 0x3006
from the above arithmetic).
jsmisc32!Ordinal501+0x1ab:
213f5e78 53 push ebx // Push size for memmove
213f5e79 56 push esi
213f5e7a e88cfaffff call jsmisc32!Ordinal510+0x4f7 (213f590b) // Calculate source
213f5e82 50 push eax // Push source
213f5e83 8b4514 mov eax,dword ptr [ebp+14h]
213f5e86 03c7 add eax,edi
...
213f5e8a 50 push eax // Push destination
213f5e8b ff1510d93f21 call dword ptr [jsmisc32!Ordinal489+0x55b3 (213fd910)] // memmove
During the manual unallocation of the 0x3004
chunk from the larger 0x36b00
chunk, the corrupted size value results in a pointer that is read out of bounds of the 0x36b00
initial chunk and is saved for later use. Because this pointer is trusted by the application and reused later, an attacker can potentially gain arbitrary write conditions leading to arbitrary code execution.
2016-08-29 - Vendor Disclosure
2017-02-24 - Public Release
Discovered by a Talos team member and Cory Duplantis of Cisco Talos.