Talos Vulnerability Report

TALOS-2017-0310

IrfanView JPEG 2000 Reference Tile Width Arbitrary Code Execution Vulnerability

April 26, 2017
CVE Number

CVE-2017-2813

IrfanView JPEG 2000 Reference Tile Width Arbitrary Code Execution Vulnerability

Summary

An exploitable integer overflow vulnerability exists in the JPEG 2000 parser functionality of IrfanView 4.44. A specially crafted jpeg2000 image can cause an integer overflow leading to wrong memory allocation resulting in arbitrary code execution. Vulnerability can be triggered by viewing the image in via the application or by using thumbnailing feature of IrfanView.

Tested Versions

IrfanView 4.44

Product URLs

http://www.irfanview.com/

CVSSv3 Score

8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

CWE

CWE-190: Integer Overflow or Wraparound

Details

IrfanView is a popular image viewer application for Windows due to its support for large number of file formats through its plugins and numerous image manipulation options.

While parsing a JPEG 2000 file, IrfanView will use the reference tile width value in a buffer size calculation. Due to insufficient checks for integer wraparound, these calculations can result in a small buffer being allocated for a seemingly large tile which later results in a controlled out-of-bounds write vulnerability which can be further abused to achieve arbitrary code execution in the context of the application.

Erroneous allocation happens in function sub_10027E40 (image base of unpacked JPEG2000.dll is at 0x10001000) and the condition for the integer overflow can be observed at the following breakpoint:

eax=00000000 ebx=000000c8 ecx=0019ed74 edx=0ccccccf esi=0a521da0 edi=00000001
eip=516f7e67 esp=0019ed80 ebp=0019ed8c iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
JPEG2000!ShowPlugInSaveOptions+0x23937:
516f7e67 e8d413ffff      call    JPEG2000!ShowPlugInSaveOptions+0x14d10 (516e9240)
0:000> dd esp
0019ed80  0a513fe8 00000050 0a521da0 0019eda4
0019ed90  516e84d3 00000050 3da65ff8 00000000
0019eda0  00000008 0019edd0 516e8366 0a521da0
0019edb0  00000000 00000001 00000000 00000001
0019edc0  00000000 00000000 00000000 00000000
0019edd0  0019eec0 516d35a9 0a521da0 0056602c
0019ede0  0019f06c 00000000 00000001 00000009
0019edf0  516d0000 3da65ff8 00000000 0054fdb0
0:000>

Second argument to the above call is 0x50 which ends up being a size argument to malloc call, and is actually derived from xTsiz value in the siz marker of the sample file which in this case is 0x0CCCCCCF, the smallest value that will trigger the integer overflow. Notice that the edx register holds the same value, before buffer size calculations are done. Buffer size calculations basically boil down to multiplying the value by 20 plus a small value. Therefore, instead of allocating a large enough buffer, a small memory region will be reserved after the call:

0:000> !heap -p -a eax
    address 0a6e6fb0 found in
    _DPH_HEAP_ROOT @ 3f01000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                3da70a90:          a6e6fa8               58 -          a6e6000             2000
    56a89abc verifier!AVrfDebugPageHeapAllocate+0x0000023c
    779ed816 ntdll!RtlDebugAllocateHeap+0x0000003c
    7794fb40 ntdll!RtlpAllocateHeap+0x000000f0
    7794decb ntdll!RtlpAllocateHeapInternal+0x0000027b
    7794dc2e ntdll!RtlAllocateHeap+0x0000002e
    516d45e6 JPEG2000!ShowPlugInSaveOptions+0x000000b6
    516e921b JPEG2000!ShowPlugInSaveOptions+0x00014ceb
    516e9269 JPEG2000!ShowPlugInSaveOptions+0x00014d39
    516f7e6c JPEG2000!ShowPlugInSaveOptions+0x0002393c
    516e84d3 JPEG2000!ShowPlugInSaveOptions+0x00013fa3
    516e8366 JPEG2000!ShowPlugInSaveOptions+0x00013e36
    516d35a9 JPEG2000!ReadJPG2000+0x00000c09
*** ERROR: Module load completed but symbols could not be loaded for image00400000
    0043c3bc image00400000+0x0003c3bc

Later, when actual values are to be written in this buffer, a controlled out-of-bounds write will happen potentially overwriting sensitive memory. Note that due to different memory layouts, an out-of-bounds write can happen on actually accessible memory and in general won’t result in a crash.

