Contents

Fuzzing GPicView - the default image viewer for LXDE

Write a harness

The code in exif.c is poorly written, and I suspect there are vulnerabilities in it. That’s why I’ve chosen to fuzz the ProcessExifDir function here. It is called after user rotates a jpeg file and press the “save” button.

Harness:

int harness(const char * fname, int new_angle)
{
    int fail = FALSE;
    int exif_angle = 0;
    int a;
    
    if(new_angle == 0)
      return TRUE;
    
    // use jhead functions
    ResetJpgfile();

    // Start with an empty image information structure.
    memset(&ImageInfo, 0, sizeof(ImageInfo));

    if (!ReadJpegFile( fname, READ_ALL)) return FALSE;
    
    if (NumOrientations != 0)
    {
        if(new_angle == 0) 		new_angle = 1;
        else if(new_angle == 90)	new_angle = 6;
        else if(new_angle == 180)	new_angle = 3;
        else if(new_angle == 270)	new_angle = 8;
        else if(new_angle == -45)	new_angle = 7;
        else if(new_angle == -90)	new_angle = 2;
        else if(new_angle == -135)	new_angle = 5;
        else if(new_angle == -180)	new_angle = 4;
        
        exif_angle = ExifRotateFlipMapping[ImageInfo.Orientation][new_angle];

        for (a=0;a<NumOrientations;a++){
            switch(OrientationNumFormat[a]){
                case FMT_SBYTE:
                case FMT_BYTE:      
                    *(uchar *)(OrientationPtr[a]) = (uchar) exif_angle;
                    break;

                case FMT_USHORT:    
                    Put16u(OrientationPtr[a], exif_angle);
                    break;

                case FMT_ULONG:     
                case FMT_SLONG:     
                    memset(OrientationPtr, 0, 4);
                    // Can't be bothered to write  generic Put32 if I only use it once.
                    if (MotorolaOrder){
                        ((uchar *)OrientationPtr[a])[3] = exif_angle;
                    }else{
                        ((uchar *)OrientationPtr[a])[0] = exif_angle;
                    }
                    break;

                default:
                    fail = TRUE;
                    break;
            }
        }
    }
    
    // if(fail == FALSE)
    // {
    //     WriteJpegFile(fname);
    // }
    
    // free jhead structure
    DiscardData();
    
    return (NumOrientations != 0) ? TRUE : FALSE;
}

Replace main() to this:

extern int harness(const char * fname, int new_angle);

int main(int argc, char *argv[])
{
    #ifdef __AFL_HAVE_MANUAL_CONTROL
    __AFL_INIT();
    #endif

    while (__AFL_LOOP(10000)) 
    {
        harness(argv[1], 90);
    }

    return 0;
}

Start fuzzing

I use tmux to monit AFL++ fuzzers.

import os
import time
import random

os.system('tmux kill-server')
time.sleep(.5)
os.system('mkdir -p /dev/shm/afl-current-work')

cmd = '/work/aflpp/afl-fuzz -i corpus -o /dev/shm/afl-current-work %s -- ../gpicview @@'

import libtmux
server = libtmux.Server()
server.new_session(session_name="fuzzer-master", attach=False, window_command=(cmd % '-M master'))

for x in range(40):
    opt = f'-S slave{x+1} '

    if random.randint(1, 10) <= 1:
        opt += '-Z '
    if random.randint(1, 10) <= 4:
        opt += '-p explore '
    elif random.randint(1, 10) < 4:
        opt += '-p exploit '
    
    
    server.new_session(session_name=f"fuzzer-slave-{x} " + opt, attach=False, window_command=(cmd % opt))


server.new_session(session_name=f"fuzzer-slave-asan", attach=False, window_command=(cmd % '-S slave-asan').replace('gpicview', 'asan_gpicview'))

After several minutes:

After 10h:

Reproduce

To exploit, user must open a crafted JPEG file using GPicView, and press the “rotate” button, finally press the “save” button.

Tested on Debian 12 (2023/12/09):

