From 8f577ad9630752ce2a8237ec55a8d2d221fc180e Mon Sep 17 00:00:00 2001 From: nvx Date: Sat, 30 Dec 2023 19:26:23 +1000 Subject: [PATCH] Improved magic detection Magic detection no longer stops when a single type is found as cards may support multiple types of magic, so all detected types will be reported now. GDM/USCUID chips are now detected when GDM magic auth is disabled but magic WUP (40 or 20) is enabled. Gen2/CUID/DirectWrite is now detected when default keys and ACLs are used by attempting to write to block 0 but aborting before actually completing the write. --- CHANGELOG.md | 1 + armsrc/mifarecmd.c | 198 ++++++++++++++++++++------------- client/src/mifare/mifarehost.c | 89 ++++++++------- include/protocols.h | 6 +- 4 files changed, 173 insertions(+), 121 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1acd29e49..2c447b941 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] + - Changed `hf 14a info` magic detection to better detect different GDM/USCUID configurations and improved Gen2/CUID detection when default keys are used (@nvx) - Changed `hf_cardhopper` standalone mode to allow running over the internal Proxmark3 USB-CDC serial port (@nvx) - Fixed CLI prompt - Update connection type prompt after running `hw connect` (@wh201906) - Changed `uart_receive()` - Check if TCP connection is lost (@wh201906) diff --git a/armsrc/mifarecmd.c b/armsrc/mifarecmd.c index 87b00c6b2..afbe8610a 100644 --- a/armsrc/mifarecmd.c +++ b/armsrc/mifarecmd.c @@ -1067,13 +1067,13 @@ void MifareNested(uint8_t blockNo, uint8_t keyType, uint8_t targetBlockNo, uint8 dmax = MAX(dmax, i); } else { // allow some slack for proper timing - delta_time = auth2_time - auth1_time + 32; + delta_time = auth2_time - auth1_time + 32; } if (g_dbglevel >= DBG_DEBUG) Dbprintf("Nested: calibrating... ntdist=%d", i); } else { unsuccessful_tries++; // card isn't vulnerable to nested attack (random numbers are not predictable) - if (unsuccessful_tries > NESTED_MAX_TRIES) { + if (unsuccessful_tries > NESTED_MAX_TRIES) { isOK = PM3_EFAILED; } } @@ -2208,6 +2208,9 @@ int MifareECardLoad(uint8_t sectorcnt, uint8_t keytype) { static uint8_t wupC1[] = { MIFARE_MAGICWUPC1 }; static uint8_t wupC2[] = { MIFARE_MAGICWUPC2 }; static uint8_t wipeC[] = { MIFARE_MAGICWIPEC }; +// GDM alt magic wakeup +static uint8_t wupGDM1[] = { MIFARE_MAGIC_GDM_WUPC1 }; +//static uint8_t wupGDM2[] = { MIFARE_MAGIC_GDM_WUPC2 }; void MifareCSetBlock(uint32_t arg0, uint32_t arg1, uint8_t *datain) { @@ -2413,15 +2416,17 @@ void MifareCGetBlock(uint32_t arg0, uint32_t arg1, uint8_t *datain) { void MifareCIdent(bool is_mfc) { // variables - uint8_t isGen = 0; uint8_t rec[1] = {0x00}; uint8_t recpar[1] = {0x00}; uint8_t rats[4] = {ISO14443A_CMD_RATS, 0x80, 0x31, 0x73}; uint8_t rdblf0[4] = {ISO14443A_CMD_READBLOCK, 0xF0, 0x8D, 0x5f}; uint8_t rdbl00[4] = {ISO14443A_CMD_READBLOCK, 0x00, 0x02, 0xa8}; - uint8_t gen4gdm[4] = {MIFARE_MAGIC_GDM_AUTH_KEY, 0x00, 0x6C, 0x92}; + uint8_t gen4gdmAuth[4] = {MIFARE_MAGIC_GDM_AUTH_KEY, 0x00, 0x6C, 0x92}; + uint8_t gen4gdmGetConf[4] = {MIFARE_MAGIC_GDM_READ_CFG, 0x00, 0x39, 0xF7}; uint8_t gen4GetConf[8] = {GEN_4GTU_CMD, 0x00, 0x00, 0x00, 0x00, GEN_4GTU_GETCNF, 0, 0}; uint8_t superGen1[9] = {0x0A, 0x00, 0x00, 0xA6, 0xB0, 0x00, 0x10, 0x14, 0x1D}; + bool isGen2 = false; + bool isGen1AGdm = false; uint8_t *par = BigBuf_malloc(MAX_PARITY_SIZE); uint8_t *buf = BigBuf_malloc(PM3_CMD_DATA_SIZE); @@ -2432,20 +2437,27 @@ void MifareCIdent(bool is_mfc) { memset(uid, 0x00, 10); uint32_t cuid = 0; - uint8_t data[1] = {0x00}; + size_t data_off = 0; + uint8_t data[16] = {0x00}; iso14443a_setup(FPGA_HF_ISO14443A_READER_LISTEN); // Generation 1 test ReaderTransmitBitsPar(wupC1, 7, NULL, NULL); if (ReaderReceive(rec, recpar) && (rec[0] == 0x0a)) { + uint8_t isGen = MAGIC_GEN_1A; ReaderTransmit(wupC2, sizeof(wupC2), NULL); if (!ReaderReceive(rec, recpar) || (rec[0] != 0x0a)) { isGen = MAGIC_GEN_1B; - goto OUT; }; - isGen = MAGIC_GEN_1A; - goto OUT; + data[data_off++] = isGen; + + // check for GDM config + ReaderTransmit(gen4gdmGetConf, sizeof(gen4gdmGetConf), NULL); + int res = ReaderReceive(buf, par); + if (res > 1) { + isGen1AGdm = true; + } } // reset card @@ -2453,6 +2465,11 @@ void MifareCIdent(bool is_mfc) { SpinDelay(40); iso14443a_setup(FPGA_HF_ISO14443A_READER_LISTEN); + // reset card + FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); + SpinDelay(40); + iso14443a_setup(FPGA_HF_ISO14443A_READER_LISTEN); + int res = iso14443a_select_card(uid, NULL, &cuid, true, 0, true); if (res == 2) { // Check for Magic Gen4 GTU with default password: @@ -2461,8 +2478,7 @@ void MifareCIdent(bool is_mfc) { ReaderTransmit(gen4GetConf, sizeof(gen4GetConf), NULL); res = ReaderReceive(buf, par); if (res == 32 || res == 34) { - isGen = MAGIC_GEN_4GTU; - goto OUT; + data[data_off++] = MAGIC_GEN_4GTU; } } @@ -2474,8 +2490,7 @@ void MifareCIdent(bool is_mfc) { res = iso14443a_select_card(uid, NULL, &cuid, true, 0, true); if (res == 2) { if (cuid == 0xAA55C396) { - isGen = MAGIC_GEN_UNFUSED; - goto OUT; + data[data_off++] = MAGIC_GEN_UNFUSED; } ReaderTransmit(rats, sizeof(rats), NULL); @@ -2485,7 +2500,7 @@ void MifareCIdent(bool is_mfc) { ReaderTransmit(superGen1, sizeof(superGen1), NULL); res = ReaderReceive(buf, par); if (res == 22) { - isGen = MAGIC_SUPER_GEN1; + uint8_t isGen = MAGIC_SUPER_GEN1; // check for super card gen2 // not available after RATS, reset card before executing @@ -2500,42 +2515,37 @@ void MifareCIdent(bool is_mfc) { isGen = MAGIC_SUPER_GEN2; } - goto OUT; + data[data_off++] = isGen; } - // test for some MFC gen2 + if (memcmp(buf, "\x09\x78\x00\x91\x02\xDA\xBC\x19\x10\xF0\x05", 11) == 0) { - isGen = MAGIC_GEN_2; - goto OUT; - } - // test for some MFC 7b gen2 - if (memcmp(buf, "\x0D\x78\x00\x71\x02\x88\x49\xA1\x30\x20\x15\x06\x08\x56\x3D", 15) == 0) { - isGen = MAGIC_GEN_2; - goto OUT; - } - // test for Ultralight magic gen2 - if (memcmp(buf, "\x0A\x78\x00\x81\x02\xDB\xA0\xC1\x19\x40\x2A\xB5", 12) == 0) { - isGen = MAGIC_GEN_2; - goto OUT; - } - // test for Ultralight EV1 magic gen2 - if (memcmp(buf, "\x85\x00\x00\xA0\x00\x00\x0A\xC3\x00\x04\x03\x01\x01\x00\x0B\x03\x41\xDF", 18) == 0) { - isGen = MAGIC_GEN_2; - goto OUT; - } - // test for some other Ultralight EV1 magic gen2 - if (memcmp(buf, "\x85\x00\x00\xA0\x0A\x00\x0A\xC3\x00\x04\x03\x01\x01\x00\x0B\x03\x16\xD7", 18) == 0) { - isGen = MAGIC_GEN_2; - goto OUT; - } - // test for some other Ultralight magic gen2 - if (memcmp(buf, "\x85\x00\x00\xA0\x0A\x00\x0A\xB0\x00\x00\x00\x00\x00\x00\x00\x00\x18\x4D", 18) == 0) { - isGen = MAGIC_GEN_2; - goto OUT; - } - // test for NTAG213 magic gen2 - if (memcmp(buf, "\x85\x00\x00\xA0\x00\x00\x0A\xA5\x00\x04\x04\x02\x01\x00\x0F\x03\x79\x0C", 18) == 0) { - isGen = MAGIC_GEN_2; - goto OUT; + // test for some MFC gen2 + isGen2 = true; + data[data_off++] = MAGIC_GEN_2; + } else if (memcmp(buf, "\x0D\x78\x00\x71\x02\x88\x49\xA1\x30\x20\x15\x06\x08\x56\x3D", 15) == 0) { + // test for some MFC 7b gen2 + isGen2 = true; + data[data_off++] = MAGIC_GEN_2; + } else if (memcmp(buf, "\x0A\x78\x00\x81\x02\xDB\xA0\xC1\x19\x40\x2A\xB5", 12) == 0) { + // test for Ultralight magic gen2 + isGen2 = true; + data[data_off++] = MAGIC_GEN_2; + } else if (memcmp(buf, "\x85\x00\x00\xA0\x00\x00\x0A\xC3\x00\x04\x03\x01\x01\x00\x0B\x03\x41\xDF", 18) == 0) { + // test for Ultralight EV1 magic gen2 + isGen2 = true; + data[data_off++] = MAGIC_GEN_2; + } else if (memcmp(buf, "\x85\x00\x00\xA0\x0A\x00\x0A\xC3\x00\x04\x03\x01\x01\x00\x0B\x03\x16\xD7", 18) == 0) { + // test for some other Ultralight EV1 magic gen2 + isGen2 = true; + data[data_off++] = MAGIC_GEN_2; + } else if (memcmp(buf, "\x85\x00\x00\xA0\x0A\x00\x0A\xB0\x00\x00\x00\x00\x00\x00\x00\x00\x18\x4D", 18) == 0) { + // test for some other Ultralight magic gen2 + isGen2 = true; + data[data_off++] = MAGIC_GEN_2; + } else if (memcmp(buf, "\x85\x00\x00\xA0\x00\x00\x0A\xA5\x00\x04\x04\x02\x01\x00\x0F\x03\x79\x0C", 18) == 0) { + // test for NTAG213 magic gen2 + isGen2 = true; + data[data_off++] = MAGIC_GEN_2; } } @@ -2549,10 +2559,35 @@ void MifareCIdent(bool is_mfc) { ReaderTransmit(rdblf0, sizeof(rdblf0), NULL); res = ReaderReceive(buf, par); if (res == 18) { - isGen = MAGIC_NTAG21X; + data[data_off++] = MAGIC_NTAG21X; } } } else { + if (!isGen2) { + // CUID (with default sector 0 B key) test + // regular cards will NAK the WRITEBLOCK(0) command, while DirectWrite will ACK it + // if we do get an ACK, we immediately abort to ensure nothing is ever actually written + FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); + SpinDelay(40); + iso14443a_setup(FPGA_HF_ISO14443A_READER_LISTEN); + res = iso14443a_select_card(uid, NULL, &cuid, true, 0, true); + if (res == 2) { + struct Crypto1State mpcs = {0, 0}; + struct Crypto1State *pcs; + pcs = &mpcs; + if (mifare_classic_authex(pcs, cuid, 0, MF_KEY_B, 0xFFFFFFFFFFFF, AUTH_FIRST, NULL, NULL) == 0) { + uint8_t receivedAnswer[MAX_MIFARE_FRAME_SIZE] = {0x00}; + uint8_t receivedAnswerPar[MAX_MIFARE_PARITY_SIZE] = {0x00}; + if ((mifare_sendcmd_short(pcs, 1, ISO14443A_CMD_WRITEBLOCK, 0, receivedAnswer, receivedAnswerPar, NULL) == 1) && (receivedAnswer[0] == 0x0A)) { + data[data_off++] = MAGIC_GEN_2; + // turn off immediately to ensure nothing ever accidentally writes to the block + FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); + } + } + crypto1_deinit(pcs); + } + } + // magic MFC Gen3 test 1 FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); SpinDelay(40); @@ -2562,48 +2597,51 @@ void MifareCIdent(bool is_mfc) { ReaderTransmit(rdbl00, sizeof(rdbl00), NULL); res = ReaderReceive(buf, par); if (res == 18) { - isGen = MAGIC_GEN_3; + data[data_off++] = MAGIC_GEN_3; } } - // magic MFC Gen4 GDM test - if (isGen != MAGIC_GEN_3) { - FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); - SpinDelay(40); - iso14443a_setup(FPGA_HF_ISO14443A_READER_LISTEN); - res = iso14443a_select_card(uid, NULL, &cuid, true, 0, true); - if (res == 2) { - ReaderTransmit(gen4gdm, sizeof(gen4gdm), NULL); - res = ReaderReceive(buf, par); - if (res == 4) { - isGen = MAGIC_GEN_4GDM; - } - } - - if (isGen != MAGIC_GEN_4GDM) { - FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); - SpinDelay(40); - iso14443a_setup(FPGA_HF_ISO14443A_READER_LISTEN); - res = iso14443a_select_card(uid, NULL, &cuid, true, 0, true); - if (res == 2) { - struct Crypto1State mpcs = {0, 0}; - struct Crypto1State *pcs; - pcs = &mpcs; - if (mifare_classic_authex(pcs, cuid, 68, MF_KEY_B, 0x707B11FC1481, AUTH_FIRST, NULL, NULL) == 0) { - isGen = MAGIC_QL88; - } - crypto1_deinit(pcs); - } + // magic MFC Gen4 GDM magic auth test + FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); + SpinDelay(40); + iso14443a_setup(FPGA_HF_ISO14443A_READER_LISTEN); + res = iso14443a_select_card(uid, NULL, &cuid, true, 0, true); + if (res == 2) { + ReaderTransmit(gen4gdmAuth, sizeof(gen4gdmAuth), NULL); + res = ReaderReceive(buf, par); + if (res == 4) { + data[data_off++] = MAGIC_GDM_AUTH; } } + // QL88 test + FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); + SpinDelay(40); + iso14443a_setup(FPGA_HF_ISO14443A_READER_LISTEN); + res = iso14443a_select_card(uid, NULL, &cuid, true, 0, true); + if (res == 2) { + struct Crypto1State mpcs = {0, 0}; + struct Crypto1State *pcs; + pcs = &mpcs; + if (mifare_classic_authex(pcs, cuid, 68, MF_KEY_B, 0x707B11FC1481, AUTH_FIRST, NULL, NULL) == 0) { + data[data_off++] = MAGIC_QL88; + } + crypto1_deinit(pcs); + } } }; -OUT: + if (isGen1AGdm == true) { + data[data_off++] = MAGIC_GDM_WUP_40; + } - data[0] = isGen; - reply_ng(CMD_HF_MIFARE_CIDENT, PM3_SUCCESS, data, sizeof(data)); + // GEM alt magic wakeup (20) + ReaderTransmitBitsPar(wupGDM1, 7, NULL, NULL); + if (ReaderReceive(rec, recpar) && (rec[0] == 0x0a)) { + data[data_off++] = MAGIC_GDM_WUP_20; + } + + reply_ng(CMD_HF_MIFARE_CIDENT, PM3_SUCCESS, data, data_off); // turns off OnSuccessMagic(); BigBuf_free(); diff --git a/client/src/mifare/mifarehost.c b/client/src/mifare/mifarehost.c index 96f099d8f..a4fa8f13d 100644 --- a/client/src/mifare/mifarehost.c +++ b/client/src/mifare/mifarehost.c @@ -612,7 +612,7 @@ int mfStaticNested(uint8_t blockNo, uint8_t keyType, uint8_t *key, uint8_t trgBl uint32_t uid; StateList_t statelists[2]; - struct Crypto1State *p1, * p2, * p3, * p4; + struct Crypto1State *p1, *p2, *p3, *p4; struct { uint8_t block; @@ -1392,54 +1392,63 @@ int detect_classic_static_encrypted_nonce(uint8_t block_no, uint8_t key_type, ui /* try to see if card responses to "Chinese magic backdoor" commands. */ int detect_mf_magic(bool is_mfc) { - uint8_t isGeneration = 0; + uint8_t isMagic = 0; PacketResponseNG resp; clearCommandBuffer(); uint8_t payload[] = { is_mfc }; SendCommandNG(CMD_HF_MIFARE_CIDENT, payload, sizeof(payload)); if (WaitForResponseTimeout(CMD_HF_MIFARE_CIDENT, &resp, 1500)) { - if (resp.status == PM3_SUCCESS) { - isGeneration = resp.data.asBytes[0]; + if (resp.status != PM3_SUCCESS) { + return 0; } } - switch (isGeneration) { - case MAGIC_GEN_1A: - PrintAndLogEx(SUCCESS, "Magic capabilities : " _GREEN_("Gen 1a")); - break; - case MAGIC_GEN_1B: - PrintAndLogEx(SUCCESS, "Magic capabilities : " _GREEN_("Gen 1b")); - break; - case MAGIC_GEN_2: - PrintAndLogEx(SUCCESS, "Magic capabilities : " _GREEN_("Gen 2 / CUID")); - break; - case MAGIC_GEN_3: - PrintAndLogEx(SUCCESS, "Magic capabilities : possibly " _GREEN_("Gen 3 / APDU")); - break; - case MAGIC_GEN_4GTU: - PrintAndLogEx(SUCCESS, "Magic capabilities : " _GREEN_("Gen 4 GTU")); - break; - case MAGIC_GEN_4GDM: - PrintAndLogEx(SUCCESS, "Magic capabilities : " _GREEN_("Gen 4 GDM")); - break; - case MAGIC_GEN_UNFUSED: - PrintAndLogEx(SUCCESS, "Magic capabilities : " _GREEN_("Write Once / FUID")); - break; - case MAGIC_SUPER_GEN1: - PrintAndLogEx(SUCCESS, "Magic capabilities : " _GREEN_("Super card (") _CYAN_("Gen 1") _GREEN_(")")); - break; - case MAGIC_SUPER_GEN2: - PrintAndLogEx(SUCCESS, "Magic capabilities : " _GREEN_("Super card (") _CYAN_("Gen 2") _GREEN_(")")); - break; - case MAGIC_NTAG21X: - PrintAndLogEx(SUCCESS, "Magic capabilities : " _GREEN_("NTAG21x")); - break; - case MAGIC_QL88: - PrintAndLogEx(SUCCESS, "Magic capabilities : " _GREEN_("QL88")); - default: - break; + for (size_t i = 0; i < resp.length; i++) { + isMagic = 1; + switch (resp.data.asBytes[i]) { + case MAGIC_GEN_1A: + PrintAndLogEx(SUCCESS, "Magic capabilities : " _GREEN_("Gen 1a")); + break; + case MAGIC_GEN_1B: + PrintAndLogEx(SUCCESS, "Magic capabilities : " _GREEN_("Gen 1b")); + break; + case MAGIC_GEN_2: + PrintAndLogEx(SUCCESS, "Magic capabilities : " _GREEN_("Gen 2 / CUID")); + break; + case MAGIC_GEN_3: + PrintAndLogEx(SUCCESS, "Magic capabilities : possibly " _GREEN_("Gen 3 / APDU")); + break; + case MAGIC_GEN_4GTU: + PrintAndLogEx(SUCCESS, "Magic capabilities : " _GREEN_("Gen 4 GTU")); + break; + case MAGIC_GDM_AUTH: + PrintAndLogEx(SUCCESS, "Magic capabilities : " _GREEN_("Gen 4 GDM / USCUID (Magic Auth)")); + break; + case MAGIC_GDM_WUP_20: + PrintAndLogEx(SUCCESS, "Magic capabilities : " _GREEN_("Gen 4 GDM / USCUID (Alt Magic Wakeup)")); + break; + case MAGIC_GDM_WUP_40: + PrintAndLogEx(SUCCESS, "Magic capabilities : " _GREEN_("Gen 4 GDM / USCUID (Gen1 Magic Wakeup)")); + break; + case MAGIC_GEN_UNFUSED: + PrintAndLogEx(SUCCESS, "Magic capabilities : " _GREEN_("Write Once / FUID")); + break; + case MAGIC_SUPER_GEN1: + PrintAndLogEx(SUCCESS, "Magic capabilities : " _GREEN_("Super card (") _CYAN_("Gen 1") _GREEN_(")")); + break; + case MAGIC_SUPER_GEN2: + PrintAndLogEx(SUCCESS, "Magic capabilities : " _GREEN_("Super card (") _CYAN_("Gen 2") _GREEN_(")")); + break; + case MAGIC_NTAG21X: + PrintAndLogEx(SUCCESS, "Magic capabilities : " _GREEN_("NTAG21x")); + break; + case MAGIC_QL88: + PrintAndLogEx(SUCCESS, "Magic capabilities : " _GREEN_("QL88")); + default: + break; + } } - return isGeneration; + return isMagic; } bool detect_mfc_ev1_signature(void) { diff --git a/include/protocols.h b/include/protocols.h index 1c8dac3de..d17952fa7 100644 --- a/include/protocols.h +++ b/include/protocols.h @@ -191,6 +191,8 @@ ISO 7816-4 Basic interindustry commands. For command APDU's. #define MIFARE_CMD_RESTORE 0xC2 #define MIFARE_CMD_TRANSFER 0xB0 +#define MIFARE_MAGIC_GDM_WUPC1 0x20 +#define MIFARE_MAGIC_GDM_WUPC2 0x23 #define MIFARE_MAGIC_GDM_AUTH_KEY 0x80 #define MIFARE_MAGIC_GDM_READBLOCK 0x38 #define MIFARE_MAGIC_GDM_WRITEBLOCK 0xA8 @@ -264,8 +266,10 @@ ISO 7816-4 Basic interindustry commands. For command APDU's. #define MAGIC_NTAG21X 8 #define MAGIC_GEN_3 9 #define MAGIC_GEN_4GTU 10 -#define MAGIC_GEN_4GDM 11 +#define MAGIC_GDM_AUTH 11 #define MAGIC_QL88 12 +#define MAGIC_GDM_WUP_20 13 +#define MAGIC_GDM_WUP_40 14 // Commands for configuration of Gen4 GTU cards.