Archivo de la etiqueta: SIP

SIP Monitor

Cuando en un entorno de VoIP gestionas muchos usuarios es muy complicado seguir el log que muestra el CLI para ver exactamente dónde hay un error concreto. Muchas veces debes filtrar algún usuario con determinados problemas, por ejemplo de registro, y la verdad es que se hace bastante engorroso realizar un seguimiento de los paquetes. Por otro lado, también resulta un poco coñazo realizar capturas con Tcpdump para luego descargar en otro equipo con entorno gráfico y analizar posteriormente mediante Wireshark.

Para agilizar un poco este proceso he creado un script en Perl muy sencillo que permite aplicar algunos filtros de búsquedas, mostrando por pantalla únicamente aquellos paquetes que necesitemos.

sipmon

El script no es más que un sniffer que monitoriza el tráfico TCP y/o UDP en los puertos que especifiquemos y que nos mostrará los paquetes de señalización SIP que capture. Como se puede ver en la imagen, podemos realizar las capturas que más nos interesen, por ejemplo:

  • Analizar los paquetes con origen o destino hacia una determinada IP.
  • Buscar paquetes con un usuario de registro específico.
  • Monitorizar TCP y/o UDP.
  • Filtrar por método: INVITE, REGISTER, OPTIONS, etc.

Para poderlo usar necesitamos instalar algunas dependencias:

Net::PcapUtils -> http://search.cpan.org/CPAN/authors/id/T/TI/TIMPOTTER/Net-PcapUtils-0.01.tar.gz
Net::Pcap -> http://search.cpan.org/CPAN/authors/id/K/KC/KCARNUT/Net-Pcap-0.05.tar.gz – apt-get install libnet-pcap-perl
Net::SIP -> http://search.cpan.org/CPAN/authors/id/S/SU/SULLR/Net-SIP-0.68.tar.gz – apt-get install libnet-sip-perl
NetPacket::Ethernet -> http://search.cpan.org/CPAN/authors/id/A/AT/ATRAK/NetPacket-0.04.tar.gz
Net::Address::IP::Local -> http://search.cpan.org/CPAN/authors/id/J/JM/JMEHNLE/net-address-ip-local/Net-Address-IP-Local-0.1.2.tar.gz

Y el script:

#!/usr/bin/perl
# Jose Luis Verdeguer AKA Pepelux <verdeguer@zoonsuite.com>
# Twitter: @pepeluxx

use strict;
use Net::PcapUtils;
use Getopt::Long;
use Net::SIP ':all';
use NetPacket::Ethernet;
use NetPacket::IP;
use NetPacket::TCP;
use Net::Address::IP::Local;

# Do no buffering - flushing output directly
$|=1;

# Declaration of functions
sub f_probe_pcapinit;
sub f_probe_read80211b_func;
sub f_probe_ctrl_c;

# Declaration of global variables
my $g_pcap_err = '';
my $g_cap_descrip;
my ($address, $netmask, $err);
my $ip;
my $ether;
my $tcp;
my $myfilter;
my $myaddress;

# Params
my $dev = '';
my $sudp= 0;
my $stcp = 0;
my $port = '';
my $method = '';
my $user = '';
my $oip = '';
my $h = 0;

# Packet struct
my $len = 38;      # 0x26 -> srcport (2)
my $request = 42;  # 0x2a -> SIP request

sub init() {
	# check params
	my $result = GetOptions ("i=s" => \$dev,
				 "p=s" => \$port,
				 "m=s" => \$method,
				 "udp+" => \$sudp,
				 "tcp+" => \$stcp,
				 "user=s" => \$user,
				 "ip=s" => \$oip,
				 "help+" => \$h,
				 "h+" => \$h);

	help() if ($h eq 1);

	my $login = (getpwuid $>);
	die "must run as root" if $login ne 'root';

	$myaddress = Net::Address::IP::Local->public;

	my $proto = '';
	my $port1 = '';
	my $port2 = '';
	my $addr1 = '';
	my $addr2 = '';

	if ($sudp eq 0 && $stcp eq 0) {
		$sudp = 1;
		$stcp = 1;
		$proto = "(udp || tcp)";
		$port1 = "(dst port 5060 || dst port 5061)" if ($port eq "");
		$port2 = "(src port 5060 || src port 5061)" if ($port eq "");
	}
	else {
		if ($sudp eq 1) {
			$proto = "udp";
			$port1 = "dst port 5060" if ($port eq "");
			$port2 = "src port 5060" if ($port eq "");
		}
		else {
			$proto = "tcp";
			$port1 = "dst port 5061" if ($port eq "");
			$port2 = "src port 5061" if ($port eq "");
		}
	}

	if ($port ne "") {
		$port1 = "dst port $port";
		$port2 = "src port $port";
	}

	if ($oip eq "") {
		$addr1 = "dst $myaddress && $port1";
		$addr2 = "src $myaddress && $port2";
	}
	else {
		$addr1 = "(dst $myaddress && $port1 && src $oip)";
		$addr2 = "(src $myaddress && $port2 && dst $oip)";
	}

	$dev = Net::Pcap::lookupdev(\$err) if ($dev eq "");

	if (Net::Pcap::lookupnet($dev, \$address, \$netmask, \$err)) {
	    die 'Unable to look up device information for ', $dev, ' - ', $err;
	}

	$myfilter = "$proto && (($addr1) || ($addr2))";

	print "Interface: $dev ($myaddress)\n";
	print "Filter: $myfilter\n";

	if ($method eq "") { print "Method: ALL\n"; }
	else { print "Method: " . uc($method) . "\n"; }

        print "\n";

	f_probe_pcapinit;
}

