Talos Vulnerability Report

TALOS-2019-0933

Kakadu Software SDK ATK marker code execution vulnerability

December 11, 2019
CVE Number

CVE-2019-5144

Summary

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.

Tested Versions

Kakadu Software SDK 7.10.2 - Windows

Product URLs

https://kakadusoftware.com/downloads/

CVSSv3 Score

8.1 - CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H

CWE

CWE-191: Integer Underflow (Wrap or Wraparound)

Details

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]:

  • one object kernel, a structure to store informations about the way the discrete wavelet transforms
  • one which indicates the number of objects through the variable num_objects to help computing memory needs

It'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.

Crash Information

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
---------

Timeline

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

Credit

Discovered by Aleksandar Nikolic and Emmanuel Tacheau of Cisco Talos.