CVE: CVE-2019-16337

Tested Versions:

  • Hancom Office NEO (HncBD90 version 9.6.1.9403)

Product URL(s): https://www.hancom.com/cs_center/csDownload.do

Description of the vulnerability

Hangul Office is published by Hancom, Inc. and is considered one of the more popular Office suites used within South Korea. When opening a specially crafted Office Open XML Workbook (.xlsx), HncBD90 uses realloc function to reallocate a memory buffer, but after the realloc it continues using the old pointer that has been freed, resulting in a use-after-free vulnerability. This could lead to code execution under the context of the application.

Technical Details

Crash context:

(784.a8c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=ffffffff ebx=00000001 ecx=00000140 edx=47f6a500 esi=47d4cf54 edi=00358cbc
eip=710b16de esp=00358a48 ebp=00358bf0 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00210246
HncBD90!CHncDeviceContext::CHncDeviceContext+0x7cfe:
710b16de 8b06            mov     eax,dword ptr [esi]  ds:002b:47d4cf54=????????

0:000> kb
ChildEBP RetAddr  Args to Child              
WARNING: Stack unwind information not available. Following frames may be wrong.
00358bf0 710b1dc8 00000001 00358cbc 47d4cf54 HncBD90!CHncDeviceContext::CHncDeviceContext+0x7cfe
00358c58 710b2074 00358cbc 20cb5ea0 00000000 HncBD90!CHncDeviceContext::CHncDeviceContext+0x83e8
00358c90 710aa584 00000816 48f9bfd0 0035ddf8 HncBD90!CHncDeviceContext::CHncDeviceContext+0x8694
0035b8ec 6f70b679 48f9bfd0 00000816 00000000 HncBD90!CHncDeviceContext::CHncDeviceContext+0xba4
0035b928 6f70bb79 00000000 48f9bfd0 00000816 HCellBook!CWordWrapFormatIterator::ChangeFormat+0x4f69
0035d9e8 6f6b8725 00000000 48f9bfd0 00000816 HCellBook!CWordWrapFormatIterator::ChangeFormat+0x5469
0035da7c 6fb20073 0035ddf8 48f9bfd0 00000816 HCellBook!CHclGlobal::GetTextExtent+0x195
0035db84 6fbe8c59 0035ddf8 ffffffb0 00000001 HCellApp!CBookViewInfo::ClearAssistButtons+0xce3

address 47d4cf54 found in
    _DPH_HEAP_ROOT @ 361000
    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)
                                   218c209c:         47d4a000             5000
    73ce947d verifier!VerifierDisableFaultInjectionExclusionRange+0x0000352d
    7731136b ntdll!RtlpNtEnumerateSubKey+0x00004494
    772cdf2e ntdll!RtlUlonglongByteSwap+0x0001118e
    739622b0 MSVCR120!realloc+0x0000003d
    710cc56f HncBD90!HncDRGetTopOffset+0x00000a1f
    710b1dc8 HncBD90!CHncDeviceContext::CHncDeviceContext+0x000083e8
    710b2074 HncBD90!CHncDeviceContext::CHncDeviceContext+0x00008694
    710aa584 HncBD90!CHncDeviceContext::CHncDeviceContext+0x00000ba4
    6f70b679 HCellBook!CWordWrapFormatIterator::ChangeFormat+0x00004f69

