mirror of
https://github.com/bettercap/bettercap
synced 2025-08-22 14:24:38 -07:00
Merge https://github.com/bettercap/bettercap into merge-upstream
This commit is contained in:
commit
00fe2eaf3d
78 changed files with 2615 additions and 411 deletions
|
@ -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
19
Gopkg.lock
generated
|
@ -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",
|
||||
|
|
|
@ -73,3 +73,7 @@
|
|||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/kr/binarydist"
|
||||
version = "0.1.0"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,7 +2,7 @@ package core
|
|||
|
||||
const (
|
||||
Name = "bettercap"
|
||||
Version = "2.19"
|
||||
Version = "2.22"
|
||||
Author = "Simone 'evilsocket' Margaritelli"
|
||||
Website = "https://bettercap.org/"
|
||||
)
|
||||
|
|
11
core/core.go
11
core/core.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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."),
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
116
modules/api_rest/api_rest_record.go
Normal file
116
modules/api_rest/api_rest_record.go
Normal 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
|
||||
}
|
81
modules/api_rest/api_rest_replay.go
Normal file
81
modules/api_rest/api_rest_replay.go
Normal 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
298
modules/api_rest/record.go
Normal 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()
|
||||
}
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ func (mod *HIDRecon) setSniffMode(mode string, silent bool) error {
|
|||
mod.sniffAddrRaw = raw
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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])
|
||||
}))
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
190
modules/ui/ui.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(), ",")
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
BIN
session.record
Executable file
Binary file not shown.
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
122
session/session_json.go
Normal 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)
|
||||
}
|
7
vendor/github.com/bettercap/gatt/adv.go
generated
vendored
7
vendor/github.com/bettercap/gatt/adv.go
generated
vendored
|
@ -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:]
|
||||
|
|
42
vendor/github.com/evilsocket/islazy/zip/unzip.go
generated
vendored
42
vendor/github.com/evilsocket/islazy/zip/unzip.go
generated
vendored
|
@ -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
1
vendor/github.com/kr/binarydist/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
test.*
|
22
vendor/github.com/kr/binarydist/License
generated
vendored
Normal file
22
vendor/github.com/kr/binarydist/License
generated
vendored
Normal 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
7
vendor/github.com/kr/binarydist/Readme.md
generated
vendored
Normal 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
40
vendor/github.com/kr/binarydist/bzip2.go
generated
vendored
Normal 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
408
vendor/github.com/kr/binarydist/diff.go
generated
vendored
Normal 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
24
vendor/github.com/kr/binarydist/doc.go
generated
vendored
Normal 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
53
vendor/github.com/kr/binarydist/encoding.go
generated
vendored
Normal 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
1
vendor/github.com/kr/binarydist/go.mod
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
module "github.com/kr/binarydist"
|
109
vendor/github.com/kr/binarydist/patch.go
generated
vendored
Normal file
109
vendor/github.com/kr/binarydist/patch.go
generated
vendored
Normal 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
43
vendor/github.com/kr/binarydist/seek.go
generated
vendored
Normal 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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue