added "lf hitag crack2" to support the second attack vector against Hitag2, based on all work from @kevsecurity Kev Sheldrake in the RFIDler repo. This is WIP, not working at the moment

This commit is contained in:
iceman1001 2024-04-26 15:38:06 +02:00
commit dee84b5b6f
5 changed files with 332 additions and 39 deletions

View file

@ -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)

View file

@ -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;
}

View file

@ -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));
}

View file

@ -22,6 +22,6 @@
#include <stdbool.h>
#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

View file

@ -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", "<hex>", "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"},