@@ -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' )
17771919class HFMFVALUE (ReaderRequiredUnit ):
0 commit comments