mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-09-29 17:38:44 +00:00
hf mfdes: put a DESFire card image in emulator memory
Adds the on-device card image a DESFire simulation will read, the client side that packs a dump into it, and eload / esave / eview. The layout is in include/desfire_em.h. Tables grow up from the header, file data grows down from the end, and an allocation only has to leave the two frontiers apart -- so a three-file card spends a few hundred bytes rather than a worst case, and the space between them is what the card has left. The PICC is application 000000 and uses the same struct as any other application, so key settings and keys are always stated against an AID. Delete sets a tombstone rather than compacting, which is not a shortcut: a real card does not reclaim on delete either, measured as 2080 bytes free with zero applications before an experiment and 2560 after FormatPICC. Two size limits, and a reader only ever sees the first. cardsize is what the emulated card claims to hold, so GetFreeMem answers from that and CreateFile will refuse with OUT_OF_EEPROM when it runs out. Without it a card impersonating a 2K part would report 7434 bytes free, which no 2K part does. The image size is what emulator memory physically holds, is the harder limit, and is never visible. cardsize is anchored to the free memory the real card reported when the dump was taken -- observed free plus what we reserve for the same content -- so the emulation answers what its original answered. That is also a check on the reservation rule rather than only a convenience: for the bench card it computed 576 bytes spent, and 1984 + 576 is 2560, exactly what that card reports when formatted. Reservation follows what CommitTransaction covers. Backup data, value and record files each get a shadow region because writes to them are staged until commit; a standard data file writes through and does not. Sizes round to a 32 byte granule, which is the granule a real card allocates in. It deliberately does not reproduce NXP's allocator -- that is undocumented and does not fit a simple model, a declared 1024 byte record file costs 1088 on silicon -- so what matters is that the figure is self-consistent and shrinks as the reader writes. No new device command. Emulator memory is one shared region, so CMD_HF_MIFARE_EML_MEMSET and BIG_BUF_EML already reach it, and both inherit the bounds checking those paths gained earlier. Verified with the client only, no simulation yet: packing a dump taken from a DESFire EV2 and walking it back reproduces every field including file contents byte for byte, the same round trip through the device via eload and esave is identical, and an image too large is refused by name rather than truncated -- 'Out of emulator memory laying out AID 112233 file 00: needs 4066 more bytes'. Co-Authored-By: Claude Opus 5 (1M context)
This commit is contained in:
co-authored by
Claude Opus 5 (1M context)
parent
669319b690
commit
ee2d55fafb
@@ -0,0 +1,284 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// See LICENSE.txt for the text of the license.
|
||||
//-----------------------------------------------------------------------------
|
||||
// DESFire card image, as it lives in emulator memory.
|
||||
//
|
||||
// The client packs a desfire_dump_t (see client/src/fileutils.h, and
|
||||
// doc/mfdes_dump_format.md for the json it comes from) into this layout and
|
||||
// uploads it with eload. The device simulation reads it, and writes back into
|
||||
// it -- a reader that writes to the simulated card changes this image, so an
|
||||
// esave afterwards shows what the reader did. That is the difference between
|
||||
// an emulator and a replay rig.
|
||||
//
|
||||
// Layout, inside the CARD_MEMORY_SIZE region:
|
||||
//
|
||||
// 0 +---------------------------+
|
||||
// | desfire_em_hdr_t | fixed
|
||||
// +---------------------------+
|
||||
// | desfire_em_app_t [] | appcount entries
|
||||
// | desfire_em_file_t [] | filecount entries grows up
|
||||
// | desfire_em_key_t [] | keycount entries
|
||||
// tables_end
|
||||
// | |
|
||||
// | ..... free ..... | <- the emulated card's free memory
|
||||
// | |
|
||||
// data_start
|
||||
// | file data regions | grows down
|
||||
// size +---------------------------+
|
||||
//
|
||||
// Two frontiers rather than fixed-size tables: a card with three files spends a
|
||||
// few hundred bytes, not the worst case. Tables append as the reader creates
|
||||
// applications and files; a data region is reserved when a file is created and
|
||||
// never moves afterwards, so WriteData and WriteRecord are pure writes. The
|
||||
// only allocation check anywhere is tables_end < data_start, and the space
|
||||
// between them is what GetFreeMem reports -- so the simulated card fills up and
|
||||
// refuses with OUT_OF_EEPROM the way a real one does.
|
||||
//
|
||||
// Nothing here knows how big emulator memory is. The device reports that to the
|
||||
// client in capabilities_t, and a platform with more of it holds a bigger card
|
||||
// with no change to this format.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef __DESFIRE_EM_H
|
||||
#define __DESFIRE_EM_H
|
||||
|
||||
#include "common.h"
|
||||
#include "desfire.h"
|
||||
|
||||
#define DESFIRE_EM_MAGIC 0x31534544 // "DES1", little endian
|
||||
#define DESFIRE_EM_LAYOUT_VERSION 1
|
||||
|
||||
// Allocation granule. A genuine DESFire rounds NV consumption to 32 bytes:
|
||||
// measured on a DESFire EV2 2K, a 1 byte file costs 32 and a 33 byte file costs
|
||||
// 64. Rounding the same way keeps GetFreeMem card-shaped. Note the rounding is
|
||||
// invisible to a reader otherwise -- GetFileSettings reports the size the file
|
||||
// was created with, also measured.
|
||||
#define DESFIRE_EM_GRANULE 32
|
||||
#define DESFIRE_EM_ROUNDUP(x) (((x) + (DESFIRE_EM_GRANULE - 1)) & ~(DESFIRE_EM_GRANULE - 1))
|
||||
|
||||
// NV an application costs before its keys. Measured on a DESFire EV2 2K: an
|
||||
// application with one key costs 96 bytes, with fourteen keys 352. 64 plus the
|
||||
// key material reproduces the first exactly and the second about 18% low, which
|
||||
// is close enough for a free-memory figure that only has to look card-shaped --
|
||||
// see the note on not reproducing NXP's allocator, below.
|
||||
#define DESFIRE_EM_APP_OVERHEAD 64
|
||||
|
||||
// EV1 hard limits. 28 applications is the PICC's own, reported as COUNT_ERROR
|
||||
// 0xCE; 32 files and 14 keys follow the file/key number ranges 0x00..0x1F and
|
||||
// 0x00..0x0D. These bound the tables; what actually fits is decided by the
|
||||
// frontiers, not by these.
|
||||
#define DESFIRE_EM_MAX_APPS 28
|
||||
#define DESFIRE_EM_MAX_FILES 32
|
||||
#define DESFIRE_EM_MAX_KEYS DESFIRE_MAX_KEY_COUNT
|
||||
|
||||
// Which generation the image impersonates. The simulation answers a different
|
||||
// command set for each, so it is stored rather than re-derived from the version
|
||||
// bytes at run time. Identification bytes are versionhw[3] and [4]: EV1 is
|
||||
// 01 00, EV2 12 00 or 42 00, EV2 XL 22 00, EV3 33 00, DuoX A0 00.
|
||||
typedef enum {
|
||||
DESFIRE_EM_GEN_UNKNOWN = 0,
|
||||
DESFIRE_EM_GEN_D40 = 1,
|
||||
DESFIRE_EM_GEN_EV1 = 2,
|
||||
DESFIRE_EM_GEN_EV2 = 3,
|
||||
DESFIRE_EM_GEN_EV3 = 4,
|
||||
DESFIRE_EM_GEN_LIGHT = 5,
|
||||
DESFIRE_EM_GEN_DUOX = 6,
|
||||
} desfire_em_gen_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic; // DESFIRE_EM_MAGIC
|
||||
uint8_t layout; // DESFIRE_EM_LAYOUT_VERSION
|
||||
uint8_t generation; // desfire_em_gen_t
|
||||
uint8_t flags; // RFU
|
||||
uint8_t rfu0;
|
||||
|
||||
// card identity, straight from the dump
|
||||
uint8_t uid[10];
|
||||
uint8_t uidlen;
|
||||
uint8_t atqa[2];
|
||||
uint8_t sak;
|
||||
uint8_t atslen;
|
||||
uint8_t ats[20];
|
||||
|
||||
// GetVersion answers over three chained frames and they are not the same
|
||||
// shape across generations, so each is kept raw
|
||||
uint8_t versionhw[7];
|
||||
uint8_t versionhwlen;
|
||||
uint8_t versionsw[7];
|
||||
uint8_t versionswlen;
|
||||
uint8_t versionprod[14];
|
||||
uint8_t versionprodlen;
|
||||
|
||||
uint8_t signature[56]; // NXP originality signature
|
||||
uint8_t signaturelen;
|
||||
uint8_t rfu1;
|
||||
|
||||
// app[0] is the PICC, AID 000000. It holds the PICC master key settings and
|
||||
// keys and never has files, the same way the json format stores it.
|
||||
uint8_t appcount;
|
||||
uint8_t filecount;
|
||||
uint8_t keycount;
|
||||
uint8_t rfu2;
|
||||
|
||||
uint16_t app_off; // byte offsets from the start of the image
|
||||
uint16_t file_off;
|
||||
uint16_t key_off;
|
||||
|
||||
// the two frontiers. tables grow up to tables_end, file data down from
|
||||
// data_start, and an allocation only has to leave them apart
|
||||
uint16_t tables_end;
|
||||
uint16_t data_start;
|
||||
uint16_t size; // bytes of emulator memory this image may use
|
||||
|
||||
// Two different limits, and a reader must only ever see the first.
|
||||
//
|
||||
// cardsize is the NV the emulated card claims to have, so GetFreeMem
|
||||
// answers cardsize - reserved and CreateFile refuses with OUT_OF_EEPROM
|
||||
// once that runs out. Without it a card impersonating a 2K part would
|
||||
// report several kB free, which no 2K part does.
|
||||
//
|
||||
// size is what emulator memory physically holds, and is the harder limit
|
||||
// of the two. It is never visible to a reader.
|
||||
uint16_t cardsize;
|
||||
uint16_t reserved; // NV the card has spent, against cardsize
|
||||
} PACKED desfire_em_hdr_t;
|
||||
|
||||
// ---------------------------------------------------------------- application
|
||||
|
||||
#define DESFIRE_EM_APP_DELETED (1 << 0) // tombstone, see below
|
||||
#define DESFIRE_EM_APP_AUTHED (1 << 1) // dump ran authenticated here
|
||||
#define DESFIRE_EM_APP_ISOFIDS (1 << 2) // application uses ISO file ids
|
||||
|
||||
typedef struct {
|
||||
uint8_t aid[3];
|
||||
uint8_t flags;
|
||||
uint8_t keysettings;
|
||||
uint8_t numkeysraw; // key type in bits 6-7, iso-fid flag bit 5, count 0-4
|
||||
uint16_t isofid; // 0 = none
|
||||
uint8_t dfname[16];
|
||||
uint8_t dfnamelen;
|
||||
uint8_t keytype; // DesfireCryptoAlgorithm, one per application
|
||||
} PACKED desfire_em_app_t;
|
||||
|
||||
// ----------------------------------------------------------------------- file
|
||||
|
||||
// flags bits 0-1 carry the raw 2-bit file communication mode, not a
|
||||
// DesfireCommunicationMode. Use DesfireFileCommModeToCommMode() to convert.
|
||||
#define DESFIRE_EM_FILE_COMM_MASK 0x03
|
||||
#define DESFIRE_EM_FILE_UNKNOWN (1 << 2) // contents were never read, see below
|
||||
#define DESFIRE_EM_FILE_LIMCREDIT (1 << 3) // value file, LimitedCredit enabled
|
||||
#define DESFIRE_EM_FILE_FREEGETVAL (1 << 4) // value file, free GetValue
|
||||
#define DESFIRE_EM_FILE_DELETED (1 << 5) // tombstone
|
||||
#define DESFIRE_EM_FILE_DIRTY (1 << 6) // uncommitted writes live in the shadow
|
||||
|
||||
typedef struct {
|
||||
uint8_t app; // index into the application table
|
||||
uint8_t num; // file number 0x00..0x1F
|
||||
uint8_t type; // raw file type byte, 0x00..0x04 for EV1
|
||||
uint8_t flags;
|
||||
uint16_t isofid; // 0 = none
|
||||
uint16_t rights; // raw access rights word
|
||||
|
||||
uint16_t dataoff; // offset of the committed region
|
||||
uint16_t datalen; // bytes reserved for it
|
||||
|
||||
// CommitTransaction covers backup data, value and record files; a standard
|
||||
// data file writes straight through. For the three that are covered,
|
||||
// shadow_off is a second region of datalen bytes that writes land in until
|
||||
// CommitTransaction swaps them, or AbortTransaction discards them. Zero
|
||||
// for standard data files.
|
||||
uint16_t shadow_off;
|
||||
|
||||
union {
|
||||
struct { // 0x00 standard, 0x01 backup
|
||||
uint32_t size; // as created, NOT rounded -- GetFileSettings
|
||||
} data; // reports this verbatim, measured
|
||||
struct { // 0x02 value
|
||||
uint32_t lower;
|
||||
uint32_t upper;
|
||||
uint32_t value;
|
||||
} value;
|
||||
struct { // 0x03 linear, 0x04 cyclic
|
||||
uint32_t recordsize;
|
||||
uint32_t maxrecords; // as created. A cyclic file stores one
|
||||
uint32_t currecords; // fewer than this, the backup mechanism
|
||||
} record; // consumes one -- EV1 spec 9.5.9
|
||||
} u;
|
||||
} PACKED desfire_em_file_t;
|
||||
|
||||
// ------------------------------------------------------------------------ key
|
||||
|
||||
// The key algorithm is the application's, so it is not repeated here. A key
|
||||
// version is readable without knowing the key, so the two are tracked apart: a
|
||||
// version with no value is the normal shape for a key that was found but never
|
||||
// recovered, and the simulation must refuse an authentication against it rather
|
||||
// than authenticate with zeros.
|
||||
#define DESFIRE_EM_KEY_PRESENT (1 << 0) // we hold the key value
|
||||
#define DESFIRE_EM_KEY_VERKNOWN (1 << 1) // we read the key version
|
||||
|
||||
typedef struct {
|
||||
uint8_t app; // index into the application table
|
||||
uint8_t num; // key number
|
||||
uint8_t flags;
|
||||
uint8_t ver;
|
||||
uint8_t key[DESFIRE_MAX_KEY_SIZE];
|
||||
} PACKED desfire_em_key_t;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Notes for whoever implements against this
|
||||
//
|
||||
// Tombstones, not compaction. DeleteFile and DeleteApplication set a flag and
|
||||
// leave the space stranded. That is not a shortcut: a genuine card does not
|
||||
// give the memory back either. Measured on a DESFire EV2 2K -- 2080 bytes free
|
||||
// with zero applications before an experiment, 2560 after FormatPICC. Only
|
||||
// FormatPICC reclaims, and here that is resetting both frontiers.
|
||||
//
|
||||
// The unknown flag. A file whose contents could not be read carries
|
||||
// DESFIRE_EM_FILE_UNKNOWN and its region is reserved but meaningless. The
|
||||
// simulation must answer an error for it, never zeros. "8 bytes of 00" and "we
|
||||
// could not read 8 bytes" are different facts and a card image that confuses
|
||||
// them lies to the reader.
|
||||
//
|
||||
// Reservation at CreateFile, in granules:
|
||||
// standard DESFIRE_EM_ROUNDUP(size)
|
||||
// backup DESFIRE_EM_ROUNDUP(size) twice, data and shadow
|
||||
// value one granule twice
|
||||
// record DESFIRE_EM_ROUNDUP(recordsize * maxrecords) twice
|
||||
// Reserve the full declared extent, not just the records that exist, so
|
||||
// WriteRecord never has to grow anything. The json stores only the records
|
||||
// that exist; eload expands, esave compacts.
|
||||
//
|
||||
// This deliberately does not reproduce NXP's own allocator. It is undocumented
|
||||
// and the numbers do not fit a simple model -- a declared 1024 byte record file
|
||||
// costs 1088 on real silicon, neither 1x nor 2x. What matters is that
|
||||
// GetFreeMem is self-consistent and shrinks as the reader writes.
|
||||
//
|
||||
// Card capacity. The GetVersion storage size byte codes 2^(n>>1) bytes, but
|
||||
// that understates what the part actually hands out: a DESFire EV2 2K reports
|
||||
// storage 0x16, 2048 bytes, and 2560 bytes free when formatted -- measured. So
|
||||
// the nominal figure is a floor, not the capacity, and cardsize is set to
|
||||
// whichever is larger of the nominal figure and what the image already holds.
|
||||
// A dump taken from a real card therefore always fits the card it came from.
|
||||
//
|
||||
// Response timing. Everything from SelectApplication onward sits behind the
|
||||
// ISO14443-4 frame waiting time, which the EV1 ATS sets to FWI 8, 77.33 ms --
|
||||
// about 3.7 million ARM cycles at MCK. A software AES block is well under a
|
||||
// microsecond of that, so responses are computed on demand from this image
|
||||
// rather than precompiled. The pre-RATS layer is the opposite: ATQA, SAK and
|
||||
// ATS have no such slack and must be precompiled, as the 14a simulation already
|
||||
// does.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#endif // __DESFIRE_EM_H
|
||||
Reference in New Issue
Block a user