FLIMP!

Exploit Howto

The FLIC parser is vulnerable to code execution in the "open file" dialog. Gimp creates a preview for the user when a file is selected, but before actually opening it. The worst part is that a properly crafted file won't crash the parser, making this attack hard to detect on vulnerable systems.

By this time, I am not aware of a successful attack on 64 bit systems and/or with activated ASLR. Circumventing ASLR would need a more sophisticated approach, including address leaks which seem to be quite unfeasable with the memory layout of the FLIC parser. If you are aware of such an attack vector, please contact me.

Environment

To mount a successful attack, I decided to use the latest FreeBSD release (11.1) on a 32 bit environment. As FreeBSD is one of the last systems which do not support ASLR by default, it allowed us to prepare an attack with fixed addresses. The 32 bit environment also allowed us to easily overflow the address range, overwriting arbitrary memory locations.

Code Analysis

The FLIC file format is used for animations, consisting of multiple frames per file. A frame can be compressed by using an RLE algorithm, which was a popular way of compressing files many many years ago.

The Gimp parser fails to properly check boundaries, which allows out of boundary writes. Even worse, the memory write operations do not have to be continuous. Instead, parts of the image can be skipped before writing the next chunk of bytes into RAM.

You can see the function fli_read_lc here:

        unsigned short yc, firstline, numline;
        unsigned char *pos;
        memcpy(framebuf, old_framebuf, fli_header->width * fli_header->height);
        firstline = fli_read_short(f);
        numline = fli_read_short(f);
        for (yc=0; yc < numline; yc++) {
                unsigned short xc, pc, pcnt;
                pc=fli_read_char(f);
                xc=0;
                pos=framebuf+(fli_header->width * (firstline+yc));
                for (pcnt=pc; pcnt>0; pcnt--) {
                        unsigned short ps,skip;
                        skip=fli_read_char(f);
                        ps=fli_read_char(f);
                        xc+=skip;
                        if (ps & 0x80) {
                                unsigned char val;
                                ps=-(signed char)ps;
                                val=fli_read_char(f);
                                memset(&(pos[xc]), val, ps);
                                xc+=ps;
                        } else {
                                fread(&(pos[xc]), ps, 1, f);
                                xc+=ps;
                        }
                }
        }

The width of an image in Gimp cannot be larger than 2^18. But as you can see, firstline and numline, which are values between 0 and 2^16, are not validated to be within the height limit. For each line in question, there can be 2^8 instructions, each instruction skipping up to 2^8 bytes in the target memory and writing not more than 2^8 bytes.

Which means that our attack can cover around 2^50 bytes, which is a lot, but not enough to overflow the address range of 64 bit sytems. Therefore I chose to attack 32 bit systems, having the whole address range under control.

Proof of Concept

I decided to go for a PLT/GOT attack, i.e. overwriting the actual location of a chosen library function to trigger a system(3) call with a string contained within the image, leading to full flexibility of programs to execute.

The function fli_read_lc can be triggered multiple times in a row during the preview generation. Our idea was to modify the GOT entry of memset, overwriting it with system. Fortunately the rest of the code does not access memset directly during iterations of fli_read_lc, giving us a good foundation for our attack. Also, memset uses attacker-controlled memory for the first argument, which therefore could hold our string of a program to execute, e.g. "/usr/local/bin/xcalc".

The attack is performed in three steps:

  1. Overflow framebuf and write "/usr/local/bin/xcalc" at the start of old_framebuf. This way, the next steps will always have that string at the start of framebuf (see memcpy call).
  2. Overwrite GOT entry of memset with system. To actually perform this step, I need a fixed address of framebuf. This is not always the same value among FreeBSD systems and definitely not when ASLR is enabled.
  3. Trigger memset right away by having a byte sequence with "ps" being 0x80. It will directly call memset with &(pos[xc]) being framebuf, which in turn contains the string of our program to execute.

To cover our traces, step 4 could actually overwrite the framebuf content with an actually usable picture.

Tobias Stöckmann