This commit is contained in:
Moritz Wagner 2019-03-31 23:04:15 +02:00
commit 00fe2eaf3d
78 changed files with 2615 additions and 411 deletions

View file

@ -18,6 +18,7 @@ RUN git clone https://github.com/bettercap/caplets /usr/local/share/bettercap/ca
# final stage # final stage
FROM alpine FROM alpine
RUN apk add --update ca-certificates
RUN apk add --no-cache --update bash iproute2 libpcap libusb-dev libnetfilter_queue wireless-tools RUN apk add --no-cache --update bash iproute2 libpcap libusb-dev libnetfilter_queue wireless-tools
COPY --from=build-env /go/src/github.com/bettercap/bettercap/bettercap /app/ COPY --from=build-env /go/src/github.com/bettercap/bettercap/bettercap /app/
COPY --from=build-env /go/src/github.com/bettercap/bettercap/caplets /app/ COPY --from=build-env /go/src/github.com/bettercap/bettercap/caplets /app/

19
Gopkg.lock generated
View file

@ -27,7 +27,7 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:a2c142e6c2aa1c71796c748bbe42d224e23d6638fd5b3ae153e70a4b08a8da4e" digest = "1:881bb9d751b9408f038b83e9331ce3c57603710f3546f16e7d43b5c24e974f6d"
name = "github.com/bettercap/gatt" name = "github.com/bettercap/gatt"
packages = [ packages = [
".", ".",
@ -40,7 +40,7 @@
"xpc", "xpc",
] ]
pruneopts = "UT" pruneopts = "UT"
revision = "277ee0d0ef94d26e3190252c59fa34dde0df4f26" revision = "d1a17475747afe7c0d78813596d4e95801a5d592"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -83,7 +83,7 @@
revision = "2ce16c963a8ac5bd6af851d4877e38701346983f" revision = "2ce16c963a8ac5bd6af851d4877e38701346983f"
[[projects]] [[projects]]
digest = "1:da1be9af4c3f262bd385cc722b08d98d4a47ddea57731e98b85c7ba21b35bc31" digest = "1:5247f5757ba31623c464db149dc272a37604516d8fbae1561b36e0d7cee070a5"
name = "github.com/evilsocket/islazy" name = "github.com/evilsocket/islazy"
packages = [ packages = [
"data", "data",
@ -96,8 +96,8 @@
"zip", "zip",
] ]
pruneopts = "UT" pruneopts = "UT"
revision = "6ef79e84ded205e48f296d21e3bc65d1cf4f5c78" revision = "c5c7a41bb1c20e6df409825ed24af8de5fb7fb70"
version = "v1.10.3" version = "v1.10.4"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -184,6 +184,14 @@
pruneopts = "UT" pruneopts = "UT"
revision = "f16ca3b7b383d3f0373109cac19147de3e8ae2d1" revision = "f16ca3b7b383d3f0373109cac19147de3e8ae2d1"
[[projects]]
digest = "1:7ad278b575635babef38e4ad4219500c299a58ea14b30eb21383d0efca00b369"
name = "github.com/kr/binarydist"
packages = ["."]
pruneopts = "UT"
revision = "88f551ae580780cc79d12ab4c218ba1ca346b83a"
version = "v0.1.0"
[[projects]] [[projects]]
digest = "1:4701b2acabe16722ecb1e387d39741a29269386bfc4ba6283ecda362d289eff1" digest = "1:4701b2acabe16722ecb1e387d39741a29269386bfc4ba6283ecda362d289eff1"
name = "github.com/malfunkt/iprange" name = "github.com/malfunkt/iprange"
@ -333,6 +341,7 @@
"github.com/gorilla/websocket", "github.com/gorilla/websocket",
"github.com/inconshreveable/go-vhost", "github.com/inconshreveable/go-vhost",
"github.com/jpillora/go-tld", "github.com/jpillora/go-tld",
"github.com/kr/binarydist",
"github.com/malfunkt/iprange", "github.com/malfunkt/iprange",
"github.com/mdlayher/dhcp6", "github.com/mdlayher/dhcp6",
"github.com/mdlayher/dhcp6/dhcp6opts", "github.com/mdlayher/dhcp6/dhcp6opts",

View file

@ -73,3 +73,7 @@
[prune] [prune]
go-tests = true go-tests = true
unused-packages = true unused-packages = true
[[constraint]]
name = "github.com/kr/binarydist"
version = "0.1.0"

View file

