Skip to content

Instantly share code, notes, and snippets.

@nlitsme
Last active August 29, 2015 14:08
Show Gist options
  • Save nlitsme/c2ae41247f12c5e0e6a7 to your computer and use it in GitHub Desktop.
Save nlitsme/c2ae41247f12c5e0e6a7 to your computer and use it in GitHub Desktop.
parse tcpdump output, to display tcp and udp data streams in hex
#!/usr/bin/perl -w
use strict;
use Compress::Zlib;
use Getopt::Long;
Getopt::Long::Configure ("bundling");
# xpcap shows the payloads of udp and tcp connections, parsing
# the output of tcpdump -x
$|=1;
my $ascii; # -aa : no hex, print byte count
my $usetcpdump;
my $gzip;
my $noempty;
my $filterports;
my $verbose;
my $savedir;
my %ctcp;
my %cudp;
GetOptions(
"a+"=>\$ascii,
"t"=>\$usetcpdump,
"z"=>\$gzip,
"v"=>\$verbose,
"p=s"=>\$filterports,
"w=s"=>\$savedir,
"n"=>\$noempty) or die "Usage: xpcap [-a[a]] [-t] [-z] [-v] [-p PORT(S)] [-w SAVEDIR] [-n]\n";
my ($t, $p, $d, $previnf);
$filterports= ",$filterports," if $filterports;
while (<>) {
# todo: output beacon + probes
if (/^(\S+) (.*)/) {
my ($curt, $line)= ($1, $2);
if (defined $d && length($p)) {
if (!dumppkt($t, $p, $d, $previnf) && $verbose) {
print "ignoring $t\n";
}
}
($t, $d)= ($curt, "");
next if $line =~ / bad-fcs /;
if ($line =~ /^(.*?\s\S+\s>\s\S+):(.*)/) {
my ($curp, $curinf)= ($1, $2);
$curp =~ s/\d+us.*?Mb\/s.*?noise\santenna\s\d+//;
($p, $previnf)= ($curp, $curinf);
}
else {
($p, $previnf)= ("", "");
}
}
elsif (/^\s+0x\w+:\s+((?:\s\w+)+)/) {
my $x=$1; $x=~s/\s//g;
$d.=pack("H*", $x);
}
}
if (defined $d && length($p)) {
if (!dumppkt($t, $p, $d, $previnf) && $verbose) {
print "ignoring $t\n";
}
}
flushdata() if $savedir;
sub dumppkt {
my ($t,$p,$d, $info)=@_;
return if (length($d)==0);
my $pos= 0;
if (length($d)>8 && unpack("n", substr($d, $pos, 2))==0xaaaa) { # wifi header
$pos+= 8;
}
if (length($d)>4 && unpack("n", substr($d, $pos+2, 2))==0x0800) { # found in pcap-ng files
$pos+= 4;
}
my $ipv=ord(substr($d,$pos,1))>>4;
my $type=0;
my $ethextra; # leftover in ethernet packet beyond ip packet
my ($srcaddr, $dstaddr);
if ($ipv==4) {
return if $pos+4>length($d);
my $iplen= unpack("n", substr($d,$pos+2,2));
$ethextra=substr($d,$pos+$iplen) if $iplen<length($d);
# todo: not ethextra
$d= substr($d,0,$iplen+$pos);
my $ihl=ord(substr($d,$pos,1))&15;
return if $ihl<5;
return if (length($d)<$pos+$ihl*4);
$type= unpack("C", substr($d, $pos+9,1));
$srcaddr= join(".", unpack("C4", substr($d, $pos+12, 4)));
$dstaddr= join(".", unpack("C4", substr($d, $pos+16, 4)));
$pos+= $ihl*4;
return if $pos>=length($d);
}
elsif ($ipv==6) {
return if $pos+0x28>length($d);
return if (unpack("N", substr($d, $pos, 4))!=0x60000000);
my $datalen= unpack("n", substr($d, $pos+4, 2));
$type= unpack("C", substr($d, $pos+6, 1));
$srcaddr= join(":", map { sprintf("%x", $_) } unpack("n8", substr($d, $pos+12, 16))); $srcaddr =~ s/::+/::/g;
$dstaddr= join(":", map { sprintf("%x", $_) } unpack("n8", substr($d, $pos+16, 16))); $dstaddr =~ s/::+/::/g;
$pos+= 0x28;
while (hasnext($type) && $pos<length($d)) {
$type= unpack("C", substr($d, $pos, 1));
my $hdlen= $type==44 ? 8 : (8+unpack("C", substr($d, $pos+1, 1)));
$pos += $hdlen;
}
return if $pos>=length($d);
}
else {
return;
}
my $flags; # tcp flags
my ($srcport, $dstport);
my $data;
my $s; # sequencenr
if ($type==6) {
return if $pos+20>length($d);
my $thl= ord(substr($d,$pos+12,1))>>4;
return if $pos+$thl*4>length($d);
$data= substr($d, $pos+4*$thl);
$p =~ s/\bIP\b/TCP/;
$flags= "";
if ($info =~ /Flags \[(.*?)\]/) {
$flags= $1;
$flags=~ s/[.P]//g;
}
($srcport, $dstport, $s)= unpack("n2N", substr($d, $pos, 8));
savetcpdata(tag($p), $data, $s) if $savedir;
}
elsif ($type==17) { # udp
return if $pos+8>length($d);
my $udplen= unpack("n", substr($d,$pos+4,2));
if ($udplen!=0xffff && $pos+$udplen > length($d)) {
warn sprintf("udplen too large: %04x > %04x\n", $udplen, length($d)-$pos);
}
my $ipextra= substr($d, $pos+$udplen) if $udplen!=0xffff && $pos+$udplen<length($d);
# todo: not ipextra
$data= $udplen!=0xffff ? substr($d, $pos+8, $udplen-8) : substr($d, $pos+8);
$p =~ s/\bIP\b/UDP/;
($srcport, $dstport)= unpack("n2", substr($d, $pos, 4));
saveudpdata(tag($p), $data) if $savedir;
}
elsif ($type==1) { # icmp
# return if $pos+8>length($d);
# my ($type, $code, $chk, $ident, $seq)= unpack("CCnnn", substr($d, $pos, 8));
# my %types= (0=>'echoreply', 3=>'unreachable', 4=>'srcquench', 5=>'redirect',
# 6=>'altaddr', 8=>'echoreq', 9=>'routeradv', 10=>'routersel', 11=>'timeexceeded',
# 12=>'paramproblem', 13=>'timestampreq', 14=>'timestampreply', 15=>'inforeq', 16=>'inforeply',
# 17=>'maskreq', 18=>'maskreply', 30=>'traceroute'
# );
$p =~ s/\bIP\b/ICMP/;
}
else {
$data= substr($d, $pos);
}
if ($filterports) {
my $msrc= (!$srcport || index($filterports, ",$srcport,")<0);
my $mdst= (!$dstport || index($filterports, ",$dstport,")<0);
return if $msrc && $mdst;
}
if ($type==1) { # icmp
printf("%s %-45s %s\n", $t, tag($p), $info);
}
elsif ($usetcpdump && $type==17 && ($srcport==53 || $dstport==53 || $srcport==68 || $dstport==68 || $srcport==67 || $dstport==67 || $srcport==5353 || $dstport==5353)) {
printf("%s %-45s %s\n", $t, tag($p), $info);
}
elsif ($ascii && $data =~ /^[\t\r\n\x20-\xef]{8,}/) {
my $a= $&;
my $d= substr($data, length($a));
if ($gzip && substr($d,0,2) eq "\x1f\x8b") {
my $o= Compress::Zlib::memGunzip($d) || "";
$a .= $o;
}
my $l= length($d);
if (defined $s) {
printf("%s %-45s [%08x]%1s%s\n", $t, tag($p), $s, $flags, $l ? "... ".($ascii>1 ? sprintf("%d bytes", $l) : unpack("H*", $d)) : "");
}
else {
printf("%s %-45s %s\n", $t, tag($p), $l ? "... ".($ascii>1 ? sprintf("%d bytes", $l) : unpack("H*", $d)) : "");
}
$a =~ s/^/ | /gm;
print "$a\n";
}
elsif (!defined $s) {
printf("%s %-45s %s\n", $t, tag($p), unpack("H*", $data));
}
elsif (length($data) || !$noempty) {
printf("%s %-45s %1s[%08x] %s\n", $t, tag($p), $flags, $s, unpack("H*", $data));
}
return 1;
}
sub hasnext {
my $t= shift;
return $t==0 || $t==43 || $t==44 || $t==60;
}
my %t;
sub tag {
my $dir=shift;
if ($dir =~ /(?:(\w+)\s+)?(\S+) > (\S+)/) {
my ($p, $A, $B) = ($1 || "", $2, $3);
my $fwd= "$p $A > $B";
my $rev= "$p $B > $A";
if (exists $t{$fwd}) {
return $t{$fwd};
}
elsif (exists $t{$rev}) {
return $t{$dir}= "$p $B < $A";
}
else {
return $t{$dir}= "$p $A > $B";
}
}
printf("!!!!!!! %s\n", $dir);
return "";
}
sub saveudpdata {
my ($tag, $data)= @_;
$tag =~ s/ > /-/;
open(my $fh, ">> $savedir/$tag") or die "$tag: $!\n";
$fh->print($data);
$fh->close();
}
sub savetcpdata {
my ($tag, $data, $seq)= @_;
$tag =~ s/ > /-/;
if (!exists $ctcp{$tag}{$seq} || length($data)>length($ctcp{$tag}{$seq})) {
$ctcp{$tag}{$seq}= $data;
}
}
sub flushdata {
for my $tag (keys %ctcp) {
my @seq= sort { $a<=>$b } keys %{$ctcp{$tag}};
my $curseq;
my $curdata= "";
for my $seq (@seq) {
if (!defined $curseq) {
$curseq= $seq;
$curdata= $ctcp{$tag}{$seq};
$curseq += length($ctcp{$tag}{$seq});
}
elsif ($seq-$curseq<0x10000) {
$curdata .= "\x00" x ($seq-$curseq);
$curseq = $seq;
$curdata .= $ctcp{$tag}{$seq};
$curseq += length($ctcp{$tag}{$seq});
}
else {
if (defined $curdata) {
open(my $fh, "> $savedir/$tag-$curseq") or die "$tag: $!\n";
$fh->print($curdata);
$fh->close();
}
$curseq= $seq;
$curdata= $ctcp{$tag}{$seq};
$curseq += length($ctcp{$tag}{$seq});
}
}
if (defined $curdata) {
open(my $fh, "> $savedir/$tag-$curseq") or die "$tag: $!\n";
$fh->print($curdata);
$fh->close();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment