2025-07-26 16:54:35 +02:00
|
|
|
package main
|
2025-07-26 18:49:35 +02:00
|
|
|
|
2025-07-26 16:54:35 +02:00
|
|
|
import (
|
|
|
|
"bufio"
|
2025-07-26 21:09:11 +02:00
|
|
|
"bytes"
|
2025-07-26 16:54:35 +02:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"os"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
type OpenPort struct {
|
2025-07-26 18:49:35 +02:00
|
|
|
IP string
|
|
|
|
Port int
|
|
|
|
Hostname string
|
2025-07-26 16:54:35 +02:00
|
|
|
Fingerprint string
|
|
|
|
}
|
2025-07-26 18:49:35 +02:00
|
|
|
|
2025-07-26 16:54:35 +02:00
|
|
|
const batchSize = 255
|
2025-07-26 21:09:11 +02:00
|
|
|
var minecraftServers []string
|
2025-07-26 16:54:35 +02:00
|
|
|
var (
|
2025-07-26 18:49:35 +02:00
|
|
|
printMutex sync.Mutex
|
|
|
|
fileLock sync.Mutex
|
|
|
|
openPorts []OpenPort
|
2025-07-26 16:54:35 +02:00
|
|
|
openPortsLock sync.Mutex
|
2025-07-26 18:49:35 +02:00
|
|
|
ipLineMap = make(map[string]int)
|
|
|
|
openPortMsgs []string
|
|
|
|
summaryFile *os.File
|
2025-07-26 16:54:35 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
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{}) {
|
2025-07-26 18:49:35 +02:00
|
|
|
printMutex.Lock()
|
|
|
|
defer printMutex.Unlock()
|
|
|
|
fmt.Printf(format, a...)
|
2025-07-26 16:54:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func safePrintln(a ...interface{}) {
|
2025-07-26 18:49:35 +02:00
|
|
|
printMutex.Lock()
|
|
|
|
defer printMutex.Unlock()
|
|
|
|
fmt.Println(a...)
|
2025-07-26 16:54:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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 ""
|
|
|
|
}
|
|
|
|
|
2025-07-26 21:09:11 +02:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-07-26 16:54:35 +02:00
|
|
|
func scanPort(ip string, port int, wg *sync.WaitGroup) {
|
2025-07-26 18:49:35 +02:00
|
|
|
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)
|
2025-07-26 21:09:11 +02:00
|
|
|
if port == 25565 {
|
|
|
|
minecraftServers = append(minecraftServers, ip)
|
|
|
|
}
|
2025-07-26 18:49:35 +02:00
|
|
|
openPortsLock.Lock()
|
|
|
|
openPorts = append(openPorts, OpenPort{ip, port, hostname, fingerprint})
|
2025-07-26 16:54:35 +02:00
|
|
|
line := fmt.Sprintf(" - %s : port %d open - hostname: %s", ip, port, hostname)
|
|
|
|
if fingerprint != "" {
|
|
|
|
line += " | " + fingerprint
|
|
|
|
}
|
|
|
|
|
|
|
|
fileLock.Lock()
|
|
|
|
summaryFile.WriteString(line + "\n")
|
|
|
|
fileLock.Unlock()
|
2025-07-26 18:49:35 +02:00
|
|
|
openPortsLock.Unlock()
|
2025-07-26 16:54:35 +02:00
|
|
|
|
2025-07-26 18:49:35 +02:00
|
|
|
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))
|
|
|
|
}
|
|
|
|
}
|
2025-07-26 16:54:35 +02:00
|
|
|
|
|
|
|
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() {
|
2025-07-26 18:49:35 +02:00
|
|
|
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()
|
|
|
|
|
2025-07-26 16:54:35 +02:00
|
|
|
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()
|
2025-07-26 18:49:35 +02:00
|
|
|
for idx, ip := range ipList {
|
2025-07-26 16:54:35 +02:00
|
|
|
ipLineMap[ip] = idx + 1
|
|
|
|
printStatusLine(ip, "waiting to scan...")
|
|
|
|
}
|
|
|
|
|
|
|
|
var wg sync.WaitGroup
|
2025-07-26 18:49:35 +02:00
|
|
|
for i := 0; i < len(ipList); i += batchSize {
|
2025-07-26 16:54:35 +02:00
|
|
|
end := i + batchSize
|
2025-07-26 18:49:35 +02:00
|
|
|
if end > len(ipList) {
|
|
|
|
end = len(ipList)
|
2025-07-26 16:54:35 +02:00
|
|
|
}
|
2025-07-26 18:49:35 +02:00
|
|
|
batch := ipList[i:end]
|
2025-07-26 16:54:35 +02:00
|
|
|
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("--------------------------------------------------")
|
2025-07-26 21:09:11 +02:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-07-27 14:01:46 +02:00
|
|
|
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")
|
|
|
|
|
2025-07-26 16:54:35 +02:00
|
|
|
}
|