Pdf Fuzzing via Byte Flipping

On this post let’s study fuzzing, more specifically the technique of byte flipping (or bit flipping). Byte flipping is a method to produce malformed input files by sequentially stamping ‘\xFF’ values on a sample file. Our file here is a sample PDF document and we are going to fuzz Adobe Reader just for learning purposes.

The mutator part of the code I’m going to use is from the wonderful book of Tobias Klein: “A Bug Hunter’s Diary”. I strongly recommend studying Chapter 8: The Ringstone Massacre. Ok, let’s start:

First, prepare a sample PDF document and name it testcase.pdf. This is going to be our template sample input file. Type the following command to learn its size:

du -b testcase.pdf

File Size Info

Now, compile and run the following code fuzz.c:

gcc fuzz.c -o fuzz


 
//Reference: A Bug Hunter's Diary - Tobias Klein
//fuzz.c
 
#include <stdio.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
 
int main (int argc, char *argv[])
{
int fd = 0;
char * p = NULL;
char * name = NULL;
unsigned int file_size = 0;
unsigned int file_offset = 0;
unsigned int file_value = 0;
 
if (argc < 2) {
printf ("[-] Error: not enough arguments\n");
return (1);
} else {
file_size = atol (argv[1]);
file_offset = atol (argv[2]);
file_value = atol (argv[3]);
name = argv[4];
}
 
// open file
fd = open (name, O_RDWR);
if (fd < 0) {
perror ("open");
exit (1);
}
 
// mmap file
p = mmap (0, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if ((int) p == -1) {
perror ("mmap");
close (fd);
exit (1);
}
 
// mutate file
printf ("[+] file offset: 0x%08x (value: 0x%08x)\n", file_offset, file_value);
fflush (stdout);
p[file_offset] = file_value;
 
close (fd);
munmap (p, file_size);
 
return (0);
}


This is our mutator program. Now we can prepare a bash script fuzz.sh to make this mutator go over our testcase.pdf file:



#!/bin/bash
 
# file size
filesize=4201
 
# file offset
off=0
 
# number of files
num=5
 
# fuzz value
val=255
 
# name counter
cnt=0
 
while [ $cnt -lt $num ]
do
cp ./testcase.pdf ./file$cnt.pdf
./fuzz $filesize $off $val ./file$cnt.pdf
let "off+=1"
let "cnt+=1"
done


After running the fuzz.sh, we are going to have 5 sample pdf files produced. Run now the following command on each of the produced file to see how each of the files has been ‘\xFF’ stamped:

xxd file0.pdf | head -1

xxd file1.pdf | head -1

xxd file2.pdf | head -1

xxd file3.pdf | head -1

xxd file4.pdf | head -1

Fuzz Running

Very interesting, isn’t it? Now it is time to test these files with actual program. Unlike the sample of “A Bug Hunter’s Diary” where the author fuzzes Safari, we need a code to monitor Adobe Reader Application. Here is the solution:



#!/usr/bin/python
# Reference: https://gist.github.com/Radcliffe/7208419
from os import listdir
from os.path import isfile, join
mypath = 'C:\Python27\pdfs\\'
file_list = [ mypath+f for f in listdir(mypath) if isfile(join(mypath,f)) ]
apps = ['C:\Program Files\Adobe\Reader 11.0\Reader\AcroRd32.exe']
fuzz_output = "fuzz.pdf"
FuzzFactor = 2500
start = 0
stop = 4
import math
import random
import string
import subprocess
import time
random.seed(1337)
out = []
 
out.append("Logging Crashes Started for files:" + " " + str(start) + " " + str(stop))
 
for i in range(start, stop):
    file_choice = file_list[i]
    app = apps[0]
 
    print i
    buf = bytearray(open(file_choice, 'rb').read())
    numwrites = random.randrange(math.ceil((float(len(buf)) / FuzzFactor)))+1
    for j in range(numwrites):
        rbyte = random.randrange(256)
        rn = random.randrange(len(buf))
        buf[rn] = "%c"%(rbyte)
    open(fuzz_output, 'wb').write(buf)
    process = subprocess.Popen([app, fuzz_output])
    time.sleep(1)
    crashed = process.poll()
    if not crashed:
        process.terminate()
    else:
        out.append("Attempt #%d crashed!" % (i+1))
        out.append("Application: %s" % app)
        out.append("Return code: %s" % process.returncode)
        out.append("")
        open(fuzz_output + '_' + str(i+1), 'wb').write(buf)
    time.sleep(1)
out.append("Logging Crashes Finished")
f = open(mypath +'fuzzer'+str(start)+ '_' + str(stop) + '.log', 'w')
f.write('\n'.join(out))
f.close()


Here on this sample we make all of these 5 files be opened by Adobe Reader and monitor for a possible crash. Not surprisingly nothing happens for these small files. Of course you can increase this number to thousands and add some randomization both in files and applications being used.

Enjoy :)