Skip to content

Instantly share code, notes, and snippets.

@mdeweerd
Last active May 27, 2021 22:14
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mdeweerd/47ea977028259ee9a6b6fd8f2356c65d to your computer and use it in GitHub Desktop.
Save mdeweerd/47ea977028259ee9a6b6fd8f2356c65d to your computer and use it in GitHub Desktop.
Perl script to convert a regular log file as syslog messages in a pcap file for wireshark
#!/bin/perl
# SCRIPT TO CONVERT TEXT LOG TO PCAP FILE
# READS DATES FROM LOG FILE
# WHEN READING FROM PIPE (UNTESTED) WITHOUT DATES,
# THE CURRENT TIMESTAMP IS USED.
#
# Potential parameters to the script:
# Parameter ending in ".log" is the log input file
# Parameter ending in ".pcap" is the log output file
# Digit from 0 to 7 is the log index.
# Other number is timeshift to apply in seconds (can be floating point 213.213)
#
#
# Requires some packages, you may need to install File::PCAM
#
# If you have CPAN as a command: `cpan install File::PCAP`
# With perl command, and CPAN module: `perl -MCPAN install File::PCAP`
# Otherwise: visit the CPAN site, download the package and follow instructions.
use File::PCAP::Writer; # Needed to write the pcap file
use Time::HiRes qw(gettimeofday); # Needed to get usec precision for current time
use Time::Piece; # May no longer be needed, possibly needed for date in Syslog message (disabled)
use Time::Local; # needed for gmtime
use Date::Parse; # May no longer be needed, possibly needed for date in Syslog message (disabled)
use Socket; # Needed for inet_aton, etc - could be avoided
use Scalar::Util qw(looks_like_number); # Need to test if argument is a number
use List::Util qw(sum); # Needed for the checksum which is not currently used
use POSIX qw(locale_h); # Needed for locale type
use locale; # Needed for setlocale
# Make sure text is in English.
setlocale(LC_CTYPE);
my $fname; # Default output file name
my $logname; # Default log file, when '', use STDIN
my $timeshiftSec; # Timeshift to apply to log.
my $logidx=0;
# Check CLI parameters, parameter selection based on tyep
for(my $i = 0; $i < @ARGV; $i++){
if( $ARGV[$i] =~ /^([0-7])$/ ) {
$logidx=scalar($1);
} elsif( $ARGV[$i] =~ /.pcap$/ ) {
if(!defined($fname)) {
$fname=$ARGV[$i];
}
} elsif(looks_like_number($ARGV[$i])) {
if(!defined($timeshiftSec)) {
$timeshiftSec=$ARGV[$i];
}
} elsif(!defined($logname)) {
$logname=$ARGV[$i];
}
}
# Set defaults for missing options.
if(!defined($logname)) {
$logname='';
}
if(!defined($fname)) {
if($logname=~/^(.*).log/) {
$fname=$1.'_log.pcap';
} else {
$fname='file.pcap';
}
}
if(!defined($timeshiftSec)) {
$timeshiftSec=0;
}
my $logtype=134+$logidx*8;
# Delete the output file
unlink($fname);
# Open the output pcap file
my $fpw = File::PCAP::Writer->new( {
fname => $fname,
dlt => 1,
} );
# Open the input file
my $fh;
my $timeFromFile=0;
if($logname ne '') {
open( $fh => $logname) || die "Cannot open $logname: $!";
$timeFromFile=1;
} else {
$fh=*STDIN;
$logname="STDIN";
}
# Inform about what we are doing
print "Convert $logname to $fname";
# Do conversion
my $t=0;
my $usec;
my $tOffset=int($timeshiftSec);
my $usOffset=int(($timeshiftSec-$tOffset)*1000000);
if($usOffset<0) {
$usOffset+=1000000;
$tOffset-=1;
}
while( my $line = <$fh> ) {
chomp;
if(length($line)==0||$line=~/^\s*$/) {
next;
}
#print $t;
$line=stripAnsi($line);
if($line=~/^\[([^[]*?)\]\s?(.*)/) {
my $timestr=$1;
$line=$2;
$t=str2time($1,"+0000");
#print $timestr." ".$t;
$usec = 0;
$timeFromFile=1;
}
# Special case where another timestamp is present.
if($line=~/^(\[(\d\d):(\d\d):(\d\d)\.(\d\d\d),(\d\d\d)])\s?(.*)/) {
my $nhour=$2;
my $nmin=$3;
my $nsec=$4;
$usec=$5*1000;
$usec+=$6;
$line=$7;
#print $1.$usec."\n"; # Original time for debugging
my($sec,$min,$heure,$day,$mon,$year) = gmtime($t);
$t=timegm($sec,$min,$heure,$day,$mon,$year);
$t+=$nmsec/1000;
#print $timestr." ".$t."\n"; # For debugging
$timeFromFile=1;
}
if(!$timeFromFile) {
($t, $usec) = gettimeofday();
}
$line="<${logtype}>".$line;
#$line=gmtime($t)->strftime("%M %b %H:%M:%S")." ".$line;
#print $line."\n";
#print $usec." ";
$buf=getPacketForText($line);
$blen=$plen=length($buf);
# Adjust times
my $nTime=$t+$tOffset;
my $uTime=$usec+$usOffset;
if($uTime<0||$uTime>=1000000) {
my $mTime=$uTime%1000000;
$nTime+=($uTime-$mTime)/1000000;
$uTime=$mTime;
}
$fpw->packet( $nTime, $uTime, $blen, $plen, $buf );
}
#$fpw->close();
sub stripAnsi {
my $l=shift;
$l=~s/\e\[[0-9;]*m(?:\e\[K)?//g; # Adam Kats froma https://superuser.com/questions/380772/removing-ansi-color-codes-from-text-stream
return $l;
}
# Helper functions, adapted from https://stackoverflow.com/questions/51061864/sending-spoofed-syslog-messages-in-perl
sub udp_checksum {
# thanks to ikegami in post https://stackoverflow.com/questions/46181281/udp-checksum-function-in-perl
my $packed_src_addr = shift; # As 4-char string, e.g. as returned by inet_aton.
my $packed_dst_addr = shift; # As 4-char string, e.g. as returned by inet_aton.
my $udp_packet = shift; # UDP header and data as a string.
my $sum = sum(
IPPROTO_UDP,
length($udp_packet),
map({ unpack('n*', $_) }
$packed_src_addr,
$packed_dst_addr,
$udp_packet."\0", # Extra byte ignored if length($udp_packet) is even.
),
);
while (my $hi = $sum >> 16) {
$sum = ($sum & 0xFFFF) + $hi;
}
return ~$sum & 0xFFFF;
}
sub getPacketForText {
$data=shift;
$src_host = "localhost";
$dst_host = 127.0.0.1;
$src_port = 33333;
$dest_port = 514;
$cksum = 0; #initialise, we will sort this later
#$udp_len is the udp packet length in the udp header. Its just 8 plus the length of the data
#for this test harness we will set the data here too
#$data = "<132> %FWSM-3-106010: Deny inbound tcp src outside:215.251.218.222/11839 dst inside:192.168.1.1/369";
#$data="<132> May 22 08:59:12 pf: 00:00:35.159056 rule 3/0(match): block in on em2: (tos 0x0, ttl 128, id 6204, offset 0, flags [none], proto UDP (17), length 235)";
$udp_len = 8+length($data);
$udp_proto = 17; #17 is the code for udp
#Prepare the udp packet, needed for the checksum to happen, then get the checksum (horrifically complicated, just google it)
$udp_packet = pack("nnnna*", $src_port,$dest_port,$udp_len, $cksum, $data);
$cksum = udp_checksum(inet_aton($src_host),inet_aton($dst_host),$udp_packet);
$zero_cksum = 0;
#test harness checks about host IPs
my $dst_host = (gethostbyname($dst_host))[4]; my $src_host = (gethostbyname($src_host))[4];
# Now lets construct the IP packet
my $ip_ver = 4;
my $ip_len = 5;
my $ip_ver_len = $ip_ver . $ip_len;
my $ip_tos = 00;
my ($ip_tot_len) = $udp_len + 20;
my $ip_frag_id = 19245;
my $ip_frag_flag = "010";
my $ip_frag_oset = "0000000000000";
my $ip_fl_fr = $ip_frag_flag . $ip_frag_oset;
my $ip_ttl = 30;
#H2H2nnB16C2na4a4 for the IP Header part#nnnna* for the UDP Header part.
#To understand these, see the manual of pack function and IP and UDP Header formats
#IP checksum ($zero_cksum is calculated by the kernel. Dont worry about it.)
my ($pkt) = pack("H*","0000000000000000000000000800")
#.pack('H2H2nnB16C2na4a4nnnna*',
.pack('H2H2nnB16C2na4a4nnnna*',
$ip_ver_len,$ip_tos,$ip_tot_len,$ip_frag_id,
$ip_fl_fr,$ip_ttl,$udp_proto,$zero_cksum,$src_host,
$dst_host,$src_port,$dest_port,
$udp_len, $cksum, $data);
##bit that makes the socket and sends the packet
##socket(RAW, AF_INET, SOCK_RAW, 255) || die $!; setsockopt(RAW, 0, 1, 1);
##my ($destination) = pack('Sna4x8', AF_INET, $dest_port, $dst_host);
##send(RAW,$pkt,0,$destination);
#replace by return
return $pkt;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment