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
9 # easy_install bitstring
11 # pip install bitstring
13 # pm3_mfdread.py ./dump.mfd
19 from collections
import defaultdict
22 from bitstring
import BitArray
23 except ModuleNotFoundError
:
24 print("Please install bitstring module first.")
31 if len(sys
.argv
) == 1:
34 Usage: pm3_mfdread.py ./dump.mfd
40 decoded
= codecs
.decode(bytes
, "hex")
42 return str(decoded
, "utf-8").rstrip(b
'\0')
55 def accbits_for_blocknum(accbits_str
, blocknum
):
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.
62 inverted
= BitArray([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]])
71 bits
= BitArray([accbits_str
[10], accbits_str
[22], accbits_str
[18]])
72 inverted
= BitArray([accbits_str
[6], accbits_str
[2], accbits_str
[14]])
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
84 if bits
.bin
== inverted
.bin
:
90 def accbits_to_permission_sector(accbits
):
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")
107 def accbits_to_permission_data(accbits
):
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")
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
)
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
)
141 def print_info(data
):
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
))
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
169 if sector_number
< 32:
172 elif sector_number
== 32:
179 if start
== data_size
:
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
221 accbits
= bashcolors
.WARNING
+ "ERR" + bashcolors
.ENDC
223 if q
== 0 and z
== 0:
226 elif z
== n_blocks
- 1:
227 permissions
= accbits_to_permission_sector(blockrights
[q
][z
])
229 permissions
= accbits_to_permission_data(blockrights
[q
][z
])
231 # Print the sector number in the second third row
237 print("║ %-5s║ %-3d ║ %s ║ %s ║ %-35s ║ %s" % (qn
, block_number
, blocksmatrix
[q
][z
],
238 accbits
, permissions
,
239 decode(blocksmatrix_clear
[q
][z
])))
243 print("╚═════════╩═══════╩══════════════════════════════════╩════════╩═════════════════════════════════════╝")
251 bashcolors
.GREEN
= ""
252 bashcolors
.WARNING
= ""
257 Options
.FORCE_1K
= True
260 with
open(filename
, "rb") as f
:
265 if __name__
== "__main__":