Talos Vulnerability Report

TALOS-2019-0875

Accusoft ImageGear TIFF TIF_decode_thunderscan code execution vulnerability

December 2, 2019
CVE Number

CVE-2019-5083

Summary

An exploitable out-of-bounds write vulnerability exists in the igcore19d.dll TIF_decode_thunderscan function of Accusoft ImageGear 19.3.0 library. A specially crafted TIFF file can cause an out of bounds write, resulting in a remote code execution. An attacker needs to provide a malformed file to the victim to trigger the vulnerability.

Tested Versions

Accusoft ImageGear 19.3.0

Product URLs

https://www.accusoft.com/products/imagegear/overview/

CVSSv3 Score

9.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

CWE

CWE-787: Out-of-bounds Write

Details

This vulnerability is present in the Accusoft ImageGear library which is a document imaging developer toolkit providing all kinds of functionality related with an image conversion, creation, editing,etc.

There is a vulnerability in the TIF_decode_thunderscan function . A specially crafted TIFF file can lead to an out of bounds write and remote code execution. Trying to load a malformed TIFF file via IG_load_file function, we end up in the following situation:

(21b8.2a30): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
igCore19d!IG_mpi_page_set+0x1402e0:
00007ff8`1d35f480 f3aa            rep stos byte ptr [rdi]
0:000> kb
 # RetAddr           : Args to Child                                                           : Call Site
00 00007ff8`1d35e760 : 000000de`ce1ef210 00007ff8`1d35f23f 000001e4`08fb8ff0 00000000`00000000 : igCore19d!IG_mpi_page_set+0x1402e0
01 00007ff8`1d35df56 : 000000de`ce1ef8b0 00000000`1000001e 00000000`00000003 000001e4`08b65d50 : igCore19d!IG_mpi_page_set+0x13f5c0
02 00007ff8`1d362867 : 00000000`00000004 000000de`ce1ef8b0 00000000`00000000 000001e4`000003e8 : igCore19d!IG_mpi_page_set+0x13edb6
03 00007ff8`1d35b154 : 00000000`00000001 000001e4`08b65d50 00000000`1000001e 000000de`ce1ef380 : igCore19d!IG_mpi_page_set+0x1436c7
04 00007ff8`1d1ed704 : 000001e4`022e8ff0 00000000`00000004 00000000`00000000 00007ff8`00000037 : igCore19d!IG_mpi_page_set+0x13bfb4
05 00007ff8`1d236c99 : 00000000`00000028 000000de`ce1efdd8 000000de`ce1ef8b0 00000000`00000000 : igCore19d!IG_image_savelist_get+0xef4
06 00007ff8`1d23653c : 000001e4`0221cfc8 00007ff8`1d22cc95 00000000`00000000 00000000`00000000 : igCore19d!IG_mpi_page_set+0x17af9
07 00007ff8`1d1be7c0 : 000001e4`08908fa5 00007ff8`58519570 00000000`00000000 00007ff8`585180b0 : igCore19d!IG_mpi_page_set+0x1739c
*** WARNING: Unable to verify checksum for igFuzzer.exe
08 00007ff6`33951112 : 00007ff8`58519570 00000000`00000000 00007ff8`58519570 000000de`ce1efe08 : igCore19d!IG_load_file+0x80
09 00007ff6`3395122c : 000001e4`08908fa5 000001e4`08908fe8 000001e4`00000021 00000000`00001000 : igFuzzer!fuzzme+0x32 [d:\projects\imagegear\fuzzer\igfuzzer\igfuzzer\igfuzzer.cpp @ 47] 
0a 00007ff6`33951474 : 00000000`00000004 000001e4`08908f70 00000000`00000000 00000000`00000000 : igFuzzer!main+0xbc [d:\projects\imagegear\fuzzer\igfuzzer\igfuzzer\igfuzzer.cpp @ 79] 
0b (Inline Function) : --------`-------- --------`-------- --------`-------- --------`-------- : igFuzzer!invoke_main+0x22 [d:\agent\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78] 
0c 00007ff8`5b974034 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : igFuzzer!__scrt_common_main_seh+0x10c [d:\agent\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288] 
0d 00007ff8`5bef3691 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
0e 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21

As we can see, an out-of-bounds write operation occurred. Looking at the pseudo-code of this vulnerable function we can see that:

