Commit d3fa11ec authored by Sophie Brun's avatar Sophie Brun

New upstream version 2.20

parent 0a36f3e6
......@@ -4,7 +4,7 @@ FROM golang:alpine AS build-env
ENV SRC_DIR $GOPATH/src/github.com/bettercap/bettercap
RUN apk add --update ca-certificates
RUN apk add --no-cache --update bash iptables wireless-tools build-base libpcap-dev libusb-1.0-dev linux-headers libnetfilter_queue-dev git
RUN apk add --no-cache --update bash iptables wireless-tools build-base libpcap-dev libusb-dev linux-headers libnetfilter_queue-dev git
WORKDIR $SRC_DIR
ADD . $SRC_DIR
......@@ -13,11 +13,13 @@ RUN make deps
RUN make
# get caplets
RUN git clone https://github.com/bettercap/caplets
RUN mkdir -p /usr/local/share/bettercap
RUN git clone https://github.com/bettercap/caplets /usr/local/share/bettercap/caplets
# final stage
FROM alpine
RUN apk add --no-cache --update bash iproute2 libpcap libnetfilter_queue wireless-tools
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/
WORKDIR /app
......
......@@ -27,7 +27,7 @@
[[projects]]
branch = "master"
digest = "1:c309b41787813f80ec393023471014b0be16346bba6e16bf5fa01ce1d310a4ea"
digest = "1:a2c142e6c2aa1c71796c748bbe42d224e23d6638fd5b3ae153e70a4b08a8da4e"
name = "github.com/bettercap/gatt"
packages = [
".",
......@@ -40,7 +40,7 @@
"xpc",
]
pruneopts = "UT"
revision = "1353e80bee488dc02d1f7e42759c1352492bf18b"
revision = "277ee0d0ef94d26e3190252c59fa34dde0df4f26"
[[projects]]
branch = "master"
......@@ -83,7 +83,7 @@
revision = "2ce16c963a8ac5bd6af851d4877e38701346983f"
[[projects]]
digest = "1:a5fab5a807cac4da733996f46b917283fe43aac5fd32798a3c9279a44eb5156c"
digest = "1:da1be9af4c3f262bd385cc722b08d98d4a47ddea57731e98b85c7ba21b35bc31"
name = "github.com/evilsocket/islazy"
packages = [
"data",
......@@ -96,8 +96,8 @@
"zip",
]
pruneopts = "UT"
revision = "e6f5e33089826f0d17eaab3c8c3603048d615a0e"
version = "v1.10.2"
revision = "6ef79e84ded205e48f296d21e3bc65d1cf4f5c78"
version = "v1.10.3"
[[projects]]
branch = "master"
......
......@@ -6,6 +6,9 @@ all: deps build
deps: godep golint gofmt gomegacheck
@dep ensure
build_with_race_detector: resources
@go build -race -o $(TARGET) .
build: resources
@go build -o $(TARGET) .
......@@ -56,4 +59,4 @@ gomegacheck:
@go get honnef.co/go/tools/cmd/megacheck
gofmt:
gofmt -s -w $(PACKAGES)
\ No newline at end of file
gofmt -s -w $(PACKAGES)
......@@ -97,19 +97,21 @@ fi
printf "@ Building for $WHAT ...\n\n"
case "$WHAT" in
all|linux)
build_linux_amd64 && create_archive bettercap_linux_amd64_$VERSION.zip
;;
all|osx|mac|macos)
build_macos_amd64 && create_archive bettercap_macos_amd64_$VERSION.zip
;;
all|windows|win)
build_windows_amd64 && create_exe_archive bettercap_windows_amd64_$VERSION.zip
;;
all|android)
build_android_arm && create_archive bettercap_android_arm_$VERSION.zip
esac
if [[ "$WHAT" == "all" || "$WHAT" == "linux" ]]; then
build_linux_amd64 && create_archive bettercap_linux_amd64_$VERSION.zip
fi
if [[ "$WHAT" == "all" || "$WHAT" == "osx" || "$WHAT" == "mac" || "$WHAT" == "macos" ]]; then
build_macos_amd64 && create_archive bettercap_macos_amd64_$VERSION.zip
fi
if [[ "$WHAT" == "all" || "$WHAT" == "win" || "$WHAT" == "windows" ]]; then
build_windows_amd64 && create_exe_archive bettercap_windows_amd64_$VERSION.zip
fi
if [[ "$WHAT" == "all" || "$WHAT" == "android" ]]; then
build_android_arm && create_archive bettercap_android_arm_$VERSION.zip
fi
sha256sum * > checksums.txt
......
......@@ -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.20"
Author = "Simone 'evilsocket' Margaritelli"
Website = "https://bettercap.org/"
)
// +build android
package core
func Shell(cmd string) (string, error) {
return Exec("/system/bin/sh", []string{"-c", cmd})
}
// +build !windows
// +build !windows,!android
package core
......
......@@ -42,7 +42,7 @@ func NewRestAPI(s *session.Session) *RestAPI {
}
mod.AddParam(session.NewStringParameter("api.rest.address",
session.ParamIfaceAddress,
"127.0.0.1",
session.IPv4Validator,
"Address to bind the API REST server to."))
......@@ -172,7 +172,12 @@ func (mod *RestAPI) Configure() error {
router := mux.NewRouter()
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)
......
......@@ -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)
......@@ -41,7 +45,10 @@ func (mod *RestAPI) setSecurityHeaders(w http.ResponseWriter) {
w.Header().Add("X-Content-Type-Options", "nosniff")
w.Header().Add("X-XSS-Protection", "1; mode=block")
w.Header().Add("Referrer-Policy", "same-origin")
w.Header().Set("Access-Control-Allow-Origin", mod.allowOrigin)
w.Header().Add("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
w.Header().Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
}
func (mod *RestAPI) checkAuth(r *http.Request) bool {
......@@ -151,11 +158,16 @@ 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 {
http.Error(w, err.Error(), 400)
} else {
mod.toJSON(w, APIResponse{Success: true})
}
for _, aCommand := range session.ParseCommands(cmd.Command) {
if err = mod.Session.Run(aCommand); err != nil {
http.Error(w, err.Error(), 400)
return
}
}
mod.toJSON(w, APIResponse{Success: true})
}
func (mod *RestAPI) showEvents(w http.ResponseWriter, r *http.Request) {
......@@ -164,7 +176,13 @@ func (mod *RestAPI) showEvents(w http.ResponseWriter, r *http.Request) {
if mod.useWebsocket {
mod.startStreamingEvents(w, r)
} else {
events := session.I.Events.Sorted()
events := make([]session.Event, 0)
for _, e := range session.I.Events.Sorted() {
if mod.Session.EventsIgnoreList.Ignored(e) == false {
events = append(events, e)
}
}
nevents := len(events)
nmax := nevents
n := nmax
......@@ -190,6 +208,11 @@ func (mod *RestAPI) clearEvents(w http.ResponseWriter, r *http.Request) {
session.I.Events.Clear()
}
func (mod *RestAPI) corsRoute(w http.ResponseWriter, r *http.Request) {
mod.setSecurityHeaders(w)
w.WriteHeader(http.StatusNoContent)
}
func (mod *RestAPI) sessionRoute(w http.ResponseWriter, r *http.Request) {
mod.setSecurityHeaders(w)
......@@ -250,6 +273,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)
......@@ -266,3 +327,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)
}
}
......@@ -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())
......@@ -214,23 +219,19 @@ func (mod *ArpSpoofer) getTargets(probe bool) map[string]net.HardwareAddr {
// add targets specified by IP address
for _, ip := range mod.addresses {
if mod.Session.Skip(ip) {
mod.Debug("skipping IP %s from arp spoofing.", ip)
continue
}
// do we have this ip mac address?
if hw, err := mod.Session.FindMAC(ip, probe); err != nil {
mod.Debug("could not find hardware address for %s", ip.String())
} else {
if hw, err := mod.Session.FindMAC(ip, probe); err == nil {
targets[ip.String()] = hw
}
}
// add targets specified by MAC address
for _, hw := range mod.macs {
if ip, err := network.ArpInverseLookup(mod.Session.Interface.Name(), hw.String(), false); err != nil {
mod.Warning("could not find IP address for %s", hw.String())
} else if mod.Session.Skip(net.ParseIP(ip)) {
mod.Debug("skipping address %s from arp spoofing.", ip)
} else {
if ip, err := network.ArpInverseLookup(mod.Session.Interface.Name(), hw.String(), false); err == nil {
if mod.Session.Skip(net.ParseIP(ip)) {
continue
}
targets[ip] = hw
}
}
......@@ -295,6 +296,7 @@ func (mod *ArpSpoofer) arpSpoofTargets(saddr net.IP, smac net.HardwareAddr, chec
}
if gwPacket != nil {
mod.Debug("sending %d bytes of ARP packet to the gateway", len(gwPacket))
if err = mod.Session.Queue.Send(gwPacket); err != nil {
mod.Error("error while sending packet: %v", err)
}
......
......@@ -6,7 +6,6 @@ package ble
import (
"encoding/hex"
"fmt"
"io/ioutil"
golog "log"
"time"
......@@ -15,6 +14,8 @@ import (
"github.com/bettercap/bettercap/session"
"github.com/bettercap/gatt"
"github.com/evilsocket/islazy/str"
)
type BLERecon struct {
......@@ -41,6 +42,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")
......@@ -126,15 +129,25 @@ func (mod *BLERecon) isEnumerating() bool {
return mod.currDevice != nil
}
type dummyWriter struct {
mod *BLERecon
}
func (w dummyWriter) Write(p []byte) (n int, err error) {
w.mod.Debug("[gatt.log] %s", str.Trim(string(p)))
return len(p), nil
}
func (mod *BLERecon) Configure() (err error) {
if mod.Running() {
return session.ErrAlreadyStarted
} else if mod.gattDevice == nil {
mod.Debug("initializing device ...")
// hey Paypal GATT library, could you please just STFU?!
golog.SetOutput(ioutil.Discard)
golog.SetFlags(0)
golog.SetOutput(dummyWriter{mod})
if mod.gattDevice, err = gatt.NewDevice(defaultBLEClientOptions...); err != nil {
mod.Debug("error while creating new gatt device: %v", err)
return err
}
......@@ -160,9 +173,21 @@ func (mod *BLERecon) Start() error {
<-mod.quit
mod.Info("stopping scan ...")
if mod.gattDevice != nil {
mod.Info("stopping scan ...")
mod.gattDevice.StopScanning()
if mod.currDevice != nil && mod.currDevice.Device != nil {
mod.Debug("resetting connection with %v", mod.currDevice.Device)
mod.gattDevice.CancelConnection(mod.currDevice.Device)
}
mod.Debug("stopping device")
if err := mod.gattDevice.Stop(); err != nil {
mod.Warning("error while stopping device: %v", err)
} else {
mod.Debug("gatt device closed")
}
}
mod.done <- true
})
......@@ -172,6 +197,10 @@ func (mod *BLERecon) Stop() error {
return mod.SetRunning(false, func() {
mod.quit <- true
<-mod.done
mod.Debug("module stopped, cleaning state")
mod.gattDevice = nil
mod.setCurrentDevice(nil)
mod.ResetState()
})
}
......@@ -192,6 +221,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 {
......
......@@ -15,8 +15,12 @@ func (mod *BLERecon) onStateChanged(dev gatt.Device, s gatt.State) {
if mod.currDevice == nil {
mod.Info("starting discovery ...")
dev.Scan([]gatt.UUID{}, true)
} else {
mod.Debug("current device was not cleaned: %v", mod.currDevice)
}
case gatt.StatePoweredOff:
mod.Debug("resetting device instance")
mod.gattDevice.StopScanning()
mod.setCurrentDevice(nil)
mod.gattDevice = nil
......@@ -30,6 +34,7 @@ func (mod *BLERecon) onPeriphDiscovered(p gatt.Peripheral, a *gatt.Advertisement
}
func (mod *BLERecon) onPeriphDisconnected(p gatt.Peripheral, err error) {
mod.Session.Events.Add("ble.device.disconnected", mod.currDevice)
mod.setCurrentDevice(nil)
if mod.Running() {
mod.Info("device disconnected, restoring discovery.")
......@@ -51,6 +56,7 @@ func (mod *BLERecon) onPeriphConnected(p gatt.Peripheral, err error) {
defer func(per gatt.Peripheral) {
mod.Info("disconnecting from %s ...", per.ID())
per.Device().CancelConnection(per)
mod.setCurrentDevice(nil)
}(p)
mod.Session.Events.Add("ble.device.connected", mod.currDevice)
......@@ -61,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
}
......
......@@ -10,6 +10,7 @@ import (
"strconv"
"strings"
"github.com/bettercap/bettercap/network"
"github.com/bettercap/gatt"
"github.com/evilsocket/islazy/tui"
......@@ -276,7 +277,17 @@ func (mod *BLERecon) showServices(p gatt.Peripheral, services []*gatt.Service) {
wantsToWrite := mod.writeUUID != nil
foundToWrite := false
mod.currDevice.Services = make([]network.BLEService, 0)
for _, svc := range services {
service := network.BLEService{
UUID: svc.UUID().String(),
Name: svc.Name(),
Handle: svc.Handle(),
EndHandle: svc.EndHandle(),
Characteristics: make([]network.BLECharacteristic, 0),
}
mod.Session.Events.Add("ble.device.service.discovered", svc)
name := svc.Name()
......@@ -298,83 +309,99 @@ func (mod *BLERecon) showServices(p gatt.Peripheral, services []*gatt.Service) {
chars, err := p.DiscoverCharacteristics(nil, svc)
if err != nil {
mod.Error("error while enumerating chars for service %s: %s", svc.UUID(), err)
continue
}
} else {
for _, ch := range chars {
props, isReadable, isWritable, withResponse := parseProperties(ch)
char := network.BLECharacteristic{
UUID: ch.UUID().String(),
Name: ch.Name(),
Handle: ch.VHandle(),
Properties: props,
}
for _, ch := range chars {
mod.Session.Events.Add("ble.device.characteristic.discovered", ch)
mod.Session.Events.Add("ble.device.characteristic.discovered", ch