Simple UDP "port unreachable" port scanner


Di seguito un semplice codice di esempio riguardante l'implementazione di un basilare UDP scanner.
La tecnica di scanning impiegata è la stessa che adotta nmap per mezzo dell'opzione -sU (esempio: nmap -sU -p 53 127.0.0.1) e sfrutta l'eventuale pacchetto ICMP di
tipo port unreachable (type 3) che può tornare indietro nel caso in cui la porta UDP scansionata risulti essere irraggiungibile.
UDP, infatti, non ha alcun meccanismo per informare il client che la porta presso cui intende instaurare la connessione è irraggiungibile, viene cosi incaricato ICMP di farlo al posto suo.
Nel semplice codice sotto riportato non si è fatto altro che:
  • Inizializzare un socket e mandare un comune pacchetto UDP all'host remoto sulla porta interessata.
  • Inizializzare un secondo socket di tipo RAW pronto a ricevere eventuali pacchetti ICMP di ritorno.
  • Nel caso in cui tale socket riceva un pacchetto ICMP type 3 dall'host remoto a cui si è precedentemente mandato il pacchetto UDP si assume che la porta scansionata sia chiusa.
  • Nel caso in cui tale socket non riceva nessun pacchetto ICMP type 3 dall'host remoto nell'arco di 2 secondi si assume che la porta risulti essere aperta.
Come metodo non risulta essere molto affidabile in quanto una banale regola sul firewall dell'host remoto potrebbe evitare che in seguito a una richiesta del tipo sopra menzionato non venga mandato alcun pacchetto ICMP di tipo 3 e che il risultato dello scanning possa di conseguenza risultare sfalsato generando un falso positivo.
Il caso più classico di falso positivo si può verificare nel caso in cui la macchina remota che si intende scansionare sia down e non mandando indietro alcun pacchetto (ovviamente) venga considerata raggiungibile e con porta UDP in stato listening.
A tal proposito prima di cominciare lo scanning il programma pinga preventivamente la macchina remota e in caso di mancata risposta avvisa che vi è una alta probabilità di intercorrere in falsi positivi. Le porte in stato "open" rilevate durante lo scanning molto probabilmente si riferiranno a una macchina che non è neanche operativa, o che ha installato un firewall che droppa preventivamente i pacchetti ICMP di tipo ECHO in entrata e proibisce il ritorno di pacchetti ICMP di tipo PORT_UNREACHABLE in uscita in seguito alla ricezione di un pacchetto UDP su una porta chiusa.


  #!/usr/bin/python

# simple UDP scanner by billiejoex
# http://billiejoex.altervista.org    

import select
import socket
import sys
import os
import impacket
import getopt
import popen2
from   impacket import ImpactDecoder
    
def verify():
    p_num = socket.getprotobyname('icmp')
    s = socket.socket(socket.AF_INET, socket.SOCK_RAW, p_num)
    s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
    decoder = ImpactDecoder.EthDecoder()
    if s in select.select([s], [], [],2)[0]:
        pkt = s.recvfrom(4096)[0]
        ip = ImpactDecoder.IPDecoder().decode(pkt) #layer ip pointer
        icmp = ip.child() #layer icmp pointer
        ipsrc = ip.get_ip_src()
        ipdst = ip.get_ip_dst()
        type  = icmp.get_icmp_type()
        code  = icmp.get_icmp_code()
        if (type == 3) and (code == 3) and (ipsrc == host):
            return False
    return True

def helper():
    print """\
Simple UDP scanner

Syntax:
  %s [-p int,int] [-r int-int] remote_host
 
Options:
  -p      port(s)
  -r      range port

Examples:
  python %s -p 137,139 10.0.0.1
  python %s -p 53 -r 137-139 10.0.0.1
""" %(sys.argv[0],sys.argv[0],sys.argv[0])
    
def parser():
    global args, ports
    ports = []
    try: opts, args = getopt.getopt(sys.argv[1:], "h, p:p, r:r")    
    except: helper(); os._exit(0)
    x = 2
    for o, a in opts:
        if o == "-p":
            x += 1
            f = a.split(',')
            for i in f: ports.append(int(i))            
        if o == '-r':
            x += 2
            f = a.split('-')
            for i in range(int(f[0]), int(f[1])):
                ports.append(int(i))        
    if opts == []: helper(); os._exit(0)

parser()
host = args[0]
exec_cmd = popen2.popen4('ping -c 2 '+host) #try to ping host
output = exec_cmd[0].read()
if 'bytes from' in output: print " Host %s seems to be up" %host
else: print " Host %s seems to be down.\n Starting false positive UDP scan" %host
u = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
ports.sort()
for i in ports:
    addr = (host,i)
    u.sendto('',addr)
    if verify(): print "  open: %-25s" %i
    else: print "  closed: %-25s" %i


  • root@server:/home/user# python udp.py -r 136-142 12.255.10.2
    Host 12.255.10.2 seems to be up.
      closed  136
      open    137    netbios-ns
      open    138    netbios-dgm
      closed  139    netbios-ssn
      closed  140
      closed  141
      closed  142
    Finished.