diff --git a/doc/mfdes_dump_format.md b/doc/mfdes_dump_format.md new file mode 100644 index 000000000..dd44e3975 --- /dev/null +++ b/doc/mfdes_dump_format.md @@ -0,0 +1,292 @@ +# MIFARE DESFire dump file format + + + +The `mfdes v1` json format is the on-disk image of a DESFire PICC: the card level +details, every application, every file's settings, and the file contents that +could actually be read. It is what `hf mfdes dump` writes and `hf mfdes view` +reads, and it is the format an emulator-memory loader (`hf mfdes eload`) will be +built on. + +# Table of Contents + +- [MIFARE DESFire dump file format](#mifare-desfire-dump-file-format) +- [Table of Contents](#table-of-contents) +- [Commands](#commands) +- [File identity](#file-identity) +- [Card level](#card-level) +- [Applications](#applications) +- [Files](#files) + - [Common fields](#common-fields) + - [Standard and backup data files](#standard-and-backup-data-files) + - [Value files](#value-files) + - [Record files](#record-files) + - [Transaction MAC files](#transaction-mac-files) +- [Keys](#keys) +- [The Read flag](#the-read-flag) +- [Token spellings](#token-spellings) +- [In-memory representation](#in-memory-representation) +- [Not in v1](#not-in-v1) + +## Commands +^[Top](#top) + +``` +hf mfdes dump walk the whole PICC, save hf-mfdes--dump.json +hf mfdes dump --aid 123456 dump only that application +hf mfdes dump --keys take per-application keys from a `hf mfdes chk` key file +hf mfdes dump --ns collect and print, save nothing +hf mfdes dump -f myfile save under a name of your choosing +hf mfdes view -f print a dump file, no device needed +``` + +With no `--aid` / `--isoid` / `--dfname` the command walks every application the +PICC lists. The default filename is `hf-mfdes--dump.json`; only json is +written, there is no `.bin` companion. + +## File identity +^[Top](#top) + +```json +{ + "Created": "proxmark3", + "FileType": "mfdes v1", + "Version": 1 +} +``` + +`FileType` is what the loader dispatches on. `Version` is the same number in +machine-readable form. A reader that does not understand a `FileType` must +refuse the file rather than guess -- `mfdes v2` is already taken by the key +recovery output of `hf mfdes chk`, which is a different, much smaller format. + +## Card level +^[Top](#top) + +Everything under `$.Card` describes the PICC itself. + +| Field | Type | Meaning | +|---|---|---| +| `UID` | hex | 4 or 7 byte UID from the 14a select | +| `ATQA` | hex, 2 bytes | wire order, the same way every other pm3 dump format stores it. `hf 14a info` prints the two bytes the other way round | +| `SAK` | hex, 1 byte | | +| `ATS` | hex | answer to RATS, absent when the card gave none | +| `Version` | hex, 28 bytes | raw `GetVersion` answer, hardware + software + production details concatenated | +| `Signature` | hex, 56 bytes | NXP originality signature, absent when the card refused it | +| `FreeMem` | int | bytes reported by `GetFreeMem`, absent when the card refused it | +| `KeySettings` | hex, 1 byte | PICC master key settings | +| `NumKeysRaw` | hex, 1 byte | raw key-count byte, carries the key type in the top bits | +| `NumKeys` | int | key count decoded from `NumKeysRaw` | +| `KeyVersion0` | hex, 1 byte | version of the PICC master key | +| `Keys` | object | see [Keys](#keys) | + +Absent means the field is not written at all. Do not read a missing field as a +zero. + +## Applications +^[Top](#top) + +`$.Applications` is an object keyed by the 3-byte AID as 6 uppercase hex digits. + +```json +"Applications": { + "F51800": { + "ISOFileID": "DF01", + "DFName": "D2760000850100", + "KeySettings": "0B", + "NumKeysRaw": "81", + "NumKeys": 1, + "KeyType": "aes", + "KeyVersions": { "0": "00" }, + "Authenticated": true, + "Keys": { }, + "Files": { } + } +} +``` + +| Field | Type | Meaning | +|---|---|---| +| `ISOFileID` | hex, 2 bytes | ISO DF ID, absent when the application has none | +| `DFName` | hex, 1-16 bytes | ISO DF name, absent when the application has none | +| `KeySettings` | hex, 1 byte | | +| `NumKeysRaw` | hex, 1 byte | | +| `NumKeys` | int | | +| `KeyType` | token | `des`, `2tdea`, `3tdea` or `aes` | +| `KeyVersions` | object | key number -> 1 byte version | +| `Authenticated` | bool | whether the dump ran authenticated against this application | +| `Keys` | object | see [Keys](#keys) | +| `Files` | object | see [Files](#files) | + +`Authenticated: false` is the flag that says how much to trust the rest of the +application: settings and contents may be whatever the card hands out to an +unauthenticated reader, which on many cards is nothing at all. + +JSON objects are unordered. A reader must look applications and files up by +key, not by position. + +## Files +^[Top](#top) + +`Files` is an object keyed by the file number as 2 uppercase hex digits. + +### Common fields +^[Top](#top) + +| Field | Type | Meaning | +|---|---|---| +| `Type` | token | see [Token spellings](#token-spellings) | +| `TypeRaw` | hex, 1 byte | the file type byte as the card reported it. This is the authoritative one, `Type` is for humans | +| `ISOFileID` | hex, 2 bytes | absent when the file has none | +| `CommMode` | token | `plain`, `mac`, `plain_rfu` or `encrypt`, from the raw 2-bit file comm mode | +| `AccessRights` | hex, 2 bytes | raw access rights word: read, write, read/write, change, 4 bits each | +| `AdditionalAccessRights` | object | index -> 2 byte rights word, absent when the file has none | +| `Read` | bool | see [The Read flag](#the-read-flag) | + +### Standard and backup data files +^[Top](#top) + +`TypeRaw` `00` and `01`. + +| Field | Type | Meaning | +|---|---|---| +| `FileSize` | int | size the card reports | +| `Data` | hex | contents. May be shorter than `FileSize` when `hf mfdes dump --length` capped the read | + +### Value files +^[Top](#top) + +`TypeRaw` `02`. A value file has no `Data`. + +| Field | Type | Meaning | +|---|---|---| +| `LowerLimit` | int | | +| `UpperLimit` | int | | +| `LimitedCredit` | hex, 1 byte | | +| `Value` | int | present only when `Read` is true | + +### Record files +^[Top](#top) + +`TypeRaw` `03` (linear) and `04` (cyclic). + +| Field | Type | Meaning | +|---|---|---| +| `RecordSize` | int | bytes per record | +| `MaxRecords` | int | | +| `CurRecords` | int | | +| `Records` | object | record index -> hex string of exactly `RecordSize` bytes | + +Records are numbered from `0` and must be contiguous. A record file whose +contents could not be split into whole records falls back to a single flat +`Data` string instead of `Records`. + +### Transaction MAC files +^[Top](#top) + +`TypeRaw` `05`. Stored like a data file: the 12 byte answer goes in `Data` +(4 byte counter, 8 byte MAC), and is not decoded further. + +## Keys +^[Top](#top) + +Keys are part of the image, not a side file. A simulator cannot answer an +authentication without them, and splitting them across two files means a card +image that silently cannot be used. + +Both `$.Card` and each application carry the same shape: + +```json +"Keys": { + "0": { "Algo": "aes", "Key": "E757178E13516A4F3171BC6EA85E165A" } +} +``` + +Keyed by key number in decimal. `Algo` fixes the key length: `des` 8 bytes, +`2tdea` 16, `3tdea` 24, `aes` 16. Only key numbers actually known are written, +so an absent entry means "we do not have this key", never "the key is zero". + +`$.Card.Keys` holds the PICC master keys, which are the keys of AID `000000`. +Note the difference from the `mfdes v2` key recovery format, where PICC keys sit +under `Applications.000000` and are indexed by algorithm as well as key number. + +## The Read flag +^[Top](#top) + +Every file carries `Read`. It is true only when the contents were actually +fetched from the card. + +* `Read: true` with `Data` -- these are the card's bytes. +* `Read: true` without `Data` (or with an empty one) -- the card answered, with nothing in it. +* `Read: false` -- we never got the contents. Access was denied, the key was + wrong, or the session died. There is no `Data` key at all. + +A reader must never turn a missing `Data` into zeros. The whole point of the +flag is that "8 bytes of 00" and "we could not read 8 bytes" are different +facts, and a simulator that confuses them will confidently answer a reader with +contents the card never had. + +## Token spellings +^[Top](#top) + +String tokens match what the `hf mfdes` CLI already accepts, so a value read out +of a dump file can be handed straight back to a command. + +| `Type` | `TypeRaw` | +|---|---| +| `standard` | `00` | +| `backup` | `01` | +| `value` | `02` | +| `linear_record` | `03` | +| `cyclic_record` | `04` | +| `transaction_mac` | `05` | + +| `CommMode` | raw | +|---|---| +| `plain` | `0` | +| `mac` | `1` | +| `plain_rfu` | `2` | +| `encrypt` | `3` | + +`plain_rfu` is the card's second encoding for plain. It is kept distinct from +`plain` so the file records what the card actually said. + +| `Algo` / `KeyType` | key length | +|---|---| +| `des` | 8 | +| `2tdea` | 16 | +| `3tdea` | 24 | +| `aes` | 16 | + +Hex strings are uppercase with no separators. Byte order is as the bytes travel +on the wire, except `AccessRights` and the ISO file IDs, which are written big +endian the way they are printed. + +## In-memory representation +^[Top](#top) + +`desfire_dump_t` in `client/src/fileutils.h`, written by `prepareJSON()` and read +by `loadFileJSONex()` in `client/src/fileutils.c`. + +File contents hang off `desfire_dump_file_t` as heap pointers, so an image can +hold real file sizes without the struct itself being enormous. Whoever fills or +loads one owns those buffers and must call `desfire_dump_free()` before dropping +the image. + +The struct is bounded by `DESFIRE_MAX_APP_COUNT` (64) applications and +`DESFIRE_MAX_FILE_COUNT` (32) files per application, both in `include/desfire.h`. +Reading a file with more than that logs a warning and keeps the first N. + +## Not in v1 +^[Top](#top) + +Deliberately absent, listed so the next version does not have to rediscover them: + +* **No emulator memory layout.** This format is a card image, not a memory map. + How a PICC image is packed into the 4096 bytes of `CARD_MEMORY_SIZE` is a + separate problem, and the answer will not fit a large card. +* **No delegated application / DAM slot details.** +* **No key derivation state.** If a key in the file came out of AN10922 or the + Gallagher KDF, only the derived key is stored, not the diversification input. +* **No LRP / EV2 secure channel state**, no transaction counter, no `TI`. +* **No ISO 7816 file structure** beyond the ISO file IDs.