diff --git a/armsrc/appmain.c b/armsrc/appmain.c index a2ad9b86b..dc7a8c8b1 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -1864,12 +1864,12 @@ static void PacketReceived(PacketCommandNG *packet) { MifareUReadBlock((mful_readblock_t *)packet->data.asBytes); break; } - case CMD_HF_MIFAREUC_AUTH: { - MifareUC_Auth(packet->oldarg[0], packet->data.asBytes); + case CMD_HF_MIFAREU3P_AUTH: { + MifareU3PassAuth((mful_3passauth_t *)packet->data.asBytes); break; } - case CMD_HF_MIFAREULAES_AUTH: { - MifareUL_AES_Auth((mfulaes_keys_t *)packet->data.asBytes); + case CMD_HF_MIFAREU3P_CHKKEY: { + MifareU3PassChkKeys((mful_3passchk_t *)packet->data.asBytes); break; } case CMD_HF_MIFAREU_READCARD: { diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index 7b486152f..8e7d9b0d4 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -3043,7 +3043,7 @@ static void iso14a_set_ATS_times(const uint8_t *ats) { } -static int GetATQA(uint8_t *resp, uint16_t resp_len, uint8_t *resp_par, const iso14a_polling_parameters_t *polling_parameters) { +int GetATQA(uint8_t *resp, uint16_t resp_len, uint8_t *resp_par, const iso14a_polling_parameters_t *polling_parameters) { #define RETRY_TIMEOUT 10 uint32_t save_iso14a_timeout = iso14a_get_timeout(); diff --git a/armsrc/iso14443a.h b/armsrc/iso14443a.h index c9efbf118..f00f686f8 100644 --- a/armsrc/iso14443a.h +++ b/armsrc/iso14443a.h @@ -161,6 +161,7 @@ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data, bool GetIso14443aCommandFromReader(uint8_t *received, uint16_t received_maxlen, uint8_t *par, int *len); void iso14443a_antifuzz(uint32_t flags); +int GetATQA(uint8_t *resp, uint16_t resp_len, uint8_t *resp_par, const iso14a_polling_parameters_t *polling_parameters); void ReaderIso14443a(PacketCommandNG *c); void ReaderTransmit(const uint8_t *frame, uint16_t len, uint32_t *timing); void ReaderTransmitBitsPar(const uint8_t *frame, uint16_t bits, uint8_t *par, uint32_t *timing); diff --git a/armsrc/mifarecmd.c b/armsrc/mifarecmd.c index 2ea1a9bc9..d15637d1d 100644 --- a/armsrc/mifarecmd.c +++ b/armsrc/mifarecmd.c @@ -256,68 +256,189 @@ void MifareReadSector(uint8_t sector_no, uint8_t key_type, uint8_t *key) { reply_old(CMD_ACK, retval == PM3_SUCCESS, 0, 0, outbuf, 16 * num_blocks); } -void MifareUC_Auth(uint8_t arg0, uint8_t *keybytes) { +static int MifareUFastRead0(void) { + uint8_t resp[18] = { 0 }; // 4 pages + crc + uint8_t resp_par[1] = {0}; - bool turnOffField = (arg0 == 1); - - LED_A_ON(); - LED_B_OFF(); - LED_C_OFF(); - - iso14443a_setup(FPGA_HF_ISO14443A_READER_LISTEN); - - clear_trace(); - set_tracing(true); - - if (iso14443a_select_card(NULL, NULL, NULL, true, 0, true) == 0) { - if (g_dbglevel >= DBG_ERROR) Dbprintf("Can't select card"); - OnError(0); - return; - }; - - if (mifare_ultra_auth(keybytes) == 0) { - if (g_dbglevel >= DBG_ERROR) Dbprintf("Authentication failed"); - OnError(1); - return; + if (GetATQA(resp, sizeof(resp), resp_par, NULL) == 0) { + return 0; } + uint8_t read0[4] = {0x30, 0x00, 0x00, 0x00}; + AddCrc14A(read0, 2); + ReaderTransmit(read0, sizeof(read0), NULL); - if (turnOffField) { - FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); - LEDsoff(); + // Receive 4 pages, 2 Byte CRC + if (ReaderReceive(resp, sizeof(resp), resp_par) != 18) { + return 0; } - reply_mix(CMD_ACK, 1, 0, 0, 0, 0); + return 1; } -void MifareUL_AES_Auth(mfulaes_keys_t *packet) { +#define MIFAREU3P_KEY_SIZE 16 +#define MIFAREULC_KEY_INDEX 3 + +// checks one key. +// fast select, tries 5 times to select +// +// return: +// 4 = failed to select +// 3 = failed auth +// 2 = timeout +// 1 = failed auth +// 0 = correct + +static uint8_t chkKey3Pass(uint8_t keyno, uint8_t *keybytes, uint32_t *auths, bool check_answer) { + + uint8_t i = 0, res = 2; + bool selected = false; + while (i < 5) { + if (MifareUFastRead0() == 0) { + ++i; + continue; + } + selected = true; + if (g_dbglevel >= DBG_EXTENDED) Dbhexdump(MIFAREU3P_KEY_SIZE, keybytes, false); + if (keyno == MIFAREULC_KEY_INDEX) { + res = mifare_ultra_3des_auth(keybytes, check_answer) == 1 ? 0 : 1; // 0 = correct, 1 = failed auth + } else { + bool use_schann = false; + res = mifare_ultra_aes_auth(keyno, keybytes, use_schann, check_answer) == 1 ? 0 : 1; // 0 = correct, 1 = failed auth + } + (*auths)++; + break; + } + if (selected == false) { + Dbprintf("chkKey: Failed at fast selecting the card!"); + res = 4; + } + return res; +} + +void MifareU3PassAuth(mful_3passauth_t *packet) { LED_A_ON(); LED_B_OFF(); LED_C_OFF(); + uint32_t auths = 0; + int res = PM3_ESOFT; + struct rp { + uint32_t auths; + uint32_t ticks; + } PACKED rpayload; iso14443a_setup(FPGA_HF_ISO14443A_READER_LISTEN); clear_trace(); set_tracing(true); - if (iso14443a_select_card(NULL, NULL, NULL, true, 0, true) == 0) { - if (g_dbglevel >= DBG_ERROR) Dbprintf("Can't select card"); - OnErrorNG(CMD_HF_MIFAREULAES_AUTH, PM3_ESOFT); - return; - }; + uint32_t ti = GetTickCount(); - if (mifare_ultra_aes_auth(packet->keyno, packet->key, packet->use_schann) == 0) { - if (g_dbglevel >= DBG_ERROR) Dbprintf("Authentication failed"); - OnErrorNG(CMD_HF_MIFAREULAES_AUTH, PM3_ESOFT); - return; + for (uint16_t r = 0; r < 1 + packet->retries; r++) { + WDT_HIT(); + if (chkKey3Pass(packet->keyno, packet->key, &auths, packet->check_answer) == 0) { + res = PM3_SUCCESS; + goto out; + } } +out: + rpayload.auths = auths; + rpayload.ticks = GetTickCountDelta(ti); + if (g_dbglevel >= DBG_ERROR) { + if (res != PM3_SUCCESS) { + Dbprintf("Authentication failed"); + } + } + reply_ng(CMD_HF_MIFAREU3P_AUTH, res, (uint8_t *)&rpayload, sizeof(rpayload)); if (packet->turn_off_field) { FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); LEDsoff(); } - reply_ng(CMD_HF_MIFAREULAES_AUTH, PM3_SUCCESS, NULL, 0); } +void MifareU3PassChkKeys(mful_3passchk_t *packet) { + static uint8_t foundkeys = 0; + uint8_t keysize = packet->segment != 4 ? MIFAREU3P_KEY_SIZE / 4 : MIFAREU3P_KEY_SIZE; + + int oldbg = g_dbglevel; + int res = PM3_ESOFT; + struct rp { + uint32_t auths; + uint32_t ticks; + uint8_t key[16]; + } PACKED rpayload; + uint32_t auths = 0; + + iso14443a_setup(FPGA_HF_ISO14443A_READER_LISTEN); + + LEDsoff(); + LED_A_ON(); + + uint32_t ti = GetTickCount(); + if (packet->firstchunk) { + clear_trace(); + set_tracing(false); + + if (iso14443a_select_card(NULL, NULL, NULL, true, 0, true) == 0) { + if (g_dbglevel >= DBG_ERROR) Dbprintf("ChkKeys_fast: Can't select card (ALL)"); + goto out; + } + + CHK_TIMEOUT(); + } + + // clear debug level. We are expecting lots of authentication failures... + g_dbglevel = DBG_NONE; + + for (uint16_t i = 0; i < packet->nkeys; ++i) { + + // Allow button press / usb cmd to interrupt device + if (BUTTON_PRESS() || data_available()) { + goto out; + } + + WDT_HIT(); + uint8_t fullkeybytes[MIFAREU3P_KEY_SIZE] = {0}; + memcpy(fullkeybytes, packet->ref_key, MIFAREU3P_KEY_SIZE); + if (keysize == MIFAREU3P_KEY_SIZE) { + if (packet->xor_ref_key) { + for (uint8_t j = 0; j < keysize; ++j) { + fullkeybytes[j] ^= packet->data[j + (i * keysize)]; + } + } else { + memcpy(fullkeybytes, packet->data + (i * keysize), keysize); + } + } else { + if (packet->xor_ref_key) { + for (uint8_t j = 0; j < keysize; ++j) { + fullkeybytes[j + (packet->segment * keysize)] ^= packet->data[j + (i * keysize)]; + } + } else { + memcpy(fullkeybytes + (packet->segment * keysize), packet->data + (i * keysize), keysize); + } + } + if (chkKey3Pass(packet->key_index, fullkeybytes, &auths, packet->check_answer) == 0) { + foundkeys++; + memcpy(rpayload.key, fullkeybytes, MIFAREU3P_KEY_SIZE); + res = PM3_SUCCESS; + goto out; + } + } +out: + rpayload.auths = auths; + rpayload.ticks = GetTickCountDelta(ti); + reply_ng(CMD_HF_MIFAREU3P_CHKKEY, res, (uint8_t *)&rpayload, sizeof(rpayload)); + LEDsoff(); + if (foundkeys || packet->lastchunk) { + set_tracing(false); + FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); + BigBuf_free(); + BigBuf_Clear_ext(false); + } + g_dbglevel = oldbg; +} + + // Arg0 = BlockNo, // Arg1 = UsePwd bool // datain = PWD bytes, @@ -344,7 +465,7 @@ void MifareUReadBlock(mful_readblock_t *packet) { // UL-C authentication if (useCKey) { - if (mifare_ultra_auth(packet->key) == 0) { + if (mifare_ultra_3des_auth(packet->key, true) == 0) { OnErrorNG(CMD_HF_MIFAREU_READBL, PM3_ESOFT); return; } @@ -352,7 +473,7 @@ void MifareUReadBlock(mful_readblock_t *packet) { // UL-AES authentication, hardcode to use keyno 0 if (useAESKey) { - if (mifare_ultra_aes_auth(0, packet->key, packet->use_schann) == 0) { + if (mifare_ultra_aes_auth(0, packet->key, packet->use_schann, true) == 0) { OnErrorNG(CMD_HF_MIFAREU_READBL, PM3_ESOFT); return; } @@ -427,7 +548,7 @@ void MifareUReadCard(mful_readblock_t *packet) { // UL-C authentication if (useCKey) { - if (mifare_ultra_auth(packet->key) == 0) { + if (mifare_ultra_3des_auth(packet->key, true) == 0) { OnErrorNG(CMD_HF_MIFAREU_READCARD, PM3_ESOFT); return; } @@ -435,7 +556,7 @@ void MifareUReadCard(mful_readblock_t *packet) { // UL-AES authentication if (useAESKey) { - if (mifare_ultra_aes_auth(0, packet->key, schann) == 0) { + if (mifare_ultra_aes_auth(0, packet->key, schann, true) == 0) { OnErrorNG(CMD_HF_MIFAREU_READCARD, PM3_ESOFT); return; } @@ -615,7 +736,7 @@ static void MifareUWriteBlockEx(mful_writeblock_t *packet, bool reply) { // UL-C authentication if (useCKey) { - if (mifare_ultra_auth(packet->key) == 0) { + if (mifare_ultra_3des_auth(packet->key, true) == 0) { OnErrorNG(CMD_HF_MIFAREU_READBL, PM3_ESOFT); return; } @@ -623,7 +744,7 @@ static void MifareUWriteBlockEx(mful_writeblock_t *packet, bool reply) { // UL-AES authentication if (useAESKey) { - if (mifare_ultra_aes_auth(0, packet->key, packet->use_schann) == 0) { + if (mifare_ultra_aes_auth(0, packet->key, packet->use_schann, true) == 0) { OnErrorNG(CMD_HF_MIFAREU_READBL, PM3_ESOFT); return; } @@ -699,7 +820,15 @@ void MifareUWriteBlockCompat(mful_writeblock_t *packet) { // UL-C authentication if (useCKey) { - if (mifare_ultra_auth(packet->key) == 0) { + if (mifare_ultra_3des_auth(packet->key, true) == 0) { + OnErrorNG(CMD_HF_MIFAREU_WRITEBL_COMPAT, PM3_ESOFT); + return; + } + } + + // UL-AES authentication + if (useAESKey) { + if (mifare_ultra_aes_auth(0, packet->key, packet->use_schann, true) == 0) { OnErrorNG(CMD_HF_MIFAREU_WRITEBL_COMPAT, PM3_ESOFT); return; } diff --git a/armsrc/mifarecmd.h b/armsrc/mifarecmd.h index 721a5cce3..31a58a566 100644 --- a/armsrc/mifarecmd.h +++ b/armsrc/mifarecmd.h @@ -26,14 +26,14 @@ void MifareReadSector(uint8_t sector_no, uint8_t key_type, uint8_t *key); void MifareValue(uint8_t arg0, uint8_t arg1, uint8_t arg2, uint8_t *datain); void MifareUReadBlock(mful_readblock_t *packet); -void MifareUC_Auth(uint8_t arg0, uint8_t *keybytes); - -void MifareUL_AES_Auth(mfulaes_keys_t *packet); void MifareUReadCard(mful_readblock_t *packet); void MifareUWriteBlockCompat(mful_writeblock_t *packet); void MifareUWriteBlock(mful_writeblock_t *packet); +void MifareU3PassAuth(mful_3passauth_t *packet); +void MifareU3PassChkKeys(mful_3passchk_t *packet); + void MifareNested(uint8_t blockNo, uint8_t keyType, uint8_t targetBlockNo, uint8_t targetKeyType, bool calibrate, uint8_t *key); void MifareStaticNested(uint8_t blockNo, uint8_t keyType, uint8_t targetBlockNo, uint8_t targetKeyType, uint8_t *key, uint8_t forceDetectDist); diff --git a/armsrc/mifareutil.c b/armsrc/mifareutil.c index 4f4ad431d..25081c274 100644 --- a/armsrc/mifareutil.c +++ b/armsrc/mifareutil.c @@ -380,12 +380,11 @@ int mifare_ul_ev1_auth(uint8_t *keybytes, uint8_t *pack) { return 1; } -int mifare_ultra_auth(uint8_t *keybytes) { +int mifare_ultra_3des_auth(uint8_t *keybytes, bool check_answer) { /// 3des2k - uint8_t random_a[8] = {1, 1, 1, 1, 1, 1, 1, 1}; + uint8_t random_a[8] = {1, 1, 1, 1, 1, 1, 1, 2}; uint8_t random_b[8] = {0x00}; - uint8_t enc_random_b[8] = {0x00}; uint8_t rnd_ab[16] = {0x00}; uint8_t IV[8] = {0x00}; uint8_t key[16] = {0x00}; @@ -398,77 +397,71 @@ int mifare_ultra_auth(uint8_t *keybytes) { // REQUEST AUTHENTICATION len = mifare_sendcmd_short(NULL, CRYPT_NONE, MIFARE_ULC_AUTH_1, 0x00, resp, sizeof(resp), respPar, NULL); if (len != 11) { - if (g_dbglevel >= DBG_ERROR) Dbprintf("Cmd Error: %02x", resp[0]); + if (g_dbglevel >= DBG_ERROR) Dbprintf("Cmd Error: %02x - expected 11 got " _RED_("%u"), resp[0], len); return 0; } - // tag nonce. - memcpy(enc_random_b, resp + 1, 8); - // decrypt nonce. - tdes_nxp_receive((void *)enc_random_b, (void *)random_b, sizeof(random_b), (const void *)key, IV, 2); + tdes_nxp_receive((void *)(resp + 1), (void *)random_b, sizeof(random_b), (const void *)key, IV, 2); rol(random_b, 8); memcpy(rnd_ab, random_a, 8); memcpy(rnd_ab + 8, random_b, 8); if (g_dbglevel >= DBG_EXTENDED) { - Dbprintf("enc_B: %02x %02x %02x %02x %02x %02x %02x %02x", - enc_random_b[0], enc_random_b[1], enc_random_b[2], enc_random_b[3], enc_random_b[4], enc_random_b[5], enc_random_b[6], enc_random_b[7]); + Dbprintf("enc_B:"); + Dbhexdump(8, resp + 1, false); - Dbprintf(" B: %02x %02x %02x %02x %02x %02x %02x %02x", - random_b[0], random_b[1], random_b[2], random_b[3], random_b[4], random_b[5], random_b[6], random_b[7]); + Dbprintf("B:"); + Dbhexdump(8, random_b, false); - Dbprintf("rnd_ab: %02x %02x %02x %02x %02x %02x %02x %02x", - rnd_ab[0], rnd_ab[1], rnd_ab[2], rnd_ab[3], rnd_ab[4], rnd_ab[5], rnd_ab[6], rnd_ab[7]); - - Dbprintf("rnd_ab: %02x %02x %02x %02x %02x %02x %02x %02x", - rnd_ab[8], rnd_ab[9], rnd_ab[10], rnd_ab[11], rnd_ab[12], rnd_ab[13], rnd_ab[14], rnd_ab[15]); + Dbprintf("rnd_ab:"); + Dbhexdump(16, rnd_ab, false); } - // encrypt out, in, length, key, iv - tdes_nxp_send(rnd_ab, rnd_ab, sizeof(rnd_ab), key, enc_random_b, 2); + // reuse rnd_ab as enc_rnd_ab + if (check_answer) { + tdes_nxp_send((void *)rnd_ab, (void *)rnd_ab, sizeof(rnd_ab), (const void *)key, resp + 1, 2); + } else { + tdes_nxp_send((void *)(rnd_ab + sizeof(random_a)), (void *)(rnd_ab + sizeof(random_a)), sizeof(random_b), (const void *)key, rnd_ab, 2); + // tdes_nxp_send destroys IV, let's restore it + memcpy(rnd_ab, random_a, 8); + } len = mifare_sendcmd(MIFARE_ULC_AUTH_2, rnd_ab, sizeof(rnd_ab), resp, sizeof(resp), respPar, NULL); + if ((!check_answer) && (len == 11)) { + return 1; + } if (len != 11) { - if (g_dbglevel >= DBG_ERROR) Dbprintf("Cmd Error: %02x", resp[0]); + if (g_dbglevel >= DBG_INFO) Dbprintf("Cmd Error: %02x - expected 11 got " _RED_("%u"), resp[0], len); return 0; } - uint8_t enc_resp[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; - uint8_t resp_random_a[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; - memcpy(enc_resp, resp + 1, 8); - + // reuse random_b as resp_random_a // decrypt out, in, length, key, iv - tdes_nxp_receive(enc_resp, resp_random_a, 8, key, enc_random_b, 2); - if (memcmp(resp_random_a, random_a, 8) != 0) { + tdes_nxp_receive(resp + 1, random_b, 8, key, rnd_ab + 8, 2); + if (g_dbglevel >= DBG_EXTENDED) { + Dbprintf("e_AB:"); + Dbhexdump(16, rnd_ab, false); + + Dbprintf("A sent:"); + Dbhexdump(8, random_a, false); + + Dbprintf("A' recv:"); + Dbhexdump(8, random_b, false); + } + rol(random_a, 8); + if (memcmp(random_b, random_a, 8) != 0) { if (g_dbglevel >= DBG_ERROR) Dbprintf("failed authentication"); return 0; } - if (g_dbglevel >= DBG_EXTENDED) { - Dbprintf("e_AB: %02x %02x %02x %02x %02x %02x %02x %02x", - rnd_ab[0], rnd_ab[1], rnd_ab[2], rnd_ab[3], - rnd_ab[4], rnd_ab[5], rnd_ab[6], rnd_ab[7]); - - Dbprintf("e_AB: %02x %02x %02x %02x %02x %02x %02x %02x", - rnd_ab[8], rnd_ab[9], rnd_ab[10], rnd_ab[11], - rnd_ab[12], rnd_ab[13], rnd_ab[14], rnd_ab[15]); - - Dbprintf("a: %02x %02x %02x %02x %02x %02x %02x %02x", - random_a[0], random_a[1], random_a[2], random_a[3], - random_a[4], random_a[5], random_a[6], random_a[7]); - - Dbprintf("b: %02x %02x %02x %02x %02x %02x %02x %02x", - resp_random_a[0], resp_random_a[1], resp_random_a[2], resp_random_a[3], - resp_random_a[4], resp_random_a[5], resp_random_a[6], resp_random_a[7]); - } return 1; } -int mifare_ultra_aes_auth(uint8_t keyno, uint8_t *keybytes, bool schann) { +int mifare_ultra_aes_auth(uint8_t keyno, uint8_t *keybytes, bool schann, bool check_answer) { /// aes-128 - uint8_t random_a[16] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + uint8_t random_a[16] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2}; uint8_t random_b[16] = { 0x00 }; uint8_t rnd_ab[32] = { 0x00 }; uint8_t enc_rnd_ab[32] = { 0x00 }; @@ -513,12 +506,20 @@ int mifare_ultra_aes_auth(uint8_t keyno, uint8_t *keybytes, bool schann) { } // encrypt reader response - memset(IV, 0, 16); mbedtls_aes_setkey_enc(&actx, key, 128); - mbedtls_aes_crypt_cbc(&actx, MBEDTLS_AES_ENCRYPT, sizeof(enc_rnd_ab), IV, rnd_ab, enc_rnd_ab); + if (check_answer) { + memset(IV, 0, 16); + mbedtls_aes_crypt_cbc(&actx, MBEDTLS_AES_ENCRYPT, sizeof(rnd_ab), IV, rnd_ab, enc_rnd_ab); + } else { + memcpy(enc_rnd_ab, rnd_ab, sizeof(rnd_ab)); + mbedtls_aes_crypt_cbc(&actx, MBEDTLS_AES_ENCRYPT, sizeof(random_b), rnd_ab, rnd_ab + sizeof(random_a), enc_rnd_ab + sizeof(random_a)); + } // send & receive len = mifare_sendcmd(MIFARE_ULAES_AUTH_2, enc_rnd_ab, sizeof(enc_rnd_ab), resp, sizeof(resp), respPar, NULL); + if ((! check_answer) && (len == 19)) { + return 1; + } if (len != 19) { if (g_dbglevel >= DBG_INFO) { Dbprintf("Cmd Error: %02x - expected 19 got " _RED_("%u"), resp[0], len); @@ -531,7 +532,17 @@ int mifare_ultra_aes_auth(uint8_t keyno, uint8_t *keybytes, bool schann) { uint8_t rec_rnd_a[16] = {0}; mbedtls_aes_crypt_cbc(&actx, MBEDTLS_AES_DECRYPT, sizeof(rec_rnd_a), IV, resp + 1, rec_rnd_a); mbedtls_aes_free(&actx); + if (g_dbglevel >= DBG_EXTENDED) { + Dbprintf("e_AB:"); + Dbhexdump(32, rnd_ab, false); + Dbprintf("A sent:"); + Dbhexdump(16, random_a, false); + + Dbprintf("A' recv:"); + Dbhexdump(16, rec_rnd_a, false); + } + rol(random_a, 16); if (memcmp(rec_rnd_a, random_a, 16) != 0) { if (g_dbglevel >= DBG_INFO) { Dbprintf("failed authentication"); @@ -544,7 +555,7 @@ int mifare_ultra_aes_auth(uint8_t keyno, uint8_t *keybytes, bool schann) { return 1; } - // Session key calculation setion + // Session key calculation section // clear global session variable init_secure_session(); @@ -555,7 +566,8 @@ int mifare_ultra_aes_auth(uint8_t keyno, uint8_t *keybytes, bool schann) { uint8_t *ra = random_a; uint8_t *rb = random_b; - // do we need to unroll it? + // we need to unroll randoms + ror(ra, 16); ror(rb, 16); uint8_t session_vec[] = { diff --git a/armsrc/mifareutil.h b/armsrc/mifareutil.h index 1e319ff6c..c0f948846 100644 --- a/armsrc/mifareutil.h +++ b/armsrc/mifareutil.h @@ -104,8 +104,8 @@ int mifare_classic_value(struct Crypto1State *pcs, uint8_t blockNo, uint8_t *blo // Ultralight/NTAG... int mifare_ul_ev1_auth(uint8_t *keybytes, uint8_t *pack); -int mifare_ultra_auth(uint8_t *keybytes); -int mifare_ultra_aes_auth(uint8_t keyno, uint8_t *keybytes, bool schann); +int mifare_ultra_3des_auth(uint8_t *keybytes, bool check_answer); +int mifare_ultra_aes_auth(uint8_t keyno, uint8_t *keybytes, bool schann, bool check_answer); int mifare_ultra_readblock(uint8_t blockNo, uint8_t *blockData); int mifare_ultra_writeblock_compat(uint8_t blockNo, uint8_t *blockData); int mifare_ultra_writeblock(uint8_t blockNo, uint8_t *blockData); diff --git a/client/pyscripts/mfulc_counterfeit_recovery.py b/client/pyscripts/mfulc_counterfeit_recovery.py index a72dbbcc0..0feec6623 100755 --- a/client/pyscripts/mfulc_counterfeit_recovery.py +++ b/client/pyscripts/mfulc_counterfeit_recovery.py @@ -382,8 +382,10 @@ def main(): description=main.__doc__, formatter_class=argparse.RawDescriptionHelpFormatter ) - parser.add_argument('-c', '--challenges', help='Set number of challenges to collect (default:1000)', type=int, default=1000) - parser.add_argument('-t', '--threads', help='Set number of threads to use for key recovery (default:1)', type=int, default=1) + parser.add_argument('-c', '--challenges', help='Set number of challenges to collect (default:1000)', + type=int, default=1000) + parser.add_argument('-t', '--threads', help='Set number of threads to use for key recovery (default:1)', + type=int, default=16) parser.add_argument('-d', '--debug', action='store_true', help='Enable debug mode') parser.add_argument('-j', '--json', help='Path to JSON file to load or save collected challenges') parser.add_argument('-o', '--offline', action='store_true', help='Use offline mode with pre-collected challenges') diff --git a/client/src/cmdhfmfu.c b/client/src/cmdhfmfu.c index 1dec2edc8..45548f886 100644 --- a/client/src/cmdhfmfu.c +++ b/client/src/cmdhfmfu.c @@ -32,6 +32,7 @@ #include "cmdmain.h" #include "amiibo.h" // amiiboo fcts #include "base64.h" +#include "util_posix.h" // msclock #include "fileutils.h" // saveFile #include "cmdtrace.h" // trace list #include "preferences.h" // setDeviceDebugLevel @@ -60,6 +61,9 @@ #define MAX_ST25TN512 0x3F #define MAX_ST25TN01K 0x3F +#define MIFAREU3P_KEY_SIZE 16 +#define MIFAREULC_KEY_INDEX 3 + static int CmdHelp(const char *Cmd); static const char *key_type[] = { "DataProtKey", "UIDRetrKey", "OriginalityKey" }; @@ -517,38 +521,38 @@ Default AES key is 00-00h. Both the data and UID one. Data key is 00, UID is 01. Authenticity is 02h Auth is 1A[Key ID][CRC] - AF[RndB] - AF[RndA][RndB'] - 00[RndA'] */ -static int ulaes_requestAuthentication(const uint8_t *key, uint8_t keyno, bool switch_off_field, bool schann) { - - mfulaes_keys_t payload = { +static int ul3pass_authentication(const uint8_t *key, uint8_t keyno, bool switch_off_field, int retries, uint32_t *auths, uint32_t *ms, bool schann, bool check_answer) { + // keyno < 3: ULAES + // keyno = 3: ULC + mful_3passauth_t payload = { .turn_off_field = switch_off_field, + .check_answer = check_answer, .use_schann = schann, - .keyno = keyno + .keyno = keyno, + .retries = retries, }; memcpy(payload.key, key, sizeof(payload.key)); - clearCommandBuffer(); - SendCommandNG(CMD_HF_MIFAREULAES_AUTH, (uint8_t *)&payload, sizeof(payload)); + SendCommandNG(CMD_HF_MIFAREU3P_AUTH, (uint8_t *)&payload, sizeof(payload)); PacketResponseNG resp; - if (WaitForResponseTimeout(CMD_HF_MIFAREULAES_AUTH, &resp, 1500) == false) { + if (WaitForResponseTimeout(CMD_HF_MIFAREU3P_AUTH, &resp, 1500 + (retries * 15)) == false) { return PM3_ETIMEOUT; } + struct rp { + uint32_t auths; + uint32_t ticks; + } PACKED; + struct rp *rpayload = (struct rp *) resp.data.asBytes; + + if (auths != NULL) { + *auths += rpayload->auths; + } + if (ms != NULL) { + *ms += rpayload->ticks; + } return resp.status; } -static int ulc_authentication(const uint8_t *key, bool switch_off_field) { - - clearCommandBuffer(); - SendCommandMIX(CMD_HF_MIFAREUC_AUTH, switch_off_field, 0, 0, key, 16); - PacketResponseNG resp; - if (WaitForResponseTimeout(CMD_ACK, &resp, 1500) == false) { - return PM3_ETIMEOUT; - } - if (resp.oldarg[0] == 1) { - return PM3_SUCCESS; - } - return PM3_ESOFT; -} - static int trace_mfuc_try_key(uint8_t *key, int state, uint8_t (*authdata)[16]) { uint8_t iv[8] = {0}; uint8_t RndB[8] = {0}; @@ -629,7 +633,7 @@ static int try_default_3des_keys(bool override, uint8_t **correct_key) { for (uint8_t i = 0; i < ARRAYLEN(default_3des_keys); ++i) { uint8_t *key = default_3des_keys[i]; - if (ulc_authentication(key, true) == PM3_SUCCESS) { + if (ul3pass_authentication(key, MIFAREULC_KEY_INDEX, true, 0, NULL, NULL, false, true) == PM3_SUCCESS) { *correct_key = key; res = PM3_SUCCESS; break; @@ -666,7 +670,7 @@ static int try_default_aes_keys(bool override, bool use_schann) { for (uint8_t keyno = 0; keyno < 3; keyno++) { - if (ulaes_requestAuthentication(key, keyno, true, use_schann) == PM3_SUCCESS) { + if (ul3pass_authentication(key, keyno, true, 0, NULL, NULL, use_schann, true) == PM3_SUCCESS) { char keystr[20] = {0}; switch (keyno) { @@ -706,13 +710,13 @@ static int ul_auth_select(iso14a_card_select_t *card, uint64_t tagtype, bool has if (hasAuthKey && (tagtype & MFU_TT_UL_C)) { //will select card automatically and close connection on error - if (ulc_authentication(authkey, false) != PM3_SUCCESS) { + if (ul3pass_authentication(authkey, MIFAREULC_KEY_INDEX, false, 0, NULL, NULL, false, true) != PM3_SUCCESS) { PrintAndLogEx(WARNING, "Authentication Failed UL-C"); return PM3_ESOFT; } } else if (hasAuthKey && (tagtype & MFU_TT_UL_AES)) { //will select card automatically and close connection on error - if (ulaes_requestAuthentication(authkey, 0, false, use_schann) != PM3_SUCCESS) { + if (ul3pass_authentication(authkey, 0, false, 0, NULL, NULL, use_schann, true) != PM3_SUCCESS) { PrintAndLogEx(WARNING, "Authentication Failed UL-AES"); return PM3_ESOFT; } @@ -4240,6 +4244,128 @@ static int CmdHF14AMfUSim(const char *Cmd) { return CmdHF14ASim(Cmd); } + +//------------------------------------------------------------------------------- +// Ultralight C & AES helpers +//------------------------------------------------------------------------------- + +static int mfu_3pass_load_keys(uint8_t **pkeyBlock, uint32_t *pkeycnt, const char *filename, int fnlen, uint8_t keysize) { + // Handle Keys + *pkeycnt = 0; + *pkeyBlock = NULL; + uint8_t *p; + // Handle user supplied dictionary file + if (fnlen > 0) { + uint32_t loaded_numKeys = 0; + uint8_t *keyBlock_tmp = NULL; + int res = loadFileDICTIONARY_safe(filename, (void **) &keyBlock_tmp, keysize, &loaded_numKeys); + if (res != PM3_SUCCESS || loaded_numKeys == 0 || keyBlock_tmp == NULL) { + PrintAndLogEx(FAILED, "An error occurred while loading the dictionary!"); + free(keyBlock_tmp); + free(*pkeyBlock); + return PM3_EFILE; + } else { + p = realloc(*pkeyBlock, (*pkeycnt + loaded_numKeys) * keysize); + if (p == NULL) { + PrintAndLogEx(WARNING, "Failed to allocate memory"); + free(keyBlock_tmp); + free(*pkeyBlock); + return PM3_EMALLOC; + } + *pkeyBlock = p; + memcpy(*pkeyBlock + *pkeycnt * keysize, keyBlock_tmp, loaded_numKeys * keysize); + *pkeycnt += loaded_numKeys; + free(keyBlock_tmp); + } + } + return PM3_SUCCESS; +} + +#define MIFAREU3P_CHKKEY_HEADER (3 + MIFAREU3P_KEY_SIZE) + +static int mfu_3pass_check_keys(uint8_t key_index, uint8_t firstChunk, uint8_t lastChunk, + uint32_t nkeys, int segment, uint8_t *ref_key, bool xor_ref_key, uint8_t *keyBlock, + bool verbose, bool quiet, uint32_t *auths, uint32_t *ms, bool check_answer) { + // send keychunk + clearCommandBuffer(); + + mful_3passchk_t payload = { + .key_index = key_index, + .firstchunk = firstChunk, + .lastchunk = lastChunk, + .xor_ref_key = xor_ref_key, + .segment = segment != -1 ? segment : 4, + .check_answer = check_answer, + .nkeys = nkeys + }; + struct rp { + uint32_t auths; + uint32_t ticks; + uint8_t key[16]; + } PACKED; + uint8_t keysize = segment != -1 ? MIFAREU3P_KEY_SIZE / 4 : MIFAREU3P_KEY_SIZE; + memcpy(payload.ref_key, ref_key, MIFAREU3P_KEY_SIZE); + if (nkeys * keysize > sizeof(payload.data)) { + PrintAndLogEx(ERR, "Key chunk size exceeds payload size"); + return PM3_ESOFT; + } + memcpy(payload.data, keyBlock, nkeys * keysize); + + clearCommandBuffer(); + SendCommandNG(CMD_HF_MIFAREU3P_CHKKEY, (uint8_t *)&payload, sizeof(payload)); + + PacketResponseNG resp; + + uint32_t timeout = 0; + while (WaitForResponseTimeout(CMD_HF_MIFAREU3P_CHKKEY, &resp, 2000) == false) { + + while (kbd_enter_pressed()) { + SendCommandNG(CMD_BREAK_LOOP, NULL, 0); + PrintAndLogEx(INFO, "aborted via keyboard!"); + return PM3_EOPABORTED; + } + + if (quiet == false) { + PrintAndLogEx((timeout) ? NORMAL : INFO, "." NOLF); + fflush(stdout); + } + + timeout++; + + // max timeout for one chunk of 85keys, 60*3sec = 180seconds + // s70 with 40*2 keys to check, 80*85 = 6800 auth. + // takes about 97s, still some margin before abort + // timeout = 180 => ~360s @ Mifare Classic 1k @ ~2300 keys in dict + // ~2300 keys @ Mifare Classic 1k => ~620s + if (timeout > 60 * 12) { + PrintAndLogEx(WARNING, "\nNo response from Proxmark3. Aborting..."); + return PM3_ETIMEOUT; + } + } + + if (timeout && (quiet == false)) { + PrintAndLogEx(NORMAL, ""); + } + // time to convert the returned data. + struct rp *rpayload = (struct rp *) resp.data.asBytes; + + if (auths != NULL) { + *auths += rpayload->auths; + } + if (ms != NULL) { + *ms += rpayload->ticks; + } + + if (resp.status == PM3_SUCCESS) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(SUCCESS, "Target key " _GREEN_("%1u") " -- found valid key [ " _GREEN_("%s") " ]", + key_index, + sprint_hex_inrow(rpayload->key, MIFAREU3P_KEY_SIZE) + ); + } + return resp.status; +} + //------------------------------------------------------------------------------- // Ultralight C Methods //------------------------------------------------------------------------------- @@ -4260,6 +4386,8 @@ static int CmdHF14AMfUCAuth(const char *Cmd) { arg_str0(NULL, "key", "", "Authentication key (16 bytes in hex)"), arg_lit0("l", NULL, "Swap entered key's endianness"), arg_lit0("k", NULL, "Keep field on (only if a key is provided)"), + arg_int0("r", "retries", "", "Number of retries with provided key (def: 0)"), + arg_lit0("n", "nocheck", "Skip checking tag answer correctness (only if a key is provided)"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); @@ -4270,12 +4398,26 @@ static int CmdHF14AMfUCAuth(const char *Cmd) { CLIGetHexWithReturn(ctx, 1, authenticationkey, &ak_len); bool swap_endian = arg_get_lit(ctx, 2); bool keep_field_on = arg_get_lit(ctx, 3); + int retries = arg_get_int_def(ctx, 4, 0); + bool check_answer = !arg_get_lit(ctx, 5); CLIParserFree(ctx); if (ak_len != 16 && ak_len != 0) { PrintAndLogEx(WARNING, "ERROR: Key is incorrect length"); return PM3_EINVARG; } + if (retries < 0 || retries > 10000) { + PrintAndLogEx(ERR, "Invalid retries (must be 0..10000)"); + return PM3_EINVARG; + } + if ((retries > 0) && (ak_len == 0)) { + PrintAndLogEx(WARNING, "ERROR: Key is required for retries"); + return PM3_EINVARG; + } + if ((! check_answer) && (ak_len == 0)) { + PrintAndLogEx(WARNING, "ERROR: Key is required for nocheck"); + return PM3_EINVARG; + } // Swap endianness if (swap_endian && ak_len) { @@ -4283,6 +4425,8 @@ static int CmdHF14AMfUCAuth(const char *Cmd) { } int isok; + uint32_t auths = 0; + uint32_t ms = 0; // If no hex key is specified, try default keys if (ak_len == 0) { @@ -4291,7 +4435,8 @@ static int CmdHF14AMfUCAuth(const char *Cmd) { isok = try_default_3des_keys(false, &auth_key_ptr); } else { // try user-supplied - isok = ulc_authentication(auth_key_ptr, !keep_field_on); + + isok = ul3pass_authentication(auth_key_ptr, MIFAREULC_KEY_INDEX, !keep_field_on, retries, &auths, &ms, false, check_answer); } if (isok == PM3_SUCCESS) { @@ -4299,9 +4444,127 @@ static int CmdHF14AMfUCAuth(const char *Cmd) { } else { PrintAndLogEx(WARNING, "Authentication ( " _RED_("fail") " )"); } - return PM3_SUCCESS; + if (retries > 0) { + PrintAndLogEx(INFO, "Time spent " _YELLOW_("%.1fs"), (float)(ms / 1000.0)); + PrintAndLogEx(INFO, "Authentication attempts: %u", auths); + PrintAndLogEx(INFO, "Speed: %.1f auths/s", (float)(auths * 1000.0 / ms)); + } + return isok; } +static int CmdHF14AMfUCAuthChk(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf mfu cchk", + "It checks MIFARE Ultralight C tags keys against a dictionary file with keys\n", + "hf mfu cchk -f mfulc_default_keys.dic"); + + void *argtable[] = { + arg_param_begin, + arg_str0("f", "file", "", "filename of dictionary"), + arg_int0("s", "segment", "<0..3>", "Segment index (full key if not specified)"), + arg_int0("r", "retries", "<0..255>", "Number of retries (def: 0)"), + arg_str0("k", "key", "", "Starting key, 16 hex bytes (def: zero key), for segment check"), + arg_lit0("x", "xor", "XOR starting key with segment candidates (def: override)"), + arg_lit0("n", "nocheck", "Skip checking tag answer correctness"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + int fnlen = 0; + char filename[FILE_PATH_SIZE] = {0}; + CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); + int segment = arg_get_int_def(ctx, 2, -1); // -1 means full key + int retries = arg_get_int_def(ctx, 3, 0); + int ref_keylen = 0; + uint8_t ref_key[16] = {0}; + CLIGetHexWithReturn(ctx, 4, ref_key, &ref_keylen); + bool xor_ref_key = arg_get_lit(ctx, 5); + bool check_answer = !arg_get_lit(ctx, 6); + CLIParserFree(ctx); + + if (fnlen == 0) { + PrintAndLogEx(ERR, "No dictionary file specified"); + return PM3_EFILE; + } + if (segment < -1 || segment > 3) { + PrintAndLogEx(ERR, "Invalid segment (must be 0..3)"); + return PM3_EINVARG; + } + if (retries < 0 || retries > 255) { + PrintAndLogEx(ERR, "Invalid retries (must be 0..255)"); + return PM3_EINVARG; + } + if (ref_keylen && ref_keylen != MIFAREU3P_KEY_SIZE) { + PrintAndLogEx(WARNING, "Key must be %i hex bytes. Got %d", MIFAREU3P_KEY_SIZE, ref_keylen); + return PM3_EINVARG; + } + if (ref_keylen == 0) { + ref_keylen = MIFAREU3P_KEY_SIZE; + } + + uint8_t *keyBlock = NULL; + uint32_t keycnt = 0; + int keysize = segment != -1 ? MIFAREU3P_KEY_SIZE / 4 : MIFAREU3P_KEY_SIZE; + int ret = mfu_3pass_load_keys(&keyBlock, &keycnt, filename, fnlen, keysize); + if (ret != PM3_SUCCESS) { + return ret; + } + if (keycnt == 0) { + PrintAndLogEx(ERR, "Dictionary contains no keys"); + free(keyBlock); + return PM3_ESOFT; + } + + uint32_t chunksize = (keycnt > (PM3_CMD_DATA_SIZE - MIFAREU3P_CHKKEY_HEADER) / keysize) ? + ((PM3_CMD_DATA_SIZE - MIFAREU3P_CHKKEY_HEADER) / keysize) : keycnt; + bool firstChunk = true, lastChunk = false; + + int i = 0; + + // time + uint32_t auths = 0; + uint32_t ms = 0; + + // main keychunk loop + for (int r = 0; r < retries + 1; r++) { + for (i = 0; i < keycnt; i += chunksize) { + if (kbd_enter_pressed()) { + clearCommandBuffer(); + SendCommandNG(CMD_BREAK_LOOP, NULL, 0); + SendCommandNG(CMD_FPGA_MAJOR_MODE_OFF, NULL, 0); // field is still ON if not on last chunk + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(WARNING, "\naborted via keyboard!"); + goto out; + } + + uint32_t nkeys = ((keycnt - i) > chunksize) ? chunksize : keycnt - i; + + // last chunk? + if (nkeys == keycnt - i) { + lastChunk = true; + } + int res = mfu_3pass_check_keys(MIFAREULC_KEY_INDEX, firstChunk, lastChunk, nkeys, segment, ref_key, xor_ref_key, keyBlock + (i * keysize), false, true, &auths, &ms, check_answer); + if (firstChunk) + firstChunk = false; + + // all keys, aborted + if (res == PM3_SUCCESS || res == 2) { + PrintAndLogEx(NORMAL, ""); + goto out; + } + PrintAndLogEx(INPLACE, "Testing %5i/%5i ( " _YELLOW_("%02.1f %%") " )", i, keycnt, (float)i * 100 / keycnt); + } // end chunks of keys + } + PrintAndLogEx(NORMAL, ""); +out: + PrintAndLogEx(INFO, "Time spent " _YELLOW_("%.1fs"), (float)(ms / 1000.0)); + PrintAndLogEx(INFO, "Authentication attempts: %u", auths); + PrintAndLogEx(INFO, "Speed: %.1f auths/s", (float)(auths * 1000.0 / ms)); + + free(keyBlock); + PrintAndLogEx(NORMAL, ""); + return PM3_SUCCESS; +} //------------------------------------------------------------------------------- // Ultralight AES Methods //------------------------------------------------------------------------------- @@ -4328,6 +4591,8 @@ static int CmdHF14AMfUAESAuth(const char *Cmd) { arg_lit0("l", NULL, "Swap entered key's endianness"), arg_lit0("k", NULL, "Keep field on (only if a key is provided)"), arg_lit0(NULL, "schann", "use secure channel. Must have key"), + arg_int0("r", "retries", "", "Number of retries (def: 0)"), + arg_lit0("n", "nocheck", "Skip checking tag answer correctness"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); @@ -4340,6 +4605,8 @@ static int CmdHF14AMfUAESAuth(const char *Cmd) { bool swap_endian = arg_get_lit(ctx, 3); bool keep_field_on = arg_get_lit(ctx, 4); bool use_schann = arg_get_lit(ctx, 5); + int retries = arg_get_int_def(ctx, 6, 0); + bool check_answer = !arg_get_lit(ctx, 7); CLIParserFree(ctx); if (ak_len == 0) { @@ -4361,7 +4628,14 @@ static int CmdHF14AMfUAESAuth(const char *Cmd) { auth_key_ptr = SwapEndian64(authentication_key, ak_len, 16); } - int result = ulaes_requestAuthentication(auth_key_ptr, key_index, !keep_field_on, use_schann); + if (retries < 0 || retries > 10000) { + PrintAndLogEx(ERR, "Invalid retries (must be 0..10000)"); + return PM3_EINVARG; + } + uint32_t auths = 0; + uint32_t ms = 0; + + int result = ul3pass_authentication(auth_key_ptr, key_index, !keep_field_on, retries, &auths, &ms, use_schann, check_answer); if (result == PM3_SUCCESS) { PrintAndLogEx(SUCCESS, "Authentication with " _YELLOW_("%s") " " _GREEN_("%s") " ( " _GREEN_("ok")" )" , key_type[key_index] @@ -4370,9 +4644,137 @@ static int CmdHF14AMfUAESAuth(const char *Cmd) { } else { PrintAndLogEx(WARNING, "Authentication with " _YELLOW_("%s") " ( " _RED_("fail") " )", key_type[key_index]); } + if (retries > 0) { + PrintAndLogEx(INFO, "Time spent " _YELLOW_("%.1fs"), (float)(ms / 1000.0)); + PrintAndLogEx(INFO, "Authentication attempts: %u", auths); + PrintAndLogEx(INFO, "Speed: %.1f auths/s", (float)(auths * 1000.0 / ms)); + } return result; } +static int CmdHF14AMfUAESAuthChk(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf mfu aeschk", + "It checks MIFARE Ultralight AES tags keys against a dictionary file with keys\n" + " Key index 0... DataProtKey (default)\n" + " Key index 1... UIDRetrKey\n" + " Key index 2... OriginalityKey\n", + "hf mfu aeschk -f mfulaes_default_keys.dic"); + + void *argtable[] = { + arg_param_begin, + arg_str0("f", "file", "", "filename of dictionary"), + arg_int0("i", "idx", "<0..2>", "Key index (def: 0)"), + arg_int0("s", "segment", "<0..3>", "Segment index (full key if not specified)"), + arg_int0("r", "retries", "<0..255>", "Number of retries (def: 0)"), + arg_str0("k", "key", "", "Starting key, 16 hex bytes (def: zero key), for segment check"), + arg_lit0("x", "xor", "XOR starting key with segment candidates (def: override)"), + arg_lit0("n", "nocheck", "Skip checking tag answer correctness"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + int fnlen = 0; + char filename[FILE_PATH_SIZE] = {0}; + CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); + int key_index = arg_get_int_def(ctx, 2, 0); + int segment = arg_get_int_def(ctx, 3, -1); // -1 means full key + int retries = arg_get_int_def(ctx, 4, 0); + int ref_keylen = 0; + uint8_t ref_key[16] = {0}; + CLIGetHexWithReturn(ctx, 5, ref_key, &ref_keylen); + bool xor_ref_key = arg_get_lit(ctx, 6); + bool check_answer = !arg_get_lit(ctx, 7); + CLIParserFree(ctx); + + if (fnlen == 0) { + PrintAndLogEx(ERR, "No dictionary file specified"); + return PM3_EFILE; + } + if (key_index < 0 || key_index > 2) { + PrintAndLogEx(ERR, "Invalid key index (must be 0..2)"); + return PM3_EINVARG; + } + if (segment < -1 || segment > 3) { + PrintAndLogEx(ERR, "Invalid segment (must be 0..3)"); + return PM3_EINVARG; + } + if (retries < 0 || retries > 255) { + PrintAndLogEx(ERR, "Invalid retries (must be 0..255)"); + return PM3_EINVARG; + } + if (ref_keylen && ref_keylen != MIFAREU3P_KEY_SIZE) { + PrintAndLogEx(WARNING, "Key must be %i hex bytes. Got %d", MIFAREU3P_KEY_SIZE, ref_keylen); + return PM3_EINVARG; + } + if (ref_keylen == 0) { + ref_keylen = MIFAREU3P_KEY_SIZE; + } + + uint8_t *keyBlock = NULL; + uint32_t keycnt = 0; + int keysize = segment != -1 ? MIFAREU3P_KEY_SIZE / 4 : MIFAREU3P_KEY_SIZE; + int ret = mfu_3pass_load_keys(&keyBlock, &keycnt, filename, fnlen, keysize); + if (ret != PM3_SUCCESS) { + return ret; + } + if (keycnt == 0) { + PrintAndLogEx(ERR, "Dictionary contains no keys"); + free(keyBlock); + return PM3_ESOFT; + } + + uint32_t chunksize = (keycnt > (PM3_CMD_DATA_SIZE - MIFAREU3P_CHKKEY_HEADER) / keysize) ? + ((PM3_CMD_DATA_SIZE - MIFAREU3P_CHKKEY_HEADER) / keysize) : keycnt; + bool firstChunk = true, lastChunk = false; + + int i = 0; + + uint32_t auths = 0; + uint32_t ms = 0; + + // main keychunk loop + for (int r = 0; r < retries + 1; r++) { + for (i = 0; i < keycnt; i += chunksize) { + if (kbd_enter_pressed()) { + clearCommandBuffer(); + SendCommandNG(CMD_BREAK_LOOP, NULL, 0); + SendCommandNG(CMD_FPGA_MAJOR_MODE_OFF, NULL, 0); // field is still ON if not on last chunk + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(WARNING, "\naborted via keyboard!"); + goto out; + } + + uint32_t nkeys = ((keycnt - i) > chunksize) ? chunksize : keycnt - i; + + // last chunk? + if (nkeys == keycnt - i) { + lastChunk = true; + } + + int res = mfu_3pass_check_keys(key_index, firstChunk, lastChunk, nkeys, segment, ref_key, xor_ref_key, keyBlock + (i * keysize), false, true, &auths, &ms, check_answer); + if (firstChunk) + firstChunk = false; + + // all keys, aborted + if (res == PM3_SUCCESS || res == 2) { + PrintAndLogEx(NORMAL, ""); + goto out; + } + PrintAndLogEx(INPLACE, "Testing %5i/%5i ( " _YELLOW_("%02.1f %%") " )", i, keycnt, (float)i * 100 / keycnt); + } // end chunks of keys + } + PrintAndLogEx(NORMAL, ""); +out: + PrintAndLogEx(INFO, "Time spent " _YELLOW_("%.1fs"), (float)(ms / 1000.0)); + PrintAndLogEx(INFO, "Authentication attempts: %u", auths); + PrintAndLogEx(INFO, "Speed: %.1f auths/s", (float)(auths * 1000.0 / ms)); + + free(keyBlock); + PrintAndLogEx(NORMAL, ""); + return PM3_SUCCESS; +} + /** A test function to validate that the polarssl-function works the same was as the openssl-implementation. @@ -6519,8 +6921,10 @@ static command_t CommandTable[] = { {"otptear", CmdHF14AMfuOtpTearoff, IfPm3Iso14443a, "Tear-off test on OTP bits"}, // {"tear_cnt", CmdHF14AMfuEv1CounterTearoff, IfPm3Iso14443a, "Tear-off test on Ev1/NTAG Counter bits"}, {"-----------", CmdHelp, IfPm3Iso14443a, "----------------------- " _CYAN_("operations") " -----------------------"}, - {"cauth", CmdHF14AMfUCAuth, IfPm3Iso14443a, "Ultralight C - Authentication"}, - {"aesauth", CmdHF14AMfUAESAuth, IfPm3Iso14443a, "Ultralight AES - Authentication"}, + {"cauth", CmdHF14AMfUCAuth, IfPm3Iso14443a, "Ultralight-C - Authentication"}, + {"cchk", CmdHF14AMfUCAuthChk, IfPm3Iso14443a, "Ultralight-C - Authentication dictionary check"}, + {"aesauth", CmdHF14AMfUAESAuth, IfPm3Iso14443a, "Ultralight-AES - Authentication"}, + {"aeschk", CmdHF14AMfUAESAuthChk, IfPm3Iso14443a, "Ultralight-AES - Authentication dictionary check"}, {"setkey", CmdHF14AMfUSetKey, IfPm3Iso14443a, "Ultralight C/AES - Set 3DES/AES keys"}, {"dump", CmdHF14AMfUDump, IfPm3Iso14443a, "Dump MIFARE Ultralight family tag to binary file"}, {"incr", CmdHF14AMfUIncr, IfPm3Iso14443a, "Increments Ev1/NTAG counter"}, diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index bfb7011c2..7328f9358 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -436,7 +436,9 @@ const static vocabulary_t vocabulary[] = { { 1, "hf mfu pwdgen" }, { 0, "hf mfu otptear" }, { 0, "hf mfu cauth" }, + { 0, "hf mfu cchk" }, { 0, "hf mfu aesauth" }, + { 0, "hf mfu aeschk" }, { 0, "hf mfu setkey" }, { 0, "hf mfu dump" }, { 0, "hf mfu incr" }, @@ -920,6 +922,10 @@ const static vocabulary_t vocabulary[] = { { 0, "script run ice.py" }, { 0, "script run intertic.py" }, { 0, "script run mf_backdoor_dump.py" }, + { 0, "script run mfulaes_mask_recovery.py" }, + { 0, "script run mfulc_counterfeit_recovery.py" }, + { 0, "script run ntag22x_libsuncmac.py" }, + { 0, "script run ntag22x_suncmac_recovery.py" }, { 0, "script run parity.py" }, { 0, "script run paxton_convert.py" }, { 0, "script run paxton_net.py" }, diff --git a/doc/commands.json b/doc/commands.json index c20dee914..44e42f9e9 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -1411,10 +1411,10 @@ "-x Performs the 'reader attack', nr/ar attack against a reader", "--sk Fill simulator keys from found keys", "-v, --verbose verbose output", - "--z1 ULC/ULAES Auth - all zero handshake part 1", - "--z2 ULC/ULAES Auth - all zero handshake part 2" + "--1a1 <8|16> hex bytes ULC/ULAES Auth reply step1: ek(RndB)", + "--1a2 <8|16> hex bytes ULC/ULAES Auth reply step2: ek(RndA')" ], - "usage": "hf 14a sim [-hxv] -t <1-14> [-u ] [-n ] [--sk] [--z1] [--z2]" + "usage": "hf 14a sim [-hxv] -t <1-14> [-u ] [-n ] [--sk] [--1a1 ] [--1a2 ]" }, "hf 14a simaid": { "command": "hf 14a simaid", @@ -7183,9 +7183,30 @@ "-i, --idx <0..2> Key index (def: 0)", "-l Swap entered key's endianness", "-k Keep field on (only if a key is provided)", - "--schann use secure channel. Must have key" + "--schann use secure channel. Must have key", + "-r, --retries Number of retries (def: 0)", + "-n, --nocheck Skip checking tag answer correctness" ], - "usage": "hf mfu aesauth [-hlk] [--key ] [-i <0..2>] [--schann]" + "usage": "hf mfu aesauth [-hlkn] [--key ] [-i <0..2>] [--schann] [-r ]" + }, + "hf mfu aeschk": { + "command": "hf mfu aeschk", + "description": "It checks MIFARE Ultralight AES tags keys against a dictionary file with keys Key index 0... DataProtKey (default) Key index 1... UIDRetrKey Key index 2... OriginalityKey", + "notes": [ + "hf mfu aeschk -f mfulaes_default_keys.dic" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-f, --file filename of dictionary", + "-i, --idx <0..2> Key index (def: 0)", + "-s, --segment <0..3> Segment index (full key if not specified)", + "-r, --retries <0..255> Number of retries (def: 0)", + "-k, --key Starting key, 16 hex bytes (def: zero key), for segment check", + "-x, --xor XOR starting key with segment candidates (def: override)", + "-n, --nocheck Skip checking tag answer correctness" + ], + "usage": "hf mfu aeschk [-hxn] [-f ] [-i <0..2>] [-s <0..3>] [-r <0..255>] [-k ]" }, "hf mfu amiibo": { "command": "hf mfu amiibo", @@ -7217,9 +7238,29 @@ "-h, --help This help", "--key Authentication key (16 bytes in hex)", "-l Swap entered key's endianness", - "-k Keep field on (only if a key is provided)" + "-k Keep field on (only if a key is provided)", + "-r, --retries Number of retries with provided key (def: 0)", + "-n, --nocheck Skip checking tag answer correctness (only if a key is provided)" ], - "usage": "hf mfu cauth [-hlk] [--key ]" + "usage": "hf mfu cauth [-hlkn] [--key ] [-r ]" + }, + "hf mfu cchk": { + "command": "hf mfu cchk", + "description": "It checks MIFARE Ultralight C tags keys against a dictionary file with keys", + "notes": [ + "hf mfu cchk -f mfulc_default_keys.dic" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-f, --file filename of dictionary", + "-s, --segment <0..3> Segment index (full key if not specified)", + "-r, --retries <0..255> Number of retries (def: 0)", + "-k, --key Starting key, 16 hex bytes (def: zero key), for segment check", + "-x, --xor XOR starting key with segment candidates (def: override)", + "-n, --nocheck Skip checking tag answer correctness" + ], + "usage": "hf mfu cchk [-hxn] [-f ] [-s <0..3>] [-r <0..255>] [-k ]" }, "hf mfu dump": { "command": "hf mfu dump", @@ -7510,10 +7551,10 @@ "-u, --uid <4|7|10> hex bytes UID", "-n, --num Exit simulation after blocks. 0 = infinite", "-v, --verbose Verbose output", - "--z1 ULC/ULAES Auth - all zero handshake part 1", - "--z2 ULC/ULAES Auth - all zero handshake part 2" + "--1a1 <8|16> hex bytes ULC/ULAES Auth reply step1: ek(RndB)", + "--1a2 <8|16> hex bytes ULC/ULAES Auth reply step2: ek(RndA')" ], - "usage": "hf mfu sim [-hv] -t <1..14> [-u ] [-n ] [--z1] [--z2]" + "usage": "hf mfu sim [-hv] -t <1..14> [-u ] [-n ] [--1a1 ] [--1a2 ]" }, "hf mfu tamper": { "command": "hf mfu tamper", diff --git a/doc/commands.md b/doc/commands.md index 40170c4c8..a6c1ebffd 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -622,8 +622,10 @@ Check column "offline" for their availability. |`hf mfu keygen `|Y |`Generate DES/3DES/AES MIFARE diversified keys` |`hf mfu pwdgen `|Y |`Generate pwd from known algos` |`hf mfu otptear `|N |`Tear-off test on OTP bits` -|`hf mfu cauth `|N |`Ultralight C - Authentication` -|`hf mfu aesauth `|N |`Ultralight AES - Authentication` +|`hf mfu cauth `|N |`Ultralight-C - Authentication` +|`hf mfu cchk `|N |`Ultralight-C - Authentication dictionary check` +|`hf mfu aesauth `|N |`Ultralight-AES - Authentication` +|`hf mfu aeschk `|N |`Ultralight-AES - Authentication dictionary check` |`hf mfu setkey `|N |`Ultralight C/AES - Set 3DES/AES keys` |`hf mfu dump `|N |`Dump MIFARE Ultralight family tag to binary file` |`hf mfu incr `|N |`Increments Ev1/NTAG counter` diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index 860dfa9de..eb3331ecc 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -366,17 +366,30 @@ typedef struct { } PACKED mfc_eload_t; typedef struct { - bool use_flashmem; - uint16_t keycount; - uint8_t keys[]; -} PACKED mfulc_keys_t; - -typedef struct { - bool turn_off_field; - bool use_schann; - uint8_t keyno; + uint8_t turn_off_field : 1; + uint8_t check_answer : 1; + uint8_t keyno : 2; + uint8_t use_schann : 1; + uint8_t reserved : 3; + uint16_t retries; uint8_t key[16]; -} PACKED mfulaes_keys_t; +} PACKED mful_3passauth_t; + +enum { + MIFAREU3P_KEY_SIZE = 16, + MIFAREU3P_CHKKEY_HEADER = 2 + MIFAREU3P_KEY_SIZE +}; +typedef struct { + uint8_t key_index : 2; + uint8_t firstchunk : 1; + uint8_t lastchunk : 1; + uint8_t xor_ref_key : 1; + uint8_t segment : 3; + uint8_t check_answer : 1; + uint8_t nkeys : 7; + uint8_t ref_key[MIFAREU3P_KEY_SIZE]; + uint8_t data[PM3_CMD_DATA_SIZE - MIFAREU3P_CHKKEY_HEADER]; +} PACKED mful_3passchk_t; typedef struct { bool use_schann; @@ -798,11 +811,10 @@ typedef struct { #define CMD_HF_MIFARE_MFKEY 0x0631 #define CMD_HF_MIFARE_PERSONALIZE_UID 0x0632 -// ultralight-C -#define CMD_HF_MIFAREUC_AUTH 0x0724 -// Ultralight AES -#define CMD_HF_MIFAREULAES_AUTH 0x0725 -// 0x0726 no longer used +// ultralight-C & AES +#define CMD_HF_MIFAREU3P_AUTH 0x0724 +// 0x0725 is free +#define CMD_HF_MIFAREU3P_CHKKEY 0x0726 #define CMD_HF_MIFAREU_SETKEY 0x0727 // mifare desfire diff --git a/tools/deprecated-hid-flasher/flasher/usb_cmd.h b/tools/deprecated-hid-flasher/flasher/usb_cmd.h index 1e9ac2f70..ab8cc3b8c 100644 --- a/tools/deprecated-hid-flasher/flasher/usb_cmd.h +++ b/tools/deprecated-hid-flasher/flasher/usb_cmd.h @@ -195,9 +195,10 @@ typedef struct { #define CMD_HF_MIFARE_SETMOD 0x0624 #define CMD_HF_MIFARE_SNIFF 0x0630 -//ultralightC -#define CMD_HF_MIFAREUC_AUTH 0x0724 -//0x0725 and 0x0726 no longer used +// ultralight-C & AES +#define CMD_HF_MIFAREU3P_AUTH 0x0724 +// 0x0725 is free +#define CMD_HF_MIFAREU3P_CHKKEY 0x0726 #define CMD_HF_MIFAREU_SETKEY 0x0727