You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

239 lines
10 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. #!/usr/bin/python
  2. #
  3. # ble-dump: SDR Bluetooth LE packet dumper
  4. #
  5. # Copyright (C) 2016 Jan Wagner <mail@jwagner.eu>
  6. #
  7. # This program 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 of the License, or
  10. # (at your option) any later version.
  11. #
  12. from grc.gr_ble import gr_ble as gr_block
  13. from optparse import OptionParser, OptionGroup
  14. from gnuradio.eng_option import eng_option
  15. from datetime import datetime, timedelta
  16. from proto import *
  17. # Print current Gnu Radio capture settings
  18. def print_settings(gr, opts):
  19. print '\n ble-dump: SDR Bluetooth LE packet dumper'
  20. print '\nCapture settings:'
  21. print ' %-22s: %s Hz' % ('Base Frequency', '{:d}'.format(int(gr.get_ble_base_freq())))
  22. print ' %-22s: %s Hz' % ('Sample rate', '{:d}'.format(int(gr.get_sample_rate())))
  23. print ' %-22s: %s dB' % ('Squelch threshold', '{:d}'.format(int(gr.get_squelch_threshold())))
  24. print '\nLow-pass filter:'
  25. print ' %-22s: %s Hz' % ('Cutoff frequency', '{:d}'.format(int(gr.get_cutoff_freq())))
  26. print ' %-22s: %s Hz' % ('Transition width', '{:d}'.format(int(gr.get_transition_width())))
  27. print '\nGMSK demodulation:'
  28. print ' %-22s: %s' % ('Samples per Symbol', '{:.4f}'.format(gr.get_gmsk_sps()))
  29. print ' %-22s: %s' % ('Gain Mu', '{:.4f}'.format(gr.get_gmsk_gain_mu()))
  30. print ' %-22s: %s' % ('Mu', '{:,}'.format(gr.get_gmsk_mu()))
  31. print ' %-22s: %s' % ('Omega Limit', '{:.4f}'.format(gr.get_gmsk_omega_limit()))
  32. print '\nBluetooth LE:'
  33. print ' %-22s: %s' % ('Scanning Channels', '{:s}'.format(opts.current_ble_channels.replace(',', ', ')))
  34. print ' %-22s: %ss' % ('Scanning Window', '{:.2f}'.format(opts.ble_scan_window))
  35. print ' %-22s: %s' % ('Disable CRC check', '{0}'.format(opts.disable_crc))
  36. print ' %-22s: %s' % ('Disable De-Whitening', '{0}'.format(opts.disable_dewhitening))
  37. print '\n%-23s: %s\n' % ('PCAP output file', '{:s}'.format(opts.pcap_file))
  38. if opts.raw_replay_file:
  39. print '%-23s: %s\n' % ('Raw replay input file', '{:s}'.format(opts.raw_replay_file))
  40. if opts.raw_capture_file:
  41. print '%-23s: %s\n' % ('Raw capture output file', '{:s}'.format(opts.raw_capture_file))
  42. # Setup Gnu Radio with defined command line arguments
  43. def init_args(gr, opts):
  44. gr.set_sample_rate(int(opts.sample_rate))
  45. gr.set_squelch_threshold(int(opts.squelch_threshold))
  46. gr.set_cutoff_freq(int(opts.cutoff_freq))
  47. gr.set_transition_width(int(opts.transition_width))
  48. gr.set_gmsk_sps(opts.samples_per_symbol)
  49. gr.set_gmsk_gain_mu(opts.gain_mu)
  50. gr.set_gmsk_mu(opts.mu)
  51. gr.set_gmsk_omega_limit(opts.omega_limit)
  52. gr.set_ble_channel(int(opts.scan_channels[0]))
  53. # Initialize command line arguments
  54. def init_opts(gr):
  55. parser = OptionParser(option_class=eng_option, usage="%prog: [opts]")
  56. # Capture
  57. capture = OptionGroup(parser, 'Capture settings')
  58. capture.add_option("-o", "--pcap_file", type="string", default='', help="PCAP output file or named pipe (FIFO)")
  59. capture.add_option("-m", "--min_buffer_size", type="int", default=65, help="Minimum buffer size [default=%default]")
  60. capture.add_option("-s", "--sample-rate", type="eng_float", default=gr.sample_rate, help="Sample rate [default=%default]")
  61. capture.add_option("-t", "--squelch_threshold", type="eng_float", default=gr.squelch_threshold, help="Squelch threshold (simple squelch) [default=%default]")
  62. capture.add_option("", "--pcap_fifo_file", type="string", default='', help="Secondary PCAP output file, preferrably a FIFO file [default=%default]")
  63. capture.add_option("", "--raw_replay_file", type="string", default='', help="Replays a raw capture [default=%default]")
  64. capture.add_option("", "--raw_capture_file", type="string", default='', help="Captures raw data to this file [default=%default]")
  65. # Low Pass filter
  66. filters = OptionGroup(parser, 'Low-pass filter:')
  67. filters.add_option("-C", "--cutoff_freq", type="eng_float", default=gr.cutoff_freq, help="Filter cutoff [default=%default]")
  68. filters.add_option("-T", "--transition_width", type="eng_float", default=gr.transition_width, help="Filter transition width [default=%default]")
  69. # GMSK demodulation
  70. gmsk = OptionGroup(parser, 'GMSK demodulation:')
  71. gmsk.add_option("-S", "--samples_per_symbol", type="eng_float", default=gr.gmsk_sps, help="Samples per symbol [default=%default]")
  72. gmsk.add_option("-G", "--gain_mu", type="eng_float", default=gr.gmsk_gain_mu, help="Gain mu [default=%default]")
  73. gmsk.add_option("-M", "--mu", type="eng_float", default=gr.gmsk_mu, help="Mu [default=%default]")
  74. gmsk.add_option("-O", "--omega_limit", type="eng_float", default=gr.gmsk_omega_limit, help="Omega limit [default=%default]")
  75. # Bluetooth L
  76. ble= OptionGroup(parser, 'Bluetooth LE:')
  77. ble.add_option("-c", "--current_ble_channels", type="string", default='37,38,39', help="BLE channels to scan [default=%default]")
  78. ble.add_option("-w", "--ble_scan_window", type="eng_float", default=10.24, help="BLE scan window [default=%default]")
  79. ble.add_option("-x", "--disable_crc", action="store_true", default=False, help="Disable CRC verification [default=%default]")
  80. ble.add_option("-y", "--disable_dewhitening", action="store_true", default=False, help="Disable De-Whitening [default=%default]")
  81. parser.add_option_group(capture)
  82. parser.add_option_group(filters)
  83. parser.add_option_group(gmsk)
  84. parser.add_option_group(ble)
  85. return parser.parse_args()
  86. if __name__ == '__main__':
  87. MIN_BUFFER_LEN = 65
  88. # Initialize Gnu Radio
  89. gr_block = gr_block()
  90. gr_block.start()
  91. # Initialize command line arguments
  92. (opts, args) = init_opts(gr_block)
  93. if not opts.pcap_file:
  94. print '\nerror: please specify pcap output file (-p)'
  95. exit(1)
  96. # Verify BLE channels argument
  97. if ',' not in opts.current_ble_channels:
  98. opts.current_ble_channels += ','
  99. # Prepare BLE channels argument
  100. opts.scan_channels = [int(x) for x in opts.current_ble_channels.split(',')]
  101. # Set Gnu Radio opts
  102. init_args(gr_block, opts)
  103. # Print capture settings
  104. print_settings(gr_block, opts)
  105. # Open PCAP file descriptor
  106. pcap_fd = open_pcap(opts.pcap_file)
  107. pcap_fifo_fd = open_pcap(opts.pcap_fifo_file) if opts.pcap_fifo_file else None
  108. raw_capture_fd = open_pcap(opts.raw_capture_file) if opts.raw_capture_file else None
  109. raw_replay_fd = open(opts.raw_replay_file, "rb") if opts.raw_replay_file else None
  110. current_hop = 1
  111. hopping_time = datetime.now() + timedelta(seconds=opts.ble_scan_window)
  112. # Set initial BLE channel
  113. current_ble_chan = opts.scan_channels[0]
  114. gr_block.set_ble_channel(BLE_CHANS[current_ble_chan])
  115. # Prepare Gnu Radio receive buffers
  116. gr_buffer = ''
  117. lost_data = ''
  118. print 'Capturing on BLE channel [ {:d} ] @ {:d} MHz'.format(current_ble_chan, int(gr_block.get_freq() / 1000000))
  119. try:
  120. while True:
  121. # Move to the next BLE scanning channel
  122. if datetime.now() >= hopping_time:
  123. current_ble_chan = opts.scan_channels[current_hop % len(opts.scan_channels)]
  124. gr_block.set_ble_channel(BLE_CHANS[current_ble_chan])
  125. hopping_time = datetime.now() + timedelta(seconds=opts.ble_scan_window)
  126. current_hop +=1
  127. print 'Switching to BLE channel [ {:d} ] @ {:d} MHz'.format(current_ble_chan, int(gr_block.get_freq() / 1000000))
  128. # Fetch data from Gnu Radio message queue
  129. if raw_replay_fd != None:
  130. chunk = raw_replay_fd.read(1024)
  131. else:
  132. chunk = gr_block.message_queue.delete_head().to_string()
  133. gr_buffer += chunk
  134. if raw_capture_fd != None:
  135. raw_capture_fd.write(chunk)
  136. raw_capture_fd.flush()
  137. if len(gr_buffer) > opts.min_buffer_size:
  138. # Prepend lost data
  139. if len(lost_data) > 0:
  140. gr_buffer = ''.join(str(x) for x in lost_data) + gr_buffer
  141. lost_data = ''
  142. # Search for BLE_PREAMBLE in received data
  143. for pos in [position for position, byte, in enumerate(gr_buffer) if byte == BLE_PREAMBLE]:
  144. pos += BLE_PREAMBLE_LEN
  145. # Check enough data is available for parsing the BLE Access Address
  146. if len(gr_buffer[pos:]) < (BLE_ADDR_LEN + BLE_PDU_HDR_LEN):
  147. continue
  148. # Extract BLE Access Address
  149. ble_access_address = unpack('I', gr_buffer[pos:pos + BLE_ADDR_LEN])[0]
  150. pos += BLE_ADDR_LEN
  151. # Dewhitening received BLE Header
  152. if opts.disable_dewhitening == False:
  153. ble_header = dewhitening(gr_buffer[pos:pos + BLE_PDU_HDR_LEN], current_ble_chan)
  154. else:
  155. ble_header = gr_buffer[pos:pos + BLE_PDU_HDR_LEN]
  156. # Check BLE PDU type
  157. ble_pdu_type = ble_header[0] & 0x0f
  158. if ble_pdu_type not in BLE_PDU_TYPE.values():
  159. continue
  160. if ble_access_address == BLE_ACCESS_ADDR:
  161. # Extract BLE Length
  162. ble_len = ble_header[1] & 0x3f
  163. else:
  164. ble_llid = ble_header[0] & 0x3
  165. if ble_llid == 0:
  166. continue
  167. # Extract BLE Length
  168. ble_len = ble_header[1] & 0x1f
  169. # Dewhitening BLE packet
  170. if opts.disable_dewhitening == False:
  171. ble_data = dewhitening(gr_buffer[pos:pos + BLE_PDU_HDR_LEN + BLE_CRC_LEN + ble_len], current_ble_chan)
  172. else:
  173. ble_data = gr_buffer[pos:pos + BLE_PDU_HDR_LEN + BLE_CRC_LEN + ble_len]
  174. # Verify BLE data length
  175. if len(ble_data) != (BLE_PDU_HDR_LEN + BLE_CRC_LEN + ble_len):
  176. lost_data = gr_buffer[pos - BLE_PREAMBLE_LEN - BLE_ADDR_LEN:pos + BLE_PREAMBLE_LEN + BLE_ADDR_LEN + BLE_PDU_HDR_LEN + BLE_CRC_LEN + ble_len]
  177. continue
  178. # Verify BLE packet checksum
  179. if opts.disable_crc == False:
  180. if ble_data[-3:] != crc(ble_data, BLE_PDU_HDR_LEN + ble_len):
  181. continue
  182. # Write BLE packet to PCAP file descriptor
  183. write_pcap(pcap_fd, pcap_fifo_fd, current_ble_chan, ble_access_address, ble_data)
  184. gr_buffer = ''
  185. except KeyboardInterrupt:
  186. pass
  187. if raw_capture_fd != None:
  188. raw_capture_fd.close()
  189. if raw_replay_fd != None:
  190. raw_replay_fd.close()
  191. pcap_fd.close()
  192. gr_block.stop()
  193. gr_block.wait()