2019-02-22 01:34:12 +08:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Copyright (C) 2019 Merlok
|
|
|
|
//
|
|
|
|
// This code is licensed to you under the terms of the GNU GPL, version 2 or,
|
|
|
|
// at your option, any later version. See the LICENSE.txt file for the text of
|
|
|
|
// the license.
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// MIFARE Application Directory (MAD) functions
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
#include "mad.h"
|
|
|
|
#include "ui.h"
|
2019-08-08 22:57:33 +08:00
|
|
|
#include "commonutil.h" // ARRAYLEN
|
|
|
|
|
2019-02-22 02:06:53 +08:00
|
|
|
#include "crc.h"
|
2019-02-23 00:05:45 +08:00
|
|
|
#include "util.h"
|
2019-02-22 01:34:12 +08:00
|
|
|
|
2019-02-23 00:06:37 +08:00
|
|
|
// https://www.nxp.com/docs/en/application-note/AN10787.pdf
|
2019-02-23 00:05:45 +08:00
|
|
|
static madAIDDescr madKnownAIDs[] = {
|
2019-03-10 06:35:06 +08:00
|
|
|
{0x0000, "free"},
|
|
|
|
{0x0001, "defect, e.g. access keys are destroyed or unknown"},
|
|
|
|
{0x0002, "reserved"},
|
|
|
|
{0x0003, "contains additional directory info"},
|
|
|
|
{0x0004, "contains card holder information in ASCII format."},
|
|
|
|
{0x0005, "not applicable (above memory size)"},
|
|
|
|
|
|
|
|
{0x03e1, "NDEF"},
|
2019-02-23 00:05:45 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static madAIDDescr madKnownClusterCodes[] = {
|
2019-03-10 06:35:06 +08:00
|
|
|
{0x00, "cluster: card administration"},
|
|
|
|
{0x01, "cluster: miscellaneous applications"},
|
|
|
|
{0x02, "cluster: miscellaneous applications"},
|
|
|
|
{0x03, "cluster: miscellaneous applications"},
|
|
|
|
{0x04, "cluster: miscellaneous applications"},
|
|
|
|
{0x05, "cluster: miscellaneous applications"},
|
|
|
|
{0x06, "cluster: miscellaneous applications"},
|
|
|
|
{0x07, "cluster: miscellaneous applications"},
|
|
|
|
{0x08, "cluster: airlines"},
|
|
|
|
{0x09, "cluster: ferry traffic"},
|
|
|
|
{0x10, "cluster: railway services"},
|
|
|
|
{0x11, "cluster: miscellaneous applications"},
|
|
|
|
{0x12, "cluster: transport"},
|
|
|
|
{0x14, "cluster: security solutions"},
|
|
|
|
{0x18, "cluster: city traffic"},
|
|
|
|
{0x19, "cluster: Czech Railways"},
|
|
|
|
{0x20, "cluster: bus services"},
|
|
|
|
{0x21, "cluster: multi modal transit"},
|
|
|
|
{0x28, "cluster: taxi"},
|
|
|
|
{0x30, "cluster: road toll"},
|
|
|
|
{0x31, "cluster: generic transport"},
|
|
|
|
{0x38, "cluster: company services"},
|
|
|
|
{0x40, "cluster: city card services"},
|
|
|
|
{0x47, "cluster: access control & security"},
|
|
|
|
{0x48, "cluster: access control & security"},
|
|
|
|
{0x49, "cluster: VIGIK"},
|
|
|
|
{0x4A, "cluster: Ministry of Defence, Netherlands"},
|
|
|
|
{0x4B, "cluster: Bosch Telecom, Germany"},
|
|
|
|
{0x4C, "cluster: European Union Institutions"},
|
|
|
|
{0x50, "cluster: ski ticketing"},
|
|
|
|
{0x51, "cluster: access control & security"},
|
|
|
|
{0x52, "cluster: access control & security"},
|
|
|
|
{0x53, "cluster: access control & security"},
|
|
|
|
{0x54, "cluster: access control & security"},
|
|
|
|
{0x55, "cluster: SOAA standard for offline access standard"},
|
|
|
|
{0x56, "cluster: access control & security"},
|
|
|
|
{0x58, "cluster: academic services"},
|
|
|
|
{0x60, "cluster: food"},
|
|
|
|
{0x68, "cluster: non-food trade"},
|
|
|
|
{0x70, "cluster: hotel"},
|
|
|
|
{0x71, "cluster: loyalty"},
|
|
|
|
{0x75, "cluster: airport services"},
|
|
|
|
{0x78, "cluster: car rental"},
|
|
|
|
{0x79, "cluster: Dutch government"},
|
|
|
|
{0x80, "cluster: administration services"},
|
|
|
|
{0x88, "cluster: electronic purse"},
|
|
|
|
{0x90, "cluster: television"},
|
|
|
|
{0x91, "cluster: cruise ship"},
|
|
|
|
{0x95, "cluster: IOPTA"},
|
|
|
|
{0x97, "cluster: metering"},
|
|
|
|
{0x98, "cluster: telephone"},
|
|
|
|
{0xA0, "cluster: health services"},
|
|
|
|
{0xA8, "cluster: warehouse"},
|
|
|
|
{0xB0, "cluster: electronic trade"},
|
|
|
|
{0xB8, "cluster: banking"},
|
|
|
|
{0xC0, "cluster: entertainment & sports"},
|
|
|
|
{0xC8, "cluster: car parking"},
|
|
|
|
{0xC9, "cluster: fleet management"},
|
|
|
|
{0xD0, "cluster: fuel, gasoline"},
|
|
|
|
{0xD8, "cluster: info services"},
|
|
|
|
{0xE0, "cluster: press"},
|
|
|
|
{0xE1, "cluster: NFC Forum"},
|
|
|
|
{0xE8, "cluster: computer"},
|
|
|
|
{0xF0, "cluster: mail"},
|
|
|
|
{0xF8, "cluster: miscellaneous applications"},
|
2019-02-22 01:34:12 +08:00
|
|
|
};
|
|
|
|
|
2019-02-23 00:05:45 +08:00
|
|
|
static const char unknownAID[] = "";
|
|
|
|
|
2019-03-10 18:20:22 +08:00
|
|
|
static const char *GetAIDDescription(uint16_t AID) {
|
2019-03-10 07:00:59 +08:00
|
|
|
for (int i = 0; i < ARRAYLEN(madKnownAIDs); i++)
|
2019-03-10 06:35:06 +08:00
|
|
|
if (madKnownAIDs[i].AID == AID)
|
|
|
|
return madKnownAIDs[i].Description;
|
2019-02-23 00:05:45 +08:00
|
|
|
|
2019-03-10 07:00:59 +08:00
|
|
|
for (int i = 0; i < ARRAYLEN(madKnownClusterCodes); i++)
|
2019-03-10 06:35:06 +08:00
|
|
|
if (madKnownClusterCodes[i].AID == (AID >> 8)) // high byte - cluster code
|
|
|
|
return madKnownClusterCodes[i].Description;
|
2019-03-09 15:59:13 +08:00
|
|
|
|
2019-03-10 06:35:06 +08:00
|
|
|
return unknownAID;
|
2019-02-23 00:05:45 +08:00
|
|
|
}
|
|
|
|
|
2019-04-13 06:25:43 +08:00
|
|
|
static int madCRCCheck(uint8_t *sector, bool verbose, int MADver) {
|
2019-03-10 06:35:06 +08:00
|
|
|
if (MADver == 1) {
|
|
|
|
uint8_t crc = CRC8Mad(§or[16 + 1], 15 + 16);
|
|
|
|
if (crc != sector[16]) {
|
|
|
|
PrintAndLogEx(WARNING, "Wrong MAD%d CRC. Calculated: 0x%02x, from card: 0x%02x", MADver, crc, sector[16]);
|
|
|
|
return 3;
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
uint8_t crc = CRC8Mad(§or[1], 15 + 16 + 16);
|
|
|
|
if (crc != sector[0]) {
|
|
|
|
PrintAndLogEx(WARNING, "Wrong MAD%d CRC. Calculated: 0x%02x, from card: 0x%02x", MADver, crc, sector[16]);
|
|
|
|
return 3;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2019-02-23 00:05:45 +08:00
|
|
|
}
|
|
|
|
|
2019-04-13 06:25:43 +08:00
|
|
|
static uint16_t madGetAID(uint8_t *sector, int MADver, int sectorNo) {
|
2019-03-10 06:35:06 +08:00
|
|
|
if (MADver == 1)
|
|
|
|
return (sector[16 + 2 + (sectorNo - 1) * 2] << 8) + (sector[16 + 2 + (sectorNo - 1) * 2 + 1]);
|
|
|
|
else
|
|
|
|
return (sector[2 + (sectorNo - 1) * 2] << 8) + (sector[2 + (sectorNo - 1) * 2 + 1]);
|
2019-02-23 00:05:45 +08:00
|
|
|
}
|
|
|
|
|
2019-03-10 18:20:22 +08:00
|
|
|
int MADCheck(uint8_t *sector0, uint8_t *sector10, bool verbose, bool *haveMAD2) {
|
2019-03-10 06:35:06 +08:00
|
|
|
int res = 0;
|
2019-03-09 15:59:13 +08:00
|
|
|
|
2019-03-10 06:35:06 +08:00
|
|
|
if (!sector0)
|
|
|
|
return 1;
|
2019-03-09 15:59:13 +08:00
|
|
|
|
2019-03-10 06:35:06 +08:00
|
|
|
uint8_t GPB = sector0[3 * 16 + 9];
|
|
|
|
if (verbose)
|
|
|
|
PrintAndLogEx(NORMAL, "GPB: 0x%02x", GPB);
|
2019-03-09 15:59:13 +08:00
|
|
|
|
2019-03-10 06:35:06 +08:00
|
|
|
// DA (MAD available)
|
|
|
|
if (!(GPB & 0x80)) {
|
|
|
|
PrintAndLogEx(ERR, "DA=0! MAD not available.");
|
|
|
|
return 1;
|
|
|
|
}
|
2019-03-09 15:59:13 +08:00
|
|
|
|
2019-03-10 06:35:06 +08:00
|
|
|
// MA (multi-application card)
|
|
|
|
if (verbose) {
|
|
|
|
if (GPB & 0x40)
|
|
|
|
PrintAndLogEx(NORMAL, "Multi application card.");
|
|
|
|
else
|
|
|
|
PrintAndLogEx(NORMAL, "Single application card.");
|
|
|
|
}
|
2019-03-09 15:59:13 +08:00
|
|
|
|
2019-03-10 06:35:06 +08:00
|
|
|
uint8_t MADVer = GPB & 0x03;
|
|
|
|
if (verbose)
|
|
|
|
PrintAndLogEx(NORMAL, "MAD version: %d", MADVer);
|
2019-03-09 15:59:13 +08:00
|
|
|
|
2019-03-10 06:35:06 +08:00
|
|
|
// MAD version
|
|
|
|
if ((MADVer != 0x01) && (MADVer != 0x02)) {
|
|
|
|
PrintAndLogEx(ERR, "Wrong MAD version: 0x%02x", MADVer);
|
|
|
|
return 2;
|
|
|
|
};
|
2019-03-09 15:59:13 +08:00
|
|
|
|
2019-03-10 06:35:06 +08:00
|
|
|
if (haveMAD2)
|
|
|
|
*haveMAD2 = (MADVer == 2);
|
2019-02-23 00:05:45 +08:00
|
|
|
|
2019-03-10 06:35:06 +08:00
|
|
|
res = madCRCCheck(sector0, true, 1);
|
2019-03-09 15:59:13 +08:00
|
|
|
|
2019-03-10 06:35:06 +08:00
|
|
|
if (verbose && !res)
|
|
|
|
PrintAndLogEx(NORMAL, "CRC8-MAD1 OK.");
|
2019-03-09 15:59:13 +08:00
|
|
|
|
2019-03-10 06:35:06 +08:00
|
|
|
if (MADVer == 2 && sector10) {
|
|
|
|
int res2 = madCRCCheck(sector10, true, 2);
|
|
|
|
if (!res)
|
|
|
|
res = res2;
|
2019-03-05 01:18:44 +08:00
|
|
|
|
2019-03-10 06:35:06 +08:00
|
|
|
if (verbose & !res2)
|
|
|
|
PrintAndLogEx(NORMAL, "CRC8-MAD2 OK.");
|
|
|
|
}
|
2019-03-09 15:59:13 +08:00
|
|
|
|
2019-03-10 06:35:06 +08:00
|
|
|
return res;
|
2019-03-05 01:18:44 +08:00
|
|
|
}
|
|
|
|
|
2019-03-10 18:20:22 +08:00
|
|
|
int MADDecode(uint8_t *sector0, uint8_t *sector10, uint16_t *mad, size_t *madlen) {
|
2019-03-10 06:35:06 +08:00
|
|
|
*madlen = 0;
|
|
|
|
bool haveMAD2 = false;
|
|
|
|
MADCheck(sector0, sector10, false, &haveMAD2);
|
|
|
|
|
|
|
|
for (int i = 1; i < 16; i++) {
|
|
|
|
mad[*madlen] = madGetAID(sector0, 1, i);
|
|
|
|
(*madlen)++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (haveMAD2) {
|
|
|
|
// mad2 sector (0x10 == 16dec) here
|
|
|
|
mad[*madlen] = 0x0005;
|
|
|
|
(*madlen)++;
|
|
|
|
|
|
|
|
for (int i = 1; i < 24; i++) {
|
|
|
|
mad[*madlen] = madGetAID(sector10, 2, i);
|
|
|
|
(*madlen)++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2019-03-05 01:18:44 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-03-10 18:20:22 +08:00
|
|
|
int MAD1DecodeAndPrint(uint8_t *sector, bool verbose, bool *haveMAD2) {
|
2019-03-05 01:18:44 +08:00
|
|
|
|
2019-03-10 06:35:06 +08:00
|
|
|
// check MAD1 only
|
|
|
|
MADCheck(sector, NULL, verbose, haveMAD2);
|
|
|
|
|
|
|
|
// info byte
|
|
|
|
uint8_t InfoByte = sector[16 + 1] & 0x3f;
|
|
|
|
if (InfoByte) {
|
|
|
|
PrintAndLogEx(NORMAL, "Card publisher sector: 0x%02x", InfoByte);
|
|
|
|
} else {
|
|
|
|
if (verbose)
|
|
|
|
PrintAndLogEx(NORMAL, "Card publisher sector not present.");
|
|
|
|
}
|
|
|
|
if (InfoByte == 0x10 || InfoByte >= 0x28)
|
2019-07-14 18:17:34 +08:00
|
|
|
PrintAndLogEx(WARNING, "Info byte error");
|
2019-03-10 06:35:06 +08:00
|
|
|
|
|
|
|
PrintAndLogEx(NORMAL, "00 MAD1");
|
2019-03-10 07:00:59 +08:00
|
|
|
for (int i = 1; i < 16; i++) {
|
2019-03-10 06:35:06 +08:00
|
|
|
uint16_t AID = madGetAID(sector, 1, i);
|
|
|
|
PrintAndLogEx(NORMAL, "%02d [%04X] %s", i, AID, GetAIDDescription(AID));
|
|
|
|
};
|
|
|
|
|
|
|
|
return 0;
|
2019-02-22 01:34:12 +08:00
|
|
|
};
|
|
|
|
|
2019-03-10 18:20:22 +08:00
|
|
|
int MAD2DecodeAndPrint(uint8_t *sector, bool verbose) {
|
2019-03-10 06:35:06 +08:00
|
|
|
PrintAndLogEx(NORMAL, "16 MAD2");
|
2019-03-02 00:49:53 +08:00
|
|
|
|
2019-03-10 06:35:06 +08:00
|
|
|
int res = madCRCCheck(sector, true, 2);
|
2019-02-22 01:34:12 +08:00
|
|
|
|
2019-03-10 06:35:06 +08:00
|
|
|
if (verbose && !res)
|
|
|
|
PrintAndLogEx(NORMAL, "CRC8-MAD2 OK.");
|
2019-03-02 01:13:59 +08:00
|
|
|
|
2019-03-10 06:35:06 +08:00
|
|
|
uint8_t InfoByte = sector[1] & 0x3f;
|
|
|
|
PrintAndLogEx(NORMAL, "MAD2 Card publisher sector: 0x%02x", InfoByte);
|
2019-03-02 00:49:53 +08:00
|
|
|
|
2019-03-10 07:00:59 +08:00
|
|
|
for (int i = 1; i < 8 + 8 + 7 + 1; i++) {
|
2019-03-10 06:35:06 +08:00
|
|
|
uint16_t AID = madGetAID(sector, 2, i);
|
|
|
|
PrintAndLogEx(NORMAL, "%02d [%04X] %s", i + 16, AID, GetAIDDescription(AID));
|
|
|
|
};
|
2019-03-09 15:59:13 +08:00
|
|
|
|
2019-03-10 06:35:06 +08:00
|
|
|
return 0;
|
2019-02-22 01:34:12 +08:00
|
|
|
};
|