@ -8,11 +8,32 @@ import (
"github.com/evilsocket/islazy/fs" "github.com/evilsocket/islazy/fs"
) )
type Script struct {
Path string `json:"path"`
Size int64 `json:"size"`
Code []string `json:"code"`
}
func newScript(path string, size int64) Script {
return Script{
Path: path,
Size: size,
Code: make([]string, 0),
}
}
type Caplet struct { type Caplet struct {
Name string Script
Path string Name string `json:"name"`
Size int64 Scripts []Script `json:"scripts"`
Code []string }
func NewCaplet(name string, path string, size int64) Caplet {
return Caplet{
Script: newScript(path, size),
Name: name,
Scripts: make([]Script, 0),
}
} }
func (cap *Caplet) Eval(argv []string, lineCb func(line string) error) error { func (cap *Caplet) Eval(argv []string, lineCb func(line string) error) error {
@ -23,6 +44,10 @@ func (cap *Caplet) Eval(argv []string, lineCb func(line string) error) error {
// temporarily change the working directory // temporarily change the working directory
return fs.Chdir(filepath.Dir(cap.Path), func() error { return fs.Chdir(filepath.Dir(cap.Path), func() error {
for _, line := range cap.Code { for _, line := range cap.Code {
// skip empty lines and comments
if line == "" || line[0] == '#' {
continue
}
// replace $0 with argv[0], $1 with argv[1] and so on // replace $0 with argv[0], $1 with argv[1] and so on
for i, arg := range argv { for i, arg := range argv {
what := fmt.Sprintf("$%d", i) what := fmt.Sprintf("$%d", i)

View file

@ -2,6 +2,7 @@ package caplets
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
@ -16,22 +17,22 @@ var (
cacheLock = sync.Mutex{} cacheLock = sync.Mutex{}
) )
func List() []Caplet { func List() []*Caplet {
caplets := make([]Caplet, 0) caplets := make([]*Caplet, 0)
for _, searchPath := range LoadPaths { for _, searchPath := range LoadPaths {
files, _ := filepath.Glob(searchPath + "/*" + Suffix) files, _ := filepath.Glob(searchPath + "/*" + Suffix)
files2, _ := filepath.Glob(searchPath + "/*/*" + Suffix) files2, _ := filepath.Glob(searchPath + "/*/*" + Suffix)
for _, fileName := range append(files, files2...) { for _, fileName := range append(files, files2...) {
if stats, err := os.Stat(fileName); err == nil { if _, err := os.Stat(fileName); err == nil {
base := strings.Replace(fileName, searchPath+"/", "", -1) base := strings.Replace(fileName, searchPath+"/", "", -1)
base = strings.Replace(base, Suffix, "", -1) base = strings.Replace(base, Suffix, "", -1)
caplets = append(caplets, Caplet{ if err, caplet := Load(base); err != nil {
Name: base, fmt.Fprintf(os.Stderr, "wtf: %v\n", err)
Path: fileName, } else {
Size: stats.Size(), caplets = append(caplets, caplet)
}) }
} }
} }
} }
@ -51,6 +52,7 @@ func Load(name string) (error, *Caplet) {
return nil, caplet return nil, caplet
} }
baseName := name
names := []string{} names := []string{}
if !strings.HasSuffix(name, Suffix) { if !strings.HasSuffix(name, Suffix) {
name += Suffix name += Suffix
@ -64,23 +66,41 @@ func Load(name string) (error, *Caplet) {
names = append(names, name) names = append(names, name)
} }
for _, filename := range names { for _, fileName := range names {
if fs.Exists(filename) { if stats, err := os.Stat(fileName); err == nil {
cap := &Caplet{ cap := &Caplet{
Path: filename, Script: newScript(fileName, stats.Size()),
Code: make([]string, 0), Name: baseName,
Scripts: make([]Script, 0),
} }
cache[name] = cap cache[name] = cap
if reader, err := fs.LineReader(filename); err != nil { if reader, err := fs.LineReader(fileName); err != nil {
return fmt.Errorf("error reading caplet %s: %v", filename, err), nil return fmt.Errorf("error reading caplet %s: %v", fileName, err), nil
} else { } else {
for line := range reader { for line := range reader {
if line == "" || line[0] == '#' {
continue
}
cap.Code = append(cap.Code, line) cap.Code = append(cap.Code, line)
} }
// the caplet has a dedicated folder
if strings.Contains(baseName, "/") || strings.Contains(baseName, "\\") {
dir := filepath.Dir(fileName)
// get all secondary .cap and .js files
if files, err := ioutil.ReadDir(dir); err == nil && len(files) > 0 {
for _, f := range files {
subFileName := filepath.Join(dir, f.Name())
if subFileName != fileName && (strings.HasSuffix(subFileName, ".cap") || strings.HasSuffix(subFileName, ".js")) {
if reader, err := fs.LineReader(subFileName); err == nil {
script := newScript(subFileName, f.Size())
for line := range reader {
script.Code = append(script.Code, line)
}
cap.Scripts = append(cap.Scripts, script)
}
}
}
}
}
} }
return nil, cap return nil, cap

View file

@ -2,7 +2,7 @@ package core
const ( const (
Name = "bettercap" Name = "bettercap"
Version = "2.19" Version = "2.22"
Author = "Simone 'evilsocket' Margaritelli" Author = "Simone 'evilsocket' Margaritelli"
Website = "https://bettercap.org/" Website = "https://bettercap.org/"
) )

View file

@ -1,7 +1,6 @@
package core package core
import ( import (
"fmt"
"os/exec" "os/exec"
"sort" "sort"
@ -34,7 +33,7 @@ func HasBinary(executable string) bool {
return true return true
} }
func ExecSilent(executable string, args []string) (string, error) { func Exec(executable string, args []string) (string, error) {
path, err := exec.LookPath(executable) path, err := exec.LookPath(executable)
if err != nil { if err != nil {
return "", err return "", err
@ -47,11 +46,3 @@ func ExecSilent(executable string, args []string) (string, error) {
return str.Trim(string(raw)), nil 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
}

View file

@ -22,7 +22,7 @@ func ParseOptions() (Options, error) {
o := Options{ o := Options{
InterfaceName: flag.String("iface", "", "Network interface to bind to, if empty the default interface will be auto selected."), InterfaceName: flag.String("iface", "", "Network interface to bind to, if empty the default interface will be auto selected."),
Gateway: flag.String("gateway-override", "", "Use the provided IP address instead of the default gateway. If not specified or invalid, the default gateway will be used."), Gateway: flag.String("gateway-override", "", "Use the provided IP address instead of the default gateway. If not specified or invalid, the default gateway will be used."),
AutoStart: flag.String("autostart", "events.stream, net.recon", "Comma separated list of modules to auto start."), AutoStart: flag.String("autostart", "events.stream", "Comma separated list of modules to auto start."),
Caplet: flag.String("caplet", "", "Read commands from this file and execute them in the interactive session."), Caplet: flag.String("caplet", "", "Read commands from this file and execute them in the interactive session."),
Debug: flag.Bool("debug", false, "Print debug messages."), Debug: flag.Bool("debug", false, "Print debug messages."),
PrintVersion: flag.Bool("version", false, "Print the version and exit."), PrintVersion: flag.Bool("version", false, "Print the version and exit."),

View file

@ -41,7 +41,7 @@ func Make(iface *network.Endpoint) FirewallManager {
} }
func (f PfFirewall) sysCtlRead(param string) (string, error) { 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 return "", err
} else if m := sysCtlParser.FindStringSubmatch(out); len(m) == 3 && m[1] == param { } else if m := sysCtlParser.FindStringSubmatch(out); len(m) == 3 && m[1] == param {
return m[2], nil 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) { func (f PfFirewall) sysCtlWrite(param string, value string) (string, error) {
args := []string{"-w", fmt.Sprintf("%s=%s", param, value)} args := []string{"-w", fmt.Sprintf("%s=%s", param, value)}
_, err := core.ExecSilent("sysctl", args) _, err := core.Exec("sysctl", args)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -115,9 +115,9 @@ func (f PfFirewall) generateRule(r *Redirection) string {
func (f *PfFirewall) enable(enabled bool) { func (f *PfFirewall) enable(enabled bool) {
f.enabled = enabled f.enabled = enabled
if enabled { if enabled {
core.ExecSilent("pfctl", []string{"-e"}) core.Exec("pfctl", []string{"-e"})
} else { } 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) f.enable(true)
// load the rule // 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 return err
} }
} else { } else {

View file

@ -80,7 +80,7 @@ func (mod *AnyProxy) Configure() error {
var dstAddress string var dstAddress string
if mod.Running() { if mod.Running() {
return session.ErrAlreadyStarted return session.ErrAlreadyStarted(mod.Name())
} else if err, iface = mod.StringParam("any.proxy.iface"); err != nil { } else if err, iface = mod.StringParam("any.proxy.iface"); err != nil {
return err return err
} else if err, protocol = mod.StringParam("any.proxy.protocol"); err != nil { } else if err, protocol = mod.StringParam("any.proxy.protocol"); err != nil {

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"net/http" "net/http"
"sync"
"time" "time"
"github.com/bettercap/bettercap/session" "github.com/bettercap/bettercap/session"
@ -26,6 +27,17 @@ type RestAPI struct {
useWebsocket bool useWebsocket bool
upgrader websocket.Upgrader upgrader websocket.Upgrader
quit chan bool 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 { func NewRestAPI(s *session.Session) *RestAPI {
@ -39,10 +51,30 @@ func NewRestAPI(s *session.Session) *RestAPI {
ReadBufferSize: 1024, ReadBufferSize: 1024,
WriteBufferSize: 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", mod.AddParam(session.NewStringParameter("api.rest.address",
session.ParamIfaceAddress, "127.0.0.1",
session.IPv4Validator, session.IPv4Validator,
"Address to bind the API REST server to.")) "Address to bind the API REST server to."))
@ -93,6 +125,34 @@ func NewRestAPI(s *session.Session) *RestAPI {
return mod.Stop() 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 return mod
} }
@ -126,7 +186,7 @@ func (mod *RestAPI) Configure() error {
var port int var port int
if mod.Running() { if mod.Running() {
return session.ErrAlreadyStarted return session.ErrAlreadyStarted(mod.Name())
} else if err, ip = mod.StringParam("api.rest.address"); err != nil { } else if err, ip = mod.StringParam("api.rest.address"); err != nil {
return err return err
} else if err, port = mod.IntParam("api.rest.port"); err != nil { } else if err, port = mod.IntParam("api.rest.port"); err != nil {
@ -174,7 +234,10 @@ func (mod *RestAPI) Configure() error {
router.Methods("OPTIONS").HandlerFunc(mod.corsRoute) router.Methods("OPTIONS").HandlerFunc(mod.corsRoute)
router.HandleFunc("/api/file", mod.fileRoute)
router.HandleFunc("/api/events", mod.eventsRoute) router.HandleFunc("/api/events", mod.eventsRoute)
router.HandleFunc("/api/session", mod.sessionRoute) router.HandleFunc("/api/session", mod.sessionRoute)
router.HandleFunc("/api/session/ble", mod.sessionRoute) router.HandleFunc("/api/session/ble", mod.sessionRoute)
router.HandleFunc("/api/session/ble/{mac}", mod.sessionRoute) router.HandleFunc("/api/session/ble/{mac}", mod.sessionRoute)
@ -202,7 +265,9 @@ func (mod *RestAPI) Configure() error {
} }
func (mod *RestAPI) Start() 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 return err
} }
@ -226,6 +291,12 @@ func (mod *RestAPI) Start() error {
} }
func (mod *RestAPI) Stop() error { func (mod *RestAPI) Stop() error {
if mod.recording {
mod.stopRecording()
} else if mod.replaying {
mod.stopReplay()
}
return mod.SetRunning(false, func() { return mod.SetRunning(false, func() {
go func() { go func() {
mod.quit <- true mod.quit <- true

View file

@ -3,7 +3,11 @@ package api_rest
import ( import (
"crypto/subtle" "crypto/subtle"
"encoding/json" "encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http" "net/http"
"os"
"strconv" "strconv"
"strings" "strings"
@ -22,7 +26,7 @@ type APIResponse struct {
} }
func (mod *RestAPI) setAuthFailed(w http.ResponseWriter, r *http.Request) { func (mod *RestAPI) setAuthFailed(w http.ResponseWriter, r *http.Request) {
mod.Warning("Unauthorized authentication attempt from %s", r.RemoteAddr) mod.Warning("Unauthorized authentication attempt from %s to %s", r.RemoteAddr, r.URL.String())
w.Header().Set("WWW-Authenticate", `Basic realm="auth"`) w.Header().Set("WWW-Authenticate", `Basic realm="auth"`)
w.WriteHeader(401) w.WriteHeader(401)
@ -32,7 +36,7 @@ func (mod *RestAPI) setAuthFailed(w http.ResponseWriter, r *http.Request) {
func (mod *RestAPI) toJSON(w http.ResponseWriter, o interface{}) { func (mod *RestAPI) toJSON(w http.ResponseWriter, o interface{}) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(o); err != nil { 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)
} }
} }
@ -60,8 +64,68 @@ func (mod *RestAPI) checkAuth(r *http.Request) bool {
return true 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) { 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) { func (mod *RestAPI) showBLE(w http.ResponseWriter, r *http.Request) {
@ -69,8 +133,8 @@ func (mod *RestAPI) showBLE(w http.ResponseWriter, r *http.Request) {
mac := strings.ToLower(params["mac"]) mac := strings.ToLower(params["mac"])
if mac == "" { if mac == "" {
mod.toJSON(w, session.I.BLE) mod.toJSON(w, mod.Session.BLE)
} else if dev, found := session.I.BLE.Get(mac); found { } else if dev, found := mod.Session.BLE.Get(mac); found {
mod.toJSON(w, dev) mod.toJSON(w, dev)
} else { } else {
http.Error(w, "Not Found", 404) http.Error(w, "Not Found", 404)
@ -82,8 +146,8 @@ func (mod *RestAPI) showHID(w http.ResponseWriter, r *http.Request) {
mac := strings.ToLower(params["mac"]) mac := strings.ToLower(params["mac"])
if mac == "" { if mac == "" {
mod.toJSON(w, session.I.HID) mod.toJSON(w, mod.Session.HID)
} else if dev, found := session.I.HID.Get(mac); found { } else if dev, found := mod.Session.HID.Get(mac); found {
mod.toJSON(w, dev) mod.toJSON(w, dev)
} else { } else {
http.Error(w, "Not Found", 404) http.Error(w, "Not Found", 404)
@ -91,19 +155,19 @@ func (mod *RestAPI) showHID(w http.ResponseWriter, r *http.Request) {
} }
func (mod *RestAPI) showEnv(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) { 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) { 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) { 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) { func (mod *RestAPI) showLAN(w http.ResponseWriter, r *http.Request) {
@ -111,8 +175,8 @@ func (mod *RestAPI) showLAN(w http.ResponseWriter, r *http.Request) {
mac := strings.ToLower(params["mac"]) mac := strings.ToLower(params["mac"])
if mac == "" { if mac == "" {
mod.toJSON(w, session.I.Lan) mod.toJSON(w, mod.Session.Lan)
} else if host, found := session.I.Lan.Get(mac); found { } else if host, found := mod.Session.Lan.Get(mac); found {
mod.toJSON(w, host) mod.toJSON(w, host)
} else { } else {
http.Error(w, "Not Found", 404) http.Error(w, "Not Found", 404)
@ -120,15 +184,15 @@ func (mod *RestAPI) showLAN(w http.ResponseWriter, r *http.Request) {
} }
func (mod *RestAPI) showOptions(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) { 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) { 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) { func (mod *RestAPI) showWiFi(w http.ResponseWriter, r *http.Request) {
@ -136,10 +200,10 @@ func (mod *RestAPI) showWiFi(w http.ResponseWriter, r *http.Request) {
mac := strings.ToLower(params["mac"]) mac := strings.ToLower(params["mac"])
if mac == "" { if mac == "" {
mod.toJSON(w, session.I.WiFi) mod.toJSON(w, mod.Session.WiFi)
} else if station, found := session.I.WiFi.Get(mac); found { } else if station, found := mod.Session.WiFi.Get(mac); found {
mod.toJSON(w, station) 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) mod.toJSON(w, client)
} else { } else {
http.Error(w, "Not Found", 404) http.Error(w, "Not Found", 404)
@ -154,43 +218,84 @@ func (mod *RestAPI) runSessionCommand(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Bad Request", 400) http.Error(w, "Bad Request", 400)
} else if err = json.NewDecoder(r.Body).Decode(&cmd); err != nil { } else if err = json.NewDecoder(r.Body).Decode(&cmd); err != nil {
http.Error(w, "Bad Request", 400) http.Error(w, "Bad Request", 400)
} else if err = session.I.Run(cmd.Command); err != nil {
http.Error(w, err.Error(), 400)
} else {
mod.toJSON(w, APIResponse{Success: true})
} }
for _, aCommand := range session.ParseCommands(cmd.Command) {
if err = mod.Session.Run(aCommand); err != nil {
http.Error(w, err.Error(), 400)
return
}
}
mod.toJSON(w, APIResponse{Success: true})
}
func (mod *RestAPI) getEvents(limit int) []session.Event {
events := make([]session.Event, 0)
for _, e := range mod.Session.Events.Sorted() {
if mod.Session.EventsIgnoreList.Ignored(e) == false {
events = append(events, e)
}
}
nevents := len(events)
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) { func (mod *RestAPI) showEvents(w http.ResponseWriter, r *http.Request) {
var err error q := r.URL.Query()
if mod.replaying {
if !mod.record.Events.Over() {
from := mod.record.Events.CurFrame() - 1
vals := q["from"]
if len(vals) > 0 {
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 {
return
}
} else {
mod.stopReplay()
}
}
if mod.useWebsocket { if mod.useWebsocket {
mod.startStreamingEvents(w, r) mod.startStreamingEvents(w, r)
} else { } else {
events := session.I.Events.Sorted()
nevents := len(events)
nmax := nevents
n := nmax
q := r.URL.Query()
vals := q["n"] vals := q["n"]
limit := 0
if len(vals) > 0 { if len(vals) > 0 {
n, err = strconv.Atoi(q["n"][0]) if n, err := strconv.Atoi(q["n"][0]); err == nil {
if err == nil { limit = n
if n > nmax {
n = nmax
}
} else {
n = nmax
} }
} }
mod.toJSON(w, events[nevents-n:]) mod.toJSON(w, mod.getEvents(limit))
} }
} }
func (mod *RestAPI) clearEvents(w http.ResponseWriter, r *http.Request) { 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) { func (mod *RestAPI) corsRoute(w http.ResponseWriter, r *http.Request) {
@ -212,10 +317,10 @@ func (mod *RestAPI) sessionRoute(w http.ResponseWriter, r *http.Request) {
return return
} }
session.I.Lock() mod.Session.Lock()
defer session.I.Unlock() defer mod.Session.Unlock()
path := r.URL.String() path := r.URL.Path
switch { switch {
case path == "/api/session": case path == "/api/session":
mod.showSession(w, r) mod.showSession(w, r)
@ -258,6 +363,44 @@ func (mod *RestAPI) sessionRoute(w http.ResponseWriter, r *http.Request) {
} }
} }
func (mod *RestAPI) readFile(fileName string, w http.ResponseWriter, r *http.Request) {
fp, err := os.Open(fileName)
if err != nil {
msg := fmt.Sprintf("could not open %s for reading: %s", fileName, err)
mod.Debug(msg)
http.Error(w, msg, 404)
return
}
defer fp.Close()
w.Header().Set("Content-type", "application/octet-stream")
io.Copy(w, fp)
}
func (mod *RestAPI) writeFile(fileName string, w http.ResponseWriter, r *http.Request) {
data, err := ioutil.ReadAll(r.Body)
if err != nil {
msg := fmt.Sprintf("invalid file upload: %s", err)
mod.Warning(msg)
http.Error(w, msg, 404)
return
}
err = ioutil.WriteFile(fileName, data, 0666)
if err != nil {
msg := fmt.Sprintf("can't write to %s: %s", fileName, err)
mod.Warning(msg)
http.Error(w, msg, 404)
return
}
mod.toJSON(w, APIResponse{
Success: true,
Message: fmt.Sprintf("%s created", fileName),
})
}
func (mod *RestAPI) eventsRoute(w http.ResponseWriter, r *http.Request) { func (mod *RestAPI) eventsRoute(w http.ResponseWriter, r *http.Request) {
mod.setSecurityHeaders(w) mod.setSecurityHeaders(w)
@ -274,3 +417,22 @@ func (mod *RestAPI) eventsRoute(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Bad Request", 400) http.Error(w, "Bad Request", 400)
} }
} }
func (mod *RestAPI) fileRoute(w http.ResponseWriter, r *http.Request) {
mod.setSecurityHeaders(w)
if !mod.checkAuth(r) {
mod.setAuthFailed(w, r)
return
}
fileName := r.URL.Query().Get("name")
if fileName != "" && r.Method == "GET" {
mod.readFile(fileName, w, r)
} else if fileName != "" && r.Method == "POST" {
mod.writeFile(fileName, w, r)
} else {
http.Error(w, "Bad Request", 400)
}
}

View file

@ -0,0 +1,116 @@
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
}

View file

@ -0,0 +1,81 @@
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)
return nil
}
func (mod *RestAPI) stopReplay() error {
if !mod.replaying {
return errNotReplaying
}
mod.replaying = false
mod.Info("stopped replaying from %s ...", mod.recordFileName)
mod.recordFileName = ""
return nil
}

298
modules/api_rest/record.go Normal file
View file

@ -0,0 +1,298 @@
package api_rest
import (
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"sync"
"time"
"github.com/bettercap/bettercap/session"
"github.com/evilsocket/islazy/fs"
"github.com/kr/binarydist"
)
type patch []byte
type frame []byte
type progressCallback func(done int)
type RecordEntry struct {
sync.Mutex
Data []byte `json:"data"`
Cur []byte `json:"-"`
States []patch `json:"states"`
NumStates int `json:"-"`
CurState int `json:"-"`
frames []frame
progress progressCallback
}
func NewRecordEntry(progress progressCallback) *RecordEntry {
return &RecordEntry{
Data: nil,
Cur: nil,
States: make([]patch, 0),
NumStates: 0,
CurState: 0,
frames: nil,
progress: progress,
}
}
func (e *RecordEntry) AddState(state []byte) error {
e.Lock()
defer e.Unlock()
// set reference state
if e.Data == nil {
e.Data = state
} else {
// create a patch
oldReader := bytes.NewReader(e.Cur)
newReader := bytes.NewReader(state)
writer := new(bytes.Buffer)
if err := binarydist.Diff(oldReader, newReader, writer); err != nil {
return err
}
e.States = append(e.States, patch(writer.Bytes()))
e.NumStates++
e.CurState = 0
}
e.Cur = state
return nil
}
func (e *RecordEntry) Reset() {
e.Lock()
defer e.Unlock()
e.Cur = e.Data
e.NumStates = len(e.States)
e.CurState = 0
}
func (e *RecordEntry) Compile() error {
e.Lock()
defer e.Unlock()
// reset the state
e.Cur = e.Data
e.NumStates = len(e.States)
e.CurState = 0
e.frames = make([]frame, e.NumStates+1)
// first is the master frame
e.frames[0] = frame(e.Data)
// precompute frames so they can be accessed by index
for i := 0; i < e.NumStates; i++ {
patch := e.States[i]
oldReader := bytes.NewReader(e.Cur)
patchReader := bytes.NewReader(patch)
newWriter := new(bytes.Buffer)
if err := binarydist.Patch(oldReader, newWriter, patchReader); err != nil {
return err
}
e.Cur = newWriter.Bytes()
e.frames[i+1] = e.Cur
e.progress(1)
}
e.progress(1)
return nil
}
func (e *RecordEntry) Frames() int {
e.Lock()
defer e.Unlock()
// master + sub states
return e.NumStates + 1
}
func (e *RecordEntry) CurFrame() int {
e.Lock()
defer e.Unlock()
return e.CurState + 1
}
func (e *RecordEntry) SetFrom(from int) {
e.Lock()
defer e.Unlock()
e.CurState = from
}
func (e *RecordEntry) Over() bool {
e.Lock()
defer e.Unlock()
return e.CurState > e.NumStates
}
func (e *RecordEntry) Next() []byte {
e.Lock()
defer e.Unlock()
cur := e.CurState
e.CurState++
return e.frames[cur]
}
func (e *RecordEntry) TimeOf(idx int) time.Time {
e.Lock()
defer e.Unlock()
buf := e.frames[idx]
frame := make(map[string]interface{})
if err := json.Unmarshal(buf, &frame); err != nil {
fmt.Printf("%v\n", err)
return time.Time{}
} else if tm, err := time.Parse(time.RFC3339, frame["polled_at"].(string)); err != nil {
fmt.Printf("%v\n", err)
return time.Time{}
} else {
return tm
}
}
func (e *RecordEntry) StartedAt() time.Time {
return e.TimeOf(0)
}
func (e *RecordEntry) StoppedAt() time.Time {
return e.TimeOf(e.NumStates)
}
func (e *RecordEntry) Duration() time.Duration {
return e.StoppedAt().Sub(e.StartedAt())
}
// the Record object represents a recorded session
type Record struct {
sync.Mutex
mod *session.SessionModule `json:"-"`
fileName string `json:"-"`
done int `json:"-"`
total int `json:"-"`
progress float64 `json:"-"`
Session *RecordEntry `json:"session"`
Events *RecordEntry `json:"events"`
}
func NewRecord(fileName string, mod *session.SessionModule) *Record {
r := &Record{
fileName: fileName,
mod: mod,
}
r.Session = NewRecordEntry(r.onProgress)
r.Events = NewRecordEntry(r.onProgress)
return r
}
func (r *Record) onProgress(done int) {
r.done += done
r.progress = float64(r.done) / float64(r.total) * 100.0
r.mod.State.Store("load_progress", r.progress)
}
func LoadRecord(fileName string, mod *session.SessionModule) (*Record, error) {
if !fs.Exists(fileName) {
return nil, fmt.Errorf("%s does not exist", fileName)
}
compressed, err := ioutil.ReadFile(fileName)
if err != nil {
return nil, fmt.Errorf("error while reading %s: %s", fileName, err)
}
decompress, err := gzip.NewReader(bytes.NewReader(compressed))
if err != nil {
return nil, fmt.Errorf("error while reading gzip file %s: %s", fileName, err)
}
defer decompress.Close()
raw, err := ioutil.ReadAll(decompress)
if err != nil {
return nil, fmt.Errorf("error while decompressing %s: %s", fileName, err)
}
rec := &Record{}
decoder := json.NewDecoder(bytes.NewReader(raw))
if err = decoder.Decode(rec); err != nil {
return nil, fmt.Errorf("error while parsing %s: %s", fileName, err)
}
rec.fileName = fileName
rec.mod = mod
rec.Session.NumStates = len(rec.Session.States)
rec.Session.progress = rec.onProgress
rec.Events.NumStates = len(rec.Events.States)
rec.Events.progress = rec.onProgress
rec.done = 0
rec.total = rec.Session.NumStates + rec.Events.NumStates + 2
rec.progress = 0.0
// reset state and precompute frames
if err = rec.Session.Compile(); err != nil {
return nil, err
} else if err = rec.Events.Compile(); err != nil {
return nil, err
}
return rec, nil
}
func (r *Record) NewState(session []byte, events []byte) error {
if err := r.Session.AddState(session); err != nil {
return err
} else if err := r.Events.AddState(events); err != nil {
return err
}
return r.Flush()
}
func (r *Record) save() error {
buf := new(bytes.Buffer)
encoder := json.NewEncoder(buf)
if err := encoder.Encode(r); err != nil {
return err
}
data := buf.Bytes()
compressed := new(bytes.Buffer)
compress := gzip.NewWriter(compressed)
if _, err := compress.Write(data); err != nil {
return err
} else if err = compress.Flush(); err != nil {
return err
} else if err = compress.Close(); err != nil {
return err
}
return ioutil.WriteFile(r.fileName, compressed.Bytes(), os.ModePerm)
}
func (r *Record) Flush() error {
r.Lock()
defer r.Unlock()
return r.save()
}

View file

@ -127,9 +127,14 @@ func (mod *ArpSpoofer) Start() error {
return err return err
} }
nTargets := len(mod.addresses) + len(mod.macs)
if nTargets == 0 {
mod.Warning("list of targets is empty, module not starting.")
return nil
}
return mod.SetRunning(true, func() { return mod.SetRunning(true, func() {
neighbours := []net.IP{} neighbours := []net.IP{}
nTargets := len(mod.addresses) + len(mod.macs)
if mod.internal { if mod.internal {
list, _ := iprange.ParseList(mod.Session.Interface.CIDR()) list, _ := iprange.ParseList(mod.Session.Interface.CIDR())

View file

@ -20,6 +20,7 @@ import (
type BLERecon struct { type BLERecon struct {
session.SessionModule session.SessionModule
deviceId int
gattDevice gatt.Device gattDevice gatt.Device
currDevice *network.BLEDevice currDevice *network.BLEDevice
writeUUID *gatt.UUID writeUUID *gatt.UUID
@ -34,6 +35,7 @@ type BLERecon struct {
func NewBLERecon(s *session.Session) *BLERecon { func NewBLERecon(s *session.Session) *BLERecon {
mod := &BLERecon{ mod := &BLERecon{
SessionModule: session.NewSessionModule("ble.recon", s), SessionModule: session.NewSessionModule("ble.recon", s),
deviceId: -1,
gattDevice: nil, gattDevice: nil,
quit: make(chan bool), quit: make(chan bool),
done: make(chan bool), done: make(chan bool),
@ -42,6 +44,8 @@ func NewBLERecon(s *session.Session) *BLERecon {
connected: false, connected: false,
} }
mod.InitState("scanning")
mod.selector = utils.ViewSelectorFor(&mod.SessionModule, mod.selector = utils.ViewSelectorFor(&mod.SessionModule,
"ble.show", "ble.show",
[]string{"rssi", "mac", "seen"}, "rssi asc") []string{"rssi", "mac", "seen"}, "rssi asc")
@ -108,6 +112,10 @@ func NewBLERecon(s *session.Session) *BLERecon {
mod.AddHandler(write) mod.AddHandler(write)
mod.AddParam(session.NewIntParameter("ble.device",
fmt.Sprintf("%d", mod.deviceId),
"Index of the HCI device to use, -1 to autodetect."))
return mod return mod
} }
@ -138,13 +146,23 @@ func (w dummyWriter) Write(p []byte) (n int, err error) {
func (mod *BLERecon) Configure() (err error) { func (mod *BLERecon) Configure() (err error) {
if mod.Running() { if mod.Running() {
return session.ErrAlreadyStarted return session.ErrAlreadyStarted(mod.Name())
} else if mod.gattDevice == nil { } else if mod.gattDevice == nil {
mod.Debug("initializing device ...") if err, mod.deviceId = mod.IntParam("ble.device"); err != nil {
return err
}
mod.Debug("initializing device (id:%d) ...", mod.deviceId)
golog.SetFlags(0) golog.SetFlags(0)
golog.SetOutput(dummyWriter{mod}) golog.SetOutput(dummyWriter{mod})
if mod.gattDevice, err = gatt.NewDevice(defaultBLEClientOptions...); err != nil {
options := []gatt.Option{
gatt.LnxMaxConnections(255),
gatt.LnxDeviceID(mod.deviceId, true),
}
if mod.gattDevice, err = gatt.NewDevice(options...); err != nil {
mod.Debug("error while creating new gatt device: %v", err) mod.Debug("error while creating new gatt device: %v", err)
return err return err
} }
@ -171,18 +189,20 @@ func (mod *BLERecon) Start() error {
<-mod.quit <-mod.quit
mod.Info("stopping scan ...") if mod.gattDevice != nil {
mod.Info("stopping scan ...")
if mod.currDevice != nil && mod.currDevice.Device != nil && mod.gattDevice != nil { if mod.currDevice != nil && mod.currDevice.Device != nil {
mod.Debug("resetting connection with %v", mod.currDevice.Device) mod.Debug("resetting connection with %v", mod.currDevice.Device)
mod.gattDevice.CancelConnection(mod.currDevice.Device) mod.gattDevice.CancelConnection(mod.currDevice.Device)
} }
mod.Debug("stopping device") mod.Debug("stopping device")
if err := mod.gattDevice.Stop(); err != nil { if err := mod.gattDevice.Stop(); err != nil {
mod.Warning("error while stopping device: %v", err) mod.Warning("error while stopping device: %v", err)
} else { } else {
mod.Debug("gatt device closed") mod.Debug("gatt device closed")
}
} }
mod.done <- true mod.done <- true
@ -196,6 +216,7 @@ func (mod *BLERecon) Stop() error {
mod.Debug("module stopped, cleaning state") mod.Debug("module stopped, cleaning state")
mod.gattDevice = nil mod.gattDevice = nil
mod.setCurrentDevice(nil) mod.setCurrentDevice(nil)
mod.ResetState()
}) })
} }
@ -216,6 +237,7 @@ func (mod *BLERecon) pruner() {
func (mod *BLERecon) setCurrentDevice(dev *network.BLEDevice) { func (mod *BLERecon) setCurrentDevice(dev *network.BLEDevice) {
mod.connected = false mod.connected = false
mod.currDevice = dev mod.currDevice = dev
mod.State.Store("scanning", dev)
} }
func (mod *BLERecon) writeBuffer(mac string, uuid gatt.UUID, data []byte) error { func (mod *BLERecon) writeBuffer(mac string, uuid gatt.UUID, data []byte) error {
@ -233,7 +255,7 @@ func (mod *BLERecon) enumAllTheThings(mac string) error {
} }
mod.setCurrentDevice(dev) mod.setCurrentDevice(dev)
if err := mod.Configure(); err != nil && err != session.ErrAlreadyStarted { if err := mod.Configure(); err != nil && err.Error() != session.ErrAlreadyStarted("ble.recon").Error() {
return err return err
} }

View file

@ -67,7 +67,8 @@ func (mod *BLERecon) onPeriphConnected(p gatt.Peripheral, err error) {
mod.Info("connected, enumerating all the things for %s!", p.ID()) mod.Info("connected, enumerating all the things for %s!", p.ID())
services, err := p.DiscoverServices(nil) services, err := p.DiscoverServices(nil)
if err != nil { // https://github.com/bettercap/bettercap/issues/498
if err != nil && err.Error() != "success" {
mod.Error("error discovering services: %s", err) mod.Error("error discovering services: %s", err)
return return
} }

View file

@ -78,7 +78,7 @@ func (mod *DHCP6Spoofer) Configure() error {
var err error var err error
if mod.Running() { if mod.Running() {
return session.ErrAlreadyStarted return session.ErrAlreadyStarted(mod.Name())
} }
if mod.Handle, err = pcap.OpenLive(mod.Session.Interface.Name(), 65536, true, pcap.BlockForever); err != nil { if mod.Handle, err = pcap.OpenLive(mod.Session.Interface.Name(), 65536, true, pcap.BlockForever); err != nil {

View file

@ -133,7 +133,7 @@ func (mod *DNSSpoofer) Configure() error {
var proxyip string var proxyip string
if mod.Running() { if mod.Running() {
return session.ErrAlreadyStarted return session.ErrAlreadyStarted(mod.Name())
} else if mod.Handle, err = pcap.OpenLive(mod.Session.Interface.Name(), 65536, true, pcap.BlockForever); err != nil { } else if mod.Handle, err = pcap.OpenLive(mod.Session.Interface.Name(), 65536, true, pcap.BlockForever); err != nil {
return err return err
} else if err = mod.Handle.SetBPFFilter("udp and port 53"); err != nil { } else if err = mod.Handle.SetBPFFilter("udp and port 53"); err != nil {

View file

@ -46,7 +46,7 @@ func NewHostEntry(host string, address net.IP) HostEntry {
return entry return entry
} }
func HostsFromFile(filename string,defaultAddress net.IP) (err error, entries []HostEntry) { func HostsFromFile(filename string, defaultAddress net.IP) (err error, entries []HostEntry) {
input, err := os.Open(filename) input, err := os.Open(filename)
if err != nil { if err != nil {
return return

View file

@ -29,7 +29,6 @@ type EventsStream struct {
outputName string outputName string
output *os.File output *os.File
rotation rotation rotation rotation
ignoreList *IgnoreList
triggerList *TriggerList triggerList *TriggerList
waitFor string waitFor string
waitChan chan *session.Event waitChan chan *session.Event
@ -47,10 +46,11 @@ func NewEventsStream(s *session.Session) *EventsStream {
quit: make(chan bool), quit: make(chan bool),
waitChan: make(chan *session.Event), waitChan: make(chan *session.Event),
waitFor: "", waitFor: "",
ignoreList: NewIgnoreList(),
triggerList: NewTriggerList(), triggerList: NewTriggerList(),
} }
mod.State.Store("ignoring", &mod.Session.EventsIgnoreList)
mod.AddHandler(session.NewModuleHandler("events.stream on", "", mod.AddHandler(session.NewModuleHandler("events.stream on", "",
"Start events stream.", "Start events stream.",
func(args []string) error { func(args []string) error {
@ -127,7 +127,7 @@ func NewEventsStream(s *session.Session) *EventsStream {
ignore := session.NewModuleHandler("events.ignore FILTER", "events.ignore ([^\\s]+)", ignore := session.NewModuleHandler("events.ignore FILTER", "events.ignore ([^\\s]+)",
"Events with an identifier matching this filter will not be shown (use multiple times to add more filters).", "Events with an identifier matching this filter will not be shown (use multiple times to add more filters).",
func(args []string) error { func(args []string) error {
return mod.ignoreList.Add(args[0]) return mod.Session.EventsIgnoreList.Add(args[0])
}) })
ignore.Complete("events.ignore", s.EventsCompleter) ignore.Complete("events.ignore", s.EventsCompleter)
@ -137,7 +137,7 @@ func NewEventsStream(s *session.Session) *EventsStream {
include := session.NewModuleHandler("events.include FILTER", "events.include ([^\\s]+)", include := session.NewModuleHandler("events.include FILTER", "events.include ([^\\s]+)",
"Used to remove filters passed with the events.ignore command.", "Used to remove filters passed with the events.ignore command.",
func(args []string) error { func(args []string) error {
return mod.ignoreList.Remove(args[0]) return mod.Session.EventsIgnoreList.Remove(args[0])
}) })
include.Complete("events.include", s.EventsCompleter) include.Complete("events.include", s.EventsCompleter)
@ -147,13 +147,13 @@ func NewEventsStream(s *session.Session) *EventsStream {
mod.AddHandler(session.NewModuleHandler("events.filters", "", mod.AddHandler(session.NewModuleHandler("events.filters", "",
"Print the list of filters used to ignore events.", "Print the list of filters used to ignore events.",
func(args []string) error { func(args []string) error {
if mod.ignoreList.Empty() { if mod.Session.EventsIgnoreList.Empty() {
fmt.Printf("Ignore filters list is empty.\n") fmt.Printf("Ignore filters list is empty.\n")
} else { } else {
mod.ignoreList.RLock() mod.Session.EventsIgnoreList.RLock()
defer mod.ignoreList.RUnlock() defer mod.Session.EventsIgnoreList.RUnlock()
for _, filter := range mod.ignoreList.Filters() { for _, filter := range mod.Session.EventsIgnoreList.Filters() {
fmt.Printf(" '%s'\n", string(filter)) fmt.Printf(" '%s'\n", string(filter))
} }
} }
@ -163,7 +163,7 @@ func NewEventsStream(s *session.Session) *EventsStream {
mod.AddHandler(session.NewModuleHandler("events.filters.clear", "", mod.AddHandler(session.NewModuleHandler("events.filters.clear", "",
"Clear the list of filters passed with the events.ignore command.", "Clear the list of filters passed with the events.ignore command.",
func(args []string) error { func(args []string) error {
mod.ignoreList = NewIgnoreList() mod.Session.EventsIgnoreList.Clear()
return nil return nil
})) }))
@ -281,7 +281,7 @@ func (mod *EventsStream) Start() error {
mod.waitChan <- &e mod.waitChan <- &e
} }
if !mod.ignoreList.Ignored(e) { if !mod.Session.EventsIgnoreList.Ignored(e) {
mod.View(e, true) mod.View(e, true)
} }
@ -303,7 +303,7 @@ func (mod *EventsStream) Show(limit int) error {
selected := []session.Event{} selected := []session.Event{}
for i := range events { for i := range events {
e := events[num-1-i] e := events[num-1-i]
if !mod.ignoreList.Ignored(e) { if !mod.Session.EventsIgnoreList.Ignored(e) {
selected = append(selected, e) selected = append(selected, e)
if len(selected) == limit { if len(selected) == limit {
break break

View file

@ -72,7 +72,7 @@ func (mod *GPS) Author() string {
func (mod *GPS) Configure() (err error) { func (mod *GPS) Configure() (err error) {
if mod.Running() { if mod.Running() {
return session.ErrAlreadyStarted return session.ErrAlreadyStarted(mod.Name())
} else if err, mod.serialPort = mod.StringParam("gps.device"); err != nil { } else if err, mod.serialPort = mod.StringParam("gps.device"); err != nil {
return err return err
} else if err, mod.baudRate = mod.IntParam("gps.baudrate"); err != nil { } else if err, mod.baudRate = mod.IntParam("gps.baudrate"); err != nil {
@ -126,11 +126,14 @@ func (mod *GPS) Start() error {
return mod.SetRunning(true, func() { return mod.SetRunning(true, func() {
defer mod.serial.Close() defer mod.serial.Close()
mod.Info("started on port %s ...", mod.serialPort)
for mod.Running() { for mod.Running() {
if line, err := mod.readLine(); err == nil { if line, err := mod.readLine(); err == nil {
if s, err := nmea.Parse(line); err == nil { if s, err := nmea.Parse(line); err == nil {
// http://aprs.gids.nl/nmea/#gga // http://aprs.gids.nl/nmea/#gga
if m, ok := s.(nmea.GNGGA); ok { if m, ok := s.(nmea.GNGGA); ok {
mod.Session.GPS.Updated = time.Now()
mod.Session.GPS.Latitude = m.Latitude mod.Session.GPS.Latitude = m.Latitude
mod.Session.GPS.Longitude = m.Longitude mod.Session.GPS.Longitude = m.Longitude
mod.Session.GPS.FixQuality = m.FixQuality mod.Session.GPS.FixQuality = m.FixQuality
@ -139,6 +142,7 @@ func (mod *GPS) Start() error {
mod.Session.GPS.Altitude = m.Altitude mod.Session.GPS.Altitude = m.Altitude
mod.Session.GPS.Separation = m.Separation mod.Session.GPS.Separation = m.Separation
} else if m, ok := s.(nmea.GPGGA); ok { } else if m, ok := s.(nmea.GPGGA); ok {
mod.Session.GPS.Updated = time.Now()
mod.Session.GPS.Latitude = m.Latitude mod.Session.GPS.Latitude = m.Latitude
mod.Session.GPS.Longitude = m.Longitude mod.Session.GPS.Longitude = m.Longitude
mod.Session.GPS.FixQuality = m.FixQuality mod.Session.GPS.FixQuality = m.FixQuality

View file

@ -63,6 +63,10 @@ func NewHIDRecon(s *session.Session) *HIDRecon {
scriptPath: "", scriptPath: "",
} }
mod.State.Store("sniffing", &mod.sniffAddr)
mod.State.Store("injecting", &mod.inInjectMode)
mod.State.Store("layouts", SupportedLayouts())
mod.AddHandler(session.NewModuleHandler("hid.recon on", "", mod.AddHandler(session.NewModuleHandler("hid.recon on", "",
"Start scanning for HID devices on the 2.4Ghz spectrum.", "Start scanning for HID devices on the 2.4Ghz spectrum.",
func(args []string) error { func(args []string) error {
@ -159,7 +163,7 @@ func (mod *HIDRecon) Configure() error {
var n int var n int
if mod.Running() { if mod.Running() {
return session.ErrAlreadyStarted return session.ErrAlreadyStarted(mod.Name())
} }
if err, mod.useLNA = mod.BoolParam("hid.lna"); err != nil { if err, mod.useLNA = mod.BoolParam("hid.lna"); err != nil {
@ -200,10 +204,20 @@ func (mod *HIDRecon) Configure() error {
return nil return nil
} }
func (mod *HIDRecon) forceStop() error {
return mod.SetRunning(false, func() {
if mod.dongle != nil {
mod.dongle.Close()
mod.Debug("device closed")
}
})
}
func (mod *HIDRecon) Stop() error { func (mod *HIDRecon) Stop() error {
return mod.SetRunning(false, func() { return mod.SetRunning(false, func() {
mod.waitGroup.Wait() mod.waitGroup.Wait()
mod.dongle.Close() if mod.dongle != nil {
mod.Debug("device closed") mod.dongle.Close()
mod.Debug("device closed")
}
}) })
} }

View file

@ -52,16 +52,15 @@ func (mod *HIDRecon) prepInjection() (error, *network.HIDDevice, []*Command) {
dev, found := mod.Session.HID.Get(mod.sniffAddr) dev, found := mod.Session.HID.Get(mod.sniffAddr)
if found == false { if found == false {
mod.Warning("device %s is not visible, will use HID type %s", mod.sniffAddr, tui.Yellow(mod.sniffType)) mod.Warning("device %s is not visible, will use HID type %s", mod.sniffAddr, tui.Yellow(mod.sniffType))
} else if dev.Type == network.HIDTypeUnknown {
mod.Warning("device %s type has not been detected yet, falling back to '%s'", mod.sniffAddr, tui.Yellow(mod.sniffType))
} }
var builder FrameBuilder var builder FrameBuilder
if found { if found && dev.Type != network.HIDTypeUnknown {
// get the device specific protocol handler // get the device specific protocol handler
builder, found = FrameBuilders[dev.Type] builder, found = FrameBuilders[dev.Type]
if found == false { if found == false {
if dev.Type == network.HIDTypeUnknown {
return errNoType(mod.sniffAddr), nil, nil
}
return errNotSupported(dev), nil, nil return errNotSupported(dev), nil, nil
} }
} else { } else {

View file

@ -4,6 +4,7 @@ import (
"time" "time"
"github.com/bettercap/nrf24" "github.com/bettercap/nrf24"
"github.com/google/gousb"
) )
func (mod *HIDRecon) doHopping() { func (mod *HIDRecon) doHopping() {
@ -26,7 +27,13 @@ func (mod *HIDRecon) doHopping() {
mod.channel = 1 mod.channel = 1
} }
if err := mod.dongle.SetChannel(mod.channel); err != nil { if err := mod.dongle.SetChannel(mod.channel); err != nil {
mod.Warning("error hopping on channel %d: %v", mod.channel, err) if err == gousb.ErrorNoDevice || err == gousb.TransferStall {
mod.Error("device disconnected, stopping module")
mod.forceStop()
return
} else {
mod.Warning("error hopping on channel %d: %v", mod.channel, err)
}
} else { } else {
mod.lastHop = time.Now() mod.lastHop = time.Now()
} }
@ -61,6 +68,25 @@ func (mod *HIDRecon) onDeviceDetected(buf []byte) {
} }
} }
var maxDeviceTTL = 20 * time.Minute
func (mod *HIDRecon) devPruner() {
mod.waitGroup.Add(1)
defer mod.waitGroup.Done()
mod.Debug("devices pruner started.")
for mod.Running() {
for _, dev := range mod.Session.HID.Devices() {
sinceLastSeen := time.Since(dev.LastSeen)
if sinceLastSeen > maxDeviceTTL {
mod.Debug("device %s not seen in %s, removing.", dev.Address, sinceLastSeen)
mod.Session.HID.Remove(dev.Address)
}
}
time.Sleep(30 * time.Second)
}
}
func (mod *HIDRecon) Start() error { func (mod *HIDRecon) Start() error {
if err := mod.Configure(); err != nil { if err := mod.Configure(); err != nil {
return err return err
@ -70,6 +96,8 @@ func (mod *HIDRecon) Start() error {
mod.waitGroup.Add(1) mod.waitGroup.Add(1)
defer mod.waitGroup.Done() defer mod.waitGroup.Done()
go mod.devPruner()
mod.Info("hopping on %d channels every %s", nrf24.TopChannel, mod.hopPeriod) mod.Info("hopping on %d channels every %s", nrf24.TopChannel, mod.hopPeriod)
for mod.Running() { for mod.Running() {
if mod.isSniffing() { if mod.isSniffing() {
@ -86,6 +114,11 @@ func (mod *HIDRecon) Start() error {
buf, err := mod.dongle.ReceivePayload() buf, err := mod.dongle.ReceivePayload()
if err != nil { if err != nil {
if err == gousb.ErrorNoDevice || err == gousb.TransferStall {
mod.Error("device disconnected, stopping module")
mod.forceStop()
return
}
mod.Warning("error receiving payload from channel %d: %v", mod.channel, err) mod.Warning("error receiving payload from channel %d: %v", mod.channel, err)
continue continue
} }

View file

@ -41,6 +41,7 @@ func (mod *HIDRecon) setSniffMode(mode string, silent bool) error {
mod.sniffAddrRaw = raw mod.sniffAddrRaw = raw
} }
} }
return nil return nil
} }

View file

@ -2,6 +2,8 @@ package http_proxy
import ( import (
"github.com/bettercap/bettercap/session" "github.com/bettercap/bettercap/session"
"github.com/evilsocket/islazy/str"
) )
type HttpProxy struct { type HttpProxy struct {
@ -38,6 +40,12 @@ func NewHttpProxy(s *session.Session) *HttpProxy {
"", "",
"URL, path or javascript code to inject into every HTML page.")) "URL, path or javascript code to inject into every HTML page."))
mod.AddParam(session.NewStringParameter("http.proxy.blacklist", "", "",
"Comma separated list of hostnames to skip while proxying (wildcard expressions can be used)."))
mod.AddParam(session.NewStringParameter("http.proxy.whitelist", "", "",
"Comma separated list of hostnames to proxy if the blacklist is used (wildcard expressions can be used)."))
mod.AddParam(session.NewBoolParameter("http.proxy.sslstrip", mod.AddParam(session.NewBoolParameter("http.proxy.sslstrip",
"false", "false",
"Enable or disable SSL stripping.")) "Enable or disable SSL stripping."))
@ -77,9 +85,11 @@ func (mod *HttpProxy) Configure() error {
var scriptPath string var scriptPath string
var stripSSL bool var stripSSL bool
var jsToInject string var jsToInject string
var blacklist string
var whitelist string
if mod.Running() { if mod.Running() {
return session.ErrAlreadyStarted return session.ErrAlreadyStarted(mod.Name())
} else if err, address = mod.StringParam("http.proxy.address"); err != nil { } else if err, address = mod.StringParam("http.proxy.address"); err != nil {
return err return err
} else if err, proxyPort = mod.IntParam("http.proxy.port"); err != nil { } else if err, proxyPort = mod.IntParam("http.proxy.port"); err != nil {
@ -92,8 +102,15 @@ func (mod *HttpProxy) Configure() error {
return err return err
} else if err, jsToInject = mod.StringParam("http.proxy.injectjs"); err != nil { } else if err, jsToInject = mod.StringParam("http.proxy.injectjs"); err != nil {
return err return err
} else if err, blacklist = mod.StringParam("http.proxy.blacklist"); err != nil {
return err
} else if err, whitelist = mod.StringParam("http.proxy.whitelist"); err != nil {
return err
} }
mod.proxy.Blacklist = str.Comma(blacklist)
mod.proxy.Whitelist = str.Comma(whitelist)
return mod.proxy.Configure(address, proxyPort, httpPort, scriptPath, jsToInject, stripSSL) return mod.proxy.Configure(address, proxyPort, httpPort, scriptPath, jsToInject, stripSSL)
} }

View file

@ -11,6 +11,7 @@ import (
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -41,6 +42,8 @@ type HTTPProxy struct {
Script *HttpProxyScript Script *HttpProxyScript
CertFile string CertFile string
KeyFile string KeyFile string
Blacklist []string
Whitelist []string
jsHook string jsHook string
isTLS bool isTLS bool
@ -61,13 +64,15 @@ func stripPort(s string) string {
func NewHTTPProxy(s *session.Session) *HTTPProxy { func NewHTTPProxy(s *session.Session) *HTTPProxy {
p := &HTTPProxy{ p := &HTTPProxy{
Name: "http.proxy", Name: "http.proxy",
Proxy: goproxy.NewProxyHttpServer(), Proxy: goproxy.NewProxyHttpServer(),
sess: s, sess: s,
stripper: NewSSLStripper(s, false), stripper: NewSSLStripper(s, false),
isTLS: false, isTLS: false,
Server: nil, Server: nil,
tag: session.AsTag("http.proxy"), Blacklist: make([]string, 0),
Whitelist: make([]string, 0),
tag: session.AsTag("http.proxy"),
} }
p.Proxy.Verbose = false p.Proxy.Verbose = false
@ -111,20 +116,41 @@ func (p *HTTPProxy) Fatal(format string, args ...interface{}) {
} }
func (p *HTTPProxy) doProxy(req *http.Request) bool { func (p *HTTPProxy) doProxy(req *http.Request) bool {
blacklist := []string{
"localhost",
"127.0.0.1",
}
if req.Host == "" { if req.Host == "" {
p.Error("got request with empty host: %v", req) p.Error("got request with empty host: %v", req)
return false return false
} }
host := strings.Split(req.Host, ":")[0] hostname := strings.Split(req.Host, ":")[0]
for _, blacklisted := range blacklist { for _, local := range []string{"localhost", "127.0.0.1"} {
if host == blacklisted { if hostname == local {
p.Error("got request with blacklisted host: %s", req.Host) p.Error("got request with localed host: %s", req.Host)
return false
}
}
return true
}
func (p *HTTPProxy) shouldProxy(req *http.Request) bool {
hostname := strings.Split(req.Host, ":")[0]
// check for the whitelist
for _, expr := range p.Whitelist {
if matched, err := filepath.Match(expr, hostname); err != nil {
p.Error("error while using proxy whitelist expression '%s': %v", expr, err)
} else if matched {
p.Debug("hostname '%s' matched whitelisted element '%s'", hostname, expr)
return true
}
}
// then the blacklist
for _, expr := range p.Blacklist {
if matched, err := filepath.Match(expr, hostname); err != nil {
p.Error("error while using proxy blacklist expression '%s': %v", expr, err)
} else if matched {
p.Debug("hostname '%s' matched blacklisted element '%s'", hostname, expr)
return false return false
} }
} }

View file

@ -19,32 +19,34 @@ func (p *HTTPProxy) fixRequestHeaders(req *http.Request) {
} }
func (p *HTTPProxy) onRequestFilter(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { func (p *HTTPProxy) onRequestFilter(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
p.Debug("< %s %s %s%s", req.RemoteAddr, req.Method, req.Host, req.URL.Path) if p.shouldProxy(req) {
p.Debug("< %s %s %s%s", req.RemoteAddr, req.Method, req.Host, req.URL.Path)
p.fixRequestHeaders(req) p.fixRequestHeaders(req)
redir := p.stripper.Preprocess(req, ctx) redir := p.stripper.Preprocess(req, ctx)
if redir != nil { if redir != nil {
// we need to redirect the user in order to make // we need to redirect the user in order to make
// some session cookie expire // some session cookie expire
return req, redir return req, redir
} }
// do we have a proxy script? // do we have a proxy script?
if p.Script == nil { if p.Script == nil {
return req, nil return req, nil
} }
// run the module OnRequest callback if defined // run the module OnRequest callback if defined
jsreq, jsres := p.Script.OnRequest(req) jsreq, jsres := p.Script.OnRequest(req)
if jsreq != nil { if jsreq != nil {
// the request has been changed by the script // the request has been changed by the script
p.logRequestAction(req, jsreq) p.logRequestAction(req, jsreq)
return jsreq.ToRequest(), nil return jsreq.ToRequest(), nil
} else if jsres != nil { } else if jsres != nil {
// a fake response has been returned by the script // a fake response has been returned by the script
p.logResponseAction(req, jsres) p.logResponseAction(req, jsres)
return req, jsres.ToResponse(req) return req, jsres.ToResponse(req)
}
} }
return req, nil return req, nil
@ -123,28 +125,30 @@ func (p *HTTPProxy) onResponseFilter(res *http.Response, ctx *goproxy.ProxyCtx)
return nil return nil
} }
p.Debug("> %s %s %s%s", res.Request.RemoteAddr, res.Request.Method, res.Request.Host, res.Request.URL.Path) if p.shouldProxy(res.Request) {
p.Debug("> %s %s %s%s", res.Request.RemoteAddr, res.Request.Method, res.Request.Host, res.Request.URL.Path)
p.fixResponseHeaders(res) p.fixResponseHeaders(res)
p.stripper.Process(res, ctx) p.stripper.Process(res, ctx)
// do we have a proxy script? // do we have a proxy script?
if p.Script != nil { if p.Script != nil {
_, jsres := p.Script.OnResponse(res) _, jsres := p.Script.OnResponse(res)
if jsres != nil { if jsres != nil {
// the response has been changed by the script // the response has been changed by the script
p.logResponseAction(res.Request, jsres) p.logResponseAction(res.Request, jsres)
return jsres.ToResponse(res.Request) return jsres.ToResponse(res.Request)
}
} }
}
// inject javascript code if specified and needed // inject javascript code if specified and needed
if doInject, cType := p.isScriptInjectable(res); doInject { if doInject, cType := p.isScriptInjectable(res); doInject {
if err, injectedResponse := p.doScriptInjection(res, cType); err != nil { if err, injectedResponse := p.doScriptInjection(res, cType); err != nil {
p.Error("error while injecting javascript: %s", err) p.Error("error while injecting javascript: %s", err)
} else if injectedResponse != nil { } else if injectedResponse != nil {
return injectedResponse return injectedResponse
}
} }
} }

View file

@ -71,7 +71,7 @@ func (mod *HttpServer) Configure() error {
var port int var port int
if mod.Running() { if mod.Running() {
return session.ErrAlreadyStarted return session.ErrAlreadyStarted(mod.Name())
} }
if err, path = mod.StringParam("http.server.path"); err != nil { if err, path = mod.StringParam("http.server.path"); err != nil {
@ -82,7 +82,7 @@ func (mod *HttpServer) Configure() error {
fileServer := http.FileServer(http.Dir(path)) fileServer := http.FileServer(http.Dir(path))
router.HandleFunc("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { router.HandleFunc("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mod.Info("%s %s %s%s", tui.Bold(strings.Split(r.RemoteAddr, ":")[0]), r.Method, r.Host, r.URL.Path) mod.Debug("%s %s %s%s", tui.Bold(strings.Split(r.RemoteAddr, ":")[0]), r.Method, r.Host, r.URL.Path)
fileServer.ServeHTTP(w, r) fileServer.ServeHTTP(w, r)
})) }))
@ -110,7 +110,8 @@ func (mod *HttpServer) Start() error {
var err error var err error
mod.Info("starting on http://%s", mod.server.Addr) mod.Info("starting on http://%s", mod.server.Addr)
if err = mod.server.ListenAndServe(); err != nil && err != http.ErrServerClosed { if err = mod.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
panic(err) mod.Error("%v", err)
mod.Stop()
} }
}) })
} }

View file

@ -6,6 +6,7 @@ import (
"github.com/bettercap/bettercap/tls" "github.com/bettercap/bettercap/tls"
"github.com/evilsocket/islazy/fs" "github.com/evilsocket/islazy/fs"
"github.com/evilsocket/islazy/str"
) )
type HttpsProxy struct { type HttpsProxy struct {
@ -58,6 +59,12 @@ func NewHttpsProxy(s *session.Session) *HttpsProxy {
"", "",
"Path of a proxy JS script.")) "Path of a proxy JS script."))
mod.AddParam(session.NewStringParameter("https.proxy.blacklist", "", "",
"Comma separated list of hostnames to skip while proxying (wildcard expressions can be used)."))
mod.AddParam(session.NewStringParameter("https.proxy.whitelist", "", "",
"Comma separated list of hostnames to proxy if the blacklist is used (wildcard expressions can be used)."))
mod.AddHandler(session.NewModuleHandler("https.proxy on", "", mod.AddHandler(session.NewModuleHandler("https.proxy on", "",
"Start HTTPS proxy.", "Start HTTPS proxy.",
func(args []string) error { func(args []string) error {
@ -95,9 +102,11 @@ func (mod *HttpsProxy) Configure() error {
var keyFile string var keyFile string
var stripSSL bool var stripSSL bool
var jsToInject string var jsToInject string
var whitelist string
var blacklist string
if mod.Running() { if mod.Running() {
return session.ErrAlreadyStarted return session.ErrAlreadyStarted(mod.Name())
} else if err, address = mod.StringParam("https.proxy.address"); err != nil { } else if err, address = mod.StringParam("https.proxy.address"); err != nil {
return err return err
} else if err, proxyPort = mod.IntParam("https.proxy.port"); err != nil { } else if err, proxyPort = mod.IntParam("https.proxy.port"); err != nil {
@ -118,8 +127,15 @@ func (mod *HttpsProxy) Configure() error {
return err return err
} else if err, jsToInject = mod.StringParam("https.proxy.injectjs"); err != nil { } else if err, jsToInject = mod.StringParam("https.proxy.injectjs"); err != nil {
return err return err
} else if err, blacklist = mod.StringParam("https.proxy.blacklist"); err != nil {
return err
} else if err, whitelist = mod.StringParam("https.proxy.whitelist"); err != nil {
return err
} }
mod.proxy.Blacklist = str.Comma(blacklist)
mod.proxy.Whitelist = str.Comma(whitelist)
if !fs.Exists(certFile) || !fs.Exists(keyFile) { if !fs.Exists(certFile) || !fs.Exists(keyFile) {
err, cfg := tls.CertConfigFromModule("https.proxy", mod.SessionModule) err, cfg := tls.CertConfigFromModule("https.proxy", mod.SessionModule)
if err != nil { if err != nil {

View file

@ -89,7 +89,7 @@ func (mod *HttpsServer) Configure() error {
var keyFile string var keyFile string
if mod.Running() { if mod.Running() {
return session.ErrAlreadyStarted return session.ErrAlreadyStarted(mod.Name())
} }
if err, path = mod.StringParam("https.server.path"); err != nil { if err, path = mod.StringParam("https.server.path"); err != nil {
@ -100,7 +100,7 @@ func (mod *HttpsServer) Configure() error {
fileServer := http.FileServer(http.Dir(path)) fileServer := http.FileServer(http.Dir(path))
router.HandleFunc("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { router.HandleFunc("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mod.Info("%s %s %s%s", tui.Bold(strings.Split(r.RemoteAddr, ":")[0]), r.Method, r.Host, r.URL.Path) mod.Debug("%s %s %s%s", tui.Bold(strings.Split(r.RemoteAddr, ":")[0]), r.Method, r.Host, r.URL.Path)
fileServer.ServeHTTP(w, r) fileServer.ServeHTTP(w, r)
})) }))
@ -159,7 +159,8 @@ func (mod *HttpsServer) Start() error {
return mod.SetRunning(true, func() { return mod.SetRunning(true, func() {
mod.Info("starting on https://%s", mod.server.Addr) mod.Info("starting on https://%s", mod.server.Addr)
if err := mod.server.ListenAndServeTLS(mod.certFile, mod.keyFile); err != nil && err != http.ErrServerClosed { if err := mod.server.ListenAndServeTLS(mod.certFile, mod.keyFile); err != nil && err != http.ErrServerClosed {
panic(err) mod.Error("%v", err)
mod.Stop()
} }
}) })
} }

View file

@ -103,7 +103,7 @@ func (mod *MacChanger) setMac(mac net.HardwareAddr) error {
func (mod *MacChanger) Start() error { func (mod *MacChanger) Start() error {
if mod.Running() { if mod.Running() {
return session.ErrAlreadyStarted return session.ErrAlreadyStarted(mod.Name())
} else if err := mod.Configure(); err != nil { } else if err := mod.Configure(); err != nil {
return err return err
} else if err := mod.setMac(mod.fakeMac); err != nil { } else if err := mod.setMac(mod.fakeMac); err != nil {

View file

@ -24,6 +24,7 @@ import (
"github.com/bettercap/bettercap/modules/syn_scan" "github.com/bettercap/bettercap/modules/syn_scan"
"github.com/bettercap/bettercap/modules/tcp_proxy" "github.com/bettercap/bettercap/modules/tcp_proxy"
"github.com/bettercap/bettercap/modules/ticker" "github.com/bettercap/bettercap/modules/ticker"
"github.com/bettercap/bettercap/modules/ui"
"github.com/bettercap/bettercap/modules/update" "github.com/bettercap/bettercap/modules/update"
"github.com/bettercap/bettercap/modules/wifi" "github.com/bettercap/bettercap/modules/wifi"
"github.com/bettercap/bettercap/modules/wol" "github.com/bettercap/bettercap/modules/wol"
@ -36,7 +37,6 @@ func LoadModules(sess *session.Session) {
sess.Register(arp_spoof.NewArpSpoofer(sess)) sess.Register(arp_spoof.NewArpSpoofer(sess))
sess.Register(api_rest.NewRestAPI(sess)) sess.Register(api_rest.NewRestAPI(sess))
sess.Register(ble.NewBLERecon(sess)) sess.Register(ble.NewBLERecon(sess))
sess.Register(caplets.NewCapletsModule(sess))
sess.Register(dhcp6_spoof.NewDHCP6Spoofer(sess)) sess.Register(dhcp6_spoof.NewDHCP6Spoofer(sess))
sess.Register(net_recon.NewDiscovery(sess)) sess.Register(net_recon.NewDiscovery(sess))
sess.Register(dns_spoof.NewDNSSpoofer(sess)) sess.Register(dns_spoof.NewDNSSpoofer(sess))
@ -54,8 +54,11 @@ func LoadModules(sess *session.Session) {
sess.Register(syn_scan.NewSynScanner(sess)) sess.Register(syn_scan.NewSynScanner(sess))
sess.Register(tcp_proxy.NewTcpProxy(sess)) sess.Register(tcp_proxy.NewTcpProxy(sess))
sess.Register(ticker.NewTicker(sess)) sess.Register(ticker.NewTicker(sess))
sess.Register(update.NewUpdateModule(sess))
sess.Register(wifi.NewWiFiModule(sess)) sess.Register(wifi.NewWiFiModule(sess))
sess.Register(wol.NewWOL(sess)) sess.Register(wol.NewWOL(sess))
sess.Register(hid.NewHIDRecon(sess)) sess.Register(hid.NewHIDRecon(sess))
sess.Register(caplets.NewCapletsModule(sess))
sess.Register(update.NewUpdateModule(sess))
sess.Register(ui.NewUIModule(sess))
} }

View file

@ -79,7 +79,7 @@ func (mod *MySQLServer) Configure() error {
var port int var port int
if mod.Running() { if mod.Running() {
return session.ErrAlreadyStarted return session.ErrAlreadyStarted(mod.Name())
} else if err, mod.infile = mod.StringParam("mysql.server.infile"); err != nil { } else if err, mod.infile = mod.StringParam("mysql.server.infile"); err != nil {
return err return err
} else if err, mod.outfile = mod.StringParam("mysql.server.outfile"); err != nil { } else if err, mod.outfile = mod.StringParam("mysql.server.outfile"); err != nil {

View file

@ -48,13 +48,13 @@ func NewDiscovery(s *session.Session) *Discovery {
})) }))
mod.AddHandler(session.NewModuleHandler("net.show ADDRESS1, ADDRESS2", `net.show (.+)`, mod.AddHandler(session.NewModuleHandler("net.show ADDRESS1, ADDRESS2", `net.show (.+)`,
"Show information about a specific list of addresses (by IP or MAC).", "Show information about a specific comma separated list of addresses (by IP or MAC).",
func(args []string) error { func(args []string) error {
return mod.Show(args[0]) return mod.Show(args[0])
})) }))
mod.AddHandler(session.NewModuleHandler("net.show.meta ADDRESS1, ADDRESS2", `net\.show\.meta (.+)`, mod.AddHandler(session.NewModuleHandler("net.show.meta ADDRESS1, ADDRESS2", `net\.show\.meta (.+)`,
"Show meta information about a specific list of addresses (by IP or MAC).", "Show meta information about a specific comma separated list of addresses (by IP or MAC).",
func(args []string) error { func(args []string) error {
return mod.showMeta(args[0]) return mod.showMeta(args[0])
})) }))

View file

@ -146,7 +146,7 @@ func (mod *Sniffer) Configure() error {
var err error var err error
if mod.Running() { if mod.Running() {
return session.ErrAlreadyStarted return session.ErrAlreadyStarted(mod.Name())
} else if err, mod.Ctx = mod.GetContext(); err != nil { } else if err, mod.Ctx = mod.GetContext(); err != nil {
if mod.Ctx != nil { if mod.Ctx != nil {
mod.Ctx.Close() mod.Ctx.Close()

View file

@ -190,7 +190,7 @@ func dummyCallback(payload *nfqueue.Payload) int {
func (mod *PacketProxy) Start() error { func (mod *PacketProxy) Start() error {
if mod.Running() { if mod.Running() {
return session.ErrAlreadyStarted return session.ErrAlreadyStarted(mod.Name())
} else if err := mod.Configure(); err != nil { } else if err := mod.Configure(); err != nil {
return err return err
} }

View file

@ -45,6 +45,9 @@ func NewSynScanner(s *session.Session) *SynScanner {
progressEvery: time.Duration(1) * time.Second, progressEvery: time.Duration(1) * time.Second,
} }
mod.State.Store("scanning", &mod.addresses)
mod.State.Store("progress", 0.0)
mod.AddParam(session.NewIntParameter("syn.scan.show-progress-every", mod.AddParam(session.NewIntParameter("syn.scan.show-progress-every",
"1", "1",
"Period in seconds for the scanning progress reporting.")) "Period in seconds for the scanning progress reporting."))
@ -58,7 +61,7 @@ func NewSynScanner(s *session.Session) *SynScanner {
return mod.Stop() return mod.Stop()
})) }))
mod.AddHandler(session.NewModuleHandler("syn.scan IP-RANGE [START-PORT] [END-PORT]", "syn.scan ([^\\s]+) ?(\\d+)?([\\s\\d]*)?", mod.AddHandler(session.NewModuleHandler("syn.scan IP-RANGE START-PORT END-PORT", "syn.scan ([^\\s]+) ?(\\d+)?([\\s\\d]*)?",
"Perform a syn port scanning against an IP address within the provided ports range.", "Perform a syn port scanning against an IP address within the provided ports range.",
func(args []string) error { func(args []string) error {
period := 0 period := 0
@ -155,6 +158,7 @@ func plural(n uint64) string {
func (mod *SynScanner) showProgress() error { func (mod *SynScanner) showProgress() error {
progress := 100.0 * (float64(mod.stats.doneProbes) / float64(mod.stats.totProbes)) progress := 100.0 * (float64(mod.stats.doneProbes) / float64(mod.stats.totProbes))
mod.State.Store("progress", progress)
mod.Info("[%.2f%%] found %d open port%s for %d address%s, sent %d/%d packets in %s", mod.Info("[%.2f%%] found %d open port%s for %d address%s, sent %d/%d packets in %s",
progress, progress,
mod.stats.openPorts, mod.stats.openPorts,
@ -172,12 +176,17 @@ func (mod *SynScanner) Stop() error {
return mod.SetRunning(false, func() { return mod.SetRunning(false, func() {
mod.waitGroup.Wait() mod.waitGroup.Wait()
mod.showProgress() mod.showProgress()
mod.addresses = []net.IP{}
mod.State.Store("progress", 0.0)
}) })
} }
func (mod *SynScanner) synScan() error { func (mod *SynScanner) synScan() error {
mod.SetRunning(true, func() { mod.SetRunning(true, func() {
defer mod.SetRunning(false, nil) defer mod.SetRunning(false, func() {
mod.addresses = []net.IP{}
mod.State.Store("progress", 0.0)
})
mod.waitGroup.Add(1) mod.waitGroup.Add(1)
defer mod.waitGroup.Done() defer mod.waitGroup.Done()
@ -199,6 +208,8 @@ func (mod *SynScanner) synScan() error {
mod.Info("scanning %d address%s on port %d ...", mod.stats.numAddresses, plural, mod.startPort) mod.Info("scanning %d address%s on port %d ...", mod.stats.numAddresses, plural, mod.startPort)
} }
mod.State.Store("progress", 0.0)
// set the collector // set the collector
mod.Session.Queue.OnPacket(mod.onPacket) mod.Session.Queue.OnPacket(mod.onPacket)
defer mod.Session.Queue.OnPacket(nil) defer mod.Session.Queue.OnPacket(nil)

View file

@ -95,7 +95,7 @@ func (mod *TcpProxy) Configure() error {
var tunnelPort int var tunnelPort int
if mod.Running() { if mod.Running() {
return session.ErrAlreadyStarted return session.ErrAlreadyStarted(mod.Name())
} else if err, address = mod.StringParam("tcp.address"); err != nil { } else if err, address = mod.StringParam("tcp.address"); err != nil {
return err return err
} else if err, proxyAddress = mod.StringParam("tcp.proxy.address"); err != nil { } else if err, proxyAddress = mod.StringParam("tcp.proxy.address"); err != nil {

View file

@ -59,7 +59,7 @@ func (mod *Ticker) Configure() error {
var period int var period int
if mod.Running() { if mod.Running() {
return session.ErrAlreadyStarted return session.ErrAlreadyStarted(mod.Name())
} else if err, commands = mod.StringParam("ticker.commands"); err != nil { } else if err, commands = mod.StringParam("ticker.commands"); err != nil {
return err return err
} else if err, period = mod.IntParam("ticker.period"); err != nil { } else if err, period = mod.IntParam("ticker.period"); err != nil {

190
modules/ui/ui.go Normal file
View file

@ -0,0 +1,190 @@
package ui
import (
"context"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"regexp"
"github.com/bettercap/bettercap/session"
"github.com/google/go-github/github"
"github.com/evilsocket/islazy/fs"
"github.com/evilsocket/islazy/tui"
"github.com/evilsocket/islazy/zip"
)
var versionParser = regexp.MustCompile(`name:"ui",version:"([^"]+)"`)
type UIModule struct {
session.SessionModule
client *github.Client
tmpFile string
basePath string
uiPath string
}
func NewUIModule(s *session.Session) *UIModule {
mod := &UIModule{
SessionModule: session.NewSessionModule("ui", s),
client: github.NewClient(nil),
}
mod.AddParam(session.NewStringParameter("ui.basepath",
"/usr/local/share/bettercap/",
"",
"UI base installation path."))
mod.AddParam(session.NewStringParameter("ui.tmpfile",
filepath.Join(os.TempDir(), "ui.zip"),
"",
"Temporary file to use while downloading UI updates."))
mod.AddHandler(session.NewModuleHandler("ui.version", "",
"Print the currently installed UI version.",
func(args []string) error {
return mod.showVersion()
}))
mod.AddHandler(session.NewModuleHandler("ui.update", "",
"Download the latest available version of the UI and install it.",
func(args []string) error {
return mod.Start()
}))
return mod
}
func (mod *UIModule) Name() string {
return "ui"
}
func (mod *UIModule) Description() string {
return "A module to manage bettercap's UI updates and installed version."
}
func (mod *UIModule) Author() string {
return "Simone Margaritelli <evilsocket@gmail.com>"
}
func (mod *UIModule) Configure() (err error) {
if err, mod.basePath = mod.StringParam("ui.basepath"); err != nil {
return err
} else {
mod.uiPath = filepath.Join(mod.basePath, "ui")
}
if err, mod.tmpFile = mod.StringParam("ui.tmpfile"); err != nil {
return err
}
return nil
}
func (mod *UIModule) Stop() error {
return nil
}
func (mod *UIModule) download(version, url string) error {
if !fs.Exists(mod.basePath) {
mod.Warning("creating ui install path %s ...", mod.basePath)
if err := os.MkdirAll(mod.basePath, os.ModePerm); err != nil {
return err
}
}
out, err := os.Create(mod.tmpFile)
if err != nil {
return err
}
defer out.Close()
defer os.Remove(mod.tmpFile)
mod.Info("downloading ui %s from %s ...", tui.Bold(version), url)
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
if _, err := io.Copy(out, resp.Body); err != nil {
return err
}
if fs.Exists(mod.uiPath) {
mod.Warning("removing previously installed UI from %s ...", mod.uiPath)
if err := os.RemoveAll(mod.uiPath); err != nil {
return err
}
}
mod.Info("installing to %s ...", mod.uiPath)
if _, err = zip.Unzip(mod.tmpFile, mod.basePath); err != nil {
return err
}
mod.Info("installation complete, you can now run the %s (or https-ui) caplet to start the UI.", tui.Bold("http-ui"))
return nil
}
func (mod *UIModule) showVersion() error {
if err := mod.Configure(); err != nil {
return err
}
if !fs.Exists(mod.uiPath) {
return fmt.Errorf("path %s does not exist, ui not installed", mod.uiPath)
}
search := filepath.Join(mod.uiPath, "/main.*.js")
matches, err := filepath.Glob(search)
if err != nil {
return err
} else if len(matches) == 0 {
return fmt.Errorf("can't find any main.*.js files in %s", mod.uiPath)
}
for _, filename := range matches {
if raw, err := ioutil.ReadFile(filename); err != nil {
return err
} else if m := versionParser.FindStringSubmatch(string(raw)); m != nil {
version := m[1]
mod.Info("v%s", version)
return nil
}
}
return fmt.Errorf("can't parse version from %s", search)
}
func (mod *UIModule) Start() error {
if err := mod.Configure(); err != nil {
return err
} else if err := mod.SetRunning(true, nil); err != nil {
return err
}
defer mod.SetRunning(false, nil)
mod.Info("checking latest stable release ...")
if releases, _, err := mod.client.Repositories.ListReleases(context.Background(), "bettercap", "ui", nil); err == nil {
latest := releases[0]
for _, a := range latest.Assets {
if *a.Name == "ui.zip" {
return mod.download(*latest.TagName, *a.BrowserDownloadURL)
}
}
} else {
mod.Error("error while fetching latest release info from GitHub: %s", err)
}
return nil
}

View file

@ -38,7 +38,7 @@ func init() {
entry_list := []string{} entry_list := []string{}
for _, file := range dir { for _, file := range dir {
entry_list = append( entry_list, file.Name() ) entry_list = append(entry_list, file.Name())
} }
v, err := otto.Otto.ToValue(*call.Otto, entry_list) v, err := otto.Otto.ToValue(*call.Otto, entry_list)
@ -167,21 +167,21 @@ func init() {
return errOtto("gzipCompress: expected 1 argument, %d given instead.", argc) return errOtto("gzipCompress: expected 1 argument, %d given instead.", argc)
} }
uncompressed_bytes := []byte( argv[0].String() ) uncompressed_bytes := []byte(argv[0].String())
var writer_buffer bytes.Buffer var writer_buffer bytes.Buffer
gzip_writer := gzip.NewWriter(&writer_buffer) gzip_writer := gzip.NewWriter(&writer_buffer)
_, err := gzip_writer.Write(uncompressed_bytes) _, err := gzip_writer.Write(uncompressed_bytes)
if err != nil { if err != nil {
return errOtto( "gzipCompress: could not compress data: %s", err.Error() ) return errOtto("gzipCompress: could not compress data: %s", err.Error())
} }
gzip_writer.Close() gzip_writer.Close()
compressed_bytes := writer_buffer.Bytes() compressed_bytes := writer_buffer.Bytes()
v, err := otto.ToValue( string(compressed_bytes) ) v, err := otto.ToValue(string(compressed_bytes))
if err != nil { if err != nil {
return errOtto( "Could not convert to string: %s", err.Error() ) return errOtto("Could not convert to string: %s", err.Error())
} }
return v return v
@ -195,24 +195,24 @@ func init() {
return errOtto("gzipDecompress: expected 1 argument, %d given instead.", argc) return errOtto("gzipDecompress: expected 1 argument, %d given instead.", argc)
} }
compressed_bytes := []byte( argv[0].String() ) compressed_bytes := []byte(argv[0].String())
reader_buffer := bytes.NewBuffer(compressed_bytes) reader_buffer := bytes.NewBuffer(compressed_bytes)
gzip_reader, err := gzip.NewReader(reader_buffer) gzip_reader, err := gzip.NewReader(reader_buffer)
if err != nil { if err != nil {
return errOtto( "gzipDecompress: could not create gzip reader: %s", err.Error() ) return errOtto("gzipDecompress: could not create gzip reader: %s", err.Error())
} }
var decompressed_buffer bytes.Buffer var decompressed_buffer bytes.Buffer
_, err = decompressed_buffer.ReadFrom(gzip_reader) _, err = decompressed_buffer.ReadFrom(gzip_reader)
if err != nil { if err != nil {
return errOtto( "gzipDecompress: could not decompress data: %s", err.Error() ) return errOtto("gzipDecompress: could not decompress data: %s", err.Error())
} }
decompressed_bytes := decompressed_buffer.Bytes() decompressed_bytes := decompressed_buffer.Bytes()
v, err := otto.ToValue( string(decompressed_bytes) ) v, err := otto.ToValue(string(decompressed_bytes))
if err != nil { if err != nil {
return errOtto( "Could not convert to string: %s", err.Error() ) return errOtto("Could not convert to string: %s", err.Error())
} }
return v return v

View file

@ -38,6 +38,7 @@ type WiFiModule struct {
frequencies []int frequencies []int
ap *network.AccessPoint ap *network.AccessPoint
stickChan int stickChan int
shakesFile string
skipBroken bool skipBroken bool
pktSourceChan chan gopacket.Packet pktSourceChan chan gopacket.Packet
pktSourceChanClosed bool pktSourceChanClosed bool
@ -47,7 +48,6 @@ type WiFiModule struct {
assocSkip []net.HardwareAddr assocSkip []net.HardwareAddr
assocSilent bool assocSilent bool
assocOpen bool assocOpen bool
shakesFile string
apRunning bool apRunning bool
showManuf bool showManuf bool
apConfig packets.Dot11ApConfig apConfig packets.Dot11ApConfig
@ -81,6 +81,8 @@ func NewWiFiModule(s *session.Session) *WiFiModule {
chanLock: &sync.Mutex{}, chanLock: &sync.Mutex{},
} }
mod.InitState("channels")
mod.AddParam(session.NewStringParameter("wifi.interface", mod.AddParam(session.NewStringParameter("wifi.interface",
"", "",
"", "",
@ -124,7 +126,8 @@ func NewWiFiModule(s *session.Session) *WiFiModule {
func(args []string) (err error) { func(args []string) (err error) {
mod.ap = nil mod.ap = nil
mod.stickChan = 0 mod.stickChan = 0
mod.frequencies, err = network.GetSupportedFrequencies(mod.iface.Name()) freqs, err := network.GetSupportedFrequencies(mod.iface.Name())
mod.setFrequencies(freqs)
mod.hopChanges <- true mod.hopChanges <- true
return err return err
})) }))
@ -258,7 +261,7 @@ func NewWiFiModule(s *session.Session) *WiFiModule {
"false", "false",
"If true, wifi.show will also show the devices manufacturers.")) "If true, wifi.show will also show the devices manufacturers."))
mod.AddHandler(session.NewModuleHandler("wifi.recon.channel", `wifi\.recon\.channel[\s]+([0-9]+(?:[, ]+[0-9]+)*|clear)`, mod.AddHandler(session.NewModuleHandler("wifi.recon.channel CHANNEL", `wifi\.recon\.channel[\s]+([0-9]+(?:[, ]+[0-9]+)*|clear)`,
"WiFi channels (comma separated) or 'clear' for channel hopping.", "WiFi channels (comma separated) or 'clear' for channel hopping.",
func(args []string) (err error) { func(args []string) (err error) {
freqs := []int{} freqs := []int{}
@ -285,8 +288,7 @@ func NewWiFiModule(s *session.Session) *WiFiModule {
} }
} }
mod.Debug("new frequencies: %v", freqs) mod.setFrequencies(freqs)
mod.frequencies = freqs
// if wifi.recon is not running, this would block forever // if wifi.recon is not running, this would block forever
if mod.Running() { if mod.Running() {
@ -330,6 +332,17 @@ const (
ErrIfaceNotUp = "Interface Not Up" ErrIfaceNotUp = "Interface Not Up"
) )
func (mod *WiFiModule) setFrequencies(freqs []int) {
mod.Debug("new frequencies: %v", freqs)
mod.frequencies = freqs
channels := []int{}
for _, freq := range freqs {
channels = append(channels, network.Dot11Freq2Chan(freq))
}
mod.State.Store("channels", channels)
}
func (mod *WiFiModule) Configure() error { func (mod *WiFiModule) Configure() error {
var ifName string var ifName string
var hopPeriod int var hopPeriod int
@ -428,21 +441,21 @@ func (mod *WiFiModule) Configure() error {
mod.hopPeriod = time.Duration(hopPeriod) * time.Millisecond mod.hopPeriod = time.Duration(hopPeriod) * time.Millisecond
if mod.source == "" { if mod.source == "" {
// No channels setted, retrieve frequencies supported by the card if freqs, err := network.GetSupportedFrequencies(ifName); err != nil {
if len(mod.frequencies) == 0 { return fmt.Errorf("error while getting supported frequencies of %s: %s", ifName, err)
if mod.frequencies, err = network.GetSupportedFrequencies(ifName); err != nil { } else {
return fmt.Errorf("error while getting supported frequencies of %s: %s", ifName, err) mod.setFrequencies(freqs)
}
mod.Debug("wifi supported frequencies: %v", mod.frequencies)
// we need to start somewhere, this is just to check if
// this OS supports switching channel programmatically.
if err = network.SetInterfaceChannel(ifName, 1); err != nil {
return fmt.Errorf("error while initializing %s to channel 1: %s", ifName, err)
}
mod.Info("started (min rssi: %d dBm)", mod.minRSSI)
} }
mod.Debug("wifi supported frequencies: %v", mod.frequencies)
// we need to start somewhere, this is just to check if
// this OS supports switching channel programmatically.
if err = network.SetInterfaceChannel(ifName, 1); err != nil {
return fmt.Errorf("error while initializing %s to channel 1: %s", ifName, err)
}
mod.Info("started (min rssi: %d dBm)", mod.minRSSI)
} }
return nil return nil
@ -480,13 +493,17 @@ func (mod *WiFiModule) updateStats(dot11 *layers.Dot11, packet gopacket.Packet)
bytes := uint64(len(packet.Data())) bytes := uint64(len(packet.Data()))
dst := dot11.Address1.String() dst := dot11.Address1.String()
if station, found := mod.Session.WiFi.Get(dst); found { if ap, found := mod.Session.WiFi.Get(dst); found {
station.Received += bytes ap.Received += bytes
} else if sta, found := mod.Session.WiFi.GetClient(dst); found {
sta.Received += bytes
} }
src := dot11.Address2.String() src := dot11.Address2.String()
if station, found := mod.Session.WiFi.Get(src); found { if ap, found := mod.Session.WiFi.Get(src); found {
station.Sent += bytes ap.Sent += bytes
} else if sta, found := mod.Session.WiFi.GetClient(src); found {
sta.Sent += bytes
} }
} }
} }
@ -544,6 +561,17 @@ func (mod *WiFiModule) Start() error {
return nil return nil
} }
func (mod *WiFiModule) forcedStop() error {
return mod.SetRunning(false, func() {
// signal the main for loop we want to exit
if !mod.pktSourceChanClosed {
mod.pktSourceChan <- nil
}
// close the pcap handle to make the main for exit
mod.handle.Close()
})
}
func (mod *WiFiModule) Stop() error { func (mod *WiFiModule) Stop() error {
return mod.SetRunning(false, func() { return mod.SetRunning(false, func() {
// wait any pending write operation // wait any pending write operation

View file

@ -35,7 +35,7 @@ func (mod *WiFiModule) startAp() error {
if !mod.Running() { if !mod.Running() {
return errNoRecon return errNoRecon
} else if mod.apRunning { } else if mod.apRunning {
return session.ErrAlreadyStarted return session.ErrAlreadyStarted(mod.Name())
} }
go func() { go func() {

View file

@ -1,11 +1,47 @@
package wifi package wifi
import ( import (
"net"
"time" "time"
"github.com/bettercap/bettercap/network" "github.com/bettercap/bettercap/network"
) )
func (mod *WiFiModule) isInterfaceConnected() bool {
ifaces, err := net.Interfaces()
if err != nil {
mod.Error("error while enumerating interfaces: %s", err)
return false
}
for _, iface := range ifaces {
if mod.iface.HwAddress == network.NormalizeMac(iface.HardwareAddr.String()) {
return true
}
}
return false
}
func (mod *WiFiModule) hop(channel int) (mustStop bool) {
mod.chanLock.Lock()
defer mod.chanLock.Unlock()
mod.Debug("hopping on channel %d", channel)
if err := network.SetInterfaceChannel(mod.iface.Name(), channel); err != nil {
// check if the device has been disconnected
if mod.isInterfaceConnected() == false {
mod.Error("interface %s disconnected, stopping module", mod.iface.Name())
mustStop = true
} else {
mod.Warning("error while hopping to channel %d: %s", channel, err)
}
}
return
}
func (mod *WiFiModule) onChannel(channel int, cb func()) { func (mod *WiFiModule) onChannel(channel int, cb func()) {
mod.chanLock.Lock() mod.chanLock.Lock()
defer mod.chanLock.Unlock() defer mod.chanLock.Unlock()
@ -13,11 +49,7 @@ func (mod *WiFiModule) onChannel(channel int, cb func()) {
prev := mod.stickChan prev := mod.stickChan
mod.stickChan = channel mod.stickChan = channel
if err := network.SetInterfaceChannel(mod.iface.Name(), channel); err != nil { mod.hop(channel)
mod.Warning("error while hopping to channel %d: %s", channel, err)
} else {
mod.Debug("hopped on channel %d", channel)
}
cb() cb()
@ -50,13 +82,10 @@ func (mod *WiFiModule) channelHopper() {
channel = mod.stickChan channel = mod.stickChan
} }
mod.Debug("hopping on channel %d", channel) if stop := mod.hop(channel); stop {
mod.forcedStop()
mod.chanLock.Lock() return
if err := network.SetInterfaceChannel(mod.iface.Name(), channel); err != nil {
mod.Warning("error while hopping to channel %d: %s", channel, err)
} }
mod.chanLock.Unlock()
select { select {
case _ = <-mod.hopChanges: case _ = <-mod.hopChanges:

View file

@ -124,7 +124,7 @@ func (mod *WiFiModule) discoverClients(radiotap *layers.RadioTap, dot11 *layers.
freq := int(radiotap.ChannelFrequency) freq := int(radiotap.ChannelFrequency)
rssi := radiotap.DBMAntennaSignal rssi := radiotap.DBMAntennaSignal
if station, isNew := ap.AddClientIfNew(bssid, freq, rssi, mod.Session.Lan.Aliases()); isNew { if station, isNew := ap.AddClientIfNew(bssid, freq, rssi); isNew {
mod.Session.Events.Add("wifi.client.new", ClientEvent{ mod.Session.Events.Add("wifi.client.new", ClientEvent{
AP: ap, AP: ap,
Client: station, Client: station,

View file

@ -35,8 +35,9 @@ func (mod *WiFiModule) discoverHandshakes(radiotap *layers.RadioTap, dot11 *laye
// in order to have a consistent association of AP, client and handshakes. // in order to have a consistent association of AP, client and handshakes.
staIsUs := bytes.Equal(staMac, mod.iface.HW) staIsUs := bytes.Equal(staMac, mod.iface.HW)
station, found := ap.Get(staMac.String()) station, found := ap.Get(staMac.String())
staAdded := false
if !found { if !found {
station, _ = ap.AddClientIfNew(staMac.String(), ap.Frequency, ap.RSSI, mod.Session.Lan.Aliases()) station, staAdded = ap.AddClientIfNew(staMac.String(), ap.Frequency, ap.RSSI)
} }
rawPMKID := []byte(nil) rawPMKID := []byte(nil)
@ -96,10 +97,9 @@ func (mod *WiFiModule) discoverHandshakes(radiotap *layers.RadioTap, dot11 *laye
// is persisted even after stations are pruned due to inactivity // is persisted even after stations are pruned due to inactivity
ap.WithKeyMaterial(true) ap.WithKeyMaterial(true)
} }
// if we added ourselves as a client station but we didn't get any // if we added ourselves as a client station but we didn't get any
// PMKID, just remove it from the list of clients of this AP. // PMKID, just remove it from the list of clients of this AP.
if staIsUs && rawPMKID == nil { if staAdded || (staIsUs && rawPMKID == nil) {
ap.RemoveClient(staMac.String()) ap.RemoveClient(staMac.String())
} }
} }

View file

@ -9,6 +9,8 @@ import (
"time" "time"
"github.com/bettercap/gatt" "github.com/bettercap/gatt"
"github.com/evilsocket/islazy/data"
) )
const BLEMacValidator = "([a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2})" const BLEMacValidator = "([a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2})"
@ -18,6 +20,7 @@ type BLEDevLostCallback func(dev *BLEDevice)
type BLE struct { type BLE struct {
sync.RWMutex sync.RWMutex
aliases *data.UnsortedKV
devices map[string]*BLEDevice devices map[string]*BLEDevice
newCb BLEDevNewCallback newCb BLEDevNewCallback
lostCb BLEDevLostCallback lostCb BLEDevLostCallback
@ -27,9 +30,10 @@ type bleJSON struct {
Devices []*BLEDevice `json:"devices"` Devices []*BLEDevice `json:"devices"`
} }
func NewBLE(newcb BLEDevNewCallback, lostcb BLEDevLostCallback) *BLE { func NewBLE(aliases *data.UnsortedKV, newcb BLEDevNewCallback, lostcb BLEDevLostCallback) *BLE {
return &BLE{ return &BLE{
devices: make(map[string]*BLEDevice), devices: make(map[string]*BLEDevice),
aliases: aliases,
newCb: newcb, newCb: newcb,
lostCb: lostcb, lostCb: lostcb,
} }
@ -55,14 +59,19 @@ func (b *BLE) AddIfNew(id string, p gatt.Peripheral, a *gatt.Advertisement, rssi
defer b.Unlock() defer b.Unlock()
id = NormalizeMac(id) id = NormalizeMac(id)
alias := b.aliases.GetOr(id, "")
if dev, found := b.devices[id]; found { if dev, found := b.devices[id]; found {
dev.LastSeen = time.Now() dev.LastSeen = time.Now()
dev.RSSI = rssi dev.RSSI = rssi
dev.Advertisement = a dev.Advertisement = a
if alias != "" {
dev.Alias = alias
}
return dev return dev
} }
newDev := NewBLEDevice(p, a, rssi) newDev := NewBLEDevice(p, a, rssi)
newDev.Alias = alias
b.devices[id] = newDev b.devices[id] = newDev
if b.newCb != nil { if b.newCb != nil {

View file

@ -27,6 +27,7 @@ type BLEService struct {
} }
type BLEDevice struct { type BLEDevice struct {
Alias string
LastSeen time.Time LastSeen time.Time
DeviceName string DeviceName string
Vendor string Vendor string
@ -40,6 +41,7 @@ type bleDeviceJSON struct {
LastSeen time.Time `json:"last_seen"` LastSeen time.Time `json:"last_seen"`
Name string `json:"name"` Name string `json:"name"`
MAC string `json:"mac"` MAC string `json:"mac"`
Alias string `json:"alias"`
Vendor string `json:"vendor"` Vendor string `json:"vendor"`
RSSI int `json:"rssi"` RSSI int `json:"rssi"`
Connectable bool `json:"connectable"` Connectable bool `json:"connectable"`
@ -81,6 +83,7 @@ func (d *BLEDevice) MarshalJSON() ([]byte, error) {
LastSeen: d.LastSeen, LastSeen: d.LastSeen,
Name: d.Name(), Name: d.Name(),
MAC: d.Device.ID(), MAC: d.Device.ID(),
Alias: d.Alias,
Vendor: d.Vendor, Vendor: d.Vendor,
RSSI: d.RSSI, RSSI: d.RSSI,
Connectable: d.Advertisement.Connectable, Connectable: d.Advertisement.Connectable,

View file

@ -5,10 +5,13 @@ package network
import ( import (
"encoding/json" "encoding/json"
"time" "time"
"github.com/evilsocket/islazy/data"
) )
type BLEDevice struct { type BLEDevice struct {
LastSeen time.Time LastSeen time.Time
Alias string
} }
func NewBLEDevice() *BLEDevice { func NewBLEDevice() *BLEDevice {
@ -21,6 +24,7 @@ type BLEDevNewCallback func(dev *BLEDevice)
type BLEDevLostCallback func(dev *BLEDevice) type BLEDevLostCallback func(dev *BLEDevice)
type BLE struct { type BLE struct {
aliases *data.UnsortedKV
devices map[string]*BLEDevice devices map[string]*BLEDevice
newCb BLEDevNewCallback newCb BLEDevNewCallback
lostCb BLEDevLostCallback lostCb BLEDevLostCallback
@ -30,8 +34,9 @@ type bleJSON struct {
Devices []*BLEDevice `json:"devices"` Devices []*BLEDevice `json:"devices"`
} }
func NewBLE(newcb BLEDevNewCallback, lostcb BLEDevLostCallback) *BLE { func NewBLE(aliases *data.UnsortedKV, newcb BLEDevNewCallback, lostcb BLEDevLostCallback) *BLE {
return &BLE{ return &BLE{
aliases: aliases,
devices: make(map[string]*BLEDevice), devices: make(map[string]*BLEDevice),
newCb: newcb, newCb: newcb,
lostCb: lostcb, lostCb: lostcb,

View file

@ -4,6 +4,8 @@ import (
"encoding/json" "encoding/json"
"sync" "sync"
"time" "time"
"github.com/evilsocket/islazy/data"
) )
type HIDDevNewCallback func(dev *HIDDevice) type HIDDevNewCallback func(dev *HIDDevice)
@ -11,6 +13,7 @@ type HIDDevLostCallback func(dev *HIDDevice)
type HID struct { type HID struct {
sync.RWMutex sync.RWMutex
aliases *data.UnsortedKV
devices map[string]*HIDDevice devices map[string]*HIDDevice
newCb HIDDevNewCallback newCb HIDDevNewCallback
lostCb HIDDevLostCallback lostCb HIDDevLostCallback
@ -20,9 +23,10 @@ type hidJSON struct {
Devices []*HIDDevice `json:"devices"` Devices []*HIDDevice `json:"devices"`
} }
func NewHID(newcb HIDDevNewCallback, lostcb HIDDevLostCallback) *HID { func NewHID(aliases *data.UnsortedKV, newcb HIDDevNewCallback, lostcb HIDDevLostCallback) *HID {
return &HID{ return &HID{
devices: make(map[string]*HIDDevice), devices: make(map[string]*HIDDevice),
aliases: aliases,
newCb: newcb, newCb: newcb,
lostCb: lostcb, lostCb: lostcb,
} }
@ -52,14 +56,20 @@ func (b *HID) AddIfNew(address []byte, channel int, payload []byte) (bool, *HIDD
defer b.Unlock() defer b.Unlock()
id := HIDAddress(address) id := HIDAddress(address)
alias := b.aliases.GetOr(id, "")
if dev, found := b.devices[id]; found { if dev, found := b.devices[id]; found {
dev.LastSeen = time.Now() dev.LastSeen = time.Now()
dev.AddChannel(channel) dev.AddChannel(channel)
dev.AddPayload(payload) dev.AddPayload(payload)
if alias != "" {
dev.Alias = alias
}
return false, dev return false, dev
} }
newDev := NewHIDDevice(address, channel, payload) newDev := NewHIDDevice(address, channel, payload)
newDev.Alias = alias
b.devices[id] = newDev b.devices[id] = newDev
if b.newCb != nil { if b.newCb != nil {

View file

@ -1,12 +1,15 @@
package network package network
import ( import (
"encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"sort" "sort"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/evilsocket/islazy/str"
) )
type HIDType int type HIDType int
@ -39,6 +42,7 @@ type HIDDevice struct {
sync.Mutex sync.Mutex
LastSeen time.Time LastSeen time.Time
Type HIDType Type HIDType
Alias string
Address string Address string
RawAddress []byte RawAddress []byte
channels map[int]bool channels map[int]bool
@ -47,10 +51,13 @@ type HIDDevice struct {
} }
type hidDeviceJSON struct { type hidDeviceJSON struct {
LastSeen time.Time `json:"last_seen"` LastSeen time.Time `json:"last_seen"`
Type string `json:"type"` Type string `json:"type"`
Address string `json:"address"` Address string `json:"address"`
Channels []string `json:"channels"` Alias string `json:"alias"`
Channels []string `json:"channels"`
Payloads []string `json:"payloads"`
PayloadsSize uint64 `json:"payloads_size"`
} }
func NormalizeHIDAddress(address string) string { func NormalizeHIDAddress(address string) string {
@ -90,12 +97,28 @@ func NewHIDDevice(address []byte, channel int, payload []byte) *HIDDevice {
} }
func (dev *HIDDevice) MarshalJSON() ([]byte, error) { func (dev *HIDDevice) MarshalJSON() ([]byte, error) {
dev.Lock()
defer dev.Unlock()
doc := hidDeviceJSON{ doc := hidDeviceJSON{
LastSeen: dev.LastSeen, LastSeen: dev.LastSeen,
Type: dev.Type.String(), Type: dev.Type.String(),
Address: dev.Address, Address: dev.Address,
Channels: dev.ChannelsList(), Alias: dev.Alias,
Channels: dev.channelsListUnlocked(),
Payloads: make([]string, 0),
PayloadsSize: dev.payloadsSz,
} }
// get the latest 50 payloads
for i := len(dev.payloads) - 1; i >= 0; i-- {
data := str.Trim(hex.Dump(dev.payloads[i]))
doc.Payloads = append([]string{data}, doc.Payloads...)
if len(doc.Payloads) == 50 {
break
}
}
return json.Marshal(doc) return json.Marshal(doc)
} }
@ -106,10 +129,7 @@ func (dev *HIDDevice) AddChannel(ch int) {
dev.channels[ch] = true dev.channels[ch] = true
} }
func (dev *HIDDevice) ChannelsList() []string { func (dev *HIDDevice) channelsListUnlocked() []string {
dev.Lock()
defer dev.Unlock()
chans := []string{} chans := []string{}
for ch := range dev.channels { for ch := range dev.channels {
chans = append(chans, fmt.Sprintf("%d", ch)) chans = append(chans, fmt.Sprintf("%d", ch))
@ -119,6 +139,11 @@ func (dev *HIDDevice) ChannelsList() []string {
return chans return chans
} }
func (dev *HIDDevice) ChannelsList() []string {
dev.Lock()
defer dev.Unlock()
return dev.channelsListUnlocked()
}
func (dev *HIDDevice) Channels() string { func (dev *HIDDevice) Channels() string {
return strings.Join(dev.ChannelsList(), ",") return strings.Join(dev.ChannelsList(), ",")

View file

@ -2,23 +2,18 @@ package network
import ( import (
"encoding/json" "encoding/json"
"fmt"
"net" "net"
"strings" "strings"
"sync" "sync"
"github.com/evilsocket/islazy/data" "github.com/evilsocket/islazy/data"
"github.com/evilsocket/islazy/fs"
) )
const LANDefaultttl = 10 const LANDefaultttl = 10
const LANAliasesFile = "~/bettercap.aliases"
type EndpointNewCallback func(e *Endpoint) type EndpointNewCallback func(e *Endpoint)
type EndpointLostCallback func(e *Endpoint) type EndpointLostCallback func(e *Endpoint)
var aliasesFileName, _ = fs.Expand(LANAliasesFile)
type LAN struct { type LAN struct {
sync.Mutex sync.Mutex
hosts map[string]*Endpoint hosts map[string]*Endpoint
@ -34,12 +29,7 @@ type lanJSON struct {
Hosts []*Endpoint `json:"hosts"` Hosts []*Endpoint `json:"hosts"`
} }
func NewLAN(iface, gateway *Endpoint, newcb EndpointNewCallback, lostcb EndpointLostCallback) *LAN { func NewLAN(iface, gateway *Endpoint, aliases *data.UnsortedKV, newcb EndpointNewCallback, lostcb EndpointLostCallback) *LAN {
aliases, err := data.NewUnsortedKV(aliasesFileName, data.FlushOnEdit)
if err != nil {
fmt.Printf("error loading %s: %s", aliasesFileName, err)
}
return &LAN{ return &LAN{
iface: iface, iface: iface,
gateway: gateway, gateway: gateway,
@ -63,19 +53,6 @@ func (l *LAN) MarshalJSON() ([]byte, error) {
return json.Marshal(doc) return json.Marshal(doc)
} }
func (lan *LAN) SetAliasFor(mac, alias string) bool {
lan.Lock()
defer lan.Unlock()
mac = NormalizeMac(mac)
lan.aliases.Set(mac, alias)
if e, found := lan.hosts[mac]; found {
e.Alias = alias
return true
}
return false
}
func (lan *LAN) Get(mac string) (*Endpoint, bool) { func (lan *LAN) Get(mac string) (*Endpoint, bool) {
lan.Lock() lan.Lock()
defer lan.Unlock() defer lan.Unlock()

View file

@ -286,7 +286,7 @@ func ActivateInterface(name string) error {
func SetInterfaceTxPower(name string, txpower int) error { func SetInterfaceTxPower(name string, txpower int) error {
if core.HasBinary("iwconfig") { if core.HasBinary("iwconfig") {
if out, err := core.ExecSilent("iwconfig", []string{name, "txpower", fmt.Sprintf("%d", txpower)}); err != nil { if out, err := core.Exec("iwconfig", []string{name, "txpower", fmt.Sprintf("%d", txpower)}); err != nil {
return err return err
} else if out != "" { } else if out != "" {
return fmt.Errorf("unexpected output while setting txpower to %d for interface %s: %s", txpower, name, out) return fmt.Errorf("unexpected output while setting txpower to %d for interface %s: %s", txpower, name, out)

View file

@ -11,6 +11,7 @@ import (
"github.com/google/gopacket/layers" "github.com/google/gopacket/layers"
"github.com/google/gopacket/pcapgo" "github.com/google/gopacket/pcapgo"
"github.com/evilsocket/islazy/data"
"github.com/evilsocket/islazy/fs" "github.com/evilsocket/islazy/fs"
) )
@ -43,22 +44,24 @@ type APLostCallback func(ap *AccessPoint)
type WiFi struct { type WiFi struct {
sync.Mutex sync.Mutex
aps map[string]*AccessPoint aliases *data.UnsortedKV
iface *Endpoint aps map[string]*AccessPoint
newCb APNewCallback iface *Endpoint
lostCb APLostCallback newCb APNewCallback
lostCb APLostCallback
} }
type wifiJSON struct { type wifiJSON struct {
AccessPoints []*AccessPoint `json:"aps"` AccessPoints []*AccessPoint `json:"aps"`
} }
func NewWiFi(iface *Endpoint, newcb APNewCallback, lostcb APLostCallback) *WiFi { func NewWiFi(iface *Endpoint, aliases *data.UnsortedKV, newcb APNewCallback, lostcb APLostCallback) *WiFi {
return &WiFi{ return &WiFi{
aps: make(map[string]*AccessPoint), aps: make(map[string]*AccessPoint),
iface: iface, aliases: aliases,
newCb: newcb, iface: iface,
lostCb: lostcb, newCb: newcb,
lostCb: lostcb,
} }
} }
@ -134,6 +137,7 @@ func (w *WiFi) AddIfNew(ssid, mac string, frequency int, rssi int8) (*AccessPoin
defer w.Unlock() defer w.Unlock()
mac = NormalizeMac(mac) mac = NormalizeMac(mac)
alias := w.aliases.GetOr(mac, "")
if ap, found := w.aps[mac]; found { if ap, found := w.aps[mac]; found {
ap.LastSeen = time.Now() ap.LastSeen = time.Now()
if rssi != 0 { if rssi != 0 {
@ -143,10 +147,15 @@ func (w *WiFi) AddIfNew(ssid, mac string, frequency int, rssi int8) (*AccessPoin
if !isBogusMacESSID(ssid) { if !isBogusMacESSID(ssid) {
ap.Hostname = ssid ap.Hostname = ssid
} }
if alias != "" {
ap.Alias = alias
}
return ap, false return ap, false
} }
newAp := NewAccessPoint(ssid, mac, frequency, rssi) newAp := NewAccessPoint(ssid, mac, frequency, rssi, w.aliases)
newAp.Alias = alias
w.aps[mac] = newAp w.aps[mac] = newAp
if w.newCb != nil { if w.newCb != nil {

View file

@ -12,6 +12,7 @@ type AccessPoint struct {
*Station *Station
sync.Mutex sync.Mutex
aliases *data.UnsortedKV
clients map[string]*Station clients map[string]*Station
withKeyMaterial bool withKeyMaterial bool
} }
@ -22,9 +23,10 @@ type apJSON struct {
Handshake bool `json:"handshake"` Handshake bool `json:"handshake"`
} }
func NewAccessPoint(essid, bssid string, frequency int, rssi int8) *AccessPoint { func NewAccessPoint(essid, bssid string, frequency int, rssi int8, aliases *data.UnsortedKV) *AccessPoint {
return &AccessPoint{ return &AccessPoint{
Station: NewStation(essid, bssid, frequency, rssi), Station: NewStation(essid, bssid, frequency, rssi),
aliases: aliases,
clients: make(map[string]*Station), clients: make(map[string]*Station),
} }
} }
@ -67,11 +69,12 @@ func (ap *AccessPoint) RemoveClient(mac string) {
} }
} }
func (ap *AccessPoint) AddClientIfNew(bssid string, frequency int, rssi int8, aliases *data.UnsortedKV) (*Station, bool) { func (ap *AccessPoint) AddClientIfNew(bssid string, frequency int, rssi int8) (*Station, bool) {
ap.Lock() ap.Lock()
defer ap.Unlock() defer ap.Unlock()
bssid = NormalizeMac(bssid) bssid = NormalizeMac(bssid)
alias := ap.aliases.GetOr(bssid, "")
if s, found := ap.clients[bssid]; found { if s, found := ap.clients[bssid]; found {
// update // update
@ -79,17 +82,15 @@ func (ap *AccessPoint) AddClientIfNew(bssid string, frequency int, rssi int8, al
s.RSSI = rssi s.RSSI = rssi
s.LastSeen = time.Now() s.LastSeen = time.Now()
if aliases != nil { if alias != "" {
s.Alias = aliases.GetOr(bssid, "") s.Alias = alias
} }
return s, false return s, false
} }
s := NewStation("", bssid, frequency, rssi) s := NewStation("", bssid, frequency, rssi)
if aliases != nil { s.Alias = alias
s.Alias = aliases.GetOr(bssid, "")
}
ap.clients[bssid] = s ap.clients[bssid] = s
return s, true return s, true

View file

@ -38,10 +38,11 @@ type PacketCallback func(pkt gopacket.Packet)
type Queue struct { type Queue struct {
sync.RWMutex sync.RWMutex
Activities chan Activity // keep on top because of https://github.com/bettercap/bettercap/issues/500
Stats Stats Stats Stats
Protos sync.Map Protos sync.Map
Traffic sync.Map Traffic sync.Map
Activities chan Activity
iface *network.Endpoint iface *network.Endpoint
handle *pcap.Handle handle *pcap.Handle
@ -62,6 +63,7 @@ func NewQueue(iface *network.Endpoint) (q *Queue, err error) {
q = &Queue{ q = &Queue{
Protos: sync.Map{}, Protos: sync.Map{},
Traffic: sync.Map{}, Traffic: sync.Map{},
Stats: Stats{},
Activities: make(chan Activity), Activities: make(chan Activity),
writes: &sync.WaitGroup{}, writes: &sync.WaitGroup{},
@ -165,6 +167,10 @@ func (q *Queue) trackActivity(eth *layers.Ethernet, ip4 *layers.IPv4, address ne
} }
func (q *Queue) TrackPacket(size uint64) { func (q *Queue) TrackPacket(size uint64) {
// https://github.com/bettercap/bettercap/issues/500
if q == nil {
panic("track packet on nil queue!")
}
atomic.AddUint64(&q.Stats.PktReceived, 1) atomic.AddUint64(&q.Stats.PktReceived, 1)
atomic.AddUint64(&q.Stats.Received, size) atomic.AddUint64(&q.Stats.Received, size)
} }

BIN
session.record Executable file

Binary file not shown.

View file

@ -111,7 +111,16 @@ func (p *EventPool) Add(tag string, data interface{}) {
// broadcast the event to every listener // broadcast the event to every listener
for _, l := range p.listeners { for _, l := range p.listeners {
l <- e // do not block!
go func(ch *chan Event) {
// channel might be closed
defer func() {
if recover() != nil {
}
}()
*ch <- e
}(&l)
} }
} }

View file

@ -1,13 +1,12 @@
package events_stream package session
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
"sync" "sync"
"github.com/bettercap/bettercap/session"
"github.com/evilsocket/islazy/str" "github.com/evilsocket/islazy/str"
) )
@ -15,24 +14,30 @@ var (
ErrEmptyExpression = errors.New("expression can not be empty") ErrEmptyExpression = errors.New("expression can not be empty")
) )
type IgnoreFilter string type filter string
func (f IgnoreFilter) Matches(s string) bool { func (f filter) Matches(s string) bool {
return string(f) == s || strings.HasPrefix(s, string(f)) return string(f) == s || strings.HasPrefix(s, string(f))
} }
type IgnoreList struct { type EventsIgnoreList struct {
sync.RWMutex sync.RWMutex
filters []IgnoreFilter filters []filter
} }
func NewIgnoreList() *IgnoreList { func NewEventsIgnoreList() *EventsIgnoreList {
return &IgnoreList{ return &EventsIgnoreList{
filters: make([]IgnoreFilter, 0), filters: make([]filter, 0),
} }
} }
func (l *IgnoreList) checkExpression(expr string) (string, error) { func (l *EventsIgnoreList) MarshalJSON() ([]byte, error) {
l.RLock()
defer l.RUnlock()
return json.Marshal(l.filters)
}
func (l *EventsIgnoreList) checkExpression(expr string) (string, error) {
expr = str.Trim(expr) expr = str.Trim(expr)
if expr == "" { if expr == "" {
return "", ErrEmptyExpression return "", ErrEmptyExpression
@ -41,7 +46,7 @@ func (l *IgnoreList) checkExpression(expr string) (string, error) {
return expr, nil return expr, nil
} }
func (l *IgnoreList) Add(expr string) (err error) { func (l *EventsIgnoreList) Add(expr string) (err error) {
if expr, err = l.checkExpression(expr); err != nil { if expr, err = l.checkExpression(expr); err != nil {
return err return err
} }
@ -57,12 +62,12 @@ func (l *IgnoreList) Add(expr string) (err error) {
} }
// all good // all good
l.filters = append(l.filters, IgnoreFilter(expr)) l.filters = append(l.filters, filter(expr))
return nil return nil
} }
func (l *IgnoreList) Remove(expr string) (err error) { func (l *EventsIgnoreList) Remove(expr string) (err error) {
if expr, err = l.checkExpression(expr); err != nil { if expr, err = l.checkExpression(expr); err != nil {
return err return err
} }
@ -71,8 +76,8 @@ func (l *IgnoreList) Remove(expr string) (err error) {
defer l.Unlock() defer l.Unlock()
// build a new list with everything that does not match // build a new list with everything that does not match
toRemove := IgnoreFilter(expr) toRemove := filter(expr)
newList := make([]IgnoreFilter, 0) newList := make([]filter, 0)
for _, filter := range l.filters { for _, filter := range l.filters {
if !toRemove.Matches(string(filter)) { if !toRemove.Matches(string(filter)) {
newList = append(newList, filter) newList = append(newList, filter)
@ -89,7 +94,13 @@ func (l *IgnoreList) Remove(expr string) (err error) {
return nil return nil
} }
func (l *IgnoreList) Ignored(e session.Event) bool { func (l *EventsIgnoreList) Clear() {
l.Lock()
defer l.Unlock()
l.filters = make([]filter, 0)
}
func (l *EventsIgnoreList) Ignored(e Event) bool {
l.RLock() l.RLock()
defer l.RUnlock() defer l.RUnlock()
@ -102,12 +113,12 @@ func (l *IgnoreList) Ignored(e session.Event) bool {
return false return false
} }
func (l *IgnoreList) Empty() bool { func (l *EventsIgnoreList) Empty() bool {
l.RLock() l.RLock()
defer l.RUnlock() defer l.RUnlock()
return len(l.filters) == 0 return len(l.filters) == 0
} }
func (l *IgnoreList) Filters() []IgnoreFilter { func (l *EventsIgnoreList) Filters() []filter {
return l.filters return l.filters
} }

View file

@ -1,6 +1,7 @@
package session package session
import ( import (
"encoding/json"
"fmt" "fmt"
"net" "net"
"strings" "strings"
@ -19,16 +20,47 @@ type Module interface {
Handlers() []ModuleHandler Handlers() []ModuleHandler
Parameters() map[string]*ModuleParam Parameters() map[string]*ModuleParam
Extra() map[string]interface{}
Running() bool Running() bool
Start() error Start() error
Stop() error Stop() error
} }
type ModuleList []Module
type moduleJSON struct {
Name string `json:"name"`
Description string `json:"description"`
Author string `json:"author"`
Parameters map[string]*ModuleParam `json:"parameters"`
Handlers []ModuleHandler `json:"handlers"`
Running bool `json:"running"`
State map[string]interface{} `json:"state"`
}
func (mm ModuleList) MarshalJSON() ([]byte, error) {
mods := []moduleJSON{}
for _, m := range mm {
mJSON := moduleJSON{
Name: m.Name(),
Description: m.Description(),
Author: m.Author(),
Parameters: m.Parameters(),
Handlers: m.Handlers(),
Running: m.Running(),
State: m.Extra(),
}
mods = append(mods, mJSON)
}
return json.Marshal(mods)
}
type SessionModule struct { type SessionModule struct {
Name string `json:"name"` Name string
Session *Session `json:"-"` Session *Session
Started bool `json:"started"` Started bool
StatusLock *sync.RWMutex `json:"-"` StatusLock *sync.RWMutex
State *sync.Map
handlers []ModuleHandler handlers []ModuleHandler
params map[string]*ModuleParam params map[string]*ModuleParam
@ -45,6 +77,7 @@ func NewSessionModule(name string, s *Session) SessionModule {
Session: s, Session: s,
Started: false, Started: false,
StatusLock: &sync.RWMutex{}, StatusLock: &sync.RWMutex{},
State: &sync.Map{},
handlers: make([]ModuleHandler, 0), handlers: make([]ModuleHandler, 0),
params: make(map[string]*ModuleParam), params: make(map[string]*ModuleParam),
@ -54,6 +87,28 @@ func NewSessionModule(name string, s *Session) SessionModule {
return m return m
} }
func (m *SessionModule) Extra() map[string]interface{} {
extra := make(map[string]interface{})
m.State.Range(func(k, v interface{}) bool {
extra[k.(string)] = v
return true
})
return extra
}
func (m *SessionModule) InitState(keys ...string) {
for _, key := range keys {
m.State.Store(key, nil)
}
}
func (m *SessionModule) ResetState() {
m.State.Range(func(k, v interface{}) bool {
m.State.Store(k, nil)
return true
})
}
func (m *SessionModule) Debug(format string, args ...interface{}) { func (m *SessionModule) Debug(format string, args ...interface{}) {
m.Session.Events.Log(log.DEBUG, m.tag+format, args...) m.Session.Events.Log(log.DEBUG, m.tag+format, args...)
} }
@ -176,9 +231,9 @@ func (m *SessionModule) Running() bool {
func (m *SessionModule) SetRunning(running bool, cb func()) error { func (m *SessionModule) SetRunning(running bool, cb func()) error {
if running == m.Running() { if running == m.Running() {
if m.Started { if m.Started {
return ErrAlreadyStarted return ErrAlreadyStarted(m.Name)
} else { } else {
return ErrAlreadyStopped return ErrAlreadyStopped(m.Name)
} }
} }

View file

@ -1,7 +1,6 @@
package session package session
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"net" "net"
@ -21,6 +20,7 @@ import (
"github.com/bettercap/bettercap/network" "github.com/bettercap/bettercap/network"
"github.com/bettercap/bettercap/packets" "github.com/bettercap/bettercap/packets"
"github.com/evilsocket/islazy/data"
"github.com/evilsocket/islazy/fs" "github.com/evilsocket/islazy/fs"
"github.com/evilsocket/islazy/log" "github.com/evilsocket/islazy/log"
"github.com/evilsocket/islazy/ops" "github.com/evilsocket/islazy/ops"
@ -35,43 +35,24 @@ const (
var ( var (
I = (*Session)(nil) I = (*Session)(nil)
ErrAlreadyStarted = errors.New("module is already running") ErrNotSupported = errors.New("this component is not supported on this OS")
ErrAlreadyStopped = errors.New("module is not running")
ErrNotSupported = errors.New("this component is not supported on this OS")
reCmdSpaceCleaner = regexp.MustCompile(`^([^\s]+)\s+(.+)$`) reCmdSpaceCleaner = regexp.MustCompile(`^([^\s]+)\s+(.+)$`)
reEnvVarCapture = regexp.MustCompile(`{env\.([^}]+)}`) reEnvVarCapture = regexp.MustCompile(`{env\.([^}]+)}`)
) )
func ErrAlreadyStarted(name string) error {
return fmt.Errorf("module %s is already running", name)
}
func ErrAlreadyStopped(name string) error {
return fmt.Errorf("module %s is not running", name)
}
type UnknownCommandCallback func(cmd string) bool type UnknownCommandCallback func(cmd string) bool
type ModuleList []Module
type JSONModule struct {
Name string `json:"name"`
Description string `json:"description"`
Author string `json:"author"`
Parameters map[string]*ModuleParam `json:"parameters"`
Handlers []ModuleHandler `json:"handlers"`
Running bool `json:"running"`
}
func (mm ModuleList) MarshalJSON() ([]byte, error) {
mods := []JSONModule{}
for _, m := range mm {
mods = append(mods, JSONModule{
Name: m.Name(),
Description: m.Description(),
Author: m.Author(),
Parameters: m.Parameters(),
Handlers: m.Handlers(),
Running: m.Running(),
})
}
return json.Marshal(mods)
}
type GPS struct { type GPS struct {
Updated time.Time
Latitude float64 // Latitude. Latitude float64 // Latitude.
Longitude float64 // Longitude. Longitude float64 // Longitude.
FixQuality string // Quality of fix. FixQuality string // Quality of fix.
@ -81,27 +62,33 @@ type GPS struct {
Separation float64 // Geoidal separation Separation float64 // Geoidal separation
} }
type Session struct { const AliasesFile = "~/bettercap.aliases"
Options core.Options `json:"options"`
Interface *network.Endpoint `json:"interface"`
Gateway *network.Endpoint `json:"gateway"`
Env *Environment `json:"env"`
Lan *network.LAN `json:"lan"`
WiFi *network.WiFi `json:"wifi"`
BLE *network.BLE `json:"ble"`
HID *network.HID `json:"hid"`
Queue *packets.Queue `json:"packets"`
StartedAt time.Time `json:"started_at"`
Active bool `json:"active"`
GPS GPS `json:"gps"`
Modules ModuleList `json:"modules"`
Input *readline.Instance `json:"-"` var aliasesFileName, _ = fs.Expand(AliasesFile)
Prompt Prompt `json:"-"`
CoreHandlers []CommandHandler `json:"-"` type Session struct {
Events *EventPool `json:"-"` Options core.Options
UnkCmdCallback UnknownCommandCallback `json:"-"` Interface *network.Endpoint
Firewall firewall.FirewallManager `json:"-"` Gateway *network.Endpoint
Env *Environment
Lan *network.LAN
WiFi *network.WiFi
BLE *network.BLE
HID *network.HID
Queue *packets.Queue
StartedAt time.Time
Active bool
GPS GPS
Modules ModuleList
Aliases *data.UnsortedKV
Input *readline.Instance
Prompt Prompt
CoreHandlers []CommandHandler
Events *EventPool
EventsIgnoreList *EventsIgnoreList
UnkCmdCallback UnknownCommandCallback
Firewall firewall.FirewallManager
} }
func New() (*Session, error) { func New() (*Session, error) {
@ -122,10 +109,11 @@ func New() (*Session, error) {
Active: false, Active: false,
Queue: nil, Queue: nil,
CoreHandlers: make([]CommandHandler, 0), CoreHandlers: make([]CommandHandler, 0),
Modules: make([]Module, 0), Modules: make([]Module, 0),
Events: nil, Events: nil,
UnkCmdCallback: nil, EventsIgnoreList: NewEventsIgnoreList(),
UnkCmdCallback: nil,
} }
if *s.Options.CpuProfile != "" { if *s.Options.CpuProfile != "" {
@ -140,6 +128,10 @@ func New() (*Session, error) {
return nil, err return nil, err
} }
if s.Aliases, err = data.NewUnsortedKV(aliasesFileName, data.FlushOnEdit); err != nil {
return nil, err
}
s.Events = NewEventPool(*s.Options.Debug, *s.Options.Silent) s.Events = NewEventPool(*s.Options.Debug, *s.Options.Silent)
s.registerCoreHandlers() s.registerCoreHandlers()
@ -260,25 +252,25 @@ func (s *Session) Start() error {
s.Firewall = firewall.Make(s.Interface) s.Firewall = firewall.Make(s.Interface)
s.HID = network.NewHID(func(dev *network.HIDDevice) { s.HID = network.NewHID(s.Aliases, func(dev *network.HIDDevice) {
s.Events.Add("hid.device.new", dev) s.Events.Add("hid.device.new", dev)
}, func(dev *network.HIDDevice) { }, func(dev *network.HIDDevice) {
s.Events.Add("hid.device.lost", dev) s.Events.Add("hid.device.lost", dev)
}) })
s.BLE = network.NewBLE(func(dev *network.BLEDevice) { s.BLE = network.NewBLE(s.Aliases, func(dev *network.BLEDevice) {
s.Events.Add("ble.device.new", dev) s.Events.Add("ble.device.new", dev)
}, func(dev *network.BLEDevice) { }, func(dev *network.BLEDevice) {
s.Events.Add("ble.device.lost", dev) s.Events.Add("ble.device.lost", dev)
}) })
s.WiFi = network.NewWiFi(s.Interface, func(ap *network.AccessPoint) { s.WiFi = network.NewWiFi(s.Interface, s.Aliases, func(ap *network.AccessPoint) {
s.Events.Add("wifi.ap.new", ap) s.Events.Add("wifi.ap.new", ap)
}, func(ap *network.AccessPoint) { }, func(ap *network.AccessPoint) {
s.Events.Add("wifi.ap.lost", ap) s.Events.Add("wifi.ap.lost", ap)
}) })
s.Lan = network.NewLAN(s.Interface, s.Gateway, func(e *network.Endpoint) { s.Lan = network.NewLAN(s.Interface, s.Gateway, s.Aliases, func(e *network.Endpoint) {
s.Events.Add("endpoint.new", e) s.Events.Add("endpoint.new", e)
}, func(e *network.Endpoint) { }, func(e *network.Endpoint) {
s.Events.Add("endpoint.lost", e) s.Events.Add("endpoint.lost", e)

View file

@ -4,7 +4,9 @@ import (
"bufio" "bufio"
"fmt" "fmt"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"runtime"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@ -249,13 +251,14 @@ func (s *Session) readHandler(args []string, sess *Session) error {
} }
func (s *Session) clsHandler(args []string, sess *Session) error { func (s *Session) clsHandler(args []string, sess *Session) error {
// fixes a weird bug which causes the screen not to be fully cmd := "clear"
// cleared if a "clear; net.show" commands chain is executed if runtime.GOOS == "windows" {
// in the interactive session. cmd = "cls"
for i := 0; i < 180; i++ {
fmt.Println()
} }
readline.ClearScreen(s.Input.Stdout())
c := exec.Command(cmd)
c.Stdout = os.Stdout
c.Run()
return nil return nil
} }
@ -271,10 +274,55 @@ func (s *Session) shHandler(args []string, sess *Session) error {
return err return err
} }
func normalizeMac(mac string) string {
var parts []string
if strings.ContainsRune(mac, '-') {
parts = strings.Split(mac, "-")
} else {
parts = strings.Split(mac, ":")
}
for i, p := range parts {
if len(p) < 2 {
parts[i] = "0" + p
}
}
return strings.ToLower(strings.Join(parts, ":"))
}
func (s *Session) propagateAlias(mac, alias string) {
mac = normalizeMac(mac)
s.Aliases.Set(mac, alias)
if dev, found := s.BLE.Get(mac); found {
dev.Alias = alias
}
if dev, found := s.HID.Get(mac); found {
dev.Alias = alias
}
if ap, found := s.WiFi.Get(mac); found {
ap.Alias = alias
}
if sta, found := s.WiFi.GetClient(mac); found {
sta.Alias = alias
}
if host, found := s.Lan.Get(mac); found {
host.Alias = alias
}
}
func (s *Session) aliasHandler(args []string, sess *Session) error { func (s *Session) aliasHandler(args []string, sess *Session) error {
mac := args[0] mac := args[0]
alias := str.Trim(args[1]) alias := str.Trim(args[1])
s.Lan.SetAliasFor(mac, alias) if alias == "\"\"" || alias == "''" {
alias = ""
}
s.propagateAlias(mac, alias)
return nil return nil
} }
@ -380,7 +428,7 @@ func (s *Session) registerCoreHandlers() {
readline.PcItem("!")) readline.PcItem("!"))
s.addHandler(NewCommandHandler("alias MAC NAME", s.addHandler(NewCommandHandler("alias MAC NAME",
"^alias\\s+([a-fA-F0-9:]{17})\\s*(.*)", "^alias\\s+([a-fA-F0-9:]{14,17})\\s*(.*)",
"Assign an alias to a given endpoint given its MAC address.", "Assign an alias to a given endpoint given its MAC address.",
s.aliasHandler), s.aliasHandler),
readline.PcItem("alias", readline.PcItemDynamic(func(prefix string) []string { readline.PcItem("alias", readline.PcItemDynamic(func(prefix string) []string {

122
session/session_json.go Normal file
View file

@ -0,0 +1,122 @@
package session
import (
"encoding/json"
"net"
"runtime"
"time"
"github.com/bettercap/bettercap/caplets"
"github.com/bettercap/bettercap/core"
"github.com/bettercap/bettercap/network"
"github.com/bettercap/bettercap/packets"
)
var flagNames = []string{
"UP",
"BROADCAST",
"LOOPBACK",
"POINT2POINT",
"MULTICAST",
}
type addrJSON struct {
Address string `json:"address"`
Type string `json:"type"`
}
type ifaceJSON struct {
Index int `json:"index"`
MTU int `json:"mtu"`
Name string `json:"name"`
MAC string `json:"mac"`
Vendor string `json:"vendor"`
Flags []string `json:"flags"`
Addresses []addrJSON `json:"addresses"`
}
type SessionJSON struct {
Version string `json:"version"`
OS string `json:"os"`
Arch string `json:"arch"`
GoVersion string `json:"goversion"`
Interfaces []ifaceJSON `json:"interfaces"`
Options core.Options `json:"options"`
Interface *network.Endpoint `json:"interface"`
Gateway *network.Endpoint `json:"gateway"`
Env *Environment `json:"env"`
Lan *network.LAN `json:"lan"`
WiFi *network.WiFi `json:"wifi"`
BLE *network.BLE `json:"ble"`
HID *network.HID `json:"hid"`
Queue *packets.Queue `json:"packets"`
StartedAt time.Time `json:"started_at"`
PolledAt time.Time `json:"polled_at"`
Active bool `json:"active"`
GPS GPS `json:"gps"`
Modules ModuleList `json:"modules"`
Caplets []*caplets.Caplet `json:"caplets"`
}
func (s *Session) MarshalJSON() ([]byte, error) {
doc := SessionJSON{
Version: core.Version,
OS: runtime.GOOS,
Arch: runtime.GOARCH,
GoVersion: runtime.Version(),
Interfaces: make([]ifaceJSON, 0),
Options: s.Options,
Interface: s.Interface,
Gateway: s.Gateway,
Env: s.Env,
Lan: s.Lan,
WiFi: s.WiFi,
BLE: s.BLE,
HID: s.HID,
Queue: s.Queue,
StartedAt: s.StartedAt,
PolledAt: time.Now(),
Active: s.Active,
GPS: s.GPS,
Modules: s.Modules,
Caplets: caplets.List(),
}
ifaces, err := net.Interfaces()
if err != nil {
return nil, err
}
for _, iface := range ifaces {
mac := network.NormalizeMac(iface.HardwareAddr.String())
ij := ifaceJSON{
Index: iface.Index,
MTU: iface.MTU,
Name: iface.Name,
MAC: mac,
Vendor: network.ManufLookup(mac),
Flags: make([]string, 0),
Addresses: make([]addrJSON, 0),
}
if addrs, err := iface.Addrs(); err == nil {
for _, addr := range addrs {
ij.Addresses = append(ij.Addresses, addrJSON{
Address: addr.String(),
Type: addr.Network(),
})
}
}
for bit, name := range flagNames {
if iface.Flags&(1<<uint(bit)) != 0 {
ij.Flags = append(ij.Flags, name)
}
}
doc.Interfaces = append(doc.Interfaces, ij)
}
return json.Marshal(doc)
}

View file

@ -1981,6 +1981,13 @@ func (a *Advertisement) unmarshall(b []byte) error {
// Utility function for creating a list of uuids. // Utility function for creating a list of uuids.
uuidList := func(u []UUID, d []byte, w int) []UUID { uuidList := func(u []UUID, d []byte, w int) []UUID {
// https://github.com/bettercap/gatt/issues/8
defer func() {
if recover() != nil {
}
}()
for len(d) > 0 { for len(d) > 0 {
u = append(u, UUID{d[:w]}) u = append(u, UUID{d[:w]})
d = d[w:] d = d[w:]

View file

@ -13,6 +13,8 @@ import (
// within the zip file (parameter 1) to an output directory (parameter 2). // within the zip file (parameter 1) to an output directory (parameter 2).
// Credits to https://golangcode.com/unzip-files-in-go/ // Credits to https://golangcode.com/unzip-files-in-go/
func Unzip(src string, dest string) ([]string, error) { func Unzip(src string, dest string) ([]string, error) {
var outFile *os.File
var zipFile io.ReadCloser
var filenames []string var filenames []string
r, err := zip.OpenReader(src) r, err := zip.OpenReader(src)
@ -21,33 +23,55 @@ func Unzip(src string, dest string) ([]string, error) {
} }
defer r.Close() defer r.Close()
clean := func() {
if outFile != nil {
outFile.Close()
outFile = nil
}
if zipFile != nil {
zipFile.Close()
zipFile = nil
}
}
for _, f := range r.File { for _, f := range r.File {
rc, err := f.Open() zipFile, err = f.Open()
if err != nil { if err != nil {
return filenames, err return filenames, err
} }
defer rc.Close()
// Store filename/path for returning and using later on // Store filename/path for returning and using later on
fpath := filepath.Join(dest, f.Name) fpath := filepath.Join(dest, f.Name)
// Check for ZipSlip. More Info: https://snyk.io/research/zip-slip-vulnerability#go // Check for ZipSlip. More Info: https://snyk.io/research/zip-slip-vulnerability#go
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) { if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
clean()
return filenames, fmt.Errorf("%s: illegal file path", fpath) return filenames, fmt.Errorf("%s: illegal file path", fpath)
} }
filenames = append(filenames, fpath) filenames = append(filenames, fpath)
if f.FileInfo().IsDir() { if f.FileInfo().IsDir() {
os.MkdirAll(fpath, os.ModePerm) os.MkdirAll(fpath, os.ModePerm)
} else if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil { clean()
continue
}
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
clean()
return filenames, err return filenames, err
} else if outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()); err != nil { }
outFile, err = os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
clean()
return filenames, err
}
_, err = io.Copy(outFile, zipFile)
clean()
if err != nil {
return filenames, err return filenames, err
} else {
defer outFile.Close()
if _, err = io.Copy(outFile, rc); err != nil {
return filenames, err
}
} }
} }

1
vendor/github.com/kr/binarydist/.gitignore generated vendored Normal file
View file

@ -0,0 +1 @@
test.*

22
vendor/github.com/kr/binarydist/License generated vendored Normal file
View file

@ -0,0 +1,22 @@
Copyright 2012 Keith Rarick
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

7
vendor/github.com/kr/binarydist/Readme.md generated vendored Normal file
View file

@ -0,0 +1,7 @@
# binarydist
Package binarydist implements binary diff and patch as described on
<http://www.daemonology.net/bsdiff/>. It reads and writes files
compatible with the tools there.
Documentation at <http://go.pkgdoc.org/github.com/kr/binarydist>.

40
vendor/github.com/kr/binarydist/bzip2.go generated vendored Normal file
View file

@ -0,0 +1,40 @@
package binarydist
import (
"io"
"os/exec"
)
type bzip2Writer struct {
c *exec.Cmd
w io.WriteCloser
}
func (w bzip2Writer) Write(b []byte) (int, error) {
return w.w.Write(b)
}
func (w bzip2Writer) Close() error {
if err := w.w.Close(); err != nil {
return err
}
return w.c.Wait()
}
// Package compress/bzip2 implements only decompression,
// so we'll fake it by running bzip2 in another process.
func newBzip2Writer(w io.Writer) (wc io.WriteCloser, err error) {
var bw bzip2Writer
bw.c = exec.Command("bzip2", "-c")
bw.c.Stdout = w
if bw.w, err = bw.c.StdinPipe(); err != nil {
return nil, err
}
if err = bw.c.Start(); err != nil {
return nil, err
}
return bw, nil
}

408
vendor/github.com/kr/binarydist/diff.go generated vendored Normal file
View file

@ -0,0 +1,408 @@
package binarydist
import (
"bytes"
"encoding/binary"
"io"
"io/ioutil"
)
func swap(a []int, i, j int) { a[i], a[j] = a[j], a[i] }
func split(I, V []int, start, length, h int) {
var i, j, k, x, jj, kk int
if length < 16 {
for k = start; k < start+length; k += j {
j = 1
x = V[I[k]+h]
for i = 1; k+i < start+length; i++ {
if V[I[k+i]+h] < x {
x = V[I[k+i]+h]
j = 0
}
if V[I[k+i]+h] == x {
swap(I, k+i, k+j)
j++
}
}
for i = 0; i < j; i++ {
V[I[k+i]] = k + j - 1
}
if j == 1 {
I[k] = -1
}
}
return
}
x = V[I[start+length/2]+h]
jj = 0
kk = 0
for i = start; i < start+length; i++ {
if V[I[i]+h] < x {
jj++
}
if V[I[i]+h] == x {
kk++
}
}
jj += start
kk += jj
i = start
j = 0
k = 0
for i < jj {
if V[I[i]+h] < x {
i++
} else if V[I[i]+h] == x {
swap(I, i, jj+j)
j++
} else {
swap(I, i, kk+k)
k++
}
}
for jj+j < kk {
if V[I[jj+j]+h] == x {
j++
} else {
swap(I, jj+j, kk+k)
k++
}
}
if jj > start {
split(I, V, start, jj-start, h)
}
for i = 0; i < kk-jj; i++ {
V[I[jj+i]] = kk - 1
}
if jj == kk-1 {
I[jj] = -1
}
if start+length > kk {
split(I, V, kk, start+length-kk, h)
}
}
func qsufsort(obuf []byte) []int {
var buckets [256]int
var i, h int
I := make([]int, len(obuf)+1)
V := make([]int, len(obuf)+1)
for _, c := range obuf {
buckets[c]++
}
for i = 1; i < 256; i++ {
buckets[i] += buckets[i-1]
}
copy(buckets[1:], buckets[:])
buckets[0] = 0
for i, c := range obuf {
buckets[c]++
I[buckets[c]] = i
}
I[0] = len(obuf)
for i, c := range obuf {
V[i] = buckets[c]
}
V[len(obuf)] = 0
for i = 1; i < 256; i++ {
if buckets[i] == buckets[i-1]+1 {
I[buckets[i]] = -1
}
}
I[0] = -1
for h = 1; I[0] != -(len(obuf) + 1); h += h {
var n int
for i = 0; i < len(obuf)+1; {
if I[i] < 0 {
n -= I[i]
i -= I[i]
} else {
if n != 0 {
I[i-n] = -n
}
n = V[I[i]] + 1 - i
split(I, V, i, n, h)
i += n
n = 0
}
}
if n != 0 {
I[i-n] = -n
}
}
for i = 0; i < len(obuf)+1; i++ {
I[V[i]] = i
}
return I
}
func matchlen(a, b []byte) (i int) {
for i < len(a) && i < len(b) && a[i] == b[i] {
i++
}
return i
}
func search(I []int, obuf, nbuf []byte, st, en int) (pos, n int) {
if en-st < 2 {
x := matchlen(obuf[I[st]:], nbuf)
y := matchlen(obuf[I[en]:], nbuf)
if x > y {
return I[st], x
} else {
return I[en], y
}
}
x := st + (en-st)/2
if bytes.Compare(obuf[I[x]:], nbuf) < 0 {
return search(I, obuf, nbuf, x, en)
} else {
return search(I, obuf, nbuf, st, x)
}
panic("unreached")
}
// Diff computes the difference between old and new, according to the bsdiff
// algorithm, and writes the result to patch.
func Diff(old, new io.Reader, patch io.Writer) error {
obuf, err := ioutil.ReadAll(old)
if err != nil {
return err
}
nbuf, err := ioutil.ReadAll(new)
if err != nil {
return err
}
pbuf, err := diffBytes(obuf, nbuf)
if err != nil {
return err
}
_, err = patch.Write(pbuf)
return err
}
func diffBytes(obuf, nbuf []byte) ([]byte, error) {
var patch seekBuffer
err := diff(obuf, nbuf, &patch)
if err != nil {
return nil, err
}
return patch.buf, nil
}
func diff(obuf, nbuf []byte, patch io.WriteSeeker) error {
var lenf int
I := qsufsort(obuf)
db := make([]byte, len(nbuf))
eb := make([]byte, len(nbuf))
var dblen, eblen int
var hdr header
hdr.Magic = magic
hdr.NewSize = int64(len(nbuf))
err := binary.Write(patch, signMagLittleEndian{}, &hdr)
if err != nil {
return err
}
// Compute the differences, writing ctrl as we go
pfbz2, err := newBzip2Writer(patch)
if err != nil {
return err
}
var scan, pos, length int
var lastscan, lastpos, lastoffset int
for scan < len(nbuf) {
var oldscore int
scan += length
for scsc := scan; scan < len(nbuf); scan++ {
pos, length = search(I, obuf, nbuf[scan:], 0, len(obuf))
for ; scsc < scan+length; scsc++ {
if scsc+lastoffset < len(obuf) &&
obuf[scsc+lastoffset] == nbuf[scsc] {
oldscore++
}
}
if (length == oldscore && length != 0) || length > oldscore+8 {
break
}
if scan+lastoffset < len(obuf) && obuf[scan+lastoffset] == nbuf[scan] {
oldscore--
}
}
if length != oldscore || scan == len(nbuf) {
var s, Sf int
lenf = 0
for i := 0; lastscan+i < scan && lastpos+i < len(obuf); {
if obuf[lastpos+i] == nbuf[lastscan+i] {
s++
}
i++
if s*2-i > Sf*2-lenf {
Sf = s
lenf = i
}
}
lenb := 0
if scan < len(nbuf) {
var s, Sb int
for i := 1; (scan >= lastscan+i) && (pos >= i); i++ {
if obuf[pos-i] == nbuf[scan-i] {
s++
}
if s*2-i > Sb*2-lenb {
Sb = s
lenb = i
}
}
}
if lastscan+lenf > scan-lenb {
overlap := (lastscan + lenf) - (scan - lenb)
s := 0
Ss := 0
lens := 0
for i := 0; i < overlap; i++ {
if nbuf[lastscan+lenf-overlap+i] == obuf[lastpos+lenf-overlap+i] {
s++
}
if nbuf[scan-lenb+i] == obuf[pos-lenb+i] {
s--
}
if s > Ss {
Ss = s
lens = i + 1
}
}
lenf += lens - overlap
lenb -= lens
}
for i := 0; i < lenf; i++ {
db[dblen+i] = nbuf[lastscan+i] - obuf[lastpos+i]
}
for i := 0; i < (scan-lenb)-(lastscan+lenf); i++ {
eb[eblen+i] = nbuf[lastscan+lenf+i]
}
dblen += lenf
eblen += (scan - lenb) - (lastscan + lenf)
err = binary.Write(pfbz2, signMagLittleEndian{}, int64(lenf))
if err != nil {
pfbz2.Close()
return err
}
val := (scan - lenb) - (lastscan + lenf)
err = binary.Write(pfbz2, signMagLittleEndian{}, int64(val))
if err != nil {
pfbz2.Close()
return err
}
val = (pos - lenb) - (lastpos + lenf)
err = binary.Write(pfbz2, signMagLittleEndian{}, int64(val))
if err != nil {
pfbz2.Close()
return err
}
lastscan = scan - lenb
lastpos = pos - lenb
lastoffset = pos - scan
}
}
err = pfbz2.Close()
if err != nil {
return err
}
// Compute size of compressed ctrl data
l64, err := patch.Seek(0, 1)
if err != nil {
return err
}
hdr.CtrlLen = int64(l64 - 32)
// Write compressed diff data
pfbz2, err = newBzip2Writer(patch)
if err != nil {
return err
}
n, err := pfbz2.Write(db[:dblen])
if err != nil {
pfbz2.Close()
return err
}
if n != dblen {
pfbz2.Close()
return io.ErrShortWrite
}
err = pfbz2.Close()
if err != nil {
return err
}
// Compute size of compressed diff data
n64, err := patch.Seek(0, 1)
if err != nil {
return err
}
hdr.DiffLen = n64 - l64
// Write compressed extra data
pfbz2, err = newBzip2Writer(patch)
if err != nil {
return err
}
n, err = pfbz2.Write(eb[:eblen])
if err != nil {
pfbz2.Close()
return err
}
if n != eblen {
pfbz2.Close()
return io.ErrShortWrite
}
err = pfbz2.Close()
if err != nil {
return err
}
// Seek to the beginning, write the header, and close the file
_, err = patch.Seek(0, 0)
if err != nil {
return err
}
err = binary.Write(patch, signMagLittleEndian{}, &hdr)
if err != nil {
return err
}
return nil
}

24
vendor/github.com/kr/binarydist/doc.go generated vendored Normal file
View file

@ -0,0 +1,24 @@
// Package binarydist implements binary diff and patch as described on
// http://www.daemonology.net/bsdiff/. It reads and writes files
// compatible with the tools there.
package binarydist
var magic = [8]byte{'B', 'S', 'D', 'I', 'F', 'F', '4', '0'}
// File format:
// 0 8 "BSDIFF40"
// 8 8 X
// 16 8 Y
// 24 8 sizeof(newfile)
// 32 X bzip2(control block)
// 32+X Y bzip2(diff block)
// 32+X+Y ??? bzip2(extra block)
// with control block a set of triples (x,y,z) meaning "add x bytes
// from oldfile to x bytes from the diff block; copy y bytes from the
// extra block; seek forwards in oldfile by z bytes".
type header struct {
Magic [8]byte
CtrlLen int64
DiffLen int64
NewSize int64
}

53
vendor/github.com/kr/binarydist/encoding.go generated vendored Normal file
View file

@ -0,0 +1,53 @@
package binarydist
// SignMagLittleEndian is the numeric encoding used by the bsdiff tools.
// It implements binary.ByteOrder using a sign-magnitude format
// and little-endian byte order. Only methods Uint64 and String
// have been written; the rest panic.
type signMagLittleEndian struct{}
func (signMagLittleEndian) Uint16(b []byte) uint16 { panic("unimplemented") }
func (signMagLittleEndian) PutUint16(b []byte, v uint16) { panic("unimplemented") }
func (signMagLittleEndian) Uint32(b []byte) uint32 { panic("unimplemented") }
func (signMagLittleEndian) PutUint32(b []byte, v uint32) { panic("unimplemented") }
func (signMagLittleEndian) Uint64(b []byte) uint64 {
y := int64(b[0]) |
int64(b[1])<<8 |
int64(b[2])<<16 |
int64(b[3])<<24 |
int64(b[4])<<32 |
int64(b[5])<<40 |
int64(b[6])<<48 |
int64(b[7]&0x7f)<<56
if b[7]&0x80 != 0 {
y = -y
}
return uint64(y)
}
func (signMagLittleEndian) PutUint64(b []byte, v uint64) {
x := int64(v)
neg := x < 0
if neg {
x = -x
}
b[0] = byte(x)
b[1] = byte(x >> 8)
b[2] = byte(x >> 16)
b[3] = byte(x >> 24)
b[4] = byte(x >> 32)
b[5] = byte(x >> 40)
b[6] = byte(x >> 48)
b[7] = byte(x >> 56)
if neg {
b[7] |= 0x80
}
}
func (signMagLittleEndian) String() string { return "signMagLittleEndian" }

1
vendor/github.com/kr/binarydist/go.mod generated vendored Normal file
View file

@ -0,0 +1 @@
module "github.com/kr/binarydist"

109
vendor/github.com/kr/binarydist/patch.go generated vendored Normal file
View file

@ -0,0 +1,109 @@
package binarydist
import (
"bytes"
"compress/bzip2"
"encoding/binary"
"errors"
"io"
"io/ioutil"
)
var ErrCorrupt = errors.New("corrupt patch")
// Patch applies patch to old, according to the bspatch algorithm,
// and writes the result to new.
func Patch(old io.Reader, new io.Writer, patch io.Reader) error {
var hdr header
err := binary.Read(patch, signMagLittleEndian{}, &hdr)
if err != nil {
return err
}
if hdr.Magic != magic {
return ErrCorrupt
}
if hdr.CtrlLen < 0 || hdr.DiffLen < 0 || hdr.NewSize < 0 {
return ErrCorrupt
}
ctrlbuf := make([]byte, hdr.CtrlLen)
_, err = io.ReadFull(patch, ctrlbuf)
if err != nil {
return err
}
cpfbz2 := bzip2.NewReader(bytes.NewReader(ctrlbuf))
diffbuf := make([]byte, hdr.DiffLen)
_, err = io.ReadFull(patch, diffbuf)
if err != nil {
return err
}
dpfbz2 := bzip2.NewReader(bytes.NewReader(diffbuf))
// The entire rest of the file is the extra block.
epfbz2 := bzip2.NewReader(patch)
obuf, err := ioutil.ReadAll(old)
if err != nil {
return err
}
nbuf := make([]byte, hdr.NewSize)
var oldpos, newpos int64
for newpos < hdr.NewSize {
var ctrl struct{ Add, Copy, Seek int64 }
err = binary.Read(cpfbz2, signMagLittleEndian{}, &ctrl)
if err != nil {
return err
}
// Sanity-check
if newpos+ctrl.Add > hdr.NewSize {
return ErrCorrupt
}
// Read diff string
_, err = io.ReadFull(dpfbz2, nbuf[newpos:newpos+ctrl.Add])
if err != nil {
return ErrCorrupt
}
// Add old data to diff string
for i := int64(0); i < ctrl.Add; i++ {
if oldpos+i >= 0 && oldpos+i < int64(len(obuf)) {
nbuf[newpos+i] += obuf[oldpos+i]
}
}
// Adjust pointers
newpos += ctrl.Add
oldpos += ctrl.Add
// Sanity-check
if newpos+ctrl.Copy > hdr.NewSize {
return ErrCorrupt
}
// Read extra string
_, err = io.ReadFull(epfbz2, nbuf[newpos:newpos+ctrl.Copy])
if err != nil {
return ErrCorrupt
}
// Adjust pointers
newpos += ctrl.Copy
oldpos += ctrl.Seek
}
// Write the new file
for len(nbuf) > 0 {
n, err := new.Write(nbuf)
if err != nil {
return err
}
nbuf = nbuf[n:]
}
return nil
}

43
vendor/github.com/kr/binarydist/seek.go generated vendored Normal file
View file

@ -0,0 +1,43 @@
package binarydist
import (
"errors"
)
type seekBuffer struct {
buf []byte
pos int
}
func (b *seekBuffer) Write(p []byte) (n int, err error) {
n = copy(b.buf[b.pos:], p)
if n == len(p) {
b.pos += n
return n, nil
}
b.buf = append(b.buf, p[n:]...)
b.pos += len(p)
return len(p), nil
}
func (b *seekBuffer) Seek(offset int64, whence int) (ret int64, err error) {
var abs int64
switch whence {
case 0:
abs = offset
case 1:
abs = int64(b.pos) + offset
case 2:
abs = int64(len(b.buf)) + offset
default:
return 0, errors.New("binarydist: invalid whence")
}
if abs < 0 {
return 0, errors.New("binarydist: negative position")
}
if abs >= 1<<31 {
return 0, errors.New("binarydist: position out of range")
}
b.pos = int(abs)
return abs, nil
}