Talos Vulnerability Report

TALOS-2016-0196

Ichitaro Office JTD Figure handling Code Execution Vulnerability

February 24, 2017
CVE Number

CVE-2017-2789

Summary

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.

Tested Versions

JustSystems Ichitaro

Product URLs

http://www.ichitaro.com/

CVSSv3 Score

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

Details

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.

Timeline

2016-08-29 - Vendor Disclosure
2017-02-24 - Public Release

Credit

Discovered by a Talos team member and Cory Duplantis of Cisco Talos.