CVE-2019-5144
An exploitable heap underflow vulnerability exists in the derive_taps_and_gains
function in kdu_v7ar.dll
of Kakadu Software SDK 7.10.2. A specially crafted jp2 file can cause a heap overflow, which can result in remote code execution. An attacker could provide a malformed file to the victim to trigger this vulnerability.
Kakadu Software SDK 7.10.2 - Windows
https://kakadusoftware.com/downloads/
8.1 - CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-191: Integer Underflow (Wrap or Wraparound)
Kakadu Software SDK is a document imaging developer toolkit providing all kinds of functionality related with image conversion, creation, editing, etc.
We identified a vulnerability in the derive_taps_and_gains
function. A specially crafted jp2
file can cause a heap overflow, leading to remote code execution.
Trying to load a malformed jp2 file via the derive_taps_and_gains
function we end up in the following situation:
===========================================================
VERIFIER STOP 00000010: pid 0x1A5C: corrupted start stamp
15BF1000 : Heap handle
1CE2DFF0 : Heap block
42CE0000 : Block size
428A0000 : Corrupted stamp
===========================================================
This verifier stop is not continuable. Process will be terminated
when you use the `go' debugger command.
===========================================================
AVRF: Noncontinuable verifier stop 10 encountered. Terminating process ...
TTD: End of trace reached.
(1a5c.19c4): Break instruction exception - code 80000003 (first/second chance not available)
0:000> kb
# ChildEBP RetAddr Args to Child
WARNING: Frame IP not in any known module. Following frames may be wrong.
00 0019dec0 77b1002c 737de1c4 ffffffff c0000421 0x77a96000
01 0019dec4 737de1c4 ffffffff c0000421 00000018 ntdll!NtTerminateProcess+0xc
02 0019def8 737dbc4c 00000010 737d1db0 15bf1000 verifier!VerifierStopMessage+0x344
03 0019df64 737dc06a 15bf1000 00000004 1ce2dff0 verifier!AVrfpDphReportCorruptedBlock+0x2fc
04 0019dfc0 737dc562 15bf1000 1ce2dff0 00000004 verifier!AVrfpDphCheckNormalHeapBlock+0x11a
05 0019dfe0 737dadb3 15bf1000 15dd0000 01001002 verifier!AVrfpDphNormalHeapFree+0x22
06 0019e004 77b8b519 15bf0000 01001002 1ce2dff0 verifier!AVrfDebugPageHeapFree+0xe3
07 0019e06c 77b32dc9 1ce2dff0 7d2cbc43 00000000 ntdll!RtlDebugFreeHeap+0x3e
08 0019e1c0 77ae4581 00000000 1ce2dff0 c24c0000 ntdll!RtlpFreeHeap+0x4e4a9
09 0019e210 736b016a 15bf0000 00000000 1ce2dff0 ntdll!RtlFreeHeap+0x201
0a 0019e224 15a12d25 1ce2dff0 00000000 0019f1bc MSVCR100!free+0x1c [f:\dd\vctools\crt_bld\self_x86\crt\src\free.c @ 51]
0b 0019e248 15a90299 1ce2dff4 15ad14a4 0019f1bc kdu_v7AR!kdu_core::kdu_membroker::`default constructor closure'+0x5d5
0c 0019f42c 15a44183 ff8461df 15eb7ff4 00000000 kdu_v7AR!kdu_core::kdu_synthesis::kdu_synthesis+0x259
0d 0019f47c 15a4fd33 00000003 00000000 ff846167 kdu_v7AR!kdu_core::kdu_codestream_comment::put_text+0x2853
0e 0019f4c4 00452681 0019f54c 00000003 00000000 kdu_v7AR!kdu_core::kdu_codestream::open_tile+0x123
0f 0019f53c 00453aba 00000001 00000001 00000070 kdu_render+0x52681
10 0019f5e8 004568aa 00000001 00000004 ffffff0e kdu_render+0x53aba
11 0019f620 0043a5dd 1cdde4c0 0019f6d0 00000004 kdu_render+0x568aa
12 0019f6e4 00446322 0003e800 0019f70c 0019fcf8 kdu_render+0x3a5dd
13 0019f72c 00406e28 0003e800 0019f80c 00000000 kdu_render+0x46322
14 0019fe30 004084c7 15dfafe0 3f800000 00000000 kdu_render+0x6e28
15 0019ff2c 0045726c 00000005 15de8f90 15bfdf50 kdu_render+0x84c7
16 0019ff70 76f2fe09 003e9000 76f2fdf0 0019ffdc kdu_render!std::_Init_locks::operator=+0x780
17 0019ff80 77b0607d 003e9000 7d2ca25f 00000000 KERNEL32!BaseThreadInitThunk+0x19
18 0019ffdc 77b0604d ffffffff 77b245c3 00000000 ntdll!__RtlUserThreadStart+0x2f
19 0019ffec 00000000 0045738d 003e9000 00000000 ntdll!_RtlUserThreadStart+0x1b
As we can see, a heap overflow occurred, corrupting heap metadata.
To get a better view, we need first to understand some functions around memory allocation used in this library.
Line1 float *__thiscall allocate_floats(kd_coremem *this, unsigned int number_object) // [1]
Line2 {
Line3 if (..)
Line4 {
Line5 (...)
Line6 }
Line7 else
Line8 {
Line9 size_to_alloc = 4 * number_object + 4i64; // [2]
Line10 (...)
Line11 ptr_buff = (char *)malloc(size_to_alloc);
Line12 if ( !ptr_buff )
Line13 in_case_memory_not_suffisant(v2, size_to_alloc);
Line14 result = (float *)(ptr_buff + 4);
Line15 *((_DWORD *)result - 1) = 4 * number_object; // [3]
Line16 }
Line17 (...)
Line18 return result;
Line19 }
The function allocate_floats
is responsible to ensure an allocation on heap for a table of floats. This function is taking a parameter
number_object
[1] to be used as a size of integers, plus an extra size [2] to store the total size of the global table [3].
The second function we need to describe is enlarge_work_buffers
, using the following pseudo code:
Line1 void __thiscall enlarge_work_buffers(kdu_kernels *this, unsigned int num_objects) // [4]
Line2 {
Line3 kdu_kernels *v2; // esi
Line4 struct kd_coremem *kd_coremem; // ecx
Line5 float *buff_mem_1; // edi
Line6 float *buff_mem_2; // ebx
Line7 float *v6; // eax
Line8 float *v7; // eax
Line9
Line10 v2 = this;
Line11 if ( (signed int)this->work_L < (signed int)num_objects ) // [5]
Line12 {
Line13 kd_coremem = this->kd_coremem_1;
Line14 if ( (num_objects | 2) > 0xFFFF && num_objects > 0x7FFFFFFF )
Line15 in_case_memory_not_suffisant(kd_coremem, 0i64);
Line16 if ( 2 * num_objects == -1 )
Line17 in_case_memory_not_suffisant(kd_coremem, 0i64);
Line18 buff_mem_1 = &allocate_floats(v2->kd_coremem_1, 2 * num_objects + 1)[num_objects];
Line19 buff_mem_2 = &allocate_floats(v2->kd_coremem_1, 2 * num_objects + 1)[num_objects];
Line20 v6 = v2->work1;
Line21 if ( v6 )
Line22 {
Line23 memcpy(&buff_mem_1[-v2->work_L], &v6[-v2->work_L], 8 * v2->work_L + 4);
Line24 perform_free(v2->kd_coremem_1, (int)&v2->work1[-v2->work_L]);
Line25 v2->work1 = 0;
Line26 }
Line27 v7 = v2->work2;
Line28 if ( v7 ) // [6]
Line29 {
Line30 memcpy(&buff_mem_2[-v2->work_L], &v7[-v2->work_L], 8 * v2->work_L + 4);
Line31 perform_free(v2->kd_coremem_1, (int)&v2->work2[-v2->work_L]);
Line32 v2->work2 = 0;
Line33 }
Line34 v2->work2 = buff_mem_2; // [7]
Line35 v2->work1 = buff_mem_1;
Line36 v2->work_L = num_objects;
Line37 }
Line38 }
The function enlarge_work_buffers
accepts two arguments [4]:
num_objects
to help computing memory needsIt’s a very simple function designed to check if the value of the memory allocated is sufficient. If this is not the case [5], two allocations of buffers will be done using the function previously described allocate_floats
, to store bigger objects. In case [6]
previous pointers were not null, e.g an object was existing already, content is copied into new buffer before freeing. Then the object kernel is updated with the two memory buffers [7] before returning.
Now let’s have a look at the vulnerable function we named derive_taps_and_gains
, the pseudo code is the following:
line1 int __thiscall derive_taps_and_gains(kdu_kernels *this)
line2 {
line3 kdu_kernels *kernel; // esi
line4 double *bibo_step_gains; // eax
line5 int which; // ecx
line6 float v4; // edx
line7 int previous_num_steps; // eax
line8 int v6; // ecx
line9 int value_branch_max; // eax
line10 int value_branch_min; // ebx
line11 _DWORD *step_info_n; // edx
line12 int v10; // ecx
line13 int next_step_info; // edi
line14 int v12; // eax
line15 int step_info; // edx
line16 int DWT_Kernel; // edi
line17 int integer_overflowed; // ebx
line18 bool some_condition; // cc
line19 int *v17; // ecx
line20 void (__thiscall *v18)(int *, const char *); // edx
line21 float *work_2; // edx
line22 unsigned int v20; // eax
line23 int v21; // eax
line24 int num_steps; // eax
line25 int v23; // edi
line26 int v24; // ecx
line27 int v25; // eax
line28 struct kdu_kernel_step_info *steps; // edx
line29 struct kd_coremem *number_of_coefficiant; // ebx
line30 int number_of_coefficiant_minus_1; // ecx
line31 int v29; // edx
line32 int v30; // ecx
line33 float *v31; // edx
line34 int i; // eax
line35 int v33; // eax
line36 int v34; // edx
line37 int j; // ecx
line38 int v36; // ecx
line39 float *v37; // edx
line40 float *v38; // ecx
line41 int v39; // edi
line42 double v40; // st6
line43 float *v41; // eax
line44 unsigned int v42; // edx
line45 double v43; // st5
line46 float *v44; // ecx
line47 float *v45; // ecx
line48 float *v46; // ecx
line49 float *v47; // eax
line50 int v48; // edx
line51 double v49; // st5
line52 int impulse_min; // edx
line53 int impulse_max; // ebx
line54 struct kd_coremem *v52; // edi
line55 float *v53; // eax
line56 int k; // ebx
line57 int v55; // edi
line58 float *v56; // ecx
line59 float *v57; // edx
line60 int v58; // eax
line61 double v59; // st7
line62 struct kd_coremem *v60; // ecx
line63 int v61; // ebx
line64 int result; // eax
line65 int v63; // ecx
line66 float *v64; // edi
line67 float *v65; // edx
line68 double v66; // st7
line69 int v67; // eax
line70 double v68; // st7
line71 bool v69; // zf
line72 double v70; // st7
line73 double v71; // st7
line74 float *v72; // edi
line75 float *v73; // edx
line76 int v74; // eax
line77 double v75; // st7
line78 unsigned int v76; // edx
line79 int v77; // ecx
line80 int v78; // ecx
line81 int v79; // ecx
line82 int v80; // edi
line83 int v81; // eax
line84 int v82; // eax
line85 float *v83; // edx
line86 int v84; // ecx
line87 double v85; // st6
line88 float *v86; // ecx
line89 int l; // eax
line90 float *v88; // ecx
line91 double v89; // st4
line92 float *v90; // ecx
line93 int v91; // edi
line94 int v92; // ebx
line95 int v93; // eax
line96 float *v94; // ecx
line97 double v95; // st6
line98 int v96; // eax
line99 double v97; // st7
line100 float *v98; // ecx
line101 float *v99; // ecx
line102 double v100; // st6
line103 float *v101; // ecx
line104 int v102; // [esp+10h] [ebp-64h]
line105 float *work_1; // [esp+24h] [ebp-50h]
line106 float *_work2; // [esp+28h] [ebp-4Ch]
line107 int branch_min[2]; // [esp+2Ch] [ebp-48h]
line108 int branch_max[2]; // [esp+34h] [ebp-40h]
line109 int v107; // [esp+3Ch] [ebp-38h]
line110 struct kd_coremem *v108; // [esp+40h] [ebp-34h]
line111 int v109; // [esp+44h] [ebp-30h]
line112 int v110; // [esp+48h] [ebp-2Ch]
line113 int v111; // [esp+4Ch] [ebp-28h]
line114 unsigned int v112; // [esp+50h] [ebp-24h]
line115 int offset; // [esp+54h] [ebp-20h]
line116 int v114; // [esp+58h] [ebp-1Ch]
line117 unsigned int number_of_object; // [esp+5Ch] [ebp-18h]
line118 int v116; // [esp+60h] [ebp-14h]
line119 int num_steps_value; // [esp+64h] [ebp-10h]
line120 int v118; // [esp+70h] [ebp-4h]
line121
line122 kernel = this;
line123 bibo_step_gains = new_double(this->kd_coremem_1, this->num_steps);
line124 which = 0;
line125 v4 = 0.0;
line126 kernel->bibo_step_gains = bibo_step_gains;
line127 number_of_object = 1; // [9]
line128 v111 = 0;
line129 v110 = 0;
line130 do
line131 {
line132 previous_num_steps = kernel->num_steps - 1;
line133 *(int *)((char *)branch_max + LODWORD(v4)) = 0;
line134 *(int *)((char *)branch_min + LODWORD(v4)) = 0;
line135 *(int *)((char *)&branch_min[1] + which) = 1;
line136 *(int *)((char *)&branch_max[1] + which) = -1;
line137 num_steps_value = previous_num_steps;
line138 if ( previous_num_steps >= 0 )
line139 {
line140 offset = 16 * previous_num_steps;
line141 do
line142 {
line143 v6 = previous_num_steps & 1;
line144 value_branch_max = branch_max[previous_num_steps & 1];
line145 value_branch_min = branch_min[v6];
line146 if ( value_branch_max >= value_branch_min )
line147 {
line148 step_info_n = (uint *)((char *)&kernel->step_info->support_length + offset);
line149 v10 = -(v6 * 4);
line150 next_step_info = step_info_n[1];
line151 v12 = value_branch_max - next_step_info;
line152 step_info = *step_info_n + next_step_info - 1; // [10]
line153 DWT_Kernel = number_of_object;
line154 if ( v12 > *(int *)((char *)&branch_max[1] + v10) )
line155 {
line156 *(int *)((char *)&branch_max[1] + v10) = v12;
line157 if ( v12 > DWT_Kernel )
line158 {
line159 DWT_Kernel = v12;
line160 number_of_object = v12;
line161 }
line162 }
line163 integer_overflowed = value_branch_min - step_info; // [11]
line164
line165 some_condition = integer_overflowed < *(int *)((char *)&branch_min[1] + v10); // [12]
line166 v17 = (int *)((char *)&branch_min[1] + v10);
line167 if ( some_condition ) // [13]
line168 {
line169 *v17 = integer_overflowed;
line170 if ( integer_overflowed > DWT_Kernel )
line171 {
line172 DWT_Kernel = integer_overflowed;
line173 number_of_object = integer_overflowed;
line174 }
line175 }
line176 if ( DWT_Kernel > 0x800 )
line177 {
line178 kdu_core::kdu_error::kdu_error((kdu_core::kdu_error *)&v102, "Kakadu Core Error:\n");
line179 v18 = *(void (__thiscall **)(int *, const char *))(v102 + 8);
line180 v118 = 0;
line181 v18(
line182 &v102,
line183 "Riculously large or displaced DWT kernel with support not contained within the interval [-4096,4096]. Loo"
line184 "ks like an ATK marker segment may be bogus.");
line185 v118 = -1;
line186 kdu_core::kdu_error::~kdu_error((kdu_core::kdu_error *)&v102);
line187 }
line188 }
line189 offset -= 16;
line190 previous_num_steps = num_steps_value - 1;
line191 num_steps_value = previous_num_steps;
line192 }
line193 while ( previous_num_steps >= 0 );
line194 which = v111;
line195 v4 = *(float *)&v110;
line196 }
line197 which -= 4;
line198 LODWORD(v4) += 4;
line199 v110 = LODWORD(v4);
line200 v111 = which;
line201 }
line202 while ( which > -8 );
line203 enlarge_work_buffers(kernel, number_of_object); // [8]
line204 work_2 = kernel->work2; // [14]
line205 work_1 = kernel->work1;
line206 _work2 = work_2;
line207 (...) // [15]
line208 }
While observing this function derive_taps_and_gains
, we can put our attention on the function call at [8], where we find the function
named enlarge_work_buffers
, described earlier, which is taking the variable named number_of_object
as parameter. number_of_object
is initialized in [9] to the value of 1, and will keep this value for the whole execution. By relying on the value of the step_info_n
, read in [10], the variable step_info
will cause an integer overflow comparison with a signed integer at [11].
The consequence of this signed comparison is that the test at [12] will never pass, thus number_of_object
won’t be updated during conditions testing at [13].
Thus number_of_object
will always keep the value of 1 regardless the number in step_info_n
, leading to a buffer of 16 bytes only.
The heap overflow is happening once the value of step_info_n
is greater than 16.
The variable step_info_n
can be controlled from the file, in particular in the ATK marker segment (0xff79). Specific values may be present or not depending on the bitwise operations over the Satk
value.
The ATK marker could be decoded this way:
An unsigned short value for the Marker Code e.g (0xFF79)
An unsigned short value indicating the length segment (Total length of the data without including the 2 bytes corresponding to the marker code)
An unsigned short value indicating the Satk value.
The Satk value is a bitwise indicator to specify the filter category, reversible or irreversible criteria at least. Based on the presence or not, an extra field can be present in the flow of bytes before reaching the step_info_n
(i.e. Latk
) in some documentation.
0:000> !analyze -v
*******************************************************************************
* *
* Exception Analysis *
* *
*******************************************************************************
APPLICATION_VERIFIER_HEAPS_CORRUPTED_HEAP_BLOCK_START_STAMP (10)
Corrupted start stamp for heap block.
This happens for buffer underruns.
Arguments:
Arg1: 15bf1000, Heap handle used in the call.
Arg2: 1ce2dff0, Heap block involved in the operation.
Arg3: 42ce0000, Size of the heap block.
Arg4: 428a0000, Corrupted stamp value.
KEY_VALUES_STRING: 1
Key : Analysis.CPU.Sec
Value: 0
Key : Analysis.DebugAnalysisProvider.CPP
Value: Create: 8007007e on DESKTOP-NNJIVJA
Key : Analysis.DebugData
Value: CreateObject
Key : Analysis.DebugModel
Value: CreateObject
Key : Analysis.Elapsed.Sec
Value: 1
Key : Analysis.Memory.CommitPeak.Mb
Value: 145
Key : Analysis.System
Value: CreateObject
Key : Timeline.OS.Boot.DeltaSec
Value: 339
NTGLOBALFLAG: 2000000
APPLICATION_VERIFIER_FLAGS: 0
APPLICATION_VERIFIER_LOADED: 1
EXCEPTION_RECORD: (.exr -1)
ExceptionAddress: 00000000
ExceptionCode: 80000003 (Break instruction exception)
ExceptionFlags: 00000000
NumberParameters: 0
FAULTING_THREAD: 000019c4
PROCESS_NAME: kdu_render.exe
ERROR_CODE: (NTSTATUS) 0x80000003 - {EXCEPTION} Breakpoint A breakpoint has been reached.
EXCEPTION_CODE_STR: 80000003
IP_ON_HEAP: 77a96000
STACK_TEXT:
WARNING: Frame IP not in any known module. Following frames may be wrong.
0019dec0 77b1002c 737de1c4 ffffffff c0000421 0x77a96000
0019dec4 737de1c4 ffffffff c0000421 00000018 ntdll!NtTerminateProcess+0xc
0019def8 737dbc4c 00000010 737d1db0 15bf1000 verifier!VerifierStopMessage+0x344
0019df64 737dc06a 15bf1000 00000004 1ce2dff0 verifier!AVrfpDphReportCorruptedBlock+0x2fc
0019dfc0 737dc562 15bf1000 1ce2dff0 00000004 verifier!AVrfpDphCheckNormalHeapBlock+0x11a
0019dfe0 737dadb3 15bf1000 15dd0000 01001002 verifier!AVrfpDphNormalHeapFree+0x22
0019e004 77b8b519 15bf0000 01001002 1ce2dff0 verifier!AVrfDebugPageHeapFree+0xe3
0019e06c 77b32dc9 1ce2dff0 7d2cbc43 00000000 ntdll!RtlDebugFreeHeap+0x3e
0019e1c0 77ae4581 00000000 1ce2dff0 c24c0000 ntdll!RtlpFreeHeap+0x4e4a9
0019e210 736b016a 15bf0000 00000000 1ce2dff0 ntdll!RtlFreeHeap+0x201
0019e224 15a12d25 1ce2dff0 00000000 0019f1bc MSVCR100!free+0x1c
0019e248 15a90299 1ce2dff4 15ad14a4 0019f1bc kdu_v7AR!kdu_core::kdu_membroker::`default constructor closure'+0x5d5
0019f42c 15a44183 ff8461df 15eb7ff4 00000000 kdu_v7AR!kdu_core::kdu_synthesis::kdu_synthesis+0x259
0019f47c 15a4fd33 00000003 00000000 ff846167 kdu_v7AR!kdu_core::kdu_codestream_comment::put_text+0x2853
0019f4c4 00452681 0019f54c 00000003 00000000 kdu_v7AR!kdu_core::kdu_codestream::open_tile+0x123
0019f53c 00453aba 00000001 00000001 00000070 kdu_render+0x52681
0019f5e8 004568aa 00000001 00000004 ffffff0e kdu_render+0x53aba
0019f620 0043a5dd 1cdde4c0 0019f6d0 00000004 kdu_render+0x568aa
0019f6e4 00446322 0003e800 0019f70c 0019fcf8 kdu_render+0x3a5dd
0019f72c 00406e28 0003e800 0019f80c 00000000 kdu_render+0x46322
0019fe30 004084c7 15dfafe0 3f800000 00000000 kdu_render+0x6e28
0019ff2c 0045726c 00000005 15de8f90 15bfdf50 kdu_render+0x84c7
0019ff70 76f2fe09 003e9000 76f2fdf0 0019ffdc kdu_render!std::_Init_locks::operator=+0x780
0019ff80 77b0607d 003e9000 7d2ca25f 00000000 KERNEL32!BaseThreadInitThunk+0x19
0019ffdc 77b0604d ffffffff 77b245c3 00000000 ntdll!__RtlUserThreadStart+0x2f
0019ffec 00000000 0045738d 003e9000 00000000 ntdll!_RtlUserThreadStart+0x1b
STACK_COMMAND: ~0s ; .cxr ; kb
SYMBOL_NAME: kdu_v7ar!kdu_core::kdu_membroker::`default constructor closure'+5d5
MODULE_NAME: kdu_v7AR
IMAGE_NAME: kdu_v7AR.dll
FAILURE_BUCKET_ID: BREAKPOINT_AVRF_80000003_kdu_v7AR.dll!kdu_core::kdu_membroker::_default_constructor_closure_
OSPLATFORM_TYPE: x86
OSNAME: Windows 8
FAILURE_ID_HASH: {d1aecb61-a6ee-297f-2efa-e78a05d007a6}
Followup: MachineOwner
---------
2019-10-28 - Vendor Disclosure
2019-11-15 - Follow up with vendor
2019-12-01 - Vendor acknowledged issue under review
2019-12-11 - Public Release
Discovered by Aleksandar Nikolic and Emmanuel Tacheau of Cisco Talos.