First, we will take a look at the pseudocode around the crash point:

  v7 = v56;
  if ( *(unsigned __int16 *)(v56 + 22) + 1 + *(unsigned __int16 *)(v56 + 20) <= *(unsigned __int16 *)(v56 + 18)
    	|| (v5 = sub_710CC500(v56)) != 0 )  // realloc happens in here
 {
   v5 = *(unsigned __int16 *)(v7 + 22);
    v28 = *(_DWORD *)(v7 + 12) + 44 * (v5 + *(unsigned __int16 *)(v7 + 20));
    if ( v28 )
    {
v29 = v15 == v22;
 v30 = v51;
 if ( v29 )
   v15 = -1;

*(_DWORD *)(v28 + 16) = v15;
 v6 = v58;
 *(_DWORD *)v28 = *v58;            // Crash Here
 *(_WORD *)(v28 + 8) = v49;
 *(_WORD *)(v28 + 10) = v30;
 *(_BYTE *)(v28 + 14) = *((_BYTE *)v6 + 14);
....

As v58 causes an access violation, we will see where v58 came from first. In this function, v58 is from a4, which is the third parameter of this function.

__int16 __thiscall sub_710B1290(_DWORD *this, int a2, int a3, int *a4, unsigned __int16 a5)
{
  LOWORD(v5) = a2;
  v6 = a4;
  v7 = a3;
  v62 = this;
  v60 = a2;
  v56 = a3;
  v58 = a4;
  v67 = 0;
...
}            
esi is 47d4cf54
eax=00000001 ebx=20cb5ea0 ecx=20cb5ea0 edx=0000a006 esi=47d4cf54 edi=00358cbc
eip=710b12c3 esp=00358a48 ebp=00358bf0 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200206
HncBD90!CHncDeviceContext::CHncDeviceContext+0x78e3:
710b12c3 89b5a0feffff    mov     dword ptr [ebp-160h],esi ss:002b:00358a90=47d4bb38

address 47d4cf54 found in
    _DPH_HEAP_ROOT @ 361000
    in busy allocation (  DPH_HEAP_BLOCK:    UserAddr    UserSize -    VirtAddr    VirtSize)
                                218c209c:    47d4a900        3700 -    47d4a000        5000

    73ce8e89 verifier!VerifierDisableFaultInjectionExclusionRange+0x00002f39
    7731103e ntdll!RtlpNtEnumerateSubKey+0x00004167
    772cabe2 ntdll!RtlUlonglongByteSwap+0x0000de42
    772734a1 ntdll!RtlQueryPerformanceCounter+0x00000add
    7395ed63 MSVCR120!malloc+0x00000033
    710cc51d HncBD90!HncDRGetTopOffset+0x000009cd
    710aa584 HncBD90!CHncDeviceContext::CHncDeviceContext+0x00000ba4
    6f70b679 HCellBook!CWordWrapFormatIterator::ChangeFormat+0x00004f69
    6f70bb79 HCellBook!CWordWrapFormatIterator::ChangeFormat+0x00005469
    6f6b8725 HCellBook!CHclGlobal::GetTextExtent+0x00000195

As you can see, ESI points to an object which is 0x3700 bytes. When the program executes to line #3, a call to sub_710CC500 will realloc this buffer, let’s set a breakpoint at hncbd90+0x716a2:

Breakpoint 1 hit
eax=00000140 ebx=00000006 ecx=00358cbc edx=00358a44 esi=00000006 edi=00358cbc
eip=710b16a2 esp=00358a48 ebp=00358bf0 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200202
HncBD90!CHncDeviceContext::CHncDeviceContext+0x7cc2:
710b16a2 e859ae0100      call    HncBD90!HncDRGetTopOffset+0x9b0 (710cc500)
0:000> dd ecx
00358cbc  48f9bfd0 00000816 00000000 47d4a900
00358ccc  01400000 0011012f 00000002 48f9efa0

ECX is the parameter passed to the function sub_710CC500 , ecx+0xC stores a pointer which is the buffer to be reallocated and v58 is in this region, let’s see the process of reallocation:

eax=00004200 ebx=00000006 ecx=00358cbc edx=00358a44 esi=00358cbc edi=00358cbc
eip=710cc569 esp=00358a30 ebp=00358bf0 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200206
HncBD90!HncDRGetTopOffset+0xa19:
710cc569 ff1520041171    call    dword ptr [HncBD90!HncDRSetAbortProc+0x205d0 (71110420)] ds:002b:71110420={MSVCR120!realloc (73962273)}

0:000> dd esp
00358a30  47d4a900 00004200 00358cbc 00000006
00358a40  00000006 710b16a7 00000006 47d4cf54

The new size of the block is 0x4200 bytes. After reallocation, the old pointer, 0x47d4a900, will be freed, and the new pointer is 0x228a3e00:

0:000> p
eax=228a3e00 ebx=00000006 ecx=773117bb edx=003610d0 esi=00358cbc edi=00358cbc
eip=710cc56f esp=00358a30 ebp=00358bf0 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200206
HncBD90!HncDRGetTopOffset+0xa1f:
710cc56f 83c408          add     esp,8
0:000> dd 47d4a900 
47d4a900  ???????? ???????? ???????? ????????
47d4a910  ???????? ???????? ???????? ????????
47d4a920  ???????? ???????? ???????? ????????
47d4a930  ???????? ???????? ???????? ????????
47d4a940  ???????? ???????? ???????? ????????
47d4a950  ???????? ???????? ???????? ????????
47d4a960  ???????? ???????? ???????? ????????
47d4a970  ???????? ???????? ???????? ????????
0:000> dd 228a3e00 
228a3e00  00000001 00000001 00050000 00000001
228a3e10  ffffffff 00000000 00000000 00000000
228a3e20  00000000 00000000 00000000 00000001
228a3e30  00000001 00080005 00000001 ffffffff
228a3e40  00000000 00000000 00000000 00000000
228a3e50  00000000 00000000 00000001 00000001
228a3e60  0003000d 00000001 ffffffff 00000000
228a3e70  00000000 00000000 00000000 00000000

Back to sub_710B1290, at line #15, the program doesn’t update the pointer v58 and use the freed pointer again which will cause an access violation. To fix this problem, v58 needs to be updated after sub_710CC500 is called, for example:

 if ( *(unsigned __int16 *)(v56 + 22) + 1 + *(unsigned __int16 *)(v56 + 20) <= *(unsigned __int16 *)(v56 + 18) || (v5 = sub_710CC500(v56)) != 0 )
   {
     v5 = *(unsigned __int16 *)(v7 + 22);
     v28 = *(_DWORD *)(v7 + 12) + 44 * (v5 + *(unsigned __int16 *)(v7 + 20));
     if ( v28 )
     {
       v58=v56+0xC			//Patch,update v58 instead of using it directly
       v29 = v15 == v22;
       v30 = v51;
       if ( v29 )
         v15 = -1;
       *(_DWORD *)(v28 + 16) = v15;
       v6 = v58;
       *(_DWORD *)v28 = *v58;            // Crash Here

Timeline

  • 2019-01-10 Vendor silently patched

Vendor Response

The vendor has silently released a fix for the issue some time after reporting.