1#!/usr/bin/env python 2# 3# Copyright 2009 Joshua Wright, Michael Ossmann 4# 5# This file is part of gr-bluetooth 6# 7# gr-bluetooth is free software; you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation; either version 2, or (at your option) 10# any later version. 11# 12# gr-bluetooth is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with gr-bluetooth; see the file COPYING. If not, write to 19# the Free Software Foundation, Inc., 51 Franklin Street, 20# Boston, MA 02110-1301, USA. 21 22import sys 23import struct 24import time 25from pcapdump.pcapdump import * 26 27DLT_EN10MB = 1 28DLT_BLUETOOTH_HCI_H4 = 187 29USBHID_MAP = { 30 0x04 : "a", 31 0x05 : "b", 32 0x06 : "c", 33 0x07 : "d", 34 0x08 : "e", 35 0x09 : "f", 36 0x0A : "g", 37 0x0B : "h", 38 0x0C : "i", 39 0x0D : "j", 40 0x0E : "k", 41 0x0F : "l", 42 0x10 : "m", 43 0x11 : "n", 44 0x12 : "o", 45 0x13 : "p", 46 0x14 : "q", 47 0x15 : "r", 48 0x16 : "s", 49 0x17 : "t", 50 0x18 : "u", 51 0x19 : "v", 52 0x1A : "w", 53 0x1B : "x", 54 0x1C : "y", 55 0x1D : "z", 56 0x1E : "1", 57 0x1F : "2", 58 0x20 : "3", 59 0x21 : "4", 60 0x22 : "5", 61 0x23 : "6", 62 0x24 : "7", 63 0x25 : "8", 64 0x26 : "9", 65 0x27 : "0", 66 0x28 : "[Return]\n", 67 0x29 : "[Esc]", 68 0x2A : "[Backspace]", 69 0x2B : "[Tab]\t", 70 0x2C : " ", 71 0x2D : "-", 72 0x2E : "=", 73 0x2F : "[", 74 0x30 : "]", 75 0x31 : "\\", 76 0x32 : "#", 77 0x33 : ";", 78 0x34 : "'", 79 0x35 : "[Grave Accent]", 80 0x36 : ",", 81 0x37 : ".", 82 0x38 : "/", 83 0x39 : "[Caps Lock]", 84 0x3A : "[F1]", 85 0x3B : "[F2]", 86 0x3C : "[F3]", 87 0x3D : "[F4]", 88 0x3E : "[F5]", 89 0x3F : "[F6]", 90 0x40 : "[F7]", 91 0x41 : "[F8]", 92 0x42 : "[F9]", 93 0x43 : "[F10]", 94 0x44 : "[F11]", 95 0x45 : "[F12]", 96 0x46 : "[PrintScreen]", 97 0x47 : "[Scroll]", 98 0x48 : "[Pause]", 99 0x49 : "[Insert]", 100 0x4A : "[Home]", 101 0x4B : "[PageUp]", 102 0x4C : "[Delete]", 103 0x4D : "[End]", 104 0x4E : "[PageDown]", 105 0x4F : "[RightArrow]", 106 0x50 : "[LeftArrow]", 107 0x51 : "[DownArrow]", 108 0x52 : "[UpArrow]", 109 0x53 : "[Keypad Num Lock and Clear]", 110 0x54 : "[Keypad /]", 111 0x55 : "[Keypad *]", 112 0x56 : "[Keypad -]", 113 0x57 : "[Keypad +]", 114 0x58 : "[Keypad Enter]\n", 115 0x59 : "[Keypad 1 and End]", 116 0x5A : "[Keypad 2 and Down Arrow]", 117 0x5B : "[Keypad 3 and PageDn]", 118 0x5C : "[Keypad 4 and Left Arrow]", 119 0x5D : "[Keypad 5]", 120 0x5E : "[Keypad 6 and Right Arrow]", 121 0x5F : "[Keypad 7 and Home]", 122 0x60 : "[Keypad 8 and Up Arrow]", 123 0x61 : "[Keypad 9 and PageUp]", 124 0x62 : "[Keypad 0 and Insert]", 125 0x63 : "[Keypad . and Delete]", 126 0x64 : "\\", 127 0x65 : "[WinKey]", 128 0x66 : "[Power9]", 129 0x67 : "[Keypad =]", 130 0x68 : "[F13]", 131 0x69 : "[F14]", 132 0x6A : "[F15]", 133 0x6B : "[F16]", 134 0x6C : "[F17]", 135 0x6D : "[F18]", 136 0x6E : "[F19]", 137 0x6F : "[F20]", 138 0x70 : "[F21]", 139 0x71 : "[F22]", 140 0x72 : "[F23]", 141 0x73 : "[F24]", 142 0x74 : "[Execute]", 143 0x75 : "[Help]", 144 0x76 : "[Menu]", 145 0x77 : "[Select]", 146 0x78 : "[Stop]", 147 0x79 : "[Again]", 148 0x7A : "[Undo]", 149 0x7B : "[Cut]", 150 0x7C : "[Copy]", 151 0x7D : "[Paste]", 152 0x7E : "[Find]", 153 0x7F : "[Mute]", 154 0x80 : "[Volume Up]", 155 0x81 : "[Volume Down]", 156 0x82 : "[Locking Caps Lock]", 157 0x83 : "[Locking Num Lock]", 158 0x84 : "[Locking Scroll Lock]", 159 0x85 : "[Keypad Comma]", 160 0x86 : "[Keypad Equal]", 161 0x87 : "[International1]", 162 0x88 : "[International2]", 163 0x89 : "[International3]", 164 0x8A : "[International4]", 165 0x8B : "[International5]", 166 0x8C : "[International6]", 167 0x8D : "[International7]", 168 0x8E : "[International8]", 169 0x8F : "[International9]", 170 0x90 : "[LANG1]", 171 0x91 : "[LANG2]", 172 0x92 : "[LANG3]", 173 0x93 : "[LANG4]", 174 0x94 : "[LANG5]", 175 0x95 : "[LANG6]", 176 0x96 : "[LANG7]", 177 0x97 : "[LANG8]", 178 0x98 : "[LANG9]", 179 0x99 : "[Alternate Erase]", 180 0x9A : "[SysReq/Attention]", 181 0x9B : "[Cancel]", 182 0x9C : "[Clear]", 183 0x9D : "[Prior]", 184 0x9E : "[Return]\n", 185 0x9F : "[Separator]", 186 0xA0 : "[Out]", 187 0xA1 : "[Oper]", 188 0xA2 : "[Clear/Again]", 189 0xA3 : "[CrSel/Props]", 190 0xA4 : "[ExSel]", 191 0xB0 : "[Keypad 00]", 192 0xB1 : "[Keypad 000]", 193 0xB2 : "[Thousands Separator]", 194 0xB3 : "[Decimal Separator]", 195 0xB4 : "[Currency Unit]", 196 0xB5 : "[Currency Sub-unit]", 197 0xB6 : "[Keypad (]", 198 0xB7 : "[Keypad )]", 199 0xB8 : "[Keypad {]", 200 0xB9 : "[Keypad }]", 201 0xBA : "[Keypad Tab]\t", 202 0xBB : "[Keypad Backspace]", 203 0xBC : "[Keypad A]", 204 0xBD : "[Keypad B]", 205 0xBE : "[Keypad C]", 206 0xBF : "[Keypad D]", 207 0xC0 : "[Keypad E]", 208 0xC1 : "[Keypad F]", 209 0xC2 : "[Keypad XOR]", 210 0xC3 : "[Keypad ^]", 211 0xC4 : "[Keypad %]", 212 0xC5 : "[Keypad <]", 213 0xC6 : "[Keypad >]", 214 0xC7 : "[Keypad &]", 215 0xC8 : "[Keypad &&]", 216 0xC9 : "[Keypad |]", 217 0xCA : "[Keypad ||]", 218 0xCB : "[Keypad :]", 219 0xCC : "[Keypad #]", 220 0xCD : "[Keypad Space]", 221 0xCE : "[Keypad @]", 222 0xCF : "[Keypad !]", 223 0xD0 : "[Keypad Memory Store]", 224 0xD1 : "[Keypad Memory Recall]", 225 0xD2 : "[Keypad Memory Clear]", 226 0xD3 : "[Keypad Memory Add]", 227 0xD4 : "[Keypad Memory Subtract]", 228 0xD5 : "[Keypad Memory Multiply]", 229 0xD6 : "[Keypad Memory Divide]", 230 0xD7 : "[Keypad +/-]", 231 0xD8 : "[Keypad Clear]", 232 0xD9 : "[Keypad Clear Entry]", 233 0xDA : "[Keypad Binary]", 234 0xDB : "[Keypad Octal]", 235 0xDC : "[Keypad Decimal]", 236 0xDD : "[Keypad Hexadecimal]", 237 0xE0 : "[LeftControl]", 238 0xE1 : "[LeftShift]", 239 0xE2 : "[LeftAlt]", 240 0xE3 : "[LeftWinKey]", 241 0xE4 : "[RightControl]", 242 0xE5 : "[RightShift]", 243 0xE6 : "[RightAlt]", 244 0xE7 : "[RightWinKey]" 245} 246 247# some keycodes represent different things when Shift is held down 248USBHID_SHIFT_MAP = { 249 0x04 : "A", 250 0x05 : "B", 251 0x06 : "C", 252 0x07 : "D", 253 0x08 : "E", 254 0x09 : "F", 255 0x0A : "G", 256 0x0B : "H", 257 0x0C : "I", 258 0x0D : "J", 259 0x0E : "K", 260 0x0F : "L", 261 0x10 : "M", 262 0x11 : "N", 263 0x12 : "O", 264 0x13 : "P", 265 0x14 : "Q", 266 0x15 : "R", 267 0x16 : "S", 268 0x17 : "T", 269 0x18 : "U", 270 0x19 : "V", 271 0x1A : "W", 272 0x1B : "X", 273 0x1C : "Y", 274 0x1D : "Z", 275 0x1E : "!", 276 0x1F : "@", 277 0x20 : "#", 278 0x21 : "$", 279 0x22 : "%", 280 0x23 : "^", 281 0x24 : "&", 282 0x25 : "*", 283 0x26 : "(", 284 0x27 : ")", 285 0x2D : "_", 286 0x2E : "+", 287 0x2F : "{", 288 0x30 : "}", 289 0x31 : "|", 290 0x32 : "~", 291 0x33 : ":", 292 0x34 : "\"", 293 0x35 : "~", 294 0x36 : "<", 295 0x37 : ">", 296 0x38 : "?", 297 0x64 : "|" 298} 299 300# global variable to track currently depressed keys 301active_keys = [] 302 303def hid2ascii(scancode, shift): 304 ''' 305 Convert the specified scancode value to the ASCII equivalent using the 306 USBHID_MAP list. 307 ''' 308 if shift: 309 try: 310 code = USBHID_SHIFT_MAP[scancode] 311 return code 312 except KeyError: 313 pass 314 try: 315 code = USBHID_MAP[scancode] 316 except KeyError: 317 return "[Reserved]" 318 return code 319 320def usage(): 321 print >>sys.stderr, "Usage: btaptap [-r pcapfile.pcap] [-c count] [-h]\n" 322 sys.exit(0) 323 324def parse_l2cap_keydata(l2cappkt): 325 global active_keys 326 327 TRANS_HDR_IN_DATA = 0xA1 328 REPORT_ID_KEYBOARD = 0x01 329 CTRL = 1 330 SHIFT = 2 331 ALT = 4 332 GUI = 8 333 334 # Keyboard keystrokes are only seen in L2CAP packets at least 10 bytes long 335 l2clen = (ord(l2cappkt[1]) << 8) | ord(l2cappkt[0]) 336 if l2clen < 10: 337 return 338 339 # Keyboard keystrokes are only carried by Channel ID >= 0x40 340 cid = (ord(l2cappkt[3]) << 8) | ord(l2cappkt[2]) 341 if cid < 0x40: 342 return 343 # Ideally we would check for the particular CID for the HID_INTERRUPT 344 # channel, but we don't handle the negotiation (and may not have even 345 # seen it). 346 347 # Transaction Header should indicate input data 348 thdr = ord(l2cappkt[4]) 349 if thdr != TRANS_HDR_IN_DATA: 350 return 351 352 # Report ID should indicate this is a keyboard 353 rid = ord(l2cappkt[5]) 354 if rid != REPORT_ID_KEYBOARD: 355 return 356 357 # This byte describes modifier key status (one bit per key) 358 mod = ord(l2cappkt[6]) 359 360 # We don't care whether left or right modifier keys are pressed, so we 361 # combine the status bits. 362 leftmod = mod & 0x0f 363 rightmod = (mod & 0xf0) >> 4 364 mod = leftmod | rightmod 365 366 # up to six keys can be reported at once 367 keycodes = [] 368 for byte in range(8,14): 369 keystroke = ord(l2cappkt[byte]) 370 if keystroke != 0x00: 371 keycodes.append(keystroke) 372 373 for keystroke in keycodes: 374 # don't repeat keys that are still held down 375 if active_keys.count(keystroke) == 0: 376 377 if (mod & CTRL): 378 sys.stdout.write("CTRL^") 379 if (mod & ALT): 380 sys.stdout.write("ALT^") 381 if (mod & GUI): # e.g. Windows key 382 sys.stdout.write("GUI^") 383 384 sys.stdout.write(hid2ascii(keystroke, mod & SHIFT)) 385 386 active_keys = keycodes 387 388def parse_bb_keydata(packet): 389 390 BTBBHDR_TYPE_MASK = 0x78 391 BTBBHDR_TYPE_SHIFT = 3 392 BTBBHDR_TYPE_DM1 = 3 393 BTBBPAYLOADHDR_LLID_MASK = 0x03 394 BTBBPAYLOADHDR_LLID_SHIFT = 0 395 BTBBPAYLOADHDR_LEN_MASK = 0xF8 396 BTBBPAYLOADHDR_LEN_SHIFT = 3 397 LLID_L2CAP = 2 398 399 # Keyboard keystrokes are only seen in frames at least 40 bytes long 400 if len(packet) < 40: 401 return 402 403 # Keyboard keystrokes are only seen in DM1 frames 404 btbbhdr = packet[20:23] 405 type = (ord(btbbhdr[0]) & BTBBHDR_TYPE_MASK) >> BTBBHDR_TYPE_SHIFT 406 if type != BTBBHDR_TYPE_DM1: 407 return 408 409 # Keyboard keystrokes are only seen in L2CAP packets 14 bytes long 410 btbbpayloadhdr = ord(packet[23]) 411 llid = btbbpayloadhdr & (BTBBPAYLOADHDR_LLID_MASK) >> BTBBPAYLOADHDR_LLID_SHIFT 412 l2clen = (btbbpayloadhdr & BTBBPAYLOADHDR_LEN_MASK) >> BTBBPAYLOADHDR_LEN_SHIFT 413 #print "Debug btbbpayloadhdr 0x%02x, llid %d, l2clen %d"%(btbbpayloadhdr, llid, l2clen) 414 if llid != LLID_L2CAP or l2clen < 14: 415 return 416 417 parse_l2cap_keydata(packet[24:38]) 418 419def parse_hci_keydata(packet): 420 421 HCI_TYPE_ACL_DATA = 2 422 423 # Keyboard keystrokes are only seen in frames at least 19 bytes long 424 if len(packet) < 19: 425 return 426 427 # Keyboard keystrokes are only seen in ACL Data frames 428 type = ord(packet[0]) 429 if type != HCI_TYPE_ACL_DATA: 430 return 431 432 parse_l2cap_keydata(packet[5:]) 433 434if __name__ == '__main__': 435 436 arg_pcapfile = None 437 arg_count = -1 438 packetcount = 0 439 440 while len(sys.argv) > 1: 441 op = sys.argv.pop(1) 442 if op == '-r': 443 arg_pcapfile = sys.argv.pop(1) 444 if op == '-c': 445 arg_count = int(sys.argv.pop(1)) 446 if op == '-h': 447 usage() 448 sys.exit(0) 449 450 if arg_pcapfile == None: 451 print >>sys.stderr, "Must specify a libpcap filename." 452 usage() 453 sys.exit(1) 454 455 cap = PcapReader(arg_pcapfile) 456 457 while arg_count != packetcount: 458 try: 459 (pheader, packet) = cap.pnext() 460 pkttime = pheader[0] 461 packetcount+=1 462 463 if cap.datalink() == DLT_EN10MB: 464 parse_bb_keydata(packet) 465 elif cap.datalink() == DLT_BLUETOOTH_HCI_H4: 466 parse_hci_keydata(packet) 467 else: 468 print >>sys.stderr, "Unsupported libpcap data link layer: %d\n" % cap.datalink() 469 except TypeError: # raised when pnext returns Null (end of capture) 470 break 471 472 cap.close() 473 print 474