Line 1 	__int64 __fastcall TIF_decode_thunderscan(__int64 a1, unsigned int a2, char *a3)
Line 2 	{
Line 3 	  unsigned int currentOffset; // ebp
Line 4 	  char v4; // si
Line 5 	  char *dstBuffer; // rbx
Line 6 	  unsigned int value_div2; // er15
Line 7 	  struct_a1 *v7; // r13
Line 8 	  unsigned int loopCounter; // er14
Line 9 	  unsigned int valFromFile; // er12
Line 10	  char _readByteFromFile; // r8
Line 11	  char v11; // al
Line 12	  signed int v12; // edx
Line 13	  int v13; // eax
Line 14	  char v14; // si
Line 15	  char v15; // al
Line 16	  signed int loopLimit; // edx
Line 17	  int x; // eax
Line 18	  char v18; // si
Line 19	  char v19; // al
Line 20	  char v20; // si
Line 21	  char *v21; // rdi
Line 22	  unsigned __int64 v22; // rdx
Line 23	  char readByteFromFile; // [rsp+58h] [rbp+10h]
Line 24
Line 25	  currentOffset = 0;
Line 26	  v4 = 0;
Line 27	  dstBuffer = a3;
Line 28	  value_div2 = a2;
Line 29	  v7 = (struct_a1 *)a1;
Line 30	  loopCounter = 0;
Line 31	  if ( !a2 )
Line 32		return 0i64;
Line 33	  valFromFile = 2 * a2;
Line 34	  do
Line 35	  {
Line 36		if ( currentOffset >= valFromFile || !(unsigned int)IOb_byte_read(v7, &readByteFromFile) )
Line 37		  break;
Line 38		_readByteFromFile = readByteFromFile;
Line 39		if ( readByteFromFile & 0xC0 )
Line 40		{
Line 41		  switch ( readByteFromFile & 0xC0 )
Line 42		  {
Line 43			(...)
Line 44		  }
Line 45		}
Line 46		else
Line 47		{
Line 48		  (...)
Line 49		  if ( _readByteFromFile > 0 )
Line 50		  {
Line 51			v21 = dstBuffer;
Line 52			v22 = (unsigned __int8)(((unsigned __int8)(_readByteFromFile - 1) >> 1) + 1);
Line 53			dstBuffer += v22;
Line 54			memset(v21, v20, v22);
Line 55			_readByteFromFile += -2 * v22;
Line 56			readByteFromFile = _readByteFromFile;
Line 57		  }
Line 58		  if ( _readByteFromFile == -1 )
Line 59		  {
Line 60			--dstBuffer;
Line 61			*dstBuffer &= 0xF0u;
Line 62		  }
Line 63		  v4 = v20 & 0xF;
Line 64		}
Line 65		++loopCounter;
Line 66	  }
Line 67	  while ( loopCounter < value_div2 );
Line 68	  return 0i64;
Line 69	}

A while loop is controlled via the value_div2 variable. Its value is equal to 1 and comes from a value read directly from the file at offset 0xAA (IFD->ENT->value [2 bytes size]) and later divided by 2 (the value can not be smaller than 1 so for 1 the result is 1 and for 2 and 3 it is also 1). dstBuffer is also allocated based on that value :

0:000> !heap -p -a 0x000002b99906eff0
	address 000002b99906eff0 found in
	_DPH_HEAP_ROOT @ 2b9919a1000
	in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
							 2b99963a000:      2b99906eff0                1 -      2b99906e000             2000
	00007ff85bf7f4bf ntdll!RtlDebugAllocateHeap+0x000000000000003f
	00007ff85bf2b530 ntdll!RtlpAllocateHeap+0x000000000008f760
	00007ff85be99725 ntdll!RtlpAllocateHeapInternal+0x00000000000005e5
	00007ff838cc6407 MSVCR110!malloc+0x000000000000005b
	00007ff81d21781b igCore19d!AF_memm_alloc+0x000000000000002b
	00007ff81d35e613 igCore19d!IG_mpi_page_set+0x000000000013f473
	00007ff81d35df56 igCore19d!IG_mpi_page_set+0x000000000013edb6
	00007ff81d362867 igCore19d!IG_mpi_page_set+0x00000000001436c7
	00007ff81d35b154 igCore19d!IG_mpi_page_set+0x000000000013bfb4
	00007ff81d1ed704 igCore19d!IG_image_savelist_get+0x0000000000000ef4
	00007ff81d236c99 igCore19d!IG_mpi_page_set+0x0000000000017af9
	00007ff81d23653c igCore19d!IG_mpi_page_set+0x000000000001739c
	00007ff81d1be7c0 igCore19d!IG_load_file+0x0000000000000080
