diff --git a/client/src/cmdhfgallagher.c b/client/src/cmdhfgallagher.c index d80bea83a..2365c2703 100644 --- a/client/src/cmdhfgallagher.c +++ b/client/src/cmdhfgallagher.c @@ -11,6 +11,7 @@ */ #include "cmdhfgallagher.h" +#include "generator.h" #include "mifare.h" #include "mifare/desfirecore.h" #include "mifare/gallaghercore.h" @@ -48,6 +49,8 @@ static int readCardApplicationDirectory(DesfireContext_t *ctx, uint8_t *destBuf, for (uint8_t i = 0; i < 3; i++) { size_t readLen; DesfireReadFile(ctx, i, 0, 36, &destBuf[i * 36], &readLen); + // end if the last entry is NULL + if (memcmp(&destBuf[36 * i + 30], "\0\0\0\0\0\0", 6) == 0) break; } // Keep only valid entries @@ -82,11 +85,11 @@ static int readCardApplicationCredentials(DesfireContext_t *ctx, uint8_t *aid, u DesfireSetCommandSet(ctx, DCCNativeISO); // Select and authenticate to AID - int res = DesfireSelectAndAuthenticateAppW(ctx, DACEV1, ISW6bAID, le24toh(aid), false, verbose); + int res = DesfireSelectAndAuthenticateAppW(ctx, DACEV1, ISW6bAID, DesfireAIDByteToUint(aid), false, verbose); HF_GALLAGHER_RETURN_IF_ERROR(res); // Read file 0 (contains credentials) - uint8_t buf[16] = { 0x00 }; + uint8_t buf[16] = {0}; size_t readLen = 0; DesfireSetCommMode(ctx, DCMEncrypted); res = DesfireReadFile(ctx, 0, 0, 16, buf, &readLen); @@ -220,39 +223,362 @@ static int CmdGallagherReader(const char *Cmd) { return continuousMode ? PM3_SUCCESS : res; } +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); + HF_GALLAGHER_RETURN_IF_ERROR(res); + + // 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; +} + +static int createGallagherCredentialsApplication(DesfireContext_t *ctx, uint8_t *sitekey, uint8_t *aid, bool verbose) { + DesfireSetCommMode(ctx, DCMPlain); + DesfireSetCommandSet(ctx, DCCNativeISO); + int res = DesfireSelectAndAuthenticateAppW(ctx, DACEV1, ISW6bAID, 0x000000, false, verbose); + HF_GALLAGHER_RETURN_IF_ERROR(res); + + // 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}; + memcpy(&data[0], aid, 3); + data[3] = ks1; + data[4] = ks2; + + DesfireSetCommMode(ctx, DCMMACed); + res = DesfireCreateApplication(ctx, data, ARRAYLEN(data)); + HF_GALLAGHER_RETURN_IF_ERROR(res); + + // Select the new application + DesfireSetCommMode(ctx, DCMPlain); + res = DesfireSelectEx(ctx, true, ISW6bAID, DesfireAIDByteToUint(aid), NULL); + HF_GALLAGHER_RETURN_IF_ERROR(res); + if (verbose) + PrintAndLogEx(INFO, "AID: %06x is " _GREEN_("selected"), DesfireAIDByteToUint(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 = GallagherDiversifyKey(sitekey, ctx->uid, ctx->uidlen, i, DesfireAIDByteToUint(aid), buf); + HF_GALLAGHER_RETURN_IF_ERROR(res); + + if (verbose) + PrintAndLogEx(INFO, " Diversified key %d: " _GREEN_("%s"), i, sprint_hex_inrow(buf, ARRAYLEN(buf))); + + // Authenticate + uint8_t blankKey[DESFIRE_MAX_KEY_SIZE] = {0}; + DesfireSetKeyNoClear(ctx, 0, T_AES, blankKey); + DesfireSetCommMode(ctx, DCMPlain); + res = DesfireAuthenticate(ctx, DACEV1, verbose); + HF_GALLAGHER_RETURN_IF_ERROR(res); + + // Change key + DesfireSetCommMode(ctx, DCMEncryptedPlain); + res = DesfireChangeKey(ctx, false, i, dstalgo, 1, buf, dstalgo, blankKey, true); + HF_GALLAGHER_RETURN_IF_ERROR(res); + if (verbose) + PrintAndLogEx(INFO, " Successfully set key %d", i); + } + + return PM3_SUCCESS; +} + +static int createGallagherCredentialsFile(DesfireContext_t *ctx, uint8_t *sitekey, uint8_t *aid, GallagherCredentials_t *creds, bool verbose) { + // Set up context + DesfireSetKeyNoClear(ctx, 0, T_AES, sitekey); + DesfireSetKdf(ctx, MFDES_KDF_ALGO_GALLAGHER, NULL, 0); + DesfireSetCommMode(ctx, DCMPlain); + + // Select application + int res = DesfireSelectAndAuthenticateAppW(ctx, DACEV1, ISW6bAID, DesfireAIDByteToUint(aid), false, verbose); + HF_GALLAGHER_RETURN_IF_ERROR(res); + + // 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); + HF_GALLAGHER_RETURN_IF_ERROR(res); + + // 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); + HF_GALLAGHER_RETURN_IF_ERROR(res); + + return PM3_SUCCESS; +} + +static int createGallagherCAD(DesfireContext_t *ctx, uint8_t *sitekey, bool verbose) { + DesfireClearSession(ctx); + DesfireSetCommMode(ctx, DCMPlain); + DesfireSetCommandSet(ctx, DCCNativeISO); + int res = DesfireSelectAndAuthenticateAppW(ctx, DACEV1, ISW6bAID, 0x000000, false, verbose); + HF_GALLAGHER_RETURN_IF_ERROR(res); + + // Create application + uint32_t aid = 0x2F81F4; + DesfireCryptoAlgorithm dstalgo = T_AES; + uint8_t keycount = 1; + uint8_t ks1 = 0x0B; + uint8_t ks2 = (DesfireKeyAlgoToType(dstalgo) << 6) | keycount;; + + uint8_t data[5] = {0}; + DesfireAIDUintToByte(aid, &data[0]); + data[3] = ks1; + data[4] = ks2; + + DesfireSetCommMode(ctx, DCMMACed); + res = DesfireCreateApplication(ctx, data, ARRAYLEN(data)); + HF_GALLAGHER_RETURN_IF_ERROR(res); + + // Select the new application + DesfireSetCommMode(ctx, DCMPlain); + res = DesfireSelectEx(ctx, true, ISW6bAID, aid, NULL); + HF_GALLAGHER_RETURN_IF_ERROR(res); + if (verbose) + PrintAndLogEx(INFO, "AID: %06x is " _GREEN_("selected"), aid); + + // Diversify key + uint8_t buf[CRYPTO_AES128_KEY_SIZE] = {0}; + res = GallagherDiversifyKey(sitekey, ctx->uid, ctx->uidlen, 0, aid, buf); + HF_GALLAGHER_RETURN_IF_ERROR(res); + + if (verbose) + PrintAndLogEx(INFO, " Diversified key 0: " _GREEN_("%s"), sprint_hex_inrow(buf, ARRAYLEN(buf))); + + // Authenticate + uint8_t blankKey[DESFIRE_MAX_KEY_SIZE] = {0}; + DesfireSetKeyNoClear(ctx, 0, T_AES, blankKey); + DesfireSetCommMode(ctx, DCMPlain); + res = DesfireAuthenticate(ctx, DACEV1, verbose); + HF_GALLAGHER_RETURN_IF_ERROR(res); + + // Change key + DesfireSetCommMode(ctx, DCMEncryptedPlain); + res = DesfireChangeKey(ctx, false, 0, dstalgo, 1, buf, dstalgo, blankKey, true); + HF_GALLAGHER_RETURN_IF_ERROR(res); + if (verbose) + PrintAndLogEx(INFO, " Successfully set key 0"); + + return PM3_SUCCESS; +} + +static int updateGallagherCAD(DesfireContext_t *ctx, uint8_t *sitekey, uint8_t *aid, GallagherCredentials_t *creds, bool verbose) { + uint32_t cadAid = 0x2F81F4; + + + // Check if CAD exists + uint8_t cad[36 * 3] = {0}; + uint8_t numEntries = 0; + int res = DesfireSelectEx(ctx, true, ISW6bAID, cadAid, NULL); + if (res == PM3_SUCCESS) { + res = readCardApplicationDirectory(ctx, cad, ARRAYLEN(cad), &numEntries, verbose); + HF_GALLAGHER_RETURN_IF_ERROR(res); + } else { + DesfireSetCommandSet(ctx, DCCNativeISO); + res = createGallagherCAD(ctx, sitekey, verbose); + HF_GALLAGHER_RETURN_IF_ERROR(res); + + // Check that there is space for the new entry + if (numEntries == 18) { + PrintAndLogEx(ERR, "Card application directory is full."); + return PM3_EFATAL; + } + } + + 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; + memcpy(&entry[3], aid, 3); + reverseAid(&entry[3]); // CAD stores AIDs backwards + + // Set up context + DesfireSetKeyNoClear(ctx, 0, T_AES, sitekey); + DesfireSetKdf(ctx, MFDES_KDF_ALGO_GALLAGHER, NULL, 0); + DesfireSetCommMode(ctx, DCMPlain); + + // Select application + res = DesfireSelectAndAuthenticateAppW(ctx, DACEV1, ISW6bAID, cadAid, false, verbose); + HF_GALLAGHER_RETURN_IF_ERROR(res); + + // Create file if necessary + if (entryNum == 0) { + // 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); + HF_GALLAGHER_RETURN_IF_ERROR(res); + + // Write file + res = DesfireWriteFile(ctx, fileId, fileId * 36, 36, entry); + HF_GALLAGHER_RETURN_IF_ERROR(res); + } else { + // Write file + res = DesfireWriteFile(ctx, fileId, entryNum * 6, 6, entry); + HF_GALLAGHER_RETURN_IF_ERROR(res); + } + + return PM3_SUCCESS; +} + static int CmdGallagherClone(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf gallagher clone", "clone a GALLAGHER card to a blank DESFire card.", - "hf gallagher clone --rc 1 --fc 22 --cn 3333 --il 4" + "hf gallagher clone --rc 1 --fc 22 --cn 3333 --il 4 --sitekey 00112233445566778899aabbccddeeff" ); void *argtable[] = { arg_param_begin, - 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_lit0(NULL, "apdu", "show APDU requests and responses"), + arg_lit0("v", "verbose", "Verbose mode"), + arg_int0("n", "keyno", "", "Key number [default=0]"), + arg_str0("t", "algo", "", "Crypt algo: DES, 2TDEA, 3TDEA, AES"), + arg_str0("k", "key", "", "Key for authenticate (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=2081F4]"), + arg_str1(NULL, "sitekey", "", "Master site key to compute diversified keys (16 bytes)"), 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 + 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)) { + PrintAndLogEx(ERR, "%s key must have %d bytes length instead of %d.", CLIGetOptionListStr(DesfireAlgoOpts, algo), desfire_get_key_length(algo), keyLen); + return PM3_EINVARG; + } + 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; + uint8_t aid[3] = "\x20\x81\xF4"; + CLIGetHexWithReturn(ctx, 10, aid, &aidLen); + if (aidLen > 0 && aidLen != 3) { + PrintAndLogEx(ERR, "--aid must be 3 bytes"); + return PM3_EINVARG; + } + reverseAid(aid); // PM3 displays AIDs backwards + + // Check that the AID is in the expected range + if (memcmp(aid, "\xF4\x81", 2) != 0 || aid[2] < 0x20 || aid[2] > 0x2B) { + PrintAndLogEx(WARNING, "Invalid Gallagher AID %06X, expected 2?81F4.", DesfireAIDByteToUint(aid)); + } + + int sitekeyLen = 0; + uint8_t sitekey[16] = {0}; + CLIGetHexWithReturn(ctx, 11, sitekey, &sitekeyLen); + if (sitekeyLen > 0 && sitekeyLen != 16) { + PrintAndLogEx(ERR, "--sitekey must be 16 bytes"); + return PM3_EINVARG; + } CLIParserFree(ctx); if (!isValidGallagherCredentials(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, + }; - // TODO: create data + // Set up context + DropField(); + DesfireContext_t dctx = {0}; + DesfireClearContext(&dctx); - PrintAndLogEx(INFO, "Preparing to clone Gallagher from specified data."); + // Get card UID (for key diversification) + int res = DesfireGetCardUID(&dctx); + HF_GALLAGHER_FAIL_IF_ERROR(res, true, "Failed retrieving card UID."); - // TODO: write data + // Create application + DesfireSetKeyNoClear(&dctx, keyNum, algo, key); + DesfireSetKdf(&dctx, MFDES_KDF_ALGO_NONE, NULL, 0); + res = createGallagherCredentialsApplication(&dctx, sitekey, aid, verbose); + HF_GALLAGHER_FAIL_IF_ERROR(res, true, "Failed creating Gallagher application."); + DropField(); + // Create credential files + // Don't need to set keys here, they're generated automatically + res = createGallagherCredentialsFile(&dctx, sitekey, aid, &creds, verbose); + HF_GALLAGHER_FAIL_IF_ERROR(res, true, "Failed creating Gallagher credential file."); + DropField(); + + // Update card application directory + DesfireSetKeyNoClear(&dctx, keyNum, algo, key); + DesfireSetKdf(&dctx, MFDES_KDF_ALGO_NONE, NULL, 0); + res = updateGallagherCAD(&dctx, sitekey, aid, &creds, verbose); + HF_GALLAGHER_FAIL_IF_ERROR(res, true, "Failed updating Gallagher card application directory."); + DropField(); + + DropField(); PrintAndLogEx(SUCCESS, "Done"); PrintAndLogEx(HINT, "Hint: try " _YELLOW_("`hf gallagher reader`") " to verify"); return PM3_ENOTIMPL; diff --git a/client/src/cmdhfgallagher.h b/client/src/cmdhfgallagher.h index ba1073fe2..0baef92f7 100644 --- a/client/src/cmdhfgallagher.h +++ b/client/src/cmdhfgallagher.h @@ -13,9 +13,24 @@ #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 GallagherDiversifyKey(uint8_t *sitekey, uint8_t *uid, uint8_t uidLen, uint8_t keyNum, uint32_t aid, uint8_t *keyOut); + #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; }