]>
Commit | Line | Data |
---|---|---|
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:]) |