Projects STRLCPY CatSniffer Commits 35bc718d
🤬
  • ■ ■ ■ ■ ■ ■
    firmware/pycatsniffer/devpycatsniffer.py
    1 1  import serial
    2  -import time
    3  -import sys
    4  - 
    5 2  import argparse
    6 3  import binascii
    7  -import threading
     4 +import errno
     5 +import io
    8 6  import logging.handlers
     7 +import os
     8 +import select
     9 +import stat
    9 10  import struct
     11 +import sys
     12 +import threading
     13 +import time
    10 14   
    11 15  __version__ = '0.0.1'
    12 16   
    skipped 17 lines
    30 34  initiator = bytearray([0x40, 0x53, 0x70, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x40, 0x45])
    31 35  letsgo = bytearray([0x40, 0x53, 0x41, 0x00, 0x00, 0x41, 0x40, 0x45])
    32 36   
     37 + 
     38 +class Frame(object):
     39 + PCAP_FRAME_HDR_FMT = '<LLLL'
     40 + 
     41 + def __init__(self, macPDUByteArray, timestampBy32):
     42 + self.__macPDUByteArray = macPDUByteArray
     43 + self.timestampBy32 = timestampBy32
     44 + self.timestampUsec = timestampBy32 / 32.0
     45 + self.len = len(self.__macPDUByteArray)
     46 + 
     47 + self.__pcap_hdr = self.__generate_frame_hdr()
     48 + 
     49 + self.pcap = self.__pcap_hdr + self.__macPDUByteArray
     50 + self.hex = ''.join('%02x ' % c
     51 + for c in self.__macPDUByteArray).rstrip()
     52 + 
     53 + def __generate_frame_hdr(self):
     54 + sec = int(self.timestampUsec / 1000000)
     55 + usec = int(self.timestampUsec - sec)
     56 + return struct.pack(Frame.PCAP_FRAME_HDR_FMT, sec, usec, self.len,
     57 + self.len)
     58 + 
     59 + def get_pcap(self):
     60 + return self.pcap
     61 + 
     62 + def get_hex(self):
     63 + return self.hex
     64 + 
     65 + def get_timestamp(self):
     66 + return self.timestampUsec
     67 + 
     68 + 
     69 +#####################################
     70 + 
     71 + 
     72 +class PCAPHelper:
     73 + LINKTYPE_IEEE802_15_4_NOFCS = 230
     74 + LINKTYPE_IEEE802_15_4 = 195
     75 + MAGIC_NUMBER = 0xA1B2C3D4
     76 + VERSION_MAJOR = 2
     77 + VERSION_MINOR = 4
     78 + THISZONE = 0
     79 + SIGFIGS = 0
     80 + SNAPLEN = 0xFFFF
     81 + NETWORK = LINKTYPE_IEEE802_15_4
     82 + 
     83 + PCAP_GLOBAL_HDR_FMT = '<LHHlLLL'
     84 + 
     85 + @staticmethod
     86 + def writeGlobalHeader():
     87 + return struct.pack(PCAPHelper.PCAP_GLOBAL_HDR_FMT,
     88 + PCAPHelper.MAGIC_NUMBER, PCAPHelper.VERSION_MAJOR,
     89 + PCAPHelper.VERSION_MINOR, PCAPHelper.THISZONE,
     90 + PCAPHelper.SIGFIGS, PCAPHelper.SNAPLEN,
     91 + PCAPHelper.NETWORK)
     92 + 
     93 + 
     94 +class FifoHandler(object):
     95 + def __init__(self, out_fifo):
     96 + self.out_fifo = out_fifo
     97 + self.of = None
     98 + self.needs_pcap_hdr = True
     99 + self.thread = None
     100 + self.running = False
     101 + stats['Piped'] = 0
     102 + stats['Not Piped'] = 0
     103 + self.__create_fifo()
     104 + self.__start()
     105 + 
     106 + def __del__(self):
     107 + self.__stop()
     108 + 
     109 + def __start(self):
     110 + logger.debug("start FIFO watcher thread")
     111 + self.running = True
     112 + self.thread = threading.Thread(target=self.__fifo_watcher)
     113 + self.thread.daemon = True
     114 + self.thread.start()
     115 + 
     116 + def __stop(self):
     117 + logger.debug("stop FIFO watcher thread")
     118 + self.running = False
     119 + self.thread.join()
     120 + 
     121 + def __fifo_watcher(self):
     122 + while self.running:
     123 + self.__open_fifo(keepalive=True)
     124 + time.sleep(0.01)
     125 + 
     126 + def __create_fifo(self):
     127 + try:
     128 + os.mkfifo(self.out_fifo)
     129 + logger.info(f'Opened FIFO {self.out_fifo}')
     130 + except OSError as e:
     131 + if e.errno == errno.EEXIST:
     132 + if stat.S_ISFIFO(os.stat(self.out_fifo).st_mode) is False:
     133 + logger.error(
     134 + 'File {self.out_fifo} exists and is not a FIFO')
     135 + sys.exit(1)
     136 + else:
     137 + logger.warning(f'FIFO {self.out_fifo} exists. Using it')
     138 + else:
     139 + raise
     140 + 
     141 + def __open_fifo(self, keepalive=False):
     142 + try:
     143 + fd = os.open(self.out_fifo, os.O_NONBLOCK | os.O_WRONLY)
     144 + self.of = os.fdopen(fd, 'wb')
     145 + except OSError as e:
     146 + if e.errno == errno.ENXIO:
     147 + if not keepalive:
     148 + logger.warning('Remote end not reading')
     149 + stats['Not Piped'] += 1
     150 + self.of = None
     151 + self.needs_pcap_hdr = True
     152 + else:
     153 + raise
     154 + 
     155 + def triggerNewGlobalHeader(self):
     156 + self.needs_pcap_hdr = True
     157 + 
     158 + def handle(self, data):
     159 + if self.of is None:
     160 + self.__open_fifo()
     161 + 
     162 + if self.of is not None:
     163 + try:
     164 + if self.needs_pcap_hdr is True:
     165 + logger.info('Write global PCAP header')
     166 + self.of.write(PCAPHelper.writeGlobalHeader())
     167 + self.needs_pcap_hdr = False
     168 + self.of.write(data.pcap)
     169 + self.of.flush()
     170 + logger.debug(f'Wrote a frame of size {data.len} bytes')
     171 + stats['Piped'] += 1
     172 + except IOError as e:
     173 + if e.errno == errno.EPIPE:
     174 + logger.info('Remote end stopped reading')
     175 + stats['Not Piped'] += 1
     176 + self.of = None
     177 + self.needs_pcap_hdr = True
     178 + else:
     179 + raise
     180 + 
     181 + 
     182 +#####################################
     183 +class PcapDumpHandler(object):
     184 + def __init__(self, filename):
     185 + self.filename = filename
     186 + stats['Dumped to PCAP'] = 0
     187 + 
     188 + try:
     189 + self.of = open(self.filename, 'wb')
     190 + self.of.write(PCAPHelper.writeGlobalHeader())
     191 + logger.info(f'Dumping PCAP to {self.filename}')
     192 + except IOError as e:
     193 + self.of = None
     194 + logger.warning(
     195 + f'Error opening {self.filename} to save pcap. Skipping')
     196 + logger.warning(f'The error was: {e.args}')
     197 + 
     198 + def handle(self, frame):
     199 + if self.of is None:
     200 + return
     201 + self.of.write(frame.get_pcap())
     202 + self.of.flush()
     203 + logger.info(
     204 + f'PcapDumpHandler: Dumped a frame of size {frame.len} bytes')
     205 + stats['Dumped to PCAP'] += 1
     206 + 
     207 + 
     208 +class HexdumpHandler(object):
     209 + def __init__(self, filename):
     210 + self.filename = filename
     211 + stats['Dumped as Hex'] = 0
     212 + try:
     213 + self.of = open(self.filename, 'wb')
     214 + logger.info(f'Dumping hex to {self.filename}')
     215 + except IOError as e:
     216 + logger.warning(
     217 + f'Error opening {self.filename} for hex dumps. Skipping')
     218 + logger.warning(f'The error was: {e.args}')
     219 + self.of = None
     220 + 
     221 + def handle(self, frame):
     222 + if self.of is None:
     223 + return
     224 + 
     225 + try:
     226 + # Prepend the original timestamp in big-endian format
     227 + self.of.write(
     228 + binascii.hexlify(
     229 + struct.pack(">I ", int(frame.get_timestamp() * 32))))
     230 + #self.of.write(str(frame.get_timestamp()))
     231 + self.of.write(bytes(" ", 'ascii'))
     232 + # self.of.write('0000 ')
     233 + self.of.write(bytes(frame.get_hex(), 'ascii'))
     234 + self.of.write(bytes('\n', 'ascii'))
     235 + self.of.flush()
     236 + stats['Dumped as Hex'] += 1
     237 + logger.info(
     238 + f'HexdumpHandler: Dumped a frame of size {frame.len} bytes')
     239 + except IOError as e:
     240 + logger.warning(
     241 + f'Error writing hex to {self.of} for hex dumps. Skipping')
     242 + logger.warning(f'The error was: {e.args}')
     243 + 
     244 + 
     245 + 
     246 + 
    33 247  class CC1352:
    34 248   
    35 249   DEFAULT_CHANNEL = 0x0B # 11
    skipped 17 lines
    53 267  
    54 268   BYTE_STREAM = 0
    55 269   
    56  - def __init__(self, port, callback):
     270 + def __init__(self, port, callback, channel=DEFAULT_CHANNEL):
    57 271   
    58 272   baudrate = 921600
    59 273   rts_cts = False
    skipped 1 lines
    61 275   stats['Captured'] = 0
    62 276   stats['Non-Frame'] = 0
    63 277   
     278 + self.serial_port = None
     279 + self.channel = channel
    64 280   self.callback = callback
    65 281   self.thread = None
    66 282   self.running = False
    skipped 51 lines
    118 334   
    119 335   def stop(self):
    120 336   # end sniffing
     337 + self.serial_port.write(stop)
    121 338   self.running = False
    122 339   self.thread.join()
    123 340   #self.dev.ctrl_transfer(CC2531.DIR_OUT, CC2531.SET_STOP)
    skipped 7 lines
    131 348   while self.running:
    132 349   if self.serial_port.in_waiting > 0:
    133 350   bytestream = self.serial_port.read(self.serial_port.in_waiting)
    134  - print ("RECV>> %s" % binascii.hexlify(bytestream))
     351 +
     352 +# print ("RECV>> %s" % binascii.hexlify(bytestream))
     353 + 
    135 354   time.sleep(0.5)
    136 355   start_index = 0
    137 356  
    skipped 8 lines
    146 365   # If not found, break out of the loop
    147 366   if end_index == -1:
    148 367   break
    149  - # Get the substring between start_index and end_index
    150  - substring = bytestream[start_index:end_index+2]
    151  - # Do something with the substring
    152  - #print(substring)
    153  - print ("SUBSRECV>> %s" % binascii.hexlify(substring))
     368 + # Get the substream between start_index and end_index
     369 + substream = bytestream[start_index:end_index+2]
     370 +
     371 + # Do something with the substream
     372 + #print(substream)
     373 + 
     374 + print ("SUBSRECV>> %s" % binascii.hexlify(substream))
     375 + 
     376 + if len(substream) >= 3:
     377 + (cmd, cmdLen) = struct.unpack_from("<BH", substream)
     378 + payload = substream[3:]
     379 + if len(payload) == cmdLen:
     380 + # buffer contains the correct number of bytes
     381 + if CC1352.COMMAND_FRAME == cmd:
     382 + logger.info(f'Read a frame of size {cmdLen}')
     383 + stats['Captured'] += 1
     384 + (timestamp, pktLen) = struct.unpack_from("<IB", payload)
     385 + frame = payload[5:]
     386 + 
     387 + if len(frame) == pktLen:
     388 + self.callback(timestamp, frame.tobytes())
     389 + else:
     390 + logger.warning(
     391 + f'Received a frame with incorrect length, pktLen:{pktLen}, len(frame):{len(frame)}'
     392 + )
     393 + else:
     394 + logger.warning(
     395 + 'Received a command response with unknown code - CMD:{:02x} byte:{}'
     396 + .format(cmd, substream))
     397 + 
     398 + #packet = self.parse_packet(substream)
     399 + #if packet:
     400 + #print("HELL O WORLD!")
     401 + # self.callback(packet)
     402 + 
    154 403   # Set the start index to end_index + 2 (to skip over the 0x40 0x45 bytes)
    155 404   start_index = end_index + 2
    156 405  
    skipped 32 lines
    189 438  
    190 439   def parse_packet(self, packet):
    191 440   
    192  - packetlen = packet[1]
     441 + #print("***HELL***")
    193 442   
    194  - if len(packet) - 3 != packetlen:
    195  - return None
     443 + packetlen = packet[3:5]
     444 + 
     445 + #if len(packet) - 3 != packetlen:
     446 + # return None
    196 447   
    197 448   # unknown header produced by the radio chip
    198  - header = packet[3:7].tostring()
     449 + header = packet[0:2]
    199 450   
    200 451   # the data in the payload
    201  - payload = packet[8:-2].tostring()
     452 + payload = packet[11:-4]
    202 453   
    203 454   # length of the payload
    204  - payloadlen = packet[7] - 2 # without fcs
     455 + #payloadlen = packet[7] - 2 # without fcs
    205 456   
    206  - if len(payload) != payloadlen:
    207  - return None
     457 + #if len(payload) != payloadlen:
     458 + # return None
    208 459   
    209 460   # current time
    210 461   timestamp = time.gmtime()
    211 462   
    212 463   # used to derive other values
    213  - fcs1, fcs2 = packet[-2:]
     464 + fcs1, fcs2 = packet[-4:-2]
    214 465   
    215 466   # rssi is the signed value at fcs1
    216 467   rssi = (fcs1 + 2**7) % 2**8 - 2**7 - 73
    skipped 38 lines
    255 506   
    256 507   return "\n".join(ret)
    257 508   
    258  -if __name__ == "__main__":
     509 +# if __name__ == "__main__":
     510 + 
     511 +# def callback(packet):
     512 +# print("-"*30)
     513 +# print(packet)
     514 +# print("-"*30)
     515 +
     516 +# sniffer = CC1352('/dev/ttyACM0', callback)
     517 +# #sniffer = CC1352(callback)
     518 +
     519 +# #print(sniffer)
     520 +# #sniffer.startc()
     521 +# #sniffer.pingc()
     522 +# #time.sleep(2)
     523 +# #sniffer.stopc()
     524 +
     525 +# sniffer.pingc()
     526 +# sniffer.stopc()
     527 +# sniffer.cfgphyc()
     528 +# sniffer.cfgfreqc()
     529 +# sniffer.initiatorc()
     530 +# sniffer.startc()
     531 +# print ("start")
     532 +# time.sleep(1)
     533 +# sniffer.stop()
     534 +# sniffer.close()
     535 + 
     536 +def arg_parser():
     537 + debug_choices = ('DEBUG', 'INFO', 'WARNING', 'ERROR')
     538 + 
     539 + parser = argparse.ArgumentParser(add_help=False,
     540 + description='Read IEEE802.15.4 frames \
     541 + from a CC1352 packet sniffer device, convert them to pcap and pipe them \
     542 + into wireshark over a FIFO pipe for online analysis. Frames \
     543 + can also be saved in a file in hexdump and/or pcap format for offline \
     544 + analysis.')
     545 + 
     546 + in_group = parser.add_argument_group('Input Options')
     547 + in_group.add_argument(
     548 + '-c',
     549 + '--channel',
     550 + type=int,
     551 + action='store',
     552 + choices=list(range(11, 27)),
     553 + default=defaults['channel'],
     554 + help='Set the sniffer\'s CHANNEL. Valid range: 11-26. \
     555 + (Default: %s)' % (defaults['channel'], ))
     556 + out_group = parser.add_argument_group('Output Options')
     557 + out_group.add_argument(
     558 + '-f',
     559 + '--fifo',
     560 + action='store',
     561 + default=defaults['out_fifo'],
     562 + help='Set FIFO as the named pipe for sending to wireshark. \
     563 + If argument is omitted and -o option is not specified \
     564 + the capture will pipe to: %s' %
     565 + (defaults['out_fifo'], ))
     566 + out_group.add_argument(
     567 + '-o',
     568 + '--offline',
     569 + action='store_true',
     570 + default=False,
     571 + help='Disables sending the capture to the named pipe.')
     572 + out_group.add_argument('-x',
     573 + '--hex-file',
     574 + action='store',
     575 + nargs='?',
     576 + const=defaults['hex_file'],
     577 + default=False,
     578 + help='Save the capture (hexdump) in HEX_FILE. \
     579 + If -x is specified but HEX_FILE is omitted, \
     580 + %s will be used. If the argument is \
     581 + omitted altogether, the capture will not \
     582 + be saved.' % (defaults['hex_file'], ))
     583 + out_group.add_argument('-p',
     584 + '--pcap-file',
     585 + action='store',
     586 + nargs='?',
     587 + const=defaults['pcap_file'],
     588 + default=False,
     589 + help='Save the capture (pcap format) in PCAP_FILE. \
     590 + If -p is specified but PCAP_FILE is omitted, \
     591 + %s will be used. If the argument is \
     592 + omitted altogether, the capture will not \
     593 + be saved.' % (defaults['pcap_file'], ))
     594 + 
     595 + log_group = parser.add_argument_group('Verbosity and Logging')
     596 + log_group.add_argument(
     597 + '-d',
     598 + '--headless',
     599 + action='store_true',
     600 + default=False,
     601 + help='Run in non-interactive/headless mode, without \
     602 + accepting user input. (Default Disabled)')
     603 + log_group.add_argument('-D',
     604 + '--debug-level',
     605 + action='store',
     606 + choices=debug_choices,
     607 + default=defaults['debug_level'],
     608 + help='Print messages of severity DEBUG_LEVEL \
     609 + or higher (Default %s)' %
     610 + (defaults['debug_level'], ))
     611 + log_group.add_argument('-L',
     612 + '--log-file',
     613 + action='store',
     614 + nargs='?',
     615 + const=defaults['log_file'],
     616 + default=False,
     617 + help='Log output in LOG_FILE. If -L is specified \
     618 + but LOG_FILE is omitted, %s will be used. \
     619 + If the argument is omitted altogether, \
     620 + logging will not take place at all.' %
     621 + (defaults['log_file'], ))
     622 + log_group.add_argument('-l',
     623 + '--log-level',
     624 + action='store',
     625 + choices=debug_choices,
     626 + default=defaults['log_level'],
     627 + help='Log messages of severity LOG_LEVEL or \
     628 + higher. Only makes sense if -L is also \
     629 + specified (Default %s)' %
     630 + (defaults['log_level'], ))
     631 + 
     632 + gen_group = parser.add_argument_group('General Options')
     633 + gen_group.add_argument('-v',
     634 + '--version',
     635 + action='version',
     636 + version='ccsniffpiper v%s' % (__version__))
     637 + gen_group.add_argument('-h',
     638 + '--help',
     639 + action='help',
     640 + help='Shows this message and exits')
     641 + 
     642 + return parser.parse_args()
     643 + 
     644 + 
     645 +def dump_stats():
     646 + s = io.StringIO()
     647 + 
     648 + s.write('Frame Stats:\n')
     649 + for k, v in list(stats.items()):
     650 + s.write('%20s: %d\n' % (k, v))
     651 + 
     652 + print((s.getvalue()))
     653 + 
     654 + 
     655 +def log_init():
     656 + logger.setLevel(logging.DEBUG)
     657 + ch = logging.StreamHandler()
     658 + ch.setLevel(getattr(logging, args.debug_level))
     659 + cf = logging.Formatter('%(message)s')
     660 + ch.setFormatter(cf)
     661 + logger.addHandler(ch)
     662 + 
     663 + if args.log_file is not False:
     664 + fh = logging.handlers.RotatingFileHandler(filename=args.log_file,
     665 + maxBytes=5000000)
     666 + fh.setLevel(getattr(logging, args.log_level))
     667 + ff = logging.Formatter('%(asctime)s - %(levelname)8s - %(message)s')
     668 + fh.setFormatter(ff)
     669 + logger.addHandler(fh)
     670 + 
     671 + 
     672 +if __name__ == '__main__':
     673 + args = arg_parser()
     674 + log_init()
     675 + 
     676 + logger.info('Started logging')
     677 + 
     678 + handlers = []
     679 + 
     680 + def handlerDispatcher(timestamp, macPDU):
     681 + """ Dispatches any received frames to all registered handlers
     682 + timestamp -> The timestamp the frame was received, as reported by the sniffer device, in microseconds
     683 + macPDU -> The 802.15.4 MAC-layer PDU, starting with the Frame Control Field (FCF)
     684 + """
     685 + if len(macPDU) > 0:
     686 + frame = Frame(macPDU, timestamp)
     687 + for h in handlers:
     688 + h.handle(frame)
     689 + 
     690 + if args.offline is not True:
     691 + f = FifoHandler(out_fifo=args.fifo)
     692 + handlers.append(f)
     693 + if args.hex_file is not False:
     694 + handlers.append(HexdumpHandler(args.hex_file))
     695 + if args.pcap_file is not False:
     696 + handlers.append(PcapDumpHandler(args.pcap_file))
     697 + 
     698 + if args.headless is False:
     699 + h = io.StringIO()
     700 + h.write('Commands:\n')
     701 + h.write('c: Print current RF Channel\n')
     702 + h.write('n: Trigger new pcap header before the next frame\n')
     703 + h.write('h,?: Print this message\n')
     704 + h.write('[11,26]: Change RF channel\n')
     705 + h.write('s: Start/stop the packet capture\n')
     706 + h.write('q: Quit')
     707 + h = h.getvalue()
    259 708   
    260  - def callback(packet):
    261  - print("-"*30)
    262  - print(packet)
    263  - print("-"*30)
     709 + e = 'Unknown Command. Type h or ? for help'
    264 710   
    265  - sniffer = CC1352('/dev/ttyACM0', callback)
    266  - #sniffer = CC1352(callback)
     711 + print(h)
    267 712   
    268  - #print(sniffer)
    269  - #sniffer.startc()
    270  - #sniffer.pingc()
    271  - #time.sleep(2)
    272  - #sniffer.stopc()
     713 + snifferDev = CC1352('/dev/ttyACM0', handlerDispatcher, args.channel)
     714 + try:
    273 715   
    274  - sniffer.pingc()
    275  - sniffer.stopc()
    276  - sniffer.cfgphyc()
    277  - sniffer.cfgfreqc()
    278  - sniffer.initiatorc()
    279  - sniffer.startc()
    280  - print ("start")
    281  - time.sleep(5)
    282  - sniffer.stop()
    283  - sniffer.close()
     716 + while 1:
     717 + if args.headless is True:
     718 + if not snifferDev.isRunning():
     719 + snifferDev.start()
     720 + # block until terminated (Ctrl+C or killed)
     721 + snifferDev.thread.join()
     722 + else:
     723 + try:
     724 + if select.select([
     725 + sys.stdin,
     726 + ], [], [], 10.0)[0]:
     727 + cmd = sys.stdin.readline().rstrip()
     728 + logger.debug(f'User input: "{cmd}"')
     729 + if cmd in ('h', '?'):
     730 + print(h)
     731 + elif cmd == 'c':
     732 + # We'll only ever see this if the user asked for it, so we are
     733 + # running interactive. Print away
     734 + print(
     735 + f'Sniffing in channel: {snifferDev.get_channel()}'
     736 + )
     737 + elif cmd == 'n':
     738 + f.triggerNewGlobalHeader()
     739 + elif cmd == 'q':
     740 + logger.info('User requested shutdown')
     741 + sys.exit(0)
     742 + elif cmd == 's':
     743 + if snifferDev.isRunning():
     744 + snifferDev.stop()
     745 + else:
     746 + snifferDev.start()
     747 + elif int(cmd) in range(11, 27):
     748 + snifferDev.set_channel(int(cmd))
     749 + else:
     750 + raise ValueError
     751 +# else:
     752 +# logger.debug('No user input')
     753 + except select.error:
     754 + logger.warning('Error while trying to read stdin')
     755 + except ValueError as e:
     756 + print(e)
     757 + except UnboundLocalError:
     758 + # Raised by command 'n' when -o was specified at command line
     759 + pass
    284 760   
     761 + except (KeyboardInterrupt, SystemExit):
     762 + logger.info('Shutting down')
     763 + if snifferDev.isRunning():
     764 + snifferDev.stop()
     765 + dump_stats()
     766 + sys.exit(0)
  • ■ ■ ■ ■ ■ ■
    firmware/pycatsniffer/devpycatsniffer00.py
    1  -import serial
    2  -import argparse
    3  -import binascii
    4  -import errno
    5  -import io
    6  -import logging.handlers
    7  -import os
    8  -import select
    9  -import stat
    10  -import struct
    11  -import sys
    12  -import threading
    13  -import time
    14  - 
    15  -__version__ = '0.0.1'
    16  - 
    17  -defaults = {
    18  - 'hex_file': 'ccsniffpiper.hexdump',
    19  - 'out_fifo': '/tmp/ccsniffpiper',
    20  - 'pcap_file': 'ccsniffpiper.pcap',
    21  - 'debug_level': 'WARNING',
    22  - 'log_level': 'INFO',
    23  - 'log_file': 'ccsniffpiper.log',
    24  - 'channel': 11,
    25  -}
    26  - 
    27  -logger = logging.getLogger(__name__)
    28  -stats = {}
    29  - 
    30  -ping = bytearray([0x40, 0x53, 0x40, 0x00, 0x00, 0x40, 0x40, 0x45])
    31  -stop = bytearray([0x40, 0x53, 0x42, 0x00, 0x00, 0x42, 0x40, 0x45])
    32  -cfgphy = bytearray([0x40, 0x53, 0x47, 0x01, 0x00, 0x09, 0x51, 0x40, 0x45])
    33  -cfgfreq = bytearray([0x40, 0x53, 0x45, 0x04, 0x00, 0x62, 0x09, 0x00, 0x00, 0xb4, 0x40, 0x45])
    34  -initiator = bytearray([0x40, 0x53, 0x70, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x40, 0x45])
    35  -letsgo = bytearray([0x40, 0x53, 0x41, 0x00, 0x00, 0x41, 0x40, 0x45])
    36  - 
    37  - 
    38  -class Frame(object):
    39  - PCAP_FRAME_HDR_FMT = '<LLLL'
    40  - 
    41  - def __init__(self, macPDUByteArray, timestampBy32):
    42  - self.__macPDUByteArray = macPDUByteArray
    43  - self.timestampBy32 = timestampBy32
    44  - self.timestampUsec = timestampBy32 / 32.0
    45  - self.len = len(self.__macPDUByteArray)
    46  - 
    47  - self.__pcap_hdr = self.__generate_frame_hdr()
    48  - 
    49  - self.pcap = self.__pcap_hdr + self.__macPDUByteArray
    50  - self.hex = ''.join('%02x ' % c
    51  - for c in self.__macPDUByteArray).rstrip()
    52  - 
    53  - def __generate_frame_hdr(self):
    54  - sec = int(self.timestampUsec / 1000000)
    55  - usec = int(self.timestampUsec - sec)
    56  - return struct.pack(Frame.PCAP_FRAME_HDR_FMT, sec, usec, self.len,
    57  - self.len)
    58  - 
    59  - def get_pcap(self):
    60  - return self.pcap
    61  - 
    62  - def get_hex(self):
    63  - return self.hex
    64  - 
    65  - def get_timestamp(self):
    66  - return self.timestampUsec
    67  - 
    68  - 
    69  -#####################################
    70  - 
    71  - 
    72  -class PCAPHelper:
    73  - LINKTYPE_IEEE802_15_4_NOFCS = 230
    74  - LINKTYPE_IEEE802_15_4 = 195
    75  - MAGIC_NUMBER = 0xA1B2C3D4
    76  - VERSION_MAJOR = 2
    77  - VERSION_MINOR = 4
    78  - THISZONE = 0
    79  - SIGFIGS = 0
    80  - SNAPLEN = 0xFFFF
    81  - NETWORK = LINKTYPE_IEEE802_15_4
    82  - 
    83  - PCAP_GLOBAL_HDR_FMT = '<LHHlLLL'
    84  - 
    85  - @staticmethod
    86  - def writeGlobalHeader():
    87  - return struct.pack(PCAPHelper.PCAP_GLOBAL_HDR_FMT,
    88  - PCAPHelper.MAGIC_NUMBER, PCAPHelper.VERSION_MAJOR,
    89  - PCAPHelper.VERSION_MINOR, PCAPHelper.THISZONE,
    90  - PCAPHelper.SIGFIGS, PCAPHelper.SNAPLEN,
    91  - PCAPHelper.NETWORK)
    92  - 
    93  - 
    94  -class FifoHandler(object):
    95  - def __init__(self, out_fifo):
    96  - self.out_fifo = out_fifo
    97  - self.of = None
    98  - self.needs_pcap_hdr = True
    99  - self.thread = None
    100  - self.running = False
    101  - stats['Piped'] = 0
    102  - stats['Not Piped'] = 0
    103  - self.__create_fifo()
    104  - self.__start()
    105  - 
    106  - def __del__(self):
    107  - self.__stop()
    108  - 
    109  - def __start(self):
    110  - logger.debug("start FIFO watcher thread")
    111  - self.running = True
    112  - self.thread = threading.Thread(target=self.__fifo_watcher)
    113  - self.thread.daemon = True
    114  - self.thread.start()
    115  - 
    116  - def __stop(self):
    117  - logger.debug("stop FIFO watcher thread")
    118  - self.running = False
    119  - self.thread.join()
    120  - 
    121  - def __fifo_watcher(self):
    122  - while self.running:
    123  - self.__open_fifo(keepalive=True)
    124  - time.sleep(0.01)
    125  - 
    126  - def __create_fifo(self):
    127  - try:
    128  - os.mkfifo(self.out_fifo)
    129  - logger.info(f'Opened FIFO {self.out_fifo}')
    130  - except OSError as e:
    131  - if e.errno == errno.EEXIST:
    132  - if stat.S_ISFIFO(os.stat(self.out_fifo).st_mode) is False:
    133  - logger.error(
    134  - 'File {self.out_fifo} exists and is not a FIFO')
    135  - sys.exit(1)
    136  - else:
    137  - logger.warning(f'FIFO {self.out_fifo} exists. Using it')
    138  - else:
    139  - raise
    140  - 
    141  - def __open_fifo(self, keepalive=False):
    142  - try:
    143  - fd = os.open(self.out_fifo, os.O_NONBLOCK | os.O_WRONLY)
    144  - self.of = os.fdopen(fd, 'wb')
    145  - except OSError as e:
    146  - if e.errno == errno.ENXIO:
    147  - if not keepalive:
    148  - logger.warning('Remote end not reading')
    149  - stats['Not Piped'] += 1
    150  - self.of = None
    151  - self.needs_pcap_hdr = True
    152  - else:
    153  - raise
    154  - 
    155  - def triggerNewGlobalHeader(self):
    156  - self.needs_pcap_hdr = True
    157  - 
    158  - def handle(self, data):
    159  - if self.of is None:
    160  - self.__open_fifo()
    161  - 
    162  - if self.of is not None:
    163  - try:
    164  - if self.needs_pcap_hdr is True:
    165  - logger.info('Write global PCAP header')
    166  - self.of.write(PCAPHelper.writeGlobalHeader())
    167  - self.needs_pcap_hdr = False
    168  - self.of.write(data.pcap)
    169  - self.of.flush()
    170  - logger.debug(f'Wrote a frame of size {data.len} bytes')
    171  - stats['Piped'] += 1
    172  - except IOError as e:
    173  - if e.errno == errno.EPIPE:
    174  - logger.info('Remote end stopped reading')
    175  - stats['Not Piped'] += 1
    176  - self.of = None
    177  - self.needs_pcap_hdr = True
    178  - else:
    179  - raise
    180  - 
    181  - 
    182  -#####################################
    183  -class PcapDumpHandler(object):
    184  - def __init__(self, filename):
    185  - self.filename = filename
    186  - stats['Dumped to PCAP'] = 0
    187  - 
    188  - try:
    189  - self.of = open(self.filename, 'wb')
    190  - self.of.write(PCAPHelper.writeGlobalHeader())
    191  - logger.info(f'Dumping PCAP to {self.filename}')
    192  - except IOError as e:
    193  - self.of = None
    194  - logger.warning(
    195  - f'Error opening {self.filename} to save pcap. Skipping')
    196  - logger.warning(f'The error was: {e.args}')
    197  - 
    198  - def handle(self, frame):
    199  - if self.of is None:
    200  - return
    201  - self.of.write(frame.get_pcap())
    202  - self.of.flush()
    203  - logger.info(
    204  - f'PcapDumpHandler: Dumped a frame of size {frame.len} bytes')
    205  - stats['Dumped to PCAP'] += 1
    206  - 
    207  - 
    208  -class HexdumpHandler(object):
    209  - def __init__(self, filename):
    210  - self.filename = filename
    211  - stats['Dumped as Hex'] = 0
    212  - try:
    213  - self.of = open(self.filename, 'wb')
    214  - logger.info(f'Dumping hex to {self.filename}')
    215  - except IOError as e:
    216  - logger.warning(
    217  - f'Error opening {self.filename} for hex dumps. Skipping')
    218  - logger.warning(f'The error was: {e.args}')
    219  - self.of = None
    220  - 
    221  - def handle(self, frame):
    222  - if self.of is None:
    223  - return
    224  - 
    225  - try:
    226  - # Prepend the original timestamp in big-endian format
    227  - self.of.write(
    228  - binascii.hexlify(
    229  - struct.pack(">I ", int(frame.get_timestamp() * 32))))
    230  - #self.of.write(str(frame.get_timestamp()))
    231  - self.of.write(bytes(" ", 'ascii'))
    232  - # self.of.write('0000 ')
    233  - self.of.write(bytes(frame.get_hex(), 'ascii'))
    234  - self.of.write(bytes('\n', 'ascii'))
    235  - self.of.flush()
    236  - stats['Dumped as Hex'] += 1
    237  - logger.info(
    238  - f'HexdumpHandler: Dumped a frame of size {frame.len} bytes')
    239  - except IOError as e:
    240  - logger.warning(
    241  - f'Error writing hex to {self.of} for hex dumps. Skipping')
    242  - logger.warning(f'The error was: {e.args}')
    243  - 
    244  - 
    245  - 
    246  - 
    247  -class CC1352:
    248  - 
    249  - DEFAULT_CHANNEL = 0x0B # 11
    250  - 
    251  - DATA_EP = 0x83
    252  - DATA_TIMEOUT = 2500
    253  - 
    254  - DIR_OUT = 0x40
    255  - DIR_IN = 0xc0
    256  - 
    257  - GET_IDENT = 0xc0
    258  - SET_POWER = 0xc5
    259  - GET_POWER = 0xc6
    260  - 
    261  - SET_START = 0xd0 # bulk in starts
    262  - SET_STOP = 0xd1 # bulk in stops
    263  - SET_CHAN = 0xd2 # 0x0d (idx 0) + data)0x00 (idx 1)
    264  - 
    265  - HEARTBEAT_FRAME = 0x01
    266  - COMMAND_FRAME = 0x00
    267  -
    268  - BYTE_STREAM = 0
    269  - 
    270  - def __init__(self, port, callback, channel=DEFAULT_CHANNEL):
    271  - 
    272  - baudrate = 921600
    273  - rts_cts = False
    274  -
    275  - stats['Captured'] = 0
    276  - stats['Non-Frame'] = 0
    277  - 
    278  - self.channel = channel
    279  - self.callback = callback
    280  - self.thread = None
    281  - self.running = False
    282  -
    283  - try:
    284  - #self.serial_port = serial.Serial(port, baudrate, 8, 'N', 1, timeout=1)
    285  - self.serial_port = serial.Serial(port = port,
    286  - baudrate = baudrate,
    287  - bytesize = serial.EIGHTBITS,
    288  - parity = serial.PARITY_NONE,
    289  - stopbits = serial.STOPBITS_ONE,
    290  - xonxoff = False,
    291  - rtscts = rts_cts,
    292  - timeout = 0.1)
    293  - self.serial_port.flushInput()
    294  - self.serial_port.flushOutput()
    295  - except (serial.SerialException, ValueError, IOError, OSError) as e:
    296  - logger.error('Error opening port: %s' % (port,))
    297  - logger.error('The error was: %s' % (e.args,))
    298  - sys.exit(1)
    299  - logger.info('Serial port %s opened' % (self.serial_port.name))
    300  -
    301  - def close(self):
    302  - self.serial_port.close()
    303  - 
    304  - def pingc(self):
    305  - self.serial_port.write(ping)
    306  -
    307  - def stopc(self):
    308  - self.serial_port.write(stop)
    309  -
    310  - def cfgphyc(self):
    311  - self.serial_port.write(cfgphy)
    312  -
    313  - def cfgfreqc(self):
    314  - self.serial_port.write(cfgfreq)
    315  - 
    316  - def initiatorc(self):
    317  - self.serial_port.write(initiator)
    318  - 
    319  - #def startc(self):
    320  - # self.serial_port.write(letsgo)
    321  -
    322  - 
    323  - def startc(self):
    324  - # start sniffing
    325  - self.running = True
    326  - #self.dev.ctrl_transfer(CC2531.DIR_OUT, CC2531.SET_START)
    327  -
    328  - self.serial_port.write(letsgo)
    329  -
    330  - self.thread = threading.Thread(target=self.recv)
    331  - #self.thread.daemon = True
    332  - self.thread.start()
    333  - 
    334  - def stop(self):
    335  - # end sniffing
    336  - self.serial_port.write(stop)
    337  - self.running = False
    338  - self.thread.join()
    339  - #self.dev.ctrl_transfer(CC2531.DIR_OUT, CC2531.SET_STOP)
    340  - 
    341  - def isRunning(self):
    342  - return self.running
    343  - 
    344  - 
    345  - def recv(self):
    346  -
    347  - while self.running:
    348  - if self.serial_port.in_waiting > 0:
    349  - bytestream = self.serial_port.read(self.serial_port.in_waiting)
    350  - #print ("RECV>> %s" % binascii.hexlify(bytestream))
    351  - time.sleep(0.5)
    352  - start_index = 0
    353  -
    354  - while True:
    355  - # Find the index of the next occurrence of 0x40 0x53
    356  - start_index = bytestream.find(b'\x40\x53', start_index)
    357  - # If not found, break out of the loop
    358  - if start_index == -1:
    359  - break
    360  - # Find the index of the next occurrence of 0x40 0x45 after the start index
    361  - end_index = bytestream.find(b'\x40\x45', start_index)
    362  - # If not found, break out of the loop
    363  - if end_index == -1:
    364  - break
    365  - # Get the substring between start_index and end_index
    366  - substring = bytestream[start_index:end_index+2]
    367  - 
    368  - packet = self.parse_packet(substring)
    369  - if packet:
    370  - print("HELL O WORLD!")
    371  - self.callback(packet)
    372  - 
    373  - # Do something with the substring
    374  - #print(substring)
    375  - 
    376  - print ("SUBSRECV>> %s" % binascii.hexlify(substring))
    377  - 
    378  - # Set the start index to end_index + 2 (to skip over the 0x40 0x45 bytes)
    379  - start_index = end_index + 2
    380  -
    381  -
    382  - #if bytestream[0:2] == bytes([0x40, 0x53]):
    383  - # packet = self.parse_packet(bytestream)
    384  - #if packet:
    385  - # self.callback(packet)
    386  -
    387  - 
    388  - def set_channel(self, channel):
    389  - was_running = self.running
    390  - 
    391  - if channel >= 11 and channel <= 26:
    392  - if self.running:
    393  - self.stop()
    394  - 
    395  - self.channel = channel
    396  - 
    397  - # set channel command
    398  - #self.dev.ctrl_transfer(CC2531.DIR_OUT, CC2531.SET_CHAN, 0, 0,
    399  - # [channel])
    400  - #self.dev.ctrl_transfer(CC2531.DIR_OUT, CC2531.SET_CHAN, 0, 1,
    401  - # [0x00])
    402  - 
    403  - self.get_channel()
    404  - 
    405  - if was_running:
    406  - self.start()
    407  - 
    408  - else:
    409  - raise ValueError("Channel must be between 11 and 26")
    410  - 
    411  - def get_channel(self):
    412  - return self.channel
    413  -
    414  - def parse_packet(self, packet):
    415  - 
    416  - #print("***HELL***")
    417  - 
    418  - packetlen = packet[3:5]
    419  - 
    420  - #if len(packet) - 3 != packetlen:
    421  - # return None
    422  - 
    423  - # unknown header produced by the radio chip
    424  - header = packet[0:2]
    425  - 
    426  - # the data in the payload
    427  - payload = packet[11:-4]
    428  - 
    429  - # length of the payload
    430  - #payloadlen = packet[7] - 2 # without fcs
    431  - 
    432  - #if len(payload) != payloadlen:
    433  - # return None
    434  - 
    435  - # current time
    436  - timestamp = time.gmtime()
    437  - 
    438  - # used to derive other values
    439  - fcs1, fcs2 = packet[-4:-2]
    440  - 
    441  - # rssi is the signed value at fcs1
    442  - rssi = (fcs1 + 2**7) % 2**8 - 2**7 - 73
    443  - 
    444  - # crc ok is the 7th bit in fcs2
    445  - crc_ok = fcs2 & (1 << 7) > 0
    446  - 
    447  - # correlation value is the unsigned 0th-6th bit in fcs2
    448  - corr = fcs2 & 0x7f
    449  - 
    450  - return Packet(timestamp, self.channel, header, payload, rssi, crc_ok, corr)
    451  -
    452  - 
    453  - def __repr__(self):
    454  - 
    455  - if self.dev:
    456  - return "%s <Channel: %d>" % (self.name, self.channel)
    457  - else:
    458  - return "Not connected"
    459  - 
    460  -class Packet:
    461  - 
    462  - def __init__(self, timestamp, channel, header, payload, rssi, crc_ok, correlation):
    463  - self.timestamp = timestamp
    464  - self.channel = channel
    465  - self.header = header
    466  - self.payload = payload
    467  - self.rssi = rssi
    468  - self.crc_ok = crc_ok
    469  - self.correlation = correlation
    470  - 
    471  - def __repr__(self):
    472  -
    473  - ret = []
    474  - ret.append("Channel: %d" % self.channel)
    475  - ret.append("Timestamp: %s" % time.strftime("%H:%M:%S", self.timestamp))
    476  - ret.append("Header: %s" % binascii.hexlify(self.header))
    477  - ret.append("RSSI: %d" % self.rssi)
    478  - ret.append("CRC OK: %s" % self.crc_ok)
    479  - ret.append("Correlation: %d" % self.correlation)
    480  - ret.append("Payload: %s" % binascii.hexlify(self.payload))
    481  - 
    482  - return "\n".join(ret)
    483  - 
    484  -if __name__ == "__main__":
    485  - 
    486  - def callback(packet):
    487  - print("-"*30)
    488  - print(packet)
    489  - print("-"*30)
    490  -
    491  - sniffer = CC1352('/dev/ttyACM0', callback)
    492  - #sniffer = CC1352(callback)
    493  -
    494  - #print(sniffer)
    495  - #sniffer.startc()
    496  - #sniffer.pingc()
    497  - #time.sleep(2)
    498  - #sniffer.stopc()
    499  -
    500  - sniffer.pingc()
    501  - sniffer.stopc()
    502  - sniffer.cfgphyc()
    503  - sniffer.cfgfreqc()
    504  - sniffer.initiatorc()
    505  - sniffer.startc()
    506  - print ("start")
    507  - time.sleep(1)
    508  - sniffer.stop()
    509  - sniffer.close()
    510  - 
  • ■ ■ ■ ■
    firmware/pycatsniffer/pingcatsniffer.py
    skipped 9 lines
    10 10  ser.stopbits = serial.STOPBITS_ONE
    11 11  ser.timeout = 1
    12 12   
    13  -cmd = bytearray([0x40, 0x53, 0x40, 0x00, 0x00, 0x40, 0x40, 0x45])
     13 +ping = bytearray([0x40, 0x53, 0x40, 0x00, 0x00, 0x40, 0x40, 0x45])
    14 14   
    15 15  time.sleep(1)
    16 16  ser.open()
    skipped 18 lines
Please wait...
Page is in error, reload to recover