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

  1. Set’s the network adapter your scanning for packets
  2. 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
  3. The reason the protocol matters is we look at the SCCP packets coming in to decided whether or not a conversation has started / ended
  4. 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
  5. It was 6 years ago I wrote this and I don’t remember what I was using to do that right now
    1. 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.

      • 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.
        • 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