Talos Vulnerability Report

TALOS-2019-0865

Accusoft ImageGear PNG IHDR width code execution vulnerability

December 2, 2019
CVE Number

CVE-2019-5076

Summary

An exploitable out-of-bounds write vulnerability exists in the igcore19d.dll PNG header-parser of the Accusoft ImageGear 19.3.0 library. A specially crafted PNG file can cause an out-of-bounds write, resulting in a remote code execution. An attacker needs to provide a malformed file to the viction 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 document0imaging developer toolkit providing all kinds of functionality related with an image conversion, creation, editing, etc.

There is a vulnerability in the function responsible for handling the PNG header. A specially crafted PNG IHDR Width field file can lead to an out of bounds write and remote code execution.

If we try to load a malformed PNG file via the IG_load_file function, we end up in the following situation:

First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
Time Travel Position: 488D3:0
igCore19d!IG_mpi_page_set+0x111d27:
00007fff`b8a00ec7 8022f0          and     byte ptr [rdx],0F0h ds:00000247`f35d0001=??
0:000> kb
 # RetAddr           : Args to Child                                                           : Call Site
00 00007fff`b89fc097 : 00000247`f35c0000 000000b7`fa6fd0b0 00000000`00000005 00000000`00000005 : igCore19d!IG_mpi_page_set+0x111d27
01 00007fff`b89fd770 : 00000000`00000000 000000b7`fa6fee30 00000000`00000000 00000247`e3101f20 : igCore19d!IG_mpi_page_set+0x10cef7
02 00007fff`b89fa75d : 00000000`00000000 000000b7`fa6feee0 00000000`1000001a 00007fff`d28040a4 : igCore19d!IG_mpi_page_set+0x10e5d0
03 00007fff`b88bd704 : 00000247`dc910ff0 00000000`00000008 00000247`e30f7fe0 6cca9737`00000037 : igCore19d!IG_mpi_page_set+0x10b5bd
04 00007fff`b8906c99 : 00000000`00000021 000000b7`fa6ff908 000000b7`fa6ff3e0 00000000`00000000 : igCore19d!IG_image_savelist_get+0xef4
05 00007fff`b890653c : 00000247`dc844fc8 00007fff`b88fcc95 00000000`00000000 00000000`00000000 : igCore19d!IG_mpi_page_set+0x17af9
06 00007fff`b888e7c0 : 00000247`e2fc4fa5 00007fff`ece39570 00000000`00000000 00007fff`ece380b0 : igCore19d!IG_mpi_page_set+0x1739c
07 00007ff7`55ff1112 : 00007fff`ece39570 00000000`00000000 00007fff`ece39570 000000b7`fa6ff938 : igCore19d!IG_load_file+0x80
08 00007ff7`55ff122c : 00000247`e2fc4fa5 00000247`e2fc4fec 00000247`00000021 00000000`00001000 : igFuzzer!fuzzme+0x32 [d:\projects\imagegear\fuzzer\igfuzzer\igfuzzer\igfuzzer.cpp @ 47] 
09 00007ff7`55ff1474 : 00000000`00000004 00000247`e2fc4f70 00007fff`efa83670 00000000`00000033 : igFuzzer!main+0xbc [d:\projects\imagegear\fuzzer\igfuzzer\igfuzzer\igfuzzer.cpp @ 79] 
0a (Inline Function) : --------`-------- --------`-------- --------`-------- --------`-------- : igFuzzer!invoke_main+0x22 [d:\agent\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78] 
0b 00007fff`edc04034 : 00000000`00000000 00000000`00000000 00000000`00000ca8 00000000`00000000 : igFuzzer!__scrt_common_main_seh+0x10c [d:\agent\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288] 
0c 00007fff`efa83691 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
0d 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21

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

