Repository: zhovner/mfdread
Branch: master
Commit: b0697dcd6a42
Files: 4
Total size: 14.3 KB
Directory structure:
gitextract_37e9sl0k/
├── LICENSE
├── README.md
├── dump.mfd
└── mfdread.py
================================================
FILE CONTENTS
================================================
================================================
FILE: LICENSE
================================================
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org>
================================================
FILE: README.md
================================================
Mifare dumps parser
=======
Mifare Classic 1k/4k and Mifare Mini (320 bytes) dumps parser in human readable format.
Dumps can be grabbed with [mfterm](https://github.com/4ZM/mfterm), [mfoc](https://github.com/nfc-tools/mfoc) or nfc-mfclassic tools from libnfc.org
Dump file size must be 1024 or 4096 bytes.
Included ```dump.mfd``` -- Mifare 4k dump for testing.
## Another tools
- [010 Editor](https://www.sweetscape.com/010editor/) — hex editor that has Mifare template. Very handy for editing `mfd` files.
- [mfterm](https://github.com/4ZM/mfterm) — Mifare terminal. Also can view and edit `mfd` dumps.
#### Dependencies:
```easy_install bitstring```
or
```pip install bitstring```
#### Usage:
```mfdread.py ./dump.mfd```

The total memory of 1024 bytes in Mifare Classic (1k) and 4096 bytes in Mifare 4k is divided into 16 sectors of 64 bytes, each of the sectors is divided into 4 blocks of 16 bytes. Blocks 0, 1 and 2 of each sector can store data and block 3 is used to store keys and access bits (the exception is the ‘Manufacturer Block’ which can not store data).

The memory of 1KB and 4KB MIFARE Classic cards is ordered in a similar way. On both cards the first block (block 0) contains the UID, BCC, SAK, ATQA and Manufacturer data. This block is locked and cannot be altered. But some times it can be ;)

##### Access bits
Access bits define the way the data in the sector trailer and the data blocks can be accessed. Access bits are stored twice – inverted and non-inverted in the sector trailer as shown in the images.

Some examples:
Data stored in the sector trailer:
01 02 03 04 05 06 FF 07 80 69 11 12 13 14 15 16
01 02 03 04 05 06 – Key A
FF 07 80 69 – Access bits
11 12 13 14 15 16 – Key B (or data if Key B is not used)
Bytes 6, 7, 8 are access data
FF 07 80
Binary representation:
**1**111**1**111 = FF
**(0)**000**0**111 = 07
**(1)**000**(0)**000 = 80
The bits that are bolded and in parentheses are the ones that define access to keys (C13, C23, C33 in the image above) and they form the 001 sequence. The bits that are bolded and not in parentheses are the same bits inverted. They form, as expected, the sequence 110.
From the table above I can see that 001 means that Key A can not be read, but can be written and Key B may be read. This is the "transport configuration" and was read from the card that was never used.
Another example where access bits 6,7,8 are 0x78 0x77 0x88

#### Terms
Abbreviation | Meaning
------------- | -------------
T=CL | ISO 14443-4 protocol
T=0 | ISO 7816-3 character-level protocol
T=1 | ISO 7816-3 block-level protocol
UID | Unique Identifier, Type A
RID | Random ID, typically dynamically generated at Power-on Reset (UID0 = “0x08”, Random number in UID1… UID3)
NUID | Non-Unique Identifier
ATQA | Answer To Request, type A
ATQB | Answer To Request, type B
SAK | Select Acknowledge, Type A
RATS | Request for Answer To Select
ATS | Answer To Select
ATR | Anser To Reset [What's really ATR means](#ATR)
APDU | Application Protocol Data Unit
DIF | Dual Interface (cards)
COS | Card Operating System
CL | Cascade Level acc. to ISO/IEC 14443-3
CT | Cascade Tag, Type A
NFC | Near Field Communication
PCD | Proximity Coupling Device (“Contactless Reader”)
PICC | Proximity Integrated Circuit (“Contactless Card”)
PKE | Public Key Encryption (like RSA or ECC)
REQA | Request Command, Type A (command 0x26)
WUPA | Wake-up type A (command 0x52)
SEL | Select Command, Type A
RFU | Reserved for future use
### SAK (Select Acknowledge, Type A) parsing
SAK response is 1 bytes length and 2 bytes CRC16.
<img width="300" src="https://i.imgur.com/NmUAYR4.png" />
Bit 3 is cascade bit indicates that UID is not complete and additional select needed.
<img width="500" src="https://i.imgur.com/5BxyYcm.png" />
The bit 6 in the SAK indicates, whether the PICC is compliant to the ISO/IEC14443-4 or not. However, it
does not necessarily indicate, whether the PICC supports the MIFARE Protocol or not.
<img width="500" src="https://i.imgur.com/TUPcVxn.png" />
Other bits in SAK (b1, b2, b4, b5, b7, b8) is not described in ISO 14443-3.
### <a name="ATR"></a>What's really ATR means
ATR is for contact cards and is specified in ISO 7816. For contacless cards, it is the PC/SC reader (IFD) that generates the ATR.
The ATR is constructed based on:
ATS (Answer to Select) for ISO 14443 Type A cards
ATQB and ATTRIB bytes for ISO 14443 Type B cards
The ATR will be of the form 3B 8X 80 01 HB_ATS Parity_Byte where X is the number of bytes of Historical Bytes of ATS (HB_ATS).
The exact construction of ATR for contactless cards is given in section 3.1.3.2.3 of the PC/SC spec.
Given that the only variable is ATS, it should be the same regardless of the reader.
================================================
FILE: mfdread.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# mfdread.py - Mifare dumps parser in human readable format
# Pavel Zhovner <pavel@zhovner.com>
# https://github.com/zhovner/mfdread
import codecs
import copy
import sys
from collections import defaultdict
from bitstring import BitArray
class Options:
FORCE_1K = False
if len(sys.argv) == 1:
sys.exit('''
------------------
Usage: mfdread.py ./dump.mfd
Mifare dumps reader.
''')
def decode(bytes):
decoded = codecs.decode(bytes, "hex")
try:
return str(decoded, "utf-8").rstrip(b'\0')
except:
return ""
class bashcolors:
BLUE = '\033[34m'
RED = '\033[91m'
GREEN = '\033[32m'
WARNING = '\033[93m'
ENDC = '\033[0m'
def accbits_for_blocknum(accbits_str, blocknum):
'''
Decodes the access bit string for block "blocknum".
Returns the three access bits for the block or False if the
inverted bits do not match the access bits.
'''
bits = BitArray([0])
inverted = BitArray([0])
# Block 0 access bits
if blocknum == 0:
bits = BitArray([accbits_str[11], accbits_str[23], accbits_str[19]])
inverted = BitArray([accbits_str[7], accbits_str[3], accbits_str[15]])
# Block 0 access bits
elif blocknum == 1:
bits = BitArray([accbits_str[10], accbits_str[22], accbits_str[18]])
inverted = BitArray([accbits_str[6], accbits_str[2], accbits_str[14]])
# Block 0 access bits
elif blocknum == 2:
bits = BitArray([accbits_str[9], accbits_str[21], accbits_str[17]])
inverted = BitArray([accbits_str[5], accbits_str[1], accbits_str[13]])
# Sector trailer / Block 3 access bits
elif blocknum in (3, 15):
bits = BitArray([accbits_str[8], accbits_str[20], accbits_str[16]])
inverted = BitArray([accbits_str[4], accbits_str[0], accbits_str[12]])
# Check the decoded bits
inverted.invert()
if bits.bin == inverted.bin:
return bits
else:
return False
def accbits_to_permission_sector(accbits):
permissions = {
'000': "- A | A - | A A [read B]",
'010': "- - | A - | A - [read B]",
'100': "- B | A/B - | - B",
'110': "- - | A/B - | - -",
'001': "- A | A A | A A [transport]",
'011': "- B | A/B B | - B",
'101': "- - | A/B B | - -",
'111': "- - | A/B - | - -",
}
if isinstance(accbits, BitArray):
return permissions.get(accbits.bin, "unknown")
else:
return ""
def accbits_to_permission_data(accbits):
permissions = {
'000': "A/B | A/B | A/B | A/B [transport]",
'010': "A/B | - | - | - [r/w]",
'100': "A/B | B | - | - [r/w]",
'110': "A/B | B | B | A/B [value]",
'001': "A/B | - | - | A/B [value]",
'011': " B | B | - | - [r/w]",
'101': " B | - | - | - [r/w]",
'111': " - | - | - | - [r/w]",
}
if isinstance(accbits, BitArray):
return permissions.get(accbits.bin, "unknown")
else:
return ""
def accbit_info(accbits, sector_size):
'''
Returns a dictionary of a access bits for all three blocks in a sector.
If the access bits for block could not be decoded properly, the value is set to False.
'''
access_bits = defaultdict(lambda: False)
if sector_size == 15:
access_bits[sector_size] = accbits_for_blocknum(accbits, sector_size)
return access_bits
# Decode access bits for all 4 blocks of the sector
for i in range(0, 4):
access_bits[i] = accbits_for_blocknum(accbits, i)
return access_bits
def print_info(data):
blocksmatrix = []
blockrights = {}
block_number = 0
data_size = len(data)
if data_size not in {4096, 1024, 320}:
sys.exit("Wrong file size: %d bytes.\nOnly 320, 1024 or 4096 bytes allowed." % len(data))
if Options.FORCE_1K:
data_size = 1024
# read all sectors
sector_number = 0
start = 0
end = 64
while True:
sector = data[start:end]
sector = codecs.encode(sector, 'hex')
if not isinstance(sector, str):
sector = str(sector, 'ascii')
sectors = [sector[x:x + 32] for x in range(0, len(sector), 32)]
blocksmatrix.append(sectors)
# after 32 sectors each sector has 16 blocks instead of 4
sector_number += 1
if sector_number < 32:
start += 64
end += 64
elif sector_number == 32:
start += 64
end += 256
else:
start += 256
end += 256
if start == data_size:
break
blocksmatrix_clear = copy.deepcopy(blocksmatrix)
# add colors for each keyA, access bits, KeyB
for c in range(0, len(blocksmatrix)):
sector_size = len(blocksmatrix[c]) - 1
# Fill in the access bits
blockrights[c] = accbit_info(BitArray('0x' + blocksmatrix[c][sector_size][12:20]), sector_size)
# Prepare colored output of the sector trailor
keyA = bashcolors.RED + blocksmatrix[c][sector_size][0:12] + bashcolors.ENDC
accbits = bashcolors.GREEN + blocksmatrix[c][sector_size][12:20] + bashcolors.ENDC
keyB = bashcolors.BLUE + blocksmatrix[c][sector_size][20:32] + bashcolors.ENDC
blocksmatrix[c][sector_size] = keyA + accbits + keyB
print("File size: %d bytes. Expected %d sectors" % (len(data), sector_number))
print("\n\tUID: " + blocksmatrix[0][0][0:8])
print("\tBCC: " + blocksmatrix[0][0][8:10])
print("\tSAK: " + blocksmatrix[0][0][10:12])
print("\tATQA: " + blocksmatrix[0][0][12:14])
print(" %sKey A%s %sAccess Bits%s %sKey B%s" % (
bashcolors.RED, bashcolors.ENDC, bashcolors.GREEN, bashcolors.ENDC, bashcolors.BLUE, bashcolors.ENDC))
print("╔═════════╦═══════╦══════════════════════════════════╦════════╦═════════════════════════════════════╗")
print("║ Sector ║ Block ║ Data ║ Access ║ A | Acc. | B ║")
print("║ ║ ║ ║ ║ r w | r w | r w [info] ║")
print("║ ║ ║ ║ ║ r | w | i | d/t/r ║")
for q in range(0, len(blocksmatrix)):
print("╠═════════╬═══════╬══════════════════════════════════╬════════╬═════════════════════════════════════╣")
n_blocks = len(blocksmatrix[q])
# z is the block in each sector
for z in range(0, len(blocksmatrix[q])):
# Format the access bits. Print ERR in case of an error
if isinstance(blockrights[q][z], BitArray):
accbits = bashcolors.GREEN + blockrights[q][z].bin + bashcolors.ENDC
else:
accbits = bashcolors.WARNING + "ERR" + bashcolors.ENDC
if q == 0 and z == 0:
permissions = "-"
elif z == n_blocks - 1:
permissions = accbits_to_permission_sector(blockrights[q][z])
else:
permissions = accbits_to_permission_data(blockrights[q][z])
# Print the sector number in the second third row
if z == 2:
qn = q
else:
qn = ""
print("║ %-5s║ %-3d ║ %s ║ %s ║ %-35s ║ %s" % (qn, block_number, blocksmatrix[q][z],
accbits, permissions,
decode(blocksmatrix_clear[q][z])))
block_number += 1
print("╚═════════╩═══════╩══════════════════════════════════╩════════╩═════════════════════════════════════╝")
def main(args):
if args[0] == '-n':
args.pop(0)
bashcolors.BLUE = ""
bashcolors.RED = ""
bashcolors.GREEN = ""
bashcolors.WARNING = ""
bashcolors.ENDC = ""
if args[0] == '-1':
args.pop(0)
Options.FORCE_1K = True
filename = args[0]
with open(filename, "rb") as f:
data = f.read()
print_info(data)
if __name__ == "__main__":
main(sys.argv[1:])
gitextract_37e9sl0k/ ├── LICENSE ├── README.md ├── dump.mfd └── mfdread.py
SYMBOL INDEX (9 symbols across 1 files) FILE: mfdread.py class Options (line 17) | class Options: function decode (line 29) | def decode(bytes): class bashcolors (line 37) | class bashcolors: function accbits_for_blocknum (line 45) | def accbits_for_blocknum(accbits_str, blocknum): function accbits_to_permission_sector (line 80) | def accbits_to_permission_sector(accbits): function accbits_to_permission_data (line 97) | def accbits_to_permission_data(accbits): function accbit_info (line 114) | def accbit_info(accbits, sector_size): function print_info (line 131) | def print_info(data): function main (line 236) | def main(args):
Condensed preview — 4 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (16K chars).
[
{
"path": "LICENSE",
"chars": 1211,
"preview": "This is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, c"
},
{
"path": "README.md",
"chars": 5179,
"preview": "Mifare dumps parser \n=======\n\nMifare Classic 1k/4k and Mifare Mini (320 bytes) dumps parser in human readable format. \n"
},
{
"path": "mfdread.py",
"chars": 8248,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n# mfdread.py - Mifare dumps parser in human readable format\n# Pavel Zho"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the zhovner/mfdread GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 4 files (14.3 KB), approximately 4.2k tokens, and a symbol index with 9 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.