sub f_probe_pcapinit {
	$g_cap_descrip = Net::Pcap::open_live($dev, 1500, 0, 0, \$err);

	unless (defined $g_cap_descrip) {
	    die 'Unable to create packet capture on device ', $dev, ' - ', $err;
        }

        # Create filter.
	my $filter;
	Net::Pcap::compile($g_cap_descrip, \$filter, $myfilter, 0, $netmask) && die 'Unable to compile packet capture filter';
	Net::Pcap::setfilter($g_cap_descrip, $filter) && die 'Unable to set packet capture filter';

	# Initiate endless packet gathering.
	Net::Pcap::loop($g_cap_descrip, -1, \&f_probe_read80211b_func , '' );
};

sub f_probe_read80211b_func {
	my($data, $header, $packet) = @_;
	$data = unpack('H*',$packet);

	$ether = NetPacket::Ethernet::strip($packet);
	$ip = NetPacket::IP->decode($ether);
	$tcp = NetPacket::TCP->decode($ip->{'data'});

	if (len($data) > 12) {
		my $sipdata = siprequest($data);
		log_data($sipdata) if (ascii2hex(substr($sipdata, 0, 1)) ne "00");
	}
};

sub siprequest {
	my $data = shift;
	$data = substr($data, $request*2);

	return hex2ascii($data);
};

sub len {
	my $data = shift;
	$data = substr($data, $len*2, 4);

	return hex($data);
};

sub log_data {
	my $sip_pkt = Net::SIP::Packet->new_from_string(@_);
	my $req = Net::SIP::Packet->new_from_string($sip_pkt->as_string);

	my $from = $sip_pkt->get_header('from');
	{
		my @values = split(';', $from);
		$from = $values[0];
	}

	my $to = $sip_pkt->get_header('to');
	{
		my @values = split(';', $to);
		$to = $values[0];
	}

	if ($method eq "" || $req->method eq uc($method)) {
		if ($user eq '' || $from =~ /$user/i || $to =~ /$user/i) {
			print $ip->{'src_ip'}.':'.$tcp->{'src_port'}.' => '.$ip->{'dest_ip'}.':'.$tcp->{'dest_port'}."\t(".$req->method.") ";
			if($sip_pkt->is_response()) { print "\tResponse: ".$req->code; }
			else { print "\tRequest"; }
			print "\n";
			print $sip_pkt->as_string."\n\n";
		}
	}
}

sub ascii2hex($) {
	(my $str = shift) =~ s/(.|\n)/sprintf("%02lx", ord $1)/eg;
	return $str;
};

sub hex2ascii($) {
	(my $str = shift) =~ s/([a-fA-F0-9]{2})/chr(hex $1)/eg;
	return $str;
};

sub help {
	print qq {
Usage:  $0 [options]

    == Options ==
      -i <iface>       = interface (default try to get automatically)
      -p <port>        = port (default 5060/udp and 5061/tcp)
      -m <method>      = scan only a method (default show all: REGISTER, INVITE, ...)
      -udp             = scan only UDP (default UDP + TCP)
      -tcp             = scan only TCP (default UDP + TCP)
      -ip <addr>       = filter remote IP (default show all)
      -user <data>     = filter user in 'To' or 'From' (default show all)
      -h, -help        = This help

    == Examples ==
      $0 -udp -ip "80.80.80.0/24" -p 5080
      $0 -i eth0 -user "mysipuser"
      $0 -ip "80.80.80.0/24" -user "mysipuser"

};

	exit 1;
};

init();

Supongamos el caso de que queremos monitorizar el registro de un usuario ‘pruebas’. Podemos hacer lo siguiente:


