Talos Vulnerability Report

TALOS-2023-1760

Accusoft ImageGear pictwread heap-based buffer overflow vulnerability

September 25, 2023
CVE Number

CVE-2023-35002

SUMMARY

A heap-based buffer overflow vulnerability exists in the pictwread functionality of Accusoft ImageGear 20.1. A specially crafted malformed file can lead to arbitrary code execution. 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

9.8 - CVSS:3.1/AV:N/AC:L/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 PICT file can lead to a heap-based buffer overflow in pictwread, due to a missing bounds check.

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

eax=000000b9 ebx=05f20feb ecx=00000008 edx=0000001d esi=0c63554c edi=05f21000
eip=6e0e0f64 esp=0019f634 ebp=0019f648 iopl=0         nv up ei pl nz na pe cy
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000207
igCore20d!IG_GUI_page_title_set+0x3d494:
6e0e0f64 f3aa            rep stos byte ptr es:[edi]

The copy is happening in the _memset function.

6e0e0f40  char* _memset(char* dst, char val, int32_t size)

6e0e0f40  8b54240c           mov     edx, dword [esp+0xc {size}]
6e0e0f44  8b4c2404           mov     ecx, dword [esp+0x4 {dst}]
6e0e0f48  85d2               test    edx, edx
6e0e0f4a  747f               je      0x6e0e0fcb

6e0e0f4c  0fb6442408         movzx   eax, byte [esp+0x8 {val}]
6e0e0f51  0fba2554d71d6e01   bt      dword [0x6e1dd754], 0x1
6e0e0f59  730d               jae     0x6e0e0f68

6e0e0f5b  8b4c240c           mov     ecx, dword [esp+0xc {size}]
6e0e0f5f  57                 push    edi {__saved_ebx}
6e0e0f60  8b7c2408           mov     edi, dword [esp+0x8 {dst}]
6e0e0f64  f3aa               rep stosb byte [edi]  {0x0}
6e0e0f66  eb5d               jmp     0x6e0e0fc5

To determine the buffer allocation and its size, we have to investigate the callstack.

0:000> kb
 # ChildEBP RetAddr      Args to Child              
00 0019f648 6e00c812     05f20feb 000000b9 0000001d igCore20d!IG_GUI_page_title_set+0x3d494
01 0019f668 6e00b230     05f20f88 05f21008 0c635558 igCore20d!IG_mpi_page_set+0xe06f2
02 0019f734 6e00a321     0019fc3c 1000001e 106a0ff8 igCore20d!IG_mpi_page_set+0xdf110
03 0019fbb4 6df015b9     0019fc3c 106a0ff8 00000001 igCore20d!IG_mpi_page_set+0xde201
04 0019fbec 6df408bc     00000000 106a0ff8 0019fc3c igCore20d!IG_image_savelist_get+0xb29
05 0019fe68 6df40239     00000000 05b36fe0 00000001 igCore20d!IG_mpi_page_set+0x1479c
06 0019fe88 6ded5bc7     00000000 05b36fe0 00000001 igCore20d!IG_mpi_page_set+0x14119
07 0019fea8 00402399     05b36fe0 0019febc 759ffb80 igCore20d!IG_load_file+0x47
08 0019fec0 004026c0     05b36fe0 0019fef8 05a9cf50 Fuzzme!fuzzme+0x19
09 0019ff28 00408407     00000005 05a96f90 05a9cf50 Fuzzme!fuzzme+0x340
0a 0019ff70 75a000c9     00324000 75a000b0 0019ffdc Fuzzme!fuzzme+0x6087
0b 0019ff80 77ba7b4e     00324000 4e3b0cdc 00000000 KERNEL32!BaseThreadInitThunk+0x19
0c 0019ffdc 77ba7b1e     ffffffff 77bc8c8a 00000000 ntdll!__RtlUserThreadStart+0x2f
0d 0019ffec 00000000     0040848f 00324000 00000000 ntdll!_RtlUserThreadStart+0x1b

The first return address 6e00c812 corresponds to the following pseudo-code.

