Talos Vulnerability Report

TALOS-2020-1095

Accusoft ImageGear TIFF handle_COMPRESSION_PACKBITS memory corruption vulnerability

September 1, 2020
CVE Number

CVE-2020-6151

SUMMARY

A memory corruption vulnerability exists in the TIFF handle_COMPRESSION_PACKBITS functionality of Accusoft ImageGear 19.7. A specially crafted malformed file can cause a memory corruption. An attacker can provide a malicious file to trigger this vulnerability.

CONFIRMED VULNERABLE VERSIONS

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

Accusoft ImageGear 19.7

PRODUCT URLS

ImageGear - https://www.accusoft.com/products/imagegear-collection/

CVSSv3 SCORE

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

CWE

CWE-704 - Incorrect Type Conversion or Cast

DETAILS

The ImageGear library is a document imaging developer toolkit that offers image conversion, creation, editing, annotation and more. It supports more than 100 formats, including many image formats, DICOM, PDF, Microsoft Office and others.

There is a vulnerability in the handle_COMPRESSION_PACKBITS function, due to an integer overflow caused by a missing or wrong cast operation.

A specially crafted TIFF file can lead to an out-of-bounds write which can result in a memory corruption.

Trying to load a malformed TIFF file, we end up in the following situation:

First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000178 ecx=fffffe9e edx=ffffff21 esi=ffffff21 edi=0d7f1000
eip=61c91323 esp=0093edf0 ebp=0093ee04 iopl=0         nv up ei ng nz na pe cy
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010287
MSVCR110!memset+0x24:
61c91323 f3aa            rep stos byte ptr es:[edi]

0:000> !heap -p -a edi
    address 0d7f1000 found in
    _DPH_HEAP_ROOT @ 9f1000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                 d7a2e38:          d7f0e88              178 -          d7f0000             2000
    626aab70 verifier!AVrfDebugPageHeapAllocate+0x00000240
    774e909b ntdll!RtlDebugAllocateHeap+0x00000039
    7743bbad ntdll!RtlpAllocateHeap+0x000000ed
    7743b0cf ntdll!RtlpAllocateHeapInternal+0x0000022f
    7743ae8e ntdll!RtlAllocateHeap+0x0000003e
    61c6dcff MSVCR110!malloc+0x00000049
    61da61de igCore19d!AF_memm_alloc+0x0000001e
    61eb8883 igCore19d!IG_mpi_page_set+0x0010cb33
    61eb842b igCore19d!IG_mpi_page_set+0x0010c6db
    61ebc11b igCore19d!IG_mpi_page_set+0x001103cb
    61eb604b igCore19d!IG_mpi_page_set+0x0010a2fb
    61d810d9 igCore19d!IG_image_savelist_get+0x00000b29
    61dc0557 igCore19d!IG_mpi_page_set+0x00014807
    61dbfeb9 igCore19d!IG_mpi_page_set+0x00014169
    61d55777 igCore19d!IG_load_file+0x00000047
    00f61372 Fuzzme!fuzzme+0x00000162 
    00f61778 Fuzzme!main+0x00000368 
    00f61f7d Fuzzme!__scrt_common_main_seh+0x000000fa 
    760e6359 KERNEL32!BaseThreadInitThunk+0x00000019
    77467c24 ntdll!__RtlUserThreadStart+0x0000002f
    77467bf4 ntdll!_RtlUserThreadStart+0x0000001b

As we can see, an out-of-bounds operation occurred.
The pseudo-code of the vulnerable function looks like this:

