From a356902789e8d2c726b690c5f2e7a20daf5da0ac Mon Sep 17 00:00:00 2001 From: juzeon Date: Thu, 8 Feb 2024 20:11:18 +0800 Subject: [PATCH] refactor structure --- .gitignore | 3 + main.go | 214 ++++++++++++++++------------------------------------- scanner.go | 50 +++++++++++++ utils.go | 70 ++++++++++++++++++ 4 files changed, 185 insertions(+), 152 deletions(-) create mode 100644 scanner.go create mode 100644 utils.go diff --git a/.gitignore b/.gitignore index 1ca1e4b..d019f9a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ RealiTLScanner RealiTLScanner.exe results.txt +.idea +in.txt +out.txt diff --git a/main.go b/main.go index ce4d67c..ed4a459 100644 --- a/main.go +++ b/main.go @@ -1,173 +1,83 @@ package main import ( - "crypto/tls" "flag" - "fmt" - "math/big" + "io" + "log/slog" "net" "os" + "strings" "sync" "time" ) +var addr string +var in string +var port int +var thread int +var out string +var timeout int +var verbose bool +var enableIPv6 bool + func main() { - addrPtr := flag.String("addr", "127.0.0.1", "Destination to start scan") - portPtr := flag.String("port", "443", "Port to scan") - threadPtr := flag.Int("thread", 2, "Number of threads to scan in parallel") - outPutFile := flag.Bool("o", false, "Is output to results.txt") - timeOutPtr := flag.Int("timeOut", 10, "Time out of a scan") - showFailPtr := flag.Bool("showFail", false, "Is Show fail logs") + _ = os.Unsetenv("ALL_PROXY") + _ = os.Unsetenv("HTTP_PROXY") + _ = os.Unsetenv("HTTPS_PROXY") + _ = os.Unsetenv("NO_PROXY") + flag.StringVar(&addr, "addr", "", "specify an IP, IP CIDR or domain to scan") + flag.StringVar(&in, "in", "", "specify a file that contains multiple "+ + "IPs, IP CIDRs or domains to scan, divided by line break") + flag.IntVar(&port, "port", 443, "specify a HTTPS port to check") + flag.IntVar(&thread, "thread", 1, "count of concurrent tasks") + flag.StringVar(&out, "out", "", "output file to store the result") + flag.IntVar(&timeout, "timeout", 10, "timeout for every check") + flag.BoolVar(&verbose, "v", false, "verbose output") + flag.BoolVar(&enableIPv6, "46", false, "Enable IPv6 in additional to IPv4") flag.Parse() - s := Scanner{ - addr: *addrPtr, - port: *portPtr, - showFail: *showFailPtr, - output: *outPutFile, - timeout: time.Duration(*timeOutPtr) * time.Second, - numberOfThread: *threadPtr, - mu: new(sync.Mutex), + if verbose { + slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + Level: slog.LevelDebug, + }))) } - if *outPutFile { - s.logFile, _ = os.OpenFile("results.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) - } - s.Print(fmt.Sprint("Reality TLS Scanner running: ", *addrPtr, ":", *portPtr, " start scan at : ", time.Now().Format(time.RFC3339))) - defer s.logFile.Close() - s.Run() -} - -type Scanner struct { - addr string - port string - output bool - showFail bool - logFile *os.File - timeout time.Duration - numberOfThread int - mu *sync.Mutex - high net.IP - low net.IP -} - -func (s *Scanner) Run() { - str := s.addr - addr := net.ParseIP(s.addr) - if addr != nil && addr.To4() == nil { - str = "[" + addr.String() + "]" - } - conn, err := net.DialTimeout("tcp", str+":"+s.port, s.timeout) - if err != nil { - s.Print(fmt.Sprint("Dial failed: ", err)) - } else { - addr = conn.RemoteAddr().(*net.TCPAddr).IP - line := "" + conn.RemoteAddr().String() + " \t" - conn.SetDeadline(time.Now().Add(s.timeout)) - c := tls.Client(conn, &tls.Config{ - InsecureSkipVerify: true, - NextProtos: []string{"h2", "http/1.1"}, - }) - err = c.Handshake() - if err != nil { - s.Print(fmt.Sprint("", line, "TLS handshake failed: ", err)) - } else { - state := c.ConnectionState() - alpn := state.NegotiatedProtocol - if alpn == "" { - alpn = " " - } - s.Print(fmt.Sprint("", line, "----- Found TLS v", TlsDic[state.Version], "\tALPN ", alpn, "\t", state.PeerCertificates[0].Subject)) - c.Close() - } - } - if addr == nil { - s.Print("Invalid address format") + if addr != "" && in != "" || + addr == "" && in == "" { + slog.Error("You must specify either `addr` or `in`") + flag.PrintDefaults() return } - - s.mu.Lock() - s.high = addr - s.low = addr - s.mu.Unlock() - for i := 0; i < s.numberOfThread; i++ { - go s.Scan(i%2 == 0) - } - for { - // now the scans are performed in goroutines - } -} - -func (s *Scanner) Scan(increment bool) { - var addr net.IP - s.mu.Lock() - if increment { - s.high = nextIP(s.high, increment) - addr = s.high - } else { - s.low = nextIP(s.low, increment) - addr = s.low - } - s.mu.Unlock() - str := addr.String() - if addr.To4() == nil { - str = "[" + str + "]" - } - conn, err := net.DialTimeout("tcp", str+":"+s.port, s.timeout) - if err != nil { - if s.showFail { - s.Print(fmt.Sprint("Dial failed: ", err)) - } - } else { - line := "" + conn.RemoteAddr().String() + " \t" - conn.SetDeadline(time.Now().Add(s.timeout)) - c := tls.Client(conn, &tls.Config{ - InsecureSkipVerify: true, - NextProtos: []string{"h2", "http/1.1"}, - }) - err = c.Handshake() + outWriter := io.Discard + if out != "" { + f, err := os.OpenFile(out, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) if err != nil { - if s.showFail { - s.Print(fmt.Sprint("", line, "TLS handshake failed: ", err)) - } - } else { - defer c.Close() - state := c.ConnectionState() - alpn := state.NegotiatedProtocol - if alpn == "" { - alpn = " " - } - if s.showFail || (state.Version == 0x0304 && alpn == "h2") { - s.Print(fmt.Sprint("", line, "----- Found TLS v", TlsDic[state.Version], "\tALPN ", alpn, "\t", state.PeerCertificates[0].Subject)) - } + slog.Error("Error opening file", "path", out) + return } + outWriter = f } - go s.Scan(increment) -} - -func (s *Scanner) Print(outStr string) { - if s.output { - s.logFile.WriteString(outStr + "\n") - } - fmt.Println(outStr) -} - -func nextIP(ip net.IP, increment bool) net.IP { - // Convert to big.Int and increment - ipb := big.NewInt(0).SetBytes([]byte(ip)) - if increment { - ipb.Add(ipb, big.NewInt(1)) + var ipChan <-chan net.IP + if addr != "" { + ipChan = Iterate(strings.NewReader(addr)) } else { - ipb.Sub(ipb, big.NewInt(1)) + f, err := os.Open(in) + if err != nil { + slog.Error("Error reading file", "path", in) + return + } + ipChan = Iterate(f) } - - // Add leading zeros - b := ipb.Bytes() - b = append(make([]byte, len(ip)-len(b)), b...) - return net.IP(b) -} - -var TlsDic = map[uint16]string{ - 0x0301: "1.0", - 0x0302: "1.1", - 0x0303: "1.2", - 0x0304: "1.3", + var wg sync.WaitGroup + wg.Add(thread) + for i := 0; i < thread; i++ { + go func() { + for ip := range ipChan { + ScanTLS(ip, outWriter) + } + wg.Done() + }() + } + t := time.Now() + slog.Info("Started all scanning threads", "time", t) + wg.Wait() + slog.Info("Scanning completed", "time", time.Now(), "elapsed", time.Since(t).String()) } diff --git a/scanner.go b/scanner.go new file mode 100644 index 0000000..e5e492b --- /dev/null +++ b/scanner.go @@ -0,0 +1,50 @@ +package main + +import ( + "crypto/tls" + "io" + "log/slog" + "net" + "strconv" + "time" +) + +var TLSDictionary = map[uint16]string{ + 0x0301: "1.0", + 0x0302: "1.1", + 0x0303: "1.2", + 0x0304: "1.3", +} + +func ScanTLS(ip net.IP, out io.Writer) { + hostPort := net.JoinHostPort(ip.String(), strconv.Itoa(port)) + conn, err := net.DialTimeout("tcp", hostPort, time.Duration(timeout)*time.Second) + if err != nil { + slog.Debug("Cannot dial", "target", hostPort) + return + } + defer conn.Close() + err = conn.SetDeadline(time.Now().Add(time.Duration(timeout) * time.Second)) + if err != nil { + slog.Error("Error setting deadline", "err", err) + return + } + c := tls.Client(conn, &tls.Config{ + InsecureSkipVerify: true, + NextProtos: []string{"h2", "http/1.1"}, + }) + err = c.Handshake() + if err != nil { + slog.Debug("TLS handshake failed", "target", hostPort) + return + } + state := c.ConnectionState() + alpn := state.NegotiatedProtocol + log := slog.Debug + if state.Version == 0x0304 && alpn == "h2" { + log = slog.Info + _, _ = io.WriteString(out, ip.String()+"\n") + } + log("Connected to target", "host", ip.String(), "tls", TLSDictionary[state.Version], + "alpn", alpn, "cert", state.PeerCertificates[0].Subject) +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..caad5ba --- /dev/null +++ b/utils.go @@ -0,0 +1,70 @@ +package main + +import ( + "bufio" + "errors" + "io" + "log/slog" + "net" + "net/netip" + "strings" +) + +func Iterate(reader io.Reader) <-chan net.IP { + scanner := bufio.NewScanner(reader) + ipChan := make(chan net.IP) + go func() { + defer close(ipChan) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" { + continue + } + ip := net.ParseIP(line) + if ip != nil && (ip.To4() != nil || enableIPv6) { + // ip address + ipChan <- ip + continue + } + _, _, err := net.ParseCIDR(line) + if err == nil { + // ip cidr + p, err := netip.ParsePrefix(line) + if err != nil { + slog.Warn("Invalid cidr", "cidr", line, "err", err) + } + if !p.Addr().Is4() && !enableIPv6 { + continue + } + p = p.Masked() + addr := p.Addr() + for { + if !p.Contains(addr) { + break + } + ip = net.ParseIP(addr.String()) + if ip != nil { + ipChan <- ip + } + addr = addr.Next() + } + continue + } + ips, err := net.LookupIP(line) + if err == nil { + // domain + for _, ip = range ips { + if ip.To4() != nil || enableIPv6 { + ipChan <- ip + } + } + continue + } + slog.Warn("Not a valid IP, IP CIDR or domain", "line", line) + } + if err := scanner.Err(); err != nil && !errors.Is(err, io.EOF) { + slog.Error("Read file error", "err", err) + } + }() + return ipChan +}