proxmark3/client/src/cmdhfgallagher.c

703 lines
27 KiB
C
Raw Normal View History

2021-12-29 18:42:06 +08:00
/**
* Matt Moran (@DarkMatterMatt), 2021
* -----------------------------------------------------------------------------
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
* -----------------------------------------------------------------------------
* High frequency GALLAGHER tag commands.
* MIFARE DESFire, AIDs 2081F4-2F81F4
*/
#include "cmdhfgallagher.h"
2022-01-03 12:02:10 +08:00
#include "generator.h"
2022-01-01 17:22:57 +08:00
#include "mifare.h"
#include "mifare/desfirecore.h"
2021-12-29 18:42:06 +08:00
#include "mifare/gallaghercore.h"
#include <stdio.h>
2022-01-01 17:22:57 +08:00
#include <string.h>
2021-12-29 18:42:06 +08:00
#include "common.h"
2022-01-01 17:22:57 +08:00
#include "commonutil.h"
2021-12-29 18:42:06 +08:00
#include "cmdparser.h"
#include "cliparser.h"
#include "ui.h"
2022-01-04 12:03:22 +08:00
/** Application ID for the Gallagher Card Application Directory */
static const uint32_t CAD_AID = 0x2F81F4;
2022-01-04 11:59:59 +08:00
/**
* @brief Reverses the bytes in aid. Used when encoding/decoding Card Application Directory entries.
*/
2022-01-01 17:22:57 +08:00
static void reverseAid(uint8_t *aid) {
uint8_t tmp = aid[0];
aid[0] = aid[2];
aid[2] = tmp;
};
2021-12-29 18:42:06 +08:00
static int CmdHelp(const char *Cmd);
2022-01-04 11:59:59 +08:00
/**
* @brief Read Gallagher Card Application Directory from card.
*
* @param destBuf Buffer to copy Card Application Directory into.
* @param destBufLen Size of destBuf. Must be at least 108 bytes.
* @param numEntries Will be set to the number of entries in the Card Application Directory.
*/
2022-01-01 17:22:57 +08:00
static int readCardApplicationDirectory(DesfireContext_t *ctx, uint8_t *destBuf, uint8_t destBufLen, uint8_t *numEntries, bool verbose) {
2022-01-04 11:59:59 +08:00
if (destBufLen < 3 * 36) {
2022-01-01 17:22:57 +08:00
PrintAndLogEx(ERR, "readCardApplicationDirectory destination buffer is incorrectly sized. "
2022-01-04 11:59:59 +08:00
"Received length %d, must be at least %d", destBufLen, 3 * 36);
2022-01-01 17:22:57 +08:00
return PM3_EINVARG;
}
// Get card AIDs from Card Application Directory (which contains 1 to 3 files)
2022-01-04 11:59:59 +08:00
DesfireSetCommMode(ctx, DCMPlain);
2022-01-04 12:03:22 +08:00
int res = DesfireSelectAIDHex(ctx, CAD_AID, false, 0);
2022-01-04 11:59:59 +08:00
HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed selecting Card Application Directory, does AID F4812F exist?");
2022-01-01 17:22:57 +08:00
// Read up to 3 files with 6x 6-byte entries each
for (uint8_t i = 0; i < 3; i++) {
size_t readLen;
2022-01-04 11:59:59 +08:00
res = DesfireReadFile(ctx, i, 0, 36, &destBuf[i * 36], &readLen);
if (res != PM3_SUCCESS)
PrintAndLogEx(WARNING, "Failed reading file %d in Card Application Directory (AID F4812F)", i);
2022-01-03 12:02:10 +08:00
// end if the last entry is NULL
if (memcmp(&destBuf[36 * i + 30], "\0\0\0\0\0\0", 6) == 0) break;
2022-01-01 17:22:57 +08:00
}
2022-01-04 11:59:59 +08:00
// Count number of entries (i.e. count until we hit a NULL entry)
2022-01-01 17:22:57 +08:00
*numEntries = 0;
for (uint8_t i = 0; i < destBufLen; i += 6) {
if (memcmp(&destBuf[i], "\0\0\0\0\0\0", 6) == 0) break;
*numEntries += 1;
}
if (verbose) {
// Print what we found
PrintAndLogEx(SUCCESS, "Card Application Directory contains:" NOLF);
for (int i = 0; i < *numEntries; i++)
2022-01-04 11:59:59 +08:00
PrintAndLogEx(NORMAL, "%s %06X" NOLF, (i == 0) ? "" : ",", DesfireAIDByteToUint(&destBuf[i*6 + 3]));
2022-01-01 17:22:57 +08:00
PrintAndLogEx(NORMAL, "");
}
return PM3_SUCCESS;
}
2022-01-04 11:59:59 +08:00
/**
* @brief Read credentials from a single AID.
*
* @param aid Application ID to read.
* @param sitekey MIFARE site key.
* @param creds Decoded credentials will be stored in this structure.
*/
static int readCardApplicationCredentials(DesfireContext_t *ctx, uint32_t aid, uint8_t *sitekey, GallagherCredentials_t *creds, bool verbose) {
2022-01-01 17:22:57 +08:00
// Check that card UID has been set
2022-01-04 11:59:59 +08:00
if (ctx->uidlen == 0)
HFGAL_RET_ERR(PM3_EINVARG, "Card UID must be set in DesfireContext (required for key diversification)");
2022-01-01 17:22:57 +08:00
// Set up context
DesfireSetKeyNoClear(ctx, 2, T_AES, sitekey);
DesfireSetKdf(ctx, MFDES_KDF_ALGO_GALLAGHER, NULL, 0);
DesfireSetCommMode(ctx, DCMPlain);
// Select and authenticate to AID
2022-01-04 11:59:59 +08:00
int res = DesfireSelectAndAuthenticateAppW(ctx, DACEV1, ISW6bAID, aid, false, verbose);
HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed selecting/authenticating to AID %06X", aid);
2022-01-01 17:22:57 +08:00
// Read file 0 (contains credentials)
2022-01-03 12:02:10 +08:00
uint8_t buf[16] = {0};
2022-01-01 17:22:57 +08:00
size_t readLen = 0;
DesfireSetCommMode(ctx, DCMEncrypted);
res = DesfireReadFile(ctx, 0, 0, 16, buf, &readLen);
2022-01-04 11:59:59 +08:00
HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed reading file 0 in AID %06X", aid);
2022-01-01 17:22:57 +08:00
// Check file contained 16 bytes of data
if (readLen != 16) {
2022-01-04 11:59:59 +08:00
HFGAL_RET_ERR(PM3_EFAILED, "Failed reading file 0 in AID %06X, expected 16 bytes but received %d bytes", aid, readLen);
2022-01-01 17:22:57 +08:00
}
// Check second half of file is the bitwise inverse of the first half
for (uint8_t i = 8; i < 16; i++)
buf[i] ^= 0xFF;
if (memcmp(buf, &buf[8], 8) != 0) {
2022-01-04 11:59:59 +08:00
HFGAL_RET_ERR(PM3_EFAILED, "Invalid cardholder data in file 0 in AID %06X. Received %s", sprint_hex_inrow(buf, 16));
2022-01-01 17:22:57 +08:00
}
decodeCardholderCredentials(buf, creds);
// TODO: read MIFARE Enhanced Security file
// https://github.com/megabug/gallagher-research/blob/master/formats/mes.md
return PM3_SUCCESS;
}
2022-01-04 11:59:59 +08:00
/**
* @brief Read credentials from a Gallagher card.
*
* @param aid Application ID to read. If 0, then the Card Application Directory will be queried and all entries will be read.
* @param sitekey MIFARE site key.
* @param quiet Suppress error messages. Used when in continuous reader mode.
*/
static int readCard(uint32_t aid, uint8_t *sitekey, bool verbose, bool quiet) {
2022-01-01 17:22:57 +08:00
DropField();
clearCommandBuffer();
// Set up context
DesfireContext_t dctx = {0};
DesfireClearContext(&dctx);
// Get card UID (for key diversification)
int res = DesfireGetCardUID(&dctx);
2022-01-04 11:59:59 +08:00
HFGAL_RET_IF_ERR_MAYBE_MSG(res, !quiet, "Failed retrieving card UID.");
2022-01-01 17:22:57 +08:00
// Find AIDs to process (from CLI args or the Card Application Directory)
uint8_t cad[36 * 3] = {0};
uint8_t numEntries = 0;
2022-01-04 11:59:59 +08:00
if (aid != 0) {
DesfireAIDUintToByte(aid, &cad[3]);
2022-01-01 17:22:57 +08:00
reverseAid(&cad[3]); // CAD stores AIDs backwards
numEntries = 1;
} else {
res = readCardApplicationDirectory(&dctx, cad, ARRAYLEN(cad), &numEntries, verbose);
2022-01-04 11:59:59 +08:00
HFGAL_RET_IF_ERR_MAYBE_MSG(res, !quiet, "Failed reading card application directory.");
2022-01-01 17:22:57 +08:00
}
// Loop through each application in the CAD
for (uint8_t i = 0; i < numEntries * 6; i += 6) {
2022-01-04 11:59:59 +08:00
uint16_t regionCode = cad[i + 0];
uint16_t facilityCode = (cad[i + 1] << 8) + cad[i + 2];
2022-01-01 17:22:57 +08:00
// Copy AID out of CAD record
2022-01-04 11:59:59 +08:00
uint8_t currentAidBuf[3];
memcpy(currentAidBuf, &cad[3], 3);
reverseAid(currentAidBuf); // CAD stores AIDs backwards
uint32_t currentAid = DesfireAIDByteToUint(currentAidBuf);
2022-01-01 17:22:57 +08:00
if (verbose) {
2022-01-04 11:59:59 +08:00
if (regionCode > 0 || facilityCode > 0)
PrintAndLogEx(INFO, "Reading AID: %06X, region: %u, facility: %u", currentAid, regionCode, facilityCode);
2022-01-01 17:22:57 +08:00
else
2022-01-04 11:59:59 +08:00
PrintAndLogEx(INFO, "Reading AID: %06X", currentAid);
2022-01-01 17:22:57 +08:00
}
// Read & decode credentials
GallagherCredentials_t creds = {0};
res = readCardApplicationCredentials(&dctx, currentAid, sitekey, &creds, verbose);
2022-01-04 11:59:59 +08:00
HFGAL_RET_IF_ERR_MAYBE_MSG(res, !quiet, "Failed reading card application credentials.");
2022-01-01 17:22:57 +08:00
PrintAndLogEx(SUCCESS, "GALLAGHER - Region: " _GREEN_("%u") ", Facility: " _GREEN_("%u") ", Card No.: " _GREEN_("%u") ", Issue Level: " _GREEN_("%u"),
creds.region_code, creds.facility_code, creds.card_number, creds.issue_level);
}
return PM3_SUCCESS;
}
2021-12-29 18:42:06 +08:00
static int CmdGallagherReader(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf gallagher reader",
"read a GALLAGHER tag",
2022-01-01 17:22:57 +08:00
"hf gallagher reader --aid 2081f4 --sitekey 00112233445566778899aabbccddeeff"
" -> act as a reader that doesn't skips the Card Application Directory and uses a non-default site key\n"
"hf gallagher reader -@ -> continuous reader mode"
2021-12-29 18:42:06 +08:00
);
void *argtable[] = {
arg_param_begin,
2022-01-01 17:22:57 +08:00
arg_str0(NULL, "aid", "<hex>", "Application ID to read (3 bytes)"),
arg_str1("k", "sitekey", "<hex>", "Master site key to compute diversified keys (16 bytes)"),
arg_lit0(NULL, "apdu", "show APDU requests and responses"),
arg_lit0("v", "verbose", "Verbose mode"),
arg_lit0("@", "continuous", "Continuous reader mode"),
2021-12-29 18:42:06 +08:00
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
2022-01-01 17:22:57 +08:00
int aidLen = 0;
2022-01-04 11:59:59 +08:00
uint8_t aidBuf[3] = {0};
CLIGetHexWithReturn(ctx, 1, aidBuf, &aidLen);
if (aidLen > 0 && aidLen != 3)
HFGAL_RET_ERR(PM3_EINVARG, "--aid must be 3 bytes");
reverseAid(aidBuf); // PM3 displays AIDs backwards
uint32_t aid = DesfireAIDByteToUint(aidBuf);
2022-01-01 17:22:57 +08:00
int sitekeyLen = 0;
uint8_t sitekey[16] = {0};
CLIGetHexWithReturn(ctx, 2, sitekey, &sitekeyLen);
2022-01-04 11:59:59 +08:00
if (sitekeyLen > 0 && sitekeyLen != 16)
HFGAL_RET_ERR(PM3_EINVARG, "--sitekey must be 16 bytes");
2022-01-01 17:22:57 +08:00
SetAPDULogging(arg_get_lit(ctx, 3));
bool verbose = arg_get_lit(ctx, 4);
bool continuousMode = arg_get_lit(ctx, 5);
2021-12-29 18:42:06 +08:00
CLIParserFree(ctx);
2022-01-04 11:59:59 +08:00
if (!continuousMode) {
// Read single card
return readCard(aid, sitekey, verbose, false);
}
2022-01-01 17:22:57 +08:00
2022-01-04 11:59:59 +08:00
// Loop until <Enter> is pressed
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to exit");
while (!kbd_enter_pressed()) {
readCard(aid, sitekey, verbose, !verbose);
}
return PM3_SUCCESS;
2021-12-29 18:42:06 +08:00
}
2022-01-03 12:02:10 +08:00
int GallagherDiversifyKey(uint8_t *sitekey, uint8_t *uid, uint8_t uidLen, uint8_t keyNo, uint32_t aid, uint8_t *keyOut) {
// Generate diversification input
uint8_t kdfInputLen = 11;
int res = mfdes_kdf_input_gallagher(uid, uidLen, keyNo, aid, keyOut, &kdfInputLen);
2022-01-04 11:59:59 +08:00
HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed generating Gallagher key diversification input.");
2022-01-03 12:02:10 +08:00
// Make temporary DesfireContext
DesfireContext_t dctx = {0};
DesfireSetKey(&dctx, 0, T_AES, sitekey);
// Diversify input & copy to output buffer
MifareKdfAn10922(&dctx, DCOMasterKey, keyOut, kdfInputLen);
memcpy(keyOut, dctx.key, CRYPTO_AES128_KEY_SIZE);
return PM3_SUCCESS;
}
2022-01-04 11:59:59 +08:00
/**
* @brief Create a new application to store Gallagher cardholder credentials.
*
* @param sitekey MIFARE site key.
* @param aid New application ID. Should be 0x2?81F4, where 0 <= ? <= 0xB.
*/
static int createGallagherCredentialsApplication(DesfireContext_t *ctx, uint8_t *sitekey, uint32_t aid, bool verbose) {
2022-01-03 12:02:10 +08:00
DesfireSetCommMode(ctx, DCMPlain);
DesfireSetCommandSet(ctx, DCCNativeISO);
int res = DesfireSelectAndAuthenticateAppW(ctx, DACEV1, ISW6bAID, 0x000000, false, verbose);
2022-01-04 11:59:59 +08:00
HFGAL_RET_IF_ERR(res);
// UID is required for key diversification
if (ctx->uidlen == 0)
HFGAL_RET_ERR(PM3_EINVARG, "UID is required for key diversification. Please fetch it before calling `createGallagherCredentialsApplication`.");
2022-01-03 12:02:10 +08:00
// Create application
DesfireCryptoAlgorithm dstalgo = T_AES;
uint8_t keycount = 3;
uint8_t ks1 = 0x0B;
uint8_t ks2 = (DesfireKeyAlgoToType(dstalgo) << 6) | keycount;;
uint8_t data[5] = {0};
2022-01-04 11:59:59 +08:00
DesfireAIDUintToByte(aid, &data[0]);
2022-01-03 12:02:10 +08:00
data[3] = ks1;
data[4] = ks2;
DesfireSetCommMode(ctx, DCMMACed);
res = DesfireCreateApplication(ctx, data, ARRAYLEN(data));
2022-01-04 11:59:59 +08:00
HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed creating application %06X. Does it already exist?", aid);
if (verbose)
PrintAndLogEx(INFO, "Created application %06X (current has empty contents & blank keys)", aid);
2022-01-03 12:02:10 +08:00
// Select the new application
DesfireSetCommMode(ctx, DCMPlain);
2022-01-04 11:59:59 +08:00
res = DesfireSelectEx(ctx, true, ISW6bAID, aid, NULL);
HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed selecting application %06X", aid);
2022-01-03 12:02:10 +08:00
// Add key 2, then key 0 (we must authenticate with key 0 in order to make changes)
for (int i = 2; i >= 0; i -= 2) {
// Diversify key
uint8_t buf[CRYPTO_AES128_KEY_SIZE] = {0};
2022-01-04 11:59:59 +08:00
res = GallagherDiversifyKey(sitekey, ctx->uid, ctx->uidlen, i, aid, buf);
HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed diversifying key %d for AID %06X", i, aid);
2022-01-03 12:02:10 +08:00
2022-01-04 11:59:59 +08:00
PrintAndLogEx(INFO, "Diversified key %d for AID %06X: " _GREEN_("%s"), i, aid, sprint_hex_inrow(buf, ARRAYLEN(buf)));
2022-01-03 12:02:10 +08:00
// Authenticate
2022-01-04 11:59:59 +08:00
uint8_t blankKey[CRYPTO_AES128_KEY_SIZE] = {0};
2022-01-03 12:02:10 +08:00
DesfireSetKeyNoClear(ctx, 0, T_AES, blankKey);
DesfireSetCommMode(ctx, DCMPlain);
res = DesfireAuthenticate(ctx, DACEV1, verbose);
2022-01-04 11:59:59 +08:00
HFGAL_RET_IF_ERR_WITH_MSG(res, "Desfire authenticate error. Result: [%d] %s", res, DesfireAuthErrorToStr(res));
2022-01-03 12:02:10 +08:00
// Change key
DesfireSetCommMode(ctx, DCMEncryptedPlain);
2022-01-04 11:59:59 +08:00
res = DesfireChangeKey(ctx, false, i, dstalgo, 1, buf, dstalgo, blankKey, verbose);
HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed setting key %d for AID %06X", i, aid);
2022-01-03 12:02:10 +08:00
if (verbose)
2022-01-04 11:59:59 +08:00
PrintAndLogEx(INFO, "Successfully set key %d for AID %06X", i, aid);
2022-01-03 12:02:10 +08:00
}
2022-01-04 11:59:59 +08:00
PrintAndLogEx(INFO, "Successfully created credentials application %06X", aid);
2022-01-03 12:02:10 +08:00
return PM3_SUCCESS;
}
2022-01-04 11:59:59 +08:00
/**
* @brief Create a new file containing Gallagher cardholder credentials.
*
* @param sitekey MIFARE site key.
* @param aid Application ID to put the new file in.
* @param creds Gallagher cardholder credentials.
*/
static int createGallagherCredentialsFile(DesfireContext_t *ctx, uint8_t *sitekey, uint32_t aid, GallagherCredentials_t *creds, bool verbose) {
2022-01-03 12:02:10 +08:00
// Set up context
DesfireSetKeyNoClear(ctx, 0, T_AES, sitekey);
DesfireSetKdf(ctx, MFDES_KDF_ALGO_GALLAGHER, NULL, 0);
DesfireSetCommMode(ctx, DCMPlain);
// Select application
2022-01-04 11:59:59 +08:00
int res = DesfireSelectAndAuthenticateAppW(ctx, DACEV1, ISW6bAID, aid, false, verbose);
HFGAL_RET_IF_ERR(res);
2022-01-03 12:02:10 +08:00
// Prepare create file command
uint8_t fileType = 0; // standard data file
uint8_t fileId = 0x00;
uint8_t fileSize = 16;
uint8_t fileAccessMode = 0x03; // encrypted
uint32_t fileRights = 0x2000; // key 0 has God mode, key 2 can read
uint8_t data[7] = {0};
data[0] = fileId;
data[1] = fileAccessMode;
data[2] = fileRights & 0xff;
data[3] = (fileRights >> 8) & 0xff;
Uint3byteToMemLe(&data[4], fileSize);
// Create file
res = DesfireCreateFile(ctx, fileType, data, ARRAYLEN(data), false);
2022-01-04 11:59:59 +08:00
HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed creating file 0 in AID %06X", aid);
if (verbose)
PrintAndLogEx(INFO, "Created file 0 in AID %06X (current has empty contents)", aid);
2022-01-03 12:02:10 +08:00
// Create file contents (2nd half is the bitwise inverse of the encoded creds)
uint8_t contents[16] = {0};
encodeCardholderCredentials(contents, creds);
for (int i = 0; i < 8; i++)
contents[i + 8] = contents[i] ^ 0xFF;
// Write file
DesfireSetCommMode(ctx, DCMEncrypted);
res = DesfireWriteFile(ctx, fileId, 0, ARRAYLEN(contents), contents);
2022-01-04 11:59:59 +08:00
HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed writing data to file 0 in AID %06X");
PrintAndLogEx(INFO, "Successfully wrote cardholder credentials to file 0 in AID %06X", aid);
2022-01-03 12:02:10 +08:00
return PM3_SUCCESS;
}
2022-01-04 11:59:59 +08:00
/**
* @brief Create the Gallagher Card Application Directory.
*
* @param sitekey MIFARE site key.
*/
2022-01-03 12:02:10 +08:00
static int createGallagherCAD(DesfireContext_t *ctx, uint8_t *sitekey, bool verbose) {
2022-01-04 11:59:59 +08:00
// Check that card UID has been set
if (ctx->uidlen == 0)
HFGAL_RET_ERR(PM3_EINVARG, "Card UID must be set in DesfireContext (required for key diversification)");
2022-01-03 12:02:10 +08:00
DesfireClearSession(ctx);
DesfireSetCommMode(ctx, DCMPlain);
DesfireSetCommandSet(ctx, DCCNativeISO);
2022-01-04 11:59:59 +08:00
2022-01-03 12:02:10 +08:00
int res = DesfireSelectAndAuthenticateAppW(ctx, DACEV1, ISW6bAID, 0x000000, false, verbose);
2022-01-04 11:59:59 +08:00
HFGAL_RET_IF_ERR(res);
2022-01-03 12:02:10 +08:00
// Create application
DesfireCryptoAlgorithm dstalgo = T_AES;
uint8_t keycount = 1;
uint8_t ks1 = 0x0B;
uint8_t ks2 = (DesfireKeyAlgoToType(dstalgo) << 6) | keycount;;
uint8_t data[5] = {0};
2022-01-04 12:03:22 +08:00
DesfireAIDUintToByte(CAD_AID, &data[0]);
2022-01-03 12:02:10 +08:00
data[3] = ks1;
data[4] = ks2;
DesfireSetCommMode(ctx, DCMMACed);
res = DesfireCreateApplication(ctx, data, ARRAYLEN(data));
2022-01-04 12:03:22 +08:00
HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed creating Card Application Directory. Does it already exist?", CAD_AID);
2022-01-03 12:02:10 +08:00
if (verbose)
2022-01-04 12:03:22 +08:00
PrintAndLogEx(INFO, "Created Card Application Directory (AID %06X, current has empty contents & blank keys)", CAD_AID);
2022-01-03 12:02:10 +08:00
2022-01-04 11:59:59 +08:00
// Select & authenticate
2022-01-03 12:02:10 +08:00
uint8_t blankKey[DESFIRE_MAX_KEY_SIZE] = {0};
DesfireSetKeyNoClear(ctx, 0, T_AES, blankKey);
DesfireSetCommMode(ctx, DCMPlain);
2022-01-04 11:59:59 +08:00
DesfireSetCommandSet(ctx, DCCNativeISO);
2022-01-04 12:03:22 +08:00
res = DesfireSelectAndAuthenticateAppW(ctx, DACEV1, ISW6bAID, CAD_AID, false, verbose);
2022-01-04 11:59:59 +08:00
HFGAL_RET_IF_ERR(res);
// Diversify key
uint8_t buf[CRYPTO_AES128_KEY_SIZE] = {0};
2022-01-04 12:03:22 +08:00
res = GallagherDiversifyKey(sitekey, ctx->uid, ctx->uidlen, 0, CAD_AID, buf);
HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed diversifying key 0 for AID %06X", CAD_AID);
2022-01-04 11:59:59 +08:00
2022-01-04 12:03:22 +08:00
PrintAndLogEx(INFO, "Diversified key 0 for CAD (AID %06X): " _GREEN_("%s"), CAD_AID, sprint_hex_inrow(buf, ARRAYLEN(buf)));
2022-01-03 12:02:10 +08:00
// Change key
DesfireSetCommMode(ctx, DCMEncryptedPlain);
2022-01-04 11:59:59 +08:00
res = DesfireChangeKey(ctx, false, 0, dstalgo, 1, buf, dstalgo, blankKey, verbose);
HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed setting key 0 for CAD");
2022-01-03 12:02:10 +08:00
if (verbose)
2022-01-04 11:59:59 +08:00
PrintAndLogEx(INFO, "Successfully set key 0 for CAD");
2022-01-03 12:02:10 +08:00
2022-01-04 12:03:22 +08:00
PrintAndLogEx(INFO, "Successfully created Card Application Directory (AID %06X)", CAD_AID);
2022-01-03 12:02:10 +08:00
return PM3_SUCCESS;
}
2022-01-04 11:59:59 +08:00
/**
* @brief Update the Gallagher Card Application Directory with a new entry.
*
* @param sitekey MIFARE site key.
* @param aid Application ID to add to the CAD.
* @param creds Gallagher cardholder credentials (region_code & facility_code are required).
*/
static int updateGallagherCAD(DesfireContext_t *ctx, uint8_t *sitekey, uint32_t aid, GallagherCredentials_t *creds, bool verbose) {
2022-01-03 12:02:10 +08:00
// Check if CAD exists
uint8_t cad[36 * 3] = {0};
uint8_t numEntries = 0;
2022-01-04 12:03:22 +08:00
int res = DesfireSelectEx(ctx, true, ISW6bAID, CAD_AID, NULL);
2022-01-03 12:02:10 +08:00
if (res == PM3_SUCCESS) {
2022-01-04 11:59:59 +08:00
if (verbose)
PrintAndLogEx(INFO, "Card Application Directory exists, reading entries...");
2022-01-03 12:02:10 +08:00
res = readCardApplicationDirectory(ctx, cad, ARRAYLEN(cad), &numEntries, verbose);
2022-01-04 11:59:59 +08:00
HFGAL_RET_IF_ERR(res);
2022-01-03 12:02:10 +08:00
// Check that there is space for the new entry
2022-01-04 11:59:59 +08:00
if (numEntries >= 18)
HFGAL_RET_ERR(PM3_EFATAL, "Card application directory is full.");
} else {
if (verbose)
PrintAndLogEx(INFO, "Card Application Directory does not exist, creating it now...");
res = createGallagherCAD(ctx, sitekey, verbose);
HFGAL_RET_IF_ERR(res);
2022-01-03 12:02:10 +08:00
}
uint8_t fileId = numEntries / 6; // 6 entries per file
uint8_t entryNum = numEntries % 6;
// Create entry
uint8_t *entry = &cad[numEntries * 6];
entry[0] = creds->region_code;
entry[1] = (creds->facility_code >> 8) & 0xFF;
entry[2] = creds->facility_code & 0xFF;
2022-01-04 11:59:59 +08:00
DesfireAIDUintToByte(aid, &entry[3]);
2022-01-03 12:02:10 +08:00
reverseAid(&entry[3]); // CAD stores AIDs backwards
2022-01-04 11:59:59 +08:00
if (verbose)
PrintAndLogEx(INFO, "Adding entry to CAD (position %d in file %d): %s", entryNum, fileId, sprint_hex_inrow(entry, 6));
2022-01-03 12:02:10 +08:00
// Set up context
DesfireSetKeyNoClear(ctx, 0, T_AES, sitekey);
DesfireSetKdf(ctx, MFDES_KDF_ALGO_GALLAGHER, NULL, 0);
DesfireSetCommMode(ctx, DCMPlain);
2022-01-04 11:59:59 +08:00
DesfireSetCommandSet(ctx, DCCNativeISO);
2022-01-03 12:02:10 +08:00
// Select application
2022-01-04 12:03:22 +08:00
res = DesfireSelectAndAuthenticateAppW(ctx, DACEV1, ISW6bAID, CAD_AID, false, verbose);
2022-01-04 11:59:59 +08:00
HFGAL_RET_IF_ERR(res);
2022-01-03 12:02:10 +08:00
// Create file if necessary
if (entryNum == 0) {
2022-01-04 11:59:59 +08:00
if (verbose)
PrintAndLogEx(INFO, "Creating new file in CAD");
2022-01-03 12:02:10 +08:00
// Prepare create file command
uint8_t fileType = 0; // standard data file
uint8_t fileSize = 36;
uint8_t fileAccessMode = 0x00; // plain
uint32_t fileRights = 0xE000; // key 0 has God mode, everyone can read
uint8_t data[7] = {0};
data[0] = fileId;
data[1] = fileAccessMode;
data[2] = fileRights & 0xff;
data[3] = (fileRights >> 8) & 0xff;
Uint3byteToMemLe(&data[4], fileSize);
// Create file
res = DesfireCreateFile(ctx, fileType, data, ARRAYLEN(data), false);
2022-01-04 12:03:22 +08:00
HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed creating file %d in CAD (AID %06X)", fileId, CAD_AID);
2022-01-04 11:59:59 +08:00
if (verbose)
PrintAndLogEx(INFO, "Created file %d in CAD (current has empty contents)", fileId);
2022-01-03 12:02:10 +08:00
// Write file
res = DesfireWriteFile(ctx, fileId, fileId * 36, 36, entry);
} else {
// Write file
res = DesfireWriteFile(ctx, fileId, entryNum * 6, 6, entry);
}
2022-01-04 12:03:22 +08:00
HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed writing data to file %d in CAD (AID %06X)", fileId, CAD_AID);
2022-01-04 11:59:59 +08:00
PrintAndLogEx(INFO, "Successfully added new entry for %06X to the Card Application Directory", aid);
2022-01-03 12:02:10 +08:00
return PM3_SUCCESS;
}
2021-12-29 18:42:06 +08:00
static int CmdGallagherClone(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf gallagher clone",
"clone a GALLAGHER card to a blank DESFire card.",
2022-01-03 12:02:10 +08:00
"hf gallagher clone --rc 1 --fc 22 --cn 3333 --il 4 --sitekey 00112233445566778899aabbccddeeff"
2021-12-29 18:42:06 +08:00
);
void *argtable[] = {
arg_param_begin,
2022-01-03 12:02:10 +08:00
arg_lit0(NULL, "apdu", "show APDU requests and responses"),
arg_lit0("v", "verbose", "Verbose mode"),
arg_int0("n", "keyno", "<decimal>", "Key number [default=0]"),
arg_str0("t", "algo", "<DES/2TDEA/3TDEA/AES>", "Crypt algo: DES, 2TDEA, 3TDEA, AES"),
arg_str0("k", "key", "<hex>", "Key for authenticate (HEX 8(DES), 16(2TDEA or AES) or 24(3TDEA) bytes)"),
arg_u64_1(NULL, "rc", "<decimal>", "Region code. 4 bits max"),
arg_u64_1(NULL, "fc", "<decimal>", "Facility code. 2 bytes max"),
arg_u64_1(NULL, "cn", "<decimal>", "Card number. 3 bytes max"),
arg_u64_1(NULL, "il", "<decimal>", "Issue level. 4 bits max"),
arg_str0(NULL, "aid", "<hex>", "Application ID to write (3 bytes) [default=2081F4]"),
arg_str1(NULL, "sitekey", "<hex>", "Master site key to compute diversified keys (16 bytes)"),
2021-12-29 18:42:06 +08:00
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
2022-01-03 12:02:10 +08:00
SetAPDULogging(arg_get_lit(ctx, 1));
bool verbose = arg_get_lit(ctx, 2) || true;
int keyNum = arg_get_int_def(ctx, 3, 0);
int algo = T_DES;
if (CLIGetOptionList(arg_get_str(ctx, 4), DesfireAlgoOpts, &algo)) return PM3_ESOFT;
int keyLen = 0;
uint8_t key[DESFIRE_MAX_KEY_SIZE] = {0};
CLIGetHexWithReturn(ctx, 5, key, &keyLen);
if (keyLen && keyLen != desfire_get_key_length(algo)) {
2022-01-04 11:59:59 +08:00
HFGAL_RET_ERR(PM3_EINVARG, "%s key must have %d bytes length instead of %d", CLIGetOptionListStr(DesfireAlgoOpts, algo), desfire_get_key_length(algo), keyLen);
2022-01-03 12:02:10 +08:00
}
if (keyLen == 0) {
// Default to a key of all zeros
keyLen = desfire_get_key_length(algo);
}
uint64_t region_code = arg_get_u64(ctx, 6); // uint16, will be validated later
uint64_t facility_code = arg_get_u64(ctx, 7); // uint32, will be validated later
uint64_t card_number = arg_get_u64(ctx, 8); // uint64
uint64_t issue_level = arg_get_u64(ctx, 9); // uint32, will be validated later
int aidLen = 0;
2022-01-04 11:59:59 +08:00
uint8_t aidBuf[3] = "\x20\x81\xF4";
CLIGetHexWithReturn(ctx, 10, aidBuf, &aidLen);
2022-01-03 12:02:10 +08:00
if (aidLen > 0 && aidLen != 3) {
2022-01-04 11:59:59 +08:00
HFGAL_RET_ERR(PM3_EINVARG, "--aid must be 3 bytes");
2022-01-03 12:02:10 +08:00
}
2022-01-04 11:59:59 +08:00
reverseAid(aidBuf); // PM3 displays AIDs backwards
uint32_t aid = DesfireAIDByteToUint(aidBuf);
2022-01-03 12:02:10 +08:00
// Check that the AID is in the expected range
2022-01-04 11:59:59 +08:00
if (memcmp(aidBuf, "\xF4\x81", 2) != 0 || aidBuf[2] < 0x20 || aidBuf[2] > 0x2B)
// TODO: this should probably be a warning, but key diversification will throw an error later even if we don't
HFGAL_RET_ERR(PM3_EINVARG, "Invalid Gallagher AID %06X, expected 2?81F4, where 0 <= ? <= 0xB", aid);
2022-01-03 12:02:10 +08:00
int sitekeyLen = 0;
uint8_t sitekey[16] = {0};
CLIGetHexWithReturn(ctx, 11, sitekey, &sitekeyLen);
2022-01-04 11:59:59 +08:00
if (sitekeyLen > 0 && sitekeyLen != 16)
HFGAL_RET_ERR(PM3_EINVARG, "--sitekey must be 16 bytes");
2021-12-29 18:42:06 +08:00
CLIParserFree(ctx);
2022-01-04 11:59:59 +08:00
if (!isValidGallagherCredentials(region_code, facility_code, card_number, issue_level))
2021-12-29 18:42:06 +08:00
return PM3_EINVARG;
2022-01-04 11:59:59 +08:00
2022-01-03 12:02:10 +08:00
GallagherCredentials_t creds = {
.region_code = region_code,
.facility_code = facility_code,
.card_number = card_number,
.issue_level = issue_level,
};
2021-12-29 18:42:06 +08:00
2022-01-03 12:02:10 +08:00
// Set up context
DropField();
DesfireContext_t dctx = {0};
DesfireClearContext(&dctx);
2021-12-29 18:42:06 +08:00
2022-01-03 12:02:10 +08:00
// Get card UID (for key diversification)
int res = DesfireGetCardUID(&dctx);
2022-01-04 11:59:59 +08:00
HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed retrieving card UID");
2022-01-03 12:02:10 +08:00
// Create application
2022-01-04 11:59:59 +08:00
DesfireSetKeyNoClear(&dctx, keyNum, algo, key);
DesfireSetKdf(&dctx, MFDES_KDF_ALGO_NONE, NULL, 0);
res = createGallagherCredentialsApplication(&dctx, sitekey, aid, verbose);
HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed creating Gallagher application");
2022-01-03 12:02:10 +08:00
// Create credential files
2022-01-04 11:59:59 +08:00
// Don't need to set keys here, they're generated automatically
res = createGallagherCredentialsFile(&dctx, sitekey, aid, &creds, verbose);
HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed creating Gallagher credential file.");
2022-01-03 12:02:10 +08:00
// Update card application directory
2022-01-04 11:59:59 +08:00
DesfireSetKeyNoClear(&dctx, keyNum, algo, key);
DesfireSetKdf(&dctx, MFDES_KDF_ALGO_NONE, NULL, 0);
res = updateGallagherCAD(&dctx, sitekey, aid, &creds, verbose);
HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed updating Gallagher card application directory.");
2021-12-29 18:42:06 +08:00
PrintAndLogEx(SUCCESS, "Done");
PrintAndLogEx(HINT, "Hint: try " _YELLOW_("`hf gallagher reader`") " to verify");
2022-01-04 11:59:59 +08:00
return PM3_SUCCESS;
2021-12-29 18:42:06 +08:00
}
static int CmdGallagherSim(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf gallagher sim",
"Enables simulation of GALLAGHER card with specified card number.\n"
"Simulation runs until the button is pressed or another USB command is issued.\n",
"hf gallagher sim --rc 1 --fc 22 --cn 3333 --il 4"
);
void *argtable[] = {
arg_param_begin,
arg_u64_1(NULL, "rc", "<decimal>", "Region code. 4 bits max"),
arg_u64_1(NULL, "fc", "<decimal>", "Facility code. 2 bytes max"),
arg_u64_1(NULL, "cn", "<decimal>", "Card number. 3 bytes max"),
arg_u64_1(NULL, "il", "<decimal>", "Issue level. 4 bits max"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
uint64_t region_code = arg_get_u64(ctx, 1); // uint16, will be validated later
uint64_t facility_code = arg_get_u64(ctx, 2); // uint32, will be validated later
uint64_t card_number = arg_get_u64(ctx, 3); // uint64
uint64_t issue_level = arg_get_u64(ctx, 4); // uint32, will be validated later
CLIParserFree(ctx);
2022-01-04 11:59:59 +08:00
if (!isValidGallagherCredentials(region_code, facility_code, card_number, issue_level))
2021-12-29 18:42:06 +08:00
return PM3_EINVARG;
// TODO: create data
// TODO: simulate
return PM3_ENOTIMPL;
}
static command_t CommandTable[] = {
{"help", CmdHelp, AlwaysAvailable, "This help"},
{"reader", CmdGallagherReader, IfPm3Iso14443, "attempt to read and extract tag data"},
{"clone", CmdGallagherClone, IfPm3Iso14443, "clone GALLAGHER tag to a blank DESFire card"},
{"sim", CmdGallagherSim, IfPm3Iso14443, "simulate GALLAGHER tag"},
{NULL, NULL, NULL, NULL}
};
static int CmdHelp(const char *Cmd) {
(void) Cmd; // Cmd is not used so far
CmdsHelp(CommandTable);
return PM3_SUCCESS;
}
int CmdHFGallagher(const char *Cmd) {
clearCommandBuffer();
return CmdsParse(CommandTable, Cmd);
}