CVE-2023-32284
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.
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 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.
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
---------
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-05-24 - Vendor Disclosure
2023-09-20 - Vendor Patch Release
2023-09-25 - Public Release
Discovered by Emmanuel Tacheau of Cisco Talos.