f2ba7885 |
1 | #!/usr/bin/env python |
2 | # -*- coding: utf-8 -*- |
3 | |
4 | # mfdread.py - Mifare dumps parser in human readable format |
5 | # Pavel Zhovner <pavel@zhovner.com> |
6 | # https://github.com/zhovner/mfdread |
7 | |
8 | |
9 | |
10 | import codecs |
11 | import sys |
ce1cccd6 |
12 | import copy |
f2ba7885 |
13 | from struct import unpack |
14 | from datetime import datetime |
15 | from bitstring import BitArray |
16 | |
ce1cccd6 |
17 | class Options: |
18 | FORCE_1K = False |
f2ba7885 |
19 | |
20 | if len(sys.argv) == 1: |
21 | print(''' |
22 | ------------------ |
23 | Usage: mfdread.py ./dump.mfd |
24 | Mifare dumps reader. |
f2ba7885 |
25 | ''') |
26 | sys.exit(); |
27 | |
ce1cccd6 |
28 | def d(bytes): |
29 | decoded = codecs.decode(bytes, "hex") |
30 | try: |
31 | return str(decoded, "utf-8").rstrip('\0') |
32 | except: |
33 | return "" |
f2ba7885 |
34 | |
35 | |
36 | class bashcolors: |
37 | BLUE = '\033[34m' |
38 | RED = '\033[91m' |
39 | GREEN = '\033[32m' |
40 | WARNING = '\033[93m' |
41 | ENDC = '\033[0m' |
42 | |
43 | |
44 | def accbits_for_blocknum(accbits_str, blocknum): |
45 | ''' |
46 | Decodes the access bit string for block "blocknum". |
47 | Returns the three access bits for the block or False if the |
48 | inverted bits do not match the access bits. |
49 | ''' |
50 | bits = BitArray([0]) |
51 | inverted = BitArray([0]) |
52 | # Block 0 access bits |
53 | if blocknum == 0: |
54 | bits = BitArray([accbits_str[11], accbits_str[23], accbits_str[19]]) |
55 | inverted = BitArray([accbits_str[7], accbits_str[3], accbits_str[15]]) |
56 | |
57 | # Block 0 access bits |
58 | elif blocknum == 1: |
59 | bits = BitArray([accbits_str[10], accbits_str[22], accbits_str[18]]) |
60 | inverted = BitArray([accbits_str[6], accbits_str[2], accbits_str[14]]) |
61 | # Block 0 access bits |
62 | elif blocknum == 2: |
63 | bits = BitArray([accbits_str[9], accbits_str[21], accbits_str[17]]) |
64 | inverted = BitArray([accbits_str[5], accbits_str[1], accbits_str[13]]) |
65 | # Sector trailer / Block 3 access bits |
66 | elif blocknum == 3: |
67 | bits = BitArray([accbits_str[8], accbits_str[20], accbits_str[16]]) |
68 | inverted = BitArray([accbits_str[4], accbits_str[0], accbits_str[12]]) |
69 | |
70 | # Check the decoded bits |
71 | inverted.invert() |
72 | if bits.bin == inverted.bin: |
73 | return bits |
74 | else: |
75 | return False |
76 | |
77 | |
ce1cccd6 |
78 | def accbits_to_permission_sector(accbits): |
79 | permissions = { |
80 | '000': "- A | A - | A A [read B]", |
81 | '010': "- - | A - | A - [read B]", |
82 | '100': "- B | A/B - | - B", |
83 | '110': "- - | A/B - | - -", |
84 | '001': "- A | A A | A A [transport]", |
85 | '011': "- B | A/B B | - B", |
86 | '101': "- - | A/B B | - -", |
87 | '111': "- - | A/B - | - -", |
88 | } |
89 | if isinstance(accbits, BitArray): |
90 | return permissions.get(accbits.bin, "unknown") |
91 | else: |
92 | return "" |
93 | |
94 | def accbits_to_permission_data(accbits): |
95 | permissions = { |
96 | '000': "A/B | A/B | A/B | A/B [transport]", |
97 | '010': "A/B | - | - | - [r/w]", |
98 | '100': "A/B | B | - | - [r/w]", |
99 | '110': "A/B | B | B | A/B [value]", |
100 | '001': "A/B | - | - | A/B [value]", |
101 | '011': " B | B | - | - [r/w]", |
102 | '101': " B | - | - | - [r/w]", |
103 | '111': " - | - | - | - [r/w]", |
104 | } |
105 | if isinstance(accbits, BitArray): |
106 | return permissions.get(accbits.bin, "unknown") |
107 | else: |
108 | return "" |
f2ba7885 |
109 | |
110 | |
111 | def accbit_info(accbits): |
112 | ''' |
113 | Returns a dictionary of a access bits for all three blocks in a sector. |
114 | If the access bits for block could not be decoded properly, the value is set to False. |
115 | ''' |
116 | decAccbits = {} |
117 | # Decode access bits for all 4 blocks of the sector |
118 | for i in range(0, 4): |
119 | decAccbits[i] = accbits_for_blocknum(accbits, i) |
120 | return decAccbits |
121 | |
122 | |
123 | |
124 | |
125 | |
126 | def print_info(data): |
127 | |
128 | blocksmatrix = [] |
129 | blockrights = {} |
130 | |
131 | # determine what dump we get 1k or 4k |
132 | if len(data) == 4096: |
133 | cardsize = 64 |
134 | elif len(data) == 1024: |
135 | cardsize = 16 |
136 | else: |
137 | print("Wrong file size: %d bytes.\nOnly 1024 or 4096 allowed." % len(data)) |
138 | sys.exit(); |
139 | |
ce1cccd6 |
140 | if Options.FORCE_1K: |
141 | cardsize = 16 |
142 | |
f2ba7885 |
143 | # read all sectors |
144 | for i in range(0, cardsize): |
145 | start = i * 64 |
146 | end = (i + 1) * 64 |
147 | sector = data[start:end] |
148 | sector = codecs.encode(sector, 'hex') |
149 | if not type(sector) is str: |
150 | sector = str(sector, 'ascii') |
151 | blocksmatrix.append([sector[x:x+32] for x in range(0, len(sector), 32)]) |
152 | |
ce1cccd6 |
153 | blocksmatrix_clear = copy.deepcopy(blocksmatrix) |
f2ba7885 |
154 | # add colors for each keyA, access bits, KeyB |
155 | for c in range(0, len(blocksmatrix)): |
156 | # Fill in the access bits |
157 | blockrights[c] = accbit_info(BitArray('0x'+blocksmatrix[c][3][12:20])) |
158 | # Prepare colored output of the sector trailor |
159 | keyA = bashcolors.RED + blocksmatrix[c][3][0:12] + bashcolors.ENDC |
160 | accbits = bashcolors.GREEN + blocksmatrix[c][3][12:20] + bashcolors.ENDC |
161 | keyB = bashcolors.BLUE + blocksmatrix[c][3][20:32] + bashcolors.ENDC |
162 | blocksmatrix[c][3] = keyA + accbits + keyB |
163 | |
164 | |
165 | print("File size: %d bytes. Expected %d sectors" %(len(data),cardsize)) |
166 | print("\n\tUID: " + blocksmatrix[0][0][0:8]) |
167 | print("\tBCC: " + blocksmatrix[0][0][8:10]) |
168 | print("\tSAK: " + blocksmatrix[0][0][10:12]) |
169 | print("\tATQA: " + blocksmatrix[0][0][12:14]) |
170 | print(" %sKey A%s %sAccess Bits%s %sKey B%s" %(bashcolors.RED,bashcolors.ENDC,bashcolors.GREEN,bashcolors.ENDC,bashcolors.BLUE,bashcolors.ENDC)) |
ce1cccd6 |
171 | print("╔═════════╦═════╦══════════════════════════════════╦════════╦═════════════════════════════════════╗") |
172 | print("║ Sector ║Block║ Data ║ Access ║ A | Acc. | B ║") |
173 | print("║ ║ ║ ║ ║ r w | r w | r w [info] ║") |
174 | print("║ ║ ║ ║ ║ r | w | i | d/t/r ║") |
f2ba7885 |
175 | for q in range(0, len(blocksmatrix)): |
ce1cccd6 |
176 | print("╠═════════╬═════╬══════════════════════════════════╬════════╬═════════════════════════════════════╣") |
f2ba7885 |
177 | |
178 | # z is the block in each sector |
179 | for z in range(0, len(blocksmatrix[q])): |
180 | # Format the access bits. Print ERR in case of an error |
181 | accbits = "" |
182 | if isinstance(blockrights[q][z], BitArray): |
183 | accbits = bashcolors.GREEN + blockrights[q][z].bin + bashcolors.ENDC |
184 | else: |
185 | accbits = bashcolors.WARNING + "ERR" + bashcolors.ENDC |
186 | |
ce1cccd6 |
187 | if (q == 0 and z == 0): |
188 | permissions = "-" |
189 | elif (z == 3): |
190 | permissions = accbits_to_permission_sector(blockrights[q][z]) |
191 | else: |
192 | permissions = accbits_to_permission_data(blockrights[q][z]) |
193 | |
f2ba7885 |
194 | # Add Padding after the sector number |
195 | padLen = max(1, 5 - len(str(q))) |
196 | padding = " " * padLen |
197 | # Only print the sector number in the second third row |
198 | if (z == 2): |
ce1cccd6 |
199 | print("║ %d%s║ %d ║ %s ║ %s ║ %-35s ║ %s" %(q,padding,z,blocksmatrix[q][z], accbits, permissions, d(blocksmatrix_clear[q][z]))) |
f2ba7885 |
200 | else: |
ce1cccd6 |
201 | print("║ ║ %d ║ %s ║ %s ║ %-35s ║ %s" %(z,blocksmatrix[q][z], accbits, permissions, d(blocksmatrix_clear[q][z]))) |
202 | print("╚═════════╩═════╩══════════════════════════════════╩════════╩═════════════════════════════════════╝") |
203 | |
204 | |
205 | def main(args): |
206 | if args[0] == '-n': |
207 | args.pop(0) |
208 | bashcolors.BLUE = "" |
209 | bashcolors.RED = "" |
210 | bashcolors.GREEN = "" |
211 | bashcolors.WARNING = "" |
212 | bashcolors.ENDC = "" |
f2ba7885 |
213 | |
ce1cccd6 |
214 | if args[0] == '-1': |
215 | args.pop(0) |
216 | Options.FORCE_1K = True |
f2ba7885 |
217 | |
ce1cccd6 |
218 | filename = args[0] |
f2ba7885 |
219 | with open(filename, "rb") as f: |
220 | data = f.read() |
221 | print_info(data) |
222 | |
223 | if __name__ == "__main__": |
ce1cccd6 |
224 | main(sys.argv[1:]) |