root@sip:~# perl sipmon.pl -m REGISTER -user "pruebas"
Interface: eth0 (X.X.224.7)
Filter: (udp || tcp) && ((dst X.X.224.7 && (dst port 5060 || dst port 5061)) || (src X.X.224.7 && (src port 5060 || src port 5061)))
Method: REGISTER

X.X.194.138:5080 => X.X.224.7:5060 (REGISTER) Request
REGISTER sip:sip.myserver.com SIP/2.0
Via: SIP/2.0/UDP 192.168.200.2:5080;branch=z9hG4bK85544896f396b9b8
From: "pruebas" <sip:pruebas204@sip.myserver.com>;tag=2d3ce7700b58c4d9
To: <sip:pruebas204@sip.myserver.com>
Contact: <sip:pruebas204@192.168.200.2:5080;transport=udp>
Supported: path
Authorization: Digest username="pruebas204", realm="sip", algorithm=MD5, uri="sip:sip.myserver.com", nonce="31dff2d2", response="a4a80fd271da7fae67b2d7f070d99674"
Call-ID: ec2eede904f23ba9@192.168.1.146
CSeq: 47423 REGISTER
Expires: 120
User-Agent: Grandstream GXP2000 1.2.5.3
Max-Forwards: 70
Allow: INVITE,ACK,CANCEL,BYE,NOTIFY,REFER,OPTIONS,INFO,SUBSCRIBE,UPDATE,PRACK,MESSAGE
Content-Length: 0

X.X.224.7:5060 => X.X.194.138:5080 (REGISTER) Response: 401
SIP/2.0 401 Unauthorized
Via: SIP/2.0/UDP 192.168.200.2:5080;branch=z9hG4bK85544896f396b9b8;received=X.X.194.138;rport=5080
From: "pruebas" <sip:pruebas204@sip.myserver.com>;tag=2d3ce7700b58c4d9
To: <sip:pruebas204@sip.myserver.com>;tag=as39c9e1bd
Call-ID: ec2eede904f23ba9@192.168.1.146
CSeq: 47423 REGISTER
Server: sip
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH
Supported: replaces, timer
WWW-Authenticate: Digest algorithm=MD5, realm="sip", nonce="2f022db4"
Content-Length: 0

X.X.194.138:5080 => X.X.224.7:5060 (REGISTER) Request
REGISTER sip:sip.myserver.com SIP/2.0
Via: SIP/2.0/UDP 192.168.200.2:5080;branch=z9hG4bKa5b835310120bb10
From: "pruebas" <sip:pruebas204@sip.myserver.com>;tag=2d3ce7700b58c4d9
To: <sip:pruebas204@sip.myserver.com>
Contact: <sip:pruebas204@192.168.200.2:5080;transport=udp>
Supported: path
Authorization: Digest username="pruebas204", realm="sip", algorithm=MD5, uri="sip:sip.myserver.com", nonce="2f022db4", response="7cc357c57b0198d9bdbcad0cf2870710"
Call-ID: ec2eede904f23ba9@192.168.1.146
CSeq: 47424 REGISTER
Expires: 120
User-Agent: Grandstream GXP2000 1.2.5.3
Max-Forwards: 70
Allow: INVITE,ACK,CANCEL,BYE,NOTIFY,REFER,OPTIONS,INFO,SUBSCRIBE,UPDATE,PRACK,MESSAGE
Content-Length: 0

X.X.224.7:5060 => X.X.194.138:5080 (REGISTER) Response: 200
SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.200.2:5080;branch=z9hG4bKa5b835310120bb10;received=X.X.194.138;rport=5080
From: "pruebas" <sip:pruebas204@sip.myserver.com>;tag=2d3ce7700b58c4d9
To: <sip:pruebas204@sip.myserver.com>;tag=as39c9e1bd
Call-ID: ec2eede904f23ba9@192.168.1.146
CSeq: 47424 REGISTER
Server: sip
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH
Supported: replaces, timer
Expires: 120
Contact: <sip:pruebas204@192.168.200.2:5080;transport=udp>;expires=120
Date: Thu, 04 Apr 2013 20:11:14 GMT
Content-Length: 0

Como podemos ver en el resultado, únicamente nos aparece el tráfico de la señalización SIP correspondiente a ese usuario, algo bastante útil si tenemos cientos de clientes conectados. En este caso:

Cliente -> Servidor (REGISTER)
Servidor -> Cliente (401 Unauthorized)
Cliente -> Servidor (REGISTER)
Servidor -> Cliente (200 OK)

Asterisk SIP cracking

