Commit 13d6cf85 authored by Sophie Brun's avatar Sophie Brun

New upstream version 2.22

parent f4269504
......@@ -27,7 +27,7 @@
[[projects]]
branch = "master"
digest = "1:a2c142e6c2aa1c71796c748bbe42d224e23d6638fd5b3ae153e70a4b08a8da4e"
digest = "1:881bb9d751b9408f038b83e9331ce3c57603710f3546f16e7d43b5c24e974f6d"
name = "github.com/bettercap/gatt"
packages = [
".",
......@@ -40,7 +40,7 @@
"xpc",
]
pruneopts = "UT"
revision = "277ee0d0ef94d26e3190252c59fa34dde0df4f26"
revision = "d1a17475747afe7c0d78813596d4e95801a5d592"
[[projects]]
branch = "master"
......@@ -184,6 +184,14 @@
pruneopts = "UT"
revision = "f16ca3b7b383d3f0373109cac19147de3e8ae2d1"
[[projects]]
digest = "1:7ad278b575635babef38e4ad4219500c299a58ea14b30eb21383d0efca00b369"
name = "github.com/kr/binarydist"
packages = ["."]
pruneopts = "UT"
revision = "88f551ae580780cc79d12ab4c218ba1ca346b83a"
version = "v0.1.0"
[[projects]]
digest = "1:4701b2acabe16722ecb1e387d39741a29269386bfc4ba6283ecda362d289eff1"
name = "github.com/malfunkt/iprange"
......@@ -333,6 +341,7 @@
"github.com/gorilla/websocket",
"github.com/inconshreveable/go-vhost",
"github.com/jpillora/go-tld",
"github.com/kr/binarydist",
"github.com/malfunkt/iprange",
"github.com/mdlayher/dhcp6",
"github.com/mdlayher/dhcp6/dhcp6opts",
......
......@@ -73,3 +73,7 @@
[prune]
go-tests = true
unused-packages = true
[[constraint]]
name = "github.com/kr/binarydist"
version = "0.1.0"
......@@ -2,7 +2,7 @@ package core
const (
Name = "bettercap"
Version = "2.21.1"
Version = "2.22"
Author = "Simone 'evilsocket' Margaritelli"
Website = "https://bettercap.org/"
)
package core
import (
"fmt"
"os/exec"
"sort"
......@@ -34,7 +33,7 @@ func HasBinary(executable string) bool {
return true
}
func ExecSilent(executable string, args []string) (string, error) {
func Exec(executable string, args []string) (string, error) {
path, err := exec.LookPath(executable)
if err != nil {
return "", err
......@@ -47,11 +46,3 @@ func ExecSilent(executable string, args []string) (string, error) {
return str.Trim(string(raw)), nil
}
}
func Exec(executable string, args []string) (string, error) {
out, err := ExecSilent(executable, args)
if err != nil {
fmt.Printf("ERROR for '%s %s': %s\n", executable, args, err)
}
return out, err
}
......@@ -41,7 +41,7 @@ func Make(iface *network.Endpoint) FirewallManager {
}
func (f PfFirewall) sysCtlRead(param string) (string, error) {
if out, err := core.ExecSilent("sysctl", []string{param}); err != nil {
if out, err := core.Exec("sysctl", []string{param}); err != nil {
return "", err
} else if m := sysCtlParser.FindStringSubmatch(out); len(m) == 3 && m[1] == param {
return m[2], nil
......@@ -52,7 +52,7 @@ func (f PfFirewall) sysCtlRead(param string) (string, error) {
func (f PfFirewall) sysCtlWrite(param string, value string) (string, error) {
args := []string{"-w", fmt.Sprintf("%s=%s", param, value)}
_, err := core.ExecSilent("sysctl", args)
_, err := core.Exec("sysctl", args)
if err != nil {
return "", err
}
......@@ -115,9 +115,9 @@ func (f PfFirewall) generateRule(r *Redirection) string {
func (f *PfFirewall) enable(enabled bool) {
f.enabled = enabled
if enabled {
core.ExecSilent("pfctl", []string{"-e"})
core.Exec("pfctl", []string{"-e"})
} else {
core.ExecSilent("pfctl", []string{"-d"})
core.Exec("pfctl", []string{"-d"})
}
}
......@@ -139,7 +139,7 @@ func (f PfFirewall) EnableRedirection(r *Redirection, enabled bool) error {
f.enable(true)
// load the rule
if _, err := core.ExecSilent("pfctl", []string{"-f", f.filename}); err != nil {
if _, err := core.Exec("pfctl", []string{"-f", f.filename}); err != nil {
return err
}
} else {
......
......@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
"sync"
"time"
"github.com/bettercap/bettercap/session"
......@@ -26,6 +27,17 @@ type RestAPI struct {
useWebsocket bool
upgrader websocket.Upgrader
quit chan bool
recClock int
recording bool
recTime int
loading bool
replaying bool
recordFileName string
recordWait *sync.WaitGroup
record *Record
recStarted time.Time
recStopped time.Time
}
func NewRestAPI(s *session.Session) *RestAPI {
......@@ -39,8 +51,28 @@ func NewRestAPI(s *session.Session) *RestAPI {
ReadBufferSize: 1024,
WriteBufferSize: 1024,
},
recClock: 1,
recording: false,
recTime: 0,
loading: false,
replaying: false,
recordFileName: "",
recordWait: &sync.WaitGroup{},
record: nil,
}
mod.State.Store("recording", &mod.recording)
mod.State.Store("rec_clock", &mod.recClock)
mod.State.Store("replaying", &mod.replaying)
mod.State.Store("loading", &mod.loading)
mod.State.Store("load_progress", 0)
mod.State.Store("rec_time", &mod.recTime)
mod.State.Store("rec_filename", &mod.recordFileName)
mod.State.Store("rec_frames", 0)
mod.State.Store("rec_cur_frame", 0)
mod.State.Store("rec_started", &mod.recStarted)
mod.State.Store("rec_stopped", &mod.recStopped)
mod.AddParam(session.NewStringParameter("api.rest.address",
"127.0.0.1",
session.IPv4Validator,
......@@ -93,6 +125,34 @@ func NewRestAPI(s *session.Session) *RestAPI {
return mod.Stop()
}))
mod.AddParam(session.NewIntParameter("api.rest.record.clock",
"1",
"Number of seconds to wait while recording with api.rest.record between one sample and the next one."))
mod.AddHandler(session.NewModuleHandler("api.rest.record off", "",
"Stop recording the session.",
func(args []string) error {
return mod.stopRecording()
}))
mod.AddHandler(session.NewModuleHandler("api.rest.record FILENAME", `api\.rest\.record (.+)`,
"Start polling the rest API periodically recording each sample in a compressed file that can be later replayed.",
func(args []string) error {
return mod.startRecording(args[0])
}))
mod.AddHandler(session.NewModuleHandler("api.rest.replay off", "",
"Stop replaying the recorded session.",
func(args []string) error {
return mod.stopReplay()
}))
mod.AddHandler(session.NewModuleHandler("api.rest.replay FILENAME", `api\.rest\.replay (.+)`,
"Start the rest API module in replay mode using FILENAME as the recorded session file, will revert to normal mode once the replay is over.",
func(args []string) error {
return mod.startReplay(args[0])
}))
return mod
}
......@@ -205,7 +265,9 @@ func (mod *RestAPI) Configure() error {
}
func (mod *RestAPI) Start() error {
if err := mod.Configure(); err != nil {
if mod.replaying {
return fmt.Errorf("the api is currently in replay mode, run api.rest.replay off before starting it")
} else if err := mod.Configure(); err != nil {
return err
}
......@@ -229,6 +291,12 @@ func (mod *RestAPI) Start() error {
}
func (mod *RestAPI) Stop() error {
if mod.recording {
mod.stopRecording()
} else if mod.replaying {
mod.stopReplay()
}
return mod.SetRunning(false, func() {
go func() {
mod.quit <- true
......
......@@ -36,7 +36,7 @@ func (mod *RestAPI) setAuthFailed(w http.ResponseWriter, r *http.Request) {
func (mod *RestAPI) toJSON(w http.ResponseWriter, o interface{}) {
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(o); err != nil {
mod.Error("error while encoding object to JSON: %v", err)
mod.Warning("error while encoding object to JSON: %v", err)
}
}
......@@ -64,8 +64,68 @@ func (mod *RestAPI) checkAuth(r *http.Request) bool {
return true
}
func (mod *RestAPI) patchFrame(buf []byte) (frame map[string]interface{}, err error) {
// this is ugly but necessary: since we're replaying, the
// api.rest state object is filled with *old* values (the
// recorded ones), but the UI needs updated values at least
// of that in order to understand that a replay is going on
// and where we are at it. So we need to parse the record
// back into a session object and update only the api.rest.state
frame = make(map[string]interface{})
if err = json.Unmarshal(buf, &frame); err != nil {
return
}
for _, i := range frame["modules"].([]interface{}) {
m := i.(map[string]interface{})
if m["name"] == "api.rest" {
state := m["state"].(map[string]interface{})
mod.State.Range(func(key interface{}, value interface{}) bool {
state[key.(string)] = value
return true
})
break
}
}
return
}
func (mod *RestAPI) showSession(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I)
if mod.replaying {
if !mod.record.Session.Over() {
from := mod.record.Session.CurFrame() - 1
q := r.URL.Query()
vals := q["from"]
if len(vals) > 0 {
if n, err := strconv.Atoi(vals[0]); err == nil {
from = n
}
}
mod.record.Session.SetFrom(from)
mod.Debug("replaying session %d of %d from %s",
mod.record.Session.CurFrame(),
mod.record.Session.Frames(),
mod.recordFileName)
mod.State.Store("rec_frames", mod.record.Session.Frames())
mod.State.Store("rec_cur_frame", mod.record.Session.CurFrame())
buf := mod.record.Session.Next()
if frame, err := mod.patchFrame(buf); err != nil {
mod.Error("%v", err)
} else {
mod.toJSON(w, frame)
return
}
} else {
mod.stopReplay()
}
}
mod.toJSON(w, mod.Session)
}
func (mod *RestAPI) showBLE(w http.ResponseWriter, r *http.Request) {
......@@ -73,8 +133,8 @@ func (mod *RestAPI) showBLE(w http.ResponseWriter, r *http.Request) {
mac := strings.ToLower(params["mac"])
if mac == "" {
mod.toJSON(w, session.I.BLE)
} else if dev, found := session.I.BLE.Get(mac); found {
mod.toJSON(w, mod.Session.BLE)
} else if dev, found := mod.Session.BLE.Get(mac); found {
mod.toJSON(w, dev)
} else {
http.Error(w, "Not Found", 404)
......@@ -86,8 +146,8 @@ func (mod *RestAPI) showHID(w http.ResponseWriter, r *http.Request) {
mac := strings.ToLower(params["mac"])
if mac == "" {
mod.toJSON(w, session.I.HID)
} else if dev, found := session.I.HID.Get(mac); found {
mod.toJSON(w, mod.Session.HID)
} else if dev, found := mod.Session.HID.Get(mac); found {
mod.toJSON(w, dev)
} else {
http.Error(w, "Not Found", 404)
......@@ -95,19 +155,19 @@ func (mod *RestAPI) showHID(w http.ResponseWriter, r *http.Request) {
}
func (mod *RestAPI) showEnv(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I.Env)
mod.toJSON(w, mod.Session.Env)
}
func (mod *RestAPI) showGateway(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I.Gateway)
mod.toJSON(w, mod.Session.Gateway)
}
func (mod *RestAPI) showInterface(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I.Interface)
mod.toJSON(w, mod.Session.Interface)
}
func (mod *RestAPI) showModules(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I.Modules)
mod.toJSON(w, mod.Session.Modules)
}
func (mod *RestAPI) showLAN(w http.ResponseWriter, r *http.Request) {
......@@ -115,8 +175,8 @@ func (mod *RestAPI) showLAN(w http.ResponseWriter, r *http.Request) {
mac := strings.ToLower(params["mac"])
if mac == "" {
mod.toJSON(w, session.I.Lan)
} else if host, found := session.I.Lan.Get(mac); found {
mod.toJSON(w, mod.Session.Lan)
} else if host, found := mod.Session.Lan.Get(mac); found {
mod.toJSON(w, host)
} else {
http.Error(w, "Not Found", 404)
......@@ -124,15 +184,15 @@ func (mod *RestAPI) showLAN(w http.ResponseWriter, r *http.Request) {
}
func (mod *RestAPI) showOptions(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I.Options)
mod.toJSON(w, mod.Session.Options)
}
func (mod *RestAPI) showPackets(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I.Queue)
mod.toJSON(w, mod.Session.Queue)
}
func (mod *RestAPI) showStartedAt(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I.StartedAt)
mod.toJSON(w, mod.Session.StartedAt)
}
func (mod *RestAPI) showWiFi(w http.ResponseWriter, r *http.Request) {
......@@ -140,10 +200,10 @@ func (mod *RestAPI) showWiFi(w http.ResponseWriter, r *http.Request) {
mac := strings.ToLower(params["mac"])
if mac == "" {
mod.toJSON(w, session.I.WiFi)
} else if station, found := session.I.WiFi.Get(mac); found {
mod.toJSON(w, mod.Session.WiFi)
} else if station, found := mod.Session.WiFi.Get(mac); found {
mod.toJSON(w, station)
} else if client, found := session.I.WiFi.GetClient(mac); found {
} else if client, found := mod.Session.WiFi.GetClient(mac); found {
mod.toJSON(w, client)
} else {
http.Error(w, "Not Found", 404)
......@@ -170,14 +230,9 @@ func (mod *RestAPI) runSessionCommand(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, APIResponse{Success: true})
}
func (mod *RestAPI) showEvents(w http.ResponseWriter, r *http.Request) {
var err error
if mod.useWebsocket {
mod.startStreamingEvents(w, r)
} else {
func (mod *RestAPI) getEvents(limit int) []session.Event {
events := make([]session.Event, 0)
for _, e := range session.I.Events.Sorted() {
for _, e := range mod.Session.Events.Sorted() {
if mod.Session.EventsIgnoreList.Ignored(e) == false {
events = append(events, e)
}
......@@ -187,25 +242,60 @@ func (mod *RestAPI) showEvents(w http.ResponseWriter, r *http.Request) {
nmax := nevents
n := nmax
if limit > 0 && limit < nmax {
n = limit
}
return events[nevents-n:]
}
func (mod *RestAPI) showEvents(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
vals := q["n"]
if mod.replaying {
if !mod.record.Events.Over() {
from := mod.record.Events.CurFrame() - 1
vals := q["from"]
if len(vals) > 0 {
n, err = strconv.Atoi(q["n"][0])
if err == nil {
if n > nmax {
n = nmax
if n, err := strconv.Atoi(vals[0]); err == nil {
from = n
}
}
mod.record.Events.SetFrom(from)
mod.Debug("replaying events %d of %d from %s",
mod.record.Events.CurFrame(),
mod.record.Events.Frames(),
mod.recordFileName)
buf := mod.record.Events.Next()
if _, err := w.Write(buf); err != nil {
mod.Error("%v", err)
} else {
n = nmax
return
}
} else {
mod.stopReplay()
}
}
if mod.useWebsocket {
mod.startStreamingEvents(w, r)
} else {
vals := q["n"]
limit := 0
if len(vals) > 0 {
if n, err := strconv.Atoi(q["n"][0]); err == nil {
limit = n
}
}
mod.toJSON(w, events[nevents-n:])
mod.toJSON(w, mod.getEvents(limit))
}
}
func (mod *RestAPI) clearEvents(w http.ResponseWriter, r *http.Request) {
session.I.Events.Clear()
mod.Session.Events.Clear()
}
func (mod *RestAPI) corsRoute(w http.ResponseWriter, r *http.Request) {
......@@ -227,10 +317,10 @@ func (mod *RestAPI) sessionRoute(w http.ResponseWriter, r *http.Request) {
return
}
session.I.Lock()
defer session.I.Unlock()
mod.Session.Lock()
defer mod.Session.Unlock()
path := r.URL.String()
path := r.URL.Path
switch {
case path == "/api/session":
mod.showSession(w, r)
......
package api_rest
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/evilsocket/islazy/fs"
)
var (
errNotRecording = errors.New("not recording")
)
func (mod *RestAPI) errAlreadyRecording() error {
return fmt.Errorf("the module is already recording to %s", mod.recordFileName)
}
func (mod *RestAPI) recordState() error {
mod.Session.Lock()
defer mod.Session.Unlock()
session := new(bytes.Buffer)
encoder := json.NewEncoder(session)
if err := encoder.Encode(mod.Session); err != nil {
return err
}
events := new(bytes.Buffer)
encoder = json.NewEncoder(events)
if err := encoder.Encode(mod.getEvents(0)); err != nil {
return err
}
return mod.record.NewState(session.Bytes(), events.Bytes())
}
func (mod *RestAPI) recorder() {
clock := time.Duration(mod.recClock) * time.Second
mod.recTime = 0
mod.recording = true
mod.replaying = false
mod.record = NewRecord(mod.recordFileName, &mod.SessionModule)
mod.Info("started recording to %s (clock %s) ...", mod.recordFileName, clock)
mod.recordWait.Add(1)
defer mod.recordWait.Done()
tick := time.NewTicker(1 * time.Second)
lastSampled := time.Time{}
for range tick.C {
if !mod.recording {
break
}
mod.recTime++
if time.Since(lastSampled) >= clock {
lastSampled = time.Now()
if err := mod.recordState(); err != nil {
mod.Error("error while recording: %s", err)
mod.recording = false
break
}
}
}
mod.Info("stopped recording to %s ...", mod.recordFileName)
}
func (mod *RestAPI) startRecording(filename string) (err error) {
if mod.recording {
return mod.errAlreadyRecording()
} else if mod.replaying {
return mod.errAlreadyReplaying()
} else if err, mod.recClock = mod.IntParam("api.rest.record.clock"); err != nil {
return err
} else if mod.recordFileName, err = fs.Expand(filename); err != nil {
return err
}
// we need the api itself up and running
if !mod.Running() {
if err = mod.Start(); err != nil {
return err
}
}
go mod.recorder()
return nil
}
func (mod *RestAPI) stopRecording() error {
if !mod.recording {
return errNotRecording
}
mod.recording = false
mod.recordWait.Wait()
err := mod.record.Flush()
mod.recordFileName = ""
mod.record = nil
return err
}
package api_rest
import (
"errors"
"fmt"
"time"
"github.com/evilsocket/islazy/fs"
)
var (
errNotReplaying = errors.New("not replaying")
)
func (mod *RestAPI) errAlreadyReplaying() error {
return fmt.Errorf("the module is already replaying a session from %s", mod.recordFileName)
}
func (mod *RestAPI) startReplay(filename string) (err error) {
if mod.replaying {
return mod.errAlreadyReplaying()
} else if mod.recording {
return mod.errAlreadyRecording()
} else if mod.recordFileName, err = fs.Expand(filename); err != nil {
return err
}
mod.State.Store("load_progress", 0)
defer func() {
mod.State.Store("load_progress", 100.0)
}()
mod.loading = true
defer func() {
mod.loading = false
}()
mod.Info("loading %s ...", mod.recordFileName)
start := time.Now()
if mod.record, err = LoadRecord(mod.recordFileName, &mod.SessionModule); err != nil {
return err
}
loadedIn := time.Since(start)
// we need the api itself up and running
if !mod.Running() {
if err := mod.Start(); err != nil {
return err
}
}
mod.recStarted = mod.record.Session.StartedAt()
mod.recStopped = mod.record.Session.StoppedAt()
duration := mod.recStopped.Sub(mod.recStarted)
mod.recTime = int(duration.Seconds())
mod.replaying = true
mod.recording = false
mod.Info("loaded %s of recording (%d frames) started at %s in %s, started replaying ...",
duration,
mod.record.Session.Frames(),
mod.recStarted,
loadedIn)