#!/bin/sh
#
# american fuzzy lop++ - Advanced Persistent Graphing
# -------------------------------------------------
#
# Originally written by Michal Zalewski
# Based on a design & prototype by Michael Rash.
#
# Copyright 2014, 2015 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
#   https://www.apache.org/licenses/LICENSE-2.0
#

get_abs_path() {
  echo $(cd "`dirname "$1"`" && pwd)/"`basename "$1"`"
}

echo "progress plotting utility for afl-fuzz by Michal Zalewski"
echo

GRAPHICAL="0"

if [ "$1"  = "-g" ] || [ "$1" = "--graphical" ]; then
GRAPHICAL="1"
shift
fi

if [ "$#" != "2" ]; then

  cat 1>&2 <<_EOF_
$0 [ -g | --graphical ] afl_state_dir graph_output_dir

This program generates gnuplot images from afl-fuzz output data.

Usage:

    afl_state_dir       should point to an existing state directory for any
                        active or stopped instance of afl-fuzz
    graph_output_dir    should point to an empty directory where this
                        tool can write the resulting plots to
    -g, --graphical     (optional) display the plots in a graphical window
                        (you should have built afl-plot-ui to use this option)

The program will put index.html and three PNG images in the output directory;
you should be able to view it with any web browser of your choice.
_EOF_

  exit 1

fi

inputdir=`get_abs_path "$1"`
outputdir=`get_abs_path "$2"`

#if [ "$AFL_ALLOW_TMP" = "" ]; then
#
#  echo "$inputdir" | grep -qE '^(/var)?/tmp/'
#  T1="$?"
#
#  echo "$outputdir" | grep -qE '^(/var)?/tmp/'
#  T2="$?"
#
#  if [ "$T1" = "0" -o "$T2" = "0" ]; then
#
#    echo "[-] Error: this script shouldn't be used with shared /tmp directories." 1>&2
#    exit 1
#
#  fi
#
#fi

if [ ! -f "$inputdir/plot_data" ]; then

  if [ -f "$inputdir/default/plot_data" ]; then

    echo "[-] Error: input directory is not valid (missing 'plot_data'), likely you mean $inputdir/default?" 1>&2
    exit 1

  else

    echo "[-] Error: input directory is not valid (missing 'plot_data')." 1>&2
    exit 1

  fi

fi

LINES=`cat "$inputdir/plot_data" | wc -l`

if [ "$LINES" -lt 3 ]; then

  echo "[-] Error: plot_data carries too little data, let it run longer." 1>&2
  exit 1

fi

BANNER="`cat "$inputdir/fuzzer_stats" 2> /dev/null | grep '^afl_banner ' | cut -d: -f2- | cut -b2-`"

test "$BANNER" = "" && BANNER="(none)"

GNUPLOT=`command -v gnuplot 2>/dev/null`

if [ "$GNUPLOT" = "" ]; then

  echo "[-] Error: can't find 'gnuplot' in your \$PATH." 1>&2
  exit 1

fi

mkdir "$outputdir" 2>/dev/null

if [ ! -d "$outputdir" ]; then

  echo "[-] Error: unable to create the output directory - pick another location." 1>&2
  exit 1

fi

rm -f "$outputdir/high_freq.png" "$outputdir/low_freq.png" "$outputdir/exec_speed.png" "$outputdir/edges.png"
mv -f "$outputdir/index.html" "$outputdir/index.html.orig" 2>/dev/null

GNUPLOT_SETUP="
#set xdata time
#set timefmt '%s'
#set format x \"%b %d\n%H:%M\"
set tics font 'small'
unset mxtics
unset mytics

set grid xtics linetype 0 linecolor rgb '#e0e0e0'
set grid ytics linetype 0 linecolor rgb '#e0e0e0'
set border linecolor rgb '#50c0f0'
set tics textcolor rgb '#000000'
set key outside

set autoscale xfixmin
set autoscale xfixmax

set xlabel \"relative time in seconds\" font \"small\"
"

PLOT_HF="
set terminal png truecolor enhanced size 1000,300 butt
set output '$outputdir/high_freq.png'

$GNUPLOT_SETUP

plot '$inputdir/plot_data' using 1:4 with filledcurve x1 title 'corpus count' linecolor rgb '#000000' fillstyle transparent solid 0.2 noborder, \\
     '' using 1:3 with filledcurve x1 title 'current item' linecolor rgb '#f0f0f0' fillstyle transparent solid 0.5 noborder, \\
     '' using 1:5 with lines title 'pending items' linecolor rgb '#0090ff' linewidth 3, \\
     '' using 1:6 with lines title 'pending favs' linecolor rgb '#c00080' linewidth 3, \\
     '' using 1:2 with lines title 'cycles done' linecolor rgb '#c000f0' linewidth 3
"

PLOT_LF="
set terminal png truecolor enhanced size 1000,200 butt
set output '$outputdir/low_freq.png'

$GNUPLOT_SETUP

