Talos Vulnerability Report

TALOS-2019-0922

Accusoft ImageGear BMP code execution vulnerability

December 2, 2019
CVE Number

CVE-2019-5133

Summary

An exploitable out-of-bounds write vulnerability exists in the igcore19d.dll BMP parser of the ImageGear 19.3.0 library. A specially crafted BMP 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

The ImageGear library is a document imaging developer toolkit providing all kinds of functionality related to image conversion, creation, editing, annotation, etc. It supports more than 100 formats, including many image formats, DICOM, PDF, Microsoft Office and others.

There is a vulnerability in the BMP image parser. A specially crafted BMP file can lead to an out-of-bounds write which can result in remote code execution.

Trying to load a malformed BMP file via IG_load_file function we end up in the following situation:

0:000> g
(4630.28c4): Access violation - code c0000005 (first/second chance not available)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
Time Travel Position: 193FF9:0
eax=000000d0 ebx=00000008 ecx=220e7ff8 edx=00000001 esi=00000008 edi=220ebff8
eip=63c8472f esp=004fee94 ebp=004feec0 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
igCore19d!IG_mpi_page_set+0x8bcf:
63c8472f 88040e          mov     byte ptr [esi+ecx],al      ds:002b:220e8000=??

Pseudo-code of the vulnerable function looks like this:

