123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- 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
- }
|