diff --git a/caplets/rest-api.cap b/caplets/rest-api.cap index 17c4704e..dd0dfee8 100644 --- a/caplets/rest-api.cap +++ b/caplets/rest-api.cap @@ -1,6 +1,7 @@ # change these! set api.rest.username bcap set api.rest.password bcap +# set api.rest.port 8082 net.probe on net.recon on diff --git a/main.go b/main.go index 6d5daef9..36233afc 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "runtime" "strings" @@ -20,8 +21,8 @@ func main() { panic(err) } - log.Infof("Starting %s v%s\n", core.Name, core.Version) - log.Infof("Build: date=%s os=%s arch=%s\n", core.BuildDate, runtime.GOOS, runtime.GOARCH) + fmt.Printf("%s v%s\n", core.Name, core.Version) + fmt.Printf("Build: date=%s os=%s arch=%s\n\n", core.BuildDate, runtime.GOOS, runtime.GOARCH) sess.Register(session_modules.NewProber(sess)) sess.Register(session_modules.NewDiscovery(sess)) diff --git a/net/endpoint.go b/net/endpoint.go index 51adf834..5ff660c0 100644 --- a/net/endpoint.go +++ b/net/endpoint.go @@ -8,19 +8,19 @@ import ( "github.com/evilsocket/bettercap-ng/core" ) +type OnHostResolvedCallback func(e *Endpoint) type Endpoint struct { - IP net.IP - HW net.HardwareAddr - IpAddress string - SubnetBits uint32 - IpAddressUint32 uint32 - HwAddress string - Hostname string - Vendor string + IP net.IP `json:"-"` + HW net.HardwareAddr `json:"-"` + IpAddress string `json:"address"` + SubnetBits uint32 `json:"-"` + IpAddressUint32 uint32 `json:"-"` + HwAddress string `json:"mac"` + Hostname string `json:"hostname"` + Vendor string `json:"vendor"` + ResolvedCallback OnHostResolvedCallback `json:"-"` } -type OnHostResolvedAction func(e *Endpoint) - func NewEndpointNoResolve(ip, mac, name string, bits uint32) *Endpoint { hw, err := net.ParseMAC(mac) if err != nil { @@ -28,14 +28,15 @@ func NewEndpointNoResolve(ip, mac, name string, bits uint32) *Endpoint { } e := &Endpoint{ - IP: net.ParseIP(ip), - HW: hw, - IpAddress: ip, - SubnetBits: bits, - IpAddressUint32: binary.BigEndian.Uint32(net.ParseIP(ip)[12:16]), - HwAddress: mac, - Hostname: name, - Vendor: OuiLookup(mac), + IP: net.ParseIP(ip), + HW: hw, + IpAddress: ip, + SubnetBits: bits, + IpAddressUint32: binary.BigEndian.Uint32(net.ParseIP(ip)[12:16]), + HwAddress: mac, + Hostname: name, + Vendor: OuiLookup(mac), + ResolvedCallback: nil, } return e @@ -48,7 +49,10 @@ func NewEndpoint(ip, mac string) *Endpoint { go func() { if names, err := net.LookupAddr(e.IpAddress); err == nil { e.Hostname = names[0] - log.Debugf("Endpoint %s is now known as %s\n", e.IpAddress, core.Green(e.Hostname)) + if e.ResolvedCallback != nil { + // log.Debugf("Endpoint %s is now known as %s\n", e.IpAddress, core.Green(e.Hostname)) + e.ResolvedCallback(e) + } } }() diff --git a/session/environment.go b/session/environment.go index 522d2b48..d1c9bc73 100644 --- a/session/environment.go +++ b/session/environment.go @@ -11,13 +11,15 @@ type Environment struct { Padding int `json:"-"` Storage map[string]string `json:"storage"` lock *sync.Mutex + sess *Session } -func NewEnvironment() *Environment { +func NewEnvironment(s *Session) *Environment { env := &Environment{ Padding: 0, Storage: make(map[string]string), lock: &sync.Mutex{}, + sess: s, } return env @@ -39,6 +41,14 @@ func (env *Environment) Set(name, value string) string { old, _ := env.Storage[name] env.Storage[name] = value + env.sess.Events.Add("env.change", struct { + name string + value string + }{ + name, + value, + }) + width := len(name) if width > env.Padding { env.Padding = width diff --git a/session/events.go b/session/events.go new file mode 100644 index 00000000..47c0ce4d --- /dev/null +++ b/session/events.go @@ -0,0 +1,53 @@ +package session + +import ( + "fmt" + "sync" + "time" + + "github.com/evilsocket/bettercap-ng/core" +) + +type Event struct { + Tag string `json:"tag"` + Time time.Time `json:"time"` + Data interface{} `json:"data"` +} + +func NewEvent(tag string, data interface{}) Event { + return Event{ + Tag: tag, + Time: time.Now(), + Data: data, + } +} + +func (e Event) Print() { + fmt.Printf("[%s] [%s] [%s] %+v\n", e.Time, core.Bold("event"), core.Green(e.Tag), e.Data) +} + +type EventPool struct { + events []Event + lock *sync.Mutex +} + +func NewEventPool() *EventPool { + return &EventPool{ + events: make([]Event, 0), + lock: &sync.Mutex{}, + } +} + +func (p *EventPool) Add(tag string, data interface{}) { + p.lock.Lock() + defer p.lock.Unlock() + e := NewEvent(tag, data) + p.events = append([]Event{e}, p.events...) + e.Print() +} + +func (p *EventPool) Events() []Event { + p.lock.Lock() + defer p.lock.Unlock() + return p.events +} diff --git a/session/module.go b/session/module.go index e7a58b09..5ae99dee 100644 --- a/session/module.go +++ b/session/module.go @@ -18,16 +18,18 @@ type Module interface { } type SessionModule struct { - Session *Session - Started bool - StatusLock *sync.Mutex + Name string `json:"name"` + Session *Session `json:"-"` + Started bool `json:"started"` + StatusLock *sync.Mutex `json:"-"` handlers []ModuleHandler params map[string]*ModuleParam } -func NewSessionModule(s *Session) SessionModule { +func NewSessionModule(name string, s *Session) SessionModule { m := SessionModule{ + Name: name, Session: s, Started: false, StatusLock: &sync.Mutex{}, @@ -70,6 +72,12 @@ func (m *SessionModule) SetRunning(running bool) { m.StatusLock.Lock() defer m.StatusLock.Unlock() m.Started = running + + if running { + m.Session.Events.Add("mod.started", m.Name) + } else { + m.Session.Events.Add("mod.stopped", m.Name) + } } func (m *SessionModule) OnSessionStarted(s *Session) { diff --git a/session/modules/api_rest.go b/session/modules/api_rest.go index a56a5442..3faf8d1c 100644 --- a/session/modules/api_rest.go +++ b/session/modules/api_rest.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net/http" + "strconv" "strings" "github.com/evilsocket/bettercap-ng/core" @@ -20,7 +21,7 @@ type RestAPI struct { func NewRestAPI(s *session.Session) *RestAPI { api := &RestAPI{ - SessionModule: session.NewSessionModule(s), + SessionModule: session.NewSessionModule("api.rest", s), server: &http.Server{}, username: "", password: "", @@ -58,6 +59,7 @@ func NewRestAPI(s *session.Session) *RestAPI { })) http.HandleFunc("/api/session", api.sessRoute) + http.HandleFunc("/api/events", api.eventsRoute) return api } @@ -113,6 +115,45 @@ func (api *RestAPI) sessRoute(w http.ResponseWriter, r *http.Request) { } } +func (api *RestAPI) eventsRoute(w http.ResponseWriter, r *http.Request) { + if api.checkAuth(w, r) == false { + return + } + + if r.Method == "GET" { + var err error + + events := api.Session.Events.Events() + nmax := len(events) + n := nmax + + keys, ok := r.URL.Query()["n"] + if len(keys) == 1 && ok { + sn := keys[0] + n, err = strconv.Atoi(sn) + if err == nil { + if n > nmax { + n = nmax + } + } else { + n = nmax + } + } + + js, err := json.Marshal(events[0:n]) + if err != nil { + log.Errorf("Error while returning events: %s", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.Write(js) + } else { + http.Error(w, "Not Found", 404) + } +} + func (api RestAPI) checkAuth(w http.ResponseWriter, r *http.Request) bool { if api.Authenticated(w, r) == false { log.Warningf("Unauthenticated access!") diff --git a/session/modules/arp_spoof.go b/session/modules/arp_spoof.go index 1a774a2b..a958bd7a 100644 --- a/session/modules/arp_spoof.go +++ b/session/modules/arp_spoof.go @@ -17,7 +17,7 @@ type ArpSpoofer struct { func NewArpSpoofer(s *session.Session) *ArpSpoofer { p := &ArpSpoofer{ - SessionModule: session.NewSessionModule(s), + SessionModule: session.NewSessionModule("arp.spoof", s), Done: make(chan bool), } diff --git a/session/modules/http_proxy.go b/session/modules/http_proxy.go index 43a56a30..4d63bac6 100644 --- a/session/modules/http_proxy.go +++ b/session/modules/http_proxy.go @@ -38,7 +38,7 @@ func (p HttpProxy) logAction(req *http.Request, jsres *JSResponse) { func NewHttpProxy(s *session.Session) *HttpProxy { p := &HttpProxy{ - SessionModule: session.NewSessionModule(s), + SessionModule: session.NewSessionModule("http.proxy", s), proxy: nil, address: "", redirection: nil, diff --git a/session/modules/net_probe.go b/session/modules/net_probe.go index cd21f0ff..2c4cbf80 100644 --- a/session/modules/net_probe.go +++ b/session/modules/net_probe.go @@ -15,7 +15,7 @@ type Prober struct { func NewProber(s *session.Session) *Prober { p := &Prober{ - SessionModule: session.NewSessionModule(s), + SessionModule: session.NewSessionModule("net.probe", s), } p.AddParam(session.NewIntParameter("net.probe.throttle", diff --git a/session/modules/net_recon.go b/session/modules/net_recon.go index 8ee7ccac..f3cb8690 100644 --- a/session/modules/net_recon.go +++ b/session/modules/net_recon.go @@ -18,7 +18,7 @@ type Discovery struct { func NewDiscovery(s *session.Session) *Discovery { d := &Discovery{ - SessionModule: session.NewSessionModule(s), + SessionModule: session.NewSessionModule("net.recon", s), refresh: 1, before: nil, diff --git a/session/modules/net_sniff.go b/session/modules/net_sniff.go index eda5b9e1..48d82179 100644 --- a/session/modules/net_sniff.go +++ b/session/modules/net_sniff.go @@ -117,7 +117,7 @@ type Sniffer struct { func NewSniffer(s *session.Session) *Sniffer { sniff := &Sniffer{ - SessionModule: session.NewSessionModule(s), + SessionModule: session.NewSessionModule("net.sniffer", s), Stats: nil, } diff --git a/session/session.go b/session/session.go index de64f448..30d0d187 100644 --- a/session/session.go +++ b/session/session.go @@ -32,25 +32,30 @@ type Session struct { Input *readline.Instance `json:"-"` Active bool `json:"active"` - // Watcher *discovery.Watcher CoreHandlers []CommandHandler `json:"-"` Modules []Module `json:"-"` HelpPadding int `json:"-"` + + Events *EventPool `json:"-"` } func New() (*Session, error) { var err error s := &Session{ - Env: NewEnvironment(), + Env: nil, Active: false, Queue: nil, CoreHandlers: make([]CommandHandler, 0), Modules: make([]Module, 0), HelpPadding: 0, + + Events: NewEventPool(), } + s.Env = NewEnvironment(s) + if s.Options, err = core.ParseOptions(); err != nil { return nil, err } @@ -265,6 +270,8 @@ func (s *Session) setupInput() error { } func (s *Session) Close() { + s.Events.Add("session.closing", nil) + for _, m := range s.Modules { m.OnSessionEnded(s) } @@ -311,7 +318,7 @@ func (s *Session) Start() error { log.Debugf("[%sgateway%s] %s\n", core.GREEN, core.RESET, s.Gateway) - s.Targets = NewTargets(s.Interface, s.Gateway) + s.Targets = NewTargets(s, s.Interface, s.Gateway) s.Firewall = firewall.Make() if err := s.setupInput(); err != nil { @@ -354,6 +361,8 @@ func (s *Session) Start() error { m.OnSessionStarted(s) } + s.Events.Add("session.started", nil) + return nil } diff --git a/session/targets.go b/session/targets.go index 426d27a1..a741b3e6 100644 --- a/session/targets.go +++ b/session/targets.go @@ -14,14 +14,16 @@ import ( var log = logging.MustGetLogger("mitm") type Targets struct { + Session *Session `json:"-"` Interface *net.Endpoint Gateway *net.Endpoint Targets map[string]*net.Endpoint lock sync.Mutex } -func NewTargets(iface, gateway *net.Endpoint) *Targets { +func NewTargets(s *Session, iface, gateway *net.Endpoint) *Targets { return &Targets{ + Session: s, Interface: iface, Gateway: gateway, Targets: make(map[string]*net.Endpoint), @@ -33,7 +35,7 @@ func (tp *Targets) Remove(ip, mac string) { defer tp.lock.Unlock() if e, found := tp.Targets[mac]; found { - log.Infof("[%slost%s] %s\n", core.RED, core.RESET, e) + tp.Session.Events.Add("target-lost", e) delete(tp.Targets, mac) return } @@ -69,9 +71,14 @@ func (tp *Targets) AddIfNotExist(ip, mac string) *net.Endpoint { } e := net.NewEndpoint(ip, mac) - log.Infof("[%snew%s] %s\n", core.GREEN, core.RESET, e) + e.ResolvedCallback = func(e *net.Endpoint) { + tp.Session.Events.Add("target.resolved", e) + } + tp.Targets[mac] = e + tp.Session.Events.Add("target.new", e) + return nil }