From 0bc01f565aa1c1c196f1ea2d614c4b459a02b0ab Mon Sep 17 00:00:00 2001 From: simonemarfo <56085019+simonemarfo@users.noreply.github.com> Date: Wed, 24 Apr 2024 04:51:11 +0200 Subject: [PATCH] Add CLI view commands to display memory content (#208) * Add utility to print memory dump (xxd like) * Add CLI eview. Dump emulation memory data * Add CLI view. Display content from tag memory or dump file --------- Co-authored-by: marfo --- software/script/chameleon_cli_unit.py | 80 +++++++++++++++++++++++++++ software/script/chameleon_utils.py | 15 +++++ 2 files changed, 95 insertions(+) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index db198b4..bc36994 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -21,6 +21,7 @@ import chameleon_cmd from chameleon_utils import ArgumentParserNoExit, ArgsParserError, UnexpectedResponseError from chameleon_utils import CLITree from chameleon_utils import CR, CG, CB, CC, CY, C0 +from chameleon_utils import print_mem_dump from chameleon_enum import Command, Status, SlotNumber, TagSenseType, TagSpecificType from chameleon_enum import MifareClassicWriteMode, MifareClassicPrngType, MifareClassicDarksideStatus, MfcKeyType from chameleon_enum import AnimationMode, ButtonPressFunction, ButtonType, MfcValueBlockOperator @@ -1011,6 +1012,52 @@ class HFMFWRBL(MF1AuthArgsUnit): else: print(f" - {CR}Write fail.{C0}") +@hf_mf.command('view') +class HFMFView(MF1AuthArgsUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = ArgumentParserNoExit() + parser.description = 'Display content from tag memory or dump file' + mifare_type_group = parser.add_mutually_exclusive_group() + mifare_type_group.add_argument('--mini', help='MIFARE Classic Mini / S20', action='store_const', dest='maxSectors', const=5) + mifare_type_group.add_argument('--1k', help='MIFARE Classic 1k / S50 (default)', action='store_const', dest='maxSectors', const=16) + mifare_type_group.add_argument('--2k', help='MIFARE Classic/Plus 2k', action='store_const', dest='maxSectors', const=32) + mifare_type_group.add_argument('--4k', help='MIFARE Classic 4k / S70', action='store_const', dest='maxSectors', const=40) + parser.add_argument('-d', '--dump-file', required=False, type=argparse.FileType("rb"), help="Dump file to read") + parser.add_argument('-k', '--key-file', required=False, type=argparse.FileType("r"), help="File containing keys of tag to write (exported with fchk --export)") + parser.set_defaults(maxSectors=16) + return parser + + def on_exec(self, args: argparse.Namespace): + data = bytearray(0) + if args.dump_file is not None: + print("Reading dump file") + data = args.dump_file.read() + elif args.key_file is not None: + print("Reading tag memory") + # read keys from file + keys = list() + for line in args.key_file.readlines(): + a, b = [bytes.fromhex(h) for h in line[:-1].split(":")] + keys.append((a, b)) + if len(keys) != args.maxSectors: + raise ArgsParserError(f"Invalid key file. Found {len(keys)}, expected {args.maxSectors}") + # iterate over blocks + for blk in range(0, args.maxSectors * 4): + resp = None + try: + # first try with key B + resp = self.cmd.mf1_read_one_block(blk, MfcKeyType.B, keys[blk//4][1]) + except UnexpectedResponseError: + # ignore read errors at this stage as we want to try key A + pass + if not resp: + # try with key A if B was unsuccessful + # this will raise an exception if key A fails too + resp = self.cmd.mf1_read_one_block(blk, MfcKeyType.A, keys[blk//4][0]) + data.extend(resp) + else: + raise ArgsParserError("Missing args. Specify --dump-file (-d) or --key-file (-k)") + print_mem_dump(data,16) @hf_mf.command('value') class HFMFVALUE(ReaderRequiredUnit): @@ -1420,6 +1467,39 @@ class HFMFESave(SlotIndexArgsAndGoUnit, DeviceRequiredUnit): fd.write(data) print("\n - Read success") +@hf_mf.command('eview') +class HFMFEView(SlotIndexArgsAndGoUnit, DeviceRequiredUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = ArgumentParserNoExit() + parser.description = 'View data from emulator memory' + self.add_slot_args(parser) + return parser + + def on_exec(self, args: argparse.Namespace): + selected_slot = self.cmd.get_active_slot() + slot_info = self.cmd.get_slot_info() + tag_type = TagSpecificType(slot_info[selected_slot]['hf']) + + if tag_type == TagSpecificType.MIFARE_Mini: + block_count = 20 + elif tag_type == TagSpecificType.MIFARE_1024: + block_count = 64 + elif tag_type == TagSpecificType.MIFARE_2048: + block_count = 128 + elif tag_type == TagSpecificType.MIFARE_4096: + block_count = 256 + else: + raise Exception("Card in current slot is not Mifare Classic/Plus in SL1 mode") + index = 0 + data = bytearray(0) + max_blocks = self.device_com.data_max_length // 16 + while block_count > 0: + # read all the blocks + chunk_count = min(block_count, max_blocks) + data.extend(self.cmd.mf1_read_emu_block_data(index, chunk_count)) + index += chunk_count + block_count -= chunk_count + print_mem_dump(data,16) @hf_mf.command('econfig') class HFMFEConfig(SlotIndexArgsAndGoUnit, HF14AAntiCollArgsUnit, DeviceRequiredUnit): diff --git a/software/script/chameleon_utils.py b/software/script/chameleon_utils.py index 3ace362..b758b4a 100644 --- a/software/script/chameleon_utils.py +++ b/software/script/chameleon_utils.py @@ -102,6 +102,21 @@ class ArgumentParserNoExit(argparse.ArgumentParser): print('') self.help_requested = True +def print_mem_dump(bindata, blocksize): + + hexadecimal_len = blocksize*3+1 + ascii_len = blocksize+1 + print(f"[=] ----+{hexadecimal_len*'-'}+{ascii_len*'-'}") + print(f"[=] blk | data{(hexadecimal_len-5)*' '}| ascii") + print(f"[=] ----+{hexadecimal_len*'-'}+{ascii_len*'-'}") + + blocks = [bindata[i:i+blocksize] for i in range(0, len(bindata), blocksize)] + blk_index = 1 + for b in blocks: + hexstr = ' '.join(b.hex()[i:i+2] for i in range(0, len(b.hex()), 2)) + asciistr = ''.join([chr(b[i]) if (b[i] > 31 and b[i] < 127) else '.' for i in range(0,len(b),1)]) + print(f"[=] {blk_index:3} | {hexstr.upper()} | {asciistr} ") + blk_index += 1 def expect_response(accepted_responses: Union[int, list[int]]) -> Callable[..., Any]: """