mirror of
https://github.com/bettercap/bettercap.git
synced 2024-11-08 06:30:13 -08:00
387 lines
9.8 KiB
Go
387 lines
9.8 KiB
Go
package c2
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"github.com/acarl005/stripansi"
|
|
"github.com/bettercap/bettercap/v2/modules/events_stream"
|
|
"github.com/bettercap/bettercap/v2/session"
|
|
"github.com/evilsocket/islazy/log"
|
|
"github.com/evilsocket/islazy/str"
|
|
irc "github.com/thoj/go-ircevent"
|
|
)
|
|
|
|
type settings struct {
|
|
server string
|
|
tls bool
|
|
tlsVerify bool
|
|
nick string
|
|
user string
|
|
password string
|
|
saslUser string
|
|
saslPassword string
|
|
operator string
|
|
controlChannel string
|
|
eventsChannel string
|
|
outputChannel string
|
|
}
|
|
|
|
type C2 struct {
|
|
session.SessionModule
|
|
|
|
settings settings
|
|
stream *events_stream.EventsStream
|
|
templates map[string]*template.Template
|
|
channels map[string]string
|
|
client *irc.Connection
|
|
eventBus session.EventBus
|
|
quit chan bool
|
|
}
|
|
|
|
type eventContext struct {
|
|
Session *session.Session
|
|
Event session.Event
|
|
}
|
|
|
|
func NewC2(s *session.Session) *C2 {
|
|
mod := &C2{
|
|
SessionModule: session.NewSessionModule("c2", s),
|
|
stream: events_stream.NewEventsStream(s),
|
|
templates: make(map[string]*template.Template),
|
|
channels: make(map[string]string),
|
|
quit: make(chan bool),
|
|
settings: settings{
|
|
server: "localhost:6697",
|
|
tls: true,
|
|
tlsVerify: false,
|
|
nick: "bettercap",
|
|
user: "bettercap",
|
|
password: "password",
|
|
operator: "admin",
|
|
eventsChannel: "#events",
|
|
outputChannel: "#events",
|
|
controlChannel: "#events",
|
|
},
|
|
}
|
|
|
|
mod.AddParam(session.NewStringParameter("c2.server",
|
|
mod.settings.server,
|
|
"",
|
|
"IRC server address and port."))
|
|
|
|
mod.AddParam(session.NewBoolParameter("c2.server.tls",
|
|
"true",
|
|
"Enable TLS."))
|
|
|
|
mod.AddParam(session.NewBoolParameter("c2.server.tls.verify",
|
|
"false",
|
|
"Enable TLS certificate validation."))
|
|
|
|
mod.AddParam(session.NewStringParameter("c2.operator",
|
|
mod.settings.operator,
|
|
"",
|
|
"IRC nickname of the user allowed to run commands."))
|
|
|
|
mod.AddParam(session.NewStringParameter("c2.nick",
|
|
mod.settings.nick,
|
|
"",
|
|
"IRC nickname."))
|
|
|
|
mod.AddParam(session.NewStringParameter("c2.username",
|
|
mod.settings.user,
|
|
"",
|
|
"IRC username."))
|
|
|
|
mod.AddParam(session.NewStringParameter("c2.password",
|
|
mod.settings.password,
|
|
"",
|
|
"IRC server password."))
|
|
|
|
mod.AddParam(session.NewStringParameter("c2.sasl.username",
|
|
mod.settings.saslUser,
|
|
"",
|
|
"IRC SASL username."))
|
|
|
|
mod.AddParam(session.NewStringParameter("c2.sasl.password",
|
|
mod.settings.saslPassword,
|
|
"",
|
|
"IRC server SASL password."))
|
|
|
|
mod.AddParam(session.NewStringParameter("c2.channel.output",
|
|
mod.settings.outputChannel,
|
|
"",
|
|
"IRC channel to send commands output to."))
|
|
|
|
mod.AddParam(session.NewStringParameter("c2.channel.events",
|
|
mod.settings.eventsChannel,
|
|
"",
|
|
"IRC channel to send events to."))
|
|
|
|
mod.AddParam(session.NewStringParameter("c2.channel.control",
|
|
mod.settings.controlChannel,
|
|
"",
|
|
"IRC channel to receive commands from."))
|
|
|
|
mod.AddHandler(session.NewModuleHandler("c2 on", "",
|
|
"Start the C2 module.",
|
|
func(args []string) error {
|
|
return mod.Start()
|
|
}))
|
|
|
|
mod.AddHandler(session.NewModuleHandler("c2 off", "",
|
|
"Stop the C2 module.",
|
|
func(args []string) error {
|
|
return mod.Stop()
|
|
}))
|
|
|
|
mod.AddHandler(session.NewModuleHandler("c2.channel.set EVENT_TYPE CHANNEL",
|
|
"c2.channel.set ([^\\s]+) (.+)",
|
|
"Set a specific channel to report events of this type.",
|
|
func(args []string) error {
|
|
eventType := args[0]
|
|
channel := args[1]
|
|
|
|
mod.Debug("setting channel for event %s: %v", eventType, channel)
|
|
mod.channels[eventType] = channel
|
|
return nil
|
|
}))
|
|
|
|
mod.AddHandler(session.NewModuleHandler("c2.channel.clear EVENT_TYPE",
|
|
"c2.channel.clear ([^\\s]+)",
|
|
"Clear the channel to use for a specific event type.",
|
|
func(args []string) error {
|
|
eventType := args[0]
|
|
if _, found := mod.channels[args[0]]; found {
|
|
delete(mod.channels, eventType)
|
|
mod.Debug("cleared channel for %s", eventType)
|
|
} else {
|
|
return fmt.Errorf("channel for event %s not set", args[0])
|
|
}
|
|
return nil
|
|
}))
|
|
|
|
mod.AddHandler(session.NewModuleHandler("c2.template.set EVENT_TYPE TEMPLATE",
|
|
"c2.template.set ([^\\s]+) (.+)",
|
|
"Set the reporting template to use for a specific event type.",
|
|
func(args []string) error {
|
|
eventType := args[0]
|
|
eventTemplate := args[1]
|
|
|
|
parsed, err := template.New(eventType).Parse(eventTemplate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mod.Debug("setting template for event %s: %v", eventType, parsed)
|
|
mod.templates[eventType] = parsed
|
|
return nil
|
|
}))
|
|
|
|
mod.AddHandler(session.NewModuleHandler("c2.template.clear EVENT_TYPE",
|
|
"c2.template.clear ([^\\s]+)",
|
|
"Clear the reporting template to use for a specific event type.",
|
|
func(args []string) error {
|
|
eventType := args[0]
|
|
if _, found := mod.templates[args[0]]; found {
|
|
delete(mod.templates, eventType)
|
|
mod.Debug("cleared template for %s", eventType)
|
|
} else {
|
|
return fmt.Errorf("template for event %s not set", args[0])
|
|
}
|
|
return nil
|
|
}))
|
|
|
|
mod.Session.Events.OnPrint(mod.onPrint)
|
|
|
|
return mod
|
|
}
|
|
|
|
func (mod *C2) Name() string {
|
|
return "c2"
|
|
}
|
|
|
|
func (mod *C2) Description() string {
|
|
return "A CnC module that connects to an IRC server for reporting and commands."
|
|
}
|
|
|
|
func (mod *C2) Author() string {
|
|
return "Simone Margaritelli <evilsocket@gmail.com>"
|
|
}
|
|
|
|
func (mod *C2) Configure() (err error) {
|
|
if mod.Running() {
|
|
return session.ErrAlreadyStarted(mod.Name())
|
|
}
|
|
|
|
if err, mod.settings.server = mod.StringParam("c2.server"); err != nil {
|
|
return err
|
|
} else if err, mod.settings.tls = mod.BoolParam("c2.server.tls"); err != nil {
|
|
return err
|
|
} else if err, mod.settings.tlsVerify = mod.BoolParam("c2.server.tls.verify"); err != nil {
|
|
return err
|
|
} else if err, mod.settings.nick = mod.StringParam("c2.nick"); err != nil {
|
|
return err
|
|
} else if err, mod.settings.user = mod.StringParam("c2.username"); err != nil {
|
|
return err
|
|
} else if err, mod.settings.password = mod.StringParam("c2.password"); err != nil {
|
|
return err
|
|
} else if err, mod.settings.saslUser = mod.StringParam("c2.sasl.username"); err != nil {
|
|
return err
|
|
} else if err, mod.settings.saslPassword = mod.StringParam("c2.sasl.password"); err != nil {
|
|
return err
|
|
} else if err, mod.settings.operator = mod.StringParam("c2.operator"); err != nil {
|
|
return err
|
|
} else if err, mod.settings.eventsChannel = mod.StringParam("c2.channel.events"); err != nil {
|
|
return err
|
|
} else if err, mod.settings.controlChannel = mod.StringParam("c2.channel.control"); err != nil {
|
|
return err
|
|
} else if err, mod.settings.outputChannel = mod.StringParam("c2.channel.output"); err != nil {
|
|
return err
|
|
}
|
|
|
|
mod.eventBus = mod.Session.Events.Listen()
|
|
|
|
mod.client = irc.IRC(mod.settings.nick, mod.settings.user)
|
|
|
|
if log.Level == log.DEBUG {
|
|
mod.client.VerboseCallbackHandler = true
|
|
mod.client.Debug = true
|
|
}
|
|
|
|
mod.client.Password = mod.settings.password
|
|
mod.client.UseTLS = mod.settings.tls
|
|
mod.client.TLSConfig = &tls.Config{
|
|
InsecureSkipVerify: !mod.settings.tlsVerify,
|
|
}
|
|
|
|
if mod.settings.saslUser != "" || mod.settings.saslPassword != "" {
|
|
mod.client.SASLLogin = mod.settings.saslUser
|
|
mod.client.SASLPassword = mod.settings.saslPassword
|
|
mod.client.UseSASL = true
|
|
}
|
|
|
|
mod.client.AddCallback("PRIVMSG", func(event *irc.Event) {
|
|
channel := event.Arguments[0]
|
|
message := event.Message()
|
|
from := event.Nick
|
|
|
|
if from != mod.settings.operator {
|
|
mod.client.Privmsg(event.Nick, "nope")
|
|
return
|
|
}
|
|
|
|
if channel != mod.settings.controlChannel && channel != mod.settings.nick {
|
|
mod.Debug("from:%s on:%s - '%s'", from, channel, message)
|
|
return
|
|
}
|
|
|
|
mod.Debug("from:%s on:%s - '%s'", from, channel, message)
|
|
|
|
parts := strings.SplitN(message, " ", 2)
|
|
cmd := parts[0]
|
|
args := ""
|
|
if len(parts) > 1 {
|
|
args = parts[1]
|
|
}
|
|
|
|
if cmd == "join" {
|
|
mod.client.Join(args)
|
|
} else if cmd == "part" {
|
|
mod.client.Part(args)
|
|
} else if cmd == "nick" {
|
|
mod.client.Nick(args)
|
|
} else if err = mod.Session.Run(message); err == nil {
|
|
|
|
} else {
|
|
mod.client.Privmsgf(event.Nick, "error: %v", stripansi.Strip(err.Error()))
|
|
}
|
|
})
|
|
|
|
mod.client.AddCallback("001", func(e *irc.Event) {
|
|
mod.Debug("got 101")
|
|
mod.client.Join(mod.settings.controlChannel)
|
|
mod.client.Join(mod.settings.outputChannel)
|
|
mod.client.Join(mod.settings.eventsChannel)
|
|
})
|
|
|
|
return mod.client.Connect(mod.settings.server)
|
|
}
|
|
|
|
func (mod *C2) onPrint(format string, args ...interface{}) {
|
|
if !mod.Running() {
|
|
return
|
|
}
|
|
|
|
msg := stripansi.Strip(str.Trim(fmt.Sprintf(format, args...)))
|
|
|
|
for _, line := range strings.Split(msg, "\n") {
|
|
mod.client.Privmsg(mod.settings.outputChannel, line)
|
|
}
|
|
}
|
|
|
|
func (mod *C2) onEvent(e session.Event) {
|
|
if mod.Session.EventsIgnoreList.Ignored(e) {
|
|
return
|
|
}
|
|
|
|
// default channel or event specific channel?
|
|
channel := mod.settings.eventsChannel
|
|
if custom, found := mod.channels[e.Tag]; found {
|
|
channel = custom
|
|
}
|
|
|
|
var out bytes.Buffer
|
|
if tpl, found := mod.templates[e.Tag]; found {
|
|
// use a custom template to render this event
|
|
if err := tpl.Execute(&out, eventContext{
|
|
Session: mod.Session,
|
|
Event: e,
|
|
}); err != nil {
|
|
fmt.Fprintf(&out, "%v", err)
|
|
}
|
|
} else {
|
|
// use the default view to render this event
|
|
mod.stream.Render(&out, e)
|
|
}
|
|
|
|
// make sure colors and in general bash escape sequences are removed
|
|
msg := stripansi.Strip(str.Trim(string(out.Bytes())))
|
|
|
|
mod.client.Privmsg(channel, msg)
|
|
}
|
|
|
|
func (mod *C2) Start() error {
|
|
if err := mod.Configure(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return mod.SetRunning(true, func() {
|
|
mod.Info("started")
|
|
|
|
for mod.Running() {
|
|
var e session.Event
|
|
select {
|
|
case e = <-mod.eventBus:
|
|
mod.onEvent(e)
|
|
|
|
case <-mod.quit:
|
|
mod.Debug("got quit")
|
|
return
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func (mod *C2) Stop() error {
|
|
return mod.SetRunning(false, func() {
|
|
mod.quit <- true
|
|
mod.Session.Events.Unlisten(mod.eventBus)
|
|
mod.client.Quit()
|
|
mod.client.Disconnect()
|
|
})
|
|
}
|