Hay mucha gente que dado un problema en el registro con su Asterisk no duda en pegar el log entero en un foro para ver si alguien le puede ayudar con su problema. Como en el log no se ve ninguna contraseña, se supone que es seguro pegarlo, ya que no aparece en claro. Me refiero a algo así:


<--- SIP read from UDP:X.X.X.X:5064 --->
REGISTER sip:sip.server.com SIP/2.0
Via: SIP/2.0/UDP 192.168.1.34:5064;branch=z9hG6bK7786e6f33d4b2d9d
From: "pruebas" <sip:pruebas@sip.server.com>;tag=2d3ce7700b58c2d9
To: <sip:pruebas@sip.server.com>
Contact: <sip:pruebas@192.168.1.34:5064;transport=udp>
Supported: path
Call-ID: 83a422169dd5328e@192.168.1.34
CSeq: 40001 REGISTER
Expires: 120
User-Agent: Grandstream GXP2000 1.2.5.3
Max-Forwards: 70
Allow: INVITE,ACK,CANCEL,BYE,NOTIFY,REFER,OPTIONS,INFO,SUBSCRIBE,UPDATE,PRACK,MESSAGE
Content-Length: 0

<------------->
--- (13 headers 0 lines)
---Sending to X.X.X.X:5064 (no NAT)
<--- Transmitting (NAT) to X.X.X.X:5064 --->
SIP/2.0 401 Unauthorized
Via: SIP/2.0/UDP 192.168.1.34:5064;branch=z9hG6bK7786e6f33d4b2d9d;received=X.X.X.X;rport=5064
From: "pruebas" <sip:pruebas@sip.server.com>;tag=2d3ce7700b58c2d9
To: <sip:pruebas@sip.server.com>;tag=as6f72938e
Call-ID: 83a422169dd5328e@192.168.1.34
CSeq: 40001 REGISTERServer: sip3
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH
Supported: replaces, timer
WWW-Authenticate: Digest algorithm=MD5, realm="sip3", nonce="5f56a903"
Content-Length: 0

<------------>
Scheduling destruction of SIP dialog '83a422169dd5328e@192.168.1.34' in 32000 ms (Method: REGISTER)
<--- SIP read from UDP:X.X.X.X:5064 --->
REGISTER sip:sip.server.com SIP/2.0
Via: SIP/2.0/UDP 192.168.1.34:5064;branch=z9hG6bK7786e6f33d4b2d9d
From: "pruebas" <sip:pruebas@sip.server.com>;tag=2d3ce7700b58c2d9
To: <sip:pruebas@sip.server.com>
Contact: <sip:pruebas@192.168.1.34:5064;transport=udp>
Supported: path
Authorization: Digest username="pruebas", realm="sip3", algorithm=MD5, uri="sip:sip.server.com",
nonce="5f56a903", response="f43b50de15bb07f909787dfb8ab487f8"
Call-ID: 83a422169dd5328e@192.168.1.34
CSeq: 40002 REGISTER
Expires: 120User-Agent: Grandstream GXP2000 1.2.5.3
Max-Forwards: 70
Allow: INVITE,ACK,CANCEL,BYE,NOTIFY,REFER,OPTIONS,INFO,SUBSCRIBE,UPDATE,PRACK,MESSAGE
Content-Length: 0

<------------->
--- (14 headers 0 lines)
---Sending to X.X.X.X:5064 (NAT)
<--- Transmitting (NAT) to X.X.X.X:5064 --->
SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.1.34:5064;branch=z9hG6bK7786e6f33d4b2d9d;received=X.X.X.X;rport=5064
From: "pruebas" <sip:pruebas@sip.server.com>;tag=2d3ce7700b58c2d9
To: <sip:pruebas@sip.server.com>;tag=as6f72238e
Call-ID: 83a422169dd5328e@192.168.1.34
CSeq: 40002 REGISTER
Server: sip3
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH
Supported: replaces, timer
Expires: 120
Contact: <sip:pruebas@192.168.1.34:5064;transport=udp>;expires=120
Date: Sat, 11 Aug 2012 08:59:34 GMT
Content-Length: 0

<------------>

sip3*CLI>


Antes de ver cómo podemos sacar partido a esta información, que el usuario tan amablemente nos publica en los foros, vamos a ver cómo se hace un registro en un Asterisk.

En primer lugar mandamos un paquete de registro al servidor de VoIP, indicando nuestro deseo de registrar un terminal. Para ello le enviaremos una serie de datos (a través de un paquete REGISTER) , pero nunca nuestra contraseña.

