Talos Vulnerability Report

TALOS-2023-1750

Accusoft ImageGear tiff_planar_adobe out-of-bounds write vulnerability

September 25, 2023
CVE Number

CVE-2023-32284

SUMMARY

An out-of-bounds write vulnerability exists in the tiff_planar_adobe functionality of Accusoft ImageGear 20.1. A specially crafted malformed file can lead to 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 20.1

PRODUCT URLS

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

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-119 - Improper Restriction of Operations within the Bounds of a Memory Buffer

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 such as DICOM, PDF, Microsoft Office and others.

A specially crafted TIFF file can lead to a heap-based buffer overflow in tiff_planar_adobe, due to a missing bounds check.

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

0:000> r
eax=00000000 ebx=0b086ff0 ecx=00000010 edx=0b087000 esi=0b9d0fb8 edi=00000044
eip=6e86d4a3 esp=0019da24 ebp=0019da38 iopl=0         nv up ei ng nz ac pe cy
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010297
igCore20d!IG_mpi_page_set+0x11383:
6e86d4a3 8802            mov     byte ptr [edx],al          ds:002b:0b087000=??

When we look at the edx memory allocation we can see the buffer allocated is quite small, only 10 bytes:

0:000> !heap -p -a edx
    address 0b087000 found in
    _DPH_HEAP_ROOT @ 4dc1000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                 b270bfc:          b086ff0               10 -          b086000             2000
    6ebaa8b0 verifier!AVrfDebugPageHeapAllocate+0x00000240
    773df29e ntdll!RtlDebugAllocateHeap+0x00000039
    77347170 ntdll!RtlpAllocateHeap+0x000000f0
    77346ecc ntdll!RtlpAllocateHeapInternal+0x0000104c
    77345e6e ntdll!RtlAllocateHeap+0x0000003e
    6ea11fa6 igCore20d!IG_GUI_page_title_set+0x0003e4d6
    6ea104aa igCore20d!IG_GUI_page_title_set+0x0003c9da
    6e8646ba igCore20d!IG_mpi_page_set+0x0000859a
    6e826c86 igCore20d!IG_comm_is_comp_exist+0x000048f6
    6e825f75 igCore20d!IG_comm_is_comp_exist+0x00003be5
    6e8604c4 igCore20d!IG_mpi_page_set+0x000043a4
    6e86588e igCore20d!IG_mpi_page_set+0x0000976e
    6e80c7f2 igCore20d!GPb_image_associate+0x00000092
    6e86d050 igCore20d!IG_mpi_page_set+0x00010f30
    6e84929e igCore20d!IG_cpm_profiles_reset+0x0000dfae
    6e96fc93 igCore20d!IG_mpi_page_set+0x00113b73
    6e969b9b igCore20d!IG_mpi_page_set+0x0010da7b
    6e8315b9 igCore20d!IG_image_savelist_get+0x00000b29
    6e8708bc igCore20d!IG_mpi_page_set+0x0001479c
    6e870239 igCore20d!IG_mpi_page_set+0x00014119
    6e805bc7 igCore20d!IG_load_file+0x00000047
    00402399 Fuzzme!fuzzme+0x00000019
    004026c0 Fuzzme!fuzzme+0x00000340
    00408407 Fuzzme!fuzzme+0x00006087
    75bf0099 KERNEL32!BaseThreadInitThunk+0x00000019
    77367b6e ntdll!__RtlUserThreadStart+0x0000002f
    77367b3e ntdll!_RtlUserThreadStart+0x0000001b

The call stack will indicate where this is happening:

STACK_TEXT:  
WARNING: Stack unwind information not available. Following frames may be wrong.
0019da38 6e84a238 00000001 0b9d0fb8 0b086ff0 igCore20d!IG_mpi_page_set+0x11383
0019da54 6e96b904 0019fc3c 0b9d0fb8 00000000 igCore20d!IG_cpm_profiles_reset+0xef48
0019f678 6e96fdb4 0019fc3c 10000021 0019f6d0 igCore20d!IG_mpi_page_set+0x10f7e4
0019f6a0 6e969b9b 0019fc3c 10000021 09046d68 igCore20d!IG_mpi_page_set+0x113c94
0019fbb4 6e8315b9 0019fc3c 09046d68 00000001 igCore20d!IG_mpi_page_set+0x10da7b
0019fbec 6e8708bc 00000000 09046d68 0019fc3c igCore20d!IG_image_savelist_get+0xb29
0019fe68 6e870239 00000000 05281fe0 00000001 igCore20d!IG_mpi_page_set+0x1479c
0019fe88 6e805bc7 00000000 05281fe0 00000001 igCore20d!IG_mpi_page_set+0x14119
0019fea8 00402399 05281fe0 0019febc 75befb60 igCore20d!IG_load_file+0x47
0019fec0 004026c0 05281fe0 0019fef8 051ddf40 Fuzzme!fuzzme+0x19
0019ff28 00408407 00000005 051d6f98 051ddf40 Fuzzme!fuzzme+0x340
0019ff70 75bf0099 0023a000 75bf0080 0019ffdc Fuzzme!fuzzme+0x6087
0019ff80 77367b6e 0023a000 6666f0ed 00000000 KERNEL32!BaseThreadInitThunk+0x19
0019ffdc 77367b3e ffffffff 77388ca1 00000000 ntdll!__RtlUserThreadStart+0x2f
0019ffec 00000000 0040848f 0023a000 00000000 ntdll!_RtlUserThreadStart+0x1b

