Post

Escaner de puertos escrito en Python

El objetivo de este articulo es demostrar como es la creación de un escaner de puertos simples en python.

Librerias utilizadas

  • socket
  • argparse
  • sys
  • concurrent.futures
  • termcolor

Definir Ctrl+C

Objetivo: Permitir que el usuario interrumpa el programa de manera segura

1
2
3
4
5
6
7
8
9
10
11
12
open_sockets = []

def def_handler(sig, frame):

    print(colored(f"[!] Saliendo del programa...", 'red'))

    for socket in open_sockets:
        socket.close()

    sys.exit(1)

signal.signal(signal.SIGINT, def_handler) # CTRL+C

Detalles:

  • Muestra un mensaje indicando que el programa se está cerrando.
  • Cierra todos los sockets que están abiertos en open_sockets para liberar recursos.
  • Termina el programa con sys.exit(1).

Capturar el objetivo (target) y los puertos (port) que se quieren escanear

1
2
3
4
5
6
7
def get_arguments(): # Definir argumentos
    parser = argparse.ArgumentParser(description='Escaner de puertos TCP')
    parser.add_argument("-t", "--target", dest="target", required=True, help="Target a escanear (Ej: -t 192.168.1.1)")
    parser.add_argument("-p", "--port", dest="port", required=True, help="Puertos a escanear (Ej: -p 1-1000)")
    options = parser.parse_args() # Recopila todos los argumentos y los almacena en options

    return options.target, options.port # Retorna los argumentos

Detalles:

  • Define los argumentos requeridos: -t para el objetivo y -p para los puertos.
  • Si alguno de los argumentos no se proporciona, muestra un mensaje de ayuda automáticamente.
  • Retorna el objetivo y la cadena de puertos proporcionada por el usuario.

Crear un socket TCP con un tiempo de espera

1
2
3
4
5
6
7
def create_socket():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(1) # Timeout de 1 segundo para determinar si el puerto esta cerrado o no

    open_sockets.append(s)

    return s 

Detalles:

  • El socket se configura para usar IPv4 y TCP.
  • Se establece un tiempo de espera de 1 segundo, lo que ayuda a determinar si un puerto está cerrado.
  • Se agrega el socket a la lista open_sockets para poder cerrarlo más tarde si es necesario.
  • Retorna el socket creado.

Verificar si un puerto específico está abierto o cerrado

1
2
3
4
5
6
7
8
9
10
11
def port_scanner(port, host):

    s = create_socket()

    try:
        s.connect((host, port)) # Se entabla la conexion
        print(colored(f"\n[+] El puerto {port} esta abierto", 'green'))
        s.close()

    except (socket.timeout, ConnectionRefusedError): # Manejo de excepciones
        pass

Detalles:

  • Crea un nuevo socket llamando a create_socket().
  • Intenta conectar el socket al puerto y dirección IP proporcionados.
  • Si la conexión es exitosa, imprime un mensaje indicando que el puerto está abierto y cierra el socket.
  • Si no puede conectar (por timeout o rechazo de conexión), no hace nada y deja que el socket se cierre.

Escanear múltiples puertos simultáneamente para acelerar el proceso

1
2
3
4
def scan_ports(ports, target):

    with ThreadPoolExecutor(max_workers=100) as executor:
        executor.map(lambda port: port_scanner(port, target), ports)

Detalles:

  • Utiliza ThreadPoolExecutor para gestionar hasta 100 hilos simultáneamente.
  • Cada hilo ejecuta port_scanner para un puerto en particular.
  • La función executor.map() asigna la tarea de escaneo a cada puerto.

Interpretar la entrada del usuario sobre los puertos y convertirla en un formato usable (rango o lista)

1
2
3
4
5
6
7
8
9
def parse_ports(ports_str):

    if '-' in ports_str: # Escanear un rango de puertos
        start, end = map(int, ports_str.split('-'))
        return range(start, end + 1)
    elif ',' in ports_str:
        return map(int, ports_str.split(','))
    else:
        return (int(ports_str),)

Detalles:

  • Si la cadena contiene un guion (-), se interpreta como un rango de puertos (por ejemplo, 1-100 se convierte en range(1, 101)).
  • Si la cadena contiene comas (,), se convierte en una lista de puertos específicos (por ejemplo, 22,80,443 se convierte en [22, 80, 443]).
  • Si se proporciona un solo puerto, lo convierte en una tupla con un solo número.

Orquestar la ejecución de todas las funciones para llevar a cabo el escaneo de puertos

1
2
3
4
5
6
7
8
def main():
    
    target, ports_str = get_arguments() # Recibir los argumentos
    ports = parse_ports(ports_str)
    scan_ports(ports, target)
    
if __name__ == '__main__':
    main()

Detalles:

  • Llama a get_arguments() para obtener el objetivo y los puertos a escanear.
  • Utiliza parse_ports() para interpretar los puertos proporcionados.
  • Llama a scan_ports() para comenzar el escaneo concurrente de los puertos.

Flujo General del Programa

  1. Inicia el programa: Se define el manejador de señales y se capturan los argumentos.
  2. Interpretación de puertos: Se convierten los puertos introducidos en un rango o lista de puertos.
  3. Escaneo de puertos: Se escanean los puertos de manera concurrente usando hilos.
  4. Manejo de interrupciones: Si el usuario presiona CTRL+C, el programa se cierra limpiamente, cerrando todos los sockets abiertos.

Este flujo permite escanear puertos de manera eficiente y manejar la interrupción del programa de forma segura.

Screenshot

screenshot screenshot

This post is licensed under CC BY 4.0 by the author.