int handle_COMPRESSION_PACKBITS (mys_temporary_buffer *buffer_src,          [1]
                                 byte param_2,                              [4]
                                 byte *dst_buffer,                          [2]
                                 int parse_TiffBitRevTable,                 [3]
                                 int param_5)
{
  uint size_dst_buffer;
  int status;
  byte *src_data;
  uint num_bytes;
  uint offset_buffer;
  int value_to_set;
  
  size_dst_buffer = param_2;
  offset_buffer = 0;
  if (param_2 == 0) {
    return 0;
  }
  do {                                                                                              [5]
                    /* get a byte from param_1 */
    status = myf_get_one_byte_into_dst(buffer_src,param_2 + 3);                                     [7]
    if (status == 0) {
                    /* no more byte left into buffer_src */
      return 0;
    }
    if (parse_TiffBitRevTable != 0) {
                    /* perform bit swift */
      param_2[3] = TIFFBitRevTable[param_2[3]];                                                     [8]
      param_2 = param_2 & 0xffffff | (uint)param_2[3] << 0x18;
    }
    num_bytes = offset_buffer;
    if (param_2[3] != 0x80) {
      if ((char)param_2[3] < '\0') {                                                                [9]
                    /* get another byte */
        myf_get_one_byte_into_dst(buffer_src,(undefined *)&value_to_set);
        num_bytes = (1 - (uint)param_2[3]) + offset_buffer;                                         [10]
        if (size_dst_buffer < num_bytes) {                                                          [12]
          if (param_5 != 0) {
            return 1;
          }
          wrapper_memset(dst_buffer + offset_buffer,value_to_set,size_dst_buffer - offset_buffer);
          return 0;
        }
                    /* lead to crash */
        wrapper_memset(dst_buffer + offset_buffer,value_to_set,1 - (uint)param_2[3]);               [13]
      }
      else {
        num_bytes = (uint)param_2[3] + 1;
        if (size_dst_buffer < num_bytes + offset_buffer) {
          if (param_5 != 0) {
            return 1;
          }
          num_bytes = size_dst_buffer - offset_buffer;
        }
        src_data = myf_tiff_read_data(buffer_src,num_bytes);
        if (src_data != (byte *)0x0) {
          wrapper_memcpy(dst_buffer + offset_buffer,src_data,num_bytes);
        }
        num_bytes = offset_buffer + num_bytes;
      }
    }
    offset_buffer = num_bytes;                                                                      [11]
                    /* continue until end of buffer */
  } while (num_bytes < size_dst_buffer);                                                            [6]
  return 0;
}

The function handle_COMPRESSION_PACKBITS decompresses a source buffer buffer_src [1] into a destination buffer dst_buffer [2], performing some swap operations if needed (depending on the value of parse_TiffBitRevTable [3]).
The destination buffer size is stored into param_2 [4]. The source buffer contains the image bytes, and the size used to allocate the buffer is controlled directly by the tags present in the file, and is extracted earlier in the program.

The do-while loop from [5] to [6] is processing each byte of the source buffer read at [7]. Depending on the parse_TiffBitRevTable [3] parameter, each byte may be bit-swapped using the table TIFFBitRevTable [8].

Then the interesting part is coming up with the test performed in [9], which checks if the result (in param2) is negative or not. It’s important to notice here that the value is casted to char.
At [10] the length of the buffer is computed, but this time param_2 is used as an unsigned integer. This makes 1-(uint)param_2[3] become a very large number, and, depending on the value of offset_buffer, increases consistently [11], leading to an integer overflow.
Thus, the result stored into num_bytes against the size of the buffer could lead an incorrect test in [12], and cause the program to execute the memset in [13] with a very large size parameter, corrupting the memory past the end of the buffer.

Crash Information

Crash output:

0:000> !analyze -v
*******************************************************************************
*                                                                             *
*                        Exception Analysis                                   *
*                                                                             *
*******************************************************************************


KEY_VALUES_STRING: 1

    Key  : AV.Fault
    Value: Write

    Key  : Analysis.CPU.Sec
    Value: 1

    Key  : Analysis.DebugAnalysisProvider.CPP
    Value: Create: 8007007e on DESKTOP-4DAOCFH

    Key  : Analysis.DebugData
    Value: CreateObject

    Key  : Analysis.DebugModel
    Value: CreateObject

    Key  : Analysis.Elapsed.Sec
    Value: 17

    Key  : Analysis.Memory.CommitPeak.Mb
    Value: 87

    Key  : Analysis.System
    Value: CreateObject

    Key  : Timeline.OS.Boot.DeltaSec
    Value: 495

    Key  : Timeline.Process.Start.DeltaSec
    Value: 69