The crash is happening in the function tiff_planar_adobe with the following pseudo code at LINE63:

LINE1   BOOLEAN tiff_planar_adobe(HIGEAR lpHigear,BYTE *raster_buffer,BYTE *at_pixpos,int size_raster_buffer
LINE2                            ,int loop_sample_per_pixel)
LINE3   {
LINE4     undefined4 uVar1;
LINE5     LPCHAR pCVar2;
LINE6     AT_INT AVar3;
LINE7     BOOLEAN BVar4;
LINE8     int bits_per_channel;
LINE9     int sample_per_pixel;
LINE10    int max_loop;
LINE11    int bit_depth_sums;
LINE12    undefined4 *_dest_buffer;
LINE13    undefined2 *puVar5;
LINE14    int loop_cnt;
LINE15    BYTE *oobw;
LINE16    uint uVar6;
LINE17    HIGEAR hiGear;
LINE18    
LINE19    hiGear = *(HIGEAR *)lpHigear->valid_image;
LINE20    if (lpHigear->lpfnDIBGet != 0) {
LINE21      uVar1 = getHeight(hiGear);
LINE22      pCVar2 = (LPCHAR)(*(code *)lpHigear->lpfnDIBGet)(lpHigear->IGDIBRunEnds,at_pixpos,uVar1);
LINE23      if (pCVar2 == (LPCHAR)0x0) {
LINE24        AVar3 = getHeight(hiGear);
LINE25        AF_err_record_set("..\\..\\..\\..\\Common\\Io\\dfltldcb.c",0x20d,-0x841,0,(AT_INT)at_pixpos,
LINE26                          AVar3,pCVar2);
LINE27        BVar4 = AF_error_check();
LINE28        return BVar4;
LINE29      }
LINE30    }
LINE31    bit_depth_sums = getWidth(hiGear);
LINE32    bits_per_channel = get_ptr_bits_per_channel_table(hiGear);
LINE33    sample_per_pixel = get_sample_per_pixel_table(hiGear);
LINE34    max_loop = size_raster_buffer / ((int)((bits_per_channel >> 0x1f & 7U) + bits_per_channel) >> 3);
LINE35    if (bit_depth_sums < max_loop) {
LINE36      max_loop = bit_depth_sums;
LINE37    }
LINE38    at_pixpos = GPb_image_row_pointer_get(hiGear,(AT_PIXPOS)at_pixpos);
LINE39    if (bits_per_channel == 8) {
[...]
LINE57      }
LINE58      else {
LINE59        oobw = at_pixpos + loop_sample_per_pixel;
LINE60        loop_cnt = 0;
LINE61        if (0 < max_loop) {
LINE62          do {
LINE63            *oobw = raster_buffer[loop_cnt];
LINE64            oobw = oobw + sample_per_pixel;
LINE65            loop_cnt = loop_cnt + 1;
LINE66          } while (loop_cnt < max_loop);
LINE67        }
LINE68      }
LINE69    }
[..]
LINE105 }

This is a do-while loop with a bound size controlled by the variable max_loop LINE66. The max_loop is computed at LINE34 and derived from two variables: size_raster_buffer and bits_per_channel. In our case bits_per_channel equals ‘8’, forcing max_loop to be controlled directly by size_raster_buffer

size_raster_buffer passed in argument to tiff_planar_adobe is computed earlier in function DIB1bit_packed_raster_size_get with following pseudo code :

LINE106 uint DIB1bit_packed_raster_size_get(HIGDIBINFO hdib)
LINE107 
LINE108 {
LINE109   void *local_10;
LINE110   undefined *puStack_c;
LINE111   undefined4 local_8;
LINE112   
LINE113   puStack_c = &LAB_10261950;
LINE114   local_10 = ExceptionList;
LINE115   ExceptionList = &local_10;
LINE116   local_8 = 0;
LINE117   if (hdib->nUnits != 1) {
LINE118     wrapper_thow_exception
LINE119               ((undefined *)0xfffffe6f,(char *)0x0,(undefined *)0x0,(undefined *)0x0,
LINE120                (undefined **)"..\\..\\..\\..\\Common\\Core\\c_DIB.cpp",(undefined *)0x23b);
LINE121   }
LINE122   ExceptionList = local_10;
LINE123   return hdib->size_X + 0x1f >> 3 & 0xfffffffc;
LINE124 }

