ble_show_services.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. // +build !windows
  2. package ble
  3. import (
  4. "encoding/binary"
  5. "fmt"
  6. "strconv"
  7. "strings"
  8. "github.com/bettercap/bettercap/network"
  9. "github.com/bettercap/gatt"
  10. "github.com/evilsocket/islazy/tui"
  11. )
  12. var appearances = map[uint16]string{
  13. 0: "Unknown",
  14. 64: "Generic Phone",
  15. 128: "Generic Computer",
  16. 192: "Generic Watch",
  17. 193: "Watch: Sports Watch",
  18. 256: "Generic Clock",
  19. 320: "Generic Display",
  20. 384: "Generic Remote Control",
  21. 448: "Generic Eye-glasses",
  22. 512: "Generic Tag",
  23. 576: "Generic Keyring",
  24. 640: "Generic Media Player",
  25. 704: "Generic Barcode Scanner",
  26. 768: "Generic Thermometer",
  27. 769: "Thermometer: Ear",
  28. 832: "Generic Heart rate Sensor",
  29. 833: "Heart Rate Sensor: Heart Rate Belt",
  30. 896: "Generic Blood Pressure",
  31. 897: "Blood Pressure: Arm",
  32. 898: "Blood Pressure: Wrist",
  33. 960: "Human Interface Device (HID)",
  34. 961: "Keyboard",
  35. 962: "Mouse",
  36. 963: "Joystick",
  37. 964: "Gamepad",
  38. 965: "Digitizer Tablet",
  39. 966: "Card Reader",
  40. 967: "Digital Pen",
  41. 968: "Barcode Scanner",
  42. 1024: "Generic Glucose Meter",
  43. 1088: "Generic: Running Walking Sensor",
  44. 1089: "Running Walking Sensor: In-Shoe",
  45. 1090: "Running Walking Sensor: On-Shoe",
  46. 1091: "Running Walking Sensor: On-Hip",
  47. 1152: "Generic: Cycling",
  48. 1153: "Cycling: Cycling Computer",
  49. 1154: "Cycling: Speed Sensor",
  50. 1155: "Cycling: Cadence Sensor",
  51. 1156: "Cycling: Power Sensor",
  52. 1157: "Cycling: Speed and Cadence Sensor",
  53. 1216: "Generic Control Device",
  54. 1217: "Switch",
  55. 1218: "Multi-switch",
  56. 1219: "Button",
  57. 1220: "Slider",
  58. 1221: "Rotary",
  59. 1222: "Touch-panel",
  60. 1280: "Generic Network Device",
  61. 1281: "Access Point",
  62. 1344: "Generic Sensor",
  63. 1345: "Motion Sensor",
  64. 1346: "Air Quality Sensor",
  65. 1347: "Temperature Sensor",
  66. 1348: "Humidity Sensor",
  67. 1349: "Leak Sensor",
  68. 1350: "Smoke Sensor",
  69. 1351: "Occupancy Sensor",
  70. 1352: "Contact Sensor",
  71. 1353: "Carbon Monoxide Sensor",
  72. 1354: "Carbon Dioxide Sensor",
  73. 1355: "Ambient Light Sensor",
  74. 1356: "Energy Sensor",
  75. 1357: "Color Light Sensor",
  76. 1358: "Rain Sensor",
  77. 1359: "Fire Sensor",
  78. 1360: "Wind Sensor",
  79. 1361: "Proximity Sensor",
  80. 1362: "Multi-Sensor",
  81. 1408: "Generic Light Fixtures",
  82. 1409: "Wall Light",
  83. 1410: "Ceiling Light",
  84. 1411: "Floor Light",
  85. 1412: "Cabinet Light",
  86. 1413: "Desk Light",
  87. 1414: "Troffer Light",
  88. 1415: "Pendant Light",
  89. 1416: "In-ground Light",
  90. 1417: "Flood Light",
  91. 1418: "Underwater Light",
  92. 1419: "Bollard with Light",
  93. 1420: "Pathway Light",
  94. 1421: "Garden Light",
  95. 1422: "Pole-top Light",
  96. 1423: "Spotlight",
  97. 1424: "Linear Light",
  98. 1425: "Street Light",
  99. 1426: "Shelves Light",
  100. 1427: "High-bay / Low-bay Light",
  101. 1428: "Emergency Exit Light",
  102. 1472: "Generic Fan",
  103. 1473: "Ceiling Fan",
  104. 1474: "Axial Fan",
  105. 1475: "Exhaust Fan",
  106. 1476: "Pedestal Fan",
  107. 1477: "Desk Fan",
  108. 1478: "Wall Fan",
  109. 1536: "Generic HVAC",
  110. 1537: "Thermostat",
  111. 1600: "Generic Air Conditioning",
  112. 1664: "Generic Humidifier",
  113. 1728: "Generic Heating",
  114. 1729: "Radiator",
  115. 1730: "Boiler",
  116. 1731: "Heat Pump",
  117. 1732: "Infrared Heater",
  118. 1733: "Radiant Panel Heater",
  119. 1734: "Fan Heater",
  120. 1735: "Air Curtain",
  121. 1792: "Generic Access Control",
  122. 1793: "Access Door",
  123. 1794: "Garage Door",
  124. 1795: "Emergency Exit Door",
  125. 1796: "Access Lock",
  126. 1797: "Elevator",
  127. 1798: "Window",
  128. 1799: "Entrance Gate",
  129. 1856: "Generic Motorized Device",
  130. 1857: "Motorized Gate",
  131. 1858: "Awning",
  132. 1859: "Blinds or Shades",
  133. 1860: "Curtains",
  134. 1861: "Screen",
  135. 1920: "Generic Power Device",
  136. 1921: "Power Outlet",
  137. 1922: "Power Strip",
  138. 1923: "Plug",
  139. 1924: "Power Supply",
  140. 1925: "LED Driver",
  141. 1926: "Fluorescent Lamp Gear",
  142. 1927: "HID Lamp Gear",
  143. 1984: "Generic Light Source",
  144. 1985: "Incandescent Light Bulb",
  145. 1986: "LED Bulb",
  146. 1987: "HID Lamp",
  147. 1988: "Fluorescent Lamp",
  148. 1989: "LED Array",
  149. 1990: "Multi-Color LED Array",
  150. 3136: "Generic: Pulse Oximeter",
  151. 3137: "Fingertip",
  152. 3138: "Wrist Worn",
  153. 3200: "Generic: Weight Scale",
  154. 3264: "Generic",
  155. 3265: "Powered Wheelchair",
  156. 3266: "Mobility Scooter",
  157. 3328: "Generic",
  158. 5184: "Generic: Outdoor Sports Activity",
  159. 5185: "Location Display Device",
  160. 5186: "Location and Navigation Display Device",
  161. 5187: "Location Pod",
  162. 5188: "Location and Navigation Pod",
  163. }
  164. func parseProperties(ch *gatt.Characteristic) (props []string, isReadable bool, isWritable bool, withResponse bool) {
  165. isReadable = false
  166. isWritable = false
  167. withResponse = false
  168. props = make([]string, 0)
  169. mask := ch.Properties()
  170. if (mask & gatt.CharBroadcast) != 0 {
  171. props = append(props, "BCAST")
  172. }
  173. if (mask & gatt.CharRead) != 0 {
  174. isReadable = true
  175. props = append(props, "READ")
  176. }
  177. if (mask&gatt.CharWriteNR) != 0 || (mask&gatt.CharWrite) != 0 {
  178. props = append(props, tui.Bold("WRITE"))
  179. isWritable = true
  180. withResponse = (mask & gatt.CharWriteNR) == 0
  181. }
  182. if (mask & gatt.CharNotify) != 0 {
  183. props = append(props, "NOTIFY")
  184. }
  185. if (mask & gatt.CharIndicate) != 0 {
  186. props = append(props, "INDICATE")
  187. }
  188. if (mask & gatt.CharSignedWrite) != 0 {
  189. props = append(props, tui.Yellow("SIGN WRITE"))
  190. isWritable = true
  191. withResponse = true
  192. }
  193. if (mask & gatt.CharExtended) != 0 {
  194. props = append(props, "X")
  195. }
  196. return
  197. }
  198. func parseRawData(raw []byte) string {
  199. s := ""
  200. for _, b := range raw {
  201. if strconv.IsPrint(rune(b)) {
  202. s += tui.Yellow(string(b))
  203. } else {
  204. s += tui.Dim(fmt.Sprintf("%02x", b))
  205. }
  206. }
  207. return s
  208. }
  209. // org.bluetooth.characteristic.gap.appearance
  210. func parseAppearance(raw []byte) string {
  211. app := binary.LittleEndian.Uint16(raw[0:2])
  212. if appName, found := appearances[app]; found {
  213. return tui.Green(appName)
  214. }
  215. return fmt.Sprintf("0x%x", app)
  216. }
  217. // org.bluetooth.characteristic.pnp_id
  218. func parsePNPID(raw []byte) []string {
  219. vendorIdSrc := byte(raw[0])
  220. vendorId := binary.LittleEndian.Uint16(raw[1:3])
  221. prodId := binary.LittleEndian.Uint16(raw[3:5])
  222. prodVer := binary.LittleEndian.Uint16(raw[5:7])
  223. src := ""
  224. if vendorIdSrc == 1 {
  225. src = " (Bluetooth SIG assigned Company Identifier)"
  226. } else if vendorIdSrc == 2 {
  227. src = " (USB Implementer’s Forum assigned Vendor ID value)"
  228. }
  229. return []string{
  230. tui.Green("Vendor ID") + fmt.Sprintf(": 0x%04x%s", vendorId, tui.Dim(src)),
  231. tui.Green("Product ID") + fmt.Sprintf(": 0x%04x", prodId),
  232. tui.Green("Product Version") + fmt.Sprintf(": 0x%04x", prodVer),
  233. }
  234. }
  235. // org.bluetooth.characteristic.gap.peripheral_preferred_connection_parameters
  236. func parseConnectionParams(raw []byte) []string {
  237. minConInt := binary.LittleEndian.Uint16(raw[0:2])
  238. maxConInt := binary.LittleEndian.Uint16(raw[2:4])
  239. slaveLat := binary.LittleEndian.Uint16(raw[4:6])
  240. conTimeMul := binary.LittleEndian.Uint16(raw[6:8])
  241. return []string{
  242. tui.Green("Connection Interval") + fmt.Sprintf(": %d -> %d", minConInt, maxConInt),
  243. tui.Green("Slave Latency") + fmt.Sprintf(": %d", slaveLat),
  244. tui.Green("Connection Supervision Timeout Multiplier") + fmt.Sprintf(": %d", conTimeMul),
  245. }
  246. }
  247. // org.bluetooth.characteristic.gap.peripheral_privacy_flag
  248. func parsePrivacyFlag(raw []byte) string {
  249. if raw[0] == 0x0 {
  250. return tui.Green("Privacy Disabled")
  251. }
  252. return tui.Red("Privacy Enabled")
  253. }
  254. func (mod *BLERecon) showServices(p gatt.Peripheral, services []*gatt.Service) {
  255. columns := []string{"Handles", "Service > Characteristics", "Properties", "Data"}
  256. rows := make([][]string, 0)
  257. wantsToWrite := mod.writeUUID != nil
  258. foundToWrite := false
  259. mod.currDevice.Services = make([]network.BLEService, 0)
  260. for _, svc := range services {
  261. service := network.BLEService{
  262. UUID: svc.UUID().String(),
  263. Name: svc.Name(),
  264. Handle: svc.Handle(),
  265. EndHandle: svc.EndHandle(),
  266. Characteristics: make([]network.BLECharacteristic, 0),
  267. }
  268. mod.Session.Events.Add("ble.device.service.discovered", svc)
  269. name := svc.Name()
  270. if name == "" {
  271. name = svc.UUID().String()
  272. } else {
  273. name = fmt.Sprintf("%s (%s)", tui.Green(name), tui.Dim(svc.UUID().String()))
  274. }
  275. row := []string{
  276. fmt.Sprintf("%04x -> %04x", svc.Handle(), svc.EndHandle()),
  277. name,
  278. "",
  279. "",
  280. }
  281. rows = append(rows, row)
  282. chars, err := p.DiscoverCharacteristics(nil, svc)
  283. if err != nil {
  284. mod.Error("error while enumerating chars for service %s: %s", svc.UUID(), err)
  285. } else {
  286. for _, ch := range chars {
  287. props, isReadable, isWritable, withResponse := parseProperties(ch)
  288. char := network.BLECharacteristic{
  289. UUID: ch.UUID().String(),
  290. Name: ch.Name(),
  291. Handle: ch.VHandle(),
  292. Properties: props,
  293. }
  294. mod.Session.Events.Add("ble.device.characteristic.discovered", ch)
  295. name = ch.Name()
  296. if name == "" {
  297. name = " " + ch.UUID().String()
  298. } else {
  299. name = fmt.Sprintf(" %s (%s)", tui.Green(name), tui.Dim(ch.UUID().String()))
  300. }
  301. if wantsToWrite && mod.writeUUID.Equal(ch.UUID()) {
  302. foundToWrite = true
  303. if isWritable {
  304. mod.Debug("writing %d bytes to characteristics %s ...", len(mod.writeData), mod.writeUUID)
  305. } else {
  306. mod.Warning("attempt to write %d bytes to non writable characteristics %s ...", len(mod.writeData), mod.writeUUID)
  307. }
  308. if err := p.WriteCharacteristic(ch, mod.writeData, !withResponse); err != nil {
  309. mod.Error("error while writing: %s", err)
  310. }
  311. }
  312. sz := 0
  313. raw := ([]byte)(nil)
  314. err := error(nil)
  315. if isReadable {
  316. if raw, err = p.ReadCharacteristic(ch); raw != nil {
  317. sz = len(raw)
  318. }
  319. }
  320. data := ""
  321. multi := ([]string)(nil)
  322. if err != nil {
  323. data = tui.Red(err.Error())
  324. } else if ch.Name() == "Appearance" && sz >= 2 {
  325. data = parseAppearance(raw)
  326. } else if ch.Name() == "PnP ID" && sz >= 7 {
  327. multi = parsePNPID(raw)
  328. } else if ch.Name() == "Peripheral Preferred Connection Parameters" && sz >= 8 {
  329. multi = parseConnectionParams(raw)
  330. } else if ch.Name() == "Peripheral Privacy Flag" && sz >= 1 {
  331. data = parsePrivacyFlag(raw)
  332. } else {
  333. data = parseRawData(raw)
  334. }
  335. if ch.Name() == "Device Name" && data != "" && mod.currDevice.DeviceName == "" {
  336. mod.currDevice.DeviceName = data
  337. }
  338. if multi == nil {
  339. char.Data = data
  340. rows = append(rows, []string{
  341. fmt.Sprintf("%04x", ch.VHandle()),
  342. name,
  343. strings.Join(props, ", "),
  344. data,
  345. })
  346. } else {
  347. char.Data = multi
  348. for i, m := range multi {
  349. if i == 0 {
  350. rows = append(rows, []string{
  351. fmt.Sprintf("%04x", ch.VHandle()),
  352. name,
  353. strings.Join(props, ", "),
  354. m,
  355. })
  356. } else {
  357. rows = append(rows, []string{"", "", "", m})
  358. }
  359. }
  360. }
  361. service.Characteristics = append(service.Characteristics, char)
  362. }
  363. // blank row after every service, bleah style
  364. rows = append(rows, []string{"", "", "", ""})
  365. }
  366. mod.currDevice.Services = append(mod.currDevice.Services, service)
  367. }
  368. if wantsToWrite && !foundToWrite {
  369. mod.Error("writable characteristics %s not found.", mod.writeUUID)
  370. } else {
  371. tui.Table(mod.Session.Events.Stdout, columns, rows)
  372. mod.Session.Refresh()
  373. }
  374. }