CVE-2017-2813
IrfanView JPEG 2000 Reference Tile Width Arbitrary Code Execution Vulnerability
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.
IrfanView 4.44
8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CWE-190: Integer Overflow or Wraparound
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.
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>
2017-04-18 - Vendor Disclosure
2017-04-26 - Public Release
Discovered by Aleksandar Nikolic of Cisco Talos.