diff --git a/README.md b/README.md new file mode 100644 index 0000000..c49b386 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# this is NOT for scraping public IPs +i had like zero clue that it was a legal gray zone in europe so like i suggest you do NOT use it on public ips \ No newline at end of file diff --git a/compile.sh b/compile.sh new file mode 100644 index 0000000..a1bcf7b --- /dev/null +++ b/compile.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +# Existing builds +GOOS=darwin GOARCH=arm64 go build -o bin/portscraper_OSX_ARM64 main.go +GOOS=darwin GOARCH=amd64 go build -o bin/portscraper_OSX_X64 main.go +GOOS=windows GOARCH=amd64 go build -o bin/portscraper_WIN_X64_86.exe main.go +GOOS=windows GOARCH=386 go build -o bin/portscraper_WIN_X86.exe main.go +GOOS=freebsd GOARCH=amd64 go build -o bin/portscraper_FREEBSD_X64 main.go +GOOS=freebsd GOARCH=386 go build -o bin/portscraper_FREEBSD_X32 main.go +GOOS=freebsd GOARCH=arm64 go build -o bin/portscraper_FREEBSD_ARM64 main.go +GOOS=linux GOARCH=386 go build -o bin/portscraper_LINUX_X32 main.go +GOOS=linux GOARCH=amd64 go build -o bin/portscraper_LINUX_X64 main.go +GOOS=linux GOARCH=arm64 go build -o bin/portscraper_LINUX_ARM64 main.go +GOOS=linux GOARCH=arm go build -o bin/portscraper_LINUX_ARM main.go +GOOS=linux GOARCH=mipsle go build -o bin/portscraper_LINUX_MIPSLE main.go +GOOS=linux GOARCH=mips go build -o bin/portscraper_LINUX_MIPS main.go + +# my stupid fucking router GL.iNet GL-E750 +GOOS=linux GOARCH=mips GOMIPS=softfloat CGO_ENABLED=0 go build \ + -ldflags="-s -w -extldflags '-static'" \ + -o bin/portscraper_LINUX_MIPS_SOFT main.go \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..d7e5636 --- /dev/null +++ b/main.go @@ -0,0 +1,370 @@ +package main + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "net" + "os" + "strconv" + "strings" + "sync" + "time" +) + +type OpenPort struct { + IP string + Port int + Hostname string + Fingerprint string +} + +const batchSize = 255 +var minecraftServers []string +var ( + printMutex sync.Mutex + fileLock sync.Mutex + openPorts []OpenPort + openPortsLock sync.Mutex + ipLineMap = make(map[string]int) + openPortMsgs []string + summaryFile *os.File +) + +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 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 queryMinecraftServer(ip string, port int) string { + conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ip, port), 3*time.Second) + if err != nil { + return "" + } + defer conn.Close() + writeVarInt := func(val int) []byte { + var out []byte + for { + temp := byte(val & 0x7F) + val >>= 7 + if val != 0 { + temp |= 0x80 + } + out = append(out, temp) + if val == 0 { + break + } + } + return out + } + + protocolVersion := 754 + serverAddr := ip + state := 1 + // so confusing for me i actually had to comment stuff + var payload []byte + payload = append(payload, 0x00) // fackin packet ID for handshake + payload = append(payload, writeVarInt(protocolVersion)...) // protocol version + payload = append(payload, writeVarInt(len(serverAddr))...) // address length + payload = append(payload, []byte(serverAddr)...) // address + payload = append(payload, byte(port>>8), byte(port&0xFF)) // port + payload = append(payload, byte(state)) // next state: status + + packet := append(writeVarInt(len(payload)), payload...) // full packet = length + payload + conn.Write(packet) // send handshake + conn.Write([]byte{0x01, 0x00}) // send status request + + conn.SetReadDeadline(time.Now().Add(3 * time.Second)) + buf := make([]byte, 4096) + n, err := conn.Read(buf) + if err != nil || n == 0 { + return "" + } + + start := bytes.IndexByte(buf, '{') + if start == -1 { + return "" + } + jsonData := string(buf[start:n]) + + var status struct { + Version struct { + Name string `json:"name"` + } `json:"version"` + Description interface{} `json:"description"` + Players struct { + Online int `json:"online"` + Max int `json:"max"` + } `json:"players"` + } + + if err := json.Unmarshal([]byte(jsonData), &status); err != nil { + return "" + } + + desc := "" + switch v := status.Description.(type) { + case string: + desc = v + case map[string]interface{}: + if text, ok := v["text"].(string); ok { + desc = text + } + } + + return fmt.Sprintf("%s | %d/%d players | %s", status.Version.Name, status.Players.Online, status.Players.Max, desc) +} + + +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) + if port == 25565 { + minecraftServers = append(minecraftServers, ip) + } + 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 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("--------------------------------------------------") + + clearScreen() + for idx, ip := range ipList { + ipLineMap[ip] = idx + 1 + printStatusLine(ip, "waiting to scan...") + } + + var wg sync.WaitGroup + for i := 0; i < len(ipList); i += batchSize { + end := i + batchSize + if end > len(ipList) { + end = len(ipList) + } + batch := ipList[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("--------------------------------------------------") + if len(minecraftServers) > 0 { + safePrintln("[*] querying Minecraft servers on port 25565...") + for _, ip := range minecraftServers { + status := queryMinecraftServer(ip, 25565) + if status != "" { + safePrintln("[MC] Server at", ip, "responded.") + summaryFile.WriteString("[MC] " + ip + ":25565 " + status + "\n") + } else { + summaryFile.WriteString("[MC] " + ip + ":25565 no response or malformed\n") + } + } + } + if len(openPorts) == 0 { + summaryFile.WriteString("no open ports found.\n") + } else { + summaryFile.WriteString("[+] scan summary: open ports found with fingerprints above\n") + summaryFile.WriteString(fmt.Sprintf("[+] scanned %s to %s\n[+] ports %d to %d\n[+] %d scanned\n[+] %d hits\n", ipParts[0], ipParts[1], portStart, portEnd, len(ipList), len(openPorts))) + + + } + fmt.Println("[+] scan summary written to summary.txt") + +} diff --git a/main.py b/main.py deleted file mode 100644 index f0f3aac..0000000 --- a/main.py +++ /dev/null @@ -1,253 +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...") - for ip in ips_to_scan: - if shutdown_event.is_set(): - break - scan_ports_for_ip(ip, port_start, port_end) - - 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) diff --git a/services.json b/services.json index d6f6b2a..8cb0264 100644 --- a/services.json +++ b/services.json @@ -11,6 +11,7 @@ "143": {"name": "IMAP"}, "161": {"name": "SNMP"}, "445": {"name": "SMB (Windows File Sharing)"}, + "443": {"name": "HTTPS"}, "1433": {"name": "MSSQL"}, "1521": {"name": "Oracle DB"}, "3306": {"name": "MySQL"},