*** WARNING: Unable to verify checksum for igFuzzer.exe
	00007ff633951112 igFuzzer!fuzzme+0x0000000000000032 [d:\projects\imagegear\fuzzer\igfuzzer\igfuzzer\igfuzzer.cpp @ 47]
	00007ff63395122c igFuzzer!main+0x00000000000000bc [d:\projects\imagegear\fuzzer\igfuzzer\igfuzzer\igfuzzer.cpp @ 79]
	00007ff633951474 igFuzzer!__scrt_common_main_seh+0x000000000000010c [d:\agent\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288]
	00007ff85b974034 KERNEL32!BaseThreadInitThunk+0x0000000000000014
	00007ff85bef3691 ntdll!RtlUserThreadStart+0x0000000000000021

During each loop cycle a byte is read from the file at line 36 into the variable readByteFromFile starting from offset:

0 + sizeof(IFH) == 8

Next based on the value of readByteFromFile a proper action is taken. The vulnerability appears if value of readByteFromFile is in the range <1;63> and its value after specific calculations (((i - 1)/ 2 ) + 1) is bigger than the value used for dstBuffer allocation. In our case the capacity of dstBuffer is 1 and the byte read from offset 0x8 is equal to 0x3F. Passing 0x3F value into above formula we obtain a value equal to 0x20. That value is used in a memcpy operation as a size argument.

Below code generating possible pair values :

>>> for i in range(0,255):
	if not (i & 0xC0):
		print "Read byte : 0x{0:X} - memcpy size arg : 0x{1:X}".format(i,(((i - 1)/ 2 ) + 1))
	
	(...)
	Read byte : 0x3F - memcpy size arg : 0x20

All these circumstances lead to an out of bounds write which can allow an attacker to gain remote code execution.

Crash Information

0:000> !analyze -v
*******************************************************************************
*                                                                             *
*                        Exception Analysis                                   *
*                                                                             *
*******************************************************************************


DUMP_CLASS: 2

DUMP_QUALIFIER: 0

MODLIST_WITH_TSCHKSUM_HASH:  e6311aced106b626c7f0882ce5e44d10d5417c9e

MODLIST_SHA1_HASH:  65c7eea70564f728d27c5e9e2c5360b823e6fec9

NTGLOBALFLAG:  2100000

APPLICATION_VERIFIER_FLAGS:  0

PRODUCT_TYPE:  1

SUITE_MASK:  272

DUMP_TYPE:  fe

APPLICATION_VERIFIER_LOADED: 1

FAULTING_IP: 
igCore19d!IG_mpi_page_set+1402e0
00007ff8`1d35f480 f3aa            rep stos byte ptr [rdi]

EXCEPTION_RECORD:  (.exr -1)
ExceptionAddress: 00007ff81d35f480 (igCore19d!IG_mpi_page_set+0x00000000001402e0)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 0000000000000001
   Parameter[1]: 000002b99906f000
Attempt to write to address 000002b99906f000

FAULTING_THREAD:  000008ec

PROCESS_NAME:  igFuzzer.exe

FOLLOWUP_IP: 
igCore19d!IG_mpi_page_set+1402e0
00007ff8`1d35f480 f3aa            rep stos byte ptr [rdi]

WRITE_ADDRESS:  000002b99906f000 

ERROR_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%p referenced memory at 0x%p. The memory could not be %s.

EXCEPTION_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:  0000000000000001

EXCEPTION_PARAMETER2:  000002b99906f000

WATSON_BKT_PROCSTAMP:  5d26f980

WATSON_BKT_MODULE:  igCore19d.dll

WATSON_BKT_MODSTAMP:  5c101edf

WATSON_BKT_MODOFFSET:  1bf480

WATSON_BKT_MODVER:  19.3.0.0

MODULE_VER_PRODUCT:  Accusoft ImageGear

BUILD_VERSION_STRING:  17134.1.amd64fre.rs4_release.180410-1804

ANALYSIS_SESSION_HOST:  ICELENOVO

ANALYSIS_SESSION_TIME:  07-19-2019 07:14:24.0934

ANALYSIS_VERSION: 10.0.18914.1001 amd64fre

THREAD_ATTRIBUTES: 
OS_LOCALE:  ENU

BUGCHECK_STR:  APPLICATION_FAULT_INVALID_POINTER_WRITE_AVRF

DEFAULT_BUCKET_ID:  INVALID_POINTER_WRITE_AVRF

