diff --git a/CHANGELOG.md b/CHANGELOG.md index ef066f1..eef1081 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac ## [unreleased][unreleased] - Fixed argument completion bug inserting additional hyphens (@szymex73) + - Fix device address is not the same with APP (@taichunmin) - Added initial version of the user guides (@GameTec-live) - Added support for pasting several command lines at once with prompt_toolkit (@doegox) - Added support for interrupting sleep sequence with a button press during animation (@doegox) diff --git a/docs/cli.md b/docs/cli.md index a39669b..4bcf5e0 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -32,7 +32,7 @@ Using ProxSpace to build the CLI is the easiest and most comfortable way to get 8. Now go into the newly created folder with `cd ChameleonUltra/software/src` -9. Prepare for package installation with `pacman-key --init; pacman-key --populate` +9. Prepare for package installation with `pacman-key --init; pacman-key --populate; pacman -S msys2-keyring --noconfirm` 10. Proceed by installing Ninja with `pacman -S ninja --noconfirm` diff --git a/docs/gui.md b/docs/gui.md index 3a25115..9a62aba 100644 --- a/docs/gui.md +++ b/docs/gui.md @@ -4,4 +4,4 @@ There are multiple GUIs to control your Chameleon, two are featured in this docu - [Chameleon Ultra GUI](./chameleonultragui.md) ([github](https://github.com/GameTec-live/ChameleonUltraGUI)) -- Mtools (No Info Yet) +- [MTools](https://shop.mtoolstec.com/mifare-classic-tool-for-ios) diff --git a/docs/quickstart.md b/docs/quickstart.md index 1364b5b..f309ae9 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -10,9 +10,9 @@ Quickly get up and running with your Chameleon and no technical skill - [Linux](https://nightly.link/GameTec-live/ChameleonUltraGUI/workflows/buildapp/main/linux.zip) - - IOS: Search for Mtools + - [IOS](https://apps.apple.com/at/app/mtools-ble-rfid-reader/id1531345398)(MTools) -2. Plug in Chameleon +2. Plug in or connect via Bluetooth your Chameleon 3. Open GUI diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index a916eff..b679a44 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -83,8 +83,12 @@ data_frame_tx_t *cmd_processor_get_device_chip_id(uint16_t cmd, uint16_t status, data_frame_tx_t *cmd_processor_get_device_address(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint32_t device_address[2]; + // The FICR value is a just a random number, with no knowledge + // of the Bluetooth Specification requirements for random addresses. + // So we need to set a Bluetooth LE random address as a static address. + // See: https://github.com/zephyrproject-rtos/zephyr/blob/7b6b1328a0cb96fe313a5e2bfc57047471df236e/subsys/bluetooth/controller/hci/nordic/hci_vendor.c#L29 device_address[0] = NRF_FICR->DEVICEADDR[0]; - device_address[1] = NRF_FICR->DEVICEADDR[1]; + device_address[1] = NRF_FICR->DEVICEADDR[1] | 0xC000; return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 6, (uint8_t *)(&device_address[0])); } diff --git a/resource/tools/enter_dfu.py b/resource/tools/enter_dfu.py index 7111c8a..2b11cb3 100755 --- a/resource/tools/enter_dfu.py +++ b/resource/tools/enter_dfu.py @@ -3,7 +3,7 @@ import serial import serial.tools.list_ports as list_ports -DFUCMD=b'\x11\xef\x03\xf2\x00\x00\x00\x00\x0b\x00' +DFUCMD = b'\x11\xef\x03\xf2\x00\x00\x00\x00\x0b\x00' port = None for comport in list_ports.comports(): @@ -11,8 +11,8 @@ for comport in list_ports.comports(): print("Chameleon already in DFU mode") exit(0) if comport.vid == 0x6868 and comport.pid == 0x8686: - port = comport.device - break + port = comport.device + break if port is None: print("Chameleon not found") diff --git a/resource/tools/enter_dfu_over_ble.py b/resource/tools/enter_dfu_over_ble.py index 53f0864..eb38c55 100755 --- a/resource/tools/enter_dfu_over_ble.py +++ b/resource/tools/enter_dfu_over_ble.py @@ -6,19 +6,20 @@ from bleak import BleakScanner UUID_NORDIC_TX = "6e400002-b5a3-f393-e0a9-e50e24dcca9e" UUID_NORDIC_RX = "6e400003-b5a3-f393-e0a9-e50e24dcca9e" -DFUCMD=b'\x11\xef\x03\xf2\x00\x00\x00\x00\x0b\x00' +DFUCMD = b'\x11\xef\x03\xf2\x00\x00\x00\x00\x0b\x00' + async def main(): print("Searching ChameleonUltra or ChameleonLite...") device = await BleakScanner.find_device_by_filter(lambda d, ad: d.name and - ((d.name == "ChameleonUltra") or (d.name == "ChameleonLite"))) + ((d.name == "ChameleonUltra") or (d.name == "ChameleonLite"))) if device is None: print("Could not find ChameleonUltra or ChameleonLite") return print(f"Found {device.name} with address {device.address}") print("Sending DFU command") async with BleakClient(device) as client: - c=DFUCMD + c = DFUCMD await client.write_gatt_char(UUID_NORDIC_TX, bytearray(DFUCMD), False) print("Done!") diff --git a/resource/tools/make_style.sh b/resource/tools/make_style.sh index f087b45..eeedc88 100755 --- a/resource/tools/make_style.sh +++ b/resource/tools/make_style.sh @@ -5,17 +5,29 @@ if ! command -v astyle >/dev/null; then echo "Please install 'astyle' package first" ; exit 1 fi +if ! command -v autopep8 >/dev/null; then + echo "Please install 'python3-autopep8' package first" ; + exit 1 +fi # Remove spaces & tabs at EOL, add LF at EOF if needed on *.c, *.h, *.py, Makefile, *.txt -find . \( -not -path "./.git/*" -and -not -path "./firmware/nrf52_sdk/*" -and -not -path "*/venv/*" -and -not -path "*/tmp/*" -and \( -name "*.[ch]" -or -name "*.py" -or -name "Makefile" -or -name "*.txt" \) \) \ +find . \( -not -path "./.git/*" -and -not -path "./firmware/nrf52_sdk/*" -and -not -path "*/venv/*" -and -not -path "*/tmp/*" -and \ + \( -name "*.[ch]" -or -name "*.py" -or -name "Makefile" -or -name "*.txt" \) \) \ -exec perl -pi -e 's/[ \t]+$$//' {} \; \ -exec sh -c "tail -c1 {} | xxd -p | tail -1 | grep -q -v 0a\$" \; \ -exec sh -c "echo >> {}" \; # Apply astyle on *.c, *.h -find . \( -not -path "./.git/*" -and -not -path "./firmware/nrf52_sdk/*" -and -not -path "*/venv/*" -and -not -path "*/tmp/*" -and -name "*.[ch]" \) -exec astyle --formatted --mode=c --suffix=none \ +find . \( -not -path "./.git/*" -and -not -path "./firmware/nrf52_sdk/*" -and -not -path "*/venv/*" -and -not -path "*/tmp/*" -and \ + -name "*.[ch]" \) \ + -exec astyle --formatted --mode=c --suffix=none \ --indent=spaces=4 --indent-switches \ --keep-one-line-blocks --max-instatement-indent=60 \ --style=google --pad-oper --unpad-paren --pad-header \ --align-pointer=name {} \; +# Apply autopep8 on *py +find . \( -not -path "./.git/*" -and -not -path "./firmware/nrf52_sdk/*" -and -not -path "*/venv/*" -and -not -path "*/tmp/*" -and \ + -name "*.py" \) \ + -exec autopep8 -i {} \; + # Detecting tabs. if [[ "$(uname)" == "Darwin" ]]; then @@ -32,5 +44,6 @@ else fi # to remove tabs within lines, one can try with: vi $file -c ':set tabstop=4' -c ':set et|retab' -c ':wq' -find . \( -not -path "./.git/*" -and -not -path "./firmware/nrf52_sdk/*" -and -not -path "*/venv/*" -and -not -path "*/tmp/*" -and \( -name "*.[ch]" -or -name "*.py" -or -name "*.md" -or -name "*.txt" \) \) \ +find . \( -not -path "./.git/*" -and -not -path "./firmware/nrf52_sdk/*" -and -not -path "*/venv/*" -and -not -path "*/tmp/*" -and \ + \( -name "*.[ch]" -or -name "*.py" -or -name "*.md" -or -name "*.txt" \) \) \ -exec sh -c "$TABSCMD" \; diff --git a/software/script/chameleon_cli_main.py b/software/script/chameleon_cli_main.py index a7153f4..a150e08 100755 --- a/software/script/chameleon_cli_main.py +++ b/software/script/chameleon_cli_main.py @@ -42,7 +42,8 @@ class ChameleonCLI: """ def __init__(self): - self.completer = chameleon_utils.CustomNestedCompleter.from_nested_dict(chameleon_cli_unit.root_commands) + self.completer = chameleon_utils.CustomNestedCompleter.from_nested_dict( + chameleon_cli_unit.root_commands) self.session = prompt_toolkit.PromptSession(completer=self.completer, history=FileHistory(pathlib.Path.home() / ".chameleon_history")) @@ -103,12 +104,14 @@ class ChameleonCLI: else: # wait user input try: - cmd_str = self.session.prompt(ANSI(self.get_prompt())).strip() + cmd_str = self.session.prompt( + ANSI(self.get_prompt())).strip() except EOFError: closing = True except KeyboardInterrupt: closing = True - cmd_strs = cmd_str.replace("\r\n", "\n").replace("\r", "\n").split("\n") + cmd_strs = cmd_str.replace( + "\r\n", "\n").replace("\r", "\n").split("\n") cmd_str = cmd_strs.pop(0) if closing or cmd_str in ["exit", "quit", "q", "e"]: @@ -129,18 +132,21 @@ class ChameleonCLI: print("".ljust(18, "-") + "".ljust(10) + "".ljust(30, "-")) for cmd_name, cmd_node in chameleon_cli_unit.root_commands.items(): cmd_title = f"{colorama.Fore.GREEN}{cmd_name}{colorama.Style.RESET_ALL}" - help_line = (f" - {cmd_title}".ljust(37)) + f"[ {cmd_node.help_text} ]" + help_line = (f" - {cmd_title}".ljust(37) + ) + f"[ {cmd_node.help_text} ]" print(help_line) continue - tree_node, arg_list = self.get_cmd_node(chameleon_cli_unit.root_commands[root_cmd], argv[1:]) + tree_node, arg_list = self.get_cmd_node( + chameleon_cli_unit.root_commands[root_cmd], argv[1:]) if not tree_node.cls: # Found tree node is a group without an implementation, print children print("".ljust(18, "-") + "".ljust(10) + "".ljust(30, "-")) for child in tree_node.children: cmd_title = f"{colorama.Fore.GREEN}{child.name}{colorama.Style.RESET_ALL}" - help_line = (f" - {cmd_title}".ljust(37)) + f"[ {child.help_text} ]" + help_line = (f" - {cmd_title}".ljust(37) + ) + f"[ {child.help_text} ]" print(help_line) continue @@ -170,7 +176,8 @@ class ChameleonCLI: except (chameleon_utils.UnexpectedResponseError, chameleon_utils.ArgsParserError) as e: print(f"{colorama.Fore.RED}{str(e)}{colorama.Style.RESET_ALL}") except Exception: - print(f"CLI exception: {colorama.Fore.RED}{traceback.format_exc()}{colorama.Style.RESET_ALL}") + print( + f"CLI exception: {colorama.Fore.RED}{traceback.format_exc()}{colorama.Style.RESET_ALL}") if __name__ == '__main__': diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 1ac759a..6f35221 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -158,8 +158,10 @@ hw_mode = hw.subgroup('mode', 'Device mode get/set') hw_slot = hw.subgroup('slot', 'Emulation tag slot.') hw_slot_nick = hw_slot.subgroup('nick', 'Get/Set tag nick name for slot') hw_settings = hw.subgroup('settings', 'Chameleon settings management') -hw_settings_animation = hw_settings.subgroup('animation', 'Manage wake-up and sleep animation modes') -hw_settings_button_press = hw_settings.subgroup('btnpress', 'Manage button press function') +hw_settings_animation = hw_settings.subgroup( + 'animation', 'Manage wake-up and sleep animation modes') +hw_settings_button_press = hw_settings.subgroup( + 'btnpress', 'Manage button press function') hf = CLITree('hf', 'high frequency tag/reader') hf_14a = hf.subgroup('14a', 'ISO14443-a tag read/write/info...') @@ -169,7 +171,8 @@ hf_mf_detection = hf.subgroup( lf = CLITree('lf', 'low frequency tag/reader') lf_em = lf.subgroup('em', 'EM410x read/write/emulator') -lf_em_sim = lf_em.subgroup('sim', 'Manage EM410x emulation data for selected slot') +lf_em_sim = lf_em.subgroup( + 'sim', 'Manage EM410x emulation data for selected slot') root_commands: dict[str, CLITree] = { 'hw': hw, @@ -194,7 +197,8 @@ class HWConnect(BaseCLIUnit): platform_name = uname().release if 'Microsoft' in platform_name: path = os.environ["PATH"].split(os.pathsep) - path.append("/mnt/c/Windows/System32/WindowsPowerShell/v1.0/") + path.append( + "/mnt/c/Windows/System32/WindowsPowerShell/v1.0/") for prefix in path: fn = os.path.join(prefix, "powershell.exe") if not os.path.isdir(fn) and os.access(fn, os.X_OK): @@ -221,7 +225,8 @@ class HWConnect(BaseCLIUnit): args.port = port.device break if args.port is None: # If no chameleon was found, exit - print("Chameleon not found, please connect the device or try connecting manually with the -p flag.") + print( + "Chameleon not found, please connect the device or try connecting manually with the -p flag.") return self.device_com.open(args.port) print(" { Chameleon connected } ") @@ -253,7 +258,8 @@ class HWModeGet(DeviceRequiredUnit): pass def on_exec(self, args: argparse.Namespace): - print(f"- Device Mode ( Tag {'Reader' if self.cmd.is_reader_device_mode() else 'Emulator'} )") + print( + f"- Device Mode ( Tag {'Reader' if self.cmd.is_reader_device_mode() else 'Emulator'} )") @hw_chipid.command('get', 'Get device chipset ID') @@ -351,7 +357,8 @@ class HFMFNested(ReaderRequiredUnit): help="The block where the key of the card is known") parser.add_argument('--type-known', type=str, required=True, choices=type_choices, help="The key type of the tag") - parser.add_argument('--key-known', type=str, required=True, metavar="hex", help="tag sector key") + parser.add_argument('--key-known', type=str, + required=True, metavar="hex", help="tag sector key") parser.add_argument('--block-target', type=int, metavar="decimal", help="The key of the target block to recover") parser.add_argument('--type-target', type=str, choices=type_choices, @@ -370,10 +377,13 @@ class HFMFNested(ReaderRequiredUnit): :return: """ # acquire - dist_resp = self.cmd.detect_nt_distance(block_known, type_known, key_known) - nt_resp = self.cmd.acquire_nested(block_known, type_known, key_known, block_target, type_target) + dist_resp = self.cmd.detect_nt_distance( + block_known, type_known, key_known) + nt_resp = self.cmd.acquire_nested( + block_known, type_known, key_known, block_target, type_target) # parse - dist_obj = chameleon_cstruct.parse_nt_distance_detect_result(dist_resp.data) + dist_obj = chameleon_cstruct.parse_nt_distance_detect_result( + dist_resp.data) nt_obj = chameleon_cstruct.parse_nested_nt_acquire_group(nt_resp.data) # create cmd cmd_param = f"{dist_obj['uid']} {dist_obj['dist']}" @@ -405,7 +415,8 @@ class HFMFNested(ReaderRequiredUnit): print(f" - [{len(key_list)} candidate keys found ]") for key in key_list: key_bytes = bytearray.fromhex(key) - ret = self.cmd.auth_mf1_key(block_target, type_target, key_bytes) + ret = self.cmd.auth_mf1_key( + block_target, type_target, key_bytes) if ret.status == chameleon_status.Device.HF_TAG_OK: return key else: @@ -429,8 +440,10 @@ class HFMFNested(ReaderRequiredUnit): type_target = args.type_target if block_target is not None and type_target is not None: type_target = 0x60 if type_target == 'A' or type_target == 'a' else 0x61 - print(f" - {colorama.Fore.CYAN}Nested recover one key running...{colorama.Style.RESET_ALL}") - key = self.recover_a_key(block_known, type_known, key_known, block_target, type_target) + print( + f" - {colorama.Fore.CYAN}Nested recover one key running...{colorama.Style.RESET_ALL}") + key = self.recover_a_key( + block_known, type_known, key_known, block_target, type_target) if key is None: print("No keys found, you can retry recover.") else: @@ -439,7 +452,8 @@ class HFMFNested(ReaderRequiredUnit): print("Please input block_target and type_target") self.args_parser().print_help() else: - raise NotImplementedError("hf mf nested recover all key not implement.") + raise NotImplementedError( + "hf mf nested recover all key not implement.") return @@ -464,9 +478,11 @@ class HFMFDarkside(ReaderRequiredUnit): first_recover = True retry_count = 0 while retry_count < 0xFF: - darkside_resp = self.cmd.acquire_darkside(block_target, type_target, first_recover, 15) + darkside_resp = self.cmd.acquire_darkside( + block_target, type_target, first_recover, 15) first_recover = False # not first run. - darkside_obj = chameleon_cstruct.parse_darkside_acquire_result(darkside_resp.data) + darkside_obj = chameleon_cstruct.parse_darkside_acquire_result( + darkside_resp.data) self.darkside_list.append(darkside_obj) recover_params = f"{darkside_obj['uid']}" for darkside_item in self.darkside_list: @@ -497,7 +513,8 @@ class HFMFDarkside(ReaderRequiredUnit): # auth key for key in key_list: key_bytes = bytearray.fromhex(key) - auth_ret = self.cmd.auth_mf1_key(block_target, type_target, key_bytes) + auth_ret = self.cmd.auth_mf1_key( + block_target, type_target, key_bytes) if auth_ret.status == chameleon_status.Device.HF_TAG_OK: return key return None @@ -520,7 +537,8 @@ class BaseMF1AuthOpera(ReaderRequiredUnit): help="The block where the key of the card is known") parser.add_argument('-t', '--type', type=str, required=True, choices=type_choices, help="The key type of the tag") - parser.add_argument('-k', '--key', type=str, required=True, metavar="hex", help="tag sector key") + parser.add_argument('-k', '--key', type=str, + required=True, metavar="hex", help="tag sector key") return parser def get_param(self, args): @@ -562,9 +580,11 @@ class HFMFWRBL(BaseMF1AuthOpera): if not re.match(r"^[a-fA-F0-9]{32}$", args.data): raise ArgsParserError("Data must include 32 HEX symbols") param.data = bytearray.fromhex(args.data) - resp = self.cmd.write_mf1_block(param.block, param.type, param.key, param.data) + resp = self.cmd.write_mf1_block( + param.block, param.type, param.key, param.data) if resp.status == chameleon_status.Device.HF_TAG_OK: - print(f" - {colorama.Fore.GREEN}Write done.{colorama.Style.RESET_ALL}") + print( + f" - {colorama.Fore.GREEN}Write done.{colorama.Style.RESET_ALL}") else: print(f" - {colorama.Fore.RED}Write fail.{colorama.Style.RESET_ALL}") @@ -573,7 +593,8 @@ class HFMFWRBL(BaseMF1AuthOpera): class HFMFDetectionEnable(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() - parser.add_argument('-e', '--enable', type=int, required=True, choices=[1, 0], help="1 = enable, 0 = disable") + parser.add_argument('-e', '--enable', type=int, required=True, + choices=[1, 0], help="1 = enable, 0 = disable") return parser # hf mf detection enable -e 1 @@ -628,7 +649,8 @@ class HFMFDetectionDecrypt(DeviceRequiredUnit): # get output output_str = process.get_output_sync() # print(output_str) - sea_obj = re.search(r"([a-fA-F0-9]{12})", output_str, flags=re.MULTILINE) + sea_obj = re.search( + r"([a-fA-F0-9]{12})", output_str, flags=re.MULTILINE) if sea_obj is not None: keys.append(sea_obj[1]) @@ -638,19 +660,22 @@ class HFMFDetectionDecrypt(DeviceRequiredUnit): def on_exec(self, args: argparse.Namespace): buffer = bytearray() index = 0 - count = int.from_bytes(self.cmd.get_mf1_detection_count().data, "little", signed=False) + count = int.from_bytes( + self.cmd.get_mf1_detection_count().data, "little", signed=False) if count == 0: print(" - No detection log to download") return print(f" - MF1 detection log count = {count}, start download", end="") while index < count: tmp = self.cmd.get_mf1_detection_log(index).data - recv_count = int(len(tmp) / HFMFDetectionDecrypt.detection_log_size) + recv_count = int( + len(tmp) / HFMFDetectionDecrypt.detection_log_size) index += recv_count buffer.extend(tmp) print(".", end="") print() - print(f" - Download done ({len(buffer)}bytes), start parse and decrypt") + print( + f" - Download done ({len(buffer)}bytes), start parse and decrypt") result_maps = chameleon_cstruct.parse_mf1_detection_result(buffer) for uid in result_maps.keys(): @@ -662,18 +687,22 @@ class HFMFDetectionDecrypt(DeviceRequiredUnit): # print(f" - A record: { result_maps[block]['A'] }") records = result_maps_for_uid[block]['A'] if len(records) > 1: - result_maps[uid][block]['A'] = self.decrypt_by_list(records) + result_maps[uid][block]['A'] = self.decrypt_by_list( + records) if 'B' in result_maps_for_uid[block]: # print(f" - B record: { result_maps[block]['B'] }") records = result_maps_for_uid[block]['B'] if len(records) > 1: - result_maps[uid][block]['B'] = self.decrypt_by_list(records) + result_maps[uid][block]['B'] = self.decrypt_by_list( + records) print(" > Result ---------------------------") for block in result_maps_for_uid.keys(): if 'A' in result_maps_for_uid[block]: - print(f" > Block {block}, A key result: {result_maps_for_uid[block]['A']}") + print( + f" > Block {block}, A key result: {result_maps_for_uid[block]['A']}") if 'B' in result_maps_for_uid[block]: - print(f" > Block {block}, B key result: {result_maps_for_uid[block]['B']}") + print( + f" > Block {block}, B key result: {result_maps_for_uid[block]['B']}") return @@ -681,8 +710,10 @@ class HFMFDetectionDecrypt(DeviceRequiredUnit): class HFMFELoad(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() - parser.add_argument('-f', '--file', type=str, required=True, help="file path") - parser.add_argument('-t', '--type', type=str, required=False, help="content type", choices=['bin', 'hex']) + parser.add_argument('-f', '--file', type=str, + required=True, help="file path") + parser.add_argument('-t', '--type', type=str, required=False, + help="content type", choices=['bin', 'hex']) return parser # hf mf eload -f test.bin -t bin @@ -695,7 +726,8 @@ class HFMFELoad(DeviceRequiredUnit): elif file.endswith('.eml'): content_type = 'hex' else: - raise Exception("Unknown file format, Specify content type with -t option") + raise Exception( + "Unknown file format, Specify content type with -t option") else: content_type = args.type buffer = bytearray() @@ -728,8 +760,10 @@ class HFMFELoad(DeviceRequiredUnit): class HFMFERead(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() - parser.add_argument('-f', '--file', type=str, required=True, help="file path") - parser.add_argument('-t', '--type', type=str, required=False, help="content type", choices=['bin', 'hex']) + parser.add_argument('-f', '--file', type=str, + required=True, help="file path") + parser.add_argument('-t', '--type', type=str, required=False, + help="content type", choices=['bin', 'hex']) return parser def on_exec(self, args: argparse.Namespace): @@ -740,7 +774,8 @@ class HFMFERead(DeviceRequiredUnit): elif file.endswith('.eml'): content_type = 'hex' else: - raise Exception("Unknown file format, Specify content type with -t option") + raise Exception( + "Unknown file format, Specify content type with -t option") else: content_type = args.type @@ -756,7 +791,8 @@ class HFMFERead(DeviceRequiredUnit): elif tag_type == chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_4096: block_count = 256 else: - raise Exception("Card in current slot is not Mifare Classic/Plus in SL1 mode") + raise Exception( + "Card in current slot is not Mifare Classic/Plus in SL1 mode") with open(file, 'wb') as fd: block = 0 @@ -800,16 +836,20 @@ class HFMFSettings(DeviceRequiredUnit): def on_exec(self, args: argparse.Namespace): if args.gen1a != -1: self.cmd.set_mf1_gen1a_mode(args.gen1a) - print(f' - Set gen1a mode to {"enabled" if args.gen1a else "disabled"} success') + print( + f' - Set gen1a mode to {"enabled" if args.gen1a else "disabled"} success') if args.gen2 != -1: self.cmd.set_mf1_gen2_mode(args.gen2) - print(f' - Set gen2 mode to {"enabled" if args.gen2 else "disabled"} success') + print( + f' - Set gen2 mode to {"enabled" if args.gen2 else "disabled"} success') if args.coll != -1: self.cmd.set_mf1_block_anti_coll_mode(args.coll) - print(f' - Set anti-collision mode to {"enabled" if args.coll else "disabled"} success') + print( + f' - Set anti-collision mode to {"enabled" if args.coll else "disabled"} success') if args.write != -1: self.cmd.set_mf1_write_mode(args.write) - print(f' - Set write mode to {chameleon_cmd.MifareClassicWriteMode(args.write)} success') + print( + f' - Set write mode to {chameleon_cmd.MifareClassicWriteMode(args.write)} success') print(' - Emulator settings updated') @@ -817,9 +857,12 @@ class HFMFSettings(DeviceRequiredUnit): class HFMFSim(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() - parser.add_argument('--sak', type=str, required=True, help="Select AcKnowledge(hex)", metavar="hex") - parser.add_argument('--atqa', type=str, required=True, help="Answer To Request(hex)", metavar="hex") - parser.add_argument('--uid', type=str, required=True, help="Unique ID(hex)", metavar="hex") + parser.add_argument('--sak', type=str, required=True, + help="Select AcKnowledge(hex)", metavar="hex") + parser.add_argument('--atqa', type=str, required=True, + help="Answer To Request(hex)", metavar="hex") + parser.add_argument('--uid', type=str, required=True, + help="Unique ID(hex)", metavar="hex") return parser # hf mf sim --sak 08 --atqa 0400 --uid DEADBEEF @@ -859,14 +902,16 @@ class LFEMRead(ReaderRequiredUnit): def on_exec(self, args: argparse.Namespace): resp = self.cmd.read_em_410x() id_hex = resp.data.hex() - print(f" - EM410x ID(10H): {colorama.Fore.GREEN}{id_hex}{colorama.Style.RESET_ALL}") + print( + f" - EM410x ID(10H): {colorama.Fore.GREEN}{id_hex}{colorama.Style.RESET_ALL}") class LFEMCardRequiredUnit(DeviceRequiredUnit): @staticmethod def add_card_arg(parser: ArgumentParserNoExit): - parser.add_argument("--id", type=str, required=True, help="EM410x tag id", metavar="hex") + parser.add_argument("--id", type=str, required=True, + help="EM410x tag id", metavar="hex") return parser def before_exec(self, args: argparse.Namespace): @@ -963,7 +1008,8 @@ class HWSlotList(DeviceRequiredUnit): # hw slot list def on_exec(self, args: argparse.Namespace): data = self.cmd.get_slot_info().data - selected = chameleon_cmd.SlotNumber.from_fw(self.cmd.get_active_slot().data[0]) + selected = chameleon_cmd.SlotNumber.from_fw( + self.cmd.get_active_slot().data[0]) enabled = self.cmd.get_enabled_slots().data for slot in chameleon_cmd.SlotNumber: print( @@ -980,11 +1026,16 @@ class HWSlotList(DeviceRequiredUnit): if args.extend: config = self.cmd.get_mf1_emulator_settings().data print(' - Mifare Classic emulator settings:') - print(f' Detection (mfkey32) mode: {"enabled" if config[0] else "disabled"}') - print(f' Gen1A magic mode: {"enabled" if config[1] else "disabled"}') - print(f' Gen2 magic mode: {"enabled" if config[2] else "disabled"}') - print(f' Use anti-collision data from block 0: {"enabled" if config[3] else "disabled"}') - print(f' Write mode: {chameleon_cmd.MifareClassicWriteMode(config[4])}') + print( + f' Detection (mfkey32) mode: {"enabled" if config[0] else "disabled"}') + print( + f' Gen1A magic mode: {"enabled" if config[1] else "disabled"}') + print( + f' Gen2 magic mode: {"enabled" if config[2] else "disabled"}') + print( + f' Use anti-collision data from block 0: {"enabled" if config[3] else "disabled"}') + print( + f' Write mode: {chameleon_cmd.MifareClassicWriteMode(config[4])}') @hw_slot.command('change', 'Set emulation tag slot activated.') @@ -1078,7 +1129,8 @@ class HWSlotEnableSet(SlotIndexRequireUnit): def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() self.add_slot_args(parser) - parser.add_argument('-e', '--enable', type=int, required=True, help="1 is Enable or 0 Disable", choices=[0, 1]) + parser.add_argument('-e', '--enable', type=int, required=True, + help="1 is Enable or 0 Disable", choices=[0, 1]) return parser # hw slot enable -s 1 -e 0 @@ -1086,7 +1138,8 @@ class HWSlotEnableSet(SlotIndexRequireUnit): slot_num = args.slot enable = args.enable self.cmd.set_slot_enable(slot_num, enable) - print(f' - Set slot {slot_num} {"enable" if enable else "disable"} success.') + print( + f' - Set slot {slot_num} {"enable" if enable else "disable"} success.') @lf_em_sim.command('set', 'Set simulated em410x card id') @@ -1123,7 +1176,8 @@ class HWSlotNickSet(SlotIndexRequireUnit, SenseTypeRequireUnit): parser = ArgumentParserNoExit() self.add_slot_args(parser) self.add_sense_type_args(parser) - parser.add_argument('-n', '--name', type=str, required=True, help="Your tag nick name for slot") + parser.add_argument('-n', '--name', type=str, + required=True, help="Your tag nick name for slot") return parser # hw slot nick set -s 1 -st 1 -n Save the test name @@ -1242,7 +1296,8 @@ class HWSettingsAnimationSet(DeviceRequiredUnit): def on_exec(self, args: argparse.Namespace): mode = args.mode self.cmd.set_settings_animation(mode) - print("Animation mode change success. Do not forget to store your settings in flash!") + print( + "Animation mode change success. Do not forget to store your settings in flash!") @hw_settings.command('store', 'Store current settings to flash') @@ -1280,7 +1335,8 @@ class HWFactoryReset(DeviceRequiredUnit): parser.description = "Permanently wipes Chameleon to factory settings. " \ "This will delete all your slot data and custom settings. " \ "There's no going back." - parser.add_argument("--i-know-what-im-doing", default=False, action="store_true", help="Just to be sure :)") + parser.add_argument("--i-know-what-im-doing", default=False, + action="store_true", help="Just to be sure :)") return parser def on_exec(self, args: argparse.Namespace): @@ -1312,7 +1368,8 @@ class HWBatteryInfo(DeviceRequiredUnit): print(f" voltage -> {voltage}mV") print(f" percentage -> {percentage}%") if percentage < HWBatteryInfo.BATTERY_LOW_LEVEL: - print(f"{colorama.Fore.RED}[!] Low battery, please charge.{colorama.Style.RESET_ALL}") + print( + f"{colorama.Fore.RED}[!] Low battery, please charge.{colorama.Style.RESET_ALL}") @hw_settings_button_press.command('get', 'Get button press function of Button A and Button B.') @@ -1331,8 +1388,10 @@ class HWButtonSettingsGet(DeviceRequiredUnit): for button in button_list: resp = self.cmd.get_button_press_fun(button) resp_long = self.cmd.get_long_button_press_fun(button) - button_fn = chameleon_cmd.ButtonPressFunction.from_int(resp.data[0]) - button_long_fn = chameleon_cmd.ButtonPressFunction.from_int(resp_long.data[0]) + button_fn = chameleon_cmd.ButtonPressFunction.from_int( + resp.data[0]) + button_long_fn = chameleon_cmd.ButtonPressFunction.from_int( + resp_long.data[0]) print(f" - {colorama.Fore.GREEN}{button} {colorama.Fore.YELLOW}short{colorama.Style.RESET_ALL}:" " {button_fn}") print(f" usage: {button_fn.usage()}") diff --git a/software/script/chameleon_com.py b/software/script/chameleon_com.py index 1acd0ee..f3a62dbf 100644 --- a/software/script/chameleon_com.py +++ b/software/script/chameleon_com.py @@ -70,7 +70,8 @@ class ChameleonCom: error = None try: # open serial port - self.serial_instance = serial.Serial(port=port, baudrate=115200) + self.serial_instance = serial.Serial( + port=port, baudrate=115200) except Exception as e: error = e finally: @@ -98,7 +99,8 @@ class ChameleonCom: :return: """ if not self.isOpen(): - raise NotOpenException("Please call open() function to start device.") + raise NotOpenException( + "Please call open() function to start device.") @staticmethod def lrc_calc(array): @@ -196,7 +198,8 @@ class ChameleonCom: if callable(fn_call): # delete wait task from map del self.wait_response_map[data_cmd] - fn_call(data_cmd, data_status, data_response) + fn_call(data_cmd, data_status, + data_response) else: self.wait_response_map[data_cmd]['response'] = Response(data_cmd, data_status, data_response) @@ -227,7 +230,8 @@ class ChameleonCom: task_close = task['close'] # register to wait map if 'callback' in task and callable(task['callback']): - self.wait_response_map[task_cmd] = {'callback': task['callback']} # The callback for this task + self.wait_response_map[task_cmd] = { + 'callback': task['callback']} # The callback for this task else: self.wait_response_map[task_cmd] = {'response': None} # set start time @@ -258,7 +262,8 @@ class ChameleonCom: if time.time() > self.wait_response_map[task_cmd]['end_time']: if 'callback' in self.wait_response_map[task_cmd]: # not sync, call function to notify timeout. - self.wait_response_map[task_cmd]['callback'](task_cmd, None, None) + self.wait_response_map[task_cmd]['callback']( + task_cmd, None, None) else: # sync mode, set timeout flag self.wait_response_map[task_cmd]['is_timeout'] = True @@ -303,7 +308,8 @@ class ChameleonCom: del self.wait_response_map[cmd] # make data frame data_frame = self.make_data_frame_bytes(cmd, status, data) - task = {'cmd': cmd, 'frame': data_frame, 'timeout': timeout, 'close': close} + task = {'cmd': cmd, 'frame': data_frame, + 'timeout': timeout, 'close': close} if callable(callback): task['callback'] = callback self.send_data_queue.put(task) diff --git a/software/script/chameleon_cstruct.py b/software/script/chameleon_cstruct.py index 76844f3..14edc5f 100644 --- a/software/script/chameleon_cstruct.py +++ b/software/script/chameleon_cstruct.py @@ -43,7 +43,8 @@ def parse_nested_nt_acquire_group(data: bytearray): """ group = [] if len(data) % 9 != 0: - raise ValueError("Nt data length error, except: { nt(4byte), nt_enc(4byte), par(1byte) } * N") + raise ValueError( + "Nt data length error, except: { nt(4byte), nt_enc(4byte), par(1byte) } * N") i = 0 while i < len(data): group.append({ diff --git a/software/script/chameleon_utils.py b/software/script/chameleon_utils.py index 557e8e8..d2e8e85 100644 --- a/software/script/chameleon_utils.py +++ b/software/script/chameleon_utils.py @@ -57,9 +57,11 @@ def expect_response(accepted_responses: Union[int, list[int]]): if ret.status not in accepted_responses: if ret.status in chameleon_status.Device and ret.status in chameleon_status.message: - raise UnexpectedResponseError(chameleon_status.message[ret.status]) + raise UnexpectedResponseError( + chameleon_status.message[ret.status]) else: - raise UnexpectedResponseError(f"Unexpected response and unknown status {ret.status}") + raise UnexpectedResponseError( + f"Unexpected response and unknown status {ret.status}") return ret @@ -137,7 +139,8 @@ class CustomNestedCompleter(NestedCompleter): elif isinstance(value, dict): options[key] = cls.from_nested_dict(value) elif isinstance(value, set): - options[key] = cls.from_nested_dict({item: None for item in value}) + options[key] = cls.from_nested_dict( + {item: None for item in value}) elif isinstance(value, CLITree): if value.cls: # CLITree is a standalone command @@ -160,7 +163,8 @@ class CustomNestedCompleter(NestedCompleter): for child_node in node.children: if child_node.cls and child_node.cls().args_parser(): # CLITree is a standalone command with arguments - options[child_node.name] = ArgparseCompleter(child_node.cls().args_parser()) + options[child_node.name] = ArgparseCompleter( + child_node.cls().args_parser()) else: # CLITree is a command group options[child_node.name] = cls.from_clitree(child_node) @@ -240,7 +244,8 @@ class ArgparseCompleter(Completer): parsed.append(value) if check_arg(unparsed): - parsed, unparsed, suggestions = self.check_tokens(parsed, unparsed) + parsed, unparsed, suggestions = self.check_tokens( + parsed, unparsed) else: # Show all possible values @@ -251,7 +256,8 @@ class ArgparseCompleter(Completer): else: # No choices, process further arguments if check_arg(unparsed): - parsed, unparsed, suggestions = self.check_tokens(parsed, unparsed) + parsed, unparsed, suggestions = self.check_tokens( + parsed, unparsed) break elif any(opt.startswith(token) for opt in action.option_strings): for opt in action.option_strings: