Talos Vulnerability Report

TALOS-2020-0993

Accusoft ImageGear JPEG jpegread precision code execution vulnerability

February 10, 2020
CVE Number

CVE-2020-6069

Summary

An exploitable out-of-bounds write vulnerability exists in the igcore19d.dll JPEG jpegread precision parser of the Accusoft ImageGear 19.5.0 library. A specially crafted JPEG 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.5.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 JPEG raster image parser. A specially crafted JPEG file can lead to an out-of-bounds write resulting in remote code execution.

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

(397c.4b88): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=ffff887f ebx=00000000 ecx=ffffe380 edx=0c067004 esi=00000fff edi=ffff887f
eip=5b9d4669 esp=0100f084 ebp=0100f09c iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
igCore19d!IG_mpi_page_set+0xc92d9:
5b9d4669 66895afc        mov     word ptr [edx-4],bx      ds:002b:0c067000=????

Checking the buffer capacity pointed to by edx, we can see:

0:000> !heap -p -a 0c067000
    address 0c067000 found in
    _DPH_HEAP_ROOT @ 3811000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                 c060bfc:          c066d48              2b4 -          c066000             2000
          unknown!fillpattern
    5bbfab70 verifier!AVrfDebugPageHeapAllocate+0x00000240
    77378fcb ntdll!RtlDebugAllocateHeap+0x00000039
    772cbb0d ntdll!RtlpAllocateHeap+0x000000ed
    772cb02f ntdll!RtlpAllocateHeapInternal+0x0000022f
    772cadee ntdll!RtlAllocateHeap+0x0000003e
    5b56daff MSVCR110!malloc+0x00000049
    5b90582e igCore19d!AF_memm_alloc+0x0000001e
    5b9d24ad igCore19d!IG_mpi_page_set+0x000c711d
    5b9befc5 igCore19d!IG_mpi_page_set+0x000b3c35
    5b9d6115 igCore19d!IG_mpi_page_set+0x000cad85
    5b9d5f3d igCore19d!IG_mpi_page_set+0x000cabad
    5b9d40b1 igCore19d!IG_mpi_page_set+0x000c8d21
    5b9d57da igCore19d!IG_mpi_page_set+0x000ca44a
    5b8e07c9 igCore19d!IG_image_savelist_get+0x00000b29
    5b91fb97 igCore19d!IG_mpi_page_set+0x00014807
    5b91f4f9 igCore19d!IG_mpi_page_set+0x00014169
    5b8b6007 igCore19d!IG_load_file+0x00000047
    00ef59ac simple_exe_141+0x000159ac
    00ef61a7 simple_exe_141+0x000161a7
    00ef6cbe simple_exe_141+0x00016cbe
    00ef6b27 simple_exe_141+0x00016b27
    00ef69bd simple_exe_141+0x000169bd
    00ef6d38 simple_exe_141+0x00016d38
    74f56359 KERNEL32!BaseThreadInitThunk+0x00000019
    772f7b74 ntdll!__RtlUserThreadStart+0x0000002f
    772f7b44 ntdll!_RtlUserThreadStart+0x0000001b

The calculated address 0c067000 is outside of the allocation for this buffer space.

Going back to the allocation, we land in the following place:

Line 1  int __stdcall sub_5B9D2100(int a1, void *a2, int a3, int a4)
Line 2  {
Line 3      (...)
Line 4    mem_size = getMemSize((_DWORD *)v4->dword84);
Line 5    v4->dword8C = mem_size;
Line 6    v31 = AF_memm_alloc(v4->dword80, mem_size + 24, (int)"..\\..\\..\\..\\Common\\Formats\\jpgread.c", 3348);
Line 7    (...)
Line 8  }

The getMemSize function at line 4 calculates the returned mem_size as follows:

precision < 0x8  = 0x8
precision < 0x10 = 0x10 
precision < 0x20 = 0x20
    exception

(((value_base_on_precision * nr_comp * x_image) + 0x1F) >> 3 ) & 0FFFFFFFCh
0x29c = (((8 * 3 * 0xde) + 0x1F ) >> 3) & 0x0FFFFFFFC 

Where the variable/fields are located in the file at offsets:

    0xA2 : precision : SOFx[0]->precision = 7
    0xA5 : SOFx[0]->x_image = 0xDE
    0xA7 : SOFx[0]->nr_comp = 3

Next, a fixed value of 24 is added to returned value and we end up with 0x2b4 allocated bytes.

Let use take a look now at the vulnerable function:

