Skip to content

Commit dfa2680

Browse files
authored
Merge pull request RfidResearchGroup#201 from rickNmorty2/enh-clone
Add CLI commands to dump and clone tag
2 parents d7ae363 + 4d34799 commit dfa2680

2 files changed

Lines changed: 143 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
33
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...
44

55
## [unreleased][unreleased]
6+
- Added commands to dump and clone Mifare tags
67
- Fix bad missing tools warning (@suut)
78
- Fix for FAST_READ command for nfc - mf0 tags
89
- Rewrite of the dynamic and static locks logic for NTAG213, NTAG215 and NTAG216; we shouldn't take into account the block lock bits

software/script/chameleon_cli_unit.py

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1772,6 +1772,148 @@ def on_exec(self, args: argparse.Namespace):
17721772
raise ArgsParserError("Missing args. Specify --dump-file (-d) or --key-file (-k)")
17731773
print_mem_dump(data, 16)
17741774

1775+
@hf_mf.command('dump')
1776+
class HFMFDump(MF1AuthArgsUnit):
1777+
def args_parser(self) -> ArgumentParserNoExit:
1778+
parser = ArgumentParserNoExit()
1779+
parser.description = 'Mifare Classic dump tag'
1780+
parser.add_argument('-t', '--dump-file-type', type=str, required=False, help="Dump file content type", choices=['bin', 'hex'])
1781+
parser.add_argument('-f', '--dump-file', type=argparse.FileType("wb"), required=True,
1782+
help="Dump file to write data from tag")
1783+
parser.add_argument('-d', '--dic', type=argparse.FileType("r"), required=True,
1784+
help="Read keys (to communicate with tag to dump) from .dic format file")
1785+
return parser
1786+
1787+
def on_exec(self, args: argparse.Namespace):
1788+
# check dump type
1789+
if args.dump_file_type is None:
1790+
if args.dump_file.name.endswith('.bin'):
1791+
content_type = 'bin'
1792+
elif args.dump_file.name.endswith('.eml'):
1793+
content_type = 'hex'
1794+
else:
1795+
raise Exception("Unknown file format, Specify content type with -t option")
1796+
else:
1797+
content_type = args.dump_file_type
1798+
1799+
# read keys from file
1800+
keys = [bytes.fromhex(line[:-1]) for line in args.dic.readlines()]
1801+
1802+
# data to write from dump file
1803+
buffer = bytearray()
1804+
1805+
# iterate over sectors
1806+
for s in range(16):
1807+
# try all keys for this sector
1808+
typ = None
1809+
for key in keys:
1810+
# first try key B
1811+
try:
1812+
self.cmd.mf1_read_one_block(4*s, MfcKeyType.B, key)
1813+
typ = MfcKeyType.B
1814+
break
1815+
except UnexpectedResponseError:
1816+
# ignore read errors at this stage as we want to try key A
1817+
pass
1818+
# try with key A if B was unsuccessful
1819+
try:
1820+
self.cmd.mf1_read_one_block(4*s, MfcKeyType.A, key)
1821+
typ = MfcKeyType.A
1822+
break
1823+
except UnexpectedResponseError:
1824+
pass
1825+
else:
1826+
raise Exception(f"No key found for sector {s}")
1827+
# iterate over blocks
1828+
for b in range(4):
1829+
block_data = self.cmd.mf1_read_one_block(4*s + b, typ, key)
1830+
# add data to buffer
1831+
if content_type == 'bin':
1832+
buffer.extend(block_data)
1833+
elif content_type == 'hex':
1834+
buffer.extend(block_data.hex().encode("utf-8"))
1835+
# write buffer to file
1836+
args.dump_file.write(buffer)
1837+
1838+
@hf_mf.command('clone')
1839+
class HFMFClone(MF1AuthArgsUnit):
1840+
def args_parser(self) -> ArgumentParserNoExit:
1841+
parser = ArgumentParserNoExit()
1842+
parser.description = 'Mifare Classic clone tag from dump'
1843+
parser.add_argument('-t', '--dump-file-type', type=str, required=False, help="Dump file content type", choices=['bin', 'hex'])
1844+
parser.add_argument('-a', '--clone-access', type=bool, default=False, help="Write ACL from original dump too (/!\ could brick your tag)")
1845+
parser.add_argument('-f', '--dump-file', type=argparse.FileType("rb"), required=True,
1846+
help="Dump file containing data to write on new tag")
1847+
parser.add_argument('-d', '--dic', type=argparse.FileType("r"), required=True,
1848+
help="Read keys (to communicate with tag to write) from .dic format file")
1849+
return parser
1850+
1851+
def on_exec(self, args: argparse.Namespace):
1852+
if args.dump_file_type is None:
1853+
if args.dump_file.name.endswith('.bin'):
1854+
content_type = 'bin'
1855+
elif args.dump_file.name.endswith('.eml'):
1856+
content_type = 'hex'
1857+
else:
1858+
raise Exception("Unknown file format, Specify content type with -t option")
1859+
else:
1860+
content_type = args.dump_file_type
1861+
1862+
# data to write from dump file
1863+
buffer = bytearray()
1864+
if content_type == 'bin':
1865+
buffer.extend(args.dump_file.read())
1866+
if content_type == 'hex':
1867+
buffer.extend(bytearray.fromhex(args.dump_file.read().decode()))
1868+
if len(buffer) % 16 != 0:
1869+
raise Exception("Data block not align for 16 bytes")
1870+
if len(buffer) / 16 > 256:
1871+
raise Exception("Data block memory overflow")
1872+
1873+
# keys to use from file
1874+
keys = [bytes.fromhex(line[:-1]) for line in args.dic.readlines()]
1875+
1876+
# iterate over sectors
1877+
for s in range(16):
1878+
# try all keys for this sector
1879+
keyA, keyB = None, None
1880+
for key in keys:
1881+
# first try key B
1882+
try:
1883+
self.cmd.mf1_read_one_block(4*s, MfcKeyType.B, key)
1884+
keyB = key
1885+
except UnexpectedResponseError:
1886+
# ignore read errors at this stage as we want to try key A
1887+
pass
1888+
# try with key A if B was unsuccessful
1889+
try:
1890+
self.cmd.mf1_read_one_block(4*s, MfcKeyType.A, key)
1891+
keyA = key
1892+
except UnexpectedResponseError:
1893+
pass
1894+
# both keys were found, no need to continue iterating
1895+
if keyA and keyB:
1896+
break
1897+
# neither A or B key was found
1898+
if not keyA and not keyB:
1899+
raise Exception(f"No key found for sector {s}")
1900+
# iterate over blocks
1901+
for b in range(4):
1902+
block_data = buffer[(4*s+b)*16:(4*s+b+1)*16]
1903+
# special case for last block of each sector
1904+
if b == 3:
1905+
# check ACL option
1906+
if not args.clone_access:
1907+
# if option is not specified, use generic ACL to be able to write again
1908+
block_data = block_data[:6] + bytes.fromhex("ff0780") + block_data[9:]
1909+
try:
1910+
# try B key first
1911+
self.cmd.mf1_write_one_block(4*s + b, MfcKeyType.B, keyB, block_data)
1912+
continue
1913+
except UnexpectedResponseError:
1914+
pass
1915+
self.cmd.mf1_write_one_block(4*s + b, MfcKeyType.A, keyA, block_data)
1916+
17751917

17761918
@hf_mf.command('value')
17771919
class HFMFVALUE(ReaderRequiredUnit):

0 commit comments

Comments
 (0)