diff --git a/client/src/cmdhfgallagher.c b/client/src/cmdhfgallagher.c index 90fff183c..d80bea83a 100644 --- a/client/src/cmdhfgallagher.c +++ b/client/src/cmdhfgallagher.c @@ -11,39 +11,213 @@ */ #include "cmdhfgallagher.h" +#include "mifare.h" +#include "mifare/desfirecore.h" #include "mifare/gallaghercore.h" #include +#include #include "common.h" +#include "commonutil.h" #include "cmdparser.h" #include "cliparser.h" #include "ui.h" +static void reverseAid(uint8_t *aid) { + uint8_t tmp = aid[0]; + aid[0] = aid[2]; + aid[2] = tmp; +}; + static int CmdHelp(const char *Cmd); +static int readCardApplicationDirectory(DesfireContext_t *ctx, uint8_t *destBuf, uint8_t destBufLen, uint8_t *numEntries, bool verbose) { + if (destBufLen != 3 * 36) { + PrintAndLogEx(ERR, "readCardApplicationDirectory destination buffer is incorrectly sized. " + "Received length %d, expected %d", destBufLen, 3 * 36); + return PM3_EINVARG; + } + + DesfireSetCommMode(ctx, DCMPlain); + + // Get card AIDs from Card Application Directory (which contains 1 to 3 files) + uint8_t cadAid[] = { 0xF4, 0x81, 0x2F }; + int res = DesfireSelectAID(ctx, cadAid, NULL); + HF_GALLAGHER_RETURN_IF_ERROR(res); + + // Read up to 3 files with 6x 6-byte entries each + for (uint8_t i = 0; i < 3; i++) { + size_t readLen; + DesfireReadFile(ctx, i, 0, 36, &destBuf[i * 36], &readLen); + } + + // Keep only valid entries + *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++) + PrintAndLogEx(NORMAL, "%s %06x" NOLF, (i == 0) ? "" : ",", DesfireAIDByteToUint(&destBuf[i*6 + 3])); + PrintAndLogEx(NORMAL, ""); + } + + return PM3_SUCCESS; +} + +static int readCardApplicationCredentials(DesfireContext_t *ctx, uint8_t *aid, uint8_t *sitekey, GallagherCredentials_t *creds, bool verbose) { + // Check that card UID has been set + if (ctx->uidlen == 0) { + PrintAndLogEx(ERR, "Card UID must be set in DesfireContext (required for key diversification)"); + return PM3_EINVARG; + } + + // Set up context + DesfireSetKeyNoClear(ctx, 2, T_AES, sitekey); + DesfireSetKdf(ctx, MFDES_KDF_ALGO_GALLAGHER, NULL, 0); + DesfireSetCommMode(ctx, DCMPlain); + DesfireSetCommandSet(ctx, DCCNativeISO); + + // Select and authenticate to AID + int res = DesfireSelectAndAuthenticateAppW(ctx, DACEV1, ISW6bAID, le24toh(aid), false, verbose); + HF_GALLAGHER_RETURN_IF_ERROR(res); + + // Read file 0 (contains credentials) + uint8_t buf[16] = { 0x00 }; + size_t readLen = 0; + DesfireSetCommMode(ctx, DCMEncrypted); + res = DesfireReadFile(ctx, 0, 0, 16, buf, &readLen); + HF_GALLAGHER_RETURN_IF_ERROR(res); + + // Check file contained 16 bytes of data + if (readLen != 16) { + PrintAndLogEx(ERR, "Failed reading file 0 in %06x", DesfireAIDByteToUint(aid)); + return PM3_EFAILED; + } + + // 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) { + PrintAndLogEx(ERR, "Invalid cardholder data in file 0. Received %s", sprint_hex_inrow(buf, 16)); + return PM3_EFAILED; + } + + decodeCardholderCredentials(buf, creds); + + // TODO: read MIFARE Enhanced Security file + // https://github.com/megabug/gallagher-research/blob/master/formats/mes.md + + return PM3_SUCCESS; +} + +static int readCard(uint8_t *aid, uint8_t *sitekey, bool verbose, bool continuousMode) { + DropField(); + clearCommandBuffer(); + + // Set up context + DesfireContext_t dctx = {0}; + DesfireClearContext(&dctx); + + // Get card UID (for key diversification) + int res = DesfireGetCardUID(&dctx); + HF_GALLAGHER_FAIL_IF_ERROR(res, verbose || !continuousMode, "Failed retrieving card UID."); + + // Find AIDs to process (from CLI args or the Card Application Directory) + uint8_t cad[36 * 3] = {0}; + uint8_t numEntries = 0; + if (aid != NULL) { + memcpy(&cad[3], aid, 3); + reverseAid(&cad[3]); // CAD stores AIDs backwards + numEntries = 1; + } else { + res = readCardApplicationDirectory(&dctx, cad, ARRAYLEN(cad), &numEntries, verbose); + HF_GALLAGHER_FAIL_IF_ERROR(res, verbose || !continuousMode, "Failed reading card application directory."); + } + + // Loop through each application in the CAD + for (uint8_t i = 0; i < numEntries * 6; i += 6) { + uint16_t region_code = cad[i + 0]; + uint16_t facility_code = (cad[i + 1] << 8) + cad[i + 2]; + + // Copy AID out of CAD record + uint8_t currentAid[3]; + memcpy(currentAid, &cad[3], 3); + reverseAid(currentAid); // CAD stores AIDs backwards + + if (verbose) { + if (region_code > 0 || facility_code > 0) + PrintAndLogEx(INFO, "Reading AID: %06x, region: %u, facility: %u", DesfireAIDByteToUint(currentAid), region_code, facility_code); + else + PrintAndLogEx(INFO, "Reading AID: %06x", DesfireAIDByteToUint(currentAid)); + } + + // Read & decode credentials + GallagherCredentials_t creds = {0}; + res = readCardApplicationCredentials(&dctx, currentAid, sitekey, &creds, verbose); + HF_GALLAGHER_FAIL_IF_ERROR(res, verbose || !continuousMode, "Failed reading card application credentials."); + + 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; +} + static int CmdGallagherReader(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf gallagher reader", "read a GALLAGHER tag", - "hf gallagher reader -@ -> continuous reader mode" + "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" ); void *argtable[] = { arg_param_begin, - arg_lit0("@", NULL, "optional - continuous reader mode"), + arg_str0(NULL, "aid", "", "Application ID to read (3 bytes)"), + arg_str1("k", "sitekey", "", "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"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); - bool cm = arg_get_lit(ctx, 1); - CLIParserFree(ctx); - if (cm) { - PrintAndLogEx(INFO, "Press " _GREEN_("") " to exit"); + int aidLen = 0; + uint8_t aid[3] = {0}; + CLIGetHexWithReturn(ctx, 1, aid, &aidLen); + if (aidLen > 0 && aidLen != 3) { + PrintAndLogEx(ERR, "--aid must be 3 bytes"); + return PM3_EINVARG; + } + reverseAid(aid); // PM3 displays AIDs backwards + + int sitekeyLen = 0; + uint8_t sitekey[16] = {0}; + CLIGetHexWithReturn(ctx, 2, sitekey, &sitekeyLen); + if (sitekeyLen > 0 && sitekeyLen != 16) { + PrintAndLogEx(ERR, "--sitekey must be 16 bytes"); + return PM3_EINVARG; } + SetAPDULogging(arg_get_lit(ctx, 3)); + bool verbose = arg_get_lit(ctx, 4); + bool continuousMode = arg_get_lit(ctx, 5); + CLIParserFree(ctx); + + if (continuousMode) + PrintAndLogEx(INFO, "Press " _GREEN_("") " to exit"); + + int res; do { - // read - } while (cm && !kbd_enter_pressed()); - return PM3_SUCCESS; + res = readCard(aidLen > 0 ? aid : NULL, sitekey, verbose, continuousMode); + } while (continuousMode && !kbd_enter_pressed()); + + return continuousMode ? PM3_SUCCESS : res; } static int CmdGallagherClone(const char *Cmd) { diff --git a/client/src/cmdhfgallagher.h b/client/src/cmdhfgallagher.h index 26faa11b0..ba1073fe2 100644 --- a/client/src/cmdhfgallagher.h +++ b/client/src/cmdhfgallagher.h @@ -16,5 +16,7 @@ int CmdHFGallagher(const char *Cmd); -#endif +#define HF_GALLAGHER_RETURN_IF_ERROR(res) if (res != PM3_SUCCESS) { return res; } +#define HF_GALLAGHER_FAIL_IF_ERROR(res, verbose, reason) if (res != PM3_SUCCESS) { if (verbose) PrintAndLogEx(ERR, reason " Error code %d", res); DropField(); return res; } +#endif