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.