CVE-2023-23567
A heap-based buffer overflow vulnerability exists in the CreateDIBfromPict functionality of Accusoft ImageGear 20.1. A specially crafted 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/
8.1 - CVSS:3.1/AV:N/AC:H/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 or Quickdraw v2 file can lead to a heap-based buffer overflow in CreateDIBfromPict
, due to a wrongly sized heap buffer due to a missing size check.
Trying to load a malformed PICT, we end up in the following situation:
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0d354fe0 ebx=00000020 ecx=00000040 edx=00000040 esi=0d354fa0 edi=0d327000
eip=6fe9091a esp=0019f62c ebp=0019f644 iopl=0 nv up ei ng nz na pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010287
igCore20d!IG_GUI_page_title_set+0x3ce4a:
6fe9091a f3a4 rep movs byte ptr es:[edi],byte ptr [esi]
*** WARNING: Unable to verify checksum for Fuzzme.exe
Looking at source of buffer pointed by the register esi
, we can observe how to control the source:
0:000> db esi
0d354fa0 32 00 00 33 00 01 34 00-00 35 00 00 36 00 00 37 2..3..4..5..6..7
0d354fb0 00 00 38 00 fd 39 00 7f-30 00 00 31 00 00 32 00 ..8..9..0..1..2.
0d354fc0 01 33 00 00 34 00 00 35-00 00 36 00 21 37 00 21 .3..4..5..6.!7.!
0d354fd0 38 00 21 39 00 21 30 00-21 31 00 21 32 00 21 33 8.!9.!0.!1.!2.!3
0d354fe0 00 21 34 00 21 44 00 c0-45 00 c0 41 00 c0 44 00 .!4.!D..E..A..D.
0d354ff0 c0 42 00 c0 45 00 c0 41-00 c0 46 00 c0 30 10 c0 .B..E..A..F..0..
0d355000 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????
0d355010 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????
The destination buffer pointer by the register edi
has a size of 0xae0 bytes and is allocated in the heap:
0:000> !heap -p -a edi
address 0d327000 found in
_DPH_HEAP_ROOT @ 2cc1000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
d1e3c64: d326520 ae0 - d326000 2000
unknown!fillpattern
7009a8b0 verifier!AVrfDebugPageHeapAllocate+0x00000240
77f0f53e ntdll!RtlDebugAllocateHeap+0x00000039
77e771b0 ntdll!RtlpAllocateHeap+0x000000f0
77e76f0c ntdll!RtlpAllocateHeapInternal+0x0000104c
77e75eae ntdll!RtlAllocateHeap+0x0000003e
6fe91fa6 igCore20d!IG_GUI_page_title_set+0x0003e4d6
6fcd661d igCore20d!AF_memm_alloc+0x0000001d
6fdbaf66 igCore20d!IG_mpi_page_set+0x000dee46
6fdba321 igCore20d!IG_mpi_page_set+0x000de201
6fcb15b9 igCore20d!IG_image_savelist_get+0x00000b29
6fcf08bc igCore20d!IG_mpi_page_set+0x0001479c
6fcf0239 igCore20d!IG_mpi_page_set+0x00014119
6fc85bc7 igCore20d!IG_load_file+0x00000047
00402399 Fuzzme!fuzzme+0x00000019
004026c0 Fuzzme!fuzzme+0x00000340
00408407 Fuzzme!fuzzme+0x00006087
75fb00f9 KERNEL32!BaseThreadInitThunk+0x00000019
77e97bbe ntdll!__RtlUserThreadStart+0x0000002f
77e97b8e ntdll!_RtlUserThreadStart+0x0000001b
The exception happens at LINE152 in the __memcpy
function:
LINE1 void * __cdecl __memcpy(void *_Dst,void *_Src,size_t _Size)
LINE2
LINE3 {
[...]
LINE148 puVar23 = (undefined *)_Dst;
LINE149 if ((DAT_1031d754 >> 1 & 1) != 0) {
LINE150 /* WARNING: Load size is inaccurate */
LINE151 for (; _Size != 0; _Size = _Size - 1) {
LINE152 *puVar23 = *_Src;
LINE153 _Src = (undefined *)((int)_Src + 1);
LINE154 puVar23 = puVar23 + 1;
LINE155 }
LINE156 return _Dst;
LINE157 }
[...]
LINE532 }
Investigating the call stack will guide us to the culprit function. We can observe the __memcpy
function is called at LINE550 by a function named perform_OS_memcpy
with the following pseudo-code:
LINE537 void perform_OS_memcpy(void *dest,size_t size,void *src,dword size_copy,dword pixelSize,
LINE538 dword x_value_top_left,dword _number_of_element,dword dstRect_width)
LINE539
LINE540 {
LINE541 if ((int)pixelSize < 8) {
LINE542 OS_memcpy((void *)((int)x_value_top_left / (int)(8 / (longlong)(int)pixelSize) + (int)dest),src,
LINE543 size_copy);
LINE544 return;
LINE545 }
LINE546 if (pixelSize == 8) {
LINE547 OS_memcpy((void *)(x_value_top_left + (int)dest),src,size_copy);
LINE548 return;
LINE549 }
LINE550 OS_memcpy((void *)(((16 < (int)pixelSize) + 2) * x_value_top_left + (int)dest),src,size_copy);
LINE551 return;
LINE552 }
LINE553
After investigation, we can see this is happening only when a pixelSize
has value bigger than 8
. Continuing through the call stack leads us to LINE767 in a function named CreateDIBfromPict
.
LINE557 BOOLEAN CreateDIBfromPict(mys_table_function *mys_table_function,uint param_2,undefined4 param_3,PICT_Header *pict_header,HIGDIBINFO higdibinfo)
LINE558 {
LINE559 [...]
LINE560
LINE561 local_14[3] = DAT_10319e74 ^ (uint)&stack0xfffffffc;
LINE562 _kind_of_heap = param_2;
LINE563 _pict_header = pict_header;
LINE564 pixelsize_32_component_count4 = 0;
LINE565 io_buff.buffer_size = 0;
LINE566 DIB_width_get(higdibinfo);
LINE567 Height = DIB_height_get(higdibinfo);
LINE568 __size_src_buffer = IO_raster_size_get(higdibinfo);
LINE569 p_opcode_data = pict_header->opcode_data;
LINE570 local_68 = (int)(short)(p_opcode_data->bounds).x_value_lower_right -
LINE571 (int)(short)(p_opcode_data->bounds).x_value_top_left;
LINE572 related_bound_size = local_68 * 5;
LINE573 if ((int)related_bound_size < (int)__size_src_buffer) {
LINE574 AF_err_record_set("..\\..\\..\\..\\Common\\Formats\\pctwread.c",0x2f6,-0x194,0,__size_src_buffer
LINE575 ,local_68,(LPCHAR)0x0);
LINE576 }
LINE577 else {
LINE578 pixelSize = (uint)p_opcode_data->pixelSize;
LINE579 component_count = p_opcode_data->component_count;
LINE580 b_break_dowhile = IO_DIB_create_ex(mys_table_function,higdibinfo);
LINE581 __sz_src_buffer = __size_src_buffer;
LINE582 if (b_break_dowhile != False) {
LINE583 AF_err_record_set("..\\..\\..\\..\\Common\\Formats\\pctwread.c",0x2ff,-0x9a0,0,0,0,(LPCHAR)0x0
LINE584 );
LINE585 BVar1 = @__security_check_cookie@4();
LINE586 return BVar1;
LINE587 }
LINE588 raster_source_buffer = (BYTE *)AF_memm_alloc(_kind_of_heap,__size_src_buffer);
LINE589 __source_buffer = (undefined4 *)AF_memm_alloc(_kind_of_heap,related_bound_size);
LINE590 size_target_buffer = __sz_src_buffer * Height;
LINE591 ___source_buffer = __source_buffer;
LINE592 _target_buffer = (byte *)AF_memm_alloc(_kind_of_heap,size_target_buffer);
LINE593 another_buffer = AF_memm_alloc(_kind_of_heap,size_target_buffer);
LINE594 if (((raster_source_buffer == (BYTE *)0x0) || (__source_buffer == (undefined4 *)0x0)) ||
LINE595 (_target_buffer == (byte *)0x0)) {
LINE596 b_break_dowhile =
LINE597 AF_err_record_set("..\\..\\..\\..\\Common\\Formats\\pctwread.c",0x309,-1000,0,0,0,
LINE598 (LPCHAR)0x0);
LINE599 }
LINE600 else if ((pixelSize == 0x20) && (component_count == 4)) {
LINE601 pixelsize_32_component_count4 = 1;
LINE602 }
LINE603 __target_buffer = _target_buffer;
LINE604 OS_memset(_target_buffer,0,size_target_buffer);
LINE605 _wide_size = _kind_of_heap;
LINE606 bIobInit = IOb_init(mys_table_function,_kind_of_heap,&io_buff,0x5000,1);
LINE607 __pict_header = _pict_header;
LINE608 b_break_dowhile = b_break_dowhile + bIobInit;
LINE609 if (b_break_dowhile == False) {
LINE610 IO_attribute_set(mys_table_function,4,
LINE611 (AT_RESOLUTION *)&_pict_header->original_horizontal_resolution);
LINE612 opcode_data_processed = 0;
LINE613 index_opcode_data = 0;
LINE614 do {
LINE615 _index_opcode_data = index_opcode_data;
LINE616 if ((int)__pict_header->len_opcode_data <= opcode_data_processed) break;
LINE617 p_opcode_data = __pict_header->opcode_data;
LINE618 nb_component = (dword)*(ushort *)((int)&p_opcode_data->component_count + index_opcode_data);
LINE619 if (2 < nb_component) {
LINE620 nb_component = 3;
LINE621 }
LINE622 iVar2 = 0;
LINE623 if ((bits_per_channel_table *)nb_component != (bits_per_channel_table *)0x0) {
LINE624 do {
LINE625 local_14[iVar2] =
LINE626 (uint)*(ushort *)((int)&p_opcode_data->component_size + index_opcode_data);
LINE627 iVar2 = iVar2 + 1;
LINE628 } while (iVar2 < (int)nb_component);
LINE629 }
LINE630 _wide_size = (int)*(short *)((int)&(p_opcode_data->dstRect).x_value_lower_right +
LINE631 index_opcode_data) -
LINE632 (int)*(short *)((int)&(p_opcode_data->dstRect).x_value_top_left +
LINE633 index_opcode_data);
LINE634 _height_size = (int)*(short *)((int)&(p_opcode_data->dstRect).y_value_lower_right +
LINE635 index_opcode_data) -
LINE636 (int)*(short *)((int)&(p_opcode_data->dstRect).y_value_top_left +
LINE637 index_opcode_data);
LINE638 dstRect_width = _wide_size;
LINE639 IO_raster_size_calc(_wide_size,nb_component,(int *)local_14);
LINE640 if (pixelSize == 1) {
LINE641 raster_size_calculated = DIB1bit_packed_raster_size_calc(_wide_size);
LINE642 }
LINE643 else {
LINE644 raster_size_calculated =
LINE645 DIBStd_raster_size_calc_simple
LINE646 (_wide_size,(bits_per_channel_table *)nb_component,
LINE647 (uint)*(ushort *)
LINE648 ((int)&_pict_header->opcode_data->component_size +
LINE649 _index_opcode_data));
LINE650 }
LINE651 related_bound_size = __size_src_buffer;
LINE652 __raster_size_calculated = raster_size_calculated;
LINE653 if ((int)__size_src_buffer < (int)raster_size_calculated) {
LINE654 AF_err_record_set("..\\..\\..\\..\\Common\\Formats\\pctwread.c",0x348,-0x834,0,0,0,
LINE655 (LPCHAR)0x0);
LINE656 break;
LINE657 }
LINE658 p_opcode_data = _pict_header->opcode_data;
LINE659 bounds_rect_width =
LINE660 (int)*(short *)((int)&(p_opcode_data->bounds).x_value_lower_right + _index_opcode_data)
LINE661 - (int)*(short *)((int)&(p_opcode_data->bounds).x_value_top_left + _index_opcode_data);
LINE662 IOb_seek(&io_buff,*(int *)((int)&p_opcode_data->current_offset + _index_opcode_data),
LINE663 SEEK_SET);
LINE664 OS_memset(raster_source_buffer,0,related_bound_size);
LINE665 if (pixelSize != 0x18) {
LINE666 raster_size_calculated =
LINE667 (uint)*(ushort *)((int)&_pict_header->opcode_data->rowBytes + index_opcode_data);
LINE668 }
LINE669 p_opcode_data = _pict_header->opcode_data;
LINE670 io_buff.size_buffer =
LINE671 (jpeg_related *)(uint)*(ushort *)((int)&p_opcode_data->packType + index_opcode_data);
LINE672 _number_of_element =
LINE673 (int)*(short *)((int)&(p_opcode_data->dstRect).x_value_lower_right + index_opcode_data)
LINE674 - (int)*(short *)((int)&(p_opcode_data->dstRect).x_value_top_left + index_opcode_data);
LINE675 _loop_count = 0;
LINE676 local_64 = raster_size_calculated;
LINE677 if (b_break_dowhile == False) {
LINE678 for (; __source_buffer = (undefined4 *)raster_source_buffer,
LINE679 _wide_size = __size_src_buffer, _loop_count < _height_size;
LINE680 _loop_count = _loop_count + 1) {
LINE681 if (((7 < (int)raster_size_calculated) && (io_buff.size_buffer != (jpeg_related *)0x1))
LINE682 && ((io_buff.size_buffer != (jpeg_related *)0x2 || ((int)pixelSize < 0x18)))) {
LINE683 if ((int)raster_size_calculated < 0xfb) {
LINE684 b_read = IOb_byte_read(&io_buff,&local_25);
LINE685 if (b_read != False) {
LINE686 _lsize_to_read = (uint)local_25;
LINE687 goto process_data;
LINE688 }
LINE689 _index_opcode_data = 0x38b;
LINE690 }
LINE691 else {
LINE692 BVar1 = IOBuff_Read_ushort(&io_buff,local_58);
LINE693 if (BVar1 == False) {
LINE694 _index_opcode_data = 0x382;
LINE695 }
LINE696 else {
LINE697 _lsize_to_read = (uint)local_58[0];
LINE698 process_data:
LINE699 _read_from_file = get_data_from_file(&io_buff,_lsize_to_read);
LINE700 src = (BYTE *)___source_buffer;
LINE701 if (_read_from_file != (byte *)0x0) {
LINE702 switch(pixelSize) {
LINE703 case 1:
LINE704 case 8:
LINE705 perform_some_copy_on_dest
LINE706 (raster_source_buffer,_read_from_file,_lsize_to_read,
LINE707 __raster_size_calculated);
LINE708 break;
LINE709 case 4:
LINE710 __target_buffer = raster_source_buffer + (int)_wide_size / 2;
LINE711 perform_some_copy_on_dest
LINE712 (__target_buffer,_read_from_file,_lsize_to_read,
LINE713 __raster_size_calculated);
LINE714 FUN_10023390(1,pixelSize,local_68,__target_buffer,raster_source_buffer,
LINE715 (int)_wide_size / 2);
LINE716 raster_size_calculated = local_64;
LINE717 break;
LINE718 case 0x10:
LINE719 FUN_1014c6b0(___source_buffer,_read_from_file,_lsize_to_read,
LINE720 raster_size_calculated);
LINE721 goto switchD_1014b26f_caseD_10;
LINE722 case 0x18:
LINE723 case 0x20:
LINE724 perform_some_copy_on_dest
LINE725 ((byte *)___source_buffer,_read_from_file,_lsize_to_read,
LINE726 raster_size_calculated);
LINE727 copySomeData_2(raster_source_buffer,src,dstRect_width,bounds_rect_width,
LINE728 pixelsize_32_component_count4);
LINE729 }
LINE730 goto switchD_1014b26f_caseD_2;
LINE731 }
LINE732 _index_opcode_data = 0x393;
LINE733 }
LINE734 }
LINE735 LAB_1014b340:
LINE736 b_break_dowhile =
LINE737 AF_err_record_set("..\\..\\..\\..\\Common\\Formats\\pctwread.c",
LINE738 _index_opcode_data,-0x834,0,0,0,(LPCHAR)0x0);
LINE739 break;
LINE740 }
LINE741 src = get_data_from_file(&io_buff,raster_size_calculated);
LINE742 _wide_size = dstRect_width;
LINE743 if (src == (BYTE *)0x0) {
LINE744 _index_opcode_data = 0x365;
LINE745 goto LAB_1014b340;
LINE746 }
LINE747 switch(pixelSize) {
LINE748 case 1:
LINE749 case 4:
LINE750 case 8:
LINE751 _wide_size = raster_size_calculated;
LINE752 LAB_1014b29c:
LINE753 OS_memcpy(__source_buffer,src,_wide_size);
LINE754 break;
LINE755 case 0x10:
LINE756 switchD_1014b26f_caseD_10:
LINE757 copySomeData(raster_source_buffer,src,dstRect_width);
LINE758 break;
LINE759 case 0x18:
LINE760 case 0x20:
LINE761 copySomeData_2(raster_source_buffer,src,dstRect_width,bounds_rect_width,
LINE762 pixelsize_32_component_count4);
LINE763 __source_buffer = ___source_buffer;
LINE764 if (pixelsize_32_component_count4 != 0) goto LAB_1014b29c;
LINE765 }
LINE766 switchD_1014b26f_caseD_2:
LINE767 perform_OS_memcpy(_target_buffer +
LINE768 (*(short *)((int)&(_pict_header->opcode_data->dstRect).
LINE769 y_value_top_left + index_opcode_data) + _loop_count)
LINE770 * __size_src_buffer,__size_src_buffer,raster_source_buffer,
LINE771 __raster_size_calculated,pixelSize,
LINE772 (int)*(short *)((int)&(_pict_header->opcode_data->dstRect).
LINE773 x_value_top_left + index_opcode_data),
LINE774 _number_of_element,dstRect_width);
LINE775 }
LINE776 }
[...]
LINE818 }
The destination buffer represented by _target_buffer
at LINE767 corresponding to our edi
register is allocated at LINE592, and the size represented by size_target_buffer
is computed at LINE590 with the product of two variables: Height
and __sz_src_buffer
.
The source buffer represented by raster_source_buffer
passed as third parameter of perform_OS_memcpy
is allocated at LINE588 and is also controlled by __sz_src_buffer
.
The values used for the size calculation are directly read from the PICT file, either from the PICT header or the values from a DirectBitsRect
opcode data.
The vulnerability ocurs when the _height_size
is bigger than Height
. The _height_size
is used to control, at LINE678, the maximum number of times the for-loop repeats to perform the copy. This same _height_size
value is also computed for the dstRect
record data following the pixmap records of the DirectBitsRect
opcode data.
This issue allows for an out-of-bounds write in heap, which could in turn allow an attacker to execute arbitrary code.
**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-I6GKV78
Key : Analysis.DebugData
Value: CreateObject
Key : Analysis.DebugModel
Value: CreateObject
Key : Analysis.Elapsed.Sec
Value: 3
Key : Analysis.Memory.CommitPeak.Mb
Value: 92
Key : Analysis.System
Value: CreateObject
Key : Timeline.OS.Boot.DeltaSec
Value: 1319490
Key : Timeline.Process.Start.DeltaSec
Value: 102
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: 6fe9091a (igCore20d!IG_GUI_page_title_set+0x0003ce4a)
ExceptionCode: c0000005 (Access violation)
ExceptionFlags: 00000000
NumberParameters: 2
Parameter[0]: 00000001
Parameter[1]: 0d327000
Attempt to write to address 0d327000
FAULTING_THREAD: 0000171c
PROCESS_NAME: Fuzzme.exe
WRITE_ADDRESS: 0d327000
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: 0d327000
STACK_TEXT:
WARNING: Stack unwind information not available. Following frames may be wrong.
0019f644 6fdba3d5 0d327000 0d354fa0 00000040 igCore20d!IG_GUI_page_title_set+0x3ce4a
0019f658 6fdbb2d7 0d327000 00000060 0d354fa0 igCore20d!IG_mpi_page_set+0xde2b5
0019f734 6fdba321 0019fc3c 1000001e 0fd08ff8 igCore20d!IG_mpi_page_set+0xdf1b7
0019fbb4 6fcb15b9 0019fc3c 0fd08ff8 00000001 igCore20d!IG_mpi_page_set+0xde201
0019fbec 6fcf08bc 00000000 0fd08ff8 0019fc3c igCore20d!IG_image_savelist_get+0xb29
0019fe68 6fcf0239 00000000 05361fe0 00000001 igCore20d!IG_mpi_page_set+0x1479c
0019fe88 6fc85bc7 00000000 05361fe0 00000001 igCore20d!IG_mpi_page_set+0x14119
0019fea8 00402399 05361fe0 0019febc 75fafb80 igCore20d!IG_load_file+0x47
0019fec0 004026c0 05361fe0 0019fef8 052bdf40 Fuzzme!fuzzme+0x19
0019ff28 00408407 00000005 052b6f98 052bdf40 Fuzzme!fuzzme+0x340
0019ff70 75fb00f9 003e3000 75fb00e0 0019ffdc Fuzzme!fuzzme+0x6087
0019ff80 77e97bbe 003e3000 5b1011ff 00000000 KERNEL32!BaseThreadInitThunk+0x19
0019ffdc 77e97b8e ffffffff 77eb8d0b 00000000 ntdll!__RtlUserThreadStart+0x2f
0019ffec 00000000 0040848f 003e3000 00000000 ntdll!_RtlUserThreadStart+0x1b
STACK_COMMAND: ~0s ; .cxr ; kb
SYMBOL_NAME: igCore20d!IG_GUI_page_title_set+3ce4a
MODULE_NAME: igCore20d
IMAGE_NAME: igCore20d.dll
FAILURE_BUCKET_ID: INVALID_POINTER_WRITE_AVRF_c0000005_igCore20d.dll!IG_GUI_page_title_set
OS_VERSION: 10.0.19041.1
BUILDLAB_STR: vb_release
OSPLATFORM_TYPE: x86
OSNAME: Windows 10
FAILURE_ID_HASH: {b42f49c1-788b-e135-f7d3-b58730240707}
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-03-16 - Vendor Disclosure
2023-09-20 - Vendor Patch Release
2023-09-25 - Public Release
Discovered by Emmanuel Tacheau of Cisco Talos.