3 # Adjusted to PRADS by Edward Fjellskaal
4 # Author:William Metcalf <william.metcalf@gmail.com>
5 # Copyright (C) 2010 Open Information Security Foundation
6 # http://wiki.wireshark.org/FuzzTesting
8 # Options for getting thre required perl modules:
10 # sudo apt-get install libdevel-gdb-perl libcapture-tiny-perl
13 # yum -y install cpanspec perl-Module-Build
14 # cpanspec --packager OISF -v -s --follow Capture::Tiny
15 # cpanspec --packager OISF -v -s --follow Devel::GDB
16 # rpmbuild --rebuild *.src.rpm
17 # rpm -ivh /usr/src/redhat/RPMS/noarch/perl-Devel-GDB*.rpm
18 # rpm -ivh /usr/src/redhat/RPMS/noarch/perl-Capture-Tiny*.rpm
21 # yum -y install perl-Capture-Tiny perl-Devel-GDB
23 # Other debain based versions, try the Ubunutu instructions if this doesn't work try the following.
24 # sudo apt-get install dh-make-perl
25 # mkdir fuzzmodules && cd fuzzmodules
26 # dh-make-perl --cpan Devel-GDB --build
27 # dh-make-perl --cpan Capture-Tiny --build
30 #TODO: Figure out a better way to deal with signal handling.
31 #TODO: Try to determine flow/stream that caused segv by extracting from the bt and extract it from the pcap.
32 #TODO: E-mail notification on segv?
33 #TODO: Parse Valgrind output and alert on errors
37 use Capture
::Tiny
'capture';
38 use List
::Util
'shuffle';
60 Getopt
::Long
::Configure
("prefix_pattern=(-|--)");
61 GetOptions
( \
%config, qw(n=s r=s c=s e=s v=s p=s l=s s=s y z h help) );
68 #display help if asked
69 if ( $config{h
} || $config{help
} ) {
73 #filemask of pcaps to read?
75 @tmpfiles = <$config{r
}>;
77 print "parseopts: Pcap filemask was invalid we couldn't find any matching files\n";
82 print "parseopts: Pcap filemask not specified or doesn't exist\n";
86 #filemask do we have a path to PRADS bin?
87 if ( $config{p
} && -e
$config{p
} ) {
88 $PRADSbin = $config{p
};
90 #do wrapper script detection lt-PRADS won't be created until first run but .libs/PRADS should exist.
92 open my $in, '<', $PRADSbin or die "Can't read old file: $!";
95 m/PRADS \- temporary wrapper script for \.libs\/PRADS
/
98 print "parseopts: PRADS bin file appears to be a wrapper script going to try to find the real bin for gdb.\n";
99 my $tmpdirname = dirname
$PRADSbin;
100 my $tmpltsuriname = $tmpdirname . "/.libs/PRADS";
101 if ( -e
$tmpltsuriname && -B
$tmpltsuriname ) {
102 $ltsuribin = $tmpltsuriname;
103 print "parseopts: telling gdb to use " . $ltsuribin . "\n";
111 elsif ( -B
$PRADSbin ) {
112 print "parseopts: PRADS bin file checks out\n";
115 print "parseopts: PRADS bin file is not a text or a bin exiting.\n";
120 print "parseopts: Path to PRADS bin not provided or doesn't exist\n";
124 #number of times to loop
126 $loopnum = $config{n
};
127 print "parseopts: looping through the pcaps " . $loopnum . " times or until we have an error\n";
130 print "parseopts: looping through the pcaps forever or until we have an error\n";
131 $loopnum = "infinity";
134 #rules file do we have a path and does it exist
135 if ( $config{s
} && -e
$config{s
} ) {
137 print "parseopts: telling PRADS to use rules file " . $rules . "\n";
140 print("parseopts: rules file not specified or doesn't exist\n");
143 #log dir does it exist
144 if ( $config{l
} && -e
$config{l
} ) {
145 $logdir = $config{l
};
146 print "parseopts: using log dir " . $logdir . "\n";
152 #config file do we have a path and does it exist
153 if ( $config{c
} && -e
$config{c
} ) {
154 $configfile = $config{c
};
155 print "parseopts: telling PRADS to use the config file " . $configfile . "\n";
158 print "parseopts: config file not specified or doesn't exist\n";
162 #% chance that a byte will be modified.
166 my $tmperatio = $config{e
} * 100;
167 if ( $tmperatio <= 100 && $tmperatio >= 0 ) {
168 $editeratio = $config{e
};
169 print "parseopts: using error ratio " . $editeratio . "\n";
172 print "parseopts: error ratio specified but outside of range. Valid range is 0.00-1.0\n";
177 print("parseopts: not going to fuzz pcap(s)\n");
180 #parse the valgrind opts
182 if ( $config{v
} =~ /^(memcheck|drd|helgrind|callgrind)$/ ) {
183 $valgrindopt = $config{v
};
184 print "parseopts: using valgrind opt " . $valgrindopt . "\n";
187 print "invalid valgrind opt " . $valgrindopt . "\n";
191 #shuffle the array if we are starting multiple fuzzers at once. GO-GO gadget shuffle
193 print "parseopts: going to shuffle the array\n";
196 print "******************Initialization Complete**********************\n";
203 -h or help <this output>
204 -r=<filemask for pcaps to read>
205 -n=<(optional) number of iterations or if not specified will run until error>
206 -c=<(optional) path to PRADS config file will be passed as -c to PRADS>
207 -e=<(optional) editcap error ratio to introduce if not specified will not fuzz. Valid range for this is 0.00 - 1.0>
208 -p=<path to the PRADS bin>
209 -l=<(optional) log dir for output if not specified will use current directory.>
210 -v=<(optional) (memcheck|drd|helgrind|callgrind) will run the command through one of the specified valgrind tools.>
211 -y <shuffle the array, this is useful if running multiple instances of this script.>
214 First thing to do is download and build PRADS from git with -O0 so vars don't get optimized out. See the example below:
215 git clone git://phalanx.openinfosecfoundation.org/oisf.git PRADSfuzz1 && cd PRADSfuzz1 && make profile
217 Second thing to do is to edit prads.conf to fit your environment.
219 Third go ahead and run the script.
221 In the example below the script will loop forever until an error is encountered will behave in the following way.
222 1.-r Process all pcaps in subdirectories of /home/somepath/pcaps/
223 2.-c Tell PRADS to use the config file /home/somepath/prads.conf
224 3.-y Shuffle the array of pcaps this is useful if running multiple instances of this script.
225 5.-e Tell editcap to introduce a 2% error ratio, i.e. there is a 2% chance that a byte will be fuzzed see http://wiki.wireshark.org/FuzzTesting for more info.
226 7.-p Use src/PRADS as our PRADS bin file. The script will determin if the argument passed is a bin file or a txt wrapper and will adjust accordingly.
228 prads-wirefuzz.pl -r=/home/somepath/pcaps/*/* -c=/home/somepath/prads.conf -y -e=0.02 -p src/PRADS
230 If an error is encountered a file named <fuzzedfile>ERR.txt will be created in the log dir (current dir in this example) that will contain output from stderr,stdout, and gdb.
232 Take a look at the opts make it work for you environtment and from the PRADS QA team thanks for helping us make our meerkat fuzzier! ;-)\n";
236 #escapes for filenames
237 foreach my $file (@tmpfiles) {
243 my $logfile = $logdir . "prads-wirefuzzlog.txt";
244 open( LOGFILE
, ">>$logfile" )
245 || die( print "error: Could not open logfile! $logfile\n" );
248 while ( $successcnt < $loopnum ) {
249 if ( defined $shuffle ) {
250 @files = shuffle
(@tmpfiles);
256 foreach my $file (@files) {
258 #split out the path from the filename
259 my $filedir = dirname
$file;
260 my $filename = basename
$file;
261 my ( $fuzzedfile, $editcapcmd, $editcapout, $editcaperr, $editcapexit,
262 $editcap_sys_signal, $editcap_sys_coredump );
263 my ( $fuzzedfiledir, $fuzzedfilename, $fullcmd, $out, $err, $exit,
264 $PRADS_sys_signal, $PRADS_sys_coredump );
265 print "Going to work with file: $file\n";
266 my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
268 my $timestamp = sprintf "%4d-%02d-%02d-%02d-%02d-%02d", $year + 1900,
269 $mon + 1, $mday, $hour, $min, $sec;
271 if ( defined $editeratio ) {
272 $fuzzedfile = $logdir . $filename . "-fuzz-" . $timestamp;
274 "editcap -E " . $editeratio . " " . $file . " " . $fuzzedfile;
275 print( "editcap: " . $editcapcmd . "\n" );
276 ( $editcapout, $editcaperr ) = capture
{
278 $editcapexit = $?
>> 8;
279 $editcap_sys_signal = $?
& 127;
280 $editcap_sys_coredump = $?
& 128;
282 if ( $editcapexit ne 0 ) {
284 #this could still cause us to loop forever if all pcaps are bad but it's better than nothing.
286 print "editcap: had an error and this was our only pcap:" . $editcaperr . "\n";
290 print "editcap: had an error going to the next pcap:" . $editcaperr . "\n";
294 elsif ( $editcap_sys_signal eq 2 ) {
295 print "editcap: system() got a ctl+c we are bailing as well\n";
300 print("editcap: ran successfully\n");
302 "******************Editcap Complete**********************\n";
309 #split out the path from the filename
310 $fuzzedfiledir = dirname
$fuzzedfile;
311 $fuzzedfilename = basename
$fuzzedfile;
313 $fullcmd = "ulimit -c unlimited; ";
315 if ( defined $valgrindopt ) {
316 if ( $valgrindopt eq "memcheck" ) {
319 . "valgrind -v --log-file="
323 . "-memcheck-vg.log ";
325 elsif ( $valgrindopt eq "drd" ) {
328 . "valgrind --tool=drd --var-info=yes -v --log-file="
334 elsif ( $valgrindopt eq "helgrind" ) {
337 . "valgrind --tool=helgrind -v --log-file="
341 . "-helgrind-vg.log ";
343 elsif ( $valgrindopt eq "callgrind" ) {
346 . "valgrind --tool=callgrind -v --callgrind-out-file="
350 . "-callgrind-vg.log ";
354 $fullcmd = $fullcmd . "/home/edward/GIT/prads/src/prads -r " . $fuzzedfile;
356 #. $PRADSbin . " -c "
357 #. $configfile . " -r "
358 #. $fuzzedfile . " -l "
360 if ( defined $configfile ) {
361 $fullcmd = $fullcmd . " -c " . $configfile;
363 print "PRADS: $fullcmd \n";
364 my $starttime = time();
365 ( $out, $err ) = capture
{
368 $PRADS_sys_signal = $?
& 127;
369 $PRADS_sys_coredump = $?
& 128;
372 my $stoptime = time();
373 my $timetotal = $stoptime - $starttime;
374 print LOGFILE
$fullcmd . ","
377 . $PRADS_sys_signal . ","
378 . $PRADS_sys_coredump . "\n";
379 print "PRADS: exit value $exit\n";
384 #fuzzer genrated some random link type we can't deal with
386 /datalink type \d+ not \(yet\) supported in module PcapFile\./ )
388 print "PRADS: we matched a known error going to the next file\n";
391 if ( $knownerr eq 1 ) {
393 print "PRADS: we have run with success " . $successcnt . " times\n";
394 &clean_logs
($fuzzedfilename);
397 my $report = $logdir . $fuzzedfilename . "ERR.txt";
398 open( REPORT
, ">$report" )
399 || ( print "Could not open report file! $report\n" );
400 print REPORT
"COMMAND:$fullcmd\n";
401 print REPORT
"STDERR:$err\n";
402 print REPORT
"EXITVAL:$exit\n";
403 print REPORT
"STDOUT:$out\n";
405 &process_core_dump
();
407 print REPORT
$core_dump;
408 print "core dump \n $core_dump";
420 elsif ( $PRADS_sys_signal eq 2 ) {
421 print "PRADS: system() got a ctl+c we are bailing as well\n";
426 print "PRADS: we have run with success " . $successcnt . " times\n";
427 print "******************PRADS Complete**********************\n";
428 &clean_logs
($fuzzedfilename);
429 print "******************Next Packet or Exit *******************\n";
434 sub process_core_dump
{
436 my $gdb = new Devel
::GDB
();
437 my $coremask = $ENV{'PWD'} . "/core*";
438 my @coredumps = <${coremask
}>;
439 if (@coredumps eq 1 ) {
440 my $corefile = $coredumps[0];
441 print "gdb: core dump found $corefile processesing with";
442 if ( $useltsuri eq "yes" ) {
443 $gdbbin = $ltsuribin;
448 print " the following bin file" . $gdbbin . "\n";
449 $core_dump .= join '',
450 $gdb->get("file $gdbbin"), $gdb->get("core $corefile"),
451 $gdb->get('info threads'), $gdb->get('thread apply all bt full');
452 print "gdb: core dump \n $core_dump";
455 elsif ( @coredumps gt 1 ) {
456 print "gdb: multiple core dumps, please clear all core dumps and try the test again. We found:\n";
457 foreach my $corefile (@coredumps) {
458 print $corefile . "\n";
462 print "gdb: no coredumps found returning.\n";
464 print " $#coredumps" . "\n";
466 print "******************GDB Complete**********************\n";
471 my $deleteme = shift;
472 my $deletemerge = $logdir . $deleteme;
474 if ( defined $editeratio ) {
475 if ( unlink($deletemerge) == 1 ) {
476 print "clean_logs: " . $deletemerge . " deleted successfully.\n";
479 print "clean_logs: error " . $deletemerge . " was not deleted. You may have to delete the file manually.\n";
483 if ( defined $valgrindopt ) {
484 #uncomment the following lines if you want to remove valgrind logs
485 #$rmcmd = "rm -f " . $deletemerge . "*vg.log";
486 #print( "running " . $rmcmd . "\n" );
490 #remove unified logs for next run
491 if ( unlink(<$logdir . unified
*>) > 0 ) {
492 print "clean_logs: deleted unifed logs for next run \n";
495 print "clean_logs: failed to delete unified logs\n:";
497 print "******************Log Cleanup Complete**********************\n";