Recording Conversations off Span Port on the Cisco IP Phone
This is going to be a quick blog post to release some old code from 5-6 years ago when I was just playing around. As many of you know there are alot of ways to record conversations in a VoIP network but for Cisco the 2 most popular are desktop application capturing from a SPAN port and a centralized server gathering SPAN’s from random switch ports. This application is made to be run in EITHER of those scenarios.
Some important things to note about this
- Set’s the network adapter your scanning for packets
- Is currently specific to certain models of Cisco phones and the SCCP/SIP messaging they send. I originally tested this with 7940/41/42/60/61/62 so all those should work fine if you use SCCP
- The reason the protocol matters is we look at the SCCP packets coming in to decided whether or not a conversation has started / ended
- RTP streams are one way (there is two of them) The sending and the receiving. This app captures each of them separately then I haveanother utility that muxes them to form the whole conversation as we are used to hearing it
- It was 6 years ago I wrote this and I don’t remember what I was using to do that right now
- Pretty much uses the equivelent of winpcap to capture all packets and analyze them
- look for SCCP/SIP messaging to understand the start of a call
- leaves 2 separate audio files for each call (send and receive) that you must mux yourself.
So as I said earlier you could hook this up to any server/NIC be it end user PC connected to the phone or centralized server that is receiving RTP streams from SPAN’s on a switch and the app will capture and separate all the streams. The main reason I am releasing this is its written in PERL, which I no longer write, and I have no interest in trying to translate this to Ruby right now. I remember there being next to NOTHING on the topic when I was searching, but I’ll be damned if I didn’t figure out how to record phone calls 🙂
I also made a screen scraper in C# to record agent activities into a video, but I never figured out how to get good performance on it. I may release this code if I can dig it up as well…. it could be gone forever though honestly..
Here is the code, I will not break it down line for line, but I will give you a 5000 ft overview.
use strict; use Win32::Console::ANSI; use Win32::NetPacket qw/ :ndis GetAdapterNames /; use NetPacket::Ethernet qw(:strip); use Term::ReadKey; use NetPacket::IP $|++; use constant SizeOfInt => 4; # for word alignment # select the adapter my @filter = qw/ 23 40 0 0 12 21 0 7 34525 48 0 0 20 21 0 18 17 40 0 0 54 53 0 1 16384 37 0 14 32767 40 0 0 56 53 11 13 16384 21 0 12 2048 48 0 0 23 21 0 10 17 40 0 0 20 69 8 0 8191 177 0 0 14 72 0 0 14 53 0 1 16384 37 0 3 32767 72 0 0 16 53 0 2 16384 37 1 0 32767 6 0 0 96 6 0 0 0/; my %desc; my @adpts = GetAdapterNames( \%desc ); @adpts > 0 or die "No adapter installed !\n"; my $i = 1; if ( @adpts > 1 ) { print "Adapters installed:\n\n"; print $i++, " - $desc{$_}\n $_\n" foreach @adpts; do { print "\nSelect the number of the adapter to open : "; $i = ; chomp $i; } until ( $i =~ /^(\d)+$/ and 0 < $i and $i new( adapter_name => $adpts[ $i - 1 ], driver_buffer_size => 512 * 1024, # 512 kbytes kernel buffer read_timeout => 1000, # 1s timeout ) or die $@; $nic->SetHwFilter(NDIS_PACKET_TYPE_PROMISCUOUS); # set nic in promiscuous mode # print infos my ( $name, $description, $type, $speed, $ip, $mask, $mac ) = $nic->GetInfo(); $description ||= $desc{$name}; $ip ||= '?.?.?.?'; $mask ||= '?.?.?.?'; $mac = join '-', unpack 'A2' x 6, $mac; print "Listening $name\n($description)\nMAC: $mac IP: $ip Mask: $mask\n"; print "** press [enter] to terminate\n"; # set user's buffer my $Buff; my $ip_obj; my ($totalsize, $totalsize2); my $count; my ($dgsize, $dgsize2); my ($ugdata, $ugdata2); my $alreadypassed; my $rxwav = ">receive.wav"; open(NEW, $rxwav); my $txwav = ">transmit.wav"; open(NEW2, $txwav); my $recordstatus; my ($totalfile, $totalfile2); $nic->SetUserBuffer( $Buff, 128 * 1024 ); #$nic->SetBpf(@filter) or die "Unable to set Bpf filter"; # main capture loop my $BytesReceived; while ( !ReadKey(-1) ) { # press (enter) to terminate $BytesReceived = $nic->ReceivePacket(); # capture the packets printPackets(); # print the packets } printf "\n\n%d packets received,\n%d packets lost.\n", $nic->GetStats; # ------ printPackets routine sub printPackets { my $nic = shift; my $offset = 0; while ( $offset decode(eth_strip($data)); print("$ip_obj->{src_ip}:$ip_obj->{dest_ip} $ip_obj->{proto}\n"); my $srcmac = substr $data, $i, 6; my $destmac = substr $data, $i+6, 6; my $srcip = substr $data, $i+26, 4; my $destip = substr $data, $i+30, 4; my $srcport = substr $data, $i+34, 2; my $destport = substr $data, $i+36, 2; my $packetlength = substr $data, $i+38, 2; $srcmac = unpack( 'H12', $srcmac ); $destmac = unpack( 'H12', $destmac ); $srcip = unpack( 'H8' x 4, $srcip ); $destip = unpack( 'H8', $destip ); $srcport = unpack( 'H4', $srcport ); $destport = unpack( 'H4', $destport ); my $srcportdec = hex($srcport); my $destportdec = hex($destport); if($alreadypassed == 0 && $destportdec == 2000 || $alreadypassed == 0 && $srcportdec == 2000 || $alreadypassed == 0 && $srcportdec == 5060 || $alreadypassed == 0 && $destportdec == 5060) { my $sccpmessage; my $sipmessage; if($srcportdec == 5060 || $destportdec == 5060){ $sipsta = substr $data, $i+51, 3; $sipsta = unpack( 'H6', $sipsta ); my $sipstatus = substr $data, $i+42, 3; $sipmessage = unpack( 'H6', $sipstatus ); } else { $sccpmessage = substr $data, $i+62, 4; $sccpmessage = unpack( 'H8', $sccpmessage ); } if ($sccpmessage == "22000000") { printf "sccphex = $sccpmessage || siphex = $sipmessage\n"; $alreadypassed = 1; } elsif($sipmessage == "41434b") { $alreadypassed = 1; } } if($alreadypassed == 1 && $destportdec == 2000 || $alreadypassed == 1 && $srcportdec == 2000 || $alreadypassed == 1 && $destportdec == 5060 || $alreadypassed == 1 && $srcportdec == 5060) { my $sipmessage; my $sccpmessage; if($srcportdec == 5060 || $destportdec == 5060){ my $sipstatus = substr $data, $i+42, 3; $sipmessage = unpack( 'H6', $sipstatus ); } else { $sccpmessage = substr $data, $i+62, 4; $sccpmessage = unpack( 'H8', $sccpmessage ); } if ($sccpmessage == "06010000" || $sipmessage == "425945"){ printf "srcmac = $sccpmessage || siphex = $sipmessage\n"; $count++; if ($count == 2){ $alreadypassed = 2;} } } my $pl = unpack( 'H4', $packetlength ); my $decval = hex($pl); if ($alreadypassed == 1 && $ip_obj->{src_ip} !~ /$ip/ && $srcportdec >= 16384 && $srcportdec = 16384 && $destportdec {src_ip} =~ /$ip/ && $srcportdec >= 16384 && $srcportdec = 16384 && $destportdec = $datalen; # Packet word alignment $offset = ( ( $offset + $caplen ) + ( SizeOfInt - 1 ) ) & ~( SizeOfInt - 1 ); } if($alreadypassed == 2) { $alreadypassed = 0; my $string = "52494646E485000057415645666D74201200000007000100401F0000401F00000100080000006661637404000000B285000064617461"; my @array = ( $string =~ m/../g ); my $wavheader = pack("H2" x 54, @array); print NEW $wavheader; print NEW2 $wavheader; my $stff = sprintf("%08x", $totalsize); print $stff; my @array2 = reverse( $stff =~ m/../g ); my $wavsize = pack("H2" x 4, @array2); print NEW $wavsize; print NEW $totalfile; close(NEW); my $stff = sprintf("%08x", $totalsize2); my @array2 = reverse( $stff =~ m/../g ); my $wavsize = pack("H2" x 4, @array2); print NEW2 $wavsize; print NEW2 $totalfile2; close(NEW2); } } sub hex_to_ascii ($) { ## Convert each two-digit hex number back to an ASCII character. (my $str = shift) =~ s/([a-fA-F0-9]{2})/chr(hex $1)/eg; return $str; } sub ascii_to_hex ($) { ## Convert each ASCII character to a two-digit hex number. (my $str = shift) =~ s/(.|\n)/sprintf("%02lx", ord $1)/eg; return $str; }
I have also posted the whole file HERE on my dropbox.
Happy coding,
Chad Stachowicz