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 <marfo@localhost.localdomain>
This commit is contained in:
simonemarfo
2024-04-24 10:51:11 +08:00
committed by GitHub
co-authored by marfo
parent 767f6e2f7e
commit 0bc01f565a
2 changed files with 95 additions and 0 deletions
+80
View File
@@ -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):
+15
View File
@@ -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]:
"""