Headline
CVE-2021-21773: TALOS-2021-1227 || Cisco Talos Intelligence Group
An out-of-bounds write vulnerability exists in the TIFF header count-processing functionality of Accusoft ImageGear 19.8. A specially crafted malformed file can lead to memory corruption. An attacker can provide a malicious file to trigger this vulnerability.
Summary
An out-of-bounds write vulnerability exists in the TIFF header count-processing functionality of Accusoft ImageGear 19.8. A specially crafted malformed file can lead to memory corruption. An attacker can provide a malicious file to trigger this vulnerability.
Tested Versions
Accusoft ImageGear 19.8
Product URLs
https://www.accusoft.com/products/imagegear-collection/
CVSSv3 Score
8.1 - CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE
CWE-131 - Incorrect Calculation of Buffer Size
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.
There is a vulnerability in the tiff_read_sub_1 function, due to a buffer overflow caused by a missing null check for a size field.
A specially crafted TIFF file can lead to an out-of-bounds write which can result in memory corruption.
Trying to load a malformed TIFF file, we end up in the following situation:
(8920.8408): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=10000021 ebx=0019fc3c ecx=00000000 edx=0adb4ff8 esi=00190001 edi=0adb0ff8
eip=7c1ab972 esp=0019f5f8 ebp=0019f604 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010202
igCore19d!IG_cpm_profiles_reset+0x10b62:
7c1ab972 66897708 mov word ptr [edi+8],si ds:002b:0adb1000=????
When we look at the edi memory allocation we can see the buffer allocated is very small, only 1 byte:
0:000> !heap -p -a edi
address 0adb0ff8 found in
_DPH_HEAP_ROOT @ 4ea1000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
a993d9c: adb0ff8 1 - adb0000 2000
7c0ea8b0 verifier!AVrfDebugPageHeapAllocate+0x00000240
774eef8e ntdll!RtlDebugAllocateHeap+0x00000039
77456150 ntdll!RtlpAllocateHeap+0x000000f0
774557fe ntdll!RtlpAllocateHeapInternal+0x000003ee
774553fe ntdll!RtlAllocateHeap+0x0000003e
7c53dcff MSVCR110!malloc+0x00000049
7c1b61de igCore19d!AF_memm_alloc+0x0000001e
7c2c6ff0 igCore19d!IG_mpi_page_set+0x0010b2a0
7c2c6f9a igCore19d!IG_mpi_page_set+0x0010b24a
7c2cc0a3 igCore19d!IG_mpi_page_set+0x00110353
7c2c604b igCore19d!IG_mpi_page_set+0x0010a2fb
7c1910d9 igCore19d!IG_image_savelist_get+0x00000b29
7c1d0557 igCore19d!IG_mpi_page_set+0x00014807
7c1cfeb9 igCore19d!IG_mpi_page_set+0x00014169
7c165777 igCore19d!IG_load_file+0x00000047
004021f9 Fuzzme!fuzzme+0x00000019
00402504 Fuzzme!fuzzme+0x00000324
0040666d Fuzzme!fuzzme+0x0000448d
7719fa29 KERNEL32!BaseThreadInitThunk+0x00000019
774775f4 ntdll!__RtlUserThreadStart+0x0000002f
774775c4 ntdll!_RtlUserThreadStart+0x0000001b
The crash is happening in the following pseudo code of the function init_io_buffer in LINE7 as edi contains the io_buff pointer:
LINE1 dword init_io_buffer(mys_table_function *mys_table_function_obj,uint kind_of_heap,io_buffer *io_buff
LINE2 ,int size_to_allocate,short constant_1)
LINE3 {
LINE4
LINE5 set_into_param2_endian_type(mys_table_function_obj,&io_buff->endian_type);
LINE6 io_buff->ptr_mys_table_function = mys_table_function_obj;
LINE7 io_buff->field_0x8 = constant_1;
LINE8 io_buff->kind_of_heap = kind_of_heap;
LINE9 io_buff->buffer_size = size_to_allocate;
LINE10 io_buff->field_0xc = 0;
LINE11 [...]
LINE12 }
LINE13
The root cause is coming from the caller function tiff_read_sub_1 which is responsible for the allocation of io_buff. The pseudo-code of tiff_read_sub_1:
LINE14 void tiff_read_sub_1(mys_table_function *mys_table_function_obj,uint kind_of_heap,
LINE15 mys_tags_data *tags_data,undefined4 param_4,HIGDIBINFO Obj_HIGDIBINFO,
LINE16 short *param_6)
LINE17 {
LINE18 io_buffer *ptr_io_buff;
LINE19 byte **dst;
LINE20 byte **error_message;
LINE21 size_t size;
LINE22 BYTE *pBVar1;
LINE23 int iVar2;
LINE24 dword dVar3;
LINE25 byte *pbVar4;
LINE26 int iVar5;
LINE27 int iVar6;
LINE28 io_buffer *piVar7;
LINE29 uint uVar8;
LINE30 HIGDIBINFO pIVar9;
LINE31 undefined4 uVar10;
LINE32 int local_28;
LINE33 byte *local_24;
LINE34 byte *local_20;
LINE35 int *local_1c;
LINE36 int local_18;
LINE37 uint size_to_allocate;
LINE38 uint local_c;
LINE39
LINE40 local_20 = NULL;
LINE41 size_to_allocate = 0;
LINE42 local_c = 0;
LINE43 local_24 = NULL;
LINE44 local_1c = NULL;
LINE45 ptr_io_buff = (io_buffer *)
LINE46 AF_memm_alloc(kind_of_heap,
LINE47 (uint)*(ushort *)&tags_data->SamplesPerPixel_Or_CountValue * 0x34,
LINE48 (dword)"..\\..\\..\\..\\Common\\Formats\\tifread.c");
LINE49 if (ptr_io_buff == NULL) {
LINE50 AF_err_record_set("..\\..\\..\\..\\Common\\Formats\\tifread.c",0x178b,-1000,0,0,0,NULL);
LINE51 AF_error_check();
LINE52 return;
LINE53 }
LINE54 [...]
LINE55 LAB_101771f6:
LINE56 dVar3 = init_io_buffer(mys_table_function_obj,kind_of_heap,ptr_io_buff,
LINE57 (int)*error_message * 5,1);
LINE58 [...]
LINE59 }
The tiff_read_sub_1 function starts by allocating in LINE45 the memory buffer which can be overwritten represented by the variable ptr_io_buff . The size used for the memory allocation is directly read from the file, and corresponds to the count value of TIFF tags ImageWidth or ImageLength. By default this value is set to 1 according to documentation. There is no check against the buffer size before the call to the init_io_buffer in LINE56. The function AF_memm_alloc is a wrapper to malloc, thus when passing a null value it returns a buffer of one byte.
So, when calling init_io_buffer, that function assumes the ptr_io_buff points to a properly allocated io_buffer structure, and initializes some of its fields by writing inside the pointed structure. Since in our case the buffer is only 1 byte long, most of the assignments happening inside that function (e.g. LINE7 and LINE10 are writing constant values 1 and 0 respectively) are out-of-bounds heap writes which lead to memory corruption and possibly code execution.
Crash Information
0:000> !analyze -v
*******************************************************************************
* *
* Exception Analysis *
* *
*******************************************************************************
KEY_VALUES_STRING: 1
Key : AV.Fault
Value: Write
Key : Analysis.CPU.mSec
Value: 1249
Key : Analysis.DebugAnalysisProvider.CPP
Value: Create: 8007007e on DESKTOP-4DAOCFH
Key : Analysis.DebugData
Value: CreateObject
Key : Analysis.DebugModel
Value: CreateObject
Key : Analysis.Elapsed.mSec
Value: 9397
Key : Analysis.Memory.CommitPeak.Mb
Value: 72
Key : Analysis.System
Value: CreateObject
Key : Timeline.OS.Boot.DeltaSec
Value: 261278
Key : Timeline.Process.Start.DeltaSec
Value: 2029
Key : WER.OS.Branch
Value: vb_release
Key : WER.OS.Timestamp
Value: 2019-12-06T14:06:00Z
Key : WER.OS.Version
Value: 10.0.19041.1
Key : WER.Process.Version
Value: 1.0.1.1
ADDITIONAL_XML: 1
OS_BUILD_LAYERS: 1
NTGLOBALFLAG: 2100000
APPLICATION_VERIFIER_FLAGS: 0
APPLICATION_VERIFIER_LOADED: 1
EXCEPTION_RECORD: (.exr -1)
ExceptionAddress: 7c1ab972 (igCore19d!IG_cpm_profiles_reset+0x00010b62)
ExceptionCode: c0000005 (Access violation)
ExceptionFlags: 00000000
NumberParameters: 2
Parameter[0]: 00000001
Parameter[1]: 0adb1000
Attempt to write to address 0adb1000
FAULTING_THREAD: 00008408
PROCESS_NAME: Fuzzme.exe
WRITE_ADDRESS: 0adb1000
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: 0adb1000
STACK_TEXT:
WARNING: Stack unwind information not available. Following frames may be wrong.
0019f604 7c2c7648 0019fc3c 10000021 0adb0ff8 igCore19d!IG_cpm_profiles_reset+0x10b62
0019f650 7c2c6f9a 0019fc3c 10000021 0adb4ff8 igCore19d!IG_mpi_page_set+0x10b8f8
0019f678 7c2cc0a3 0019fc3c 10000021 0019f6d0 igCore19d!IG_mpi_page_set+0x10b24a
0019f6a0 7c2c604b 0019fc3c 10000021 0ab7dd68 igCore19d!IG_mpi_page_set+0x110353
0019fbb4 7c1910d9 0019fc3c 0ab7dd68 00000001 igCore19d!IG_mpi_page_set+0x10a2fb
0019fbec 7c1d0557 00000000 0ab7dd68 0019fc3c igCore19d!IG_image_savelist_get+0xb29
0019fe68 7c1cfeb9 00000000 05295f80 00000001 igCore19d!IG_mpi_page_set+0x14807
0019fe88 7c165777 00000000 05295f80 00000001 igCore19d!IG_mpi_page_set+0x14169
0019fea8 004021f9 05295f80 0019febc 00000001 igCore19d!IG_load_file+0x47
0019fec0 00402504 05295f80 05293fc0 051e3f28 Fuzzme!fuzzme+0x19
0019ff28 0040666d 00000005 051daee8 051e3f28 Fuzzme!fuzzme+0x324
0019ff70 7719fa29 00268000 7719fa10 0019ffdc Fuzzme!fuzzme+0x448d
0019ff80 774775f4 00268000 0f9e4eaf 00000000 KERNEL32!BaseThreadInitThunk+0x19
0019ffdc 774775c4 ffffffff 77497336 00000000 ntdll!__RtlUserThreadStart+0x2f
0019ffec 00000000 004066f5 00268000 00000000 ntdll!_RtlUserThreadStart+0x1b
STACK_COMMAND: ~0s ; .cxr ; kb
SYMBOL_NAME: igCore19d!IG_cpm_profiles_reset+10b62
MODULE_NAME: igCore19d
IMAGE_NAME: igCore19d.dll
FAILURE_BUCKET_ID: INVALID_POINTER_WRITE_AVRF_c0000005_igCore19d.dll!IG_cpm_profiles_reset
OS_VERSION: 10.0.19041.1
BUILDLAB_STR: vb_release
OSPLATFORM_TYPE: x86
OSNAME: Windows 10
IMAGE_VERSION: 19.8.0.0
FAILURE_ID_HASH: {749e662e-5382-aab8-f02d-cecd73653ce6}
Followup: MachineOwner
---------
Timeline
2021-01-15 - Vendor Disclosure
2021-02-05 - Vendor Patched
2021-03-30 - Public Release
Discovered by Emmanuel Tacheau of Cisco Talos.