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

413 lines
11 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//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 Implementers 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()
}
}