diff --git a/modules/dns_spoof/dns_spoof.go b/modules/dns_spoof/dns_spoof.go index 7c197274..52ac77a7 100644 --- a/modules/dns_spoof/dns_spoof.go +++ b/modules/dns_spoof/dns_spoof.go @@ -5,6 +5,10 @@ import ( "fmt" "net" "sync" + "context" + "bufio" + "os" + "regexp" "github.com/bettercap/bettercap/packets" "github.com/bettercap/bettercap/session" @@ -21,6 +25,9 @@ type DNSSpoofer struct { Handle *pcap.Handle Hosts Hosts All bool + ProxyDNS bool + ProxyDNSIP net.IP + ProxyResolver *net.Resolver waitGroup *sync.WaitGroup pktSourceChan chan gopacket.Packet } @@ -30,6 +37,9 @@ func NewDNSSpoofer(s *session.Session) *DNSSpoofer { SessionModule: session.NewSessionModule("dns.spoof", s), Handle: nil, All: false, + ProxyDNS: false, + ProxyDNSIP: nil, + ProxyResolver: nil, Hosts: Hosts{}, waitGroup: &sync.WaitGroup{}, } @@ -53,6 +63,15 @@ func NewDNSSpoofer(s *session.Session) *DNSSpoofer { "false", "If true the module will reply to every DNS request, otherwise it will only reply to the one targeting the local pc.")) + mod.AddParam(session.NewBoolParameter("dns.spoof.proxy", + "false", + "If true the module will reply to every DNS request, with faked entries for selected domains and real ones for non-selected domains.")) + + mod.AddParam(session.NewStringParameter("dns.spoof.proxy.srv", + "system", + "", + "IP address of proxy dns server or 'system' for system dns")) + mod.AddHandler(session.NewModuleHandler("dns.spoof on", "", "Start the DNS spoofer in the background.", func(args []string) error { @@ -80,17 +99,44 @@ func (mod DNSSpoofer) Author() string { return "Simone Margaritelli " } +func (mod *DNSSpoofer)getSystemResolver() (net.IP,error) { + file, err := os.Open("/etc/resolv.conf") + if err != nil { + return nil,err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + r := regexp.MustCompile(`^\s*nameserver\s*(?P\d+\.\d+\.\d+\.\d+)`) + for scanner.Scan() { + matches := r.FindStringSubmatch(scanner.Text()) + if len(matches) == 2 { + ip := net.ParseIP(matches[1]) + if ip != nil { + mod.Info("using system dns server for proxy dns: %s",matches[1]) + return ip,nil + } + } + } + + if err := scanner.Err(); err != nil { + return nil,err + } + return nil,fmt.Errorf("no nameserver found for proxy dns") +} + func (mod *DNSSpoofer) Configure() error { var err error var hostsFile string var domains []string var address net.IP + var proxyip string if mod.Running() { return session.ErrAlreadyStarted } else if mod.Handle, err = pcap.OpenLive(mod.Session.Interface.Name(), 65536, true, pcap.BlockForever); err != nil { return err - } else if err = mod.Handle.SetBPFFilter("udp"); err != nil { + } else if err = mod.Handle.SetBPFFilter("udp and port 53"); err != nil { return err } else if err, mod.All = mod.BoolParam("dns.spoof.all"); err != nil { return err @@ -100,6 +146,10 @@ func (mod *DNSSpoofer) Configure() error { return err } else if err, hostsFile = mod.StringParam("dns.spoof.hosts"); err != nil { return err + } else if err, mod.ProxyDNS = mod.BoolParam("dns.spoof.proxy"); err != nil { + return err + } else if err, proxyip = mod.StringParam("dns.spoof.proxy.srv"); err != nil { + return err } mod.Hosts = Hosts{} @@ -124,6 +174,33 @@ func (mod *DNSSpoofer) Configure() error { mod.Info("%s -> %s", entry.Host, entry.Address) } + if mod.ProxyDNS { + if proxyip == "system" { + if mod.ProxyDNSIP, err = mod.getSystemResolver(); err != nil { + return err + } + } else { + mod.ProxyDNSIP = net.ParseIP(proxyip) + if mod.ProxyDNSIP == nil { + return fmt.Errorf("invalid dns server ip %s",proxyip) + } + } + + dailer := func(ctx context.Context, network, address string) (net.Conn, error) { + d := net.Dialer{} + return d.DialContext(ctx, "udp", proxyip + ":53") + } + + mod.ProxyResolver = &net.Resolver{ + Dial: dailer, + PreferGo: true, + } + + if mod.ProxyResolver == nil { + return fmt.Errorf("dns.spoof.proxy.srv must be filled with dns server ip or 'system'") + } + } + if !mod.Session.Firewall.IsForwardingEnabled() { mod.Info("enabling forwarding.") mod.Session.Firewall.EnableForwarding(true) @@ -132,49 +209,111 @@ func (mod *DNSSpoofer) Configure() error { return nil } -func (mod *DNSSpoofer) dnsReply(pkt gopacket.Packet, peth *layers.Ethernet, pudp *layers.UDP, domain string, address net.IP, req *layers.DNS, target net.HardwareAddr) { - redir := fmt.Sprintf("(->%s)", address.String()) - who := target.String() +func (mod *DNSSpoofer) dnsSend(eth *layers.Ethernet, src net.IP, dst net.IP, udp *layers.UDP, dns *layers.DNS) { + var err error = nil + var raw []byte - if t, found := mod.Session.Lan.Get(target.String()); found { - who = t.String() + if len(src) == net.IPv4len { + ip4 := layers.IPv4{ + Protocol: layers.IPProtocolUDP, + Version: 4, + TTL: 64, + SrcIP: src, + DstIP: dst, + } + udp.SetNetworkLayerForChecksum(&ip4) + err, raw = packets.Serialize(eth, &ip4, udp, dns) + } else if len(src) == net.IPv6len { + ip6 := layers.IPv6{ + Version: 6, + NextHeader: layers.IPProtocolUDP, + HopLimit: 64, + SrcIP: src, + DstIP: dst, + } + udp.SetNetworkLayerForChecksum(&ip6) + err, raw = packets.Serialize(eth, &ip6, udp, dns) + } else { + mod.Error("invalid protocol for dns proxy.") + } + if err != nil { + mod.Error("error serializing packet: %s.", err) + return } - mod.Info("sending spoofed DNS reply for %s %s to %s.", tui.Red(domain), tui.Dim(redir), tui.Bold(who)) - - var err error - var src, dst net.IP + mod.Debug("sending %d bytes of packet ...", len(raw)) + if err := mod.Session.Queue.Send(raw); err != nil { + mod.Error("error sending packet: %s", err) + } +} +func (mod *DNSSpoofer) generateGenericLayers(pkt gopacket.Packet, peth *layers.Ethernet, pudp *layers.UDP, target net.HardwareAddr)(eth *layers.Ethernet, src net.IP, dst net.IP, udp *layers.UDP, err error) { nlayer := pkt.NetworkLayer() if nlayer == nil { - mod.Debug("missing network layer skipping packet.") - return + return nil,nil,nil,nil,fmt.Errorf("missing network layer skipping packet.") } var eType layers.EthernetType - var ipv6 bool - if nlayer.LayerType() == layers.LayerTypeIPv4 { pip := pkt.Layer(layers.LayerTypeIPv4).(*layers.IPv4) src = pip.DstIP dst = pip.SrcIP - ipv6 = false eType = layers.EthernetTypeIPv4 } else { pip := pkt.Layer(layers.LayerTypeIPv6).(*layers.IPv6) src = pip.DstIP dst = pip.SrcIP - ipv6 = true eType = layers.EthernetTypeIPv6 } - eth := layers.Ethernet{ + eth = &layers.Ethernet{ SrcMAC: peth.DstMAC, DstMAC: target, EthernetType: eType, } + udp = &layers.UDP{ + SrcPort: pudp.DstPort, + DstPort: pudp.SrcPort, + } + + return eth,src,dst,udp,nil +} + +func (mod *DNSSpoofer) dnsNXDomain(pkt gopacket.Packet, peth *layers.Ethernet, pudp *layers.UDP, req *layers.DNS, target net.HardwareAddr) { + + eth, src, dst, udp, err := mod.generateGenericLayers(pkt, peth, pudp, target) + if err != nil { + mod.Debug("%s",err) + } + + dns := &layers.DNS{ + ID: req.ID, + QR: true, + OpCode: layers.DNSOpCodeQuery, + ResponseCode: layers.DNSResponseCodeNXDomain, + QDCount: req.QDCount, + Questions: req.Questions, + } + + mod.dnsSend(eth, src, dst, udp, dns) +} + +func (mod *DNSSpoofer) dnsReply(pkt gopacket.Packet, peth *layers.Ethernet, pudp *layers.UDP, domain string, address net.IP, req *layers.DNS, target net.HardwareAddr) { redir := fmt.Sprintf("(->%s)", address.String()) + who := target.String() + + if t, found := mod.Session.Lan.Get(target.String()); found { + who = t.String() + } + + eth, src, dst, udp, err := mod.generateGenericLayers(pkt, peth, pudp, target) + if err != nil { + mod.Debug("%s",err) + } + + mod.Info("sending spoofed DNS reply for %s %s to %s.", tui.Red(domain), tui.Dim(redir), tui.Bold(who)) + answers := make([]layers.DNSResourceRecord, 0) for _, q := range req.Questions { answers = append(answers, @@ -187,7 +326,7 @@ func (mod *DNSSpoofer) dnsReply(pkt gopacket.Packet, peth *layers.Ethernet, pudp }) } - dns := layers.DNS{ + dns := &layers.DNS{ ID: req.ID, QR: true, OpCode: layers.DNSOpCodeQuery, @@ -196,56 +335,10 @@ func (mod *DNSSpoofer) dnsReply(pkt gopacket.Packet, peth *layers.Ethernet, pudp Answers: answers, } - var raw []byte + mod.dnsSend(eth, src, dst, udp, dns) +} - if ipv6 { - ip6 := layers.IPv6{ - Version: 6, - NextHeader: layers.IPProtocolUDP, - HopLimit: 64, - SrcIP: src, - DstIP: dst, - } - - udp := layers.UDP{ - SrcPort: pudp.DstPort, - DstPort: pudp.SrcPort, - } - - udp.SetNetworkLayerForChecksum(&ip6) - - err, raw = packets.Serialize(ð, &ip6, &udp, &dns) - if err != nil { - mod.Error("error serializing packet: %s.", err) - return - } - } else { - ip4 := layers.IPv4{ - Protocol: layers.IPProtocolUDP, - Version: 4, - TTL: 64, - SrcIP: src, - DstIP: dst, - } - - udp := layers.UDP{ - SrcPort: pudp.DstPort, - DstPort: pudp.SrcPort, - } - - udp.SetNetworkLayerForChecksum(&ip4) - - err, raw = packets.Serialize(ð, &ip4, &udp, &dns) - if err != nil { - mod.Error("error serializing packet: %s.", err) - return - } - } - - mod.Debug("sending %d bytes of packet ...", len(raw)) - if err := mod.Session.Queue.Send(raw); err != nil { - mod.Error("error sending packet: %s", err) - } +func (mod *DNSSpoofer) proxyPacket() { } func (mod *DNSSpoofer) onPacket(pkt gopacket.Packet) { @@ -254,22 +347,77 @@ func (mod *DNSSpoofer) onPacket(pkt gopacket.Packet) { if typeEth == nil || typeUDP == nil { return } - eth := typeEth.(*layers.Ethernet) - if mod.All || bytes.Equal(eth.DstMAC, mod.Session.Interface.HW) { - dns, parsed := pkt.Layer(layers.LayerTypeDNS).(*layers.DNS) - if parsed && dns.OpCode == layers.DNSOpCodeQuery && len(dns.Questions) > 0 && len(dns.Answers) == 0 { - udp := typeUDP.(*layers.UDP) - for _, q := range dns.Questions { - qName := string(q.Name) - if address := mod.Hosts.Resolve(qName); address != nil { - mod.dnsReply(pkt, eth, udp, qName, address, dns, eth.SrcMAC) - break - } else { - mod.Debug("skipping domain %s", qName) - } - } - } + + var sip net.IP = nil + var dip net.IP = nil + srcip := "n/a" + destip := "n/a" + + if mod.ProxyDNS { + if eth.NextLayerType() == layers.LayerTypeIPv4 { + pip := pkt.Layer(layers.LayerTypeIPv4).(*layers.IPv4) + sip = pip.SrcIP + dip = pip.DstIP + } else if eth.NextLayerType() == layers.LayerTypeIPv6 { + pip := pkt.Layer(layers.LayerTypeIPv6).(*layers.IPv6) + sip = pip.DstIP + dip = pip.SrcIP + } else { + mod.Debug("skip invalid ip protocol: %s",eth.NextLayerType()) + return + } + if sip != nil { + srcip = sip.String() + } + if dip != nil { + destip = dip.String() + } + if sip != nil && sip.Equal(mod.ProxyDNSIP) && dip != nil && mod.Session.Interface.IP.Equal(dip) { + mod.Debug("dns.spoof.proxyDNS skipping dns proxy-answer (from server) %s => %s", srcip, destip) + return + } + if sip != nil && mod.Session.Interface.IP.Equal(sip) && dip != nil && dip.Equal(mod.ProxyDNSIP) { + mod.Debug("dns.spoof.proxyDNS skipping dns proxy-request (to server) %s => %s", srcip, destip) + return + } + if sip != nil && mod.Session.Interface.IP.Equal(sip) { + mod.Debug("dns.spoof.proxyDNS skipping dns proxy-answer (to client) %s => %s", srcip, destip) + return + } + } else if !mod.All && !bytes.Equal(eth.DstMAC, mod.Session.Interface.HW) { + mod.Debug("!dns.spoof.All => deny non-local") + return + } + + dns, parsed := pkt.Layer(layers.LayerTypeDNS).(*layers.DNS) + if !parsed || dns.OpCode != layers.DNSOpCodeQuery || len(dns.Questions) == 0 || len(dns.Answers) > 0 { + mod.Debug("blackholing dns answer: %s => %s", srcip, destip) + return + } + + udp := typeUDP.(*layers.UDP) + for _, q := range dns.Questions { + qName := string(q.Name) + if address := mod.Hosts.Resolve(qName); address != nil { + mod.Debug("replying dns query by host lookup %s: %s => %s", qName, srcip, destip ) + mod.dnsReply(pkt, eth, udp, qName, address, dns, eth.SrcMAC) + break + } else if mod.ProxyDNS { + mod.Debug("proxying dns query %s: %s => %s", qName, srcip, destip ) + addr, err := net.LookupHost(qName) + if err != nil || len(addr) == 0 { + mod.Debug("replying dns query by nxdomain %s: %s => %s", qName, srcip, destip ) + mod.dnsNXDomain(pkt, eth, udp, dns, eth.SrcMAC) + break + } else { + mod.Debug("replying dns query by proxy lookup %s: %s => %s", qName, srcip, destip ) + mod.dnsReply(pkt, eth, udp, qName, net.ParseIP(addr[0]), dns, eth.SrcMAC) + break + } + } else { + mod.Debug("skipping domain %s", string(q.Name)) + } } }