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.
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.
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