From 98cfb706cf595828cc25797e6dc3c205b14a5ad8 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Sat, 26 Jul 2025 16:54:35 +0200 Subject: [PATCH] swapped to golang --- main.go | 372 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ main.py | 262 --------------------------------------- 2 files changed, 372 insertions(+), 262 deletions(-) create mode 100644 main.go delete mode 100644 main.py diff --git a/main.go b/main.go new file mode 100644 index 0000000..c980bfc --- /dev/null +++ b/main.go @@ -0,0 +1,372 @@ +package main +// sorry in advance for anyone who has to read this +import ( + "bufio" + "encoding/json" + "fmt" + "net" + "os" + "os/exec" + "runtime" + "strconv" + "strings" + "sync" + "time" +) + +type OpenPort struct { + IP string + Port int + Hostname string + Fingerprint string +} +const batchSize = 255 +var printMutex sync.Mutex +var ( + summaryFile *os.File + writer *bufio.Writer + fileLock sync.Mutex +) +var ( + openPorts []OpenPort + openPortsLock sync.Mutex + ipLineMap = make(map[string]int) + openPortMsgs []string +) + +func clearScreen() { + fmt.Print("\033[2J") +} + +func moveCursor(row, col int) { + fmt.Printf("\033[%d;%dH", row, col) +} + +func clearLine() { + fmt.Print("\033[K") +} + +func safePrint(format string, a ...interface{}) { + printMutex.Lock() + defer printMutex.Unlock() + fmt.Printf(format, a...) +} + +func safePrintln(a ...interface{}) { + printMutex.Lock() + defer printMutex.Unlock() + fmt.Println(a...) +} + +func printStatusLine(ip, msg string) { + if line, ok := ipLineMap[ip]; ok { + moveCursor(line, 0) + clearLine() + fmt.Printf("[%s] %s", ip, msg) + } +} + +func printOpenPortLine(msg string) { + openPortsLock.Lock() + defer openPortsLock.Unlock() + openPortMsgs = append(openPortMsgs, msg) + moveCursor(len(ipLineMap)+2+len(openPortMsgs), 0) + clearLine() + fmt.Println(msg) +} + +func isPrivateIP(ipStr string) bool { + ip := net.ParseIP(ipStr) + if ip == nil { + return false + } + privateBlocks := []string{ + "10.0.0.0/8", + "172.16.0.0/12", + "192.168.0.0/16", + "127.0.0.0/8", + } + for _, cidr := range privateBlocks { + _, block, _ := net.ParseCIDR(cidr) + if block.Contains(ip) { + return true + } + } + return false +} + + +func pingIP(ip string, pingCmd []string) bool { + cmd := exec.Command(pingCmd[0], append(pingCmd[1:], ip)...) + err := cmd.Run() + return err == nil +} + +func grabBanner(ip string, port int) string { + conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ip, port), 1*time.Second) + if err != nil { + return "" + } + defer conn.Close() + conn.SetReadDeadline(time.Now().Add(1 * time.Second)) + buf := make([]byte, 1024) + n, _ := conn.Read(buf) + return strings.TrimSpace(string(buf[:n])) +} + +func identifyServiceAndOS(ip string, port int) string { + data, err := os.ReadFile("services.json") + if err != nil { + return "" + } + var services map[string]map[string]string + json.Unmarshal(data, &services) + + portStr := strconv.Itoa(port) + if info, ok := services[portStr]; ok { + banner := grabBanner(ip, port) + if banner != "" { + return fmt.Sprintf("%s banner: %s", info["name"], banner) + } + return info["name"] + } + return "" +} + +func scanPort(ip string, port int, wg *sync.WaitGroup) { + printStatusLine(ip, fmt.Sprintf("scanning port %d...", port)) + conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ip, port), 500*time.Millisecond) + if err == nil { + conn.Close() + host, err := net.LookupAddr(ip) + hostname := "unknown hostname" + if err == nil && len(host) > 0 { + hostname = strings.TrimSuffix(host[0], ".") + } + fingerprint := identifyServiceAndOS(ip, port) + openPortsLock.Lock() + openPorts = append(openPorts, OpenPort{ip, port, hostname, fingerprint}) + line := fmt.Sprintf(" - %s : port %d open - hostname: %s", ip, port, hostname) + if fingerprint != "" { + line += " | " + fingerprint + } + + fileLock.Lock() + summaryFile.WriteString(line + "\n") + fileLock.Unlock() + openPortsLock.Unlock() + msg := fmt.Sprintf("[%s] port %d is OPEN (%s)", ip, port, hostname) + if fingerprint != "" { + msg += " | " + fingerprint + } + printOpenPortLine(msg) + printStatusLine(ip, fmt.Sprintf("open port found: %d", port)) + } +} + + +func scanPortsForIP(ip string, portStart, portEnd int) { + const maxGoroutines = 50 + guard := make(chan struct{}, maxGoroutines) + var wg sync.WaitGroup + + for port := portStart; port <= portEnd; port++ { + guard <- struct{}{} + wg.Add(1) + go func(p int) { + defer wg.Done() + scanPort(ip, p, &wg) + <-guard + }(port) + } + wg.Wait() + printStatusLine(ip, "scan done.") +} + +func pingIPsConcurrently(ips []string, pingCmd []string) []string { + var alive []string + var wg sync.WaitGroup + var lock sync.Mutex + for _, ip := range ips { + wg.Add(1) + go func(ip string) { + defer wg.Done() + if !isPrivateIP(ip) { + fmt.Printf("[+] skipping ping for public IP %s\n", ip) + lock.Lock() + alive = append(alive, ip) + lock.Unlock() + return + } + + if pingIP(ip, pingCmd) { + fmt.Printf("[+] found %s\n", ip) + lock.Lock() + alive = append(alive, ip) + lock.Unlock() + } else { + fmt.Printf("[~] no response from %s\n", ip) + } + + }(ip) + } + wg.Wait() + return alive +} + +func ipRange(start, end string) ([]string, error) { + startIP := net.ParseIP(start).To4() + endIP := net.ParseIP(end).To4() + if startIP == nil || endIP == nil { + return nil, fmt.Errorf("invalid IP") + } + + var ips []string + for ip := startIP; !ipAfter(ip, endIP); ip = nextIP(ip) { + ips = append(ips, ip.String()) + } + return ips, nil +} + +func ipAfter(a, b net.IP) bool { + for i := 0; i < 4; i++ { + if a[i] > b[i] { + return true + } + if a[i] < b[i] { + return false + } + } + return false +} + +func nextIP(ip net.IP) net.IP { + next := make(net.IP, len(ip)) + copy(next, ip) + for i := 3; i >= 0; i-- { + next[i]++ + if next[i] != 0 { + break + } + } + return next +} + +func main() { + var err error + summaryFile, err = os.Create("summary.txt") + if err != nil { + fmt.Println("[!] failed to open summary file:", err) + os.Exit(1) + } + defer summaryFile.Close() + reader := bufio.NewReader(os.Stdin) + fmt.Print("enter IP range (e.g., 192.168.1.1-192.168.1.20): ") + ipInput, _ := reader.ReadString('\n') + ipInput = strings.TrimSpace(ipInput) + if ipInput == "" { + ipInput = "192.168.0.0-192.168.255.255" + } + fmt.Print("port range (e.g., 1-65535): ") + portInput, _ := reader.ReadString('\n') + portInput = strings.TrimSpace(portInput) + if portInput == "" { + portInput = "1-10000" + } + ipParts := strings.Split(ipInput, "-") + portParts := strings.Split(portInput, "-") + if len(ipParts) != 2 || len(portParts) != 2 { + fmt.Println("[!] bad input.") + os.Exit(1) + } + ipList, err := ipRange(ipParts[0], ipParts[1]) + if err != nil { + fmt.Println("[!] bad IP range:", err) + os.Exit(1) + } + portStart, _ := strconv.Atoi(portParts[0]) + portEnd, _ := strconv.Atoi(portParts[1]) + + fmt.Println("--------------------------------------------------") + fmt.Printf("scanning %s to %s\n", ipParts[0], ipParts[1]) + fmt.Printf("ports %d to %d\n", portStart, portEnd) + fmt.Println("started at:", time.Now()) + fmt.Println("--------------------------------------------------") + + pingCmd := []string{} + switch runtime.GOOS { + case "windows": + pingCmd = []string{"ping", "-n", "1", "-w", "1000"} + case "darwin": + pingCmd = []string{"ping", "-c", "1"} + default: + pingCmd = []string{"ping", "-c", "1", "-W", "1"} + } + + fmt.Println("[~] pinging IPs...") + var alive []string + for i := 0; i < len(ipList); i += batchSize { + end := i + batchSize + if end > len(ipList) { + end = len(ipList) + } + batch := ipList[i:end] + fmt.Printf("[~] pinging IPs %d to %d...\n", i+1, end) + batchAlive := pingIPsConcurrently(batch, pingCmd) + alive = append(alive, batchAlive...) + } + if len(alive) == 0 { + fmt.Println("[-] no IPs responded to ping. exiting.") + return + } + + clearScreen() + for idx, ip := range alive { + ipLineMap[ip] = idx + 1 + printStatusLine(ip, "waiting to scan...") + } + + var wg sync.WaitGroup + for i := 0; i < len(alive); i += batchSize { + end := i + batchSize + if end > len(alive) { + end = len(alive) + } + batch := alive[i:end] + for _, ip := range batch { + wg.Add(1) + go func(ip string) { + defer wg.Done() + scanPortsForIP(ip, portStart, portEnd) + }(ip) + } + wg.Wait() + } + + moveCursor(len(ipLineMap)+3+len(openPortMsgs), 0) + clearLine() + fmt.Println("\n--------------------------------------------------") + fmt.Println("scan done at:", time.Now()) + fmt.Println("--------------------------------------------------") + + file, err := os.Create("summary.txt") + if err != nil { + fmt.Println("[!] failed to write summary:", err) + return + } + defer file.Close() + + if len(openPorts) == 0 { + file.WriteString("no open ports found.\n") + } else { + file.WriteString("[+] scan summary: open ports found with fingerprints\n") + for _, p := range openPorts { + line := fmt.Sprintf(" - %s : port %d open - hostname: %s", p.IP, p.Port, p.Hostname) + if p.Fingerprint != "" { + line += " | " + p.Fingerprint + } + file.WriteString(line + "\n") + } + } + fmt.Println("[+] scan summary written to summary.txt") +} diff --git a/main.py b/main.py deleted file mode 100644 index 4539f26..0000000 --- a/main.py +++ /dev/null @@ -1,262 +0,0 @@ -import socket -from datetime import datetime -import ipaddress -import sys -import subprocess -import platform -from concurrent.futures import ThreadPoolExecutor, as_completed -import threading -import queue -import json - -log_queue = queue.Queue() - -print_lock = threading.Lock() -ip_status_lock = threading.Lock() -shutdown_event = threading.Event() -ip_line_map = {} -open_ports = [] -open_ports_lock = threading.Lock() -open_port_msgs = [] -open_port_msgs_lock = threading.Lock() -bottom_log_start_line = 0 - -def clear_screen(): - sys.stdout.write("\033[2J") - sys.stdout.flush() - -def move_cursor(row, col=0): - sys.stdout.write(f"\033[{row};{col}H") - -def clear_line(): - sys.stdout.write("\033[K") - -def print_status_line(ip, msg): - with ip_status_lock: - line = ip_line_map.get(ip) - if line: - move_cursor(line) - clear_line() - sys.stdout.write(f"[{ip}] {msg}") - sys.stdout.flush() - -def print_open_port_line(msg): - with open_port_msgs_lock: - open_port_msgs.append(msg) - bottom_line = len(ip_line_map) + 2 + len(open_port_msgs) - move_cursor(bottom_line) - clear_line() - sys.stdout.write(msg + "\n") - sys.stdout.flush() - -def ping_ip(ip, ping_cmd): - try: - cmd = ping_cmd + [ip] - result = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=1) - return result.returncode == 0 - except subprocess.TimeoutExpired: - return False - except: - return False - - -def is_private_ip(ip): - return ipaddress.IPv4Address(ip).is_private - -def grab_banner(ip, port): - try: - s = socket.socket() - s.settimeout(1) - s.connect((ip, port)) - if port == 22: - banner = s.recv(1024).decode(errors='ignore').strip() - return banner - elif port == 5900: - banner = s.recv(12).decode(errors='ignore').strip() - return banner - else: - return None - except: - return None - finally: - s.close() - -def identify_service_and_os(ip, port): - json_path = "services.json" - try: - with open(json_path, "r") as f: - data = json.load(f) - services = {int(k): v for k, v in data.items()} - except Exception: - services = {} - - service_info = services.get(port) - fingerprint = None - if service_info: - banner = grab_banner(ip, port) - if banner: - fingerprint = f"{service_info['name']} banner: {banner}" - else: - fingerprint = f"{service_info['name']}" - - return fingerprint - -def scan_port(ip, port): - print_status_line(ip, f"scanning port {port}...") - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.settimeout(0.5) - try: - result = s.connect_ex((ip, port)) - if result == 0: - try: - hostname = socket.gethostbyaddr(ip)[0] - except socket.herror: - hostname = "unknown hostname" - fingerprint = identify_service_and_os(ip, port) - with open_ports_lock: - open_ports.append((ip, port, hostname, fingerprint)) - msg = f"[{ip}] port {port} is OPEN ({hostname})" - if fingerprint: - msg += f" | {fingerprint}" - print_open_port_line(msg) - print_status_line(ip, f"open port found: {port}") - finally: - s.close() - -def scan_ports_for_ip(ip, port_start, port_end): - with ThreadPoolExecutor(max_workers=10) as p_exec: - futures = [p_exec.submit(scan_port, ip, port) for port in range(port_start, port_end + 1)] - for future in as_completed(futures): - if shutdown_event.is_set(): - break - try: - future.result() - except Exception as e: - with print_lock: - print(f"[!] port scan err on {ip}: {e}") - print_status_line(ip, "scan done.") - -def ping_ip_threaded(ip, ping_cmd): - if shutdown_event.is_set(): - return None - return ip if ping_ip(ip, ping_cmd) else None - -def ping_ips_concurrently(ips, ping_cmd, max_workers=50): - alive_ips = [] - with ThreadPoolExecutor(max_workers=max_workers) as executor: - futures = {executor.submit(ping_ip_threaded, ip, ping_cmd): ip for ip in ips} - for future in as_completed(futures): - if shutdown_event.is_set(): - break - ip = futures[future] - try: - result = future.result() - if result: - alive_ips.append(ip) - print(f"[+] found {ip}") - else: - print(f"[~] no response from {ip}") - except Exception as e: - print(f"[!] ping error for {ip}: {e}") - return alive_ips - -def main(): - try: - ip_range = input("enter ip range (e.g., 192.168.1.1-192.168.1.20) [default 192.168.0.0-192.168.255.255]: ").strip() - port_range = input("port range (e.g., 1-65535) [default 1-10000]: ").strip() - if ip_range == "": - ip_start_str, ip_end_str = "192.168.0.0", "192.168.255.255" - else: - ip_start_str, ip_end_str = ip_range.split('-') - if port_range == "": - port_start, port_end = 1, 10000 - else: - port_start, port_end = map(int, port_range.split('-')) - ip_start = ipaddress.IPv4Address(ip_start_str) - ip_end = ipaddress.IPv4Address(ip_end_str) - if ip_start > ip_end: - raise ValueError("start ip > end ip") - except Exception as e: - print(f"[!] bad input: {e}") - sys.exit(1) - - print("-"*60) - print(f"scanning {ip_start} to {ip_end}") - print(f"ports {port_start} to {port_end}") - print("started at:", str(datetime.now())) - print("-"*60) - - osname = platform.system().lower() - if osname == "windows": - ping_cmd = ["ping", "-n", "1", "-w", "1000"] - elif osname == "darwin": - ping_cmd = ["ping", "-c", "1"] - else: - ping_cmd = ["ping", "-c", "1", "-W", "1"] - - all_ips = [str(ipaddress.IPv4Address(i)) for i in range(int(ip_start), int(ip_end)+1)] - - any_public = False - for ip_str in all_ips: - ip_obj = ipaddress.IPv4Address(ip_str) - if not (ip_obj.is_private or ip_obj.is_loopback or ip_obj.is_reserved): - any_public = True - break - - if any_public: - print("[~] public IPs detected, skipping ping and scanning directly...") - ips_to_scan = all_ips - else: - private_ips = [ip for ip in all_ips if is_private_ip(ip)] - print("[~] pinging private IPs...") - alive_ips = ping_ips_concurrently(private_ips, ping_cmd) - if not alive_ips: - print("[-] no IPs responded to ping. exiting.") - sys.exit(0) - ips_to_scan = alive_ips - - clear_screen() - for idx, ip in enumerate(ips_to_scan): - ip_line_map[ip] = idx + 1 - for ip in ips_to_scan: - print_status_line(ip, "waiting to scan...") - with ThreadPoolExecutor(max_workers=50) as ip_executor: - futures = [] - for ip in ips_to_scan: - if shutdown_event.is_set(): - break - futures.append(ip_executor.submit(scan_ports_for_ip, ip, port_start, port_end)) - - for future in as_completed(futures): - try: - future.result() - except Exception as e: - print(f"[!] error scanning IP: {e}") - - - bottom_line = len(ip_line_map) + 3 + len(open_port_msgs) - move_cursor(bottom_line, 0) - clear_line() - - print("\n" + "-"*60) - print("scan done at:", str(datetime.now())) - print("-"*60 + "\n") - - with open("summary.txt", "w") as f: - if open_ports: - f.write("[+] scan summary: open ports found with fingerprints\n") - for ip, port, host, fingerprint in open_ports: - f.write(f" - {ip} : port {port} open - hostname: {host}") - if fingerprint: - f.write(f" | {fingerprint}") - f.write("\n") - else: - f.write("no open ports found.\n") - - print("[+] scan summary written to summary.txt :p\n") -if __name__ == "__main__": - try: - main() - except KeyboardInterrupt: - print("\n[!] ctrl+c caught. stopping...") - sys.exit(1)