diff --git a/core/core.go b/core/core.go index 69faa7c3..1c8bf05b 100644 --- a/core/core.go +++ b/core/core.go @@ -6,6 +6,7 @@ import ( "os/exec" "os/user" "path/filepath" + "sort" "strings" ) @@ -21,6 +22,25 @@ func TrimRight(s string) string { return strings.TrimRight(s, defaultTrimSet) } +func UniqueInts(a []int, sorted bool) []int { + tmp := make(map[int]bool) + uniq := make([]int, 0) + + for _, n := range a { + tmp[n] = true + } + + for n, _ := range tmp { + uniq = append(uniq, n) + } + + if sorted { + sort.Ints(uniq) + } + + return uniq +} + func Exec(executable string, args []string) (string, error) { path, err := exec.LookPath(executable) if err != nil { diff --git a/main.go b/main.go index eeadd63b..8eea497f 100644 --- a/main.go +++ b/main.go @@ -50,6 +50,7 @@ func main() { sess.Register(modules.NewRestAPI(sess)) sess.Register(modules.NewWOL(sess)) sess.Register(modules.NewWiFiRecon(sess)) + sess.Register(modules.NewSynScanner(sess)) if err = sess.Start(); err != nil { log.Fatal("%s", err) diff --git a/modules/arp_spoof.go b/modules/arp_spoof.go index 243ed108..25292c3d 100644 --- a/modules/arp_spoof.go +++ b/modules/arp_spoof.go @@ -6,7 +6,7 @@ import ( "time" "github.com/evilsocket/bettercap-ng/log" - network "github.com/evilsocket/bettercap-ng/network" + "github.com/evilsocket/bettercap-ng/network" "github.com/evilsocket/bettercap-ng/packets" "github.com/evilsocket/bettercap-ng/session" diff --git a/modules/syn_scan.go b/modules/syn_scan.go new file mode 100644 index 00000000..54018531 --- /dev/null +++ b/modules/syn_scan.go @@ -0,0 +1,254 @@ +package modules + +import ( + "fmt" + "net" + "strconv" + "strings" + "time" + + "github.com/evilsocket/bettercap-ng/core" + "github.com/evilsocket/bettercap-ng/log" + "github.com/evilsocket/bettercap-ng/network" + "github.com/evilsocket/bettercap-ng/packets" + "github.com/evilsocket/bettercap-ng/session" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + + "github.com/malfunkt/iprange" +) + +const synSourcePort = 666 + +type SynScanner struct { + session.SessionModule + addresses []net.IP + startPort int + endPort int +} + +func NewSynScanner(s *session.Session) *SynScanner { + ss := &SynScanner{ + SessionModule: session.NewSessionModule("syn.scan", s), + addresses: make([]net.IP, 0), + startPort: 0, + endPort: 0, + } + + ss.AddHandler(session.NewModuleHandler("syn.scan IP-RANGE START-PORT END-PORT", "syn.scan ([^\\s]+) (\\d+)([\\s\\d]*)", + "Perform a syn port scanning against an IP address within the provided ports range.", + func(args []string) error { + var err error + + list, err := iprange.Parse(args[0]) + if err != nil { + return fmt.Errorf("Error while parsing IP range '%s': %s", args[0], err) + } + + ss.addresses = list.Expand() + ss.startPort = 0 + ss.endPort = 0 + + if ss.startPort, err = strconv.Atoi(core.Trim(args[1])); err != nil { + return fmt.Errorf("Invalid START-PORT: %s", err) + } + + if ss.startPort > 65535 { + ss.startPort = 65535 + } + ss.endPort = ss.startPort + + argc := len(args) + if argc > 2 && core.Trim(args[2]) != "" { + if ss.endPort, err = strconv.Atoi(core.Trim(args[2])); err != nil { + return fmt.Errorf("Invalid END-PORT: %s", err) + } + } + + if ss.endPort < ss.startPort { + return fmt.Errorf("END-PORT is greater than START-PORT") + } + + return ss.synScan() + })) + + return ss +} + +func (s *SynScanner) Name() string { + return "syn.scan" +} + +func (s *SynScanner) Description() string { + return "A module to perform SYN port scanning." +} + +func (s *SynScanner) Author() string { + return "Simone Margaritelli " +} + +func (s *SynScanner) Configure() error { + return nil +} + +func (s *SynScanner) Start() error { + return nil +} + +func (s *SynScanner) Stop() error { + return nil +} + +func (s *SynScanner) getMAC(ip net.IP, probe bool) (net.HardwareAddr, error) { + var mac string + var hw net.HardwareAddr + var err error + + // do we have this ip mac address? + mac, err = network.ArpLookup(s.Session.Interface.Name(), ip.String(), false) + if err != nil && probe == true { + from := s.Session.Interface.IP + from_hw := s.Session.Interface.HW + + if err, probe := packets.NewUDPProbe(from, from_hw, ip, 139); err != nil { + log.Error("Error while creating UDP probe packet for %s: %s", ip.String(), err) + } else { + s.Session.Queue.Send(probe) + } + + time.Sleep(500 * time.Millisecond) + + mac, err = network.ArpLookup(s.Session.Interface.Name(), ip.String(), false) + } + + if mac == "" { + return nil, fmt.Errorf("Could not find hardware address for %s.", ip.String()) + } + + mac = network.NormalizeMac(mac) + hw, err = net.ParseMAC(mac) + if err != nil { + return nil, fmt.Errorf("Error while parsing hardware address '%s' for %s: %s", mac, ip.String(), err) + } + + return hw, nil +} + +func (s *SynScanner) inRange(ip net.IP) bool { + for _, a := range s.addresses { + if a.Equal(ip) { + return true + } + } + return false +} + +func (s *SynScanner) onPacket(pkt gopacket.Packet) { + var eth layers.Ethernet + var ip layers.IPv4 + var tcp layers.TCP + foundLayerTypes := []gopacket.LayerType{} + + parser := gopacket.NewDecodingLayerParser( + layers.LayerTypeEthernet, + ð, + &ip, + &tcp, + ) + + err := parser.DecodeLayers(pkt.Data(), &foundLayerTypes) + if err != nil { + return + } + + if s.inRange(ip.SrcIP) && tcp.DstPort == synSourcePort && tcp.SYN && tcp.ACK { + from := ip.SrcIP.String() + + log.Info("Found open port %d for %s", tcp.SrcPort, core.Bold(from)) + + var host *network.Endpoint + + if ip.SrcIP.Equal(s.Session.Interface.IP) { + host = s.Session.Interface + } else if ip.SrcIP.Equal(s.Session.Gateway.IP) { + host = s.Session.Gateway + } else { + host = s.Session.Lan.GetByIp(from) + } + + if host != nil { + sports := strings.Split(host.Meta.Get("tcp-ports").(string), ",") + ports := []int{int(tcp.SrcPort)} + + for _, s := range sports { + n, err := strconv.Atoi(s) + if err == nil { + ports = append(ports, n) + } + } + + ports = core.UniqueInts(ports, true) + list := make([]string, len(ports)) + for i, p := range ports { + list[i] = fmt.Sprintf("%d", p) + } + + host.Meta.Set("tcp-ports", strings.Join(list, ",")) + } + } +} + +func (s *SynScanner) synScan() error { + if s.Running() == true { + return fmt.Errorf("A scan is already running, wait for it to end before starting a new one.") + } + + s.SetRunning(true, func() { + defer s.SetRunning(false, nil) + + naddrs := len(s.addresses) + plural := "es" + if naddrs == 1 { + plural = "" + } + + if s.startPort != s.endPort { + log.Info("SYN scanning %d address%s from port %d to port %d ...", naddrs, plural, s.startPort, s.endPort) + } else { + log.Info("SYN scanning %d address%s on port %d ...", naddrs, plural, s.startPort) + } + + // set the collector + s.Session.Queue.OnPacket(s.onPacket) + defer s.Session.Queue.OnPacket(nil) + + // start sending SYN packets and wait + for _, address := range s.addresses { + mac, err := s.getMAC(address, true) + if err != nil { + log.Debug("Could not get MAC for %s: %s", address.String(), err) + continue + } + + for dstPort := s.startPort; dstPort < s.endPort+1; dstPort++ { + err, raw := packets.NewTCPSyn(s.Session.Interface.IP, s.Session.Interface.HW, address, mac, synSourcePort, dstPort) + if err != nil { + log.Error("Error creating SYN packet: %s", err) + continue + } + + if err := s.Session.Queue.Send(raw); err != nil { + log.Error("Error sending SYN packet: %s", err) + } else { + log.Debug("Sent %d bytes of SYN packet to %s for port %d", len(raw), address.String(), dstPort) + } + } + } + + nports := s.endPort - s.startPort + 1 + time.Sleep(time.Duration(nports*500) * time.Millisecond) + }) + + return nil +} diff --git a/network/meta.go b/network/meta.go index b2f72d72..5ac6fb01 100644 --- a/network/meta.go +++ b/network/meta.go @@ -43,6 +43,16 @@ func (m *Meta) Get(name string) interface{} { return "" } +func (m *Meta) GetOr(name string, dflt interface{}) interface{} { + m.Lock() + defer m.Unlock() + + if v, found := m.m[name]; found == true { + return v + } + return dflt +} + func (m *Meta) Each(cb func(name string, value interface{})) { m.Lock() defer m.Unlock() diff --git a/packets/queue.go b/packets/queue.go index 56432ff0..83a139d8 100644 --- a/packets/queue.go +++ b/packets/queue.go @@ -32,8 +32,10 @@ type Stats struct { Errors uint64 } +type PacketCallback func(pkt gopacket.Packet) + type Queue struct { - sync.Mutex + sync.RWMutex Activities chan Activity `json:"-"` @@ -44,6 +46,7 @@ type Queue struct { iface *network.Endpoint handle *pcap.Handle source *gopacket.PacketSource + pktCb PacketCallback active bool } @@ -55,6 +58,7 @@ func NewQueue(iface *network.Endpoint) (q *Queue, err error) { iface: iface, active: !iface.IsMonitor(), + pktCb: nil, } if q.active == true { @@ -69,6 +73,21 @@ func NewQueue(iface *network.Endpoint) (q *Queue, err error) { return } +func (q *Queue) OnPacket(cb PacketCallback) { + q.Lock() + defer q.Unlock() + q.pktCb = cb +} + +func (q *Queue) onPacketCallback(pkt gopacket.Packet) { + q.RLock() + defer q.RUnlock() + + if q.pktCb != nil { + q.pktCb(pkt) + } +} + func (q *Queue) trackProtocols(pkt gopacket.Packet) { // gather protocols stats pktLayers := pkt.Layers() @@ -130,6 +149,8 @@ func (q *Queue) worker() { atomic.AddUint64(&q.Stats.PktReceived, 1) atomic.AddUint64(&q.Stats.Received, pktSize) + q.onPacketCallback(pkt) + // decode eth and ipv4 layers leth := pkt.Layer(layers.LayerTypeEthernet) lip4 := pkt.Layer(layers.LayerTypeIPv4) diff --git a/packets/tcp.go b/packets/tcp.go new file mode 100644 index 00000000..3b646b3e --- /dev/null +++ b/packets/tcp.go @@ -0,0 +1,29 @@ +package packets + +import ( + "github.com/google/gopacket/layers" + "net" +) + +func NewTCPSyn(from net.IP, from_hw net.HardwareAddr, to net.IP, to_hw net.HardwareAddr, srcPort int, dstPort int) (error, []byte) { + eth := layers.Ethernet{ + SrcMAC: from_hw, + DstMAC: to_hw, + EthernetType: layers.EthernetTypeIPv4, + } + ip4 := layers.IPv4{ + Protocol: layers.IPProtocolTCP, + Version: 4, + TTL: 64, + SrcIP: from, + DstIP: to, + } + tcp := layers.TCP{ + SrcPort: layers.TCPPort(srcPort), + DstPort: layers.TCPPort(dstPort), + SYN: true, + } + tcp.SetNetworkLayerForChecksum(&ip4) + + return Serialize(ð, &ip4, &tcp) +} diff --git a/session/session.go b/session/session.go index 6475e796..faa64a1e 100644 --- a/session/session.go +++ b/session/session.go @@ -484,5 +484,5 @@ func (s *Session) Run(line string) error { } } - return fmt.Errorf("Unknown command %s%s%s, type %shelp%s for the help menu.", core.BOLD, line, core.RESET, core.BOLD, core.RESET) + return fmt.Errorf("Unknown or invalid syntax \"%s%s%s\", type %shelp%s for the help menu.", core.BOLD, line, core.RESET, core.BOLD, core.RESET) }