By modifying the xTsiz value slightly (to 0x0dcccccf), to force a crash, we can observe the following:

Breakpoint 0 hit
eax=00000000 ebx=000000c8 ecx=0019ed74 edx=0dcccccf esi=0a427da0 edi=00000001
eip=516f7e67 esp=0019ed80 ebp=0019ed8c iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
JPEG2000!ShowPlugInSaveOptions+0x23937:
516f7e67 e8d413ffff      call    JPEG2000!ShowPlugInSaveOptions+0x14d10 (516e9240)
0:000> dd esp
0019ed80  0a419fe8 14000050 0a427da0 0019eda4
0019ed90  516e84d3 14000050 41965ff8 00000000
0019eda0  00000008 0019edd0 516e8366 0a427da0
0019edb0  00000000 00000001 00000000 00000001
0019edc0  00000000 00000000 00000000 00000000
0019edd0  0019eec0 516d35a9 0a427da0 0056602c
0019ede0  0019f06c 00000000 00000001 00000009
0019edf0  516d0000 41965ff8 00000000 0054fdb0
0:000>

In the above debugging output, we can again see edx having the original xTsiz value from the file, and an overflowed value pushed as a second argument on the stack (0x14000050, overflown by multiplying edx by 20). After this call we can see our buffer being allocated:

Breakpoint 1 hit
eax=7fff0fb0 ebx=000000c8 ecx=7fff0fa8 edx=01000002 esi=0a487da0 edi=00000001
eip=516f7e6c esp=0019ed80 ebp=0019ed8c iopl=0         nv up ei pl nz ac po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000212
JPEG2000!ShowPlugInSaveOptions+0x2393c:
516f7e6c 83c408          add     esp,8
0:000> !heap -p -a eax
    address 7fff0fb0 found in
    _DPH_HEAP_ROOT @ 4fd1000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                419d098c:         7fff0fa8         14000058 -         7fff0000         14002000
    56a89abc verifier!AVrfDebugPageHeapAllocate+0x0000023c
    779ed816 ntdll!RtlDebugAllocateHeap+0x0000003c
    7794fb40 ntdll!RtlpAllocateHeap+0x000000f0
    7794decb ntdll!RtlpAllocateHeapInternal+0x0000027b
    7794dc2e ntdll!RtlAllocateHeap+0x0000002e
    516d45e6 JPEG2000!ShowPlugInSaveOptions+0x000000b6
    516e921b JPEG2000!ShowPlugInSaveOptions+0x00014ceb
    516e9269 JPEG2000!ShowPlugInSaveOptions+0x00014d39
    516f7e6c JPEG2000!ShowPlugInSaveOptions+0x0002393c
    516e84d3 JPEG2000!ShowPlugInSaveOptions+0x00013fa3
    516e8366 JPEG2000!ShowPlugInSaveOptions+0x00013e36
    516d35a9 JPEG2000!ReadJPG2000+0x00000c09
*** ERROR: Module load completed but symbols could not be loaded for image00400000
    0043c3bc image00400000+0x0003c3bc

So, a buffer of size 0x14000058 has been successfully allocated. Continuing the execution results in the following crash:

(2778.3b14): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=b7324300 ebx=00000000 ecx=b73242f0 edx=00000000 esi=0a487da0 edi=00000000
eip=516f82ea esp=0019ed64 ebp=0019ed78 iopl=0         nv up ei ng nz ac po cy
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010293
JPEG2000!ShowPlugInSaveOptions+0x23dba:
516f82ea 89040b          mov     dword ptr [ebx+ecx],eax ds:002b:b73242f0=????????
0:000> dd ecx
b73242f0  ???????? ???????? ???????? ????????
b7324300  ???????? ???????? ???????? ????????
b7324310  ???????? ???????? ???????? ????????
b7324320  ???????? ???????? ???????? ????????
b7324330  ???????? ???????? ???????? ????????
b7324340  ???????? ???????? ???????? ????????
b7324350  ???????? ???????? ???????? ????????
b7324360  ???????? ???????? ???????? ????????
0:000>

The crash occurs in a function at offset 0x10028200, specifically while writing values in a loop controlled based on number of components. Upper bound for ebx in the above out-of-bounds write loop is the numberOfComponents value specified in the file which can be easily controlled to achieve multiple out of bounds writes. Also, backing up a bit in the crashing function reveals that ecx pointer is derived from the xTsiz value which causes the initial integer overflow:

