From e186f355332a992ed5e15a6206b1f62dd875d5c9 Mon Sep 17 00:00:00 2001 From: thomasciesla Date: Fri, 24 Oct 2025 17:08:08 +0200 Subject: [PATCH] =?UTF-8?q?scan.py=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scan.py | 594 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 594 insertions(+) create mode 100644 scan.py diff --git a/scan.py b/scan.py new file mode 100644 index 0000000..211c303 --- /dev/null +++ b/scan.py @@ -0,0 +1,594 @@ +#!/usr/bin/env python3 +""" +Netzwerk IP-Scanner (Ultra-Zuverlässig) +Scannt angegebene Netzwerke nach belegten und freien IP-Adressen +Mehrschichtige Erkennung für 100% Zuverlässigkeit +""" + +import argparse +import ipaddress +import subprocess +import sys +import socket +from concurrent.futures import ThreadPoolExecutor, as_completed +from dataclasses import dataclass +from typing import List, Set, Dict, Tuple, Optional +import time +import shutil +import re + +# ANSI Farben +class Color: + RED = '\033[91m' + GREEN = '\033[92m' + YELLOW = '\033[93m' + BLUE = '\033[94m' + MAGENTA = '\033[95m' + CYAN = '\033[96m' + WHITE = '\033[97m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + RESET = '\033[0m' + +@dataclass +class HostInfo: + ip: str + hostname: Optional[str] = None + mac: Optional[str] = None + responding: bool = True + method: str = "unknown" + +@dataclass +class ScanResult: + network: str + used_hosts: Dict[str, HostInfo] + free_ips: Set[str] + total: int + scan_time: float + method: str + +def print_banner(): + """Zeigt einen Banner""" + banner = f""" +{Color.CYAN}{Color.BOLD}╔═══════════════════════════════════════════════════════╗ +║ Netzwerk IP-Scanner v3.0 (Ultra-Zuverlässig) ║ +╚═══════════════════════════════════════════════════════╝{Color.RESET} +""" + print(banner) + +def check_tool(tool: str) -> bool: + """Prüft ob ein Tool verfügbar ist""" + return shutil.which(tool) is not None + +def get_hostname(ip: str, timeout: float = 0.3) -> Optional[str]: + """Versucht den Hostnamen einer IP-Adresse aufzulösen""" + try: + socket.setdefaulttimeout(timeout) + hostname = socket.gethostbyaddr(ip)[0] + return hostname if hostname != ip else None + except (socket.herror, socket.gaierror, socket.timeout, OSError): + return None + +def arp_scan_network(network_str: str) -> Tuple[Dict[str, HostInfo], Set[str]]: + """ + Scannt Netzwerk mit arp-scan (Layer 2 - sehr zuverlässig) + Benötigt root/sudo! + """ + try: + network = ipaddress.ip_network(network_str, strict=False) + + # arp-scan mit optimalen Parametern + cmd = ['arp-scan', '--localnet', '--numeric', '--quiet', '--ignoredups', str(network)] + + result = subprocess.run( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=90, + text=True + ) + + used_hosts = {} + all_ips = set(str(ip) for ip in network.hosts()) + + # Parse arp-scan output + # Format: IPMACVendor + for line in result.stdout.split('\n'): + line = line.strip() + if not line or line.startswith('#'): + continue + + # Split by tabs oder whitespace + parts = re.split(r'\s+', line) + if len(parts) >= 2: + ip = parts[0].strip() + mac = parts[1].strip() + + # Prüfe ob IP gültig ist und im Netzwerk liegt + try: + ip_obj = ipaddress.ip_address(ip) + if str(ip_obj) in all_ips: + hostname = get_hostname(ip) + used_hosts[ip] = HostInfo(ip=ip, hostname=hostname, mac=mac, method="arp-scan") + except ValueError: + continue + + free_ips = all_ips - set(used_hosts.keys()) + return used_hosts, free_ips + + except (subprocess.TimeoutExpired, FileNotFoundError, subprocess.CalledProcessError, Exception) as e: + print(f" {Color.YELLOW}⚠ ARP-Scan Fehler: {e}{Color.RESET}") + return None, None + +def nmap_aggressive_scan(network_str: str, scan_ports: bool = False) -> Tuple[Dict[str, HostInfo], Set[str]]: + """ + Scannt Netzwerk mit nmap - sehr aggressiv und zuverlässig + """ + try: + network = ipaddress.ip_network(network_str, strict=False) + + if scan_ports: + # Mit Port-Scan für maximale Zuverlässigkeit + cmd = [ + 'nmap', + '-sS', # TCP SYN Scan + '-p', '21,22,23,25,80,111,135,139,443,445,993,995,1723,3306,3389,5900,8080,8443', # Häufige Ports + '--min-rate', '1000', # Schneller + '-T4', # Aggressives Timing + '-n', # Keine DNS-Auflösung durch nmap + str(network) + ] + else: + # Nur Host-Discovery + cmd = [ + 'nmap', + '-sn', # Ping Scan + '-PS21,22,23,25,80,443,3389,8080', # TCP SYN Ping + '-PA80,443', # TCP ACK Ping + '-PU53,67,68,161', # UDP Ping + '-PE', # ICMP Echo + '-PP', # ICMP Timestamp + '--min-rate', '500', + '-T4', + '-n', + str(network) + ] + + result = subprocess.run( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=300, + text=True + ) + + used_hosts = {} + all_ips = set(str(ip) for ip in network.hosts()) + + # Parse nmap output + current_ip = None + for line in result.stdout.split('\n'): + # Suche nach "Nmap scan report for" + if 'Nmap scan report for' in line: + match = re.search(r'(\d+\.\d+\.\d+\.\d+)', line) + if match: + current_ip = match.group(1) + + # Prüfe ob Host up ist + if current_ip and ('Host is up' in line or 'open' in line): + if current_ip in all_ips and current_ip not in used_hosts: + hostname = get_hostname(current_ip) + used_hosts[current_ip] = HostInfo(ip=current_ip, hostname=hostname, method="nmap") + + free_ips = all_ips - set(used_hosts.keys()) + return used_hosts, free_ips + + except (subprocess.TimeoutExpired, FileNotFoundError, Exception) as e: + print(f" {Color.YELLOW}⚠ Nmap Fehler: {e}{Color.RESET}") + return None, None + +def check_tcp_ports(ip: str, ports: List[int]) -> bool: + """Prüft ob TCP-Ports offen sind""" + for port in ports: + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(0.3) + result = sock.connect_ex((ip, port)) + sock.close() + if result == 0: + return True + except: + continue + return False + +def ping_check(ip: str) -> bool: + """ICMP Ping""" + try: + result = subprocess.run( + ['ping', '-c', '1', '-W', '1', str(ip)], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=2 + ) + return result.returncode == 0 + except: + return False + +def comprehensive_check(ip: str, tcp_ports: List[int]) -> bool: + """Kombinierte Prüfung: Ping + TCP-Ports""" + if ping_check(ip): + return True + return check_tcp_ports(ip, tcp_ports) + +def scan_network(network_str: str, method: str = 'auto', max_workers: int = 100, + tcp_ports: List[int] = None, aggressive: bool = False) -> ScanResult: + """Scannt ein Netzwerk nach aktiven IPs""" + + if tcp_ports is None: + tcp_ports = [22, 80, 443, 3389, 8080, 8443, 3306, 5432] + + try: + network = ipaddress.ip_network(network_str, strict=False) + except ValueError as e: + print(f"{Color.RED}✗ Fehler beim Parsen von {network_str}: {e}{Color.RESET}") + return None + + all_ips = list(network.hosts()) + total_ips = len(all_ips) + + print(f"\n{Color.YELLOW}► Scanne Netzwerk: {Color.BOLD}{network_str}{Color.RESET}") + print(f" {Color.WHITE}IPs zu scannen: {total_ips}{Color.RESET}") + + start_time = time.time() + used_hosts = {} + free_ips = set() + scan_method = method + + # Automatische Methodenwahl + if method == 'auto': + if check_tool('arp-scan'): + scan_method = 'arp-scan' + elif check_tool('nmap'): + scan_method = 'nmap' + else: + scan_method = 'comprehensive' + + print(f" {Color.CYAN}Methode: {scan_method}{Color.RESET}") + + # ARP-Scan (beste Option) + if scan_method == 'arp-scan': + print(f" {Color.CYAN}Führe ARP-Scan durch...{Color.RESET}") + used_hosts, free_ips = arp_scan_network(network_str) + + if used_hosts is None or len(used_hosts) == 0: + print(f" {Color.YELLOW}⚠ ARP-Scan lieferte keine Ergebnisse, fallback zu nmap{Color.RESET}") + scan_method = 'nmap' + + # Nmap-Scan + if scan_method == 'nmap' and not used_hosts: + print(f" {Color.CYAN}Führe Nmap-Scan durch...{Color.RESET}") + used_hosts, free_ips = nmap_aggressive_scan(network_str, scan_ports=aggressive) + + if used_hosts is None: + print(f" {Color.YELLOW}⚠ Nmap fehlgeschlagen, fallback zu comprehensive{Color.RESET}") + scan_method = 'comprehensive' + + # Comprehensive Scan (Fallback) + if scan_method == 'comprehensive' or not used_hosts: + print(f" {Color.CYAN}Führe umfassenden Scan durch (Ping + TCP)...{Color.RESET}") + + scanned = 0 + def print_progress(): + progress = (scanned / total_ips) * 100 + bar_length = 40 + filled = int(bar_length * scanned / total_ips) + bar = '█' * filled + '░' * (bar_length - filled) + print(f"\r {Color.CYAN}[{bar}] {progress:.1f}% ({scanned}/{total_ips}){Color.RESET}", end='', flush=True) + + with ThreadPoolExecutor(max_workers=max_workers) as executor: + futures = {executor.submit(comprehensive_check, str(ip), tcp_ports): ip for ip in all_ips} + + for future in as_completed(futures): + ip = str(futures[future]) + try: + is_alive = future.result() + if is_alive: + hostname = get_hostname(ip) + used_hosts[ip] = HostInfo(ip=ip, hostname=hostname, method="comprehensive") + else: + free_ips.add(ip) + except Exception: + free_ips.add(ip) + + scanned += 1 + print_progress() + + print() + + # Sicherstellen dass free_ips korrekt ist + all_ips_set = set(str(ip) for ip in all_ips) + free_ips = all_ips_set - set(used_hosts.keys()) + + scan_time = time.time() - start_time + + return ScanResult( + network=network_str, + used_hosts=used_hosts, + free_ips=free_ips, + total=total_ips, + scan_time=scan_time, + method=scan_method + ) + +def print_result(result: ScanResult, show_all: bool = False, show_free: bool = True): + """Gibt das Scan-Ergebnis formatiert aus""" + if not result: + return + + used_count = len(result.used_hosts) + free_count = len(result.free_ips) + usage_percent = (used_count / result.total) * 100 if result.total > 0 else 0 + + print(f"\n{Color.BLUE}{Color.BOLD}╔═══ Ergebnis für {result.network} ═══╗{Color.RESET}") + print(f"{Color.WHITE} Scan-Methode: {result.method}{Color.RESET}") + print(f"{Color.WHITE} Scan-Dauer: {result.scan_time:.2f}s{Color.RESET}") + print(f"{Color.GREEN} ✓ Belegte IPs: {used_count} ({usage_percent:.1f}%){Color.RESET}") + print(f"{Color.RED} ✗ Freie IPs: {free_count} ({100-usage_percent:.1f}%){Color.RESET}") + + # Belegte IPs mit Hostnamen ausgeben + if used_count > 0: + print(f"\n{Color.GREEN}{Color.BOLD} Belegte IP-Adressen:{Color.RESET}") + sorted_hosts = sorted(result.used_hosts.values(), key=lambda x: ipaddress.ip_address(x.ip)) + + display_hosts = sorted_hosts if (show_all or used_count <= 20) else sorted_hosts[:10] + [None] + sorted_hosts[-10:] + + for host in display_hosts: + if host is None: + print(f" {Color.YELLOW}... ({used_count - 20} weitere) ...{Color.RESET}") + continue + + hostname_str = f" → {Color.CYAN}{host.hostname}{Color.RESET}" if host.hostname else "" + mac_str = f" [{host.mac}]" if host.mac else "" + print(f" {Color.GREEN}{host.ip:15}{Color.RESET}{hostname_str}{mac_str}") + + # Freie IPs ausgeben + if show_free and free_count > 0: + print(f"\n{Color.RED}{Color.BOLD} Freie IP-Adressen:{Color.RESET}") + sorted_free = sorted(result.free_ips, key=lambda x: ipaddress.ip_address(x)) + + if free_count <= 20 or show_all: + for i, ip in enumerate(sorted_free, 1): + if i % 4 == 1: + print(f" {Color.RED}", end='') + print(f"{ip:15}", end=' ') + if i % 4 == 0: + print(Color.RESET) + if free_count % 4 != 0: + print(Color.RESET) + else: + for i, ip in enumerate(sorted_free[:10], 1): + if i % 4 == 1: + print(f" {Color.RED}", end='') + print(f"{ip:15}", end=' ') + if i % 4 == 0: + print(Color.RESET) + if len(sorted_free[:10]) % 4 != 0: + print(Color.RESET) + print(f"\n {Color.YELLOW} ... ({free_count - 20} weitere freie IPs) ...{Color.RESET}\n") + remaining = sorted_free[-10:] + for i, ip in enumerate(remaining, 1): + if i % 4 == 1: + print(f" {Color.RED}", end='') + print(f"{ip:15}", end=' ') + if i % 4 == 0: + print(Color.RESET) + if len(remaining) % 4 != 0: + print(Color.RESET) + + print(f"{Color.BLUE}{Color.BOLD}╚{'═' * 50}╝{Color.RESET}\n") + +def print_common_free_hosts(results: List[ScanResult], show_all: bool = False): + """ + Zeigt Host-IDs an, die in ALLEN gescannten Netzwerken gleichzeitig frei sind + """ + if len(results) < 2: + return + + free_host_ids_per_network = [] + + for result in results: + host_ids = set() + for ip_str in result.free_ips: + try: + ip_parts = ip_str.split('.') + if len(ip_parts) == 4: + host_id = int(ip_parts[3]) + host_ids.add(host_id) + except (ValueError, IndexError): + continue + free_host_ids_per_network.append(host_ids) + + common_free = set.intersection(*free_host_ids_per_network) if free_host_ids_per_network else set() + + if not common_free: + print(f"{Color.YELLOW}⚠ Keine gemeinsam freien Host-IDs in allen Netzwerken gefunden{Color.RESET}\n") + return + + common_count = len(common_free) + sorted_common = sorted(common_free) + + networks_str = ", ".join([r.network for r in results]) + + print(f"{Color.MAGENTA}{Color.BOLD}╔═══ Gemeinsam freie Host-IDs ═══╗{Color.RESET}") + print(f"{Color.WHITE} Netzwerke: {networks_str}{Color.RESET}") + print(f"{Color.MAGENTA} Anzahl gemeinsam frei: {common_count}{Color.RESET}") + print(f"\n{Color.MAGENTA}{Color.BOLD} Verfügbare Host-IDs (letztes Oktett):{Color.RESET}") + + print(f"\n{Color.CYAN} Host-ID ", end='') + for result in results: + network_prefix = '.'.join(result.network.split('.')[:3]) + print(f"│ {network_prefix:15}", end=' ') + print(Color.RESET) + print(f"{Color.CYAN} ─────────{'─' * (len(results) * 19)}{Color.RESET}") + + display_limit = None if show_all else 30 + + if display_limit and common_count > display_limit: + display_ids = sorted_common[:15] + [None] + sorted_common[-15:] + else: + display_ids = sorted_common + + for host_id in display_ids: + if host_id is None: + print(f"{Color.YELLOW} ... {'│' * len(results)} ({common_count - 30} weitere){Color.RESET}") + continue + + print(f" {Color.MAGENTA}{host_id:>3} {Color.RESET}", end='') + for result in results: + network_prefix = '.'.join(result.network.split('.')[:3]) + full_ip = f"{network_prefix}.{host_id}" + print(f"│ {Color.GREEN}{full_ip:15}{Color.RESET}", end=' ') + print() + + print(f"\n{Color.MAGENTA}{Color.BOLD}╚{'═' * 50}╝{Color.RESET}\n") + + if common_count > 0: + example_id = sorted_common[0] + example_ips = [] + for result in results: + network_prefix = '.'.join(result.network.split('.')[:3]) + example_ips.append(f"{network_prefix}.{example_id}") + + print(f"{Color.CYAN}💡 Tipp: Für eine VM mit mehreren Interfaces kannst du z.B. verwenden:{Color.RESET}") + for i, ip in enumerate(example_ips, 1): + print(f" Interface {i}: {Color.GREEN}{ip}{Color.RESET}") + print() + +def main(): + parser = argparse.ArgumentParser( + description='Scannt Netzwerke nach belegten und freien IP-Adressen (100%% zuverlässig)', + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Scan-Methoden: + auto - Automatisch beste verfügbare Methode (Standard) + arp-scan - ARP-Scan (Layer 2, am zuverlässigsten, benötigt sudo!) + nmap - Nmap Scan (sehr zuverlässig) + comprehensive - Ping + TCP Port-Check (Fallback) + +Wichtig für maximale Zuverlässigkeit: + sudo ./ip.py 10.150.8.0/24 --method arp-scan + +Beispiele: + sudo %(prog)s 10.150.8.0/24 10.150.56.0/24 + sudo %(prog)s 192.168.0.0/24 --method arp-scan -a + %(prog)s 10.0.0.0/24 --aggressive + %(prog)s 192.168.1.0/24 --no-free + """ + ) + + parser.add_argument( + 'networks', + nargs='+', + help='Netzwerke im CIDR-Format (z.B. 192.168.0.0/24)' + ) + + parser.add_argument( + '-a', '--show-all', + action='store_true', + help='Zeige alle IPs (auch bei großen Listen)' + ) + + parser.add_argument( + '-m', '--method', + choices=['auto', 'arp-scan', 'nmap', 'comprehensive'], + default='auto', + help='Scan-Methode (Standard: auto)' + ) + + parser.add_argument( + '-t', '--threads', + type=int, + default=100, + help='Anzahl paralleler Threads (Standard: 100)' + ) + + parser.add_argument( + '-p', '--ports', + type=str, + default='22,80,443,3389,8080,8443,3306,5432', + help='TCP-Ports für Port-Check (Standard: 22,80,443,3389,8080,8443,3306,5432)' + ) + + parser.add_argument( + '--aggressive', + action='store_true', + help='Aggressiver Nmap-Scan mit Port-Scan (langsamer aber zuverlässiger)' + ) + + parser.add_argument( + '--no-free', + action='store_true', + help='Zeige keine freien IPs' + ) + + args = parser.parse_args() + + # Parse TCP-Ports + try: + tcp_ports = [int(p.strip()) for p in args.ports.split(',')] + except ValueError: + print(f"{Color.RED}✗ Ungültiges Port-Format{Color.RESET}") + return + + print_banner() + + # Warnung wenn nicht als root + if args.method == 'arp-scan' and subprocess.run(['id', '-u'], capture_output=True, text=True).stdout.strip() != '0': + print(f"{Color.RED}⚠ WARNUNG: arp-scan benötigt root-Rechte! Führe mit 'sudo' aus.{Color.RESET}\n") + + # Verfügbare Tools anzeigen + tools_available = [] + if check_tool('arp-scan'): + tools_available.append('arp-scan') + if check_tool('nmap'): + tools_available.append('nmap') + tools_available.append('ping+tcp') + + print(f"{Color.CYAN}Verfügbare Scan-Methoden: {', '.join(tools_available)}{Color.RESET}\n") + + results = [] + total_start = time.time() + + for network in args.networks: + result = scan_network( + network, + method=args.method, + max_workers=args.threads, + tcp_ports=tcp_ports, + aggressive=args.aggressive + ) + if result: + results.append(result) + print_result(result, args.show_all, not args.no_free) + + total_time = time.time() - total_start + + # Zusammenfassung + if len(results) > 1: + total_used = sum(len(r.used_hosts) for r in results) + total_free = sum(len(r.free_ips) for r in results) + total_ips = sum(r.total for r in results) + + print(f"\n{Color.CYAN}{Color.BOLD}╔═══ Gesamtzusammenfassung ═══╗{Color.RESET}") + print(f"{Color.WHITE} Gescannte Netzwerke: {len(results)}{Color.RESET}") + print(f"{Color.WHITE} Gesamte IPs: {total_ips}{Color.RESET}") + print(f"{Color.GREEN} Belegte IPs: {total_used}{Color.RESET}") + print(f"{Color.RED} Freie IPs: {total_free}{Color.RESET}") + print(f"{Color.WHITE} Gesamtdauer: {total_time:.2f}s{Color.RESET}") + print(f"{Color.CYAN}{Color.BOLD}╚{'═' * 30}╝{Color.RESET}\n") + + # Gemeinsam freie Host-IDs + print_common_free_hosts(results, args.show_all) + +if __name__ == '__main__': + main() \ No newline at end of file