Tras mandar la petición al servidor, este nos devolverá un error ‘401 Unauthorized‘ pasándonos 2 valores que necesitaremos para completar el registro. Estos valores son: ‘realm‘ y ‘nonce‘. Acto seguido, generaremos un MD5 (indicándolo como valor de la variable response) que nos servirá para registrarnos, enviando un nuevo paquete REGISTER .  El servidor analizará ese response y nos devolverá un 200 si todo va bien, o un nuevo 401 en caso de que la contraseña sea incorrecta.

¿Y cómo generamos ese MD5 que realizará la validación? Pues de la siguiente forma:



response = md5(A:nonce:B)

-> nonce recordemos que nos lo facilita el servidor tras el primer REGISTER

A = md5(user:realm:pass)

-> user es nuestro usuario, que podemos ver, en claro, en el paquete generado en el primer REGISTER
-> realm también nos lo facilita el servidor tras el primer REGISTER
-> pass es la contraseña del usuario que desconocemos

B = md5(REGISTER:uri)

-> REGISTER es esa palabra tal cual
-> uri la podemos ver también en el primer paquete y tiene la forma 'sip:host.com'

De manera que con el log que nos han posteado tenemos todos los ingredientes, incluyendo el ‘response’, que es el MD5 final con la contraseña incluida y, lo único que desconocemos de todo esto, es la pass, algo que podremos intentar crackear teniendo el resto de datos.

Analicemos:


A = md5(user:realm:pass)
A = md5(pruebas:sip3:???)

B = md5(REGISTER:uri)
B = md5(REGISTER:sip:sip.server.com)
B = b726873895519048d1838bf7b4d78df6

response = md5(A:nonce:B)
response = md5(md5(pruebas:sip3:???):5f56a903:b726873895519048d1838bf7b4d78df6)
response = f43b50de15bb07f909787dfb8ab487f8  <- según la captura

Con un simple crackeador de MD5 podremos averiguar la contraseña de ese usuario que ha publicado el log en un foro.

A ver quién averigua la contraseña del ejemplo, que es de las fáciles 🙂

Escaner de servicios SIP

