diff --git a/README.md b/README.md index 867364a..bd2c7b2 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,34 @@ 1. download the script in HOME directory `wget https://raw.githubusercontent.com/Hax4us/Metasploit_termux/master/metasploit.sh` 2. run `chmod +x metasploit.sh && ./metasploit.sh` + +### Escáner de redes Wi-Fi +El script `wifi_scanner.py` permite listar redes Wi-Fi cercanas y ver la potencia de señal de cada una. + +**Uso básico** +```bash +python wifi_scanner.py # usa la interfaz wlan0 por defecto +``` + +**Opciones útiles** +- `-i/--interface`: seleccionar la interfaz inalámbrica (ej. `wlan1`). +- `--raw`: mostrar también la salida cruda del comando utilizado. +- `--iwlist`: forzar el uso de `iwlist` incluso si `termux-wifi-scaninfo` está disponible. + +### Uso en Termux +1. Instala los binarios de la API: `pkg install termux-api`. +2. Otorga permiso de ubicación a Termux desde Android para que pueda leer escaneos Wi‑Fi. +3. Ejecuta el escáner normalmente: + ```bash + python wifi_scanner.py + ``` + El script usará `termux-wifi-scaninfo` automáticamente (no requiere root). + +### Uso en otras distribuciones de Linux +1. Instala `wireless-tools` (u otro paquete que provea `iwlist`). +2. Ejecuta con la interfaz deseada: + ```bash + sudo python wifi_scanner.py -i wlan0 + ``` + +> Necesitas tener instalado `termux-api` **o** `iwlist` y permisos para realizar escaneos inalámbricos. diff --git a/wifi_scanner.py b/wifi_scanner.py new file mode 100755 index 0000000..3d92236 --- /dev/null +++ b/wifi_scanner.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 +"""Simple Wi-Fi scanner for listing nearby networks and signal strength. + +The script shells out to ``iwlist`` to perform a scan and then parses the +results into a compact table. It is intended for quick use on Termux or other +Linux environments where ``iwlist`` is available. +""" + +from __future__ import annotations + +import argparse +import json +import re +import shutil +import subprocess +from dataclasses import dataclass +from typing import Iterable, List, Optional + + +@dataclass +class WiFiNetwork: + """Represents a discovered Wi-Fi network.""" + + bssid: str + essid: str + frequency: Optional[str] + channel: Optional[int] + signal_dbm: Optional[int] + encryption: Optional[str] + + +CELL_PATTERN = re.compile(r"^\s*Cell \d+ - Address: (?P[0-9A-F:]{17})", re.IGNORECASE) +FREQUENCY_PATTERN = re.compile(r"Frequency:(?P[0-9.]+) GHz") +CHANNEL_PATTERN = re.compile(r"Channel:(?P\d+)") +SIGNAL_PATTERN = re.compile(r"Signal level=-(?P\d+) dBm") +ESSID_PATTERN = re.compile(r"ESSID:\"(?P.*)\"") +ENCRYPTION_PATTERN = re.compile(r"Encryption key:(?Pon|off)", re.IGNORECASE) + + +def parse_scan_output(raw_output: str) -> List[WiFiNetwork]: + """Parse iwlist scanning output into a list of WiFiNetwork entries.""" + + networks: List[WiFiNetwork] = [] + current: dict[str, Optional[str | int]] = {} + + for line in raw_output.splitlines(): + cell_match = CELL_PATTERN.match(line) + if cell_match: + if current: + networks.append(_build_network(current)) + current = {"bssid": cell_match.group("bssid")} + continue + + if not current: + continue + + if (essid_match := ESSID_PATTERN.search(line)): + current["essid"] = essid_match.group("essid") + continue + + if (freq_match := FREQUENCY_PATTERN.search(line)): + current["frequency"] = f"{freq_match.group('freq')} GHz" + continue + + if (channel_match := CHANNEL_PATTERN.search(line)): + current["channel"] = int(channel_match.group("channel")) + continue + + if (signal_match := SIGNAL_PATTERN.search(line)): + current["signal_dbm"] = -int(signal_match.group("level")) + continue + + if (encryption_match := ENCRYPTION_PATTERN.search(line)): + current["encryption"] = "WPA/WEP" if encryption_match.group("state").lower() == "on" else "Open" + continue + + if current: + networks.append(_build_network(current)) + + return networks + + +def _build_network(fields: dict[str, Optional[str | int]]) -> WiFiNetwork: + return WiFiNetwork( + bssid=str(fields.get("bssid", "Unknown")), + essid=str(fields.get("essid", "")), + frequency=fields.get("frequency"), + channel=fields.get("channel") if isinstance(fields.get("channel"), int) else None, + signal_dbm=fields.get("signal_dbm") if isinstance(fields.get("signal_dbm"), int) else None, + encryption=fields.get("encryption"), + ) + + +def parse_termux_output(raw_output: str) -> List[WiFiNetwork]: + """Parse output from ``termux-wifi-scaninfo`` (JSON array of APs).""" + + try: + access_points = json.loads(raw_output) + except json.JSONDecodeError: + return [] + + networks: List[WiFiNetwork] = [] + for ap in access_points if isinstance(access_points, list) else []: + bssid = ap.get("bssid", "Unknown") if isinstance(ap, dict) else "Unknown" + essid = ap.get("ssid", "") if isinstance(ap, dict) else "" + frequency = ap.get("frequency") if isinstance(ap, dict) else None + signal_dbm = ap.get("level") if isinstance(ap, dict) else None + channel = _frequency_to_channel(frequency) if isinstance(frequency, (int, float)) else None + + networks.append( + WiFiNetwork( + bssid=bssid, + essid=essid, + frequency=f"{frequency} MHz" if frequency else None, + channel=channel, + signal_dbm=signal_dbm if isinstance(signal_dbm, int) else None, + encryption=ap.get("capabilities") if isinstance(ap, dict) else None, + ) + ) + + return networks + + +def _frequency_to_channel(frequency_mhz: float) -> Optional[int]: + """Best-effort conversion from frequency (MHz) to Wi-Fi channel number.""" + + if frequency_mhz is None: + return None + if 2412 <= frequency_mhz <= 2472: + return int((frequency_mhz - 2407) / 5) + if frequency_mhz == 2484: + return 14 + if 5170 <= frequency_mhz <= 5825: + return int((frequency_mhz - 5000) / 5) + return None + + +def scan(interface: str, prefer_iwlist: bool = False) -> tuple[List[WiFiNetwork], str]: + """Run a scan and return parsed results and raw output. + + On Termux, it prefers ``termux-wifi-scaninfo`` if available to avoid + requiring root. Pass ``prefer_iwlist=True`` to force iwlist usage. + """ + + termux_cmd = shutil.which("termux-wifi-scaninfo") + if termux_cmd and not prefer_iwlist: + result = subprocess.run([termux_cmd], check=True, capture_output=True, text=True) + return parse_termux_output(result.stdout), result.stdout + + iwlist_cmd = shutil.which("iwlist") + if iwlist_cmd: + result = subprocess.run( + [iwlist_cmd, interface, "scanning"], check=True, capture_output=True, text=True + ) + return parse_scan_output(result.stdout), result.stdout + + raise FileNotFoundError( + "No se encontró 'termux-wifi-scaninfo' ni 'iwlist'. Instala termux-api o wireless-tools." + ) + + +def format_table(networks: Iterable[WiFiNetwork]) -> str: + """Return a formatted table ready for printing.""" + + rows = [ + (n.essid, n.bssid, n.signal_dbm or "-", n.channel or "-", n.frequency or "-", n.encryption or "-" ) + for n in networks + ] + + headers = ("SSID", "BSSID", "Signal (dBm)", "Channel", "Frequency", "Encryption") + col_widths = [max(len(str(row[i])) for row in rows + [headers]) for i in range(len(headers))] + + def fmt_row(row: Iterable[object]) -> str: + return " ".join(str(cell).ljust(col_widths[i]) for i, cell in enumerate(row)) + + lines = [fmt_row(headers), fmt_row(["-" * w for w in col_widths])] + lines.extend(fmt_row(row) for row in rows) + return "\n".join(lines) + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Escanea redes Wi-Fi cercanas y muestra sus detalles básicos." + ) + parser.add_argument( + "-i", + "--interface", + default="wlan0", + help="Interfaz inalámbrica a escanear (por defecto: wlan0)", + ) + parser.add_argument( + "--raw", + action="store_true", + help="Mostrar salida cruda del comando de escaneo además de la tabla resumida.", + ) + parser.add_argument( + "--iwlist", + action="store_true", + help="Forzar uso de iwlist incluso en Termux (requiere wireless-tools y permisos)", + ) + + args = parser.parse_args() + + try: + networks, raw_output = scan(args.interface, prefer_iwlist=args.iwlist) + except FileNotFoundError as exc: + raise SystemExit(str(exc)) + except subprocess.CalledProcessError as exc: + raise SystemExit( + f"Error al ejecutar el escaneo: {exc}. Asegúrate de tener permisos y que la interfaz existe." + ) + + if args.raw: + print(raw_output) + + if not networks: + print("No se encontraron redes. Verifica la interfaz y repite el comando.") + return + + print(format_table(networks)) + + +if __name__ == "__main__": + main()