package kyoketsu import ( "fmt" "log" "net" "os" "sync" "time" "github.com/go-ping/ping" "golang.org/x/net/icmp" "golang.org/x/net/ipv4" ) var PORT_MAP = map[int]string{ 22: "ssh", 23: "telnet", 53: "dns", 80: "http", 25: "smtp", 443: "https", 8080: "unknown", 8081: "unknown", //8082: "unknown", 8085: "unknown", 8090: "unknown", 8091: "unknown", 9010: "unknown", 9012: "unknown", 10000: "unknown", 1433: "microsoft_sql", 3306: "mysql", 3050: "firebird", 5432: "postgres", 27017: "mongo", 6379: "redis", 8005: "tomcat", 6443: "kubernetes", 853: "dns-tls", 143: "imap", 389: "ldap", 445: "smb", 543: "kerberos", 544: "kerberos", 749: "kerberos", 760: "kerberos", } /* Need to work with with a database schema in mind, and revolve functionality around that */ type Host struct { Fqdn string // The FQDN of the address targeted as per the systems default resolver IpAddress string // the IPv4 address (no ipv6 support yet) PingResponse bool // boolean value representing if the host responded to ICMP ListeningPorts []map[int]string // list of maps depicting a port number -> service name } /* Perform a concurrent TCP port dial on a host, either by domain name or IP. :param addr: the address of fqdn to scan :param portmap: a key/value pair of port numbers to service names to dial the host with */ func PortWalk(addr string, portmap map[int]string) *Host { wg := &sync.WaitGroup{} out := []*PortScanResult{} ports := RetrieveScanDirectives() for p, s := range ports.Pairs { wg.Add(1) go func(target string, p int, s string) { defer wg.Done() out = append(out, singlePortScan(target, p, s)) }(addr, p, s) } wg.Wait() host := &Host{IpAddress: addr, ListeningPorts: []map[int]string{}} for i := range out { if out[i].Listening { host.ListeningPorts = append(host.ListeningPorts, map[int]string{ out[i].PortNumber: out[i].Service, }) } } return host } type PortScanResult struct { // This is used to represent the results of a port scan against one host PortNumber int `json:"port_number"` // The port number that was scanned Service string `json:"service"` // the name of the service that the port was identified/mapped to Protocol string `json:"protocol"` // The IP protocol (TCP/UDP) Listening bool `json:"listening"` // A boolean value that depicts if the service is listening or not } type PortScanDirective struct { // Struct for dependency injecting the dynamic port map used for scans Pairs map[int]string } /* Wrapper function to dependency inject the resource for a port -> service name mapping. May move to a database, or something. */ func RetrieveScanDirectives() PortScanDirective { return PortScanDirective{Pairs: PORT_MAP} } /* Scans a single host on a single port :param addr: the address to dial :param port: the port number to dial :param svcs: the name of the service that the port is associate with */ func singlePortScan(addr string, port int, svcs string) *PortScanResult { address := fmt.Sprintf("%v:%d", addr, port) conn, err := net.DialTimeout("tcp", address, 5*time.Second) if err != nil { return &PortScanResult{PortNumber: port, Protocol: "tcp", Service: svcs, Listening: false} } conn.Close() return &PortScanResult{PortNumber: port, Protocol: "tcp", Service: svcs, Listening: true} } /* This function makes use of an external dependency, may or may not keep. It still needs to be implemented :param addr: the ip address to send an ICMP request to :param pinger: a pointer to a ping.Pinger struct to reuse */ func PingTarget(addr string, pinger *ping.Pinger) bool { err := pinger.SetAddr(addr) if err != nil { log.Println(err) return false } err = pinger.Run() if err != nil { log.Println(err) return false } stats := pinger.Statistics() if stats.PacketsRecv > 0 { return true } return false } /* Listen for ICMP responses on a specific address :param listening: the address of your server */ func IcmpListen(listening string) *icmp.PacketConn { icmpSrv, err := icmp.ListenPacket("udp4", listening) icmpSrv.SetDeadline(time.Now().Add(5 * time.Second)) if err != nil { log.Println(err) } return icmpSrv } /* Send an ICMP request to an address and listen for a response (UNFINISHED/NON-FUNCTIONAL) :param icmpSrv: a pointer to an ICMP PacketConn struct, for reading and sending ICMP requests. :param addr: the address to send the request to */ func icmpAsk(icmpSrv *icmp.PacketConn, addr string) bool { icmpReq := icmp.Message{ Type: ipv4.ICMPTypeEcho, Code: 0, Body: &icmp.Echo{ ID: os.Getpid() & 0xffff, Seq: 1, Data: []byte("DIALING FROM KYOKETSU"), }, } icmpB, err := icmpReq.Marshal(nil) if err != nil { log.Println(err) return false } if _, err := icmpSrv.WriteTo(icmpB, &net.UDPAddr{IP: net.ParseIP(addr)}); err != nil { log.Println(err) return false } respB := make([]byte, 1500) n, peer, err := icmpSrv.ReadFrom(respB) if err != nil { log.Println(err) return false } rm, err := icmp.ParseMessage(ipv4.ICMPTypeEcho.Protocol(), respB[:n]) if err != nil { log.Println(err) return false } switch rm.Type { case ipv4.ICMPTypeEchoReply: echo, ok := rm.Body.(*icmp.Echo) if !ok { return false } if peer.(*net.UDPAddr).IP.String() == addr && echo.Seq == 1 { return true } default: return false } return false }