1#!/usr/local/bin/perl
2# (C) Copyright 1998 Ivan S. Bishop (isb@notoryus.genmagic.com)
3#
4############### START SUBROUTINE DECLARATIONS ###########
5
6
7sub usage {
8    print "\n" x 24;
9    print "USAGE: ipfanalyze.pl -h  [-p port# or all] [-g] [-s] [-v] [-o] portnum -t [target ip address] [-f] logfilename\n";
10    print "\n arguments to -p -f -o REQUIRED\n";
11    print "\n -h show this help\n";
12    print "\n -p limit stats/study to this port number.(eg 25 not smtp)\n";
13    print " -g make graphs, one per 4 hour interval called outN.gif 1<=N<=5\n";
14    print " -s make  security report only (no graphical or full port info generated) \n";
15    print " -o  lowest port number incoming traffic can talk to and be regarded as safe\n";
16    print " -v verbose report with graphs and textual AND SECURITY REPORTS with -o 1024 set\n";
17    print " -t the ip address of the inerface on which you collected data!\n";
18    print " -f name ipfilter log file (compatible with V 3.2.9) [ipfilter.log]\n";
19    print " \nExample: ./ipfanalyze.pl -p all -g -f log1\n";
20    print "Will look at traffic to/from all ports and make graphs from file log1\n";
21    print " \nExample2 ./ipfanalyze.pl -p 25 -g -f log2\n";
22    print "Will look at SMTP traffic and make graphs from file log2\n";
23    print " \nExample3 ./ipfanalyze.pl -p all -g -f log3 -o 1024\n";
24    print "Will look at all traffic,make graphs from file log3 and log security info for anthing talking inwards below port 1024\n";
25    print " \nExample4 ./ipfanalyze.pl -p all -f log3 -v \n";
26    print "Report the works.....when ports below 1024 are contacted highlight (like -s -o 1024)\n";
27}
28
29
30
31
32sub makegifs {
33local  ($maxin,$maxout,$lookat,$xmax)=@_;
34$YMAX=$maxin;
35$XMAX=$xmax;
36
37if ($maxout > $maxin)
38  { $YMAX=$maxout;}
39
40($dateis,$junk)=split " " ,  @recs[0];
41($dayis,$monthis,$yearis)=split "/",$dateis;
42$month=$months{$monthis};
43$dateis="$dayis " . "$month " . "$yearis ";
44# split graphs in to 6 four hour spans for 24 hours
45$numgraphs=int($XMAX/240);
46
47$junk=0;
48$junk=$XMAX - 240*($numgraphs);
49if($junk gt 0 )
50{
51$numgraphs++;
52}
53
54$cnt1=0;
55$end=0;
56$loop=0;
57
58while ($cnt1++ < $numgraphs)
59{
60 $filename1="in$cnt1.dat";
61 $filename2="out$cnt1.dat";
62 $filename3="graph$cnt1.conf";
63 open(OUTDATA,"> $filename2") || die "Couldnt open $filename2 for writing \n";
64 open(INDATA,"> $filename1") || die "Couldnt open $filename1 for writing \n";
65
66 $loop=$end;
67 $end=($end + 240);
68
69# write all files as x time coord from 1 to 240 minutes
70# set hour in graph via conf file
71 $arraycnt=0;
72 while ($loop++ < $end )
73  {
74   $arraycnt++;
75   $val1="";
76   $val2="";
77   $val1=$inwards[$loop] [1];
78   if($val1 eq "")
79     {$val1=0};
80   $val2=$outwards[$loop] [1];
81   if($val2 eq "")
82     {$val2=0};
83   print INDATA "$arraycnt:$val1\n";
84   print OUTDATA "$arraycnt:$val2\n";
85  }
86  close INDATA;
87  close OUTDATA;
88  $gnum=($cnt1 - 1);
89 open(INCONFIG,"> $filename3") || die "Couldnt open ./graph.conf for writing \n";
90   print INCONFIG "NUMBERYCELLGRIDSIZE:5\n";
91   print INCONFIG "MAXYVALUE:$YMAX\n";
92   print INCONFIG "MINYVALUE:0\n";
93   print INCONFIG "XCELLGRIDSIZE:1.3\n";
94   print INCONFIG "XMAX: 240\n";
95   print INCONFIG "Bar:0\n";
96   print INCONFIG "Average:0\n";
97   print INCONFIG "Graphnum:$gnum\n";
98   print INCONFIG "Title: port $lookat packets/minute to/from gatekeep on $dateis  \n";
99   print INCONFIG "Transparent:no\n";
100   print INCONFIG "Rbgcolour:0\n";
101   print INCONFIG "Gbgcolour:255\n";
102   print INCONFIG "Bbgcolour:255\n";
103   print INCONFIG "Rfgcolour:0\n";
104   print INCONFIG "Gfgcolour:0\n";
105   print INCONFIG "Bfgcolour:0\n";
106   print INCONFIG "Rcolour:0\n";
107   print INCONFIG "Gcolour:0\n";
108   print INCONFIG "Bcolour:255\n";
109   print INCONFIG "Racolour:255\n";
110   print INCONFIG "Gacolour:255\n";
111   print INCONFIG "Bacolour:0\n";
112   print INCONFIG "Rincolour:100\n";
113   print INCONFIG "Gincolour:100\n";
114   print INCONFIG "Bincolour:60\n";
115   print INCONFIG "Routcolour:60\n";
116   print INCONFIG "Goutcolour:100\n";
117   print INCONFIG "Boutcolour:100\n";
118   close INCONFIG;
119
120}
121
122
123$cnt1=0;
124while ($cnt1++ < $numgraphs)
125{
126 $filename1="in$cnt1.dat";
127 $out="out$cnt1.gif";
128 $filename2="out$cnt1.dat";
129 $filename3="graph$cnt1.conf";
130 system( "cp ./$filename1  ./in.dat;
131          cp ./$filename2  ./out.dat;
132          cp ./$filename3  ./graph.conf");
133 system( "./isbgraph -conf graph.conf;mv graphmaker.gif $out");
134 system(" cp $out /isb/local/etc/httpd/htdocs/.");
135
136}
137
138} # end of subroutine make gifs
139
140
141
142
143sub packbytime {
144local  ($xmax)=@_;
145$XMAX=$xmax;
146# pass in the dest port number or get graph for all packets
147# at 1 minute intervals
148# @shortrecs has form 209.24.1.217 123 192.216.16.2 123 udp len 20 76
149# @recs has form 27/07/1998 00:01:05.216596  le0 @0:2 L 192.216.21.16,2733 -> 192.216.16.2,53 PR udp len 20 62
150#
151# dont uses hashes to store how many packets per minite as they
152# return random x coordinate order
153@inwards=();
154@outwards=();
155$cnt=-1;
156$value5=0;
157$maxin=0;
158$maxout=0;
159$xpos=0;
160while ($cnt++ <= $#recs )
161 {
162 ($srcip,$srcport,$destip,$destport,$pro)= split " " ,  @shortrecs[$cnt];
163  $bit=substr(@recs[$cnt],11);
164  ($bit,$junkit)= split " " , $bit ;
165 ($hour,$minute,$sec,$junk) = split ":", $bit;
166#
167# covert the time to decimal minutes and bucket to nearest minute
168#
169 $xpos=($hour * 3600) + ($minute * 60) + ($sec) ;
170# xpos is number of seconds since 00:00:00 on day......
171 $xpos=int($xpos / 60);
172# if we just want to see all packet in/out activity
173 if("$lookat" eq "all")
174   {
175    if("$destip" eq "$gatekeep")
176      {
177#      TO GATEKEEP port lookat
178#        print "to gatekeep at $xpos\n";
179        $value5=$inwards[$xpos] [1];
180        $value5++ ;
181#        $maxin = $value5 if $maxin < $value5 ;
182
183         if($value5 > $maxin)
184                   {
185                        $maxin=$value5;
186                        $timemaxin="$hour:$minute";
187                   }
188        $inwards[$xpos][1]=$value5;
189        }
190    else
191      {
192#       FROM GATEKEEP to port lookat
193#        print "from gatekeep at $xpos\n";
194        $value4=$outwards[$xpos] [1];
195        $value4++ ;
196#        $maxout = $value4 if $maxout < $value4 ;
197	if($value4 > $maxout)
198           {
199                $maxout=$value4;
200                $timemaxout="$hour:$minute";
201           }
202
203        $outwards[$xpos][1]=$value4;
204      }
205    }
206
207
208
209
210 if("$destport" eq "$lookat")
211   {
212    if("$destip" eq "$gatekeep")
213      {
214#      TO GATEKEEP port lookat
215#        print "to gatekeep at $xpos\n";
216        $value5=$inwards[$xpos] [1];
217        $value5++ ;
218        $maxin = $value5 if $maxin < $value5 ;
219        $inwards[$xpos][1]=$value5;
220        }
221    else
222      {
223#       FROM GATEKEEP to port lookat
224#        print "from gatekeep at $xpos\n";
225        $value4=$outwards[$xpos] [1];
226        $value4++ ;
227        $maxout = $value4 if $maxout < $value4 ;
228        $outwards[$xpos][1]=$value4;
229      }
230   }
231 } # end while
232
233# now call gif making stuff
234if("$opt_g" eq "1")
235{
236 print "Making plots of in files outN.gif\n";;
237 makegifs($maxin,$maxout,$lookat,$#inwards);
238}
239if ("$timemaxin" ne "")
240{print "\nTime of peak packets/minute in was $timemaxin\n";}
241if ("$timemaxout" ne "")
242{print "\nTime of peak packets/minute OUT was $timemaxout\n";}
243
244} # end of subroutine packets by time
245
246
247
248
249
250sub posbadones {
251
252$safenam="";
253@dummy=$saferports;
254foreach $it (split " ",$saferports) {
255if ($it eq "icmp" )
256 {
257   $safenam = $safenam . " icmp";
258 }
259else
260 {
261   $safenam = $safenam . " $services{$it}" ;
262 }
263
264}
265print "\n\n########################################################################\n";
266print "well known ports are 0->1023\n";
267print "Registered ports are 1024->49151\n";
268print "Dynamic/Private ports are 49152->65535\n\n";
269print "Sites that contacted gatekeep on 'less safe' ports  (<$ITRUSTABOVE)\n";
270
271print " 'safe'  ports are $safenam                               \n";
272print "\n variables  saferports and safehosts hardwire what/who we trust\n";
273print "########################################################################\n";
274
275$loop=-1;
276while ($loop++ <= $#recs )
277 {
278 ($srcip,$srcport,$destip,$destport,$pro)= split " " ,  @shortrecs[$loop];
279  if ("$destip" eq "$gatekeep")
280    {
281     if ($destport < $ITRUSTABOVE )
282     {
283#      if index not found (ie < 0) then we have a low port attach to gatekeep
284#      that is not to a safer port (see top of this file)
285#      ie no ports 25 (smtp), 53 (dns) , 113 (ident), 123 (ntp), icmp
286       $where=index($saferports,$destport);
287       if ($where < 0)
288         {
289          $nameis=$services{$destport};
290          if ("$nameis" eq "" )
291            {
292              $nameis=$destport;
293            }
294              print " Warning: $srcip contacted gatekeep $nameis\n";
295         }
296      }
297    }
298  }
299print "\n\n";
300} # end of subroutine posbadones
301
302
303
304
305sub toobusy_site {
306$percsafe=1;
307print "\n\n########################################################################\n";
308print "# Sites sending > $percsafe % of all packets to gatekeep MAY be attacking/probing\n";
309print "Trusted hosts are $safehosts\n";
310print "\nTOTAL packets were $#recs \n";
311print "########################################################################\n";
312while(($ipadd,$numpacketsent)=each %numpacks)
313{
314$perc=$numpacketsent/$#recs*100;
315if ($perc > $percsafe)
316# dont believe safehosts are attacking!
317 {
318   $where=index($safehosts,$ipadd);
319#  if not found (ie < 0 then the source host IP address
320#  isn't in the saferhosts list, a list we trust......
321   if ($where < 0 )
322   {
323     printf "$ipadd	 sent %4.1f (\045) of all packets to gatekeep\n",$perc;
324   }
325 }
326}
327
328print "\n\n";
329} # end of subroutine toobusy_site
330
331
332############### END SUBROUTINE DECLARATIONS ###########
333
334use Getopt::Std;
335
336getopt('pfot');
337
338if("$opt_t" eq "0")
339 {usage;print "\n---->ERROR: You must psecify the IP address of the interface that collected the data!\n";
340exit;
341}
342
343if("$opt_h" eq "1")
344 {usage;exit 0};
345if("$opt_H" eq "1")
346 {usage;exit 0};
347
348if("$opt_v" eq "1")
349{
350$ITRUSTABOVE=1024;
351$opt_s=1;
352$opt_o=$ITRUSTABOVE;
353print "\n" x 5;
354print "NOTE: when the final section of the verbose report is generated\n";
355print "      every host IP address that contacted $gatekeep has \n";
356print "      a tally of how many times packets from a particular port on that host\n";
357print "      reached $gatekeep, and WHICH source port or source portname \n";
358print "      these packets originated from.\n";
359print "      Many non RFC obeying boxes do not use high ports and respond to requests from\n";
360print "      $gatekeep using  reserved low ports... hence you'll see things like\n";
361print "      #### with 207.50.191.60 as the the source for packets ####\n";
362print "      1 connections from topx to gatekeep\n\n\n\n";
363
364}
365
366if("$opt_o" eq "")
367 {usage;print "\n---->ERROR: Must specify lowest safe port name for incoming trafic\n";exit 0}
368else
369{
370$ITRUSTABOVE=$opt_o;$opt_s=1;}
371
372if("$opt_f" eq "")
373 {usage;print "\n---->ERROR: Must specify filename with -f \n";exit 0};
374$FILENAME=$opt_f;
375
376if("$opt_p" eq "")
377 {usage;print "\n---->ERROR: Must specify port number or 'all' with -p \n";exit 0};
378
379# -p arg must be all or AN INTEGER in range 1<=N<=64K
380if ("$opt_p" ne "all")
381 {
382   $_=$opt_p;
383   unless (/^[+-]?\d+$/)
384   {
385     usage;
386     print "\n---->ERROR: Must specify port number (1-64K) or 'all' with -p \n";
387     exit 0;
388   }
389 }
390
391
392# if we get here then the port option is either 'all' or an integer...
393# good enough.....
394$lookat=$opt_p;
395
396# -o arg must be all or AN INTEGER in range 1<=N<=64K
397   $_=$opt_o;
398   unless (/^[+-]?\d+$/)
399   {
400     usage;
401     print "\n---->ERROR: Must specify port number (1-64K)  with -o \n";
402     exit 0;
403   }
404
405
406#---------------------------------------------------------------------
407
408
409%danger=();
410%numpacks=();
411
412$saferports="25 53 113 123 icmp";
413$gatekeep="192.216.16.2";
414#genmagic is  192.216.25.254
415$safehosts="$gatekeep 192.216.25.254";
416
417
418
419# load hash with service numbers versus names
420
421# hash  called $services
422print "Creating hash of service names / numbers \n";
423$SERV="./services";
424open (INFILE, $SERV) || die "Cant open $SERV: $!n";
425while(<INFILE>)
426{
427 ($servnum,$servname,$junk)=split(/ /,$_);
428# chop off null trailing.....
429  $servname =~ s/\n$//;
430  $services{$servnum}=$servname;
431}
432print "Create hash of month numbers as month names\n";
433%months=("01","January","02","February","03","March","04","April","05","May","06","June","07","July","08","August","09","September","10","October","11","November","12","December");
434
435print "Reading log file into an array\n";
436#$FILENAME="./ipfilter.log";
437open (REC, $FILENAME) || die "Cant open $FILENAME: \n";
438($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$junk)=stat REC;
439print "Log file $FILENAME is $size bytes in size\n";
440#each record is an element of array rec[] now
441while(<REC>)
442 {
443 @recs[$numrec++]=$_;
444 }
445
446
447# get list of UNIQUE source IP addresses now, records look like
448# 192.216.25.254,62910 ->  192.216.16.2,113 PR tcp len 20 40 -R
449# this is slow on big log files, about 1minute for every 2.5M log file
450print "Making list of unique source IP addresses (1minute for every 2M log parsed)\n";
451$loop=-1;
452$where=-1;
453while ($loop++ < $#recs )
454 {
455# get the LHS = source IP address, need fiddle as icmp rcords are logged oddly
456  $bit=substr(@recs[$loop],39);
457  $bit =~ s/,/ /g;
458  ($sourceip,$junkit)= split " " , $bit ;
459
460# NOTE the  . is the string concat command NOT + .......!!!!
461
462  $sourceip =~ split " ", $sourceip;
463   $where=index($allips,$sourceip);
464#  if not found (ie < 0, add it)
465   if ($where < 0 )
466   {
467     $allips = $allips . "$sourceip " ;
468   }
469 }
470
471print "Put all unique ip addresses into a 1D array\n";
472@allips=split " ", $allips;
473
474#set loop back to -1 as first array element in recs is element 0 NOT 1 !!
475print "Making compact array of logged entries\n";
476$loop=-1;
477$icmp=" icmp ";
478$ptr=" -> ";
479$lenst=" len ";
480$numpackets=0;
481
482while ($loop++ < $#recs )
483 {
484# this prints from 39 char to EOR
485 $a=substr(@recs[$loop],39);
486 ($srcip,$dummy,$destip,$dummy2,$dummy3,$dummy4,$lenicmp)= split " " ,  $a ;
487# need to rewrite icmp ping records.... they dont have service numbers
488 $whereicmp=index($a,"PR icmp");
489 if($whereicmp > 0 )
490 {
491  $a = $srcip . $icmp .  $ptr . $destip  . $icmp . $icmp . $lenst . $lenicmp ;
492 }
493
494# dump the "->"  and commas from logging
495 $a =~ s/->//g;
496 $a =~ s/PR//g;
497 $a =~ s/,/ /g;
498# shortrec has records that look like
499# 209.24.1.217 123 192.216.16.2 123 udp len 20 76
500 @shortrecs[$loop]= "$a";
501
502# count number packets from each IP address into hash
503 ($srcip,$junk) = split " ","$a";
504 $numpackets=$numpacks{"$srcip"};
505 $numpackets++ ;
506 $numpacks{"$srcip"}=$numpackets;
507
508}
509
510
511
512# call sub to analyse packets by time
513# @shortrecs has form 209.24.1.217 123 192.216.16.2 123 udp len 20 76
514# @recs has form 27/07/1998 00:01:05.216596  le0 @0:2 L 192.216.21.16,2733 -> 192.216.16.2,53 PR udp len 20 62
515packbytime($XMAX);
516
517if("$opt_s" eq "1")
518{
519# call subroutine to scan for connections to ports on gatekeep
520# other than those listed in saferports, connections to high
521# ports are assumed OK.....
522posbadones;
523
524# call subroutine to print out which sites had sent more than
525# a defined % of packets to gatekeep
526toobusy_site;
527}
528
529
530# verbose reporting?
531if ("$opt_v" eq "1")
532{
533$cnt=-1;
534# loop over ALL unique IP source destinations
535while ($cnt++ < $#allips)
536{
537  %tally=();
538  %unknownsrcports=();
539  $uniqip=@allips[$cnt];
540  $loop=-1;
541  $value=0;
542  $value1=0;
543  $value2=0;
544  $value3=0;
545  $set="N";
546
547  while ($loop++ < $#recs )
548   {
549#    get src IP num,    src port number,
550#    destination IP num, destnation port number,protocol
551     ($srcip,$srcport,$destip,$destport,$pro)= split " " ,  @shortrecs[$loop];
552# loop over all records for the machine $uniqip
553# NOTE THE STRINGS ARE COMPARED WITH eq NOT cmp and NOT = !!!!
554   if(  "$uniqip" eq "$srcip")
555    {
556# look up hash of service names to get key... IF ITS NOT THERE THEN WHAT???
557# its more than likely  a request coming back in on a high port
558# ....So...
559# find out the destination port from the unknown (high) src port
560# and tally these as they may be a port attack
561  if ("$srcport" eq "icmp")
562   { $srcportnam="icmp";}
563  else
564   {
565     $srcportnam=$services{$srcport};
566   }
567#    try and get dest portname, if not there, leave it as the
568#    dest portnumber
569  if ("$destport" eq "icmp")
570   { $destportnam="icmp";}
571  else
572   {
573  $destportnam=$services{$destport};
574   }
575
576  if ($destportnam eq "")
577   {
578     $destportnam=$destport;
579   }
580
581  if ($srcportnam eq "")
582    {
583#    increment number of times a (high)/unknown port has gone to destport
584     $value1=$unknownsrcports{$destportnam};
585     $value1++ ;
586     $unknownsrcports{$destportnam}=$value1;
587    }
588  else
589   {
590#    want tally(srcport) counter to be increased by 1
591     $value3=$tally{$srcportnam};
592     $value3++ ;
593     $tally{$srcportnam}=$value3;
594    }
595   }
596
597
598   }
599#  end of loop over ALL IP's
600
601if ($set eq "N")
602{
603$set="Y";
604
605print "\n#### with $uniqip as the the source for packets ####\n";
606while(($key,$value)=each %tally)
607  {
608   if (not "$uniqip" eq "$gatekeep")
609    {
610      print "$value connections from $key to gatekeep\n";
611    }
612   else
613    {
614      print "$value connections from gatekeep to $key\n";
615     }
616  }
617
618
619
620while(($key2,$value2)=each %unknownsrcports)
621  {
622   if (not "$uniqip" eq "$gatekeep")
623    {
624     print "$value2 high port connections to $key2 on gatekeep\n";
625    }
626   else
627    {
628      print "$value2 high port connections to $key2 from gatekeep\n";
629     }
630  }
631
632}
633# print if rests for UNIQIP IF flag is set to N then toggle flag
634
635} # end of all IPs loop
636} # end of if verbose option set block
637
638
639
640