mirror of
https://github.com/bettercap/bettercap
synced 2025-07-05 20:42:09 -07:00
init dns.proxy
This commit is contained in:
parent
e190737c91
commit
a49d561dce
6 changed files with 736 additions and 0 deletions
124
modules/dns_proxy/dns_proxy.go
Normal file
124
modules/dns_proxy/dns_proxy.go
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
package dns_proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bettercap/bettercap/v2/session"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DnsProxy struct {
|
||||||
|
session.SessionModule
|
||||||
|
proxy *DNSProxy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mod *DnsProxy) Author() string {
|
||||||
|
return "Yarwin Kolff <@buffermet>"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mod *DnsProxy) Configure() error {
|
||||||
|
var err error
|
||||||
|
var address string
|
||||||
|
var dnsPort int
|
||||||
|
var doRedirect bool
|
||||||
|
var nameserver string
|
||||||
|
var netProtocol string
|
||||||
|
var proxyPort int
|
||||||
|
var scriptPath string
|
||||||
|
|
||||||
|
if mod.Running() {
|
||||||
|
return session.ErrAlreadyStarted(mod.Name())
|
||||||
|
} else if err, dnsPort = mod.IntParam("dns.port"); err != nil {
|
||||||
|
return err
|
||||||
|
} else if err, address = mod.StringParam("dns.proxy.address"); err != nil {
|
||||||
|
return err
|
||||||
|
} else if err, nameserver = mod.StringParam("dns.proxy.nameserver"); err != nil {
|
||||||
|
return err
|
||||||
|
} else if err, netProtocol = mod.StringParam("dns.proxy.networkprotocol"); err != nil {
|
||||||
|
return err
|
||||||
|
} else if err, proxyPort = mod.IntParam("dns.proxy.port"); err != nil {
|
||||||
|
return err
|
||||||
|
} else if err, doRedirect = mod.BoolParam("dns.proxy.redirect"); err != nil {
|
||||||
|
return err
|
||||||
|
} else if err, scriptPath = mod.StringParam("dns.proxy.script"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
error := mod.proxy.Configure(address, dnsPort, doRedirect, nameserver, netProtocol, proxyPort, scriptPath)
|
||||||
|
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mod *DnsProxy) Description() string {
|
||||||
|
return "A full featured DNS proxy that can be used to manipulate DNS traffic."
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mod *DnsProxy) Name() string {
|
||||||
|
return "dns.proxy"
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDnsProxy(s *session.Session) *DnsProxy {
|
||||||
|
mod := &DnsProxy{
|
||||||
|
SessionModule: session.NewSessionModule("dns.proxy", s),
|
||||||
|
proxy: NewDNSProxy(s, "dns.proxy"),
|
||||||
|
}
|
||||||
|
|
||||||
|
mod.AddParam(session.NewIntParameter("dns.port",
|
||||||
|
"53",
|
||||||
|
"DNS port to redirect when the proxy is activated."))
|
||||||
|
|
||||||
|
mod.AddParam(session.NewStringParameter("dns.proxy.address",
|
||||||
|
session.ParamIfaceAddress,
|
||||||
|
session.IPv4Validator,
|
||||||
|
"Address to bind the DNS proxy to."))
|
||||||
|
|
||||||
|
mod.AddParam(session.NewStringParameter("dns.proxy.nameserver",
|
||||||
|
"1.1.1.1",
|
||||||
|
session.IPv4Validator,
|
||||||
|
"DNS resolver address."))
|
||||||
|
|
||||||
|
mod.AddParam(session.NewStringParameter("dns.proxy.networkprotocol",
|
||||||
|
"udp",
|
||||||
|
"^(udp|tcp|tcp-tls)$",
|
||||||
|
"Network protocol for the DNS proxy server to use. Accepted values: udp, tcp, tcp-tls"))
|
||||||
|
|
||||||
|
mod.AddParam(session.NewIntParameter("dns.proxy.port",
|
||||||
|
"8053",
|
||||||
|
"Port to bind the DNS proxy to."))
|
||||||
|
|
||||||
|
mod.AddParam(session.NewBoolParameter("dns.proxy.redirect",
|
||||||
|
"true",
|
||||||
|
"Enable or disable port redirection with iptables."))
|
||||||
|
|
||||||
|
mod.AddParam(session.NewStringParameter("dns.proxy.script",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"Path of a JS proxy script."))
|
||||||
|
|
||||||
|
mod.AddHandler(session.NewModuleHandler("dns.proxy on", "",
|
||||||
|
"Start the DNS proxy.",
|
||||||
|
func(args []string) error {
|
||||||
|
return mod.Start()
|
||||||
|
}))
|
||||||
|
|
||||||
|
mod.AddHandler(session.NewModuleHandler("dns.proxy off", "",
|
||||||
|
"Stop the DNS proxy.",
|
||||||
|
func(args []string) error {
|
||||||
|
return mod.Stop()
|
||||||
|
}))
|
||||||
|
|
||||||
|
return mod
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mod *DnsProxy) Start() error {
|
||||||
|
if err := mod.Configure(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mod.SetRunning(true, func() {
|
||||||
|
mod.proxy.Start()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mod *DnsProxy) Stop() error {
|
||||||
|
return mod.SetRunning(false, func() {
|
||||||
|
mod.proxy.Stop()
|
||||||
|
})
|
||||||
|
}
|
194
modules/dns_proxy/dns_proxy_base.go
Normal file
194
modules/dns_proxy/dns_proxy_base.go
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
package dns_proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bettercap/bettercap/v2/firewall"
|
||||||
|
"github.com/bettercap/bettercap/v2/session"
|
||||||
|
"github.com/evilsocket/islazy/log"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
dialTimeout = 2 * time.Second
|
||||||
|
readTimeout = 2 * time.Second
|
||||||
|
writeTimeout = 2 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
type DNSProxy struct {
|
||||||
|
Address string
|
||||||
|
Name string
|
||||||
|
Nameserver string
|
||||||
|
NetProtocol string
|
||||||
|
Redirection *firewall.Redirection
|
||||||
|
Script *DnsProxyScript
|
||||||
|
Server *dns.Server
|
||||||
|
Sess *session.Session
|
||||||
|
|
||||||
|
doRedirect bool
|
||||||
|
isRunning bool
|
||||||
|
tag string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DNSProxy) Configure(address string, dnsPort int, doRedirect bool, nameserver string, netProtocol string, proxyPort int, scriptPath string) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
p.Address = address
|
||||||
|
p.doRedirect = doRedirect
|
||||||
|
|
||||||
|
if scriptPath != "" {
|
||||||
|
if err, p.Script = LoadDnsProxyScript(scriptPath, p.Sess); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
p.Debug("proxy script %s loaded.", scriptPath)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.Script = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsClient := dns.Client{
|
||||||
|
DialTimeout: dialTimeout,
|
||||||
|
Net: netProtocol,
|
||||||
|
ReadTimeout: readTimeout,
|
||||||
|
WriteTimeout: writeTimeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
resolverAddr := fmt.Sprintf("%s:%d", nameserver, dnsPort)
|
||||||
|
|
||||||
|
handler := dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
|
||||||
|
m := new(dns.Msg)
|
||||||
|
m.SetReply(req)
|
||||||
|
|
||||||
|
clientIP := strings.Split(w.RemoteAddr().String(), ":")[0]
|
||||||
|
|
||||||
|
req, res := p.onRequestFilter(req, clientIP)
|
||||||
|
if res == nil {
|
||||||
|
// unused var is time til res
|
||||||
|
res, _, err := dnsClient.Exchange(req, resolverAddr)
|
||||||
|
if err != nil {
|
||||||
|
p.Debug("error while resolving DNS query: %s", err.Error())
|
||||||
|
m.SetRcode(req, dns.RcodeServerFailure)
|
||||||
|
w.WriteMsg(m)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res = p.onResponseFilter(req, res, clientIP)
|
||||||
|
if res == nil {
|
||||||
|
p.Error("response filter returned nil")
|
||||||
|
m.SetRcode(req, dns.RcodeServerFailure)
|
||||||
|
w.WriteMsg(m)
|
||||||
|
} else {
|
||||||
|
if err := w.WriteMsg(res); err != nil {
|
||||||
|
p.Error("Error writing response: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := w.WriteMsg(res); err != nil {
|
||||||
|
p.Error("Error writing response: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
p.Server = &dns.Server{
|
||||||
|
Addr: fmt.Sprintf("%s:%d", address, proxyPort),
|
||||||
|
Net: netProtocol,
|
||||||
|
Handler: handler,
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.doRedirect {
|
||||||
|
if !p.Sess.Firewall.IsForwardingEnabled() {
|
||||||
|
p.Info("enabling forwarding.")
|
||||||
|
p.Sess.Firewall.EnableForwarding(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Redirection = firewall.NewRedirection(p.Sess.Interface.Name(),
|
||||||
|
netProtocol,
|
||||||
|
dnsPort,
|
||||||
|
p.Address,
|
||||||
|
proxyPort)
|
||||||
|
|
||||||
|
if err := p.Sess.Firewall.EnableRedirection(p.Redirection, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Debug("applied redirection %s", p.Redirection.String())
|
||||||
|
} else {
|
||||||
|
p.Warning("port redirection disabled, the proxy must be set manually to work")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Sess.UnkCmdCallback = func(cmd string) bool {
|
||||||
|
if p.Script != nil {
|
||||||
|
return p.Script.OnCommand(cmd)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DNSProxy) dnsWorker() error {
|
||||||
|
p.isRunning = true
|
||||||
|
return p.Server.ListenAndServe()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DNSProxy) Debug(format string, args ...interface{}) {
|
||||||
|
p.Sess.Events.Log(log.DEBUG, p.tag+format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DNSProxy) Info(format string, args ...interface{}) {
|
||||||
|
p.Sess.Events.Log(log.INFO, p.tag+format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DNSProxy) Warning(format string, args ...interface{}) {
|
||||||
|
p.Sess.Events.Log(log.WARNING, p.tag+format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DNSProxy) Error(format string, args ...interface{}) {
|
||||||
|
p.Sess.Events.Log(log.ERROR, p.tag+format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DNSProxy) Fatal(format string, args ...interface{}) {
|
||||||
|
p.Sess.Events.Log(log.FATAL, p.tag+format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDNSProxy(s *session.Session, tag string) *DNSProxy {
|
||||||
|
p := &DNSProxy{
|
||||||
|
Name: "dns.proxy",
|
||||||
|
Sess: s,
|
||||||
|
Server: nil,
|
||||||
|
doRedirect: true,
|
||||||
|
tag: session.AsTag(tag),
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DNSProxy) Start() {
|
||||||
|
go func() {
|
||||||
|
p.Info("started on %s", p.Server.Addr)
|
||||||
|
|
||||||
|
err := p.dnsWorker()
|
||||||
|
// TODO: check the dns server closed error
|
||||||
|
if err != nil && err.Error() != "dns: Server closed" {
|
||||||
|
p.Fatal("%s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DNSProxy) Stop() error {
|
||||||
|
if p.doRedirect && p.Redirection != nil {
|
||||||
|
p.Debug("disabling redirection %s", p.Redirection.String())
|
||||||
|
if err := p.Sess.Firewall.EnableRedirection(p.Redirection, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Redirection = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Sess.UnkCmdCallback = nil
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
return p.Server.ShutdownContext(ctx)
|
||||||
|
}
|
86
modules/dns_proxy/dns_proxy_base_filters.go
Normal file
86
modules/dns_proxy/dns_proxy_base_filters.go
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
package dns_proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func shortenResourceRecords(records []string) []string {
|
||||||
|
shorterRecords := []string{}
|
||||||
|
for _, record := range records {
|
||||||
|
shorterRecord := strings.ReplaceAll(record, "\t", " ")
|
||||||
|
shorterRecords = append(shorterRecords, shorterRecord)
|
||||||
|
}
|
||||||
|
return shorterRecords
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DNSProxy) logRequestAction(j *JSQuery) {
|
||||||
|
p.Sess.Events.Add(p.Name+".spoofed-request", struct {
|
||||||
|
Client string
|
||||||
|
Questions []string
|
||||||
|
}{
|
||||||
|
j.Client["IP"],
|
||||||
|
shortenResourceRecords(j.Questions),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DNSProxy) logResponseAction(j *JSQuery) {
|
||||||
|
p.Sess.Events.Add(p.Name+".spoofed-response", struct {
|
||||||
|
client string
|
||||||
|
Extras []string
|
||||||
|
Answers []string
|
||||||
|
Nameservers []string
|
||||||
|
Questions []string
|
||||||
|
}{
|
||||||
|
j.Client["IP"],
|
||||||
|
shortenResourceRecords(j.Extras),
|
||||||
|
shortenResourceRecords(j.Answers),
|
||||||
|
shortenResourceRecords(j.Nameservers),
|
||||||
|
shortenResourceRecords(j.Questions),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DNSProxy) onRequestFilter(query *dns.Msg, clientIP string) (req, res *dns.Msg) {
|
||||||
|
p.Debug("< %s %s", clientIP, query.Question)
|
||||||
|
|
||||||
|
// do we have a proxy script?
|
||||||
|
if p.Script == nil {
|
||||||
|
return query, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// run the module OnRequest callback if defined
|
||||||
|
jsreq, jsres := p.Script.OnRequest(query, clientIP)
|
||||||
|
if jsreq != nil {
|
||||||
|
// the request has been changed by the script
|
||||||
|
p.logRequestAction(jsreq)
|
||||||
|
return jsreq.ToQuery(), nil
|
||||||
|
} else if jsres != nil {
|
||||||
|
// a fake response has been returned by the script
|
||||||
|
p.logResponseAction(jsres)
|
||||||
|
return query, jsres.ToQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
return query, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DNSProxy) onResponseFilter(req, res *dns.Msg, clientIP string) *dns.Msg {
|
||||||
|
// sometimes it happens ¯\_(ツ)_/¯
|
||||||
|
if res == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Debug("> %s %s", clientIP, res.Answer)
|
||||||
|
|
||||||
|
// do we have a proxy script?
|
||||||
|
if p.Script != nil {
|
||||||
|
_, jsres := p.Script.OnResponse(req, res, clientIP)
|
||||||
|
if jsres != nil {
|
||||||
|
// the response has been changed by the script
|
||||||
|
p.logResponseAction(jsres)
|
||||||
|
return jsres.ToQuery()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
231
modules/dns_proxy/dns_proxy_js_query.go
Normal file
231
modules/dns_proxy/dns_proxy_js_query.go
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
package dns_proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bettercap/bettercap/v2/log"
|
||||||
|
"github.com/bettercap/bettercap/v2/session"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
var whiteSpaceRegexp = regexp.MustCompile(`\s+`)
|
||||||
|
var stripWhiteSpaceRegexp = regexp.MustCompile(`^\s*(.*?)\s*$`)
|
||||||
|
|
||||||
|
type JSQuery struct {
|
||||||
|
Answers []string
|
||||||
|
Client map[string]string
|
||||||
|
Compress bool `json:"-"`
|
||||||
|
Extras []string
|
||||||
|
Header *JSQueryHeader
|
||||||
|
Nameservers []string
|
||||||
|
Questions []string
|
||||||
|
|
||||||
|
refHash string
|
||||||
|
}
|
||||||
|
|
||||||
|
type JSQueryHeader struct {
|
||||||
|
AuthenticatedData bool
|
||||||
|
Authoritative bool
|
||||||
|
CheckingDisabled bool
|
||||||
|
Id uint16
|
||||||
|
Opcode int
|
||||||
|
Rcode int
|
||||||
|
RecursionAvailable bool
|
||||||
|
RecursionDesired bool
|
||||||
|
Response bool
|
||||||
|
Truncated bool
|
||||||
|
Zero bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JSQuery) NewHash() string {
|
||||||
|
headerHash := fmt.Sprintf("%t.%t.%t.%d.%d.%d.%t.%t.%t.%t.%t",
|
||||||
|
j.Header.AuthenticatedData,
|
||||||
|
j.Header.Authoritative,
|
||||||
|
j.Header.CheckingDisabled,
|
||||||
|
j.Header.Id,
|
||||||
|
j.Header.Opcode,
|
||||||
|
j.Header.Rcode,
|
||||||
|
j.Header.RecursionAvailable,
|
||||||
|
j.Header.RecursionDesired,
|
||||||
|
j.Header.Response,
|
||||||
|
j.Header.Truncated,
|
||||||
|
j.Header.Zero)
|
||||||
|
hash := fmt.Sprintf("%s.%s.%t.%s.%s.%s.%s",
|
||||||
|
strings.Join(j.Answers, ""),
|
||||||
|
j.Client["IP"],
|
||||||
|
j.Compress,
|
||||||
|
strings.Join(j.Extras, ""),
|
||||||
|
headerHash,
|
||||||
|
strings.Join(j.Nameservers, ""),
|
||||||
|
strings.Join(j.Questions, ""))
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewJSQuery(query *dns.Msg, clientIP string) *JSQuery {
|
||||||
|
answers := []string{}
|
||||||
|
extras := []string{}
|
||||||
|
nameservers := []string{}
|
||||||
|
questions := []string{}
|
||||||
|
|
||||||
|
header := &JSQueryHeader{
|
||||||
|
AuthenticatedData: query.MsgHdr.AuthenticatedData,
|
||||||
|
Authoritative: query.MsgHdr.Authoritative,
|
||||||
|
CheckingDisabled: query.MsgHdr.CheckingDisabled,
|
||||||
|
Id: query.MsgHdr.Id,
|
||||||
|
Opcode: query.MsgHdr.Opcode,
|
||||||
|
Rcode: query.MsgHdr.Rcode,
|
||||||
|
RecursionAvailable: query.MsgHdr.RecursionAvailable,
|
||||||
|
RecursionDesired: query.MsgHdr.RecursionDesired,
|
||||||
|
Response: query.MsgHdr.Response,
|
||||||
|
Truncated: query.MsgHdr.Truncated,
|
||||||
|
Zero: query.MsgHdr.Zero,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rr := range query.Answer {
|
||||||
|
answers = append(answers, rr.String())
|
||||||
|
}
|
||||||
|
for _, rr := range query.Extra {
|
||||||
|
extras = append(extras, rr.String())
|
||||||
|
}
|
||||||
|
for _, rr := range query.Ns {
|
||||||
|
nameservers = append(nameservers, rr.String())
|
||||||
|
}
|
||||||
|
for _, q := range query.Question {
|
||||||
|
qType := dns.Type(q.Qtype).String()
|
||||||
|
qClass := dns.Class(q.Qclass).String()
|
||||||
|
questions = append(questions, fmt.Sprintf("%s\t%s\t%s", q.Name, qClass, qType))
|
||||||
|
}
|
||||||
|
|
||||||
|
clientMAC := ""
|
||||||
|
clientAlias := ""
|
||||||
|
if endpoint := session.I.Lan.GetByIp(clientIP); endpoint != nil {
|
||||||
|
clientMAC = endpoint.HwAddress
|
||||||
|
clientAlias = endpoint.Alias
|
||||||
|
}
|
||||||
|
client := map[string]string{"IP": clientIP, "MAC": clientMAC, "Alias": clientAlias}
|
||||||
|
|
||||||
|
jsquery := &JSQuery{
|
||||||
|
Answers: answers,
|
||||||
|
Client: client,
|
||||||
|
Compress: query.Compress,
|
||||||
|
Extras: extras,
|
||||||
|
Header: header,
|
||||||
|
Nameservers: nameservers,
|
||||||
|
Questions: questions,
|
||||||
|
}
|
||||||
|
jsquery.UpdateHash()
|
||||||
|
|
||||||
|
return jsquery
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringToClass(s string) (uint16, error) {
|
||||||
|
for i, dnsClass := range dns.ClassToString {
|
||||||
|
if s == dnsClass {
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("unkown DNS class (got %s)", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringToType(s string) (uint16, error) {
|
||||||
|
for i, dnsType := range dns.TypeToString {
|
||||||
|
if s == dnsType {
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("unkown DNS type (got %s)", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JSQuery) ToQuery() *dns.Msg {
|
||||||
|
var answers []dns.RR
|
||||||
|
var extras []dns.RR
|
||||||
|
var nameservers []dns.RR
|
||||||
|
var questions []dns.Question
|
||||||
|
|
||||||
|
for _, s := range j.Answers {
|
||||||
|
rr, err := dns.NewRR(s)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("error parsing DNS answer resource record: %s", err.Error())
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
answers = append(answers, rr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, s := range j.Extras {
|
||||||
|
rr, err := dns.NewRR(s)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("error parsing DNS extra resource record: %s", err.Error())
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
extras = append(extras, rr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, s := range j.Nameservers {
|
||||||
|
rr, err := dns.NewRR(s)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("error parsing DNS nameserver resource record: %s", err.Error())
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
nameservers = append(nameservers, rr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range j.Questions {
|
||||||
|
qStripped := stripWhiteSpaceRegexp.FindStringSubmatch(s)
|
||||||
|
qParts := whiteSpaceRegexp.Split(qStripped[1], -1)
|
||||||
|
|
||||||
|
if len(qParts) != 3 {
|
||||||
|
log.Error("invalid DNS question format: (got %s)", s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
qName := dns.Fqdn(qParts[0])
|
||||||
|
qClass, err := stringToClass(qParts[1])
|
||||||
|
if err != nil {
|
||||||
|
log.Error("error parsing DNS question class: %s", err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
qType, err := stringToType(qParts[2])
|
||||||
|
if err != nil {
|
||||||
|
log.Error("error parsing DNS question type: %s", err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
questions = append(questions, dns.Question{qName, qType, qClass})
|
||||||
|
}
|
||||||
|
|
||||||
|
query := &dns.Msg{
|
||||||
|
MsgHdr: dns.MsgHdr{
|
||||||
|
Id: j.Header.Id,
|
||||||
|
Response: j.Header.Response,
|
||||||
|
Opcode: j.Header.Opcode,
|
||||||
|
Authoritative: j.Header.Authoritative,
|
||||||
|
Truncated: j.Header.Truncated,
|
||||||
|
RecursionDesired: j.Header.RecursionDesired,
|
||||||
|
RecursionAvailable: j.Header.RecursionAvailable,
|
||||||
|
Zero: j.Header.Zero,
|
||||||
|
AuthenticatedData: j.Header.AuthenticatedData,
|
||||||
|
CheckingDisabled: j.Header.CheckingDisabled,
|
||||||
|
Rcode: j.Header.Rcode,
|
||||||
|
},
|
||||||
|
Compress: j.Compress,
|
||||||
|
Question: questions,
|
||||||
|
Answer: answers,
|
||||||
|
Ns: nameservers,
|
||||||
|
Extra: extras,
|
||||||
|
}
|
||||||
|
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JSQuery) UpdateHash() {
|
||||||
|
j.refHash = j.NewHash()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JSQuery) WasModified() bool {
|
||||||
|
// check if any of the fields has been changed
|
||||||
|
return j.NewHash() != j.refHash
|
||||||
|
}
|
99
modules/dns_proxy/dns_proxy_script.go
Normal file
99
modules/dns_proxy/dns_proxy_script.go
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
package dns_proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bettercap/bettercap/v2/log"
|
||||||
|
"github.com/bettercap/bettercap/v2/session"
|
||||||
|
"github.com/evilsocket/islazy/plugin"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
|
||||||
|
"github.com/robertkrimen/otto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DnsProxyScript struct {
|
||||||
|
*plugin.Plugin
|
||||||
|
|
||||||
|
doOnRequest bool
|
||||||
|
doOnResponse bool
|
||||||
|
doOnCommand bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadDnsProxyScript(path string, sess *session.Session) (err error, s *DnsProxyScript) {
|
||||||
|
log.Debug("loading proxy script %s ...", path)
|
||||||
|
|
||||||
|
plug, err := plugin.Load(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// define session pointer
|
||||||
|
if err = plug.Set("env", sess.Env.Data); err != nil {
|
||||||
|
log.Error("Error while defining environment: %+v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// run onLoad if defined
|
||||||
|
if plug.HasFunc("onLoad") {
|
||||||
|
if _, err = plug.Call("onLoad"); err != nil {
|
||||||
|
log.Error("Error while executing onLoad callback: %s", "\nTraceback:\n "+err.(*otto.Error).String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s = &DnsProxyScript{
|
||||||
|
Plugin: plug,
|
||||||
|
doOnRequest: plug.HasFunc("onRequest"),
|
||||||
|
doOnResponse: plug.HasFunc("onResponse"),
|
||||||
|
doOnCommand: plug.HasFunc("onCommand"),
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DnsProxyScript) OnRequest(req *dns.Msg, clientIP string) (jsreq, jsres *JSQuery) {
|
||||||
|
if s.doOnRequest {
|
||||||
|
jsreq := NewJSQuery(req, clientIP)
|
||||||
|
jsres := NewJSQuery(req, clientIP)
|
||||||
|
|
||||||
|
if _, err := s.Call("onRequest", jsreq, jsres); err != nil {
|
||||||
|
log.Error("%s", err)
|
||||||
|
return nil, nil
|
||||||
|
} else if jsreq.WasModified() {
|
||||||
|
jsreq.UpdateHash()
|
||||||
|
return jsreq, nil
|
||||||
|
} else if jsres.WasModified() {
|
||||||
|
jsres.UpdateHash()
|
||||||
|
return nil, jsres
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DnsProxyScript) OnResponse(req, res *dns.Msg, clientIP string) (jsreq, jsres *JSQuery) {
|
||||||
|
if s.doOnResponse {
|
||||||
|
jsreq := NewJSQuery(req, clientIP)
|
||||||
|
jsres := NewJSQuery(res, clientIP)
|
||||||
|
|
||||||
|
if _, err := s.Call("onResponse", jsreq, jsres); err != nil {
|
||||||
|
log.Error("%s", err)
|
||||||
|
return nil, nil
|
||||||
|
} else if jsres.WasModified() {
|
||||||
|
jsres.UpdateHash()
|
||||||
|
return nil, jsres
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DnsProxyScript) OnCommand(cmd string) bool {
|
||||||
|
if s.doOnCommand {
|
||||||
|
if ret, err := s.Call("onCommand", cmd); err != nil {
|
||||||
|
log.Error("Error while executing onCommand callback: %+v", err)
|
||||||
|
return false
|
||||||
|
} else if v, ok := ret.(bool); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/bettercap/bettercap/v2/modules/can"
|
"github.com/bettercap/bettercap/v2/modules/can"
|
||||||
"github.com/bettercap/bettercap/v2/modules/caplets"
|
"github.com/bettercap/bettercap/v2/modules/caplets"
|
||||||
"github.com/bettercap/bettercap/v2/modules/dhcp6_spoof"
|
"github.com/bettercap/bettercap/v2/modules/dhcp6_spoof"
|
||||||
|
"github.com/bettercap/bettercap/v2/modules/dns_proxy"
|
||||||
"github.com/bettercap/bettercap/v2/modules/dns_spoof"
|
"github.com/bettercap/bettercap/v2/modules/dns_spoof"
|
||||||
"github.com/bettercap/bettercap/v2/modules/events_stream"
|
"github.com/bettercap/bettercap/v2/modules/events_stream"
|
||||||
"github.com/bettercap/bettercap/v2/modules/gps"
|
"github.com/bettercap/bettercap/v2/modules/gps"
|
||||||
|
@ -45,6 +46,7 @@ func LoadModules(sess *session.Session) {
|
||||||
sess.Register(can.NewCanModule(sess))
|
sess.Register(can.NewCanModule(sess))
|
||||||
sess.Register(dhcp6_spoof.NewDHCP6Spoofer(sess))
|
sess.Register(dhcp6_spoof.NewDHCP6Spoofer(sess))
|
||||||
sess.Register(net_recon.NewDiscovery(sess))
|
sess.Register(net_recon.NewDiscovery(sess))
|
||||||
|
sess.Register(dns_proxy.NewDnsProxy(sess))
|
||||||
sess.Register(dns_spoof.NewDNSSpoofer(sess))
|
sess.Register(dns_spoof.NewDNSSpoofer(sess))
|
||||||
sess.Register(events_stream.NewEventsStream(sess))
|
sess.Register(events_stream.NewEventsStream(sess))
|
||||||
sess.Register(gps.NewGPS(sess))
|
sess.Register(gps.NewGPS(sess))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue