mirror of
https://github.com/bettercap/bettercap
synced 2025-08-21 05:53:20 -07:00
new: new c2 module, first draft
This commit is contained in:
parent
35dbb8a368
commit
583a54c194
23 changed files with 579 additions and 163 deletions
354
modules/c2/c2.go
Normal file
354
modules/c2/c2.go
Normal file
|
@ -0,0 +1,354 @@
|
|||
package c2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/acarl005/stripansi"
|
||||
"github.com/bettercap/bettercap/modules/events_stream"
|
||||
"github.com/bettercap/bettercap/session"
|
||||
"github.com/evilsocket/islazy/log"
|
||||
"github.com/evilsocket/islazy/str"
|
||||
irc "github.com/thoj/go-ircevent"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type settings struct {
|
||||
server string
|
||||
ssl bool
|
||||
nick string
|
||||
user string
|
||||
password 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",
|
||||
ssl: true,
|
||||
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 or disable TLS."))
|
||||
|
||||
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.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.ssl = mod.BoolParam("c2.server.tls"); 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.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.ssl
|
||||
mod.client.TLSConfig = &tls.Config{
|
||||
InsecureSkipVerify: true, // TODO: pass this by parameter?
|
||||
}
|
||||
|
||||
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 {
|
||||
// mod.client.Privmsg(event.Nick, "ok")
|
||||
} 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()
|
||||
})
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue