Merge branch 'master' into wifi.flood

This commit is contained in:
evilsocket 2018-03-13 15:13:37 +01:00
commit d6b9aa7b7f
No known key found for this signature in database
GPG key ID: 1564D7F30393A456
24 changed files with 646 additions and 335 deletions

View file

@ -3,13 +3,8 @@ FROM golang:1.10-alpine AS build-env
ENV GOPATH=/gocode
ENV SRC_DIR=/gocode/src/github.com/bettercap/bettercap
# As Alpine Linux uses a different folder, we need this
# ugly hack in order to compile gopacket statically
# https://github.com/bettercap/bettercap/issues/106
RUN apk add --update ca-certificates && \
apk add --no-cache --update bash iptables wireless-tools build-base libpcap-dev git python py-six && \
mkdir -p /usr/lib/x86_64-linux-gnu/ && \
cp /usr/lib/libpcap.a /usr/lib/x86_64-linux-gnu/libpcap.a
RUN apk add --update ca-certificates
RUN apk add --no-cache --update bash iptables wireless-tools build-base libpcap-dev git python py-six
WORKDIR $SRC_DIR
ADD . $SRC_DIR

View file

@ -1,11 +1,9 @@
### Prerequisites
# Prerequisites
* [ ] I read the [README](https://github.com/bettercap/bettercap/blob/master/README.md).
* [ ] I am running the [latest stable version](https://github.com/bettercap/bettercap/releases).
* [ ] I already searched [other issues](https://github.com/bettercap/bettercap/issues?q=is%3Aopen+is%3Aissue+label%3Abug) to see if this problem was already reported.
* [ ] I understand I don't necessarily have to paste this `Prerequisites` section in the issue.
Please, before creating this issue make sure that you read the [README](https://github.com/bettercap/bettercap/blob/master/README.md), that you are running the [latest stable version](https://github.com/bettercap/bettercap/releases) and that you already searched [other issues](https://github.com/bettercap/bettercap/issues?q=is%3Aopen+is%3Aissue+label%3Abug) to see if your problem or request was already reported.
### Description
! PLEASE REMOVE THIS PART AND LEAVE ONLY THE FOLLOWING SECTIONS IN YOUR REPORT !
---
*Description of the bug or feature request*
@ -17,8 +15,7 @@ Please provide:
* OS version and architecture you are using.
* Go version if building from sources.
* Command line arguments you are using.
* Caplet code you are using (if any).
* Interactive session commands you are using (if any).
* Caplet code you are using or the interactive session commands.
* **Full debug output** while reproducing the issue ( `bettercap -debug ...` ).
### Steps to Reproduce

View file

@ -13,6 +13,16 @@ host_dep() {
ping -c 1 $HOST > /dev/null || { echo "@ Virtual machine host $HOST not visible !"; exit 1; }
}
create_exe_archive() {
bin_dep 'zip'
OUTPUT=$1
echo "@ Creating archive $OUTPUT ..."
zip -j "$OUTPUT" bettercap.exe ../README.md ../LICENSE.md > /dev/null
rm -rf bettercap bettercap.exe
}
create_archive() {
bin_dep 'zip'
@ -172,19 +182,17 @@ mkdir $BUILD_FOLDER
cd $BUILD_FOLDER
build_linux_amd64 # && create_archive bettercap_linux_amd64_$VERSION.zip
# build_linux_arm7_static && create_archive bettercap_linux_arm7_$VERSION.zip
# build_linux_arm7hf_static && create_archive bettercap_linux_arm7hf_$VERSION.zip
# build_linux_mips_static && create_archive bettercap_linux_mips_$VERSION.zip
# build_linux_mipsle_static && create_archive bettercap_linux_mipsle_$VERSION.zip
# build_linux_mips64_static && create_archive bettercap_linux_mips64_$VERSION.zip
# build_linux_mips64le_static && create_archive bettercap_linux_mips64le_$VERSION.zip
#
# # these are still not static :(
# build_macos_amd64 && create_archive bettercap_macos_amd64_$VERSION.zip
# build_android_arm && create_archive bettercap_android_arm_$VERSION.zip
# build_windows_amd64 && create_archive bettercap_windows_amd64_$VERSION.zip
# sha256sum * > checksums.txt
build_linux_amd64 && create_archive bettercap_linux_amd64_$VERSION.zip
build_macos_amd64 && create_archive bettercap_macos_amd64_$VERSION.zip
build_android_arm && create_archive bettercap_android_arm_$VERSION.zip
build_windows_amd64 && create_exe_archive bettercap_windows_amd64_$VERSION.zip
build_linux_arm7_static && create_archive bettercap_linux_arm7_$VERSION.zip
build_linux_arm7hf_static && create_archive bettercap_linux_arm7hf_$VERSION.zip
build_linux_mips_static && create_archive bettercap_linux_mips_$VERSION.zip
build_linux_mipsle_static && create_archive bettercap_linux_mipsle_$VERSION.zip
build_linux_mips64_static && create_archive bettercap_linux_mips64_$VERSION.zip
build_linux_mips64le_static && create_archive bettercap_linux_mips64le_$VERSION.zip
sha256sum * > checksums.txt
echo
echo

View file

@ -41,6 +41,17 @@ func UniqueInts(a []int, sorted bool) []int {
return uniq
}
func CommaSplit(csv string) []string {
filtered := make([]string, 0)
for _, part := range strings.Split(csv, ",") {
part = Trim(part)
if part != "" {
filtered = append(filtered, part)
}
}
return filtered
}
func ExecSilent(executable string, args []string) (string, error) {
path, err := exec.LookPath(executable)
if err != nil {

View file

@ -5,8 +5,10 @@ import "flag"
type Options struct {
InterfaceName *string
Caplet *string
AutoStart *string
Debug *bool
Silent *bool
NoColors *bool
NoHistory *bool
EnvFile *string
Commands *string
@ -17,9 +19,11 @@ type Options struct {
func ParseOptions() (Options, error) {
o := Options{
InterfaceName: flag.String("iface", "", "Network interface to bind to, if empty the default interface will be auto selected."),
AutoStart: flag.String("autostart", "events.stream, net.recon", "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."),
Silent: flag.Bool("silent", false, "Suppress all logs which are not errors."),
NoColors: flag.Bool("no-colors", false, "Disable output color effects."),
NoHistory: flag.Bool("no-history", false, "Disable interactive session history file."),
EnvFile: flag.String("env-file", "", "Load environment variables from this file if found, set to empty to disable environment persistance."),
Commands: flag.String("eval", "", "Run one or more commands separated by ; in the interactive session, used to set variables via command line."),

View file

@ -26,14 +26,17 @@ var (
RESET = "\033[0m"
NoColors = false
HasColors = true
)
func init() {
NoColors = os.Getenv("TERM") == "dumb" ||
func isDumbTerminal() bool {
return os.Getenv("TERM") == "dumb" ||
os.Getenv("TERM") == "" ||
(!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()))
if NoColors {
}
func InitSwag(disableColors bool) {
if disableColors || isDumbTerminal() {
BOLD = ""
DIM = ""
RED = ""
@ -48,6 +51,7 @@ func init() {
BG_YELLOW = ""
BG_LBLUE = ""
RESET = ""
HasColors = false
}
}

28
main.go
View file

@ -14,8 +14,6 @@ import (
var sess *session.Session
var err error
// Some modules are enabled by default in order
// to make the interactive session useful.
var autoEnableList = []string{
"events.stream",
"net.recon",
@ -26,10 +24,15 @@ func main() {
fmt.Println(err)
os.Exit(1)
}
defer sess.Close()
if core.NoColors == true {
if core.HasColors == false {
if *sess.Options.NoColors == true {
fmt.Printf("\n\nWARNING: Terminal colors have been disabled, view will be very limited.\n\n")
} else {
fmt.Printf("\n\nWARNING: This terminal does not support colors, view will be very limited.\n\n")
}
}
appName := fmt.Sprintf("%s v%s", core.Name, core.Version)
@ -59,12 +62,6 @@ func main() {
log.Fatal("%s", err)
}
for _, modName := range autoEnableList {
if err = sess.Run(modName + " on"); err != nil {
log.Fatal("Error while starting module %s: %", modName, err)
}
}
/*
* Commands sent with -eval are used to set specific
* caplet parameters (i.e. arp.spoof.targets) via command
@ -77,6 +74,14 @@ func main() {
}
}
// Some modules are enabled by default in order
// to make the interactive session useful.
for _, modName := range core.CommaSplit(*sess.Options.AutoStart) {
if err = sess.Run(modName + " on"); err != nil {
log.Fatal("Error while starting module %s: %s", modName, err)
}
}
// Then run the caplet if specified.
if *sess.Options.Caplet != "" {
if err = sess.RunCaplet(*sess.Options.Caplet); err != nil {
@ -100,9 +105,4 @@ func main() {
}
}
}
sess.Close()
// Windows requires this otherwise the app never exits ...
os.Exit(0)
}

View file

@ -11,6 +11,7 @@ import (
"github.com/bettercap/bettercap/session"
"github.com/bettercap/bettercap/tls"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
)
@ -146,10 +147,22 @@ func (api *RestAPI) Configure() error {
api.server.Addr = fmt.Sprintf("%s:%d", ip, port)
router := http.NewServeMux()
router := mux.NewRouter()
router.HandleFunc("/api/session", api.sessionRoute)
router.HandleFunc("/api/events", api.eventsRoute)
router.HandleFunc("/api/session", api.sessionRoute)
router.HandleFunc("/api/session/ble", api.sessionRoute)
router.HandleFunc("/api/session/ble/{mac}", api.sessionRoute)
router.HandleFunc("/api/session/env", api.sessionRoute)
router.HandleFunc("/api/session/gateway", api.sessionRoute)
router.HandleFunc("/api/session/interface", api.sessionRoute)
router.HandleFunc("/api/session/lan", api.sessionRoute)
router.HandleFunc("/api/session/lan/{mac}", api.sessionRoute)
router.HandleFunc("/api/session/options", api.sessionRoute)
router.HandleFunc("/api/session/packets", api.sessionRoute)
router.HandleFunc("/api/session/started-at", api.sessionRoute)
router.HandleFunc("/api/session/wifi", api.sessionRoute)
router.HandleFunc("/api/session/wifi/{mac}", api.sessionRoute)
api.server.Handler = router

View file

@ -6,21 +6,11 @@ import (
"net/http"
"strconv"
"strings"
"time"
"github.com/bettercap/bettercap/log"
"github.com/bettercap/bettercap/session"
"github.com/gorilla/websocket"
)
const (
// Time allowed to write an event to the client.
writeWait = 10 * time.Second
// Time allowed to read the next pong message from the client.
pongWait = 60 * time.Second
// Send pings to client with this period. Must be less than pongWait.
pingPeriod = (pongWait * 9) / 10
"github.com/gorilla/mux"
)
type CommandRequest struct {
@ -67,6 +57,71 @@ func (api *RestAPI) showSession(w http.ResponseWriter, r *http.Request) {
toJSON(w, session.I)
}
func (api *RestAPI) showBle(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
mac := strings.ToLower(params["mac"])
if mac == "" {
toJSON(w, session.I.BLE)
} else if dev, found := session.I.BLE.Get(mac); found == true {
toJSON(w, dev)
} else {
http.Error(w, "Not Found", 404)
}
}
func (api *RestAPI) showEnv(w http.ResponseWriter, r *http.Request) {
toJSON(w, session.I.Env)
}
func (api *RestAPI) showGateway(w http.ResponseWriter, r *http.Request) {
toJSON(w, session.I.Gateway)
}
func (api *RestAPI) showInterface(w http.ResponseWriter, r *http.Request) {
toJSON(w, session.I.Interface)
}
func (api *RestAPI) showLan(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
mac := strings.ToLower(params["mac"])
if mac == "" {
toJSON(w, session.I.Lan)
} else if host, found := session.I.Lan.Get(mac); found == true {
toJSON(w, host)
} else {
http.Error(w, "Not Found", 404)
}
}
func (api *RestAPI) showOptions(w http.ResponseWriter, r *http.Request) {
toJSON(w, session.I.Options)
}
func (api *RestAPI) showPackets(w http.ResponseWriter, r *http.Request) {
toJSON(w, session.I.Queue)
}
func (api *RestAPI) showStartedAt(w http.ResponseWriter, r *http.Request) {
toJSON(w, session.I.StartedAt)
}
func (api *RestAPI) showWiFi(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
mac := strings.ToLower(params["mac"])
if mac == "" {
toJSON(w, session.I.WiFi)
} else if station, found := session.I.WiFi.Get(mac); found == true {
toJSON(w, station)
} else if client, found := session.I.WiFi.GetClient(mac); found == true {
toJSON(w, client)
} else {
http.Error(w, "Not Found", 404)
}
}
func (api *RestAPI) runSessionCommand(w http.ResponseWriter, r *http.Request) {
var err error
var cmd CommandRequest
@ -82,103 +137,12 @@ func (api *RestAPI) runSessionCommand(w http.ResponseWriter, r *http.Request) {
}
}
func (api *RestAPI) streamEvent(ws *websocket.Conn, event session.Event) error {
msg, err := json.Marshal(event)
if err != nil {
log.Error("Error while creating websocket message: %s", err)
return err
}
ws.SetWriteDeadline(time.Now().Add(writeWait))
if err := ws.WriteMessage(websocket.TextMessage, msg); err != nil {
if !strings.Contains(err.Error(), "closed connection") {
log.Error("Error while writing websocket message: %s", err)
return err
}
}
return nil
}
func (api *RestAPI) sendPing(ws *websocket.Conn) error {
ws.SetWriteDeadline(time.Now().Add(writeWait))
if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
log.Error("Error while writing websocket ping message: %s", err)
return err
}
return nil
}
func (api *RestAPI) streamWriter(ws *websocket.Conn, w http.ResponseWriter, r *http.Request) {
defer ws.Close()
// first we stream what we already have
events := session.I.Events.Sorted()
n := len(events)
if n > 0 {
log.Debug("Sending %d events.", n)
for _, event := range events {
if err := api.streamEvent(ws, event); err != nil {
return
}
}
}
session.I.Events.Clear()
log.Debug("Listening for events and streaming to ws endpoint ...")
pingTicker := time.NewTicker(pingPeriod)
for {
select {
case <-pingTicker.C:
if err := api.sendPing(ws); err != nil {
return
}
case event := <-api.eventListener:
if err := api.streamEvent(ws, event); err != nil {
return
}
case <-api.quit:
log.Info("Stopping websocket events streamer ...")
return
}
}
}
func (api *RestAPI) streamReader(ws *websocket.Conn) {
defer ws.Close()
ws.SetReadLimit(512)
ws.SetReadDeadline(time.Now().Add(pongWait))
ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
for {
_, _, err := ws.ReadMessage()
if err != nil {
log.Debug("Closing websocket reader.")
break
}
}
}
func (api *RestAPI) showEvents(w http.ResponseWriter, r *http.Request) {
var err error
if api.useWebsocket {
ws, err := api.upgrader.Upgrade(w, r, nil)
if err != nil {
if _, ok := err.(websocket.HandshakeError); !ok {
log.Error("Error while updating api.rest connection to websocket: %s", err)
}
return
}
log.Debug("Websocket streaming started for %s", r.RemoteAddr)
go api.streamWriter(ws, w, r)
api.streamReader(ws)
api.startStreamingEvents(w, r)
} else {
events := session.I.Events.Sorted()
nevents := len(events)
nmax := nevents
@ -210,12 +174,49 @@ func (api *RestAPI) sessionRoute(w http.ResponseWriter, r *http.Request) {
if api.checkAuth(r) == false {
setAuthFailed(w, r)
} else if r.Method == "GET" {
api.showSession(w, r)
return
} else if r.Method == "POST" {
api.runSessionCommand(w, r)
} else {
return
} else if r.Method != "GET" {
http.Error(w, "Bad Request", 400)
return
}
path := r.URL.String()
switch {
case path == "/api/session":
api.showSession(w, r)
case path == "/api/session/env":
api.showEnv(w, r)
case path == "/api/session/gateway":
api.showGateway(w, r)
case path == "/api/session/interface":
api.showInterface(w, r)
case strings.HasPrefix(path, "/api/session/lan"):
api.showLan(w, r)
case path == "/api/session/options":
api.showOptions(w, r)
case path == "/api/session/packets":
api.showPackets(w, r)
case path == "/api/session/started-at":
api.showStartedAt(w, r)
case strings.HasPrefix(path, "/api/session/ble"):
api.showBle(w, r)
case strings.HasPrefix(path, "/api/session/wifi"):
api.showWiFi(w, r)
default:
http.Error(w, "Not Found", 404)
}
}
@ -224,7 +225,10 @@ func (api *RestAPI) eventsRoute(w http.ResponseWriter, r *http.Request) {
if api.checkAuth(r) == false {
setAuthFailed(w, r)
} else if r.Method == "GET" {
return
}
if r.Method == "GET" {
api.showEvents(w, r)
} else if r.Method == "DELETE" {
api.clearEvents(w, r)

116
modules/api_rest_ws.go Normal file
View file

@ -0,0 +1,116 @@
package modules
import (
"encoding/json"
"net/http"
"strings"
"time"
"github.com/bettercap/bettercap/log"
"github.com/bettercap/bettercap/session"
"github.com/gorilla/websocket"
)
const (
// Time allowed to write an event to the client.
writeWait = 10 * time.Second
// Time allowed to read the next pong message from the client.
pongWait = 60 * time.Second
// Send pings to client with this period. Must be less than pongWait.
pingPeriod = (pongWait * 9) / 10
)
func (api *RestAPI) streamEvent(ws *websocket.Conn, event session.Event) error {
msg, err := json.Marshal(event)
if err != nil {
log.Error("Error while creating websocket message: %s", err)
return err
}
ws.SetWriteDeadline(time.Now().Add(writeWait))
if err := ws.WriteMessage(websocket.TextMessage, msg); err != nil {
if !strings.Contains(err.Error(), "closed connection") {
log.Error("Error while writing websocket message: %s", err)
return err
}
}
return nil
}
func (api *RestAPI) sendPing(ws *websocket.Conn) error {
ws.SetWriteDeadline(time.Now().Add(writeWait))
if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
log.Error("Error while writing websocket ping message: %s", err)
return err
}
return nil
}
func (api *RestAPI) streamWriter(ws *websocket.Conn, w http.ResponseWriter, r *http.Request) {
defer ws.Close()
// first we stream what we already have
events := session.I.Events.Sorted()
n := len(events)
if n > 0 {
log.Debug("Sending %d events.", n)
for _, event := range events {
if err := api.streamEvent(ws, event); err != nil {
return
}
}
}
session.I.Events.Clear()
log.Debug("Listening for events and streaming to ws endpoint ...")
pingTicker := time.NewTicker(pingPeriod)
for {
select {
case <-pingTicker.C:
if err := api.sendPing(ws); err != nil {
return
}
case event := <-api.eventListener:
if err := api.streamEvent(ws, event); err != nil {
return
}
case <-api.quit:
log.Info("Stopping websocket events streamer ...")
return
}
}
}
func (api *RestAPI) streamReader(ws *websocket.Conn) {
defer ws.Close()
ws.SetReadLimit(512)
ws.SetReadDeadline(time.Now().Add(pongWait))
ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
for {
_, _, err := ws.ReadMessage()
if err != nil {
log.Debug("Closing websocket reader.")
break
}
}
}
func (api *RestAPI) startStreamingEvents(w http.ResponseWriter, r *http.Request) {
ws, err := api.upgrader.Upgrade(w, r, nil)
if err != nil {
if _, ok := err.(websocket.HandshakeError); !ok {
log.Error("Error while updating api.rest connection to websocket: %s", err)
}
return
}
log.Debug("Websocket streaming started for %s", r.RemoteAddr)
go api.streamWriter(ws, w, r)
api.streamReader(ws)
}

View file

@ -2,6 +2,7 @@ package modules
import (
"fmt"
"os"
"strconv"
"time"
@ -12,6 +13,7 @@ import (
type EventsStream struct {
session.SessionModule
output *os.File
ignoreList *IgnoreList
waitFor string
waitChan chan *session.Event
@ -22,6 +24,7 @@ type EventsStream struct {
func NewEventsStream(s *session.Session) *EventsStream {
stream := &EventsStream{
SessionModule: session.NewSessionModule("events.stream", s),
output: os.Stdout,
quit: make(chan bool),
waitChan: make(chan *session.Event),
waitFor: "",
@ -104,6 +107,11 @@ func NewEventsStream(s *session.Session) *EventsStream {
return nil
}))
stream.AddParam(session.NewStringParameter("events.stream.output",
"",
"",
"If not empty, events will be written to this file instead of the standard output."))
return stream
}
@ -119,11 +127,25 @@ func (s EventsStream) Author() string {
return "Simone Margaritelli <evilsocket@protonmail.com>"
}
func (s *EventsStream) Configure() error {
return nil
func (s *EventsStream) Configure() (err error) {
var output string
if err, output = s.StringParam("events.stream.output"); err == nil {
if output == "" {
s.output = os.Stdout
} else if output, err = core.ExpandPath(output); err == nil {
s.output, err = os.OpenFile(output, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
}
}
return err
}
func (s *EventsStream) Start() error {
if err := s.Configure(); err != nil {
return err
}
return s.SetRunning(true, func() {
s.eventListener = s.Session.Events.Listen()
for {
@ -196,5 +218,8 @@ func (s *EventsStream) startWaitingFor(tag string, timeout int) error {
func (s *EventsStream) Stop() error {
return s.SetRunning(false, func() {
s.quit <- true
if s.output != os.Stdout {
s.output.Close()
}
})
}

View file

@ -4,6 +4,7 @@ import (
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"github.com/bettercap/bettercap/core"
@ -13,15 +14,15 @@ import (
const eventTimeFormat = "15:04:05"
func (s EventsStream) viewLogEvent(e session.Event) {
fmt.Printf("[%s] [%s] [%s] %s\n",
func (s *EventsStream) viewLogEvent(e session.Event) {
fmt.Fprintf(s.output, "[%s] [%s] [%s] %s\n",
e.Time.Format(eventTimeFormat),
core.Green(e.Tag),
e.Label(),
e.Data.(session.LogMessage).Message)
}
func (s EventsStream) viewWiFiEvent(e session.Event) {
func (s *EventsStream) viewWiFiEvent(e session.Event) {
if strings.HasPrefix(e.Tag, "wifi.ap.") {
ap := e.Data.(*network.AccessPoint)
@ -35,7 +36,7 @@ func (s EventsStream) viewWiFiEvent(e session.Event) {
}
if e.Tag == "wifi.ap.new" {
fmt.Printf("[%s] [%s] WiFi access point %s%s detected as %s%s.\n",
fmt.Fprintf(s.output, "[%s] [%s] WiFi access point %s%s detected as %s%s.\n",
e.Time.Format(eventTimeFormat),
core.Green(e.Tag),
core.Bold(ap.ESSID()),
@ -43,13 +44,13 @@ func (s EventsStream) viewWiFiEvent(e session.Event) {
core.Green(ap.BSSID()),
core.Dim(vend))
} else if e.Tag == "wifi.ap.lost" {
fmt.Printf("[%s] [%s] WiFi access point %s (%s) lost.\n",
fmt.Fprintf(s.output, "[%s] [%s] WiFi access point %s (%s) lost.\n",
e.Time.Format(eventTimeFormat),
core.Green(e.Tag),
core.Red(ap.ESSID()),
ap.BSSID())
} else {
fmt.Printf("[%s] [%s] %s\n",
fmt.Fprintf(s.output, "[%s] [%s] %s\n",
e.Time.Format(eventTimeFormat),
core.Green(e.Tag),
ap.String())
@ -67,7 +68,7 @@ func (s EventsStream) viewWiFiEvent(e session.Event) {
rssi = fmt.Sprintf(" (%d dBm)", probe.RSSI)
}
fmt.Printf("[%s] [%s] Station %s%s is probing for SSID %s%s\n",
fmt.Fprintf(s.output, "[%s] [%s] Station %s%s is probing for SSID %s%s\n",
e.Time.Format(eventTimeFormat),
core.Green(e.Tag),
probe.FromAddr.String(),
@ -77,7 +78,7 @@ func (s EventsStream) viewWiFiEvent(e session.Event) {
}
}
func (s EventsStream) viewEndpointEvent(e session.Event) {
func (s *EventsStream) viewEndpointEvent(e session.Event) {
t := e.Data.(*network.Endpoint)
vend := ""
name := ""
@ -93,7 +94,7 @@ func (s EventsStream) viewEndpointEvent(e session.Event) {
}
if e.Tag == "endpoint.new" {
fmt.Printf("[%s] [%s] Endpoint %s%s detected as %s%s.\n",
fmt.Fprintf(s.output, "[%s] [%s] Endpoint %s%s detected as %s%s.\n",
e.Time.Format(eventTimeFormat),
core.Green(e.Tag),
core.Bold(t.IpAddress),
@ -101,27 +102,27 @@ func (s EventsStream) viewEndpointEvent(e session.Event) {
core.Green(t.HwAddress),
core.Dim(vend))
} else if e.Tag == "endpoint.lost" {
fmt.Printf("[%s] [%s] Endpoint %s%s lost.\n",
fmt.Fprintf(s.output, "[%s] [%s] Endpoint %s%s lost.\n",
e.Time.Format(eventTimeFormat),
core.Green(e.Tag),
core.Red(t.IpAddress),
core.Dim(vend))
} else {
fmt.Printf("[%s] [%s] %s\n",
fmt.Fprintf(s.output, "[%s] [%s] %s\n",
e.Time.Format(eventTimeFormat),
core.Green(e.Tag),
t.String())
}
}
func (s EventsStream) viewModuleEvent(e session.Event) {
fmt.Printf("[%s] [%s] %s\n",
func (s *EventsStream) viewModuleEvent(e session.Event) {
fmt.Fprintf(s.output, "[%s] [%s] %s\n",
e.Time.Format(eventTimeFormat),
core.Green(e.Tag),
e.Data)
}
func (s EventsStream) viewSnifferEvent(e session.Event) {
func (s *EventsStream) viewSnifferEvent(e session.Event) {
se := e.Data.(SnifferEvent)
misc := ""
@ -150,16 +151,16 @@ func (s EventsStream) viewSnifferEvent(e session.Event) {
misc = fmt.Sprintf("%s", se.Data)
}
fmt.Printf("[%s] [%s] %s %s\n",
fmt.Fprintf(s.output, "[%s] [%s] %s %s\n",
e.Time.Format(eventTimeFormat),
core.Green(e.Tag),
se.Message,
misc)
}
func (s EventsStream) viewSynScanEvent(e session.Event) {
func (s *EventsStream) viewSynScanEvent(e session.Event) {
se := e.Data.(SynScanEvent)
fmt.Printf("[%s] [%s] Found open port %d for %s\n",
fmt.Fprintf(s.output, "[%s] [%s] Found open port %d for %s\n",
e.Time.Format(eventTimeFormat),
core.Green(e.Tag),
se.Port,
@ -182,10 +183,10 @@ func (s *EventsStream) View(e session.Event, refresh bool) {
} else if strings.HasPrefix(e.Tag, "syn.scan.") {
s.viewSynScanEvent(e)
} else {
fmt.Printf("[%s] [%s] %v\n", e.Time.Format(eventTimeFormat), core.Green(e.Tag), e)
fmt.Fprintf(s.output, "[%s] [%s] %v\n", e.Time.Format(eventTimeFormat), core.Green(e.Tag), e)
}
if refresh {
if refresh && s.output == os.Stdout {
s.Session.Refresh()
}
}

View file

@ -11,7 +11,7 @@ import (
"github.com/bettercap/bettercap/session"
)
func (s EventsStream) viewBLEEvent(e session.Event) {
func (s *EventsStream) viewBLEEvent(e session.Event) {
if e.Tag == "ble.device.new" {
dev := e.Data.(*network.BLEDevice)
name := dev.Device.Name()
@ -23,7 +23,7 @@ func (s EventsStream) viewBLEEvent(e session.Event) {
vend = fmt.Sprintf(" (%s)", core.Yellow(vend))
}
fmt.Printf("[%s] [%s] New BLE device%s detected as %s%s %s.\n",
fmt.Fprintf(s.output, "[%s] [%s] New BLE device%s detected as %s%s %s.\n",
e.Time.Format(eventTimeFormat),
core.Green(e.Tag),
name,
@ -41,14 +41,14 @@ func (s EventsStream) viewBLEEvent(e session.Event) {
vend = fmt.Sprintf(" (%s)", core.Yellow(vend))
}
fmt.Printf("[%s] [%s] BLE device%s %s%s lost.\n",
fmt.Fprintf(s.output, "[%s] [%s] BLE device%s %s%s lost.\n",
e.Time.Format(eventTimeFormat),
core.Green(e.Tag),
name,
dev.Device.ID(),
vend)
} /* else {
fmt.Printf("[%s] [%s]\n",
fmt.Fprintf(s.output,"[%s] [%s]\n",
e.Time.Format(eventTimeFormat),
core.Green(e.Tag))
} */

View file

@ -6,6 +6,6 @@ import (
"github.com/bettercap/bettercap/session"
)
func (s EventsStream) viewBLEEvent(e session.Event) {
func (s *EventsStream) viewBLEEvent(e session.Event) {
}

View file

@ -6,6 +6,7 @@ import (
"time"
"github.com/bettercap/bettercap/log"
"github.com/bettercap/bettercap/network"
"github.com/bettercap/bettercap/packets"
)
@ -62,7 +63,7 @@ func (w *WiFiModule) startDeauth(to net.HardwareAddr) error {
if ap, found := w.Session.WiFi.Get(bssid); found == true {
clients := ap.Clients()
log.Info("Deauthing %d clients from AP %s ...", len(clients), ap.ESSID())
w.onChannel(mhz2chan(ap.Frequency), func() {
w.onChannel(network.Dot11Freq2Chan(ap.Frequency), func() {
for _, c := range clients {
if w.Running() == false {
break
@ -81,7 +82,7 @@ func (w *WiFiModule) startDeauth(to net.HardwareAddr) error {
break
} else if c, found := ap.Get(bssid); found == true {
log.Info("Deauthing client %s from AP %s ...", c.HwAddress, ap.ESSID())
w.onChannel(mhz2chan(ap.Frequency), func() {
w.onChannel(network.Dot11Freq2Chan(ap.Frequency), func() {
w.sendDeauthPacket(ap.HW, c.HW)
})
return nil

58
modules/wifi_hopping.go Normal file
View file

@ -0,0 +1,58 @@
package modules
import (
"time"
"github.com/bettercap/bettercap/log"
"github.com/bettercap/bettercap/network"
)
func (w *WiFiModule) onChannel(channel int, cb func()) {
prev := w.stickChan
w.stickChan = channel
if err := network.SetInterfaceChannel(w.Session.Interface.Name(), channel); err != nil {
log.Warning("Error while hopping to channel %d: %s", channel, err)
} else {
log.Debug("Hopped on channel %d", channel)
}
cb()
w.stickChan = prev
}
func (w *WiFiModule) channelHopper() {
w.reads.Add(1)
defer w.reads.Done()
log.Info("Channel hopper started.")
for w.Running() == true {
delay := w.hopPeriod
// if we have both 2.4 and 5ghz capabilities, we have
// more channels, therefore we need to increase the time
// we hop on each one otherwise me lose information
if len(w.frequencies) > 14 {
delay = delay * 2
}
frequencies := w.frequencies
for _, frequency := range frequencies {
channel := network.Dot11Freq2Chan(frequency)
// stick to the access point channel as long as it's selected
// or as long as we're deauthing on it
if w.stickChan != 0 {
channel = w.stickChan
}
if err := network.SetInterfaceChannel(w.Session.Interface.Name(), channel); err != nil {
log.Warning("Error while hopping to channel %d: %s", channel, err)
}
time.Sleep(delay)
if w.Running() == false {
return
}
}
}
}

View file

@ -3,6 +3,8 @@ package modules
import (
"fmt"
"net"
"strconv"
"strings"
"sync"
"time"
@ -31,6 +33,7 @@ type WiFiModule struct {
session.SessionModule
handle *pcap.Handle
source string
channel int
hopPeriod time.Duration
frequencies []int
@ -38,6 +41,7 @@ type WiFiModule struct {
stickChan int
skipBroken bool
pktSourceChan chan gopacket.Packet
pktSourceChanClosed bool
writes *sync.WaitGroup
reads *sync.WaitGroup
}
@ -74,7 +78,7 @@ func NewWiFiModule(s *session.Session) *WiFiModule {
return err
} else if ap, found := w.Session.WiFi.Get(bssid.String()); found == true {
w.ap = ap
w.stickChan = mhz2chan(ap.Frequency)
w.stickChan = network.Dot11Freq2Chan(ap.Frequency)
return nil
}
return fmt.Errorf("Could not find station with BSSID %s", args[0])
@ -85,6 +89,10 @@ func NewWiFiModule(s *session.Session) *WiFiModule {
func(args []string) error {
w.ap = nil
w.stickChan = 0
var err error
if w.frequencies, err = network.GetSupportedFrequencies(w.Session.Interface.Name()); err != nil {
return err
}
return nil
}))
@ -110,13 +118,43 @@ func NewWiFiModule(s *session.Session) *WiFiModule {
return w.Show("rssi")
}))
w.AddParam(session.NewIntParameter("wifi.recon.channel",
w.AddHandler(session.NewModuleHandler("wifi.recon.channel", `wifi\.recon\.channel[\s]+([0-9]+(?:[, ]+[0-9]+)*|clear)`,
"WiFi channels (comma separated) or 'clear' for channel hopping.",
func(args []string) error {
newfrequencies := w.frequencies[:0]
if len(args) > 0 && args[0] != "clear" {
channels := strings.Split(args[0], ",")
for _, c := range channels {
trimmed := strings.Trim(c, " ")
channel, err := strconv.Atoi(trimmed)
if err != nil {
return err
}
newfrequencies = append(newfrequencies, network.Dot11Chan2Freq(channel))
}
} else {
// No channels setted, retrieve frequencies supported by the card
if frequencies, err := network.GetSupportedFrequencies(w.Session.Interface.Name()); err != nil {
return err
} else {
newfrequencies = frequencies
}
}
w.frequencies = newfrequencies
return nil
}))
w.AddParam(session.NewStringParameter("wifi.source.file",
"",
"WiFi channel or empty for channel hopping."))
"",
"If set, the wifi module will read from this pcap file instead of the hardware interface."))
w.AddParam(session.NewIntParameter("wifi.hop.period",
"250",
"If channel hopping is enabled (empty wifi.recon.channel), this is the time in millseconds the algorithm will hop on every channel (it'll be doubled if both 2.4 and 5.0 bands are available)."))
"If channel hopping is enabled (empty wifi.recon.channel), this is the time in milliseconds the algorithm will hop on every channel (it'll be doubled if both 2.4 and 5.0 bands are available)."))
w.AddParam(session.NewBoolParameter("wifi.skip-broken",
"true",
@ -137,21 +175,19 @@ func (w WiFiModule) Author() string {
return "Gianluca Braga <matrix86@protonmail.com> && Simone Margaritelli <evilsocket@protonmail.com>>"
}
func mhz2chan(freq int) int {
// ambo!
if freq <= 2472 {
return ((freq - 2412) / 5) + 1
} else if freq == 2484 {
return 14
} else if freq >= 5035 && freq <= 5865 {
return ((freq - 5035) / 5) + 7
}
return 0
}
func (w *WiFiModule) Configure() error {
var hopPeriod int
var err error
if err, w.source = w.StringParam("wifi.source.file"); err != nil {
return err
}
if w.source != "" {
if w.handle, err = pcap.OpenOffline(w.source); err != nil {
return err
}
} else {
ihandle, err := pcap.NewInactiveHandle(w.Session.Interface.Name())
if err != nil {
return err
@ -167,6 +203,7 @@ func (w *WiFiModule) Configure() error {
} else if w.handle, err = ihandle.Activate(); err != nil {
return err
}
}
if err, w.skipBroken = w.BoolParam("wifi.skip-broken"); err != nil {
return err
@ -176,13 +213,13 @@ func (w *WiFiModule) Configure() error {
w.hopPeriod = time.Duration(hopPeriod) * time.Millisecond
if err, w.channel = w.IntParam("wifi.recon.channel"); err == nil {
if err = network.SetInterfaceChannel(w.Session.Interface.Name(), w.channel); err != nil {
if w.source == "" {
// No channels setted, retrieve frequencies supported by the card
if len(w.frequencies) == 0 {
if w.frequencies, err = network.GetSupportedFrequencies(w.Session.Interface.Name()); err != nil {
return err
}
log.Info("WiFi recon active on channel %d.", w.channel)
} else {
w.channel = 0
// we need to start somewhere, this is just to check if
// this OS supports switching channel programmatically.
if err = network.SetInterfaceChannel(w.Session.Interface.Name(), 1); err != nil {
@ -190,55 +227,25 @@ func (w *WiFiModule) Configure() error {
}
log.Info("WiFi recon active with channel hopping.")
}
if frequencies, err := network.GetSupportedFrequencies(w.Session.Interface.Name()); err != nil {
return err
} else {
w.frequencies = frequencies
}
return nil
}
func (w *WiFiModule) onChannel(channel int, cb func()) {
prev := w.stickChan
w.stickChan = channel
if err := network.SetInterfaceChannel(w.Session.Interface.Name(), channel); err != nil {
log.Warning("Error while hopping to channel %d: %s", channel, err)
} else {
log.Debug("Hopped on channel %d", channel)
}
cb()
w.stickChan = prev
}
func isZeroBSSID(bssid net.HardwareAddr) bool {
for _, b := range bssid {
if b != 0x00 {
return false
}
}
return true
}
func isBroadcastBSSID(bssid net.HardwareAddr) bool {
for _, b := range bssid {
if b != 0xff {
return false
}
}
return true
}
func (w *WiFiModule) discoverAccessPoints(radiotap *layers.RadioTap, dot11 *layers.Dot11, packet gopacket.Packet) {
// search for Dot11InformationElementIDSSID
if ok, ssid := packets.Dot11ParseIDSSID(packet); ok == true {
if isZeroBSSID(dot11.Address3) == false && isBroadcastBSSID(dot11.Address3) == false {
bssid := dot11.Address3.String()
frequency := int(radiotap.ChannelFrequency)
from := dot11.Address3
if network.IsZeroMac(from) == false && network.IsBroadcastMac(from) == false {
var frequency int
bssid := from.String()
if found, channel := packets.Dot11ParseDSSet(packet); found {
frequency = network.Dot11Chan2Freq(channel)
} else {
frequency = int(radiotap.ChannelFrequency)
}
w.Session.WiFi.AddIfNew(ssid, bssid, frequency, radiotap.DBMAntennaSignal)
}
}
@ -317,40 +324,6 @@ func (w *WiFiModule) updateStats(dot11 *layers.Dot11, packet gopacket.Packet) {
}
}
func (w *WiFiModule) channelHopper() {
w.reads.Add(1)
defer w.reads.Done()
log.Info("Channel hopper started.")
for w.Running() == true {
delay := w.hopPeriod
// if we have both 2.4 and 5ghz capabilities, we have
// more channels, therefore we need to increase the time
// we hop on each one otherwise me lose information
if len(w.frequencies) > 14 {
delay = 500 * time.Millisecond
}
for _, frequency := range w.frequencies {
channel := mhz2chan(frequency)
// stick to the access point channel as long as it's selected
// or as long as we're deauthing on it
if w.stickChan != 0 {
channel = w.stickChan
}
if err := network.SetInterfaceChannel(w.Session.Interface.Name(), channel); err != nil {
log.Warning("Error while hopping to channel %d: %s", channel, err)
}
time.Sleep(delay)
if w.Running() == false {
return
}
}
}
}
func (w *WiFiModule) stationPruner() {
w.reads.Add(1)
defer w.reads.Done()
@ -386,7 +359,7 @@ func (w *WiFiModule) Start() error {
w.SetRunning(true, func() {
// start channel hopper if needed
if w.channel == 0 {
if w.channel == 0 && w.source == "" {
go w.channelHopper()
}
@ -423,6 +396,7 @@ func (w *WiFiModule) Start() error {
w.updateStats(dot11, packet)
}
}
w.pktSourceChanClosed = true
})
return nil
@ -433,7 +407,9 @@ func (w *WiFiModule) Stop() error {
// wait any pending write operation
w.writes.Wait()
// signal the main for loop we want to exit
if w.pktSourceChanClosed == false {
w.pktSourceChan <- nil
}
// close the pcap handle to make the main for exit
w.handle.Close()
// close the pcap handle to make the main for exit

View file

@ -17,7 +17,8 @@ func (w *WiFiModule) isApSelected() bool {
return w.ap != nil
}
func (w *WiFiModule) getRow(station *network.Station) []string {
func (w *WiFiModule) getRow(station *network.Station) ([]string, bool) {
include := false
sinceStarted := time.Since(w.Session.StartedAt)
sinceFirstSeen := time.Since(station.FirstSeen)
@ -61,16 +62,27 @@ func (w *WiFiModule) getRow(station *network.Station) []string {
recvd = humanize.Bytes(station.Received)
}
if w.source == "" {
for _, frequencies := range w.frequencies {
if frequencies == station.Frequency {
include = true
break
}
}
} else {
include = true
}
if w.isApSelected() {
return []string{
fmt.Sprintf("%d dBm", station.RSSI),
bssid,
/* station.Vendor, */
strconv.Itoa(mhz2chan(station.Frequency)),
strconv.Itoa(network.Dot11Freq2Chan(station.Frequency)),
sent,
recvd,
seen,
}
}, include
} else {
// this is ugly, but necessary in order to have this
// method handle both access point and clients
@ -88,12 +100,12 @@ func (w *WiFiModule) getRow(station *network.Station) []string {
ssid,
/* station.Vendor, */
encryption,
strconv.Itoa(mhz2chan(station.Frequency)),
strconv.Itoa(network.Dot11Freq2Chan(station.Frequency)),
clients,
sent,
recvd,
seen,
}
}, include
}
}
@ -123,7 +135,9 @@ func (w *WiFiModule) Show(by string) error {
rows := make([][]string, 0)
for _, s := range stations {
rows = append(rows, w.getRow(s))
if row, include := w.getRow(s); include == true {
rows = append(rows, row)
}
}
nrows := len(rows)

View file

@ -26,6 +26,24 @@ var (
IPv4Validator = regexp.MustCompile("^[0-9\\.]+/?\\d*$")
)
func IsZeroMac(mac net.HardwareAddr) bool {
for _, b := range mac {
if b != 0x00 {
return false
}
}
return true
}
func IsBroadcastMac(mac net.HardwareAddr) bool {
for _, b := range mac {
if b != 0xff {
return false
}
}
return true
}
func NormalizeMac(mac string) string {
var parts []string
if strings.ContainsRune(mac, '-') {

View file

@ -7,6 +7,29 @@ import (
"time"
)
func Dot11Freq2Chan(freq int) int {
if freq <= 2472 {
return ((freq - 2412) / 5) + 1
} else if freq == 2484 {
return 14
} else if freq >= 5035 && freq <= 5865 {
return ((freq - 5035) / 5) + 7
}
return 0
}
func Dot11Chan2Freq(channel int) int {
if channel <= 13 {
return ((channel - 1) * 5) + 2412
} else if channel == 14 {
return 2484
} else if channel <= 173 {
return ((channel - 7) * 5) + 5035
}
return 0
}
type APNewCallback func(ap *AccessPoint)
type APLostCallback func(ap *AccessPoint)
@ -133,6 +156,20 @@ func (w *WiFi) Get(mac string) (*AccessPoint, bool) {
return ap, found
}
func (w *WiFi) GetClient(mac string) (*Station, bool) {
w.Lock()
defer w.Unlock()
mac = NormalizeMac(mac)
for _, ap := range w.aps {
if client, found := ap.Get(mac); found == true {
return client, true
}
}
return nil, false
}
func (w *WiFi) Clear() error {
w.aps = make(map[string]*AccessPoint)
return nil

View file

@ -126,3 +126,20 @@ func Dot11IsDataFor(dot11 *layers.Dot11, station net.HardwareAddr) bool {
// packet going to this specific BSSID?
return bytes.Compare(dot11.Address1, station) == 0
}
func Dot11ParseDSSet(packet gopacket.Packet) (bool, int) {
channel := 0
found := false
for _, layer := range packet.Layers() {
info, ok := layer.(*layers.Dot11InformationElement)
if ok == true {
if info.ID == layers.Dot11InformationElementIDDSSet {
channel, _ = Dot11InformationElementIDDSSetDecode(info.Info)
found = true
break
}
}
}
return found, channel
}

View file

@ -211,3 +211,11 @@ func Dot11InformationElementRSNInfoDecode(buf []byte) (rsn RSNInfo, err error) {
return
}
func Dot11InformationElementIDDSSetDecode(buf []byte) (channel int, err error) {
if err = canParse("DSSet.channel", buf, 1); err == nil {
channel = int(buf[0])
}
return
}

View file

@ -15,23 +15,6 @@ const (
DefaultPrompt = "{by}{fw}{cidr} {fb}> {env.iface.ipv4} {reset} {bold}» {reset}"
)
var PromptEffects = map[string]string{
"{bold}": core.BOLD,
"{dim}": core.DIM,
"{r}": core.RED,
"{g}": core.GREEN,
"{b}": core.BLUE,
"{y}": core.YELLOW,
"{fb}": core.FG_BLACK,
"{fw}": core.FG_WHITE,
"{bdg}": core.BG_DGRAY,
"{br}": core.BG_RED,
"{bg}": core.BG_GREEN,
"{by}": core.BG_YELLOW,
"{blb}": core.BG_LBLUE, // Ziggy this is for you <3
"{reset}": core.RESET,
}
var PromptCallbacks = map[string]func(s *Session) string{
"{cidr}": func(s *Session) string {
return s.Interface.CIDR()
@ -71,7 +54,26 @@ func (p Prompt) Render(s *Session) string {
prompt = DefaultPrompt
}
for tok, effect := range PromptEffects {
// these are here because if colors are disabled,
// we need the updated core.* variables
var effects = map[string]string{
"{bold}": core.BOLD,
"{dim}": core.DIM,
"{r}": core.RED,
"{g}": core.GREEN,
"{b}": core.BLUE,
"{y}": core.YELLOW,
"{fb}": core.FG_BLACK,
"{fw}": core.FG_WHITE,
"{bdg}": core.BG_DGRAY,
"{br}": core.BG_RED,
"{bg}": core.BG_GREEN,
"{by}": core.BG_YELLOW,
"{blb}": core.BG_LBLUE, // Ziggy this is for you <3
"{reset}": core.RESET,
}
for tok, effect := range effects {
prompt = strings.Replace(prompt, tok, effect, -1)
}

View file

@ -138,6 +138,8 @@ func New() (*Session, error) {
return nil, err
}
core.InitSwag(*s.Options.NoColors)
if *s.Options.CpuProfile != "" {
if f, err := os.Create(*s.Options.CpuProfile); err != nil {
return nil, err