From 81d4e308a36cd06f5e7bac30d2af10145c8f193a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Elio=20Petten=C3=B2?= Date: Sun, 4 Oct 2020 15:12:44 +0100 Subject: Depend on freestyle-hid for FreeStyle support and remove tools. Instead of maintaining the reversing tools for Abbott FreeStyle devices in this repository, they are now part of their own project (https://github.com/glucometers-tech/freestyle-hid), making it easier to split the dependencies requirements. The basic I/O of the FreeStyle session is also implemented in that library. --- reversing_tools/abbott/__init__.py | 3 - .../abbott/encrypted_setup_extractor.py | 179 --------------- reversing_tools/abbott/extract_freestyle.py | 245 --------------------- reversing_tools/abbott/freestyle_hid_console.py | 69 ------ reversing_tools/abbott/known-commands.txt | 48 ---- reversing_tools/abbott/known-commands.txt.license | 3 - 6 files changed, 547 deletions(-) delete mode 100644 reversing_tools/abbott/__init__.py delete mode 100644 reversing_tools/abbott/encrypted_setup_extractor.py delete mode 100755 reversing_tools/abbott/extract_freestyle.py delete mode 100755 reversing_tools/abbott/freestyle_hid_console.py delete mode 100644 reversing_tools/abbott/known-commands.txt delete mode 100644 reversing_tools/abbott/known-commands.txt.license (limited to 'reversing_tools/abbott') diff --git a/reversing_tools/abbott/__init__.py b/reversing_tools/abbott/__init__.py deleted file mode 100644 index 4b386c3..0000000 --- a/reversing_tools/abbott/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# SPDX-FileCopyrightText: 2013 The glucometerutils Authors -# -# SPDX-License-Identifier: Unlicense diff --git a/reversing_tools/abbott/encrypted_setup_extractor.py b/reversing_tools/abbott/encrypted_setup_extractor.py deleted file mode 100644 index cc57f0f..0000000 --- a/reversing_tools/abbott/encrypted_setup_extractor.py +++ /dev/null @@ -1,179 +0,0 @@ -#!/usr/bin/env python3 -# -# SPDX-FileCopyrightText: © 2019 The usbmon-tools Authors -# SPDX-FileCopyrightText: © 2020 The glucometerutils Authors -# -# SPDX-License-Identifier: Apache-2.0 - -import argparse -import logging -import sys - -import construct -import usbmon -import usbmon.pcapng - -_SERIAL_NUMBER_RESPONSE_TYPE = 0x06 -_ENCRYPTION_SETUP_REQ_TYPE = 0x14 -_ENCRYPTION_SETUP_RESP_TYPE = 0x33 - - -_START_AUTHORIZE_CMD = 0x11 -_CHALLENGE_CMD = 0x16 -_CHALLENGE_RESPONSE_CMD = 0x17 - - -_ABBOTT_VENDOR_ID = 0x1A61 -_LIBRE2_PRODUCT_ID = 0x3950 - -_SERIAL_NO = construct.Struct( - message_type=construct.Const(_SERIAL_NUMBER_RESPONSE_TYPE, construct.Byte), - length=construct.Const(14, construct.Byte), - serial_number=construct.PaddedString(13, "ascii"), - termination=construct.Const(0, construct.Byte), -) - -_CHALLENGE = construct.Struct( - message_type=construct.Const(_ENCRYPTION_SETUP_RESP_TYPE, construct.Byte), - length=construct.Const(16, construct.Byte), - subcmd=construct.Const(_CHALLENGE_CMD, construct.Byte), - challenge=construct.Bytes(8), - iv=construct.Bytes(7), -) - -_CHALLENGE_RESPONSE = construct.Struct( - message_type=construct.Const(_ENCRYPTION_SETUP_REQ_TYPE, construct.Byte), - length=construct.Const(26, construct.Byte), - subcmd=construct.Const(_CHALLENGE_RESPONSE_CMD, construct.Byte), - challenge_response_encrypted=construct.Bytes(16), - const=construct.Const(1, construct.Byte), - mac=construct.Bytes(8), -) - - -def main(): - if sys.version_info < (3, 7): - raise Exception("Unsupported Python version, please use at least Python 3.7.") - - parser = argparse.ArgumentParser() - - parser.add_argument( - "--device_address", - action="store", - type=str, - help=( - "Device address (busnum.devnum) of the device to extract capture" - " of. If none provided, device descriptors will be relied on." - ), - ) - - parser.add_argument( - "--vlog", - action="store", - required=False, - type=int, - help=( - "Python logging level. See the levels at" - " https://docs.python.org/3/library/logging.html#logging-levels" - ), - ) - - parser.add_argument( - "pcap_files", - action="store", - type=argparse.FileType(mode="rb"), - help="Path to the pcapng file with the USB capture.", - nargs="+", - ) - - args = parser.parse_args() - - logging.basicConfig(level=args.vlog) - - for pcap_file in args.pcap_files: - session = usbmon.pcapng.parse_stream(pcap_file, retag_urbs=False) - - if not args.device_address: - for descriptor in session.device_descriptors.values(): - if ( - descriptor.vendor_id == _ABBOTT_VENDOR_ID - and descriptor.product_id == _LIBRE2_PRODUCT_ID - ): - if ( - args.device_address - and args.device_address != descriptor.address - ): - raise Exception( - "Multiple Libre2 devices present in capture, please" - " provide a --device_address flag." - ) - device_address = descriptor.address - else: - device_address = descriptor.address - - descriptor = session.device_descriptors.get(device_address, None) - if descriptor: - assert descriptor.vendor_id == _ABBOTT_VENDOR_ID - assert descriptor.product_id == _LIBRE2_PRODUCT_ID - - serial_number = "UNKNOWN" - challenge = "UNKNOWN" - iv = "UNKNOWN" - encrypted_challenge = "UNKNOWN" - mac = "UNKNOWN" - - for first, second in session.in_pairs(): - # Ignore stray callbacks/errors. - if not first.type == usbmon.constants.PacketType.SUBMISSION: - continue - - if not first.address.startswith(f"{device_address}."): - # No need to check second, they will be linked. - continue - - if first.xfer_type == usbmon.constants.XferType.INTERRUPT: - pass - elif ( - first.xfer_type == usbmon.constants.XferType.CONTROL - and not first.setup_packet - or first.setup_packet.type == usbmon.setup.Type.CLASS - ): - pass - else: - continue - - if first.direction == usbmon.constants.Direction.OUT: - packet = first - else: - packet = second - - if not packet.payload: - continue - - assert len(packet.payload) >= 2 - - message_type = packet.payload[0] - - if message_type == _SERIAL_NUMBER_RESPONSE_TYPE: - obj = _SERIAL_NO.parse(packet.payload) - serial_number = obj.serial_number - elif ( - message_type == _ENCRYPTION_SETUP_RESP_TYPE - and packet.payload[2] == _CHALLENGE_CMD - ): - obj = _CHALLENGE.parse(packet.payload) - challenge = obj.challenge.hex() - iv = obj.iv.hex() - elif ( - message_type == _ENCRYPTION_SETUP_REQ_TYPE - and packet.payload[2] == _CHALLENGE_RESPONSE_CMD - ): - obj = _CHALLENGE_RESPONSE.parse(packet.payload) - encrypted_challenge = obj.challenge_response_encrypted.hex() - mac = obj.mac.hex() - - print(f"{serial_number},{challenge},{iv},{encrypted_challenge},{mac}") - - -if __name__ == "__main__": - main() diff --git a/reversing_tools/abbott/extract_freestyle.py b/reversing_tools/abbott/extract_freestyle.py deleted file mode 100755 index 0c0888a..0000000 --- a/reversing_tools/abbott/extract_freestyle.py +++ /dev/null @@ -1,245 +0,0 @@ -#!/usr/bin/env python3 -# -# SPDX-FileCopyrightText: © 2019 The usbmon-tools Authors -# SPDX-FileCopyrightText: © 2020 The glucometerutils Authors -# -# SPDX-License-Identifier: Apache-2.0 - -import argparse -import logging -import sys -import textwrap - -import construct -import usbmon -import usbmon.chatter -import usbmon.pcapng - -_KEEPALIVE_TYPE = 0x22 - -_UNENCRYPTED_TYPES = ( - 0x01, - 0x04, - 0x05, - 0x06, - 0x0C, - 0x0D, - 0x14, - 0x15, - 0x33, - 0x34, - 0x35, - 0x71, - _KEEPALIVE_TYPE, -) - -_ENCRYPTION_SETUP_TYPES = (0x14, 0x33) - -_START_AUTHORIZE_CMD = 0x11 -_CHALLENGE_CMD = 0x16 -_CHALLENGE_RESPONSE_CMD = 0x17 -_CHALLENGE_ACCEPTED_CMD = 0x18 - -_ABBOTT_VENDOR_ID = 0x1A61 -_LIBRE2_PRODUCT_ID = 0x3950 - -_ENCRYPTED_MESSAGE = construct.Struct( - message_type=construct.Byte, - encrypted_message=construct.Bytes(64 - 1 - 4 - 4), - sequence_number=construct.Int32ul, - mac=construct.Int32ul, -) - - -def main(): - if sys.version_info < (3, 7): - raise Exception("Unsupported Python version, please use at least Python 3.7.") - - parser = argparse.ArgumentParser() - - parser.add_argument( - "--device_address", - action="store", - type=str, - help=( - "Device address (busnum.devnum) of the device to extract capture" - " of. If none provided, device descriptors will be relied on." - ), - ) - - parser.add_argument( - "--encrypted_protocol", - action="store_true", - help=( - "Whether to expect encrypted protocol in the capture." - " Ignored if the device descriptors are present in the capture." - ), - ) - - parser.add_argument( - "--verbose-encryption-setup", - action="store_true", - help=( - "Whether to parse encryption setup commands and printing their component" - " together with the raw messsage." - ), - ) - - parser.add_argument( - "--vlog", - action="store", - required=False, - type=int, - help=( - "Python logging level. See the levels at" - " https://docs.python.org/3/library/logging.html#logging-levels" - ), - ) - - parser.add_argument( - "--print_keepalive", - action="store_true", - help=( - "Whether to print the keepalive messages sent by the device. " - "Keepalive messages are usually safely ignored." - ), - ) - - parser.add_argument( - "pcap_file", - action="store", - type=argparse.FileType(mode="rb"), - help="Path to the pcapng file with the USB capture.", - ) - - args = parser.parse_args() - - logging.basicConfig(level=args.vlog) - - session = usbmon.pcapng.parse_stream(args.pcap_file, retag_urbs=False) - - if not args.device_address: - for descriptor in session.device_descriptors.values(): - if descriptor.vendor_id == _ABBOTT_VENDOR_ID: - if args.device_address and args.device_address != descriptor.address: - raise Exception( - "Multiple Abbott device present in capture, please" - " provide a --device_address flag." - ) - args.device_address = descriptor.address - - descriptor = session.device_descriptors.get(args.device_address, None) - if not descriptor: - logging.warning( - "Unable to find device %s in the capture's descriptors." - " Assuming non-encrypted protocol.", - args.device_address, - ) - else: - assert descriptor.vendor_id == _ABBOTT_VENDOR_ID - - if descriptor and descriptor.product_id == _LIBRE2_PRODUCT_ID: - args.encrypted_protocol = True - - for first, second in session.in_pairs(): - # Ignore stray callbacks/errors. - if not first.type == usbmon.constants.PacketType.SUBMISSION: - continue - - if not first.address.startswith(f"{args.device_address}."): - # No need to check second, they will be linked. - continue - - if first.xfer_type == usbmon.constants.XferType.INTERRUPT: - pass - elif ( - first.xfer_type == usbmon.constants.XferType.CONTROL - and not first.setup_packet - or first.setup_packet.type == usbmon.setup.Type.CLASS - ): - pass - else: - continue - - if first.direction == usbmon.constants.Direction.OUT: - packet = first - else: - packet = second - - if not packet.payload: - continue - - assert len(packet.payload) >= 2 - - message_type = packet.payload[0] - - if message_type == _KEEPALIVE_TYPE and not args.print_keepalive: - continue - - message_metadata = [] - - if args.encrypted_protocol and message_type not in _UNENCRYPTED_TYPES: - # With encrypted communication, the length of the message is also encrypted, - # and all the packets use the full 64 bytes. So instead, we extract what - # metadata we can. - parsed = _ENCRYPTED_MESSAGE.parse(packet.payload) - message_metadata.extend( - [f"SEQUENCE_NUMBER={parsed.sequence_number}", f"MAC={parsed.mac:04x}"] - ) - - message_type = f"x{message_type:02x}" - message = parsed.encrypted_message - elif args.verbose_encryption_setup and message_type in _ENCRYPTION_SETUP_TYPES: - message_length = packet.payload[1] - message_end_idx = 2 + message_length - message = packet.payload[2:message_end_idx] - - if message[0] == _START_AUTHORIZE_CMD: - message_metadata.append("START_AUTHORIZE") - elif message[0] == _CHALLENGE_CMD: - message_metadata.append("CHALLENGE") - challenge = message[1:9] - iv = message[9:16] - message_metadata.append(f"CHALLENGE={challenge.hex()}") - message_metadata.append(f"IV={iv.hex()}") - elif message[0] == _CHALLENGE_RESPONSE_CMD: - message_metadata.append("CHALLENGE_RESPONSE") - encrypted_challenge = message[1:17] - challenge_mac = message[18:26] - message_metadata.append( - f"ENCRYPTED_CHALLENGE={encrypted_challenge.hex()}" - ) - message_metadata.append(f"MAC={challenge_mac.hex()}") - elif message[0] == _CHALLENGE_ACCEPTED_CMD: - message_metadata.append("CHALLENGE_ACCEPTED") - - message_metadata.append(f"RAW_LENGTH={message_length}") - message_type = f" {message_type:02x}" - else: - message_length = packet.payload[1] - message_metadata.append(f"LENGTH={message_length}") - message_end_idx = 2 + message_length - message_type = f" {message_type:02x}" - message = packet.payload[2:message_end_idx] - - if message_metadata: - metadata_string = "\n".join( - textwrap.wrap( - " ".join(message_metadata), width=80, break_long_words=False - ) - ) - print(metadata_string) - - print( - usbmon.chatter.dump_bytes( - packet.direction, - message, - prefix=f"[{message_type}]", - print_empty=True, - ), - "\n", - ) - - -if __name__ == "__main__": - main() diff --git a/reversing_tools/abbott/freestyle_hid_console.py b/reversing_tools/abbott/freestyle_hid_console.py deleted file mode 100755 index 18df89c..0000000 --- a/reversing_tools/abbott/freestyle_hid_console.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-FileCopyrightText: © 2019 The glucometerutils Authors -# SPDX-License-Identifier: MIT -"""CLI tool to send messages through FreeStyle HID protocol.""" - -import argparse -import logging -import sys - -from glucometerutils import exceptions -from glucometerutils.support import freestyle - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument( - "--text_cmd_type", - action="store", - type=int, - default=0x60, - help="Message type for text commands sent to the device.", - ) - parser.add_argument( - "--text_reply_type", - action="store", - type=int, - default=0x60, - help="Message type for text replies received from the device.", - ) - parser.add_argument( - "device", action="store", help="Path to the HID device to open." - ) - - parser.add_argument( - "--vlog", - action="store", - required=False, - type=int, - help=( - "Python logging level. See the levels at " - "https://docs.python.org/3/library/logging.html#logging-levels" - ), - ) - - args = parser.parse_args() - - logging.basicConfig(level=args.vlog) - - session = freestyle.FreeStyleHidSession( - None, args.device, args.text_cmd_type, args.text_reply_type - ) - - session.connect() - - while True: - if sys.stdin.isatty(): - command = input(">>> ") - else: - command = input() - print(f">>> {command}") - - try: - print(session.send_text_command(bytes(command, "ascii"))) - except exceptions.InvalidResponse as error: - print(f"! {error}") - - -if __name__ == "__main__": - main() diff --git a/reversing_tools/abbott/known-commands.txt b/reversing_tools/abbott/known-commands.txt deleted file mode 100644 index be3f9f0..0000000 --- a/reversing_tools/abbott/known-commands.txt +++ /dev/null @@ -1,48 +0,0 @@ -$getrmndrst,0 -$getrmndr,0 -$rmdstrorder? -$actthm? -$wktrend? -$gunits? -$clktyp? -$alllang? -$lang? -$inslock? -$actinscal? -$iobstatus? -$foodunits? -$svgsdef? -$corsetup? -$insdose? -$inslog? -$inscalsetup? -$carbratio? -$svgsratio? -$mlcalget,3 -$cttype? -$bgdrop? -$bgtrgt? -$bgtgrng? -$ntsound? -$btsound? -$custthm? -$taglang? -$tagsenbl? -$tagorder? -$result? -$gettags,2,2 -$frststrt? -$marketlev? -$brandname? -$uom? -$temp? -$cksm? -$vrom? -$sn? -$serlnum? -$history? -$ptname? -$swver? -$date? -$time? -$ptid? diff --git a/reversing_tools/abbott/known-commands.txt.license b/reversing_tools/abbott/known-commands.txt.license deleted file mode 100644 index c662d53..0000000 --- a/reversing_tools/abbott/known-commands.txt.license +++ /dev/null @@ -1,3 +0,0 @@ -SPDX-FileCopyrightText: 2013 The glucometerutils Authors - -SPDX-License-Identifier: MIT -- cgit v1.2.3