From 4208aa43ab8610ebd87e6d4a357494d41234216b Mon Sep 17 00:00:00 2001 From: Automatic Build System Date: Fri, 5 Jan 2024 21:11:55 +0100 Subject: [PATCH] automatic rebuild by autodist [release 0.94-2mamba;Thu Sep 05 2013] --- README.md | 5 + chaosreader.spec | 45 + chaosreader0.94 | 6832 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 6882 insertions(+) create mode 100644 chaosreader.spec create mode 100644 chaosreader0.94 diff --git a/README.md b/README.md index b840dcd..5f6195a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,7 @@ # chaosreader +Chaosreader is an open source tool to trace TCP/UDP/... sessions and fetch application data from snoop or tcpdump logs. +This is a type of "any-snarf" program, as it will fetch telnet sessions, FTP files, HTTP transfers (HTML, GIF, JPEG, ...), SMTP emails, ... from the captured data inside network traffic logs. +A html index file is created that links to all the session details, including realtime replay programs for telnet, rlogin, IRC, X11 or VNC sessions; and reports such as image reports and HTTP GET/POST content reports. +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. + diff --git a/chaosreader.spec b/chaosreader.spec new file mode 100644 index 0000000..9c1e657 --- /dev/null +++ b/chaosreader.spec @@ -0,0 +1,45 @@ +Name: chaosreader +Version: 0.94 +Release: 2mamba +Summary: An open source tool to trace TCP/UDP/... sessions and fetch application data +Group: Network/Monitoring +Vendor: openmamba +Distribution: openmamba +Packager: Stefano Cotta Ramusino +URL: http://www.brendangregg.com/chaosreader.html +Source: http://downloads.sourceforge.net/chaosreader/chaosreader%{version} +License: GPL +BuildArch: noarch +BuildRoot: %{_tmppath}/%{name}-%{version}-root + +%description +Chaosreader is an open source tool to trace TCP/UDP/... sessions and fetch application data from snoop or tcpdump logs. +This is a type of "any-snarf" program, as it will fetch telnet sessions, FTP files, HTTP transfers (HTML, GIF, JPEG, ...), SMTP emails, ... from the captured data inside network traffic logs. +A html index file is created that links to all the session details, including realtime replay programs for telnet, rlogin, IRC, X11 or VNC sessions; and reports such as image reports and HTTP GET/POST content reports. +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. + +%prep + +%build + +%install +[ "%{buildroot}" != / ] && rm -rf "%{buildroot}" +install -D -m 755 %{S:0} \ + %{buildroot}%{_bindir}/%{name} + +%{buildroot}%{_bindir}/%{name} --help2 > README + +%clean +[ "%{buildroot}" != / ] && rm -rf "%{buildroot}" + +%files +%defattr(-,root,root) +%{_bindir}/%{name} +%doc README + +%changelog +* Thu Sep 05 2013 Automatic Build System 0.94-2mamba +- automatic rebuild by autodist + +* Wed Aug 29 2007 Stefano Cotta Ramusino 0.94-1mamba +- package created by autospec diff --git a/chaosreader0.94 b/chaosreader0.94 new file mode 100644 index 0000000..0f19145 --- /dev/null +++ b/chaosreader0.94 @@ -0,0 +1,6832 @@ +#!/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." . + "$starttime0 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/\n/
    \n/g; + $GETPOST{HTML}[$number]{query} .= + "" . + "\n"; + } + $GETPOST{HTML}[$number]{query} .= "
    $var$value
    \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/\n/
    /g; + $GETPOST{HTML}[$number]{query} .= + "" . + "\n"; + } + $GETPOST{HTML}[$number]{query} .= "
    $var$value
    \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 "\n"; + } + print FILE <

    +TCP Port Count
    +

    $IP$Count{IP}{$IP}
    +END_HTML + foreach $port (sort {$Count{TCPport}{$b} <=> $Count{TCPport}{$a}} + keys %{$Count{TCPport}}) { + $port_text = $Services_TCP{$port} || $port || "0"; + print FILE "\n"; + } + print FILE <

    +UDP Port Count
    +

    $port_text$Count{TCPport}{$port}" . + "
    +END_HTML + foreach $port (sort {$Count{UDPport}{$b} <=> $Count{UDPport}{$a}} + keys %{$Count{UDPport}}) { + $port_text = $Services_UDP{$port} || $port || "0"; + print FILE "\n"; + } + print FILE <

    +IP Protocol Count
    +

    $port_text$Count{UDPport}{$port}" . + "
    +END_HTML + foreach $protocol (sort {$Count{IPprotocol}{$b} <=> + $Count{IPprotocol}{$a}} keys %{$Count{IPprotocol}}) { + $protocol_text = $IP_Protocols{$protocol}; + print FILE "\n"; + } + print FILE <

    +Ethernet Type Count
    +

    $protocol_text" . + "$Count{IPprotocol}{$protocol}
    +END_HTML + foreach $type (sort {$Count{EtherType}{$b} <=> $Count{EtherType}{$a}} + keys %{$Count{EtherType}}) { + print FILE "\n"; + } + print FILE < + + +END_HTML + + + ###################### + # --- index.text --- + + # + # Create Text index file + # + open(FILE,">index.text") || die "ERROR23: creating index: $!\n"; + print FILE "TCP/UDP/... Sessions\nFile: $Arg{infile}, " + . "Type: $TYPE, Created at: $the_date\n\n"; + print FILE @{$Index{Text}}; + close FILE; + + + ###################### + # --- image.html --- + + # + # Create HTML Image Index file to display images + # + open(FILE,">image.html") || die "ERROR24: creating index: $!\n"; + print FILE < +Chaosreader Image Report + +Chaosreader Image Report
    +Created at: $the_date, Type: $TYPE

    +Images
    +

    $type$Count{EtherType}{$type}" . + "
    +END_HTML + for ($html_index=0; $html_index <= $#{$Index{HTML}}; $html_index++) { + $html_line = $Image{HTML}[$html_index]{info}; + $html_links = $Image{HTML}[$html_index]{links}; + next unless defined $html_links; + print FILE "$html_line $html_links \n"; + } + print FILE <

    + + +END_HTML + + + ###################### + # --- getpost.html --- + + # + # Create HTML GETPOST Index file to show HTTP GETs and POSTs + # + open(FILE,">getpost.html") || die "ERROR25: creating index: $!\n"; + print FILE < +Chaosreader GET/POST Report + +Chaosreader GET/POST Report
    +Created at: $the_date, Type: $TYPE

    +HTTP GETs and POSTs
    +

    +END_HTML + for ($html_index=0; $html_index <= $#{$GETPOST{HTML}}; $html_index++) { + $html_line = $GETPOST{HTML}[$html_index]{info}; + $html_links = $GETPOST{HTML}[$html_index]{query}; + next unless defined $html_links; + print FILE "$html_line $html_links \n"; + } + print FILE <

    + + +END_HTML + + } +} + + + +# Create_Index_Master - Create the HTML and text master index files. This +# reads @Master and creates the files on disk. +# +sub Create_Index_Master { + + my ($start,$end,$dir,$file,$index,$duration); + + if ($Arg{output_index}) { + + # + # Create most recent link + # + + $dir = $Master[$#Master]{dir}; + $recentname = "most_recent_index"; + unlink("$recentname"); + # don't die on symlink error, it's not essential + symlink("$dir","$recentname"); + + # + # Create HTML Index file containing all reports + # + open(FILE,">index.html") || die "ERROR26: creating index: $!\n"; + print FILE < +Chaosreader Master Index + +Chaosreader Master Index
    +Created at: $the_date, Type: $TYPE

    + +Most Recent Report + - Click here for the most recent index, and click reload for updates.

    +Chaosreader Reports
    +

    +END_HTML + for ($index=0; $index <= $#Master; $index++) { + $start = $Master[$index]{starttime}; + $end = $Master[$index]{endtime}; + $dir = $Master[$index]{dir}; + $file = $Master[$index]{file}; + $size = $Master[$index]{size}; + $duration = $Master[$index]{duration}; + $html_line = "" . + "\n" . + "" . "" . + "\n"; + print FILE "$html_line \n"; + } + print FILE <

    +IP Count
    +

    ". ($index+1) . "$start$end$duration s " . + "$size bytes$dir/$file
    +END_HTML + foreach $IP (sort {$CountMaster{IP}{$b} <=> $CountMaster{IP}{$a}} + keys %{$CountMaster{IP}}) { + print FILE "\n"; + } + print FILE <

    +TCP Port Count
    +

    $IP$CountMaster{IP}{$IP}" . + "
    +END_HTML + foreach $port (sort {$CountMaster{TCPport}{$b} <=> + $CountMaster{TCPport}{$a}} keys %{$CountMaster{TCPport}}) { + $port_text = $Services_TCP{$port} || $port || "0"; + print FILE "\n"; + } + print FILE <

    +UDP Port Count
    +

    $port_text" . + "$CountMaster{TCPport}{$port}
    +END_HTML + foreach $port (sort {$CountMaster{UDPport}{$b} <=> + $CountMaster{UDPport}{$a}} keys %{$CountMaster{UDPport}}) { + $port_text = $Services_UDP{$port} || $port || "0"; + print FILE "\n"; + } + print FILE <

    +IP Protocol Count
    +

    $port_text" . + "$CountMaster{UDPport}{$port}
    +END_HTML + foreach $protocol (sort {$CountMaster{IPprotocol}{$b} <=> + $CountMaster{IPprotocol}{$a}} keys %{$CountMaster{IPprotocol}}) { + $protocol_text = $IP_Protocols{$protocol}; + print FILE "\n"; + } + print FILE <

    +Ethernet Type Count
    +

    $protocol_text" . + "$CountMaster{IPprotocol}{$protocol}
    +END_HTML + foreach $type (sort {$CountMaster{EtherType}{$b} <=> + $CountMaster{EtherType}{$a}} keys %{$CountMaster{EtherType}}) { + print FILE "\n"; + } + print FILE < + + +END_HTML + + # + # Create Text index file + # + open(FILE,">index.text") || die "ERROR27: creating index: $!\n"; + print FILE "Master Indexes\nCreated at: $the_date, Type: $TYPE\n\n"; + for ($index=0; $index <= $#Master; $index++) { + $start = $Master[$index]{starttime}; + $end = $Master[$index]{endtime}; + $dir = $Master[$index]{dir}; + $file = $Master[$index]{file}; + $size = $Master[$index]{size}; + $duration = $Master[$index]{duration}; + printf FILE "%-25s %3s s %8s b %s\n",$start,$duration, + $size,"$dir/index.text"; + } + close FILE; + + + # + # Create index.file for redos + # + open(FILE,">index.file") || die "ERROR28: creating index: $!\n"; + for ($index=0; $index <= $#Master; $index++) { + $dir = $Master[$index]{dir}; + $file = $Master[$index]{file}; + $start = $Master[$index]{starttime}; + $end = $Master[$index]{endtime}; + $duration = $Master[$index]{duration}; + print FILE "$dir\t$file\t$duration\t$start\t$end\n"; + } + close FILE; + } +} + + +# Create_Log_Files - create log files such as the HTTP log. +# +sub Create_Log_Files { + #BDG some memory debug + #system("pmap -x $$"); + + # + # Create HTTPlog.text + # + open(FILE,">httplog.text") || die "ERROR29: creating HTTP log: $!\n"; + + foreach $time (sort { $a <=> $b }(keys (%{$HTTPlog{time}}))) { + print FILE $HTTPlog{time}{$time}; + } + + close FILE; +} + + + +# File_Type - return file extension for given data, else "data". +# +sub File_Type { + my $data = $_[0]; + my $type = ""; + + if ($data =~ /^GIF8[7-9]/) { $type = "gif"; } + elsif ($data =~ /^\377.....(JPEG|JFIF)/) { $type = "jpeg"; } + elsif ($data =~ /^PK\003\004/) { $type = "zip"; } + elsif ($data =~ /^\%PDF/) { $type = "pdf"; } + elsif ($data =~ /^\037\213/) { $type = "gz"; } + elsif ($data =~ /^BZh/) { $type = "bz2"; } + elsif ($data =~ /^\177ELF/) { $type = "elf"; } + elsif ($data =~ /^\%!/) { $type = "ps"; } + elsif ($data =~ //i) { $type = "html"; } + else { $type = "data"; } + + return $type; +} + + +# Is_Image - returns true if extension is for an image. +# +sub Is_Image { + my $ext = shift; + + return 1 if ($ext eq "jpeg"); + return 1 if ($ext eq "gif"); + + return 0; +} + + +# Desex_HTML - Removes HTML tags ("<" and ">") from data, so that it no +# longer interferes when printed as HTML. +# +sub Desex_HTML { + ### Input + my $data = shift; + + ### Process + # remove "<" and ">"s + $data =~ s//>/g; + + ### Return + return $data; +} + + + +# Process_BothHTML - Process the HTML 2-way session. Remove binary junk +# that dosen't render well in a browser. +# +sub Process_BothHTML { + ### Input + my $type = shift; + my $session_id = shift; + my $plain = shift; + my $wrapped = ""; + my $index = 0; + my $counter = 0; + my $intag = 0; + my ($char,$data); + + if ($type eq "TCP") { + $data = $TCP{id}{$session_id}{BothHTML}; + } elsif ($type eq "UDP") { + $data = $UDP{id}{$session_id}{BothHTML}; + } elsif ($type eq "ICMP") { + $data = $ICMP{time}{$session_id}{BothHTML}; + } + + ### Process (order dependant) + $data =~ s/font color="red"> \0]{$WRAP})/$&\n/g; + } else { + # This is a fancy line wrap, a green ">" starts the wrapped lines + $data =~ s/([^\n\f<>]{$WRAP})/$&\n><\/font>/g; + } + + ### Save + if ($type eq "TCP") { + $TCP{id}{$session_id}{BothHTML} = $data; + } elsif ($type eq "UDP") { + $UDP{id}{$session_id}{BothHTML} = $data; + } elsif ($type eq "ICMP") { + $ICMP{time}{$session_id}{BothHTML} = $data; + } + +} + +# Process_This_HTML - Process the HTML 2-way session. Remove binary junk +# that dosen't render well in a browser. +# +sub Process_This_HTML { + ### Input + my $data = shift; + my $plain = shift; + my $wrapped = ""; + my $index = 0; + my $counter = 0; + my $intag = 0; + my ($char); + + ### Process (order dependant) + $data =~ s/font color="red"> \0]{$WRAP})/$&\n/g; + } else { + # This is a fancy line wrap, a green ">" starts the wrapped lines + $data =~ s/([^\n\f<>]{$WRAP})/$&\n><\/font>/g; + } + + return $data; +} + + +# Process_Hex - Create the coloured HTML 2-way hex dump, and a text dump. +# For code reuse it uses it's own data structure %Hex. +# (Originally used %TCP{id}{$session_id}{hex}). +# +sub Process_Hex { + ### Input + my $type = shift; + my $session_id = shift; + my $data = shift; + my $colour = shift; + my $pos = $Hex{$type}{$session_id}{pos}; + my $offset = $Hex{$type}{$session_id}{offset}; + my $hexhtml = $Hex{$type}{$session_id}{hexhtml}; + my $viewhtml = $Hex{$type}{$session_id}{viewhtml}; + my $hextext = $Hex{$type}{$session_id}{hextext}; + my $viewtext = $Hex{$type}{$session_id}{viewtext}; + my (@Bytes,$byte,$view,$view2); + + + $pos = 1 unless defined $pos; + $offset = 0 unless defined $offset; + $hexhtml .= ""; + $viewhtml .= ""; + + ### Process + @Bytes = unpack("C*",$data); + foreach $byte (@Bytes) { + $view = chr($byte); + $view =~ tr/\040-\176/./c; + $view2 = $view; + $view2 =~ s//>/g; + $viewhtml .= $view2; + $viewtext .= $view; + $hexhtml .= sprintf("%2.2x",$byte); + $hextext .= sprintf("%2.2x",$byte); + $pos++; + if ($pos > 16) { + ### Save text version + $Hex{$type}{$session_id}{text} .= + sprintf("%6.08x",$offset) . " $hextext $viewtext\n"; + + ### Save HTML version + $Hex{$type}{$session_id}{HTML} .= + '' . sprintf("%6.08x",$offset) . + " $hexhtml $viewhtml\n"; + + $pos = 1; + $offset += 16; + $hexhtml = ""; + $viewhtml = ""; + $hextext = $viewtext = ""; + } + if ( ($pos != 1) && (($pos %2) == 1) ) { + $hexhtml .= " "; + $hextext .= " "; + } + } + $hexhtml .= ""; + $viewhtml .= ""; + + $Hex{$type}{$session_id}{pos} = $pos; + $Hex{$type}{$session_id}{offset} = $offset; + $Hex{$type}{$session_id}{hexhtml} = $hexhtml; + $Hex{$type}{$session_id}{viewhtml} = $viewhtml; + $Hex{$type}{$session_id}{hextext} = $hextext; + $Hex{$type}{$session_id}{viewtext} = $viewtext; +} + + + +# Process_Hex_Finish - Finish the hex dumps. +# +sub Process_Hex_Finish { + ### Input + my $type = shift; + my $session_id = shift; + my $pos = $Hex{$type}{$session_id}{pos}; + my $offset = $Hex{$type}{$session_id}{offset}; + my $hexhtml = $Hex{$type}{$session_id}{hexhtml}; + my $viewhtml = $Hex{$type}{$session_id}{viewhtml}; + my $hextext = $Hex{$type}{$session_id}{hextext}; + my $viewtext = $Hex{$type}{$session_id}{viewtext}; + my ($short); + + return unless defined $pos; + return if $pos == 1; + + $short = 39 - length($hextext); + $hexhtml .= " " x $short; + $hextext .= " " x $short; + + ### Save text version + $Hex{$type}{$session_id}{text} .= + sprintf("%6.08x",$offset) . " $hextext $viewtext\n"; + + ### Save HTML version + $Hex{$type}{$session_id}{HTML} .= + '' . sprintf("%6.08x",$offset) . + " $hexhtml $viewhtml\n"; + +} + + +# Generate_X11_HTML - fetch the text from an X11 session and save +# as bidirectional 2-way coloured HTML. +# +# Todo: check if a text or keypress event can be split during +# transmission and add code similar to X11 replay to handle this. +# +sub Generate_X11_HTML { + my ($filename,$data,$copy,$xcode,$xbyte,$xlength,$xrest,$d, + $xlv,$xvalue,$pad,$y,$yold,$chars,$colour,$session_data, + $service_name,$colourold,$store,$keytype,$gotsome); + my @Times; + + $session_data = ""; + + ### Input + my $session_id = shift; + $data = ""; + $service_name = "X11"; + + ### Processing + my $session_text = $session_id; + $session_text =~ s/,/ <-> /; + + ### Fetch raw data + $xserver = &TCP_Follow_RawA($session_id); + + # + # Determine endian of this transfer. + # + ($xjunk,$xvalue,$xjunk) = unpack('nna*',$xserver); + # + # Create aliases for "n" and "N". + # + if ($xvalue < 256) { + $n = "n"; $N = "N"; + } else { + $n = "v"; $N = "V"; + } + # + # Determine keymap style - see &Set_X11_KeyCodes() + # + if ($xserver =~ + /q...Q.*w...W.*e...E.*r...R.*t...T.*y...Y.*u...U.*i...I.*o...O.*p/) { + $keytype = "linux"; + } else { + $keytype = "sun"; + } + + # + # Fetch data from both directions, sorting on timestamps + # + @Times = sort{$a <=> $b} (keys %{$TCP{id}{$session_id}{time}}); + + # + # --- Main Loop --- + # + # (this needs to be a for loop!) + for ($i=0; $i <= $#Times; $i++) { + $time = $Times[$i]; + + ### Fetch X11 data and direction as a colour + if (defined $TCP{id}{$session_id}{time}{$time}{dir}) { + $copy = $TCP{id}{$session_id}{time}{$time}{data}; + if ($TCP{id}{$session_id}{time}{$time}{dir} eq "A") { + $colour = "red"; + } else { + $colour = "blue"; + } + } + + $xrest = $copy; + # + # Process through X11 codes + # + while (length($xrest) > 0) { + ### Fetch xcode and other values + ($xcode,$xbyte,$xlength,$xrest) = unpack("CC${n}a*",$xrest); + $chars = ""; + + # + # Fetch code values from $xrest, and trim + # $xrest. For most requests, the value length + # is a field (bytes 3,4) except for XErrors + # (code 0) where the total length is always 32. + # + if ($xcode == 0) { + $xlv = 28; + } else { + $xlv = ($xlength - 1) * 4; + $xlv = -$xlv if $xlv < 0; + } + + ### Fetch values for this xcode + ($xvalue,$xrest) = unpack("a${xlv}a*",$xrest); + + $store = 0; + + # + # Process a draw text event (76, 77) + # + if (($colour eq "blue") && (($xcode == 76)||($xcode == 77))) { + # Check if this is a xImageText16Req + if ($xcode == 77) { $xbyte *= 2; } + + ($pad,$y,$chars) = unpack("a10${n}a$xbyte",$xvalue); + if ($yold != $y) { $chars = "\n$chars"; } + $chars =~ s/\0//g; + + $store = 1; + $yold = $y; + } + + # + # Process a key pressed event (2) + # + if (($colour eq "red") && ($xcode = "2")) { + ($pad,$caps,$pad) = unpack("a24${n}a*",$xvalue); + + # + # Translate the X11 KeyCode to the actual char + # (try "xmodmap -pke") + # + $chars = $KeyCode{$keytype}{$caps}{$xbyte}; + + ### Don't keep red \n's for neatness (keep blue ones) + unless ($chars eq "\n") { + $store = 1; + } + } + + # + # Process a text scroll event (by using 62 - copy area) + # + if (($colour eq "blue") && ($xcode == 62)) { + $chars = "\n"; + $store = 1; + } + + ### Store data + if ($store) { + if ($colour ne $colourold) { + $session_data .= + "$chars"; + } else { + $session_data .= $chars; + } + $colourold = $colour; + } + } + } + + $TCP{id}{$session_id}{BothHTML} = $session_data; +} + + +# Save_Both_HTML - Save bidirectional (coloured) data into a html file. +# +sub Save_Both_HTML { + my ($filename); + + ### Input + my $type = shift; + my $session_id = shift; + my $number = shift; + my $service_name = shift; + my $session_text = shift; + my $numtext = sprintf("%04d",$number); + my ($base,$raw); + + $session_text = $session_id unless defined $session_text; + + ### Processing + $session_text =~ s/,/ <-> /; + + ### Checks + $ext = ""; + $session_data = ""; + if ($type eq "TCP") { + $base = "session"; + # + # Note, the following is similar code for TCP, UDP and ICMP. + # However UDP and ICMP use a simple strategy to store and fetch + # the processed HTML; whereas TCP uses a complex yet memory + # efficient strategy. This is intentional - the way TCP has + # been stored has been tuned to reduce memory usage, as TCP has + # the bulk of the data (and the bulk of the memory problem). This + # has not been necessary with UDP and ICMP (yet). + # + if ($TCP{id}{$session_id}{BothHTML} ne "") { + # + # If the BothHTML report has already been calculated, fetch + # + $session_data = $TCP{id}{$session_id}{BothHTML}; + } else { + # + # Generate a BothHTML report by following packets by time + # + foreach $time (sort {$a <=> $b} + (keys (%{$TCP{id}{$session_id}{time}}))) { + $raw = $TCP{id}{$session_id}{time}{$time}{data}; + $raw = &Desex_HTML($raw); + next unless length($raw); + if ($TCP{id}{$session_id}{time}{$time}{dir} eq "A") { + $session_data .= "$raw"; + } else { + $session_data .= "$raw"; + } + } + $session_data = &Process_This_HTML($session_data); + $base = "session"; + if ($TCP{id}{$session_id}{Partial}) { $ext = ".partial"; } + } + + } elsif ($type eq "UDP") { + $base = "stream"; + $session_data = $UDP{id}{$session_id}{BothHTML}; + if ($UDP{id}{$session_id}{Partial}) { $ext = ".partial"; } + } elsif ($type eq "ICMP") { + $base = "icmp"; + $session_data = $ICMP{time}{$session_id}{BothHTML}; + if ($ICMP{time}{$session_id}{Partial}) { $ext = ".partial"; } + } else { + $base = "are_belong_to_us"; + } + + ### Do nothing if there is no data ("26" is mostly due to colour tags) + return unless ((defined $session_data)&&(length($session_data) > 26)); + + ### Output + $filename = "${base}_${numtext}.${service_name}${ext}.html"; + open (OUT,">$filename") || die "ERROR30: file create, $filename: $!\n"; + binmode(OUT); + print OUT "\n$number" . + "\n" . + "

    $service_name: $session_text

    \n" . + "

    File $Arg{infile}, Session $number

    \n" . + "
    \n" .
    +         $session_data . "
    \n\n\n"; + close OUT; + + ### Global Vars + my $length = length($session_data); + $Index{HTML}[$number] .= "
  • as_html
  • \n"; + $Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n", + '"' , " $filename","",$length); +} + + + +# Save_Hex_HTML - Save bidirectional (coloured) hex data into a html file. +# +sub Save_Hex_HTML { + my ($filename); + + ### Input + my $type = shift; + my $session_id = shift; + my $number = shift; + my $service_name = shift; + my $session_text = shift; + my $session_data = $Hex{$type}{$session_id}{HTML}; + my $numtext = sprintf("%04d",$number); + my ($base); + + $session_text = $session_id unless defined $session_text; + $session_data = "" unless defined $session_data; + + + ### Processing + $session_text =~ s/,/ <-> /; + + ### Checks + $ext = ""; + if ($type eq "TCP") { + $base = "session"; + if ($TCP{id}{$session_id}{Partial}) { $ext = ".partial"; } + } elsif ($type eq "UDP") { + $base = "stream"; + if ($UDP{id}{$session_id}{Partial}) { $ext = ".partial"; } + } elsif ($type eq "ICMP") { + $base = "icmp"; + if ($ICMP{id}{$session_id}{Partial}) { $ext = ".partial"; } + } + + ### Output + $filename = "${base}_${numtext}.${service_name}${ext}.hex.html"; + open (OUT,">$filename") || die "ERROR31: file create, $filename: $!\n"; + binmode(OUT); + print OUT "\n$number" . + "\n" . + "

    $service_name: $session_text

    \n" . + "

    File $Arg{infile}, Session $number

    \n" . + "
    \n" .
    +         $session_data . "
    \n\n\n"; + close OUT; + + ### Global Vars + my $length = length($session_data); + $Index{HTML}[$number] .= "
  • "; + $Index{HTML}[$number] .= "hex
  • \n"; + $Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n", + '"' , " $filename","",$length); +} + + + +# Save_Hex_Text - Save bidirectional hex data into a text file. +# +sub Save_Hex_Text { + my ($filename); + + ### Input + my $type = shift; + my $session_id = shift; + my $number = shift; + my $session_text = shift; + my $session_data = $Hex{$type}{$session_id}{text}; + my $numtext = sprintf("%04d",$number); + my ($base); + + $session_text = $session_id unless defined $session_text; + $session_data = "" unless defined $session_data; + + ### Processing + $session_text =~ s/,/ <-> /; + + ### Checks + $ext = ""; + if ($type eq "TCP") { + $base = "session"; + if ($TCP{id}{$session_id}{Partial}) { $ext = ".partial"; } + } elsif ($type eq "UDP") { + $base = "stream"; + if ($UDP{id}{$session_id}{Partial}) { $ext = ".partial"; } + } elsif ($type eq "ICMP") { + $base = "icmp"; + if ($ICMP{id}{$session_id}{Partial}) { $ext = ".partial"; } + } + + ### Output + $filename = "${base}_${numtext}.${service_name}${ext}.hex.text"; + open (OUT,">$filename") || die "ERROR32: file create, $filename: $!\n"; + binmode(OUT); + print OUT "$service_name: $session_text\n" . + "File $Arg{infile}, Session $number\n\n$session_data\n"; + close OUT; + + ### Global Vars + my $length = length($session_data); + $Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n", + '"' , " $filename","",$length); +} + + +# Save_FTP_File - Save files from an active FTP session. +# +sub Save_FTP_File { + my ($filename,$ftp_data,$length); + my $session_id = shift; + my $number = shift; + my $numtext = sprintf("%04d",$number); + my $service_name = "ftp-data"; + + ### Input + $ftp_data = &TCP_Follow_RawB($session_id); + if (! defined $ftp_data) { + $ftp_data = &TCP_Follow_RawA($session_id); + } + + ### Checks + $ftp_type = &File_Type($ftp_data); + if ($TCP{id}{$session_id}{Partial}) { $ext = ".partial"; } + else { $ext = ""; } + + ### Output + $filename = "session_${numtext}.part_01.$service_name${ext}.$ftp_type"; + open (OUT,">$filename") || die "ERROR33: file create, $filename: $!\n"; + binmode(OUT); # for backward OSs + print OUT $ftp_data; + close OUT; + + ### Global Vars + $length = length($ftp_data); + $Index{HTML}[$number] .= + "
  • $filename $length bytes
  • \n"; + $Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n", + '"' , " $filename","",$length); + if (&Is_Image($ftp_type)) { + $Image{HTML}[$number]{links} .= + " "; + $Image{notempty} = 1; + } +} + +# NOTE On Replays +# +# The essence of these is to playback the client/server data so that +# the original session can be replayed. There are two styles, +# +# Text Replays. These playback the text component to the application +# data to the screen. These usally work well. The actual text data is not +# cleaned up in any way, so to preserve escape sequences necessary to +# redisplay in the original style. Eg, telnet. +# +# GUI Replays, or Server/Client Replays. These often use TCP/IP to send +# the data back to the server or client to playback the session. These +# are less robust, mainly becuase negotiation can occur slightly differently +# causing nothing to be displayed. There is code here to redo the +# negotiation - but it is very difficult for this to be 100% robust. +# The main reasons the GUI replays fail are colour depth mismatch +# and dropped packets. Eg, X11. +# +# Both styles print the binary data within single quotes ' '. This +# creates perl programs that can't be "cat" (use cat -vet), or edited +# in vi (use vim) due to the raw binary data. A neater style would be to +# translate the binary data into octal or hex text streams, eg +# 'print "\015\012\087\012"'... Currently this is not used, as it would +# roughly increase the file size by a factor of 4. However plopping +# data in the middle of perl programs creates problems of it's own +# (see the unusual seds). At some point I may opt for the easier, +# although lengthier, method. + + +# Save_Session_Replay - Save a replay program for this session. eg, telnet. +# +sub Save_Session_Replay { + my ($filename,$duration,$time); + my $session_id = shift; + my $number = shift; + my $service_name = shift; + my $numtext = sprintf("%04d",$number); + + ### Output + $filename = "session_${numtext}.${service_name}.replay"; + $duration = ($TCP{id}{$session_id}{EndTime} - + $TCP{id}{$session_id}{StartTime}); + $duration = sprintf("%.0f",$duration); + open (REPLAY,">$filename") || + die "ERROR34: creating $filename $!\n"; + binmode(REPLAY); # for backward OSs + + # + # Create a perl program, that when run itself will print out + # the contents of the server 1-way stream, with pauses based on + # the packet arrival times (replay the session in realtime). + # + print REPLAY "#!$PERL\n"; + print REPLAY <<'END'; +# +# This is a telnet/login replay program. It will replay a session using +# the timestamps from the packet log. +# +# USAGE: run the script as normal. You can provide a factor as an +# argument, eg "2" to run twice as fast, or "0.5" to run +# at half time. eg, +# ./session_0002.telnet.replay 2 +# +# Auto generated by Chaosreader. +# +$| = 1; +$factor = $ARGV[0] || 1; +sub ms { + $ms = shift; + $ms = $ms / $factor; + select(undef, undef, undef, $ms); +} +END + + # + # Sort the data on the timestamps, calculating timestamp differences + # to record in the replay program. + # + @Times = (); + foreach $time (keys (%{$TCP{id}{$session_id}{time}})) { + if ($TCP{id}{$session_id}{time}{$time}{dir} eq "A") { + push(@Times,$time) + } + } + @Times = sort { $a <=> $b } @Times; + + for ($i=0; $i <= $#Times; $i++) { # required + + ### Calculate time diff if possible + if ($i == $#Times) { + $timediff = 0; + } else { + $timediff = $Times[$i+1] - $Times[$i]; + if ($timediff < 0) { $timediff = 0; } + } + $time = $Times[$i]; + + ### Fetch data from mem + $data = $TCP{id}{$session_id}{time}{$time}{data}; + + # + # Clean the data a little (order important) + # + $data =~ s/\\/\\\\/g; # backslash the backslashes + $data =~ s/'/\\'/g; # backslash single quotes + + # + # Now output the data in the replay program + # + print REPLAY "print '" . $data . "';\n"; + + # + # This causes the replay program to pause + # + print REPLAY "ms($timediff);\n"; + } + close REPLAY; + + ### Better make it executable + chmod (0755, "$filename"); + + ### Global Vars + $Index{HTML}[$number] .= "
  • $filename" . + " $duration seconds
  • \n"; + $Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s seconds\n", + '"' , " $filename","",$duration); +} + + +# Save_Session_textSSH_files - Save a replay program to display the SSH +# session in a text format, a html form of this, and a key delay +# data file. +# +# The program "sshkeydata" will take the key delay data file and estimate +# the original typed commands. (It also needs a key delay data file +# from a plaintext session such as telnet, which is generated by the +# Save_Session_Keydata subroutine). +# +# This has been designed with SSH ver 2 in mind. +# +sub Save_Session_textSSH_files { + my ($filename1,$filename2,$filename3,$duration,$time,$data,$length, + $time0,$time1,$time2,$data0,$data1,$data2,$length0,$length1,$length2, + $dir0,$dir1,$dir2,$timediff,$timediff2,$outtime,$outsize,$datah, + $data00); + my $session_id = shift; + my $number = shift; + my $service_name = shift; + my $session_text = shift; + my $numtext = sprintf("%04d",$number); + my $delay = ""; # a text list of key delays + my $html = ""; # a html form of output + my $bytes = 0; # data bytes of the connection + my $minsize; # The min client packet size + my $state; + + $duration = ($TCP{id}{$session_id}{EndTime} - + $TCP{id}{$session_id}{StartTime}); + $duration2 = sprintf("%.2f",$duration); + $duration = sprintf("%.0f",$duration); + + ### Output + $filename1 = "session_${numtext}.text${service_name}.replay"; + open (REPLAY,">$filename1") || + die "ERROR35: creating $filename1 $!\n"; + binmode(REPLAY); # for backward OSs + + # + # Create a perl program that replays details of the original + # SSH session. We print the direction of traffic and size, + # paused using the original delays. + # + print REPLAY "#!$PERL\n"; + print REPLAY <<'END'; +# +# This is a text SSH replay program. It will replay details of the +# original SSH session using timestamps from the packet capture log. +# +# USAGE: run the script as normal. You can provide a factor as an +# argument, eg "2" to run twice as fast, or "0.5" to run +# at half time. eg, +# ./session_0002.textSSH.replay 2 +# +# Auto generated by Chaosreader. +# +$| = 1; +$factor = $ARGV[0] || 1; +sub ms { + $ms = shift; + $ms = $ms / $factor; + select(undef, undef, undef, $ms); +} +print <<'SUBEND'; +SSH text analysis replay +------------------------ +"*" is client traffic (including keystrokes), "." is the return text. +A number is a multiple of the previous char, eg ".32" is 32 return chars. + +SUBEND +END + + # + # Sort the data on the timestamps, calculating timestamp differences + # to record in the replay program. + # + @Times = (); + %PacketSize = (); + foreach $time (keys (%{$TCP{id}{$session_id}{time}})) { + if (length($TCP{id}{$session_id}{time}{$time}{data}) == 0) { + next; + } + push(@Times,$time); + if ($TCP{id}{$session_id}{time}{$time}{dir} eq "B") { + ### Frequency count sent sizes + $data = $TCP{id}{$session_id}{time}{$time}{data}; + $length = length($data); + $PacketSize{$length}++ if $length < 100; + } + } + @Times = sort { $a <=> $b } @Times; + $outtime = $Times[0]; + $outsize = 0; + + # + # Determine the client min size - this is the minimum length of + # a data packet, eg a keystroke. + # + foreach $length (sort {$PacketSize{$b} <=> $PacketSize{$a}} + (keys(%PacketSize))) { + $minsize = $length; + last; + } + + # The very first packet + $data00 = $TCP{id}{$session_id}{time}{$Times[0]}{data}; + + ### Process data + for ($i=0; $i <= $#Times; $i++) { # required + + ### Calculate time diff if possible + $time0 = $Times[$i]; + $time1 = $Times[$i+1]; + $time2 = $Times[$i+2]; + $time3 = $Times[$i+3]; + if ($i == $#Times) { + $timediff1 = 0; + $timediff2 = 0; + } else { + $timediff1 = $time1 - $time0; + $timediff2 = $time2 - $time0; + if ($timediff1 < 0) { $timediff1 = 0; } + } + + ### Fetch data from mem, "0" is this packet... + $data0 = $TCP{id}{$session_id}{time}{$time0}{data}; + $data1 = $TCP{id}{$session_id}{time}{$time1}{data}; + $data2 = $TCP{id}{$session_id}{time}{$time2}{data}; + $dir0 = $TCP{id}{$session_id}{time}{$time0}{dir}; + $dir1 = $TCP{id}{$session_id}{time}{$time1}{dir}; + $dir2 = $TCP{id}{$session_id}{time}{$time2}{dir}; + $dir3 = $TCP{id}{$session_id}{time}{$time3}{dir}; + $length0 = length($data0); + $length1 = length($data1); + $length2 = length($data2); + + # working variables + $bytes += $length0; + $length = $length0; + $data = $data0; + + ################## + # Process Data + # + # This is designed for a command line SSH session and + # the calculations are based on many assumptions. + # + # For example: if the client sends a small packet (which + # we'll assume is a keystroke) and the server responds + # with large packets (beyond merely echoing the keystroke), + # then we can assume that this keystroke was the enter key, + # and the large response was the output of the command. + # + # There are two states - keystrokes and output text. + # + # The follow code works well most of the time, and provides + # meaningful results for non command line sessions. + # + + # + # --- Server to Client --- + # + if ($dir0 eq "A") { + if ($i > 3 || $data00 !~ /^ssh/i) { + # a "." represents an encrypted server to client packet + $data = "."; + $html .= '' . $data; + } else { + ### Process initial plaintext negotiation + + # first we clean up the data, + $data =~ tr/\040-\176/./c; + $data =~ s/\\/\\\\/g; + $data =~ s/'/\\'/g; + $data .= "\n"; + $hdata = $data; + $hdata = &Desex_HTML($hdata); + + # This is a fancy line wrap, adds a green ">" + $hdata =~ + s/([^\n\f<>]{$WRAP})/$&\n><\/font>/g; + $html .= '' . $hdata; + } + + if ($state eq "output") { + if ($length0 > $minsize && $i > 3) { + # This prints the length in the replay files + # as a number following the symbol, + # eg ".60" would mean a "." with length 60. + # length actually means size beyond minsize. + $length -= $minsize; + $data .= "$length"; + $html .= "$length"; + $outsize += $length; + } + + ### Data -> Keystrokes + if ($dir1 eq "B" && $length1 == $minsize) { + # Process the transition from command output back + # to keystrokes. + $data .= "\n"; + $html .= "\n"; + $delay .= "s $outsize\n"; + $delay .= sprintf("t %.6f\n",$time0 - $outtime); + $delay .= " \n"; # command delimiter + $outsize = 0; + $outtime = $time0; + $state = "key"; + } + } + $html .= ''; + } + + # + # --- Client to Server --- + # + else { + if ($i == 1) { + # PuTTY appears to have an unusual way to send keystrokes + # to the server, that differs to OpenSSH and Sun's SSH. + # Remember if this is a PuTTY session. + $sshtype = "putty" if $data =~ /PuTTY/; + } + + ### Keystroke + if ($sshtype eq "") { + # If the client is sending a minsize packet and the server + # then responds, we assume this is a keystroke. + if ($length0 == $minsize && $dir1 eq "A") { + $delay .= "k \n"; + } + } elsif ($sshtype eq "putty") { + # if the client is sending a minsize packet, followed by + # another packet, then a reply packet, and then a server + # response; we assume that this is a keystroke. + # (This processes PuTTY's doubled keystrokes). + if ($length0 == $minsize && $dir1 eq "B" && $dir2 eq "A") { + $delay .= "k \n"; + } + } + + ### Process initial plaintext negotiation + if ($i > 3 || $data00 !~ /^ssh/i) { + # a "*" represents an encrypted client to server packet + $data = "*"; + $html .= '' . $data; + } else { + ### Process initial plaintext negotiation + + # first we clean up the data, + $data =~ tr/\040-\176/*/c; + $data =~ s/\\/\\\\/g; + $data =~ s/'/\\'/g; + $data .= "\n"; + $hdata = $data; + $hdata = &Desex_HTML($hdata); + + # This is a fancy line wrap, adds a green ">" + $hdata =~ + s/([^\n\f<>]{$WRAP})/$&\n><\/font>/g; + $html .= '' . $hdata; + } + + ### Keystroke -> Keystroke delay + if ($sshtype eq "") { + if ($length0 == $minsize && $dir1 eq "A" && $dir2 eq "B" && + $length2 == $minsize) { + # If this is a keystroke packet, and the next packet + # is a response, and then another keystroke packet + # is sent; then measure the keystroke delay. + $timediff2 = $time2 - $time0; + $delay .= sprintf("d %.6f\n",$timediff2); + $outsize = 0; + $outtime = $time0; + } + } elsif ($sshtype eq "putty") { + if ($length0 == $minsize && $dir1 eq "A" && $dir2 eq "B" && + $length2 == $minsize && $dir3 eq "B") { + # This is the same idea as the above, but processes + # PuTTY's doubled keystrokes. + $timediff2 = $time2 - $time0; + $delay .= sprintf("d %.6f\n",$timediff2); + $outsize = 0; + $outtime = $time0; + } + } + + if ($length0 > $minsize && $i > 3) { + # + # This prints the length in the replay files + # as a number following the symbol, + # eg ".60" would mean a "." with length 60. + # length actually means size beyond minsize. + $length -= $minsize; + $data .= "$length"; + $html .= "$length"; + } + $html .= ''; + + ### Keystrokes -> Data + if ( ($length0 == $minsize && + (($length1 + $length2) > ($minsize * 2))) || + ($dir1 eq "A" && $dir2 eq "A") ) { + $data .= "\n"; + $html .= "\n"; + # + # "r" describes the response packet. This value + # may or may not be meaningful depending on the + # SSH software. + if ($length1 > $minsize) { + $delay .= "r 1\n"; + $delay .= sprintf("p %.6f\n",$timediff1); + } else { + $delay .= "r 2\n"; + $delay .= sprintf("p %.6f\n",$timediff2); + } + $state = "output"; + } + } + + ### Now output the data in the replay program + print REPLAY "print '" . $data . "';\n"; + + ### This causes the replay program to pause + print REPLAY "ms($timediff1);\n"; + } + $speed = sprintf("%.2f",$bytes / (1024 * $duration)); + print REPLAY "print \"\n\n" . + "Summary: $duration2 seconds, $bytes bytes, $speed Kb/sec\\n\";"; + close REPLAY; + + ### Better make it executable + chmod (0755, "$filename1"); + + # + # HTML version of the replay script + # + $filename2 = "session_${numtext}.text${service_name}.html"; + open (HTML,">$filename2") || + die "ERROR36: Can't write to file, $filename2 $!\n"; + $html = "SSH text analysis\n" . + "" . + "

    $service_name: $session_text

    \n" . + "

    File $Arg{infile}, Session $number

    \n" . + "

    $duration2 seconds, $bytes bytes, $speed Kb/sec

    \n" . + '"*" is client traffic (including ' . + 'keystrokes), "." is the return ' . + 'text.
    A number is a multiple of the previous char, eg ".32" ' . + 'is 32 return chars.
    ' . + "\n
    $html
    \n\n\n"; + print HTML $html; + close HTML; + + # + # Text Database of time delays between possible keystrokes + # + $filename3 = "session_${numtext}.text${service_name}.keydata"; + open (DELAY,">$filename3") || + die "ERROR37: Can't write keydata file: $filename3 $!\n"; + $delay = "$delay \n"; + print DELAY $delay; + close DELAY; + + # + # Update Global Vars to remember new filenames + # + $Index{HTML}[$number] .= "
  • $filename1" . + " $duration seconds
  • \n"; + $Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s seconds\n", + '"' , " $filename1","",$duration); + $Index{HTML}[$number] .= "
  • $filename2" . + "
  • \n"; + $length = length($html); + $Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n", + '"' , " $filename2","",$length); + $Index{HTML}[$number] .= "
  • $filename3" . + "
  • \n"; + $length = length($delay); + $Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n", + '"' , " $filename3","",$length); +} + + +# Save_Session_Keydata - Save a key delay data file to assist SSH analysis. +# +# This code is intentionally designed to be similar to the SSH processing +# code, so that both their outputs can be compared. As a standalone +# subroutine this wouldn't make too much sense; instead bear in mind that +# I'd like the processing to mimic how SSH was processed. That way we +# run this on plenty of known text (telnet) and become familiar with +# exactly what will happen for the unknown text (SSH). +# +sub Save_Session_Keydata { + my ($filename1,$filename2,$filename3,$duration,$time,$data,$length, + $time0,$time1,$time2,$data0,$data1,$data2,$length0,$length1,$length2, + $dir0,$dir1,$dir2,$timediff,$timediff2,$outtime,$outsize); + my $session_id = shift; + my $number = shift; + my $service_name = shift; + my $session_text = shift; + my $numtext = sprintf("%04d",$number); + my $delay = ""; # a text list of key delays + my $minsize; # The min client packet size + my $state = "key"; + + ### Sort the data by timestamps + @Times = (); + %PacketSize = (); + foreach $time (keys (%{$TCP{id}{$session_id}{time}})) { + if (length($TCP{id}{$session_id}{time}{$time}{data}) == 0) { + next; + } + push(@Times,$time); + } + @Times = sort { $a <=> $b } @Times; + $outtime = $Times[0]; + $outsize = 0; + $minsize = 1; # known for telnet + + ### Process data + for ($i=0; $i <= $#Times; $i++) { # required + + ### Calculate time diff if possible + $time0 = $Times[$i]; + $time1 = $Times[$i+1]; + $time2 = $Times[$i+2]; + if ($i == $#Times) { + $timediff1 = 0; + $timediff2 = 0; + } else { + $timediff1 = $time1 - $time0; + $timediff2 = $time2 - $time0; + if ($timediff1 < 0) { $timediff1 = 0; } + } + + ### Fetch data from mem, "0" is this packet... + $data0 = $TCP{id}{$session_id}{time}{$time0}{data}; + $data1 = $TCP{id}{$session_id}{time}{$time1}{data}; + $data2 = $TCP{id}{$session_id}{time}{$time2}{data}; + $data0 = "\n" if $data0 eq "\r\n"; + $data1 = "\n" if $data1 eq "\r\n"; + $data2 = "\n" if $data2 eq "\r\n"; + $data0 = "\n" if $data0 =~ /\r./; + $data1 = "\n" if $data1 =~ /\r./; + $data2 = "\n" if $data2 =~ /\r./; + $dir0 = $TCP{id}{$session_id}{time}{$time0}{dir}; + $dir1 = $TCP{id}{$session_id}{time}{$time1}{dir}; + $dir2 = $TCP{id}{$session_id}{time}{$time2}{dir}; + $length0 = length($data0); + $length1 = length($data1); + $length2 = length($data2); + + $length = $length0; + $data = $data0; + + # + # Process Data + # + if ($dir0 eq "A") { + if ($state eq "output") { + if ($length0 > $minsize) { + $length -= $minsize; + $outsize += $length; + } + + ### Data -> Keystrokes + if ($dir1 eq "B" && $length1 == $minsize) { + $delay .= "s $outsize\n"; + $delay .= sprintf("t %.6f\n",$time0 - $outtime); + $delay .= " \n"; + $outsize = 0; + $outtime = $time0; + $state = "key"; + } + } + } else { + ### Keystroke + if ($length0 == $minsize) { + if ($data0 eq "\n") { + $delay .= "k \\n\n"; + } else { + $delay .= "k $data0\n"; + } + } + ### Keystroke -> Keystroke delay + if ($length0 == $minsize && $dir1 eq "A" && $dir2 eq "B" && + $length2 == $minsize) { + $timediff2 = $time2 - $time0; + $delay .= sprintf("d %.6f\n",$timediff2); + $outsize = 0; + $outtime = $time0; + } + + if ($length0 > $minsize) { + $length -= $minsize; + } + + ### Keystrokes -> Data + if ( ($length0 == $minsize && + (($length1 + $length2) > ($minsize * 2))) || + ($dir1 eq "A" && $dir2 eq "A") ) { + if ($length1 > $minsize) { + $delay .= "r 1\n"; + $delay .= sprintf("p %.6f\n",$timediff1); + } else { + $delay .= "r 2\n"; + $delay .= sprintf("p %.6f\n",$timediff2); + } + $state = "output"; + } + } + } + + # + # Text Database of time delays between possible keystrokes + # + $filename3 = "session_${numtext}.${service_name}.keydata"; + open (DELAY,">$filename3") || + die "ERROR38: A pink jelly hits you. You die. $filename3 $!\n"; + print DELAY "$delay \n"; + close DELAY; + + # + # Update Global Vars to remember new filenames + # + $Index{HTML}[$number] .= "
  • $filename3" . + "
  • \n"; + $Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s\n", + '"' , " $filename3","",""); +} + + +# Save_Stream_Replay - Save a replay program for this stream. eg, dns. +# +sub Save_Stream_Replay { + my ($filename,$duration); + my $session_id = shift; + my $number = shift; + my $service_name = shift; + my $numtext = sprintf("%04d",$number); + + ### Output + $filename = "stream_${numtext}.${service_name}.replay"; + $duration = ($UDP{id}{$session_id}{EndTime} - + $UDP{id}{$session_id}{StartTime}); + $duration = sprintf("%.0f",$duration); + open (REPLAY,">$filename") || + die "ERROR39: creating $filename $!\n"; + binmode(REPLAY); # for backward OSs + + # + # Create a perl program, that when run itself will print out + # the contents of the server 1-way stream, with pauses based on + # the packet arrival times (replay the stream in realtime). + # + print REPLAY "#!$PERL\n"; + print REPLAY <<'END'; +# +# This is a UDP replay program. It will replay a stream using +# the timestamps from the packet log. +# +# USAGE: run the script as normal. You can provide a factor as an +# argument, eg "2" to run twice as fast, or "0.5" to run +# at half time. eg, +# ./stream_0002.telnet.replay 2 +# +# Auto generated by Chaosreader. +# +$| = 1; +$factor = $ARGV[0] || 1; +sub ms { + $ms = shift; + $ms = $ms / $factor; + select(undef, undef, undef, $ms); +} +END + + # + # Sort the data on the timestamps, calculating timestamp differences + # to record in the replay program. + # + @Times = keys (%{$UDP{id}{$session_id}{time}}); + @Times = sort { $a <=> $b } @Times; + + for ($i=0; $i <= $#Times; $i++) { # required + + ### Calculate time diff if possible + if ($i == $#Times) { + $timediff = 0; + } else { + $timediff = $Times[$i+1] - $Times[$i]; + if ($timediff < 0) { $timediff = 0; } + } + $time = $Times[$i]; + + ### Fetch data from mem + $data = $UDP{id}{$session_id}{time}{$time}; + delete $UDP{id}{$session_id}{time}{$time}; + + # + # Clean the data a little (order important) + # + $data =~ s/\\/\\\\/g; # backslash the backslashes + $data =~ s/'/\\'/g; # backslash single quotes + + # + # Now output the data in the replay program + # + print REPLAY "print '" . $data . "';\n"; + + # + # This causes the replay program to pause + # + print REPLAY "ms($timediff);\n"; + } + close REPLAY; + + ### Better make it executable + chmod (0755, "$filename"); + + ### Global Vars + $Index{HTML}[$number] .= "
  • $filename" . + " $duration seconds
  • \n"; + $Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s seconds\n", + '"' , " $filename","",$duration); +} + + +# Save_Session_XReplay - Save a replay program for this session. eg, X11. +# This processes far more of the X11 protocol than I was hoping. +# (xscope and ethereal were used to analyse X11). +# +sub Save_Session_XReplay { + my $session_id = shift; + my $number = shift; + my $service_name = shift; + my $numtext = sprintf("%04d",$number); + my ($filename,$duration,$xcode,$xres_old,$xrest,$xwnum,$xdiff, + $xlength,$xmsb,$xstart,$xjunk,$xvalue,$readnow,$data,$newdata, + $n,$N,$chars,$y,$timediff,$texttimediff,$checkdepth,$filename2, + $x11type); + my @xWords; + + ### Initials + $xmsb = ""; + $readnow = 0; + $xres_old = -1; + $checkdepth = 0; + + # + # Output - Main X11 replay program + # + $filename = "session_${numtext}.${service_name}.replay"; + $duration = ($TCP{id}{$session_id}{EndTime} - + $TCP{id}{$session_id}{StartTime}); + $duration = sprintf("%.0f",$duration); + open (REPLAY,">$filename") || + die "ERROR40: creating $filename $!\n"; + binmode(REPLAY); # for backward OSs + + # + # Output - Text (keystroke replay) + # + $filename2 = "session_${numtext}.text${service_name}.replay"; + open (REPLAY2,">$filename2") || + die "ERROR41: creating $filename2 $!\n"; + binmode(REPLAY2); # for backward OSs + + + # --- textX11 --- + # + # Create a perl program, that when run itself will print out + # the contents of the server 1-way stream, with pauses based on + # the packet arrival times (replay the session in realtime). + # + print REPLAY2 "#!$PERL\n"; + print REPLAY2 <<'END'; +# +# This is an X11 text replay program. It will replay keystrokes and text +# of an X11 session using the timestamps from the packet log. +# +# USAGE: run the script as normal. You can provide a factor as an +# argument, eg "2" to run twice as fast, or "0.5" to run +# at half time. eg, +# ./session_0002.textX11.replay 2 +# +# Auto generated by Chaosreader. +# +$| = 1; +$factor = $ARGV[0] || 1; +sub ms { + $ms = shift; + $ms = $ms / $factor; + select(undef, undef, undef, $ms); +} +END + + + # --- X11 --- + # + # Create a perl program, that when run itself will print out + # the contents of the server 1-way stream, with pauses based on + # the packet arrival times (replay the session in realtime). + # + print REPLAY "#!$PERL\n"; + print REPLAY <<'END'; +# +# This is a X11 replay program. It will replay a session using +# the timestamps from the packet log, and transpose the X11 protocol so +# that it can be redisplayed. You must have captured from the start +# of the connection for this to work. +# +# USAGE: ./session_0001.X11.replay [-d destination host] [-p port] factor +# +# just run the script as normal. You can provide a factor as an +# argument, eg "2" to run twice as fast, or "0.5" to run +# at half time. eg, +# ./session_0002.X11.replay 2 +# a different host and port can be specified if needed. eg, +# ./session_0002.X11.replay -d 192.168.1.5 -p 6001 +# +# PROBLEMS: you may need to authorise this connection to the X11 server +# before it works. You could run "xhost +hostname" beforehand. +# The playback needs to have captured the start of the connection. +# Check you support the same colour depth as the playback. And check +# the playback file simply isn't too big! (more than 500 Kb is +# currently problematic). +# +# +# Auto generated by Chaosreader. +# + +use IO::Socket; +use Getopt::Std; + +if ($ARGV[0] =~ /^-h$|^--help$/) { &help(); } + +# Try fetching values from $DISPLAY +($hostdef,$portdef) = $ENV{DISPLAY} =~ /([^:]*):(\d*)/; +$hostdef = "127.0.0.1" if $hostdef eq ""; +$portdef += 6000; + +# Command line options take preference +&getopts('d:p:'); +if (defined $opt_d) { $host = $opt_d; } else { $host = $hostdef; } +if (defined $opt_p) { $port = $opt_p; } else { $port = $portdef; } +$factor = $ARGV[0] || 1; +$DEBUG = 0; +$| = 1; + +print "Chaosreader X11 Replay (experimental)\n\n"; +print "Connecting to $host:$port\n"; +print "(problems? try running \"xhost +hostname\" first).\n\n"; + + +# --- Open Socket --- +# +$remote = IO::Socket::INET->new( Proto => "tcp", + PeerAddr => $host, + PeerPort => $port, + ); +unless ($remote) { die "ERROR42: Can't connect to X11 daemon on $host:$port"; } +$remote->autoflush(1); + + +# --- Subroutines --- +# + +# ms - sleeps for specified milliseconds +# +sub ms { + $ms = shift; + $ms = $ms / $factor; + select(undef, undef, undef, $ms); +} +# help - print help +# +sub help { + open (MYSELF,"$0") || die "ERROR43: I can't see myself: $!\n"; + @Myself = ; + close MYSELF; + ### Print comment from top of code + foreach $line (@Myself) { + last if $line !~ /^#/; + next if $line =~ m:^#!/usr/bin/perl:; + $line =~ s/^#/ /; + print $line; + } + print "\n"; + exit(0); +} +# R - recalculates and prints a resourse setting +# The single character subroutine name saves on file space below. +# +sub R { + #$offset = shift; + #$new = $res + $offset; + my $rid = shift; + my $new; + + # final checks + $diff = $rid - $ridbaseold; + $diff = -$diff if $diff < 0; + if ((($rid < $ridbaseold) && ($rid < 8196)) || ($diff > 8196)) { + if ($msb) { return pack('N',$rid); } + else { return pack('V',$rid); } + } + + $new = $rid & $ridmaskold; + $new = $new | $ridbase; + if ($msb) { return pack('N',$new); } + else { return pack('V',$new); } +} +# D - prints the new Drawable, usually the rootid. +# +sub D { + my $rid = shift; + + # final checks + if ($rid >= $ridbaseold) { + # return mapped resource id + return R($rid); + } + # return rootid + if ($msb) { return pack('N',$rootid); } + else { return pack('V',$rootid); } +} +# C - prints the new Colour map. +# +sub C { + my $rid = shift; + + # final checks + if ($rid >= $ridbaseold) { + # return mapped resource id + return R($rid); + } + # return colour map + if ($msb) { return pack('N',$colour); } + else { return pack('V',$colour); } +} +# M - Returns a generic mapped id. Can be rootid, colour, or resource. +# These are used in Xcodes involving a mask. +# +sub M { + my $rid = shift; + + # final checks + if ($rid >= $ridbaseold) { + # return mapped resource id + return R($rid); + } + # return rootid map + if ($rid == $rootidold) { + if ($msb) { return pack('N',$rootid); } + else { return pack('V',$rootid); } + } + # return colour map + if ($rid == $colourold) { + if ($msb) { return pack('N',$colour); } + else { return pack('V',$colour); } + } + # return other + if ($msb) { return pack('N',$rid); } + else { return pack('V',$rid); } +} +# P - Check depth pixels, print warning if there is a mismatch. +# +sub P { + my $depth = shift; + if (! defined $Depth{$depth}) { + print "\nWARNING: requested depth $depth may not be ". + "supported by the server?\n"; + } +} +# debug - print out a value +# +sub debug { + my $word = shift; + my $num = shift; + my $pack = pack("N",$num); + print "$word: $num ", + sprintf("%2.2x%2.2x%2.2x%2.2x\n",unpack("C*",$pack)); +} + + +# --- MAIN --- +# +print "Sending X11 traffic:"; +END + ### Fetch raw data + $xserver = &TCP_Follow_RawA($session_id); + + # + # Determine endian of this transfer. Reading the + # second short on MSB gives 11, and on LSB 2816 + # (at least in testing). We split the difference + # on 256 (is case there is a little variation). + # + ($xjunk,$xvalue,$xjunk) = unpack('nna*',$xserver); + # + # Create aliases for "n" and "N" so I can think + # in big endian. + # + if ($xvalue < 256) { + $xmsb = 1; + $n = "n"; + $N = "N"; + } else { + $xmsb = 0; + $n = "v"; + $N = "V"; + } + my ($success,$major,$minor,$length,$release,$ridbase, + $ridmask,$mbsize,$vendor,$reqmax,$roots,$formats,$ibo, + $bbo,$bslu,$bslp,$keymin,$keymax,$pad,$rest) = + unpack("a2$n$n$n$N$N$N$N$n${n}CCCCCCCC${N}a*",$xserver); + + ($x11type,$rest) = unpack("a${vendor}a*",$rest); + $pad = ((4 - ($vendor % 4)) % 4); + ($junk,$rest) = unpack("a${pad}a*",$rest); + + foreach $i (1..$formats) { + ($junk,$rest) = unpack("a8a*",$rest); + } + ($rootid,$colour,$junk) = unpack("$N${N}a*",$rest); + + # + # Sort the data on the timestamps, calculating timestamp differences + # to record in the replay program. + # + @Times = (); + foreach $time (keys (%{$TCP{id}{$session_id}{time}})) { + if ($TCP{id}{$session_id}{time}{$time}{dir} eq "B") { + push(@Times,$time) + } + } + @Times = sort { $a <=> $b } @Times; + + # + # --- Main Loop --- + # + # (this needs to be a for loop!) + for ($i=0; $i <= $#Times; $i++) { + + ### Calculate time diff if possible + if ($i == $#Times) { + $timediff = 0; + } else { + $timediff = $Times[$i+1] - $Times[$i]; + # just in case, + if ($timediff < 0) { $timediff = 0; } + } + $time = $Times[$i]; + $texttimediff += $timediff; + + ### Fetch data from mem + $data = $TCP{id}{$session_id}{time}{$time}{data}; + + ### If initial request was fetched, + if ($readnow == 0) { + ### Populate $xstart with initial request + $xstart .= $data; + + # + # This triggers the replay program to ask the X11 + # server for the connection data - which + # needs to be processed so that various + # resource offsets can be used later on. + # + if (length($xstart) >= 12) { + $readnow = 1; + } + + } else { + # + # Change resource offsets + # (reads $data and writes to $data) + # + $xrest = $data; + $data = ""; # output stream of data & subs + + # + # Process through X11 codes + # + while (length($xrest) > 0) { + ($xcode,$xbyte,$xlength,$xrest) = + unpack("CC${n}a*",$xrest); + + ### Add xcode to output stream $data + $d = pack("CC${n}",$xcode,$xbyte,$xlength); + # the unusual seds + $d =~ s/\\/\\\\/g; + $d =~ s/'/\\'/g; + $d =~ s/\015\012/'."\\015\\012".'/gs; + $data .= $d; + + # + # Fetch code values from $xrest, and trim + # $xrest. For most requests, the value length + # is a field (bytes 3,4) except for XErrors + # (code 0) where the total length is always 32. + # + if ($xcode == 0) { + $xlv = 28; + } else { + $xlv = ($xlength - 1) * 4; + $xlv = -$xlv if $xlv < 0; + } + while (length($xrest) < $xlv) { + # some more magic + $i++; + last if ($i > $#Times); + + $next = $Times[$i]; + + ### Fetch data from mem + $xrest .= + $TCP{id}{$session_id}{time}{$next}{data}; + } + + ($xvalue,$xrest) = unpack("a${xlv}a*",$xrest); + + #$format = "%2.2x%2.2x " x ($xlv/2); + #printf("X$xcode: $xbyte,$xlength $format\n", + # unpack("C*",$xvalue)); ### Debug + + $xwnum = 0; + @xWords = unpack("${N}*",$xvalue); + + # + # If this is a text event, save the text to the + # textX11 replay program. + # + if (($xcode == 76) || ($xcode == 77)) { + + # Check if this is a xImageText16Req + if ($xcode == 77) { $xbyte *= 2; } + + ($pad,$y,$chars) = + unpack("a10${n}a$xbyte",$xvalue); + if ($yold != $y) { $chars = "\n$chars"; } + + ### Clean the data a little (order important) + $chars =~ s/\\/\\\\/g; + $chars =~ s/'/\\'/g; + $chars =~ s/\0//g; + + ### Now output the data in the replay program + print REPLAY2 "print '" . $chars . "';\n"; + + ### This causes the replay program to pause + print REPLAY2 "ms($texttimediff);\n" + unless $texttimediff < 0.002; + + $yold = $y; + $texttimediff = 0; + } + # + # Process a text scroll event (by using 62 - copy area) + # + if ($xcode == 62) { + print REPLAY2 "print \"\\n\";\n"; + $chars = "\n"; + } + + + # + # If this is a create window event, check the depth. + # + if (($xcode == 1) && ($checkdepth == 0)) { + $data .= "',P($xbyte),'"; + $checkdepth = 1; + } + + # + # Print the X11 data with embedded subroutines + # to transpose the resource IDs. + # + foreach $xw (@xWords) { + $xwnum++; + if ($X11_Codes[$xcode][$xwnum] == 1) { + $data .= "',R($xw),'"; + #print "XCODER: $xcode, $xwnum\n"; + } elsif ($X11_Codes[$xcode][$xwnum] == 2) { + $data .= "',D($xw),'"; + #print "XCODED: $xcode, $xwnum\n"; + } elsif ($X11_Codes[$xcode][$xwnum] == 3) { + $data .= "',C($xw),'"; + #print "XCODEC: $xcode, $xwnum\n"; + } elsif ($X11_Codes[$xcode][$xwnum] == 4) { + $data .= "',M($xw),'"; + #print "XCODEM: $xcode, $xwnum\n"; + } else { + $d = pack("$N",$xw); + $d =~ s/\\/\\\\/g; + $d =~ s/'/\\'/g; + $d =~ s/\015\012/'."\\015\\012".'/gs; + $data .= $d; + } + } + } + } + + # + # Now output the data in the replay program + # + print REPLAY "print '.';\n"; + print REPLAY "print \$remote '" . $data . "';\n"; + + if ($readnow == 1) { + $readnow = 2; + print REPLAY "\$msb = $xmsb;\n"; + print REPLAY "\$ridbaseold = $ridbase;\n"; + print REPLAY "\$ridmaskold = $ridmask;\n"; + print REPLAY "\$rootidold = $rootid;\n"; + print REPLAY "\$colourold = $colour;\n"; + # + # The following code implements the client to + # server connection - we need to read the + # resource and window IDs which are necessary + # when transposing the replay traffic to + # these new values. + # + print REPLAY <<'END'; +if ($msb) { + $n = "n"; + $N = "N"; +} else { + $n = "v"; + $N = "V"; +} + + +read($remote,$in,40); # (xConnSetup) +($success,$major,$minor,$length,$release,$ridbase,$ridmask,$mbsize,$vendor, +$reqmax,$roots,$formats,$ibo,$bbo,$bslu,$bslp,$keymin,$keymax,$pad) = +unpack("a2$n$n$n$N$N$N$N$n${n}CCCCCCCC${N}a*",$in); + +read($remote,$in,$vendor); +print "\nX11 Server Type: $in\n"; +read($remote,$in,((4 - ($vendor % 4)) % 4)); + +foreach $i (1..$formats) { + read($remote,$in,8); # (xPixmapFormat) + ($depth,$junk) = unpack("Ca*",$in); + $Depth{$depth} = 1; + next if $depth == 1; + print "X11 server supports $depth bit resolution\n"; +} +read($remote,$in,8); # (xWindowRoot) +($rootid,$colour,$junk) = unpack("$N$N",$in) unless defined $rootid; + +if ($DEBUG) { + debug("Resource ID new: ",$ridbase); + debug("Resource ID old: ",$ridbaseold); + debug("Root ID new: ",$rootid); + debug("Root ID old: ",$rootidold); + debug("Colour map new: ",$colour); + debug("Colour map old: ",$colourold); +} +END + } + + # + # This causes the replay program to pause + # + print REPLAY "ms($timediff);\n" + unless $timediff < 0.002; # (efficiency). + } + print REPLAY "print \"\n\";\n"; + print REPLAY "close \$remote;\n"; + close REPLAY; + + ### Better make it executable + chmod (0755, "$filename"); + + close REPLAY2; + ### Better make it executable + chmod (0755, "$filename2"); + + ### Global Vars + $Index{HTML}[$number] .= "
  • $filename" . + " $duration seconds
  • \n"; + $Index{HTML}[$number] .= "
  • $filename2" . + " $duration seconds
  • \n"; + $Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s seconds\n", + '"' , " $filename","",$duration); + $Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s seconds\n", + '"' , " $filename2","",$duration); +} + + + +# Save_Session_VNCReplay_andHTML - Save a replay program for this session. +# This creates a program that is used in conjunction with vncviewer. +# It also saves the HTML version (it would have been redundant to +# create a seperate subroutine for that). +# +sub Save_Session_VNCReplay_andHTML { + my $session_id = shift; + my $number = shift; + my $service_name = shift; + my $session_text = shift; + my $numtext = sprintf("%04d",$number); + my ($filename,$filename2,$filename3,$duration,$code,$rest,$extra, + $length,$start,$junk,$down,$value,$data,$oldtimediff,$printed,$chars, + $char,$timediff,$checkdepth,$html); + my @xWords; + + $oldtimediff = 0; + $printed = 0; + $html = ""; + + + # + # Output - Text (keystroke replay) + # + $filename2 = "session_${numtext}.text${service_name}.replay"; + open (REPLAY2,">$filename2") || + die "ERROR44: creating $filename2 $!\n"; + binmode(REPLAY2); # for backward OSs + + # + # --- textVNC --- + # + # Create a perl program, that when run itself will print out + # the contents of the client 1-way stream, with pauses based on + # the packet arrival times (replay the session in realtime). + # + print REPLAY2 "#!$PERL\n"; + print REPLAY2 <<'END'; +# +# This is an VNC text replay program. It will replay keystrokes from +# a VNC session using the timestamps from the packet log. +# +# USAGE: run the script as normal. You can provide a factor as an +# argument, eg "2" to run twice as fast, or "0.5" to run +# at half time. eg, +# ./session_0002.textVNC.replay 2 +# +# Auto generated by Chaosreader. +# +$| = 1; +$factor = $ARGV[0] || 1; +sub ms { + $ms = shift; + $ms = $ms / $factor; + select(undef, undef, undef, $ms); +} +END + + # + # Sort the data on the timestamps, calculating timestamp differences + # to record in the replay program. + # + @Times = (); + foreach $time (keys (%{$TCP{id}{$session_id}{time}})) { + if ($TCP{id}{$session_id}{time}{$time}{dir} eq "B") { + push(@Times,$time) + } + } + @Times = sort { $a <=> $b } @Times; + + # + # --- Main Loop --- + # + # (this needs to be a for loop!) + for ($i=0; $i <= $#Times; $i++) { + + ### Calculate time diff if possible + if ($i == $#Times) { + $timediff = 0; + } else { + $timediff = $Times[$i+1] - $Times[$i]; + # just in case, + if ($timediff < 0) { $timediff = 0; } + } + $time = $Times[$i]; + + ### Fetch data from mem + $data = $TCP{id}{$session_id}{time}{$time}{data}; + ($code) = unpack("C",$data); + + $chars = ""; + + # skip code 0's + if ($code > 0) { + # + # Process through VNC client codes + # + $chars = ""; + while (length($data) > 0) { + ($code) = unpack("C",$data); + $length = $VNC_Code_Size{$code}; + $length--; + last if $length <= 0; + + # Fetch this code only + ($code,$value,$data) = unpack("Ca${length}a*",$data); + + ### Process Key Pressed + if ($code == 4) { + ($down,$junk,$extra,$char) = unpack("Ca4Ca",$value); + + next if $down == 0; # record key-ups + + if ($extra == 0) { + $chars .= $char; + } else { + if (defined $KeyCode{vnc}{0}{$char}) { + $chars .= $KeyCode{vnc}{0}{$char}; + } + } + $html .= $chars; + } + } + + } + + $chars =~ s/\\/\\\\/g; + $chars =~ s/'/\\'/g; + + ### Now output the data in the replay program + unless (length($chars) == 0) { + print REPLAY2 "ms($oldtimediff);\n" + unless $oldtimediff < 0.002; + + ### Print the data + print REPLAY2 "print '" . $chars . "';\n"; + + # these counters are for efficiency, otherwise + # we print too many sequiential sleeps + $printed = 1; + $oldtimediff = 0; + } else { + $printed = 0; + $oldtimediff += $timediff; + next; + } + + ### This causes the replay program to pause + print REPLAY2 "ms($timediff);\n" + unless $timediff < 0.002; + } + close REPLAY2; + + ### Better make it executable + chmod (0755, "$filename2"); + + + # --- HTML --- + # + # Create a HTML page showing the keystrokes + + ### Clean up html + $html = &Desex_HTML($html); + + ### Output + $filename3 = "session_${numtext}.text${service_name}${ext}.html"; + open (OUT,">$filename3") ||die "ERROR45: file create, $filename3: $!\n"; + binmode(OUT); + print OUT "\n\n" . + "

    $service_name: $session_text

    \n" . + "

    File $Arg{infile}, Session $number

    \n" . + "
    \n" .
    +         "" .$html. "
    \n\n\n"; + close OUT; + + ### Global Vars + $length = length($html); + $Index{HTML}[$number] .= + "
  • keystrokes
  • \n"; + $Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n", + '"' , " $filename3","",$length); + + + # + # Output - Main VNC replay program + # + $filename = "session_${numtext}.${service_name}.replay"; + $duration = ($TCP{id}{$session_id}{EndTime} - + $TCP{id}{$session_id}{StartTime}); + $duration = sprintf("%.0f",$duration); + open (REPLAY,">$filename") || + die "ERROR46: creating $filename $!\n"; + binmode(REPLAY); # for backward OSs + + # + # --- VNC --- + # + # Create a perl program, that when run itself will create a + # playback VNC server that listens on a port. When a vncviewer + # connects, the contents of the server 1-way stream arew played back, + # with pauses. + # + print REPLAY "#!$PERL\n"; + print REPLAY <<'END'; +# +# This is a VNC replay program. This runs as a server and listens on a port, +# then vncviewer is run to connect to that port - at which point the playback +# commences. +# +# USAGE: ./session_0001.VNC.replay [-p port] factor +# +# just run the script as normal. You can provide a factor as an +# argument, eg "2" to run twice as fast, or "0.5" to run +# at half time. eg, +# ./session_0002.VNC.replay 2 +# a different host and port can be specified if needed. eg, +# ./session_0002.VNC.replay -p 5925 +# +# After the script is running, connect using vncviewer. eg, +# vncviewer -viewonly localhost:25 +# +# PROBLEMS: The playback needs to have captured the start of the connection, +# you need to be at the same colour depth as the playback (or more may +# work), and your screen should be at least as big as the playback +# resolution. Newer versions of vncviewer may be tuned to match the +# playback (eg "-8bit"). +# +# Auto generated by Chaosreader. +# + +use IO::Socket; +use Getopt::Std; +use Net::hostent; + +$| = 1; + +if ($ARGV[0] =~ /^-h$|^--help$/) { &help(); } + +# Command line options take preference +&getopts('p:'); +if (defined $opt_p) { $port = $opt_p; } else { $port = 5921; } +$vncport = $port - 5900; +if ($vncport < 0) { die "ERROR47: Port $port too low, use at least 5901.\n"; } +$factor = $ARGV[0] || 1; +$DEBUG = 0; + +print "Chaosreader VNC Replay (experimental)\n\n"; +print "Listening on port $port...\n"; + + +# --- Open Socket --- +# +$server = IO::Socket::INET->new( Proto => 'tcp', + LocalPort => $port, + Listen => SOMAXCONN, + Reuse => 1); + +die "can't setup server" unless $server; +unless ($server) { + die "ERROR48: Can't open port $port. Try a different port."; +} + +print <; + close MYSELF; + ### Print comment from top of code + foreach $line (@Myself) { + last if $line !~ /^#/; + next if $line =~ m:^#!/usr/bin/perl:; + $line =~ s/^#/ /; + print $line; + } + print "\n"; + exit(0); +} + + +# +# --- MAIN --- +# + +### Wait for connection +$client = $server->accept(); +$client->autoflush(1); + +print "Sending VNC traffic:"; + +END + + # + # Sort the data on the timestamps, calculating timestamp differences + # to record in the replay program. + # + @Times = (); + foreach $time (keys (%{$TCP{id}{$session_id}{time}})) { + if ($TCP{id}{$session_id}{time}{$time}{dir} eq "A") { + push(@Times,$time) + } + } + @Times = sort { $a <=> $b } @Times; + + # + # --- Main Loop --- + # + # (this needs to be a for loop!) + for ($i=0; $i <= $#Times; $i++) { + + ### Calculate time diff if possible + if ($i == $#Times) { + $timediff = 0; + } else { + $timediff = $Times[$i+1] - $Times[$i]; + # just in case, + if ($timediff < 0) { $timediff = 0; } + } + $time = $Times[$i]; + + ### Fetch data from mem + $data = $TCP{id}{$session_id}{time}{$time}{data}; + + $data =~ s/\\/\\\\/g; + $data =~ s/'/\\'/g; + $data =~ s/\015\012/'."\\015\\012".'/gs; + + # + # Now output the data in the replay program + # + print REPLAY "print '.';\n"; + print REPLAY "print \$client '" . $data . "';\n"; + + # + # This causes the replay program to pause + # + print REPLAY "ms($timediff);\n" + unless $timediff < 0.002; # (efficiency). + } + print REPLAY "print \"\n\";\n"; + print REPLAY "close \$client;\n"; + close REPLAY; + + ### Better make it executable + chmod (0755, "$filename"); + + ### Global Vars + $Index{HTML}[$number] .= "
  • $filename" . + " $duration seconds
  • \n"; + $Index{HTML}[$number] .= "
  • $filename2" . + " $duration seconds
  • \n"; + $Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s seconds\n", + '"' , " $filename","",$duration); + $Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s seconds\n", + '"' , " $filename2","",$duration); +} + + + +# Save_SMTP_Emails - Save emails from an SMTP session. +# +sub Save_SMTP_Emails { + my ($filename); + my $session_id = shift; + my $number = shift; + my $service_name = "smtp"; + my $numtext = sprintf("%04d",$number); + + + ### Full - Input + $snmp_data = &TCP_Follow_RawB($session_id); + + ### Full - Processing + @Snmp_parts = split(/\r\n\.\r\n|\n\.\n/,$snmp_data); + + ### LOOP + $partnum = 0; + foreach $snmp_part (@Snmp_parts) { + + next unless $snmp_part =~ /DATA/; + $partnum++; + $parttext = sprintf("%02d",$partnum); + + ### Part - Processing + $snmp_part =~ s/^.*DATA\r?\n//s; # '/s;' is new perl5, + # else '/;' with $* = 1 + + ### Part - Output + if ($TCP{id}{$session_id}{Partial}) { $ext = ".partial"; } + else { $ext = ""; } + $filename = "session_${numtext}.part_${parttext}." . + "${service_name}${ext}.email"; + open (OUT,">$filename") || + die "ERROR50: file create, $filename: $!\n"; + binmode(OUT); # for backward OSs + print OUT $snmp_part; + close OUT; + + ### Part - Global Vars + my $length = length($snmp_part); + $Index{HTML}[$number] .= "
  • $filename" . + " $length bytes
  • \n"; + $Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n", + '"' , " $filename","",$length); + } +} + + +# Save_HTTP_Files - Save HTTP components. +# +sub Save_HTTP_Files { + my ($filename); + my $session_id = shift; + my $number = shift; + my $service_name = shift; + my $numtext = sprintf("%04d",$number); + + ### Full - Input + $http_session = &TCP_Follow_RawA($session_id); + + ### Full - Processing + @HttpParts = split(/HTTP\/[0-9.]* /,$http_session); + + ### LOOP + $partnum = 0; + foreach $http_part (@HttpParts) { + + ### Part - Processing + ($http_header,$http_data) = split(/\r\n\r\n|\n\n/,$http_part,2); + next if $http_data eq ""; + next if length($http_data) < 8; + $partnum++; + $parttext = sprintf("%02d",$partnum); + + ### Part - Checks + $http_type = &File_Type($http_data); + if ($TCP{id}{$session_id}{Partial}) { $ext = ".partial"; } + else { $ext = ""; } + + ### Part - Output + $filename = "session_${numtext}.part_$parttext${ext}." . + "$http_type"; + open (OUT,">$filename") || + die "ERROR51: file create, $filename: $!\n"; + binmode(OUT); # for backward OSs + print OUT $http_data; + close OUT; + + ### Part - Global Vars + my $length = length($http_data); + $Index{HTML}[$number] .= "
  • $filename" . + " $length bytes
  • \n"; + $Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n", + '"' , " $filename","",$length); + if (&Is_Image($http_type)) { + $Image{HTML}[$number]{links} .= + " "; + $Image{notempty} = 1; + } + } +} + + +# Save_NFS_File - Save NFS file. Only works well for some files, if the NFS +# header can't be processed, a "*.nfs.raw" file is created. +# +sub Save_NFS_File { + my ($filename); + my $session_id = shift; + my $number = shift; + my $service_name = "nfs"; + my $numtext = sprintf("%04d",$number); + + ### Input + my $nfs_raw = &TCP_Follow_RawB($session_id); + + ### Processing + ($nfs_start,$nfs_size,$nfs_end) = unpack('a56a4a*',$nfs_raw); + $nfs_sizeint = unpack("N",$nfs_size); + ($nfs_start,$nfs_data) = split(/$nfs_size....$nfs_size/,$nfs_end,2); + + ### Checks + if (($nfs_sizeint > 4) && (length($nfs_data) >= $nfs_sizeint)) { + $nfs_type = &File_Type($nfs_data); + if ($nfs_sizeint < length($nfs_data)) { + $nfs_data = unpack("a${nfs_sizeint}a*",$nfs_data); + } + } else { + $nfs_type = "raw"; + $nfs_data = $nfs_raw; + } + if ($TCP{id}{$session_id}{Partial}) { $ext = ".partial"; } + else { $ext = ""; } + + ### Output + $filename = "session_${numtext}.part_01.${service_name}${ext}.nfs." . + "$nfs_type"; + open (OUT,">$filename") || die "ERROR52: file create, $filename: $!\n"; + binmode(OUT); # for backward OSs + print OUT $nfs_data; + close OUT; + + ### Global Vars + my $length = length($nfs_data); + $Index{HTML}[$number] .= "
  • $filename" . + " $length bytes
  • \n"; + $Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n", + '"' , " $filename","",$length); +} + + +# TCP_Follow_RawA - process session by TCP Seq numbers 1-way. +# (TCP ASSEMBLY) +# +sub TCP_Follow_RawA { + my $session_id = shift; + my $raw = ""; + + # + # Assemble TCP Sessions. Each hash contains session_ids as keys, + # and the value points to another hash of sequence numbers and data. + # %TCP{id}{}{Aseq} is input, and %TCP{id}{}{RawA} is output. + # + @Seqs = keys (%{$TCP{id}{$session_id}{Aseq}}); + foreach $seq (sort { $a <=> $b } @Seqs) { + $raw .= ${$TCP{id}{$session_id}{Aseq}{$seq}}; + } + + return $raw; +} + + +# TCP_Follow_RawB - process session by TCP Seq numbers 1-way. +# (TCP ASSEMBLY) +# +sub TCP_Follow_RawB { + my $session_id = shift; + my $raw = ""; + + # + # Assemble TCP Sessions. Each hash contains session_ids as keys, + # and the value points to another hash of sequence numbers and data. + # %TCP{id}{}{Aseq} is input, and %TCP{id}{}{RawA} is output. + # + @Seqs = keys (%{$TCP{id}{$session_id}{Bseq}}); + foreach $seq (sort { $a <=> $b } @Seqs) { + $raw .= ${$TCP{id}{$session_id}{Bseq}{$seq}}; + } + + return $raw; +} + + +# Pick_Service_Port - pick which port is the server. Usually is the lower +# number, however check if the direction is already known (eg SYN). +# The port arguments will not often be needed. +# +# NOTE: This code is different to Generate_TCP_IPs - which does the "<->"'s +# +sub Pick_Service_Port { + my $type = shift; + my $id = shift; + my $porta = shift; + my $portb = shift; + my $from_server = 0; + my ($hi,$low); + + # Catch active FTP, etc. + ($low,$hi) = sort { $a <=> $b } ($porta,$portb); + if ($low < 100) { + return ($low,$hi); + } + + if ($type eq "TCP") { + if (defined $TCP{id}{$id}{source}) { + if ($TCP{id}{$id}{source} eq $TCP{id}{$id}{src}) { + return ($TCP{id}{$id}{dest_port},$TCP{id}{$id}{src_port}); + } else { + return ($TCP{id}{$id}{src_port},$TCP{id}{$id}{dest_port}); + } + } + } elsif ($type eq "UDP") { + return ($UDP{id}{$id}{dest_port},$UDP{id}{$id}{src_port}); + } + + # resort to a sort + return sort { $a <=> $b } ($porta,$portb); +} + + +# Generate_SessionID - input source and dest IPs and ports, and generate +# a unique session_id based on them. this is done by sorting on +# ports and then IPs. Also returns a flag if the packet may be +# assumed to be from_server - where the lowest port is assumed to +# be the server (unless TCP SYNs have been observed). +# +sub Generate_SessionID { + my $ip_src = shift; + my $tcp_src_port = shift; + my $ip_dest = shift; + my $tcp_dest_port = shift; + my $type = shift; + my $from_server = 0; + my $session_id; + + # + # Generate session_id string using host:port,host:port sorted on + # port (low port last). + # + if ($tcp_src_port < $tcp_dest_port) { + $session_id = "$ip_dest:$tcp_dest_port,$ip_src:$tcp_src_port"; + $from_server = 1; + } elsif ($tcp_src_port > $tcp_dest_port) { + $session_id = "$ip_src:$tcp_src_port,$ip_dest:$tcp_dest_port"; + $from_server = 0; + } else { + $session_id =join(",",sort("$ip_src:$tcp_src_port", + "$ip_dest:$tcp_dest_port")); + $from_server = 1; + } + + if ($type eq "TCP") { + if (defined $TCP{id}{$session_id}{source}) { + if ($TCP{id}{$session_id}{source} eq $ip_dest) { + $from_server = 1; + } else { + $from_server = 0; + } + } + } + return ($session_id,$from_server); +} + + + +# Generate_TCP_IDs - generate a text and html version of the session ID, that +# displays direction of the TCP session if SYNs and ACKs were +# observed, else uses a "<->" symbol to represent unknown +# direction. TCP only. +# +sub Generate_TCP_IDs { + my $session_id = shift; + my ($ip_src,$tcp_src_port,$ip_dest,$tcp_dest_port,$text,$html); + + # try this direction, + $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}; + + if (defined $TCP{id}{$session_id}{source}) { + if ($TCP{id}{$session_id}{source} eq $ip_dest) { + # nope, switch ends + $ip_src = $TCP{id}{$session_id}{dest}; + $ip_dest = $TCP{id}{$session_id}{src}; + $tcp_src_port = $TCP{id}{$session_id}{dest_port}; + $tcp_dest_port = $TCP{id}{$session_id}{src_port}; + } + $text = "$ip_src:$tcp_src_port -> $ip_dest:$tcp_dest_port"; + $html = "$ip_src:$tcp_src_port -> $ip_dest:$tcp_dest_port"; + } else { + $text = "$ip_src:$tcp_src_port <-> $ip_dest:$tcp_dest_port"; + $html = "$ip_src:$tcp_src_port <-> " . + "$ip_dest:$tcp_dest_port"; + } + + return ($text,$html); +} + + + +# Generate_IP_ID - input source IP, dest IP and ident, and generate a +# unique ip_id based on them. This is necessary for IP +# fragmentation reassembely. Normally we would assume that +# the IP_ident was unique - however this program could +# process traffic from many different hosts over a long +# period of time - idents alone could clash. +# +sub Generate_IP_ID { + my $ip_src = shift; + my $ip_dest = shift; + my $ip_ident = shift; + my $ip_id; + + # + # Generate ip_id string using host:host:ident sorted on IP. + # + # + $ip_id = join(",",sort("$ip_src","$ip_dest")) . ",$ip_ident"; + + return $ip_id; +} + + + +# Read_Tcpdump_Record - Read the next tcpdump record, will "last" if +# there are no more records. +# +sub Read_Tcpdump_Record { + my $more; + + ### Fetch record header + $length = read(INFILE,$header_rec,($integerSize * 2 + 8)); + + ### Quit main loop if at end of file + last if $length < 16; + + ### Throw out extra info in tcpdump/modified1 format + if ($STYLE =~ /^modified/) { + $length = read(INFILE,$more,8); + } + + $frame++; + + ## Unpack header, endian sensitive + if ($STYLE =~ /1$/) { + ($tcpdump_seconds,$tcpdump_msecs,$tcpdump_length, + $tcpdump_length_orig) + = unpack('NNNN',$header_rec); + } else { + ($tcpdump_seconds,$tcpdump_msecs,$tcpdump_length, + $tcpdump_length_orig) + = unpack('VVVV',$header_rec); + } + $length = read(INFILE,$tcpdump_data,$tcpdump_length); + $tcpdump_drops = $tcpdump_length_orig - $tcpdump_length; +} + + +# Read_Snoop_Record - Read the next snoop record, will "last" if +# there are no more records. +# +sub Read_Snoop_Record { + ### Fetch record header + $length = read(INFILE,$header_rec,24); + + ### Quit main loop if at end of file + last if $length < 24; + + $frame++; + + ### Unpack header + ($snoop_length_orig,$snoop_length_inc,$snoop_length_rec,$snoop_drops, + $snoop_seconds,$snoop_msecs) = unpack('NNNNNN',$header_rec); + $length = read(INFILE,$snoop_data,$snoop_length_inc); + $skip = read(INFILE,$pad,($snoop_length_rec - $snoop_length_inc - 24)); +} + + +# Load_Index_File - Load the master index file "index.file" into @Master +# +sub Load_Index_File { + + my ($path,$dir,$file,$start,$end,$duration,$index); + + # + # Load index.file lines into memory + # + open (FILES,"index.file") || die "ERROR53: Can't read index.file: $!\n" + ."Standalone mode needs to have run recently from this directory.\n\n"; + + chomp(@Files = ); + close FILES; + + # + # Populate @Master + # + $index = 0; + foreach $path (@Files) { + ($dir,$file,$duration,$start,$end) = split(/\t/,$path); + $Master[$index]{starttime} = $start; + $Master[$index]{endtime} = $end; + $Master[$index]{dir} = $dir; + $Master[$index]{file} = $file; + $Master[$index]{duration} = $duration; + $Master[$index]{size} = -s "$dir/$file"; + $index++; + } +} + + +# Load_Etc_Services - load /etc/services lookup table into memory, +# into %Services_TCP and %Services_UDP. +# +sub Load_Etc_Services { + my ($line,$name,$service); + + ### Hardcoded + %Services_TCP = (20 => "ftp-data", + 21 => "ftp", + 23 => "telnet", + 25 => "smtp", + 80 => "web", + 109 => "pop2", + 110 => "pop3", + 143 => "imap", + 513 => "login", + 514 => "shell", + 3128 => "web", + 4110 => "irc4110", + 5000 => "irc5000", + 6000 => "X11", + 6660 => "irc", + 6665 => "irc", + 6666 => "irc", + 6667 => "irc", + 6668 => "irc", + 6669 => "irc", + 7000 => "irc7000", + 8000 => "irc8000", + 8080 => "web", + 9000 => "irc9000"); + # non standard IRC ports include the number in their name + + foreach (@Save_As_X11_Playback_Ports) { + $Services_TCP{$_} = "X11"; + } + + foreach (@Save_As_VNC_Playback_Ports) { + $Services_TCP{$_} = "VNC"; + } + + %Services_UDP = (53 => "dns"); + + ### File input + open(SERVICES,"/etc/services") || return; + while ($line = ) { + next if $line =~ /^#|^\s*$/; # skip comments, blank lines. + if ($line =~ /\d\/tcp/) { + $is_tcp = 1; + } else { + $is_tcp = 0; + } + $line =~ s:/.*::; + ($name,$port) = split(' ',$line); + if ($is_tcp) { + $Services_TCP{$port} = $name; + } else { + $Services_UDP{$port} = $name; + } + + } + close SERVICES; +} + + +# Set_IP_Protocols - Set a lookup hash for IP Protocols to names. +# RFC790, RFC1700. +# +sub Set_IP_Protocols { + %IP_Protocols = (0 => "Reserved", + 1 => "ICMP", + 2 => "Unassigned", + 3 => "Gateway-to-Gateway", + 4 => "CCMC Gateway Monitoring Message", + 5 => "ST", + 6 => "TCP", + 7 => "UCL", + 8 => "Unassigned", + 9 => "Secure", + 10 => "BBN RCC Monitoring", + 11 => "NVP", + 12 => "PUP", + 13 => "Pluribus", + 14 => "Telenet", + 15 => "XNET", + 16 => "Chaos", + 17 => "UDP", + 18 => "Multiplexing", + 19 => "DCN", + 20 => "TAC Monitoring", + 37 => "DDP", + 41 => "SIP", + 42 => "SDRP", + 44 => "IPv6 Frag", + 50 => "SIPP-ESP", + 51 => "SIPP-AH", + 53 => "SWIPE", + 50 => "SDRP", + 58 => "ICMPv6", + 88 => "IGRP", + 94 => "IPIP" + ); +} + +# Set_ICMP_Types - Set a lookup hash for ICMP Types. RFC792. +# +sub Set_ICMP_Types { + %ICMP_Types = (0 => "Echo Reply", + 3 => "Destination Unreachable", + 4 => "Source Quench", + 5 => "Redirect", + 8 => "Echo", + 11 => "Time Exceeded", + 12 => "Parameter Problem", + 13 => "Timestamp", + 14 => "Timestamp Reply", + 15 => "Information Request", + 16 => "Information Reply", + 128 => "Echo", + 129 => "Echo Reply", + 135 => "Neighbor solicitation", + 136 => "Neighbor advertisement" + ); +} + +# Set_Result_Names - Set a lookup hash for squid result codes. +# (This needs some fine tuning). +# +sub Set_Result_Names { + %Result_Names = ("" => "TCP_MISS", + 000 => "TCP_MISS", + 200 => "TCP_HIT", + 302 => "TCP_HIT", + 304 => "TCP_REFRESH_HIT", + 404 => "TCP_NEGATIVE_HIT" + ); +} + +# Set_X11_Codes - creates a lookup hash needed for X11 transposing. +# +sub Set_X11_Codes { + # + # This has a row per X11 code, the row describing the 16 bit + # words that make up the values. "1" means resource id. + # (some values are 8 bit, but are fortunately padded). + # + + @X11_Codes = ( +[ 0 ], # X_Error entry +[ 0, 2, 2, 0, 0, 0, 1, 0,4,4,4,4,4,4,4,4,4,4,4,4 ], # X_CreateWindow 1 +[ 0, 1, 0 ], # X_ChangeWindowAttributes +[ 0, 1 ], # X_GetWindowAttributes +[ 0 ], # X_DestroyWindow? +[ 0 ], # X_DestroySubwindows? +[ 0, 1 ], # X_ChangeSaveSet +[ 0, 1, 1, 0 ], # X_ReparentWindow +[ 0, 1 ], # X_MapWindow +[ 0, 1 ], # X_MapSubwindows +[ 0, 1 ], # X_UnmapWindow 10 +[ 0, 1 ], # X_UnmapSubwindows +[ 0, 1, 0, 4,4,4,4,4,4,4,4,4,4,4,4 ], # X_ConfigureWindow +[ 0, 1 ], # X_CirculateWindow +[ 0, 2 ], # X_GetGeometry +[ 0, 1 ], # X_QueryTree +[ 0, 1 ], # X_InternAtom (? else 0,0) +[ 0 ], # X_GetAtomName? +[ 0, 1, 0, 0, 1, 0 ], # X_ChangeProperty (? else 0,1,0,0,0,0) +[ 0, 1, 0 ], # X_DeleteProperty +[ 0, 2, 0, 0, 0, 0 ], # X_GetProperty 20 +[ 0 ], # X_ListProperties? +[ 0, 1, 0, 0 ], # X_SetSelectionOwner +[ 0 ], # X_GetSelectionOwner +[ 0, 1, 0, 0, 0, 0 ], # X_ConvertSelection +[ 0, 1, 0 ], # X_SendEvent +[ 0, 1, 0, 1, 0, 0 ], # X_GrabPointer +[ 0, 1, 0 ], # X_UngrabPointer? +[ 0, 1, 0, 1, 0, 0 ], # X_GrabButton +[ 0, 1, 0 ], # X_UngrabButton +[ 0, 1, 0, 0 ], # X_ChangeActivePointerGrab 30 +[ 0, 1, 0, 0 ], # X_GrabKeyboard +[ 0, 1, 0 ], # X_UngrabKeyboard? +[ 0, 1, 0, 0 ], # X_GrabKey +[ 0, 1, 0 ], # X_UngrabKey +[ 0, 0, 0 ], # X_AllowEvents +[ 0 ], # X_GrabServer? +[ 0 ], # X_UngrabServer? +[ 0 ], # X_QueryPointer? +[ 0, 1, 0, 0 ], # X_GetMotionEvents +[ 0, 1, 1, 0 ], # X_TranslateCoords 40 +[ 0, 1, 1, 0, 0, 0 ], # X_WarpPointer +[ 0, 1, 0 ], # X_SetInputFocus +[ 0 ], # X_GetInputFocus? +[ 0 ], # X_QueryKeymap? +[ 0, 1, 0 ], # X_OpenFont +[ 0, 1 ], # X_CloseFont +[ 0, 1 ], # X_QueryFont +[ 0, 1 ], # X_QueryTextExtents +[ 0, 0 ], # X_ListFonts +[ 0, 0 ], # X_ListFontsWithInfo 50 +[ 0, 0 ], # X_SetFontPath +[ 0 ], # X_GetFontPath? +[ 0, 1, 2, 0 ], # X_CreatePixmap +[ 0 ], # X_FreePixmap? +[ 0, 1, 2, 0, 4,4,4,4,4,4,4,4,4,4,4,4 ], # X_CreateGC ?(else 0,1,1,0) +[ 0, 1, 0, 4,4,4,4,4,4,4,4,4,4,4,4 ], # X_ChangeGC +[ 0, 1, 1, 0, 4,4,4,4,4,4,4,4,4,4,4,4 ], # X_CopyGC +[ 0, 1, 0 ], # X_SetDashes +[ 0, 1, 0 ], # X_SetClipRectangles +[ 0, 1 ], # X_FreeGC? 60 +[ 0, 1, 0, 0 ], # X_ClearArea +[ 0, 2, 2, 1, 0, 0, 0 ], # X_CopyArea +[ 0, 2, 2, 1, 0, 0, 0, 0 ], # X_CopyPlane +[ 0, 2, 1 ], # X_PolyPoint +[ 0, 2, 1 ], # X_PolyLine +[ 0, 2, 1 ], # X_PolySegment +[ 0, 2, 1 ], # X_PolyRectangle +[ 0, 2, 1 ], # X_PolyArc +[ 0, 2, 1, 0 ], # X_FillPoly +[ 0, 2, 1 ], # X_PolyFillRectangle 70 +[ 0, 2, 1 ], # X_PolyFillArc +[ 0, 2, 1, 0, 0, 0 ], # X_PutImage +[ 0, 2, 0, 0, 0 ], # X_GetImage +[ 0, 2, 1, 0 ], # X_PolyText8 +[ 0, 2, 1, 0 ], # X_PolyText16 +[ 0, 2, 1, 0 ], # X_ImageText8 +[ 0, 2, 1, 0 ], # X_ImageText16 +[ 0, 3, 1, 1 ], # X_CreateColormap +[ 0 ], # X_FreeColormap? +[ 0, 3, 3 ], # X_CopyColormapAndFree 80 +[ 0 ], # X_InstallColormap? +[ 0 ], # X_UninstallColormap? +[ 0 ], # X_ListInstalledColormaps? +[ 0, 3, 0, 0 ], # X_AllocColor +[ 0, 3, 0 ], # X_AllocNamedColor +[ 0, 3, 0 ], # X_AllocColorCells +[ 0, 3, 0, 0 ], # X_AllocColorPlanes +[ 0, 3, 0 ], # X_FreeColors +[ 0, 3 ], # X_StoreColors +[ 0, 3, 0, 0 ], # X_StoreNamedColor 90 +[ 0, 3 ], # X_QueryColors +[ 0, 3, 0 ], # X_LookupColor +[ 0, 1, 1, 1, 0, 0, 0, 0 ], # X_CreateCursor +[ 0, 1, 1, 1, 0, 0, 0, 0 ], # X_CreateGlyphCursor +[ 0 ], # X_FreeCursor? +[ 0, 1, 0, 0, 0 ], # X_RecolorCursor +[ 0, 2, 0 ], # X_QueryBestSize +[ 0, 1 ], # X_QueryExtension (? else 0,0) +[ 0, 0, 0 ], # X_ListExtensions? +[ 0, 1, 0 ], # X_ChangeKeyboardMapping 100 +[ 0, 1, 0 ], # X_GetKeyboardMapping +[ 0, 0, 4,4,4,4,4,4,4,4,4,4,4,4 ], # X_ChangeKeyboardControl +[ 0, 0, 0 ], # X_GetKeyboardControl? +[ 0 ], # X_Bell +[ 0, 0, 0 ], # X_ChangePointerControl +[ 0, 0, 0 ], # X_GetPointerControl? +[ 0, 0, 0 ], # X_SetScreenSaver +[ 0, 0, 0 ], # X_GetScreenSaver? +[ 0, 0 ], # X_ChangeHosts +[ 0 ], # X_ListHosts 110 +[ 0 ], # X_SetAccessControl +[ 0 ], # X_SetCloseDownMode +[ 0, 0, 0 ], # X_KillClient? +[ 0, 1, 0 ], # X_RotateProperties +[ 0 ], # X_ForceScreenSaver +[ 0 ], # X_SetPointerMapping +[ 0, 0, 0 ], # X_GetPointerMapping? +[ 0 ], # X_SetModifierMapping +[ 0, 0, 0 ], # X_GetModifierMapping? +[ 0 ], # undef 120 +[ 0 ], # undef +[ 0 ], # undef +[ 0 ], # undef +[ 0 ], # undef +[ 0 ], # undef +[ 0 ], # undef +[ 0, 0, 0 ] # X_NoOperation 127 + ); + +} + +# Set_X11_KeyCodes - creates a lookup hash of X11 Key codes needed +# to generate coloured 2-way HTML X11 reports. +# +sub Set_X11_KeyCodes { + my ($junk,$code,$char1,$char2,$line, + $sun_xmodmap_pke,$linux_xmodmap_pke); + my %Alias; + + # + # These are generated using "xmodmap -pke" (and trimmed a little). + # + $sun_xmodmap_pke = < slash / question ?); + + # naughty chatacrers (some of these generate warnings) + @Alias{"parenleft","parenright","space"} = ("(",")"," "); + @Alias{"Tab","Return","numbersign","comma"} = ("\t","\n","#",","); + + + # + # Populate KeyCode aliase + # + foreach $line (split(/\n/,$sun_xmodmap_pke)) { + ($junk,$code,$junk,$char1,$char2) = split(' ',$line); + if (defined $Alias{$char1}) { $char1 = $Alias{$char1}; } + if (defined $Alias{$char2}) { $char2 = $Alias{$char2}; } + if (length($char1) > 1) { $char1 = "."; } + if (length($char2) > 1) { $char2 = "."; } + $KeyCode{sun}{0}{$code} = $char1; + $KeyCode{sun}{1}{$code} = $char2; + } + foreach $line (split(/\n/,$linux_xmodmap_pke)) { + ($junk,$code,$junk,$char1,$char2) = split(' ',$line); + if (defined $Alias{$char1}) { $char1 = $Alias{$char1}; } + if (defined $Alias{$char2}) { $char2 = $Alias{$char2}; } + if (length($char1) > 1) { $char1 = "."; } + if (length($char2) > 1) { $char2 = "."; } + $KeyCode{linux}{0}{$code} = $char1; + $KeyCode{linux}{1}{$code} = $char2; + } + +} + + +# Set_VNC_Codes - set globals for VNC. +# +sub Set_VNC_Codes { + + ### set client code to request size hash. + %VNC_Code_Size = ( 0 => 20, + 1 => 6, + 2 => 4, + 3 => 10, + 4 => 8, + 5 => 6, + 6 => 8 ); + + ### Some essential keysyms + $KeyCode{vnc}{0}{"\010"} = "\b"; + $KeyCode{vnc}{0}{"\011"} = "\t"; + $KeyCode{vnc}{0}{"\015"} = "\n"; + +} + + + +# Touch_Vars - This is stops perl -w warnings about vars used only once. +# Part of my todo list is to cull this list. +# +# +sub Touch_Vars { + # + # Perl < 5.6 code + # + #use vars qw($ip_ttl $udp_checksum $ip_ident $tcp_length_data + #$ip_tos $tcp_options $opt_A $opt_D $tcp_header_rest $opt_J + #$opt_P $opt_U $opt_X $opt_e $opt_h $opt_i $pad $opt_j + #$snoop_length_orig $http_header $opt_p $opt_q $opt_r + #$header_rest $tcp_ack $ether_dest $ether_src $skip + #$ip_length $udp_length $ip_options $ip_checksum + #$opt_b $opt_B $opt_l $opt_L $ip_rest $ip_hop $ip_reserved + #$ip_flow $icmp_rest $opt_f $opt_z); + # + # Perl 5.6 code + # + #our ($ip_ttl,$udp_checksum,$ip_ident,$tcp_length_data, + #$ip_tos,$tcp_options,$opt_A,$opt_D,$tcp_header_rest,$opt_J, + #$opt_P,$opt_U,$opt_X,$opt_e,$opt_h,$opt_i,$pad,$opt_j, + #$snoop_length_orig,$http_header,$opt_p,$opt_q,$opt_r, + #$header_rest,$tcp_ack,$ether_dest,$ether_src,$skip, + #$ip_length,$udp_length,$ip_options,$ip_checksum, + #$opt_b,$opt_B,$opt_l,$opt_L,$ip_rest,$ip_hop,$ip_reserved, + #$ip_flow,$icmp_rest,$opt_f,$opt_z); + # + # Perl < 5.6 and 5.6 code (but not elegant) + # + @Once_is_okay = ($ip_ttl,$udp_checksum,$ip_ident,$tcp_length_data, + $ip_tos,$tcp_options,$opt_A,$opt_D,$tcp_header_rest,$opt_J, + $opt_P,$opt_U,$opt_X,$opt_e,$opt_h,$opt_i,$pad,$opt_j, + $snoop_length_orig,$http_header,$opt_p,$opt_q,$opt_r, + $header_rest,$tcp_ack,$ether_dest,$ether_src,$skip, + $ip_length,$udp_length,$ip_options,$ip_checksum,$tcp_rst,$tcp_fin, + $opt_b,$opt_B,$opt_l,$opt_L,$ip_rest,$ip_hop,$ip_reserved, + $ip_flow,$icmp_rest,$opt_f,$opt_z,$junk1,$opt_H,$opt_I,$opt_R); +} + + +# Check_Command - check which is the network sniffing command and save +# it to $command. +# +sub Check_Command { + + # + # Check which OS we are on, die if it looks incompatible + # + if ($^O eq "linux") { + # + # The "-s9999" tells tcpdump to keep a packet up to this + # size, otherwise the default is 68 bytes. Some versions of + # tcpdump allow using "-s0" for unlimited. + # + $command = "tcpdump -s9999 -w"; + } elsif ($^O eq "solaris") { + $command = "snoop -o"; + } else { + die "ERROR54: Can't find the sniffer command for \"$^O\".\n" . + "\t Please use log mode instead.\n"; + } + + # + # Check username + # + if ($ENV{LOGNAME} ne "root") { + print STDERR "WARNING: Are you root? If not, this probably " + . "won't work. Trying anyway...\n"; + } +} + + +# Process_Command_Line_Arguments - this process the command line arguments +# and sets various globals which are kept in %Arg. It also prints +# usage and exists if need be. +# +sub Process_Command_Line_Arguments { + my $result; + + # + # Process Global Defaults into %Arg + # + foreach (@Save_As_HTML_TCP_Ports) { + $Arg{Save_As_TCP_HTML}{$_} = 1; + } + foreach (@Save_As_HTML_UDP_Ports) { + $Arg{Save_As_UDP_HTML}{$_} = 1; + } + foreach (@Save_As_TCP_Playback_Ports) { + $Arg{Save_As_TCP_Playback}{$_} = 1; + } + foreach (@Save_As_UDP_Playback_Ports) { + $Arg{Save_As_UDP_Playback}{$_} = 1; + } + foreach (@Save_As_X11_Playback_Ports) { + $Arg{Save_As_X11_Playback}{$_} = 1; + } + foreach (@Save_As_HTML_X11_Ports) { + $Arg{Save_As_X11_HTML}{$_} = 1; + } + foreach (@Save_As_VNC_Playback_Ports) { + $Arg{Save_As_VNC_Playback}{$_} = 1; + } + + if (defined $ARGV[0]) { + ### Dump full help if asked + &Usage_Full if $ARGV[0] eq "--help"; + + ### Dump massive help if asked + &Usage_Massive if $ARGV[0] eq "--help2"; + } + + # + # Command Line Defaults + # + $Arg{output_raw} = 0; + $Arg{output_hex} = 0; + $Arg{output_UDP} = 1; + $Arg{output_TCP} = 1; + $Arg{output_ICMP} = 1; + $Arg{output_info} = 0; + $Arg{output_apps} = 1; + $Arg{output_index} = 1; + $Arg{keydata} = 0; + $Arg{debug} = 0; + + # + # Check correct switches were used + # + Getopt::Long::Configure ("bundling"); + $result = GetOptions ( + "application!" => \$opt_a, + "a" => \$opt_a, + "e|everything" => \$opt_e, + "h" => \$opt_h, + "info!" => \$opt_i, + "i" => \$opt_i, + "q|quiet" => \$opt_q, + "raw!" => \$opt_r, + "r" => \$opt_r, + "v|verbose" => \$opt_v, + "index!" => \$opt_x, + "x" => \$opt_x, + "A" => \$opt_A, + "H|hex" => \$opt_H, + "I" => \$opt_I, + "R" => \$opt_R, + "U|noudp" => \$opt_U, + "T|notcp" => \$opt_T, + "Y|noicmp" => \$opt_Y, + "X" => \$opt_X, + "D|dir=s" => \$opt_D, + "b|playtcp=s" => \$opt_b, + "B|playudp=s" => \$opt_B, + "l|htmltcp=s" => \$opt_l, + "L|htmludp=s" => \$opt_L, + "m|min=s" => \$opt_m, + "M|max=s" => \$opt_M, + "o|sort=s" => \$opt_o, + "p|port=s" => \$opt_p, + "P|noport=s" => \$opt_P, + "j|ipaddr=s" => \$opt_j, + "J|noipaddr=s" => \$opt_J, + "s|runonce=s" => \$opt_s, + "S|runmany=s" => \$opt_S, + "z|runredo" => \$opt_z, + "f|filter=s" => \$opt_f, + "k|keydata" => \$opt_k, + "debug" => \$opt_debug, + "bench" => \$opt_bench + ); + + # + # Process switches + # + &Usage() if ($opt_h || ! $result); + $Arg{output_raw} = 1 if $opt_r or $opt_v; + $Arg{output_hex} = 1 if $opt_H or $opt_e; + $Arg{output_info} = 1 if $opt_i or $opt_v; + $Arg{quiet} = 1 if $opt_q; + $Arg{output_UDP} = 0 if $opt_U; + $Arg{output_TCP} = 0 if $opt_T; + $Arg{output_ICMP} = 0 if $opt_Y; + $Arg{output_apps} = 0 if ($opt_A || (defined $opt_a && $opt_a eq "0")); + $Arg{output_index} = 0 if ($opt_X || (defined $opt_x && $opt_x eq "0")); + $Arg{output_allhtml} = 1 if $opt_e; + my $extra_TCPplayback = $opt_b; + my $extra_UDPplayback = $opt_B; + my $extra_TCPhtml = $opt_l; + my $extra_UDPhtml = $opt_L; + my $ports_accepted = $opt_p; + my $ports_rejected = $opt_P; + my $ips_accepted = $opt_j; + my $ips_rejected = $opt_J; + $Arg{output_dir} = $opt_D; + $Arg{filter} = $opt_f || ""; + $Arg{minbytes} = 0; + $Arg{maxbytes} = 0; + $Arg{sort} = "time"; + $Arg{keydata} = 1 if $opt_k; + $Arg{debug} = 1 if $opt_debug; + $Arg{bench} = 1 if $opt_bench; + + # + # Check for min/max bytes + # + if (defined $opt_m) { + if ($opt_m =~ /k$/) { + $opt_m =~ s/k$//; + $opt_m *= 1024; + } + $Arg{minbytes} = $opt_m; + } + if (defined $opt_M) { + if ($opt_M =~ /k$/) { + $opt_M =~ s/k$//; + $opt_M *= 1024; + } + $Arg{maxbytes} = $opt_M; + } + + # + # Check for sort option + # + if (defined $opt_o) { + if ($opt_o !~ /^(time|size|type|ip)$/) { + print STDERR "ERROR55: Sort must be \"time\", " . + "\"size\", \"type\" or \"ip\".\n"; + &Usage(); + } + $Arg{sort} = $opt_o; + } + + # + # Check for standalone redo mode + # + if (defined $opt_z) { + $Arg{redo} = 1; + if (defined $Arg{output_dir}) { + # bad luck + die "ERROR56: Can't use an output dir " + . "$Arg{output_dir} in redo mode.\n\n"; + } + } + + # + # Check for standalone mode + # + elsif (defined $opt_s || defined $opt_S) { + $Arg{standalone} = 1; + if (defined $opt_s) { + if ($opt_s =~ /,/) { + die "ERROR57: Unexpected comma found in " . + "\"-s$opt_s\" (did you mean \"-S$opt_s\"?)\n"; + } + $Arg{mins} = $opt_s; + $Arg{count} = 1; + } elsif (defined $opt_S) { + my ($mins,$count) = split(/,/,$opt_S); + $Arg{mins} = $mins; + ### -1 means endless + $Arg{count} = $count || -1; + } + } + + # + # This is normal mode + # + else { + $Arg{normal} = 1; + } + + # + # Build accepted or rejected port list as %Arg{Port_Accepted},... + # + if (defined $ports_accepted) { + $Arg{port_accept} = 1; + foreach $port (split(/,/,$ports_accepted)) { + $Arg{Port_Accepted}{$port} = 1; + } + } + if (defined $ports_rejected) { + $Arg{port_reject} = 1; + foreach $port (split(/,/,$ports_rejected)) { + $Arg{Port_Rejected}{$port} = 1; + } + } + + # + # Build accepted or rejected IP list as %Arg{IP_Accepted},... + # + if (defined $ips_accepted) { + $Arg{ip_accept} = 1; + foreach $ip (split(/,/,$ips_accepted)) { + $Arg{IP_Accepted}{$ip} = 1; + } + } + if (defined $ips_rejected) { + $Arg{ip_reject} = 1; + foreach $ip (split(/,/,$ips_rejected)) { + $Arg{IP_Rejected}{$ip} = 1; + } + } + + # + # Add extra ports to playback or HTML + # + if (defined $extra_TCPplayback) { + foreach $port (split(/,/,$extra_TCPplayback)) { + $Arg{Save_As_TCP_Playback}{$port} = 1; + } + } + if (defined $extra_UDPplayback) { + foreach $port (split(/,/,$extra_UDPplayback)) { + $Arg{Save_As_UDP_Playback}{$port} = 1; + } + } + if (defined $extra_TCPhtml) { + foreach $port (split(/,/,$extra_TCPhtml)) { + $Arg{Save_As_TCP_HTML}{$port} = 1; + } + } + if (defined $extra_UDPhtml) { + foreach $port (split(/,/,$extra_UDPhtml)) { + $Arg{Save_As_UDP_HTML}{$port} = 1; + } + } + + # + # Check infile was provided, or print usage + # + if (! defined $ARGV[0] && ! ($Arg{standalone} || $Arg{redo})) { + &Usage(); + } + @{$Arg{infiles}} = @ARGV; +} + + +# Usage - print command usage and exit. +# +sub Usage { + print "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'] + eg, chaosreader infile # Create application session files, indexes + chaosreader -v infile # Verbose - Create ALL files + chaosreader -i infile # Create info files + chaosreader -r infile # Create raw files + chaosreader -S 2,5 # Standalone - sniff network 5 times by 2 mins. + chaosreader -h # Print a brief help (this) + chaosreader --help # Print verbose help and version + chaosreader --help2 # Print massive help\n\n"; + exit(0); +} + + +# Usage Full - print command usage and exit. +# +sub Usage_Full { + print "Version 0.94, 01-May-2004 + +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) +"; + exit(0); +} + + +# Usage_Massive - print massive help. Actually strip it from the comments +# at the top of the code. +# +sub Usage_Massive { + open (MYSELF,"$0") || die "ERROR58: I can't see myself: $!\n"; + @Myself = ; + close MYSELF; + + ### Print comment from top of code + foreach $line (@Myself) { + last if $line !~ /^#/; + last if $line =~ /^# Todo:/; + next if $line =~ m:^#!/usr/bin/perl:; + $line =~ s/^#/ /; + print $line; + } + print "\n"; + + exit(0); +} + + + +__END__ + +Reminders for myself +==================== +/s for multiline match + + +Comments style: + +# Micro comment + +### Tiny Comment + +# +# Small comment +# + +# +# --- Meduim Comment --- +# + +######################### +# --- Large Comment --- +# + +######################## +# --- Huge Comment --- # +######################## + + +Error message style +=================== + +die "ERROR#: message: $!\n"; + + + +Data types, +=========== + %Arg + -> @infiles + -> output_raw + -> output_hex + -> output_UDP + -> output_info + -> output_apps + -> output_index + -> output_allhtml + -> Save_As_TCP_HTML + -> $port + -> Save_As_UDP_HTML + -> $port + -> Save_As_TCP_Playback + -> $port + -> Save_As_UDP_Playback + -> $port + -> Port_Accepted + -> $port + -> Port_Rejected + -> $port + -> ip_accept + -> ip_reject + -> IP_Accepted + -> $ip + -> IP_Rejected + -> $ip + -> debug + -> standalone + -> redo + -> normal + -> mins + -> count + -> output_dir + -> quiet + -> infile + -> minbytes + -> maxbytes + + %IP + -> time + -> $packet_time + -> ver + -> src + -> dest + -> protocol + -> frag + -> $ip_frag + -> fragged + -> drops + -> id + -> $ip_id + -> StartTime + + %TCP + -> id + -> $session_id + -> src + -> dest + -> source # SYN seen + -> src_port + -> dest_port + -> Aseq + -> $$tcp_seq + -> Bseq + -> $$tcp_seq + -> time + -> $time + -> dir + -> data + -> BothHTML + -> StartTime + -> EndTime + -> size + -> knowndir + + %UDP + -> id + -> $session_id + -> src + -> dest + -> src_port + -> dest_port + -> RawA + -> RawB + -> time + -> $time + -> BothHTML + -> StartTime + -> EndTime + -> size + + %ICMP + -> time + -> type + -> code + -> src + -> dest + -> Partial + -> ver + -> size + + %Count + -> IP + -> IPprotocols + -> TCPports + -> UDPports + -> EtherType + + %CountMaster + (as above) + + %Index + -> @HTML + -> @Text + -> Time_Order + -> $session_timeid + -> Sort_Lookup + -> $session_timeid + + %Image + -> @HTML + -> links + -> info + -> notempty + + %GETPOST + -> @HTML + -> query + -> info + -> notempty + + %Hex + -> $type + -> $session_id + -> offset + -> pos + -> hextext + -> hexhtml + -> viewtext + -> viewhtml + + %Filenames + -> $time + -> filename + -> service + -> session_id + + @Master + -> starttime + -> endtime + -> duration + -> size + -> dir + -> file + + + +
    $type" . + "$CountMaster{EtherType}{$type}