PRIMARY_PROBLEM_CLASS:  APPLICATION_FAULT

PROBLEM_CLASSES: 

	ID:     [0n313]
	Type:   [@ACCESS_VIOLATION]
	Class:  Addendum
	Scope:  BUCKET_ID
	Name:   Omit
	Data:   Omit
	PID:    [Unspecified]
	TID:    [0x8ec]
	Frame:  [0] : igCore19d!IG_mpi_page_set

	ID:     [0n286]
	Type:   [INVALID_POINTER_WRITE]
	Class:  Primary
	Scope:  DEFAULT_BUCKET_ID (Failure Bucket ID prefix)
			BUCKET_ID
	Name:   Add
	Data:   Omit
	PID:    [Unspecified]
	TID:    [0x8ec]
	Frame:  [0] : igCore19d!IG_mpi_page_set

	ID:     [0n98]
	Type:   [AVRF]
	Class:  Addendum
	Scope:  DEFAULT_BUCKET_ID (Failure Bucket ID prefix)
			BUCKET_ID
	Name:   Add
	Data:   Omit
	PID:    [0x1be0]
	TID:    [0x8ec]
	Frame:  [0] : igCore19d!IG_mpi_page_set

LAST_CONTROL_TRANSFER:  from 00007ff81d35e760 to 00007ff81d35f480

