mirror of
https://github.com/bettercap/bettercap.git
synced 2024-11-08 06:30:13 -08:00
413 lines
11 KiB
Go
413 lines
11 KiB
Go
//go:build !windows && !freebsd && !openbsd && !netbsd
|
||
// +build !windows,!freebsd,!openbsd,!netbsd
|
||
|
||
package ble
|
||
|
||
import (
|
||
"encoding/binary"
|
||
"fmt"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"github.com/bettercap/bettercap/v2/network"
|
||
"github.com/bettercap/gatt"
|
||
|
||
"github.com/evilsocket/islazy/tui"
|
||
)
|
||
|
||
var appearances = map[uint16]string{
|
||
0: "Unknown",
|
||
64: "Generic Phone",
|
||
128: "Generic Computer",
|
||
192: "Generic Watch",
|
||
193: "Watch: Sports Watch",
|
||
256: "Generic Clock",
|
||
320: "Generic Display",
|
||
384: "Generic Remote Control",
|
||
448: "Generic Eye-glasses",
|
||
512: "Generic Tag",
|
||
576: "Generic Keyring",
|
||
640: "Generic Media Player",
|
||
704: "Generic Barcode Scanner",
|
||
768: "Generic Thermometer",
|
||
769: "Thermometer: Ear",
|
||
832: "Generic Heart rate Sensor",
|
||
833: "Heart Rate Sensor: Heart Rate Belt",
|
||
896: "Generic Blood Pressure",
|
||
897: "Blood Pressure: Arm",
|
||
898: "Blood Pressure: Wrist",
|
||
960: "Human Interface Device (HID)",
|
||
961: "Keyboard",
|
||
962: "Mouse",
|
||
963: "Joystick",
|
||
964: "Gamepad",
|
||
965: "Digitizer Tablet",
|
||
966: "Card Reader",
|
||
967: "Digital Pen",
|
||
968: "Barcode Scanner",
|
||
1024: "Generic Glucose Meter",
|
||
1088: "Generic: Running Walking Sensor",
|
||
1089: "Running Walking Sensor: In-Shoe",
|
||
1090: "Running Walking Sensor: On-Shoe",
|
||
1091: "Running Walking Sensor: On-Hip",
|
||
1152: "Generic: Cycling",
|
||
1153: "Cycling: Cycling Computer",
|
||
1154: "Cycling: Speed Sensor",
|
||
1155: "Cycling: Cadence Sensor",
|
||
1156: "Cycling: Power Sensor",
|
||
1157: "Cycling: Speed and Cadence Sensor",
|
||
1216: "Generic Control Device",
|
||
1217: "Switch",
|
||
1218: "Multi-switch",
|
||
1219: "Button",
|
||
1220: "Slider",
|
||
1221: "Rotary",
|
||
1222: "Touch-panel",
|
||
1280: "Generic Network Device",
|
||
1281: "Access Point",
|
||
1344: "Generic Sensor",
|
||
1345: "Motion Sensor",
|
||
1346: "Air Quality Sensor",
|
||
1347: "Temperature Sensor",
|
||
1348: "Humidity Sensor",
|
||
1349: "Leak Sensor",
|
||
1350: "Smoke Sensor",
|
||
1351: "Occupancy Sensor",
|
||
1352: "Contact Sensor",
|
||
1353: "Carbon Monoxide Sensor",
|
||
1354: "Carbon Dioxide Sensor",
|
||
1355: "Ambient Light Sensor",
|
||
1356: "Energy Sensor",
|
||
1357: "Color Light Sensor",
|
||
1358: "Rain Sensor",
|
||
1359: "Fire Sensor",
|
||
1360: "Wind Sensor",
|
||
1361: "Proximity Sensor",
|
||
1362: "Multi-Sensor",
|
||
1408: "Generic Light Fixtures",
|
||
1409: "Wall Light",
|
||
1410: "Ceiling Light",
|
||
1411: "Floor Light",
|
||
1412: "Cabinet Light",
|
||
1413: "Desk Light",
|
||
1414: "Troffer Light",
|
||
1415: "Pendant Light",
|
||
1416: "In-ground Light",
|
||
1417: "Flood Light",
|
||
1418: "Underwater Light",
|
||
1419: "Bollard with Light",
|
||
1420: "Pathway Light",
|
||
1421: "Garden Light",
|
||
1422: "Pole-top Light",
|
||
1423: "Spotlight",
|
||
1424: "Linear Light",
|
||
1425: "Street Light",
|
||
1426: "Shelves Light",
|
||
1427: "High-bay / Low-bay Light",
|
||
1428: "Emergency Exit Light",
|
||
1472: "Generic Fan",
|
||
1473: "Ceiling Fan",
|
||
1474: "Axial Fan",
|
||
1475: "Exhaust Fan",
|
||
1476: "Pedestal Fan",
|
||
1477: "Desk Fan",
|
||
1478: "Wall Fan",
|
||
1536: "Generic HVAC",
|
||
1537: "Thermostat",
|
||
1600: "Generic Air Conditioning",
|
||
1664: "Generic Humidifier",
|
||
1728: "Generic Heating",
|
||
1729: "Radiator",
|
||
1730: "Boiler",
|
||
1731: "Heat Pump",
|
||
1732: "Infrared Heater",
|
||
1733: "Radiant Panel Heater",
|
||
1734: "Fan Heater",
|
||
1735: "Air Curtain",
|
||
1792: "Generic Access Control",
|
||
1793: "Access Door",
|
||
1794: "Garage Door",
|
||
1795: "Emergency Exit Door",
|
||
1796: "Access Lock",
|
||
1797: "Elevator",
|
||
1798: "Window",
|
||
1799: "Entrance Gate",
|
||
1856: "Generic Motorized Device",
|
||
1857: "Motorized Gate",
|
||
1858: "Awning",
|
||
1859: "Blinds or Shades",
|
||
1860: "Curtains",
|
||
1861: "Screen",
|
||
1920: "Generic Power Device",
|
||
1921: "Power Outlet",
|
||
1922: "Power Strip",
|
||
1923: "Plug",
|
||
1924: "Power Supply",
|
||
1925: "LED Driver",
|
||
1926: "Fluorescent Lamp Gear",
|
||
1927: "HID Lamp Gear",
|
||
1984: "Generic Light Source",
|
||
1985: "Incandescent Light Bulb",
|
||
1986: "LED Bulb",
|
||
1987: "HID Lamp",
|
||
1988: "Fluorescent Lamp",
|
||
1989: "LED Array",
|
||
1990: "Multi-Color LED Array",
|
||
3136: "Generic: Pulse Oximeter",
|
||
3137: "Fingertip",
|
||
3138: "Wrist Worn",
|
||
3200: "Generic: Weight Scale",
|
||
3264: "Generic",
|
||
3265: "Powered Wheelchair",
|
||
3266: "Mobility Scooter",
|
||
3328: "Generic",
|
||
5184: "Generic: Outdoor Sports Activity",
|
||
5185: "Location Display Device",
|
||
5186: "Location and Navigation Display Device",
|
||
5187: "Location Pod",
|
||
5188: "Location and Navigation Pod",
|
||
}
|
||
|
||
func parseProperties(ch *gatt.Characteristic) (props []string, isReadable bool, isWritable bool, withResponse bool) {
|
||
isReadable = false
|
||
isWritable = false
|
||
withResponse = false
|
||
props = make([]string, 0)
|
||
mask := ch.Properties()
|
||
|
||
if (mask & gatt.CharBroadcast) != 0 {
|
||
props = append(props, "BCAST")
|
||
}
|
||
if (mask & gatt.CharRead) != 0 {
|
||
isReadable = true
|
||
props = append(props, "READ")
|
||
}
|
||
if (mask&gatt.CharWriteNR) != 0 || (mask&gatt.CharWrite) != 0 {
|
||
props = append(props, tui.Bold("WRITE"))
|
||
isWritable = true
|
||
withResponse = (mask & gatt.CharWriteNR) == 0
|
||
}
|
||
if (mask & gatt.CharNotify) != 0 {
|
||
props = append(props, "NOTIFY")
|
||
}
|
||
if (mask & gatt.CharIndicate) != 0 {
|
||
props = append(props, "INDICATE")
|
||
}
|
||
if (mask & gatt.CharSignedWrite) != 0 {
|
||
props = append(props, tui.Yellow("SIGN WRITE"))
|
||
isWritable = true
|
||
withResponse = true
|
||
}
|
||
if (mask & gatt.CharExtended) != 0 {
|
||
props = append(props, "X")
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func parseRawData(raw []byte) string {
|
||
s := ""
|
||
for _, b := range raw {
|
||
if strconv.IsPrint(rune(b)) {
|
||
s += tui.Yellow(string(b))
|
||
} else {
|
||
s += tui.Dim(fmt.Sprintf("%02x", b))
|
||
}
|
||
}
|
||
return s
|
||
}
|
||
|
||
// org.bluetooth.characteristic.gap.appearance
|
||
func parseAppearance(raw []byte) string {
|
||
app := binary.LittleEndian.Uint16(raw[0:2])
|
||
if appName, found := appearances[app]; found {
|
||
return tui.Green(appName)
|
||
}
|
||
return fmt.Sprintf("0x%x", app)
|
||
}
|
||
|
||
// org.bluetooth.characteristic.pnp_id
|
||
func parsePNPID(raw []byte) []string {
|
||
vendorIdSrc := byte(raw[0])
|
||
vendorId := binary.LittleEndian.Uint16(raw[1:3])
|
||
prodId := binary.LittleEndian.Uint16(raw[3:5])
|
||
prodVer := binary.LittleEndian.Uint16(raw[5:7])
|
||
|
||
src := ""
|
||
if vendorIdSrc == 1 {
|
||
src = " (Bluetooth SIG assigned Company Identifier)"
|
||
} else if vendorIdSrc == 2 {
|
||
src = " (USB Implementer’s Forum assigned Vendor ID value)"
|
||
}
|
||
|
||
return []string{
|
||
tui.Green("Vendor ID") + fmt.Sprintf(": 0x%04x%s", vendorId, tui.Dim(src)),
|
||
tui.Green("Product ID") + fmt.Sprintf(": 0x%04x", prodId),
|
||
tui.Green("Product Version") + fmt.Sprintf(": 0x%04x", prodVer),
|
||
}
|
||
}
|
||
|
||
// org.bluetooth.characteristic.gap.peripheral_preferred_connection_parameters
|
||
func parseConnectionParams(raw []byte) []string {
|
||
minConInt := binary.LittleEndian.Uint16(raw[0:2])
|
||
maxConInt := binary.LittleEndian.Uint16(raw[2:4])
|
||
slaveLat := binary.LittleEndian.Uint16(raw[4:6])
|
||
conTimeMul := binary.LittleEndian.Uint16(raw[6:8])
|
||
|
||
return []string{
|
||
tui.Green("Connection Interval") + fmt.Sprintf(": %d -> %d", minConInt, maxConInt),
|
||
tui.Green("Slave Latency") + fmt.Sprintf(": %d", slaveLat),
|
||
tui.Green("Connection Supervision Timeout Multiplier") + fmt.Sprintf(": %d", conTimeMul),
|
||
}
|
||
}
|
||
|
||
// org.bluetooth.characteristic.gap.peripheral_privacy_flag
|
||
func parsePrivacyFlag(raw []byte) string {
|
||
if raw[0] == 0x0 {
|
||
return tui.Green("Privacy Disabled")
|
||
}
|
||
return tui.Red("Privacy Enabled")
|
||
}
|
||
|
||
func (mod *BLERecon) showServices(p gatt.Peripheral, services []*gatt.Service) {
|
||
columns := []string{"Handles", "Service > Characteristics", "Properties", "Data"}
|
||
rows := make([][]string, 0)
|
||
|
||
wantsToWrite := mod.writeUUID != nil
|
||
foundToWrite := false
|
||
|
||
mod.currDevice.Services = make([]network.BLEService, 0)
|
||
|
||
for _, svc := range services {
|
||
service := network.BLEService{
|
||
UUID: svc.UUID().String(),
|
||
Name: svc.Name(),
|
||
Handle: svc.Handle(),
|
||
EndHandle: svc.EndHandle(),
|
||
Characteristics: make([]network.BLECharacteristic, 0),
|
||
}
|
||
|
||
mod.Session.Events.Add("ble.device.service.discovered", svc)
|
||
|
||
name := svc.Name()
|
||
if name == "" {
|
||
name = svc.UUID().String()
|
||
} else {
|
||
name = fmt.Sprintf("%s (%s)", tui.Green(name), tui.Dim(svc.UUID().String()))
|
||
}
|
||
|
||
row := []string{
|
||
fmt.Sprintf("%04x -> %04x", svc.Handle(), svc.EndHandle()),
|
||
name,
|
||
"",
|
||
"",
|
||
}
|
||
|
||
rows = append(rows, row)
|
||
|
||
chars, err := p.DiscoverCharacteristics(nil, svc)
|
||
if err != nil {
|
||
mod.Error("error while enumerating chars for service %s: %s", svc.UUID(), err)
|
||
} else {
|
||
for _, ch := range chars {
|
||
props, isReadable, isWritable, withResponse := parseProperties(ch)
|
||
|
||
char := network.BLECharacteristic{
|
||
UUID: ch.UUID().String(),
|
||
Name: ch.Name(),
|
||
Handle: ch.VHandle(),
|
||
Properties: props,
|
||
}
|
||
|
||
mod.Session.Events.Add("ble.device.characteristic.discovered", ch)
|
||
|
||
name = ch.Name()
|
||
if name == "" {
|
||
name = " " + ch.UUID().String()
|
||
} else {
|
||
name = fmt.Sprintf(" %s (%s)", tui.Green(name), tui.Dim(ch.UUID().String()))
|
||
}
|
||
|
||
if wantsToWrite && mod.writeUUID.Equal(ch.UUID()) {
|
||
foundToWrite = true
|
||
if isWritable {
|
||
mod.Debug("writing %d bytes to characteristics %s ...", len(mod.writeData), mod.writeUUID)
|
||
} else {
|
||
mod.Warning("attempt to write %d bytes to non writable characteristics %s ...", len(mod.writeData), mod.writeUUID)
|
||
}
|
||
|
||
if err := p.WriteCharacteristic(ch, mod.writeData, !withResponse); err != nil {
|
||
mod.Error("error while writing: %s", err)
|
||
}
|
||
}
|
||
|
||
sz := 0
|
||
raw := ([]byte)(nil)
|
||
err := error(nil)
|
||
if isReadable {
|
||
if raw, err = p.ReadCharacteristic(ch); raw != nil {
|
||
sz = len(raw)
|
||
}
|
||
}
|
||
|
||
data := ""
|
||
multi := ([]string)(nil)
|
||
if err != nil {
|
||
data = tui.Red(err.Error())
|
||
} else if ch.Name() == "Appearance" && sz >= 2 {
|
||
data = parseAppearance(raw)
|
||
} else if ch.Name() == "PnP ID" && sz >= 7 {
|
||
multi = parsePNPID(raw)
|
||
} else if ch.Name() == "Peripheral Preferred Connection Parameters" && sz >= 8 {
|
||
multi = parseConnectionParams(raw)
|
||
} else if ch.Name() == "Peripheral Privacy Flag" && sz >= 1 {
|
||
data = parsePrivacyFlag(raw)
|
||
} else {
|
||
data = parseRawData(raw)
|
||
}
|
||
|
||
if ch.Name() == "Device Name" && data != "" && mod.currDevice.DeviceName == "" {
|
||
mod.currDevice.DeviceName = data
|
||
}
|
||
|
||
if multi == nil {
|
||
char.Data = data
|
||
rows = append(rows, []string{
|
||
fmt.Sprintf("%04x", ch.VHandle()),
|
||
name,
|
||
strings.Join(props, ", "),
|
||
data,
|
||
})
|
||
} else {
|
||
char.Data = multi
|
||
for i, m := range multi {
|
||
if i == 0 {
|
||
rows = append(rows, []string{
|
||
fmt.Sprintf("%04x", ch.VHandle()),
|
||
name,
|
||
strings.Join(props, ", "),
|
||
m,
|
||
})
|
||
} else {
|
||
rows = append(rows, []string{"", "", "", m})
|
||
}
|
||
}
|
||
}
|
||
|
||
service.Characteristics = append(service.Characteristics, char)
|
||
}
|
||
// blank row after every service, bleah style
|
||
rows = append(rows, []string{"", "", "", ""})
|
||
}
|
||
|
||
mod.currDevice.Services = append(mod.currDevice.Services, service)
|
||
}
|
||
|
||
if wantsToWrite && !foundToWrite {
|
||
mod.Error("writable characteristics %s not found.", mod.writeUUID)
|
||
} else {
|
||
tui.Table(mod.Session.Events.Stdout, columns, rows)
|
||
mod.Session.Refresh()
|
||
}
|
||
}
|