ADDITIONAL_XML: 1

NTGLOBALFLAG:  2100000

APPLICATION_VERIFIER_FLAGS:  0

APPLICATION_VERIFIER_LOADED: 1

EXCEPTION_RECORD:  (.exr -1)
ExceptionAddress: 61c91323 (MSVCR110!memset+0x00000024)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 00000001
   Parameter[1]: 0d7f1000
Attempt to write to address 0d7f1000

FAULTING_THREAD:  000034a8

PROCESS_NAME:  Fuzzme.exe

WRITE_ADDRESS:  0d7f1000 

ERROR_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%p referenced memory at 0x%p. The memory could not be %s.

EXCEPTION_CODE_STR:  c0000005

EXCEPTION_PARAMETER1:  00000001

EXCEPTION_PARAMETER2:  0d7f1000

STACK_TEXT:  
0093edf0 61d4fc03 0d7f0f7d 00000000 ffffff21 MSVCR110!memset+0x24
WARNING: Stack unwind information not available. Following frames may be wrong.
0093ee04 61eb92e2 0d7f0f7d 00000000 ffffff21 igCore19d+0xfc03
0093ee2c 61eb8977 0c33efc8 e0000178 0d7f0e88 igCore19d!IG_mpi_page_set+0x10d592
0093ee80 61eb842b 0093f47c 10000028 0093ef10 igCore19d!IG_mpi_page_set+0x10cc27
0093eeb8 61ebc11b 0093f47c 10000028 00000000 igCore19d!IG_mpi_page_set+0x10c6db
0093eee0 61eb604b 0093f47c 10000028 1150ad68 igCore19d!IG_mpi_page_set+0x1103cb
0093f3f4 61d810d9 0093f47c 1150ad68 00000001 igCore19d!IG_mpi_page_set+0x10a2fb
0093f42c 61dc0557 00000000 1150ad68 0093f47c igCore19d!IG_image_savelist_get+0xb29
0093f6a8 61dbfeb9 00000000 09a87f20 00000001 igCore19d!IG_mpi_page_set+0x14807
0093f6c8 61d55777 00000000 09a87f20 00000001 igCore19d!IG_mpi_page_set+0x14169
0093f6e8 00f61372 09a87f20 0093f704 09a7eeb8 igCore19d!IG_load_file+0x47
0093f710 00f61778 09a87f20 09a85fe0 00000021 Fuzzme!fuzzme+0x162
0093f7a0 00f61f7d 00000005 09a7eeb8 0603ff38 Fuzzme!main+0x368
0093f7e8 760e6359 0067f000 760e6340 0093f854 Fuzzme!__scrt_common_main_seh+0xfa
0093f7f8 77467c24 0067f000 b04a9385 00000000 KERNEL32!BaseThreadInitThunk+0x19
0093f854 77467bf4 ffffffff 77488fdd 00000000 ntdll!__RtlUserThreadStart+0x2f
0093f864 00000000 00f62005 0067f000 00000000 ntdll!_RtlUserThreadStart+0x1b


STACK_COMMAND:  ~0s ; .cxr ; kb

SYMBOL_NAME:  MSVCR110!memset+24

MODULE_NAME: MSVCR110

IMAGE_NAME:  MSVCR110.dll

FAILURE_BUCKET_ID:  INVALID_POINTER_WRITE_AVRF_c0000005_MSVCR110.dll!memset

OS_VERSION:  10.0.18362.1

BUILDLAB_STR:  19h1_release

OSPLATFORM_TYPE:  x86

OSNAME:  Windows 10

FAILURE_ID_HASH:  {cd57060f-2f30-c89b-a5b4-329faa47a0c1}

Followup:     MachineOwner
---------
TIMELINE

2020-06-23 - Vendor Disclosure
2020-09-01 - Public Release

Credit

Discovered by Emmanuel Tacheau of Cisco Talos.