.text:10028253 mov     eax, [esi+8]                [1]
.text:10028256 mov     ecx, [esi+2Ch]
.text:10028259 mov     edx, [eax+14h]                [2]
.text:1002825C imul    edx, [ecx+8]                [3]
.text:10028260 mov     eax, [ebp+arg_0]
.text:10028263 lea     eax, [eax+edx*4]                [4]
.text:10028266 mov     [ebp+arg_0], eax ;         [5]
.text:10028269 jmp     short loc_100

An original xTsiz value twice dereferenced at [1] and [2] ends up in edx, multiplied by 1 at [3] and used as offset in pointer calculation at [4], with eax as a base being a start of our previously allocated buffer. This pointer is stored as arg_0 at [5] and because the offset is multiplied by 4, and the original buffer is smaller than that, this ends up causing the out-of-bounds write. This can be observed in the following debugging session:

516f8253 8b4608          mov     eax,dword ptr [esi+8] ds:002b:0a477da8=0a479430
0:000> dd poi(esi+8)+0x14
0a479444  0dcccccf 00000001 00000000 00000000

Above, we see that the value at (esi+8)+0x14 is equal to original xTsiz value.

0:000> t
eax=0a479430 ebx=00000003 ecx=7fff0fb0 edx=00000000 esi=0a477da0 edi=0000001f
eip=516f8256 esp=0019ed64 ebp=0019ed78 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
JPEG2000!ShowPlugInSaveOptions+0x23d26:
516f8256 8b4e2c          mov     ecx,dword ptr [esi+2Ch] ds:002b:0a477dcc=0a477fd8
0:000>
eax=0a479430 ebx=00000003 ecx=0a477fd8 edx=00000000 esi=0a477da0 edi=0000001f
eip=516f8259 esp=0019ed64 ebp=0019ed78 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
JPEG2000!ShowPlugInSaveOptions+0x23d29:
516f8259 8b5014          mov     edx,dword ptr [eax+14h] ds:002b:0a479444=0dcccccf
0:000>
eax=0a479430 ebx=00000003 ecx=0a477fd8 edx=0dcccccf esi=0a477da0 edi=0000001f
eip=516f825c esp=0019ed64 ebp=0019ed78 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
JPEG2000!ShowPlugInSaveOptions+0x23d2c:
516f825c 0faf5108        imul    edx,dword ptr [ecx+8] ds:002b:0a477fe0=00000001
0:000> dd ecx+8
0a477fe0  00000001 00000001 00000001 00000000
0:000> t
eax=0a479430 ebx=00000003 ecx=0a477fd8 edx=0dcccccf esi=0a477da0 edi=0000001f
eip=516f8260 esp=0019ed64 ebp=0019ed78 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
JPEG2000!ShowPlugInSaveOptions+0x23d30:
516f8260 8b4508          mov     eax,dword ptr [ebp+8] ss:002b:0019ed80=7fff0fb0
0:000> t
eax=7fff0fb0 ebx=00000003 ecx=0a477fd8 edx=0dcccccf esi=0a477da0 edi=0000001f
eip=516f8263 esp=0019ed64 ebp=0019ed78 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
JPEG2000!ShowPlugInSaveOptions+0x23d33:
516f8263 8d0490          lea     eax,[eax+edx*4]

Stepping through this block shows how the value in edx is calculated as well as value in eax being retrieved. Last instruction above uses eax as base pointer and edx*4 as an offset into it.

0:000> dd eax
7fff0fb0  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
7fff0fc0  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
7fff0fd0  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
7fff0fe0  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
7fff0ff0  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
7fff1000  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
7fff1010  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
7fff1020  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
0:000> !heap -p -a eax
    address 7fff0fb0 found in
    _DPH_HEAP_ROOT @ 2e31000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                419c098c:         7fff0fa8         14000058 -         7fff0000         14002000
    56a89abc verifier!AVrfDebugPageHeapAllocate+0x0000023c
    779ed816 ntdll!RtlDebugAllocateHeap+0x0000003c
    7794fb40 ntdll!RtlpAllocateHeap+0x000000f0
    7794decb ntdll!RtlpAllocateHeapInternal+0x0000027b
    7794dc2e ntdll!RtlAllocateHeap+0x0000002e
    516d45e6 JPEG2000!ShowPlugInSaveOptions+0x000000b6
    516e921b JPEG2000!ShowPlugInSaveOptions+0x00014ceb
    516e9269 JPEG2000!ShowPlugInSaveOptions+0x00014d39
    516f7e6c JPEG2000!ShowPlugInSaveOptions+0x0002393c
    516e84d3 JPEG2000!ShowPlugInSaveOptions+0x00013fa3
    516e8366 JPEG2000!ShowPlugInSaveOptions+0x00013e36
    516d35a9 JPEG2000!ReadJPG2000+0x00000c09