Line 1 	void __cdecl sub_63C84560(int a1, int arg_biCompression, int a3, int a4, int biWidth, int src_buffer, PBYTE dstBuffer, int calculatedSize)
Line 2 	{
Line 3 	  type = a4;
Line 4 	  biCompression = arg_biCompression;
Line 5 	  if ( calculatedSize < biWidth * arg_biCompression * (a4 >> 3) )
Line 6 		sub_63C11590(
Line 7 		  -401,
Line 8 		  "Insufficient Dst buffer size.",
Line 9 		  calculatedSize,
Line 10		  biWidth * arg_biCompression * (a4 >> 3),
Line 11		  "..\\..\\..\\..\\Common\\Core\\Raster.cpp",
Line 12		  857);
Line 13	 (...)
Line 14	  out_index = 0;
Line 15	  index = 0;
Line 16	  v26 = 0;
Line 17	  if ( biWidth > 0 )
Line 18	  {
Line 19		biBitCount = a1;
Line 20		do
Line 21		{
Line 22		  if ( biBitCount == 8 )
Line 23		  {
Line 24			srcElement = *(unsigned __int8 *)(out_index + src_buffer);
Line 25		  }
Line 26		  else if ( biBitCount == 16 )
Line 27		  {
Line 28			srcElement = *(unsigned __int16 *)(src_buffer + 2 * out_index);
Line 29		  }
Line 30		  else
Line 31		  {
Line 32			if ( biBitCount != 32 )
Line 33			  sub_63C11590(-401, 0, biBitCount, 0, "..\\..\\..\\..\\Common\\Core\\Raster.cpp", 881);
Line 34			srcElement = *(_DWORD *)(src_buffer + 4 * out_index);
Line 35		  }
Line 36		  v25 = srcElement;
Line 37		  if ( biCompression > 0 )
Line 38		  {
Line 39			v20 = v24;
Line 40			tmp_biCompression = arg_biCompression;
Line 41			v22 = a3 - (_DWORD)v24;
Line 42			do
Line 43			{
Line 44			  element = (srcElement & *(_DWORD *)((char *)v20 + v22)) >> *v20;
Line 45			  if ( type == 8 )
Line 46			  {
Line 47				dstBuffer[index] = element;
Line 48			  }
Line 49			  else if ( type == 16 )
Line 50			  {
Line 51				*(_WORD *)&dstBuffer[2 * index] = element;
Line 52			  }
Line 53			  else
Line 54			  {
Line 55				if ( type != 32 )
Line 56				  sub_63C11590(-401, 0, type, 0, "..\\..\\..\\..\\Common\\Core\\Raster.cpp", 906);
Line 57				*(_DWORD *)&dstBuffer[4 * index] = element;
Line 58			  }
Line 59			  v22 = a3 - (_DWORD)v24;
Line 60			  srcElement = v25;
Line 61			  ++index;
Line 62			  ++v20;
Line 63			  --tmp_biCompression;
Line 64			}
Line 65			while ( tmp_biCompression );
Line 66			out_index = v26;
Line 67			biCompression = arg_biCompression;
Line 68		  }
Line 69		  biBitCount = a1;
Line 70		  v26 = ++out_index;
Line 71		}
Line 72		while ( out_index < biWidth );
Line 73	  }	

The out-of-bounds write appears at line 47. We can see that there are two while loops where the internal one is controlled by the biCompression value (a fixed value passed as argument) equal to three and the outer while is controlled by biWidth value, which is read from the file, which in our case has the value 0x40000000. The space fordstBuffer is allocated based on the following formula formula:

unsigned int __thiscall sub_63C494D0(struct_this *this)
{
  return ((this->biWidth * this->biCompression * this->dword38 + 31) >> 3) & 0xFFFFFFFC;
}

this->dword38 = 0x20 - const 
this->biCompression = 3 
this->biWidth = 0x40000000

In our case, the result from above formula is equal to 0 so the allocated space for a dstBuffer is 8 bytes in size in our case (Windows 10/x86). We can also notice that the check done at line 5 is bypassed because the signed values are compared and the right side of the inequality is below zero (0xc0000000).

An attacker could control all presented variables through proper file content manipulation. Using the biWidth field attackers can cause an out-of-bounds write, leading to memory corruption which in turn can allow them to achieve remote code execution.

Crash Information

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

*** WARNING: Unable to verify checksum for FuzzmeHeap.exe

KEY_VALUES_STRING: 1

	Key  : AV.Fault
	Value: Write

	Key  : Analysis.CPU.Sec
	Value: 6

	Key  : Analysis.DebugAnalysisProvider.CPP
	Value: Create: 8007007e on ICELENOVO

	Key  : Analysis.DebugData
	Value: CreateObject

	Key  : Analysis.DebugModel
	Value: CreateObject

	Key  : Analysis.Elapsed.Sec
	Value: 7

	Key  : Analysis.Memory.CommitPeak.Mb
	Value: 445

	Key  : Analysis.System
	Value: CreateObject

	Key  : Timeline.OS.Boot.DeltaSec
	Value: 2237994


APPLICATION_VERIFIER_LOADED: 1

EXCEPTION_RECORD:  (.exr -1)
ExceptionAddress: 63c8472f (igCore19d!IG_mpi_page_set+0x00008bcf)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 00000001
   Parameter[1]: 220e8000
Attempt to write to address 220e8000

FAULTING_THREAD:  000028c4

PROCESS_NAME:  FuzzmeHeap.exe

WRITE_ADDRESS:  220e8000 

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:  220e8000

STACK_TEXT:  
WARNING: Stack unwind information not available. Following frames may be wrong.
004feec0 63c32ca0 00000010 00000003 004fef6c igCore19d!IG_mpi_page_set+0x8bcf
004feee8 63cee2cf 00000010 00000003 004fef6c igCore19d!IG_thread_image_unlock+0x42c0
004fef7c 63cf05e3 004ff51c 1000001b 18d0efe8 igCore19d!IG_mpi_page_set+0x7276f
004fefcc 63ced685 004ff51c 1000001b 18d0efe8 igCore19d!IG_mpi_page_set+0x74a83
004ff494 63c50ff9 004ff51c 18d0efe8 00000001 igCore19d!IG_mpi_page_set+0x71b25
004ff4cc 63c90437 00000000 18d0efe8 004ff51c igCore19d!IG_image_savelist_get+0xb29
004ff748 63c8fd99 00000000 18cb6fc0 00000001 igCore19d!IG_mpi_page_set+0x148d7
004ff768 63c26767 00000000 18cb6fc0 00000001 igCore19d!IG_mpi_page_set+0x14239
004ff788 007110af 18cb6fc0 004ff7a0 19880fb4 igCore19d!IG_load_file+0x47
004ff7ac 00711252 18cb6fc0 004ff7e4 00000021 FuzzmeHeap!fuzzme+0x2f
004ff80c 00711d55 00000003 19880f70 15f3bf50 FuzzmeHeap!fuzzme+0x1d2
004ff854 756a6359 00368000 756a6340 004ff8c0 FuzzmeHeap!fuzzme+0xcd5
004ff864 77dc7b74 00368000 5cc4ad35 00000000 KERNEL32!BaseThreadInitThunk+0x19
004ff8c0 77dc7b44 ffffffff 77de8f2a 00000000 ntdll!__RtlUserThreadStart+0x2f
004ff8d0 00000000 00711dcb 00368000 00000000 ntdll!_RtlUserThreadStart+0x1b


STACK_COMMAND:  ~0s ; .cxr ; kb

SYMBOL_NAME:  igCore19d!IG_mpi_page_set+8bcf

MODULE_NAME: igCore19d

IMAGE_NAME:  igCore19d.dll

FAILURE_BUCKET_ID:  INVALID_POINTER_WRITE_AVRF_c0000005_igCore19d.dll!IG_mpi_page_set

OSPLATFORM_TYPE:  x86

OSNAME:  Windows 8

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

Followup:     MachineOwner
---------



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-10-23 - Vendor Disclosure
2019-11-27 - Vendor Patched
2019-12-02 - Public Release

Credit

Discovered by Marcin 'Icewall' Noga of Cisco Talos.