mirror of
https://github.com/bettercap/bettercap
synced 2025-08-14 02:36:57 -07:00
misc: small fix or general refactoring i did not bother commenting
This commit is contained in:
parent
91d360327a
commit
51a5b4ad6e
17 changed files with 1375 additions and 239 deletions
109
modules/zerogod/zerogod_acceptor.go
Normal file
109
modules/zerogod/zerogod_acceptor.go
Normal file
|
@ -0,0 +1,109 @@
|
|||
package zerogod
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/evilsocket/islazy/tui"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
TLS bool
|
||||
Handle func(mod *ZeroGod, client net.Conn, srvHost string, srvPort int, srvTLS bool)
|
||||
}
|
||||
|
||||
// TODO: add more and possibly autodetect from peeking at the first bytes sent by the client
|
||||
var TCP_HANDLERS = map[string]Handler{
|
||||
"_ipp": {
|
||||
Handle: ippClientHandler,
|
||||
},
|
||||
"_ipps": {
|
||||
TLS: true,
|
||||
Handle: ippClientHandler,
|
||||
},
|
||||
// TODO: _http at least
|
||||
}
|
||||
|
||||
type Acceptor struct {
|
||||
mod *ZeroGod
|
||||
srvHost string
|
||||
port uint16
|
||||
service string
|
||||
tlsConfig *tls.Config
|
||||
listener net.Listener
|
||||
running bool
|
||||
context context.Context
|
||||
ctxCancel context.CancelFunc
|
||||
handler Handler
|
||||
}
|
||||
|
||||
func NewAcceptor(mod *ZeroGod, service string, srvHost string, port uint16, tlsConfig *tls.Config) *Acceptor {
|
||||
context, ctcCancel := context.WithCancel(context.Background())
|
||||
acceptor := Acceptor{
|
||||
mod: mod,
|
||||
port: port,
|
||||
service: service,
|
||||
context: context,
|
||||
ctxCancel: ctcCancel,
|
||||
srvHost: srvHost,
|
||||
}
|
||||
|
||||
for svcName, svcHandler := range TCP_HANDLERS {
|
||||
if strings.Contains(service, svcName) {
|
||||
acceptor.tlsConfig = tlsConfig
|
||||
acceptor.handler = svcHandler
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if acceptor.handler.Handle == nil {
|
||||
mod.Warning("no protocol handler found for service %s, using generic dump handler", tui.Yellow(service))
|
||||
acceptor.handler.Handle = handleGenericTCP
|
||||
} else {
|
||||
mod.Info("found %s protocol handler", tui.Green(service))
|
||||
}
|
||||
|
||||
return &acceptor
|
||||
}
|
||||
|
||||
func (a *Acceptor) Start() (err error) {
|
||||
var lc net.ListenConfig
|
||||
|
||||
if a.listener, err = lc.Listen(a.context, "tcp", fmt.Sprintf("0.0.0.0:%d", a.port)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if a.tlsConfig != nil {
|
||||
a.listener = tls.NewListener(a.listener, a.tlsConfig)
|
||||
}
|
||||
|
||||
a.running = true
|
||||
go func() {
|
||||
a.mod.Debug("tcp listener for port %d (%s) started", a.port, tui.Green(a.service))
|
||||
for a.running {
|
||||
if conn, err := a.listener.Accept(); err != nil {
|
||||
if a.running {
|
||||
a.mod.Error("%v", err)
|
||||
}
|
||||
} else {
|
||||
a.mod.Info("accepted connection for service %s (port %d): %v", tui.Green(a.service), a.port, conn.RemoteAddr())
|
||||
go a.handler.Handle(a.mod, conn, a.srvHost, int(a.port), a.tlsConfig != nil)
|
||||
}
|
||||
}
|
||||
a.mod.Debug("tcp listener for port %d (%s) stopped", a.port, tui.Green(a.service))
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Acceptor) Stop() {
|
||||
a.mod.Debug("stopping tcp listener for port %d", a.port)
|
||||
a.running = false
|
||||
a.ctxCancel()
|
||||
<-a.context.Done()
|
||||
a.listener.Close()
|
||||
a.mod.Debug("tcp listener for port %d stopped", a.port)
|
||||
}
|
197
modules/zerogod/zerogod_advertise.go
Normal file
197
modules/zerogod/zerogod_advertise.go
Normal file
|
@ -0,0 +1,197 @@
|
|||
package zerogod
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
tls_utils "github.com/bettercap/bettercap/v2/tls"
|
||||
"github.com/evilsocket/islazy/fs"
|
||||
"github.com/evilsocket/islazy/tui"
|
||||
"github.com/grandcat/zeroconf"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Advertiser struct {
|
||||
Filename string
|
||||
Mapping map[string]zeroconf.ServiceEntry
|
||||
Servers map[string]*zeroconf.Server
|
||||
Acceptors map[string]*Acceptor
|
||||
}
|
||||
|
||||
type setupResult struct {
|
||||
err error
|
||||
key string
|
||||
server *zeroconf.Server
|
||||
}
|
||||
|
||||
func (mod *ZeroGod) startAdvertiser(fileName string) error {
|
||||
if mod.advertiser != nil {
|
||||
return fmt.Errorf("advertiser already started for %s", mod.advertiser.Filename)
|
||||
}
|
||||
|
||||
var certFile string
|
||||
var keyFile string
|
||||
var err error
|
||||
|
||||
// read tls configuration
|
||||
if err, certFile = mod.StringParam("zerogod.advertise.certificate"); err != nil {
|
||||
return err
|
||||
} else if certFile, err = fs.Expand(certFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err, keyFile = mod.StringParam("zerogod.advertise.key"); err != nil {
|
||||
return err
|
||||
} else if keyFile, err = fs.Expand(keyFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !fs.Exists(certFile) || !fs.Exists(keyFile) {
|
||||
cfg, err := tls_utils.CertConfigFromModule("zerogod.advertise", mod.SessionModule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mod.Debug("%+v", cfg)
|
||||
mod.Info("generating server TLS key to %s", keyFile)
|
||||
mod.Info("generating server TLS certificate to %s", certFile)
|
||||
if err := tls_utils.Generate(cfg, certFile, keyFile, false); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
mod.Info("loading server TLS key from %s", keyFile)
|
||||
mod.Info("loading server TLS certificate from %s", certFile)
|
||||
}
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tlsConfig := tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read %s: %v", fileName, err)
|
||||
}
|
||||
|
||||
mapping := make(map[string]zeroconf.ServiceEntry)
|
||||
if err = yaml.Unmarshal(data, &mapping); err != nil {
|
||||
return fmt.Errorf("could not deserialize %s: %v", fileName, err)
|
||||
}
|
||||
|
||||
hostName, err := os.Hostname()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get hostname: %v", err)
|
||||
}
|
||||
if !strings.HasSuffix(hostName, ".") {
|
||||
hostName += "."
|
||||
}
|
||||
|
||||
mod.Info("loaded %d services from %s, advertising with host=%s iface=%s ipv4=%s ipv6=%s",
|
||||
len(mapping),
|
||||
fileName,
|
||||
hostName,
|
||||
mod.Session.Interface.Name(),
|
||||
mod.Session.Interface.IpAddress,
|
||||
mod.Session.Interface.Ip6Address)
|
||||
|
||||
advertiser := &Advertiser{
|
||||
Filename: fileName,
|
||||
Mapping: mapping,
|
||||
Servers: make(map[string]*zeroconf.Server),
|
||||
Acceptors: make(map[string]*Acceptor),
|
||||
}
|
||||
|
||||
svcChan := make(chan setupResult)
|
||||
|
||||
// paralleize initialization
|
||||
for key, svc := range mapping {
|
||||
go func(key string, svc zeroconf.ServiceEntry) {
|
||||
mod.Info("unregistering instance %s ...", tui.Yellow(fmt.Sprintf("%s.%s.%s", svc.Instance, svc.Service, svc.Domain)))
|
||||
|
||||
// create a first instance just to deregister it from the network
|
||||
server, err := zeroconf.Register(svc.Instance, svc.Service, svc.Domain, svc.Port, svc.Text, nil)
|
||||
if err != nil {
|
||||
svcChan <- setupResult{err: fmt.Errorf("could not create service %s: %v", svc.Instance, err)}
|
||||
return
|
||||
}
|
||||
server.Shutdown()
|
||||
|
||||
// give some time to the network to adjust
|
||||
time.Sleep(time.Duration(1) * time.Second)
|
||||
|
||||
// now create it again to actually advertise
|
||||
if server, err = zeroconf.Register(svc.Instance, svc.Service, svc.Domain, svc.Port, svc.Text, nil); err != nil {
|
||||
svcChan <- setupResult{err: fmt.Errorf("could not create service %s: %v", svc.Instance, err)}
|
||||
return
|
||||
}
|
||||
|
||||
mod.Info("advertising service %s", tui.Yellow(svc.Service))
|
||||
|
||||
svcChan <- setupResult{
|
||||
key: key,
|
||||
server: server,
|
||||
}
|
||||
}(key, svc)
|
||||
}
|
||||
|
||||
for res := range svcChan {
|
||||
if res.err != nil {
|
||||
return res.err
|
||||
}
|
||||
advertiser.Servers[res.key] = res.server
|
||||
if len(advertiser.Servers) == len(mapping) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// now create the tcp acceptors
|
||||
for key, svc := range mapping {
|
||||
acceptor := NewAcceptor(mod, key, hostName, uint16(svc.Port), &tlsConfig)
|
||||
if err := acceptor.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
advertiser.Acceptors[key] = acceptor
|
||||
}
|
||||
|
||||
mod.advertiser = advertiser
|
||||
|
||||
mod.Debug("%+v", *mod.advertiser)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mod *ZeroGod) stopAdvertiser() error {
|
||||
if mod.advertiser == nil {
|
||||
return errors.New("advertiser not started")
|
||||
}
|
||||
|
||||
mod.Info("stopping %d services ...", len(mod.advertiser.Mapping))
|
||||
|
||||
for key, server := range mod.advertiser.Servers {
|
||||
mod.Info("stopping %s ...", key)
|
||||
server.Shutdown()
|
||||
}
|
||||
|
||||
mod.Info("all services stopped")
|
||||
|
||||
mod.Info("stopping %d acceptors ...", len(mod.advertiser.Acceptors))
|
||||
|
||||
for _, acceptor := range mod.advertiser.Acceptors {
|
||||
acceptor.Stop()
|
||||
}
|
||||
|
||||
mod.Info("all acceptors stopped")
|
||||
|
||||
mod.advertiser = nil
|
||||
return nil
|
||||
}
|
260
modules/zerogod/zerogod_discovery.go
Normal file
260
modules/zerogod/zerogod_discovery.go
Normal file
|
@ -0,0 +1,260 @@
|
|||
package zerogod
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/network"
|
||||
"github.com/bettercap/bettercap/v2/session"
|
||||
"github.com/bettercap/bettercap/v2/tls"
|
||||
"github.com/evilsocket/islazy/tui"
|
||||
|
||||
"github.com/grandcat/zeroconf"
|
||||
)
|
||||
|
||||
type ZeroGod struct {
|
||||
session.SessionModule
|
||||
|
||||
advertiser *Advertiser
|
||||
rootContext context.Context
|
||||
rootCancel context.CancelFunc
|
||||
resolvers map[string]*zeroconf.Resolver
|
||||
mapping map[string]map[string]*zeroconf.ServiceEntry
|
||||
}
|
||||
|
||||
func NewZeroGod(s *session.Session) *ZeroGod {
|
||||
mod := &ZeroGod{
|
||||
SessionModule: session.NewSessionModule("zerogod", s),
|
||||
mapping: make(map[string]map[string]*zeroconf.ServiceEntry),
|
||||
resolvers: make(map[string]*zeroconf.Resolver),
|
||||
}
|
||||
|
||||
mod.SessionModule.Requires("net.recon")
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("zerogod.discovery on", "",
|
||||
"Start DNS-SD / mDNS discovery.",
|
||||
func(args []string) error {
|
||||
return mod.Start()
|
||||
}))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("zerogod.discovery off", "",
|
||||
"Stop DNS-SD / mDNS discovery.",
|
||||
func(args []string) error {
|
||||
return mod.Stop()
|
||||
}))
|
||||
|
||||
// TODO: add autocomplete
|
||||
mod.AddHandler(session.NewModuleHandler("zerogod.show", "",
|
||||
"Show discovered services.",
|
||||
func(args []string) error {
|
||||
return mod.show("", false)
|
||||
}))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("zerogod.show-full", "",
|
||||
"Show discovered services and their DNS records.",
|
||||
func(args []string) error {
|
||||
return mod.show("", true)
|
||||
}))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("zerogod.show ADDRESS", "zerogod.show (.+)",
|
||||
"Show discovered services given an ip address.",
|
||||
func(args []string) error {
|
||||
return mod.show(args[0], false)
|
||||
}))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("zerogod.show-full ADDRESS", "zerogod.show-full (.+)",
|
||||
"Show discovered services and DNS records given an ip address.",
|
||||
func(args []string) error {
|
||||
return mod.show(args[0], true)
|
||||
}))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("zerogod.save ADDRESS FILENAME", "zerogod.save (.+) (.+)",
|
||||
"Save the mDNS information of a given ADDRESS in the FILENAME yaml file.",
|
||||
func(args []string) error {
|
||||
return mod.save(args[0], args[1])
|
||||
}))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("zerogod.advertise FILENAME", "zerogod.advertise (.+)",
|
||||
"Start advertising the mDNS services from the FILENAME yaml file.",
|
||||
func(args []string) error {
|
||||
if args[0] == "off" {
|
||||
return mod.stopAdvertiser()
|
||||
}
|
||||
return mod.startAdvertiser(args[0])
|
||||
}))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("zerogod.advertise off", "",
|
||||
"Start a previously started advertiser.",
|
||||
func(args []string) error {
|
||||
return mod.stopAdvertiser()
|
||||
}))
|
||||
|
||||
mod.AddParam(session.NewStringParameter("zerogod.advertise.certificate",
|
||||
"~/.bettercap-zerogod.cert.pem",
|
||||
"",
|
||||
"TLS certificate file (will be auto generated if filled but not existing) to use for advertised TCP services."))
|
||||
|
||||
mod.AddParam(session.NewStringParameter("zerogod.advertise.key",
|
||||
"~/.bettercap-zerogod.key.pem",
|
||||
"",
|
||||
"TLS key file (will be auto generated if filled but not existing) to use for advertised TCP services."))
|
||||
|
||||
tls.CertConfigToModule("zerogod.advertise", &mod.SessionModule, tls.DefaultLegitConfig)
|
||||
|
||||
return mod
|
||||
}
|
||||
|
||||
func (mod *ZeroGod) Name() string {
|
||||
return "zerogod"
|
||||
}
|
||||
|
||||
func (mod *ZeroGod) Description() string {
|
||||
return "A DNS-SD / mDNS / Bonjour / Zeroconf module for discovery and spoofing."
|
||||
}
|
||||
|
||||
func (mod *ZeroGod) Author() string {
|
||||
return "Simone Margaritelli <evilsocket@gmail.com>"
|
||||
}
|
||||
|
||||
func (mod *ZeroGod) Configure() (err error) {
|
||||
if mod.Running() {
|
||||
return session.ErrAlreadyStarted(mod.Name())
|
||||
}
|
||||
|
||||
if mod.rootContext != nil {
|
||||
mod.rootCancel()
|
||||
}
|
||||
|
||||
mod.mapping = make(map[string]map[string]*zeroconf.ServiceEntry)
|
||||
mod.resolvers = make(map[string]*zeroconf.Resolver)
|
||||
mod.rootContext, mod.rootCancel = context.WithCancel(context.Background())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type ServiceDiscoveryEvent struct {
|
||||
Service zeroconf.ServiceEntry `json:"service"`
|
||||
Endpoint *network.Endpoint `json:"endpoint"`
|
||||
}
|
||||
|
||||
func (mod *ZeroGod) onServiceDiscovered(svc *zeroconf.ServiceEntry) {
|
||||
mod.Debug("%++v", *svc)
|
||||
|
||||
if svc.Service == "_services._dns-sd._udp" && len(svc.AddrIPv4) == 0 && len(svc.AddrIPv6) == 0 {
|
||||
svcName := strings.Replace(svc.Instance, ".local", "", 1)
|
||||
if _, found := mod.resolvers[svcName]; !found {
|
||||
mod.Debug("discovered service %s", tui.Green(svcName))
|
||||
if err := mod.startResolver(svcName); err != nil {
|
||||
mod.Error("%v", err)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
mod.Debug("discovered instance %s (%s) [%v / %v]:%d",
|
||||
tui.Green(svc.ServiceInstanceName()),
|
||||
tui.Dim(svc.HostName),
|
||||
svc.AddrIPv4,
|
||||
svc.AddrIPv6,
|
||||
svc.Port)
|
||||
|
||||
event := ServiceDiscoveryEvent{
|
||||
Service: *svc,
|
||||
Endpoint: nil,
|
||||
}
|
||||
|
||||
addresses := append(svc.AddrIPv4, svc.AddrIPv6...)
|
||||
|
||||
for _, ip := range addresses {
|
||||
address := ip.String()
|
||||
if event.Endpoint = mod.Session.Lan.GetByIp(address); event.Endpoint != nil {
|
||||
// update endpoint metadata
|
||||
mod.updateEndpointMeta(address, event.Endpoint, svc)
|
||||
|
||||
// update internal module mapping
|
||||
if ipServices, found := mod.mapping[address]; found {
|
||||
ipServices[svc.ServiceInstanceName()] = svc
|
||||
} else {
|
||||
mod.mapping[address] = map[string]*zeroconf.ServiceEntry{
|
||||
svc.ServiceInstanceName(): svc,
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if event.Endpoint == nil {
|
||||
// TODO: this is probably an IPv6 only record, try to somehow check which known IPv4 it is
|
||||
mod.Debug("got mdns entry for unknown ip: %++v", *svc)
|
||||
}
|
||||
|
||||
session.I.Events.Add("mdns.service", event)
|
||||
session.I.Refresh()
|
||||
}
|
||||
|
||||
func (mod *ZeroGod) startResolver(service string) error {
|
||||
mod.Debug("starting resolver for service %s", tui.Yellow(service))
|
||||
|
||||
resolver, err := zeroconf.NewResolver(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// start listening
|
||||
channel := make(chan *zeroconf.ServiceEntry)
|
||||
go func() {
|
||||
for entry := range channel {
|
||||
mod.onServiceDiscovered(entry)
|
||||
}
|
||||
}()
|
||||
|
||||
// start browsing
|
||||
go func() {
|
||||
err = resolver.Browse(mod.rootContext, service, "local.", channel)
|
||||
if err != nil {
|
||||
mod.Error("%v", err)
|
||||
}
|
||||
mod.Debug("resolver for service %s stopped", tui.Yellow(service))
|
||||
}()
|
||||
|
||||
mod.resolvers[service] = resolver
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mod *ZeroGod) Start() (err error) {
|
||||
if err = mod.Configure(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// start the root discovery
|
||||
if err = mod.startResolver("_services._dns-sd._udp"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return mod.SetRunning(true, func() {
|
||||
mod.Info("service discovery started")
|
||||
|
||||
<-mod.rootContext.Done()
|
||||
|
||||
mod.Info("service discovery stopped")
|
||||
})
|
||||
}
|
||||
|
||||
func (mod *ZeroGod) Stop() error {
|
||||
return mod.SetRunning(false, func() {
|
||||
mod.stopAdvertiser()
|
||||
|
||||
if mod.rootCancel != nil {
|
||||
mod.Debug("stopping discovery")
|
||||
|
||||
mod.rootCancel()
|
||||
<-mod.rootContext.Done()
|
||||
|
||||
mod.Debug("stopped")
|
||||
|
||||
mod.rootContext = nil
|
||||
mod.rootCancel = nil
|
||||
}
|
||||
})
|
||||
}
|
72
modules/zerogod/zerogod_endpoint_update.go
Normal file
72
modules/zerogod/zerogod_endpoint_update.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
package zerogod
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/modules/syn_scan"
|
||||
"github.com/bettercap/bettercap/v2/network"
|
||||
"github.com/evilsocket/islazy/str"
|
||||
"github.com/grandcat/zeroconf"
|
||||
)
|
||||
|
||||
func (mod *ZeroGod) updateEndpointMeta(address string, endpoint *network.Endpoint, svc *zeroconf.ServiceEntry) {
|
||||
mod.Debug("found endpoint %s for address %s", endpoint.HwAddress, address)
|
||||
|
||||
// TODO: this is shit and needs to be refactored
|
||||
|
||||
// update mdns metadata
|
||||
meta := make(map[string]string)
|
||||
|
||||
svcType := svc.Service
|
||||
|
||||
meta[fmt.Sprintf("mdns:%s:name", svcType)] = svc.ServiceName()
|
||||
meta[fmt.Sprintf("mdns:%s:hostname", svcType)] = svc.HostName
|
||||
|
||||
// TODO: include all
|
||||
if len(svc.AddrIPv4) > 0 {
|
||||
meta[fmt.Sprintf("mdns:%s:ipv4", svcType)] = svc.AddrIPv4[0].String()
|
||||
}
|
||||
|
||||
if len(svc.AddrIPv6) > 0 {
|
||||
meta[fmt.Sprintf("mdns:%s:ipv6", svcType)] = svc.AddrIPv6[0].String()
|
||||
}
|
||||
|
||||
meta[fmt.Sprintf("mdns:%s:port", svcType)] = fmt.Sprintf("%d", svc.Port)
|
||||
|
||||
for _, field := range svc.Text {
|
||||
field = str.Trim(field)
|
||||
if len(field) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
key := ""
|
||||
value := ""
|
||||
|
||||
if strings.Contains(field, "=") {
|
||||
parts := strings.SplitN(field, "=", 2)
|
||||
key = parts[0]
|
||||
value = parts[1]
|
||||
} else {
|
||||
key = field
|
||||
}
|
||||
|
||||
meta[fmt.Sprintf("mdns:%s:info:%s", svcType, key)] = value
|
||||
}
|
||||
|
||||
mod.Debug("meta for %s: %v", address, meta)
|
||||
|
||||
endpoint.OnMeta(meta)
|
||||
|
||||
// update ports
|
||||
ports := endpoint.Meta.GetOr("ports", map[int]*syn_scan.OpenPort{}).(map[int]*syn_scan.OpenPort)
|
||||
if _, found := ports[svc.Port]; !found {
|
||||
ports[svc.Port] = &syn_scan.OpenPort{
|
||||
Proto: "tcp",
|
||||
Port: svc.Port,
|
||||
Service: network.GetServiceByPort(svc.Port, "tcp"),
|
||||
}
|
||||
}
|
||||
|
||||
endpoint.Meta.Set("ports", ports)
|
||||
}
|
69
modules/zerogod/zerogod_generic_handler.go
Normal file
69
modules/zerogod/zerogod_generic_handler.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
package zerogod
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
func Dump(by []byte) string {
|
||||
s := ""
|
||||
n := len(by)
|
||||
rowcount := 0
|
||||
width := 16
|
||||
|
||||
stop := (n / width) * width
|
||||
k := 0
|
||||
for i := 0; i <= stop; i += width {
|
||||
k++
|
||||
if i+width < n {
|
||||
rowcount = width
|
||||
} else {
|
||||
rowcount = min(k*width, n) % width
|
||||
}
|
||||
|
||||
s += fmt.Sprintf("%02d ", i)
|
||||
for j := 0; j < rowcount; j++ {
|
||||
s += fmt.Sprintf("%02x ", by[i+j])
|
||||
}
|
||||
for j := rowcount; j < width; j++ {
|
||||
s += " "
|
||||
}
|
||||
s += fmt.Sprintf(" '%s'\n", viewString(by[i:(i+rowcount)]))
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func viewString(b []byte) string {
|
||||
r := []rune(string(b))
|
||||
for i := range r {
|
||||
if r[i] < 32 || r[i] > 126 {
|
||||
r[i] = '.'
|
||||
}
|
||||
}
|
||||
return string(r)
|
||||
}
|
||||
|
||||
func handleGenericTCP(mod *ZeroGod, client net.Conn, srvHost string, srvPort int, srvTLS bool) {
|
||||
defer client.Close()
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
if read, err := client.Read(buf); err != nil {
|
||||
mod.Error("error while reading from %v: %v", client.RemoteAddr(), err)
|
||||
break
|
||||
} else if read == 0 {
|
||||
mod.Error("error while reading from %v: no data", client.RemoteAddr())
|
||||
break
|
||||
} else {
|
||||
mod.Info("read %d bytes from %v:\n%s\n", read, client.RemoteAddr(), Dump(buf[0:read]))
|
||||
}
|
||||
}
|
||||
}
|
355
modules/zerogod/zerogod_ipp_handler.go
Normal file
355
modules/zerogod/zerogod_ipp_handler.go
Normal file
|
@ -0,0 +1,355 @@
|
|||
package zerogod
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/evilsocket/islazy/ops"
|
||||
"github.com/evilsocket/islazy/tui"
|
||||
|
||||
"github.com/phin1x/go-ipp"
|
||||
)
|
||||
|
||||
var IPP_REQUEST_NAMES = map[int16]string{
|
||||
// https://tools.ietf.org/html/rfc2911#section-4.4.15
|
||||
0x0002: "Print-Job",
|
||||
0x0003: "Print-URI",
|
||||
0x0004: "Validate-Job",
|
||||
0x0005: "Create-Job",
|
||||
0x0006: "Send-Document",
|
||||
0x0007: "Send-URI",
|
||||
0x0008: "Cancel-Job",
|
||||
0x0009: "Get-Job-Attributes",
|
||||
0x000A: "Get-Jobs",
|
||||
0x000B: "Get-Printer-Attributes",
|
||||
0x000C: "Hold-Job",
|
||||
0x000D: "Release-Job",
|
||||
0x000E: "Restart-Job",
|
||||
0x0010: "Pause-Printer",
|
||||
0x0011: "Resume-Printer",
|
||||
0x0012: "Purge-Jobs",
|
||||
// https://web.archive.org/web/20061024184939/http://uw714doc.sco.com/en/cups/ipp.html
|
||||
0x4001: "CUPS-Get-Default",
|
||||
0x4002: "CUPS-Get-Printers",
|
||||
0x4003: "CUPS-Add-Modify-Printer",
|
||||
0x4004: "CUPS-Delete-Printer",
|
||||
0x4005: "CUPS-Get-Classes",
|
||||
0x4006: "CUPS-Add-Modify-Class",
|
||||
0x4007: "CUPS-Delete-Class",
|
||||
0x4008: "CUPS-Accept-Jobs",
|
||||
0x4009: "CUPS-Reject-Jobs",
|
||||
0x400A: "CUPS-Set-Default",
|
||||
0x400B: "CUPS-Get-Devices",
|
||||
0x400C: "CUPS-Get-PPDs",
|
||||
0x400D: "CUPS-Move-Job",
|
||||
}
|
||||
|
||||
func ippClientHandler(mod *ZeroGod, client net.Conn, srvHost string, srvPort int, srvTLS bool) {
|
||||
defer client.Close()
|
||||
|
||||
buf := make([]byte, 4096)
|
||||
|
||||
// read raw request
|
||||
read, err := client.Read(buf)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return
|
||||
}
|
||||
mod.Error("error while reading from %v: %v", client.RemoteAddr(), err)
|
||||
return
|
||||
} else if read == 0 {
|
||||
mod.Error("error while reading from %v: no data", client.RemoteAddr())
|
||||
return
|
||||
}
|
||||
|
||||
raw_req := buf[0:read]
|
||||
|
||||
mod.Debug("read %d bytes from %v:\n%s\n", read, client.RemoteAddr(), Dump(raw_req))
|
||||
|
||||
// parse as http
|
||||
reader := bufio.NewReader(bytes.NewReader(raw_req))
|
||||
http_req, err := http.ReadRequest(reader)
|
||||
if err != nil {
|
||||
mod.Error("error while parsing http request from %v: %v", client.RemoteAddr(), err)
|
||||
return
|
||||
}
|
||||
|
||||
mod.Info("%v -> %s", client.RemoteAddr(), tui.Green(http_req.UserAgent()))
|
||||
|
||||
ipp_body := http_req.Body
|
||||
|
||||
// check for an Expect 100-continue
|
||||
if http_req.Header.Get("Expect") == "100-continue" {
|
||||
// inform the client we're ready to read the request body
|
||||
client.Write([]byte("HTTP/1.1 100 Continue\r\n\r\n"))
|
||||
// read the body
|
||||
read, err := client.Read(buf)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return
|
||||
}
|
||||
mod.Error("error while reading ipp body from %v: %v", client.RemoteAddr(), err)
|
||||
return
|
||||
} else if read == 0 {
|
||||
mod.Error("error while reading ipp body from %v: no data", client.RemoteAddr())
|
||||
return
|
||||
}
|
||||
|
||||
ipp_body = io.NopCloser(bytes.NewReader(buf[0:read]))
|
||||
}
|
||||
|
||||
// parse as IPP
|
||||
ipp_req, err := ipp.NewRequestDecoder(ipp_body).Decode(nil)
|
||||
if err != nil {
|
||||
mod.Error("error while parsing ip request from %v: %v", client.RemoteAddr(), err)
|
||||
return
|
||||
}
|
||||
|
||||
ipp_op_name := fmt.Sprintf("<unknown 0x%x>", ipp_req.Operation)
|
||||
if name, found := IPP_REQUEST_NAMES[ipp_req.Operation]; found {
|
||||
ipp_op_name = name
|
||||
}
|
||||
|
||||
mod.Info("%v op=%s attributes=%v", client.RemoteAddr(), tui.Bold(ipp_op_name), ipp_req.OperationAttributes)
|
||||
|
||||
switch ipp_req.Operation {
|
||||
// Get-Printer-Attributes
|
||||
case 0x000B:
|
||||
ippOnGetPrinterAttributes(mod, client, ipp_req, srvHost, srvPort, srvTLS)
|
||||
|
||||
default:
|
||||
ippOnUnhandledRequest(mod, client, ipp_req, ipp_op_name)
|
||||
}
|
||||
}
|
||||
|
||||
func ippSendResponse(mod *ZeroGod, client net.Conn, response *ipp.Response) {
|
||||
mod.Debug("SENDING %++v", *response)
|
||||
|
||||
resp_data, err := response.Encode()
|
||||
if err != nil {
|
||||
mod.Error("error while encoding ipp response: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
headers := [][]byte{
|
||||
[]byte("HTTP/1.1 200 OK\r\n"),
|
||||
[]byte("Content-Type: application/ipp\r\n"),
|
||||
[]byte(fmt.Sprintf("Content-Length: %d\r\n", len(resp_data))),
|
||||
[]byte("Connection: close\r\n"),
|
||||
[]byte("\r\n"),
|
||||
}
|
||||
|
||||
for _, header := range headers {
|
||||
if _, err := client.Write(header); err != nil {
|
||||
mod.Error("error while writing header: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = client.Write(resp_data); err != nil {
|
||||
mod.Error("error while writing ipp response data: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
mod.Debug("sent %d of ipp response to %v", len(resp_data), client.RemoteAddr())
|
||||
}
|
||||
|
||||
func ippOnUnhandledRequest(mod *ZeroGod, client net.Conn, ipp_req *ipp.Request, ipp_op_name string) {
|
||||
ipp_resp := ipp.NewResponse(ipp.StatusErrorOperationNotSupported, ipp_req.RequestId)
|
||||
|
||||
ippSendResponse(mod, client, ipp_resp)
|
||||
|
||||
mod.Warning("unhandled request from %v: operation=%s", client.RemoteAddr(), ipp_op_name)
|
||||
}
|
||||
|
||||
func ippOnGetPrinterAttributes(mod *ZeroGod, client net.Conn, ipp_req *ipp.Request, srvHost string, srvPort int, srvTLS bool) {
|
||||
ipp_resp := ipp.NewResponse(ipp.StatusOk, ipp_req.RequestId)
|
||||
|
||||
// https://tools.ietf.org/html/rfc2911 section 3.1.4.2 Response Operation Attributes
|
||||
ipp_resp.OperationAttributes["attributes-charset"] = []ipp.Attribute{
|
||||
{
|
||||
Value: "utf-8",
|
||||
Tag: ipp.TagCharset,
|
||||
},
|
||||
}
|
||||
ipp_resp.OperationAttributes["attributes-natural-language"] = []ipp.Attribute{
|
||||
{
|
||||
Value: "en",
|
||||
Tag: ipp.TagLanguage,
|
||||
},
|
||||
}
|
||||
|
||||
// rfc2911 section 4.4
|
||||
ipp.AttributeTagMapping["printer-uri-supported"] = ipp.TagUri
|
||||
ipp.AttributeTagMapping["uri-authentication-supported"] = ipp.TagKeyword
|
||||
ipp.AttributeTagMapping["uri-security-supported"] = ipp.TagKeyword
|
||||
ipp.AttributeTagMapping["printer-name"] = ipp.TagName
|
||||
ipp.AttributeTagMapping["printer-info"] = ipp.TagText
|
||||
ipp.AttributeTagMapping["printer-make-and-model"] = ipp.TagText
|
||||
ipp.AttributeTagMapping["printer-state"] = ipp.TagEnum
|
||||
ipp.AttributeTagMapping["printer-state-reasons"] = ipp.TagKeyword
|
||||
ipp.AttributeTagMapping["ipp-versions-supported"] = ipp.TagKeyword
|
||||
ipp.AttributeTagMapping["operations-supported"] = ipp.TagEnum
|
||||
ipp.AttributeTagMapping["multiple-document-jobs-supported"] = ipp.TagBoolean
|
||||
ipp.AttributeTagMapping["charset-configured"] = ipp.TagCharset
|
||||
ipp.AttributeTagMapping["charset-supported"] = ipp.TagCharset
|
||||
ipp.AttributeTagMapping["natural-language-configured"] = ipp.TagLanguage
|
||||
ipp.AttributeTagMapping["generated-natural-language-supported"] = ipp.TagLanguage
|
||||
ipp.AttributeTagMapping["document-format-default"] = ipp.TagMimeType
|
||||
ipp.AttributeTagMapping["document-format-supported"] = ipp.TagMimeType
|
||||
ipp.AttributeTagMapping["printer-is-accepting-jobs"] = ipp.TagBoolean
|
||||
ipp.AttributeTagMapping["queued-job-count"] = ipp.TagInteger
|
||||
ipp.AttributeTagMapping["pdl-override-supported"] = ipp.TagKeyword
|
||||
ipp.AttributeTagMapping["printer-up-time"] = ipp.TagInteger
|
||||
ipp.AttributeTagMapping["compression-supported"] = ipp.TagKeyword
|
||||
|
||||
ipp_resp.PrinterAttributes = []ipp.Attributes{
|
||||
{
|
||||
"printer-uri-supported": []ipp.Attribute{
|
||||
{
|
||||
Value: fmt.Sprintf("%s://%s:%d/printer", ops.Ternary(srvTLS, "ipps", "ipp"), srvHost, srvPort),
|
||||
Tag: ipp.TagUri,
|
||||
},
|
||||
},
|
||||
"uri-authentication-supported": []ipp.Attribute{
|
||||
{
|
||||
Value: "none",
|
||||
Tag: ipp.TagKeyword,
|
||||
},
|
||||
},
|
||||
"uri-security-supported": []ipp.Attribute{
|
||||
{
|
||||
Value: ops.Ternary(srvTLS, "tls", "none"),
|
||||
Tag: ipp.TagKeyword,
|
||||
},
|
||||
},
|
||||
"printer-name": []ipp.Attribute{
|
||||
{
|
||||
Value: "PRINTER_NAME",
|
||||
Tag: ipp.TagName,
|
||||
},
|
||||
},
|
||||
"printer-info": []ipp.Attribute{
|
||||
{
|
||||
Value: "PRINTER_INFO",
|
||||
Tag: ipp.TagText,
|
||||
},
|
||||
},
|
||||
"printer-make-and-model": []ipp.Attribute{
|
||||
{
|
||||
Value: "PRINTER_MAKE PRINTER_MODEL",
|
||||
Tag: ipp.TagText,
|
||||
},
|
||||
},
|
||||
"printer-state": []ipp.Attribute{
|
||||
{
|
||||
Value: 3, // idle
|
||||
Tag: ipp.TagEnum,
|
||||
},
|
||||
},
|
||||
"printer-state-reasons": []ipp.Attribute{
|
||||
{
|
||||
Value: "none",
|
||||
Tag: ipp.TagKeyword,
|
||||
},
|
||||
},
|
||||
"ipp-versions-supported": []ipp.Attribute{
|
||||
{
|
||||
Value: "1.1",
|
||||
Tag: ipp.TagKeyword,
|
||||
},
|
||||
},
|
||||
"operations-supported": []ipp.Attribute{
|
||||
{
|
||||
Value: []int{
|
||||
0x0002, // print job (required by cups)
|
||||
0x0004, // validate job (required by cups)
|
||||
0x0008, // cancel job (required by cups)
|
||||
0x0009, // get job attributes (required by cups)
|
||||
0x000b, // get printer attributes
|
||||
},
|
||||
Tag: ipp.TagEnum,
|
||||
},
|
||||
},
|
||||
"multiple-document-jobs-supported": []ipp.Attribute{
|
||||
{
|
||||
Value: false,
|
||||
Tag: ipp.TagBoolean,
|
||||
},
|
||||
},
|
||||
"charset-configured": []ipp.Attribute{
|
||||
{
|
||||
Value: "utf-8",
|
||||
Tag: ipp.TagCharset,
|
||||
},
|
||||
},
|
||||
"charset-supported": []ipp.Attribute{
|
||||
{
|
||||
Value: "utf-8",
|
||||
Tag: ipp.TagCharset,
|
||||
},
|
||||
},
|
||||
"natural-language-configured": []ipp.Attribute{
|
||||
{
|
||||
Value: "en",
|
||||
Tag: ipp.TagLanguage,
|
||||
},
|
||||
},
|
||||
"generated-natural-language-supported": []ipp.Attribute{
|
||||
{
|
||||
Value: "en",
|
||||
Tag: ipp.TagLanguage,
|
||||
},
|
||||
},
|
||||
"document-format-default": []ipp.Attribute{
|
||||
{
|
||||
Value: "application/pdf",
|
||||
Tag: ipp.TagMimeType,
|
||||
},
|
||||
},
|
||||
"document-format-supported": []ipp.Attribute{
|
||||
{
|
||||
Value: "application/pdf",
|
||||
Tag: ipp.TagMimeType,
|
||||
},
|
||||
},
|
||||
"printer-is-accepting-jobs": []ipp.Attribute{
|
||||
{
|
||||
Value: true,
|
||||
Tag: ipp.TagBoolean,
|
||||
},
|
||||
},
|
||||
"queued-job-count": []ipp.Attribute{
|
||||
{
|
||||
Value: 0,
|
||||
Tag: ipp.TagInteger,
|
||||
},
|
||||
},
|
||||
"pdl-override-supported": []ipp.Attribute{
|
||||
{
|
||||
Value: "not-attempted",
|
||||
Tag: ipp.TagKeyword,
|
||||
},
|
||||
},
|
||||
"printer-up-time": []ipp.Attribute{
|
||||
{
|
||||
Value: time.Now().Unix(),
|
||||
Tag: ipp.TagInteger,
|
||||
},
|
||||
},
|
||||
"compression-supported": []ipp.Attribute{
|
||||
{
|
||||
Value: "none",
|
||||
Tag: ipp.TagKeyword,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ippSendResponse(mod, client, ipp_resp)
|
||||
}
|
35
modules/zerogod/zerogod_save.go
Normal file
35
modules/zerogod/zerogod_save.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package zerogod
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func (mod *ZeroGod) save(address, filename string) error {
|
||||
if address == "" {
|
||||
return fmt.Errorf("address cannot be empty")
|
||||
}
|
||||
if filename == "" {
|
||||
return fmt.Errorf("filename cannot be empty")
|
||||
}
|
||||
|
||||
if ipServices, found := mod.mapping[address]; found {
|
||||
data, err := yaml.Marshal(ipServices)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(filename, data, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mod.Info("mDNS information saved to %s", filename)
|
||||
} else {
|
||||
return fmt.Errorf("no mDNS information found for address %s", address)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
76
modules/zerogod/zerogod_show.go
Normal file
76
modules/zerogod/zerogod_show.go
Normal file
|
@ -0,0 +1,76 @@
|
|||
package zerogod
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/evilsocket/islazy/str"
|
||||
"github.com/evilsocket/islazy/tui"
|
||||
"github.com/grandcat/zeroconf"
|
||||
)
|
||||
|
||||
type entry struct {
|
||||
ip string
|
||||
services map[string]*zeroconf.ServiceEntry
|
||||
}
|
||||
|
||||
func (mod *ZeroGod) show(filter string, withData bool) error {
|
||||
fmt.Fprintf(mod.Session.Events.Stdout, "\n")
|
||||
|
||||
// convert to list for sorting
|
||||
entries := make([]entry, 0)
|
||||
for ip, services := range mod.mapping {
|
||||
if filter == "" || ip == filter {
|
||||
entries = append(entries, entry{ip, services})
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(entries, func(i, j int) bool {
|
||||
return entries[i].ip < entries[j].ip
|
||||
})
|
||||
|
||||
for _, entry := range entries {
|
||||
if endpoint := mod.Session.Lan.GetByIp(entry.ip); endpoint != nil {
|
||||
fmt.Fprintf(mod.Session.Events.Stdout, "* %s (%s)\n", tui.Bold(endpoint.IpAddress), tui.Dim(endpoint.Vendor))
|
||||
} else {
|
||||
fmt.Fprintf(mod.Session.Events.Stdout, "* %s\n", tui.Bold(entry.ip))
|
||||
}
|
||||
|
||||
for name, svc := range entry.services {
|
||||
fmt.Fprintf(mod.Session.Events.Stdout, " %s (%s) [%v / %v]:%s\n",
|
||||
tui.Green(name),
|
||||
tui.Dim(svc.HostName),
|
||||
svc.AddrIPv4,
|
||||
svc.AddrIPv6,
|
||||
tui.Red(fmt.Sprintf("%d", svc.Port)),
|
||||
)
|
||||
|
||||
numFields := len(svc.Text)
|
||||
if withData {
|
||||
if numFields > 0 {
|
||||
for _, field := range svc.Text {
|
||||
if field = str.Trim(field); len(field) > 0 {
|
||||
fmt.Fprintf(mod.Session.Events.Stdout, " %s\n", field)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(mod.Session.Events.Stdout, " %s\n", tui.Dim("no data"))
|
||||
}
|
||||
} else {
|
||||
if numFields > 0 {
|
||||
fmt.Fprintf(mod.Session.Events.Stdout, " <%d records>\n", numFields)
|
||||
} else {
|
||||
fmt.Fprintf(mod.Session.Events.Stdout, " %s\n", tui.Dim("<no records>"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(mod.Session.Events.Stdout, "\n")
|
||||
}
|
||||
|
||||
if len(entries) > 0 {
|
||||
mod.Session.Refresh()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue