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.