Line 1  signed __int16 *__cdecl sub_5B9D45C0(int a1, int loop_limit, signed __int16 *a3, signed __int16 *a4, signed __int16 *a5, int a6, struct_a7 *a7, int a8)
Line 2  {
Line 3    signed __int16 *result; // eax
Line 4    int v9; // edi
Line 5    int v10; // esi
Line 6    int v11; // kr00_4
Line 7    int v12; // ecx
Line 8    int v13; // eax
Line 9    int v14; // ebx
Line 10   bool v15; // zf
Line 11   int v16; // [esp+0h] [ebp-Ch]
Line 12   int v17; // [esp+4h] [ebp-8h]
Line 13   int v18; // [esp+8h] [ebp-4h]
Line 14   _WORD *buffer; // [esp+28h] [ebp+1Ch]
Line 15
Line 16   result = 0;
Line 17   v16 = 0;
Line 18   v17 = 0;
Line 19   v18 = 0;
Line 20   if ( loop_limit > 0 )
Line 21   {
Line 22     buffer = (_WORD *)(a6 + 4);
Line 23     result = a5;
Line 24     do
Line 25     {
Line 26       v9 = *a3 + 2048;
Line 27       v10 = *a4;
Line 28       v11 = 5742 * *result;
Line 29       v12 = v9 - (2925 * *result + 1410 * v10) / 4096;
Line 30       v13 = v9 + 7258 * v10 / 4096;
Line 31       v14 = v9 + v11 / 4096;
Line 32       if ( v9 + v11 / 4096 >= 0 )
Line 33       {
Line 34         if ( v14 > 4095 )
Line 35           LOWORD(v14) = 4095;
Line 36       }
Line 37       else
Line 38       {
Line 39         LOWORD(v14) = 0;
Line 40       }
Line 41       *(buffer - 2) = v14;
Line 42       if ( v12 >= 0 )
Line 43       {
Line 44         if ( v12 > 4095 )
Line 45           LOWORD(v12) = 4095;
Line 46       }
Line 47       else
Line 48       {
Line 49         LOWORD(v12) = 0;
Line 50       }
Line 51       *(buffer - 1) = v12;
Line 52       if ( v13 >= 0 )
Line 53       {
Line 54         if ( v13 > 4095 )
Line 55           LOWORD(v13) = 4095;
Line 56       }
Line 57       else
Line 58       {
Line 59         LOWORD(v13) = 0;
Line 60       }
Line 61       *buffer = v13;
Line 62       v18 += a7->dword34;
Line 63       if ( v18 == a8 )
Line 64       {
Line 65         ++a3;
Line 66         v18 = 0;
Line 67       }
Line 68       v17 += a7->dword84;
Line 69       if ( v17 == a8 )
Line 70       {
Line 71         ++a4;
Line 72         v17 = 0;
Line 73       }
Line 74       v16 += a7->dwordD4;
Line 75       if ( v16 == a8 )
Line 76       {
Line 77         v16 = 0;
Line 78         result = a5 + 1;
Line 79         ++a5;
Line 80       }
Line 81       else
Line 82       {
Line 83         result = a5;
Line 84       }
Line 85       v15 = loop_limit-- == 1;
Line 86       buffer += 3;
Line 87     }
Line 88     while ( !v15 );
Line 89   }
Line 90   return result;
Line 91 }

The loop_limit variable is equal to 0xA5 : SOFx[0]->x_image = 0xDE. Each loop cycle, the buffer pointer is increased by 3 * WORD = 6 at line 86.

So for buffer with capacity 0x2B4 bytes this loop is able to execute a maximum of 0x2B4 / 6 = 00000073 times but loop_limit equals 0xDE and is decrement by 1 during each cycle, which leads to an out-of-bounds write and memory corruption.

As we can see an attacker controls all presented variables just by proper file content manipulation.

Increasing the loop count via the loop_limit variable an attacker can cause an out-of-bounds write leading to memory corruption, which can result in remote code execution.

Crash Information

(5e3c.4934): 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: 17BF44:0
eax=ffff887f ebx=00000000 ecx=ffffe380 edx=198ed004 esi=00000fff edi=ffff887f
eip=5b9d4669 esp=00daf180 ebp=00daf198 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+0xc92d9:
5b9d4669 66895afc        mov     word ptr [edx-4],bx      ds:002b:198ed000=????

0:000> lmva eip
Browse full module list
start    end        module name
5b8a0000 5bbe9000   igCore19d   (export symbols)       d:\projects\ImageGear\current\Build\Bin\x86\igCore19d.dll
    Loaded symbol image file: d:\projects\ImageGear\current\Build\Bin\x86\igCore19d.dll
    Image path: d:\projects\ImageGear\current\Build\Bin\x86\igCore19d.dll
    Image name: igCore19d.dll
    Browse all global symbols  functions  data
    Timestamp:        Fri Nov 22 15:45:29 2019 (5DD7F489)
    CheckSum:         00356062
    ImageSize:        00349000
    File version:     19.5.0.0
    Product version:  19.5.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.5.0.0
        FileVersion:      19.5.0.0
        FileDescription:  Accusoft ImageGear CORE DLL 
        LegalCopyright:   Copyright 1996-2019 Accusoft Corporation. All rights reserved.
        LegalTrademarks:  ImageGearÆ and AccusoftÆ are registered trademarks of Accusoft Corporation

Timeline

2020-01-27 - Vendor Disclosure
2020-02-10 - Public Release

Credit

Discovered by Emmanuel Tacheau and a member of Cisco Talos.