CVE-2017-2819
An exploitable heap-based buffer overflow exists in the Hangul Word Processor component (version 9.6.1.4350) of Hancom Thinkfree Office NEO 9.6.1.4902. 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.
Thinkfree Office NEO Trial Word 9.6.1.4902 Hwp.exe Product Version: 9.6.1.4350 HwpApp.dll Product Version: 9.6.1.4350
http://www.hancom.com/global/product/productWindowsMain.do?gnb0=3&gnb1=4 http://www.hancom.com/global/cs_center/csDownload.do
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 HWPTAG_TAB_DEF 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 HWPTAG_DOCUMENT_PROPERTIES(16), which has its header read at [1]. Immediately following this, the application will parse the HWPTAG_ID_MAPPINGS(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 HWPTAG_BIN_DATA(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
HwpApp!CHncAppShield::operator=+0xfe0d0:
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)
...
HwpApp!CHncAppShield::operator=+0xfe1f9:
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)
...
HwpApp!CHncAppShield::operator=+0xfe26b:
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 HWPTAG_FACE_NAME(19) records. Two more record types are parsed before the record type containing the vulnerability is parsed. These are the HWPTAG_BORDER_FILL(20) record at [2], followed by the HWPTAG_CHAR_SHAPE(21) record at [3]. After the HWPTAG_BORDER_FILL(20) is allocated for and then HWPTAG_CHAR_SHAPE(21) record’s object is constructed, the function call at [4] will be used to parse the HWPTAG_TAB_DEF(22) records defined within the document. After parsing HWPTAB_TAB_DEF(22) records, the HWPTAG_NUMBERING(23) records will also be parsed at [5].
0:008> u hwpapp+297aff
HwpApp!CHncAppShield::operator=+0xf0fbf:
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 HWPTAG_TAB_DEF(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 HWPTAG_TAB_DEF(22) record.
0:008> u hwpapp+298d4c
HwpApp!CHncAppShield::operator=+0xf220c:
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 HWPTAG_TAB_DEF(22) record in the file, the following function will be executed. When reading each record, its header is first processed at [1]. Each HWPTAG_TAB_DEF(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 HWPTAG_TAB_DEF(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 HWPTAG_TAB_DEF(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
HwpApp!ResetCoreEngine+0x5eb10:
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 HWPTAG_TAB_DEF(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
HwpApp!ResetCoreEngine+0x69ff0:
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
HwpApp!CHncAppShield::operator=+0x241800:
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
HwpApp!CHncAppShield::operator=+0x23ec8d:
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
(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
MSVCR120!memcpy+0x2a:
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 ................
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 HWPTAG_TAB_DEF(22) data structure that is responsible for this vulnerability. The HWPTAG_TAB_DEF(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 HWPTAG_TAB_DEF(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