6e00c790  int32_t __stdcall perform_memcpy_or_memset(char* dst, char* src, void* sz_src, void* sz_dst)
6e00c790  {
6e00c79a      char* l_dst = dst;
6e00c79e      char* l_src = src;
6e00c7a1      char* l_max_src = ((char*)sz_src + l_src);
6e00c7a3      char* l_max_dest = ((char*)sz_dst + l_dst);
6e00c7a8      sz_src = l_max_src;
6e00c7ab      src = l_max_dest;  // store l_max_dest into src var
6e00c7b0      if (l_src >= (l_max_src - 1))
6e00c7a5      {
6e00c835          return 0;
6e00c835      }
6e00c7b5      while (l_dst < l_max_dest)
6e00c7b3      {
6e00c7b7          char byte_value = *(uint8_t*)l_src;
6e00c7bb          if (byte_value < 128)
6e00c7b9          {
6e00c7c0              int32_t size = (((uint32_t)byte_value) + 1);
6e00c7c6              if (&l_dst[size] > l_max_dest)
6e00c7c4              {
6e00c7ca                  size = (l_max_dest - l_dst);
6e00c7ca              }
6e00c7d3              if (&l_src[(1 + size)] > l_max_src)
6e00c7d1              {
6e00c7d9                  size = ((l_max_src - l_src) - 1);
6e00c7d9              }
6e00c7de              OS_memcpy(l_dst, &l_src[1], size);
6e00c7e3              l_max_dest = src;
6e00c7e6              l_max_src = sz_src;
6e00c7e9              l_dst = &l_dst[size];
6e00c7eb              l_src = &l_src[(1 + size)];
6e00c7eb          }
6e00c7ef          else if (byte_value <= 128)
6e00c7b9          {
6e00c81f              l_src = &l_src[1];
6e00c81f          }
6e00c7f9          else
6e00c7f9          {
6e00c7f9              void* l_size = (0x101 - ((uint32_t)byte_value));
6e00c800              if (((char*)l_size + l_dst) > l_max_dest)
6e00c7fe              {
6e00c804                  l_size = (l_max_dest - l_dst);
6e00c804              }
6e00c80d              OS_memset(l_dst, l_src[1], l_size);
6e00c812              l_max_dest = src;
6e00c815              l_max_src = sz_src;
6e00c818              l_dst = (l_dst + l_size);
6e00c81a              l_src = &l_src[2];
6e00c81a          }
6e00c825          if (l_src >= (l_max_src - 1))
6e00c820          {
6e00c825              break;
6e00c825          }
6e00c825      }
6e00c82d      return 0;
6e00c82d  }

Where the OS_memsetcall happens at 6e00c80d. The OS_memset is a wrapper leading to _memset.
The destination memory is represented by l_dst , and operations happen in a while loop controlled by l_max_dest at 6e00c7b5.
l_dst is set first with the first parameter dst at 6e00c79a. Notice also the l_max_dest is computed from the 4th argument passed to the function sz_dst at 6e00c7a3.

Depending on byte_value check at 6e00c7bb, the code execution may also lead to OS_memcpy at 6e00c7de. byte_value is read from l_src and corresponds to our second function parameter at 6e00c79e.
We’ll discuss later to this point.

In order to get a better idea of the size of dstand where is allocated, we can use time travel debugging and see the memory address 05f20f88.
Confirmed also by the callstack, as stack kept showing correct arguments still here.

0:000> kb
 # ChildEBP RetAddr      Args to Child              
00 0019f648 6e00c812     05f20feb 000000b9 0000001d igCore20d!IG_GUI_page_title_set+0x3d494
01 0019f668 6e00b230     05f20f88 05f21008 0c635558 igCore20d!IG_mpi_page_set+0xe06f2

Parsing heap allocation with windows structure, we can see the RequestedSize of the buffer set to 0x78.