*** ERROR: Module load completed but symbols could not be loaded for image00400000
    0043c3bc image00400000+0x0003c3bc

Before the lea instruction, we can see that eax points to our allocated buffer which is smaller than edx*4. This causes eax to point out-of-bounds:

0:000> u
JPEG2000!ShowPlugInSaveOptions+0x23d33:
516f8263 8d0490          lea     eax,[eax+edx*4]
516f8266 894508          mov     dword ptr [ebp+8],eax
516f8269 eb1d            jmp     JPEG2000!ShowPlugInSaveOptions+0x23d58 (516f8288)
516f826b 8b4e08          mov     ecx,dword ptr [esi+8]
516f826e 8d4707          lea     eax,[edi+7]
516f8271 99              cdq
516f8272 83e207          and     edx,7
516f8275 03c2            add     eax,edx
0:000> t
eax=b73242ec ebx=00000003 ecx=0a477fd8 edx=0dcccccf esi=0a477da0 edi=0000001f
eip=516f8266 esp=0019ed64 ebp=0019ed78 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
JPEG2000!ShowPlugInSaveOptions+0x23d36:
516f8266 894508          mov     dword ptr [ebp+8],eax ss:002b:0019ed80=7fff0fb0
0:000> dd eax
b73242ec  ???????? ???????? ???????? ????????
0:000>

Continuing the process again leads to the crash when a value is written into memory pointed to by eax plus an offset based on other SIZ fields. The value that is being written out-of-bounds is based on eax.

By carefully controlling the xTsiz value, and manipulating final arithmetics via other values in the SIZ marker, an attacker can overwrite a chosen memory location with its controlled value which can lead to arbitrary code execution.

Crash Information

0:000> r
eax=b7324300 ebx=00000000 ecx=b73242f0 edx=00000000 esi=0a477da0 edi=00000000
eip=516f82ea esp=0019ed64 ebp=0019ed78 iopl=0         nv up ei ng nz ac po cy
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010293
JPEG2000!ShowPlugInSaveOptions+0x23dba:
516f82ea 89040b          mov     dword ptr [ebx+ecx],eax ds:002b:b73242f0=????????
0:000> u
JPEG2000!ShowPlugInSaveOptions+0x23dba:
516f82ea 89040b          mov     dword ptr [ebx+ecx],eax
516f82ed 8b4608          mov     eax,dword ptr [esi+8]
516f82f0 8b4814          mov     ecx,dword ptr [eax+14h]
516f82f3 8b4508          mov     eax,dword ptr [ebp+8]
516f82f6 8d0488          lea     eax,[eax+ecx*4]
516f82f9 894508          mov     dword ptr [ebp+8],eax
516f82fc 8d4508          lea     eax,[ebp+8]
516f82ff 50              push    eax
0:000> dd ecx
b73242f0  ???????? ???????? ???????? ????????
b7324300  ???????? ???????? ???????? ????????
b7324310  ???????? ???????? ???????? ????????
b7324320  ???????? ???????? ???????? ????????
b7324330  ???????? ???????? ???????? ????????
b7324340  ???????? ???????? ???????? ????????
b7324350  ???????? ???????? ???????? ????????
b7324360  ???????? ???????? ???????? ????????
0:000> k
ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
0019ed78 516f7e80 JPEG2000!ShowPlugInSaveOptions+0x23dba
0019ed8c 516e84d3 JPEG2000!ShowPlugInSaveOptions+0x23950
0019eda4 516e8366 JPEG2000!ShowPlugInSaveOptions+0x13fa3
0019edd0 516d35a9 JPEG2000!ShowPlugInSaveOptions+0x13e36
0019eec0 0043c3bc JPEG2000!ReadJPG2000+0xc09
00000000 00000000 image00400000+0x3c3bc
0:000>

Timeline

2017-04-18 - Vendor Disclosure
2017-04-26 - Public Release

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.