Merge pull request #56 from wuruilong01/master
[prads.git] / tools / prads-wirefuzz
blob9bbc25a349154ccd29c0c06c3bdb522feef3804f
1 #!/usr/bin/perl -w
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:
9 # Ubuntu 9.10
10 # sudo apt-get install libdevel-gdb-perl libcapture-tiny-perl
12 # RedHatES/CentOS 5
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
20 # Fedora Core 12
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
28 # sudo dpkg -i *.deb
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
35 use strict;
36 use warnings;
37 use Capture::Tiny 'capture';
38 use List::Util 'shuffle';
39 use Devel::GDB;
40 use File::Find;
41 use Getopt::Long;
42 use File::Basename;
44 #globals
45 my %config;
46 my @tmpfiles;
47 my @files;
48 my $PRADSbin;
49 my $loopnum;
50 my $rules;
51 my $logdir;
52 my $configfile;
53 my $editeratio;
54 my $valgrindopt;
55 my $shuffle;
56 my $useltsuri;
57 my $ltsuribin;
58 my $core_dump;
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) );
63 &parseopts();
65 #Parse the options
66 sub parseopts {
68 #display help if asked
69 if ( $config{h} || $config{help} ) {
70 &printhelp();
73 #filemask of pcaps to read?
74 if ( $config{r} ) {
75 @tmpfiles = <$config{r}>;
76 if(@tmpfiles eq 0){
77 print "parseopts: Pcap filemask was invalid we couldn't find any matching files\n";
78 exit;
81 else {
82 print "parseopts: Pcap filemask not specified or doesn't exist\n";
83 &printhelp();
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.
91 if ( -T $PRADSbin ) {
92 open my $in, '<', $PRADSbin or die "Can't read old file: $!";
93 while (<$in>) {
94 if ( $_ =~
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";
104 $useltsuri = "yes";
106 last;
109 close $in;
111 elsif ( -B $PRADSbin ) {
112 print "parseopts: PRADS bin file checks out\n";
114 else {
115 print "parseopts: PRADS bin file is not a text or a bin exiting.\n";
116 exit;
119 else {
120 print "parseopts: Path to PRADS bin not provided or doesn't exist\n";
121 &printhelp();
124 #number of times to loop
125 if ( $config{n} ) {
126 $loopnum = $config{n};
127 print "parseopts: looping through the pcaps " . $loopnum . " times or until we have an error\n";
129 else {
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} ) {
136 $rules = $config{s};
137 print "parseopts: telling PRADS to use rules file " . $rules . "\n";
139 else {
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";
148 else {
149 $logdir = "./";
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";
157 else {
158 print "parseopts: config file not specified or doesn't exist\n";
159 #&printhelp();
162 #% chance that a byte will be modified.
163 if ( $config{e} ) {
165 #valid range?
166 my $tmperatio = $config{e} * 100;
167 if ( $tmperatio <= 100 && $tmperatio >= 0 ) {
168 $editeratio = $config{e};
169 print "parseopts: using error ratio " . $editeratio . "\n";
171 else {
172 print "parseopts: error ratio specified but outside of range. Valid range is 0.00-1.0\n";
173 exit;
176 else {
177 print("parseopts: not going to fuzz pcap(s)\n");
180 #parse the valgrind opts
181 if ( $config{v} ) {
182 if ( $config{v} =~ /^(memcheck|drd|helgrind|callgrind)$/ ) {
183 $valgrindopt = $config{v};
184 print "parseopts: using valgrind opt " . $valgrindopt . "\n";
186 else {
187 print "invalid valgrind opt " . $valgrindopt . "\n";
191 #shuffle the array if we are starting multiple fuzzers at once. GO-GO gadget shuffle
192 if ( $config{y} ) {
193 print "parseopts: going to shuffle the array\n";
194 $shuffle = "yes";
196 print "******************Initialization Complete**********************\n";
197 return;
201 sub printhelp {
202 print "
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.>
213 Example usage:
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";
233 exit;
236 #escapes for filenames
237 foreach my $file (@tmpfiles) {
238 $file =~ s/\(/\\(/g;
239 $file =~ s/\)/\\)/g;
240 $file =~ s/\&/\\&/g;
243 my $logfile = $logdir . "prads-wirefuzzlog.txt";
244 open( LOGFILE, ">>$logfile" )
245 || die( print "error: Could not open logfile! $logfile\n" );
247 my $successcnt = 0;
248 while ( $successcnt < $loopnum ) {
249 if ( defined $shuffle ) {
250 @files = shuffle(@tmpfiles);
252 else {
253 @files = @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 ) =
267 localtime(time);
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;
273 $editcapcmd =
274 "editcap -E " . $editeratio . " " . $file . " " . $fuzzedfile;
275 print( "editcap: " . $editcapcmd . "\n" );
276 ( $editcapout, $editcaperr ) = capture {
277 system $editcapcmd;
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.
285 if ( @files lt 2 ) {
286 print "editcap: had an error and this was our only pcap:" . $editcaperr . "\n";
287 exit;
289 else {
290 print "editcap: had an error going to the next pcap:" . $editcaperr . "\n";
291 next;
294 elsif ( $editcap_sys_signal eq 2 ) {
295 print "editcap: system() got a ctl+c we are bailing as well\n";
296 exit;
299 else {
300 print("editcap: ran successfully\n");
301 print
302 "******************Editcap Complete**********************\n";
305 else {
306 $fuzzedfile = $file;
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" ) {
317 $fullcmd =
318 $fullcmd
319 . "valgrind -v --log-file="
320 . $logdir
321 . $fuzzedfilename
322 . $timestamp
323 . "-memcheck-vg.log ";
325 elsif ( $valgrindopt eq "drd" ) {
326 $fullcmd =
327 $fullcmd
328 . "valgrind --tool=drd --var-info=yes -v --log-file="
329 . $logdir
330 . $fuzzedfilename
331 . $timestamp
332 . "-drd-vg.log ";
334 elsif ( $valgrindopt eq "helgrind" ) {
335 $fullcmd =
336 $fullcmd
337 . "valgrind --tool=helgrind -v --log-file="
338 . $logdir
339 . $fuzzedfilename
340 . $timestamp
341 . "-helgrind-vg.log ";
343 elsif ( $valgrindopt eq "callgrind" ) {
344 $fullcmd =
345 $fullcmd
346 . "valgrind --tool=callgrind -v --callgrind-out-file="
347 . $logdir
348 . $fuzzedfilename
349 . $timestamp
350 . "-callgrind-vg.log ";
354 $fullcmd = $fullcmd . "/home/edward/GIT/prads/src/prads -r " . $fuzzedfile;
355 #$fullcmd
356 #. $PRADSbin . " -c "
357 #. $configfile . " -r "
358 #. $fuzzedfile . " -l "
359 #. $logdir;
360 if ( defined $configfile ) {
361 $fullcmd = $fullcmd . " -c " . $configfile;
363 print "PRADS: $fullcmd \n";
364 my $starttime = time();
365 ( $out, $err ) = capture {
366 system $fullcmd;
367 $exit = $? >> 8;
368 $PRADS_sys_signal = $? & 127;
369 $PRADS_sys_coredump = $? & 128;
372 my $stoptime = time();
373 my $timetotal = $stoptime - $starttime;
374 print LOGFILE $fullcmd . ","
375 . $timetotal . ","
376 . $exit . ","
377 . $PRADS_sys_signal . ","
378 . $PRADS_sys_coredump . "\n";
379 print "PRADS: exit value $exit\n";
381 if ( $exit ne 0 ) {
382 my $knownerr = 0;
384 #fuzzer genrated some random link type we can't deal with
385 if ( $err =~
386 /datalink type \d+ not \(yet\) supported in module PcapFile\./ )
388 print "PRADS: we matched a known error going to the next file\n";
389 $knownerr = 1;
391 if ( $knownerr eq 1 ) {
392 $successcnt++;
393 print "PRADS: we have run with success " . $successcnt . " times\n";
394 &clean_logs($fuzzedfilename);
396 else {
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();
406 if ($core_dump) {
407 print REPORT $core_dump;
408 print "core dump \n $core_dump";
409 system( "mv "
410 . $ENV{'PWD'}
411 . "/core* "
412 . $logdir
413 . $fuzzedfilename
414 . ".core" );
416 close(REPORT);
417 exit;
420 elsif ( $PRADS_sys_signal eq 2 ) {
421 print "PRADS: system() got a ctl+c we are bailing as well\n";
422 exit;
424 else {
425 $successcnt++;
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 {
435 my $gdbbin;
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;
445 else {
446 $gdbbin = $PRADSbin;
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";
461 else {
462 print "gdb: no coredumps found returning.\n";
463 print @coredumps;
464 print " $#coredumps" . "\n";
466 print "******************GDB Complete**********************\n";
467 return;
470 sub clean_logs {
471 my $deleteme = shift;
472 my $deletemerge = $logdir . $deleteme;
473 my $rmcmd;
474 if ( defined $editeratio ) {
475 if ( unlink($deletemerge) == 1 ) {
476 print "clean_logs: " . $deletemerge . " deleted successfully.\n";
478 else {
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" );
487 #system("$rmcmd");
490 #remove unified logs for next run
491 if ( unlink(<$logdir . unified*>) > 0 ) {
492 print "clean_logs: deleted unifed logs for next run \n";
494 else {
495 print "clean_logs: failed to delete unified logs\n:";
497 print "******************Log Cleanup Complete**********************\n";
498 return;