]>
Commit | Line | Data |
---|---|---|
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 | |
12 | from struct import unpack | |
13 | from datetime import datetime | |
14 | from bitstring import BitArray | |
15 | ||
16 | ||
17 | if len(sys.argv) == 1: | |
18 | print(''' | |
19 | ------------------ | |
20 | Usage: mfdread.py ./dump.mfd | |
21 | Mifare dumps reader. | |
22 | ||
23 | ''') | |
24 | sys.exit(); | |
25 | ||
26 | ||
27 | ||
28 | ||
29 | class bashcolors: | |
30 | BLUE = '\033[34m' | |
31 | RED = '\033[91m' | |
32 | GREEN = '\033[32m' | |
33 | WARNING = '\033[93m' | |
34 | ENDC = '\033[0m' | |
35 | ||
36 | ||
37 | def accbits_for_blocknum(accbits_str, blocknum): | |
38 | ''' | |
39 | Decodes the access bit string for block "blocknum". | |
40 | Returns the three access bits for the block or False if the | |
41 | inverted bits do not match the access bits. | |
42 | ''' | |
43 | bits = BitArray([0]) | |
44 | inverted = BitArray([0]) | |
45 | # Block 0 access bits | |
46 | if blocknum == 0: | |
47 | bits = BitArray([accbits_str[11], accbits_str[23], accbits_str[19]]) | |
48 | inverted = BitArray([accbits_str[7], accbits_str[3], accbits_str[15]]) | |
49 | ||
50 | # Block 0 access bits | |
51 | elif blocknum == 1: | |
52 | bits = BitArray([accbits_str[10], accbits_str[22], accbits_str[18]]) | |
53 | inverted = BitArray([accbits_str[6], accbits_str[2], accbits_str[14]]) | |
54 | # Block 0 access bits | |
55 | elif blocknum == 2: | |
56 | bits = BitArray([accbits_str[9], accbits_str[21], accbits_str[17]]) | |
57 | inverted = BitArray([accbits_str[5], accbits_str[1], accbits_str[13]]) | |
58 | # Sector trailer / Block 3 access bits | |
59 | elif blocknum == 3: | |
60 | bits = BitArray([accbits_str[8], accbits_str[20], accbits_str[16]]) | |
61 | inverted = BitArray([accbits_str[4], accbits_str[0], accbits_str[12]]) | |
62 | ||
63 | # Check the decoded bits | |
64 | inverted.invert() | |
65 | if bits.bin == inverted.bin: | |
66 | return bits | |
67 | else: | |
68 | return False | |
69 | ||
70 | ||
71 | ||
72 | ||
73 | def accbit_info(accbits): | |
74 | ''' | |
75 | Returns a dictionary of a access bits for all three blocks in a sector. | |
76 | If the access bits for block could not be decoded properly, the value is set to False. | |
77 | ''' | |
78 | decAccbits = {} | |
79 | # Decode access bits for all 4 blocks of the sector | |
80 | for i in range(0, 4): | |
81 | decAccbits[i] = accbits_for_blocknum(accbits, i) | |
82 | return decAccbits | |
83 | ||
84 | ||
85 | ||
86 | ||
87 | ||
88 | def print_info(data): | |
89 | ||
90 | blocksmatrix = [] | |
91 | blockrights = {} | |
92 | ||
93 | # determine what dump we get 1k or 4k | |
94 | if len(data) == 4096: | |
95 | cardsize = 64 | |
96 | elif len(data) == 1024: | |
97 | cardsize = 16 | |
98 | else: | |
99 | print("Wrong file size: %d bytes.\nOnly 1024 or 4096 allowed." % len(data)) | |
100 | sys.exit(); | |
101 | ||
102 | # read all sectors | |
103 | for i in range(0, cardsize): | |
104 | start = i * 64 | |
105 | end = (i + 1) * 64 | |
106 | sector = data[start:end] | |
107 | sector = codecs.encode(sector, 'hex') | |
108 | if not type(sector) is str: | |
109 | sector = str(sector, 'ascii') | |
110 | blocksmatrix.append([sector[x:x+32] for x in range(0, len(sector), 32)]) | |
111 | ||
112 | # add colors for each keyA, access bits, KeyB | |
113 | for c in range(0, len(blocksmatrix)): | |
114 | # Fill in the access bits | |
115 | blockrights[c] = accbit_info(BitArray('0x'+blocksmatrix[c][3][12:20])) | |
116 | # Prepare colored output of the sector trailor | |
117 | keyA = bashcolors.RED + blocksmatrix[c][3][0:12] + bashcolors.ENDC | |
118 | accbits = bashcolors.GREEN + blocksmatrix[c][3][12:20] + bashcolors.ENDC | |
119 | keyB = bashcolors.BLUE + blocksmatrix[c][3][20:32] + bashcolors.ENDC | |
120 | blocksmatrix[c][3] = keyA + accbits + keyB | |
121 | ||
122 | ||
123 | print("File size: %d bytes. Expected %d sectors" %(len(data),cardsize)) | |
124 | print("\n\tUID: " + blocksmatrix[0][0][0:8]) | |
125 | print("\tBCC: " + blocksmatrix[0][0][8:10]) | |
126 | print("\tSAK: " + blocksmatrix[0][0][10:12]) | |
127 | print("\tATQA: " + blocksmatrix[0][0][12:14]) | |
128 | print(" %sKey A%s %sAccess Bits%s %sKey B%s" %(bashcolors.RED,bashcolors.ENDC,bashcolors.GREEN,bashcolors.ENDC,bashcolors.BLUE,bashcolors.ENDC)) | |
129 | print("╔═════════╦═════╦══════════════════════════════════╦═══════════════╗") | |
130 | print("║ Sector ║Block║ Data ║ Access Bits ║") | |
131 | for q in range(0, len(blocksmatrix)): | |
132 | print("╠═════════╬═════╬══════════════════════════════════╬═══════════════╣") | |
133 | ||
134 | # z is the block in each sector | |
135 | for z in range(0, len(blocksmatrix[q])): | |
136 | # Format the access bits. Print ERR in case of an error | |
137 | accbits = "" | |
138 | if isinstance(blockrights[q][z], BitArray): | |
139 | accbits = bashcolors.GREEN + blockrights[q][z].bin + bashcolors.ENDC | |
140 | else: | |
141 | accbits = bashcolors.WARNING + "ERR" + bashcolors.ENDC | |
142 | ||
143 | # Add Padding after the sector number | |
144 | padLen = max(1, 5 - len(str(q))) | |
145 | padding = " " * padLen | |
146 | # Only print the sector number in the second third row | |
147 | if (z == 2): | |
148 | print("║ %d%s║ %d ║ %s ║ %s ║" %(q,padding,z,blocksmatrix[q][z], accbits)) | |
149 | else: | |
150 | print("║ ║ %d ║ %s ║ %s ║" %(z,blocksmatrix[q][z], accbits)) | |
151 | print("╚═════════╩═════╩══════════════════════════════════╩═══════════════╝") | |
152 | ||
153 | ||
154 | def main(filename): | |
155 | with open(filename, "rb") as f: | |
156 | data = f.read() | |
157 | print_info(data) | |
158 | ||
159 | if __name__ == "__main__": | |
160 | main(sys.argv[1]) |