mdat.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. #!/usr/bin/env python3
  2. """
  3. Miflare Dump Analyse Tool (1K/4K)
  4. A command-line utility for analyzing and manipulating MIFARE Classic dumps.
  5. Main Features:
  6. - Load and display .bin dumps of 1K (16 sectors) or 4K (64 sectors) MIFARE Classic cards
  7. - Optional bit-level view with --bits (-b)
  8. - Visual highlight of UID, BCC, ATQA, and SAK in sector 0, block 0
  9. - Visual parsing of trailer blocks: Key A, Access Bits, User Data, and Key B
  10. - Detect MIFARE tag type and manufacturer
  11. - Calculate and verify BCC for custom UIDs (--calc-bcc)
  12. - Decode access bits (--calc-access) or generate them interactively (--gen-access)
  13. - Compare two dumps with optional diff-only mode (--compare, --diff-only)
  14. - Multilingual support: English (default) and Russian (--lang ru)
  15. Example Usage:
  16. ./mdat.py dump.bin --bits
  17. ./mdat.py --calc-bcc B7 52 3D 22
  18. ./mdat.py --calc-access FF 07 08
  19. ./mdat.py --gen-access
  20. ./mdat.py --compare dump1.bin dump2.bin --diff-only
  21. --
  22. Copyright (c) 2025 te4gh0st
  23. """
  24. import argparse
  25. import sys
  26. # ANSI escape codes for colors
  27. RESET = "\033[0m"
  28. RED = "\033[31m"
  29. GREEN = "\033[32m"
  30. YELLOW = "\033[33m"
  31. CYAN = "\033[36m"
  32. MAGENTA = "\033[35m"
  33. GRAY = "\033[90m"
  34. LANG_TEXT = {
  35. 'en': {
  36. 'sector': 'Sector', 'block': 'Block', 'uid': 'UID', 'bcc': 'BCC',
  37. 'atqa': 'ATQA', 'sak': 'SAK', 'type': 'Tag type', 'mf': 'Manufacturer',
  38. 'access': 'Access', 'calc_bcc': 'Calculated BCC',
  39. 'access_calc': 'Access bits calculation',
  40. 'access_block': 'Access Block',
  41. 'read_write': 'Key A/B read & write (insecure)',
  42. 'read_only': 'Key A read only',
  43. 'read_allow_write_never': 'Read with Key A, writing not allowed',
  44. 'read_write_key_b': 'Read/Write with Key B',
  45. 'read_key_b_write_never': 'Read with Key B, writing not allowed',
  46. 'no_access': 'No access',
  47. 'custom': 'Custom', 'trailer': 'Trailer',
  48. 'compare': 'Comparing dumps',
  49. 'diff_only': 'Differences only',
  50. 'trailer_details': {
  51. (0, 0, 0): 'Key A/B can be read/written, access bits are changeable (insecure)',
  52. (0, 1, 0): 'Key B can be read/written with Key A, access bits are changeable',
  53. (1, 0, 0): 'Key B can be read with Key A, access bits are changeable',
  54. (1, 1, 0): 'Key B can be read with Key A, access bits are changeable (requires Key B)',
  55. (1, 0, 1): 'Access bits readable with Key A/B, write only with Key B. Keys cannot be read.',
  56. (0, 0, 1): 'Transport configuration: access only via Key A, keys are not readable. (insecure)',
  57. (0, 1, 1): 'Read/write via Key B, partially via Key A.',
  58. (1, 1, 1): 'Access blocked. Authentication only.',
  59. },
  60. 'gen_access': '=== Access Bytes Generator ===\nSelect access bits for each block:',
  61. 'input_prompt': 'Choose option [1-8] for block {i}: ',
  62. 'user_data_prompt': 'UserData byte (hex, e.g. 69) [00]: ',
  63. 'invalid_choice': 'Invalid choice. Please enter a number from 1 to 8.',
  64. 'invalid_hex': 'Invalid hex input. Defaulting to 00.',
  65. 'result': 'Result:',
  66. 'no_description': 'No description available.',
  67. 'access_bytes': 'Access Bytes',
  68. "valid_access_bytes": 'Access bytes are valid',
  69. 'invalid_access_bytes': 'Access bytes are INVALID!',
  70. 'byte': 'Byte',
  71. 'differences': 'Total differences'
  72. },
  73. 'ru': {
  74. 'sector': 'Сектор', 'block': 'Блок', 'uid': 'UID', 'bcc': 'BCC',
  75. 'atqa': 'ATQA', 'sak': 'SAK', 'type': 'Тип метки', 'mf': 'Производитель',
  76. 'access': 'Права', 'calc_bcc': 'Вычисленный BCC',
  77. 'access_calc': 'Калькулятор бит доступа',
  78. 'access_block': 'Блок доступа',
  79. 'custom': 'Пользовательские', 'trailer': 'Трейлер',
  80. 'read_write': 'Чтение/запись с ключом A/B (небезопасно)',
  81. 'read_only': 'Только чтение с ключом A',
  82. 'read_allow_write_never': 'Чтение с ключом A, запись невозможна',
  83. 'read_write_key_b': 'Чтение/запись с ключом B',
  84. 'read_key_b_write_never': 'Чтение с ключом B, запись невозможна',
  85. 'no_access': 'Нет доступа',
  86. 'compare': 'Сравнение дампов',
  87. 'diff_only': 'Только различия',
  88. 'trailer_details': {
  89. (0, 0, 0): 'Ключи A/B доступны для чтения/записи, биты доступа изменяемы (небезопасно)',
  90. (0, 1, 0): 'Ключ B доступен для чтения/записи с Key A, биты доступа изменяемы',
  91. (1, 0, 0): 'Ключ B доступен для чтения с Key A, биты доступа изменяемы',
  92. (1, 1, 0): 'Ключ B доступен для чтения с Key A, биты доступа изменяемы (требуется Key B)',
  93. (1, 0, 1): 'Для чтения битов доступа нужен Key A/B, запись только с Key B. Ключи недоступны для чтения.',
  94. (0, 0, 1): 'Транспортная конфигурация: доступ только через Key A, ключи не читаются. (небезопасно)',
  95. (0, 1, 1): 'Чтение и запись через Key B, частично через Key A.',
  96. (1, 1, 1): 'Доступ заблокирован. Только аутентификация.',
  97. },
  98. 'gen_access': '=== Генератор байтов доступа ===\nВыберите биты доступа для каждого блока:',
  99. 'input_prompt': 'Выберите вариант [1-8] для блока {i}: ',
  100. 'user_data_prompt': 'Байт UserData (в hex, напр. 69) [00]: ',
  101. 'invalid_choice': 'Неверный выбор. Введите число от 1 до 8.',
  102. 'invalid_hex': 'Неверный формат hex. Используется значение по умолчанию: 00.',
  103. 'result': 'Результат:',
  104. 'no_description': 'Описание недоступно.',
  105. 'access_bytes': 'Байты доступа',
  106. "valid_access_bytes": 'Байты доступа корректны!',
  107. 'invalid_access_bytes': 'Байты доступа некорректны!',
  108. 'byte': 'Байт',
  109. 'differences': 'Всего отличий'
  110. }
  111. }
  112. access_map = {
  113. (0, 0, 0): ('read_write', None),
  114. (1, 0, 0): ('read_only', None),
  115. (0, 1, 0): ('read_allow_write_never', None),
  116. (0, 0, 1): ('read_write_key_b', None),
  117. (0, 1, 1): ('read_write_key_b', None),
  118. (1, 0, 1): ('read_key_b_write_never', None),
  119. (1, 1, 0): ('read_write_key_b', None),
  120. (1, 1, 1): ('no_access', None),
  121. }
  122. TAG_TYPES = {
  123. (0x0004, 0x08): ('MIFARE Classic 1K', 'NXP'),
  124. (0x0002, 0x18): ('MIFARE Classic 4K', 'NXP'),
  125. (0x0344, 0x38): ('MIFARE Ultralight', 'NXP'),
  126. (0x0044, 0x20): ('MIFARE DESFire EV1/EV2', 'NXP'),
  127. (0x0400, 0x88): ('Cascade Tag (7-byte UID)', 'NXP'),
  128. (0x4400, 0x98): ('MIFARE Classic 4K with 7-byte UID', 'NXP'),
  129. }
  130. def color_bits(val, mask=None, color=YELLOW):
  131. bits = ''.join(str((val >> i) & 1) for i in reversed(range(8)))
  132. if mask is None:
  133. return bits
  134. mask_bits = ''.join(str((mask >> i) & 1) for i in reversed(range(8)))
  135. out = []
  136. for b, m in zip(bits, mask_bits):
  137. out.append((color + b + RESET) if m == '1' else b)
  138. return ''.join(out)
  139. def calc_bcc(uid_bytes):
  140. b = 0
  141. for x in uid_bytes:
  142. b ^= x
  143. return b
  144. def parse_access(byte6, byte7, byte8):
  145. """
  146. Парсит access bits из трёх байт (byte6, byte7, byte8),
  147. проверяет корректность по контрольным битам,
  148. и возвращает матрицу [блок][C1, C2, C3] и флаг валидности.
  149. """
  150. def get_bit(value, bit):
  151. return (value >> bit) & 1
  152. # C1: из byte7, биты 4–7
  153. c1 = [get_bit(byte7, 4 + i) for i in range(4)]
  154. # C1': из byte6, биты 0–3
  155. c1_inv = [get_bit(byte6, i) for i in range(4)]
  156. # C2: из byte8, биты 0–3
  157. c2 = [get_bit(byte8, i) for i in range(4)]
  158. # C2': из byte6, биты 4–7
  159. c2_inv = [get_bit(byte6, 4 + i) for i in range(4)]
  160. # C3: из byte8, биты 4–7
  161. c3 = [get_bit(byte8, 4 + i) for i in range(4)]
  162. # C3': из byte7, биты 0–3
  163. c3_inv = [get_bit(byte7, i) for i in range(4)]
  164. # Проверка валидности контрольных битов
  165. valid = all(
  166. (c1[i] ^ c1_inv[i]) == 1 and
  167. (c2[i] ^ c2_inv[i]) == 1 and
  168. (c3[i] ^ c3_inv[i]) == 1
  169. for i in range(4)
  170. )
  171. # Формируем матрицу [блок][C1, C2, C3]
  172. matrix = [
  173. [c1[0], c2[0], c3[0]],
  174. [c1[1], c2[1], c3[1]],
  175. [c1[2], c2[2], c3[2]],
  176. [c1[3], c2[3], c3[3]],
  177. ]
  178. return matrix, valid
  179. def describe_access(bits_matrix, valid, lang):
  180. t = LANG_TEXT[lang]
  181. desc = {}
  182. for i, (c1, c2, c3) in enumerate(bits_matrix):
  183. if i < 3:
  184. entry = access_map.get((c1, c2, c3))
  185. d = t[entry[0]] if entry else f"{t['custom']} ({c1},{c2},{c3})"
  186. else:
  187. d = t['trailer_details'].get((c1, c2, c3), t['trailer'])
  188. desc[i] = d
  189. return {
  190. "valid": valid,
  191. "description": desc
  192. }
  193. def hexdump(b): return ' '.join(f"{x:02X}" for x in b)
  194. def show_sector(sec, idx, args, txt):
  195. print(CYAN + f"{txt['sector']} {idx}" + RESET)
  196. for i, blk in enumerate(sec):
  197. header = f" {txt['block']} {i}: {hexdump(blk)}"
  198. print(header)
  199. if args.bits:
  200. bits_str = ' '.join(color_bits(x, mask=0xFF) for x in blk)
  201. print(f" Bits : {bits_str}")
  202. # UID block
  203. if idx == 0 and i == 0:
  204. uid = blk[:4]
  205. bcc_byte = blk[4]
  206. sak = blk[5]
  207. atqa = (blk[7] << 8) | blk[6]
  208. calc = calc_bcc(uid)
  209. ok = calc == bcc_byte
  210. print(f" {txt['uid']}: " + ' '.join(MAGENTA + f"{x:02X}" + RESET for x in uid))
  211. print(f" {txt['bcc']}: {YELLOW}{bcc_byte:02X}{RESET} ({txt['calc_bcc']}: {calc:02X}) → " +
  212. (GREEN + "OK" + RESET if ok else RED + "FAIL" + RESET))
  213. print(f" {txt['atqa']}: {atqa:04X}, {txt['sak']}: {sak:02X}")
  214. # Trailer block with Key A, Access Bits, User Data, Key B
  215. if i == 3:
  216. key_a = blk[0:6]
  217. ab6, ab7, ab8 = blk[6], blk[7], blk[8]
  218. user_data = blk[9]
  219. key_b = blk[10:16]
  220. # Colored segments
  221. ka_str = ' '.join(MAGENTA + f"{x:02X}" + RESET for x in key_a)
  222. ab_str = ' '.join(YELLOW + f"{x:02X}" + RESET for x in (ab6, ab7, ab8))
  223. ud_str = CYAN + f"{user_data:02X}" + RESET
  224. kb_str = ' '.join(GREEN + f"{x:02X}" + RESET for x in key_b)
  225. print(f" Key A : {ka_str}")
  226. print(f" Access bits : {ab_str} UserData: {ud_str}")
  227. print(f" Key B : {kb_str}")
  228. # decode access
  229. bits, valid = parse_access(ab6, ab7, ab8)
  230. result = describe_access(bits, valid, args.lang)
  231. desc = result["description"]
  232. t = LANG_TEXT[args.lang]
  233. for block in sorted(desc):
  234. print(f" {t['access_block']} {block}: {MAGENTA}{desc[block]}{RESET}")
  235. # Добавим вывод информации о корректности access bits
  236. if valid:
  237. print(
  238. f" {GREEN}{txt['valid_access_bytes'] if 'valid_access_bytes' in txt else 'Access bytes are valid.'}{RESET}")
  239. else:
  240. print(
  241. f" {RED}{txt['invalid_access_bytes'] if 'invalid_access_bytes' in txt else 'Access bytes are INVALID!'}{RESET}")
  242. def load(path):
  243. d = open(path, 'rb').read()
  244. if len(d) not in (1024, 4096):
  245. sys.exit("Bad dump size")
  246. bl = [d[i:i+16] for i in range(0, len(d), 16)]
  247. return [bl[i*4:(i+1)*4] for i in range(len(bl)//4)]
  248. def generate_access_bytes(specs):
  249. """
  250. Генерирует три байта доступа (byte6, byte7, byte8) по заданным C1,C2,C3 для 4 блоков.
  251. specs — dict: ключ = номер блока 0–3, значение = кортеж (C1, C2, C3).
  252. Возвращает кортеж (byte6, byte7, byte8).
  253. """
  254. byte6 = 0
  255. byte7 = 0
  256. byte8 = 0
  257. for i in range(4):
  258. c1, c2, c3 = specs[i]
  259. # Инвертированные контрольные биты
  260. c1_inv = 1 - c1
  261. c2_inv = 1 - c2
  262. c3_inv = 1 - c3
  263. # byte6:
  264. # биты 0–3 = C1' (c1_inv)
  265. # биты 4–7 = C2' (c2_inv)
  266. byte6 |= (c1_inv << i)
  267. byte6 |= (c2_inv << (4 + i))
  268. # byte7:
  269. # биты 0–3 = C3' (c3_inv)
  270. # биты 4–7 = C1 (c1)
  271. byte7 |= (c3_inv << i)
  272. byte7 |= (c1 << (4 + i))
  273. # byte8:
  274. # биты 0–3 = C2 (c2)
  275. # биты 4–7 = C3 (c3)
  276. byte8 |= (c2 << i)
  277. byte8 |= (c3 << (4 + i))
  278. return byte6, byte7, byte8
  279. def generate_access_interactive(lang):
  280. t = LANG_TEXT[lang]
  281. print(f"{CYAN}{t['gen_access']}{RESET}")
  282. specs = {}
  283. options = list(access_map.items())
  284. print(options)
  285. print()
  286. trailer_options = list(t['trailer_details'].items())
  287. print(trailer_options)
  288. # Сбор желаемых C1,C2,C3 для каждого блока
  289. for i in range(4):
  290. print(f"\n{YELLOW}Block {i}:{RESET}")
  291. if i != 3:
  292. for idx, (bits, (key, _)) in enumerate(options, 1):
  293. print(f" {idx}. C1,C2,C3 = {bits} — {t.get(key, key)}")
  294. while True:
  295. choice = input(t['input_prompt'].format(i=i, max=len(options)))
  296. if choice.isdigit() and 1 <= int(choice) <= len(options):
  297. bits = options[int(choice)-1][0]
  298. specs[i] = bits
  299. if lang == 'ru':
  300. print(f"{MAGENTA}Выбрано: C1={bits[0]}, C2={bits[1]}, C3={bits[2]}{RESET}")
  301. else:
  302. print(f"{MAGENTA}Choice: C1={bits[0]}, C2={bits[1]}, C3={bits[2]}{RESET}")
  303. break
  304. else:
  305. print(f"{RED}{t['invalid_choice']}{RESET}")
  306. else:
  307. for idx, (bits, key) in enumerate(trailer_options, 1):
  308. print(f" {idx}. C1,C2,C3 = {bits} — {t.get(key, key)}")
  309. while True:
  310. choice = input(t['input_prompt'].format(i=i, max=len(options)))
  311. if choice.isdigit() and 1 <= int(choice) <= len(options):
  312. bits = options[int(choice) - 1][0]
  313. specs[i] = bits
  314. if lang == 'ru':
  315. print(f"{MAGENTA}Выбрано: C1={bits[0]}, C2={bits[1]}, C3={bits[2]}{RESET}")
  316. else:
  317. print(f"{MAGENTA}Choice: C1={bits[0]}, C2={bits[1]}, C3={bits[2]}{RESET}")
  318. break
  319. else:
  320. print(f"{RED}{t['invalid_choice']}{RESET}")
  321. # UserData input
  322. ud_in = input(f"{CYAN}{t['user_data_prompt']}{RESET}") or "00"
  323. try:
  324. ud = int(ud_in, 16)
  325. except ValueError:
  326. print(f"{RED}{t['invalid_hex']}{RESET}")
  327. ud = 0x00
  328. # Генерация байт
  329. byte6, byte7, byte8 = generate_access_bytes(specs)
  330. if lang == 'ru':
  331. print(f"\n{GREEN}Сгенерированные байты доступа:{RESET}")
  332. else:
  333. print(f"\n{GREEN}Generated access bytes:{RESET}")
  334. # Дополнительно можно проверить через парсер
  335. matrix, valid = parse_access(byte6, byte7, byte8)
  336. for i, (c1, c2, c3) in enumerate(matrix):
  337. print(f" Block {i}: C1={c1}, C2={c2}, C3={c3}")
  338. print(f"\n{GREEN}{t['result']}{RESET}")
  339. print(f"Access bytes: {YELLOW}{byte6:02X} {byte7:02X} {byte8:02X}{RESET} UserData: {CYAN}{ud:02X}{RESET}")
  340. sys.exit(0)
  341. def highlight_diff_bytes(b1: bytes, b2: bytes) -> tuple[str, str]:
  342. """Подсвечивает отличающиеся байты красным, совпадающие серым"""
  343. h1 = []
  344. h2 = []
  345. for byte1, byte2 in zip(b1, b2):
  346. hex1 = f"{byte1:02X}"
  347. hex2 = f"{byte2:02X}"
  348. if byte1 != byte2:
  349. h1.append(f"{RED}{hex1}{RESET}")
  350. h2.append(f"{RED}{hex2}{RESET}")
  351. else:
  352. h1.append(f"{GRAY}{hex1}{RESET}")
  353. h2.append(f"{GRAY}{hex2}{RESET}")
  354. return ' '.join(h1), ' '.join(h2)
  355. def compare_dumps(path1, path2, args, txt):
  356. d1 = load(path1)
  357. d2 = load(path2)
  358. print(f"{CYAN}{txt['compare']}{RESET}")
  359. diffs = 0
  360. for si, (s1, s2) in enumerate(zip(d1, d2)):
  361. for bi, (b1, b2) in enumerate(zip(s1, s2)):
  362. if args.diff_only:
  363. if b1 != b2:
  364. diffs += 1
  365. print(f"{YELLOW}{txt['sector']} {si} {txt['block']} {bi}:{RESET}")
  366. h1, h2 = highlight_diff_bytes(b1, b2)
  367. print(f" A: {h1}")
  368. print(f" B: {h2}")
  369. else:
  370. marker = GREEN + '==' + RESET if b1 == b2 else RED + '!=' + RESET
  371. h1, h2 = highlight_diff_bytes(b1, b2)
  372. print(f"{txt['sector']} {si:<2} {txt['block']} {bi}: {h1} {marker} {h2}")
  373. if b1 != b2:
  374. diffs += 1
  375. print(f"\n{MAGENTA}{txt['differences']}: {diffs}{RESET}")
  376. sys.exit(0)
  377. def main():
  378. p = argparse.ArgumentParser(
  379. description='Miflare Dump Analyse Tool\nCopyright (c) 2025 te4gh0st',
  380. formatter_class=argparse.RawTextHelpFormatter)
  381. p.add_argument('dump', nargs='?', help='.bin dump file')
  382. p.add_argument('--bits', '-b', action='store_true', help='Show bits view (Показать биты)')
  383. p.add_argument('--lang', choices=['en', 'ru'], default='en', help='Language / Язык')
  384. p.add_argument('--calc-bcc', nargs='+', metavar='BYTE',
  385. help='Calculate BCC for UID bytes (Вычислить BCC для байт UID)')
  386. p.add_argument('--calc-access', nargs=3, metavar='HEX',
  387. help='Decode access bytes FF 07 08 (Декодировать байты доступа FF 07 08)')
  388. p.add_argument('--gen-access', action='store_true', help='Generate access bytes interactively (Интерактивная генерация бит доступа)')
  389. p.add_argument('--compare', nargs=2, metavar=('DUMP1','DUMP2'),
  390. help='Compare two dumps (Сравнить два дампа)')
  391. p.add_argument('--diff-only', action='store_true', help='Show only differences when comparing (Только различия)')
  392. args = p.parse_args()
  393. txt = LANG_TEXT[args.lang]
  394. if args.calc_bcc:
  395. uid = [int(x, 16) for x in args.calc_bcc]
  396. print(f"{txt['uid']}: {' '.join(f"{x:02X}" for x in uid)} → {txt['bcc']}: {calc_bcc(uid):02X}")
  397. sys.exit(0)
  398. if args.calc_access:
  399. ab6, ab7, ab8 = [int(x, 16) for x in args.calc_access]
  400. bits, valid = parse_access(ab6, ab7, ab8)
  401. result = describe_access(bits, valid, args.lang)
  402. desc = result["description"]
  403. t = LANG_TEXT[args.lang]
  404. print(f"{CYAN}{t['access_calc']}{RESET}")
  405. print(f"{YELLOW}{'Access Matrix:':<20}{RESET}")
  406. for block, (c1, c2, c3) in enumerate(bits):
  407. print(f" Block {block:<2}: C1={c1} C2={c2} C3={c3}")
  408. print(f"\n{YELLOW}{'Descriptions:' if args.lang == 'en' else 'Пояснения:'}{RESET}")
  409. for block in sorted(desc):
  410. print(f" {t['access_block']} {block}: {MAGENTA}{desc[block]}{RESET}")
  411. print(f"\n{GREEN}{t['access_bytes']}:{RESET} [{t['byte']} 6] = {RED}{ab6:02X}{RESET} "
  412. f"[{t['byte']} 7] = {RED}{ab7:02X}{RESET} [{t['byte']} 8] = {RED}{ab8:02X}{RESET}")
  413. # Вывод флага корректности
  414. if valid:
  415. print(f"\n{GREEN}{t['valid_access_bytes']}{RESET}")
  416. else:
  417. print(f"\n{RED}{t['invalid_access_bytes']}{RESET}")
  418. sys.exit(0)
  419. if args.gen_access:
  420. generate_access_interactive(args.lang)
  421. if args.compare:
  422. compare_dumps(args.compare[0], args.compare[1], args, txt)
  423. if not args.dump:
  424. p.print_help()
  425. sys.exit(1)
  426. secs = load(args.dump)
  427. # Display tag type and manufacturer
  428. if secs and secs[0] and secs[0][0]:
  429. block0 = secs[0][0]
  430. sak = block0[5]
  431. atqa = (block0[7] << 8) | block0[6]
  432. tag_type, manufacturer = TAG_TYPES.get((atqa, sak), ('Unknown', 'Unknown'))
  433. print(f"{txt['type']}: {MAGENTA}{tag_type}{RESET}\n{txt['mf']}: {MAGENTA}{manufacturer}{RESET}\n")
  434. for i, sec in enumerate(secs):
  435. show_sector(sec, i, args, txt)
  436. if __name__ == '__main__':
  437. main()