bettercap/modules/ble/ble_show.go
2024-08-09 18:56:41 +02:00

155 lines
3.4 KiB
Go

//go:build !windows && !freebsd && !openbsd && !netbsd
// +build !windows,!freebsd,!openbsd,!netbsd
package ble
import (
"sort"
"time"
"github.com/bettercap/bettercap/v2/network"
"github.com/evilsocket/islazy/ops"
"github.com/evilsocket/islazy/tui"
)
var (
bleAliveInterval = time.Duration(5) * time.Second
)
func (mod *BLERecon) getRow(dev *network.BLEDevice, withName bool) []string {
rssi := network.ColorRSSI(dev.RSSI)
address := network.NormalizeMac(dev.Device.ID())
vendor := tui.Dim(ops.Ternary(dev.Vendor == "", dev.Advertisement.Company, dev.Vendor).(string))
isConnectable := ops.Ternary(dev.Advertisement.Connectable, tui.Green("✔"), tui.Red("✖")).(string)
sinceSeen := time.Since(dev.LastSeen)
lastSeen := dev.LastSeen.Format("15:04:05")
blePresentInterval := time.Duration(mod.devTTL) * time.Second
if sinceSeen <= bleAliveInterval {
lastSeen = tui.Bold(lastSeen)
} else if sinceSeen > blePresentInterval {
lastSeen = tui.Dim(lastSeen)
address = tui.Dim(address)
}
if withName {
return []string{
rssi,
address,
tui.Yellow(dev.Name()),
vendor,
dev.Advertisement.Flags.String(),
isConnectable,
lastSeen,
}
} else {
return []string{
rssi,
address,
vendor,
dev.Advertisement.Flags.String(),
isConnectable,
lastSeen,
}
}
}
func (mod *BLERecon) doFilter(dev *network.BLEDevice) bool {
if mod.selector.Expression == nil {
return true
}
return mod.selector.Expression.MatchString(dev.Device.ID()) ||
mod.selector.Expression.MatchString(dev.Device.Name()) ||
mod.selector.Expression.MatchString(dev.Vendor)
}
func (mod *BLERecon) doSelection() (devices []*network.BLEDevice, err error) {
if err = mod.selector.Update(); err != nil {
return
}
devices = mod.Session.BLE.Devices()
filtered := []*network.BLEDevice{}
for _, dev := range devices {
if mod.doFilter(dev) {
filtered = append(filtered, dev)
}
}
devices = filtered
switch mod.selector.SortField {
case "mac":
sort.Sort(ByBLEMacSorter(devices))
case "seen":
sort.Sort(ByBLESeenSorter(devices))
default:
sort.Sort(ByBLERSSISorter(devices))
}
// default is asc
if mod.selector.Sort == "desc" {
// from https://github.com/golang/go/wiki/SliceTricks
for i := len(devices)/2 - 1; i >= 0; i-- {
opp := len(devices) - 1 - i
devices[i], devices[opp] = devices[opp], devices[i]
}
}
if mod.selector.Limit > 0 {
limit := mod.selector.Limit
max := len(devices)
if limit > max {
limit = max
}
devices = devices[0:limit]
}
return
}
func (mod *BLERecon) colNames(withName bool) []string {
colNames := []string{"RSSI", "MAC", "Vendor", "Flags", "Connect", "Seen"}
seenIdx := 5
if withName {
colNames = []string{"RSSI", "MAC", "Name", "Vendor", "Flags", "Connect", "Seen"}
seenIdx = 6
}
switch mod.selector.SortField {
case "rssi":
colNames[0] += " " + mod.selector.SortSymbol
case "mac":
colNames[1] += " " + mod.selector.SortSymbol
case "seen":
colNames[seenIdx] += " " + mod.selector.SortSymbol
}
return colNames
}
func (mod *BLERecon) Show() error {
devices, err := mod.doSelection()
if err != nil {
return err
}
hasName := false
for _, dev := range devices {
if dev.Name() != "" {
hasName = true
break
}
}
rows := make([][]string, 0)
for _, dev := range devices {
rows = append(rows, mod.getRow(dev, hasName))
}
if len(rows) > 0 {
tui.Table(mod.Session.Events.Stdout, mod.colNames(hasName), rows)
mod.Session.Refresh()
}
return nil
}