mirror of
https://github.com/RfidResearchGroup/ChameleonUltra.git
synced 2026-05-12 11:22:59 -07:00
Merge pull request #201 from rickNmorty2/enh-clone
Add CLI commands to dump and clone tag
This commit is contained in:
@@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
|
||||
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
|
||||
|
||||
## [unreleased][unreleased]
|
||||
- Added commands to dump and clone Mifare tags
|
||||
- Fix bad missing tools warning (@suut)
|
||||
- Fix for FAST_READ command for nfc - mf0 tags
|
||||
- Rewrite of the dynamic and static locks logic for NTAG213, NTAG215 and NTAG216; we shouldn't take into account the block lock bits
|
||||
|
||||
@@ -1772,6 +1772,148 @@ class HFMFView(MF1AuthArgsUnit):
|
||||
raise ArgsParserError("Missing args. Specify --dump-file (-d) or --key-file (-k)")
|
||||
print_mem_dump(data, 16)
|
||||
|
||||
@hf_mf.command('dump')
|
||||
class HFMFDump(MF1AuthArgsUnit):
|
||||
def args_parser(self) -> ArgumentParserNoExit:
|
||||
parser = ArgumentParserNoExit()
|
||||
parser.description = 'Mifare Classic dump tag'
|
||||
parser.add_argument('-t', '--dump-file-type', type=str, required=False, help="Dump file content type", choices=['bin', 'hex'])
|
||||
parser.add_argument('-f', '--dump-file', type=argparse.FileType("wb"), required=True,
|
||||
help="Dump file to write data from tag")
|
||||
parser.add_argument('-d', '--dic', type=argparse.FileType("r"), required=True,
|
||||
help="Read keys (to communicate with tag to dump) from .dic format file")
|
||||
return parser
|
||||
|
||||
def on_exec(self, args: argparse.Namespace):
|
||||
# check dump type
|
||||
if args.dump_file_type is None:
|
||||
if args.dump_file.name.endswith('.bin'):
|
||||
content_type = 'bin'
|
||||
elif args.dump_file.name.endswith('.eml'):
|
||||
content_type = 'hex'
|
||||
else:
|
||||
raise Exception("Unknown file format, Specify content type with -t option")
|
||||
else:
|
||||
content_type = args.dump_file_type
|
||||
|
||||
# read keys from file
|
||||
keys = [bytes.fromhex(line[:-1]) for line in args.dic.readlines()]
|
||||
|
||||
# data to write from dump file
|
||||
buffer = bytearray()
|
||||
|
||||
# iterate over sectors
|
||||
for s in range(16):
|
||||
# try all keys for this sector
|
||||
typ = None
|
||||
for key in keys:
|
||||
# first try key B
|
||||
try:
|
||||
self.cmd.mf1_read_one_block(4*s, MfcKeyType.B, key)
|
||||
typ = MfcKeyType.B
|
||||
break
|
||||
except UnexpectedResponseError:
|
||||
# ignore read errors at this stage as we want to try key A
|
||||
pass
|
||||
# try with key A if B was unsuccessful
|
||||
try:
|
||||
self.cmd.mf1_read_one_block(4*s, MfcKeyType.A, key)
|
||||
typ = MfcKeyType.A
|
||||
break
|
||||
except UnexpectedResponseError:
|
||||
pass
|
||||
else:
|
||||
raise Exception(f"No key found for sector {s}")
|
||||
# iterate over blocks
|
||||
for b in range(4):
|
||||
block_data = self.cmd.mf1_read_one_block(4*s + b, typ, key)
|
||||
# add data to buffer
|
||||
if content_type == 'bin':
|
||||
buffer.extend(block_data)
|
||||
elif content_type == 'hex':
|
||||
buffer.extend(block_data.hex().encode("utf-8"))
|
||||
# write buffer to file
|
||||
args.dump_file.write(buffer)
|
||||
|
||||
@hf_mf.command('clone')
|
||||
class HFMFClone(MF1AuthArgsUnit):
|
||||
def args_parser(self) -> ArgumentParserNoExit:
|
||||
parser = ArgumentParserNoExit()
|
||||
parser.description = 'Mifare Classic clone tag from dump'
|
||||
parser.add_argument('-t', '--dump-file-type', type=str, required=False, help="Dump file content type", choices=['bin', 'hex'])
|
||||
parser.add_argument('-a', '--clone-access', type=bool, default=False, help="Write ACL from original dump too (/!\ could brick your tag)")
|
||||
parser.add_argument('-f', '--dump-file', type=argparse.FileType("rb"), required=True,
|
||||
help="Dump file containing data to write on new tag")
|
||||
parser.add_argument('-d', '--dic', type=argparse.FileType("r"), required=True,
|
||||
help="Read keys (to communicate with tag to write) from .dic format file")
|
||||
return parser
|
||||
|
||||
def on_exec(self, args: argparse.Namespace):
|
||||
if args.dump_file_type is None:
|
||||
if args.dump_file.name.endswith('.bin'):
|
||||
content_type = 'bin'
|
||||
elif args.dump_file.name.endswith('.eml'):
|
||||
content_type = 'hex'
|
||||
else:
|
||||
raise Exception("Unknown file format, Specify content type with -t option")
|
||||
else:
|
||||
content_type = args.dump_file_type
|
||||
|
||||
# data to write from dump file
|
||||
buffer = bytearray()
|
||||
if content_type == 'bin':
|
||||
buffer.extend(args.dump_file.read())
|
||||
if content_type == 'hex':
|
||||
buffer.extend(bytearray.fromhex(args.dump_file.read().decode()))
|
||||
if len(buffer) % 16 != 0:
|
||||
raise Exception("Data block not align for 16 bytes")
|
||||
if len(buffer) / 16 > 256:
|
||||
raise Exception("Data block memory overflow")
|
||||
|
||||
# keys to use from file
|
||||
keys = [bytes.fromhex(line[:-1]) for line in args.dic.readlines()]
|
||||
|
||||
# iterate over sectors
|
||||
for s in range(16):
|
||||
# try all keys for this sector
|
||||
keyA, keyB = None, None
|
||||
for key in keys:
|
||||
# first try key B
|
||||
try:
|
||||
self.cmd.mf1_read_one_block(4*s, MfcKeyType.B, key)
|
||||
keyB = key
|
||||
except UnexpectedResponseError:
|
||||
# ignore read errors at this stage as we want to try key A
|
||||
pass
|
||||
# try with key A if B was unsuccessful
|
||||
try:
|
||||
self.cmd.mf1_read_one_block(4*s, MfcKeyType.A, key)
|
||||
keyA = key
|
||||
except UnexpectedResponseError:
|
||||
pass
|
||||
# both keys were found, no need to continue iterating
|
||||
if keyA and keyB:
|
||||
break
|
||||
# neither A or B key was found
|
||||
if not keyA and not keyB:
|
||||
raise Exception(f"No key found for sector {s}")
|
||||
# iterate over blocks
|
||||
for b in range(4):
|
||||
block_data = buffer[(4*s+b)*16:(4*s+b+1)*16]
|
||||
# special case for last block of each sector
|
||||
if b == 3:
|
||||
# check ACL option
|
||||
if not args.clone_access:
|
||||
# if option is not specified, use generic ACL to be able to write again
|
||||
block_data = block_data[:6] + bytes.fromhex("ff0780") + block_data[9:]
|
||||
try:
|
||||
# try B key first
|
||||
self.cmd.mf1_write_one_block(4*s + b, MfcKeyType.B, keyB, block_data)
|
||||
continue
|
||||
except UnexpectedResponseError:
|
||||
pass
|
||||
self.cmd.mf1_write_one_block(4*s + b, MfcKeyType.A, keyA, block_data)
|
||||
|
||||
|
||||
@hf_mf.command('value')
|
||||
class HFMFVALUE(ReaderRequiredUnit):
|
||||
|
||||
Reference in New Issue
Block a user