Merge pull request #2593 from Akury83/master
[RRG-proxmark3.git] / tools / mfc / pm3_mfdread.py
blob7e92ea19acaf4f32b6fb0050182e75a3242b2bde
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
4 # mfdread.py - Mifare dumps parser in human readable format
5 # Pavel Zhovner <pavel@zhovner.com>
6 # https://github.com/zhovner/mfdread
8 #Dependencies:
9 # easy_install bitstring
10 #or
11 # pip install bitstring
12 #Usage:
13 # pm3_mfdread.py ./dump.mfd
16 import codecs
17 import copy
18 import sys
19 from collections import defaultdict
21 try:
22 from bitstring import BitArray
23 except ModuleNotFoundError:
24 print("Please install bitstring module first.")
25 sys.exit(1)
27 class Options:
28 FORCE_1K = False
31 if len(sys.argv) == 1:
32 sys.exit('''
33 ------------------
34 Usage: pm3_mfdread.py ./dump.mfd
35 Mifare dumps reader.
36 ''')
39 def decode(bytes):
40 decoded = codecs.decode(bytes, "hex")
41 try:
42 return str(decoded, "utf-8").rstrip(b'\0')
43 except:
44 return ""
47 class bashcolors:
48 BLUE = '\033[34m'
49 RED = '\033[91m'
50 GREEN = '\033[32m'
51 WARNING = '\033[93m'
52 ENDC = '\033[0m'
55 def accbits_for_blocknum(accbits_str, blocknum):
56 '''
57 Decodes the access bit string for block "blocknum".
58 Returns the three access bits for the block or False if the
59 inverted bits do not match the access bits.
60 '''
61 bits = BitArray([0])
62 inverted = BitArray([0])
64 # Block 0 access bits
65 if blocknum == 0:
66 bits = BitArray([accbits_str[11], accbits_str[23], accbits_str[19]])
67 inverted = BitArray([accbits_str[7], accbits_str[3], accbits_str[15]])
69 # Block 0 access bits
70 elif blocknum == 1:
71 bits = BitArray([accbits_str[10], accbits_str[22], accbits_str[18]])
72 inverted = BitArray([accbits_str[6], accbits_str[2], accbits_str[14]])
73 # Block 0 access bits
74 elif blocknum == 2:
75 bits = BitArray([accbits_str[9], accbits_str[21], accbits_str[17]])
76 inverted = BitArray([accbits_str[5], accbits_str[1], accbits_str[13]])
77 # Sector trailer / Block 3 access bits
78 elif blocknum in (3, 15):
79 bits = BitArray([accbits_str[8], accbits_str[20], accbits_str[16]])
80 inverted = BitArray([accbits_str[4], accbits_str[0], accbits_str[12]])
82 # Check the decoded bits
83 inverted.invert()
84 if bits.bin == inverted.bin:
85 return bits
86 else:
87 return False
90 def accbits_to_permission_sector(accbits):
91 permissions = {
92 '000': "- A | A - | A A [read B]",
93 '010': "- - | A - | A - [read B]",
94 '100': "- B | A/B - | - B",
95 '110': "- - | A/B - | - -",
96 '001': "- A | A A | A A [transport]",
97 '011': "- B | A/B B | - B",
98 '101': "- - | A/B B | - -",
99 '111': "- - | A/B - | - -",
101 if isinstance(accbits, BitArray):
102 return permissions.get(accbits.bin, "unknown")
103 else:
104 return ""
107 def accbits_to_permission_data(accbits):
108 permissions = {
109 '000': "A/B | A/B | A/B | A/B [transport]",
110 '010': "A/B | - | - | - [r/w]",
111 '100': "A/B | B | - | - [r/w]",
112 '110': "A/B | B | B | A/B [value]",
113 '001': "A/B | - | - | A/B [value]",
114 '011': " B | B | - | - [r/w]",
115 '101': " B | - | - | - [r/w]",
116 '111': " - | - | - | - [r/w]",
118 if isinstance(accbits, BitArray):
119 return permissions.get(accbits.bin, "unknown")
120 else:
121 return ""
124 def accbit_info(accbits, sector_size):
126 Returns a dictionary of a access bits for all three blocks in a sector.
127 If the access bits for block could not be decoded properly, the value is set to False.
129 access_bits = defaultdict(lambda: False)
131 if sector_size == 15:
132 access_bits[sector_size] = accbits_for_blocknum(accbits, sector_size)
133 return access_bits
135 # Decode access bits for all 4 blocks of the sector
136 for i in range(0, 4):
137 access_bits[i] = accbits_for_blocknum(accbits, i)
138 return access_bits
141 def print_info(data):
142 blocksmatrix = []
143 blockrights = {}
144 block_number = 0
146 data_size = len(data)
148 if data_size not in {4096, 1024}:
149 sys.exit("Wrong file size: %d bytes.\nOnly 1024 or 4096 allowed." % len(data))
151 if Options.FORCE_1K:
152 data_size = 1024
154 # read all sectors
155 sector_number = 0
156 start = 0
157 end = 64
158 while True:
159 sector = data[start:end]
160 sector = codecs.encode(sector, 'hex')
161 if not isinstance(sector, str):
162 sector = str(sector, 'ascii')
163 sectors = [sector[x:x + 32] for x in range(0, len(sector), 32)]
165 blocksmatrix.append(sectors)
167 # after 32 sectors each sector has 16 blocks instead of 4
168 sector_number += 1
169 if sector_number < 32:
170 start += 64
171 end += 64
172 elif sector_number == 32:
173 start += 64
174 end += 256
175 else:
176 start += 256
177 end += 256
179 if start == data_size:
180 break
182 blocksmatrix_clear = copy.deepcopy(blocksmatrix)
184 # add colors for each keyA, access bits, KeyB
185 for c in range(0, len(blocksmatrix)):
186 sector_size = len(blocksmatrix[c]) - 1
188 # Fill in the access bits
189 blockrights[c] = accbit_info(BitArray('0x' + blocksmatrix[c][sector_size][12:20]), sector_size)
191 # Prepare colored output of the sector trailor
192 keyA = bashcolors.RED + blocksmatrix[c][sector_size][0:12] + bashcolors.ENDC
193 accbits = bashcolors.GREEN + blocksmatrix[c][sector_size][12:20] + bashcolors.ENDC
194 keyB = bashcolors.BLUE + blocksmatrix[c][sector_size][20:32] + bashcolors.ENDC
196 blocksmatrix[c][sector_size] = keyA + accbits + keyB
198 print("File size: %d bytes. Expected %d sectors" % (len(data), sector_number))
199 print("\n\tUID: " + blocksmatrix[0][0][0:8])
200 print("\tBCC: " + blocksmatrix[0][0][8:10])
201 print("\tSAK: " + blocksmatrix[0][0][10:12])
202 print("\tATQA: " + blocksmatrix[0][0][12:14])
203 print(" %sKey A%s %sAccess Bits%s %sKey B%s" % (
204 bashcolors.RED, bashcolors.ENDC, bashcolors.GREEN, bashcolors.ENDC, bashcolors.BLUE, bashcolors.ENDC))
205 print("╔═════════╦═══════╦══════════════════════════════════╦════════╦═════════════════════════════════════╗")
206 print("║ Sector ║ Block ║ Data ║ Access ║ A | Acc. | B ║")
207 print("║ ║ ║ ║ ║ r w | r w | r w [info] ║")
208 print("║ ║ ║ ║ ║ r | w | i | d/t/r ║")
210 for q in range(0, len(blocksmatrix)):
211 print("╠═════════╬═══════╬══════════════════════════════════╬════════╬═════════════════════════════════════╣")
212 n_blocks = len(blocksmatrix[q])
214 # z is the block in each sector
215 for z in range(0, len(blocksmatrix[q])):
217 # Format the access bits. Print ERR in case of an error
218 if isinstance(blockrights[q][z], BitArray):
219 accbits = bashcolors.GREEN + blockrights[q][z].bin + bashcolors.ENDC
220 else:
221 accbits = bashcolors.WARNING + "ERR" + bashcolors.ENDC
223 if q == 0 and z == 0:
224 permissions = "-"
226 elif z == n_blocks - 1:
227 permissions = accbits_to_permission_sector(blockrights[q][z])
228 else:
229 permissions = accbits_to_permission_data(blockrights[q][z])
231 # Print the sector number in the second third row
232 if z == 2:
233 qn = q
234 else:
235 qn = ""
237 print("║ %-5s║ %-3d ║ %s %s %-35s ║ %s" % (qn, block_number, blocksmatrix[q][z],
238 accbits, permissions,
239 decode(blocksmatrix_clear[q][z])))
241 block_number += 1
243 print("╚═════════╩═══════╩══════════════════════════════════╩════════╩═════════════════════════════════════╝")
246 def main(args):
247 if args[0] == '-n':
248 args.pop(0)
249 bashcolors.BLUE = ""
250 bashcolors.RED = ""
251 bashcolors.GREEN = ""
252 bashcolors.WARNING = ""
253 bashcolors.ENDC = ""
255 if args[0] == '-1':
256 args.pop(0)
257 Options.FORCE_1K = True
259 filename = args[0]
260 with open(filename, "rb") as f:
261 data = f.read()
262 print_info(data)
265 if __name__ == "__main__":
266 main(sys.argv[1:])