Continuando con la VoIP, tengo otro script que he programado este verano y que se trata de un escaner de servicio SIP. ¿porqué? pues porque el más que conocido SIPvicious (http://code.google.com/p/sipvicious/) o se ha quedado viejo, o no está especialmente diseñado para Asterisk, y el svmap.py, muchas veces, no muestra los resultados esperados. En algunos escaneos hay que usar el parámetro -c y en otros no para obtener resultados válidos, dependiendo de la versión con la que te enfrentes. Con lo cual toca escanear los mismos rangos 2 veces para obtener todos los resultados.

Además, que el fingerprint no da muchas pistas. Es más útil la información que aparece en el user agent.

Bueno, y tras criticar a este fabuloso programa (el resto de herramientas de la suite SIPvicious funcionan bien xDD) pongo mi modesto script, que escanea, usando threads, (va un poco más rápido que SIPvicious) bien un host o bien un rango de IPs. Del mismo modo que puede escanear diferentes puertos a la vez.

También es posible indicarle el método de escaneo que deseamos, permitiendo mandar paquetes INVITE, OPTIONS o REGISTER.

Y sin enrollarme más, aquí va el script:

#!/usr/bin/perl
# -=-=-=-=-=-=
# Sipscan v1.0
# -=-=-=-=-=-=
#
# Pepelux <pepeluxx[at]gmail[dot]com>

use warnings;
use strict;
use IO::Socket;
use NetAddr::IP;
use threads;
use threads::shared;
use Getopt::Long;
use Digest::MD5;

my $maxthreads = 300;
my $time_ping = 2; # wait secs

my $threads : shared = 0;
my $found : shared = 0;
my $count : shared = 0;
my $percent : shared = 0;
my @range;
my @results;

my $host = '';	   # hosts to scan
my $port = '';	   # ports to scan
my $method = '';	# method to use (INVITE, REGISTER, OPTIONS)
my $v = 0;		   # verbose mode

my $user = "100";
my $pass = "aaaaaa";
my $lport = "5061";
my $myip = "anonymous";
my $tmpfile = "sipscan".time().".txt";

open(OUTPUT,">$tmpfile");

OUTPUT->autoflush(1);
STDOUT->autoflush(1);

sub init() {
	my $pini;
	my $pfin;

	if ($^O =~ /Win/) {system("cls");}else{system("clear");}

	# check params
	my $result = GetOptions ("h=s" => \$host,
	                         "m=s" => \$method,
	                         "p=s" => \$port,
	                         "v+" => \$v);

	help() if ($host eq "");

	$port = "5060" if ($port eq "");
	$method = uc($method);
	$method = "OPTIONS" if ($method eq "");

	if ($host =~ /\-/) {
		my $ip = $host;

		$ip =~ /([0-9|\.]*)-([0-9|\.]*)/;
		my $ipini = $1;
		my $ipfin = $2;

		my $ip2 = $ipini;
		$ip2 =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/;
	  	my $ip2_1 = int($1);
	  	my $ip2_2 = int($2);
	  	my $ip2_3 = int($3);
	  	my $ip2_4 = int($4);

		my $ip3 = $ipfin;
		$ip3 =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/;
	  	my $ip3_1 = int($1);
	  	my $ip3_2 = int($2);
	  	my $ip3_3 = int($3);
	  	my $ip3_4 = int($4);

	  	for (my $i1 = $ip2_1; $i1 <= $ip3_1; $i1++) {
		  	for (my $i2 = $ip2_2; $i2 <= $ip3_2; $i2++) {
			  	for (my $i3 = $ip2_3; $i3 <= $ip3_3; $i3++) {
				  	for (my $i4 = $ip2_4; $i4 <= $ip3_4; $i4++) {
					  	$ip = "$i1.$i2.$i3.$i4";
						push @range, $ip;
					}
				}
			}
		}

	}
	else {
		my $ip = new NetAddr::IP($host);

		if ($ip < $ip->broadcast) {
			$ip++;

			while ($ip < $ip->broadcast) {
				my $ip2 = $ip;
				$ip2 =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/;
			  	$ip2 = "$1.$2.$3.$4";
				push @range, $ip2;
				$ip++;
			}
		}
		else {
			push @range, $host;
		}
	}

	if ($port =~ /\-/) {
		$port =~ /([0-9]*)-([0-9]*)/;
		$pini = $1;
		$pfin = $2;
	}
	else {
		$pini = $port;
		$pfin = $port;
	}

	my $nhost = @range;

	for (my $i = 0; $i <= $nhost; $i++) {
		for (my $j = $pini; $j <= $pfin; $j++) {
			while (1) {
				if ($threads < $maxthreads) {
					last unless defined($range[$i]);
					my $thr = threads->new(\&scan, $range[$i], $j);
					$thr->detach();
					$percent = ($count/($nhost*($pfin-$pini+1)))*100;
					$percent = sprintf("%.1f", $percent);
					print "THREADS: $threads || STATUS: $percent% || FOUND: $found     \r";

					last;
				}
				else {
					sleep(1);
				}
			}
		}
	}

	sleep(1);

	close(OUTPUT);

	print "THREADS: 0 || STATUS: 100% || FOUND: $found     \r\n";

	open(OUTPUT, $tmpfile);

	print "\nIP:port\t\t\t  User-Agent\n";
	print "=======\t\t\t  ==========\n";

	my @results = <OUTPUT>;
	close (OUTPUT);

	unlink($tmpfile);

	@results = sort(@results);

	foreach(@results) {
		print $_;
	}

	print "\n";

	exit;
}

sub scan {
	my $ip = shift;
	my $nport = shift;

	if ($method eq "REGISTER") {
		register($ip, $nport);
	}
	if ($method eq "INVITE") {
		invite($ip, $nport);
	}
	if ($method eq "OPTIONS") {
		options($ip, $nport);
	}
}

sub options {
	{lock($count);$count++;}
	{lock($threads);$threads++;}

	my $ip = shift;
	my $nport = shift;

	my $sc = new IO::Socket::INET->new(PeerPort=>$nport, Proto=>'udp', PeerAddr=>$ip);

	$lport = $sc->sockport();

	my $branch = &generate_random_string(71, 0);
	my $callerid = &generate_random_string(32, 1);

	my $msg = "OPTIONS sip:$ip SIP/2.0\n";
	$msg .= "Supported: \n";
	$msg .= "Allow: INVITE, ACK, OPTIONS, CANCEL, BYE\n";
	$msg .= "Contact: $user <sip:".$user."@".$ip.":$lport>\n";
	$msg .= "Via: SIP/2.0/UDP $ip:$lport;branch=$branch\n";
	$msg .= "Call-id: $callerid\n";
	$msg .= "Cseq: 1 OPTIONS\n";
	$msg .= "From: $user <sip:".$user."@".$ip.">;tag=ddb044893807095baf1cf07269f03118\n";
	$msg .= "Max-forwards: 70\n";
	$msg .= "To: $user <sip:".$user."@".$ip.">\n";
	$msg .= "Content-length: 0\n\n";

	print $sc $msg;

	print "\nSending:\n=======\n$msg\n\n" if ($v eq 1);

	my $data = "";
	my $server = "";
	my $useragent = "";

	LOOP: {
		while (<$sc>) {
			my $line = $_;

			if ($line =~ /[Ss]erver/ && $server eq "") {
	   		$line =~ /[Ss]erver\:\s(.+)\r\n/;

		   	if ($1) {
					$server = $1;
				}
			}

			if ($line =~ /[Uu]ser\-[Aa]gent/ && $useragent eq "") {
	   		$line =~ /[Uu]ser\-[Aa]gent\:\s(.+)\r\n/;

		   	if ($1) {
					$useragent = $1;
				}
			}

			$data .= $line;

			if ($line =~ /^\r\n/) {
				last LOOP;
			}
		}
	}

	if ($data ne "") {
		if ($v eq 1) {
			print "\nReceiving:\n=========\n$data\n\n";
		}

		if ($server eq "") {
			$server = $useragent;
		}
		else {
			if ($useragent ne "") {
				$server .= " - $useragent";
			}
		}

		my $dhost = "$ip:$nport";
		$dhost .= "\t" if (length($dhost) < 10);
		$server = "Unknown" if ($server eq "");
		print OUTPUT "$dhost\t| $server\n";
		{lock($found);$found++;}
	}

	{lock($threads);$threads--;}
}

sub invite {
	{lock($count);$count++;}
	{lock($threads);$threads++;}

	my $ip = shift;
	my $nport = shift;

	my $sc = new IO::Socket::INET->new(PeerPort=>$nport, Proto=>'udp', PeerAddr=>$ip, Timeout => 2);

	$lport = $sc->sockport();

	my $branch = &generate_random_string(71, 0);
	my $callerid = &generate_random_string(32, 1);

	my $msg = "INVITE sip:$ip SIP/2.0\n";
	$msg .= "Supported: \n";
	$msg .= "Allow: INVITE, ACK, OPTIONS, CANCEL, BYE\n";
	$msg .= "Contact: $user <sip:".$user."@".$myip.":$lport>\n";
	$msg .= "Via: SIP/2.0/UDP $myip:$lport;branch=$branch\n";
	$msg .= "Call-id: $callerid\n";
	$msg .= "Cseq: 1 INVITE\n";
	$msg .= "From: $user <sip:".$user."@".$myip.">;tag=ddb044893807095baf1cf07269f03118\n";
	$msg .= "Max-forwards: 70\n";
	$msg .= "To: $user <sip:".$user."@".$ip.">\n";
	$msg .= "Content-length: 123\n\n";
	$msg .= "v=0\n";
	$msg .= "o=anonymous 1312841870 1312841870 IN IP4 $ip\n";
	$msg .= "s=session\n";
	$msg .= "c=IN IP4 $ip\n";
	$msg .= "t=0 0\n";
	$msg .= "m=audio 2362 RTP/AVP 0\n\n";

	print $sc $msg;

	print "\nSending:\n=======\n$msg\n\n" if ($v eq 1);

	my $data = "";
	my $server = "";
	my $useragent = "";
	my $line = "";

	LOOP: {
		while (<$sc>) {
			$line = $_;

			if ($line =~ /[Ss]erver/ && $server eq "") {
	   		$line =~ /[Ss]erver\:\s(.+)\r\n/;

		   	if ($1) {
					$server = $1;
				}
			}

			if ($line =~ /[Uu]ser\-[Aa]gent/ && $useragent eq "") {
	   		$line =~ /[Uu]ser\-[Aa]gent\:\s(.+)\r\n/;

		   	if ($1) {
					$useragent = $1;
				}
			}

			$data .= $line;

			if ($line =~ /^\r\n/) {
				last LOOP;
			}
		}
	}

	if ($data ne "") {
		if ($v eq 1) {
			print "\nReceiving:\n=========\n$data\n\n";
		}

		if ($server eq "") {
			$server = $useragent;
		}
		else {
			if ($useragent ne "") {
				$server .= " - $useragent";
			}
		}

		my $dhost = "$ip:$nport";
		$dhost .= "\t" if (length($dhost) < 10);
		$server = "Unknown" if ($server eq "");
		print OUTPUT "$dhost\t| $server\n";
		{lock($found);$found++;}
	}

	{lock($threads);$threads--;}
}

sub register {
	{lock($count);$count++;}
	{lock($threads);$threads++;}

	my $ip = shift;
	my $nport = shift;

	my $sc = new IO::Socket::INET->new(PeerPort=>$nport, Proto=>'udp', PeerAddr=>$ip);

	$lport = $sc->sockport();

	my $branch = &generate_random_string(71, 0);
	my $callerid = &generate_random_string(32, 1);

	my $msg = "REGISTER sip:$ip SIP/2.0\n";
	$msg .= "Via: SIP/2.0/UDP $myip:$lport;branch=$branch\n";
	$msg .= "Call-id: $callerid\n";
	$msg .= "Contact: $user <sip:".$user."@".$myip.":$lport>\n";
	$msg .= "Cseq: 1 REGISTER\n";
	$msg .= "Expires: 900\n";
	$msg .= "From: $user <sip:".$user."@".$myip.">;tag=ddb044893807095baf1cf07269f03118\n";
	$msg .= "Max-forwards: 70\n";
	$msg .= "To: $user <sip:".$user."@".$ip.">\n";
	$msg .= "Content-length: 0\n\n";

	print $sc $msg;

	print "\nSending:\n=======\n$msg\n\n" if ($v eq 1);

	my $nonce = "";
	my $realm = "";
	my $data = "";

	LOOP: {
		while (<$sc>) {
			my $line = $_;

			if ($line =~ /nonce/ && $nonce eq "") {
	   		$line =~ /nonce\=\"(\w+)\"/i;

		   	if ($1) {
					$nonce = $1;
				}
			}

			if ($line =~ /realm/ && $realm eq "") {
	   		$line =~ /realm\=\"(\w+)\"/i;

		   	if ($1) {
					$realm = $1;
				}
			}

			$data .= $line;

			if ($line =~ /^\r\n/) {
				last LOOP;
			}
		}
	}

	if ($data ne "") {
		print "\nReceiving:\n=========\n$data\n\n" if ($v eq 1);

		$branch = &generate_random_string(71, 0);

		my $md5 = Digest::MD5->new;
		$md5->add($user, ':', $realm, ':', $pass);
		my $HXA = $md5->hexdigest;
		my $uri = "sip:$ip";

		$md5 = Digest::MD5->new;
		$md5->add('REGISTER', ':', $uri);
		my $HXB = $md5->hexdigest;

		$md5 = Digest::MD5->new;
		$md5->add($HXA, ':', $nonce, ':', $HXB);
		my $response = $md5->hexdigest;

		$msg = "REGISTER sip:$ip SIP/2.0\n";
		$msg .= "Via: SIP/2.0/UDP $myip:$lport;branch=$branch\n";
		$msg .= "Call-id: $callerid\n";
		$msg .= "Contact: $user <sip:".$user."@".$myip.":$lport>\n";
		$msg .= "Expires: 900\n";
		$msg .= "From: $user <sip:".$user."@".$myip.">;tag=ddb044893807095baf1cf07269f03118\n";
		$msg .= "Max-forwards: 70\n";
		$msg .= "To: $user <sip:".$user."@".$ip.">\n";
		$msg .= "Authorization: Digest username=\"$user\",realm=\"$realm\",nonce=\"$nonce\",uri=\"sip:$ip\",response=\"$response\"\n";
		$msg .= "Cseq: 2 REGISTER\n";
		$msg .= "Content-length: 0\n\n";

		print $sc $msg;

		print "Sending:\n=======\n$msg\n\n" if ($v eq 1);

		$data = "";
		my $server = "";

		LOOP: {
			while (<$sc>) {
				my $line = $_;

				if ($line =~ /[Ss]erver/ && $server eq "") {
		   		$line =~ /[Ss]erver\:\s(.+)\r\n/;

			   	if ($1) {
						$server = $1;
					}
				}

				$data .= $line;

				if ($line =~ /^\r\n/) {
					last LOOP;
				}
			}
		}

		if ($v eq 1) {
			print "\nReceiving:\n=========\n$data\n\n";
		}

		my $dhost = "$ip:$nport";
		$dhost .= "\t" if (length($dhost) < 10);
		$server = "Unknown" if ($server eq "");
		print OUTPUT "$dhost\t| $server\n";
		{lock($found);$found++;}
	}

	{lock($threads);$threads--;}
}

sub generate_random_string {
	my $length_of_randomstring = shift;
	my $only_hex = shift;
	my @chars;

	if ($only_hex == 0) {
		@chars = ('a'..'z','0'..'9');
	}
	else {
		@chars = ('a'..'f','0'..'9');
	}
	my $random_string;
	foreach (1..$length_of_randomstring) {
		$random_string.=$chars[rand @chars];
	}
	return $random_string;
}

sub help {
	print qq{
Usage:  $0 -h <host> [options]

    == Options ==
      -m <string>      = Method: REGISTER/INVITE/OPTIONS/ALL (default: REGISTER)
      -p <integer>     = Remote SIP port (default: 5060)
      -v               = Verbose mode

    == Examples ==
         \$$0 -h 192.168.0.1 -m invite
         \$$0 -h 192.168.0.0/24 -p 5060-5070
         \$$0 -h 192.168.0.1-192.168.0.100 -p 5060-5070 -v

};

	exit 1;
}

init();