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
FROM alpine
RUN apk add --update ca-certificates
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/caplets /app/

19
Gopkg.lock generated
View file

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

View file

@ -73,3 +73,7 @@
[prune]
go-tests = 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"
)
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 {
Name string
Path string
Size int64
Code []string
Script
Name string `json:"name"`
Scripts []Script `json:"scripts"`
}
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 {
@ -23,6 +44,10 @@ func (cap *Caplet) Eval(argv []string, lineCb func(line string) error) error {
// temporarily change the working directory
return fs.Chdir(filepath.Dir(cap.Path), func() error {
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
for i, arg := range argv {
what := fmt.Sprintf("$%d", i)

View file

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

View file

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

View file

@ -1,7 +1,6 @@
package core
import (
"fmt"
"os/exec"
"sort"
@ -34,7 +33,7 @@ func HasBinary(executable string) bool {
return true
}
func ExecSilent(executable string, args []string) (string, error) {
func Exec(executable string, args []string) (string, error) {
path, err := exec.LookPath(executable)
if err != nil {
return "", err
@ -47,11 +46,3 @@ func ExecSilent(executable string, args []string) (string, error) {
return str.Trim(string(raw)), nil
}
}
func Exec(executable string, args []string) (string, error) {
out, err := ExecSilent(executable, args)
if err != nil {
fmt.Printf("ERROR for '%s %s': %s\n", executable, args, err)
}
return out, err
}

View file

@ -22,7 +22,7 @@ func ParseOptions() (Options, error) {
o := Options{
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."),
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."),
Debug: flag.Bool("debug", false, "Print debug messages."),
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) {
if out, err := core.ExecSilent("sysctl", []string{param}); err != nil {
if out, err := core.Exec("sysctl", []string{param}); err != nil {
return "", err
} else if m := sysCtlParser.FindStringSubmatch(out); len(m) == 3 && m[1] == param {
return m[2], nil
@ -52,7 +52,7 @@ func (f PfFirewall) sysCtlRead(param string) (string, error) {
func (f PfFirewall) sysCtlWrite(param string, value string) (string, error) {
args := []string{"-w", fmt.Sprintf("%s=%s", param, value)}
_, err := core.ExecSilent("sysctl", args)
_, err := core.Exec("sysctl", args)
if err != nil {
return "", err
}
@ -115,9 +115,9 @@ func (f PfFirewall) generateRule(r *Redirection) string {
func (f *PfFirewall) enable(enabled bool) {
f.enabled = enabled
if enabled {
core.ExecSilent("pfctl", []string{"-e"})
core.Exec("pfctl", []string{"-e"})
} else {
core.ExecSilent("pfctl", []string{"-d"})
core.Exec("pfctl", []string{"-d"})
}
}
@ -139,7 +139,7 @@ func (f PfFirewall) EnableRedirection(r *Redirection, enabled bool) error {
f.enable(true)
// load the rule
if _, err := core.ExecSilent("pfctl", []string{"-f", f.filename}); err != nil {
if _, err := core.Exec("pfctl", []string{"-f", f.filename}); err != nil {
return err
}
} else {

View file

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

View file

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
"sync"
"time"
"github.com/bettercap/bettercap/session"
@ -26,6 +27,17 @@ type RestAPI struct {
useWebsocket bool
upgrader websocket.Upgrader
quit chan bool
recClock int
recording bool
recTime int
loading bool
replaying bool
recordFileName string
recordWait *sync.WaitGroup
record *Record
recStarted time.Time
recStopped time.Time
}
func NewRestAPI(s *session.Session) *RestAPI {
@ -39,10 +51,30 @@ func NewRestAPI(s *session.Session) *RestAPI {
ReadBufferSize: 1024,
WriteBufferSize: 1024,
},
recClock: 1,
recording: false,
recTime: 0,
loading: false,
replaying: false,
recordFileName: "",
recordWait: &sync.WaitGroup{},
record: nil,
}
mod.State.Store("recording", &mod.recording)
mod.State.Store("rec_clock", &mod.recClock)
mod.State.Store("replaying", &mod.replaying)
mod.State.Store("loading", &mod.loading)
mod.State.Store("load_progress", 0)
mod.State.Store("rec_time", &mod.recTime)
mod.State.Store("rec_filename", &mod.recordFileName)
mod.State.Store("rec_frames", 0)
mod.State.Store("rec_cur_frame", 0)
mod.State.Store("rec_started", &mod.recStarted)
mod.State.Store("rec_stopped", &mod.recStopped)
mod.AddParam(session.NewStringParameter("api.rest.address",
session.ParamIfaceAddress,
"127.0.0.1",
session.IPv4Validator,
"Address to bind the API REST server to."))
@ -93,6 +125,34 @@ func NewRestAPI(s *session.Session) *RestAPI {
return mod.Stop()
}))
mod.AddParam(session.NewIntParameter("api.rest.record.clock",
"1",
"Number of seconds to wait while recording with api.rest.record between one sample and the next one."))
mod.AddHandler(session.NewModuleHandler("api.rest.record off", "",
"Stop recording the session.",
func(args []string) error {
return mod.stopRecording()
}))
mod.AddHandler(session.NewModuleHandler("api.rest.record FILENAME", `api\.rest\.record (.+)`,
"Start polling the rest API periodically recording each sample in a compressed file that can be later replayed.",
func(args []string) error {
return mod.startRecording(args[0])
}))
mod.AddHandler(session.NewModuleHandler("api.rest.replay off", "",
"Stop replaying the recorded session.",
func(args []string) error {
return mod.stopReplay()
}))
mod.AddHandler(session.NewModuleHandler("api.rest.replay FILENAME", `api\.rest\.replay (.+)`,
"Start the rest API module in replay mode using FILENAME as the recorded session file, will revert to normal mode once the replay is over.",
func(args []string) error {
return mod.startReplay(args[0])
}))
return mod
}
@ -126,7 +186,7 @@ func (mod *RestAPI) Configure() error {
var port int
if mod.Running() {
return session.ErrAlreadyStarted
return session.ErrAlreadyStarted(mod.Name())
} else if err, ip = mod.StringParam("api.rest.address"); err != nil {
return err
} 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.HandleFunc("/api/file", mod.fileRoute)
router.HandleFunc("/api/events", mod.eventsRoute)
router.HandleFunc("/api/session", mod.sessionRoute)
router.HandleFunc("/api/session/ble", mod.sessionRoute)
router.HandleFunc("/api/session/ble/{mac}", mod.sessionRoute)
@ -202,7 +265,9 @@ func (mod *RestAPI) Configure() error {
}
func (mod *RestAPI) Start() error {
if err := mod.Configure(); err != nil {
if mod.replaying {
return fmt.Errorf("the api is currently in replay mode, run api.rest.replay off before starting it")
} else if err := mod.Configure(); err != nil {
return err
}
@ -226,6 +291,12 @@ func (mod *RestAPI) Start() error {
}
func (mod *RestAPI) Stop() error {
if mod.recording {
mod.stopRecording()
} else if mod.replaying {
mod.stopReplay()
}
return mod.SetRunning(false, func() {
go func() {
mod.quit <- true

View file

@ -3,7 +3,11 @@ package api_rest
import (
"crypto/subtle"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"strconv"
"strings"
@ -22,7 +26,7 @@ type APIResponse struct {
}
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.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{}) {
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(o); err != nil {
mod.Error("error while encoding object to JSON: %v", err)
mod.Warning("error while encoding object to JSON: %v", err)
}
}
@ -60,8 +64,68 @@ func (mod *RestAPI) checkAuth(r *http.Request) bool {
return true
}
func (mod *RestAPI) patchFrame(buf []byte) (frame map[string]interface{}, err error) {
// this is ugly but necessary: since we're replaying, the
// api.rest state object is filled with *old* values (the
// recorded ones), but the UI needs updated values at least
// of that in order to understand that a replay is going on
// and where we are at it. So we need to parse the record
// back into a session object and update only the api.rest.state
frame = make(map[string]interface{})
if err = json.Unmarshal(buf, &frame); err != nil {
return
}
for _, i := range frame["modules"].([]interface{}) {
m := i.(map[string]interface{})
if m["name"] == "api.rest" {
state := m["state"].(map[string]interface{})
mod.State.Range(func(key interface{}, value interface{}) bool {
state[key.(string)] = value
return true
})
break
}
}
return
}
func (mod *RestAPI) showSession(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I)
if mod.replaying {
if !mod.record.Session.Over() {
from := mod.record.Session.CurFrame() - 1
q := r.URL.Query()
vals := q["from"]
if len(vals) > 0 {
if n, err := strconv.Atoi(vals[0]); err == nil {
from = n
}
}
mod.record.Session.SetFrom(from)
mod.Debug("replaying session %d of %d from %s",
mod.record.Session.CurFrame(),
mod.record.Session.Frames(),
mod.recordFileName)
mod.State.Store("rec_frames", mod.record.Session.Frames())
mod.State.Store("rec_cur_frame", mod.record.Session.CurFrame())
buf := mod.record.Session.Next()
if frame, err := mod.patchFrame(buf); err != nil {
mod.Error("%v", err)
} else {
mod.toJSON(w, frame)
return
}
} else {
mod.stopReplay()
}
}
mod.toJSON(w, mod.Session)
}
func (mod *RestAPI) showBLE(w http.ResponseWriter, r *http.Request) {
@ -69,8 +133,8 @@ func (mod *RestAPI) showBLE(w http.ResponseWriter, r *http.Request) {
mac := strings.ToLower(params["mac"])
if mac == "" {
mod.toJSON(w, session.I.BLE)
} else if dev, found := session.I.BLE.Get(mac); found {
mod.toJSON(w, mod.Session.BLE)
} else if dev, found := mod.Session.BLE.Get(mac); found {
mod.toJSON(w, dev)
} else {
http.Error(w, "Not Found", 404)
@ -82,8 +146,8 @@ func (mod *RestAPI) showHID(w http.ResponseWriter, r *http.Request) {
mac := strings.ToLower(params["mac"])
if mac == "" {
mod.toJSON(w, session.I.HID)
} else if dev, found := session.I.HID.Get(mac); found {
mod.toJSON(w, mod.Session.HID)
} else if dev, found := mod.Session.HID.Get(mac); found {
mod.toJSON(w, dev)
} else {
http.Error(w, "Not Found", 404)
@ -91,19 +155,19 @@ func (mod *RestAPI) showHID(w http.ResponseWriter, r *http.Request) {
}
func (mod *RestAPI) showEnv(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I.Env)
mod.toJSON(w, mod.Session.Env)
}
func (mod *RestAPI) showGateway(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I.Gateway)
mod.toJSON(w, mod.Session.Gateway)
}
func (mod *RestAPI) showInterface(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I.Interface)
mod.toJSON(w, mod.Session.Interface)
}
func (mod *RestAPI) showModules(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I.Modules)
mod.toJSON(w, mod.Session.Modules)
}
func (mod *RestAPI) showLAN(w http.ResponseWriter, r *http.Request) {
@ -111,8 +175,8 @@ func (mod *RestAPI) showLAN(w http.ResponseWriter, r *http.Request) {
mac := strings.ToLower(params["mac"])
if mac == "" {
mod.toJSON(w, session.I.Lan)
} else if host, found := session.I.Lan.Get(mac); found {
mod.toJSON(w, mod.Session.Lan)
} else if host, found := mod.Session.Lan.Get(mac); found {
mod.toJSON(w, host)
} else {
http.Error(w, "Not Found", 404)
@ -120,15 +184,15 @@ func (mod *RestAPI) showLAN(w http.ResponseWriter, r *http.Request) {
}
func (mod *RestAPI) showOptions(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I.Options)
mod.toJSON(w, mod.Session.Options)
}
func (mod *RestAPI) showPackets(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I.Queue)
mod.toJSON(w, mod.Session.Queue)
}
func (mod *RestAPI) showStartedAt(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I.StartedAt)
mod.toJSON(w, mod.Session.StartedAt)
}
func (mod *RestAPI) showWiFi(w http.ResponseWriter, r *http.Request) {
@ -136,10 +200,10 @@ func (mod *RestAPI) showWiFi(w http.ResponseWriter, r *http.Request) {
mac := strings.ToLower(params["mac"])
if mac == "" {
mod.toJSON(w, session.I.WiFi)
} else if station, found := session.I.WiFi.Get(mac); found {
mod.toJSON(w, mod.Session.WiFi)
} else if station, found := mod.Session.WiFi.Get(mac); found {
mod.toJSON(w, station)
} else if client, found := session.I.WiFi.GetClient(mac); found {
} else if client, found := mod.Session.WiFi.GetClient(mac); found {
mod.toJSON(w, client)
} else {
http.Error(w, "Not Found", 404)
@ -154,43 +218,84 @@ func (mod *RestAPI) runSessionCommand(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Bad Request", 400)
} else if err = json.NewDecoder(r.Body).Decode(&cmd); err != nil {
http.Error(w, "Bad Request", 400)
} else if err = session.I.Run(cmd.Command); err != nil {
}
for _, aCommand := range session.ParseCommands(cmd.Command) {
if err = mod.Session.Run(aCommand); err != nil {
http.Error(w, err.Error(), 400)
} else {
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)
}
}
func (mod *RestAPI) showEvents(w http.ResponseWriter, r *http.Request) {
var err error
if mod.useWebsocket {
mod.startStreamingEvents(w, r)
} else {
events := session.I.Events.Sorted()
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) {
q := r.URL.Query()
vals := q["n"]
if mod.replaying {
if !mod.record.Events.Over() {
from := mod.record.Events.CurFrame() - 1
vals := q["from"]
if len(vals) > 0 {
n, err = strconv.Atoi(q["n"][0])
if err == nil {
if n > nmax {
n = nmax
if n, err := strconv.Atoi(vals[0]); err == nil {
from = n
}
}
mod.record.Events.SetFrom(from)
mod.Debug("replaying events %d of %d from %s",
mod.record.Events.CurFrame(),
mod.record.Events.Frames(),
mod.recordFileName)
buf := mod.record.Events.Next()
if _, err := w.Write(buf); err != nil {
mod.Error("%v", err)
} else {
return
}
} else {
n = nmax
mod.stopReplay()
}
}
mod.toJSON(w, events[nevents-n:])
if mod.useWebsocket {
mod.startStreamingEvents(w, r)
} else {
vals := q["n"]
limit := 0
if len(vals) > 0 {
if n, err := strconv.Atoi(q["n"][0]); err == nil {
limit = n
}
}
mod.toJSON(w, mod.getEvents(limit))
}
}
func (mod *RestAPI) clearEvents(w http.ResponseWriter, r *http.Request) {
session.I.Events.Clear()
mod.Session.Events.Clear()
}
func (mod *RestAPI) corsRoute(w http.ResponseWriter, r *http.Request) {
@ -212,10 +317,10 @@ func (mod *RestAPI) sessionRoute(w http.ResponseWriter, r *http.Request) {
return
}
session.I.Lock()
defer session.I.Unlock()
mod.Session.Lock()
defer mod.Session.Unlock()
path := r.URL.String()
path := r.URL.Path
switch {
case path == "/api/session":
mod.showSession(w, r)
@ -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) {
mod.setSecurityHeaders(w)
@ -274,3 +417,22 @@ func (mod *RestAPI) eventsRoute(w http.ResponseWriter, r *http.Request) {
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
}
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() {
neighbours := []net.IP{}
nTargets := len(mod.addresses) + len(mod.macs)
if mod.internal {
list, _ := iprange.ParseList(mod.Session.Interface.CIDR())

View file

@ -20,6 +20,7 @@ import (
type BLERecon struct {
session.SessionModule
deviceId int
gattDevice gatt.Device
currDevice *network.BLEDevice
writeUUID *gatt.UUID
@ -34,6 +35,7 @@ type BLERecon struct {
func NewBLERecon(s *session.Session) *BLERecon {
mod := &BLERecon{
SessionModule: session.NewSessionModule("ble.recon", s),
deviceId: -1,
gattDevice: nil,
quit: make(chan bool),
done: make(chan bool),
@ -42,6 +44,8 @@ func NewBLERecon(s *session.Session) *BLERecon {
connected: false,
}
mod.InitState("scanning")
mod.selector = utils.ViewSelectorFor(&mod.SessionModule,
"ble.show",
[]string{"rssi", "mac", "seen"}, "rssi asc")
@ -108,6 +112,10 @@ func NewBLERecon(s *session.Session) *BLERecon {
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
}
@ -138,13 +146,23 @@ func (w dummyWriter) Write(p []byte) (n int, err error) {
func (mod *BLERecon) Configure() (err error) {
if mod.Running() {
return session.ErrAlreadyStarted
return session.ErrAlreadyStarted(mod.Name())
} 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.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)
return err
}
@ -171,9 +189,10 @@ func (mod *BLERecon) Start() error {
<-mod.quit
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.gattDevice.CancelConnection(mod.currDevice.Device)
}
@ -184,6 +203,7 @@ func (mod *BLERecon) Start() error {
} else {
mod.Debug("gatt device closed")
}
}
mod.done <- true
})
@ -196,6 +216,7 @@ func (mod *BLERecon) Stop() error {
mod.Debug("module stopped, cleaning state")
mod.gattDevice = nil
mod.setCurrentDevice(nil)
mod.ResetState()
})
}
@ -216,6 +237,7 @@ func (mod *BLERecon) pruner() {
func (mod *BLERecon) setCurrentDevice(dev *network.BLEDevice) {
mod.connected = false
mod.currDevice = dev
mod.State.Store("scanning", dev)
}
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)
if err := mod.Configure(); err != nil && err != session.ErrAlreadyStarted {
if err := mod.Configure(); err != nil && err.Error() != session.ErrAlreadyStarted("ble.recon").Error() {
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())
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)
return
}

View file

@ -78,7 +78,7 @@ func (mod *DHCP6Spoofer) Configure() error {
var err error
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 {

View file

@ -133,7 +133,7 @@ func (mod *DNSSpoofer) Configure() error {
var proxyip string
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 {
return err
} else if err = mod.Handle.SetBPFFilter("udp and port 53"); err != nil {

View file

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

View file

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

View file

@ -63,6 +63,10 @@ func NewHIDRecon(s *session.Session) *HIDRecon {
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", "",
"Start scanning for HID devices on the 2.4Ghz spectrum.",
func(args []string) error {
@ -159,7 +163,7 @@ func (mod *HIDRecon) Configure() error {
var n int
if mod.Running() {
return session.ErrAlreadyStarted
return session.ErrAlreadyStarted(mod.Name())
}
if err, mod.useLNA = mod.BoolParam("hid.lna"); err != nil {
@ -200,10 +204,20 @@ func (mod *HIDRecon) Configure() error {
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 {
return mod.SetRunning(false, func() {
mod.waitGroup.Wait()
if mod.dongle != nil {
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)
if found == false {
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
if found {
if found && dev.Type != network.HIDTypeUnknown {
// get the device specific protocol handler
builder, found = FrameBuilders[dev.Type]
if found == false {
if dev.Type == network.HIDTypeUnknown {
return errNoType(mod.sniffAddr), nil, nil
}
return errNotSupported(dev), nil, nil
}
} else {

View file

@ -4,6 +4,7 @@ import (
"time"
"github.com/bettercap/nrf24"
"github.com/google/gousb"
)
func (mod *HIDRecon) doHopping() {
@ -26,7 +27,13 @@ func (mod *HIDRecon) doHopping() {
mod.channel = 1
}
if err := mod.dongle.SetChannel(mod.channel); err != nil {
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 {
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 {
if err := mod.Configure(); err != nil {
return err
@ -70,6 +96,8 @@ func (mod *HIDRecon) Start() error {
mod.waitGroup.Add(1)
defer mod.waitGroup.Done()
go mod.devPruner()
mod.Info("hopping on %d channels every %s", nrf24.TopChannel, mod.hopPeriod)
for mod.Running() {
if mod.isSniffing() {
@ -86,6 +114,11 @@ func (mod *HIDRecon) Start() error {
buf, err := mod.dongle.ReceivePayload()
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)
continue
}

View file

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

View file

@ -2,6 +2,8 @@ package http_proxy
import (
"github.com/bettercap/bettercap/session"
"github.com/evilsocket/islazy/str"
)
type HttpProxy struct {
@ -38,6 +40,12 @@ func NewHttpProxy(s *session.Session) *HttpProxy {
"",
"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",
"false",
"Enable or disable SSL stripping."))
@ -77,9 +85,11 @@ func (mod *HttpProxy) Configure() error {
var scriptPath string
var stripSSL bool
var jsToInject string
var blacklist string
var whitelist string
if mod.Running() {
return session.ErrAlreadyStarted
return session.ErrAlreadyStarted(mod.Name())
} else if err, address = mod.StringParam("http.proxy.address"); err != nil {
return err
} else if err, proxyPort = mod.IntParam("http.proxy.port"); err != nil {
@ -92,8 +102,15 @@ func (mod *HttpProxy) Configure() error {
return err
} else if err, jsToInject = mod.StringParam("http.proxy.injectjs"); err != nil {
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)
}

View file

@ -11,6 +11,7 @@ import (
"net"
"net/http"
"net/url"
"path/filepath"
"strconv"
"strings"
"time"
@ -41,6 +42,8 @@ type HTTPProxy struct {
Script *HttpProxyScript
CertFile string
KeyFile string
Blacklist []string
Whitelist []string
jsHook string
isTLS bool
@ -67,6 +70,8 @@ func NewHTTPProxy(s *session.Session) *HTTPProxy {
stripper: NewSSLStripper(s, false),
isTLS: false,
Server: nil,
Blacklist: make([]string, 0),
Whitelist: make([]string, 0),
tag: session.AsTag("http.proxy"),
}
@ -111,20 +116,41 @@ func (p *HTTPProxy) Fatal(format string, args ...interface{}) {
}
func (p *HTTPProxy) doProxy(req *http.Request) bool {
blacklist := []string{
"localhost",
"127.0.0.1",
}
if req.Host == "" {
p.Error("got request with empty host: %v", req)
return false
}
host := strings.Split(req.Host, ":")[0]
for _, blacklisted := range blacklist {
if host == blacklisted {
p.Error("got request with blacklisted host: %s", req.Host)
hostname := strings.Split(req.Host, ":")[0]
for _, local := range []string{"localhost", "127.0.0.1"} {
if hostname == local {
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
}
}

View file

@ -19,6 +19,7 @@ func (p *HTTPProxy) fixRequestHeaders(req *http.Request) {
}
func (p *HTTPProxy) onRequestFilter(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
if p.shouldProxy(req) {
p.Debug("< %s %s %s%s", req.RemoteAddr, req.Method, req.Host, req.URL.Path)
p.fixRequestHeaders(req)
@ -46,6 +47,7 @@ func (p *HTTPProxy) onRequestFilter(req *http.Request, ctx *goproxy.ProxyCtx) (*
p.logResponseAction(req, jsres)
return req, jsres.ToResponse(req)
}
}
return req, nil
}
@ -123,6 +125,7 @@ func (p *HTTPProxy) onResponseFilter(res *http.Response, ctx *goproxy.ProxyCtx)
return nil
}
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)
@ -147,6 +150,7 @@ func (p *HTTPProxy) onResponseFilter(res *http.Response, ctx *goproxy.ProxyCtx)
return injectedResponse
}
}
}
return res
}

View file

@ -71,7 +71,7 @@ func (mod *HttpServer) Configure() error {
var port int
if mod.Running() {
return session.ErrAlreadyStarted
return session.ErrAlreadyStarted(mod.Name())
}
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))
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)
}))
@ -110,7 +110,8 @@ func (mod *HttpServer) Start() error {
var err error
mod.Info("starting on http://%s", mod.server.Addr)
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/evilsocket/islazy/fs"
"github.com/evilsocket/islazy/str"
)
type HttpsProxy struct {
@ -58,6 +59,12 @@ func NewHttpsProxy(s *session.Session) *HttpsProxy {
"",
"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", "",
"Start HTTPS proxy.",
func(args []string) error {
@ -95,9 +102,11 @@ func (mod *HttpsProxy) Configure() error {
var keyFile string
var stripSSL bool
var jsToInject string
var whitelist string
var blacklist string
if mod.Running() {
return session.ErrAlreadyStarted
return session.ErrAlreadyStarted(mod.Name())
} else if err, address = mod.StringParam("https.proxy.address"); err != nil {
return err
} else if err, proxyPort = mod.IntParam("https.proxy.port"); err != nil {
@ -118,8 +127,15 @@ func (mod *HttpsProxy) Configure() error {
return err
} else if err, jsToInject = mod.StringParam("https.proxy.injectjs"); err != nil {
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) {
err, cfg := tls.CertConfigFromModule("https.proxy", mod.SessionModule)
if err != nil {

View file

@ -89,7 +89,7 @@ func (mod *HttpsServer) Configure() error {
var keyFile string
if mod.Running() {
return session.ErrAlreadyStarted
return session.ErrAlreadyStarted(mod.Name())
}
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))
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)
}))
@ -159,7 +159,8 @@ func (mod *HttpsServer) Start() error {
return mod.SetRunning(true, func() {
mod.Info("starting on https://%s", mod.server.Addr)
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 {
if mod.Running() {
return session.ErrAlreadyStarted
return session.ErrAlreadyStarted(mod.Name())
} else if err := mod.Configure(); err != nil {
return err
} 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/tcp_proxy"
"github.com/bettercap/bettercap/modules/ticker"
"github.com/bettercap/bettercap/modules/ui"
"github.com/bettercap/bettercap/modules/update"
"github.com/bettercap/bettercap/modules/wifi"
"github.com/bettercap/bettercap/modules/wol"
@ -36,7 +37,6 @@ func LoadModules(sess *session.Session) {
sess.Register(arp_spoof.NewArpSpoofer(sess))
sess.Register(api_rest.NewRestAPI(sess))
sess.Register(ble.NewBLERecon(sess))
sess.Register(caplets.NewCapletsModule(sess))
sess.Register(dhcp6_spoof.NewDHCP6Spoofer(sess))
sess.Register(net_recon.NewDiscovery(sess))
sess.Register(dns_spoof.NewDNSSpoofer(sess))
@ -54,8 +54,11 @@ func LoadModules(sess *session.Session) {
sess.Register(syn_scan.NewSynScanner(sess))
sess.Register(tcp_proxy.NewTcpProxy(sess))
sess.Register(ticker.NewTicker(sess))
sess.Register(update.NewUpdateModule(sess))
sess.Register(wifi.NewWiFiModule(sess))
sess.Register(wol.NewWOL(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
if mod.Running() {
return session.ErrAlreadyStarted
return session.ErrAlreadyStarted(mod.Name())
} else if err, mod.infile = mod.StringParam("mysql.server.infile"); err != nil {
return err
} 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 (.+)`,
"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 {
return mod.Show(args[0])
}))
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 {
return mod.showMeta(args[0])
}))

View file

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

View file

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

View file

@ -45,6 +45,9 @@ func NewSynScanner(s *session.Session) *SynScanner {
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",
"1",
"Period in seconds for the scanning progress reporting."))
@ -58,7 +61,7 @@ func NewSynScanner(s *session.Session) *SynScanner {
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.",
func(args []string) error {
period := 0
@ -155,6 +158,7 @@ func plural(n uint64) string {
func (mod *SynScanner) showProgress() error {
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",
progress,
mod.stats.openPorts,
@ -172,12 +176,17 @@ func (mod *SynScanner) Stop() error {
return mod.SetRunning(false, func() {
mod.waitGroup.Wait()
mod.showProgress()
mod.addresses = []net.IP{}
mod.State.Store("progress", 0.0)
})
}
func (mod *SynScanner) synScan() error {
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)
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.State.Store("progress", 0.0)
// set the collector
mod.Session.Queue.OnPacket(mod.onPacket)
defer mod.Session.Queue.OnPacket(nil)

View file

@ -95,7 +95,7 @@ func (mod *TcpProxy) Configure() error {
var tunnelPort int
if mod.Running() {
return session.ErrAlreadyStarted
return session.ErrAlreadyStarted(mod.Name())
} else if err, address = mod.StringParam("tcp.address"); err != nil {
return err
} 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
if mod.Running() {
return session.ErrAlreadyStarted
return session.ErrAlreadyStarted(mod.Name())
} else if err, commands = mod.StringParam("ticker.commands"); err != nil {
return err
} 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,6 +38,7 @@ type WiFiModule struct {
frequencies []int
ap *network.AccessPoint
stickChan int
shakesFile string
skipBroken bool
pktSourceChan chan gopacket.Packet
pktSourceChanClosed bool
@ -47,7 +48,6 @@ type WiFiModule struct {
assocSkip []net.HardwareAddr
assocSilent bool
assocOpen bool
shakesFile string
apRunning bool
showManuf bool
apConfig packets.Dot11ApConfig
@ -81,6 +81,8 @@ func NewWiFiModule(s *session.Session) *WiFiModule {
chanLock: &sync.Mutex{},
}
mod.InitState("channels")
mod.AddParam(session.NewStringParameter("wifi.interface",
"",
"",
@ -124,7 +126,8 @@ func NewWiFiModule(s *session.Session) *WiFiModule {
func(args []string) (err error) {
mod.ap = nil
mod.stickChan = 0
mod.frequencies, err = network.GetSupportedFrequencies(mod.iface.Name())
freqs, err := network.GetSupportedFrequencies(mod.iface.Name())
mod.setFrequencies(freqs)
mod.hopChanges <- true
return err
}))
@ -258,7 +261,7 @@ func NewWiFiModule(s *session.Session) *WiFiModule {
"false",
"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.",
func(args []string) (err error) {
freqs := []int{}
@ -285,8 +288,7 @@ func NewWiFiModule(s *session.Session) *WiFiModule {
}
}
mod.Debug("new frequencies: %v", freqs)
mod.frequencies = freqs
mod.setFrequencies(freqs)
// if wifi.recon is not running, this would block forever
if mod.Running() {
@ -330,6 +332,17 @@ const (
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 {
var ifName string
var hopPeriod int
@ -428,10 +441,10 @@ func (mod *WiFiModule) Configure() error {
mod.hopPeriod = time.Duration(hopPeriod) * time.Millisecond
if mod.source == "" {
// No channels setted, retrieve frequencies supported by the card
if len(mod.frequencies) == 0 {
if mod.frequencies, err = network.GetSupportedFrequencies(ifName); err != nil {
if freqs, err := network.GetSupportedFrequencies(ifName); err != nil {
return fmt.Errorf("error while getting supported frequencies of %s: %s", ifName, err)
} else {
mod.setFrequencies(freqs)
}
mod.Debug("wifi supported frequencies: %v", mod.frequencies)
@ -441,9 +454,9 @@ func (mod *WiFiModule) Configure() error {
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
}
@ -480,13 +493,17 @@ func (mod *WiFiModule) updateStats(dot11 *layers.Dot11, packet gopacket.Packet)
bytes := uint64(len(packet.Data()))
dst := dot11.Address1.String()
if station, found := mod.Session.WiFi.Get(dst); found {
station.Received += bytes
if ap, found := mod.Session.WiFi.Get(dst); found {
ap.Received += bytes
} else if sta, found := mod.Session.WiFi.GetClient(dst); found {
sta.Received += bytes
}
src := dot11.Address2.String()
if station, found := mod.Session.WiFi.Get(src); found {
station.Sent += bytes
if ap, found := mod.Session.WiFi.Get(src); found {
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
}
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 {
return mod.SetRunning(false, func() {
// wait any pending write operation

View file

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

View file

@ -1,11 +1,47 @@
package wifi
import (
"net"
"time"
"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()) {
mod.chanLock.Lock()
defer mod.chanLock.Unlock()
@ -13,11 +49,7 @@ func (mod *WiFiModule) onChannel(channel int, cb func()) {
prev := mod.stickChan
mod.stickChan = channel
if err := network.SetInterfaceChannel(mod.iface.Name(), channel); err != nil {
mod.Warning("error while hopping to channel %d: %s", channel, err)
} else {
mod.Debug("hopped on channel %d", channel)
}
mod.hop(channel)
cb()
@ -50,13 +82,10 @@ func (mod *WiFiModule) channelHopper() {
channel = mod.stickChan
}
mod.Debug("hopping on channel %d", channel)
mod.chanLock.Lock()
if err := network.SetInterfaceChannel(mod.iface.Name(), channel); err != nil {
mod.Warning("error while hopping to channel %d: %s", channel, err)
if stop := mod.hop(channel); stop {
mod.forcedStop()
return
}
mod.chanLock.Unlock()
select {
case _ = <-mod.hopChanges:

View file

@ -124,7 +124,7 @@ func (mod *WiFiModule) discoverClients(radiotap *layers.RadioTap, dot11 *layers.
freq := int(radiotap.ChannelFrequency)
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{
AP: ap,
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.
staIsUs := bytes.Equal(staMac, mod.iface.HW)
station, found := ap.Get(staMac.String())
staAdded := false
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)
@ -96,10 +97,9 @@ func (mod *WiFiModule) discoverHandshakes(radiotap *layers.RadioTap, dot11 *laye
// is persisted even after stations are pruned due to inactivity
ap.WithKeyMaterial(true)
}
// 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.
if staIsUs && rawPMKID == nil {
if staAdded || (staIsUs && rawPMKID == nil) {
ap.RemoveClient(staMac.String())
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -286,7 +286,7 @@ func ActivateInterface(name string) error {
func SetInterfaceTxPower(name string, txpower int) error {
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
} else if 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/pcapgo"
"github.com/evilsocket/islazy/data"
"github.com/evilsocket/islazy/fs"
)
@ -43,6 +44,7 @@ type APLostCallback func(ap *AccessPoint)
type WiFi struct {
sync.Mutex
aliases *data.UnsortedKV
aps map[string]*AccessPoint
iface *Endpoint
newCb APNewCallback
@ -53,9 +55,10 @@ type wifiJSON struct {
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{
aps: make(map[string]*AccessPoint),
aliases: aliases,
iface: iface,
newCb: newcb,
lostCb: lostcb,
@ -134,6 +137,7 @@ func (w *WiFi) AddIfNew(ssid, mac string, frequency int, rssi int8) (*AccessPoin
defer w.Unlock()
mac = NormalizeMac(mac)
alias := w.aliases.GetOr(mac, "")
if ap, found := w.aps[mac]; found {
ap.LastSeen = time.Now()
if rssi != 0 {
@ -143,10 +147,15 @@ func (w *WiFi) AddIfNew(ssid, mac string, frequency int, rssi int8) (*AccessPoin
if !isBogusMacESSID(ssid) {
ap.Hostname = ssid
}
if alias != "" {
ap.Alias = alias
}
return ap, false
}
newAp := NewAccessPoint(ssid, mac, frequency, rssi)
newAp := NewAccessPoint(ssid, mac, frequency, rssi, w.aliases)
newAp.Alias = alias
w.aps[mac] = newAp
if w.newCb != nil {

View file

@ -12,6 +12,7 @@ type AccessPoint struct {
*Station
sync.Mutex
aliases *data.UnsortedKV
clients map[string]*Station
withKeyMaterial bool
}
@ -22,9 +23,10 @@ type apJSON struct {
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{
Station: NewStation(essid, bssid, frequency, rssi),
aliases: aliases,
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()
defer ap.Unlock()
bssid = NormalizeMac(bssid)
alias := ap.aliases.GetOr(bssid, "")
if s, found := ap.clients[bssid]; found {
// update
@ -79,17 +82,15 @@ func (ap *AccessPoint) AddClientIfNew(bssid string, frequency int, rssi int8, al
s.RSSI = rssi
s.LastSeen = time.Now()
if aliases != nil {
s.Alias = aliases.GetOr(bssid, "")
if alias != "" {
s.Alias = alias
}
return s, false
}
s := NewStation("", bssid, frequency, rssi)
if aliases != nil {
s.Alias = aliases.GetOr(bssid, "")
}
s.Alias = alias
ap.clients[bssid] = s
return s, true

View file

@ -38,10 +38,11 @@ type PacketCallback func(pkt gopacket.Packet)
type Queue struct {
sync.RWMutex
Activities chan Activity
// keep on top because of https://github.com/bettercap/bettercap/issues/500
Stats Stats
Protos sync.Map
Traffic sync.Map
Activities chan Activity
iface *network.Endpoint
handle *pcap.Handle
@ -62,6 +63,7 @@ func NewQueue(iface *network.Endpoint) (q *Queue, err error) {
q = &Queue{
Protos: sync.Map{},
Traffic: sync.Map{},
Stats: Stats{},
Activities: make(chan Activity),
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) {
// 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.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
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 (
"encoding/json"
"errors"
"fmt"
"strings"
"sync"
"github.com/bettercap/bettercap/session"
"github.com/evilsocket/islazy/str"
)
@ -15,24 +14,30 @@ var (
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))
}
type IgnoreList struct {
type EventsIgnoreList struct {
sync.RWMutex
filters []IgnoreFilter
filters []filter
}
func NewIgnoreList() *IgnoreList {
return &IgnoreList{
filters: make([]IgnoreFilter, 0),
func NewEventsIgnoreList() *EventsIgnoreList {
return &EventsIgnoreList{
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)
if expr == "" {
return "", ErrEmptyExpression
@ -41,7 +46,7 @@ func (l *IgnoreList) checkExpression(expr string) (string, error) {
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 {
return err
}
@ -57,12 +62,12 @@ func (l *IgnoreList) Add(expr string) (err error) {
}
// all good
l.filters = append(l.filters, IgnoreFilter(expr))
l.filters = append(l.filters, filter(expr))
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 {
return err
}
@ -71,8 +76,8 @@ func (l *IgnoreList) Remove(expr string) (err error) {
defer l.Unlock()
// build a new list with everything that does not match
toRemove := IgnoreFilter(expr)
newList := make([]IgnoreFilter, 0)
toRemove := filter(expr)
newList := make([]filter, 0)
for _, filter := range l.filters {
if !toRemove.Matches(string(filter)) {
newList = append(newList, filter)
@ -89,7 +94,13 @@ func (l *IgnoreList) Remove(expr string) (err error) {
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()
defer l.RUnlock()
@ -102,12 +113,12 @@ func (l *IgnoreList) Ignored(e session.Event) bool {
return false
}
func (l *IgnoreList) Empty() bool {
func (l *EventsIgnoreList) Empty() bool {
l.RLock()
defer l.RUnlock()
return len(l.filters) == 0
}
func (l *IgnoreList) Filters() []IgnoreFilter {
func (l *EventsIgnoreList) Filters() []filter {
return l.filters
}

View file

@ -1,6 +1,7 @@
package session
import (
"encoding/json"
"fmt"
"net"
"strings"
@ -19,16 +20,47 @@ type Module interface {
Handlers() []ModuleHandler
Parameters() map[string]*ModuleParam
Extra() map[string]interface{}
Running() bool
Start() error
Stop() error
}
type SessionModule struct {
type ModuleList []Module
type moduleJSON struct {
Name string `json:"name"`
Session *Session `json:"-"`
Started bool `json:"started"`
StatusLock *sync.RWMutex `json:"-"`
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 {
Name string
Session *Session
Started bool
StatusLock *sync.RWMutex
State *sync.Map
handlers []ModuleHandler
params map[string]*ModuleParam
@ -45,6 +77,7 @@ func NewSessionModule(name string, s *Session) SessionModule {
Session: s,
Started: false,
StatusLock: &sync.RWMutex{},
State: &sync.Map{},
handlers: make([]ModuleHandler, 0),
params: make(map[string]*ModuleParam),
@ -54,6 +87,28 @@ func NewSessionModule(name string, s *Session) SessionModule {
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{}) {
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 {
if running == m.Running() {
if m.Started {
return ErrAlreadyStarted
return ErrAlreadyStarted(m.Name)
} else {
return ErrAlreadyStopped
return ErrAlreadyStopped(m.Name)
}
}

View file

@ -1,7 +1,6 @@
package session
import (
"encoding/json"
"errors"
"fmt"
"net"
@ -21,6 +20,7 @@ import (
"github.com/bettercap/bettercap/network"
"github.com/bettercap/bettercap/packets"
"github.com/evilsocket/islazy/data"
"github.com/evilsocket/islazy/fs"
"github.com/evilsocket/islazy/log"
"github.com/evilsocket/islazy/ops"
@ -35,43 +35,24 @@ const (
var (
I = (*Session)(nil)
ErrAlreadyStarted = errors.New("module is already running")
ErrAlreadyStopped = errors.New("module is not running")
ErrNotSupported = errors.New("this component is not supported on this OS")
reCmdSpaceCleaner = regexp.MustCompile(`^([^\s]+)\s+(.+)$`)
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 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 {
Updated time.Time
Latitude float64 // Latitude.
Longitude float64 // Longitude.
FixQuality string // Quality of fix.
@ -81,27 +62,33 @@ type GPS struct {
Separation float64 // Geoidal separation
}
type Session struct {
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"`
const AliasesFile = "~/bettercap.aliases"
Input *readline.Instance `json:"-"`
Prompt Prompt `json:"-"`
CoreHandlers []CommandHandler `json:"-"`
Events *EventPool `json:"-"`
UnkCmdCallback UnknownCommandCallback `json:"-"`
Firewall firewall.FirewallManager `json:"-"`
var aliasesFileName, _ = fs.Expand(AliasesFile)
type Session struct {
Options core.Options
Interface *network.Endpoint
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) {
@ -125,6 +112,7 @@ func New() (*Session, error) {
CoreHandlers: make([]CommandHandler, 0),
Modules: make([]Module, 0),
Events: nil,
EventsIgnoreList: NewEventsIgnoreList(),
UnkCmdCallback: nil,
}
@ -140,6 +128,10 @@ func New() (*Session, error) {
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.registerCoreHandlers()
@ -260,25 +252,25 @@ func (s *Session) Start() error {
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)
}, func(dev *network.HIDDevice) {
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)
}, func(dev *network.BLEDevice) {
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)
}, func(ap *network.AccessPoint) {
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)
}, func(e *network.Endpoint) {
s.Events.Add("endpoint.lost", e)

View file

@ -4,7 +4,9 @@ import (
"bufio"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"sort"
"strconv"
"strings"
@ -249,13 +251,14 @@ func (s *Session) readHandler(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
// cleared if a "clear; net.show" commands chain is executed
// in the interactive session.
for i := 0; i < 180; i++ {
fmt.Println()
cmd := "clear"
if runtime.GOOS == "windows" {
cmd = "cls"
}
readline.ClearScreen(s.Input.Stdout())
c := exec.Command(cmd)
c.Stdout = os.Stdout
c.Run()
return nil
}
@ -271,10 +274,55 @@ func (s *Session) shHandler(args []string, sess *Session) error {
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 {
mac := args[0]
alias := str.Trim(args[1])
s.Lan.SetAliasFor(mac, alias)
if alias == "\"\"" || alias == "''" {
alias = ""
}
s.propagateAlias(mac, alias)
return nil
}
@ -380,7 +428,7 @@ func (s *Session) registerCoreHandlers() {
readline.PcItem("!"))
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.",
s.aliasHandler),
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.
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 {
u = append(u, UUID{d[:w]})
d = d[w:]

View file

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

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
}