CVE-2023-35002
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.
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
ImageGear - https://www.accusoft.com/products/imagegear-collection/
9.8 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-119 - Improper Restriction of Operations within the Bounds of a Memory Buffer
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_memset
call 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 dst
and 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.
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
---------
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
2023-06-26 - Vendor Disclosure
2023-09-20 - Vendor Patch Release
2023-09-25 - Public Release
Discovered by Emmanuel Tacheau of Cisco Talos.