mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-08-14 09:20:53 +00:00
736 lines
24 KiB
C
736 lines
24 KiB
C
//-----------------------------------------------------------------------------
|
|
// 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.
|
|
//-----------------------------------------------------------------------------
|
|
// MAD commands
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "cmdmad.h"
|
|
#include <string.h>
|
|
#include "cmdparser.h"
|
|
#include "commonutil.h"
|
|
#include "comms.h"
|
|
#include "ui.h"
|
|
#include "cliparser.h"
|
|
#include "fileutils.h"
|
|
#include "mifare/mad.h"
|
|
#include "mifare/mifarehost.h"
|
|
#include "mifare/mifare4.h"
|
|
#include "mifare/mifaredefault.h"
|
|
#include "mifare.h"
|
|
#include "crc.h"
|
|
#include "util.h"
|
|
#include "mifare/mad_test.h"
|
|
|
|
// --- Card transport adapters ---
|
|
|
|
static int mfc_read_sector(uint8_t sector_no, uint8_t key_type, const uint8_t *key, uint8_t *buf, bool verbose) {
|
|
// function pointer is used to call this fct, hence the need to match fct signature
|
|
(void)verbose;
|
|
return mf_read_sector(sector_no, key_type, key, buf);
|
|
}
|
|
|
|
static int mfc_write_sector_data(uint8_t sector_no, uint8_t key_type, const uint8_t *key, const uint8_t *data, bool verbose) {
|
|
(void)verbose;
|
|
uint8_t first = mfFirstBlockOfSector(sector_no);
|
|
uint8_t ndata = mfNumBlocksPerSector(sector_no) - 1;
|
|
|
|
for (int i = 0; i < ndata; i++) {
|
|
int res = mf_write_block(first + i, key_type, key, data + i * MFBLOCK_SIZE);
|
|
if (res != PM3_SUCCESS) {
|
|
return res;
|
|
}
|
|
}
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int mfp_read_sector(uint8_t sector_no, uint8_t key_type, const uint8_t *key, uint8_t *buf, bool verbose) {
|
|
return mfpReadSector(sector_no, key_type, key, buf, verbose);
|
|
}
|
|
|
|
static int mfp_write_sector_data(uint8_t sector_no, uint8_t key_type, const uint8_t *key, const uint8_t *data, bool verbose) {
|
|
return mfpWriteSector(sector_no, key_type, key, data, verbose);
|
|
}
|
|
|
|
// --- Card type detection ---
|
|
|
|
static int mad_detect_card(mad_card_type_t *out) {
|
|
|
|
uint8_t sector0[MFBLOCK_SIZE * 4] = {0};
|
|
|
|
if (mf_read_sector(MF_MAD1_SECTOR, MF_KEY_A, g_mifare_mad_key, sector0) == PM3_SUCCESS) {
|
|
*out = MAD_CARD_CLASSIC;
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
if (mfpReadSector(MF_MAD1_SECTOR, MF_KEY_A, (uint8_t *)g_mifarep_mad_key, sector0, false) == PM3_SUCCESS) {
|
|
*out = MAD_CARD_PLUS;
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
PrintAndLogEx(ERR, "Could not authenticate to MAD sector with default keys");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
static void mad_fill_ops(mad_ops_t *ops, mad_card_type_t card_type, const uint8_t *mad_key, const uint8_t *app_key, uint8_t key_type, bool verbose) {
|
|
|
|
if (card_type == MAD_CARD_CLASSIC) {
|
|
ops->read_sector = mfc_read_sector;
|
|
ops->write_sector_data = mfc_write_sector_data;
|
|
ops->mad_key = mad_key ? mad_key : g_mifare_mad_key;
|
|
ops->app_key = app_key ? app_key : g_mifare_ndef_key;
|
|
} else {
|
|
ops->read_sector = mfp_read_sector;
|
|
ops->write_sector_data = mfp_write_sector_data;
|
|
ops->mad_key = mad_key ? mad_key : g_mifarep_mad_key;
|
|
ops->app_key = app_key ? app_key : g_mifarep_ndef_key;
|
|
}
|
|
ops->mad_key_type = MF_KEY_A;
|
|
ops->app_key_type = key_type;
|
|
ops->verbose = verbose;
|
|
}
|
|
|
|
// Determine card type from key length, or auto-detect
|
|
static int mad_resolve_card(int keylen, int madkeylen, mad_card_type_t *out, bool force_classic, bool force_plus) {
|
|
|
|
if (force_classic) {
|
|
*out = MAD_CARD_CLASSIC;
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
if (force_plus) {
|
|
*out = MAD_CARD_PLUS;
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
if (keylen == AES_KEY_LEN || madkeylen == AES_KEY_LEN) {
|
|
*out = MAD_CARD_PLUS;
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
if (keylen == MIFARE_KEY_SIZE || madkeylen == MIFARE_KEY_SIZE) {
|
|
*out = MAD_CARD_CLASSIC;
|
|
return PM3_SUCCESS;
|
|
}
|
|
return mad_detect_card(out);
|
|
}
|
|
|
|
// --- Shared read/write/verify implementation ---
|
|
|
|
static int mad_cmd_read(const char *Cmd, const char *cmd_name, bool force_classic, bool force_plus) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, cmd_name,
|
|
"Read application data from sectors matching a MAD AID",
|
|
"mad read --aid e103 -> read NDEF data\n"
|
|
"mad read --aid e103 -k ffffffffffff -b -> Classic, key B\n"
|
|
"mad read --aid e103 -k d3f7d3f7d3f7d3f7d3f7d3f7d3f7d3f7 -> Plus\n"
|
|
"mad read --aid e103 -v -f mydata.bin");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str1(NULL, "aid", "<hex>", "application ID (2 hex bytes)"),
|
|
arg_str0("k", "key", "<hex>", "key for data sectors"),
|
|
arg_lit0("b", "keyb", "use key B"),
|
|
arg_str0(NULL, "madkey", "<hex>", "key for MAD sectors"),
|
|
arg_lit0(NULL, "be", "big-endian AID byte swap"),
|
|
arg_lit0(NULL, "override", "override failed CRC check"),
|
|
arg_str0("f", "file", "<fn>", "save raw data to file"),
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
uint8_t aid[2] = {0};
|
|
int aidlen = 0;
|
|
CLIGetHexWithReturn(ctx, 1, aid, &aidlen);
|
|
|
|
uint8_t userkey[AES_KEY_LEN] = {0};
|
|
int keylen = 0;
|
|
CLIGetHexWithReturn(ctx, 2, userkey, &keylen);
|
|
|
|
bool keyB = arg_get_lit(ctx, 3);
|
|
|
|
uint8_t madkey[AES_KEY_LEN] = {0};
|
|
int madkeylen = 0;
|
|
CLIGetHexWithReturn(ctx, 4, madkey, &madkeylen);
|
|
|
|
bool swapmad = arg_get_lit(ctx, 5);
|
|
bool override = arg_get_lit(ctx, 6);
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 7), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
|
|
bool verbose = arg_get_lit(ctx, 8);
|
|
CLIParserFree(ctx);
|
|
|
|
mad_card_type_t card_type;
|
|
int res = mad_resolve_card(keylen, madkeylen, &card_type, force_classic, force_plus);
|
|
if (res != PM3_SUCCESS) {
|
|
return res;
|
|
}
|
|
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "Using %s transport", (card_type == MAD_CARD_CLASSIC) ? "MIFARE Classic" : "MIFARE Plus");
|
|
}
|
|
|
|
mad_ops_t ops = {0};
|
|
mad_fill_ops(&ops,
|
|
card_type,
|
|
madkeylen > 0 ? madkey : NULL,
|
|
keylen > 0 ? userkey : NULL,
|
|
keyB ? MF_KEY_B : MF_KEY_A,
|
|
verbose
|
|
);
|
|
|
|
size_t datalen = 0;
|
|
uint16_t aaid = MemBeToUint2byte(aid);
|
|
uint8_t data[MIFARE_4K_MAX_BYTES] = {0};
|
|
|
|
res = mad_app_read(&ops, aaid, swapmad, override, data, sizeof(data), &datalen);
|
|
if (res != PM3_SUCCESS) {
|
|
return res;
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "read " _YELLOW_("%zu") " bytes from AID 0x" _YELLOW_("%04X"), datalen, aaid);
|
|
print_buffer_with_offset(data, datalen, 0, true);
|
|
|
|
if (fnlen) {
|
|
pm3_save_dump(filename, data, datalen, jsfRaw);
|
|
}
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int mad_cmd_write(const char *Cmd, const char *cmd_name, bool force_classic, bool force_plus) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, cmd_name,
|
|
"Write application data to sectors matching a MAD AID",
|
|
"mad write --aid e103 -d 0102030405060708 -> write data\n"
|
|
"mad write --aid e103 -f mydata.bin -> write from file\n"
|
|
"mad write --aid e103 -k ffffffffffff -d 0102030405 -> Classic, custom key");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str1(NULL, "aid", "<hex>", "application ID (2 hex bytes)"),
|
|
arg_str0("k", "key", "<hex>", "key for data sectors"),
|
|
arg_lit0("b", "keyb", "use key B (def: key A)"),
|
|
arg_str0(NULL, "madkey", "<hex>", "key for MAD sectors"),
|
|
arg_lit0(NULL, "be", "big-endian AID byte swap"),
|
|
arg_lit0(NULL, "override", "override failed CRC check"),
|
|
arg_str0("d", "data", "<hex>", "data to write"),
|
|
arg_str0("f", "file", "<fn>", "load data from file"),
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
uint8_t aid[2] = {0};
|
|
int aidlen = 0;
|
|
CLIGetHexWithReturn(ctx, 2, aid, &aidlen);
|
|
|
|
uint8_t userkey[AES_KEY_LEN] = {0};
|
|
int keylen = 0;
|
|
CLIGetHexWithReturn(ctx, 3, userkey, &keylen);
|
|
|
|
bool keyB = arg_get_lit(ctx, 3);
|
|
|
|
uint8_t madkey[AES_KEY_LEN] = {0};
|
|
int madkeylen = 0;
|
|
CLIGetHexWithReturn(ctx, 4, madkey, &madkeylen);
|
|
|
|
bool swapmad = arg_get_lit(ctx, 5);
|
|
bool override = arg_get_lit(ctx, 6);
|
|
|
|
uint8_t hexdata[MIFARE_4K_MAX_BYTES] = {0};
|
|
int hexdatalen = 0;
|
|
CLIGetHexWithReturn(ctx, 7, hexdata, &hexdatalen);
|
|
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 8), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
|
|
bool verbose = arg_get_lit(ctx, 9);
|
|
CLIParserFree(ctx);
|
|
|
|
uint8_t *wdata = hexdata;
|
|
size_t wdata_len = hexdatalen;
|
|
uint8_t *dump = NULL;
|
|
|
|
if (fnlen) {
|
|
size_t bytes_read = 0;
|
|
int res = pm3_load_dump(filename, (void **)&dump, &bytes_read, MIFARE_4K_MAX_BYTES);
|
|
if (res != PM3_SUCCESS) {
|
|
return res;
|
|
}
|
|
wdata = dump;
|
|
wdata_len = bytes_read;
|
|
}
|
|
|
|
if (wdata_len == 0) {
|
|
PrintAndLogEx(ERR, "No data to write (use `-d` or `-f`)");
|
|
free(dump);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
mad_card_type_t card_type;
|
|
int res = mad_resolve_card(keylen, madkeylen, &card_type, force_classic, force_plus);
|
|
if (res != PM3_SUCCESS) {
|
|
free(dump);
|
|
return res;
|
|
}
|
|
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "Using " _GREEN_("%s") " transport", (card_type == MAD_CARD_CLASSIC) ? "MIFARE Classic" : "MIFARE Plus");
|
|
}
|
|
|
|
mad_ops_t ops = {0};
|
|
mad_fill_ops(&ops,
|
|
card_type,
|
|
madkeylen > 0 ? madkey : NULL,
|
|
keylen > 0 ? userkey : NULL,
|
|
keyB ? MF_KEY_B : MF_KEY_A,
|
|
verbose
|
|
);
|
|
|
|
uint16_t aaid = MemBeToUint2byte(aid);
|
|
res = mad_app_write(&ops, aaid, swapmad, override, wdata, wdata_len);
|
|
free(dump);
|
|
return res;
|
|
}
|
|
|
|
static int mad_cmd_verify(const char *Cmd, const char *cmd_name, bool force_classic, bool force_plus) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, cmd_name,
|
|
"Read back and verify application data against expected content",
|
|
"mad verify --aid e103 -d 0102030405060708\n"
|
|
"mad verify --aid e103 -f mydata.bin");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str1(NULL, "aid", "<hex>", "application ID (2 hex bytes)"),
|
|
arg_str0("k", "key", "<hex>", "key for data sectors"),
|
|
arg_lit0("b", "keyb", "use key B (def: key A)"),
|
|
arg_str0(NULL, "madkey", "<hex>", "key for MAD sectors"),
|
|
arg_lit0(NULL, "be", "big-endian AID byte swap"),
|
|
arg_lit0(NULL, "override", "override failed CRC check"),
|
|
arg_str0("d", "data", "<hex>", "expected data"),
|
|
arg_str0("f", "file", "<fn>", "load expected data from file"),
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
uint8_t aid[2] = {0};
|
|
int aidlen = 0;
|
|
CLIGetHexWithReturn(ctx, 1, aid, &aidlen);
|
|
|
|
uint8_t userkey[AES_KEY_LEN] = {0};
|
|
int keylen = 0;
|
|
CLIGetHexWithReturn(ctx, 2, userkey, &keylen);
|
|
|
|
bool keyB = arg_get_lit(ctx, 3);
|
|
|
|
uint8_t madkey[AES_KEY_LEN] = {0};
|
|
int madkeylen = 0;
|
|
CLIGetHexWithReturn(ctx, 4, madkey, &madkeylen);
|
|
|
|
bool swapmad = arg_get_lit(ctx, 5);
|
|
bool override = arg_get_lit(ctx, 6);
|
|
|
|
uint8_t hexdata[MIFARE_4K_MAX_BYTES] = {0};
|
|
int hexdatalen = 0;
|
|
CLIGetHexWithReturn(ctx, 7, hexdata, &hexdatalen);
|
|
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 8), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
|
|
bool verbose = arg_get_lit(ctx, 9);
|
|
CLIParserFree(ctx);
|
|
|
|
uint8_t *edata = hexdata;
|
|
size_t edata_len = hexdatalen;
|
|
uint8_t *dump = NULL;
|
|
|
|
if (fnlen) {
|
|
size_t bytes_read = 0;
|
|
int res = pm3_load_dump(filename, (void **)&dump, &bytes_read, MIFARE_4K_MAX_BYTES);
|
|
if (res != PM3_SUCCESS) {
|
|
return res;
|
|
}
|
|
|
|
edata = dump;
|
|
edata_len = bytes_read;
|
|
}
|
|
|
|
if (edata_len == 0) {
|
|
PrintAndLogEx(ERR, "No expected data (use -d or -f)");
|
|
free(dump);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
mad_card_type_t card_type;
|
|
int res = mad_resolve_card(keylen, madkeylen, &card_type, force_classic, force_plus);
|
|
if (res != PM3_SUCCESS) {
|
|
free(dump);
|
|
return res;
|
|
}
|
|
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "Using " _GREEN_("%s") " transport", (card_type == MAD_CARD_CLASSIC) ? "MIFARE Classic" : "MIFARE Plus");
|
|
}
|
|
|
|
mad_ops_t ops = {0};
|
|
mad_fill_ops(&ops, card_type,
|
|
madkeylen > 0 ? madkey : NULL,
|
|
keylen > 0 ? userkey : NULL,
|
|
keyB ? MF_KEY_B : MF_KEY_A,
|
|
verbose
|
|
);
|
|
|
|
uint16_t aaid = MemBeToUint2byte(aid);
|
|
res = mad_app_verify(&ops, aaid, swapmad, override, edata, edata_len);
|
|
free(dump);
|
|
return res;
|
|
}
|
|
|
|
// --- Auto-detect commands (mad read / mad write / mad verify) ---
|
|
|
|
static int CmdMADRead(const char *Cmd) {
|
|
return mad_cmd_read(Cmd, "mad read", false, false);
|
|
}
|
|
static int CmdMADWrite(const char *Cmd) {
|
|
return mad_cmd_write(Cmd, "mad write", false, false);
|
|
}
|
|
static int CmdMADVerify(const char *Cmd) {
|
|
return mad_cmd_verify(Cmd, "mad verify", false, false);
|
|
}
|
|
|
|
// --- Card-specific wrappers (for hf mf / hf mfp aliases) ---
|
|
|
|
int CmdMADMFRead(const char *Cmd) {
|
|
return mad_cmd_read(Cmd, "hf mf madread", true, false);
|
|
}
|
|
int CmdMADMFWrite(const char *Cmd) {
|
|
return mad_cmd_write(Cmd, "hf mf madwrite", true, false);
|
|
}
|
|
int CmdMADMFVerify(const char *Cmd) {
|
|
return mad_cmd_verify(Cmd, "hf mf madverify", true, false);
|
|
}
|
|
int CmdMADMFPRead(const char *Cmd) {
|
|
return mad_cmd_read(Cmd, "hf mfp madread", false, true);
|
|
}
|
|
int CmdMADMFPWrite(const char *Cmd) {
|
|
return mad_cmd_write(Cmd, "hf mfp madwrite", false, true);
|
|
}
|
|
int CmdMADMFPVerify(const char *Cmd) {
|
|
return mad_cmd_verify(Cmd, "hf mfp madverify", false, true);
|
|
}
|
|
|
|
// --- Decode (offline) ---
|
|
|
|
static int CmdMADDecode(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "mad decode",
|
|
"Decode a MAD byte array and print the directory. \n"
|
|
"Use Sector 0 == MAD 1\n"
|
|
" Sector 16 == MAD 2\n",
|
|
"mad decode --mad1 <hex>\n"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_str1("1", "mad1", "<hex>", "MAD 1 Sector 0 data (64 bytes)"),
|
|
arg_str0("2", "mad2", "<hex>", "MAD 2 Sector 16 data (64 bytes)"),
|
|
arg_lit0(NULL, "be", "big-endian AID byte swap"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
bool verbose = arg_get_lit(ctx, 1);
|
|
|
|
uint8_t s0[MFBLOCK_SIZE * 4] = {0};
|
|
int s0n = 0;
|
|
int res = CLIParamHexToBuf(arg_get_str(ctx, 2), s0, sizeof(s0), &s0n);
|
|
|
|
uint8_t s16[MFBLOCK_SIZE * 4] = {0};
|
|
int s16n = 0;
|
|
int res2 = CLIParamHexToBuf(arg_get_str(ctx, 3), s16, sizeof(s16), &s16n);
|
|
bool swapmad = arg_get_lit(ctx, 4);
|
|
CLIParserFree(ctx);
|
|
|
|
if (res) {
|
|
PrintAndLogEx(FAILED, "Error parsing MAD1 hex data");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (s16n > 0 && res2) {
|
|
PrintAndLogEx(FAILED, "Error parsing MAD2 hex data");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (s0n < (int)sizeof(mad1_sector_t)) {
|
|
PrintAndLogEx(ERR, "Need at least %zu bytes for MAD1 (got %d)", sizeof(mad1_sector_t), s0n);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
MADPrintHeader();
|
|
bool haveMAD2 = false;
|
|
MAD1DecodeAndPrint((const mad1_sector_t *)s0, swapmad, verbose, &haveMAD2);
|
|
|
|
if (s16n >= (int)sizeof(mad2_sector_t)) {
|
|
MAD2DecodeAndPrint((const mad2_sector_t *)s16, swapmad, verbose);
|
|
} else if (haveMAD2 && s16n == 0) {
|
|
PrintAndLogEx(HINT, "MAD1 indicates v2, use --mad2 to provide sector 16 data");
|
|
}
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
// --- Encode ---
|
|
|
|
// parse "1-3,5,7-9" into sector numbers, returns count or -1 on error
|
|
static int parse_sector_ranges(const char *str, uint8_t *sectors, int max_sectors) {
|
|
int count = 0;
|
|
const char *p = str;
|
|
|
|
while (*p) {
|
|
|
|
while (*p == ' ' || *p == '\t') {
|
|
p++;
|
|
}
|
|
|
|
if (*p == '\0') {
|
|
break;
|
|
}
|
|
|
|
char *end = NULL;
|
|
long start = strtol(p, &end, 10);
|
|
|
|
if (end == p || start < 0 || start > 39) {
|
|
return -1;
|
|
}
|
|
|
|
long stop = start;
|
|
|
|
if (*end == '-') {
|
|
|
|
p = end + 1;
|
|
|
|
stop = strtol(p, &end, 10);
|
|
|
|
if (end == p || stop < start || stop > 39) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
for (long s = start; s <= stop; s++) {
|
|
if (s == 0 || s == 16) {
|
|
PrintAndLogEx(ERR, "Sector " _YELLOW_("%ld") " is reserved for MAD directory", s);
|
|
return -1;
|
|
}
|
|
if (count >= max_sectors) {
|
|
return -1;
|
|
}
|
|
sectors[count++] = (uint8_t)s;
|
|
}
|
|
|
|
p = end;
|
|
|
|
if (*p == ',') {
|
|
p++;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static int CmdMADEncode(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "mad encode",
|
|
"Encode a MAD byte array from AID-to-sector mappings",
|
|
"mad encode --aid E103:1-3 --aid 484D:4 -> MAD1 with NDEF + HID\n"
|
|
"mad encode --aid E103:1-3,5 -> non-contiguous sectors\n"
|
|
"mad encode --aid E103:1-3,17-18 --aid 484D:4 -> MAD1 + MAD2 + HID\n"
|
|
"mad encode --aid E103:1-3 -q -> quiet, hex only\n");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_strn(NULL, "aid", "<hex:sectors>", 1, 8, "AID and sector ranges (e.g. E103:1-3,5)"),
|
|
arg_lit0("q", "quiet", "quiet output, hex bytes only"),
|
|
arg_lit0(NULL, "be", "big-endian AID byte swap"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
struct arg_str *aid_arg = arg_get_str(ctx, 1);
|
|
int aid_count = aid_arg->count;
|
|
|
|
uint16_t sector_aids[40] = {0};
|
|
bool have_mad2 = false;
|
|
|
|
for (int a = 0; a < aid_count; a++) {
|
|
const char *val = aid_arg->sval[a];
|
|
|
|
const char *colon = strchr(val, ':');
|
|
if (colon == NULL) {
|
|
PrintAndLogEx(ERR, "Invalid format '%s', expected <aid>:<sectors> (e.g. E103:1-3)", val);
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
int aid_hex_len = colon - val;
|
|
if (aid_hex_len != 4) {
|
|
PrintAndLogEx(ERR, "AID must be 4 hex chars, got " _YELLOW_("%d"), aid_hex_len);
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
char aid_str[5] = {0};
|
|
memcpy(aid_str, val, 4);
|
|
uint16_t aid_val = (uint16_t)strtoul(aid_str, NULL, 16);
|
|
|
|
uint8_t sectors[40] = {0};
|
|
int nsectors = parse_sector_ranges(colon + 1, sectors, 40);
|
|
if (nsectors <= 0) {
|
|
PrintAndLogEx(ERR, "Invalid sector range in " _YELLOW_("'%s'"), val);
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
for (int s = 0; s < nsectors; s++) {
|
|
uint8_t sno = sectors[s];
|
|
if (sector_aids[sno] != 0) {
|
|
PrintAndLogEx(ERR, "Sector " _YELLOW_("%d") " already assigned to AID " _YELLOW_("0x%04X"), sno, sector_aids[sno]);
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
sector_aids[sno] = aid_val;
|
|
|
|
if (sno > 15) {
|
|
have_mad2 = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool quiet = arg_get_lit(ctx, 2);
|
|
bool swapmad = arg_get_lit(ctx, 3);
|
|
CLIParserFree(ctx);
|
|
|
|
// build MAD1
|
|
mad1_sector_t s0;
|
|
memset(&s0, 0, sizeof(s0));
|
|
|
|
for (int i = 0; i < MAD1_NUM_AIDS; i++) {
|
|
uint16_t a = sector_aids[i + 1];
|
|
s0.mad.aid[i] = swapmad ? BSWAP_16(a) : a;
|
|
}
|
|
|
|
s0.mad.info = 0x00;
|
|
s0.mad.crc = CRC8Mad((uint8_t *)&s0.mad.info, sizeof(mad1_t) - 1);
|
|
memcpy(s0.trailer.key_a, g_mifare_mad_key, MIFARE_KEY_SIZE);
|
|
memcpy(s0.trailer.access, "\x78\x77\x88", 3);
|
|
s0.trailer.gpb = (have_mad2) ? 0xC2 : 0xC1; // DA=1, MA=1, version
|
|
memcpy(s0.trailer.key_b, g_mifare_mad_key_b, MIFARE_KEY_SIZE);
|
|
|
|
// build MAD2 if needed
|
|
mad2_sector_t s16;
|
|
memset(&s16, 0, sizeof(s16));
|
|
|
|
if (have_mad2) {
|
|
|
|
|
|
for (int i = 0; i < MAD2_NUM_AIDS; i++) {
|
|
uint16_t a = sector_aids[i + 17];
|
|
s16.mad.aid[i] = swapmad ? BSWAP_16(a) : a;
|
|
}
|
|
s16.mad.info = 0x00;
|
|
s16.mad.crc = CRC8Mad((uint8_t *)&s16.mad.info, sizeof(mad2_t) - 1);
|
|
|
|
memcpy(s16.trailer.key_a, g_mifare_mad_key, MIFARE_KEY_SIZE);
|
|
memcpy(s16.trailer.access, "\x78\x77\x88", 3);
|
|
s16.trailer.gpb = 0xC2;
|
|
memcpy(s16.trailer.key_b, g_mifare_mad_key_b, MIFARE_KEY_SIZE);
|
|
}
|
|
|
|
if (quiet == false) {
|
|
MADPrintHeader();
|
|
bool dummy = false;
|
|
MAD1DecodeAndPrint(&s0, swapmad, false, &dummy);
|
|
if (have_mad2) {
|
|
MAD2DecodeAndPrint(&s16, swapmad, false);
|
|
}
|
|
PrintAndLogEx(NORMAL, "");
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, "MAD1 sector (64 bytes):");
|
|
PrintAndLogEx(SUCCESS, "%s", sprint_hex_inrow((const uint8_t *)&s0, sizeof(s0)));
|
|
|
|
if (have_mad2) {
|
|
PrintAndLogEx(SUCCESS, "MAD2 sector (64 bytes):");
|
|
PrintAndLogEx(SUCCESS, "%s", sprint_hex_inrow((const uint8_t *)&s16, sizeof(s16)));
|
|
}
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
// --- Command table ---
|
|
|
|
static int CmdMADTest(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "mad test",
|
|
"Run MAD regression tests. Runs offline with no card needed",
|
|
"mad test\n"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
bool verbose = arg_get_lit(ctx, 1);
|
|
CLIParserFree(ctx);
|
|
|
|
return exec_mad_test(verbose);
|
|
}
|
|
|
|
static int CmdHelp(const char *Cmd);
|
|
|
|
static command_t CommandTable[] = {
|
|
{"--------", CmdHelp, AlwaysAvailable, "------------- " _CYAN_("General") " ---------------"},
|
|
{"help", CmdHelp, AlwaysAvailable, "This help"},
|
|
{"--------", CmdHelp, AlwaysAvailable, "--------------- " _CYAN_("MAD") " -----------------"},
|
|
{"read", CmdMADRead, IfPm3Iso14443a, "Read data from MAD AID sectors"},
|
|
{"write", CmdMADWrite, IfPm3Iso14443a, "Write data to MAD AID sectors"},
|
|
{"verify", CmdMADVerify, IfPm3Iso14443a, "Verify data in MAD AID sectors"},
|
|
{"--------", CmdHelp, AlwaysAvailable, "------------- " _CYAN_("Operations") " ---------------"},
|
|
{"decode", CmdMADDecode, AlwaysAvailable, "Decode MAD byte array"},
|
|
{"encode", CmdMADEncode, AlwaysAvailable, "Encode MAD byte array from AID mappings"},
|
|
{"test", CmdMADTest, AlwaysAvailable, "Perform MAD regression self-tests"},
|
|
{NULL, NULL, NULL, NULL}
|
|
};
|
|
|
|
int CmdMAD(const char *Cmd) {
|
|
clearCommandBuffer();
|
|
return CmdsParse(CommandTable, Cmd);
|
|
}
|
|
|
|
static int CmdHelp(const char *Cmd) {
|
|
(void)Cmd;
|
|
CmdsHelp(CommandTable);
|
|
return PM3_SUCCESS;
|
|
}
|