diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 464300497..574303188 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -234,6 +234,7 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/mifare/desfiresecurechan.c ${PM3_ROOT}/client/src/mifare/desfirecore.c ${PM3_ROOT}/client/src/mifare/desfiretest.c + ${PM3_ROOT}/client/src/mifare/gallaghercore.c ${PM3_ROOT}/client/src/uart/uart_posix.c ${PM3_ROOT}/client/src/uart/uart_win32.c ${PM3_ROOT}/client/src/ui/overlays.ui @@ -254,6 +255,7 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/cmdhfepa.c ${PM3_ROOT}/client/src/cmdhffelica.c ${PM3_ROOT}/client/src/cmdhffido.c + ${PM3_ROOT}/client/src/cmdhfgallagher.c ${PM3_ROOT}/client/src/cmdhfcipurse.c ${PM3_ROOT}/client/src/cmdhficlass.c ${PM3_ROOT}/client/src/cmdhfjooki.c diff --git a/client/Makefile b/client/Makefile index 97e09be8b..2bbc72af6 100644 --- a/client/Makefile +++ b/client/Makefile @@ -506,6 +506,7 @@ SRCS = mifare/aiddesfire.c \ cmdhfemrtd.c \ cmdhffelica.c \ cmdhffido.c \ + cmdhfgallagher.c \ cmdhfksx6924.c \ cmdhfcipurse.c \ cmdhficlass.c \ @@ -612,6 +613,7 @@ SRCS = mifare/aiddesfire.c \ mifare/desfirecore.c \ mifare/desfiresecurechan.c \ mifare/desfiretest.c \ + mifare/gallaghercore.c \ mifare/mad.c \ mifare/mfkey.c \ mifare/mifare4.c \ diff --git a/client/android/CMakeLists.txt b/client/android/CMakeLists.txt index 51b36807e..9bc0f59cd 100644 --- a/client/android/CMakeLists.txt +++ b/client/android/CMakeLists.txt @@ -99,6 +99,7 @@ add_library(pm3rrg_rdv4 SHARED ${PM3_ROOT}/client/src/mifare/desfiresecurechan.c ${PM3_ROOT}/client/src/mifare/desfirecore.c ${PM3_ROOT}/client/src/mifare/desfiretest.c + ${PM3_ROOT}/client/src/mifare/gallaghercore.c ${PM3_ROOT}/client/src/uart/uart_posix.c ${PM3_ROOT}/client/src/uart/uart_win32.c ${PM3_ROOT}/client/src/ui/overlays.ui @@ -119,6 +120,7 @@ add_library(pm3rrg_rdv4 SHARED ${PM3_ROOT}/client/src/cmdhfepa.c ${PM3_ROOT}/client/src/cmdhffelica.c ${PM3_ROOT}/client/src/cmdhffido.c + ${PM3_ROOT}/client/src/cmdhfgallagher.c ${PM3_ROOT}/client/src/cmdhfcipurse.c ${PM3_ROOT}/client/src/cmdhficlass.c ${PM3_ROOT}/client/src/cmdhfjooki.c diff --git a/client/experimental_lib/CMakeLists.txt b/client/experimental_lib/CMakeLists.txt index 901c687fd..c4ab858f7 100644 --- a/client/experimental_lib/CMakeLists.txt +++ b/client/experimental_lib/CMakeLists.txt @@ -233,6 +233,7 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/mifare/desfiresecurechan.c ${PM3_ROOT}/client/src/mifare/desfirecore.c ${PM3_ROOT}/client/src/mifare/desfiretest.c + ${PM3_ROOT}/client/src/mifare/gallaghercore.c ${PM3_ROOT}/client/src/uart/uart_posix.c ${PM3_ROOT}/client/src/uart/uart_win32.c ${PM3_ROOT}/client/src/ui/overlays.ui @@ -253,6 +254,7 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/cmdhfepa.c ${PM3_ROOT}/client/src/cmdhffelica.c ${PM3_ROOT}/client/src/cmdhffido.c + ${PM3_ROOT}/client/src/cmdhfgallagher.c ${PM3_ROOT}/client/src/cmdhfcipurse.c ${PM3_ROOT}/client/src/cmdhficlass.c ${PM3_ROOT}/client/src/cmdhfjooki.c diff --git a/client/src/cmdhf.c b/client/src/cmdhf.c index 9b5841d71..ce87a5e62 100644 --- a/client/src/cmdhf.c +++ b/client/src/cmdhf.c @@ -33,6 +33,7 @@ #include "cmdhftopaz.h" // TOPAZ #include "cmdhffelica.h" // ISO18092 / FeliCa #include "cmdhffido.h" // FIDO authenticators +#include "cmdhfgallagher.h" // Gallagher DESFire cards #include "cmdhfksx6924.h" // KS X 6924 #include "cmdhfcipurse.h" // CIPURSE transport cards #include "cmdhfthinfilm.h" // Thinfilm @@ -414,6 +415,7 @@ static command_t CommandTable[] = { {"emrtd", CmdHFeMRTD, AlwaysAvailable, "{ Machine Readable Travel Document... }"}, {"felica", CmdHFFelica, AlwaysAvailable, "{ ISO18092 / FeliCa RFIDs... }"}, {"fido", CmdHFFido, AlwaysAvailable, "{ FIDO and FIDO2 authenticators... }"}, + {"gallagher", CmdHFGallagher, AlwaysAvailable, "{ Gallagher DESFire RFIDs... }"}, {"ksx6924", CmdHFKSX6924, AlwaysAvailable, "{ KS X 6924 (T-Money, Snapper+) RFIDs }"}, {"jooki", CmdHF_Jooki, AlwaysAvailable, "{ Jooki RFIDs... }"}, {"iclass", CmdHFiClass, AlwaysAvailable, "{ ICLASS RFIDs... }"}, diff --git a/client/src/cmdhfgallagher.c b/client/src/cmdhfgallagher.c new file mode 100644 index 000000000..801699480 --- /dev/null +++ b/client/src/cmdhfgallagher.c @@ -0,0 +1,1075 @@ +/** + * Matt Moran (@DarkMatterMatt), 2021 + * ----------------------------------------------------------------------------- + * 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. + * ----------------------------------------------------------------------------- + * High frequency GALLAGHER tag commands. + * MIFARE DESFire, AIDs 2081F4-2F81F4 + */ + +#include "cmdhfgallagher.h" +#include "generator.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" + +/** Application ID for the Gallagher Card Application Directory */ +static const uint32_t CAD_AID = 0x2F81F4; + +/** Default MIFARE Site Key */ +static const uint8_t DEFAULT_SITE_KEY[] = { + 0x31, 0x12, 0xB7, 0x38, 0xD8, 0x86, 0x2C, 0xCD, + 0x34, 0x30, 0x2E, 0xB2, 0x99, 0xAA, 0xB4, 0x56, +}; + +/** + * @brief Create Gallagher Application Master Key by diversifying + * the MIFARE Site Key with card UID, key number, and application ID. + * + * @param site_key MIFARE Site Key (16 bytes). + * @param uid Card unique ID (4 or 7 bytes). + * @param uid_len Length of UID. + * @param key_num Key number (0 <= key_num <= 2). + * @param aid Application ID (0x2?81F4 where 0 <= ? <= 0xB). + * @param key_output Buffer to copy the diversified key into (must be 16 bytes). + * @return PM3_SUCCESS if successful, PM3_EINVARG if an argument is invalid. + */ +int hfgal_diversify_key(uint8_t *site_key, uint8_t *uid, uint8_t uid_len, + uint8_t key_num, uint32_t aid, uint8_t *key_output) { + // Generate diversification input + uint8_t kdf_input_len = 11; + int res = mfdes_kdf_input_gallagher(uid, uid_len, key_num, aid, key_output, &kdf_input_len); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed generating Gallagher key diversification input"); + + if (site_key == NULL) { + PrintAndLogEx(INFO, "hfgal_diversify_key is using default site key: %s", + sprint_hex_inrow(DEFAULT_SITE_KEY, ARRAYLEN(DEFAULT_SITE_KEY))); + site_key = (uint8_t *) &DEFAULT_SITE_KEY; + } + + // Make temporary DesfireContext + DesfireContext_t dctx = {0}; + DesfireSetKey(&dctx, 0, T_AES, site_key); + + // Diversify input & copy to output buffer + MifareKdfAn10922(&dctx, DCOMasterKey, key_output, kdf_input_len); + memcpy(key_output, dctx.key, CRYPTO_AES128_KEY_SIZE); + + return PM3_SUCCESS; +} + +/** + * @brief Reverses the bytes in AID. Used when parsing CLI args + * (because Proxmark displays AIDs in reverse byte order). + */ +static void reverse_aid(uint8_t *aid) { + uint8_t tmp = aid[0]; + aid[0] = aid[2]; + aid[2] = tmp; +} + +/** + * @brief Converts a Card Application Directory format application ID to an integer. + * Note: the CAD stores AIDs in reverse order, so this is different to DesfireAIDByteToUint(). + */ +static uint32_t cad_aid_byte_to_uint(uint8_t *data) { + return data[2] + (data[1] << 8) + (data[0] << 16); +} + +/** + * @brief Converts an integer application ID to Card Application Directory format. + * Note: the CAD stores AIDs in reverse order, so this is different to DesfireAIDUintToByte(). + */ +static void cad_aid_uint_to_byte(uint32_t aid, uint8_t *data) { + data[2] = aid & 0xff; + data[1] = (aid >> 8) & 0xff; + data[0] = (aid >> 16) & 0xff; +} + +/** + * @brief Returns true if the Card Application Directory entry + * is for the specified region & facility, false otherwise. + */ +static bool cad_facility_match(uint8_t *entry, uint8_t region_code, uint16_t facility_code) { + return entry[0] == region_code && (entry[1] << 8) + entry[2] == facility_code; +} + +/** + * @brief Select application ID. + */ +static int select_aid(DesfireContext_t *ctx, uint32_t aid, bool verbose) { + // TODO: do these both need to be set? + DesfireSetCommMode(ctx, DCMPlain); + DesfireSetCommandSet(ctx, DCCNativeISO); + + int res = DesfireSelectEx(ctx, true, ISW6bAID, aid, NULL); + if (res != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Desfire AID %06X select " _RED_("error"), aid); + return 202; + } + + if (verbose) + PrintAndLogEx(INFO, "Selected AID %06X", aid); + + return PM3_SUCCESS; +} + +/** + * @brief Authenticate to application. Uses existing authentication keys in context. + */ +static int authenticate(DesfireContext_t *ctx, bool verbose) { + // TODO: do these both need to be set? + DesfireSetCommMode(ctx, DCMPlain); + DesfireSetCommandSet(ctx, DCCNativeISO); + DesfireClearSession(ctx); + + int res = DesfireAuthenticate(ctx, DACEV1, false); + if (res != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Desfire authenticate " _RED_("error") + ". Result: [%d] %s", res, DesfireAuthErrorToStr(res)); + return res; + } + + if (DesfireIsAuthenticated(ctx)) { + if (verbose) + PrintAndLogEx(INFO, "Authenticated to AID %06X", ctx->selectedAID); + } else + return 201; + + return PM3_SUCCESS; +} + +/** + * @brief Select application ID & authenticate. + * Uses existing authentication keys in context. + */ +static int select_aid_and_authenticate(DesfireContext_t *ctx, uint32_t aid, bool verbose) { + int res = select_aid(ctx, aid, verbose); + HFGAL_RET_IF_ERR(res); + + res = authenticate(ctx, verbose); + HFGAL_RET_IF_ERR(res); + + return PM3_SUCCESS; +} + +/** + * @brief Returns true if the specified application exists, false otherwise. + */ +static bool aid_exists(DesfireContext_t *ctx, uint32_t aid, bool verbose) { + // TODO: do these both need to be set? + DesfireSetCommMode(ctx, DCMPlain); + DesfireSetCommandSet(ctx, DCCNativeISO); + + int res = DesfireSelectAIDHex(ctx, aid, false, 0); + if (res != PM3_SUCCESS && res != PM3_EAPDU_FAIL) + HFGAL_RET_ERR(false, "Select failed with error %d, assuming AID %06X " + "does not exist", res, aid); + + if (verbose) + PrintAndLogEx(INFO, "AID %06X %s", aid, + res == PM3_SUCCESS ? "exists" : "does not exist"); + + return res == PM3_SUCCESS; +} + +/** + * @brief Returns the lowest available Gallagher application ID. + * @return 0 if no AID is available, or an AID in the range 0x2?81F4, where 0 <= ? <= 0xB. + */ +static uint32_t find_available_gallagher_aid(DesfireContext_t *ctx, bool verbose) { + for (uint8_t i = 0x0; i <= 0xB; i++) { + uint32_t aid = 0x2081F4 | (i << 16); + if (!aid_exists(ctx, aid, verbose)) + return aid; + } + return 0; +} + +/** + * @brief Delete the CAD or an application that contains cardholder credentials. + * + * @param site_key MIFARE site key. + * @param aid Application ID to remove. + */ +static int hfgal_delete_app(DesfireContext_t *ctx, uint8_t *site_key, + uint32_t aid, bool verbose) { + // Select application & authenticate + DesfireSetKeyNoClear(ctx, 0, T_AES, site_key); + DesfireSetKdf(ctx, MFDES_KDF_ALGO_GALLAGHER, NULL, 0); + int res = select_aid_and_authenticate(ctx, aid, verbose); + HFGAL_RET_IF_ERR(res); + + // Delete application + DesfireSetCommMode(ctx, DCMMACed); + res = DesfireDeleteApplication(ctx, aid); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed deleting AID %06X", aid); + + PrintAndLogEx(INFO, "Successfully deleted AID %06X", aid); + return PM3_SUCCESS; +} + +/** + * @brief Read credentials from a single AID. + * + * @param aid Application ID to read. + * @param site_key MIFARE site key. + * @param creds Decoded credentials will be stored in this structure. + */ +static int hfgal_read_creds_app(DesfireContext_t *ctx, uint32_t aid, uint8_t *site_key, + GallagherCredentials_t *creds, bool verbose) { + // 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)"); + + // Select application & authenticate + DesfireSetKeyNoClear(ctx, 2, T_AES, site_key); + DesfireSetKdf(ctx, MFDES_KDF_ALGO_GALLAGHER, NULL, 0); + int res = select_aid_and_authenticate(ctx, aid, verbose); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed selecting/authenticating to AID %06X", aid); + + // Read file 0 (contains credentials) + uint8_t buf[16] = {0}; + size_t read_len = 0; + DesfireSetCommMode(ctx, DCMEncrypted); + res = DesfireReadFile(ctx, 0, 0, 16, buf, &read_len); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed reading file 0 in AID %06X", aid); + + // Check file contained 16 bytes of data + if (read_len != 16) + HFGAL_RET_ERR(PM3_EFAILED, "Failed reading file 0 in AID %06X, expected " + "16 bytes but received %d bytes", aid, read_len); + + // 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) + HFGAL_RET_ERR(PM3_EFAILED, "Invalid cardholder data in file 0 in " + "AID %06X. Received %s", sprint_hex_inrow(buf, 16)); + + gallagher_decode_creds(buf, creds); + + // TODO: read MIFARE Enhanced Security file + // https://github.com/megabug/gallagher-research/blob/master/formats/mes.md + + return PM3_SUCCESS; +} + +/** + * @brief Create a new application to store Gallagher cardholder credentials. + * + * @param site_key MIFARE site key. + * @param aid New application ID. Should be 0x2?81F4, where 0 <= ? <= 0xB. + */ +static int hfgal_create_creds_app(DesfireContext_t *ctx, uint8_t *site_key, uint32_t aid, bool verbose) { + // Select application & authenticate + int res = select_aid_and_authenticate(ctx, 0x000000, verbose); + 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 `hfgal_create_creds_app`"); + + // Create application + DesfireCryptoAlgorithm app_algo = T_AES; + uint8_t num_keys = 3; + uint8_t ks1 = 0x0B; + uint8_t ks2 = (DesfireKeyAlgoToType(app_algo) << 6) | num_keys;; + + uint8_t data[5] = {0}; + DesfireAIDUintToByte(aid, &data[0]); + data[3] = ks1; + data[4] = ks2; + + DesfireSetCommMode(ctx, DCMMACed); + res = DesfireCreateApplication(ctx, data, ARRAYLEN(data)); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed creating application %06X. " + "Does it already exist?", aid); + + if (verbose) + PrintAndLogEx(INFO, "Created application %06X (currently has empty " + "contents & blank keys)", aid); + + // Select the new application + res = select_aid(ctx, aid, verbose); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed selecting application %06X", aid); + + // 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}; + res = hfgal_diversify_key(site_key, ctx->uid, ctx->uidlen, i, aid, buf); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed diversifying key %d for AID %06X", i, aid); + + PrintAndLogEx(INFO, "Diversified key %d for AID %06X: " _GREEN_("%s"), + i, aid, sprint_hex_inrow(buf, ARRAYLEN(buf))); + + // Authenticate + uint8_t blank_key[CRYPTO_AES128_KEY_SIZE] = {0}; + DesfireSetKeyNoClear(ctx, 0, T_AES, blank_key); + DesfireSetKdf(ctx, MFDES_KDF_ALGO_NONE, NULL, 0); + res = authenticate(ctx, verbose); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Desfire authenticate error. Result: " + "[%d] %s", res, DesfireAuthErrorToStr(res)); + + // Change key + DesfireSetCommMode(ctx, DCMEncryptedPlain); + res = DesfireChangeKey(ctx, false, i, app_algo, 1, buf, app_algo, blank_key, verbose); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed setting key %d for AID %06X", i, aid); + + if (verbose) + PrintAndLogEx(INFO, "Successfully set key %d for AID %06X", i, aid); + } + + PrintAndLogEx(INFO, "Successfully created credentials application %06X", aid); + return PM3_SUCCESS; +} + +/** + * @brief Create a new file containing Gallagher cardholder credentials. + * + * @param site_key MIFARE site key. + * @param aid Application ID to put the new file in. + * @param creds Gallagher cardholder credentials. + */ +static int hfgal_create_creds_file(DesfireContext_t *ctx, uint8_t *site_key, uint32_t aid, + GallagherCredentials_t *creds, bool verbose) { + // Select application & authenticate + DesfireSetKeyNoClear(ctx, 0, T_AES, site_key); + DesfireSetKdf(ctx, MFDES_KDF_ALGO_GALLAGHER, NULL, 0); + int res = select_aid_and_authenticate(ctx, aid, verbose); + HFGAL_RET_IF_ERR(res); + + // Prepare create file command + uint8_t file_type = 0; // standard data file + uint8_t file_id = 0x00; + uint8_t file_size = 16; + uint8_t file_access_mode = 0x03; // encrypted + uint32_t file_rights = 0x2000; // key 0 has God mode, key 2 can read + + uint8_t data[7] = {0}; + data[0] = file_id; + data[1] = file_access_mode; + data[2] = file_rights & 0xff; + data[3] = (file_rights >> 8) & 0xff; + Uint3byteToMemLe(&data[4], file_size); + + // Create file + res = DesfireCreateFile(ctx, file_type, data, ARRAYLEN(data), false); + 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 (currently has empty contents)", aid); + + // Create file contents (2nd half is the bitwise inverse of the encoded creds) + uint8_t contents[16] = {0}; + gallagher_encode_creds(contents, creds); + for (int i = 0; i < 8; i++) + contents[i + 8] = contents[i] ^ 0xFF; + + // Write file + DesfireSetCommMode(ctx, DCMEncrypted); + res = DesfireWriteFile(ctx, file_id, 0, ARRAYLEN(contents), contents); + 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); + return PM3_SUCCESS; +} + +/** + * @brief Read Gallagher Card Application Directory (CAD) from card. + * + * @param dest_buf Buffer to copy Card Application Directory into. + * @param dest_buf_len Size of dest_buf. Must be at least 108 bytes. + * @param num_entries Will be set to the number of entries in the CAD. + */ +static int hfgal_read_cad(DesfireContext_t *ctx, uint8_t *dest_buf, + uint8_t dest_buf_len, uint8_t *num_entries_out, bool verbose) { + if (dest_buf_len < 3 * 36) { + PrintAndLogEx(ERR, "hfgal_read_cad destination buffer is incorrectly sized. " + "Received length %d, must be at least %d", dest_buf_len, 3 * 36); + return PM3_EINVARG; + } + + // Get card AIDs from Card Application Directory (which contains 1 to 3 files) + int res = select_aid(ctx, CAD_AID, verbose); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed selecting Card Application " + "Directory, does AID %06X exist?", CAD_AID); + + // Read up to 3 files with 6x 6-byte entries each + for (uint8_t i = 0; i < 3; i++) { + size_t read_len; + res = DesfireReadFile(ctx, i, 0, 36, &dest_buf[i * 36], &read_len); + if (res != PM3_SUCCESS && res != PM3_EAPDU_FAIL) + HFGAL_RET_ERR(res, "Failed reading file %d in Card Application " + "Directory (AID %06X)", i, CAD_AID); + + // end if the last entry is NULL + if (memcmp(&dest_buf[36 * i + 30], "\0\0\0\0\0\0", 6) == 0) break; + } + + // Count number of entries (i.e. count until we hit a NULL entry) + uint8_t num_entries = 0; + for (uint8_t i = 0; i < dest_buf_len; i += 6) { + if (memcmp(&dest_buf[i], "\0\0\0\0\0\0", 6) == 0) break; + num_entries++; + } + *num_entries_out = num_entries; + + if (num_entries == 0) { + PrintAndLogEx(WARNING, "Card Application Directory is empty"); + } else if (verbose) { + // Print what we found + PrintAndLogEx(SUCCESS, "Card Application Directory contains:" NOLF); + for (int i = 0; i < num_entries; i++) + PrintAndLogEx(NORMAL, "%s %06X" NOLF, (i == 0) ? "" : ",", + cad_aid_byte_to_uint(&dest_buf[i * 6 + 3])); + PrintAndLogEx(NORMAL, ""); + } + + return PM3_SUCCESS; +} + +/** + * @brief Create the Gallagher Card Application Directory. + * + * @param site_key MIFARE site key. + */ +static int hfgal_create_cad(DesfireContext_t *ctx, uint8_t *site_key, bool verbose) { + // 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)"); + + // Select application & authenticate + int res = select_aid_and_authenticate(ctx, 0x000000, verbose); + HFGAL_RET_IF_ERR(res); + + // Create application + DesfireCryptoAlgorithm app_algo = T_AES; + uint8_t num_keys = 1; + uint8_t ks1 = 0x0B; + uint8_t ks2 = (DesfireKeyAlgoToType(app_algo) << 6) | num_keys;; + + uint8_t data[5] = {0}; + DesfireAIDUintToByte(CAD_AID, &data[0]); + data[3] = ks1; + data[4] = ks2; + + DesfireSetCommMode(ctx, DCMMACed); + res = DesfireCreateApplication(ctx, data, ARRAYLEN(data)); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed creating Card Application Directory. " + "Does it already exist?", CAD_AID); + + if (verbose) + PrintAndLogEx(INFO, "Created Card Application Directory (AID %06X, " + "currently has empty contents & blank keys)", CAD_AID); + + // Select application & authenticate + uint8_t blank_key[DESFIRE_MAX_KEY_SIZE] = {0}; + DesfireSetKeyNoClear(ctx, 0, T_AES, blank_key); + DesfireSetKdf(ctx, MFDES_KDF_ALGO_NONE, NULL, 0); + res = select_aid_and_authenticate(ctx, CAD_AID, verbose); + HFGAL_RET_IF_ERR(res); + + // Diversify key + uint8_t buf[CRYPTO_AES128_KEY_SIZE] = {0}; + res = hfgal_diversify_key(site_key, ctx->uid, ctx->uidlen, 0, CAD_AID, buf); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed diversifying key 0 for AID %06X", CAD_AID); + + PrintAndLogEx(INFO, "Diversified key 0 for CAD (AID %06X): " _GREEN_("%s"), + CAD_AID, sprint_hex_inrow(buf, ARRAYLEN(buf))); + + // Change key + DesfireSetCommMode(ctx, DCMEncryptedPlain); + res = DesfireChangeKey(ctx, false, 0, app_algo, 1, buf, app_algo, blank_key, verbose); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed setting key 0 for CAD"); + + if (verbose) + PrintAndLogEx(INFO, "Successfully set key 0 for CAD"); + + PrintAndLogEx(INFO, "Successfully created Card Application Directory " + "(AID %06X)", CAD_AID); + return PM3_SUCCESS; +} + +/** + * @brief Update the Gallagher Card Application Directory with a new entry. + * + * @param site_key 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 hfgal_add_aid_to_cad(DesfireContext_t *ctx, uint8_t *site_key, uint32_t aid, + GallagherCredentials_t *creds, bool verbose) { + // Check if CAD exists + uint8_t cad[36 * 3] = {0}; + uint8_t num_entries = 0; + if (aid_exists(ctx, CAD_AID, false)) { + if (verbose) + PrintAndLogEx(INFO, "Card Application Directory exists, reading entries..."); + + int res = hfgal_read_cad(ctx, cad, ARRAYLEN(cad), &num_entries, verbose); + HFGAL_RET_IF_ERR(res); + + // Check that there is space for the new entry + if (num_entries >= 18) + HFGAL_RET_ERR(PM3_EFATAL, "Card application directory is full"); + } else { + // CAD doesn't exist, we need to create it + if (verbose) + PrintAndLogEx(INFO, "Card Application Directory does not exist, creating it now..."); + + int res = hfgal_create_cad(ctx, site_key, verbose); + HFGAL_RET_IF_ERR(res); + } + + uint8_t file_id = num_entries / 6; // 6 entries per file + uint8_t entry_num = num_entries % 6; + + // Check if facility already exists in CAD. + for (uint8_t i = 0; i < ARRAYLEN(cad); i += 6) { + if (cad_facility_match(&cad[i], creds->region_code, creds->facility_code)) + HFGAL_RET_ERR(PM3_EFATAL, "Facility already exists in CAD, delete or " + "update AID %06X instead", cad_aid_byte_to_uint(&cad[i + 3])); + } + + // Create entry + uint8_t *entry = &cad[num_entries * 6]; + entry[0] = creds->region_code; + entry[1] = (creds->facility_code >> 8) & 0xFF; + entry[2] = creds->facility_code & 0xFF; + cad_aid_uint_to_byte(aid, &entry[3]); + + if (verbose) + PrintAndLogEx(INFO, "Adding entry to CAD (position %d in file %d): %s", + entry_num, file_id, sprint_hex_inrow(entry, 6)); + + // Select application & authenticate + DesfireSetKeyNoClear(ctx, 0, T_AES, site_key); + DesfireSetKdf(ctx, MFDES_KDF_ALGO_GALLAGHER, NULL, 0); + int res = select_aid_and_authenticate(ctx, CAD_AID, verbose); + HFGAL_RET_IF_ERR(res); + + // Create file if necessary + if (entry_num == 0) { + if (verbose) + PrintAndLogEx(INFO, "Creating new file in CAD"); + + // Prepare create file command + uint8_t file_type = 0; // standard data file + uint8_t file_size = 36; + uint8_t file_access_mode = 0x00; // plain + uint32_t file_rights = 0xE000; // key 0 has God mode, everyone can read + + uint8_t data[7] = {0}; + data[0] = file_id; + data[1] = file_access_mode; + data[2] = file_rights & 0xff; + data[3] = (file_rights >> 8) & 0xff; + Uint3byteToMemLe(&data[4], file_size); + + // Create file + res = DesfireCreateFile(ctx, file_type, data, ARRAYLEN(data), false); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed creating file %d in CAD " + "(AID %06X)", file_id, CAD_AID); + + if (verbose) + PrintAndLogEx(INFO, "Created file %d in CAD (currently has " + "empty contents)", file_id); + + // Write file + res = DesfireWriteFile(ctx, file_id, 0, 36, &cad[file_id * 36]); + } else + // Write file + res = DesfireWriteFile(ctx, file_id, entry_num * 6, 6, entry); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed writing data to file %d in CAD " + "(AID %06X)", file_id, CAD_AID); + + PrintAndLogEx(INFO, "Successfully added new entry for %06X to the Card " + "Application Directory", aid); + return PM3_SUCCESS; +} + +/** + * @brief Remove an entry from the Gallagher Card Application Directory. + * + * @param site_key MIFARE site key. + * @param aid Application ID to add to the CAD. + */ +static int hfgal_remove_aid_from_cad(DesfireContext_t *ctx, uint8_t *site_key, + uint32_t aid, bool verbose) { + // Check if CAD exists + uint8_t cad[36 * 3] = {0}; + uint8_t num_entries = 0; + + int res = hfgal_read_cad(ctx, cad, ARRAYLEN(cad), &num_entries, verbose); + HFGAL_RET_IF_ERR(res); + + // Check if facility already exists in CAD + uint8_t entry_idx; + for (entry_idx = 0; entry_idx < num_entries; entry_idx++) { + if (aid > 0 && aid == cad_aid_byte_to_uint(&cad[entry_idx * 6 + 3])) + break; + } + if (entry_idx >= num_entries) + HFGAL_RET_ERR(PM3_EINVARG, "Specified facility or AID does not exist " + "in the Card Application Directory"); + + // Remove entry (shift all entries left, then clear the last entry) + memmove( + &cad[entry_idx * 6], + &cad[(entry_idx + 1) * 6], + ARRAYLEN(cad) - (entry_idx + 1) * 6 + ); + memset(&cad[ARRAYLEN(cad) - 6], 0, 6); + + // Select application & authenticate + DesfireSetKeyNoClear(ctx, 0, T_AES, site_key); + DesfireSetKdf(ctx, MFDES_KDF_ALGO_GALLAGHER, NULL, 0); + res = select_aid_and_authenticate(ctx, CAD_AID, verbose); + HFGAL_RET_IF_ERR(res); + + // Determine what files we need to update + uint8_t file_id_start = entry_idx / 6; + uint8_t file_id_stop = (num_entries - 1) / 6; + bool delete_last_file = (num_entries - 1) % 6 == 0; + + for (uint8_t file_id = file_id_start; file_id <= file_id_stop - delete_last_file; file_id++) { + // Write file + res = DesfireWriteFile(ctx, file_id, 0, 36, &cad[file_id * 36]); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed writing data to file %d in CAD " + "(AID %06X)", file_id, CAD_AID); + + if (verbose) + PrintAndLogEx(INFO, "Updated file %d in CAD", file_id); + } + + // Delete empty file if necessary + if (delete_last_file) { + uint8_t file_id = file_id_stop; + + DesfireSetCommMode(ctx, DCMMACed); + res = DesfireDeleteFile(ctx, file_id); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed deleting file %d from CAD (AID %06X)", file_id, CAD_AID); + + if (verbose) + PrintAndLogEx(INFO, "Deleted unnecessary file %d from CAD (AID %06X)", file_id, CAD_AID); + } + + PrintAndLogEx(INFO, "Successfully removed %06X from the Card Application Directory", aid); + return PM3_SUCCESS; +} + +/** + * @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 site_key MIFARE site key. + * @param quiet Suppress error messages. Used when in continuous reader mode. + */ +static int hfgal_read_card(uint32_t aid, uint8_t *site_key, bool verbose, bool quiet) { + DropField(); + clearCommandBuffer(); + + // Set up context + DesfireContext_t dctx = {0}; + DesfireClearContext(&dctx); + + // Get card UID (for key diversification) + int res = DesfireGetCardUID(&dctx); + HFGAL_RET_IF_ERR_MAYBE_MSG(res, !quiet, "Failed retrieving card UID"); + + // Find AIDs to process (from CLI args or the Card Application Directory) + uint8_t cad[36 * 3] = {0}; + uint8_t num_entries = 0; + if (aid != 0) { + cad_aid_uint_to_byte(aid, &cad[3]); + num_entries = 1; + } else { + res = hfgal_read_cad(&dctx, cad, ARRAYLEN(cad), &num_entries, verbose); + HFGAL_RET_IF_ERR_MAYBE_MSG(res, !quiet, "Failed reading Card Application Directory"); + } + + // Loop through each application in the CAD + for (uint8_t i = 0; i < num_entries * 6; i += 6) { + uint16_t region_code = cad[i + 0]; + uint16_t facility_code = (cad[i + 1] << 8) + cad[i + 2]; + uint32_t current_aid = cad_aid_byte_to_uint(&cad[i + 3]); + + if (verbose) { + if (region_code > 0 || facility_code > 0) + PrintAndLogEx(INFO, "Reading AID: %06X, region: %u, facility: %u", + current_aid, region_code, facility_code); + else + PrintAndLogEx(INFO, "Reading AID: %06X", current_aid); + } + + // Read & decode credentials + GallagherCredentials_t creds = {0}; + res = hfgal_read_creds_app(&dctx, current_aid, site_key, &creds, verbose); + HFGAL_RET_IF_ERR_MAYBE_MSG(res, !quiet, "Failed reading card application credentials"); + + PrintAndLogEx(SUCCESS, "GALLAGHER (AID %06X) - Region: " _GREEN_("%u") ", Facility: " _GREEN_("%u") + ", Card No.: " _GREEN_("%u") ", Issue Level: " _GREEN_("%u"), current_aid, + 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 DESFire tag", + "hf gallagher reader --aid 2081f4 --sitekey 00112233445566778899aabbccddeeff" + " -> act as a reader that skips reading the Card Application Directory and uses a non-default site key\n" + "hf gallagher reader -@ -> continuous reader mode" + ); + + void *argtable[] = { + arg_param_begin, + arg_str0(NULL, "aid", "", "Application ID to read (3 bytes). If specified, then the Card Application Directory is not used"), + arg_str0(NULL, "sitekey", "", "MIFARE site key to compute diversified keys (16 bytes, required if using non-default key)"), + arg_lit0("@", "continuous", "Continuous reader mode"), + + arg_lit0(NULL, "apdu", "Show APDU requests and responses"), + arg_lit0("v", "verbose", "Verbose mode"), + arg_param_end + }; + CLIExecWithReturn(ctx, cmd, argtable, true); + + int aid_len = 0; + uint8_t aid_buf[3] = {0}; + CLIGetHexWithReturn(ctx, 1, aid_buf, &aid_len); + if (aid_len > 0 && aid_len != 3) + HFGAL_RET_ERR(PM3_EINVARG, "--aid must be 3 bytes"); + + reverse_aid(aid_buf); // PM3 displays AIDs backwards + uint32_t aid = DesfireAIDByteToUint(aid_buf); + + int site_key_len = 0; + uint8_t site_key[16] = {0}; + memcpy(site_key, DEFAULT_SITE_KEY, ARRAYLEN(site_key)); + CLIGetHexWithReturn(ctx, 2, site_key, &site_key_len); + if (site_key_len > 0 && site_key_len != 16) + HFGAL_RET_ERR(PM3_EINVARG, "--sitekey must be 16 bytes"); + + bool continuous_mode = arg_get_lit(ctx, 3); + SetAPDULogging(arg_get_lit(ctx, 4)); + bool verbose = arg_get_lit(ctx, 5); + CLIParserFree(ctx); + + if (!continuous_mode) + // Read single card + return hfgal_read_card(aid, site_key, verbose, false); + + // Loop until is pressed + PrintAndLogEx(INFO, "Press " _GREEN_("") " to exit"); + while (!kbd_enter_pressed()) + hfgal_read_card(aid, site_key, verbose, !verbose); + return PM3_SUCCESS; +} + +static int CmdGallagherClone(const char *cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf gallagher clone", + "Clone Gallagher credentials to a writable DESFire card", + "hf gallagher clone --rc 1 --fc 22 --cn 3333 --il 4 --sitekey 00112233445566778899aabbccddeeff" + ); + + void *argtable[] = { + arg_param_begin, + arg_int0("n", "keynum", "", "Key number [default=0]"), + arg_str0("t", "algo", "", "Crypt algo: DES, 2TDEA, 3TDEA, AES"), + arg_str0("k", "key", "", "Key for authentication to the PICC (HEX 8(DES), 16(2TDEA or AES) or 24(3TDEA) bytes)"), + + arg_u64_1(NULL, "rc", "", "Region code. 4 bits max"), + arg_u64_1(NULL, "fc", "", "Facility code. 2 bytes max"), + arg_u64_1(NULL, "cn", "", "Card number. 3 bytes max"), + arg_u64_1(NULL, "il", "", "Issue level. 4 bits max"), + arg_str0(NULL, "aid", "", "Application ID to write (3 bytes) [default finds lowest available in range 0x2?81F4, where 0 <= ? <= 0xB]"), + arg_str0(NULL, "sitekey", "", "MIFARE site key to compute diversified keys (16 bytes, required if using non-default key)"), + + arg_lit0(NULL, "apdu", "Show APDU requests and responses"), + arg_lit0("v", "verbose", "Verbose mode"), + arg_param_end + }; + CLIExecWithReturn(ctx, cmd, argtable, false); + + int key_num = arg_get_int_def(ctx, 1, 0); + + int key_algo = T_DES; + if (CLIGetOptionList(arg_get_str(ctx, 2), DesfireAlgoOpts, &key_algo)) return PM3_ESOFT; + + int key_len = 0; + uint8_t key[DESFIRE_MAX_KEY_SIZE] = {0}; + CLIGetHexWithReturn(ctx, 3, key, &key_len); + if (key_len && key_len != desfire_get_key_length(key_algo)) + HFGAL_RET_ERR(PM3_EINVARG, "%s key must have %d bytes length instead of %d", CLIGetOptionListStr(DesfireAlgoOpts, key_algo), desfire_get_key_length(key_algo), key_len); + if (key_len == 0) + // Default to a key of all zeros + key_len = desfire_get_key_length(key_algo); + + uint64_t region_code = arg_get_u64(ctx, 4); // uint4, input will be validated later + uint64_t facility_code = arg_get_u64(ctx, 5); // uint16 + uint64_t card_number = arg_get_u64(ctx, 6); // uint24 + uint64_t issue_level = arg_get_u64(ctx, 7); // uint4 + + int aid_len = 0; + uint8_t aid_buf[3] = {0}; + uint32_t aid = 0; + CLIGetHexWithReturn(ctx, 8, aid_buf, &aid_len); + if (aid_len > 0) { + if (aid_len != 3) + HFGAL_RET_ERR(PM3_EINVARG, "--aid must be 3 bytes"); + reverse_aid(aid_buf); // PM3 displays AIDs backwards + aid = DesfireAIDByteToUint(aid_buf); + + // Check that the AID is in the expected range + if (memcmp(aid_buf, "\xF4\x81", 2) != 0 || aid_buf[2] < 0x20 || aid_buf[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); + } + + int site_key_len = 0; + uint8_t site_key[16] = {0}; + memcpy(site_key, DEFAULT_SITE_KEY, ARRAYLEN(site_key)); + CLIGetHexWithReturn(ctx, 9, site_key, &site_key_len); + if (site_key_len > 0 && site_key_len != 16) + HFGAL_RET_ERR(PM3_EINVARG, "--sitekey must be 16 bytes"); + + SetAPDULogging(arg_get_lit(ctx, 10)); + bool verbose = arg_get_lit(ctx, 11); + CLIParserFree(ctx); + + if (!gallagher_is_valid_creds(region_code, facility_code, card_number, issue_level)) + return PM3_EINVARG; + + GallagherCredentials_t creds = { + .region_code = region_code, + .facility_code = facility_code, + .card_number = card_number, + .issue_level = issue_level, + }; + + // Set up context + DropField(); + DesfireContext_t dctx = {0}; + DesfireClearContext(&dctx); + + // Get card UID (for key diversification) + int res = DesfireGetCardUID(&dctx); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed retrieving card UID"); + + // Find available Gallagher AID if the user did not specify one + if (aid_len == 0) { + aid = find_available_gallagher_aid(&dctx, verbose); + if (aid == 0) + HFGAL_RET_ERR(PM3_EFATAL, "Could not find an available AID, card is full"); + } + + // Update Card Application Directory + DesfireSetKeyNoClear(&dctx, key_num, key_algo, key); + DesfireSetKdf(&dctx, MFDES_KDF_ALGO_NONE, NULL, 0); + res = hfgal_add_aid_to_cad(&dctx, site_key, aid, &creds, verbose); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed updating Gallagher Card Application Directory"); + + // Create application + DesfireSetKeyNoClear(&dctx, key_num, key_algo, key); + DesfireSetKdf(&dctx, MFDES_KDF_ALGO_NONE, NULL, 0); + res = hfgal_create_creds_app(&dctx, site_key, aid, verbose); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed creating Gallagher application"); + + // Create credential files + // Don't need to set keys here, they're generated automatically + res = hfgal_create_creds_file(&dctx, site_key, aid, &creds, verbose); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed creating Gallagher credential file"); + + PrintAndLogEx(SUCCESS, "Done"); + PrintAndLogEx(HINT, "Hint: try " _YELLOW_("`hf gallagher reader`") " to verify"); + return PM3_SUCCESS; +} + +static int CmdGallagherDelete(const char *cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf gallagher delete", + "Delete Gallagher application from a DESFire card", + "hf gallagher delete --aid 2081f4 --sitekey 00112233445566778899aabbccddeeff" + ); + + void *argtable[] = { + arg_param_begin, + arg_str1(NULL, "aid", "", "Application ID to delete (3 bytes)"), + arg_str0(NULL, "sitekey", "", "MIFARE site key to compute diversified keys (16 bytes, required if using non-default key)"), + + arg_lit0(NULL, "apdu", "Show APDU requests and responses"), + arg_lit0("v", "verbose", "Verbose mode"), + arg_param_end + }; + CLIExecWithReturn(ctx, cmd, argtable, false); + + int aid_len = 0; + uint8_t aid_buf[3] = {0}; + uint32_t aid = 0; + CLIGetHexWithReturn(ctx, 1, aid_buf, &aid_len); + + if (aid_len != 3) + HFGAL_RET_ERR(PM3_EINVARG, "--aid must be 3 bytes"); + reverse_aid(aid_buf); // PM3 displays AIDs backwards + aid = DesfireAIDByteToUint(aid_buf); + + // Check that the AID is in the expected range + if (memcmp(aid_buf, "\xF4\x81", 2) != 0 || aid_buf[2] < 0x20 || aid_buf[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); + + int site_key_len = 0; + uint8_t site_key[16] = {0}; + memcpy(site_key, DEFAULT_SITE_KEY, ARRAYLEN(site_key)); + CLIGetHexWithReturn(ctx, 2, site_key, &site_key_len); + if (site_key_len > 0 && site_key_len != 16) + HFGAL_RET_ERR(PM3_EINVARG, "--sitekey must be 16 bytes"); + + SetAPDULogging(arg_get_lit(ctx, 3)); + bool verbose = arg_get_lit(ctx, 4); + CLIParserFree(ctx); + + // Set up context + DropField(); + DesfireContext_t dctx = {0}; + DesfireClearContext(&dctx); + + // Get card UID (for key diversification) + int res = DesfireGetCardUID(&dctx); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed retrieving card UID"); + + // Update Card Application Directory + res = hfgal_remove_aid_from_cad(&dctx, site_key, aid, verbose); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed removing %06X from the Card Application Directory"); + + // Delete application + res = hfgal_delete_app(&dctx, site_key, aid, verbose); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed deleting Gallagher application"); + + PrintAndLogEx(SUCCESS, "Done"); + PrintAndLogEx(HINT, "Hint: try " _YELLOW_("`hf gallagher reader`") " to verify"); + return PM3_SUCCESS; +} + +static int CmdGallagherDiversify(const char *cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf gallagher diversify", + "Diversify Gallagher key", + "hf gallagher diversify --uid 11223344556677 --aid 2081f4" + ); + + void *argtable[] = { + arg_param_begin, + arg_str1(NULL, "aid", "", "Application ID for diversification (3 bytes)"), + arg_int0(NULL, "keynum", "", "Key number [default=0]"), + arg_str0(NULL, "uid", "", "Card UID to delete (4 or 7 bytes)"), + arg_str0(NULL, "sitekey", "", "MIFARE site key to compute diversified keys (16 bytes, required if using non-default key)"), + + arg_lit0(NULL, "apdu", "Show APDU requests and responses"), + arg_param_end + }; + CLIExecWithReturn(ctx, cmd, argtable, false); + + int aid_len = 0; + uint8_t aid_buf[3] = {0}; + uint32_t aid = 0; + CLIGetHexWithReturn(ctx, 1, aid_buf, &aid_len); + + if (aid_len != 3) + HFGAL_RET_ERR(PM3_EINVARG, "--aid must be 3 bytes"); + reverse_aid(aid_buf); // PM3 displays AIDs backwards + aid = DesfireAIDByteToUint(aid_buf); + + // Check that the AID is in the expected range + if (memcmp(aid_buf, "\xF4\x81", 2) != 0 || aid_buf[2] < 0x20 || aid_buf[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); + + int key_num = arg_get_int_def(ctx, 2, 0); + + int uid_len = 0; + uint8_t uid[7] = {0}; + CLIGetHexWithReturn(ctx, 3, uid, &uid_len); + if (uid_len > 0 && uid_len != 4 && uid_len != 7) + HFGAL_RET_ERR(PM3_EINVARG, "--uid must be 4 or 7 bytes"); + + int site_key_len = 0; + uint8_t site_key[16] = {0}; + memcpy(site_key, DEFAULT_SITE_KEY, ARRAYLEN(site_key)); + CLIGetHexWithReturn(ctx, 4, site_key, &site_key_len); + if (site_key_len > 0 && site_key_len != 16) + HFGAL_RET_ERR(PM3_EINVARG, "--sitekey must be 16 bytes"); + + SetAPDULogging(arg_get_lit(ctx, 5)); + CLIParserFree(ctx); + + if (uid_len == 0) { + // Set up context + DropField(); + DesfireContext_t dctx = {0}; + DesfireClearContext(&dctx); + + // Get card UID (for key diversification) + int res = DesfireGetCardUID(&dctx); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed retrieving card UID"); + + uid_len = dctx.uidlen; + memcpy(uid, dctx.uid, uid_len); + } + + // Diversify key + uint8_t key[CRYPTO_AES128_KEY_SIZE] = {0}; + int res = hfgal_diversify_key(site_key, uid, uid_len, key_num, aid, key); + HFGAL_RET_IF_ERR_WITH_MSG(res, "Failed diversifying key"); + + char *key_str = sprint_hex_inrow(key, ARRAYLEN(key)); + PrintAndLogEx(SUCCESS, "Successfully diversified key: " _GREEN_("%s"), key_str); + + if (IfPm3Iso14443()) + PrintAndLogEx(HINT, "Hint: try " + _YELLOW_("`hf mfdes auth --aid %06X --keyno %d --algo AES --key %s`") + " to verify", aid, key_num, key_str); + return PM3_SUCCESS; +} + +static int CmdHelp(const char *cmd); + +static command_t CommandTable[] = { + {"help", CmdHelp, AlwaysAvailable, "This help"}, + {"reader", CmdGallagherReader, IfPm3Iso14443, "Read & decode all Gallagher credentials on a DESFire card"}, + {"clone", CmdGallagherClone, IfPm3Iso14443, "Add Gallagher credentials to a DESFire card"}, + {"delete", CmdGallagherDelete, IfPm3Iso14443, "Delete Gallagher credentials from a DESFire card"}, + {"diversifykey", CmdGallagherDiversify, AlwaysAvailable, "Diversify Gallagher key"}, + {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); +} diff --git a/client/src/cmdhfgallagher.h b/client/src/cmdhfgallagher.h new file mode 100644 index 000000000..22aee9c2f --- /dev/null +++ b/client/src/cmdhfgallagher.h @@ -0,0 +1,42 @@ +/** + * Matt Moran (@DarkMatterMatt), 2021 + * ----------------------------------------------------------------------------- + * 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. + * ----------------------------------------------------------------------------- + * High frequency GALLAGHER tag commands. + * MIFARE DESFire, AIDs 2081F4-2F81F4 + */ +#ifndef CMDHFGALLAGHER_H__ +#define CMDHFGALLAGHER_H__ + +#include "common.h" +#include + +int CmdHFGallagher(const char *Cmd); + +/** + * @brief Create Gallagher Application Master Key by diversifying + * the MIFARE Site Key with card UID, key number, and application ID. + * + * @param sitekey MIFARE Site Key (16 bytes). + * @param uid Card unique ID (4 or 7 bytes). + * @param uidLen Length of UID. + * @param keyNum Key number (0 <= keyNum <= 2). + * @param aid Application ID (0x2?81F4 where 0 <= ? <= B). + * @param keyOut Buffer to copy the diversified key into (must be 16 bytes). + * @return PM3_SUCCESS if successful, PM3_EINVARG if an argument is invalid. + */ +int hfgal_diversify_key(uint8_t *sitekey, uint8_t *uid, uint8_t uidLen, uint8_t keyNum, uint32_t aid, uint8_t *keyOut); + +// Return error +#define HFGAL_RET_ERR(err, ...) { PrintAndLogEx(ERR, __VA_ARGS__); return err; } + +// HF GALlagher RETurn IF ERRor +#define HFGAL_RET_IF_ERR(res) if (res != PM3_SUCCESS) { return res; } +#define HFGAL_RET_IF_ERR_WITH_MSG(res, ...) if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, __VA_ARGS__); return res; } +#define HFGAL_RET_IF_ERR_MAYBE_MSG(res, verbose, ...) if (res != PM3_SUCCESS) { if (verbose) PrintAndLogEx(ERR, __VA_ARGS__); return res; } + +#endif diff --git a/client/src/cmdhfmfdes.c b/client/src/cmdhfmfdes.c index 4c59954ab..11aa7328b 100644 --- a/client/src/cmdhfmfdes.c +++ b/client/src/cmdhfmfdes.c @@ -155,6 +155,17 @@ typedef struct aidhdr { uint8_t name[16]; } PACKED aidhdr_t; +typedef struct { + const char *aid; + const char *comment; +} mfdesCommonAID_t; + +static const mfdesCommonAID_t commonAids[] = { + // AID, name/comment + { "\xf4\x81\x2f", "Gallagher card data application" }, + { "\xf4\x81\x20", "Gallagher card application directory" }, // Can be 0xF48120 - 0xF4812B, but I've only ever seen 0xF48120 +}; + static int CmdHelp(const char *Cmd); static int CLIGetUint32Hex(CLIParserContext *ctx, uint8_t paramnum, uint32_t defaultValue, uint32_t *value, bool *valuePresent, uint8_t nlen, const char *lengthErrorStr) { @@ -244,6 +255,16 @@ static char *getVersionStr(uint8_t major, uint8_t minor) { //04 01 01 01 00 1A 05 } +static char noCommentStr[1] = { 0x00 }; +static const char *getAidCommentStr(uint8_t *aid) { + for (int i = 0; i < ARRAYLEN(commonAids); i++) { + if (memcmp(aid, commonAids[i].aid, 3) == 0) { + return commonAids[i].comment; + } + } + return noCommentStr; +} + static nxp_cardtype_t getCardType(uint8_t major, uint8_t minor) { if (major == 0x00) @@ -3058,8 +3079,13 @@ static int CmdHF14ADesGetAIDs(const char *Cmd) { if (buflen >= 3) { PrintAndLogEx(INFO, "---- " _CYAN_("AID list") " ----"); - for (int i = 0; i < buflen; i += 3) - PrintAndLogEx(INFO, "AID: %06x", DesfireAIDByteToUint(&buf[i])); + for (int i = 0; i < buflen; i += 3) { + const char *commentStr = getAidCommentStr(&buf[i]); + if ((void *) commentStr == &noCommentStr) + PrintAndLogEx(INFO, "AID: %06x", DesfireAIDByteToUint(&buf[i])); + else + PrintAndLogEx(INFO, "AID: %06x (%s)", DesfireAIDByteToUint(&buf[i]), commentStr); + } } else { PrintAndLogEx(INFO, "There is no applications on the card"); } diff --git a/client/src/cmdlfgallagher.c b/client/src/cmdlfgallagher.c index a8981e700..35e38d735 100644 --- a/client/src/cmdlfgallagher.c +++ b/client/src/cmdlfgallagher.c @@ -9,7 +9,10 @@ // ASK/MAN, RF/32, 96 bits long (unknown cs) (0x00088060) // sample Q5 , ASK RF/32, STT, 96 bits (3blocks) ( 0x9000F006) //----------------------------------------------------------------------------- +// Modified by: Matt Moran (@DarkMatterMatt), 2021 + #include "cmdlfgallagher.h" +#include "mifare/gallaghercore.h" #include // memcpy #include // tolower #include @@ -29,56 +32,6 @@ static int CmdHelp(const char *Cmd); -static void scramble(uint8_t *arr, uint8_t len) { - const uint8_t lut[] = { - 0xa3, 0xb0, 0x80, 0xc6, 0xb2, 0xf4, 0x5c, 0x6c, 0x81, 0xf1, 0xbb, 0xeb, 0x55, 0x67, 0x3c, 0x05, - 0x1a, 0x0e, 0x61, 0xf6, 0x22, 0xce, 0xaa, 0x8f, 0xbd, 0x3b, 0x1f, 0x5e, 0x44, 0x04, 0x51, 0x2e, - 0x4d, 0x9a, 0x84, 0xea, 0xf8, 0x66, 0x74, 0x29, 0x7f, 0x70, 0xd8, 0x31, 0x7a, 0x6d, 0xa4, 0x00, - 0x82, 0xb9, 0x5f, 0xb4, 0x16, 0xab, 0xff, 0xc2, 0x39, 0xdc, 0x19, 0x65, 0x57, 0x7c, 0x20, 0xfa, - 0x5a, 0x49, 0x13, 0xd0, 0xfb, 0xa8, 0x91, 0x73, 0xb1, 0x33, 0x18, 0xbe, 0x21, 0x72, 0x48, 0xb6, - 0xdb, 0xa0, 0x5d, 0xcc, 0xe6, 0x17, 0x27, 0xe5, 0xd4, 0x53, 0x42, 0xf3, 0xdd, 0x7b, 0x24, 0xac, - 0x2b, 0x58, 0x1e, 0xa7, 0xe7, 0x86, 0x40, 0xd3, 0x98, 0x97, 0x71, 0xcb, 0x3a, 0x0f, 0x01, 0x9b, - 0x6e, 0x1b, 0xfc, 0x34, 0xa6, 0xda, 0x07, 0x0c, 0xae, 0x37, 0xca, 0x54, 0xfd, 0x26, 0xfe, 0x0a, - 0x45, 0xa2, 0x2a, 0xc4, 0x12, 0x0d, 0xf5, 0x4f, 0x69, 0xe0, 0x8a, 0x77, 0x60, 0x3f, 0x99, 0x95, - 0xd2, 0x38, 0x36, 0x62, 0xb7, 0x32, 0x7e, 0x79, 0xc0, 0x46, 0x93, 0x2f, 0xa5, 0xba, 0x5b, 0xaf, - 0x52, 0x1d, 0xc3, 0x75, 0xcf, 0xd6, 0x4c, 0x83, 0xe8, 0x3d, 0x30, 0x4e, 0xbc, 0x08, 0x2d, 0x09, - 0x06, 0xd9, 0x25, 0x9e, 0x89, 0xf2, 0x96, 0x88, 0xc1, 0x8c, 0x94, 0x0b, 0x28, 0xf0, 0x47, 0x63, - 0xd5, 0xb3, 0x68, 0x56, 0x9c, 0xf9, 0x6f, 0x41, 0x50, 0x85, 0x8b, 0x9d, 0x59, 0xbf, 0x9f, 0xe2, - 0x8e, 0x6a, 0x11, 0x23, 0xa1, 0xcd, 0xb5, 0x7d, 0xc7, 0xa9, 0xc8, 0xef, 0xdf, 0x02, 0xb8, 0x03, - 0x6b, 0x35, 0x3e, 0x2c, 0x76, 0xc9, 0xde, 0x1c, 0x4b, 0xd1, 0xed, 0x14, 0xc5, 0xad, 0xe9, 0x64, - 0x4a, 0xec, 0x8d, 0xf7, 0x10, 0x43, 0x78, 0x15, 0x87, 0xe4, 0xd7, 0x92, 0xe1, 0xee, 0xe3, 0x90 - }; - - for (int i = 0; i < len; i++) { - arr[i] = lut[arr[i]]; - } -} - -static void descramble(uint8_t *arr, uint8_t len) { - const uint8_t lut[] = { - 0x2f, 0x6e, 0xdd, 0xdf, 0x1d, 0x0f, 0xb0, 0x76, 0xad, 0xaf, 0x7f, 0xbb, 0x77, 0x85, 0x11, 0x6d, - 0xf4, 0xd2, 0x84, 0x42, 0xeb, 0xf7, 0x34, 0x55, 0x4a, 0x3a, 0x10, 0x71, 0xe7, 0xa1, 0x62, 0x1a, - 0x3e, 0x4c, 0x14, 0xd3, 0x5e, 0xb2, 0x7d, 0x56, 0xbc, 0x27, 0x82, 0x60, 0xe3, 0xae, 0x1f, 0x9b, - 0xaa, 0x2b, 0x95, 0x49, 0x73, 0xe1, 0x92, 0x79, 0x91, 0x38, 0x6c, 0x19, 0x0e, 0xa9, 0xe2, 0x8d, - 0x66, 0xc7, 0x5a, 0xf5, 0x1c, 0x80, 0x99, 0xbe, 0x4e, 0x41, 0xf0, 0xe8, 0xa6, 0x20, 0xab, 0x87, - 0xc8, 0x1e, 0xa0, 0x59, 0x7b, 0x0c, 0xc3, 0x3c, 0x61, 0xcc, 0x40, 0x9e, 0x06, 0x52, 0x1b, 0x32, - 0x8c, 0x12, 0x93, 0xbf, 0xef, 0x3b, 0x25, 0x0d, 0xc2, 0x88, 0xd1, 0xe0, 0x07, 0x2d, 0x70, 0xc6, - 0x29, 0x6a, 0x4d, 0x47, 0x26, 0xa3, 0xe4, 0x8b, 0xf6, 0x97, 0x2c, 0x5d, 0x3d, 0xd7, 0x96, 0x28, - 0x02, 0x08, 0x30, 0xa7, 0x22, 0xc9, 0x65, 0xf8, 0xb7, 0xb4, 0x8a, 0xca, 0xb9, 0xf2, 0xd0, 0x17, - 0xff, 0x46, 0xfb, 0x9a, 0xba, 0x8f, 0xb6, 0x69, 0x68, 0x8e, 0x21, 0x6f, 0xc4, 0xcb, 0xb3, 0xce, - 0x51, 0xd4, 0x81, 0x00, 0x2e, 0x9c, 0x74, 0x63, 0x45, 0xd9, 0x16, 0x35, 0x5f, 0xed, 0x78, 0x9f, - 0x01, 0x48, 0x04, 0xc1, 0x33, 0xd6, 0x4f, 0x94, 0xde, 0x31, 0x9d, 0x0a, 0xac, 0x18, 0x4b, 0xcd, - 0x98, 0xb8, 0x37, 0xa2, 0x83, 0xec, 0x03, 0xd8, 0xda, 0xe5, 0x7a, 0x6b, 0x53, 0xd5, 0x15, 0xa4, - 0x43, 0xe9, 0x90, 0x67, 0x58, 0xc0, 0xa5, 0xfa, 0x2a, 0xb1, 0x75, 0x50, 0x39, 0x5c, 0xe6, 0xdc, - 0x89, 0xfc, 0xcf, 0xfe, 0xf9, 0x57, 0x54, 0x64, 0xa8, 0xee, 0x23, 0x0b, 0xf1, 0xea, 0xfd, 0xdb, - 0xbd, 0x09, 0xb5, 0x5b, 0x05, 0x86, 0x13, 0xf3, 0x24, 0xc5, 0x3f, 0x44, 0x72, 0x7c, 0x7e, 0x36 - }; - - for (int i = 0; i < len; i++) { - arr[i] = lut[arr[i]]; - } -} - //see ASK/MAN Demod for what args are accepted int demodGallagher(bool verbose) { (void) verbose; // unused so far @@ -113,6 +66,7 @@ int demodGallagher(bool verbose) { // bytes uint8_t arr[8] = {0}; for (int i = 0, pos = 0; i < ARRAYLEN(arr); i++) { + // first 16 bits are the 7FEA prefix, then every 9th bit is a checksum-bit for the preceding byte pos = 16 + (9 * i); arr[i] = bytebits_to_byte(g_DemodBuffer + pos, 8); } @@ -121,22 +75,12 @@ int demodGallagher(bool verbose) { uint8_t crc = bytebits_to_byte(g_DemodBuffer + 16 + (9 * 8), 8); uint8_t calc_crc = CRC8Cardx(arr, ARRAYLEN(arr)); - descramble(arr, ARRAYLEN(arr)); + GallagherCredentials_t creds = {0}; + gallagher_decode_creds(arr, &creds); - // 4bit region code - uint8_t rc = (arr[3] & 0x1E) >> 1; - - // 16bit FC - uint16_t fc = (arr[5] & 0x0F) << 12 | arr[1] << 4 | ((arr[7] >> 4) & 0x0F); - - // 24bit CN - uint32_t cn = arr[0] << 16 | (arr[4] & 0x1F) << 11 | arr[2] << 3 | (arr[3] & 0xE0) >> 5; - - // 4bit issue level - uint8_t il = arr[7] & 0x0F; - - PrintAndLogEx(SUCCESS, "GALLAGHER - Region: " _GREEN_("%u") " FC: " _GREEN_("%u") " CN: " _GREEN_("%u") " Issue Level: " _GREEN_("%u"), rc, fc, cn, il); - PrintAndLogEx(SUCCESS, " Displayed: " _GREEN_("%C%u"), rc + 'A', fc); + 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); + PrintAndLogEx(SUCCESS, " Displayed: " _GREEN_("%C%u"), creds.region_code + 'A', creds.facility_code); PrintAndLogEx(SUCCESS, " Raw: %08X%08X%08X", raw1, raw2, raw3); PrintAndLogEx(SUCCESS, " CRC: %02X - %02X (%s)", crc, calc_crc, (crc == calc_crc) ? "ok" : "fail"); return PM3_SUCCESS; @@ -185,34 +129,6 @@ static int CmdGallagherReader(const char *Cmd) { return PM3_SUCCESS; } -static bool isValidGallagherParams(int8_t rc, int32_t fc, int32_t cn, int8_t il) { - bool isValid = true; - - // if one is set, all must be set - if (rc < 0 || fc < 0 || cn < 0 || il < 0) { - PrintAndLogEx(FAILED, "If rc/fc/cn/il is specified, all must be set"); - isValid = false; - } - // validate input - if (rc > 0x0f) { - PrintAndLogEx(FAILED, "Region code must be less than 16 (4 bits)"); - isValid = false; - } - if (fc > 0xffff) { - PrintAndLogEx(FAILED, "Facility code must be less than 65536 (2 bytes)"); - isValid = false; - } - if (cn > 0xffffff) { - PrintAndLogEx(FAILED, "Card number must be less than 16777216 (3 bytes)"); - isValid = false; - } - if (il > 0x0f) { - PrintAndLogEx(FAILED, "Issue level must be less than 16 (4 bits)"); - isValid = false; - } - return isValid; -} - static void setBitsInBlocks(uint32_t *blocks, uint8_t *pos, uint32_t data, uint8_t data_len) { for (int i = data_len - 1; i >= 0; i--) { uint8_t blk = *pos / 32; @@ -223,20 +139,10 @@ static void setBitsInBlocks(uint32_t *blocks, uint8_t *pos, uint32_t data, uint8 } } -static void createBlocks(uint32_t *blocks, uint8_t rc, uint16_t fc, uint32_t cn, uint8_t il) { +static void createBlocks(uint32_t *blocks, GallagherCredentials_t *creds) { // put data into the correct places (Gallagher obfuscation) uint8_t arr[8] = {0}; - arr[0] = (cn & 0xffffff) >> 16; - arr[1] = (fc & 0xfff) >> 4; - arr[2] = (cn & 0x7ff) >> 3; - arr[3] = (cn & 0x7) << 5 | (rc & 0xf) << 1; - arr[4] = (cn & 0xffff) >> 11; - arr[5] = (fc & 0xffff) >> 12; - arr[6] = 0; - arr[7] = (fc & 0xf) << 4 | (il & 0xf); - - // more obfuscation - scramble(arr, ARRAYLEN(arr)); + gallagher_encode_creds(arr, creds); blocks[0] = blocks[1] = blocks[2] = 0; uint8_t pos = 0; @@ -258,7 +164,6 @@ static void createBlocks(uint32_t *blocks, uint8_t rc, uint16_t fc, uint32_t cn, } static int CmdGallagherClone(const char *Cmd) { - CLIParserContext *ctx; CLIParserInit(&ctx, "lf gallagher clone", "clone a GALLAGHER tag to a T55x7, Q5/T5555 or EM4305/4469 tag.", @@ -273,10 +178,10 @@ static int CmdGallagherClone(const char *Cmd) { arg_str0("r", "raw", "", "raw hex data. 12 bytes max"), arg_lit0(NULL, "q5", "optional - specify writing to Q5/T5555 tag"), arg_lit0(NULL, "em", "optional - specify writing to EM4305/4469 tag"), - arg_int0(NULL, "rc", "", "Region code. 4 bits max"), - arg_int0(NULL, "fc", "", "Facility code. 2 bytes max"), - arg_int0(NULL, "cn", "", "Card number. 3 bytes max"), - arg_int0(NULL, "il", "", "Issue level. 4 bits max"), + arg_u64_0(NULL, "rc", "", "Region code. 4 bits max"), + arg_u64_0(NULL, "fc", "", "Facility code. 2 bytes max"), + arg_u64_0(NULL, "cn", "", "Card number. 3 bytes max"), + arg_u64_0(NULL, "il", "", "Issue level. 4 bits max"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, false); @@ -292,10 +197,10 @@ static int CmdGallagherClone(const char *Cmd) { bool q5 = arg_get_lit(ctx, 2); bool em = arg_get_lit(ctx, 3); - int16_t region_code = arg_get_int_def(ctx, 4, -1); - int32_t facility_code = arg_get_int_def(ctx, 5, -1); - int32_t card_number = arg_get_int_def(ctx, 6, -1); - int32_t issue_level = arg_get_int_def(ctx, 7, -1); + uint64_t region_code = arg_get_u64_def(ctx, 4, -1); // uint4, input will be validated later + uint64_t facility_code = arg_get_u64_def(ctx, 5, -1); // uint16 + uint64_t card_number = arg_get_u64_def(ctx, 6, -1); // uint24 + uint64_t issue_level = arg_get_u64_def(ctx, 7, -1); // uint4 CLIParserFree(ctx); bool use_raw = raw_len > 0; @@ -316,7 +221,7 @@ static int CmdGallagherClone(const char *Cmd) { PrintAndLogEx(FAILED, "Can't specify both raw and rc/fc/cn/il at the same time"); return PM3_EINVARG; } - if (!isValidGallagherParams(region_code, facility_code, card_number, issue_level)) { + if (!gallagher_is_valid_creds(region_code, facility_code, card_number, issue_level)) { return PM3_EINVARG; } } @@ -327,8 +232,14 @@ static int CmdGallagherClone(const char *Cmd) { blocks[i] = bytes_to_num(raw + ((i - 1) * 4), sizeof(uint32_t)); } } else { + GallagherCredentials_t creds = { + .region_code = region_code, + .facility_code = facility_code, + .card_number = card_number, + .issue_level = issue_level, + }; // fill blocks 1 to 3 with Gallagher data - createBlocks(blocks + 1, region_code, facility_code, card_number, issue_level); + createBlocks(blocks + 1, &creds); } //Pac - compat mode, NRZ, data rate 40, 3 data blocks @@ -373,10 +284,10 @@ static int CmdGallagherSim(const char *Cmd) { void *argtable[] = { arg_param_begin, arg_str0("r", "raw", "", "raw hex data. 12 bytes max"), - arg_int0(NULL, "rc", "", "Region code. 4 bits max"), - arg_int0(NULL, "fc", "", "Facility code. 2 bytes max"), - arg_int0(NULL, "cn", "", "Card number. 3 bytes max"), - arg_int0(NULL, "il", "", "Issue level. 4 bits max"), + arg_u64_0(NULL, "rc", "", "Region code. 4 bits max"), + arg_u64_0(NULL, "fc", "", "Facility code. 2 bytes max"), + arg_u64_0(NULL, "cn", "", "Card number. 3 bytes max"), + arg_u64_0(NULL, "il", "", "Issue level. 4 bits max"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, false); @@ -391,10 +302,10 @@ static int CmdGallagherSim(const char *Cmd) { return PM3_EINVARG; } - int16_t region_code = arg_get_int_def(ctx, 2, -1); - int32_t facility_code = arg_get_int_def(ctx, 3, -1); - int32_t card_number = arg_get_int_def(ctx, 4, -1); - int32_t issue_level = arg_get_int_def(ctx, 5, -1); + uint64_t region_code = arg_get_u64_def(ctx, 2, -1); // uint4, input will be validated later + uint64_t facility_code = arg_get_u64_def(ctx, 3, -1); // uint16 + uint64_t card_number = arg_get_u64_def(ctx, 4, -1); // uint24 + uint64_t issue_level = arg_get_u64_def(ctx, 5, -1); // uint4 CLIParserFree(ctx); bool use_raw = raw_len > 0; @@ -410,15 +321,21 @@ static int CmdGallagherSim(const char *Cmd) { PrintAndLogEx(FAILED, "Can't specify both raw and rc/fc/cn/il at the same time"); return PM3_EINVARG; } - if (!isValidGallagherParams(region_code, facility_code, card_number, issue_level)) { + if (!gallagher_is_valid_creds(region_code, facility_code, card_number, issue_level)) { return PM3_EINVARG; } } if (!use_raw) { // generate Gallagher data + GallagherCredentials_t creds = { + .region_code = region_code, + .facility_code = facility_code, + .card_number = card_number, + .issue_level = issue_level, + }; uint32_t blocks[3]; - createBlocks(blocks, region_code, facility_code, card_number, issue_level); + createBlocks(blocks, &creds); // convert to the normal 'raw' format for (int i = 0; i < ARRAYLEN(blocks); i++) { diff --git a/client/src/mifare/gallaghercore.c b/client/src/mifare/gallaghercore.c new file mode 100644 index 000000000..71e235286 --- /dev/null +++ b/client/src/mifare/gallaghercore.c @@ -0,0 +1,124 @@ +/** + * Matt Moran (@DarkMatterMatt), 2021 + * ----------------------------------------------------------------------------- + * 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. + * ----------------------------------------------------------------------------- + * Common functionality for low/high-frequency GALLAGHER tag encoding & decoding. + */ +#include "gallaghercore.h" +#include "common.h" +#include "ui.h" + +static void scramble(uint8_t *arr, uint8_t len) { + uint8_t lut[] = { + 0xa3, 0xb0, 0x80, 0xc6, 0xb2, 0xf4, 0x5c, 0x6c, 0x81, 0xf1, 0xbb, 0xeb, 0x55, 0x67, 0x3c, 0x05, + 0x1a, 0x0e, 0x61, 0xf6, 0x22, 0xce, 0xaa, 0x8f, 0xbd, 0x3b, 0x1f, 0x5e, 0x44, 0x04, 0x51, 0x2e, + 0x4d, 0x9a, 0x84, 0xea, 0xf8, 0x66, 0x74, 0x29, 0x7f, 0x70, 0xd8, 0x31, 0x7a, 0x6d, 0xa4, 0x00, + 0x82, 0xb9, 0x5f, 0xb4, 0x16, 0xab, 0xff, 0xc2, 0x39, 0xdc, 0x19, 0x65, 0x57, 0x7c, 0x20, 0xfa, + 0x5a, 0x49, 0x13, 0xd0, 0xfb, 0xa8, 0x91, 0x73, 0xb1, 0x33, 0x18, 0xbe, 0x21, 0x72, 0x48, 0xb6, + 0xdb, 0xa0, 0x5d, 0xcc, 0xe6, 0x17, 0x27, 0xe5, 0xd4, 0x53, 0x42, 0xf3, 0xdd, 0x7b, 0x24, 0xac, + 0x2b, 0x58, 0x1e, 0xa7, 0xe7, 0x86, 0x40, 0xd3, 0x98, 0x97, 0x71, 0xcb, 0x3a, 0x0f, 0x01, 0x9b, + 0x6e, 0x1b, 0xfc, 0x34, 0xa6, 0xda, 0x07, 0x0c, 0xae, 0x37, 0xca, 0x54, 0xfd, 0x26, 0xfe, 0x0a, + 0x45, 0xa2, 0x2a, 0xc4, 0x12, 0x0d, 0xf5, 0x4f, 0x69, 0xe0, 0x8a, 0x77, 0x60, 0x3f, 0x99, 0x95, + 0xd2, 0x38, 0x36, 0x62, 0xb7, 0x32, 0x7e, 0x79, 0xc0, 0x46, 0x93, 0x2f, 0xa5, 0xba, 0x5b, 0xaf, + 0x52, 0x1d, 0xc3, 0x75, 0xcf, 0xd6, 0x4c, 0x83, 0xe8, 0x3d, 0x30, 0x4e, 0xbc, 0x08, 0x2d, 0x09, + 0x06, 0xd9, 0x25, 0x9e, 0x89, 0xf2, 0x96, 0x88, 0xc1, 0x8c, 0x94, 0x0b, 0x28, 0xf0, 0x47, 0x63, + 0xd5, 0xb3, 0x68, 0x56, 0x9c, 0xf9, 0x6f, 0x41, 0x50, 0x85, 0x8b, 0x9d, 0x59, 0xbf, 0x9f, 0xe2, + 0x8e, 0x6a, 0x11, 0x23, 0xa1, 0xcd, 0xb5, 0x7d, 0xc7, 0xa9, 0xc8, 0xef, 0xdf, 0x02, 0xb8, 0x03, + 0x6b, 0x35, 0x3e, 0x2c, 0x76, 0xc9, 0xde, 0x1c, 0x4b, 0xd1, 0xed, 0x14, 0xc5, 0xad, 0xe9, 0x64, + 0x4a, 0xec, 0x8d, 0xf7, 0x10, 0x43, 0x78, 0x15, 0x87, 0xe4, 0xd7, 0x92, 0xe1, 0xee, 0xe3, 0x90 + }; + + for (int i = 0; i < len; i++) { + arr[i] = lut[arr[i]]; + } +} + +static void descramble(uint8_t *arr, uint8_t len) { + uint8_t lut[] = { + 0x2f, 0x6e, 0xdd, 0xdf, 0x1d, 0x0f, 0xb0, 0x76, 0xad, 0xaf, 0x7f, 0xbb, 0x77, 0x85, 0x11, 0x6d, + 0xf4, 0xd2, 0x84, 0x42, 0xeb, 0xf7, 0x34, 0x55, 0x4a, 0x3a, 0x10, 0x71, 0xe7, 0xa1, 0x62, 0x1a, + 0x3e, 0x4c, 0x14, 0xd3, 0x5e, 0xb2, 0x7d, 0x56, 0xbc, 0x27, 0x82, 0x60, 0xe3, 0xae, 0x1f, 0x9b, + 0xaa, 0x2b, 0x95, 0x49, 0x73, 0xe1, 0x92, 0x79, 0x91, 0x38, 0x6c, 0x19, 0x0e, 0xa9, 0xe2, 0x8d, + 0x66, 0xc7, 0x5a, 0xf5, 0x1c, 0x80, 0x99, 0xbe, 0x4e, 0x41, 0xf0, 0xe8, 0xa6, 0x20, 0xab, 0x87, + 0xc8, 0x1e, 0xa0, 0x59, 0x7b, 0x0c, 0xc3, 0x3c, 0x61, 0xcc, 0x40, 0x9e, 0x06, 0x52, 0x1b, 0x32, + 0x8c, 0x12, 0x93, 0xbf, 0xef, 0x3b, 0x25, 0x0d, 0xc2, 0x88, 0xd1, 0xe0, 0x07, 0x2d, 0x70, 0xc6, + 0x29, 0x6a, 0x4d, 0x47, 0x26, 0xa3, 0xe4, 0x8b, 0xf6, 0x97, 0x2c, 0x5d, 0x3d, 0xd7, 0x96, 0x28, + 0x02, 0x08, 0x30, 0xa7, 0x22, 0xc9, 0x65, 0xf8, 0xb7, 0xb4, 0x8a, 0xca, 0xb9, 0xf2, 0xd0, 0x17, + 0xff, 0x46, 0xfb, 0x9a, 0xba, 0x8f, 0xb6, 0x69, 0x68, 0x8e, 0x21, 0x6f, 0xc4, 0xcb, 0xb3, 0xce, + 0x51, 0xd4, 0x81, 0x00, 0x2e, 0x9c, 0x74, 0x63, 0x45, 0xd9, 0x16, 0x35, 0x5f, 0xed, 0x78, 0x9f, + 0x01, 0x48, 0x04, 0xc1, 0x33, 0xd6, 0x4f, 0x94, 0xde, 0x31, 0x9d, 0x0a, 0xac, 0x18, 0x4b, 0xcd, + 0x98, 0xb8, 0x37, 0xa2, 0x83, 0xec, 0x03, 0xd8, 0xda, 0xe5, 0x7a, 0x6b, 0x53, 0xd5, 0x15, 0xa4, + 0x43, 0xe9, 0x90, 0x67, 0x58, 0xc0, 0xa5, 0xfa, 0x2a, 0xb1, 0x75, 0x50, 0x39, 0x5c, 0xe6, 0xdc, + 0x89, 0xfc, 0xcf, 0xfe, 0xf9, 0x57, 0x54, 0x64, 0xa8, 0xee, 0x23, 0x0b, 0xf1, 0xea, 0xfd, 0xdb, + 0xbd, 0x09, 0xb5, 0x5b, 0x05, 0x86, 0x13, 0xf3, 0x24, 0xc5, 0x3f, 0x44, 0x72, 0x7c, 0x7e, 0x36 + }; + + for (int i = 0; i < len; i++) { + arr[i] = lut[arr[i]]; + } +} + +void gallagher_decode_creds(uint8_t *eight_bytes, GallagherCredentials_t *creds) { + uint8_t *arr = eight_bytes; + + descramble(arr, 8); + + // 4bit region code + creds->region_code = (arr[3] & 0x1E) >> 1; + + // 16bit facility code + creds->facility_code = (arr[5] & 0x0F) << 12 | arr[1] << 4 | ((arr[7] >> 4) & 0x0F); + + // 24bit card number + creds->card_number = arr[0] << 16 | (arr[4] & 0x1F) << 11 | arr[2] << 3 | (arr[3] & 0xE0) >> 5; + + // 4bit issue level + creds->issue_level = arr[7] & 0x0F; +} + +void gallagher_encode_creds(uint8_t *eight_bytes, GallagherCredentials_t *creds) { + uint8_t rc = creds->region_code; + uint16_t fc = creds->facility_code; + uint32_t cn = creds->card_number; + uint8_t il = creds->issue_level; + + // put data into the correct places (Gallagher obfuscation) + eight_bytes[0] = (cn & 0xffffff) >> 16; + eight_bytes[1] = (fc & 0xfff) >> 4; + eight_bytes[2] = (cn & 0x7ff) >> 3; + eight_bytes[3] = (cn & 0x7) << 5 | (rc & 0xf) << 1; + eight_bytes[4] = (cn & 0xffff) >> 11; + eight_bytes[5] = (fc & 0xffff) >> 12; + eight_bytes[6] = 0; + eight_bytes[7] = (fc & 0xf) << 4 | (il & 0xf); + + // more obfuscation + scramble(eight_bytes, 8); +} + +bool gallagher_is_valid_creds(uint64_t region_code, uint64_t facility_code, uint64_t card_number, uint64_t issue_level) { + bool is_valid = true; + + // validate input + if (region_code > 0x0f) { + PrintAndLogEx(ERR, "Region code must be 0 <= rc <= 15 (4 bits), received: %d", region_code); + is_valid = false; + } + if (facility_code > 0xffff) { + PrintAndLogEx(ERR, "Facility code must be 0 <= fc <= 65535 (2 bytes), received: %d", facility_code); + is_valid = false; + } + if (card_number > 0xffffff) { + PrintAndLogEx(ERR, "Card number must be 0 <= cn <= 16777215 (3 bytes), received: %d", card_number); + is_valid = false; + } + if (issue_level > 0x0f) { + PrintAndLogEx(ERR, "Issue level must be 0 <= il <= 15 (4 bits), received: %d", issue_level); + is_valid = false; + } + return is_valid; +} diff --git a/client/src/mifare/gallaghercore.h b/client/src/mifare/gallaghercore.h new file mode 100644 index 000000000..9137073bb --- /dev/null +++ b/client/src/mifare/gallaghercore.h @@ -0,0 +1,30 @@ +/** + * Matt Moran (@DarkMatterMatt), 2021 + * ----------------------------------------------------------------------------- + * 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. + * ----------------------------------------------------------------------------- + * Common functionality for low/high-frequency GALLAGHER tag encoding & decoding. + */ +#ifndef MIFARE_GALLAGHERCORE_H__ +#define MIFARE_GALLAGHERCORE_H__ + +#include "common.h" +#include + +typedef struct { + uint8_t region_code; + uint16_t facility_code; + uint32_t card_number; + uint8_t issue_level; +} GallagherCredentials_t; + +void gallagher_encode_creds(uint8_t *eight_bytes, GallagherCredentials_t *creds); + +void gallagher_decode_creds(uint8_t *eight_bytes, GallagherCredentials_t *creds); + +bool gallagher_is_valid_creds(uint64_t region_code, uint64_t facility_code, uint64_t card_number, uint64_t issue_level); + +#endif diff --git a/client/src/rl_vocabulory.h b/client/src/rl_vocabulory.h index 685c9eb8f..87dcf1b6d 100644 --- a/client/src/rl_vocabulory.h +++ b/client/src/rl_vocabulory.h @@ -216,6 +216,11 @@ const static vocabulory_t vocabulory[] = { { 0, "hf fido auth" }, { 0, "hf fido make" }, { 0, "hf fido assert" }, + { 1, "hf gallagher help" }, + { 0, "hf gallagher reader" }, + { 0, "hf gallagher clone" }, + { 0, "hf gallagher delete" }, + { 1, "hf gallagher diversifykey" }, { 1, "hf ksx6924 help" }, { 0, "hf ksx6924 balance" }, { 0, "hf ksx6924 info" }, diff --git a/doc/commands.json b/doc/commands.json index 659ba6b43..81fe8a01d 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -2320,9 +2320,83 @@ ], "usage": "hf fido reg [-havt] [-f ] [--cp ] [--ap ] [--cpx ] [--apx ]" }, + "hf gallagher clone": { + "command": "hf gallagher clone", + "description": "clone gallagher credentials to a writable desfire card", + "notes": [ + "hf gallagher clone --rc 1 --fc 22 --cn 3333 --il 4 --sitekey 00112233445566778899aabbccddeeff" + ], + "offline": false, + "options": [ + "-h, --help this help", + "-n, --keynum key number [default=0]", + "-t, --algo crypt algo: des, 2tdea, 3tdea, aes", + "-k, --key key for authentication to the picc (hex 8(des), 16(2tdea or aes) or 24(3tdea) bytes)", + "--rc region code. 4 bits max", + "--fc facility code. 2 bytes max", + "--cn card number. 3 bytes max", + "--il issue level. 4 bits max", + "--aid application id to write (3 bytes) [default finds lowest available in range 0x2?81f4, where 0 <= ? <= 0xb]", + "--sitekey mifare site key to compute diversified keys (16 bytes, required if using non-default key)", + "--apdu show apdu requests and responses", + "-v, --verbose verbose mode" + ], + "usage": "hf gallagher clone [-hv] [-n ] [-t ] [-k ] --rc --fc --cn --il [--aid ] [--sitekey ] [--apdu]" + }, + "hf gallagher delete": { + "command": "hf gallagher delete", + "description": "delete gallagher application from a desfire card", + "notes": [ + "hf gallagher delete --aid 2081f4 --sitekey 00112233445566778899aabbccddeeff" + ], + "offline": false, + "options": [ + "-h, --help this help", + "--aid application id to delete (3 bytes)", + "--sitekey mifare site key to compute diversified keys (16 bytes, required if using non-default key)", + "--apdu show apdu requests and responses", + "-v, --verbose verbose mode" + ], + "usage": "hf gallagher delete [-hv] --aid [--sitekey ] [--apdu]" + }, + "hf gallagher diversifykey": { + "command": "hf gallagher diversifykey", + "description": "diversify gallagher key", + "notes": [ + "hf gallagher diversify --uid 11223344556677 --aid 2081f4" + ], + "offline": true, + "options": [ + "-h, --help this help", + "--aid application id for diversification (3 bytes)", + "--keynum key number [default=0]", + "--uid card uid to delete (4 or 7 bytes)", + "--sitekey mifare site key to compute diversified keys (16 bytes, required if using non-default key)", + "--apdu show apdu requests and responses" + ], + "usage": "hf gallagher diversify [-h] --aid [--keynum ] [--uid ] [--sitekey ] [--apdu]" + }, + "hf gallagher help": { + "command": "hf gallagher help", + "description": "help this help diversifykey diversify gallagher key --------------------------------------------------------------------------------------- hf gallagher reader available offline: no read a gallagher desfire tag", + "notes": [ + "hf gallagher reader --aid 2081f4 --sitekey 00112233445566778899aabbccddeeff -> act as a reader that skips reading the card application directory and uses a non-default site key", + "hf gallagher reader -@ -> continuous reader mode" + ], + "offline": true, + "options": [ + "-h, --help this help", + "--aid application id to read (3 bytes). if specified, then the card application directory is not used", + "--sitekey mifare site key to compute diversified keys (16 bytes, required if using non-default key)", + "-@, --continuous continuous reader mode", + "--apdu show apdu requests and responses", + "-v, --verbose verbose mode" + ], + "usage": "hf gallagher reader [-h@v] [--aid ] [--sitekey ] [--apdu]" + }, "hf help": { "command": "hf help", - "description": "-------- ----------------------- high frequency ----------------------- 14a { iso14443a rfids... } 14b { iso14443b rfids... } 15 { iso15693 rfids... } cipurse { cipurse transport cards... } epa { german identification card... } emrtd { machine readable travel document... } felica { iso18092 / felica rfids... } fido { fido and fido2 authenticators... } ksx6924 { ks x 6924 (t-money, snapper+) rfids } jooki { jooki rfids... } iclass { iclass rfids... } legic { legic rfids... } lto { lto cartridge memory rfids... } mf { mifare rfids... } mfp { mifare plus rfids... } mfu { mifare ultralight rfids... } mfdes { mifare desfire rfids... } seos { seos rfids... } st25ta { st25ta rfids... } thinfilm { thinfilm rfids... } topaz { topaz (nfc type 1) rfids... } waveshare { waveshare nfc epaper... } ----------- --------------------- general --------------------- help this help list list protocol data in trace buffer search search for known hf tags --------------------------------------------------------------------------------------- hf list available offline: yes alias of `trace list -t raw` with selected protocol data to annotate trace buffer you can load a trace from file (see `trace load -h`) or it be downloaded from device by default it accepts all other arguments of `trace list`. note that some might not be relevant for this specific protocol", + "description": "-------- ----------------------- high frequency ----------------------- 14a { iso14443a rfids... } 14b { iso14443b rfids... } 15 { iso15693 rfids... } cipurse { cipurse transport cards... } epa { german identification card... } emrtd { machine readable travel document... } felica { iso18092 / felica rfids... } fido { fido and fido2 authenticators... } gallagher { gallagher desfire rfids... } ksx6924 { ks x 6924 (t-money, snapper+) rfids } jooki { jooki rfids... } iclass { iclass rfids... } legic { legic rfids... } lto { lto cartridge memory rfids... } mf { mifare rfids... } mfp { mifare plus rfids... } mfu { mifare ultralight rfids... } mfdes { mifare desfire rfids... } seos { seos rfids... } st25ta { st25ta rfids... } thinfilm { thinfilm rfids... } topaz { topaz (nfc type 1) rfids... } waveshare { waveshare nfc epaper... } ----------- --------------------- general --------------------- help this help list list protocol data in trace buffer search search for known hf tags --------------------------------------------------------------------------------------- hf list available offline: yes alias of `trace list -t raw` with selected protocol data to annotate trace buffer you can load a trace from file (see `trace load -h`) or it be downloaded from device by default it accepts all other arguments of `trace list`. note that some might not be relevant for this specific protocol", "notes": [ "hf list -f -> show frame delay times", "hf list -1 -> use trace buffer" @@ -5963,8 +6037,8 @@ "command": "hw connect", "description": "connects to a proxmark3 device via specified serial port. baudrate here is only for physical uart or uart-bt, not for usb-cdc or blue shark add-on", "notes": [ - "hw connect -p /dev/ttyacm0", - "hw connect -p /dev/ttyacm0 -b 115200" + "hw connect -p com3", + "hw connect -p com3 -b 115200" ], "offline": true, "options": [ @@ -10161,8 +10235,8 @@ } }, "metadata": { - "commands_extracted": 597, + "commands_extracted": 601, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2022-01-05T16:57:29" + "extracted_on": "2022-01-06T12:03:19" } } \ No newline at end of file diff --git a/doc/commands.md b/doc/commands.md index c9abff59f..7bf7ce494 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -317,6 +317,19 @@ Check column "offline" for their availability. |`hf fido assert `|N |`FIDO2 GetAssertion command.` +### hf gallagher + + { Gallagher DESFire RFIDs... } + +|command |offline |description +|------- |------- |----------- +|`hf gallagher help `|Y |`This help` +|`hf gallagher reader `|N |`Read & decode all Gallagher credentials on a DESFire card` +|`hf gallagher clone `|N |`Add Gallagher credentials to a DESFire card` +|`hf gallagher delete `|N |`Delete Gallagher credentials from a DESFire card` +|`hf gallagher diversifykey`|Y |`Diversify Gallagher key` + + ### hf ksx6924 { KS X 6924 (T-Money, Snapper+) RFIDs }