123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411 |
- // +build !windows
- package ble
- import (
- "encoding/binary"
- "fmt"
- "strconv"
- "strings"
- "github.com/bettercap/bettercap/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()
- }
- }
|