0:000> dt _DPH_BLOCK_INFORMATION 05f20f88-20
ntdll!_DPH_BLOCK_INFORMATION
   +0x000 StartStamp       : 0xabcdbbbb
   +0x004 Heap             : 0x040e1000 Void
   +0x008 RequestedSize    : 0x78
   +0x00c ActualSize       : 0x1000
   +0x010 FreeQueue        : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x010 FreePushList     : _SINGLE_LIST_ENTRY
   +0x010 TraceIndex       : 0
   +0x018 StackTrace       : 0x0427e16c Void
   +0x01c EndStamp         : 0xdcbabbbb

We could go through StackTrace to track the memory allocation, but also the call stack to identify where the call is coming from.
This happens in a very large function named pictwread at LINE263 with the following pseudo code

LINE1   int32_t __stdcall pictwread(struct mys_table_func* mys_table_func, void* heap_ptr, void* arg3, struct pict_header* pict_header, void* lpHdib)
LINE2   {
LINE3       int32_t __saved_ebp;
LINE4       int32_t eax_1 = (__security_cookie ^ &__saved_ebp);
LINE5       int32_t var_40 = 0;
LINE6       struct io_buffer l_io_buffer;
LINE7       l_io_buffer.buffer_size = 0;
LINE8       DIB_width_get(lpHdib);
LINE9       int32_t l_height_get_from_dib = DIB_height_get(lpHdib);
LINE10      int32_t* l_raster_size_get_from_dib = IO_raster_size_get(lpHdib);
LINE11      struct opcod_record* opcod_record = pict_header->buffer_opcod_record;
LINE12      int32_t l_bounds_y_value_top_left = ((int32_t)opcod_record->bounds.y_value_top_left);
LINE13      int32_t l_bounds_height = (((int32_t)opcod_record->bounds.y_value_bottom_right) - l_bounds_y_value_top_left);
LINE14      int32_t l_computed_size = (l_bounds_height * 5);
LINE15      void* ecx;
LINE16      if (l_computed_size < l_raster_size_get_from_dib)
LINE17      {
LINE18          ecx = AF_err_record_set(l_bounds_y_value_top_left, "..\..\..\..\Common\Formats\pctwr…", 0x2f6, 0xfffffe6c, 0, l_raster_size_get_from_dib, l_bounds_height, nullptr);
LINE19      }
LINE20      else
LINE21      {
LINE22          uint32_t pixelSize = ((uint32_t)opcod_record->pixelSize);
LINE23          uint32_t component_count = ((uint32_t)opcod_record->component_count);
LINE24          int32_t* l_error_IO_DIB_create_ex_status;
LINE25          void* ecx_1;
LINE26          l_error_IO_DIB_create_ex_status = IO_DIB_create_ex(mys_table_func, lpHdib);
LINE27          enum BOOLTYPE l_tmp_error_status = l_error_IO_DIB_create_ex_status;
LINE28          if (l_error_IO_DIB_create_ex_status != 0)
LINE29          {
LINE30              enum BOOLTYPE eax_3 = AF_err_record_set(ecx_1, "..\..\..\..\Common\Formats\pctwr…", 0x2ff, 0xfffff660, 0, 0, 0, nullptr);
LINE31              quit_program((eax_1 ^ &__saved_ebp));
LINE32              return eax_3;
LINE33          }
LINE34          int32_t var_bc_4 = 0x301;
LINE35          char const* const var_c0_2 = "..\..\..\..\Common\Formats\pctwr…";
LINE36          int32_t var_bc_5 = 0x302;
LINE37          char const* const var_c0_3 = "..\..\..\..\Common\Formats\pctwr…";
LINE38          void* l_buffer_sz_raster_size = AF_memm_alloc(heap_ptr, l_raster_size_get_from_dib);
LINE39          void* l_buffer_computed_size = AF_memm_alloc(heap_ptr, l_computed_size);
LINE40          SIZE_T l_raster_size_by_height = (l_raster_size_get_from_dib * l_height_get_from_dib);
LINE41          int32_t var_bc_6 = 0x304;
LINE42          char const* const var_c0_4 = "..\..\..\..\Common\Formats\pctwr…";
LINE43          void* l_buffer_sz_raster_size_by_height = AF_memm_alloc(heap_ptr, l_raster_size_by_height);
LINE44          int32_t var_bc_7 = 0x306;
LINE45          char const* const var_c0_5 = "..\..\..\..\Common\Formats\pctwr…";
LINE46          void* l_buffer_sz_raster_size_by_height_2;
LINE47          void* ecx_4;
LINE48          l_buffer_sz_raster_size_by_height_2 = AF_memm_alloc(heap_ptr, l_raster_size_by_height);
LINE49          if (((l_buffer_sz_raster_size == 0 || (l_buffer_sz_raster_size != 0 && l_buffer_computed_size == 0)) || ((l_buffer_sz_raster_size != 0 && l_buffer_computed_size != 0) && l_buffer_sz_raster_size_by_height == 0)))
LINE50          {
LINE51              l_tmp_error_status = AF_err_record_set(ecx_4, "..\..\..\..\Common\Formats\pctwr…", 0x309, 0xfffffc18, 0, 0, 0, nullptr);
LINE52          }
LINE53          if ((((l_buffer_sz_raster_size != 0 && l_buffer_computed_size != 0) && l_buffer_sz_raster_size_by_height != 0) && (pixelSize == 0x20 && component_count == 4)))
LINE54          {
LINE55              var_40 = 1;
LINE56          }
LINE57          void* l__buffer_sz_raster_size_by_height = l_buffer_sz_raster_size_by_height;
LINE58          OS_memset(l__buffer_sz_raster_size_by_height, 0, l_raster_size_by_height);
LINE59          int32_t l_heap_ptr = heap_ptr;
LINE60          enum BOOLTYPE b_Iob_init_status = IOb_init(mys_table_func, l_heap_ptr, &l_io_buffer, 0x5000, 1);
LINE61          enum BOOLTYPE err_status = (l_tmp_error_status + b_Iob_init_status);
LINE62          if (l_tmp_error_status == (-b_Iob_init_status))
LINE63          {
LINE64              struct pict_header* l_pict_header = pict_header;
LINE65              IO_attribute_set(mys_table_func, 4, &l_pict_header->original_horizontal_resolution);
LINE66              int32_t l_opcod_rec_processed = 0;
LINE67              void* l_opcod_rec_saved = nullptr;
LINE68              int32_t l_opcod_rec_processed_saved = 0;
LINE69              struct opcod_record* l_opcode_rec = nullptr;
LINE70              while (l_opcod_rec_processed < l_pict_header->num_opcod_record)
LINE71              {
LINE72                  struct opcod_record* edx_1 = (l_pict_header->buffer_opcod_record + l_opcod_rec_saved);
LINE73                  uint32_t l_component_count = ((uint32_t)edx_1->component_count);
LINE74                  uint32_t l_max_component_count = l_component_count;
LINE75                  if (l_component_count >= 3)
LINE76                  {
LINE77                      l_max_component_count = 3;
LINE78                  }
LINE79                  int32_t l_index_component = 0;
LINE80                  int32_t l_table_component_size;
LINE81                  if (l_max_component_count > 0)
LINE82                  {
LINE83                      do
LINE84                      {
LINE85                          &l_table_component_size[l_index_component] = ((uint32_t)edx_1->component_size);
LINE86                          l_index_component = (l_index_component + 1);
LINE87                      } while (l_index_component < l_max_component_count);
LINE88                  }
LINE89                  int32_t l_dstRect_heigth = (((int32_t)edx_1->dstRect.y_value_bottom_right) - ((int32_t)edx_1->dstRect.y_value_top_left));
LINE90                  int32_t l_dstRect_width = (((int32_t)edx_1->dstRect.x_value_bottom_right) - ((int32_t)edx_1->dstRect.x_value_top_left));
LINE91                  void* l_computed_raster_size = IO_raster_size_calc(l_dstRect_heigth, l_max_component_count, &l_table_component_size);
LINE92                  uint32_t var_48_1;
LINE93                  uint32_t l_size_dest;
LINE94                  if (pixelSize != 1)
LINE95                  {
LINE96                      uint32_t eax_14;
LINE97                      eax_14 = DIBStd_raster_size_calc_simple(l_dstRect_heigth, l_max_component_count, ((uint32_t)*(uint16_t*)(&pict_header->buffer_opcod_record->component_size + l_opcod_rec_saved)));
LINE98                      l_size_dest = eax_14;
LINE99                      var_48_1 = eax_14;
LINE100                 }
LINE101                 else
LINE102                 {
LINE103                     l_size_dest = DIB1bit_packed_raster_size_calc(l_dstRect_heigth);
LINE104                     var_48_1 = l_size_dest;
LINE105                 }
LINE106                 if (l_size_dest > l_raster_size_get_from_dib)
LINE107                 {
LINE108                     AF_err_record_set(l_computed_raster_size, "..\..\..\..\Common\Formats\pctwr…", 0x348, 0xfffff7cc, 0, 0, 0, nullptr);
LINE109                     break;
LINE110                 }
LINE111                 struct opcod_record* l_buffer_opcod_record = pict_header->buffer_opcod_record;
LINE112                 int32_t edx_3 = (((int32_t)*(uint16_t*)(&*(uint16_t*)((char*)l_buffer_opcod_record->bounds)[6] + l_opcod_rec_saved)) - ((int32_t)*(uint16_t*)(&*(uint16_t*)((char*)l_buffer_opcod_record->bounds)[2] + l_opcod_rec_saved)));
LINE113                 IOb_seek(&l_io_buffer, *(uint32_t*)(&l_buffer_opcod_record->offset + l_opcod_rec_saved), SEEK_SET);
LINE114                 OS_memset(l_buffer_sz_raster_size, 0, l_raster_size_get_from_dib);
LINE115                 if (pixelSize != 0x18)
LINE116                 {
LINE117                     l_size_dest = ((uint32_t)*(uint16_t*)(&l_opcode_rec->rowBytes + pict_header->buffer_opcod_record));
LINE118                 }
LINE119                 struct opcod_record** l_opcod_record_1 = pict_header->buffer_opcod_record;
LINE120                 uint32_t l_size_dest_saved = l_size_dest;
LINE121                 uint32_t edx_5 = ((uint32_t)*(uint16_t*)(&l_opcode_rec->packType + l_opcod_record_1));
LINE122                 int32_t* l_raster_size_get_from_dib_saved = l_raster_size_get_from_dib;
LINE123                 int32_t var_54_2 = (((int32_t)*(uint16_t*)(&*(uint16_t*)((char*)l_opcode_rec->dstRect)[6] + l_opcod_record_1)) - ((int32_t)*(uint16_t*)(&*(uint16_t*)((char*)l_opcode_rec->dstRect)[2] + l_opcod_record_1)));
LINE124                 void* l_buffer_raster_size = l_buffer_sz_raster_size;
LINE125                 int32_t l_dstRect_width_bound = 0;
LINE126                 l_io_buffer.field_34 = edx_5;
LINE127                 int32_t l_index_loop = 0;
LINE128                 if (err_status == FALSE)
LINE129                 {
LINE130                     uint32_t l_pixelSize = pixelSize;
LINE131                     while (l_dstRect_width_bound < l_dstRect_width)
LINE132                     {
LINE133                         int32_t var_d0_2;
LINE134                         void* ecx_7;
LINE135                         void* esi_6;
LINE136                         if ((l_size_dest >= 8 && (edx_5 != 1 && (edx_5 != 2 || (edx_5 == 2 && l_pixelSize < 0x18)))))
LINE137                         {
LINE138                             void* l_size_src;
LINE139                             if (l_size_dest <= 0xfa)
LINE140                             {
LINE141                                 char var_25;
LINE142                                 int32_t eax_22;
LINE143                                 eax_22 = IOb_byte_read(&l_io_buffer, &var_25);
LINE144                                 if (eax_22 == 0)
LINE145                                 {
LINE146                                     int32_t var_bc_29 = 0;
LINE147                                     int32_t var_c0_24 = 0;
LINE148                                     int32_t var_c4_23 = 0;
LINE149                                     int32_t var_c8_16 = 0;
LINE150                                     int32_t var_cc_8 = 0xfffff7cc;
LINE151                                     var_d0_2 = 0x38b;
LINE152                                     goto error_handling;
LINE153                                 }
LINE154                                 l_size_src = ((uint32_t)var_25);
LINE155                             }
LINE156                             else
LINE157                             {
LINE158                                 int16_t var_58;
LINE159                                 int32_t eax_21;
LINE160                                 eax_21 = IOBuff_Read_ushort(l_pixelSize, &l_io_buffer, &var_58);
LINE161                                 if (eax_21 == 0)
LINE162                                 {
LINE163                                     int32_t var_bc_27 = 0;
LINE164                                     int32_t var_c0_22 = 0;
LINE165                                     int32_t var_c4_21 = 0;
LINE166                                     int32_t var_c8_14 = 0;
LINE167                                     int32_t var_cc_6 = 0xfffff7cc;
LINE168                                     var_d0_2 = 0x382;
LINE169                                     goto error_handling;
LINE170                                 }
LINE171                                 l_size_src = ((uint32_t)var_58);
LINE172                             }
LINE173                             char* l_src;
LINE174                             l_src = get_data_from_file(&l_io_buffer, l_size_src);
LINE175                             l_io_buffer.field_38 = l_src;
LINE176                             if (l_src == 0)
LINE177                             {
LINE178                                 int32_t var_bc_28 = 0;
LINE179                                 int32_t var_c0_23 = 0;
LINE180                                 int32_t var_c4_22 = 0;
LINE181                                 int32_t var_c8_15 = 0;
LINE182                                 int32_t var_cc_7 = 0xfffff7cc;
LINE183                                 var_d0_2 = 0x393;
LINE184                                 goto error_handling;
LINE185                             }
LINE186                             if ((pixelSize - 1) > 0x1f)
LINE187                             {
LINE188                                 goto label_6e00b2a4;
LINE189                             }
LINE190                             switch (pixelSize)
LINE191                             {
LINE192                                 case 1:
LINE193                                 case 8:
LINE194                                 {
LINE195                                     perform_memcpy_or_memset(l_buffer_sz_raster_size, l_src, l_size_src, var_48_1);
LINE196                                     goto label_6e00b2a4;
LINE197                                 }
LINE198                                 case 2:
LINE199                                 case 3:
LINE200                                 case 5:
LINE201                                 case 6:
LINE202                                 case 7:
LINE203                                 case 9:
LINE204                                 case 0xa:
LINE205                                 case 0xb:
LINE206                                 case 0xc:
LINE207                                 case 0xd:
LINE208                                 case 0xe:
LINE209                                 case 0xf:
LINE210                                 case 0x11:
LINE211                                 case 0x12:
LINE212                                 case 0x13:
LINE213                                 case 0x14:
LINE214                                 case 0x15:
LINE215                                 case 0x16:
LINE216                                 case 0x17:
LINE217                                 case 0x19:
LINE218                                 case 0x1a:
LINE219                                 case 0x1b:
LINE220                                 case 0x1c:
LINE221                                 case 0x1d:
LINE222                                 case 0x1e:
LINE223                                 case 0x1f:
LINE224                                 {
LINE225                                     goto label_6e00b2a4;
LINE226                                 }
LINE227                                 case 4:
LINE228                                 {
LINE229                                     int32_t eax_25;
LINE230                                     int32_t edx_6;
LINE231                                     edx_6 = HIGHD(((int64_t)l_raster_size_get_from_dib_saved));
LINE232                                     eax_25 = LOWD(((int64_t)l_raster_size_get_from_dib_saved));
LINE233                                     void* esi_5 = ((eax_25 - edx_6) >> 1);
LINE234                                     void* edi_4 = ((char*)l_buffer_sz_raster_size + esi_5);
LINE235                                     perform_memcpy_or_memset(edi_4, l_io_buffer.field_38, l_size_src, var_48_1);
LINE236                                     ___std_atomic_compare_exchange_128@24(1, pixelSize, l_bounds_height, edi_4, l_buffer_sz_raster_size, esi_5);
LINE237                                     l_size_dest = l_size_dest_saved;
LINE238                                     goto label_6e00b2a4;
LINE239                                 }
LINE240                                 case 0x10:
LINE241                                 {
LINE242                                     esi_6 = l_buffer_computed_size;
LINE243                                     sub_6e00c6b0(esi_6, l_src, l_size_src, l_size_dest);
LINE244                                 label_6e00b21a:
LINE245                                     sub_6e00b4a0(l_buffer_sz_raster_size, esi_6, l_dstRect_heigth);
LINE246                                 label_6e00b2a4:
LINE247                                     int32_t var_bc_26 = l_dstRect_heigth;
LINE248                                     int32_t var_c0_21 = var_54_2;
LINE249                                     void* ecx_15 = ((char*)l_opcode_rec + pict_header->buffer_opcod_record);
LINE250                                     int32_t var_c4_20 = ((int32_t)*(uint16_t*)((char*)ecx_15 + 0x2c));
LINE251                                     sub_6e00a390((((((int32_t)*(uint16_t*)((char*)ecx_15 + 0x2a)) + l_index_loop) * l_raster_size_get_from_dib) + l_buffer_sz_raster_size_by_height), l_raster_size_get_from_dib, l_buffer_sz_raster_size, var_48_1, pixelSize);
LINE252                                     l_pixelSize = pixelSize;
LINE253                                     l_raster_size_get_from_dib_saved = l_raster_size_get_from_dib;
LINE254                                     edx_5 = l_io_buffer.field_34;
LINE255                                     l_buffer_raster_size = l_buffer_sz_raster_size;
LINE256                                     l_dstRect_width_bound = (l_index_loop + 1);
LINE257                                     l_index_loop = l_dstRect_width_bound;
LINE258                                     continue;
LINE259                                 }
LINE260                                 case 0x18:
LINE261                                 case 0x20:
LINE262                                 {
LINE263                                     perform_memcpy_or_memset(l_buffer_computed_size, l_src, l_size_src, l_size_dest);
LINE264                                     sub_6e00b500(l_buffer_sz_raster_size, l_buffer_computed_size, l_dstRect_heigth, edx_3, var_40);
LINE265                                     goto label_6e00b2a4;
LINE266                                 }
LINE267                             }
LINE268                         }
  [...]
LINE409 }

At LINE263 the target buffer is represented by l_buffer_computed_size, and the fourth argument is represented by l_size_dest.

l_buffer_computed_size is allocated at LINE39 with a size l_computed_size, computed LINE14 and LINE13, as it’s equal to 5 times l_bounds_height.
l_bounds_height is derived from opcod_record->bounds.y_value_bottom_right and opcod_record->bounds.y_value_top_left, LINE12 and LINE13. Theses two values are under control and read directly from the file.

The second parameter l_src, passed to perform_memcpy_or_memset at LINE263, is directly under control as well and read from the file at LINE174. So we can influence directly the call to OS_memset or OS_memcpy as seen earlier.
l_size_dest, used as boundary and fourth parameter, is in our case computed LINE117, derived from l_opcode_rec->rowBytes, also read from the file.
The issue is happening then when l_computed_size, corresponding to the allocation size, is smaller than l_size_dest, causing in the best case a heap corruption. It can also can lead to code execution.

Crash Information

eax=000000b9 ebx=05f20feb ecx=00000008 edx=0000001d esi=0c63554c edi=05f21000
eip=6e0e0f64 esp=0019f634 ebp=0019f648 iopl=0         nv up ei pl nz na pe cy
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000207
igCore20d!IG_GUI_page_title_set+0x3d494:
6e0e0f64 f3aa            rep stos byte ptr es:[edi]
0:000> !analyze -v
*******************************************************************************
*                                                                             *
*                        Exception Analysis                                   *
*                                                                             *
*******************************************************************************


KEY_VALUES_STRING: 1

    Key  : AV.Fault
    Value: Write

    Key  : Analysis.CPU.mSec
    Value: 952

    Key  : Analysis.Elapsed.mSec
    Value: 9714

    Key  : Analysis.IO.Other.Mb
    Value: 3

    Key  : Analysis.IO.Read.Mb
    Value: 5

    Key  : Analysis.IO.Write.Mb
    Value: 46

    Key  : Analysis.Init.CPU.mSec
    Value: 19281

    Key  : Analysis.Init.Elapsed.mSec
    Value: 12800294

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

    Key  : Failure.Bucket
    Value: INVALID_POINTER_WRITE_STRING_DEREFERENCE_AVRF_c0000005_igCore20d.dll!Unknown

    Key  : Failure.Hash
    Value: {f790b90e-385f-a0f1-4070-16946732921f}

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

    Key  : WER.Process.Version
    Value: 1.0.1.1


NTGLOBALFLAG:  2100000

APPLICATION_VERIFIER_FLAGS:  0

APPLICATION_VERIFIER_LOADED: 1

EXCEPTION_RECORD:  (.exr -1)
ExceptionAddress: 6e0e0f64 (igCore20d!IG_GUI_page_title_set+0x0003d494)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 00000001
   Parameter[1]: 05f21000
Attempt to write to address 05f21000

FAULTING_THREAD:  00000e74

PROCESS_NAME:  Fuzzme.exe

WRITE_ADDRESS:  05f21000 

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:  05f21000

STACK_TEXT:  
WARNING: Stack unwind information not available. Following frames may be wrong.
0019f648 6e00c812     05f20feb 000000b9 0000001d igCore20d!IG_GUI_page_title_set+0x3d494
0019f668 6e00b230     05f20f88 05f21008 0c635558 igCore20d!IG_mpi_page_set+0xe06f2
0019f734 6e00a321     0019fc3c 1000001e 106a0ff8 igCore20d!IG_mpi_page_set+0xdf110
0019fbb4 6df015b9     0019fc3c 106a0ff8 00000001 igCore20d!IG_mpi_page_set+0xde201
0019fbec 6df408bc     00000000 106a0ff8 0019fc3c igCore20d!IG_image_savelist_get+0xb29
0019fe68 6df40239     00000000 05b36fe0 00000001 igCore20d!IG_mpi_page_set+0x1479c
0019fe88 6ded5bc7     00000000 05b36fe0 00000001 igCore20d!IG_mpi_page_set+0x14119
0019fea8 00402399     05b36fe0 0019febc 759ffb80 igCore20d!IG_load_file+0x47
0019fec0 004026c0     05b36fe0 0019fef8 05a9cf50 Fuzzme!fuzzme+0x19
0019ff28 00408407     00000005 05a96f90 05a9cf50 Fuzzme!fuzzme+0x340
0019ff70 75a000c9     00324000 75a000b0 0019ffdc Fuzzme!fuzzme+0x6087
0019ff80 77ba7b4e     00324000 4e3b0cdc 00000000 KERNEL32!BaseThreadInitThunk+0x19
0019ffdc 77ba7b1e     ffffffff 77bc8c8a 00000000 ntdll!__RtlUserThreadStart+0x2f
0019ffec 00000000     0040848f 00324000 00000000 ntdll!_RtlUserThreadStart+0x1b


STACK_COMMAND:  ~0s ; .cxr ; kb

SYMBOL_NAME:  igCore20d+3d494

MODULE_NAME: igCore20d

IMAGE_NAME:  igCore20d.dll

FAILURE_BUCKET_ID:  INVALID_POINTER_WRITE_STRING_DEREFERENCE_AVRF_c0000005_igCore20d.dll!Unknown

OSPLATFORM_TYPE:  x86

OSNAME:  Windows 8

IMAGE_VERSION:  20.1.0.117

FAILURE_ID_HASH:  {f790b90e-385f-a0f1-4070-16946732921f}

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-06-26 - Vendor Disclosure
2023-09-20 - Vendor Patch Release
2023-09-25 - Public Release

Credit

Discovered by Emmanuel Tacheau of Cisco Talos.