mirror of
https://github.com/bettercap/bettercap
synced 2025-08-21 05:53:20 -07:00
Refactoring modules
This commit is contained in:
parent
c0d3c314fc
commit
ed652622e2
89 changed files with 186 additions and 138 deletions
463
modules/wifi/wifi.go
Normal file
463
modules/wifi/wifi.go
Normal file
|
@ -0,0 +1,463 @@
|
|||
package wifi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/bettercap/bettercap/modules/utils"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bettercap/bettercap/log"
|
||||
"github.com/bettercap/bettercap/network"
|
||||
"github.com/bettercap/bettercap/packets"
|
||||
"github.com/bettercap/bettercap/session"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/google/gopacket/pcap"
|
||||
|
||||
"github.com/evilsocket/islazy/fs"
|
||||
"github.com/evilsocket/islazy/str"
|
||||
"github.com/evilsocket/islazy/tui"
|
||||
)
|
||||
|
||||
type WiFiModule struct {
|
||||
session.SessionModule
|
||||
|
||||
handle *pcap.Handle
|
||||
source string
|
||||
channel int
|
||||
hopPeriod time.Duration
|
||||
hopChanges chan bool
|
||||
frequencies []int
|
||||
ap *network.AccessPoint
|
||||
stickChan int
|
||||
skipBroken bool
|
||||
pktSourceChan chan gopacket.Packet
|
||||
pktSourceChanClosed bool
|
||||
deauthSkip []net.HardwareAddr
|
||||
deauthSilent bool
|
||||
deauthOpen bool
|
||||
assocSkip []net.HardwareAddr
|
||||
assocSilent bool
|
||||
assocOpen bool
|
||||
shakesFile string
|
||||
apRunning bool
|
||||
apConfig packets.Dot11ApConfig
|
||||
writes *sync.WaitGroup
|
||||
reads *sync.WaitGroup
|
||||
chanLock *sync.Mutex
|
||||
selector *utils.ViewSelector
|
||||
}
|
||||
|
||||
func NewWiFiModule(s *session.Session) *WiFiModule {
|
||||
w := &WiFiModule{
|
||||
SessionModule: session.NewSessionModule("wifi", s),
|
||||
channel: 0,
|
||||
stickChan: 0,
|
||||
hopPeriod: 250 * time.Millisecond,
|
||||
hopChanges: make(chan bool),
|
||||
ap: nil,
|
||||
skipBroken: true,
|
||||
apRunning: false,
|
||||
deauthSkip: []net.HardwareAddr{},
|
||||
deauthSilent: false,
|
||||
deauthOpen: false,
|
||||
assocSkip: []net.HardwareAddr{},
|
||||
assocSilent: false,
|
||||
assocOpen: false,
|
||||
writes: &sync.WaitGroup{},
|
||||
reads: &sync.WaitGroup{},
|
||||
chanLock: &sync.Mutex{},
|
||||
}
|
||||
|
||||
w.AddHandler(session.NewModuleHandler("wifi.recon on", "",
|
||||
"Start 802.11 wireless base stations discovery and channel hopping.",
|
||||
func(args []string) error {
|
||||
return w.Start()
|
||||
}))
|
||||
|
||||
w.AddHandler(session.NewModuleHandler("wifi.recon off", "",
|
||||
"Stop 802.11 wireless base stations discovery and channel hopping.",
|
||||
func(args []string) error {
|
||||
return w.Stop()
|
||||
}))
|
||||
|
||||
w.AddHandler(session.NewModuleHandler("wifi.recon MAC", "wifi.recon ((?:[0-9A-Fa-f]{2}[:-]){5}(?:[0-9A-Fa-f]{2}))",
|
||||
"Set 802.11 base station address to filter for.",
|
||||
func(args []string) error {
|
||||
bssid, err := net.ParseMAC(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
} else if ap, found := w.Session.WiFi.Get(bssid.String()); found {
|
||||
w.ap = ap
|
||||
w.stickChan = ap.Channel()
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Could not find station with BSSID %s", args[0])
|
||||
}))
|
||||
|
||||
w.AddHandler(session.NewModuleHandler("wifi.recon clear", "",
|
||||
"Remove the 802.11 base station filter.",
|
||||
func(args []string) (err error) {
|
||||
w.ap = nil
|
||||
w.stickChan = 0
|
||||
w.frequencies, err = network.GetSupportedFrequencies(w.Session.Interface.Name())
|
||||
w.hopChanges <- true
|
||||
return err
|
||||
}))
|
||||
|
||||
w.AddHandler(session.NewModuleHandler("wifi.deauth BSSID", `wifi\.deauth ((?:[a-fA-F0-9:]{11,})|all|\*)`,
|
||||
"Start a 802.11 deauth attack, if an access point BSSID is provided, every client will be deauthenticated, otherwise only the selected client. Use 'all', '*' or a broadcast BSSID (ff:ff:ff:ff:ff:ff) to iterate every access point with at least one client and start a deauth attack for each one.",
|
||||
func(args []string) error {
|
||||
if args[0] == "all" || args[0] == "*" {
|
||||
args[0] = "ff:ff:ff:ff:ff:ff"
|
||||
}
|
||||
bssid, err := net.ParseMAC(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return w.startDeauth(bssid)
|
||||
}))
|
||||
|
||||
w.AddParam(session.NewStringParameter("wifi.deauth.skip",
|
||||
"",
|
||||
"",
|
||||
"Comma separated list of BSSID to skip while sending deauth packets."))
|
||||
|
||||
w.AddParam(session.NewBoolParameter("wifi.deauth.silent",
|
||||
"false",
|
||||
"If true, messages from wifi.deauth will be suppressed."))
|
||||
|
||||
w.AddParam(session.NewBoolParameter("wifi.deauth.open",
|
||||
"true",
|
||||
"Send wifi deauth packets to open networks."))
|
||||
|
||||
w.AddHandler(session.NewModuleHandler("wifi.assoc BSSID", `wifi\.assoc ((?:[a-fA-F0-9:]{11,})|all|\*)`,
|
||||
"Send an association request to the selected BSSID in order to receive a RSN PMKID key. Use 'all', '*' or a broadcast BSSID (ff:ff:ff:ff:ff:ff) to iterate for every access point.",
|
||||
func(args []string) error {
|
||||
if args[0] == "all" || args[0] == "*" {
|
||||
args[0] = "ff:ff:ff:ff:ff:ff"
|
||||
}
|
||||
bssid, err := net.ParseMAC(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return w.startAssoc(bssid)
|
||||
}))
|
||||
|
||||
w.AddParam(session.NewStringParameter("wifi.assoc.skip",
|
||||
"",
|
||||
"",
|
||||
"Comma separated list of BSSID to skip while sending association requests."))
|
||||
|
||||
w.AddParam(session.NewBoolParameter("wifi.assoc.silent",
|
||||
"false",
|
||||
"If true, messages from wifi.assoc will be suppressed."))
|
||||
|
||||
w.AddParam(session.NewBoolParameter("wifi.assoc.open",
|
||||
"false",
|
||||
"Send association requests to open networks."))
|
||||
|
||||
w.AddHandler(session.NewModuleHandler("wifi.ap", "",
|
||||
"Inject fake management beacons in order to create a rogue access point.",
|
||||
func(args []string) error {
|
||||
if err := w.parseApConfig(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return w.startAp()
|
||||
}
|
||||
}))
|
||||
|
||||
w.AddParam(session.NewStringParameter("wifi.handshakes.file",
|
||||
"~/bettercap-wifi-handshakes.pcap",
|
||||
"",
|
||||
"File path of the pcap file to save handshakes to."))
|
||||
|
||||
w.AddParam(session.NewStringParameter("wifi.ap.ssid",
|
||||
"FreeWiFi",
|
||||
"",
|
||||
"SSID of the fake access point."))
|
||||
|
||||
w.AddParam(session.NewStringParameter("wifi.ap.bssid",
|
||||
session.ParamRandomMAC,
|
||||
"[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}",
|
||||
"BSSID of the fake access point."))
|
||||
|
||||
w.AddParam(session.NewIntParameter("wifi.ap.channel",
|
||||
"1",
|
||||
"Channel of the fake access point."))
|
||||
|
||||
w.AddParam(session.NewBoolParameter("wifi.ap.encryption",
|
||||
"true",
|
||||
"If true, the fake access point will use WPA2, otherwise it'll result as an open AP."))
|
||||
|
||||
w.AddHandler(session.NewModuleHandler("wifi.show.wps BSSID",
|
||||
`wifi\.show\.wps ((?:[a-fA-F0-9:]{11,})|all|\*)`,
|
||||
"Show WPS information about a given station (use 'all', '*' or a broadcast BSSID for all).",
|
||||
func(args []string) error {
|
||||
if args[0] == "all" || args[0] == "*" {
|
||||
args[0] = "ff:ff:ff:ff:ff:ff"
|
||||
}
|
||||
return w.ShowWPS(args[0])
|
||||
}))
|
||||
|
||||
w.AddHandler(session.NewModuleHandler("wifi.show", "",
|
||||
"Show current wireless stations list (default sorting by essid).",
|
||||
func(args []string) error {
|
||||
return w.Show()
|
||||
}))
|
||||
|
||||
w.selector = utils.ViewSelectorFor(&w.SessionModule, "wifi.show",
|
||||
[]string{"rssi", "bssid", "essid", "channel", "encryption", "clients", "seen", "sent", "rcvd"}, "rssi asc")
|
||||
|
||||
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) (err error) {
|
||||
freqs := []int{}
|
||||
|
||||
if args[0] != "clear" {
|
||||
log.Debug("setting hopping channels to %s", args[0])
|
||||
for _, s := range str.Comma(args[0]) {
|
||||
if ch, err := strconv.Atoi(s); err != nil {
|
||||
return err
|
||||
} else {
|
||||
freqs = append(freqs, network.Dot11Chan2Freq(ch))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(freqs) == 0 {
|
||||
log.Debug("resetting hopping channels")
|
||||
if freqs, err = network.GetSupportedFrequencies(w.Session.Interface.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
w.frequencies = freqs
|
||||
w.hopChanges <- true
|
||||
|
||||
return nil
|
||||
}))
|
||||
|
||||
w.AddParam(session.NewStringParameter("wifi.source.file",
|
||||
"",
|
||||
"",
|
||||
"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 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",
|
||||
"If true, dot11 packets with an invalid checksum will be skipped."))
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
func (w WiFiModule) Name() string {
|
||||
return "wifi"
|
||||
}
|
||||
|
||||
func (w WiFiModule) Description() string {
|
||||
return "A module to monitor and perform wireless attacks on 802.11."
|
||||
}
|
||||
|
||||
func (w WiFiModule) Author() string {
|
||||
return "Simone Margaritelli <evilsocket@protonmail.com> && Gianluca Braga <matrix86@protonmail.com>"
|
||||
}
|
||||
|
||||
const (
|
||||
// Ugly, but gopacket folks are not exporting pcap errors, so ...
|
||||
// ref. https://github.com/google/gopacket/blob/96986c90e3e5c7e01deed713ff8058e357c0c047/pcap/pcap.go#L281
|
||||
ErrIfaceNotUp = "Interface Not Up"
|
||||
)
|
||||
|
||||
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 err, w.shakesFile = w.StringParam("wifi.handshakes.file"); err != nil {
|
||||
return err
|
||||
} else if w.shakesFile != "" {
|
||||
if w.shakesFile, err = fs.Expand(w.shakesFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ifName := w.Session.Interface.Name()
|
||||
|
||||
if w.source != "" {
|
||||
if w.handle, err = pcap.OpenOffline(w.source); err != nil {
|
||||
return fmt.Errorf("error while opening file %s: %s", w.source, err)
|
||||
}
|
||||
} else {
|
||||
for retry := 0; ; retry++ {
|
||||
ihandle, err := pcap.NewInactiveHandle(ifName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while opening interface %s: %s", ifName, err)
|
||||
}
|
||||
defer ihandle.CleanUp()
|
||||
|
||||
if err = ihandle.SetRFMon(true); err != nil {
|
||||
return fmt.Errorf("error while setting interface %s in monitor mode: %s", tui.Bold(ifName), err)
|
||||
} else if err = ihandle.SetSnapLen(65536); err != nil {
|
||||
return fmt.Errorf("error while settng span len: %s", err)
|
||||
}
|
||||
/*
|
||||
* We don't want to pcap.BlockForever otherwise pcap_close(handle)
|
||||
* could hang waiting for a timeout to expire ...
|
||||
*/
|
||||
readTimeout := 500 * time.Millisecond
|
||||
if err = ihandle.SetTimeout(readTimeout); err != nil {
|
||||
return fmt.Errorf("error while setting timeout: %s", err)
|
||||
} else if w.handle, err = ihandle.Activate(); err != nil {
|
||||
if retry == 0 && err.Error() == ErrIfaceNotUp {
|
||||
log.Warning("interface %s is down, bringing it up ...", ifName)
|
||||
if err := network.ActivateInterface(ifName); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("error while activating handle: %s", err)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err, w.skipBroken = w.BoolParam("wifi.skip-broken"); err != nil {
|
||||
return err
|
||||
} else if err, hopPeriod = w.IntParam("wifi.hop.period"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.hopPeriod = time.Duration(hopPeriod) * time.Millisecond
|
||||
|
||||
if w.source == "" {
|
||||
// No channels setted, retrieve frequencies supported by the card
|
||||
if len(w.frequencies) == 0 {
|
||||
if w.frequencies, err = network.GetSupportedFrequencies(ifName); err != nil {
|
||||
return fmt.Errorf("error while getting supported frequencies of %s: %s", ifName, err)
|
||||
}
|
||||
|
||||
log.Debug("wifi supported frequencies: %v", w.frequencies)
|
||||
|
||||
// we need to start somewhere, this is just to check if
|
||||
// this OS supports switching channel programmatically.
|
||||
if err = network.SetInterfaceChannel(ifName, 1); err != nil {
|
||||
return fmt.Errorf("error while initializing %s to channel 1: %s", ifName, err)
|
||||
}
|
||||
log.Info("WiFi recon active with channel hopping.")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WiFiModule) updateInfo(dot11 *layers.Dot11, packet gopacket.Packet) {
|
||||
if ok, enc, cipher, auth := packets.Dot11ParseEncryption(packet, dot11); ok {
|
||||
bssid := dot11.Address3.String()
|
||||
if station, found := w.Session.WiFi.Get(bssid); found {
|
||||
station.Encryption = enc
|
||||
station.Cipher = cipher
|
||||
station.Authentication = auth
|
||||
}
|
||||
}
|
||||
|
||||
if ok, bssid, info := packets.Dot11ParseWPS(packet, dot11); ok {
|
||||
if station, found := w.Session.WiFi.Get(bssid.String()); found {
|
||||
for name, value := range info {
|
||||
station.WPS[name] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WiFiModule) updateStats(dot11 *layers.Dot11, packet gopacket.Packet) {
|
||||
// collect stats from data frames
|
||||
if dot11.Type.MainType() == layers.Dot11TypeData {
|
||||
bytes := uint64(len(packet.Data()))
|
||||
|
||||
dst := dot11.Address1.String()
|
||||
if station, found := w.Session.WiFi.Get(dst); found {
|
||||
station.Received += bytes
|
||||
}
|
||||
|
||||
src := dot11.Address2.String()
|
||||
if station, found := w.Session.WiFi.Get(src); found {
|
||||
station.Sent += bytes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WiFiModule) Start() error {
|
||||
if err := w.Configure(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.SetRunning(true, func() {
|
||||
// start channel hopper if needed
|
||||
if w.channel == 0 && w.source == "" {
|
||||
go w.channelHopper()
|
||||
}
|
||||
|
||||
// start the pruner
|
||||
go w.stationPruner()
|
||||
|
||||
w.reads.Add(1)
|
||||
defer w.reads.Done()
|
||||
|
||||
src := gopacket.NewPacketSource(w.handle, w.handle.LinkType())
|
||||
w.pktSourceChan = src.Packets()
|
||||
for packet := range w.pktSourceChan {
|
||||
if !w.Running() {
|
||||
break
|
||||
} else if packet == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
w.Session.Queue.TrackPacket(uint64(len(packet.Data())))
|
||||
|
||||
// perform initial dot11 parsing and layers validation
|
||||
if ok, radiotap, dot11 := packets.Dot11Parse(packet); ok {
|
||||
// check FCS checksum
|
||||
if w.skipBroken && !dot11.ChecksumValid() {
|
||||
log.Debug("Skipping dot11 packet with invalid checksum.")
|
||||
continue
|
||||
}
|
||||
|
||||
w.discoverProbes(radiotap, dot11, packet)
|
||||
w.discoverAccessPoints(radiotap, dot11, packet)
|
||||
w.discoverClients(radiotap, dot11, packet)
|
||||
w.discoverHandshakes(radiotap, dot11, packet)
|
||||
w.updateInfo(dot11, packet)
|
||||
w.updateStats(dot11, packet)
|
||||
}
|
||||
}
|
||||
|
||||
w.pktSourceChanClosed = true
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WiFiModule) Stop() error {
|
||||
return w.SetRunning(false, func() {
|
||||
// wait any pending write operation
|
||||
w.writes.Wait()
|
||||
// signal the main for loop we want to exit
|
||||
if !w.pktSourceChanClosed {
|
||||
w.pktSourceChan <- nil
|
||||
}
|
||||
w.reads.Wait()
|
||||
// close the pcap handle to make the main for exit
|
||||
w.handle.Close()
|
||||
})
|
||||
}
|
73
modules/wifi/wifi_ap.go
Normal file
73
modules/wifi/wifi_ap.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
package wifi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/bettercap/bettercap/log"
|
||||
"github.com/bettercap/bettercap/network"
|
||||
"github.com/bettercap/bettercap/packets"
|
||||
"github.com/bettercap/bettercap/session"
|
||||
|
||||
"github.com/evilsocket/islazy/tui"
|
||||
)
|
||||
|
||||
var errNoRecon = errors.New("Module wifi.ap requires module wifi.recon to be activated.")
|
||||
|
||||
func (w *WiFiModule) parseApConfig() (err error) {
|
||||
var bssid string
|
||||
if err, w.apConfig.SSID = w.StringParam("wifi.ap.ssid"); err != nil {
|
||||
return
|
||||
} else if err, bssid = w.StringParam("wifi.ap.bssid"); err != nil {
|
||||
return
|
||||
} else if w.apConfig.BSSID, err = net.ParseMAC(network.NormalizeMac(bssid)); err != nil {
|
||||
return
|
||||
} else if err, w.apConfig.Channel = w.IntParam("wifi.ap.channel"); err != nil {
|
||||
return
|
||||
} else if err, w.apConfig.Encryption = w.BoolParam("wifi.ap.encryption"); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (w *WiFiModule) startAp() error {
|
||||
// we need channel hopping and packet injection for this
|
||||
if !w.Running() {
|
||||
return errNoRecon
|
||||
} else if w.apRunning {
|
||||
return session.ErrAlreadyStarted
|
||||
}
|
||||
|
||||
go func() {
|
||||
w.apRunning = true
|
||||
defer func() {
|
||||
w.apRunning = false
|
||||
}()
|
||||
|
||||
enc := tui.Yellow("WPA2")
|
||||
if !w.apConfig.Encryption {
|
||||
enc = tui.Green("Open")
|
||||
}
|
||||
log.Info("Sending beacons as SSID %s (%s) on channel %d (%s).",
|
||||
tui.Bold(w.apConfig.SSID),
|
||||
w.apConfig.BSSID.String(),
|
||||
w.apConfig.Channel,
|
||||
enc)
|
||||
|
||||
for seqn := uint16(0); w.Running(); seqn++ {
|
||||
w.writes.Add(1)
|
||||
defer w.writes.Done()
|
||||
|
||||
if err, pkt := packets.NewDot11Beacon(w.apConfig, seqn); err != nil {
|
||||
log.Error("Could not create beacon packet: %s", err)
|
||||
} else {
|
||||
w.injectPacket(pkt)
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
126
modules/wifi/wifi_assoc.go
Normal file
126
modules/wifi/wifi_assoc.go
Normal file
|
@ -0,0 +1,126 @@
|
|||
package wifi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
|
||||
"github.com/bettercap/bettercap/log"
|
||||
"github.com/bettercap/bettercap/network"
|
||||
"github.com/bettercap/bettercap/packets"
|
||||
)
|
||||
|
||||
func (w *WiFiModule) sendAssocPacket(ap *network.AccessPoint) {
|
||||
if err, pkt := packets.NewDot11Auth(w.Session.Interface.HW, ap.HW, 1); err != nil {
|
||||
log.Error("cloud not create auth packet: %s", err)
|
||||
} else {
|
||||
w.injectPacket(pkt)
|
||||
}
|
||||
|
||||
if err, pkt := packets.NewDot11AssociationRequest(w.Session.Interface.HW, ap.HW, ap.ESSID(), 1); err != nil {
|
||||
log.Error("cloud not create association request packet: %s", err)
|
||||
} else {
|
||||
w.injectPacket(pkt)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WiFiModule) skipAssoc(to net.HardwareAddr) bool {
|
||||
for _, mac := range w.assocSkip {
|
||||
if bytes.Equal(to, mac) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *WiFiModule) isAssocSilent() bool {
|
||||
if err, is := w.BoolParam("wifi.assoc.silent"); err != nil {
|
||||
log.Warning("%v", err)
|
||||
} else {
|
||||
w.assocSilent = is
|
||||
}
|
||||
return w.assocSilent
|
||||
}
|
||||
|
||||
func (w *WiFiModule) doAssocOpen() bool {
|
||||
if err, is := w.BoolParam("wifi.assoc.open"); err != nil {
|
||||
log.Warning("%v", err)
|
||||
} else {
|
||||
w.assocOpen = is
|
||||
}
|
||||
return w.assocOpen
|
||||
}
|
||||
|
||||
func (w *WiFiModule) startAssoc(to net.HardwareAddr) error {
|
||||
// parse skip list
|
||||
if err, assocSkip := w.StringParam("wifi.assoc.skip"); err != nil {
|
||||
return err
|
||||
} else if macs, err := network.ParseMACs(assocSkip); err != nil {
|
||||
return err
|
||||
} else {
|
||||
w.assocSkip = macs
|
||||
}
|
||||
|
||||
// if not already running, temporarily enable the pcap handle
|
||||
// for packet injection
|
||||
if !w.Running() {
|
||||
if err := w.Configure(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer w.handle.Close()
|
||||
}
|
||||
|
||||
toAssoc := make([]*network.AccessPoint, 0)
|
||||
isBcast := network.IsBroadcastMac(to)
|
||||
for _, ap := range w.Session.WiFi.List() {
|
||||
if isBcast || bytes.Equal(ap.HW, to) {
|
||||
if !w.skipAssoc(ap.HW) {
|
||||
toAssoc = append(toAssoc, ap)
|
||||
} else {
|
||||
log.Debug("skipping ap:%v because skip list %v", ap, w.assocSkip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(toAssoc) == 0 {
|
||||
if isBcast {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%s is an unknown BSSID or it is in the association skip list.", to.String())
|
||||
}
|
||||
|
||||
go func() {
|
||||
w.writes.Add(1)
|
||||
defer w.writes.Done()
|
||||
|
||||
// since we need to change the wifi adapter channel for each
|
||||
// association request, let's sort by channel so we do the minimum
|
||||
// amount of hops possible
|
||||
sort.Slice(toAssoc, func(i, j int) bool {
|
||||
return toAssoc[i].Channel() < toAssoc[j].Channel()
|
||||
})
|
||||
|
||||
// send the association request frames
|
||||
for _, ap := range toAssoc {
|
||||
if w.Running() {
|
||||
logger := log.Info
|
||||
if w.isAssocSilent() {
|
||||
logger = log.Debug
|
||||
}
|
||||
|
||||
if ap.IsOpen() && !w.doAssocOpen() {
|
||||
log.Debug("skipping association for open network %s (wifi.assoc.open is false)", ap.ESSID())
|
||||
} else {
|
||||
logger("sending association request to AP %s (channel:%d encryption:%s)", ap.ESSID(), ap.Channel(), ap.Encryption)
|
||||
|
||||
w.onChannel(ap.Channel(), func() {
|
||||
w.sendAssocPacket(ap)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
152
modules/wifi/wifi_deauth.go
Normal file
152
modules/wifi/wifi_deauth.go
Normal file
|
@ -0,0 +1,152 @@
|
|||
package wifi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/bettercap/bettercap/log"
|
||||
"github.com/bettercap/bettercap/network"
|
||||
"github.com/bettercap/bettercap/packets"
|
||||
)
|
||||
|
||||
func (w *WiFiModule) injectPacket(data []byte) {
|
||||
if err := w.handle.WritePacketData(data); err != nil {
|
||||
log.Error("cloud not inject WiFi packet: %s", err)
|
||||
w.Session.Queue.TrackError()
|
||||
} else {
|
||||
w.Session.Queue.TrackSent(uint64(len(data)))
|
||||
}
|
||||
// let the network card breath a little
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
func (w *WiFiModule) sendDeauthPacket(ap net.HardwareAddr, client net.HardwareAddr) {
|
||||
for seq := uint16(0); seq < 64 && w.Running(); seq++ {
|
||||
if err, pkt := packets.NewDot11Deauth(ap, client, ap, seq); err != nil {
|
||||
log.Error("cloud not create deauth packet: %s", err)
|
||||
continue
|
||||
} else {
|
||||
w.injectPacket(pkt)
|
||||
}
|
||||
|
||||
if err, pkt := packets.NewDot11Deauth(client, ap, ap, seq); err != nil {
|
||||
log.Error("cloud not create deauth packet: %s", err)
|
||||
continue
|
||||
} else {
|
||||
w.injectPacket(pkt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WiFiModule) skipDeauth(to net.HardwareAddr) bool {
|
||||
for _, mac := range w.deauthSkip {
|
||||
if bytes.Equal(to, mac) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *WiFiModule) isDeauthSilent() bool {
|
||||
if err, is := w.BoolParam("wifi.deauth.silent"); err != nil {
|
||||
log.Warning("%v", err)
|
||||
} else {
|
||||
w.deauthSilent = is
|
||||
}
|
||||
return w.deauthSilent
|
||||
}
|
||||
|
||||
func (w *WiFiModule) doDeauthOpen() bool {
|
||||
if err, is := w.BoolParam("wifi.deauth.open"); err != nil {
|
||||
log.Warning("%v", err)
|
||||
} else {
|
||||
w.deauthOpen = is
|
||||
}
|
||||
return w.deauthOpen
|
||||
}
|
||||
|
||||
func (w *WiFiModule) startDeauth(to net.HardwareAddr) error {
|
||||
// parse skip list
|
||||
if err, deauthSkip := w.StringParam("wifi.deauth.skip"); err != nil {
|
||||
return err
|
||||
} else if macs, err := network.ParseMACs(deauthSkip); err != nil {
|
||||
return err
|
||||
} else {
|
||||
w.deauthSkip = macs
|
||||
}
|
||||
|
||||
// if not already running, temporarily enable the pcap handle
|
||||
// for packet injection
|
||||
if !w.Running() {
|
||||
if err := w.Configure(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer w.handle.Close()
|
||||
}
|
||||
|
||||
type flow struct {
|
||||
Ap *network.AccessPoint
|
||||
Client *network.Station
|
||||
}
|
||||
|
||||
toDeauth := make([]flow, 0)
|
||||
isBcast := network.IsBroadcastMac(to)
|
||||
for _, ap := range w.Session.WiFi.List() {
|
||||
isAP := bytes.Equal(ap.HW, to)
|
||||
for _, client := range ap.Clients() {
|
||||
if isBcast || isAP || bytes.Equal(client.HW, to) {
|
||||
if !w.skipDeauth(ap.HW) && !w.skipDeauth(client.HW) {
|
||||
toDeauth = append(toDeauth, flow{Ap: ap, Client: client})
|
||||
} else {
|
||||
log.Debug("skipping ap:%v client:%v because skip list %v", ap, client, w.deauthSkip)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(toDeauth) == 0 {
|
||||
if isBcast {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%s is an unknown BSSID, is in the deauth skip list, or doesn't have detected clients.", to.String())
|
||||
}
|
||||
|
||||
go func() {
|
||||
w.writes.Add(1)
|
||||
defer w.writes.Done()
|
||||
|
||||
// since we need to change the wifi adapter channel for each
|
||||
// deauth packet, let's sort by channel so we do the minimum
|
||||
// amount of hops possible
|
||||
sort.Slice(toDeauth, func(i, j int) bool {
|
||||
return toDeauth[i].Ap.Channel() < toDeauth[j].Ap.Channel()
|
||||
})
|
||||
|
||||
// send the deauth frames
|
||||
for _, deauth := range toDeauth {
|
||||
client := deauth.Client
|
||||
ap := deauth.Ap
|
||||
if w.Running() {
|
||||
logger := log.Info
|
||||
if w.isDeauthSilent() {
|
||||
logger = log.Debug
|
||||
}
|
||||
|
||||
if ap.IsOpen() && !w.doDeauthOpen() {
|
||||
log.Debug("skipping deauth for open network %s (wifi.deauth.open is false)", ap.ESSID())
|
||||
} else {
|
||||
logger("deauthing client %s from AP %s (channel:%d encryption:%s)", client.String(), ap.ESSID(), ap.Channel(), ap.Encryption)
|
||||
|
||||
w.onChannel(ap.Channel(), func() {
|
||||
w.sendDeauthPacket(ap.HW, client.HW)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
28
modules/wifi/wifi_events.go
Normal file
28
modules/wifi/wifi_events.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package wifi
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/bettercap/bettercap/network"
|
||||
)
|
||||
|
||||
type WiFiClientEvent struct {
|
||||
AP *network.AccessPoint
|
||||
Client *network.Station
|
||||
}
|
||||
|
||||
type WiFiProbeEvent struct {
|
||||
FromAddr net.HardwareAddr
|
||||
FromVendor string
|
||||
FromAlias string
|
||||
SSID string
|
||||
RSSI int8
|
||||
}
|
||||
|
||||
type WiFiHandshakeEvent struct {
|
||||
File string
|
||||
NewPackets int
|
||||
AP net.HardwareAddr
|
||||
Station net.HardwareAddr
|
||||
PMKID []byte
|
||||
}
|
73
modules/wifi/wifi_hopping.go
Normal file
73
modules/wifi/wifi_hopping.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
package wifi
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/bettercap/bettercap/log"
|
||||
"github.com/bettercap/bettercap/network"
|
||||
)
|
||||
|
||||
func (w *WiFiModule) onChannel(channel int, cb func()) {
|
||||
w.chanLock.Lock()
|
||||
defer w.chanLock.Unlock()
|
||||
|
||||
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() {
|
||||
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
|
||||
|
||||
loopCurrentChannels:
|
||||
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
|
||||
}
|
||||
|
||||
log.Debug("hopping on channel %d", channel)
|
||||
|
||||
w.chanLock.Lock()
|
||||
if err := network.SetInterfaceChannel(w.Session.Interface.Name(), channel); err != nil {
|
||||
log.Warning("error while hopping to channel %d: %s", channel, err)
|
||||
}
|
||||
w.chanLock.Unlock()
|
||||
|
||||
select {
|
||||
case _ = <-w.hopChanges:
|
||||
log.Debug("hop changed")
|
||||
break loopCurrentChannels
|
||||
case <-time.After(delay):
|
||||
if !w.Running() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
244
modules/wifi/wifi_recon.go
Normal file
244
modules/wifi/wifi_recon.go
Normal file
|
@ -0,0 +1,244 @@
|
|||
package wifi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/bettercap/bettercap/log"
|
||||
"github.com/bettercap/bettercap/network"
|
||||
"github.com/bettercap/bettercap/packets"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
|
||||
"github.com/evilsocket/islazy/tui"
|
||||
)
|
||||
|
||||
var maxStationTTL = 5 * time.Minute
|
||||
|
||||
func (w *WiFiModule) stationPruner() {
|
||||
w.reads.Add(1)
|
||||
defer w.reads.Done()
|
||||
|
||||
log.Debug("wifi stations pruner started.")
|
||||
for w.Running() {
|
||||
// loop every AP
|
||||
for _, ap := range w.Session.WiFi.List() {
|
||||
sinceLastSeen := time.Since(ap.LastSeen)
|
||||
if sinceLastSeen > maxStationTTL {
|
||||
log.Debug("station %s not seen in %s, removing.", ap.BSSID(), sinceLastSeen)
|
||||
w.Session.WiFi.Remove(ap.BSSID())
|
||||
continue
|
||||
}
|
||||
// loop every AP client
|
||||
for _, c := range ap.Clients() {
|
||||
sinceLastSeen := time.Since(c.LastSeen)
|
||||
if sinceLastSeen > maxStationTTL {
|
||||
log.Debug("client %s of station %s not seen in %s, removing.", c.String(), ap.BSSID(), sinceLastSeen)
|
||||
ap.RemoveClient(c.BSSID())
|
||||
|
||||
w.Session.Events.Add("wifi.client.lost", WiFiClientEvent{
|
||||
AP: ap,
|
||||
Client: c,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WiFiModule) discoverAccessPoints(radiotap *layers.RadioTap, dot11 *layers.Dot11, packet gopacket.Packet) {
|
||||
// search for Dot11InformationElementIDSSID
|
||||
if ok, ssid := packets.Dot11ParseIDSSID(packet); ok {
|
||||
from := dot11.Address3
|
||||
|
||||
// skip stuff we're sending
|
||||
if w.apRunning && bytes.Equal(from, w.apConfig.BSSID) {
|
||||
return
|
||||
}
|
||||
|
||||
if !network.IsZeroMac(from) && !network.IsBroadcastMac(from) {
|
||||
var frequency int
|
||||
bssid := from.String()
|
||||
|
||||
if found, channel := packets.Dot11ParseDSSet(packet); found {
|
||||
frequency = network.Dot11Chan2Freq(channel)
|
||||
} else {
|
||||
frequency = int(radiotap.ChannelFrequency)
|
||||
}
|
||||
|
||||
if ap, isNew := w.Session.WiFi.AddIfNew(ssid, bssid, frequency, radiotap.DBMAntennaSignal); !isNew {
|
||||
ap.EachClient(func(mac string, station *network.Station) {
|
||||
station.Handshake.SetBeacon(packet)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WiFiModule) discoverProbes(radiotap *layers.RadioTap, dot11 *layers.Dot11, packet gopacket.Packet) {
|
||||
if dot11.Type != layers.Dot11TypeMgmtProbeReq {
|
||||
return
|
||||
}
|
||||
|
||||
reqLayer := packet.Layer(layers.LayerTypeDot11MgmtProbeReq)
|
||||
if reqLayer == nil {
|
||||
return
|
||||
}
|
||||
|
||||
req, ok := reqLayer.(*layers.Dot11MgmtProbeReq)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
tot := len(req.Contents)
|
||||
if tot < 3 {
|
||||
return
|
||||
}
|
||||
|
||||
avail := uint32(tot - 2)
|
||||
if avail < 2 {
|
||||
return
|
||||
}
|
||||
size := uint32(req.Contents[1])
|
||||
if size == 0 || size > avail {
|
||||
return
|
||||
}
|
||||
|
||||
w.Session.Events.Add("wifi.client.probe", WiFiProbeEvent{
|
||||
FromAddr: dot11.Address2,
|
||||
FromVendor: network.ManufLookup(dot11.Address2.String()),
|
||||
FromAlias: w.Session.Lan.GetAlias(dot11.Address2.String()),
|
||||
SSID: string(req.Contents[2 : 2+size]),
|
||||
RSSI: radiotap.DBMAntennaSignal,
|
||||
})
|
||||
}
|
||||
|
||||
func (w *WiFiModule) discoverClients(radiotap *layers.RadioTap, dot11 *layers.Dot11, packet gopacket.Packet) {
|
||||
w.Session.WiFi.EachAccessPoint(func(bssid string, ap *network.AccessPoint) {
|
||||
// packet going to this specific BSSID?
|
||||
if packets.Dot11IsDataFor(dot11, ap.HW) {
|
||||
bssid := dot11.Address2.String()
|
||||
freq := int(radiotap.ChannelFrequency)
|
||||
rssi := radiotap.DBMAntennaSignal
|
||||
|
||||
if station, isNew := ap.AddClientIfNew(bssid, freq, rssi); isNew {
|
||||
w.Session.Events.Add("wifi.client.new", WiFiClientEvent{
|
||||
AP: ap,
|
||||
Client: station,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func allZeros(s []byte) bool {
|
||||
for _, v := range s {
|
||||
if v != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *WiFiModule) discoverHandshakes(radiotap *layers.RadioTap, dot11 *layers.Dot11, packet gopacket.Packet) {
|
||||
// ref. https://wlan1nde.wordpress.com/2014/10/27/4-way-handshake/
|
||||
if keyLayer := packet.Layer(layers.LayerTypeEAPOLKey); keyLayer != nil {
|
||||
if key := keyLayer.(*layers.EAPOLKey); key.KeyType == layers.EAPOLKeyTypePairwise {
|
||||
staMac := net.HardwareAddr{}
|
||||
apMac := net.HardwareAddr{}
|
||||
if dot11.Flags.FromDS() {
|
||||
staMac = dot11.Address1
|
||||
apMac = dot11.Address2
|
||||
} else if dot11.Flags.ToDS() {
|
||||
staMac = dot11.Address2
|
||||
apMac = dot11.Address1
|
||||
}
|
||||
|
||||
// first, locate the AP in our list by its BSSID
|
||||
ap, found := w.Session.WiFi.Get(apMac.String())
|
||||
if !found {
|
||||
log.Warning("could not find AP with BSSID %s", apMac.String())
|
||||
return
|
||||
}
|
||||
|
||||
// locate the client station, if its BSSID is ours, it means we sent
|
||||
// an association request via wifi.assoc because we're trying to capture
|
||||
// the PMKID from the first EAPOL sent by the AP.
|
||||
// (Reference about PMKID https://hashcat.net/forum/thread-7717.html)
|
||||
// In this case, we need to add ourselves as a client station of the AP
|
||||
// in order to have a consistent association of AP, client and handshakes.
|
||||
staIsUs := bytes.Equal(staMac, w.Session.Interface.HW)
|
||||
station, found := ap.Get(staMac.String())
|
||||
if !found {
|
||||
station, _ = ap.AddClientIfNew(staMac.String(), ap.Frequency, ap.RSSI)
|
||||
}
|
||||
|
||||
rawPMKID := []byte(nil)
|
||||
if !key.Install && key.KeyACK && !key.KeyMIC {
|
||||
// [1] (ACK) AP is sending ANonce to the client
|
||||
rawPMKID = station.Handshake.AddAndGetPMKID(packet)
|
||||
PMKID := "without PMKID"
|
||||
if rawPMKID != nil {
|
||||
PMKID = "with PMKID"
|
||||
}
|
||||
|
||||
log.Debug("[%s] got frame 1/4 of the %s <-> %s handshake (%s) (anonce:%x)",
|
||||
tui.Green("wifi"),
|
||||
apMac,
|
||||
staMac,
|
||||
PMKID,
|
||||
key.Nonce)
|
||||
} else if !key.Install && !key.KeyACK && key.KeyMIC && !allZeros(key.Nonce) {
|
||||
// [2] (MIC) client is sending SNonce+MIC to the API
|
||||
station.Handshake.AddFrame(1, packet)
|
||||
|
||||
log.Debug("[%s] got frame 2/4 of the %s <-> %s handshake (snonce:%x mic:%x)",
|
||||
tui.Green("wifi"),
|
||||
apMac,
|
||||
staMac,
|
||||
key.Nonce,
|
||||
key.MIC)
|
||||
} else if key.Install && key.KeyACK && key.KeyMIC {
|
||||
// [3]: (INSTALL+ACK+MIC) AP informs the client that the PTK is installed
|
||||
station.Handshake.AddFrame(2, packet)
|
||||
|
||||
log.Debug("[%s] got frame 3/4 of the %s <-> %s handshake (mic:%x)",
|
||||
tui.Green("wifi"),
|
||||
apMac,
|
||||
staMac,
|
||||
key.MIC)
|
||||
}
|
||||
|
||||
// if we have unsaved packets as part of the handshake, save them.
|
||||
numUnsaved := station.Handshake.NumUnsaved()
|
||||
doSave := numUnsaved > 0
|
||||
if doSave && w.shakesFile != "" {
|
||||
log.Debug("saving handshake frames to %s", w.shakesFile)
|
||||
if err := w.Session.WiFi.SaveHandshakesTo(w.shakesFile, w.handle.LinkType()); err != nil {
|
||||
log.Error("error while saving handshake frames to %s: %s", w.shakesFile, err)
|
||||
}
|
||||
}
|
||||
|
||||
// if we had unsaved packets and either the handshake is complete
|
||||
// or it contains the PMKID, generate a new event.
|
||||
if doSave && (rawPMKID != nil || station.Handshake.Complete()) {
|
||||
w.Session.Events.Add("wifi.client.handshake", WiFiHandshakeEvent{
|
||||
File: w.shakesFile,
|
||||
NewPackets: numUnsaved,
|
||||
AP: apMac,
|
||||
Station: staMac,
|
||||
PMKID: rawPMKID,
|
||||
})
|
||||
}
|
||||
|
||||
// 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 {
|
||||
ap.RemoveClient(staMac.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
354
modules/wifi/wifi_show.go
Normal file
354
modules/wifi/wifi_show.go
Normal file
|
@ -0,0 +1,354 @@
|
|||
package wifi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/bettercap/bettercap/modules/discovery"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bettercap/bettercap/network"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
|
||||
"github.com/evilsocket/islazy/ops"
|
||||
"github.com/evilsocket/islazy/tui"
|
||||
)
|
||||
|
||||
func (w *WiFiModule) isApSelected() bool {
|
||||
return w.ap != nil
|
||||
}
|
||||
|
||||
func (w *WiFiModule) getRow(station *network.Station) ([]string, bool) {
|
||||
include := false
|
||||
sinceStarted := time.Since(w.Session.StartedAt)
|
||||
sinceFirstSeen := time.Since(station.FirstSeen)
|
||||
|
||||
bssid := station.HwAddress
|
||||
if sinceStarted > (discovery.JustJoinedTimeInterval*2) && sinceFirstSeen <= discovery.JustJoinedTimeInterval {
|
||||
// if endpoint was first seen in the last 10 seconds
|
||||
bssid = tui.Bold(bssid)
|
||||
}
|
||||
|
||||
seen := station.LastSeen.Format("15:04:05")
|
||||
sinceLastSeen := time.Since(station.LastSeen)
|
||||
if sinceStarted > discovery.AliveTimeInterval && sinceLastSeen <= discovery.AliveTimeInterval {
|
||||
// if endpoint seen in the last 10 seconds
|
||||
seen = tui.Bold(seen)
|
||||
} else if sinceLastSeen > discovery.PresentTimeInterval {
|
||||
// if endpoint not seen in the last 60 seconds
|
||||
seen = tui.Dim(seen)
|
||||
}
|
||||
|
||||
ssid := ops.Ternary(station.ESSID() == "<hidden>", tui.Dim(station.ESSID()), station.ESSID()).(string)
|
||||
|
||||
encryption := station.Encryption
|
||||
if len(station.Cipher) > 0 {
|
||||
encryption = fmt.Sprintf("%s (%s, %s)", station.Encryption, station.Cipher, station.Authentication)
|
||||
}
|
||||
|
||||
if encryption == "OPEN" || encryption == "" {
|
||||
encryption = tui.Green("OPEN")
|
||||
ssid = tui.Green(ssid)
|
||||
bssid = tui.Green(bssid)
|
||||
} else {
|
||||
// this is ugly, but necessary in order to have this
|
||||
// method handle both access point and clients
|
||||
// transparently
|
||||
if ap, found := w.Session.WiFi.Get(station.HwAddress); found && (ap.HasHandshakes() || ap.HasPMKID()) {
|
||||
encryption = tui.Red(encryption)
|
||||
}
|
||||
}
|
||||
|
||||
sent := ops.Ternary(station.Sent > 0, humanize.Bytes(station.Sent), "").(string)
|
||||
recvd := ops.Ternary(station.Received > 0, humanize.Bytes(station.Received), "").(string)
|
||||
|
||||
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,
|
||||
strconv.Itoa(station.Channel()),
|
||||
sent,
|
||||
recvd,
|
||||
seen,
|
||||
}, include
|
||||
} else {
|
||||
// this is ugly, but necessary in order to have this
|
||||
// method handle both access point and clients
|
||||
// transparently
|
||||
clients := ""
|
||||
if ap, found := w.Session.WiFi.Get(station.HwAddress); found {
|
||||
if ap.NumClients() > 0 {
|
||||
clients = strconv.Itoa(ap.NumClients())
|
||||
}
|
||||
}
|
||||
|
||||
wps := ""
|
||||
if station.HasWPS() {
|
||||
if ver, found := station.WPS["Version"]; found {
|
||||
wps = ver
|
||||
} else {
|
||||
wps = "✔"
|
||||
}
|
||||
|
||||
if state, found := station.WPS["State"]; found {
|
||||
if state == "Not Configured" {
|
||||
wps += " (not configured)"
|
||||
}
|
||||
}
|
||||
|
||||
wps = tui.Dim(tui.Yellow(wps))
|
||||
}
|
||||
|
||||
return []string{
|
||||
fmt.Sprintf("%d dBm", station.RSSI),
|
||||
bssid,
|
||||
ssid,
|
||||
encryption,
|
||||
wps,
|
||||
strconv.Itoa(station.Channel()),
|
||||
clients,
|
||||
sent,
|
||||
recvd,
|
||||
seen,
|
||||
}, include
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WiFiModule) doFilter(station *network.Station) bool {
|
||||
if w.selector.Expression == nil {
|
||||
return true
|
||||
}
|
||||
return w.selector.Expression.MatchString(station.BSSID()) ||
|
||||
w.selector.Expression.MatchString(station.ESSID()) ||
|
||||
w.selector.Expression.MatchString(station.Alias) ||
|
||||
w.selector.Expression.MatchString(station.Vendor) ||
|
||||
w.selector.Expression.MatchString(station.Encryption)
|
||||
}
|
||||
|
||||
func (w *WiFiModule) doSelection() (err error, stations []*network.Station) {
|
||||
if err = w.selector.Update(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
apSelected := w.isApSelected()
|
||||
if apSelected {
|
||||
if ap, found := w.Session.WiFi.Get(w.ap.HwAddress); found {
|
||||
stations = ap.Clients()
|
||||
} else {
|
||||
err = fmt.Errorf("Could not find station %s", w.ap.HwAddress)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
stations = w.Session.WiFi.Stations()
|
||||
}
|
||||
|
||||
filtered := []*network.Station{}
|
||||
for _, station := range stations {
|
||||
if w.doFilter(station) {
|
||||
filtered = append(filtered, station)
|
||||
}
|
||||
}
|
||||
stations = filtered
|
||||
|
||||
switch w.selector.SortField {
|
||||
case "seen":
|
||||
sort.Sort(ByWiFiSeenSorter(stations))
|
||||
case "essid":
|
||||
sort.Sort(ByEssidSorter(stations))
|
||||
case "bssid":
|
||||
sort.Sort(ByBssidSorter(stations))
|
||||
case "channel":
|
||||
sort.Sort(ByChannelSorter(stations))
|
||||
case "clients":
|
||||
sort.Sort(ByClientsSorter(stations))
|
||||
case "encryption":
|
||||
sort.Sort(ByEncryptionSorter(stations))
|
||||
case "sent":
|
||||
sort.Sort(ByWiFiSentSorter(stations))
|
||||
case "rcvd":
|
||||
sort.Sort(ByWiFiRcvdSorter(stations))
|
||||
case "rssi":
|
||||
sort.Sort(ByRSSISorter(stations))
|
||||
default:
|
||||
sort.Sort(ByRSSISorter(stations))
|
||||
}
|
||||
|
||||
// default is asc
|
||||
if w.selector.Sort == "desc" {
|
||||
// from https://github.com/golang/go/wiki/SliceTricks
|
||||
for i := len(stations)/2 - 1; i >= 0; i-- {
|
||||
opp := len(stations) - 1 - i
|
||||
stations[i], stations[opp] = stations[opp], stations[i]
|
||||
}
|
||||
}
|
||||
|
||||
if w.selector.Limit > 0 {
|
||||
limit := w.selector.Limit
|
||||
max := len(stations)
|
||||
if limit > max {
|
||||
limit = max
|
||||
}
|
||||
stations = stations[0:limit]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (w *WiFiModule) colDecorate(colNames []string, name string, dir string) {
|
||||
for i, c := range colNames {
|
||||
if c == name {
|
||||
colNames[i] += " " + dir
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WiFiModule) colNames(nrows int) []string {
|
||||
columns := []string(nil)
|
||||
|
||||
if !w.isApSelected() {
|
||||
columns = []string{"RSSI", "BSSID", "SSID", "Encryption", "WPS", "Ch", "Clients", "Sent", "Recvd", "Seen"}
|
||||
} else if nrows > 0 {
|
||||
columns = []string{"RSSI", "BSSID", "Ch", "Sent", "Recvd", "Seen"}
|
||||
fmt.Printf("\n%s clients:\n", w.ap.HwAddress)
|
||||
} else {
|
||||
fmt.Printf("\nNo authenticated clients detected for %s.\n", w.ap.HwAddress)
|
||||
}
|
||||
|
||||
if columns != nil {
|
||||
switch w.selector.SortField {
|
||||
case "seen":
|
||||
w.colDecorate(columns, "Seen", w.selector.SortSymbol)
|
||||
case "essid":
|
||||
w.colDecorate(columns, "SSID", w.selector.SortSymbol)
|
||||
case "bssid":
|
||||
w.colDecorate(columns, "BSSID", w.selector.SortSymbol)
|
||||
case "channel":
|
||||
w.colDecorate(columns, "Ch", w.selector.SortSymbol)
|
||||
case "clients":
|
||||
w.colDecorate(columns, "Clients", w.selector.SortSymbol)
|
||||
case "encryption":
|
||||
w.colDecorate(columns, "Encryption", w.selector.SortSymbol)
|
||||
case "sent":
|
||||
w.colDecorate(columns, "Sent", w.selector.SortSymbol)
|
||||
case "rcvd":
|
||||
w.colDecorate(columns, "Recvd", w.selector.SortSymbol)
|
||||
case "rssi":
|
||||
w.colDecorate(columns, "RSSI", w.selector.SortSymbol)
|
||||
}
|
||||
}
|
||||
|
||||
return columns
|
||||
}
|
||||
|
||||
func (w *WiFiModule) showStatusBar() {
|
||||
w.Session.Queue.Stats.RLock()
|
||||
defer w.Session.Queue.Stats.RUnlock()
|
||||
|
||||
parts := []string{
|
||||
fmt.Sprintf("%s (ch. %d)", w.Session.Interface.Name(), network.GetInterfaceChannel(w.Session.Interface.Name())),
|
||||
fmt.Sprintf("%s %s", tui.Red("↑"), humanize.Bytes(w.Session.Queue.Stats.Sent)),
|
||||
fmt.Sprintf("%s %s", tui.Green("↓"), humanize.Bytes(w.Session.Queue.Stats.Received)),
|
||||
fmt.Sprintf("%d pkts", w.Session.Queue.Stats.PktReceived),
|
||||
}
|
||||
|
||||
if nErrors := w.Session.Queue.Stats.Errors; nErrors > 0 {
|
||||
parts = append(parts, fmt.Sprintf("%d errs", nErrors))
|
||||
}
|
||||
|
||||
if nHandshakes := w.Session.WiFi.NumHandshakes(); nHandshakes > 0 {
|
||||
parts = append(parts, fmt.Sprintf("%d handshakes", nHandshakes))
|
||||
}
|
||||
|
||||
fmt.Printf("\n%s\n\n", strings.Join(parts, " / "))
|
||||
}
|
||||
|
||||
func (w *WiFiModule) Show() (err error) {
|
||||
var stations []*network.Station
|
||||
if err, stations = w.doSelection(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rows := make([][]string, 0)
|
||||
for _, s := range stations {
|
||||
if row, include := w.getRow(s); include {
|
||||
rows = append(rows, row)
|
||||
}
|
||||
}
|
||||
nrows := len(rows)
|
||||
if nrows > 0 {
|
||||
tui.Table(os.Stdout, w.colNames(nrows), rows)
|
||||
}
|
||||
|
||||
w.showStatusBar()
|
||||
|
||||
w.Session.Refresh()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WiFiModule) ShowWPS(bssid string) (err error) {
|
||||
toShow := []*network.Station{}
|
||||
|
||||
if bssid == network.BroadcastMac {
|
||||
for _, station := range w.Session.WiFi.List() {
|
||||
if station.HasWPS() {
|
||||
toShow = append(toShow, station.Station)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if station, found := w.Session.WiFi.Get(bssid); found {
|
||||
if station.HasWPS() {
|
||||
toShow = append(toShow, station.Station)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(toShow) == 0 {
|
||||
return fmt.Errorf("no WPS enabled access points matched the criteria")
|
||||
}
|
||||
|
||||
sort.Sort(ByBssidSorter(toShow))
|
||||
|
||||
colNames := []string{"Name", "Value"}
|
||||
|
||||
for _, station := range toShow {
|
||||
ssid := ops.Ternary(station.ESSID() == "<hidden>", tui.Dim(station.ESSID()), station.ESSID()).(string)
|
||||
|
||||
rows := [][]string{
|
||||
[]string{tui.Green("essid"), ssid},
|
||||
[]string{tui.Green("bssid"), station.BSSID()},
|
||||
}
|
||||
|
||||
keys := []string{}
|
||||
for name := range station.WPS {
|
||||
keys = append(keys, name)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, name := range keys {
|
||||
rows = append(rows, []string{
|
||||
tui.Green(name),
|
||||
tui.Yellow(station.WPS[name]),
|
||||
})
|
||||
}
|
||||
|
||||
tui.Table(os.Stdout, colNames, rows)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
100
modules/wifi/wifi_show_sort.go
Normal file
100
modules/wifi/wifi_show_sort.go
Normal file
|
@ -0,0 +1,100 @@
|
|||
package wifi
|
||||
|
||||
import (
|
||||
"github.com/bettercap/bettercap/network"
|
||||
"github.com/bettercap/bettercap/session"
|
||||
)
|
||||
|
||||
type ByRSSISorter []*network.Station
|
||||
|
||||
func (a ByRSSISorter) Len() int { return len(a) }
|
||||
func (a ByRSSISorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByRSSISorter) Less(i, j int) bool {
|
||||
if a[i].RSSI == a[j].RSSI {
|
||||
return a[i].HwAddress < a[j].HwAddress
|
||||
}
|
||||
return a[i].RSSI > a[j].RSSI
|
||||
}
|
||||
|
||||
type ByChannelSorter []*network.Station
|
||||
|
||||
func (a ByChannelSorter) Len() int { return len(a) }
|
||||
func (a ByChannelSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByChannelSorter) Less(i, j int) bool {
|
||||
return a[i].Frequency < a[j].Frequency
|
||||
}
|
||||
|
||||
type ByEncryptionSorter []*network.Station
|
||||
|
||||
func (a ByEncryptionSorter) Len() int { return len(a) }
|
||||
func (a ByEncryptionSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByEncryptionSorter) Less(i, j int) bool {
|
||||
if a[i].Encryption == a[j].Encryption {
|
||||
return a[i].HwAddress < a[j].HwAddress
|
||||
}
|
||||
return a[i].Encryption < a[j].Encryption
|
||||
}
|
||||
|
||||
type ByBssidSorter []*network.Station
|
||||
|
||||
func (a ByBssidSorter) Len() int { return len(a) }
|
||||
func (a ByBssidSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByBssidSorter) Less(i, j int) bool {
|
||||
return a[i].BSSID() < a[j].BSSID()
|
||||
}
|
||||
|
||||
type ByEssidSorter []*network.Station
|
||||
|
||||
func (a ByEssidSorter) Len() int { return len(a) }
|
||||
func (a ByEssidSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByEssidSorter) Less(i, j int) bool {
|
||||
if a[i].ESSID() == a[j].ESSID() {
|
||||
return a[i].HwAddress < a[j].HwAddress
|
||||
}
|
||||
return a[i].ESSID() < a[j].ESSID()
|
||||
}
|
||||
|
||||
type ByWiFiSeenSorter []*network.Station
|
||||
|
||||
func (a ByWiFiSeenSorter) Len() int { return len(a) }
|
||||
func (a ByWiFiSeenSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByWiFiSeenSorter) Less(i, j int) bool {
|
||||
return a[i].LastSeen.Before(a[j].LastSeen)
|
||||
}
|
||||
|
||||
type ByWiFiSentSorter []*network.Station
|
||||
|
||||
func (a ByWiFiSentSorter) Len() int { return len(a) }
|
||||
func (a ByWiFiSentSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByWiFiSentSorter) Less(i, j int) bool {
|
||||
return a[i].Sent < a[j].Sent
|
||||
}
|
||||
|
||||
type ByWiFiRcvdSorter []*network.Station
|
||||
|
||||
func (a ByWiFiRcvdSorter) Len() int { return len(a) }
|
||||
func (a ByWiFiRcvdSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByWiFiRcvdSorter) Less(i, j int) bool {
|
||||
return a[i].Received < a[j].Received
|
||||
}
|
||||
|
||||
type ByClientsSorter []*network.Station
|
||||
|
||||
func (a ByClientsSorter) Len() int { return len(a) }
|
||||
func (a ByClientsSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByClientsSorter) Less(i, j int) bool {
|
||||
left := 0
|
||||
right := 0
|
||||
|
||||
if ap, found := session.I.WiFi.Get(a[i].HwAddress); found {
|
||||
left = ap.NumClients()
|
||||
}
|
||||
if ap, found := session.I.WiFi.Get(a[j].HwAddress); found {
|
||||
right = ap.NumClients()
|
||||
}
|
||||
|
||||
if left == right {
|
||||
return a[i].HwAddress < a[j].HwAddress
|
||||
}
|
||||
return left < right
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue