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