plot '$inputdir/plot_data' using 1:8 with filledcurve x1 title '' linecolor rgb '#c00080' fillstyle transparent solid 0.2 noborder, \\
     '' using 1:8 with lines title ' uniq crashes' linecolor rgb '#c00080' linewidth 3, \\
     '' using 1:9 with lines title 'uniq hangs' linecolor rgb '#c000f0' linewidth 3, \\
     '' using 1:10 with lines title 'levels' linecolor rgb '#0090ff' linewidth 3
"

PLOT_ES="
set terminal png truecolor enhanced size 1000,200 butt
set output '$outputdir/exec_speed.png'

$GNUPLOT_SETUP

plot '$inputdir/plot_data' using 1:11 with filledcurve x1 title '' linecolor rgb '#0090ff' fillstyle transparent solid 0.2 noborder, \\
     '$inputdir/plot_data' using 1:11 with lines title '    execs/sec' linecolor rgb '#0090ff' linewidth 3 smooth bezier;
"

PLOT_EG="
set terminal png truecolor enhanced size 1000,300 butt
set output '$outputdir/edges.png'

$GNUPLOT_SETUP

plot '$inputdir/plot_data' using 1:13 with lines title '        edges' linecolor rgb '#0090ff' linewidth 3
"

if [ "$#" = "2" ] && [ "$GRAPHICAL" = "1" ]; then

afl-plot-ui -h > /dev/null 2>&1

if [ "$?" != "0" ]; then

cat 1>&2 <<_EOF_
You do not seem to have the afl-plot-ui utility installed. If you have installed afl-plot-ui, make sure the afl-plot-ui executable is in your PATH.
If you are still facing any problems, please open an issue at https://github.com/AFLplusplus/AFLplusplus/issues.

No plots have been generated. Please rerun without the "-g" or "--graphical" flag to generate the plots.
_EOF_

exit 1

fi

rm -rf "$outputdir/.tmp"
mkdir -p "$outputdir/.tmp"
mkfifo "$outputdir/.tmp/win_ids" || exit 1

afl-plot-ui > "$outputdir/.tmp/win_ids" &
W_IDS=$(cat "$outputdir/.tmp/win_ids")

rm -rf "$outputdir/.tmp"

W_ID1=$(echo "$W_IDS" | head -n 1)
W_ID2=$(echo "$W_IDS" | head -n 2 | tail -n 1)
W_ID3=$(echo "$W_IDS" | head -n 3 | tail -n 1)
W_ID4=$(echo "$W_IDS" | tail -n 1)

echo "[*] Generating plots..."

(

cat << _EOF_

$PLOT_HF
set term x11 window "$W_ID3"
set output
replot
pause mouse close

_EOF_

) | gnuplot 2> /dev/null &

(

cat << _EOF_

$PLOT_LF
set term x11 window "$W_ID4"
set output
replot
pause mouse close

_EOF_

) | gnuplot 2> /dev/null &

(

cat << _EOF_

$PLOT_ES
set term x11 window "$W_ID2"
set output
replot
pause mouse close

_EOF_

) | gnuplot 2> /dev/null &

(

cat << _EOF_

$PLOT_EG
set term x11 window "$W_ID1"
set output
replot
pause mouse close

_EOF_

) | gnuplot 2> /dev/null &

sleep 1

else

echo "[*] Generating plots..."

(

cat << _EOF_

$PLOT_HF

$PLOT_LF

$PLOT_ES

$PLOT_EG

_EOF_

) | gnuplot || echo "Note: if you see errors concerning 'unknown or ambiguous terminal type' then you need to use a gnuplot that has png support compiled in."

echo "[?] You can also use -g flag to view the plots in an GUI window, and interact with the plots (if you have built afl-plot-ui). Run \"afl-plot -h\" to know more."

fi

if [ ! -s "$outputdir/exec_speed.png" ]; then

  echo "[-] Error: something went wrong! Perhaps you have an ancient version of gnuplot?" 1>&2
  exit 1

fi

echo "[*] Generating index.html..."

cat >"$outputdir/index.html" <<_EOF_
<table style="font-family: 'Trebuchet MS', 'Tahoma', 'Arial', 'Helvetica'">
<tr><td style="width: 18ex"><b>Banner:</b></td><td>$BANNER</td></tr>
<tr><td><b>Directory:</b></td><td>$inputdir</td></tr>
<tr><td><b>Generated on:</b></td><td>`date`</td></tr>
</table>
<p>
<img src="edges.png" width=1000 height=300>
<img src="high_freq.png" width=1000 height=300><p>
<img src="low_freq.png" width=1000 height=200><p>
<img src="exec_speed.png" width=1000 height=200>

_EOF_

# Make it easy to remotely view results when outputting directly to a directory
# served by Apache or other HTTP daemon. Since the plots aren't horribly
# sensitive, this seems like a reasonable trade-off.

chmod 755 "$outputdir"
chmod 644 "$outputdir/high_freq.png" "$outputdir/low_freq.png" "$outputdir/exec_speed.png" "$outputdir/edges.png" "$outputdir/index.html"

echo "[+] All done - enjoy your charts!"

exit 0