Bug 1 - arbitary momory read

In exif.c, (almost) all offset values are used without being checked, leading to arbitrary address read.

Download poc

blue@debian:~/Desktop/work/programs/gpicview/fuzz$ ./asan_gpicview poc1.bin 
AddressSanitizer:DEADLYSIGNAL
=================================================================
==52088==ERROR: AddressSanitizer: SEGV on unknown address 0x623fb0030408 (pc 0x5591efedbe5a bp 0x7ffddb3a5030 sp 0x7ffddb3a4de0 T0)
==52088==The signal is caused by a READ memory access.
    #0 0x5591efedbe5a in Get16u /home/blue/Desktop/work/programs/gpicview/src/exif.c
    #1 0x5591efedbe5a in ProcessExifDir /home/blue/Desktop/work/programs/gpicview/src/exif.c:455:21
    #2 0x5591efee4188 in process_EXIF /home/blue/Desktop/work/programs/gpicview/src/exif.c:969:5
    #3 0x5591efee4188 in ReadJpegSections /home/blue/Desktop/work/programs/gpicview/src/jpgfile.c:234:25
    #4 0x5591efee59ae in ReadJpegFile /home/blue/Desktop/work/programs/gpicview/src/jpgfile.c:321:11
    #5 0x5591efea6a72 in harness /home/blue/Desktop/work/programs/gpicview/src/exif.c:1669:10
    #6 0x5591efea6a72 in main /home/blue/Desktop/work/programs/gpicview/src/gpicview.c:57:9
    #7 0x7f4e756011c9  (/lib/x86_64-linux-gnu/libc.so.6+0x271c9) (BuildId: 0f2b39b572b576eb49c92fd0cc6dfa2bc9904500)
    #8 0x7f4e75601284 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x27284) (BuildId: 0f2b39b572b576eb49c92fd0cc6dfa2bc9904500)
    #9 0x5591efde50a0 in _start (/home/blue/Desktop/work/programs/gpicview/fuzz/asan_gpicview+0x6d0a0) (BuildId: e2195e8b4fadfc4d)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /home/blue/Desktop/work/programs/gpicview/src/exif.c in Get16u
==52088==ABORTING

Bug 2 - arbitary momory write

GPicView calculates the address (using base + offset) of EXIF TAG_ORIENTATION data and stores it in OrientationPtr[0]. Subsequently, it writes this value with a value from ExifRotateFlipMapping[ImageInfo.Orientation][new_angle], resulting in arbitrary memory write, although the impact is quite limited.

Download poc

blue@debian:~/Desktop/work/programs/gpicview/fuzz$ ./asan_gpicview poc2.bin 
=================================================================
==52116==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x624000000000 at pc 0x55725757f13b bp 0x7fff10f0db70 sp 0x7fff10f0db68
WRITE of size 1 at 0x624000000000 thread T0
    #0 0x55725757f13a in harness /home/blue/Desktop/work/programs/gpicview/src/exif.c:1702:57
    #1 0x55725757f13a in main /home/blue/Desktop/work/programs/gpicview/src/gpicview.c:57:9
    #2 0x7f2c1dedc1c9  (/lib/x86_64-linux-gnu/libc.so.6+0x271c9) (BuildId: 0f2b39b572b576eb49c92fd0cc6dfa2bc9904500)
    #3 0x7f2c1dedc284 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x27284) (BuildId: 0f2b39b572b576eb49c92fd0cc6dfa2bc9904500)
    #4 0x5572574bd0a0 in _start (/home/blue/Desktop/work/programs/gpicview/fuzz/asan_gpicview+0x6d0a0) (BuildId: e2195e8b4fadfc4d)

Address 0x624000000000 is a wild pointer inside of access range of size 0x000000000001.
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/blue/Desktop/work/programs/gpicview/src/exif.c:1702:57 in harness
Shadow bytes around the buggy address:
  0x0c487fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c487fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c487fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c487fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c487fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c487fff8000:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c487fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c487fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c487fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c487fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c487fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==52116==ABORTING