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") }