CVE-2020-6151
A memory corruption vulnerability exists in the TIFF handle_COMPRESSION_PACKBITS functionality of Accusoft ImageGear 19.7. A specially crafted malformed file can cause a 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 19.7
ImageGear - https://www.accusoft.com/products/imagegear-collection/
8.1 - CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-704 - Incorrect Type Conversion or Cast
The ImageGear library is a document imaging developer toolkit that offers image conversion, creation, editing, annotation and more. It supports more than 100 formats, including many image formats, DICOM, PDF, Microsoft Office and others.
There is a vulnerability in the handle_COMPRESSION_PACKBITS
function, due to an integer overflow caused by a missing or wrong cast operation.
A specially crafted TIFF file can lead to an out-of-bounds write which can result in a memory corruption.
Trying to load a malformed TIFF file, we end up in the following situation:
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000178 ecx=fffffe9e edx=ffffff21 esi=ffffff21 edi=0d7f1000
eip=61c91323 esp=0093edf0 ebp=0093ee04 iopl=0 nv up ei ng nz na pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010287
MSVCR110!memset+0x24:
61c91323 f3aa rep stos byte ptr es:[edi]
0:000> !heap -p -a edi
address 0d7f1000 found in
_DPH_HEAP_ROOT @ 9f1000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
d7a2e38: d7f0e88 178 - d7f0000 2000
626aab70 verifier!AVrfDebugPageHeapAllocate+0x00000240
774e909b ntdll!RtlDebugAllocateHeap+0x00000039
7743bbad ntdll!RtlpAllocateHeap+0x000000ed
7743b0cf ntdll!RtlpAllocateHeapInternal+0x0000022f
7743ae8e ntdll!RtlAllocateHeap+0x0000003e
61c6dcff MSVCR110!malloc+0x00000049
61da61de igCore19d!AF_memm_alloc+0x0000001e
61eb8883 igCore19d!IG_mpi_page_set+0x0010cb33
61eb842b igCore19d!IG_mpi_page_set+0x0010c6db
61ebc11b igCore19d!IG_mpi_page_set+0x001103cb
61eb604b igCore19d!IG_mpi_page_set+0x0010a2fb
61d810d9 igCore19d!IG_image_savelist_get+0x00000b29
61dc0557 igCore19d!IG_mpi_page_set+0x00014807
61dbfeb9 igCore19d!IG_mpi_page_set+0x00014169
61d55777 igCore19d!IG_load_file+0x00000047
00f61372 Fuzzme!fuzzme+0x00000162
00f61778 Fuzzme!main+0x00000368
00f61f7d Fuzzme!__scrt_common_main_seh+0x000000fa
760e6359 KERNEL32!BaseThreadInitThunk+0x00000019
77467c24 ntdll!__RtlUserThreadStart+0x0000002f
77467bf4 ntdll!_RtlUserThreadStart+0x0000001b
As we can see, an out-of-bounds operation occurred.
The pseudo-code of the vulnerable function looks like this:
int handle_COMPRESSION_PACKBITS (mys_temporary_buffer *buffer_src, [1]
byte param_2, [4]
byte *dst_buffer, [2]
int parse_TiffBitRevTable, [3]
int param_5)
{
uint size_dst_buffer;
int status;
byte *src_data;
uint num_bytes;
uint offset_buffer;
int value_to_set;
size_dst_buffer = param_2;
offset_buffer = 0;
if (param_2 == 0) {
return 0;
}
do { [5]
/* get a byte from param_1 */
status = myf_get_one_byte_into_dst(buffer_src,param_2 + 3); [7]
if (status == 0) {
/* no more byte left into buffer_src */
return 0;
}
if (parse_TiffBitRevTable != 0) {
/* perform bit swift */
param_2[3] = TIFFBitRevTable[param_2[3]]; [8]
param_2 = param_2 & 0xffffff | (uint)param_2[3] << 0x18;
}
num_bytes = offset_buffer;
if (param_2[3] != 0x80) {
if ((char)param_2[3] < '\0') { [9]
/* get another byte */
myf_get_one_byte_into_dst(buffer_src,(undefined *)&value_to_set);
num_bytes = (1 - (uint)param_2[3]) + offset_buffer; [10]
if (size_dst_buffer < num_bytes) { [12]
if (param_5 != 0) {
return 1;
}
wrapper_memset(dst_buffer + offset_buffer,value_to_set,size_dst_buffer - offset_buffer);
return 0;
}
/* lead to crash */
wrapper_memset(dst_buffer + offset_buffer,value_to_set,1 - (uint)param_2[3]); [13]
}
else {
num_bytes = (uint)param_2[3] + 1;
if (size_dst_buffer < num_bytes + offset_buffer) {
if (param_5 != 0) {
return 1;
}
num_bytes = size_dst_buffer - offset_buffer;
}
src_data = myf_tiff_read_data(buffer_src,num_bytes);
if (src_data != (byte *)0x0) {
wrapper_memcpy(dst_buffer + offset_buffer,src_data,num_bytes);
}
num_bytes = offset_buffer + num_bytes;
}
}
offset_buffer = num_bytes; [11]
/* continue until end of buffer */
} while (num_bytes < size_dst_buffer); [6]
return 0;
}
The function handle_COMPRESSION_PACKBITS
decompresses a source buffer buffer_src
[1] into a destination buffer dst_buffer
[2], performing some swap operations if needed (depending on the value of parse_TiffBitRevTable
[3]).
The destination buffer size is stored into param_2
[4]. The source buffer contains the image bytes, and the size used to allocate the buffer is controlled directly by the tags present in the file, and is extracted earlier in the program.
The do-while loop from [5] to [6] is processing each byte of the source buffer read at [7]. Depending on the parse_TiffBitRevTable
[3] parameter, each byte may be bit-swapped using the table TIFFBitRevTable
[8].
Then the interesting part is coming up with the test performed in [9], which checks if the result (in param2
) is negative or not. It’s important to notice here that the value is casted to char
.
At [10] the length of the buffer is computed, but this time param_2
is used as an unsigned integer. This makes 1-(uint)param_2[3]
become a very large number, and, depending on the value of offset_buffer
, increases consistently [11], leading to an integer overflow.
Thus, the result stored into num_bytes
against the size of the buffer could lead an incorrect test in [12], and cause the program to execute the memset in [13] with a very large size parameter, corrupting the memory past the end of the buffer.
Crash output:
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-4DAOCFH
Key : Analysis.DebugData
Value: CreateObject
Key : Analysis.DebugModel
Value: CreateObject
Key : Analysis.Elapsed.Sec
Value: 17
Key : Analysis.Memory.CommitPeak.Mb
Value: 87
Key : Analysis.System
Value: CreateObject
Key : Timeline.OS.Boot.DeltaSec
Value: 495
Key : Timeline.Process.Start.DeltaSec
Value: 69
ADDITIONAL_XML: 1
NTGLOBALFLAG: 2100000
APPLICATION_VERIFIER_FLAGS: 0
APPLICATION_VERIFIER_LOADED: 1
EXCEPTION_RECORD: (.exr -1)
ExceptionAddress: 61c91323 (MSVCR110!memset+0x00000024)
ExceptionCode: c0000005 (Access violation)
ExceptionFlags: 00000000
NumberParameters: 2
Parameter[0]: 00000001
Parameter[1]: 0d7f1000
Attempt to write to address 0d7f1000
FAULTING_THREAD: 000034a8
PROCESS_NAME: Fuzzme.exe
WRITE_ADDRESS: 0d7f1000
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: 0d7f1000
STACK_TEXT:
0093edf0 61d4fc03 0d7f0f7d 00000000 ffffff21 MSVCR110!memset+0x24
WARNING: Stack unwind information not available. Following frames may be wrong.
0093ee04 61eb92e2 0d7f0f7d 00000000 ffffff21 igCore19d+0xfc03
0093ee2c 61eb8977 0c33efc8 e0000178 0d7f0e88 igCore19d!IG_mpi_page_set+0x10d592
0093ee80 61eb842b 0093f47c 10000028 0093ef10 igCore19d!IG_mpi_page_set+0x10cc27
0093eeb8 61ebc11b 0093f47c 10000028 00000000 igCore19d!IG_mpi_page_set+0x10c6db
0093eee0 61eb604b 0093f47c 10000028 1150ad68 igCore19d!IG_mpi_page_set+0x1103cb
0093f3f4 61d810d9 0093f47c 1150ad68 00000001 igCore19d!IG_mpi_page_set+0x10a2fb
0093f42c 61dc0557 00000000 1150ad68 0093f47c igCore19d!IG_image_savelist_get+0xb29
0093f6a8 61dbfeb9 00000000 09a87f20 00000001 igCore19d!IG_mpi_page_set+0x14807
0093f6c8 61d55777 00000000 09a87f20 00000001 igCore19d!IG_mpi_page_set+0x14169
0093f6e8 00f61372 09a87f20 0093f704 09a7eeb8 igCore19d!IG_load_file+0x47
0093f710 00f61778 09a87f20 09a85fe0 00000021 Fuzzme!fuzzme+0x162
0093f7a0 00f61f7d 00000005 09a7eeb8 0603ff38 Fuzzme!main+0x368
0093f7e8 760e6359 0067f000 760e6340 0093f854 Fuzzme!__scrt_common_main_seh+0xfa
0093f7f8 77467c24 0067f000 b04a9385 00000000 KERNEL32!BaseThreadInitThunk+0x19
0093f854 77467bf4 ffffffff 77488fdd 00000000 ntdll!__RtlUserThreadStart+0x2f
0093f864 00000000 00f62005 0067f000 00000000 ntdll!_RtlUserThreadStart+0x1b
STACK_COMMAND: ~0s ; .cxr ; kb
SYMBOL_NAME: MSVCR110!memset+24
MODULE_NAME: MSVCR110
IMAGE_NAME: MSVCR110.dll
FAILURE_BUCKET_ID: INVALID_POINTER_WRITE_AVRF_c0000005_MSVCR110.dll!memset
OS_VERSION: 10.0.18362.1
BUILDLAB_STR: 19h1_release
OSPLATFORM_TYPE: x86
OSNAME: Windows 10
FAILURE_ID_HASH: {cd57060f-2f30-c89b-a5b4-329faa47a0c1}
Followup: MachineOwner
---------
2020-06-23 - Vendor Disclosure
2020-09-01 - Public Release
Discovered by Emmanuel Tacheau of Cisco Talos.