Full Code of zhovner/mfdread for AI

master b0697dcd6a42 cached
4 files
14.3 KB
4.2k tokens
9 symbols
1 requests
Download .txt
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```

![Mifare mfd dump parser](https://zhovner.com/forever/mfdread1.png)

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).
![Mifare memory structure](https://zhovner.com/forever/MiFare_Memory_Structure.png)

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 ;)  
![Mifare zero block structure](https://zhovner.com/forever/0blockmifare.gif)

##### 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.
![Mifare zero block structure](https://zhovner.com/forever/MiFare_Access_Bits.gif)

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  
![mifare access bits explanation](http://i.imgur.com/zTKl6j3.png)

#### 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:])
Download .txt
gitextract_37e9sl0k/

├── LICENSE
├── README.md
├── dump.mfd
└── mfdread.py
Download .txt
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.

Copied to clipboard!