STACK_TEXT:  
000000fe`6a1be940 00007ff8`1d35e760 : 000000fe`6a1beba0 00007ff8`1d35f23f 000002b9`99068ff0 00000000`00000000 : igCore19d!IG_mpi_page_set+0x1402e0
000000fe`6a1be990 00007ff8`1d35df56 : 000000fe`6a1bf240 00000000`1000001e 00000000`00000003 000002b9`98c15d50 : igCore19d!IG_mpi_page_set+0x13f5c0
000000fe`6a1bea60 00007ff8`1d362867 : 00000000`00000004 000000fe`6a1bf240 00000000`00000000 000002b9`000003e8 : igCore19d!IG_mpi_page_set+0x13edb6
000000fe`6a1beae0 00007ff8`1d35b154 : 00000000`00000001 000002b9`98c15d50 00000000`1000001e 000000fe`6a1bed10 : igCore19d!IG_mpi_page_set+0x1436c7
000000fe`6a1beb50 00007ff8`1d1ed704 : 000002b9`92488ff0 00000000`00000004 00000000`00000000 00007ff8`00000037 : igCore19d!IG_mpi_page_set+0x13bfb4
000000fe`6a1bf150 00007ff8`1d236c99 : 00000000`00000028 000000fe`6a1bf768 000000fe`6a1bf240 00000000`00000000 : igCore19d!IG_image_savelist_get+0xef4
000000fe`6a1bf1d0 00007ff8`1d23653c : 000002b9`923bcfc8 00007ff8`1d22cc95 00000000`00000000 00000000`00000000 : igCore19d!IG_mpi_page_set+0x17af9
000000fe`6a1bf6b0 00007ff8`1d1be7c0 : 000002b9`989b8fa5 00007ff8`58519570 00000000`00000000 00007ff8`585180b0 : igCore19d!IG_mpi_page_set+0x1739c
000000fe`6a1bf6f0 00007ff6`33951112 : 00007ff8`58519570 00000000`00000000 00007ff8`58519570 000000fe`6a1bf798 : igCore19d!IG_load_file+0x80
000000fe`6a1bf740 00007ff6`3395122c : 000002b9`989b8fa5 000002b9`989b8fe8 000002b9`00000021 00000000`00001000 : igFuzzer!fuzzme+0x32
000000fe`6a1bf790 00007ff6`33951474 : 00000000`00000004 000002b9`989b8f70 00000000`00000000 00000000`00000000 : igFuzzer!main+0xbc
000000fe`6a1bf7d0 00007ff8`5b974034 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : igFuzzer!__scrt_common_main_seh+0x10c
000000fe`6a1bf810 00007ff8`5bef3691 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
000000fe`6a1bf840 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21


STACK_COMMAND:  ~0s ; .cxr ; kb

THREAD_SHA1_HASH_MOD_FUNC:  82a7bc6dd3104d830ca26855c5e07a6d00940aaa

THREAD_SHA1_HASH_MOD_FUNC_OFFSET:  b0d9fe750119f0cca8352c6ddb9fc4142deab619

THREAD_SHA1_HASH_MOD:  252eeb5ab64e7dfeac0928d869d2e1c90b7990ce

FAULT_INSTR_CODE:  244aaf3

SYMBOL_STACK_INDEX:  0

SYMBOL_NAME:  igCore19d!IG_mpi_page_set+1402e0

FOLLOWUP_NAME:  MachineOwner

MODULE_NAME: igCore19d

IMAGE_NAME:  igCore19d.dll

DEBUG_FLR_IMAGE_TIMESTAMP:  5c101edf

FAILURE_BUCKET_ID:  INVALID_POINTER_WRITE_AVRF_c0000005_igCore19d.dll!IG_mpi_page_set

BUCKET_ID:  APPLICATION_FAULT_INVALID_POINTER_WRITE_AVRF_igCore19d!IG_mpi_page_set+1402e0

FAILURE_EXCEPTION_CODE:  c0000005

FAILURE_IMAGE_NAME:  igCore19d.dll

BUCKET_ID_IMAGE_STR:  igCore19d.dll

FAILURE_MODULE_NAME:  igCore19d

BUCKET_ID_MODULE_STR:  igCore19d

FAILURE_FUNCTION_NAME:  IG_mpi_page_set

BUCKET_ID_FUNCTION_STR:  IG_mpi_page_set

BUCKET_ID_OFFSET:  1402e0

BUCKET_ID_MODTIMEDATESTAMP:  5c101edf

BUCKET_ID_MODCHECKSUM:  41928b

BUCKET_ID_MODVER_STR:  19.3.0.0

BUCKET_ID_PREFIX_STR:  APPLICATION_FAULT_INVALID_POINTER_WRITE_AVRF_

FAILURE_PROBLEM_CLASS:  APPLICATION_FAULT

FAILURE_SYMBOL_NAME:  igCore19d.dll!IG_mpi_page_set

TARGET_TIME:  2019-07-19T10:14:30.000Z

OSBUILD:  17134

OSSERVICEPACK:  753

SERVICEPACK_NUMBER: 0

OS_REVISION: 0

OSPLATFORM_TYPE:  x64

OSNAME:  Windows 10

OSEDITION:  Windows 10 WinNt SingleUserTS

USER_LCID:  0

OSBUILD_TIMESTAMP:  unknown_date

BUILDDATESTAMP_STR:  180410-1804

BUILDLAB_STR:  rs4_release

BUILDOSVER_STR:  10.0.17134.1.amd64fre.rs4_release.180410-1804

ANALYSIS_SESSION_ELAPSED_TIME:  1588

ANALYSIS_SOURCE:  UM

FAILURE_ID_HASH_STRING:  um:invalid_pointer_write_avrf_c0000005_igcore19d.dll!ig_mpi_page_set

FAILURE_ID_HASH:  {39ff52ad-9054-81fd-3e4d-ef5d82e4b2c1}


0:000> lmv a rip
Browse full module list
start             end                 module name
00007fff`b8870000 00007fff`b8c83000   igCore19d   (export symbols)       igCore19d.dll
	Loaded symbol image file: igCore19d.dll
	Mapped memory image file: d:\projects\ImageGear\Build\Bin\x64\igCore19d.dll
	Image path: d:\projects\ImageGear\Build\Bin\x64\igCore19d.dll
	Image name: igCore19d.dll
	Browse all global symbols  functions  data
	Timestamp:        Tue Dec 11 16:32:31 2018 (5C101EDF)
	CheckSum:         0041928B
	ImageSize:        00413000
	File version:     19.3.0.0
	Product version:  19.3.0.0
	File flags:       0 (Mask 3F)
	File OS:          4 Unknown Win32
	File type:        2.0 Dll
	File date:        00000000.00000000
	Translations:     0409.04b0
	Information from resource tables:
		CompanyName:      Accusoft Corporation
		ProductName:      Accusoft ImageGear
		InternalName:     igcore19d.dll
		OriginalFilename: igcore19d.dll
		ProductVersion:   19.3.0.0
		FileVersion:      19.3.0.0
		FileDescription:  Accusoft ImageGear CORE DLL 
		LegalCopyright:   Copyright© 1996-2018 Accusoft Corporation. All rights reserved.
		LegalTrademarks:  ImageGearÆ and AccusoftÆ are registered trademarks of Accusoft Corporation

Timeline

2019-07-30 - Vendor Disclosure
2019-11-27 - Vendor patched
2019-12-02 - Public Release

Credit

Discovered by Marcin Towalski & Marcin 'Icewall' Noga of Cisco Talos.