#! /bin/bash

#   Primitive fuzzer for john hashes
#   Copyright (C) 2013  Alexander Cherepanov
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License along
#   with this program; if not, write to the Free Software Foundation, Inc.,
#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

# root dir of john where run/ and src/ are located -- please configure
dir="../unstable-jumbo"

# You can also provide a list of initial samples (tests.pw) and a list of formats to test (formats.l)


# If there is no tests.pw file rip all tests from john sources
if [ ! -s tests.pw ]; then
    echo 'Ripping tests from john sources...'
    (
        (
            cd "$dir/src"
            for file in ./*.c; do
                cpp -DHAVE_NSS -DHAVE_KRB5 "$file" 2> /dev/null
            done
        ) | perl -ln0777e '
                while (/struct \s+ fmt_tests \s+\w[^{}]+ \{ ( ( [^{}] |  \{ ( [^{}] | \{ [^{}]* \} )* \} )* ) \}/gx) {
                    my $s = $&;
                    while ($s =~ /\{ (( \s* "[^"]*" )+) ([^{}] | \{ [^{}]* \})* \}/gx) {
                        my $hash = $1;
                        
                        $hash =~ s/^\s*"//;
                        $hash =~ s/"\s*"//g;
                        $hash =~ s/"\s*$//;
                        
                        $hash =~ s/\\x([0-9A-Fa-f]{2})/chr hex $1/ge;
                        
                        print "$hash\n";
                    }
                }
                '
        perl -lne 'print $& if /\$dynamic_[^":]+/' "$dir/src/dynamic_preloads.c" "$dir/run/dynamic.conf"
    ) | sort | uniq > tests.pw
fi

# fuzz hashes
echo 'Fuzzing hashes...'
perl -ln fuzz.pl tests.pw > fuzzed.pw

# If there is no formats.l list all formats
if [ ! -s formats.l ]; then
    echo 'Listing formats...'
    "$dir/run/john" --list=format-details | cut -f 1 | sort > formats.l
fi

# run fuzzed hashes through john
echo 'Running fuzzed hashes through john...'
rm -f fails.l
while read format; do
    echo ">> john: $format"
    (ulimit -t 10; "$dir/run/john" --max-run-time=1 --format="$format" fuzzed.pw)
    s=$?
    if [ $s -ge 128 ]; then
        echo "$(( $s - 128 )) $format" >> fails.l
    fi
done < formats.l |& tee out

# minimize samples for failed formats
echo 'Minimizing samples for failed formats..'
grep -vFx '9 crypt' fails.l | \
while read sig format; do
    status=$(( 128 + sig ))
    echo ">> Minimizing: $format for $(kill -l $sig) ($sig - $status)"
    if [ ! -s "fail_$format.pw" ]; then
        john_cmd="$dir/run/john --max-run-time=1 --format=$format tmp2.pw"

        cp fuzzed.pw tmp2.pw
        (ulimit -t 10; $john_cmd > /dev/null 2>&1)
        s=$?
        if [ $s -ne $status ]; then
            echo "Failed to minimize (got $s instead of $status)."
        else
            wc=$(cat tmp2.pw | wc -l)

            k=1
            while [ $wc -gt 1 -a $k -lt 100 ]; do
                wc=$(( (wc + 1) / 2 ))
                echo -n "$wc "
                mv tmp2.pw tmp1.pw
                head -n $wc tmp1.pw > tmp2.pw
                (ulimit -t 10; $john_cmd > /dev/null 2>&1)
                s=$?
                k=0
                while [ $s -ne $status -a $k -lt 100 ]; do
                    echo -n ".$s"
                    shuf -n $wc tmp1.pw > tmp2.pw
                    (ulimit -t 10; $john_cmd > /dev/null 2>&1)
                    s=$?
                    : $(( k++ ))
                done
                echo
            done

            echo Ok
            if [ $k -eq 100 ]; then
                mv tmp1.pw "fail_$format.pw"
            else
                mv tmp2.pw "fail_$format.pw"
            fi
        fi
    fi
done 2> /dev/null
