CVE-2020-6064
An exploitable out-of-bounds write vulnerability exists in the uncompress_scan_line
function of the igcore19d.dll library of Accusoft ImageGear, version 19.5.0. A specially crafted PCX 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.
Accusoft ImageGear 19.5.0
https://www.accusoft.com/products/imagegear/overview/
9.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-787: Out-of-bounds Write
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 uncompress_scan_line
function, due to missing sanity checks. A specially crafted PCX file can lead to an out-of-bounds write, which can result in remote code execution.
Trying to load a malformed PCX file via IG_load_file
function, we end up in the following situation:
eax=00000004 ebx=0cd53ff8 ecx=00000005 edx=00658000 esi=0cd3a8f8 edi=00000701
eip=5690a98c esp=0093ee1c ebp=0093ee94 iopl=0 nv up ei pl nz na pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010207
igCore19d!IG_mpi_page_set+0xdf5fc:
5690a98c 880c43 mov byte ptr [ebx+eax*2],cl ds:002b:0cd54000=??
0:000> kb
# ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
00 0093ee94 5690b446 0093f414 1000001b 0093eef4 igCore19d!IG_mpi_page_set+0xdf5fc
01 0093eebc 5690a26a 0093f414 1000001b 0b6baff8 igCore19d!IG_mpi_page_set+0xe00b6
02 0093f38c 568007c9 0093f414 0b6baff8 00000001 igCore19d!IG_mpi_page_set+0xdeeda
03 0093f3c4 5683fb97 00000000 0b6baff8 0093f414 igCore19d!IG_image_savelist_get+0xb29
04 0093f640 5683f4f9 00000000 09bf5fa8 00000001 igCore19d!IG_mpi_page_set+0x14807
05 0093f660 567d6007 00000000 09bf5fa8 00000001 igCore19d!IG_mpi_page_set+0x14169
06 0093f680 00d859ac 09bf5fa8 0093f76c 0093f790 igCore19d!IG_load_file+0x47
07 0093f780 00d861a7 09bf5fa8 0093f8b4 00000021 simple_exe_141+0x159ac
08 0093f94c 00d86cbe 00000005 09ba2f50 09a85f38 simple_exe_141+0x161a7
09 0093f960 00d86b27 9c768deb 00d815e1 00d815e1 simple_exe_141+0x16cbe
0a 0093f9bc 00d869bd 0093f9cc 00d86d38 0093f9dc simple_exe_141+0x16b27
0b 0093f9c4 00d86d38 0093f9dc 76cd6359 00655000 simple_exe_141+0x169bd
0c 0093f9cc 76cd6359 00655000 76cd6340 0093fa38 simple_exe_141+0x16d38
0d 0093f9dc 77577b74 00655000 e8b707c2 00000000 KERNEL32!BaseThreadInitThunk+0x19
0e 0093fa38 77577b44 ffffffff 77598f20 00000000 ntdll!__RtlUserThreadStart+0x2f
0f 0093fa48 00000000 00d815e1 00655000 00000000 ntdll!_RtlUserThreadStart+0x1b
As we can see, an out-of-bounds operation occurred.
To reach the path to this function, some conditions are required:
bits_per_pixel
must be set to 2
nplanes
must be 2
The pseudo-code of this vulnerable function looks like this:
LINE1 int __stdcall uncompress_scan_line(table_function *ptr_function, int a1, pcx_object *p_pcx_data, int a4, IGDIBOject *IGDIBObject)
LINE2 {
LINE3 unsigned int _buffer_size_complete_scan_line; // edi
LINE4 byte *complete_scan_line_buffer; // esi
LINE5 bool v7; // zf
LINE6 byte *buff_mem_overwritten; // ebx
LINE7 byte *v9; // eax
LINE8 int v10; // eax
LINE9 int BytesPerLine; // eax
LINE10 int v12; // edi
LINE11 byte *v13; // ecx
LINE12 byte *v14; // ecx
LINE13 char v15; // bl
LINE14 byte *v16; // ecx
LINE15 byte *v17; // esi
LINE16 byte *v18; // eax
LINE17 char v19; // bl
LINE18 int v20; // esi
LINE19 unsigned int v21; // esi
LINE20 bool v22; // cf
LINE21 void *v23; // ecx
LINE22 unsigned int i; // eax
LINE23 int v26[13]; // [esp+Ch] [ebp-6Ch]
LINE24 int v27; // [esp+40h] [ebp-38h]
LINE25 byte *v28; // [esp+48h] [ebp-30h]
LINE26 int sizeX; // [esp+4Ch] [ebp-2Ch]
LINE27 int v30; // [esp+50h] [ebp-28h]
LINE28 unsigned int buffer_size_complete_scan_line; // [esp+54h] [ebp-24h]
LINE29 unsigned int v32; // [esp+58h] [ebp-20h]
LINE30 size_t invalid_size; // [esp+5Ch] [ebp-1Ch]
LINE31 byte *v34; // [esp+60h] [ebp-18h]
LINE32 byte *_complete_scan_line_buffer; // [esp+64h] [ebp-14h]
LINE33 byte *v36; // [esp+68h] [ebp-10h]
LINE34 byte *v37; // [esp+6Ch] [ebp-Ch]
LINE35 byte *v38; // [esp+70h] [ebp-8h]
LINE36 byte *v39; // [esp+74h] [ebp-4h]
LINE37
LINE38 v34 = 0;
LINE39 sizeX = getSizeX_0(IGDIBObject);
LINE40 buffer_size_complete_scan_line = (unsigned __int16)p_pcx_data->BytesPerLine [6]
LINE41 * (unsigned __int8)p_pcx_data->color_planes;
LINE42 _buffer_size_complete_scan_line = buffer_size_complete_scan_line;
LINE43 invalid_size = compute_size_based_imagewidth_bitspersample(IGDIBObject); [7]
LINE44 sub_77C4AF60((int)ptr_function, a1, (int)v26, 5 * buffer_size_complete_scan_line, 1);
LINE45 complete_scan_line_buffer = 0;
LINE46 v7 = p_pcx_data->encoding == 0;
LINE47 _complete_scan_line_buffer = 0;
LINE48 if ( !v7 )
LINE49 {
LINE50 complete_scan_line_buffer = AF_memm_alloc( [4]
LINE51 a1,
LINE52 _buffer_size_complete_scan_line,
LINE53 (int)"..\\..\\..\\..\\Common\\Formats\\pcxread.c",
LINE54 835);
LINE55 _complete_scan_line_buffer = complete_scan_line_buffer;
LINE56 if ( !complete_scan_line_buffer )
LINE57 v34 = (byte *)kind_of_print_error(
LINE58 (int)"..\\..\\..\\..\\Common\\Formats\\pcxread.c",
LINE59 837,
LINE60 -1000,
LINE61 0,
LINE62 _buffer_size_complete_scan_line,
LINE63 a1,
LINE64 0);
LINE65 }
LINE66 buff_mem_overwritten = AF_memm_alloc(a1, invalid_size, (int)"..\\..\\..\\..\\Common\\Formats\\pcxread.c", 839); [5]
LINE67 v28 = buff_mem_overwritten;
LINE68 if ( buff_mem_overwritten )
LINE69 v9 = v34;
LINE70 else
LINE71 v9 = (byte *)kind_of_print_error(
LINE72 (int)"..\\..\\..\\..\\Common\\Formats\\pcxread.c",
LINE73 842,
LINE74 -1000,
LINE75 0,
LINE76 invalid_size,
LINE77 a1,
LINE78 0);
LINE79 if ( !v9 )
LINE80 {
LINE81 v27 = 0;
LINE82 v30 = 0;
LINE83 if ( getSizeY_0(IGDIBObject) )
LINE84 {
LINE85 while ( !p_pcx_data->encoding )
LINE86 {
LINE87 complete_scan_line_buffer = (byte *)sub_77C4B280(v26, _buffer_size_complete_scan_line);
LINE88 _complete_scan_line_buffer = complete_scan_line_buffer;
LINE89 if ( !complete_scan_line_buffer )
LINE90 {
LINE91 v10 = kind_of_print_error(
LINE92 (int)"..\\..\\..\\..\\Common\\Formats\\pcxread.c",
LINE93 856,
LINE94 -2051,
LINE95 0,
LINE96 _buffer_size_complete_scan_line,
LINE97 a1,
LINE98 0);
LINE99 LABEL_13:
LINE100 if ( v10 )
LINE101 goto LABEL_25;
LINE102 }
LINE103 if ( p_pcx_data->bits_per_pixel == 1 ) [2]
LINE104 {
LINE105 BytesPerLine = (unsigned __int16)p_pcx_data->BytesPerLine;
LINE106 v12 = 0;
LINE107 v34 = &complete_scan_line_buffer[BytesPerLine];
LINE108 v13 = &complete_scan_line_buffer[BytesPerLine + BytesPerLine];
LINE109 v38 = v13;
LINE110 v14 = &v13[BytesPerLine];
LINE111 v39 = complete_scan_line_buffer;
LINE112 v37 = v14;
LINE113 v36 = buff_mem_overwritten;
LINE114 if ( sizeX )
LINE115 {
LINE116 v32 = ((unsigned int)(sizeX - 1) >> 1) + 1;
LINE117 do
LINE118 {
LINE119 v15 = 2
LINE120 * (((unsigned __int8)(*v34 & byte_77E9E184[v12]) >> (7 - v12)) | (2
LINE121 * (((unsigned __int8)(*v38 & byte_77E9E184[v12]) >> (7 - v12)) | (2 * ((unsigned __int8)(*v14 & byte_77E9E184[v12]) >> (7 - v12))))));
LINE122 v16 = v37;
LINE123 v17 = v34;
LINE124 v18 = v36 + 1;
LINE125 *v36 = ((unsigned __int8)(*v39 & byte_77E9E184[v12]) >> (7 - v12)) | v15;
LINE126 v36 = v18;
LINE127 v19 = (unsigned __int8)(*v16 & byte_77E9E185[v12]) >> (6 - v12);
LINE128 v14 = v37;
LINE129 *v18 = ((unsigned __int8)(*v39 & byte_77E9E185[v12]) >> (6 - v12)) | (2
LINE130 * (((unsigned __int8)(*v17 & byte_77E9E185[v12]) >> (6 - v12)) | (2 * (((unsigned __int8)(*v38 & byte_77E9E185[v12]) >> (6 - v12)) | (2 * v19)))));
LINE131 if ( v12 == 6 )
LINE132 {
LINE133 ++v39;
LINE134 ++v38;
LINE135 v12 = 0;
LINE136 ++v14;
LINE137 v34 = v17 + 1;
LINE138 v37 = v14;
LINE139 }
LINE140 else
LINE141 {
LINE142 v12 += 2;
LINE143 }
LINE144 v7 = v32-- == 1;
LINE145 v36 = v18 + 1;
LINE146 }
LINE147 while ( !v7 );
LINE148 buff_mem_overwritten = v28;
LINE149 }
LINE150 _buffer_size_complete_scan_line = buffer_size_complete_scan_line;
LINE151 }
LINE152 else
LINE153 {
LINE154 for ( i = 0; i < _buffer_size_complete_scan_line; ++i ) [3]
LINE155 {
LINE156 buff_mem_overwritten[2 * i] = complete_scan_line_buffer[i] >> 4; [1]
LINE157 buff_mem_overwritten[2 * i + 1] = complete_scan_line_buffer[i] & 0xF;
LINE158 }
LINE159 }
LINE160 v20 = v30;
LINE161 if ( !sub_77C494C0((int)ptr_function, (int)buff_mem_overwritten, v30, invalid_size) )
LINE162 {
LINE163 v21 = v20 + 1;
LINE164 v30 = v21;
LINE165 v22 = v21 < getSizeY_0(IGDIBObject);
LINE166 complete_scan_line_buffer = _complete_scan_line_buffer;
LINE167 if ( v22 )
LINE168 continue;
LINE169 }
LINE170 goto LABEL_25;
LINE171 }
LINE172 v10 = sub_77D3A2C0((int)v26, complete_scan_line_buffer, _buffer_size_complete_scan_line, (int)&v27);
LINE173 goto LABEL_13;
LINE174 }
LINE175 }
LINE176 LABEL_25:
LINE177 if ( _complete_scan_line_buffer && p_pcx_data->encoding )
LINE178 sub_77C55F40((void *)a1, _complete_scan_line_buffer, (int)"..\\..\\..\\..\\Common\\Formats\\pcxread.c", 910);
LINE179 if ( buff_mem_overwritten )
LINE180 sub_77C55F40((void *)a1, buff_mem_overwritten, (int)"..\\..\\..\\..\\Common\\Formats\\pcxread.c", 912);
LINE181 sub_77C4AC30(v26);
LINE182 return sub_77C2AA00(v23);
LINE183 }
In this algorithm we can observe a function uncompress_scan_line
, whose objective is to decompress the pcx data, is crashing while filling the buffer buff_mem_overwritten
in [1]. The path taken depends of the value from the pcx header
bits_per_pixel
not equal to 1
as we can see in [2]. The out-of-bounds occurs at [3], because of the lack of constraints against the two buffers complete_scan_line_buffer
and buff_mem_overwritten
. The two buffers, allocated
respectively in [4] and [5], are using a size computed in [6] and [7]. As there is no length comparison between the two buffers, the access write violation may occur.
The size for the buffer buff_mem_overwritten
is computed in the final destination function named compute_size_for_pcx
:
LINE186 unsigned int __thiscall compute_size_for_pcx(IGDIBOject *this)
LINE187 {
LINE188 return ((this->sizeX * this->size_of_table_round * this->depth + 31) >> 3) & 0xFFFFFFFC; [7]
LINE189 //
LINE190 // round_valued_bitperpixel_plane = round_max_bits_per_sample(bitspersample);
LINE191 //
LINE192 // sizeX = pcx_object->Xmax - pcx_object->Xmin + 1;
LINE193 // size_table_round = 1 is for up to 256 colors
LINE194 // return_1_if_for_colors_less_than_256
LINE195 // product_bit_per_pixel_nplanes = 1 or product_bit_per_pixel_nplanes = 4 or product_bit_per_pixel_nplanes = 8
LINE196 //
LINE197 //
LINE198 // depth is calculated from int __cdecl round_depth(int depth)
LINE199 }
In the formula in [7], what if sizeX
is set to value 1
causing some allocation size based only against the two field left. sizeX
is computed using the formula sizeX = pcx_object->Xmax - pcx_object->Xmin + 1;
as we can see in the following pseudo code
in function build_IGDIBObject_pcxrelatedobject
.
So, if Xmax
and Xmin
are equal, sizeX
will be assigned 1
at [8], which leads then to a very small buffer to be allocated, making the OOBW easier to trigger.
LINE308 int __stdcall build_IGDIBObject_pcxrelatedobject(int a1, pcx_object *pcx_object, ColorMapTable *pColorMapTable, IGDIBOject *IGDIBOject)
LINE309 {
LINE310 pcx_object *_pcx_object; // edx
LINE311 int total_bits_per_pixel; // edi
LINE312 __int16 sizeX; // bx
LINE313 __int16 v7; // ax
LINE314 int v8; // eax
LINE315 int v9; // eax
LINE316 void *v10; // ecx
LINE317 void *dest_buffer; // eax
LINE318 int _size; // [esp-4h] [ebp-10h]
LINE319 __int16 sizeY; // [esp+18h] [ebp+Ch]
LINE320
LINE321 _pcx_object = pcx_object;
LINE322 total_bits_per_pixel = (unsigned __int8)pcx_object->bits_per_pixel * (unsigned __int8)pcx_object->color_planes;
LINE323 sizeX = pcx_object->Xmax - pcx_object->Xmin + 1; [8]
LINE324 sizeY = pcx_object->Ymax - pcx_object->Ymin + 1;
LINE325 [...]
LINE326 }
0:000> !analyze -v
*******************************************************************************
* *
* Exception Analysis *
* *
*******************************************************************************
KEY_VALUES_STRING: 1
Key : AV.Fault
Value: Write
Key : Analysis.CPU.Sec
Value: 0
Key : Analysis.DebugAnalysisProvider.CPP
Value: Create: 8007007e on DESKTOP-PJK7PVH
Key : Analysis.DebugData
Value: CreateObject
Key : Analysis.DebugModel
Value: CreateObject
Key : Analysis.Elapsed.Sec
Value: 8
Key : Analysis.Memory.CommitPeak.Mb
Value: 78
Key : Analysis.System
Value: CreateObject
Key : Timeline.OS.Boot.DeltaSec
Value: 302677
Key : Timeline.Process.Start.DeltaSec
Value: 991
ADDITIONAL_XML: 1
APPLICATION_VERIFIER_LOADED: 1
EXCEPTION_RECORD: (.exr -1)
ExceptionAddress: 5690a98c (igCore19d!IG_mpi_page_set+0x000df5fc)
ExceptionCode: c0000005 (Access violation)
ExceptionFlags: 00000000
NumberParameters: 2
Parameter[0]: 00000001
Parameter[1]: 0cd54000
Attempt to write to address 0cd54000
FAULTING_THREAD: 00003b54
PROCESS_NAME: simple.exe_141.exe
WRITE_ADDRESS: 0cd54000
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: 0cd54000
STACK_TEXT:
WARNING: Stack unwind information not available. Following frames may be wrong.
0093ee94 5690b446 0093f414 1000001b 0093eef4 igCore19d!IG_mpi_page_set+0xdf5fc
0093eebc 5690a26a 0093f414 1000001b 0b6baff8 igCore19d!IG_mpi_page_set+0xe00b6
0093f38c 568007c9 0093f414 0b6baff8 00000001 igCore19d!IG_mpi_page_set+0xdeeda
0093f3c4 5683fb97 00000000 0b6baff8 0093f414 igCore19d!IG_image_savelist_get+0xb29
0093f640 5683f4f9 00000000 09bf5fa8 00000001 igCore19d!IG_mpi_page_set+0x14807
0093f660 567d6007 00000000 09bf5fa8 00000001 igCore19d!IG_mpi_page_set+0x14169
0093f680 00d859ac 09bf5fa8 0093f76c 0093f790 igCore19d!IG_load_file+0x47
0093f780 00d861a7 09bf5fa8 0093f8b4 00000021 simple_exe_141+0x159ac
0093f94c 00d86cbe 00000005 09ba2f50 09a85f38 simple_exe_141+0x161a7
0093f960 00d86b27 9c768deb 00d815e1 00d815e1 simple_exe_141+0x16cbe
0093f9bc 00d869bd 0093f9cc 00d86d38 0093f9dc simple_exe_141+0x16b27
0093f9c4 00d86d38 0093f9dc 76cd6359 00655000 simple_exe_141+0x169bd
0093f9cc 76cd6359 00655000 76cd6340 0093fa38 simple_exe_141+0x16d38
0093f9dc 77577b74 00655000 e8b707c2 00000000 KERNEL32!BaseThreadInitThunk+0x19
0093fa38 77577b44 ffffffff 77598f20 00000000 ntdll!__RtlUserThreadStart+0x2f
0093fa48 00000000 00d815e1 00655000 00000000 ntdll!_RtlUserThreadStart+0x1b
STACK_COMMAND: ~0s ; .cxr ; kb
SYMBOL_NAME: igCore19d!IG_mpi_page_set+df5fc
MODULE_NAME: igCore19d
IMAGE_NAME: igCore19d.dll
FAILURE_BUCKET_ID: INVALID_POINTER_WRITE_AVRF_c0000005_igCore19d.dll!IG_mpi_page_set
OS_VERSION: 10.0.18362.239
BUILDLAB_STR: 19h1_release_svc_prod1
OSPLATFORM_TYPE: x86
OSNAME: Windows 10
FAILURE_ID_HASH: {39ff52ad-9054-81fd-3e4d-ef5d82e4b2c1}
Followup: MachineOwner
---------
2020-01-27 - Vendor Disclosure
2020-02-10 - Public Release
Discovered by Emmanuel Tacheau of Cisco Talos.