Responsive image

Ping-Sweep

31 mayo 2021

Ping-Sweep es una herramienta un poco más compleja a las realizadas hasta la fecha. Esta herramienta, como su nombre indica, realiza un barrido de ping de una red dada una dirección y una máscara de red en formato CIDR.

La motivación del desarrollo de esta herramienta se debe que las máquinas virtuales CTF de la plataforma HackMyVM deben descargarse y ejecutarse en una máquina anfitrión. Por tanto es necesario encontrar la dirección IP de la máquina para poder empezar con el CTF y en mi caso no puedo utilizar un escaneo de ARP como el realizado con la herramienta arp-scan debido a que despliego las máquinas en una red distinta a la red local. Esto es así porque los paquetes ARP no pueden atravesar redes ya que ARP es un protocolo de nivel 2.

La herramienta está desarrollada en Python3 y publicada en Github para que pueda ser fácilmente descargable por cualquier usuario. Además, cuenta con un modo de reconocimiento de SO basado en TTL como el de la herramienta Findos. Su uso se explica con el siguiente ejemplo:

El código de la herramienta es el siguiente:

#!/usr/bin/python3

import subprocess, sys, re
import socket, struct
import threading

class bcolors:
    HEADER = '\033[95m'
    OKBLUE = '\033[94m'
    OKCYAN = '\033[96m'
    OKGREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    ENDC = '\033[0m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'

solaris_ttl = 254
windows_ttl = 128
linux_ttl = 64

usage = "Usage: ping-sweep <IPv4 address>/<netmask> [options]\n    Use ping-sweep -h to see help."
help_usage = usage.split("\n")[0] + "\n    Example: ping-sweep 192.168.0.1/24\n    Options:\n\t-T=<0-5>\tTimeout. Higher number means faster but may cause undetections.\n\t-v\tVerbose.\n\t-O\tOS discovery (TTL).\n\t-o=<file>\tSave output in file."
cidr_pattern = re.compile("^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}$")



def variables_definition(argv):
    global os_detection, verbose, timeout, network, bits, netmask, num_hosts, out_file_name, out_file 
    global threads, hosts_up
    threads = []
    hosts_up = []
    try:
        os_detection = False
        verbose = False
        timeout = 5
        out_file_name = ""
        cidr = argv[0]
        if len(argv) > 1:
            argv = argv[1:]
            for arg in argv:                
                if "-v" in arg:
                    verbose = True
                elif "-T" in arg:
                    if len(arg) == 4 and "=" in arg:
                        arg = arg.split("=")[1]
                        if arg == '0':
                            timeout = 5
                        elif arg == '1':
                            timeout = 2
                        elif arg == '2':
                            timeout = 1
                        elif arg == '3':
                            timeout = 0.2
                        elif arg == '4':
                            timeout = 0.1
                        elif arg == '5':
                            timeout = 0.05
                        else:
                            print(bcolors.WARNING + "[!] " + bcolors.ENDC + usage)
                            exit(1)
                    else:
                        print(bcolors.WARNING + "[!] " + bcolors.ENDC + usage)
                        exit(1)
                elif "-O" in arg:
                    os_detection = True
                elif "-o" in arg:
                    if len(arg) > 3 and "=" in arg:
                        out_file_name = arg.split("=")[1]
                        out_file = open(out_file_name, "w")
                    else:
                        print(bcolors.WARNING + "[!] " + bcolors.ENDC + usage)
                        exit(1)
                else:
                    print(bcolors.WARNING + "[!] " + bcolors.ENDC + usage)
                    exit(1)

        cidr_match = cidr_pattern.match(cidr)
        if cidr_match == False:
            print(bcolors.WARNING + "[!] " + bcolors.ENDC + usage)
            exit(1)

        network, bits = cidr.split('/')
        netmask = socket.inet_ntoa(struct.pack('!I', (1 << 32) - (1 << (32-int(bits)))))
        
        network = [int(x) for x in network.split(".")]
        netmask = [int(x) for x in netmask.split(".")]
        num_hosts = pow(2, 32-int(bits))
        for i in range(len(network)):
            network[i] = network[i]&netmask[i]
    except ValueError:
        print(bcolors.WARNING + "[!] " + bcolors.ENDC + usage)
        exit(1)

