diff --git a/CHANGELOG.md b/CHANGELOG.md index 84a81d0b4..f80b7dec2 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] +- Fixed `lf hitag list` - improved HITAG2 protocol annotation (@iceman1001) ## [Zenith.4.18340][2024-03-20] - Changed `hf mf info` - some detections (@iceman1001) diff --git a/client/src/cmdlfhitag.c b/client/src/cmdlfhitag.c index a24138da9..5f59c7f82 100644 --- a/client/src/cmdlfhitag.c +++ b/client/src/cmdlfhitag.c @@ -17,15 +17,19 @@ //----------------------------------------------------------------------------- #include "cmdlfhitag.h" #include -#include "cmdparser.h" // command_t +#include "cmdparser.h" // command_t #include "comms.h" #include "cmdtrace.h" #include "commonutil.h" #include "hitag.h" -#include "fileutils.h" // savefile -#include "protocols.h" // defines +#include "fileutils.h" // savefile +#include "protocols.h" // defines #include "cliparser.h" #include "crc.h" +#include "graph.h" // MAX_GRAPH_TRACE_LEN +#include "lfdemod.h" +#include "cmddata.h" // setDemodBuff +#include "pm3_cmd.h" // return codes static int CmdHelp(const char *Cmd); @@ -375,54 +379,119 @@ static void print_hitag2_blocks(uint8_t *d, uint16_t n) { void annotateHitag1(char *exp, size_t size, const uint8_t *cmd, uint8_t cmdsize, bool is_response) { } -void annotateHitag2(char *exp, size_t size, const uint8_t *cmd, uint8_t cmdsize, bool is_response) { - - // iceman: live decrypt of trace? - if (is_response) { - uint8_t cmdbits = (cmd[0] & 0xC0) >> 6; +static struct { + enum { + STATE_HALT, + STATE_START_AUTH, + STATE_AUTH, + STATE_ENCRYPTED, + } state; +} _ht2state; + +void annotateHitag2_init(void) { + _ht2state.state = STATE_HALT; +} + +void annotateHitag2(char *exp, size_t size, const uint8_t *cmd, uint8_t cmdsize, uint8_t bits, bool is_response) { + + // I think its better to handle this log bytes as a long array of bits instead. + // 1100 0 + // 1100 0001 1100 0000 00 + if (cmdsize == 0) { + return; + } + + char *binstr = (char *)calloc((cmdsize * 8) + 1, sizeof(uint8_t)); + if (binstr == NULL) { + return; + } + + bytes_2_binstr(binstr, cmd, cmdsize); + + size_t bn = strlen(binstr); + if (bits) { if (cmdsize == 1) { - if (cmdbits == HITAG2_START_AUTH) { - snprintf(exp, size, "START AUTH"); - return; - } - if (cmdbits == HITAG2_HALT) { - snprintf(exp, size, "HALT"); - return; - } - } - - if (cmdsize == 3) { - if (cmdbits == HITAG2_START_AUTH) { - // C 1 C 0 - // 1100 0 00 1 1100 000 - uint8_t page = (cmd[0] & 0x38) >> 3; - uint8_t inv_page = ((cmd[0] & 0x1) << 2) | ((cmd[1] & 0xC0) >> 6); - snprintf(exp, size, "READ page(%x) %x", page, inv_page); - return; - } - if (cmdbits == HITAG2_WRITE_PAGE) { - uint8_t page = (cmd[0] & 0x38) >> 3; - uint8_t inv_page = ((cmd[0] & 0x1) << 2) | ((cmd[1] & 0xC0) >> 6); - snprintf(exp, size, "WRITE page(%x) %x", page, inv_page); - return; - } - } - - if (cmdsize == 9) { - snprintf(exp, size, "Nr Ar Is response"); - return; - } - } else { - - if (cmdsize == 9) { - snprintf(exp, size, "Nr Ar"); - return; + bn = bits; + } else if (cmdsize > 1) { + bn = ((cmdsize - 1) * 8) + bits; } } + // 11000 AUTH only one with 5 bits. cmdsize 1 + switch(bn) { + case 5: { + _ht2state.state = STATE_HALT; + if(memcmp(binstr, HITAG2_START_AUTH, 5) == 0) { + snprintf(exp, size, "START AUTH"); + _ht2state.state = STATE_START_AUTH; + } else { + snprintf(exp, size, "?"); + } + break; + } + case 10: { + + if (_ht2state.state == STATE_ENCRYPTED) { + snprintf(exp, size, "ENC CMD"); + break; + } + + if(memcmp(binstr, HITAG2_HALT, 2) == 0) { + snprintf(exp, size, "HALT"); + _ht2state.state = STATE_HALT; + break; + } + if(memcmp(binstr, HITAG2_READ_PAGE, 2) == 0) { + snprintf(exp, size, "READ_PAGE (%u)", 0); + break; + } + + if(memcmp(binstr, HITAG2_READ_PAGE_INVERTED, 2) == 0) { + snprintf(exp, size, "READ_PAGE_INVERTED (%u)", 0); + break; + } + + if(memcmp(binstr, HITAG2_WRITE_PAGE, 2) == 0) { + snprintf(exp, size, "WRITE_PAGE ()"); + break; + } + break; + } + + case 32: { // password or data + if (_ht2state.state == STATE_START_AUTH) { + if (is_response) { + snprintf(exp, size, "UID"); + } else { + snprintf(exp, size, "PWD: " _GREEN_("0x%02X%02X%02X%02X"), cmd[0], cmd[1], cmd[2], cmd[3]); + _ht2state.state = STATE_AUTH; + } + break; + } + + if (_ht2state.state == STATE_AUTH) { + snprintf(exp, size, "DATA"); + } else{ + snprintf(exp, size, "?"); + } + break; + } + + case 64: { // crypto handshake + snprintf(exp, size, "AUTH: Nr Ar"); + _ht2state.state = STATE_ENCRYPTED; + break; + } + default: { + snprintf(exp, size, "?"); + break; + } + } + + free(binstr); } void annotateHitagS(char *exp, size_t size, const uint8_t *cmd, uint8_t cmdsize, bool is_response) { diff --git a/client/src/cmdtrace.c b/client/src/cmdtrace.c index b5e0d98db..fa5d804cb 100644 --- a/client/src/cmdtrace.c +++ b/client/src/cmdtrace.c @@ -605,7 +605,7 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr } } - uint8_t partialbytebuff = 0; +// uint8_t partialbytebuff = 0; uint8_t offset = 0; for (int j = 0; j < data_len && (j / TRACE_MAX_HEX_BYTES) < TRACE_MAX_HEX_BYTES; j++) { uint8_t parityBits = parityBytes[j >> 3]; @@ -639,18 +639,35 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr snprintf(line[j / 18] + ((j % 18) * 4), 120, "%02x! ", frame[j]); } - } else if (((protocol == PROTO_HITAG1) || (protocol == PROTO_HITAG2) || (protocol == PROTO_HITAGS)) && (parityBytes[0] > 0)) { - // handle partial bytes + } else if (((protocol == PROTO_HITAG1) || (protocol == PROTO_HITAG2) || (protocol == PROTO_HITAGS))) { + + // handle partial bytes. The parity array[0] is used to store number of left over bits from NBYTES + // This part prints the number of bits in the trace entry for hitag. uint8_t nbits = parityBytes[0]; if (j == 0) { - partialbytebuff = frame[0] << nbits; - snprintf(line[0], 120, "%02x(%i) ", frame[0] >> (8 - nbits), nbits); - offset = 2; + + // only apply this to lesser than one byte + if (data_len == 1) { + + if (nbits == 5) { + snprintf(line[0], 120, "%2u: %02x ", nbits, frame[0] >> (8 - nbits)); + } else { + snprintf(line[0], 120, "%2u: %02x ", nbits, frame[0] >> (8 - nbits)); + } + + } else { + if (nbits == 0) { + snprintf(line[0], 120, "%2u: %02x ", (data_len * 8), frame[0]); + } else { + snprintf(line[0], 120, "%2u: %02x ", ((data_len - 1) * 8) + nbits, frame[0]); + } + } + offset = 4; + } else { - uint8_t byte = partialbytebuff | (frame[j] >> (8 - nbits)); - partialbytebuff = frame[j] << nbits; - snprintf(line[j / 18] + ((j % 18) * 4) + offset, 120, "%02x ", byte); + snprintf(line[j / 18] + ((j % 18) * 4) + offset, 120, "%02x ", frame[j]); } + } else { snprintf(line[j / 18] + ((j % 18) * 4), 120, "%02x ", frame[j]); } @@ -776,7 +793,7 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr annotateHitag1(explanation, sizeof(explanation), frame, data_len, hdr->isResponse); break; case PROTO_HITAG2: - annotateHitag2(explanation, sizeof(explanation), frame, data_len, hdr->isResponse); + annotateHitag2(explanation, sizeof(explanation), frame, data_len, parityBytes[0], hdr->isResponse); break; case PROTO_HITAGS: annotateHitagS(explanation, sizeof(explanation), frame, data_len, hdr->isResponse); @@ -1379,8 +1396,9 @@ int CmdTraceList(const char *Cmd) { if (protocol == ISO_7816_4) PrintAndLogEx(INFO, _YELLOW_("ISO7816-4 / Smartcard") " - Timings N/A"); - if (protocol == PROTO_HITAG1 || protocol == PROTO_HITAG2 || protocol == PROTO_HITAGS) + if (protocol == PROTO_HITAG1 || protocol == PROTO_HITAG2 || protocol == PROTO_HITAGS) { PrintAndLogEx(INFO, _YELLOW_("Hitag1 / Hitag2 / HitagS") " - Timings in ETU (8us)"); + } if (protocol == FELICA) { if (use_us) @@ -1427,8 +1445,14 @@ int CmdTraceList(const char *Cmd) { PrintAndLogEx(NORMAL, "------------+------------+-----+-------------------------------------------------------------------------+-----+--------------------"); // clean authentication data used with the mifare classic decrypt fct - if (protocol == ISO_14443A || protocol == PROTO_MIFARE || protocol == PROTO_MFPLUS) + if (protocol == ISO_14443A || protocol == PROTO_MIFARE || protocol == PROTO_MFPLUS) { ClearAuthData(); + } + + // reset hitag state machine + if (protocol == PROTO_HITAG1 || protocol == PROTO_HITAG2 || protocol == PROTO_HITAGS) { + annotateHitag2_init(); + } uint32_t previous_EOT = 0; uint32_t *prev_EOT = NULL; diff --git a/include/protocols.h b/include/protocols.h index 322c6c321..a2c620b05 100644 --- a/include/protocols.h +++ b/include/protocols.h @@ -908,12 +908,11 @@ ISO 7816-4 Basic interindustry commands. For command APDU's. #define HITAG1_HALT 0x70 // left 4 bits only, followed by 8 bits (dummy) page and 8 bits CRC // HITAG2 commands -#define HITAG2_START_AUTH 0x3 // left 5 bits only -#define HITAG2_HALT 0x0 // left 5 bits only - -#define HITAG2_READ_PAGE 0x3 // page number in bits 5 to 3, page number inverted in bit 0 and following 2 bits -#define HITAG2_READ_PAGE_INVERTED 0x1 // page number in bits 5 to 3, page number inverted in bit 0 and following 2 bits -#define HITAG2_WRITE_PAGE 0x2 // page number in bits 5 to 3, page number +#define HITAG2_START_AUTH "11000" // get UID and/or start the authentication process +#define HITAG2_READ_PAGE "11" // read page after auth +#define HITAG2_READ_PAGE_INVERTED "01" // as read page but all bits inverted +#define HITAG2_WRITE_PAGE "10" // write page after auth +#define HITAG2_HALT "00" // silence currently authenticated tag // HITAG S commands