mirror of
https://github.com/RfidResearchGroup/ChameleonUltra.git
synced 2026-05-12 11:22:59 -07:00
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:
@@ -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):
|
||||
|
||||
@@ -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]:
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user