def send_icmp_echo(target_ip):
    target_ip = str(target_ip[0])+"."+str(target_ip[1])+"."+str(target_ip[2])+"."+str(target_ip[3])
    #print(target_ip)
    cmd = "timeout " + str(timeout) + " bash -c \"ping " + target_ip + " | head -n2 | tail -n1 | cut -d ' ' -f 6 | cut -d '=' -f 2\""
    ttl = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    if len(str(ttl.stdout)) > 3 and "Unreachable" not in str(ttl.stdout) and "0 received" not in str(ttl.stdout):
        ttl = int(str(ttl.stdout)[2:-3])
        if os_detection == True:
            hosts_up.append([target_ip, ttl])
        else:
            hosts_up.append(target_ip)
        if verbose == True:
            if os_detection == True:
                print(bcolors.OKCYAN + "[v]" + bcolors.ENDC + "\tHost up: " + target_ip + bcolors.OKBLUE + " - " + bcolors.ENDC + str(ttl))
            else:
                print(bcolors.OKCYAN + "[v]" + bcolors.ENDC + "\tHost up: " + target_ip)


def sort_key(item):
    sort_string = ""
    if  isinstance(item, str):
        sort_string = item
    else:
        sort_string = str(item[0])
    sort_string = [int(x) for x in sort_string.split(".")]

    return(sort_string)

def show_output():
    hosts_up.sort(key=sort_key)
    for host in hosts_up:
        if os_detection == False:
            print(bcolors.OKGREEN + "[+] " + bcolors.ENDC + host + bcolors.OKGREEN)
        else:
            ttl = host[1]
            if ttl > solaris_ttl:
                print(bcolors.OKGREEN + "[+] " + bcolors.ENDC + host[0] + bcolors.OKBLUE + " - " + bcolors.ENDC +"unknown")
            if ttl > windows_ttl and ttl <= solaris_ttl:
                print(bcolors.OKGREEN + "[+] " + bcolors.ENDC + host[0] + bcolors.OKBLUE + " - " + bcolors.ENDC +"solaris")
            elif ttl > linux_ttl and ttl <= windows_ttl:
                print(bcolors.OKGREEN + "[+] " + bcolors.ENDC + host[0] + bcolors.OKBLUE + " - " + bcolors.ENDC + "windows")
            elif ttl <= linux_ttl:
                print(bcolors.OKGREEN + "[+] " + bcolors.ENDC + host[0] + bcolors.OKBLUE + " - " + bcolors.ENDC + "linux")

def main(argv):
    try:
        variables_definition(argv)

        if verbose:
            print(bcolors.OKCYAN + "[v]" + bcolors.ENDC + " network -> " + str(network[0]) + "." + str(network[1]) + "." + str(network[2]) + "." + str(network[3]))
            print(bcolors.OKCYAN + "[v]" + bcolors.ENDC + " netmask -> " + str(netmask[0]) + "." + str(netmask[1]) + "." + str(netmask[2]) + "." + str(netmask[3]))
            print(bcolors.OKCYAN + "[v]" + bcolors.ENDC + " hosts to scan ->", num_hosts)
            print(bcolors.OKCYAN + "[v]" + bcolors.ENDC + " Scan started...")

        hosts = [0,0,0,0]
        for i in range(num_hosts):
            
            target_ip = [network[0]+hosts[0], network[1]+hosts[1], network[2]+hosts[2], network[3]+hosts[3]]
            string = "thread-"+str(i)
            t = threading.Thread(target=send_icmp_echo, args=(target_ip, ))
            threads.append(t)
            t.start()
            if hosts[3] < 255:
                hosts[3] += 1
            else:
                hosts[3] = 0
                if hosts[2] < 255:
                    hosts[2] += 1
                else:
                    hosts[2] = 0
                    if hosts[1] < 255:
                        hosts[1] += 1
                    else:
                        hosts[1] = 0
                        if hosts[0] < 255:
                            hosts[0] += 1

        #wait for all threads to finish
        for i in range(len(threads)):
            threads[i].join()
        if verbose:
            print(bcolors.OKCYAN + "[v]" + bcolors.ENDC + " Scan finished")
        
        show_output()

        if out_file_name != "" and len(hosts_up) > 0:
            if isinstance(hosts_up[0], str):
                for host in hosts_up:
                    out_file.write(host + "\n")
            else:
                for host in hosts_up:
                    out_file.write(host[0] + "\n") 
            out_file.close()

    except KeyboardInterrupt:
        print("\n"+bcolors.FAIL + "[-] " + bcolors.ENDC + "Scan stopped")

if __name__ == "__main__":
    if len(sys.argv) >= 2:
        if "-h" in sys.argv[1]:
            print(bcolors.HEADER + "[?] " + bcolors.ENDC + help_usage)
            exit(1)
        main(sys.argv[1:])
    else:
        print(bcolors.WARNING + "[!] " + bcolors.ENDC + usage)