mirror of
https://github.com/bettercap/bettercap.git
synced 2024-11-08 06:30:13 -08:00
406 lines
9.8 KiB
Go
406 lines
9.8 KiB
Go
package wifi
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/bettercap/bettercap/v2/modules/net_recon"
|
|
"github.com/bettercap/bettercap/v2/network"
|
|
"github.com/bettercap/bettercap/v2/session"
|
|
|
|
"github.com/dustin/go-humanize"
|
|
|
|
"github.com/evilsocket/islazy/ops"
|
|
"github.com/evilsocket/islazy/tui"
|
|
)
|
|
|
|
func (mod *WiFiModule) isApSelected() bool {
|
|
return mod.ap != nil
|
|
}
|
|
|
|
func (mod *WiFiModule) getRow(station *network.Station) ([]string, bool) {
|
|
rssi := network.ColorRSSI(int(station.RSSI))
|
|
bssid := station.HwAddress
|
|
sinceStarted := time.Since(mod.Session.StartedAt)
|
|
sinceFirstSeen := time.Since(station.FirstSeen)
|
|
if sinceStarted > (net_recon.JustJoinedTimeInterval*2) && sinceFirstSeen <= net_recon.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 > net_recon.AliveTimeInterval && sinceLastSeen <= net_recon.AliveTimeInterval {
|
|
// if endpoint seen in the last 10 seconds
|
|
seen = tui.Bold(seen)
|
|
} else if sinceLastSeen > net_recon.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 := mod.Session.WiFi.Get(station.HwAddress); found && ap.HasKeyMaterial() {
|
|
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)
|
|
|
|
include := false
|
|
if mod.source == "" {
|
|
for _, frequencies := range mod.frequencies {
|
|
if frequencies == station.Frequency {
|
|
include = true
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
include = true
|
|
}
|
|
|
|
if int(station.RSSI) < mod.minRSSI {
|
|
include = false
|
|
}
|
|
|
|
if mod.isApSelected() {
|
|
if mod.showManuf {
|
|
return []string{
|
|
rssi,
|
|
bssid,
|
|
tui.Dim(station.Vendor),
|
|
strconv.Itoa(station.Channel),
|
|
sent,
|
|
recvd,
|
|
seen,
|
|
}, include
|
|
} else {
|
|
return []string{
|
|
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 := mod.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))
|
|
}
|
|
|
|
if mod.showManuf {
|
|
return []string{
|
|
rssi,
|
|
bssid,
|
|
tui.Dim(station.Vendor),
|
|
ssid,
|
|
encryption,
|
|
wps,
|
|
strconv.Itoa(station.Channel),
|
|
clients,
|
|
sent,
|
|
recvd,
|
|
seen,
|
|
}, include
|
|
} else {
|
|
return []string{
|
|
rssi,
|
|
bssid,
|
|
ssid,
|
|
encryption,
|
|
wps,
|
|
strconv.Itoa(station.Channel),
|
|
clients,
|
|
sent,
|
|
recvd,
|
|
seen,
|
|
}, include
|
|
}
|
|
}
|
|
}
|
|
|
|
func (mod *WiFiModule) doFilter(station *network.Station) bool {
|
|
if mod.selector.Expression == nil {
|
|
return true
|
|
}
|
|
return mod.selector.Expression.MatchString(station.BSSID()) ||
|
|
mod.selector.Expression.MatchString(station.ESSID()) ||
|
|
mod.selector.Expression.MatchString(station.Alias) ||
|
|
mod.selector.Expression.MatchString(station.Vendor) ||
|
|
mod.selector.Expression.MatchString(station.Encryption)
|
|
}
|
|
|
|
func (mod *WiFiModule) doSelection() (err error, stations []*network.Station) {
|
|
if err = mod.selector.Update(); err != nil {
|
|
return
|
|
}
|
|
|
|
apSelected := mod.isApSelected()
|
|
if apSelected {
|
|
if ap, found := mod.Session.WiFi.Get(mod.ap.HwAddress); found {
|
|
stations = ap.Clients()
|
|
} else {
|
|
err = fmt.Errorf("could not find station %s", mod.ap.HwAddress)
|
|
return
|
|
}
|
|
} else {
|
|
stations = mod.Session.WiFi.Stations()
|
|
}
|
|
|
|
filtered := []*network.Station{}
|
|
for _, station := range stations {
|
|
if mod.doFilter(station) {
|
|
filtered = append(filtered, station)
|
|
}
|
|
}
|
|
stations = filtered
|
|
|
|
switch mod.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 mod.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 mod.selector.Limit > 0 {
|
|
limit := mod.selector.Limit
|
|
max := len(stations)
|
|
if limit > max {
|
|
limit = max
|
|
}
|
|
stations = stations[0:limit]
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (mod *WiFiModule) colDecorate(colNames []string, name string, dir string) {
|
|
for i, c := range colNames {
|
|
if c == name {
|
|
colNames[i] += " " + dir
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func (mod *WiFiModule) colNames(nrows int) []string {
|
|
columns := []string(nil)
|
|
|
|
if !mod.isApSelected() {
|
|
if mod.showManuf {
|
|
columns = []string{"RSSI", "BSSID", "Manufacturer", "SSID", "Encryption", "WPS", "Ch", "Clients", "Sent", "Recvd", "Seen"}
|
|
} else {
|
|
columns = []string{"RSSI", "BSSID", "SSID", "Encryption", "WPS", "Ch", "Clients", "Sent", "Recvd", "Seen"}
|
|
}
|
|
} else if nrows > 0 {
|
|
if mod.showManuf {
|
|
columns = []string{"RSSI", "BSSID", "Manufacturer", "Ch", "Sent", "Recvd", "Seen"}
|
|
} else {
|
|
columns = []string{"RSSI", "BSSID", "Ch", "Sent", "Recvd", "Seen"}
|
|
}
|
|
mod.Printf("\n%s clients:\n", mod.ap.HwAddress)
|
|
} else {
|
|
mod.Printf("\nNo authenticated clients detected for %s.\n", mod.ap.HwAddress)
|
|
}
|
|
|
|
if columns != nil {
|
|
switch mod.selector.SortField {
|
|
case "seen":
|
|
mod.colDecorate(columns, "Seen", mod.selector.SortSymbol)
|
|
case "essid":
|
|
mod.colDecorate(columns, "SSID", mod.selector.SortSymbol)
|
|
case "bssid":
|
|
mod.colDecorate(columns, "BSSID", mod.selector.SortSymbol)
|
|
case "channel":
|
|
mod.colDecorate(columns, "Ch", mod.selector.SortSymbol)
|
|
case "clients":
|
|
mod.colDecorate(columns, "Clients", mod.selector.SortSymbol)
|
|
case "encryption":
|
|
mod.colDecorate(columns, "Encryption", mod.selector.SortSymbol)
|
|
case "sent":
|
|
mod.colDecorate(columns, "Sent", mod.selector.SortSymbol)
|
|
case "rcvd":
|
|
mod.colDecorate(columns, "Recvd", mod.selector.SortSymbol)
|
|
case "rssi":
|
|
mod.colDecorate(columns, "RSSI", mod.selector.SortSymbol)
|
|
}
|
|
}
|
|
|
|
return columns
|
|
}
|
|
|
|
func (mod *WiFiModule) showStatusBar() {
|
|
parts := []string{
|
|
fmt.Sprintf("%s (ch. %d)", mod.iface.Name(), network.GetInterfaceChannel(mod.iface.Name())),
|
|
fmt.Sprintf("%s %s", tui.Red("↑"), humanize.Bytes(mod.Session.Queue.Stats.Sent)),
|
|
fmt.Sprintf("%s %s", tui.Green("↓"), humanize.Bytes(mod.Session.Queue.Stats.Received)),
|
|
fmt.Sprintf("%d pkts", mod.Session.Queue.Stats.PktReceived),
|
|
}
|
|
|
|
if nErrors := mod.Session.Queue.Stats.Errors; nErrors > 0 {
|
|
parts = append(parts, fmt.Sprintf("%d errs", nErrors))
|
|
}
|
|
|
|
if nHandshakes := mod.Session.WiFi.NumHandshakes(); nHandshakes > 0 {
|
|
parts = append(parts, fmt.Sprintf("%d handshakes", nHandshakes))
|
|
}
|
|
|
|
mod.Printf("\n%s\n\n", strings.Join(parts, " / "))
|
|
}
|
|
|
|
func (mod *WiFiModule) Show() (err error) {
|
|
// module has not been started yet
|
|
if mod.iface == nil {
|
|
if err := mod.Configure(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
var stations []*network.Station
|
|
if err, stations = mod.doSelection(); err != nil {
|
|
return
|
|
}
|
|
|
|
if err, mod.showManuf = mod.BoolParam("wifi.show.manufacturer"); err != nil {
|
|
return err
|
|
}
|
|
|
|
rows := make([][]string, 0)
|
|
for _, s := range stations {
|
|
if row, include := mod.getRow(s); include {
|
|
rows = append(rows, row)
|
|
}
|
|
}
|
|
nrows := len(rows)
|
|
if nrows > 0 {
|
|
tui.Table(mod.Session.Events.Stdout, mod.colNames(nrows), rows)
|
|
}
|
|
|
|
mod.showStatusBar()
|
|
|
|
mod.Session.Refresh()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (mod *WiFiModule) ShowWPS(bssid string) (err error) {
|
|
if mod.Running() == false {
|
|
return session.ErrAlreadyStopped(mod.Name())
|
|
}
|
|
|
|
toShow := []*network.Station{}
|
|
if bssid == network.BroadcastMac {
|
|
for _, station := range mod.Session.WiFi.List() {
|
|
if station.HasWPS() {
|
|
toShow = append(toShow, station.Station)
|
|
}
|
|
}
|
|
} else {
|
|
if station, found := mod.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{
|
|
{tui.Green("essid"), ssid},
|
|
{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(mod.Session.Events.Stdout, colNames, rows)
|
|
}
|
|
|
|
return nil
|
|
}
|