#!/usr/bin/perl
#
# Chaosreader can trace TCP/UDP/... sessions and fetch application data
# from tcpdump or snoop logs. This is like an "any-snarf" program, it will
# fetch telnet sessions, FTP files, HTTP transfers (HTML, GIF, JPEG, ...),
# SMTP emails, etc ... from the captured data inside the network traffic
# logs. It creates a html index file that links to all the session details,
# including realtime replay programs for telnet, rlogin or IRC sessions;
# and reports such as image reports and HTTP GET/POST content reports.
# It also creates replay programs for telnet sessions, so that you can
# play them back in realtime (or even different speeds).
#
# Chaosreader can also run in standalone mode - where it invokes tcpdump or
# snoop (if they are available) to create the log files and then processes
# them.
#
#
# 05-May-2004, ver 0.94 (check for new versions, http://www.brendangregg.com)
# (or run a web search for "chaosreader")
#
#
# QUICK USAGE:
# tcpdump -s9000 -w out1; chaosreader out1; netscape index.html
# or,
# snoop -o out1; chaosreader out1; netscape index.html
# or,
# ethereal (save as "out1"); chaosreader out1; netscape index.html
# or,
# chaosreader -s 5; netscape index.html
#
#
# USAGE: chaosreader [-aehikqrvxAHIRTUXY] [-D dir]
# [-b port[,...]] [-B port[,...]]
# [-j IPaddr[,...]] [-J IPaddr[,...]]
# [-l port[,...]] [-L port[,...]] [-m bytes[k]]
# [-M bytes[k]] [-o "time"|"size"|"type"|"ip"]
# [-p port[,...]] [-P port[,...]]
# infile [infile2 ...]
#
# chaosreader -s [mins] | -S [mins[,count]]
# [-z] [-f 'filter']
#
# chaosreader # Create application session files, indexes
#
# -a, --application # Create application session files (default)
# -e, --everything # Create HTML 2-way & hex files for everything
# -h # Print a brief help
# --help # Print verbose help (this) and version
# --help2 # Print massive help
# -i, --info # Create info file
# -q, --quiet # Quiet, no output to screen
# -r, --raw # Create raw files
# -v, --verbose # Verbose - Create ALL files .. (except -e)
# -x, --index # Create index files (default)
# -A, --noapplication # Exclude application session files
# -H, --hex # Include hex dumps (slow)
# -I, --noinfo # Exclude info files
# -R, --noraw # Exclude raw files
# -T, --notcp # Exclude TCP traffic
# -U, --noudp # Exclude UDP traffic
# -Y, --noicmp # Exclude ICMP traffic
# -X, --noindex # Exclude index files
# -k, --keydata # Create extra files for keystroke analysis
# -D dir --dir dir # Output all files to this directory
# -b 25,79 --playtcp 25,79 # replay these TCP ports as well (playback)
# -B 36,42 --playudp 36,42 # replay these UDP ports as well (playback)
# -l 7,79 --htmltcp 7,79 # Create HTML for these TCP ports as well
# -L 7,123 --htmludp 7,123 # Create HTML for these UDP ports as well
# -m 1k --min 1k # Min size of connection to save ("k" for Kb)
# -M 1024k --max 1k # Max size of connection to save ("k" for Kb)
# -o size --sort size # sort Order: time/size/type/ip (Default time)
# -p 21,23 --port 21,23 # Only examine these ports (TCP & UDP)
# -P 80,81 --noport 80,81 # Exclude these ports (TCP & UDP)
# -s 5 --runonce 5 # Standalone. Run tcpdump/snoop for 5 mins.
# -S 5,10 --runmany 5,10 # Standalone, many. 10 samples of 5 mins each.
# -S 5 --runmany 5 # Standalone, endless. 5 min samples forever.
# -z --runredo # Standalone, redo. Rereads last run's logs.
# -j 10.1.2.1 --ipaddr 10.1.2.1 # Only examine these IPs
# -J 10.1.2.1 --noipaddr 10.1.2.1 # Exclude these IPs
# -f 'port 7' --filter 'port 7' # With standalone, use this dump filter.
#
# eg1,
# tcpdump -s9000 -w output1 # create tcpdump capture file
# chaosreader output1 # extract recognised sessions, or,
# chaosreader -ve output1 # gimme everything, or,
# chaosreader -p 20,21,23 output1 # only ftp and telnet...
# eg2,
# snoop -o output1 # create snoop capture file instead
# chaosreader output1 # extract recognised sessions...
# eg3,
# chaosreader -S 2,5 # Standalone, sniff network 5 times for 2 mins
# # each. View index.html for progress (or .text)
#
# Output Files: Many will be created, run this in a clean directory.
# Short example,
# index.html Html index (full details)
# index.text Text index
# index.file File index for standalone redo mode
# image.html HTML report of images
# getpost.html HTML report of HTTP GET/POST requests
# session_0001.info Info file describing TCP session #1
# session_0001.telnet.html HTML coloured 2-way capture (time sorted)
# session_0001.telnet.raw Raw data 2-way capture (time sorted)
# session_0001.telnet.raw1 Raw 1-way capture (assembeled) server->client
# session_0001.telnet.raw2 Raw 1-way capture (assembeled) client->server
# session_0002.web.html HTML coloured 2-way
# session_0002.part_01.html HTTP portion of the above, a HTML file
# session_0003.web.html HTML coloured 2-way
# session_0003.part_01.jpeg HTTP portion of the above, a JPEG file
# session_0004.web.html HTML coloured 2-way
# session_0004.part_01.gif HTTP portion of the above, a GIF file
# session_0005.part_01.ftp-data.gz An FTP transfer, a gz file.
# ...
# The convention is,
# session_* TCP Sessions
# stream_* UDP Streams
# icmp_* ICMP packets
# index.html HTML Index
# index.text Text Index
# index.file File Index for standalone redo mode only
# image.html HTML report of images
# getpost.html HTML report of HTTP GET/POST requests
# *.info Info file describing the Session/Stream
# *.raw Raw data 2-way capture (time sorted)
# *.raw1 Raw 1-way capture (assembeled) server->client
# *.raw2 Raw 1-way capture (assembeled) client->server
# *.replay Session replay program (perl)
# *.partial.* Partial capture (tcpdump/snoop were aware of drops)
# *.hex.html 2-way Hex dump, rendered in coloured HTML
# *.hex.text 2-way Hex dump in plain text
# *.X11.replay X11 replay script (talks X11)
# *.textX11.replay X11 communicated text replay script (text only)
# *.textX11.html 2-way text report, rendered in red/blue HTML
# *.keydata Keystroke delay data file. Used for SSH analysis.
#
# Modes:
# * Normal - eg "chaosreader infile", this is where a tcpdump/snoop file
# was created previously and chaosreader reads and processes it.
# * Standalone, once - eg "chaosreader -s 10", this is where chaosreader
# runs tcpdump/snoop and generates the log file, in this case for 10 i
# minutes, and then processes the result. Some OS's may not have
# tcpdump or snoop available so this will not work (instead you may be
# able to get Ethereal, run it, save to a file, then use normal mode).
# There is a master index.html and the report index.html in a sub dir,
# which is of the format out_YYYYMMDD-hhmm, eg "out_20031003-2221".
# * Standalone, many - eg "chaosreader -S 5,12", this is where chaosreader
# runs tcpdump/snoop and generates many log files, in this case it
# samples 12 times for 5 minutes each. While this is running, the master
# index.html can be viewed to watch progress, which links to minor
# index.html reports in each sub directory.
# * Standalone, redo - eg "chaosreader -ve -z", (the -z), this is where
# a standalone capture was previously performed - and now you would like
# to reprocess the logs - perhaps with different options (in this case,
# "-ve"). It reads index.file to determine which capture logs to read.
# * Standalone, endless - eg "chaosreader -S 5", like standalone many -
# but runs forever (if you ever had the need?). Watch your disk space!
#
# Note: this is a work in progress, some of the code is a little unpolished.
#
# Advice:
# * Run chaosreader in an empty directory.
# * Create small packet dumps. Chaosreader uses around 5x the dump size
# in memory. A 100Mb file could need 500Mb of RAM to process.
# * Your tcpdump may allow "-s0" (entire packet) instead of "-s9000".
# * Beware of using too much disk space, especially standalone mode.
# * If you capture too many small connections giving a huge index.html,
# try using the -m option to ignore small connections. eg "-m 1k".
# * snoop logs may actually work better. Snoop logs are based on RFC1761,
# however there are many varients of tcpdump/libpcap and this program
# cannot read them all. If you have Ethereal you can create snoop logs
# during the "save as" option. On Solaris use "snoop -o logfile".
# * tcpdump logs may not be portable between OSs that use different sized
# timestamps or endian.
# * Logs are best created in a memory filesystem for speed, usually /tmp.
# * For X11 or VNC playbacks, first practise by replaying a recent captured
# session of your own. The biggest problem is colour depth, your screen
# must match the capture. For X11 check authentication (xhost +), for
# VNC check the viewers options (-8bit, "Hextile", ...)
# * SSH analysis can be performed with the "sshkeydata" program as
# demonstrated on http://www.brendangregg.com/sshanalysis.html .
# chaosreader provides the input files (*.keydata) that sshkeydata
# analyses.
#
# Bugs: The following assumptions may cause problems (check for new vers);
# * A lower port number = the service type. Eg with ports 31247 and 23,
# the actual type of session is telnet (23). This may not work for
# some things (eg, VNC).
# * Time based order is more important for 2-way sessions (eg telnet),
# SEQ order is more import for 1-way transfers (eg ftp-data).
# * One particular TCP session isn't active for long enough that the SEQ
# number loops (or even wraps).
#
# WARNING: Please don't use this software for anything illegal. That definition
# differs for every country, please check the law first.
# This is a great network troubleshooting and development tool, not a
# "cracking" or "hacking" tool - a misidentification that could render owning
# this software illegal in some countries.
#
# SEE ALSO: ethereal (GUI packet viewer), dsniff (sniffing toolkit)
#
# COPYRIGHT: Copyright (c) 2003, 2004 Brendan Gregg.
#
# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# (http://www.gnu.org/copyleft/gpl.html)
#
# Author: Brendan Gregg [Sydney, Australia]
#
# Todo:
# * Rework code to improve structure.
# * Add more application protocol filters. ARP, RARP.
# * Ensure current application filters are robust (more testing).
# * Process captured filenames from FTP, HTTP and NFS transfers.
# * Add more file types (magic numbers/frequency analysis).
# * Process more IPv6 extension headers, ICMP types.
#
# 28-Sep-2003 Brendan Gregg Began writing this.
# 08-Oct-2003 " " Released version 0.7 beta
# 09-Oct-2003 " " Added telnet replays
# 12-Oct-2003 " " Added IRC ports and replays
# 19-Oct-2003 " " Made code more robust on different OSs
# 01-Nov-2003 " " Code cleanup, complex data types, IPv6, ICMP
# 03-Nov-2003 " " Added Standalone mode, standalone redo, ...
# 05-Nov-2003 " " Added Image indexes, GETPOST indexes
# 15-Nov-2003 " " Added HTTP proxy style log, hex dumps
# 27-Jan-2004 " " Released experimental X11 & VNC processing
# 30-Mar-2004 " " 802.11b, sorts, less RAM used, tun packets.
# 01-May-2004 " " CLI enhanced, faster, SSH analysis.
use Getopt::Long;
use Benchmark;
#####################
# --- Variables ---
#
#
# Some defaults
#
$PERL = "/usr/bin/perl"; # perl path for replay scripts
$integerSize = length(pack('I',0)); # can make a difference for tcpdumps
$the_date = scalar localtime(); # this is printed in the reports
$WRAP = 108; # wordwrap chars
$BM = 0; # benchmark counter
$| = 1; # flush output
#
# The following is needed for old perl5 multiline matching. New perl5 uses
# a "/s" on the RE (which is used in this program as well).
#
$* = 1; # old perl5
#
# These ports have been selected to be saved as coloured 2-way HTML files
#
@Save_As_HTML_TCP_Ports = (21,23,25,79,80,109,110,119,143,513,514,1080,
3128,4110,5000,5555,6660,6665,6666,6667,6668,7000,8000,8080,9000);
@Save_As_HTML_UDP_Ports = (53);
#
# These ports have been selected to be saved as realtime playback scripts
# (telnet, login, and numerous IRC ports)
#
@Save_As_TCP_Playback_Ports = (23,513,4110,5000,5555,6660,6666,6667,
6668,7000,8000,9000);
@Save_As_UDP_Playback_Ports = (7);
#
# These are the X11 ports to save as X11 playback scripts
#
@Save_As_X11_Playback_Ports = (6000,6001,6002,6003,6004,6005,6006,6007);
#
# These X11 ports will have the text saved as coloured 2-way HTML files
#
@Save_As_HTML_X11_Ports = (6000,6001,6002,6003,6004,6005,6006,6007);
#
# These are the VNC ports to save as VNC playback scripts
#
@Save_As_VNC_Playback_Ports = (5900,5901,5902,5903,5904,5905,5906,5907);
#
# --- Arguments ---
#
&Process_Command_Line_Arguments();
### Record program start
$Bench{++$BM}{mark} = new Benchmark if $Arg{bench};
$Bench{$BM}{text} = "Program Start";
#
# Load some lookup tables for number -> name translations.
#
&Load_Etc_Services();
&Set_IP_Protocols();
&Set_ICMP_Types();
&Set_Result_Names();
&Set_X11_Codes();
&Set_X11_KeyCodes();
&Set_VNC_Codes();
###########################
# --- MODE 1 - Normal --- #
###########################
#
# Process log files,
#
if ($Arg{normal}) {
#
# Initial values
#
$frame = 0; $number = 0;
%IP = (); %TCP = (); %UDP = (); %ICMP = (); %Count = (); %Hex = ();
### Print version
&Print_Welcome();
######################################
# --- INPUT - Read Packet Log(s) ---
#
foreach $filename (@{$Arg{infiles}}) {
#
# Check input file type and Open
#
&Open_Input_File($filename);
#
# Read though the entire input file, saving all packet
# data in memory (mainly %TCP and %UDP).
#
&Read_Input_File();
}
#############################################
# --- OUTPUT - Process TCP/UDP Sessions ---
#
### cd to output
&Chdir($Arg{output_dir});
&Print_Header2();
### Determine Session and Stream time order
%Index = (); %Image = (); %GETPOST = ();
&Sort_Index();
#
# Process %TCP and create session* output files, write %Index
#
&Process_TCP_Sessions();
#
# Process %UDP and create session* output files, write %Index
#
&Process_UDP_Streams();
#
# Process %ICMP
#
&Process_ICMP();
#
# Create Index Files from %Index
#
&Create_Index_Files();
&Create_Log_Files();
###############
# --- END ---
#
&Print_Footer1();
}
###############################
# --- MODE 2 - Standalone --- #
###############################
elsif ($Arg{standalone}) {
############################################################
# --- STANDALONE - Create Packet Logs and Process them ---
#
$limit = $Arg{count};
$filenum = 0;
### Check for the sniffer command
&Check_Command();
### cd to output
&Chdir($Arg{output_dir});
### Print welcome
&Print_Welcome();
#
# MAIN LOOP
#
while ($limit != 0) {
#
# Create a meaningful directory and filename
#
@Times = localtime();
$dirname = sprintf("out_%d%02d%02d-%02d%02d",($Times[5]+1900),
$Times[4],$Times[3],$Times[2],$Times[1]);
$filename = "$dirname.log";
#
# Initial values
#
$frame = 0; $number = 0;
%IP = (); %TCP = (); %UDP = (); %ICMP = (); %Count = (); %Hex = ();
#
# Record details in a Master Index
#
$Master[$filenum]{starttime} = scalar localtime();
$Master[$filenum]{duration} = - time(); # will +end time
$Master[$filenum]{dir} = $dirname;
$Master[$filenum]{file} = $filename;
#
# Create and cd to output dir
#
mkdir ("$dirname",0755) || die "ERROR01: Couldn't mkdir (perms?): $!\n";
chdir "$dirname" || die "ERROR02: Couldn't cd $dirname: $!\n";
print "\nCreating log: $dirname/$filename\n" unless $Arg{quiet};
#
# fork, so that one process can exec tcpdump/snoop while the other
# sleeps and then kills it.
#
$pid = fork();
die "ERROR03: Can't fork (resources?): $!\n" if (! defined $pid);
if ($pid == 0) {
###############################
# --- CREATE - Packet Log ---
#
print "Running: $command $filename $Arg{filter}\n"
unless $Arg{quiet};
### exec, so $pid points to sniffer
exec("$command $filename $Arg{filter}") &&
die "ERROR04: couldn't run $command file: $!\n";
} else {
### Wait for logfile to be populated
sleep($Arg{mins} * 60);
### Kill child (TERM, INT)
kill 15, $pid;
kill 2, $pid;
}
exit if $pid == 0; # check for impossibility
### Record end time, duration, size
$Master[$filenum]{endtime} = scalar localtime();
$Master[$filenum]{duration} += time();
# finish writing the log before reading it's size
system("sync") if (($^O eq "linux") || ($^O eq "solaris"));
$Master[$filenum]{size} = -s "$filename";
print "\nProcessing: $dirname/$filename\n" unless $Arg{quiet};
$bak = $Arg{quiet}; $Arg{quiet} = 1;
###############################
# --- INPUT - Process Log ---
#
&Open_Input_File($filename);
### Populate memory (%TCP, %UDP, ...).
&Read_Input_File();
#############################################
# --- OUTPUT - Process TCP/UDP Sessions ---
#
### Determine Session and Stream time order
%Index = (); %Image = (); %GETPOST = ();
&Sort_Index();
### Process %TCP, %UDP, ..., create output fies, write %Index
&Process_TCP_Sessions();
&Process_UDP_Streams();
&Process_ICMP();
### Create Index Files from %Index
&Create_Index_Files();
&Create_Log_Files();
chdir ".." || die "ERROR05: Couldn't cd ..: $!\n";
$Arg{quiet} = $bak;
### Create Master Index from @Master
&Create_Index_Master();
$limit--;
$filenum++;
}
}
##########################
# --- MODE 3 - Redo --- #
##########################
elsif ($Arg{redo}) {
#############################################################
# --- STANDALONE REDO - Redo last run from sniffer logs ---
#
$filenum = 0;
### Read index.file for logs to process
&Load_Index_File();
### Print welcome
&Print_Welcome();
#
# MAIN LOOP
#
for ($index=0; $index <= $#Master; $index++) {
### Get previous run values
$dirname = $Master[$index]{dir};
$filename = $Master[$index]{file};
### Initial values
$frame = 0; $number = 0;
%IP = (); %TCP = (); %UDP = (); %ICMP = (); %Count = (); %Hex = ();
### Create and cd to output dir
chdir "$dirname" || die "ERROR06: Couldn't cd $dirname: $!\n";
print "Processing: $dirname/$filename\n" unless $Arg{quiet};
$bak = $Arg{quiet}; $Arg{quiet} = 1;
###############################
# --- INPUT - Process Log ---
#
&Open_Input_File($filename);
### Populate memory (%TCP, %UDP, ...).
&Read_Input_File();
#############################################
# --- OUTPUT - Process TCP/UDP Sessions ---
#
### Determine Session and Stream time order
%Index = (); %Image = (); %GETPOST = ();
&Sort_Index();
### Process %TCP, %UDP, ..., create output fies, write %Index
&Process_TCP_Sessions();
&Process_UDP_Streams();
&Process_ICMP();
### Create Index Files from %Index
&Create_Index_Files();
&Create_Log_Files();
chdir ".." || die "ERROR07: Couldn't cd ..: $!\n";
$Arg{quiet} = $bak;
$limit--;
$filenum++;
}
### Create Master Index from @Master
&Create_Index_Master();
}
#
# BENCHMARK REPORT
#
if ($Arg{bench}) {
$Bench{++$BM}{mark} = new Benchmark;
$Bench{$BM}{text} = "Program End";
print "\nBenchmarks,\n\n";
for ($bm=1; $bm <= $BM; $bm++) {
$bdiff = timediff($Bench{$bm}{mark},$Bench{1}{mark});
printf(" %-32s %s\n",$Bench{$bm}{text},timestr($bdiff));
}
}
#####################
# --- SUBROUTINES ---
# (Most of these subroutines are used as shortcuts to code, not traditional
# scoped subroutines as with other languages)
# Open_Input_File - open the packet log specified. This checks the header
# of the file to determine whether it is a tcpdump/libpcap or snoop
# log (including several styles of tcpdump/libpcap).
#
sub Open_Input_File {
my $infile = shift;
my ($length,$size);
$Bench{++$BM}{mark} = new Benchmark if $Arg{bench};
$Bench{$BM}{text} = "Open Input File";
print "Opening, $infile\n\n" unless $Arg{quiet};
#
# Open packet log
#
open(INFILE,$infile) || die "Can't open $infile: $!\n";
binmode(INFILE); # for backward OSs
#
# Fetch header
#
$length = read(INFILE,$header,8);
die "ERROR08: Can't read from $infile\n" if $length < 8;
### Print status
print "Reading file contents,\n" unless $Arg{quiet};
$SIZE = -s $infile;
#
# Try to determine if this is a tcpdump or a snoop file
#
($ident) = unpack('a8',$header);
if ($ident =~ /^snoop/) {
$TYPE = "snoop";
$length = read(INFILE,$header,8);
($version,$type) = unpack('NN',$header);
} elsif ($ident =~ /^\241\262\303\324|^\324\303\262\241/ ||
$ident =~ /^\241\262\315\064|^\064\315\262\241/) {
$TYPE = "tcpdump";
$ident = unpack('a4',$header); # try again
# standard/modified defines style, 1/2 defines endian
if ($ident =~ /^\241\262\303\324/) { $STYLE = "standard1"; }
if ($ident =~ /^\324\303\262\241/) { $STYLE = "standard2"; }
if ($ident =~ /^\241\262\315\064/) { $STYLE = "modified1"; }
if ($ident =~ /^\064\315\262\241/) { $STYLE = "modified2"; }
if ($STYLE =~ /1$/) {
# reread in big-endian
($ident,$major,$minor) = unpack('a4nn',$header);
} else {
# reread in little-endian
($ident,$major,$minor) = unpack('a4vv',$header);
}
#
# Check tcpdump header carefully to ensure this is ver 2.4.
#
if ($major != 2 && $minor != 4) {
#
# Die if this is an unknown version. (there could
# be new vers of tcpdump/libpcap in the future).
#
print STDERR "ERROR09: Wrong tcpdump version ";
print STDERR "($version.$type).\n(expected 2.4).\n";
exit 1;
}
#
# Nudge the filehandle past the rest of the header...
#
$length = read(INFILE,$header_rest,16);
} else {
#
# Die - unknown file format
#
print STDERR "ERROR10: Input dosen't look like a tcpdump or ";
print STDERR "snoop output file.\n\tIf it is tcpdump, it ";
print STDERR "may be a wrong or new version.\n";
exit 1;
}
### Record the filename into the global %Arg
$Arg{infile} = $infile;
}
# Read_Input_File - this subroutine loops through the records in the packet
# log, storing all the TCP and UDP data into %TCP and %UDP. (see the end
# of the program for the structure of these data types). %Count is also
# populated with various frequency counts.
#
sub Read_Input_File {
my ($trailers,$pppoe_verNtype,$pppoe_code,$pppoe_id,$pppoe_length,
$ppp_protocol,$wless_fc,$wless_version,$wless_type,$wless_duration,
$wless_subtype,$wless_from,$wless_to,$wless_flag,$wless_WEP,
$wless_bss,$wless_src,$wless_dest,$wless_cksum,$llc_head,$llc_control,
$llc_org,$llc_type,$wless_OK,$bytes,$counter,$packets);
$Bench{++$BM}{mark} = new Benchmark if $Arg{bench};
$Bench{$BM}{text} = "Read Input File - start";
local $packet = 0; # counter
if ($TYPE eq "snoop") {
$bytes = 16;
} else {
$bytes = 24;
}
#
# --- Pass #1, Store IP data in memory (%IP) --
#
while (1) {
#
# --- Read Record from Log ---
#
if ($TYPE eq "snoop") {
&Read_Snoop_Record(); # will "last" on error
$packet_data = $snoop_data;
$packet_time = $snoop_seconds;
$packet_timefull = $snoop_seconds + $snoop_msecs/1000000;
$record_size = $snoop_length_rec;
} else {
&Read_Tcpdump_Record(); # will "last" on error
$packet_data = $tcpdump_data;
$packet_time = $tcpdump_seconds;
$packet_timefull = $tcpdump_seconds + $tcpdump_msecs/1000000;
$record_size = $tcpdump_length + ($integerSize * 2 + 8);
}
### Print status summary
unless ($Arg{quiet}) {
$bytes += $record_size;
if (($packet % 16) == 0) {
printf("%s %2.0f%% (%d/%d)","\b"x24,
(100*$bytes/$SIZE),$bytes,$SIZE);
}
}
#
# --- Parse TCP/IP layers (a little ;) ---
#
#-------------------------------------------------------------------
#
# Wireless, 802.11b
#
$decoded = 0; # this flag is true if wireless was found
# unpack a little first, (efficiency)
($wless_fc) = unpack('H4',$packet_data);
# this matches on possible send or receive wireless traffic, however
# this could also be the start of an 802.3 frame - making this part
# of a MAC address. (The IEEE list on OUIs had these as unassigned).
if ($wless_fc =~ /^080[1256]/) {
# now dig deeper,
# (this is one form of 802.11 - the form we are interested
# in, however note that there is a lot more to 802.11).
($wless_fc,$wless_duration,$wless_bss,$wless_src,
$wless_dest,$wless_cksum,$llc_head,$llc_control,$llc_org,
$llc_type,$ether_data)
= unpack('nnH12H12H12na2CH6H4a*',$packet_data);
$wless_to = $wless_fc & 1;
# Check this is IP and encapsulated Ethernet,
if (($llc_type eq "0800") && ($llc_org eq "000000")) {
### Populate ether variables for use later on
$ether_type = $llc_type;
if ($wless_to) {
$ether_dest = $wless_dest;
$ether_src = $wless_src;
} else {
$ether_dest = $wless_src;
$ether_src = $wless_dest;
}
$decoded = 1; # remember we did this
}
# (else try redecoding this using 802.3)
}
#-------------------------------------------------------------------
#
# Tun device
#
# unpack a little first, (efficiency)
($tun_id) = unpack('H8',$packet_data);
# this checks if the frame looks like a tun device frame
if ($tun_id eq "02000000") {
# now dig deeper,
($tun_id,$ether_data) = unpack('a4a*',$packet_data);
$ether_src = "0";
$ether_dest = "0";
$ether_type = "0800";
$decoded = 1; # remember we did this
}
#-------------------------------------------------------------------
#
# Ethernet, 802.3
#
### Unpack ether data
($ether_dest,$ether_src,$ether_type,$ether_data) =
unpack('H12H12H4a*',$packet_data) unless $decoded;
### Count ether types seen
$Count{EtherType}{$ether_type}++;
$CountMaster{EtherType}{$ether_type}++;
#
# Process extended Ethernet types (wireless, PPPoE)
#
### PPPoE
if ($ether_type eq "8864") {
($pppoe_verNtype,$pppoe_code,$pppoe_id,$pppoe_length,
$ppp_protocol,$ether_data) = unpack("CCnnna*",$ether_data);
### Skip anything but data (we just want data - code 0)
next if $pppoe_code != 0;
# (May like to add code here later to process $ppp_protocol,
# eg, to process LCP).
}
elsif (($ether_type ne "0800") && ($ether_type ne "86dd")) {
next;
}
#-------------------------------------------------------------------
#
# IP
#
### Check for IP ver
($ip_verNihl,$ip_rest) = unpack('Ca*',$ether_data);
$ip_ver = $ip_verNihl & 240;
$ip_ver = $ip_ver >> 4;
if ($ip_ver == 4) {
#-----------------------------------------------------------
#
# IPv4
#
### Unpack IP data
($ip_verNihl,$ip_tos,$ip_length,$ip_ident,$ip_flagNfrag,
$ip_ttl,$ip_protocol,$ip_checksum,@ip_src[0..3],
@ip_dest[0..3],$ip_data) = unpack('CCnnnCCa2CCCCCCCCa*',
$ether_data);
### Get frag and flag data
$ip_frag = $ip_flagNfrag & 8191;
$ip_flag = $ip_flagNfrag & 57344;
$ip_flag = $ip_flag >> 13;
$ip_MF = $ip_flag & 1;
### Strip off IP options if present
$ip_ihl = $ip_verNihl & 15;
$ip_ihl = $ip_ihl << 2;
$ip_options_num = $ip_ihl - 20;
if ($ip_options_num > 0) {
($ip_options,$ip_data) =
unpack("a${ip_options_num}a*",$ip_data);
}
### Strip off Ethernet trailers
$ip_dlength = $ip_length - $ip_options_num - 20;
($ip_data,$trailers) = unpack("a${ip_dlength}a*",$ip_data);
### Build text strings of IP addresses
$ip_src = sprintf("%u.%u.%u.%u",@ip_src);
$ip_dest = sprintf("%u.%u.%u.%u",@ip_dest);
} elsif ($ip_ver == 6) {
#-----------------------------------------------------------
#
# IPv6
#
($ip_verNihl,$ip_flow,$ip_length,$ip_next,$ip_hop,
@ip_src[0..15],@ip_dest[0..15],$ip_data) =
unpack('Ca3nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCa*',
$ether_data);
$ip_protocol = $ip_next;
### Build text strings of IP addresses
$ip_src = sprintf("%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x",
@ip_src);
$ip_dest = sprintf("%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x",
@ip_dest);
### Compress IPv6 text Address
$ip_src =~ s/:00:/:0:/g;
$ip_src =~ s/:00:/:0:/g;
$ip_dest =~ s/:00:/:0:/g;
$ip_dest =~ s/:00:/:0:/g;
$ip_src =~ s/(:0)+/::/;
$ip_dest =~ s/(:0)+/::/;
#
# Check for IPv6 Fragmentation (embedded)
#
if ($ip_protocol == 44) {
($ip_next,$ip_reserved,$ip_fragNmf,$ip_ident,$ip_data)
= unpack('CCnNa*',$ip_data);
$ip_protocol = $ip_next;
$ip_MF = $ip_fragNmf & 1;
$ip_frag = $ip_fragNmf >> 3;
} else {
$ip_MF = 0;
$ip_ident = 0;
$ip_frag = 0;
}
} else {
### Not IPv4 or IPv6 - could be LCP (skip for now)
next;
}
### Count IP Protocols seen
$Count{IPprotocol}{$ip_protocol}++;
$CountMaster{IPprotocol}{$ip_protocol}++;
### Count IP Addresses seen
$Count{IP}{$ip_src}++;
$CountMaster{IP}{$ip_src}++;
### Generate unique IP id (not just the ident)
$ip_id = &Generate_IP_ID($ip_src,$ip_dest,$ip_ident);
#
# Store IP data in %IP so we can do frag reassembly next
#
if (! defined $IP{id}{$ip_id}{StartTime}) {
$IP{time}{$packet_timefull}{ver} = $ip_ver;
$IP{time}{$packet_timefull}{src} = $ip_src;
$IP{time}{$packet_timefull}{dest} = $ip_dest;
$IP{time}{$packet_timefull}{protocol} = $ip_protocol;
$IP{time}{$packet_timefull}{frag}{$ip_frag} = $ip_data;
if ($snoop_drops || $tcpdump_drops) {
$IP{time}{$packet_timefull}{drops} = 1;
}
#
# If there are more fragments, remember this starttime
#
unless (($ip_MF == 0) && ($ip_frag == 0)) {
$IP{id}{$ip_id}{StartTime} = $packet_timefull;
}
if (($ip_MF == 1) || ($ip_frag > 0)) {
$IP{time}{$packet_timefull}{fragged} = 1;
}
} else {
$start_time = $IP{id}{$ip_id}{StartTime};
$IP{time}{$start_time}{frag}{$ip_frag} = $ip_data;
if ($snoop_drops || $tcpdump_drops) {
$IP{time}{$packet_timefull}{drops} = 1;
}
if ($ip_MF == 0) {
#
# Comlpete this IP packet. This assumes that the
# last frag arrives last.
#
undef $IP{ident}{StartTime}{$ip_id};
}
}
$packet++;
}
close INFILE;
### Print status summary
unless ($Arg{quiet}) {
printf("%s %2.0f%% (%d/%d)","\b"x24,
100,$bytes,$SIZE);
print "\nReassembling packets,\n";
}
###################################################################
# --- Pass #2, Reassemble IP data in %IP; create %TCP and %UDP ---
#
&Print_Header1() if $Arg{debug};
$packets = $packet;
$packet = 0;
@Times = sort { $a <=> $b } ( keys(%{$IP{time}}) );
foreach $time (@Times) {
### Print status summary
unless ($Arg{quiet}) {
if (($packet % 16) == 0) {
printf("%s %2.0f%% (%d/%d)","\b"x32,
(100*$packet/$packets),$packet,$packets);
}
}
#
# Get IP data from %IP
#
$ip_ver = $IP{time}{$time}{ver};
$ip_src = $IP{time}{$time}{src};
$ip_dest = $IP{time}{$time}{dest};
$ip_protocol = $IP{time}{$time}{protocol};
$drops = $IP{time}{$time}{drops};
undef $ip_data;
#
# Reassemble IP frags
#
if (defined $IP{time}{$time}{fragged}) {
@IP_Frags = sort {$a <=> $b} (keys(%{$IP{time}{$time}{frag}}));
### If never recieved the start of the packet, skip
if ($IP_Frags[0] != 0) { next; }
foreach $ip_frag (@IP_Frags) {
$ip_data .= $IP{time}{$time}{frag}{$ip_frag};
}
} else {
$ip_data = $IP{time}{$time}{frag}{0};
}
$length = length($ip_data);
#
# --- UDP ---
#
if ($ip_protocol == 17 && $Arg{output_UDP}) {
&Process_UDP_Packet($ip_data,$ip_src,$ip_dest,$time,$drops);
}
#
# --- TCP ---
#
if ($ip_protocol == 6 && $Arg{output_TCP}) {
&Process_TCP_Packet($ip_data,$ip_src,$ip_dest,$time,$drops);
}
#
# --- ICMP ---
#
if ($ip_protocol == 1 && $Arg{output_ICMP}) {
&Process_ICMP_Packet($ip_data,$ip_src,$ip_dest,$time,$drops,
"ICMP");
}
#
# --- ICMPv6 ---
#
if ($ip_protocol == 58 && $Arg{output_ICMP}) {
&Process_ICMP_Packet($ip_data,$ip_src,$ip_dest,$time,$drops,
"ICMPv6");
}
#
# Skip packet if it isn't TCP (protocol = 6). (Will add routines for
# ICMP, ARP, RARP later on)...
#
$packet++;
### Memory Cleanup
delete $IP{time}{$time};
}
### Memory Cleanup
undef %IP;
### Print status summary
unless ($Arg{quiet}) {
printf("%s %2.0f%% (%d/%d)\n","\b"x24,
100,$packet,$packets);
}
$Bench{++$BM}{mark} = new Benchmark if $Arg{bench};
$Bench{$BM}{text} = "Read Input File - end";
}
# Process_TCP_Packet - process a TCP packet and store it in memory. It takes
# the raw ip data and populates the data structure %TCP. (and %Count).
#
sub Process_TCP_Packet {
my $ip_data = shift;
my $ip_src = shift;
my $ip_dest = shift;
my $time = shift;
my $drops = shift;
my $copy;
#-------------------------------------------------------------------
#
# TCP
#
### Unpack TCP data
($tcp_src_port,$tcp_dest_port,$tcp_seq,$tcp_ack,$tcp_offset,$tcp_flags,
$tcp_header_rest,$tcp_data) = unpack('nnNNCCa6a*',$ip_data);
### Strip off TCP options, if present
$tcp_offset = $tcp_offset >> 4; # chuck out reserved bits
$tcp_offset = $tcp_offset << 2; # now times by 4
$tcp_options_num = $tcp_offset - 20;
if ($tcp_options_num > 0) {
($tcp_options,$tcp_data) =
unpack("a${tcp_options_num}a*",$tcp_data);
}
### Fetch length and FIN,RST flags
$tcp_length_data = length($tcp_data);
$tcp_fin = $tcp_flags & 1;
$tcp_syn = $tcp_flags & 2;
$tcp_rst = $tcp_flags & 4;
$tcp_ack = $tcp_flags & 16;
$copy = $tcp_data;
#
# Generate $session_id as a unique id for this stream
# (this is built from host:port,host:port - sorting on port).
#
($session_id,$from_server) = &Generate_SessionID($ip_src,$tcp_src_port,
$ip_dest,$tcp_dest_port,"TCP");
### Record direction if single SYN was seen
if ($tcp_syn && ! $tcp_ack) {
$TCP{id}{$session_id}{source} = $ip_src;
# better repeat this,
($session_id,$from_server) = &Generate_SessionID($ip_src,
$tcp_src_port,$ip_dest,$tcp_dest_port,"TCP");
}
### Count TCP Ports seen
if ($from_server) {
$Count{TCPport}{$tcp_src_port}++;
$CountMaster{TCPport}{$tcp_src_port}++;
} else {
$Count{TCPport}{$tcp_dest_port}++;
$CountMaster{TCPport}{$tcp_dest_port}++;
}
#
# Flag this session as a Partial if either tcpdump or snoop
# confesses to dropping packets.
#
$TCP{id}{$session_id}{Partial}++ if $drops;
### Store size
$TCP{id}{$session_id}{size} += length($tcp_data);
### Store the packet timestamp for the first seen packet
if (! defined $TCP{id}{$session_id}{StartTime}) {
$TCP{id}{$session_id}{StartTime} = $time;
### Store other info once
if ($from_server) {
$TCP{id}{$session_id}{src} = $ip_dest;
$TCP{id}{$session_id}{dest} = $ip_src;
$TCP{id}{$session_id}{src_port} = $tcp_dest_port;
$TCP{id}{$session_id}{dest_port} = $tcp_src_port;
} else {
$TCP{id}{$session_id}{src} = $ip_src;
$TCP{id}{$session_id}{dest} = $ip_dest;
$TCP{id}{$session_id}{src_port} = $tcp_src_port;
$TCP{id}{$session_id}{dest_port} = $tcp_dest_port;
}
}
### Store the packet timestamp in case this is the last packet
$TCP{id}{$session_id}{EndTime} = $time;
### Print status line
printf "%6s %-45s %s\n",$packet,$session_id,$length
if $Arg{debug};
#
# --- Store Session Data in Memory ---
#
# Since TCP is usually the bulk of the data, we minimise
# the number of copies of data in memory. UDP and ICMP
# are handled differently.
if ($from_server) {
#
# Populate %TCP{id}{}{time} with raw traffic by time.
# This is the master structure to store the data.
#
$TCP{id}{$session_id}{time}{$time}{data} .= $tcp_data;
$TCP{id}{$session_id}{time}{$time}{dir} .= "A";
#
#
# Populate %TCP{id}{}{Aseq} with server to client
# 1-way raw traffic, with the TCP sequence number as
# the key (for future reassembly).
#
# This is a pointer to the time structure above,
# to save on memory used (originally stored a
# duplicate copy of the data).
#
if ((! defined $TCP{id}{$session_id}{Aseq}{$tcp_seq}) ||
(length(${$TCP{id}{$session_id}{Aseq}{$tcp_seq}}) <
length($tcp_data))) {
$TCP{id}{$session_id}{Aseq}{$tcp_seq} =
\$TCP{id}{$session_id}{time}{$time}{data};
}
#
# Populate %Hex{TCP}{} with coloured HTML 2-way
# traffic, if needed.
#
if ($Arg{output_hex}) {
&Process_Hex("TCP",$session_id,$tcp_data,"blue");
}
} else {
#
# Populate %TCP{id}{}{Btime} with raw 1-way traffic by time.
# This is the master structure to store the data.
#
$TCP{id}{$session_id}{time}{$time}{data} .= $tcp_data;
$TCP{id}{$session_id}{time}{$time}{dir} .= "B";
#
#
# Populate %TCP{id}{}{Bseq} with client to server
# 1-way raw traffic, with the TCP sequence number as
# the key (for future reassembly).
#
# This is a pointer to the time structure above,
# to save on memory used (originally stored a
# duplicate copy of the data).
#
if ((! defined $TCP{id}{$session_id}{Bseq}{$tcp_seq}) ||
(length(${$TCP{id}{$session_id}{Bseq}{$tcp_seq}}) <
length($tcp_data))) {
$TCP{id}{$session_id}{Bseq}{$tcp_seq} =
\$TCP{id}{$session_id}{time}{$time}{data};
}
#
# Populate %Hex{TCP}{} with coloured HTML 2-way
# traffic, if needed.
#
if ($Arg{output_hex}) {
&Process_Hex("TCP",$session_id,$tcp_data,"red");
}
}
}
# Process_UDP_Packet - process a UDP packet and store it in memory. It takes
# the raw ip data and populates the data structure %UDP.
#
sub Process_UDP_Packet {
my $ip_data = shift;
my $ip_src = shift;
my $ip_dest = shift;
my $time = shift;
my $drops = shift;
my $copy;
#-------------------------------------------------------------------
#
# UDP
#
### Unpack UDP data
($udp_src_port,$udp_dest_port,$udp_length,$udp_checksum,
$udp_data) = unpack('nnnna*',$ip_data);
#
# Generate $session_id as a unique id for this stream
# (this is built from host:port,host:port - sorting on port).
#
($session_id,$from_server) = &Generate_SessionID($ip_src,$udp_src_port,
$ip_dest,$udp_dest_port,"UDP");
#
# Flag this session as a Partial if either tcpdump or snoop
# confesses to dropping packets.
#
$UDP{id}{$session_id}{Partial}++ if $drops;
### Store size
$UDP{id}{$session_id}{size} += length($udp_data);
### Count UDP ports seen
if ($from_server) {
$Count{UDPport}{$udp_src_port}++;
$CountMaster{UDPport}{$udp_src_port}++;
} else {
$Count{UDPport}{$udp_dest_port}++;
$CountMaster{UDPport}{$udp_dest_port}++;
}
#
# --- Store Stream Data in Memory ---
#
if ($from_server) {
#
# Populate %UDP{id}{}{RawA} with server to client
# 1-way raw traffic
#
$UDP{id}{$session_id}{RawA} .= $udp_data;
#
# Populate %UDP{id}{}{BothHTML} with coloured HTML
# 2-way traffic, blue for server to client
#
$copy = &Desex_HTML($udp_data);
$UDP{id}{$session_id}{BothHTML} .=
"$copy";
#
# Populate %Hex{UDP}{} with coloured HTML 2-way
# traffic, if needed.
#
if ($Arg{output_hex}) {
&Process_Hex("UDP",$session_id,$udp_data,"blue");
}
} else {
#
# Populate %UDP{id}{}{RawB} with client to server
# 1-way raw traffic
#
$UDP{id}{$session_id}{RawB} .= $udp_data;
#
# Populate %UDP{id}{}{BothHTML} with coloured HTML
# 2-way traffic, red for client to server
#
$copy = &Desex_HTML($udp_data);
$UDP{id}{$session_id}{BothHTML} .=
"$copy";
#
# Populate %Hex{UDP}{} with coloured HTML 2-way
# traffic, if needed.
#
if ($Arg{output_hex}) {
&Process_Hex("UDP",$session_id,$udp_data,"red");
}
}
#
# Populate %UDP{id}{}{time}{} with raw 1-way traffic by time
#
$UDP{id}{$session_id}{time}{$time} .= $udp_data;
### Store the packet timestamp for the first seen packet
if (! defined $UDP{id}{$session_id}{StartTime}) {
$UDP{id}{$session_id}{StartTime} = $time;
### Store other info once
if ($from_server) {
$UDP{id}{$session_id}{src} = $ip_dest;
$UDP{id}{$session_id}{dest} = $ip_src;
$UDP{id}{$session_id}{src_port} = $udp_dest_port;
$UDP{id}{$session_id}{dest_port} = $udp_src_port;
} else {
$UDP{id}{$session_id}{src} = $ip_src;
$UDP{id}{$session_id}{dest} = $ip_dest;
$UDP{id}{$session_id}{src_port} = $udp_src_port;
$UDP{id}{$session_id}{dest_port} = $udp_dest_port;
}
}
### Store the packet timestamp in case this is the last packet
$UDP{id}{$session_id}{EndTime} = $time;
### Print status line
printf "%6s %-45s %s\n",$packet,$session_id,$length
if $Arg{debug};
}
# Process_ICMP_Packet - process a ICMP packet and store it in memory. It takes
# the raw ip data and populates the data structure %ICMP.
# time is the session_id.
#
sub Process_ICMP_Packet {
my $ip_data = shift;
my $ip_src = shift;
my $ip_dest = shift;
my $time = shift;
my $drops = shift;
my $ver = shift;
#-------------------------------------------------------------------
#
# ICMP
#
### Unpack ICMP data
($icmp_type,$icmp_code,$icmp_cksum,$icmp_rest) =
unpack('CCna*',$ip_data);
#
# --- Store ICMP data in memory ---
#
### Store Fields
$ICMP{time}{$time}{type} = $icmp_type;
$ICMP{time}{$time}{code} = $icmp_code;
$ICMP{time}{$time}{src} = $ip_src;
$ICMP{time}{$time}{dest} = $ip_dest;
$ICMP{time}{$time}{ver} = $ver;
#
# Flag this session as a Partial if either tcpdump or snoop
# confesses to dropping packets.
#
$ICMP{time}{$time}{Partial}++ if $drops;
#
# Save data if ICMP echo/reply
#
if (($icmp_type == 0) || ($icmp_type == 8) ||
($icmp_type == 128) || ($icmp_type == 129) || 1) {
### Unpack some more
($icmp_type,$icmp_code,$icmp_cksum,$icmp_id,$icmp_seq,
$icmp_data) = unpack('CCnnna*',$ip_data);
### Save extra fields
$ICMP{time}{$time}{id} = $icmp_id;
$ICMP{time}{$time}{seq} = $icmp_seq;
$ICMP{time}{$time}{data} = $icmp_data;
}
### Store size
$ICMP{time}{$time}{size} += length($icmp_data);
if ($icmp_data ne "") {
#
# Populate %ICMP{time}{}{BothHTML} with coloured HTML
# 1-way traffic, blue
#
$copy = &Desex_HTML($icmp_data);
$ICMP{time}{$time}{BothHTML} .=
"$copy";
}
#
# Populate %Hex{ICMP}{} with coloured HTML
# traffic, if needed.
#
if ($Arg{output_hex}) {
&Process_Hex("ICMP",$time,$icmp_data,"blue");
}
### Print status line
printf "%6s %-45s %s\n",$packet,"$ip_src,$ip_dest",$length
if $Arg{debug};
}
# Process_TCP_Sessions - this subroutine processes %TCP, saving the
# sessions to various "session*" files on disk. It populates %Index
# with information on files that it has created. It also checks
# the application port numbers and triggers further processing -
# eg telnet replay files. Min/Max size checks are also done here.
#
sub Process_TCP_Sessions {
my ($filename,$id_text,$id_html,$rawboth,$time,$raw);
my @Time;
$Bench{++$BM}{mark} = new Benchmark if $Arg{bench};
$Bench{$BM}{text} = "Process TCP Sessions - start";
#
# Loop through all TCP sessions
#
foreach $session_id (keys %{$TCP{id}}) {
$number = $Index{Sort_Lookup}{"TCP:$session_id"};
#
# Determine the service - usually by the lowest numbered port, eg,
# ports 51321 and 23 would give 23 (telnet).
#
$ip_src = $TCP{id}{$session_id}{src};
$ip_dest = $TCP{id}{$session_id}{dest};
$tcp_src_port = $TCP{id}{$session_id}{src_port};
$tcp_dest_port = $TCP{id}{$session_id}{dest_port};
($service,$client) = &Pick_Service_Port("TCP",$session_id,
$tcp_src_port,$tcp_dest_port);
### Fetch text name for this port
$service_name = $Services_TCP{$service} || $service || "0";
#
# Don't actually save any files if CLI args say not to
#
if ($Arg{port_reject} && $Arg{Port_Rejected}{$service}) { next; }
if ($Arg{port_accept} && !$Arg{Port_Accepted}{$service}) { next; }
if ($Arg{ip_reject}) {
if ($Arg{IP_Rejected}{$ip_src} || $Arg{IP_Rejected}{$ip_dest}) {
next;
}
}
if ($Arg{ip_accept}) {
unless ($Arg{IP_Accepted}{$ip_src} ||
$Arg{IP_Accepted}{$ip_dest}) {
next;
}
}
#
# --- Fetch RawBoth ---
#
# rawboth will contain the raw data in time order.
$rawboth = "";
foreach $time (sort {$a <=> $b}
(keys (%{$TCP{id}{$session_id}{time}}))) {
$rawboth .= $TCP{id}{$session_id}{time}{$time}{data};
}
$length = length($rawboth);
#
# --- Check for Min and Max size ---
#
next if $length < $Arg{minbytes};
next if (($Arg{maxbytes} != 0) && ($length > $Arg{maxbytes}));
### Print status line
$numtext = sprintf("%04d",$number);
printf "%6s %-45s %s\n",$numtext,$session_id,$service_name
unless $Arg{quiet};
#
# --- Save Info File to Disk ---
#
if ($Arg{output_info}) {
$filename = "session_${numtext}.info";
$firsttime = localtime($TCP{id}{$session_id}{StartTime});
$lasttime = localtime($TCP{id}{$session_id}{EndTime});
$duration = ($TCP{id}{$session_id}{EndTime} -
$TCP{id}{$session_id}{StartTime});
$duration = sprintf("%.0f",$duration);
if ($TCP{id}{$session_id}{Partial}) { $partial = "yes"; }
else { $partial = "no"; }
### Build output text
$outtext = "$numtext===$session_id===$service===" .
"$service_name===$length\n\n" .
"Source addr : $ip_src\n" .
"Source port : $tcp_src_port\n" .
"Dest addr : $ip_dest\n" .
"Dest port : $tcp_dest_port\n" .
"Dest service: $service_name\n" .
"Length bytes: $length\n" .
"First time : $firsttime\n" .
"Last time : $lasttime\n" .
"Duration : $duration seconds\n" .
"Partial : $partial\n";
### Write info file
open (OUT,">$filename") ||
die "ERROR11: creating $filename $!\n";
print OUT $outtext;
close OUT;
}
#
# --- Save Index data to Memory ---
#
## Fetch times
$starttime = scalar localtime($TCP{id}{$session_id}{StartTime});
$duration = ($TCP{id}{$session_id}{EndTime} -
$TCP{id}{$session_id}{StartTime});
$duration = sprintf("%.0f",$duration);
### Generate session strings
($id_text,$id_html) = &Generate_TCP_IDs($session_id);
### Construct HTML table row containing session data
$Index{HTML}[$number] = "
$number. | " .
"$starttime | $duration s | " .
"$id_html " .
" | " .
"$service_name | " .
"$length bytes | \n";
### Construct text line containing session data
$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n",$number,
$id_text,"($service_name)",$length);
### Construct image info line (in case it is needed)
$Image{HTML}[$number]{info} = " |
$number." .
" | $starttime | " .
"$id_html | | \n";
### Construct GETPOST info line (in case it is needed)
# starttime and host:port... are formatted differently so that
# they are narrow and leave more room for the sub table.
$GETPOST{HTML}[$number]{info} = " |
$number." .
" | $starttime | " .
"$id_html | | \n";
#
# --- Save Raw Sessions to Disk ---
#
if ($Arg{output_raw}) {
#
# Save ".raw" file, all raw 2-way data time-sorted.
#
$filename = "session_${numtext}.${service_name}.raw";
open (OUT,">$filename") ||
die "ERROR12: creating $filename $!\n";
binmode(OUT); # for backward OSs
print OUT $rawboth;
close OUT;
### Update HTML index table with link
$Index{HTML}[$number] .= "raw ";
#
# Save ".raw1" file, server->client 1-way data assembled.
#
$filename = "session_${numtext}.${service_name}.raw1";
open (OUT,">$filename") ||
die "ERROR13: creating $filename $!\n";
binmode(OUT); # for backward OSs
print OUT &TCP_Follow_RawA($session_id);
close OUT;
### Update HTML index table with link
$Index{HTML}[$number] .= "raw1 ";
#
# Save ".raw2" file, client->server 1-way data assembled.
#
$filename = "session_${numtext}.${service_name}.raw2";
open (OUT,">$filename") ||
die "ERROR14: creating $filename $!\n";
binmode(OUT); # for backward OSs
print OUT &TCP_Follow_RawB($session_id);
close OUT;
### Update HTML index table with link
$Index{HTML}[$number] .= "raw2 ";
}
next unless $Arg{output_apps};
#
# --- Save Session as HTML ---
#
if ($Arg{Save_As_TCP_HTML}{$service} || $Arg{output_allhtml}) {
&Save_Both_HTML("TCP",$session_id,$number,$service_name,
$id_html);
}
#
# --- Save X11 Session as HTML ---
#
if ($Arg{Save_As_X11_HTML}{$service}) {
#
# HTML Postprocessing can go here
#
&Generate_X11_HTML($session_id);
&Process_BothHTML("TCP",$session_id,1);
&Save_Both_HTML("TCP",$session_id,$number,"text$service_name",
$id_html);
}
#
# --- Save Hex Dump as HTML ---
#
if ($Arg{output_hex}) {
&Process_Hex_Finish("TCP",$session_id);
&Save_Hex_HTML("TCP",$session_id,$number,$service_name,
$id_html);
&Save_Hex_Text("TCP",$session_id,$number,$service_name,
$id_text);
}
#
# --- Process Application Data ---
#
if ($service == 20) {
&Save_FTP_File($session_id,$number);
}
if ($service == 22) {
&Save_Session_textSSH_files($session_id,$number,
"SSH",$id_html);
}
if ($Arg{keydata} && $Arg{Save_As_TCP_Playback}{$service}) {
# The following is for special analysis,
&Save_Session_Keydata($session_id,$number,
$service_name,$id_html);
}
if ($service == 25) {
&Save_SMTP_Emails($session_id,$number);
}
if ($service == 80 or $service == 8080 or
$service == 3127 or $service == 1080) {
&Save_HTTP_Files($session_id,$number,$service_name);
&Process_HTTP($session_id);
}
if ($Arg{Save_As_X11_Playback}{$service}) {
&Save_Session_XReplay($session_id,$number,$service_name);
}
if ($Arg{Save_As_VNC_Playback}{$service}) {
&Save_Session_VNCReplay_andHTML($session_id,$number,
$service_name,$id_html);
}
$raw = &TCP_Follow_RawB($session_id);
if ($raw =~ /^\200\0\0p0\211/) {
&Save_NFS_File($session_id,$number);
}
if ($Arg{Save_As_TCP_Playback}{$service}) {
&Save_Session_Replay($session_id,$number,$service_name);
}
}
$Bench{++$BM}{mark} = new Benchmark if $Arg{bench};
$Bench{$BM}{text} = "Process TCP Sessions - end";
}
# Process_UDP_Streams - this subroutine processes %UDP, saving the
# sessions to various "session*" files on disk. It populates %Index
# with information on the files that were created. It also checks
# the application port numbers and triggers further processing -
# eg DNS html output files.
#
sub Process_UDP_Streams {
my ($filename,$id_html,$id_text,$time,$rawboth);
$Bench{++$BM}{mark} = new Benchmark if $Arg{bench};
$Bench{$BM}{text} = "Process UDP Sessions - start";
#
# Loop through all UDP Streams
#
foreach $session_id (keys %{$UDP{id}}) {
$number = $Index{Sort_Lookup}{"UDP:$session_id"};
#
# Determine the service - usually by the lowest numbered port, eg,
# ports 51327 and 53 would give 53 (dns). (big assumption!)
#
$ip_src = $UDP{id}{$session_id}{src};
$ip_dest = $UDP{id}{$session_id}{dest};
$udp_src_port = $UDP{id}{$session_id}{src_port};
$udp_dest_port = $UDP{id}{$session_id}{dest_port};
($service,$client) = &Pick_Service_Port("UDP",$session_id,
$udp_src_port,$udp_dest_port);
### Fetch text name for this port
$service_name = $Services_UDP{$service} || $service || "0";
#
# Don't actually save any files if CLI args say not to
#
if ($Arg{port_reject} && $Arg{Port_Rejected}{$service}) { next; }
if ($Arg{port_accept} && !$Arg{Port_Accepted}{$service}) { next; }
if ($Arg{ip_reject}) {
if ($Arg{IP_Rejected}{$ip_src} || $Arg{IP_Rejected}{$ip_dest}) {
next;
}
}
if ($Arg{ip_accept}) {
unless ($Arg{IP_Accepted}{$ip_src} ||
$Arg{IP_Accepted}{$ip_dest}) {
next;
}
}
#
# --- Fetch RawBoth ---
#
# rawboth will contain the raw data in time order.
$rawboth = "";
foreach $time (sort {$a <=> $b}
(keys (%{$UDP{id}{$session_id}{time}}))) {
$rawboth .= $UDP{id}{$session_id}{time}{$time};
}
$length = length($rawboth);
#
# --- Check for Min and Max Size ---
#
next if $length < $Arg{minbytes};
next if (($Arg{maxbytes} != 0) && ($length > $Arg{maxbytes}));
### Print status line
$numtext = sprintf("%04d",$number);
printf "%6s %-45s %s\n",$numtext,$session_id,$service_name
unless $Arg{quiet};
#
# --- Save Info File to Disk ---
#
if ($Arg{output_info}) {
$filename = "stream_${numtext}.info";
$firsttime = localtime($UDP{id}{$session_id}{StartTime});
$lasttime = localtime($UDP{id}{$session_id}{EndTime});
$duration = ($UDP{id}{$session_id}{EndTime} -
$UDP{id}{$session_id}{StartTime});
$duration = sprintf("%.0f",$duration);
if ($UDP{id}{$session_id}{Partial}) { $partial = "yes"; }
else { $partial = "no"; }
### Build output text
$outtext = "$numtext===$session_id===$service===" .
"$service_name===$length\n\n" .
"Source addr : $ip_src\n" .
"Source port : $udp_src_port\n" .
"Dest addr : $ip_dest\n" .
"Dest port : $udp_dest_port\n" .
"Dest service: $service_name\n" .
"Length bytes: $length\n" .
"First time : $firsttime\n" .
"Last time : $lasttime\n" .
"Duration : $duration seconds\n" .
"Partial : $partial\n";
### Write info file
open (OUT,">$filename") ||
die "ERROR15: creating $filename $!\n";
print OUT $outtext;
close OUT;
}
#
# --- Save Index data in Memory ---
#
### Fetch Times
$starttime = scalar localtime($UDP{id}{$session_id}{StartTime});
$duration = ($UDP{id}{$session_id}{EndTime} -
$UDP{id}{$session_id}{StartTime});
$duration = sprintf("%.0f",$duration);
### Construct HTML table row containing stream data
$id_html = "$ip_src:$udp_src_port <-> $ip_dest:$udp_dest_port";
$Index{HTML}[$number] = " |
$number. | " .
"$starttime | $duration s | " .
"$id_html " .
" | " .
"$service_name | " .
"$length bytes | \n";
### Construct text line containing session data
$id_text = "$ip_src:$udp_src_port <-> $ip_dest:$udp_dest_port";
$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n",$number,
$id_text,"($service_name)",$length);
#
# --- Save Raw Stream to Disk ---
#
if ($Arg{output_raw}) {
#
# Save ".raw" file, all raw 2-way data time-sorted.
#
$filename = "stream_${numtext}.${service_name}.raw";
open (OUT,">$filename") ||
die "ERROR16: creating $filename $!\n";
binmode(OUT); # for backward OSs
print OUT $rawboth;
close OUT;
### Update HTML index table with link
$Index{HTML}[$number] .= "raw ";
#
# Save ".raw1" file, server->client 1-way data time-sorted.
#
$filename = "stream_${numtext}.${service_name}.raw1";
open (OUT,">$filename") ||
die "ERROR17: creating $filename $!\n";
binmode(OUT); # for backward OSs
print OUT $UDP{id}{$session_id}{RawA};
close OUT;
### Update HTML index table with link
$Index{HTML}[$number] .= "raw1 ";
#
# Save ".raw2" file, client->server 1-way data time-sorted.
#
$filename = "stream_${numtext}.${service_name}.raw2";
open (OUT,">$filename") ||
die "ERROR18: creating $filename $!\n";
binmode(OUT); # for backward OSs
print OUT $UDP{id}{$session_id}{RawB};
close OUT;
### Update HTML index table with link
$Index{HTML}[$number] .= "raw2 ";
}
next unless $Arg{output_apps};
#
# --- Save Stream as HTML ---
#
if ($Arg{Save_As_UDP_HTML}{$service} || $Arg{output_allhtml}) {
#
# HTML Postprocessing can go here
#
&Process_BothHTML("UDP",$session_id);
&Save_Both_HTML("UDP",$session_id,$number,$service_name);
}
#
# --- Save Hex Dump as HTML ---
#
if ($Arg{output_hex}) {
&Process_Hex_Finish("UDP",$session_id);
&Save_Hex_HTML("UDP",$session_id,$number,$service_name,
$id_html);
&Save_Hex_Text("UDP",$session_id,$number,$service_name,
$id_text);
}
#
# --- Process Application Data ---
#
if ($Arg{Save_As_UDP_Playback}{$service}) {
&Save_Stream_Replay($session_id,$number,$service_name);
}
}
$Bench{++$BM}{mark} = new Benchmark if $Arg{bench};
$Bench{$BM}{text} = "Process UDP Sessions - end";
}
# Process_ICMP - this subroutine processes %ICMP.
#
sub Process_ICMP {
my ($filename,$id_text,$id_html);
$Bench{++$BM}{mark} = new Benchmark if $Arg{bench};
$Bench{$BM}{text} = "Process ICMP Sessions - start";
#
# Loop through all ICMP Streams
#
foreach $time (keys %{$ICMP{time}}) {
$number = $Index{Sort_Lookup}{"ICMP:$time"};
### Fetch Data
$icmp_type = $ICMP{time}{$time}{type};
$icmp_code = $ICMP{time}{$time}{code};
$icmp_ver = $ICMP{time}{$time}{ver};
$ip_src = $ICMP{time}{$time}{src};
$ip_dest = $ICMP{time}{$time}{dest};
$session_id = "$ip_src,$ip_dest";
### Fetch text name for this port
$type_name = $ICMP_Types{$icmp_type} || $icmp_type || "0";
$service_name = $icmp_type;
#
# Don't actually save any files if CLI args say not to
#
if ($Arg{ip_reject}) {
if ($Arg{IP_Rejected}{$ip_src} || $Arg{IP_Rejected}{$ip_dest}){
next;
}
}
if ($Arg{ip_accept}) {
unless ($Arg{IP_Accepted}{$ip_src} ||
$Arg{IP_Accepted}{$ip_dest}) {
next;
}
}
#
# --- Check for Min and Max Size ---
#
$length = length($ICMP{time}{$time}{data});
next if $length < $Arg{minbytes};
next if (($Arg{maxbytes} != 0) && ($length > $Arg{maxbytes}));
### Print status line
$numtext = sprintf("%04d",$number);
printf "%6s %-45s ICMP %s\n",$numtext,$session_id,$type_name
unless $Arg{quiet};
#
# --- Save Info File to Disk ---
#
if (($Arg{output_info}) && ($length > 0)) {
$filename = "icmp_${numtext}.${service_name}.info";
if ($ICMP{time}{$time}{Partial}) { $partial = "yes"; }
else { $partial = "no"; }
$starttime = scalar localtime($time);
### Build output text
$outtext = "$numtext===$session_id===$icmp_type===" .
"$type_name===$length\n\n" .
"Source addr : $ip_src\n" .
"Dest addr : $ip_dest\n" .
"ICMP version: $icmp_ver\n" .
"ICMP type : $icmp_type\n" .
"ICMP code : $icmp_code\n" .
"ICMP name : $type_name\n" .
"Length bytes: $length\n" .
"Time : $starttime\n" .
"Partial : $partial\n";
### Write info file
open (OUT,">$filename") ||
die "ERROR19: creating $filename $!\n";
print OUT $outtext;
close OUT;
}
#
# --- Save Index data in Memory ---
#
### Fetch Times
$starttime = scalar localtime($time);
### Construct HTML table row containing stream data
$id_html = "$ip_src -> $ip_dest";
$Index{HTML}[$number] = " |
$number. | " .
"$starttime | 0 s | " .
"$id_html" .
" | " .
"$icmp_ver | " .
"$length bytes | $type_name\n";
### Construct text line containing session data
$id_text = "$ip_src -> $ip_dest";
$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n",$number,
$id_text, "($icmp_ver $type_name)",$length);
#
# --- Save Raw Stream to Disk ---
#
if (($Arg{output_raw}) && ($length > 0)) {
#
# Save ".raw" file, all raw 2-way data time-sorted.
#
$filename = "icmp_${numtext}.${service_name}.raw";
open (OUT,">$filename") ||
die "ERROR20: creating $filename $!\n";
binmode(OUT); # for backward OSs
print OUT $ICMP{time}{$time}{data};
close OUT;
### Update HTML index table with link
$Index{HTML}[$number] .= "raw ";
}
#
# --- Save Stream as HTML ---
#
if ($Arg{output_allhtml}) {
#
# HTML Postprocessing can go here
#
&Process_BothHTML("ICMP",$time);
&Save_Both_HTML("ICMP",$time,$number,$service_name,$id_html);
}
#
# --- Save Hex Dump as HTML ---
#
if ($Arg{output_hex}) {
&Process_Hex_Finish("ICMP",$time);
&Save_Hex_HTML("ICMP",$time,$number,$service_name,$id_html);
&Save_Hex_Text("ICMP",$time,$number,$service_name,$id_text);
}
}
$Bench{++$BM}{mark} = new Benchmark if $Arg{bench};
$Bench{$BM}{text} = "Process ICMP Sessions - end";
}
# Process_HTTP - HTTP processing. Looks for GETs and POSTs, and process them
# into %GETPOST. Constructs a HTTP log in %HTTPlog.
#
sub Process_HTTP {
my ($junk,$var,$value,$term,$data,$request,$site,$post,$get,$reply);
my ($start,$src,$num,$req,$recv,$type,$status,$time1,$duration,$dest);
my @Terms;
my $index = 0;
my $indexA = 0;
my $indexB = 0;
### Input
my $session_id = shift;
$src = $TCP{id}{$session_id}{src};
$dest = $TCP{id}{$session_id}{dest};
#
# Process
#
### Get packet times (may need to use seqs instead)
@Times = sort{$a <=> $b} (keys(%{$TCP{id}{$session_id}{time}}));
### Step through each packet
for ($i=0; $i <= $#Times; $i++) {
### Fetch data from mem
$time = $Times[$i];
$request = $TCP{id}{$session_id}{time}{$time}{data};
$request =~ s/^\0\0*//;
#
# --- Do HTTPlog Processing ---
#
next unless $request =~ /^(GET|POST)\s/; # speed
### Calc duration
$time1 = $Times[$i+1] || $time;
$duration = $time1 - $time;
# some magic
$reply = "";
foreach $inc (1..16) {
$next = $TCP{id}{$session_id}{time}{$Times[$i+$inc]}{data};
$next =~ s/^\0\0*//;
if ($next =~ /^U*\0*HTTP/) {
$reply = $next;
$time1 = $Times[$i+$inc] || $time;
$duration = $time1 - $time;
last;
} else {
$request .= $next;
}
}
$i++; # speed
if ($request =~ /^GET \S* HTTP/) {
### Get the site string
($site) = $request =~ /^GET (\S*)\s/;
if ($site =~ m:^/:) {
# assume this was a http, missing the "http://host"
$site = "http://${dest}$site";
}
### Get the status and mime type from reply
($status) = $reply =~ /HTTP\/\S*\s(\S*)/s;
($type) = $reply =~ /Content-Type:\s(\S*)/s;
($size) = $reply =~ /Content-Length:\s(\S*)/s;
$type = "-" if $type eq "";
$size = 0 if $size eq "";
$result = $Result_Names{$status} || "TCP_HIT";
### Store the log entry
$HTTPlog{time}{$time} =
sprintf("%9d.%03d %6d %s %s/%03d %d %s %s %s %s%s/%s %s\n",
int($time),(($time - int($time))*1000),($duration*1000),
$src,$result,$status,$size,"GET",$site,"-","NONE","",
"-",$type);
$HTTPlog{notempty} = 1;
} elsif ($request =~ /^POST .* HTTP/) {
### Get the site string
($site) = $request =~ /^POST (\S*)\s/;
if ($site =~ m:^/:) {
# assume this was a http, missing the "http://host"
$site = "http://${dest}$site";
}
### Get the status and mime type
($status) = $reply =~ /HTTP\/\S*\s(\S*)/s;
($type) = $reply =~ /Content-Type:\s(\S*)/s;
($size) = $reply =~ /Content-Length:\s(\S*)/s;
$type = "-" if $type eq "";
$size = length($TCP{id}{$session_id}) if $size eq "";
$result = $Result_Names{$status} || "TCP_HIT";
### Store the log entry
$HTTPlog{time}{$time} =
sprintf("%9d.%03d %6d %s %s/%03d %d %s %s %s %s%s/%s %s\n",
int($time),(($time - int($time))*1000),($duration*1000),
$src,$result,$status,$size,"POST",$site,"-","NONE","",
"-",$type);
$HTTPlog{notempty} = 1;
}
#
# --- Do GETPOST Processing ---
#
if ($request =~ /^GET \S*\?\S* HTTP/) {
### Get the GET string
($site,$get) = $request =~ /^GET (\S*)\?(\S*)\s/;
# check it looks like a GET,
if ($get =~ /=/) {
#
# Populate %GETPOST with a table containing the GET data
#
if (! defined $GETPOST{HTML}[$number]{query}) {
$GETPOST{HTML}[$number]{info} .=
"GET | ";
$GETPOST{notempty} = 1;
} else {
$GETPOST{HTML}[$number]{query} .= " \n";
}
#
# Generate table of query key value pairs
#
$GETPOST{HTML}[$number]{query} .= "$site
\n";
@Terms = split(/&/,$get);
foreach $term (@Terms) {
($var,$value) = split(/=/,$term);
$value =~ tr/+/ /;
$value =~ s/%([a-f0-9][a-f0-9])/pack("C",hex($1))/egi;
$value =~ s/</g;
$value =~ s/>/>/g;
$value =~ s/\n/ \n/g;
$GETPOST{HTML}[$number]{query} .=
"$var | " .
"$value | \n";
}
$GETPOST{HTML}[$number]{query} .= " \n";
}
} elsif ($request =~ /^POST .* HTTP/) {
### Get the POST strings
($junk,$post,$junk1) = split(/\n\n|\r\n\r\n/,$request);
# check it looks like a POST
if ($post =~ /=/) {
#
# Populate %GETPOST with a table containing the POST data
#
if (! defined $GETPOST{HTML}[$number]{query}) {
$GETPOST{HTML}[$number]{info} .=
"POST | ";
$GETPOST{notempty} = 1;
} else {
$GETPOST{HTML}[$number]{query} .= " \n";
}
($site) = $request =~ /^POST (\S*)\s/;
$post =~ s/HTTP .*//s;
#
# Generate table of query key value pairs
#
$GETPOST{HTML}[$number]{query} .= "$site
\n";
@Terms = split(/&/,$post);
foreach $term (@Terms) {
($var,$value) = split(/=/,$term);
$value =~ tr/+/ /;
$value =~
s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$value =~ s/</g;
$value =~ s/>/>/g;
$value =~ s/\n/ /g;
$GETPOST{HTML}[$number]{query} .=
"$var | " .
"$value | \n";
}
$GETPOST{HTML}[$number]{query} .= " \n";
}
}
}
}
# Sort_Index - this creates a sort order for the master index.html, based
# on the sort argument (defaults to sort by time).
#
sub Sort_Index {
if ($Arg{sort} eq "size") {
&Sort_Index_By_Size();
} elsif ($Arg{sort} eq "type") {
&Sort_Index_By_Type();
} elsif ($Arg{sort} eq "ip") {
&Sort_Index_By_IP();
} else {
&Sort_Index_By_Time();
}
}
# Sort_Index_By_Time - this calculates an appropriate order for the index
# files based on session start time.
#
sub Sort_Index_By_Time {
my ($session_id,$time,$number);
#
# Determine Session and Stream time order
#
foreach $session_id (keys %{$TCP{id}}) {
$Index{Time_Order}{"TCP:$session_id"} =
$TCP{id}{$session_id}{StartTime};
}
foreach $session_id (keys %{$UDP{id}}) {
$Index{Time_Order}{"UDP:$session_id"} =
$UDP{id}{$session_id}{StartTime};
}
foreach $time (keys %{$ICMP{time}}) {
$Index{Time_Order}{"ICMP:$time"} = $time;
}
$number = 0;
foreach $session (sort {$Index{Time_Order}{$a} <=>
$Index{Time_Order}{$b}} keys %{$Index{Time_Order}}) {
$number++;
$Index{Sort_Lookup}{$session} = $number;
}
}
# Sort_Index_By_Size - this calculates an appropriate order for the index
# files based on session size.
#
sub Sort_Index_By_Size {
my ($session_id,$time,$number);
#
# Determine Session and Stream size order
#
foreach $session_id (keys %{$TCP{id}}) {
$Index{Size_Order}{"TCP:$session_id"} =
$TCP{id}{$session_id}{size};
}
foreach $session_id (keys %{$UDP{id}}) {
$Index{Size_Order}{"UDP:$session_id"} =
$UDP{id}{$session_id}{size};
}
foreach $time (keys %{$ICMP{time}}) {
$Index{Size_Order}{"ICMP:$time"} =
$ICMP{time}{$time}{size};
}
$number = 0;
foreach $session (sort {$Index{Size_Order}{$b} <=>
$Index{Size_Order}{$a}} keys %{$Index{Size_Order}}) {
$number++;
$Index{Sort_Lookup}{$session} = $number;
}
}
# Sort_Index_By_Type - this calculates an appropriate order for the index
# files based on session type, followed by time.
#
sub Sort_Index_By_Type {
my ($service,$tcp_src_port,$tcp_dest_port,$client,$udp_src_port,
$udp_dest_port,$session_id,$time,$number);
#
# Determine Session and Stream time order
#
foreach $session_id (keys %{$TCP{id}}) {
# Determine the service - usually by the lowest numbered port
$tcp_src_port = $TCP{id}{$session_id}{src_port};
$tcp_dest_port = $TCP{id}{$session_id}{dest_port};
($service,$client) = &Pick_Service_Port("TCP",$session_id,
$tcp_src_port,$tcp_dest_port);
$Index{Type_Order}{"TCP:$session_id"}{1} = 1;
$Index{Type_Order}{"TCP:$session_id"}{2} = $service;
$Index{Type_Order}{"TCP:$session_id"}{3} =
$TCP{id}{$session_id}{StartTime};
}
foreach $session_id (keys %{$UDP{id}}) {
# Determine the service - usually by the lowest numbered port
$udp_src_port = $UDP{id}{$session_id}{src_port};
$udp_dest_port = $UDP{id}{$session_id}{dest_port};
($service,$client) = &Pick_Service_Port("UDP",$session_id,
$udp_src_port,$udp_dest_port);
$Index{Type_Order}{"UDP:$session_id"}{1} = 2;
$Index{Type_Order}{"UDP:$session_id"}{2} = $service;
$Index{Type_Order}{"UDP:$session_id"}{3} =
$UDP{id}{$session_id}{StartTime};
}
foreach $time (keys %{$ICMP{time}}) {
$Index{Type_Order}{"ICMP:$time"}{1} = 3;
$Index{Type_Order}{"ICMP:$time"}{2} = 0;
$Index{Type_Order}{"ICMP:$time"}{3} = $time;
}
# now we sort by TCP->UDP->IP then port then time.
$number = 0;
foreach $session (sort {
$Index{Type_Order}{$a}{1} <=> $Index{Type_Order}{$b}{1} ||
$Index{Type_Order}{$a}{2} <=> $Index{Type_Order}{$b}{2} ||
$Index{Type_Order}{$a}{3} <=> $Index{Type_Order}{$b}{3}
} keys %{$Index{Type_Order}}) {
$number++;
$Index{Sort_Lookup}{$session} = $number;
}
}
# Sort_Index_By_IP - this calculates an appropriate order for the index
# files based on client IP, followed by time.
#
sub Sort_Index_By_IP {
my ($service,$ip,$ip_dest,$ip_src,$client,
$session_id,$time,$number,$text,$html,$rest);
my @IP;
#
# Determine Session and Stream time order
#
foreach $session_id (keys %{$TCP{id}}) {
# Determine source IP
# here we use the same subroutine as the index.html
# so that they match up.
($text,$html) = &Generate_TCP_IDs($session_id);
($ip,$rest) = split(/:/,$text,2);
# Split on IPv4 or IPv6
$IP = ();
if ($ip =~ /\./) { @IP = split(/\./,$ip); }
else { $IP[0] = $ip; }
$Index{Type_Order}{"TCP:$session_id"}{1} = $IP[0];
$Index{Type_Order}{"TCP:$session_id"}{2} = $IP[1];
$Index{Type_Order}{"TCP:$session_id"}{3} = $IP[2];
$Index{Type_Order}{"TCP:$session_id"}{4} = $IP[3];
$Index{Type_Order}{"TCP:$session_id"}{5} =
$TCP{id}{$session_id}{StartTime};
}
foreach $session_id (keys %{$UDP{id}}) {
# Determine source IP
$ip = $UDP{id}{$session_id}{src};
# Split on IPv4 or IPv6
$IP = ();
if ($ip =~ /\./) { @IP = split(/\./,$ip); }
else { $IP[0] = $ip; }
$Index{Type_Order}{"UDP:$session_id"}{1} = $IP[0];
$Index{Type_Order}{"UDP:$session_id"}{2} = $IP[1];
$Index{Type_Order}{"UDP:$session_id"}{3} = $IP[2];
$Index{Type_Order}{"UDP:$session_id"}{4} = $IP[3];
$Index{Type_Order}{"UDP:$session_id"}{5} =
$UDP{id}{$session_id}{StartTime};
}
foreach $time (keys %{$ICMP{time}}) {
# Determine source IP
$ip = $ICMP{time}{$time}{src};
# Split on IPv4 or IPv6
$IP = ();
if ($ip =~ /\./) { @IP = split(/\./,$ip); }
else { $IP[0] = $ip; }
$Index{Type_Order}{"ICMP:$time"}{1} = $IP[0];
$Index{Type_Order}{"ICMP:$time"}{2} = $IP[1];
$Index{Type_Order}{"ICMP:$time"}{3} = $IP[2];
$Index{Type_Order}{"ICMP:$time"}{4} = $IP[3];
$Index{Type_Order}{"ICMP:$time"}{5} = $time;
}
# now we sort by IP then time
$number = 0;
foreach $session (sort {
$Index{Type_Order}{$a}{1} <=> $Index{Type_Order}{$b}{1} ||
$Index{Type_Order}{$a}{2} <=> $Index{Type_Order}{$b}{2} ||
$Index{Type_Order}{$a}{3} <=> $Index{Type_Order}{$b}{3} ||
$Index{Type_Order}{$a}{4} <=> $Index{Type_Order}{$b}{4} ||
$Index{Type_Order}{$a}{1} cmp $Index{Type_Order}{$b}{1} ||
$Index{Type_Order}{$a}{5} <=> $Index{Type_Order}{$b}{5}
} keys %{$Index{Type_Order}}) {
$number++;
$Index{Sort_Lookup}{$session} = $number;
}
}
# Print_Welcome - print short program welcome message.
#
sub Print_Welcome {
unless ($Arg{quiet}) {
print "Chaosreader ver 0.94\n\n";
}
}
# Print_Header1 - print program welcome message.
#
sub Print_Header1 {
unless ($Arg{quiet}) {
print "Reading $TYPE log...\n";
printf "%6s %-45s %s\n","Packet",
"Session (host:port <=> host:port)","Length";
}
}
# Print_Header2 - print header before loading the file
#
sub Print_Header2 {
print "\nCreating files...\n" unless $Arg{quiet};
printf "%6s %-45s %s\n","Num","Session (host:port <=> host:port)",
"Service" unless $Arg{quiet};
}
# Print_Footer1 - print footer at end of program.
#
sub Print_Footer1 {
if ($Arg{output_index}) {
print "\nindex.html created.\n" unless $Arg{quiet};
}
}
# Chdir - change directory with error
#
sub Chdir {
my $dir = shift;
#
# This can be invoked with $Arg{output_dir}, so $dir won't
# always be defined - which is okay.
#
if (defined $dir) {
chdir "$dir" ||
die "ERROR21: Can't cd to $dir: $!\n";
}
}
# Create_Index_Files - Create the HTML and text index files. This reads
# %Index and creates the files on disk.
#
sub Create_Index_Files {
my ($html_index,$html_line,$html_links,$image_empty,$getpost_empty);
$getpost_empty = $image_empty = "";
if ($Arg{output_index}) {
######################
# --- index.html ---
$image_empty = "(Empty) " unless $Image{notempty};
$getpost_empty = "(Empty) " unless $GETPOST{notempty};
$httplog_empty = "(Empty) " unless $HTTPlog{notempty};
#
# Create HTML Index file containing all reports
#
open(FILE,">index.html") || die "ERROR22: creating index: $!\n";
print FILE <
Chaosreader Report, $Arg{infile}
Chaosreader Report
File: $Arg{infile}, Type: $TYPE, Created at: $the_date
Image Report
$image_empty - Click here for a report on captured images.
GET/POST Report
$getpost_empty - Click here for a report on HTTP GETs and POSTs.
HTTP Proxy Log
$httplog_empty - Click here for a generated proxy style HTTP log.
TCP/UDP/... Sessions
END_HTML
for ($html_index=0; $html_index <= $#{$Index{HTML}}; $html_index++) {
$html_line = $Index{HTML}[$html_index];
next unless defined $html_line;
print FILE "$html_line \n";
}
print FILE <
IP Count
END_HTML
foreach $IP (sort {$Count{IP}{$b} <=> $Count{IP}{$a}}
keys %{$Count{IP}}) {
print FILE "$IP | $Count{IP}{$IP} | \n";
}
print FILE <
TCP Port Count
END_HTML
foreach $port (sort {$Count{TCPport}{$b} <=> $Count{TCPport}{$a}}
keys %{$Count{TCPport}}) {
$port_text = $Services_TCP{$port} || $port || "0";
print FILE "$port_text | $Count{TCPport}{$port}" .
" | \n";
}
print FILE <
UDP Port Count
END_HTML
foreach $port (sort {$Count{UDPport}{$b} <=> $Count{UDPport}{$a}}
keys %{$Count{UDPport}}) {
$port_text = $Services_UDP{$port} || $port || "0";
print FILE "$port_text | $Count{UDPport}{$port}" .
" | \n";
}
print FILE <
IP Protocol Count
END_HTML
foreach $protocol (sort {$Count{IPprotocol}{$b} <=>
$Count{IPprotocol}{$a}} keys %{$Count{IPprotocol}}) {
$protocol_text = $IP_Protocols{$protocol};
print FILE "$protocol_text | " .
"$Count{IPprotocol}{$protocol} | \n";
}
print FILE <
Ethernet Type Count
END_HTML
foreach $type (sort {$Count{EtherType}{$b} <=> $Count{EtherType}{$a}}
keys %{$Count{EtherType}}) {
print FILE "$type | $Count{EtherType}{$type}" .
" | \n";
}
print FILE <
|