#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
simulador_ataques.py

Simulador de Ataques ENS (versión ampliada sin Paramiko).

Módulos incluidos:
  1) TCP SYN scan (puertos especificados o 1–1024 si se usa --full-tcp)
  2) FTP anónimo (puerto 21)
  3) Telnet banner (puerto 23)
  4) HTTP banner grabbing (puerto 80)
  5) ICMP flood (cantidad parametrizable)
  6) UDP scan (puertos especificados o por defecto [53,67,123])
  7) SNMP community check (puerto 161, community "public")
  8) Informe JSON y HTML final

Uso:
  sudo python3 simulador_ataques.py <IP_OBJETIVO> [--tcp-ports …] [--full-tcp] \
      [--udp-ports …] [--icmp-flood N]

Ejemplo:
  sudo python3 simulador_ataques.py 192.168.100.2 \
         --tcp-ports 21 22 23 80 139 445 3389 \
         --udp-ports 53 67 123 \
         --icmp-flood 20
"""

import sys
import argparse
import socket
import threading
import json
import os
from datetime import datetime

# Intentar importar Scapy
try:
    from scapy.all import IP, TCP, UDP, ICMP, sr1, send
except ImportError:
    sys.exit("Falta Scapy. Ejecuta: pip install scapy")

# ──────────────────────────────────────────────────────────
# FUNCIONES DE ESCANEO / PRUEBAS
# ──────────────────────────────────────────────────────────

def scan_tcp_port(target, port, timeout=1):
    """
    Envía un SYN a target:port. Devuelve True si recibe SYN-ACK (puerto abierto).
    """
    pkt = IP(dst=target)/TCP(dport=port, flags="S")
    resp = sr1(pkt, timeout=timeout, verbose=0)
    if resp is not None and resp.haslayer(TCP) and resp[TCP].flags == 0x12:
        # SYN-ACK → puerto abierto. Enviar RST para cortar handshake.
        rst = IP(dst=target)/TCP(dport=port, flags="R")
        send(rst, verbose=0)
        return True
    return False

def tcp_syn_scan(target, ports):
    """
    Escanea varios puertos TCP en paralelo (threads). Devuelve lista de puertos abiertos.
    """
    abiertos = []
    lock = threading.Lock()

    def worker(p):
        if scan_tcp_port(target, p):
            with lock:
                abiertos.append(p)

    threads = []
    for p in ports:
        t = threading.Thread(target=worker, args=(p,))
        t.start()
        threads.append(t)
    for t in threads:
        t.join()

    return sorted(abiertos)

def full_tcp_scan(target, start=1, end=1024):
    """
    Escaneo TCP completo de puertos [start..end], usando hilos en bloques de 100 puertos.
    """
    all_ports = list(range(start, end+1))
    return tcp_syn_scan(target, all_ports)

def prueba_ftp_anonimo(target, port=21):
    """
    Intenta login FTP anónimo en target:port. Devuelve True si permite conexión anónima.
    """
    s = socket.socket()
    s.settimeout(3)
    try:
        s.connect((target, port))
        _ = s.recv(1024).decode(errors="ignore")  # banner
        s.send(b"USER anonymous\r\n")
        resp = s.recv(1024).decode(errors="ignore")
        if "331" in resp:
            s.send(b"PASS test@example.com\r\n")
            resp2 = s.recv(1024).decode(errors="ignore")
            if "230" in resp2:
                return True
        return False
    except:
        return False
    finally:
        s.close()

def telnet_banner(target, port=23):
    """
    Conecta a target:23 por Telnet y devuelve el banner (si hay), o None.
    """
    s = socket.socket()
    s.settimeout(3)
    try:
        s.connect((target, port))
        banner = s.recv(1024).decode(errors="ignore").strip()
        return banner if banner else None
    except:
        return None
    finally:
        s.close()

def http_banner(target, port=80):
    """
    Envía un GET minimal a target:80. Devuelve valor de cabecera 'Server' o None.
    """
    try:
        s = socket.socket()
        s.settimeout(3)
        s.connect((target, port))
        request = b"GET / HTTP/1.1\r\nHost: " + target.encode() + b"\r\nConnection: close\r\n\r\n"
        s.send(request)
        data = b""
        while True:
            chunk = s.recv(1024)
            if not chunk:
                break
            data += chunk
        text = data.decode(errors="ignore")
        for line in text.split("\r\n"):
            if line.lower().startswith("server:"):
                return line.split(":", 1)[1].strip()
        return None
    except:
        return None
    finally:
        s.close()

def icmp_flood(target, count=10):
    """
    Envía 'count' paquetes ICMP (Echo Request) a 'target'.
    """
    pkt = IP(dst=target)/ICMP()
    for _ in range(count):
        send(pkt, verbose=0)

def udp_scan(target, ports, timeout=2):
    """
    Envía un paquete UDP vacío a cada puerto en 'ports'.
    Si no llega ICMP 'Puerto inalcanzable', lo considera abierto/filtrado.
    Devuelve lista de puertos abiertos/filtrados.
    """
    abiertos = []
    for p in ports:
        pkt = IP(dst=target)/UDP(dport=p)
        resp = sr1(pkt, timeout=timeout, verbose=0)
        if resp is None:
            abiertos.append(p)
        elif resp.haslayer(ICMP) and resp[ICMP].type == 3 and resp[ICMP].code == 3:
            # Puerto cerrado → no añadir
            pass
    return sorted(abiertos)

def snmp_check(target, port=161, community="public"):
    """
    Envía un paquete SNMP GetRequest vacío (comunidad 'public').
    Si responde, marca como 'SNMP abierto con comunidad public'.
    """
    try:
        from pysnmp.hlapi import SnmpEngine, CommunityData, UdpTransportTarget, ContextData, ObjectType, ObjectIdentity, getCmd
    except ImportError:
        return None  # Pysnmp no instalado

    iterator = getCmd(
        SnmpEngine(),
        CommunityData(community, mpModel=0),
        UdpTransportTarget((target, port), timeout=2, retries=0),
        ContextData(),
        ObjectType(ObjectIdentity('1.3.6.1.2.1.1.1.0'))
    )
    errorIndication, errorStatus, errorIndex, varBinds = next(iterator)
    if errorIndication or errorStatus:
        return None
    return str(varBinds[0][1])  # Retorna el valor del sysDescr

# ──────────────────────────────────────────────────────────
# FUNCIÓN PRINCIPAL
# ──────────────────────────────────────────────────────────

def main():
    parser = argparse.ArgumentParser(
        description="Simulador de Ataques ENS (TCP, FTP, Telnet, HTTP, ICMP, UDP, SNMP)."
    )
    parser.add_argument("target", help="IP o hostname del objetivo")
    parser.add_argument(
        "--tcp-ports", nargs="+", type=int,
        help="Lista de puertos TCP a escanear (ej.: 21 22 23 80 445)."
    )
    parser.add_argument(
        "--full-tcp", action="store_true",
        help="Si se especifica, escanea puertos TCP 1–1024."
    )
    parser.add_argument(
        "--udp-ports", nargs="+", type=int,
        default=[53,67,123],
        help="Lista de puertos UDP a escanear (por defecto: 53 67 123)."
    )
    parser.add_argument(
        "--icmp-flood", type=int, default=0,
        help="Cantidad de paquetes ICMP a enviar (0 = sin flood)."
    )
    args = parser.parse_args()

    target = args.target

    # Determinar puertos TCP
    if args.full_tcp:
        tcp_ports = list(range(1, 1025))
    elif args.tcp_ports:
        tcp_ports = args.tcp_ports
    else:
        tcp_ports = [21,22,23,80,139,445,3389]

    udp_ports  = args.udp_ports
    icmp_count = args.icmp_flood

    resultados = {
        "target":        target,
        "timestamp":     datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        "tcp_abiertos":  [],
        "ftp_anonimo":   False,
        "telnet_banner": None,
        "http_banner":   None,
        "icmp_enviados": icmp_count,
        "udp_abiertos":  [],
        "snmp_info":     None
    }

    print(f"[{resultados['timestamp']}] Iniciando simulador contra {target}\n")

    # 1) Escaneo TCP
    print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Ejecutando TCP SYN scan en {tcp_ports}")
    abiertos_tcp = tcp_syn_scan(target, tcp_ports)
    resultados["tcp_abiertos"] = abiertos_tcp
    print(f"→ Puertos TCP abiertos: {abiertos_tcp}\n")

    # 2) FTP anónimo si 21 está abierto
    if 21 in abiertos_tcp:
        print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Probando FTP anónimo en {target}:21")
        ftp_vuln = prueba_ftp_anonimo(target, 21)
        resultados["ftp_anonimo"] = ftp_vuln
        if ftp_vuln:
            print("→ ¡FTP anónimo HABILITADO! (vulnerable)\n")
        else:
            print("→ FTP anónimo DENEGADO (seguro)\n")

    # 3) Telnet banner si 23 está abierto
    if 23 in abiertos_tcp:
        print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Obteniendo banner Telnet en {target}:23")
        banner = telnet_banner(target, 23)
        resultados["telnet_banner"] = banner
        if banner:
            print(f"→ Banner Telnet: {banner}\n")
        else:
            print("→ No se pudo leer banner Telnet (filtrado o sin respuesta)\n")

    # 4) HTTP banner si 80 está abierto
    if 80 in abiertos_tcp:
        print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Obteniendo HTTP banner en {target}:80")
        banner = http_banner(target, 80)
        resultados["http_banner"] = banner
        if banner:
            print(f"→ Cabecera 'Server': {banner}\n")
        else:
            print("→ No se pudo obtener banner HTTP (filtrado o sin servidor)\n")

    # 5) ICMP flood
    if icmp_count > 0:
        print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Iniciando ICMP flood ({icmp_count} paquetes) a {target}")
        icmp_flood(target, icmp_count)
        print("→ ICMP flood terminado\n")

    # 6) UDP scan
    if udp_ports:
        print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Iniciando UDP scan en {udp_ports}")
        abiertos_udp = udp_scan(target, udp_ports)
        resultados["udp_abiertos"] = abiertos_udp
        print(f"→ Puertos UDP abiertos/filtrados: {abiertos_udp}\n")

    # 7) SNMP check si 161 está en lista UDP o instintivamente
    if 161 in udp_ports:
        print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Comprobando SNMP en {target}:161 (comunidad 'public')")
        info_snmp = snmp_check(target, 161, "public")
        resultados["snmp_info"] = info_snmp
        if info_snmp:
            print(f"→ SNMP accesible. sysDescr: {info_snmp}\n")
        else:
            print("→ SNMP no accesible o comunidad 'public' denegada\n")

    # 8) Generar informe JSON final
    informe_json = os.path.join(os.getcwd(), "informe_simulador.json")
    try:
        with open(informe_json, "w", encoding="utf-8") as f:
            json.dump(resultados, f, ensure_ascii=False, indent=2)
        print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Informe guardado en {informe_json}\n")
    except Exception as e:
        print(f"[!] No pude escribir el informe JSON: {e}\n")

    # 9) Generar informe HTML ligero
    informe_html = os.path.join(os.getcwd(), "informe_simulador.html")
    try:
        with open(informe_html, "w", encoding="utf-8") as f:
            f.write("<html><head><meta charset='utf-8'><title>Informe Simulador ENS</title></head><body>\n")
            f.write(f"<h1>Informe Simulador ENS</h1>\n")
            f.write(f"<p><strong>Target:</strong> {resultados['target']}<br>\n")
            f.write(f"<strong>Timestamp:</strong> {resultados['timestamp']}</p>\n")
            f.write("<h2>Puertos TCP abiertos</h2><ul>\n")
            for p in resultados["tcp_abiertos"]:
                f.write(f"  <li>{p}</li>\n")
            f.write("</ul>\n")
            f.write(f"<h2>FTP Anónimo</h2><p>{'Sí (vulnerable)' if resultados['ftp_anonimo'] else 'No'}</p>\n")
            f.write(f"<h2>Banner Telnet</h2><p>{resultados['telnet_banner'] or 'N/A'}</p>\n")
            f.write(f"<h2>Banner HTTP</h2><p>{resultados['http_banner'] or 'N/A'}</p>\n")
            f.write(f"<h2>ICMP enviadas</h2><p>{resultados['icmp_enviados']}</p>\n")
            f.write("<h2>Puertos UDP abiertos/filtrados</h2><ul>\n")
            for p in resultados["udp_abiertos"]:
                f.write(f"  <li>{p}</li>\n")
            f.write("</ul>\n")
            if resultados["snmp_info"]:
                f.write(f"<h2>SNMP sysDescr</h2><p>{resultados['snmp_info']}</p>\n")
            f.write("</body></html>\n")
        print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Informe guardado en {informe_html}\n")
    except Exception as e:
        print(f"[!] No pude escribir el informe HTML: {e}\n")

    print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Fin del simulador.\n")


if __name__ == "__main__":
    main()