new: dns.spoof now supports a hosts file with multiple mappings (closes #344)

This commit is contained in:
evilsocket 2018-09-20 14:42:10 +02:00
parent 3ed4db132c
commit d16b0c7cf5
No known key found for this signature in database
GPG key ID: 1564D7F30393A456
4 changed files with 131 additions and 43 deletions

View file

@ -14,15 +14,12 @@ import (
"github.com/google/gopacket" "github.com/google/gopacket"
"github.com/google/gopacket/layers" "github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap" "github.com/google/gopacket/pcap"
"github.com/gobwas/glob"
) )
type DNSSpoofer struct { type DNSSpoofer struct {
session.SessionModule session.SessionModule
Handle *pcap.Handle Handle *pcap.Handle
Domains []glob.Glob Hosts Hosts
Address net.IP
All bool All bool
waitGroup *sync.WaitGroup waitGroup *sync.WaitGroup
pktSourceChan chan gopacket.Packet pktSourceChan chan gopacket.Packet
@ -33,13 +30,18 @@ func NewDNSSpoofer(s *session.Session) *DNSSpoofer {
SessionModule: session.NewSessionModule("dns.spoof", s), SessionModule: session.NewSessionModule("dns.spoof", s),
Handle: nil, Handle: nil,
All: false, All: false,
Domains: make([]glob.Glob, 0), Hosts: Hosts{},
waitGroup: &sync.WaitGroup{}, waitGroup: &sync.WaitGroup{},
} }
spoof.AddParam(session.NewStringParameter("dns.spoof.hosts",
"",
"",
"If not empty, this hosts file will be used to map domains to IP addresses."))
spoof.AddParam(session.NewStringParameter("dns.spoof.domains", spoof.AddParam(session.NewStringParameter("dns.spoof.domains",
"*", "",
``, "",
"Comma separated values of domain names to spoof.")) "Comma separated values of domain names to spoof."))
spoof.AddParam(session.NewStringParameter("dns.spoof.address", spoof.AddParam(session.NewStringParameter("dns.spoof.address",
@ -80,43 +82,46 @@ func (s DNSSpoofer) Author() string {
func (s *DNSSpoofer) Configure() error { func (s *DNSSpoofer) Configure() error {
var err error var err error
var addr string var hostsFile string
var domains []string var domains []string
var address net.IP
if s.Running() { if s.Running() {
return session.ErrAlreadyStarted return session.ErrAlreadyStarted
} } else if s.Handle, err = pcap.OpenLive(s.Session.Interface.Name(), 65536, true, pcap.BlockForever); err != nil {
if s.Handle, err = pcap.OpenLive(s.Session.Interface.Name(), 65536, true, pcap.BlockForever); err != nil {
return err return err
} } else if err = s.Handle.SetBPFFilter("udp"); err != nil {
err = s.Handle.SetBPFFilter("udp")
if err != nil {
return err return err
} } else if err, s.All = s.BoolParam("dns.spoof.all"); err != nil {
if err, s.All = s.BoolParam("dns.spoof.all"); err != nil {
return err return err
} } else if err, address = s.IPParam("dns.spoof.address"); err != nil {
return err
if err, domains = s.ListParam("dns.spoof.domains"); err != nil { } else if err, domains = s.ListParam("dns.spoof.domains"); err != nil {
return err
} else if err, hostsFile = s.StringParam("dns.spoof.hosts"); err != nil {
return err return err
} }
for _, domain := range domains { for _, domain := range domains {
if expr, err := glob.Compile(domain); err != nil { s.Hosts = append(s.Hosts, NewHostEntry(domain, address))
return fmt.Errorf("'%s' is not a valid domain glob expression: %s", domain, err) }
if hostsFile != "" {
log.Info("loading hosts from file %s ...", hostsFile)
if err, hosts := HostsFromFile(hostsFile); err != nil {
return fmt.Errorf("error reading hosts from file %s: %v", hostsFile, err)
} else { } else {
s.Domains = append(s.Domains, expr) s.Hosts = append(s.Hosts, hosts...)
} }
} }
if err, addr = s.StringParam("dns.spoof.address"); err != nil { if len(s.Hosts) == 0 {
return err return fmt.Errorf("at least dns.spoof.hosts or dns.spoof.domains must be filled")
} }
s.Address = net.ParseIP(addr) for _, entry := range s.Hosts {
log.Info("[%s] %s -> %s", core.Green("dns.spoof"), entry.Host, entry.Address)
}
if !s.Session.Firewall.IsForwardingEnabled() { if !s.Session.Firewall.IsForwardingEnabled() {
log.Info("Enabling forwarding.") log.Info("Enabling forwarding.")
@ -126,8 +131,8 @@ func (s *DNSSpoofer) Configure() error {
return nil return nil
} }
func (s *DNSSpoofer) dnsReply(pkt gopacket.Packet, peth *layers.Ethernet, pudp *layers.UDP, domain string, req *layers.DNS, target net.HardwareAddr) { func (s *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)", s.Address) redir := fmt.Sprintf("(->%s)", address.String())
who := target.String() who := target.String()
if t, found := s.Session.Lan.Get(target.String()); found { if t, found := s.Session.Lan.Get(target.String()); found {
@ -177,7 +182,7 @@ func (s *DNSSpoofer) dnsReply(pkt gopacket.Packet, peth *layers.Ethernet, pudp *
Type: q.Type, Type: q.Type,
Class: q.Class, Class: q.Class,
TTL: 1024, TTL: 1024,
IP: s.Address, IP: address,
}) })
} }
@ -242,15 +247,6 @@ func (s *DNSSpoofer) dnsReply(pkt gopacket.Packet, peth *layers.Ethernet, pudp *
} }
} }
func (s *DNSSpoofer) shouldSpoof(domain string) bool {
for _, expr := range s.Domains {
if expr.Match(domain) {
return true
}
}
return false
}
func (s *DNSSpoofer) onPacket(pkt gopacket.Packet) { func (s *DNSSpoofer) onPacket(pkt gopacket.Packet) {
typeEth := pkt.Layer(layers.LayerTypeEthernet) typeEth := pkt.Layer(layers.LayerTypeEthernet)
typeUDP := pkt.Layer(layers.LayerTypeUDP) typeUDP := pkt.Layer(layers.LayerTypeUDP)
@ -265,8 +261,8 @@ func (s *DNSSpoofer) onPacket(pkt gopacket.Packet) {
udp := typeUDP.(*layers.UDP) udp := typeUDP.(*layers.UDP)
for _, q := range dns.Questions { for _, q := range dns.Questions {
qName := string(q.Name) qName := string(q.Name)
if s.shouldSpoof(qName) { if address := s.Hosts.Resolve(qName); address != nil {
s.dnsReply(pkt, eth, udp, qName, dns, eth.SrcMAC) s.dnsReply(pkt, eth, udp, qName, address, dns, eth.SrcMAC)
break break
} else { } else {
log.Debug("skipping domain %s", qName) log.Debug("skipping domain %s", qName)

View file

@ -0,0 +1,83 @@
package modules
import (
"bufio"
"fmt"
"net"
"os"
"regexp"
"strings"
"github.com/bettercap/bettercap/core"
"github.com/gobwas/glob"
)
var hostsSplitter = regexp.MustCompile(`\s+`)
type HostEntry struct {
Host string
Suffix string
Expr glob.Glob
Address net.IP
}
func (e HostEntry) Matches(host string) bool {
return e.Host == host || strings.HasSuffix(host, e.Suffix) || (e.Expr != nil && e.Expr.Match(host))
}
type Hosts []HostEntry
func NewHostEntry(host string, address net.IP) HostEntry {
entry := HostEntry{
Host: host,
Address: address,
}
if host[0] == '.' {
entry.Suffix = host
} else {
entry.Suffix = "." + host
}
if expr, err := glob.Compile(host); err == nil {
entry.Expr = expr
}
return entry
}
func HostsFromFile(filename string) (err error, entries []HostEntry) {
input, err := os.Open(filename)
if err != nil {
return
}
defer input.Close()
scanner := bufio.NewScanner(input)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
line := core.Trim(scanner.Text())
if line == "" || line[0] == '#' {
continue
}
if parts := hostsSplitter.Split(line, 2); len(parts) == 2 {
address := net.ParseIP(parts[0])
domain := parts[1]
entries = append(entries, NewHostEntry(domain, address))
} else {
return fmt.Errorf("'%s' invalid hosts line", line), nil
}
}
return
}
func (h Hosts) Resolve(host string) net.IP {
for _, entry := range h {
if entry.Matches(host) {
return entry.Address
}
}
return nil
}

View file

@ -62,8 +62,8 @@ func (p *EventPool) Listen() <-chan Event {
// make sure, without blocking, the new listener // make sure, without blocking, the new listener
// will receive all the queued events // will receive all the queued events
go func() { go func() {
for _, e := range p.events { for i := len(p.events) - 1; i >= 0; i-- {
l <- e l <- p.events[i]
} }
}() }()

View file

@ -2,6 +2,7 @@ package session
import ( import (
"fmt" "fmt"
"net"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -86,6 +87,14 @@ func (m SessionModule) StringParam(name string) (error, string) {
} }
} }
func (m SessionModule) IPParam(name string) (error, net.IP) {
if err, v := m.StringParam(name); err != nil {
return err, nil
} else {
return nil, net.ParseIP(v)
}
}
func (m SessionModule) IntParam(name string) (error, int) { func (m SessionModule) IntParam(name string) (error, int) {
if p, found := m.params[name]; found { if p, found := m.params[name]; found {
if err, v := p.Get(m.Session); err != nil { if err, v := p.Get(m.Session); err != nil {