At LINE123 we can see size_raster_buffer is derived from hdib->size_X, which corresponds to the value read from the file in TIFF tag ImageWidth.

The presence of some other tags like Planar and Compression is a prerequisite to trigger this issue.
As the loop is arbitrarily controlled and has no bounds checks, the command at LINE63 would write out-of-bounds on the stack, leading to memory corruption.

Crash Information

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


KEY_VALUES_STRING: 1

    Key  : AV.Fault
    Value: Write

    Key  : Analysis.CPU.Sec
    Value: 2

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

    Key  : Analysis.DebugData
    Value: CreateObject

    Key  : Analysis.DebugModel
    Value: CreateObject

    Key  : Analysis.Elapsed.Sec
    Value: 16

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

    Key  : Analysis.System
    Value: CreateObject

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

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


NTGLOBALFLAG:  2100000

PROCESS_BAM_CURRENT_THROTTLED: 0

PROCESS_BAM_PREVIOUS_THROTTLED: 0

APPLICATION_VERIFIER_FLAGS:  0

APPLICATION_VERIFIER_LOADED: 1

EXCEPTION_RECORD:  (.exr -1)
ExceptionAddress: 6e86d4a3 (igCore20d!IG_mpi_page_set+0x00011383)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 00000001
   Parameter[1]: 0b087000
Attempt to write to address 0b087000

FAULTING_THREAD:  00001a58

PROCESS_NAME:  Fuzzme.exe

WRITE_ADDRESS:  0b087000 

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:  0b087000

STACK_TEXT:  
WARNING: Stack unwind information not available. Following frames may be wrong.
0019da38 6e84a238 00000001 0b9d0fb8 0b086ff0 igCore20d!IG_mpi_page_set+0x11383
0019da54 6e96b904 0019fc3c 0b9d0fb8 00000000 igCore20d!IG_cpm_profiles_reset+0xef48
0019f678 6e96fdb4 0019fc3c 10000021 0019f6d0 igCore20d!IG_mpi_page_set+0x10f7e4
0019f6a0 6e969b9b 0019fc3c 10000021 09046d68 igCore20d!IG_mpi_page_set+0x113c94
0019fbb4 6e8315b9 0019fc3c 09046d68 00000001 igCore20d!IG_mpi_page_set+0x10da7b
0019fbec 6e8708bc 00000000 09046d68 0019fc3c igCore20d!IG_image_savelist_get+0xb29
0019fe68 6e870239 00000000 05281fe0 00000001 igCore20d!IG_mpi_page_set+0x1479c
0019fe88 6e805bc7 00000000 05281fe0 00000001 igCore20d!IG_mpi_page_set+0x14119
0019fea8 00402399 05281fe0 0019febc 75befb60 igCore20d!IG_load_file+0x47
0019fec0 004026c0 05281fe0 0019fef8 051ddf40 Fuzzme!fuzzme+0x19
0019ff28 00408407 00000005 051d6f98 051ddf40 Fuzzme!fuzzme+0x340
0019ff70 75bf0099 0023a000 75bf0080 0019ffdc Fuzzme!fuzzme+0x6087
0019ff80 77367b6e 0023a000 6666f0ed 00000000 KERNEL32!BaseThreadInitThunk+0x19
0019ffdc 77367b3e ffffffff 77388ca1 00000000 ntdll!__RtlUserThreadStart+0x2f
0019ffec 00000000 0040848f 0023a000 00000000 ntdll!_RtlUserThreadStart+0x1b


STACK_COMMAND:  ~0s ; .cxr ; kb

SYMBOL_NAME:  igCore20d!IG_mpi_page_set+11383

MODULE_NAME: igCore20d

IMAGE_NAME:  igCore20d.dll

FAILURE_BUCKET_ID:  INVALID_POINTER_WRITE_AVRF_c0000005_igCore20d.dll!IG_mpi_page_set

OS_VERSION:  10.0.19041.1

BUILDLAB_STR:  vb_release

OSPLATFORM_TYPE:  x86

OSNAME:  Windows 10

FAILURE_ID_HASH:  {fe8f80f8-683f-d41f-7c33-712a409d5fb5}

Followup:     MachineOwner
---------
VENDOR RESPONSE

Release notes from the vendor can be found here:

https://help.accusoft.com/ImageGear/v20.3/Windows/DLL/webframe.html#release-notes.html

https://help.accusoft.com/ImageGear/v20.3/Linux/webframe.html#release-notes.html

TIMELINE

2023-05-24 - Vendor Disclosure
2023-09-20 - Vendor Patch Release
2023-09-25 - Public Release

Credit

Discovered by Emmanuel Tacheau of Cisco Talos.