Mifare Classic RFID tag dump: format specification

You can get a dump for testing from this link: https://github.com/zhovner/mfdread/raw/master/dump.mfd

File extension

mfd

KS implementation details

License: BSD-2-Clause
Minimal Kaitai Struct required: 0.9

References

This page hosts a formal specification of Mifare Classic RFID tag dump using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Block diagram

Format specification in Kaitai Struct YAML

meta:
  id: mifare_classic
  title: Mifare Classic RFID tag dump
  file-extension: mfd
  xref:
    wikidata:
      - Q29000512 # MIFARE Classic 1k
      - Q29000513 # MIFARE Classic 4k
  license: BSD-2-Clause
  ks-version: 0.9
  endian: le
doc-ref: |
  https://github.com/nfc-tools/libnfc
  https://www.nxp.com/docs/en/data-sheet/MF1S70YYX_V1.pdf
doc: |
  You can get a dump for testing from this link:
  <https://github.com/zhovner/mfdread/raw/master/dump.mfd>
seq:
  - id: sectors
    size: ((_index >= 32)?4:1)*4*16 #sorry for this doubling of `block_size` (16), but we need `sector` be self-sufficient so we cannot use _root there
    type: sector(_index == 0)
    repeat: eos
types:
  key:
    seq:
      - id: key
        size: 6
  sector:
    params:
      - id: has_manufacturer
        type: bool
    seq:
      - id: manufacturer
        type: manufacturer
        if: has_manufacturer

      - id: data_filler
        -orig-id: abtData
        size: _io.size - _io.pos - 16 # sizeof(trailer)
        type: filler
      - id: trailer
        type: trailer
    instances:
      block_size:
        value: 16
      data:
        value: data_filler.data
      blocks:
        pos: 0
        io: data_filler._io
        size: block_size
        repeat: eos
      values:
        pos: 0
        io: data_filler._io
        type: values
    types:
      values:
        seq:
          - id: values
            type: value_block
            repeat: eos
        types:
          value_block:
            seq:
              - id: valuez
                type: u4
                repeat: expr
                repeat-expr: 3
              - id: addrz
                type: u1
                repeat: expr
                repeat-expr: 4
            instances:
              value_valid:
                value: 'valuez[0]==~valuez[1] and valuez[0]==valuez[2]'
              addr_valid:
                value: 'addrz[0]==~addrz[1] and addrz[0]==addrz[2] and addrz[1]==addrz[3]'
              valid:
                value: 'value_valid and addr_valid'
              addr:
                value: 'addrz[0]'
                if: valid
              value:
                value: valuez[0]
                if: valid
      filler:
        doc: "only to create _io"
        seq:
          - id: data
            size: _io.size
  manufacturer:
    seq:
      - id: nuid
        -orig-id: abtUID
        type: u4
        doc: beware for 7bytes UID it goes over next fields
      - id: bcc
        -orig-id: btBCC
        type: u1
      - id: sak # beware it's not always exactly SAK
        -orig-id: btSAK
        type: u1
      - id: atqa
        -orig-id: abtATQA
        type: u2
      - id: manufacturer
        -orig-id: abtManufacturer
        size: 8
        doc: may contain manufacture date as BCD
  trailer:
    seq:
      - id: key_a
        -orig-id: abtKeyA
        type: key
      - id: access_bits
        -orig-id: abtAccessBits
        size: 3
        type: access_conditions
      - id: user_byte
        -orig-id: abtAccessBits
        type: u1

      - id: key_b
        -orig-id: abtKeyB
        type: key
    instances:
      ac_bits:
        value: 3
      acs_in_sector:
        value: 4
      ac_count_of_chunks:
        value: ac_bits*2 #6

    types:
      access_conditions:
        seq:
          - id: raw_chunks
            type: b4 # _parent.acs_in_sector
            repeat: expr
            repeat-expr: _parent.ac_count_of_chunks
        instances:
          remaps:
            pos: 0
            repeat: expr
            repeat-expr: _parent.ac_bits
            type: chunk_bit_remap(_index)
          chunks:
            pos: 0
            type: valid_chunk(raw_chunks[remaps[_index].inv_chunk_no], raw_chunks[remaps[_index].chunk_no])
            repeat: expr
            repeat-expr: _parent.ac_bits
          acs_raw:
            pos: 0
            type: ac(_index)
            repeat: expr
            repeat-expr: _parent.acs_in_sector
          data_acs:
            pos: 0
            type: data_ac(acs_raw[_index])
            repeat: expr
            repeat-expr: _parent.acs_in_sector-1
          trailer_ac:
            pos: 0
            type: trailer_ac(acs_raw[_parent.acs_in_sector-1])
        types:
          chunk_bit_remap:
            params:
              - id: bit_no
                type: u1
            instances:
              shift_value:
                value: (bit_no==1?-1:1)
              chunk_no:
                value: '((inv_chunk_no+shift_value+_parent._parent.ac_count_of_chunks)%_parent._parent.ac_count_of_chunks)'
              inv_chunk_no:
                value: 'bit_no+shift_value'
          valid_chunk:
            params:
              - id: inv_chunk
                type: u1
              - id: chunk
                type: u1
                doc: "c3 c2 c1 c0"
            instances:
              valid:
                value: inv_chunk ^ chunk == 0b1111
          ac:
            params:
              - id: index
                type: u1
            instances:
              bits:
                pos: 0
                repeat: expr
                repeat-expr: _parent._parent.ac_bits
                type: ac_bit(index, _parent.chunks[_index].chunk)

              val:
                value: (bits[2].n << 2) | (bits[1].n << 1) | bits[0].n
                doc: "c3 c2 c1"
              inv_shift_val:
                value: (bits[0].n << 2) | (bits[1].n << 1) | bits[2].n

            types:
              ac_bit:
                params:
                  - id: i
                    type: u1
                  - id: chunk
                    type: u1
                instances:
                  n:
                    value: (chunk >> i) & 1
                  b:
                    value: n == 1

          trailer_ac:
            params:
              - id: ac
                type: ac
            instances:
              can_read_key_b:
                value: ac.inv_shift_val <= 0b010
                doc: key A is required
              can_write_keys:
                value: "(ac.inv_shift_val+1)%3 != 0 and (ac.inv_shift_val<6)"
              can_write_access_bits:
                value: ac.bits[2].b
              key_b_controls_write:
                value: not can_read_key_b
          data_ac:
            params:
              - id: ac
                type: ac
            instances:
              read_key_a_required:
                value: ac.val <= 0b100
              read_key_b_required:
                value: ac.val <= 0b110
              write_key_a_required:
                value: ac.val == 0


              write_key_b_required:
                value: (not read_key_a_required or read_key_b_required) and not ac.bits[0].b
              increment_available:
                value: (not ac.bits[0].b and not read_key_a_required and not read_key_b_required) or (not ac.bits[0].b and read_key_a_required and read_key_b_required)
              decrement_available:
                value: (ac.bits[1].b or not ac.bits[0].b) and not ac.bits[2].b