Files
bin/chaosreader0.94
T
2026-06-11 15:14:00 +02:00

6833 lines
182 KiB
Perl
Executable File

#!/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} .=
"<font color=\"blue\">$copy</font>";
#
# 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} .=
"<font color=\"red\">$copy</font>";
#
# 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} .=
"<font color=\"blue\">$copy</font>";
}
#
# 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] = "<tr><td><i>$number.</i></td>" .
"<td><b>$starttime</b></td><td>$duration s</td><td> " .
"<font color=\"blue\">$id_html " .
"</font></td><td> <font color=\"red\">" .
"$service_name</font></td><td> <font color=\"green\"> " .
"$length bytes</font></td><td>\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} = "<tr><td><i>$number.</i>" .
"</td><td><b>$starttime</b></td><td> " .
"<font color=\"blue\">$id_html </font></td><td><td>\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} = "<tr><td><i>$number.</i>" .
"</td><td><b>$starttime</b></td><td> " .
"<font color=\"blue\">$id_html </font></td><td><td>\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] .= "<li><a href=\"$filename\">raw</a> ";
#
# 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] .= "<a href=\"$filename\">raw1</a> ";
#
# 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] .= "<a href=\"$filename\">raw2</a></li> ";
}
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 &lt;-&gt; $ip_dest:$udp_dest_port";
$Index{HTML}[$number] = "<tr><td><i>$number.</i></td>" .
"<td><b>$starttime</b></td><td>$duration s</td><td> " .
"<font color=\"blue\">$id_html " .
"</font></td><td> <font color=\"red\">" .
"<i>$service_name</i></font></td><td> <font color=\"green\"> " .
"$length bytes</font></td><td>\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] .= "<li><a href=\"$filename\">raw</a> ";
#
# 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] .= "<a href=\"$filename\">raw1</a> ";
#
# 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] .= "<a href=\"$filename\">raw2</a></li> ";
}
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 -&gt; $ip_dest";
$Index{HTML}[$number] = "<tr><td><i>$number.</i></td>" .
"<td><b>$starttime</b></td><td>0 s</td><td> " .
"<font color=\"blue\">$id_html" .
"</font></td><td> <font color=\"red\">" .
"<i>$icmp_ver</i></font></td><td> <font color=\"green\"> " .
"$length bytes</font></td><td>$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] .= "<li><a href=\"$filename\">raw</a> ";
}
#
# --- 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} .=
"<font color=\"red\">GET</font></td><td width=70%>";
$GETPOST{notempty} = 1;
} else {
$GETPOST{HTML}[$number]{query} .= "<hr>\n";
}
#
# Generate table of query key value pairs
#
$GETPOST{HTML}[$number]{query} .= "$site<br><table border=1>\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/</&lt;/g;
$value =~ s/>/&gt;/g;
$value =~ s/\n/<br>\n/g;
$GETPOST{HTML}[$number]{query} .=
"<tr><td><b>$var</b></td>" .
"<td><font face=\"Courier\">$value</font></td></tr>\n";
}
$GETPOST{HTML}[$number]{query} .= "</table>\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} .=
"<font color=\"red\">POST</font></td><td width=70%>";
$GETPOST{notempty} = 1;
} else {
$GETPOST{HTML}[$number]{query} .= "<hr>\n";
}
($site) = $request =~ /^POST (\S*)\s/;
$post =~ s/HTTP .*//s;
#
# Generate table of query key value pairs
#
$GETPOST{HTML}[$number]{query} .= "$site<br><table border=1>\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/</&lt;/g;
$value =~ s/>/&gt;/g;
$value =~ s/\n/<br>/g;
$GETPOST{HTML}[$number]{query} .=
"<tr><td><b>$var</b></td>" .
"<td><font face=\"Courier\">$value</font></td></tr>\n";
}
$GETPOST{HTML}[$number]{query} .= "</table>\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 <<END_HTML;
<html>
<head><title>Chaosreader Report, $Arg{infile}</title></head>
<body bgcolor="white" textcolor="black">
<font size=+3>Chaosreader Report</font><br>
<font size=+1>File: $Arg{infile}, Type: $TYPE, Created at: $the_date</font><p>
<a href="image.html"><font color="blue"><b>Image Report</b></font></a>
$image_empty - Click here for a report on captured images.<br>
<a href="getpost.html"><font color="blue"><b>GET/POST Report</b></font></a>
$getpost_empty - Click here for a report on HTTP GETs and POSTs.<br>
<a href="httplog.text"><font color="blue"><b>HTTP Proxy Log</b></font></a>
$httplog_empty - Click here for a generated proxy style HTTP log.<p>
<font size=+2>TCP/UDP/... Sessions</font><br>
<table border=2>
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 </td></tr>\n";
}
print FILE <<END_HTML;
</table><p>
<font size=+2>IP Count</font><br>
<table border=2>
END_HTML
foreach $IP (sort {$Count{IP}{$b} <=> $Count{IP}{$a}}
keys %{$Count{IP}}) {
print FILE "<tr><td>$IP</td><td>$Count{IP}{$IP}</td></tr>\n";
}
print FILE <<END_HTML;
</table><p>
<font size=+2>TCP Port Count</font><br>
<table border=2>
END_HTML
foreach $port (sort {$Count{TCPport}{$b} <=> $Count{TCPport}{$a}}
keys %{$Count{TCPport}}) {
$port_text = $Services_TCP{$port} || $port || "0";
print FILE "<tr><td>$port_text</td><td>$Count{TCPport}{$port}" .
"</td></tr>\n";
}
print FILE <<END_HTML;
</table><p>
<font size=+2>UDP Port Count</font><br>
<table border=2>
END_HTML
foreach $port (sort {$Count{UDPport}{$b} <=> $Count{UDPport}{$a}}
keys %{$Count{UDPport}}) {
$port_text = $Services_UDP{$port} || $port || "0";
print FILE "<tr><td>$port_text</td><td>$Count{UDPport}{$port}" .
"</td></tr>\n";
}
print FILE <<END_HTML;
</table><p>
<font size=+2>IP Protocol Count</font><br>
<table border=2>
END_HTML
foreach $protocol (sort {$Count{IPprotocol}{$b} <=>
$Count{IPprotocol}{$a}} keys %{$Count{IPprotocol}}) {
$protocol_text = $IP_Protocols{$protocol};
print FILE "<tr><td>$protocol_text</td><td>" .
"$Count{IPprotocol}{$protocol}</td></tr>\n";
}
print FILE <<END_HTML;
</table><p>
<font size=+2>Ethernet Type Count</font><br>
<table border=2>
END_HTML
foreach $type (sort {$Count{EtherType}{$b} <=> $Count{EtherType}{$a}}
keys %{$Count{EtherType}}) {
print FILE "<tr><td>$type</td><td>$Count{EtherType}{$type}" .
"</td></tr>\n";
}
print FILE <<END_HTML;
</table>
</body>
</html>
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 <<END_HTML;
<html>
<head><title>Chaosreader Image Report</title></head>
<body bgcolor="white" textcolor="black">
<font size=+3>Chaosreader Image Report</font><br>
<font size=+1>Created at: $the_date, Type: $TYPE</font><p>
<font size=+2>Images</font><br>
<table border=2>
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 </td></tr>\n";
}
print FILE <<END_HTML;
</table><p>
</body>
</html>
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 <<END_HTML;
<html>
<head><title>Chaosreader GET/POST Report</title></head>
<body bgcolor="white" textcolor="black">
<font size=+3>Chaosreader GET/POST Report</font><br>
<font size=+1>Created at: $the_date, Type: $TYPE</font><p>
<font size=+2>HTTP GETs and POSTs</font><br>
<table border=2>
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 </td></tr>\n";
}
print FILE <<END_HTML;
</table><p>
</body>
</html>
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 <<END_HTML;
<html>
<head><title>Chaosreader Master Index</title></head>
<body bgcolor="white" textcolor="black" vlink="blue">
<font size=+3>Chaosreader Master Index</font><br>
<font size=+1>Created at: $the_date, Type: $TYPE</font><p>
<a href="$recentname/index.html"><font color="red">
<b>Most Recent Report</b></font></a>
- Click here for the most recent index, and click reload for updates.<p>
<font size=+2>Chaosreader Reports</font><br>
<table border=2>
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 = "<tr><td><i>". ($index+1) . "</i></td>" .
"<td><b>$start</b></td><td><b>$end</b></td>\n" .
"<td>$duration s</td>" . "<td><font color=\"green\"> " .
"$size bytes</font></td>" .
"<td><a href=\"$dir/index.html\">$dir/$file</a></td></tr>\n";
print FILE "$html_line </td></tr>\n";
}
print FILE <<END_HTML;
</table><p>
<font size=+2>IP Count</font><br>
<table border=2>
END_HTML
foreach $IP (sort {$CountMaster{IP}{$b} <=> $CountMaster{IP}{$a}}
keys %{$CountMaster{IP}}) {
print FILE "<tr><td>$IP</td><td>$CountMaster{IP}{$IP}" .
"</td></tr>\n";
}
print FILE <<END_HTML;
</table><p>
<font size=+2>TCP Port Count</font><br>
<table border=2>
END_HTML
foreach $port (sort {$CountMaster{TCPport}{$b} <=>
$CountMaster{TCPport}{$a}} keys %{$CountMaster{TCPport}}) {
$port_text = $Services_TCP{$port} || $port || "0";
print FILE "<tr><td>$port_text</td><td>" .
"$CountMaster{TCPport}{$port}</td></tr>\n";
}
print FILE <<END_HTML;
</table><p>
<font size=+2>UDP Port Count</font><br>
<table border=2>
END_HTML
foreach $port (sort {$CountMaster{UDPport}{$b} <=>
$CountMaster{UDPport}{$a}} keys %{$CountMaster{UDPport}}) {
$port_text = $Services_UDP{$port} || $port || "0";
print FILE "<tr><td>$port_text</td><td>" .
"$CountMaster{UDPport}{$port}</td></tr>\n";
}
print FILE <<END_HTML;
</table><p>
<font size=+2>IP Protocol Count</font><br>
<table border=2>
END_HTML
foreach $protocol (sort {$CountMaster{IPprotocol}{$b} <=>
$CountMaster{IPprotocol}{$a}} keys %{$CountMaster{IPprotocol}}) {
$protocol_text = $IP_Protocols{$protocol};
print FILE "<tr><td>$protocol_text</td><td>" .
"$CountMaster{IPprotocol}{$protocol}</td></tr>\n";
}
print FILE <<END_HTML;
</table><p>
<font size=+2>Ethernet Type Count</font><br>
<table border=2>
END_HTML
foreach $type (sort {$CountMaster{EtherType}{$b} <=>
$CountMaster{EtherType}{$a}} keys %{$CountMaster{EtherType}}) {
print FILE "<tr><td>$type</td><td>" .
"$CountMaster{EtherType}{$type}</td></tr>\n";
}
print FILE <<END_HTML;
</table>
</body>
</html>
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 =~ /<html>/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/</&lt;/g;
$data =~ s/>/&gt;/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</font color="red"></g;
$data =~ tr/\040-\176\n\r\f/./c; # max 376, was 245
if (defined $plain) {
# This is a plain style of line wrap
$data =~ s/([^\n\f<>]{$WRAP})/$&\n/g;
} else {
# This is a fancy line wrap, a green ">" starts the wrapped lines
$data =~ s/([^\n\f<>]{$WRAP})/$&\n<font color="green">&gt;<\/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</font color="red"></g;
$data =~ tr/\040-\176\n\r\f/./c; # max 376, was 245
if (defined $plain) {
# This is a plain style of line wrap
$data =~ s/([^\n\f<>]{$WRAP})/$&\n/g;
} else {
# This is a fancy line wrap, a green ">" starts the wrapped lines
$data =~ s/([^\n\f<>]{$WRAP})/$&\n<font color="green">&gt;<\/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 .= "<font color=\"$colour\">";
$viewhtml .= "<font color=\"$colour\">";
### Process
@Bytes = unpack("C*",$data);
foreach $byte (@Bytes) {
$view = chr($byte);
$view =~ tr/\040-\176/./c;
$view2 = $view;
$view2 =~ s/</&lt;/g;
$view2 =~ s/>/&gt;/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} .=
'<font color="green">' . sprintf("%6.08x",$offset) .
"</font> $hexhtml $viewhtml\n";
$pos = 1;
$offset += 16;
$hexhtml = "</font><font color=\"$colour\">";
$viewhtml = "</font><font color=\"$colour\">";
$hextext = $viewtext = "";
}
if ( ($pos != 1) && (($pos %2) == 1) ) {
$hexhtml .= " ";
$hextext .= " ";
}
}
$hexhtml .= "</font>";
$viewhtml .= "</font>";
$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} .=
'<font color="green">' . sprintf("%6.08x",$offset) .
"</font> $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 .=
"</font><font color=\"$colour\">$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 .= "<font color=\"blue\">$raw</font>";
} else {
$session_data .= "<font color=\"red\">$raw</font>";
}
}
$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 "<HTML>\n<HEAD><TITLE>$number</TITLE></HEAD>" .
"<BODY bgcolor=\"white\">\n" .
"<H1>$service_name: $session_text</H1>\n" .
"<H2>File $Arg{infile}, Session $number</H2>\n" .
"<PRE WRAP=\"virtual\">\n" .
$session_data . "</PRE>\n</BODY>\n</HTML>\n";
close OUT;
### Global Vars
my $length = length($session_data);
$Index{HTML}[$number] .= "<li><a href=\"$filename\">as_html</a></li>\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 "<HTML>\n<HEAD><TITLE>$number</TITLE></HEAD>" .
"<BODY bgcolor=\"white\">\n" .
"<H1>$service_name: $session_text</H1>\n" .
"<H2>File $Arg{infile}, Session $number</H2>\n" .
"<PRE WRAP=\"virtual\">\n" .
$session_data . "</PRE>\n</BODY>\n</HTML>\n";
close OUT;
### Global Vars
my $length = length($session_data);
$Index{HTML}[$number] .= "<li>";
$Index{HTML}[$number] .= "<a href=\"$filename\">hex</a></li>\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] .=
"<li><a href=\"$filename\">$filename</a> $length bytes</li>\n";
$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n",
'"' , " $filename","",$length);
if (&Is_Image($ftp_type)) {
$Image{HTML}[$number]{links} .=
"<img src=\"$filename\"> ";
$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] .= "<li><a href=\"$filename\">$filename" .
"</a> $duration seconds</li>\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 .= '<font color="blue">' . $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 color="green">&gt;<\/font>/g;
$html .= '<font color="blue">' . $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 .= '</font>';
}
#
# --- 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 .= '<font color="red">' . $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 color="green">&gt;<\/font>/g;
$html .= '<font color="red">' . $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 .= '</font>';
### 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 = "<html><head><title>SSH text analysis</title></head>\n" .
"<body bgcolor=\"white\">" .
"<H1>$service_name: $session_text</H1>\n" .
"<H2>File $Arg{infile}, Session $number</H2>\n" .
"<h3>$duration2 seconds, $bytes bytes, $speed Kb/sec</h3>\n" .
'"*" is client traffic (including ' .
'keystrokes), "." is the return ' .
'text.<br>A number is a multiple of the previous char, eg ".32" ' .
'is 32 return chars.<br>' .
"\n<b><pre>$html</pre></b>\n</body>\n</html>\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] .= "<li><a href=\"$filename1\">$filename1" .
"</a> $duration seconds</li>\n";
$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s seconds\n",
'"' , " $filename1","",$duration);
$Index{HTML}[$number] .= "<li><a href=\"$filename2\">$filename2" .
"</a> </li>\n";
$length = length($html);
$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n",
'"' , " $filename2","",$length);
$Index{HTML}[$number] .= "<li><a href=\"$filename3\">$filename3" .
"</a> </li>\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] .= "<li><a href=\"$filename3\">$filename3" .
"</a> </li>\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] .= "<li><a href=\"$filename\">$filename" .
"</a> $duration seconds</li>\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 = <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] .= "<li><a href=\"$filename\">$filename" .
"</a> $duration seconds</li>\n";
$Index{HTML}[$number] .= "<li><a href=\"$filename2\">$filename2" .
"</a> $duration seconds</li>\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 "<HTML>\n<BODY bgcolor=\"white\">\n" .
"<H1>$service_name: $session_text</H1>\n" .
"<H2>File $Arg{infile}, Session $number</H2>\n" .
"<PRE WRAP=\"virtual\">\n" .
"<font color=\"red\">" .$html. "</font></PRE>\n</BODY>\n</HTML>\n";
close OUT;
### Global Vars
$length = length($html);
$Index{HTML}[$number] .=
"<li><a href=\"$filename3\">keystrokes</a></li>\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 <<WELCOME;
Port opened successfully.
Now run vncviewer and connect to this port. eg,
vncviewer -viewonly localhost:$vncport
If you are prompted for a password, type any character and hit enter.
Waiting for connection...
WELCOME
# --- 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 "ERROR49: I can't see myself: $!\n";
@Myself = <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);
}
#
# --- 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] .= "<li><a href=\"$filename\">$filename" .
"</a> $duration seconds</li>\n";
$Index{HTML}[$number] .= "<li><a href=\"$filename2\">$filename2" .
"</a> $duration seconds</li>\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] .= "<li><a href=\"$filename\">$filename" .
"</a> $length bytes</li>\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] .= "<li><a href=\"$filename\">$filename" .
"</a> $length bytes</li>\n";
$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n",
'"' , " $filename","",$length);
if (&Is_Image($http_type)) {
$Image{HTML}[$number]{links} .=
"<img src=\"$filename\"> ";
$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] .= "<li><a href=\"$filename\">$filename</a>" .
" $length bytes</li>\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 -&gt; $ip_dest:$tcp_dest_port";
} else {
$text = "$ip_src:$tcp_src_port <-> $ip_dest:$tcp_dest_port";
$html = "$ip_src:$tcp_src_port &lt;-&gt; " .
"$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 = <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 = <SERVICES>) {
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 = <<END;
keycode 8 = Control_L
keycode 9 = Control_R
keycode 10 = Shift_L
keycode 11 = Shift_R
keycode 12 = Meta_L
keycode 13 = Meta_R
keycode 14 = Alt_L
keycode 15 = Alt_R
keycode 16 = space
keycode 17 = 0 parenright
keycode 18 = 1 exclam
keycode 19 = 2 at
keycode 20 = 3 numbersign
keycode 21 = 4 dollar
keycode 22 = 5 percent
keycode 23 = 6 asciicircum
keycode 24 = 7 ampersand
keycode 25 = 8 asterisk
keycode 26 = 9 parenleft
keycode 27 = minus underscore
keycode 28 = equal plus
keycode 29 = bracketleft braceleft
keycode 30 = bracketright braceright
keycode 31 = semicolon colon
keycode 32 = apostrophe quotedbl
keycode 33 = grave asciitilde
keycode 34 = comma less
keycode 35 = period greater
keycode 36 = slash question
keycode 37 = backslash bar
keycode 38 = a A
keycode 39 = b B
keycode 40 = c C
keycode 41 = d D
keycode 42 = e E
keycode 43 = f F
keycode 44 = g G
keycode 45 = h H
keycode 46 = i I
keycode 47 = j J
keycode 48 = k K
keycode 49 = l L
keycode 50 = m M
keycode 51 = n N
keycode 52 = o O
keycode 53 = p P
keycode 54 = q Q
keycode 55 = r R
keycode 56 = s S
keycode 57 = t T
keycode 58 = u U
keycode 59 = v V
keycode 60 = w W
keycode 61 = x X
keycode 62 = y Y
keycode 63 = z Z
keycode 64 = BackSpace
keycode 65 = Return
keycode 66 = Tab
keycode 67 = Escape
keycode 68 = Delete
END
#
# These are generated using "xmodmap -pke" (and trimmed a little).
#
$linux_xmodmap_pke = <<END;
keycode 8 =
keycode 9 = Escape
keycode 10 = 1 exclam
keycode 11 = 2 at
keycode 12 = 3 numbersign
keycode 13 = 4 dollar
keycode 14 = 5 percent
keycode 15 = 6 asciicircum
keycode 16 = 7 ampersand
keycode 17 = 8 asterisk
keycode 18 = 9 parenleft
keycode 19 = 0 parenright
keycode 20 = minus underscore
keycode 21 = equal plus
keycode 22 = BackSpace Terminate_Server
keycode 23 = Tab ISO_Left_Tab
keycode 24 = q Q
keycode 25 = w W
keycode 26 = e E
keycode 27 = r R
keycode 28 = t T
keycode 29 = y Y
keycode 30 = u U
keycode 31 = i I
keycode 32 = o O
keycode 33 = p P
keycode 34 = bracketleft braceleft
keycode 35 = bracketright braceright
keycode 36 = Return
keycode 37 = Control_L
keycode 38 = a A
keycode 39 = s S
keycode 40 = d D
keycode 41 = f F
keycode 42 = g G
keycode 43 = h H
keycode 44 = j J
keycode 45 = k K
keycode 46 = l L
keycode 47 = semicolon colon
keycode 48 = apostrophe quotedbl
keycode 49 = grave asciitilde
keycode 50 = Shift_L
keycode 51 = backslash bar
keycode 52 = z Z
keycode 53 = x X
keycode 54 = c C
keycode 55 = v V
keycode 56 = b B
keycode 57 = n N
keycode 58 = m M
keycode 59 = comma less
keycode 60 = period greater
keycode 61 = slash question
keycode 62 = Shift_R
keycode 64 = Alt_L Meta_L
keycode 65 = space
keycode 94 = less greater
END
%Alias = qw(exclam ! at @ dollar $ percent %
asciicircum ^ ampersand & asterisk * minus - underscore _
equal = plus + bracketleft [ bracketright ] braceleft {
braceright } semicolon ; colon : apostrophe ' quotedbl "
grave ` asciitilde ~ backslash \ bar | less <
period . greater > 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 = <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