Line 1  __int64 __fastcall Set_Element(PBYTE buffer, unsigned __int8 a2, __int16 a3, __int16 a4)
Line 2  {
Line 3    int index; // er8
Line 4    BYTE *ptr; // rdx
Line 5    int v4;
Line 5.1  int v5;
Line 6    v4 = a2;
Line 7    v5 = a2 * a3;
Line 8    v6 = (v5 >> 31) & 7;
Line 9    result = ((v6 + v5) & 7u) - v6;
Line 10   index = (v6 + v5) >> 3;
Line 11   
Line 12   (...)
Line 13   
Line 14   ptr = &buffer[index];
Line 15   if ( (_DWORD)result )
Line 16     *ptr &= 0xF0u;        

an access violation occurs at line 16. Let us check out the buffer variable:

0:000> !heap -p -a 0x000001a3e4859000
    address 000001a3e4859000 found in
    _DPH_HEAP_ROOT @ 1a3dcfd1000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                             1a3e4bb7ea0:      1a3e4859000             ffff -      1a3e4858000            12000
    00007ff85bf7f4bf ntdll!RtlDebugAllocateHeap+0x000000000000003f
    00007ff85bf2b530 ntdll!RtlpAllocateHeap+0x000000000008f760
    00007ff85be99725 ntdll!RtlpAllocateHeapInternal+0x00000000000005e5
    00007ff82f2a6407 MSVCR110!malloc+0x000000000000005b
    00007ff818e3781b igCore19d!AF_memm_alloc+0x000000000000002b
    00007ff818f4bdfe igCore19d!IG_mpi_page_set+0x000000000010cc5e
    00007ff818f4d770 igCore19d!IG_mpi_page_set+0x000000000010e5d0
    00007ff818f4a75d igCore19d!IG_mpi_page_set+0x000000000010b5bd
    00007ff818e0d704 igCore19d!IG_image_savelist_get+0x0000000000000ef4
    00007ff818e56c99 igCore19d!IG_mpi_page_set+0x0000000000017af9
    00007ff818e5653c igCore19d!IG_mpi_page_set+0x000000000001739c
    00007ff818dde7c0 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

The ptr variable points to a location 000001a3e4855001which is a location below the beginning of thebuffer` variable allocated space.

That situation appears because calculated index has the value : 0xffffffffffffc001 which added to beginning of the buffer address causes an integer overflow.

The size of the buffer, based on further analysis, is strictly related with the IHDR -> Width chunk field value. Which, in our case is equal to 0xFFFF.

Let us step out of this Set_Element function and see from where the two arguments based on which the index value is calculated are comming from:

Line 1   internalIndex = *((unsigned __int8 *)&v104 + v34 + 1);
Line 2   counter = 0;
Line 3   if ( *ptrToTableOfValues > 0u )
Line 4   {
Line 5      eleSize = elements[eleIndex + 1];
Line 6      do
Line 7      {
Line 8        if ( (signed __int64)DIB_bit_depth_get(objContext) > 16 )
Line 9        {
Line 10         v45 = 0i64;
Line 11         if ( (signed int)v38 > 0 )
Line 12         {
Line 13           v46 = v38 * counter;
Line 14           do
Line 15           {
Line 16             v47 = *((_BYTE *)v19 + v46);
Line 17             v46 = (unsigned int)(v46 + 1);
Line 18             v48 = v45++ + *(_QWORD *)&array[8 * index];
Line 19             *(_BYTE *)(v48 + internalIndex * (unsigned __int64)v38) = v47;
Line 20           }
Line 21           while ( v45 < v38 );
Line 22         }
Line 23       }
Line 24       else
Line 25       {
Line 26         v41 = (unsigned __int64)DIB_bit_depth_get(objContext);
Line 27         v42 = (signed int)(v41 * counter) / 8;
Line 28         if ( v41 > 8u )
Line 29           v43 = v19[v42 / 2];
Line 30         else
Line 31           v43 = ((unsigned __int16)((*((unsigned __int8 *)v19 + v42) << (signed int)(v41 * counter) % -8) & 0xFF) >> (8 - v41)) & 0xFF;
Line 32         bitDepth = (unsigned __int64)DIB_bit_depth_get(objContext);
Line 33         Set_Element(*(PBYTE *)&array[8 * index], bitDepth, internalIndex, v43);
Line 34       }
Line 35       ++counter;
Line 36       internalIndex += eleSize;
Line 37     }
Line 38     while ( counter < *maxValue );

We can see that while loop is controlled by the maxValue variable which in our case is equal 0x7FFF (it is IHDR->Width / 2) Each loop cycle the internalIndex variable is incremented by eleSize (in our case 2). Its easy to guess that internalIndex variable will be bigger than 0x7FFF because the number of iterations depends on maxValue and counter, which is incremented during each cycle just by 1. A value bigger than 0x7FFF (INT_MAX) for the internalIndex variable has additional consequences inside the Set_Elements function. The internalIndex value is passed to the Set_Elements function as the third argument in the form of __int16 (signed 16-bit integer). It means that its value in our case 0x8001 is smaller than 0 and thats why during the multiplication we end up with index < 0 ( 0xffffffffffffc001) what at the end results in ptr pointing to an address below the beginning of the buffer space. Additionaly there is no check to see whether the calculated index is not bigger than the buffer size. There are some checks inside PNG_get_deflate_data function related to IHDR->Width but for Width in the range of <0xFFBA; 0xFFFF> they are bypased and OOBW takes place. Also to trigger this vulnerability DIB_bit_depth needs to be smaller or equal 16. All these circumstances lead to out of bounds write and in consequences can allow an attacker to achieve remote code execution.

Crash Information

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



DUMP_CLASS: 2

DUMP_QUALIFIER: 2

MODLIST_WITH_TSCHKSUM_HASH:  1aa67d2c18112c8decc3d1fdc606b26bc45cbfe0

MODLIST_SHA1_HASH:  32defb2ff0bde8e528d48523aab41a049e5a1db6

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+111d27
00007fff`b8a00ec7 8022f0          and     byte ptr [rdx],0F0h

EXCEPTION_RECORD:  (.exr -1)
ExceptionAddress: 00007fffb8a00ec7 (igCore19d!IG_mpi_page_set+0x0000000000111d27)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 0000000000000000
   Parameter[1]: 00000247f35d0001
Attempt to read from address 00000247f35d0001

FAULTING_THREAD:  000032a0

PROCESS_NAME:  igFuzzer.exe

FOLLOWUP_IP: 
igCore19d!IG_mpi_page_set+111d27
00007fff`b8a00ec7 8022f0          and     byte ptr [rdx],0F0h

READ_ADDRESS:  00000247f35d0001 

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:  0000000000000000

EXCEPTION_PARAMETER2:  00000247f35d0001

WATSON_BKT_PROCSTAMP:  5d26f980

WATSON_BKT_MODULE:  igCore19d.dll

WATSON_BKT_MODSTAMP:  5c101edf

WATSON_BKT_MODOFFSET:  190ec7

WATSON_BKT_MODVER:  19.3.0.0

MODULE_VER_PRODUCT:  Accusoft ImageGear

BUILD_VERSION_STRING:  10.0.17134.753 (WinBuild.160101.0800)

ANALYSIS_SESSION_HOST:  ICELENOVO

ANALYSIS_SESSION_TIME:  07-18-2019 08:57:13.0937

ANALYSIS_VERSION: 10.0.18914.1001 amd64fre

THREAD_ATTRIBUTES: 
OS_LOCALE:  ENU

BUGCHECK_STR:  APPLICATION_FAULT_INVALID_POINTER_READ_AVRF

DEFAULT_BUCKET_ID:  INVALID_POINTER_READ_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:    [0x32a0]
    Frame:  [0] : igCore19d!IG_mpi_page_set

    ID:     [0n285]
    Type:   [INVALID_POINTER_READ]
    Class:  Primary
    Scope:  DEFAULT_BUCKET_ID (Failure Bucket ID prefix)
            BUCKET_ID
    Name:   Add
    Data:   Omit
    PID:    [Unspecified]
    TID:    [0x32a0]
    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:    [0x2d80]
    TID:    [0x32a0]
    Frame:  [0] : igCore19d!IG_mpi_page_set

LAST_CONTROL_TRANSFER:  from 00007fffb89fc097 to 00007fffb8a00ec7

STACK_TEXT:  
000000b7`fa6fcfa8 00007fff`b89fc097 : 00000247`f35c0000 000000b7`fa6fd0b0 00000000`00000005 00000000`00000005 : igCore19d!IG_mpi_page_set+0x111d27
000000b7`fa6fcfb0 00007fff`b89fd770 : 00000000`00000000 000000b7`fa6fee30 00000000`00000000 00000247`e3101f20 : igCore19d!IG_mpi_page_set+0x10cef7
000000b7`fa6fed80 00007fff`b89fa75d : 00000000`00000000 000000b7`fa6feee0 00000000`1000001a 00007fff`d28040a4 : igCore19d!IG_mpi_page_set+0x10e5d0
000000b7`fa6fede0 00007fff`b88bd704 : 00000247`dc910ff0 00000000`00000008 00000247`e30f7fe0 6cca9737`00000037 : igCore19d!IG_mpi_page_set+0x10b5bd
000000b7`fa6ff2f0 00007fff`b8906c99 : 00000000`00000021 000000b7`fa6ff908 000000b7`fa6ff3e0 00000000`00000000 : igCore19d!IG_image_savelist_get+0xef4
000000b7`fa6ff370 00007fff`b890653c : 00000247`dc844fc8 00007fff`b88fcc95 00000000`00000000 00000000`00000000 : igCore19d!IG_mpi_page_set+0x17af9
000000b7`fa6ff850 00007fff`b888e7c0 : 00000247`e2fc4fa5 00007fff`ece39570 00000000`00000000 00007fff`ece380b0 : igCore19d!IG_mpi_page_set+0x1739c
000000b7`fa6ff890 00007ff7`55ff1112 : 00007fff`ece39570 00000000`00000000 00007fff`ece39570 000000b7`fa6ff938 : igCore19d!IG_load_file+0x80
000000b7`fa6ff8e0 00007ff7`55ff122c : 00000247`e2fc4fa5 00000247`e2fc4fec 00000247`00000021 00000000`00001000 : igFuzzer!fuzzme+0x32
000000b7`fa6ff930 00007ff7`55ff1474 : 00000000`00000004 00000247`e2fc4f70 00007fff`efa83670 00000000`00000033 : igFuzzer!main+0xbc
000000b7`fa6ff970 00007fff`edc04034 : 00000000`00000000 00000000`00000000 00000000`00000ca8 00000000`00000000 : igFuzzer!__scrt_common_main_seh+0x10c
000000b7`fa6ff9b0 00007fff`efa83691 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
000000b7`fa6ff9e0 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21


STACK_COMMAND:  ~0s ; .cxr ; kb

THREAD_SHA1_HASH_MOD_FUNC:  5fc40328a3686d13dfccd6be23cd62ee5b669c24

THREAD_SHA1_HASH_MOD_FUNC_OFFSET:  e577b2c9c4565bf4611a915c81bb2af79ea54a08

THREAD_SHA1_HASH_MOD:  95fb16a8183863913ce23932c842443f579c4d87

FAULT_INSTR_CODE:  b9f02280

SYMBOL_STACK_INDEX:  0

SYMBOL_NAME:  igCore19d!IG_mpi_page_set+111d27

FOLLOWUP_NAME:  MachineOwner

MODULE_NAME: igCore19d

IMAGE_NAME:  igCore19d.dll

DEBUG_FLR_IMAGE_TIMESTAMP:  5c101edf

FAILURE_BUCKET_ID:  INVALID_POINTER_READ_AVRF_c0000005_igCore19d.dll!IG_mpi_page_set

BUCKET_ID:  APPLICATION_FAULT_INVALID_POINTER_READ_AVRF_igCore19d!IG_mpi_page_set+111d27

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:  111d27

BUCKET_ID_MODTIMEDATESTAMP:  5c101edf

BUCKET_ID_MODCHECKSUM:  41928b

BUCKET_ID_MODVER_STR:  19.3.0.0

BUCKET_ID_PREFIX_STR:  APPLICATION_FAULT_INVALID_POINTER_READ_AVRF_

FAILURE_PROBLEM_CLASS:  APPLICATION_FAULT

FAILURE_SYMBOL_NAME:  igCore19d.dll!IG_mpi_page_set

TARGET_TIME:  2019-07-16T14:59:31.000Z

OSBUILD:  9200

OSSERVICEPACK:  753

SERVICEPACK_NUMBER: 0

OS_REVISION: 0

OSPLATFORM_TYPE:  x64

OSNAME:  Windows 8

OSEDITION:  Windows 8

USER_LCID:  0

OSBUILD_TIMESTAMP:  unknown_date

BUILDDATESTAMP_STR:  160101.0800

BUILDLAB_STR:  WinBuild

BUILDOSVER_STR:  10.0.17134.753

ANALYSIS_SESSION_ELAPSED_TIME:  22489

ANALYSIS_SOURCE:  UM

FAILURE_ID_HASH_STRING:  um:invalid_pointer_read_avrf_c0000005_igcore19d.dll!ig_mpi_page_set

FAILURE_ID_HASH:  {bfd6b5ab-5824-8327-06e6-1c2f38a120f0}

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-07-30 - Vendor Disclosure
2019-11-27 - Vendor patched
2019-12-02 - Public Release

Credit

Discovered by Marcin 'Icewall' Noga of Cisco Talos.