diff --git a/CHANGELOG.md b/CHANGELOG.md index 584b2f936..589884e13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] +- Added `hf mfdes eload/esave/eview` - load a DESFire card dump into emulator memory (@iceman1001) - Fixed `PLATFORM=PM3ICOPYX` - builds again, it has no FPGA power switch and the AT91 GPIO layer used the pin unconditionally (@iceman1001) - Fixed `fpga_compress` - PM3ULTIMATE builds again, its bitstreams are an exact multiple of the interleave size (@iceman1001) - Fixed `hf legic eload` - an upload after a command that left a different FPGA bitstream loaded now writes the whole image (@iceman1001) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 19579482e..d19a0b23d 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -381,6 +381,7 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/mifare/gen4.c ${PM3_ROOT}/client/src/mifare/lrpcrypto.c ${PM3_ROOT}/client/src/mifare/desfirecrypto.c + ${PM3_ROOT}/client/src/mifare/desfireem.c ${PM3_ROOT}/client/src/mifare/desfiresecurechan.c ${PM3_ROOT}/client/src/mifare/desfirecore.c ${PM3_ROOT}/client/src/mifare/desfiretest.c diff --git a/client/Makefile b/client/Makefile index 7d38fb544..4084332f7 100644 --- a/client/Makefile +++ b/client/Makefile @@ -890,11 +890,12 @@ SRCS = mifare/aiddesfire.c \ lua_bitlib.c \ mifare/lrpcrypto.c \ mifare/desfirecrypto.c \ + mifare/desfireem.c \ mifare/desfirecore.c \ mifare/desfiresecurechan.c \ mifare/desfiretest.c \ mifare/gallaghercore.c \ - mifare/gallaghertest.c \ + mifare/gallaghertest.c \ mifare/mad.c \ mifare/mad_test.c \ mifare/mfkey.c \ diff --git a/client/experimental_lib/CMakeLists.txt b/client/experimental_lib/CMakeLists.txt index 78a76f9c7..1d04ffb44 100644 --- a/client/experimental_lib/CMakeLists.txt +++ b/client/experimental_lib/CMakeLists.txt @@ -307,6 +307,7 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/mifare/gen4.c ${PM3_ROOT}/client/src/mifare/lrpcrypto.c ${PM3_ROOT}/client/src/mifare/desfirecrypto.c + ${PM3_ROOT}/client/src/mifare/desfireem.c ${PM3_ROOT}/client/src/mifare/desfiresecurechan.c ${PM3_ROOT}/client/src/mifare/desfirecore.c ${PM3_ROOT}/client/src/mifare/desfiretest.c diff --git a/client/src/cmdhfmfdes.c b/client/src/cmdhfmfdes.c index fe4cb1aa8..230f797d9 100644 --- a/client/src/cmdhfmfdes.c +++ b/client/src/cmdhfmfdes.c @@ -52,6 +52,7 @@ #include "generator.h" #include "mifare/aiddesfire.h" #include "mifare/prime.h" +#include "mifare/desfireem.h" // emulator memory card image #include "util.h" #include "crypto/originality.h" @@ -9042,6 +9043,224 @@ static void DesfireViewPrintApp(const desfire_dump_app_t *app) { } } +static int CmdHF14ADesELoad(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf mfdes eload", + "Load a DESFire card dump into emulator memory.\n" + "The dump is packed into the on-device card image, see doc/mfdes_dump_format.md", + "hf mfdes eload -f hf-mfdes-01020304050607-dump.json"); + + void *argtable[] = { + arg_param_begin, + arg_str1("f", "file", "", "Filename of dump"), + arg_lit0("v", "verbose", "Verbose output"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + + int fnlen = 0; + char filename[FILE_PATH_SIZE] = {0}; + CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); + bool verbose = arg_get_lit(ctx, 2); + CLIParserFree(ctx); + + desfire_dump_t *dump = calloc(1, sizeof(desfire_dump_t)); + if (dump == NULL) { + PrintAndLogEx(ERR, "Failed to allocate memory"); + return PM3_EMALLOC; + } + + size_t dlen = 0; + int res = loadFileJSON(filename, dump, sizeof(desfire_dump_t), &dlen, NULL); + if (res != PM3_SUCCESS) { + free(dump); + return res; + } + + if (dlen != sizeof(desfire_dump_t)) { + PrintAndLogEx(ERR, "`" _YELLOW_("%s") "` is not a DESFire card dump", filename); + desfire_dump_free(dump); + free(dump); + return PM3_EINVARG; + } + + // the device tells us how much emulator memory it has, so a platform with + // more of it holds a bigger card with no change here + size_t emsize = g_conn.em_size; + if (emsize == 0) { + PrintAndLogEx(ERR, "Device did not report an emulator memory size"); + desfire_dump_free(dump); + free(dump); + return PM3_EDEVNOTSUPP; + } + + uint8_t *img = calloc(emsize, sizeof(uint8_t)); + if (img == NULL) { + PrintAndLogEx(ERR, "Failed to allocate memory"); + desfire_dump_free(dump); + free(dump); + return PM3_EMALLOC; + } + + size_t used = 0; + res = desfire_em_pack(dump, img, emsize, &used); + desfire_dump_free(dump); + free(dump); + + if (res != PM3_SUCCESS) { + free(img); + return res; + } + + if (verbose) { + desfire_em_print(img, emsize); + } + + res = desfire_em_upload(img, emsize); + free(img); + + if (res != PM3_SUCCESS) { + return res; + } + + PrintAndLogEx(SUCCESS, "Done!"); + PrintAndLogEx(HINT, "Hint: try " _YELLOW_("`hf mfdes eview`") " to verify"); + return PM3_SUCCESS; +} + +static int CmdHF14ADesESave(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf mfdes esave", + "Save the card image in emulator memory to a dump file.\n" + "Shows what a reader left behind if one has been talking to the simulation", + "hf mfdes esave -f myfile"); + + void *argtable[] = { + arg_param_begin, + arg_str0("f", "file", "", "Filename, if no UID will be used as filename"), + arg_lit0("v", "verbose", "Verbose output"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + int fnlen = 0; + char filename[FILE_PATH_SIZE] = {0}; + CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); + bool verbose = arg_get_lit(ctx, 2); + CLIParserFree(ctx); + + size_t emsize = g_conn.em_size; + if (emsize == 0) { + PrintAndLogEx(ERR, "Device did not report an emulator memory size"); + return PM3_EDEVNOTSUPP; + } + + uint8_t *img = calloc(emsize, sizeof(uint8_t)); + if (img == NULL) { + PrintAndLogEx(ERR, "Failed to allocate memory"); + return PM3_EMALLOC; + } + + int res = desfire_em_download(img, emsize); + if (res != PM3_SUCCESS) { + free(img); + return res; + } + + if (verbose) { + desfire_em_print(img, emsize); + } + + desfire_dump_t *dump = calloc(1, sizeof(desfire_dump_t)); + if (dump == NULL) { + PrintAndLogEx(ERR, "Failed to allocate memory"); + free(img); + return PM3_EMALLOC; + } + + res = desfire_em_unpack(img, emsize, dump); + free(img); + + if (res != PM3_SUCCESS) { + PrintAndLogEx(HINT, "Hint: emulator memory holds no DESFire card image, try " _YELLOW_("`hf mfdes eload`")); + free(dump); + return res; + } + + if (fnlen < 1) { + if (dump->card_info.uidlen == 0) { + PrintAndLogEx(WARNING, "No UID to build a filename from, use " _YELLOW_("-f ")); + desfire_dump_free(dump); + free(dump); + return PM3_ESOFT; + } + PrintAndLogEx(INFO, "Using UID as filename"); + strcat(filename, "hf-mfdes-"); + FillFileNameByUID(filename, dump->card_info.uid, "-dump", dump->card_info.uidlen); + } + + pm3_save_dump_json(filename, (uint8_t *)dump, sizeof(desfire_dump_t), jsfMfDesfire_v1); + + desfire_dump_free(dump); + free(dump); + return PM3_SUCCESS; +} + +static int CmdHF14ADesEView(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf mfdes eview", + "Show the DESFire card image currently in emulator memory", + "hf mfdes eview"); + + void *argtable[] = { + arg_param_begin, + arg_lit0("v", "verbose", "Also print every application and file"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + bool verbose = arg_get_lit(ctx, 1); + CLIParserFree(ctx); + + size_t emsize = g_conn.em_size; + if (emsize == 0) { + PrintAndLogEx(ERR, "Device did not report an emulator memory size"); + return PM3_EDEVNOTSUPP; + } + + uint8_t *img = calloc(emsize, sizeof(uint8_t)); + if (img == NULL) { + PrintAndLogEx(ERR, "Failed to allocate memory"); + return PM3_EMALLOC; + } + + int res = desfire_em_download(img, emsize); + if (res != PM3_SUCCESS) { + free(img); + return res; + } + + PrintAndLogEx(NORMAL, ""); + desfire_em_print(img, emsize); + + if (verbose) { + desfire_dump_t *dump = calloc(1, sizeof(desfire_dump_t)); + if (dump != NULL) { + if (desfire_em_unpack(img, emsize, dump) == PM3_SUCCESS) { + DesfireViewPrintApp(&dump->picc); + for (uint8_t i = 0; i < dump->appcount && i < DESFIRE_MAX_APP_COUNT; i++) { + DesfireViewPrintApp(&dump->app[i]); + } + } + desfire_dump_free(dump); + free(dump); + } + } + + free(img); + PrintAndLogEx(NORMAL, ""); + return PM3_SUCCESS; +} + static int CmdHF14ADesView(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf mfdes view", @@ -10796,6 +11015,9 @@ static command_t CommandTable[] = { {"lsfiles", CmdHF14ADesLsFiles, IfPm3Iso14443a, "Show all files list"}, {"dump", CmdHF14ADesDump, IfPm3Iso14443a, "Dump all files"}, {"view", CmdHF14ADesView, AlwaysAvailable, "Display content from tag dump file"}, + {"eload", CmdHF14ADesELoad, IfPm3Iso14443a, "Upload file into emulator memory"}, + {"esave", CmdHF14ADesESave, IfPm3Iso14443a, "Save emulator memory to file"}, + {"eview", CmdHF14ADesEView, IfPm3Iso14443a, "View emulator memory"}, {"createfile", CmdHF14ADesCreateFile, IfPm3Iso14443a, "Create Standard/Backup File"}, {"createvaluefile", CmdHF14ADesCreateValueFile, IfPm3Iso14443a, "Create Value File"}, {"createrecordfile", CmdHF14ADesCreateRecordFile, IfPm3Iso14443a, "Create Linear/Cyclic Record File"}, diff --git a/client/src/mifare/desfireem.c b/client/src/mifare/desfireem.c new file mode 100644 index 000000000..7a71a4b4b --- /dev/null +++ b/client/src/mifare/desfireem.c @@ -0,0 +1,685 @@ +//----------------------------------------------------------------------------- +// 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. +//----------------------------------------------------------------------------- +// Pack a DESFire card image for emulator memory, and read one back. +// Layout and rationale live in include/desfire_em.h. +//----------------------------------------------------------------------------- + +#include "desfireem.h" + +#include +#include +#include "commonutil.h" +#include "ui.h" +#include "desfirecrypto.h" // desfire_get_key_length +#include "mifarehost.h" // mf_eml_set_mem_xt +#include "comms.h" // g_conn +#include "cmdhw.h" // GetFromDevice + +uint8_t desfire_em_gen_from_version(const uint8_t *versionhw, uint8_t len) { + + if (versionhw == NULL || len < 5) { + return DESFIRE_EM_GEN_UNKNOWN; + } + + // hardware frame: vendor, type, subtype, major, minor, storage, protocol. + // Major and minor are what a reader keys on, see client/src/mifare/prime.c + uint8_t type = versionhw[1]; + uint8_t major = versionhw[3]; + uint8_t minor = versionhw[4]; + + if (minor != 0x00) { + return DESFIRE_EM_GEN_UNKNOWN; + } + + if (type != 0x01) { + return DESFIRE_EM_GEN_UNKNOWN; + } + + switch (major) { + case 0x00: + return DESFIRE_EM_GEN_D40; + case 0x01: + return DESFIRE_EM_GEN_EV1; + case 0x12: + case 0x22: + case 0x42: + return DESFIRE_EM_GEN_EV2; + case 0x30: + return DESFIRE_EM_GEN_LIGHT; + case 0x33: + return DESFIRE_EM_GEN_EV3; + case 0xA0: + return DESFIRE_EM_GEN_DUOX; + default: + return DESFIRE_EM_GEN_UNKNOWN; + } +} + +// NV the GetVersion storage size byte codes: 2^(n>>1) bytes, with the low bit +// meaning "between 2^n and 2^(n+1)". See client/src/mifare/prime.c, and the EV1 +// spec section 9.4.7. +uint32_t desfire_em_nominal_cardsize(const uint8_t *versionhw, uint8_t len) { + + if (versionhw == NULL || len < 6) { + return 0; + } + + uint8_t fsize = versionhw[5]; + if (fsize == 0 || (fsize >> 1) > 24) { + return 0; + } + + return 1UL << (fsize >> 1); +} + +const char *desfire_em_gen_str(uint8_t gen) { + static const char *tbl[] = {"unknown", "D40", "EV1", "EV2", "EV3", "Light", "DuoX"}; + return (gen < ARRAYLEN(tbl)) ? tbl[gen] : "unknown"; +} + +// Bytes the committed region of one file occupies, before the shadow. +static uint32_t desfire_em_file_extent(const desfire_dump_file_t *f) { + + switch (f->type) { + case 0x00: + case 0x01: + return f->size; + case 0x02: + // a value file's payload is the 4 byte value, but it is allocated + // and shadowed like anything else, so give it a whole granule + return DESFIRE_EM_GRANULE; + case 0x03: + case 0x04: + // reserve the declared extent, not the records that happen to + // exist, so WriteRecord never has to grow anything + return f->recordsize * f->maxrecords; + default: + return 0; + } +} + +// True when CommitTransaction covers this file type, ie it needs a shadow. +// EV1 spec 9.6.10 names exactly these four; a standard data file writes through. +static bool desfire_em_file_has_shadow(uint8_t type) { + return (type == 0x01 || type == 0x02 || type == 0x03 || type == 0x04); +} + +uint32_t desfire_em_file_reserve(const desfire_dump_file_t *f) { + + uint32_t extent = DESFIRE_EM_ROUNDUP(desfire_em_file_extent(f)); + if (extent == 0) { + return 0; + } + + return desfire_em_file_has_shadow(f->type) ? (extent * 2) : extent; +} + +// How many bytes of a file's committed region carry content, derived from the +// file's own declared shape rather than stored. A card holds the whole file, and +// a record file holds exactly the records that exist. +// +// Not stored because there is nothing to store: once packed, "we read 16 bytes +// of a 32 byte file" and "we read 32 bytes of which 16 were zero" are the same +// image. A dump that saw less than the whole file is an incomplete observation +// of a card, and desfire_em_pack() says so. +static uint32_t desfire_em_content_len(const desfire_dump_file_t *f) { + + if (f->read_ok == false) { + return 0; + } + + switch (f->type) { + case 0x00: + case 0x01: + return f->size; + case 0x03: + case 0x04: + return f->recordsize * f->currecords; + default: + return 0; // a value file carries its payload in the value field + } +} + +static void desfire_em_fill_app(desfire_em_app_t *dst, const desfire_dump_app_t *src) { + + dst->aid[0] = src->aid & 0xFF; + dst->aid[1] = (src->aid >> 8) & 0xFF; + dst->aid[2] = (src->aid >> 16) & 0xFF; + + dst->keysettings = src->keysettings; + dst->numkeysraw = src->numkeysraw; + dst->isofid = src->isofid; + dst->keytype = src->keytype; + + dst->dfnamelen = MIN(src->dfnamelen, (uint8_t)sizeof(dst->dfname)); + memcpy(dst->dfname, src->dfname, dst->dfnamelen); + + dst->flags = 0; + if (src->auth_ok) { + dst->flags |= DESFIRE_EM_APP_AUTHED; + } + if (src->numkeysraw & 0x20) { + dst->flags |= DESFIRE_EM_APP_ISOFIDS; + } +} + +static void desfire_em_fill_file(desfire_em_file_t *dst, const desfire_dump_file_t *src, uint8_t appidx) { + + dst->app = appidx; + dst->num = src->num; + dst->type = src->type; + dst->isofid = src->isofid; + dst->rights = src->accessrights; + + dst->flags = (src->commmode & DESFIRE_EM_FILE_COMM_MASK); + if (src->read_ok == false) { + dst->flags |= DESFIRE_EM_FILE_UNKNOWN; + } + if (src->limitedcredit) { + dst->flags |= DESFIRE_EM_FILE_LIMCREDIT; + } + + switch (src->type) { + case 0x00: + case 0x01: + dst->u.data.size = src->size; + break; + case 0x02: + dst->u.value.lower = src->lowerlimit; + dst->u.value.upper = src->upperlimit; + dst->u.value.value = src->value; + break; + case 0x03: + case 0x04: + dst->u.record.recordsize = src->recordsize; + dst->u.record.maxrecords = src->maxrecords; + dst->u.record.currecords = src->currecords; + break; + default: + break; + } +} + +static void desfire_em_fill_keys(desfire_em_key_t *tbl, uint8_t *n, const desfire_dump_app_t *app, uint8_t appidx) { + + for (uint8_t k = 0; k < DESFIRE_EM_MAX_KEYS; k++) { + + if (app->keys.present[k] == 0 && app->keys.versionknown[k] == 0) { + continue; + } + + desfire_em_key_t *e = &tbl[*n]; + memset(e, 0, sizeof(desfire_em_key_t)); + e->app = appidx; + e->num = k; + e->ver = app->keys.version[k]; + + if (app->keys.versionknown[k]) { + e->flags |= DESFIRE_EM_KEY_VERKNOWN; + } + + if (app->keys.present[k]) { + e->flags |= DESFIRE_EM_KEY_PRESENT; + memcpy(e->key, app->keys.key[k], desfire_get_key_length(app->keytype)); + } + + (*n)++; + } +} + +int desfire_em_pack(const desfire_dump_t *dump, uint8_t *out, size_t outlen, size_t *used) { + + if (dump == NULL || out == NULL || used == NULL) { + return PM3_EINVARG; + } + + *used = 0; + + if (outlen < sizeof(desfire_em_hdr_t) || outlen > UINT16_MAX) { + PrintAndLogEx(ERR, "Emulator memory of %zu bytes cannot hold a card image", outlen); + return PM3_EINVARG; + } + + if (dump->appcount > DESFIRE_EM_MAX_APPS) { + PrintAndLogEx(ERR, "Card has %u applications, the format holds %u", dump->appcount, DESFIRE_EM_MAX_APPS); + return PM3_EOUTOFBOUND; + } + + memset(out, 0, outlen); + desfire_em_hdr_t *hdr = (desfire_em_hdr_t *)out; + + hdr->magic = DESFIRE_EM_MAGIC; + hdr->layout = DESFIRE_EM_LAYOUT_VERSION; + hdr->size = outlen; + hdr->generation = desfire_em_gen_from_version(dump->versionhw, dump->versionhwlen); + + hdr->uidlen = MIN(dump->card_info.uidlen, (uint8_t)sizeof(hdr->uid)); + memcpy(hdr->uid, dump->card_info.uid, hdr->uidlen); + memcpy(hdr->atqa, dump->card_info.atqa, sizeof(hdr->atqa)); + hdr->sak = dump->card_info.sak; + hdr->atslen = MIN(dump->card_info.ats_len, (uint8_t)sizeof(hdr->ats)); + memcpy(hdr->ats, dump->card_info.ats, hdr->atslen); + + hdr->versionhwlen = MIN(dump->versionhwlen, (uint8_t)sizeof(hdr->versionhw)); + memcpy(hdr->versionhw, dump->versionhw, hdr->versionhwlen); + hdr->versionswlen = MIN(dump->versionswlen, (uint8_t)sizeof(hdr->versionsw)); + memcpy(hdr->versionsw, dump->versionsw, hdr->versionswlen); + hdr->versionprodlen = MIN(dump->versionprodlen, (uint8_t)sizeof(hdr->versionprod)); + memcpy(hdr->versionprod, dump->versionprod, hdr->versionprodlen); + + hdr->signaturelen = MIN(dump->signaturelen, (uint8_t)sizeof(hdr->signature)); + memcpy(hdr->signature, dump->signature, hdr->signaturelen); + + // app[0] is the PICC, so the table is one longer than the card's own count + uint8_t appcount = dump->appcount + 1; + + uint16_t filecount = 0; + for (uint8_t i = 0; i < dump->appcount; i++) { + filecount += MIN(dump->app[i].filecount, (uint8_t)DESFIRE_EM_MAX_FILES); + } + + // keys are sparse, so count them before laying anything out + uint16_t keycount = 0; + for (uint8_t i = 0; i < appcount; i++) { + const desfire_dump_app_t *a = (i == 0) ? &dump->picc : &dump->app[i - 1]; + for (uint8_t k = 0; k < DESFIRE_EM_MAX_KEYS; k++) { + if (a->keys.present[k] || a->keys.versionknown[k]) { + keycount++; + } + } + } + + hdr->app_off = sizeof(desfire_em_hdr_t); + hdr->file_off = hdr->app_off + (appcount * sizeof(desfire_em_app_t)); + hdr->key_off = hdr->file_off + (filecount * sizeof(desfire_em_file_t)); + + uint32_t tables_end = hdr->key_off + (keycount * sizeof(desfire_em_key_t)); + if (tables_end > outlen) { + PrintAndLogEx(ERR, "Card image tables need %u bytes, emulator memory holds %zu", tables_end, outlen); + return PM3_EOUTOFBOUND; + } + hdr->tables_end = tables_end; + + desfire_em_app_t *apps = (desfire_em_app_t *)(out + hdr->app_off); + desfire_em_file_t *files = (desfire_em_file_t *)(out + hdr->file_off); + desfire_em_key_t *keys = (desfire_em_key_t *)(out + hdr->key_off); + + // file data grows down from the end, tables grow up: the two only have to + // not meet, and what is between them is the card's free memory + uint32_t data_start = outlen; + uint32_t reserved = 0; // NV the card has spent, for GetFreeMem + uint8_t nkeys = 0; + uint16_t nfiles = 0; + + for (uint8_t i = 0; i < appcount; i++) { + + const desfire_dump_app_t *a = (i == 0) ? &dump->picc : &dump->app[i - 1]; + + desfire_em_fill_app(&apps[i], a); + if (i == 0) { + // the PICC is AID 000000 whatever the dump happens to say + memset(apps[i].aid, 0, sizeof(apps[i].aid)); + } + + desfire_em_fill_keys(keys, &nkeys, a, i); + + // a card charges for the application itself, not only for its files + reserved += DESFIRE_EM_ROUNDUP(DESFIRE_EM_APP_OVERHEAD + + (a->numkeys * desfire_get_key_length(a->keytype))); + + if (i == 0) { + continue; // the PICC has no files + } + + uint8_t fc = MIN(a->filecount, (uint8_t)DESFIRE_EM_MAX_FILES); + for (uint8_t n = 0; n < fc; n++) { + + const desfire_dump_file_t *sf = &a->files[n]; + desfire_em_file_t *df = &files[nfiles]; + + desfire_em_fill_file(df, sf, i); + + uint32_t reserve = desfire_em_file_reserve(sf); + if (reserve == 0) { + nfiles++; + continue; // a type we do not reserve for, eg transaction MAC + } + + if (data_start < reserve || (data_start - reserve) < tables_end) { + PrintAndLogEx(ERR, "Out of emulator memory laying out AID %06X file %02X: needs %u more bytes" + , a->aid, sf->num, reserve - (data_start - tables_end)); + return PM3_EOUTOFBOUND; + } + + data_start -= reserve; + reserved += reserve; + + uint32_t extent = desfire_em_file_has_shadow(sf->type) ? (reserve / 2) : reserve; + df->dataoff = data_start; + df->datalen = extent; + df->shadow_off = desfire_em_file_has_shadow(sf->type) ? (data_start + extent) : 0; + + uint32_t content = desfire_em_content_len(sf); + if (content && sf->data) { + + if (sf->datalen < content) { + PrintAndLogEx(WARNING, "AID %06X file %02X holds %u of %u bytes, the rest of the image is zero" + , a->aid, sf->num, sf->datalen, content); + content = sf->datalen; + } + + memcpy(out + df->dataoff, sf->data, MIN(content, extent)); + } + + nfiles++; + } + } + + hdr->appcount = appcount; + hdr->filecount = nfiles; + hdr->keycount = nkeys; + hdr->data_start = data_start; + hdr->reserved = reserved; + + // What the card claims to hold. + // + // Anchor it to the free memory the real card reported when the dump was + // taken: cardsize = observed free + what we reserved for the same content. + // The emulated card then answers GetFreeMem with the number its original + // answered, and that figure falls as a reader writes. Much better than + // deriving from the storage size byte, which understates -- a 2K part codes + // 2048 and hands out 2560. + // + // With no observed figure, fall back to the storage size byte as a floor, + // never below what this image already carries. + uint32_t cardsize; + if (dump->freemem_ok) { + cardsize = dump->freemem + reserved; + } else { + cardsize = desfire_em_nominal_cardsize(dump->versionhw, dump->versionhwlen); + if (cardsize < reserved) { + cardsize = reserved; + } + } + + if (cardsize > outlen) { + // a claim larger than emulator memory is one we cannot honour + cardsize = outlen; + } + hdr->cardsize = cardsize; + + if (reserved > hdr->cardsize) { + PrintAndLogEx(ERR, "Card image needs %u bytes but a %u byte card cannot hold it", reserved, hdr->cardsize); + return PM3_EOUTOFBOUND; + } + + *used = outlen; + return PM3_SUCCESS; +} + +int desfire_em_unpack(const uint8_t *img, size_t imglen, desfire_dump_t *dump) { + + if (img == NULL || dump == NULL || imglen < sizeof(desfire_em_hdr_t)) { + return PM3_EINVARG; + } + + const desfire_em_hdr_t *hdr = (const desfire_em_hdr_t *)img; + + if (hdr->magic != DESFIRE_EM_MAGIC) { + PrintAndLogEx(ERR, "Not a DESFire card image, magic is %08X", hdr->magic); + return PM3_EINVARG; + } + + if (hdr->layout != DESFIRE_EM_LAYOUT_VERSION) { + PrintAndLogEx(ERR, "Card image layout v%u, this client speaks v%u", hdr->layout, DESFIRE_EM_LAYOUT_VERSION); + return PM3_EINVARG; + } + + if (hdr->size > imglen || hdr->tables_end > imglen || hdr->data_start > imglen) { + PrintAndLogEx(ERR, "Card image is internally inconsistent with its %zu bytes", imglen); + return PM3_EINVARG; + } + + memset(dump, 0, sizeof(desfire_dump_t)); + + dump->card_info.uidlen = hdr->uidlen; + memcpy(dump->card_info.uid, hdr->uid, MIN(hdr->uidlen, (uint8_t)sizeof(dump->card_info.uid))); + memcpy(dump->card_info.atqa, hdr->atqa, sizeof(hdr->atqa)); + dump->card_info.sak = hdr->sak; + dump->card_info.ats_len = hdr->atslen; + memcpy(dump->card_info.ats, hdr->ats, MIN(hdr->atslen, (uint8_t)sizeof(dump->card_info.ats))); + + dump->versionhwlen = hdr->versionhwlen; + memcpy(dump->versionhw, hdr->versionhw, sizeof(dump->versionhw)); + dump->versionswlen = hdr->versionswlen; + memcpy(dump->versionsw, hdr->versionsw, sizeof(dump->versionsw)); + dump->versionprodlen = hdr->versionprodlen; + memcpy(dump->versionprod, hdr->versionprod, sizeof(dump->versionprod)); + + dump->signaturelen = hdr->signaturelen; + memcpy(dump->signature, hdr->signature, sizeof(dump->signature)); + + // free memory is the card's, not the frontier gap: that is the number a + // reader gets from GetFreeMem, and it has to look like the part we claim + // to be rather than like however much emulator memory happens to be spare + dump->freemem = (hdr->cardsize > hdr->reserved) ? (hdr->cardsize - hdr->reserved) : 0; + dump->freemem_ok = true; + + const desfire_em_app_t *apps = (const desfire_em_app_t *)(img + hdr->app_off); + const desfire_em_file_t *files = (const desfire_em_file_t *)(img + hdr->file_off); + const desfire_em_key_t *keys = (const desfire_em_key_t *)(img + hdr->key_off); + + if (hdr->appcount == 0 || hdr->appcount > (DESFIRE_EM_MAX_APPS + 1)) { + PrintAndLogEx(ERR, "Card image claims %u applications", hdr->appcount); + return PM3_EINVARG; + } + + dump->appcount = hdr->appcount - 1; // app[0] is the PICC + + for (uint8_t i = 0; i < hdr->appcount; i++) { + + desfire_dump_app_t *a = (i == 0) ? &dump->picc : &dump->app[i - 1]; + const desfire_em_app_t *s = &apps[i]; + + a->aid = s->aid[0] | (s->aid[1] << 8) | (s->aid[2] << 16); + a->isofid = s->isofid; + a->keysettings = s->keysettings; + a->numkeysraw = s->numkeysraw; + a->numkeys = s->numkeysraw & 0x1F; + a->keytype = s->keytype; + a->settings_ok = (s->numkeysraw != 0); + a->auth_ok = ((s->flags & DESFIRE_EM_APP_AUTHED) != 0); + a->dfnamelen = MIN(s->dfnamelen, (uint8_t)sizeof(a->dfname)); + memcpy(a->dfname, s->dfname, a->dfnamelen); + } + + for (uint8_t k = 0; k < hdr->keycount; k++) { + + const desfire_em_key_t *s = &keys[k]; + if (s->app >= hdr->appcount || s->num >= DESFIRE_EM_MAX_KEYS) { + continue; + } + + desfire_dump_app_t *a = (s->app == 0) ? &dump->picc : &dump->app[s->app - 1]; + + if (s->flags & DESFIRE_EM_KEY_VERKNOWN) { + a->keys.versionknown[s->num] = 1; + a->keys.version[s->num] = s->ver; + } + + if (s->flags & DESFIRE_EM_KEY_PRESENT) { + a->keys.present[s->num] = 1; + memcpy(a->keys.key[s->num], s->key, DESFIRE_MAX_KEY_SIZE); + } + } + + for (uint16_t n = 0; n < hdr->filecount; n++) { + + const desfire_em_file_t *s = &files[n]; + if (s->app == 0 || s->app >= hdr->appcount) { + continue; + } + + desfire_dump_app_t *a = &dump->app[s->app - 1]; + if (a->filecount >= DESFIRE_MAX_FILE_COUNT) { + continue; + } + + desfire_dump_file_t *f = &a->files[a->filecount]; + + f->num = s->num; + f->type = s->type; + f->isofid = s->isofid; + f->accessrights = s->rights; + f->commmode = s->flags & DESFIRE_EM_FILE_COMM_MASK; + f->limitedcredit = ((s->flags & DESFIRE_EM_FILE_LIMCREDIT) != 0); + f->settings_ok = true; + f->read_ok = ((s->flags & DESFIRE_EM_FILE_UNKNOWN) == 0); + + switch (s->type) { + case 0x00: + case 0x01: + f->size = s->u.data.size; + break; + case 0x02: + f->lowerlimit = s->u.value.lower; + f->upperlimit = s->u.value.upper; + f->value = s->u.value.value; + break; + case 0x03: + case 0x04: + f->recordsize = s->u.record.recordsize; + f->maxrecords = s->u.record.maxrecords; + f->currecords = s->u.record.currecords; + break; + default: + break; + } + + a->filecount++; + + uint32_t content = MIN(desfire_em_content_len(f), (uint32_t)s->datalen); + if (content == 0) { + continue; + } + + if (s->dataoff + content > imglen) { + PrintAndLogEx(WARNING, "AID %06X file %02X reaches past the image, skipping its contents", a->aid, f->num); + continue; + } + + f->data = calloc(content, sizeof(uint8_t)); + if (f->data == NULL) { + return PM3_EMALLOC; + } + + memcpy(f->data, img + s->dataoff, content); + f->datalen = content; + } + + return PM3_SUCCESS; +} + +void desfire_em_print(const uint8_t *img, size_t imglen) { + + if (img == NULL || imglen < sizeof(desfire_em_hdr_t)) { + return; + } + + const desfire_em_hdr_t *hdr = (const desfire_em_hdr_t *)img; + + PrintAndLogEx(INFO, "--- " _CYAN_("DESFire card image") " -----------------------"); + PrintAndLogEx(SUCCESS, "Generation....... %s", desfire_em_gen_str(hdr->generation)); + PrintAndLogEx(SUCCESS, "UID.............. %s", sprint_hex_inrow(hdr->uid, hdr->uidlen)); + PrintAndLogEx(SUCCESS, "Applications..... %u ( incl. the PICC )", hdr->appcount); + PrintAndLogEx(SUCCESS, "Files............ %u", hdr->filecount); + PrintAndLogEx(SUCCESS, "Keys............. %u", hdr->keycount); + PrintAndLogEx(SUCCESS, "Tables end at.... %u", hdr->tables_end); + PrintAndLogEx(SUCCESS, "Data starts at... %u", hdr->data_start); + PrintAndLogEx(SUCCESS, "Card capacity.... %u bytes, %u spent", hdr->cardsize, hdr->reserved); + PrintAndLogEx(SUCCESS, "Free memory...... " _GREEN_("%u") " bytes ( what a reader sees )" + , (hdr->cardsize > hdr->reserved) ? (hdr->cardsize - hdr->reserved) : 0); + PrintAndLogEx(SUCCESS, "Emulator memory.. %u of %u bytes spare" + , (hdr->data_start > hdr->tables_end) ? (hdr->data_start - hdr->tables_end) : 0 + , hdr->size); +} + +//----------------------------------------------------------------------------- +// Moving an image to and from the device. +// +// No DESFire specific command is needed either way. Emulator memory is one +// region shared by every simulation, so the generic MIFARE setter and the bulk +// emulator download already reach it -- CMD_HF_MIFARE_EML_MEMSET writes through +// emlSet(), which bounds-checks, and BIG_BUF_EML reads back with the device +// clamping to the size it reports in capabilities_t. +//----------------------------------------------------------------------------- + +#define DESFIRE_EM_XFER_WIDTH 16 // emlSet offset is blockno * this + +int desfire_em_upload(const uint8_t *img, size_t imglen) { + + if (img == NULL || imglen == 0) { + return PM3_EINVARG; + } + + if ((imglen % DESFIRE_EM_XFER_WIDTH) != 0) { + PrintAndLogEx(ERR, "Card image of %zu bytes is not a multiple of %d", imglen, DESFIRE_EM_XFER_WIDTH); + return PM3_EINVARG; + } + + // one MEMSET carries a header plus data, so size the chunk from what the + // device told us it can take rather than assuming + size_t perframe = (g_conn.max_cmd_data_size - 8) / DESFIRE_EM_XFER_WIDTH; + if (perframe == 0) { + return PM3_EINVARG; + } + if (perframe > 255) { + perframe = 255; // blockcnt is a uint8_t + } + + size_t blocks = imglen / DESFIRE_EM_XFER_WIDTH; + + PrintAndLogEx(INFO, "Uploading " _YELLOW_("%zu") " bytes to emulator memory", imglen); + + for (size_t b = 0; b < blocks; b += perframe) { + + size_t n = MIN(perframe, blocks - b); + + int res = mf_eml_set_mem_xt((uint8_t *)img + (b * DESFIRE_EM_XFER_WIDTH), b, n, DESFIRE_EM_XFER_WIDTH, 0); + if (res != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Upload failed at block %zu of %zu", b, blocks); + return res; + } + } + + return PM3_SUCCESS; +} + +int desfire_em_download(uint8_t *img, size_t imglen) { + + if (img == NULL || imglen == 0) { + return PM3_EINVARG; + } + + PrintAndLogEx(INFO, "Downloading " _YELLOW_("%zu") " bytes from emulator memory", imglen); + + if (GetFromDevice(BIG_BUF_EML, img, imglen, 0, NULL, 0, NULL, 2500, false) == false) { + PrintAndLogEx(WARNING, "command execution time out"); + return PM3_ETIMEOUT; + } + + return PM3_SUCCESS; +} diff --git a/client/src/mifare/desfireem.h b/client/src/mifare/desfireem.h new file mode 100644 index 000000000..e1a74cf3a --- /dev/null +++ b/client/src/mifare/desfireem.h @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------------- +// 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. +//----------------------------------------------------------------------------- +// Pack a DESFire card image for emulator memory, and read one back. +// Layout and rationale live in include/desfire_em.h. +//----------------------------------------------------------------------------- + +#ifndef __DESFIREEM_H +#define __DESFIREEM_H + +#include "common.h" +#include "desfire_em.h" +#include "fileutils.h" // desfire_dump_t + +// Which generation a GetVersion hardware frame describes. Needs at least 5 +// bytes; returns DESFIRE_EM_GEN_UNKNOWN for anything it does not recognise. +uint8_t desfire_em_gen_from_version(const uint8_t *versionhw, uint8_t len); + +// NV the GetVersion storage size byte codes. A floor on the real capacity, not +// the capacity: a 2K part codes 2048 and hands out 2560. 0 if undecodable. +uint32_t desfire_em_nominal_cardsize(const uint8_t *versionhw, uint8_t len); +const char *desfire_em_gen_str(uint8_t gen); + +// Emulator memory a file reserves, committed region and shadow together. +// Rounded to DESFIRE_EM_GRANULE. Backup, value and record files carry a shadow +// because CommitTransaction covers them; standard data files do not. +uint32_t desfire_em_file_reserve(const desfire_dump_file_t *f); + +// Pack `dump` into `out`, which may use at most `outlen` bytes. On success +// *used is the number of bytes the image occupies, which is outlen unless the +// caller wants to trim -- the data pool grows down from the end, so the image +// is not contiguous and cannot be shortened. +// +// Fails with PM3_EOUTOFBOUND, naming the shortfall, rather than truncating. +int desfire_em_pack(const desfire_dump_t *dump, uint8_t *out, size_t outlen, size_t *used); + +// Walk an image back into a desfire_dump_t. The caller owns the per-file heap +// buffers afterwards and must release them with desfire_dump_free(). +int desfire_em_unpack(const uint8_t *img, size_t imglen, desfire_dump_t *dump); + +// Print an image's structure. Does not need a device. +void desfire_em_print(const uint8_t *img, size_t imglen); + +// Move an image to and from emulator memory. imglen must be a multiple of 16 +// for the upload, which is what the generic emulator setter addresses in. +int desfire_em_upload(const uint8_t *img, size_t imglen); +int desfire_em_download(uint8_t *img, size_t imglen); + +#endif // __DESFIREEM_H diff --git a/doc/commands.json b/doc/commands.json index 5b8bb2cdc..0481f9161 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -7887,6 +7887,47 @@ ], "usage": "hf mfdes dump [-hav] [-n ] [-t ] [-k ] [--kdf ] [-i ] [-m ] [-c ] [--schann ] [--aid ] [--isoid ] [--dfname ] [-l ] [--no-auth] [-f ] [--keys ] [--ns]" }, + "hf mfdes eload": { + "command": "hf mfdes eload", + "description": "Load a DESFire card dump into emulator memory. The dump is packed into the on-device card image, see doc/mfdes_dump_format.md", + "notes": [ + "hf mfdes eload -f hf-mfdes-01020304050607-dump.json" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-f, --file Filename of dump", + "-v, --verbose Verbose output" + ], + "usage": "hf mfdes eload [-hv] -f " + }, + "hf mfdes esave": { + "command": "hf mfdes esave", + "description": "Save the card image in emulator memory to a dump file. Shows what a reader left behind if one has been talking to the simulation", + "notes": [ + "hf mfdes esave -f myfile" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-f, --file Filename, if no UID will be used as filename", + "-v, --verbose Verbose output" + ], + "usage": "hf mfdes esave [-hv] [-f ]" + }, + "hf mfdes eview": { + "command": "hf mfdes eview", + "description": "Show the DESFire card image currently in emulator memory", + "notes": [ + "hf mfdes eview" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-v, --verbose Also print every application and file" + ], + "usage": "hf mfdes eview [-hv]" + }, "hf mfdes formatpicc": { "command": "hf mfdes formatpicc", "description": "Format card. Can be done only if enabled in the configuration. Master key needs to be provided.", @@ -16561,8 +16602,8 @@ } }, "metadata": { - "commands_extracted": 921, + "commands_extracted": 924, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2026-09-14T06:50:14+00:00" + "extracted_on": "2026-09-14T17:45:11+00:00" } } diff --git a/doc/commands.md b/doc/commands.md index 4f2c8c2d8..d92ad86aa 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -818,6 +818,9 @@ Check column "offline" for their availability. |`hf mfdes lsfiles `|N |`Show all files list` |`hf mfdes dump `|N |`Dump all files` |`hf mfdes view `|Y |`Display content from tag dump file` +|`hf mfdes eload `|N |`Upload file into emulator memory` +|`hf mfdes esave `|N |`Save emulator memory to file` +|`hf mfdes eview `|N |`View emulator memory` |`hf mfdes createfile `|N |`Create Standard/Backup File` |`hf mfdes createvaluefile`|N |`Create Value File` |`hf mfdes createrecordfile`|N |`Create Linear/Cyclic Record File` diff --git a/include/desfire_em.h b/include/desfire_em.h new file mode 100644 index 000000000..64d3be604 --- /dev/null +++ b/include/desfire_em.h @@ -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